366 lines
15 KiB
HTML
366 lines
15 KiB
HTML
<!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, 1952–2026. 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>
|