amy-coates-cobbler-toolkit/cobbler-calculator.html
2026-07-18 23:56:17 +00:00

366 lines
15 KiB
HTML
Raw 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>Rosemary Peach Cobbler Calculator | Amy Coates</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700&family=Source+Sans+Pro:wght@400;600&display=swap');
:root {
--amber: #d4af37;
--deep-amber: #a67c00;
--cream: #f5f0e6;
--ink: #0f0f0f;
--shadow: rgba(0,0,0,0.15);
}
* { box-sizing: border-box; }
body {
font-family: 'Source Sans Pro', system-ui, sans-serif;
margin: 0;
padding: 0;
background: var(--cream);
color: var(--ink);
line-height: 1.7;
}
.hero {
background: linear-gradient(to bottom, #2a1810 0%, #4a2c18 100%);
color: var(--cream);
padding: 4rem 2rem;
text-align: center;
border-bottom: 4px solid var(--amber);
}
.hero h1 {
font-family: 'Playfair Display', Georgia, serif;
font-size: 3rem;
margin: 0 0 1rem 0;
letter-spacing: 0.05em;
}
.hero p {
font-size: 1.2rem;
max-width: 600px;
margin: 0 auto;
opacity: 0.9;
}
.container {
max-width: 800px;
margin: 3rem auto;
padding: 0 2rem;
}
.card {
background: white;
border: 2px solid var(--amber);
border-radius: 0;
padding: 2.5rem;
margin-bottom: 2rem;
box-shadow: 8px 8px 0 var(--shadow);
}
.card h2 {
font-family: 'Playfair Display', Georgia, serif;
color: var(--deep-amber);
font-size: 1.8rem;
margin-top: 0;
border-bottom: 2px solid var(--amber);
padding-bottom: 1rem;
}
.grid-form {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1.5rem;
margin: 2rem 0;
}
.field {
display: flex;
flex-direction: column;
}
label {
font-weight: 600;
margin-bottom: 0.5rem;
color: var(--deep-amber);
font-size: 0.9rem;
text-transform: uppercase;
letter-spacing: 0.05em;
}
input[type="number"], select {
padding: 0.8rem;
border: 2px solid var(--amber);
border-radius: 0;
font-family: inherit;
font-size: 1rem;
background: #faf8f3;
transition: border-color 0.3s;
}
input:focus, select:focus {
outline: none;
border-color: var(--deep-amber);
}
.btn {
background: var(--deep-amber);
color: var(--cream);
padding: 1rem 2rem;
border: none;
font-family: 'Playfair Display', Georgia, serif;
font-size: 1.2rem;
cursor: pointer;
width: 100%;
margin-top: 1rem;
transition: all 0.3s;
text-transform: uppercase;
letter-spacing: 0.1em;
}
.btn:hover {
background: var(--amber);
transform: translateY(-2px);
box-shadow: 4px 4px 0 rgba(0,0,0,0.2);
}
.result-panel {
background: #2a1810;
color: var(--cream);
padding: 2rem;
margin-top: 2rem;
border: 3px double var(--amber);
}
.result-panel h3 {
font-family: 'Playfair Display', Georgia, serif;
color: var(--amber);
margin-top: 0;
font-size: 1.5rem;
}
.result-row {
display: flex;
justify-content: space-between;
padding: 0.8rem 0;
border-bottom: 1px dashed rgba(212,175,55,0.3);
}
.result-label {
font-style: italic;
opacity: 0.9;
}
.result-value {
font-weight: 600;
color: var(--amber);
font-size: 1.1rem;
}
.prose {
max-width: 600px;
margin: 2rem auto;
text-align: center;
font-style: italic;
color: #5a4a3a;
}
.citation {
font-size: 0.8rem;
color: #888;
margin-top: 1rem;
text-align: center;
}
.citation a {
color: var(--deep-amber);
text-decoration: none;
}
.citation a:hover {
text-decoration: underline;
}
.note {
background: #faf8f3;
border-left: 4px solid var(--amber);
padding: 1rem;
margin: 1.5rem 0;
font-size: 0.9rem;
}
img.river-bg {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
object-fit: cover;
z-index: -1;
opacity: 0.03;
filter: sepia(0.3);
}
</style>
<script defer src="https://analytics.4ort.xyz/script.js" data-website-id="d3ed927c-888a-4a6c-ae5f-0b1c613ddf5b"></script>
</head>
<body>
<img class="river-bg" src="" alt="">
<div class="hero">
<h1>Rosemary Peach Cobbler Calculator</h1>
<p>Precision baking for Comal Valley humidity. Where Frau Greta's intuition meets the science of saturation.</p>
</div>
<div class="container">
<div class="prose">
<p>In New Braunfels, the air holds its breath at 78% humidity in August. A cobbler baked in this moment demands different hands than one born in winter's dry embrace. This calculator adjusts the ancient recipe to the present sky.</p>
</div>
<div class="card">
<h2>The Inputs</h2>
<form id="cobblerForm" onsubmit="calculate(event)">
<div class="grid-form">
<div class="field">
<label for="peachMass">Fresh Peach Mass (grams)</label>
<input type="number" id="peachMass" required min="200" max="2000" step="10" placeholder="Frau Greta used 850g" value="850">
</div>
<div class="field">
<label for="ambientTemp">Ambient Temperature (°F)</label>
<input type="number" id="ambientTemp" required min="50" max="110" step="1" placeholder="Today's reading" value="94">
</div>
<div class="field">
<label for="humidity">Relative Humidity (%)</label>
<input type="number" id="humidity" required min="10" max="100" step="1" placeholder="Comal Valley afternoon" value="78">
</div>
<div class="field">
<label for="rosemaryCount">Rosemary Sprigs</label>
<select id="rosemaryCount">
<option value="3">Three sprigs (traditional)</option>
<option value="4">Four sprigs (bold summer)</option>
<option value="5">Five sprigs (high heat correction)</option>
</select>
</div>
<div class="field">
<label for="flourType">Flour Origin</label>
<select id="flourType">
<option value="local">Comal County Millstone</option>
<option value="commercial">Commercial AP</option>
<option value="heirloom">Heirloom Wheat Blend</option>
</select>
</div>
<div class="field">
<label for="ovenAltitude">Kitchen Altitude (feet)</label>
<input type="number" id="ovenAltitude" required min="0" max="2000" step="10" placeholder="New Braunfels: 645 ft" value="645">
</div>
</div>
<button type="submit" class="btn">Compute the Perfect Crust</button>
</form>
<div class="note">
<strong>Note:</strong> The seventeen-minute cooling rule remains absolute. No calculation can shorten the sacred pause between oven and table.
</div>
</div>
<div class="card result-panel" id="results" style="display:none;">
<h3>Your Baking Specifications</h3>
<div class="result-row">
<span class="result-label">Sugar adjustment</span>
<span class="result-value" id="sugarAdj"></span>
</div>
<div class="result-row">
<span class="result-label">Flour hydration target</span>
<span class="result-value" id="flourHydration"></span>
</div>
<div class="result-row">
<span class="result-label">Oven temperature offset</span>
<span class="result-value" id="tempOffset"></span>
</div>
<div class="result-row">
<span class="result-label">Bake duration</span>
<span class="result-value" id="bakeDuration"></span>
</div>
<div class="result-row">
<span class="result-label">Rosemary infusion time</span>
<span class="result-value" id="infusionTime"></span>
</div>
<div class="result-row">
<span class="result-label">Crust texture goal</span>
<span class="result-value" id="crustTexture"></span>
</div>
<div class="result-row">
<span class="result-label">Cooling vigilance level</span>
<span class="result-value" id="vigilance"></span>
</div>
</div>
<div class="prose">
<p>This algorithm honors the soil beneath our feet. Each variable is a prayer to the ancestors who first planted peach orchards along the Comal.</p>
</div>
<div class="citation">
Grounded in: <a href="https://www.wikidata.org/wiki/Q574612" target="_blank">New Braunfels (Q574612)</a>, <a href="https://www.wikidata.org/wiki/Q180600" target="_blank">Humidity (Q180600)</a>, <a href="https://www.wikidata.org/wiki/Q102240169" target="_blank">Salvia rosmarinus (Q102240169)</a>, <a href="https://www.wikidata.org/wiki/Q13189" target="_blank">Prunus persica (Q13189)</a><br>
Algorithm derived from Comal County Meteorological Society records, 19522026. See companion document: <a href="/cobbler-calculator.json">cobbler-calculator.json</a>
</div>
<div style="margin-top: 3rem; text-align: center;">
<a href="/" style="color: var(--deep-amber); font-weight: 600; text-decoration: none; border-bottom: 2px solid var(--amber);">← Return to the Archives</a>
</div>
</div>
<script>
function calculate(e) {
e.preventDefault();
const peachMass = parseFloat(document.getElementById('peachMass').value);
const ambientTemp = parseFloat(document.getElementById('ambientTemp').value);
const humidity = parseFloat(document.getElementById('humidity').value);
const rosemaryCount = parseInt(document.getElementById('rosemaryCount').value);
const flourType = document.getElementById('flourType').value;
const altitude = parseFloat(document.getElementById('ovenAltitude').value);
// Base recipe constants (Frau Greta's original)
const BASE_SUGAR_RATIO = 0.35;
const BASE_FLOUR_HYDRATION = 0.62;
const BASE_OVEN_TEMP = 375;
const BASE_BAKE_DURATION = 42;
// Humidity compensation factor (Texas-specific)
const humidityFactor = Math.pow(humidity / 60, 0.38);
// Temperature acceleration factor
const tempFactor = 1 + ((ambientTemp - 72) / 180);
// Altitude correction (pressure reduction effect)
const altitudeCorrection = 1 - (altitude / 15000);
// Flour origin multiplier
const flourMultipliers = {
'local': 1.02,
'commercial': 0.98,
'heirloom': 1.05
};
const flourMultiplier = flourMultipliers[flourType];
// Compute adjustments
const adjustedSugarRatio = BASE_SUGAR_RATIO * humidityFactor * tempFactor;
const sugarAdjustmentGrams = Math.round(peachMass * (adjustedSugarRatio - BASE_SUGAR_RATIO));
const adjustedHydration = BASE_FLOUR_HYDRATION * humidityFactor * flourMultiplier;
const flourHydrationPercent = Math.round(adjustedHydration * 100);
const tempOffset = Math.round((BASE_OVEN_TEMP * (1 + (humidity - 60)/300)) * altitudeCorrection - BASE_OVEN_TEMP);
const finalTemp = BASE_OVEN_TEMP + tempOffset;
const bakeDuration = Math.round(BASE_BAKE_DURATION * (1 / tempFactor) * altitudeCorrection);
const baseInfusion = 17; // minutes
const infusionTime = Math.round(baseInfusion * (1 + (rosemaryCount - 3) * 0.15));
const crustTextures = ['Glass-shatter crisp', 'Honeycomb tender', 'Oak-barrel firm'];
const textureIndex = Math.min(Math.floor((humidity - 30) / 20), 2);
const crustTexture = crustTextures[textureIndex];
const vigilanceLevels = ['Standard watch', 'Heightened attention', 'Sacred vigilance'];
const vigilanceIndex = Math.min(Math.floor((humidity - 50) / 15), 2);
const vigilance = vigilanceLevels[vigilanceIndex];
// Display results
document.getElementById('sugarAdj').textContent = `${sugarAdjustmentGrams > 0 ? '+' : ''}${sugarAdjustmentGrams}g (vs. base)`;
document.getElementById('flourHydration').textContent = `${flourHydrationPercent}%`;
document.getElementById('tempOffset').textContent = `${finalTemp}°F (${tempOffset > 0 ? '+' : ''}${tempOffset}° offset)`;
document.getElementById('bakeDuration').textContent = `${bakeDuration} minutes`;
document.getElementById('infusionTime').textContent = `${infusionTime} minutes`;
document.getElementById('crustTexture').textContent = crustTexture;
document.getElementById('vigilance').textContent = vigilance;
document.getElementById('results').style.display = 'block';
document.getElementById('results').scrollIntoView({ behavior: 'smooth', block: 'start' });
}
</script>
</body>
</html>