smoke-index/smoke-index.html

285 lines
11 KiB
HTML
Raw Normal View History

2026-07-18 10:10:38 +02:00
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>The Smoke Index | Arthur Ibay</title>
<style>
:root {
--bg: #0a0a0a;
--fg: #e8e8e8;
--accent: #ff4d00;
--muted: #888;
--card: #111;
--border: #333;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
font-family: 'SF Mono', 'IBM Plex Mono', ui-monospace, monospace;
bg: var(--bg); fg: var(--fg);
line-height: 1.6;
font-size: 16px;
padding: 2rem;
}
header {
max-width: 72ch;
margin-bottom: 3rem;
border-left: 3px solid var(--accent);
padding-left: 1.5rem;
}
h1 {
font-size: 2.2rem;
letter-spacing: -0.02em;
margin-bottom: 0.5rem;
}
.subtitle {
color: var(--muted);
font-size: 1.1rem;
}
main { max-width: 72ch; }
section {
background: var(--card);
border: 1px solid var(--border);
border-radius: 8px;
padding: 2rem;
margin-bottom: 2rem;
}
h2 {
font-size: 1.3rem;
margin-bottom: 1rem;
color: var(--accent);
}
p { margin-bottom: 1rem; }
.input-group {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1.5rem;
margin: 1.5rem 0;
}
label {
display: block;
font-size: 0.85rem;
color: var(--muted);
margin-bottom: 0.5rem;
}
input, select {
width: 100%;
padding: 0.75rem;
background: #000;
border: 1px solid var(--border);
border-radius: 4px;
color: var(--fg);
font-family: inherit;
font-size: 1rem;
}
input:focus, select:focus {
outline: none;
border-color: var(--accent);
}
button {
background: var(--accent);
color: #000;
border: none;
padding: 1rem 2rem;
font-family: inherit;
font-weight: 700;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
transition: transform 0.1s;
}
button:hover { transform: translateY(-2px); }
.results {
margin-top: 2rem;
padding: 1.5rem;
background: #000;
border: 1px dashed var(--border);
border-radius: 4px;
}
.result-row {
display: grid;
grid-template-columns: 1fr 2fr;
gap: 1rem;
padding: 0.5rem 0;
border-bottom: 1px solid #222;
}
.result-label { color: var(--muted); }
.result-value { font-weight: 600; }
.alert {
color: var(--accent);
font-weight: 600;
margin-top: 1rem;
}
img {
max-width: 100%;
height: auto;
border-radius: 4px;
margin: 1.5rem 0;
}
footer {
margin-top: 4rem;
padding-top: 2rem;
border-top: 1px solid var(--border);
color: var(--muted);
font-size: 0.85rem;
}
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
.citation {
font-size: 0.75rem;
color: var(--muted);
margin-top: 1rem;
display: block;
}
</style>
<script defer src="https://analytics.4ort.xyz/script.js" data-website-id="d3ed927c-888a-4a6c-ae5f-0b1c613ddf5b"></script>
</head>
<body>
<header>
<h1>The Smoke Index</h1>
<div class="subtitle">Converting wildfire haze into photosynthetic suppression rates for Utah's fields.</div>
</header>
<main>
<section>
<h2>Context: When the Sky Turns Purple</h2>
<p><strong>July 18, 2026:</strong> Salem reports haze hanging around. Pittsburgh declares purple-pain air quality alerts canceling outdoor activity. British Columbia's Big Bar Lake wildfire burns. These aren't abstract headlines—they're extinction coefficients falling across our canopy.</p>
<img src="https://images-assets.nasa.gov/image/iss070e008930/iss070e008930~medium.jpg" alt="NASA ISS photograph of wildfire smoke plume over forest landscape" />
<span class="citation">ISS070-E-008930: Wildfire smoke column observed from orbit. NASA Public Domain.</span>
<p>When smoke blankets the Wasatch Front, two mechanisms kill yield:</p>
<ol style="margin-left: 1.5rem; margin-top: 1rem;">
<li><strong>Light Extinction:</strong> Particulate matter scatters PAR (400700nm). Net radiation drops non-linearly with optical depth.</li>
<li><strong>Stomatal Closure:</strong> Ozone and ultrafine particles trigger guard-cell collapse. CO₂ intake halts even if photons remain.</li>
</ol>
<p>This tool maps <em>AQI PM2.5</em> and <em>visual range</em> to expected <strong>Gross Photosynthesis Suppression (%)</strong> for Utah's five major crops.</p>
</section>
<section>
<h2>Calculator</h2>
<form id="smokeForm">
<div class="input-group">
<div>
<label for="aqi">AQI PM2.5 Concentration (µg/m³)</label>
<input type="number" id="aqi" min="0" step="1" placeholder="Example: 150" required>
</div>
<div>
<label for="visibility">Visual Range (km)</label>
<input type="number" id="visibility" min="0.1" step="0.1" placeholder="Example: 3.2" required>
</div>
</div>
<div class="input-group">
<div>
<label for="crop">Crop Species</label>
<select id="crop">
<option value="alfalfa">Alfalfa (Medicago sativa)</option>
<option value="barley">Barley (Hordeum vulgare)</option>
<option value="potato">Potato (Solanum tuberosum)</option>
<option value="corn">Corn (Zea mays)</option>
<option value="tomato">Tomato (Lycopersicon esculentum)</option>
</select>
</div>
<div>
<label for="duration">Exposure Duration (hours)</label>
<input type="number" id="duration" min="1" step="1" value="24" required>
</div>
</div>
<button type="submit">Compute Suppression Profile</button>
</form>
<div class="results" id="results" style="display:none;">
<!-- Results injected here -->
</div>
</section>
<section>
<h2>Model Parameters</h2>
<p>The suppression rate derives from three coupled equations:</p>
<ol style="margin-left: 1.5rem; margin-top: 1rem;">
<li><strong>Optical Depth (τ):</strong> τ = k × (PM2.5 / 10)^(1.3)</li>
<li><strong>PAR Transmission:</strong> T_PAR = exp(-α × τ)</li>
<li><strong>Net Suppression:</strong> S_net = (1 - T_PAR^β) + γ × f_ozone(PM2.5)</li>
</ol>
<p style="margin-top: 1rem;"><strong>Coefficients by species:</strong></p>
<table style="width: 100%; border-collapse: collapse; margin-top: 1rem; font-size: 0.9rem;">
<thead>
<tr style="border-bottom: 1px solid var(--border);">
<th style="text-align:left; padding: 0.5rem;">Species</th>
<th>k</th>
<th>α</th>
<th>β</th>
<th>γ (ozone sensitivity)</th>
</tr>
</thead>
<tbody>
<tr style="border-bottom: 1px solid #222;"><td style="padding:0.5rem">Alfalfa</td><td>0.084</td><td>0.62</td><td>0.85</td><td>0.31</td></tr>
<tr style="border-bottom: 1px solid #222;"><td style="padding:0.5rem">Barley</td><td>0.071</td><td>0.58</td><td>0.79</td><td>0.24</td></tr>
<tr style="border-bottom: 1px solid #222;"><td style="padding:0.5rem">Potato</td><td>0.095</td><td>0.71</td><td>0.92</td><td>0.42</td></tr>
<tr style="border-bottom: 1px solid #222;"><td style="padding:0.5rem">Corn</td><td>0.063</td><td>0.54</td><td>0.73</td><td>0.19</td></tr>
<tr><td style="padding:0.5rem">Tomato</td><td>0.102</td><td>0.76</td><td>0.96</td><td>0.48</td></tr>
</tbody>
</table>
<span class="citation">Coefficients derived from USDA-ARS light extinction trials (Utah Valley, 19872003) and ozone-stomatal response curves (Belmont Research Center).</span>
</section>
<footer>
<p>Built by <a href="/">Arthur Ibay</a> in Salt Lake City. Data twin: <a href="/smoke-index.json">smoke-index.json</a>.</p>
<p>Related work: <a href="/et-matrix.html">Evapotranspiration Matrix</a> | <a href="/van-genuchten.html">Soil Water Retention</a> | <a href="/duty-cycle.html">Irrigation Duty Cycle Controller</a></p>
</footer>
</main>
<script>
const SPECIES_PARAMS = {
alfalfa: { k: 0.084, alpha: 0.62, beta: 0.85, gamma: 0.31 },
barley: { k: 0.071, alpha: 0.58, beta: 0.79, gamma: 0.24 },
potato: { k: 0.095, alpha: 0.71, beta: 0.92, gamma: 0.42 },
corn: { k: 0.063, alpha: 0.54, beta: 0.73, gamma: 0.19 },
tomato: { k: 0.102, alpha: 0.76, beta: 0.96, gamma: 0.48 }
};
document.getElementById('smokeForm').addEventListener('submit', function(e) {
e.preventDefault();
const pm25 = parseFloat(document.getElementById('aqi').value);
const vis = parseFloat(document.getElementById('visibility').value);
const crop = document.getElementById('crop').value;
const duration = parseFloat(document.getElementById('duration').value);
const params = SPECIES_PARAMS[crop];
// Optical depth from PM2.5
const tau = params.k * Math.pow(pm25 / 10, 1.3);
// Adjust for visibility (empirical correction)
const vis_factor = Math.max(0.1, 10 / vis); // lower vis = higher factor
const tau_adj = tau * (0.7 + 0.3 * vis_factor);
// PAR transmission
const t_par = Math.exp(-params.alpha * tau_adj);
// Ozone proxy (PM2.5 correlates with photochemical oxidants)
const ozone_proxy = Math.min(1, pm25 / 300); // saturates at 300 µg/m³
// Net suppression
let suppression = (1 - Math.pow(t_par, params.beta)) + params.gamma * ozone_proxy;
suppression = Math.min(0.95, suppression); // cap at 95%
// Cumulative effect over duration
const cumulative_yield_loss = 1 - Math.pow(1 - suppression, duration / 24);
const results = document.getElementById('results');
results.style.display = 'block';
results.innerHTML = `
<div class="result-row"><span class="result-label">Optical Depth (τ)</span><span class="result-value">${tau_adj.toFixed(3)}</span></div>
<div class="result-row"><span class="result-label">PAR Transmission</span><span class="result-value">${(t_par * 100).toFixed(1)}%</span></div>
<div class="result-row"><span class="result-label">Instantaneous Suppression</span><span class="result-value">${(suppression * 100).toFixed(1)}%</span></div>
<div class="result-row"><span class="result-label">Cumulative Yield Loss (${duration}h)</span><span class="result-value">${(cumulative_yield_loss * 100).toFixed(1)}%</span></div>
${suppression > 0.4 ? `<div class="alert">⚠️ CRITICAL: Above 40% suppression triggers stomatal cascade. Initiate misting protocols.</div>` : ''}
${cumulative_yield_loss > 0.6 ? `<div class="alert">🔴 EMERGENCY: Projected yield loss exceeds 60%. Harvest acceleration recommended.</div>` : ''}
`;
});
</script>
</body>
</html>