Interactive Sensor Data Coming Soon!

Novel Crops Simulation
body { font-family: Georgia, "Times New Roman", serif; background: #f6f3ee; color: #2b2b2b; margin: 0; padding: 40px; } .sim-container { max-width: 900px; margin: auto; background: #ffffff; padding: 30px 40px; border: 1px solid #e5e0d8; box-shadow: 0 2px 10px rgba(0,0,0,0.05); } h3 { font-weight: normal; letter-spacing: 1px; text-transform: uppercase; font-size: 14px; margin-top: 30px; color: #3a3a3a; } #cultivarButtons button { background: #f4f1eb; border: 1px solid #d8d2c8; padding: 8px 12px; margin: 4px; cursor: pointer; font-size: 13px; font-family: Georgia, serif; } #cultivarButtons button:hover { background: #e9e4db; } #cultivarButtons button.active { background: #3a3a3a; color: #fff; border-color: #3a3a3a; } input[type="range"] { width: 100%; margin: 6px 0 20px 0; } p { margin: 6px 0; font-size: 14px; } img { margin-top: 20px; width: 100%; border: 1px solid #ddd; background: transparent; } strong { font-weight: normal; letter-spacing: 0.5px; } #controls button { background: #3a3a3a; color: #fff; border: none; padding: 8px 16px; margin: 4px 4px 4px 0; cursor: pointer; font-size: 13px; font-family: Georgia, serif; } #controls button:hover { background: #55524c; } #controls button.secondary { background: #f4f1eb; color: #2b2b2b; border: 1px solid #d8d2c8; } #controls button.secondary:hover { background: #e9e4db; } #progressTrack { width: 100%; height: 6px; background: #eee6d9; margin: 10px 0 4px 0; position: relative; } #progressFill { height: 100%; background: #8a9a5b; width: 0%; transition: width 0.25s linear; } #imageFrame { margin-top: 20px; width: 100%; min-height: 240px; border: 1px solid #ddd; background-image: url("https://www.nasa.gov/wp-content/uploads/2024/05/53585603978_050a55afc2_o-2.jpg"); background-size: cover; background-position: center; display: flex; align-items: center; justify-content: center; text-align: center; box-sizing: border-box; padding: 20px; } #imageFrame img { margin: 0; border: none; display: block; width: auto; max-width: 80%; max-height: 320px; background: transparent; } #imagePlaceholder { display: none; color: #8a8478; font-size: 13px; line-height: 1.6; } #imagePlaceholder .missing-path { display: block; margin-top: 6px; font-family: "Courier New", monospace; font-size: 12px; color: #b0a998; }

Select Cultivar

Selected Cultivar: Alaska Mix


Temperature: 20°C

Humidity: 40%

Barometric Pressure: 100.0 kPa

CO₂ Concentration: 400 ppm


Growth Simulation

Growth Rate: 0.00 stages / sec

Growth Stage: 1 / 18

Image not found
let currentCultivar = "Alaska_Mix"; let growthProgress = 1; // continuous value, 1..18 let isPlaying = false; let timerId = null; const TICK_MS = 250; // Base URL for the WordPress Media Library uploads folder holding // the stage photos (transparent PNGs). // FIX: this was previously wrapped in Markdown link syntax // ("[url](url)"), which is not a valid URL string. It must be a // plain URL. const IMAGE_BASE_URL = "https://fairchildgarden.org/wp-content/uploads/2026/08/"; // Maps each cultivar's internal name to the short filename prefix // used in the uploaded PNG filenames (e.g. Nas_17.png). const CULTIVAR_PREFIX = { "Alaska_Mix": "Nas", "Dwarf_Grey_Sugar_Pea": "Pea", "Dragon_Fennel": "Fen", "Papalo": "Pap", "Red_Romaine": "Rom", "Thai_Basil": "Bas", "White_Beauty_Amaranth": "Ama" }; function setCultivar(cultivar){ currentCultivar = cultivar; document.querySelectorAll("#cultivarButtons button").forEach(btn => { btn.classList.toggle("active", btn.dataset.cultivar === cultivar); }); document.getElementById("cultivarLabel").textContent = cultivar.replaceAll("_"," "); resetGrowth(); } const tempSlider = document.getElementById("tempSlider"); const humiditySlider = document.getElementById("humiditySlider"); const pressureSlider = document.getElementById("pressureSlider"); const co2Slider = document.getElementById("co2Slider"); function computeGrowthScore(){ const temp = Number(tempSlider.value); const humidity = Number(humiditySlider.value); const pressure = Number(pressureSlider.value); const co2 = Number(co2Slider.value); document.getElementById("tempLabel").textContent = temp + "°C"; document.getElementById("humidityLabel").textContent = humidity + "%"; document.getElementById("pressureLabel").textContent = (pressure/1000).toFixed(1) + " kPa"; document.getElementById("co2Label").textContent = co2 + " ppm"; let growthScore = 0; // Only CO2 affects growth rate. Temperature, humidity, pressure, // and cultivar have no effect. // // 400-700 ppm : beneficial zone — bell curve peaking at 550 ppm // 700 ppm : harmful — penalty grows the further above 700 const peak = 550; // ppm with maximum growth benefit const sigma = 75; // controls how narrow/wide the bell curve is if (co2 >= 400 && co2 <= 700) { // Normal distribution shape, scaled so the peak (at 550) // contributes +3 to growthScore. const gaussian = Math.exp(-Math.pow(co2 - peak, 2) / (2 * sigma * sigma)); growthScore += gaussian * 3; } else { // Outside the beneficial window: penalty grows linearly with // distance from the nearest edge (400 or 700). const distance = co2 = 18){ growthProgress = 18; renderStage(); pausePlayback(); return; } renderStage(); } function togglePlay(){ if (isPlaying){ pausePlayback(); } else { startPlayback(); } } function startPlayback(){ if (growthProgress >= 18) return; isPlaying = true; document.getElementById("playBtn").textContent = "Pause"; timerId = setInterval(tick, TICK_MS); } function pausePlayback(){ isPlaying = false; document.getElementById("playBtn").textContent = "Play"; if (timerId){ clearInterval(timerId); timerId = null; } } function resetGrowth(){ pausePlayback(); growthProgress = 1; refreshRateDisplay(); renderStage(); } [tempSlider, humiditySlider, pressureSlider, co2Slider].forEach(s => { s.addEventListener("input", refreshRateDisplay); }); document.querySelector('[data-cultivar="Alaska_Mix"]').classList.add("active"); refreshRateDisplay(); renderStage();
Fruiting Plants Simulation
body { font-family: Georgia, "Times New Roman", serif; background: #f6f3ee; color: #2b2b2b; margin: 0; padding: 40px; } .sim-container { max-width: 900px; margin: auto; background: #ffffff; padding: 30px 40px; border: 1px solid #e5e0d8; box-shadow: 0 2px 10px rgba(0,0,0,0.05); } h3 { font-weight: normal; letter-spacing: 1px; text-transform: uppercase; font-size: 14px; margin-top: 30px; color: #3a3a3a; } #cultivarButtons button { background: #f4f1eb; border: 1px solid #d8d2c8; padding: 8px 12px; margin: 4px; cursor: pointer; font-size: 13px; font-family: Georgia, serif; } #cultivarButtons button:hover { background: #e9e4db; } #cultivarButtons button.active { background: #3a3a3a; color: #fff; border-color: #3a3a3a; } input[type="range"] { width: 100%; margin: 6px 0 20px 0; } p { margin: 6px 0; font-size: 14px; } img { margin-top: 20px; width: 100%; border: 1px solid #ddd; background: #fafafa; } strong { font-weight: normal; letter-spacing: 0.5px; } #controls button { background: #3a3a3a; color: #fff; border: none; padding: 8px 16px; margin: 4px 4px 4px 0; cursor: pointer; font-size: 13px; font-family: Georgia, serif; } #controls button:hover { background: #55524c; } #controls button.secondary { background: #f4f1eb; color: #2b2b2b; border: 1px solid #d8d2c8; } #controls button.secondary:hover { background: #e9e4db; } #progressTrack { width: 100%; height: 6px; background: #eee6d9; margin: 10px 0 4px 0; position: relative; } #progressFill { height: 100%; background: #8a9a5b; width: 0%; transition: width 0.25s linear; } #imageFrame { margin-top: 20px; width: 100%; min-height: 240px; border: 1px solid #ddd; background-image: url("https://www.nasa.gov/wp-content/uploads/2024/05/53585603978_050a55afc2_o-2.jpg"); background-size: cover; background-position: center; display: flex; align-items: center; justify-content: center; text-align: center; box-sizing: border-box; padding: 20px; } #imageFrame img { margin: 0; border: none; display: block; background: transparent; max-width: 80%; max-height: 320px; } /* Only applied when we've fallen back to the JPG (no real alpha channel) — fakes transparency by letting white background pixels blend through to the ISS photo behind them. A real PNG with an actual alpha channel never gets this class, so it renders with its true colors and clean edges untouched. */ #imageFrame img.blend-fallback { mix-blend-mode: multiply; } #imagePlaceholder { display: none; color: #8a8478; font-size: 13px; line-height: 1.6; } #imagePlaceholder .missing-path { display: block; margin-top: 6px; font-family: "Courier New", monospace; font-size: 12px; color: #b0a998; }

Select Cultivar

Selected Cultivar: Buena Mulata Pepper


Temperature: 20°C

Humidity: 40%

Barometric Pressure: 100.0 kPa

CO₂ Concentration: 400 ppm


Growth Simulation

Growth Rate: 0.00 stages / sec

Growth Stage: 1 / 18

Trial Length: 0s

Image not found
let currentCultivar = "Buena_Mulata_Pepper"; let growthProgress = 1; // continuous value, 1..18 let isPlaying = false; let timerId = null; // Seconds the trial has been actively running (playing). Resets to // 0 on Reset, but otherwise only ever counts up while playing — the // longer the trial runs, the higher the growth-rate bonus below. let elapsedSeconds = 0; // Base URL for the WordPress Media Library uploads folder holding // the stage photos. const IMAGE_BASE_URL = "https://fairchildgarden.org/wp-content/uploads/2026/08/"; // Maps each cultivar's internal name to the short filename prefix // used in the uploaded image filenames (e.g. Robin_11.jpg). const CULTIVAR_PREFIX = { "Buena_Mulata_Pepper": "Buena", "Heartbreaker_Dora_Red_Hybrid_Tomato": "Heart", "Microtom_Tomato": "Micro", "Red_Robin_Tomato": "Robin" }; const TICK_MS = 250; // How much each second of trial length adds to growthScore. No cap: // this term keeps climbing for as long as the simulation plays. const TRIAL_LENGTH_FACTOR = 0.05; // Temperature bell curve driving growth rate: full strength // (gaussian = 1) anywhere in the 22-25°C plateau, tapering off the // further temp drifts outside that window. const TEMP_PEAK_LOW = 22; const TEMP_PEAK_HIGH = 25; const TEMP_SIGMA = 2.5; // Fruit set is shown visually through the image sequence rather // than as a number. Buena Mulata, Microtom, and Red Robin are // hard-capped at stage 12 — they never reveal the final 6 stages, // regardless of temperature. Heartbreaker Dora Red Hybrid Tomato is // also capped at 12 by default, but gradually unlocks into stages // 13-18 the closer temperature sits to the 22-23°C plateau (see // LAST6_PEAK_* below). The growth stage counter and progress bar // still advance 1-18 as normal underneath. const IMAGE_STAGE_CAP = { "Buena_Mulata_Pepper": 12, "Heartbreaker_Dora_Red_Hybrid_Tomato": 12, "Microtom_Tomato": 12, "Red_Robin_Tomato": 12 }; // Bell curve controlling how many of the last 6 images (13-18) are // unlocked for Heartbreaker Dora Red: full 6 unlocked (gaussian = 1) // anywhere in the 22-23°C plateau, fewer the further temp drifts // outside that window. const LAST6_PEAK_LOW = 22; const LAST6_PEAK_HIGH = 23; const LAST6_SIGMA = 1.5; function last6UnlockGaussian(temp){ let distance = 0; if (temp LAST6_PEAK_HIGH) distance = temp - LAST6_PEAK_HIGH; return Math.exp(-(distance * distance) / (2 * LAST6_SIGMA * LAST6_SIGMA)); } // Effective image-stage cap for a cultivar at a given temperature. function getEffectiveCap(cultivar, temp){ const baseCap = IMAGE_STAGE_CAP[cultivar] || 18; if (baseCap >= 18) return 18; if (cultivar === "Heartbreaker_Dora_Red_Hybrid_Tomato") { const unlockedExtra = Math.round(last6UnlockGaussian(temp) * 6); return Math.min(18, baseCap + unlockedExtra); } // Buena Mulata, Microtom, Red Robin: hard cap, no unlock. return baseCap; } function temperatureGaussian(temp){ let distance = 0; if (temp TEMP_PEAK_HIGH) distance = temp - TEMP_PEAK_HIGH; return Math.exp(-(distance * distance) / (2 * TEMP_SIGMA * TEMP_SIGMA)); } function setCultivar(cultivar){ currentCultivar = cultivar; document.querySelectorAll("#cultivarButtons button").forEach(btn => { btn.classList.toggle("active", btn.dataset.cultivar === cultivar); }); document.getElementById("cultivarLabel").textContent = cultivar.replaceAll("_"," "); resetGrowth(); } const tempSlider = document.getElementById("tempSlider"); const humiditySlider = document.getElementById("humiditySlider"); const pressureSlider = document.getElementById("pressureSlider"); const co2Slider = document.getElementById("co2Slider"); function computeGrowthScore(){ const temp = Number(tempSlider.value); const humidity = Number(humiditySlider.value); const pressure = Number(pressureSlider.value); const co2 = Number(co2Slider.value); document.getElementById("tempLabel").textContent = temp + "°C"; document.getElementById("humidityLabel").textContent = humidity + "%"; document.getElementById("pressureLabel").textContent = (pressure/1000).toFixed(1) + " kPa"; document.getElementById("co2Label").textContent = co2 + " ppm"; let growthScore = 0; // Temperature: normal-distribution-shaped bonus peaking across // the 22-25°C plateau (scaled so full peak contributes +6). growthScore += temperatureGaussian(temp) * 6; growthScore += (humidity - 40) * 1; growthScore += 10 - Math.abs(pressure - 100000) / 500; growthScore += (co2 - 400) * 0.01; switch(currentCultivar){ case "Buena_Mulata_Pepper": break; case "Heartbreaker_Dora_Red_Hybrid_Tomato": growthScore += 1.5; break; case "Microtom_Tomato": growthScore += 3; break; case "Red_Robin_Tomato": growthScore += 2.5; break; } // Trial length: the longer the trial has been running, the // higher the growth rate — uncapped, so this keeps climbing. growthScore += elapsedSeconds * TRIAL_LENGTH_FACTOR; return growthScore; } // Converts the combined slider/cultivar/trial-length score into a // rate, in growth-stages per second. Harsh conditions still creep // forward (floor of 0.02) rather than freezing completely; ideal // conditions move noticeably faster than poor ones. function scoreToRate(growthScore){ const rate = 0.05 + growthScore * 0.03; return Math.max(0.02, rate); } function refreshRateDisplay(){ const rate = scoreToRate(computeGrowthScore()); document.getElementById("rateLabel").textContent = rate.toFixed(2); document.getElementById("trialLengthLabel").textContent = elapsedSeconds.toFixed(0) + "s"; } function renderStage(){ const stage = Math.min(18, Math.max(1, Math.round(growthProgress))); document.getElementById("stageLabel").textContent = stage; const pct = ((growthProgress - 1) / 17) * 100; document.getElementById("progressFill").style.width = pct + "%"; const cap = getEffectiveCap(currentCultivar, Number(tempSlider.value)); const imageStage = Math.min(stage, cap); const prefix = CULTIVAR_PREFIX[currentCultivar]; // Try the PNG first — if the server has a real transparent PNG // for this stage, it renders with its true colors and no // blend-mode trick needed. If it 404s, handleImageError() falls // back to the JPG with the fake-transparency blend. const pngPath = IMAGE_BASE_URL + prefix + "_" + imageStage + ".png"; const jpgPath = IMAGE_BASE_URL + prefix + "_" + imageStage + ".jpg"; const img = document.getElementById("growthImage"); img.dataset.jpgFallback = jpgPath; img.dataset.attemptedFallback = "false"; img.classList.remove("blend-fallback"); // Hide any stale placeholder while the new image attempts to load. img.style.display = "block"; document.getElementById("imagePlaceholder").style.display = "none"; document.getElementById("missingPathLabel").textContent = pngPath; img.src = pngPath; } function handleImageError(){ const img = document.getElementById("growthImage"); if (img.dataset.attemptedFallback !== "true" && img.dataset.jpgFallback){ // PNG not found at this stage — fall back to the JPG with // the blend-mode fake-transparency trick. img.dataset.attemptedFallback = "true"; img.classList.add("blend-fallback"); document.getElementById("missingPathLabel").textContent = img.dataset.jpgFallback; img.src = img.dataset.jpgFallback; return; } // Both PNG and JPG failed — genuinely missing. img.style.display = "none"; document.getElementById("imagePlaceholder").style.display = "block"; } function handleImageLoad(){ document.getElementById("growthImage").style.display = "block"; document.getElementById("imagePlaceholder").style.display = "none"; } function tick(){ elapsedSeconds += TICK_MS / 1000; const rate = scoreToRate(computeGrowthScore()); document.getElementById("rateLabel").textContent = rate.toFixed(2); document.getElementById("trialLengthLabel").textContent = elapsedSeconds.toFixed(0) + "s"; growthProgress += rate * (TICK_MS / 1000); if (growthProgress >= 18){ growthProgress = 18; renderStage(); pausePlayback(); return; } renderStage(); } function togglePlay(){ if (isPlaying){ pausePlayback(); } else { startPlayback(); } } function startPlayback(){ if (growthProgress >= 18) return; isPlaying = true; document.getElementById("playBtn").textContent = "Pause"; timerId = setInterval(tick, TICK_MS); } function pausePlayback(){ isPlaying = false; document.getElementById("playBtn").textContent = "Play"; if (timerId){ clearInterval(timerId); timerId = null; } } function resetGrowth(){ pausePlayback(); growthProgress = 1; elapsedSeconds = 0; refreshRateDisplay(); renderStage(); } [tempSlider, humiditySlider, pressureSlider, co2Slider].forEach(s => { s.addEventListener("input", refreshRateDisplay); }); tempSlider.addEventListener("input", renderStage); document.querySelector('[data-cultivar="Buena_Mulata_Pepper"]').classList.add("active"); refreshRateDisplay(); renderStage();

GBE Project Resources

Information, video, helpful guides, and more

GBE Overview

Check out our welcome video below!
Watch Here

More questions about Growing Beyond Earth? Join us for Open Office Hours 

Office Hours will be virtual from October to April. If these times are not available for you or your students, you can schedule a one-on-one by emailing gbe@fairchildgarden.org 


Monday, February 9th, 2026 – 10:30AM-1:00PM Miami-Time – Zoom Link

Thursday, February 19th, 2026 – 2-4PM Miami-Time – Zoom Link

Monday, March 9, 2026 – 10:30AM-1:00PM Miami-Time – Zoom Link

Thursday, April 2nd, 2026 – 2-4PM Miami-Time – Zoom Link

This website is based upon work supported by NASA Grant No. 80NCCS22M0125-SciAct. Any opinions, findings and conclusions or recommendations expressed in this material are those of the authors and do not necessarily reflect the views of the National Aeronautics and Space Administration.

With generous support from

Partners:

				
					console.log( 'Code is Poetry' );
				
			

FOLLOW THE LATEST @fairchildgarden