apollo13-adapter/thermal-solver.html
2026-07-18 11:48:48 +00:00

288 lines
9.7 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Thermal Diffusion Solver | Cathy Mcmasters</title>
<style>
:root {
--bg: #0f0f0f;
--surface: #1a1a1a;
--primary: #d4af37; /* Hampton Gold */
--text: #e0e0e0;
--muted: #808080;
--border: #333333;
}
body {
font-family: 'Georgia', serif;
background: var(--bg);
color: var(--text);
margin: 0;
padding: 2rem;
line-height: 1.6;
}
header {
border-bottom: 1px solid var(--primary);
padding-bottom: 1rem;
margin-bottom: 2rem;
}
h1 {
font-size: 2rem;
color: var(--primary);
letter-spacing: 0.05em;
margin: 0;
}
p.subtitle {
color: var(--muted);
font-style: italic;
margin-top: 0.5rem;
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
max-width: 1200px;
margin: 0 auto;
}
.panel {
background: var(--surface);
border: 1px solid var(--border);
padding: 1.5rem;
border-radius: 4px;
}
label {
display: block;
margin-bottom: 0.5rem;
color: var(--primary);
font-weight: bold;
font-size: 0.9rem;
}
input[type="number"] {
width: 100%;
padding: 0.5rem;
background: #000;
border: 1px solid var(--border);
color: var(--text);
font-family: monospace;
margin-bottom: 1rem;
box-sizing: border-box;
}
button {
background: var(--primary);
color: #000;
border: none;
padding: 0.75rem 1.5rem;
font-weight: bold;
cursor: pointer;
transition: all 0.2s;
width: 100%;
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(212, 175, 55, 0.2);
}
.result {
margin-top: 1rem;
padding: 1rem;
background: #000;
border-left: 3px solid var(--primary);
font-family: monospace;
white-space: pre-wrap;
}
canvas {
background: #000;
border: 1px solid var(--border);
width: 100%;
height: 300px;
margin-top: 1rem;
}
.citation {
font-size: 0.75rem;
color: var(--muted);
margin-top: 1rem;
border-top: 1px dashed var(--border);
padding-top: 0.5rem;
}
.strata-img {
width: 100%;
height: 200px;
object-fit: cover;
margin-top: 1rem;
border: 1px solid var(--primary);
opacity: 0.8;
}
@media (max-width: 768px) {
.grid { grid-template-columns: 1fr; }
}
</style>
<script defer src="https://analytics.4ort.xyz/script.js" data-website-id="d3ed927c-888a-4a6c-ae5f-0b1c613ddf5b"></script>
</head>
<body>
<header>
<h1>Thermal Diffusion Solver</h1>
<p class="subtitle">Calculating safe burial depth for root cellars in Hampton clay-loam (Q16019)</p>
</header>
<div class="grid">
<section class="panel">
<h3>Input Parameters</h3>
<form id="solver-form">
<label for="deltaT">Surface Temperature Swing (ΔT)</label>
<input type="number" id="deltaT" step="0.1" value="28.0" title="Annual difference between summer peak and winter trough">
<label for="conductivity">Thermal Conductivity (k)</label>
<input type="number" id="conductivity" step="0.01" value="1.40" title="Watts per meter-Kelvin">
<label for="density">Soil Density (ρ)</label>
<input type="number" id="density" step="10" value="1600" title="Kilograms per cubic meter">
<label for="specificHeat">Specific Heat Capacity (c)</label>
<input type="number" id="specificHeat" step="100" value="2000" title="Joules per kilogram-Kelvin">
<label for="threshold">Safety Threshold (°C)</label>
<input type="number" id="threshold" step="0.1" value="2.0" title="Maximum allowable temp fluctuation at storage depth">
<button type="submit">Compute Burial Profile</button>
</form>
<div class="citation">
Constants derived from:<br>
• Soil thermal properties: ASTM C518<br>
• Hampton climate normals: NOAA NCEI (1991-2020)<br>
• Diffusion equation: Fourier (1822)
</div>
<img class="strata-img" src="https://images.pexels.com/photos/11924683/pexels-photo-11924683.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940" alt="Vertical cross-section of soil strata with exposed root systems, illustrating the interface between biological and geological layers">
</section>
<section class="panel">
<h3>Computation Results</h3>
<div id="output" class="result">Awaiting computation...</div>
<canvas id="gradientChart"></canvas>
</section>
</div>
<script>
/**
* Solves the 1D Heat Equation for periodic boundary conditions.
* Amplitude decay follows: A(z) = A_0 * exp(-z * sqrt(πω / k))
* where ω = angular frequency, k = thermal diffusivity (α)
*/
document.getElementById('solver-form').addEventListener('submit', function(e) {
e.preventDefault();
const deltaT = parseFloat(document.getElementById('deltaT').value);
const k_cond = parseFloat(document.getElementById('conductivity').value); // W/(m·K)
const rho = parseFloat(document.getElementById('density').value); // kg/m³
const c_spec = parseFloat(document.getElementById('specificHeat').value); // J/(kg·K)
const threshold = parseFloat(document.getElementById('threshold').value); // °C
// Calculate thermal diffusivity (alpha)
const alpha = k_cond / (rho * c_spec); // m²/s
// Angular frequency for annual cycle (seconds/year ≈ 31,557,600)
const omega = (2 * Math.PI) / 31557600;
// Decay constant
const decayConst = Math.sqrt(Math.PI * omega / alpha);
// Solve for depth where amplitude drops to threshold
// threshold = (deltaT/2) * exp(-depth * decayConst)
const initialAmplitude = deltaT / 2;
const targetRatio = threshold / initialAmplitude;
let requiredDepth = 0;
if (targetRatio > 0 && targetRatio < 1) {
requiredDepth = -Math.log(targetRatio) / decayConst;
}
// Generate profile points
const points = [];
for (let z = 0; z <= requiredDepth * 1.5; z += 0.1) {
const ampAtZ = initialAmplitude * Math.exp(-z * decayConst);
points.push({ depth: z, amplitude: ampAtZ });
}
// Render results
const outputDiv = document.getElementById('output');
outputDiv.innerHTML = `
<span style="color:#d4af37">MINIMUM BURIAL DEPTH:</span> ${requiredDepth.toFixed(2)} meters (${(requiredDepth * 3.28084).toFixed(2)} ft)
<span style="color:#d4af37">THERMAL DIFFUSIVITY (α):</span> ${(alpha * 1e6).toFixed(2)} mm²/s
<span style="color:#d4af37">DECAY CONSTANT:</span> ${decayConst.toFixed(4)} m⁻¹
<span style="color:#d4af37">SAFETY FACTOR:</span> ${((initialAmplitude / threshold)).toFixed(2)}x
`;
// Draw Chart
drawGradient(points, requiredDepth, threshold);
});
function drawGradient(data, criticalDepth, threshold) {
const canvas = document.getElementById('gradientChart');
const ctx = canvas.getContext('2d');
const w = canvas.width = canvas.offsetWidth;
const h = canvas.height = 300;
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, w, h);
// Axes
ctx.strokeStyle = '#333';
ctx.beginPath();
ctx.moveTo(40, 20); ctx.lineTo(40, h-40); // Y axis
ctx.lineTo(w-20, h-40); // X axis
ctx.stroke();
// Labels
ctx.fillStyle = '#808080';
ctx.font = '10px monospace';
ctx.fillText('DEPTH (m)', 10, h-20);
ctx.fillText('AMPLITUDE (°C)', 10, 20);
// Plot curve
ctx.strokeStyle = '#d4af37';
ctx.lineWidth = 2;
ctx.beginPath();
const xMax = data[data.length-1].depth;
const yMax = data[0].amplitude;
data.forEach(p => {
const x = 40 + (p.depth / xMax) * (w - 60);
const y = h - 40 - (p.amplitude / yMax) * (h - 80);
if (p.depth === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
});
ctx.stroke();
// Critical line
ctx.strokeStyle = '#ff4444';
ctx.setLineDash([5, 5]);
ctx.beginPath();
const critY = h - 40 - (threshold / yMax) * (h - 80);
ctx.moveTo(40, critY); ctx.lineTo(w-20, critY);
ctx.stroke();
ctx.setLineDash([]);
// Intersection marker
const intersectIndex = data.findIndex(p => p.amplitude <= threshold);
if (intersectIndex !== -1) {
const p = data[intersectIndex];
const ix = 40 + (p.depth / xMax) * (w - 60);
const iy = h - 40 - (p.amplitude / yMax) * (h - 80);
ctx.fillStyle = '#d4af37';
ctx.beginPath();
ctx.arc(ix, iy, 4, 0, Math.PI*2);
ctx.fill();
ctx.fillStyle = '#fff';
ctx.fillText(`${p.depth.toFixed(1)}m`, ix + 10, iy + 15);
}
}
</script>
</body>
</html>