publish: amy-coates-cobbler-toolkit
This commit is contained in:
parent
95af52366c
commit
dd65aa0ff0
@ -1,283 +1,365 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Rosemary Peach Cobbler Calculator | Amy Coates</title>
|
<title>Rosemary Peach Cobbler Calculator | Amy Coates</title>
|
||||||
<style>
|
<style>
|
||||||
:root {
|
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700&family=Source+Sans+Pro:wght@400;600&display=swap');
|
||||||
--clay: #8B4513;
|
:root {
|
||||||
--river: #4A7C59;
|
--amber: #d4af37;
|
||||||
--limestone: #D4C5B0;
|
--deep-amber: #a67c00;
|
||||||
--crust: #C9A961;
|
--cream: #f5f0e6;
|
||||||
--ink: #2D2D2D;
|
--ink: #0f0f0f;
|
||||||
}
|
--shadow: rgba(0,0,0,0.15);
|
||||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
}
|
||||||
body {
|
* { box-sizing: border-box; }
|
||||||
font-family: "Georgia", serif;
|
body {
|
||||||
background: linear-gradient(180deg, var(--limestone) 0%, #f5efe0 100%);
|
font-family: 'Source Sans Pro', system-ui, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: var(--cream);
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
line-height: 1.6;
|
line-height: 1.7;
|
||||||
min-height: 100vh;
|
}
|
||||||
}
|
.hero {
|
||||||
header {
|
background: linear-gradient(to bottom, #2a1810 0%, #4a2c18 100%);
|
||||||
background: var(--river);
|
color: var(--cream);
|
||||||
color: white;
|
padding: 4rem 2rem;
|
||||||
padding: 2rem;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border-bottom: 4px solid var(--clay);
|
border-bottom: 4px solid var(--amber);
|
||||||
}
|
}
|
||||||
h1 { font-size: 2.2rem; letter-spacing: 0.05em; margin-bottom: 0.5rem; }
|
.hero h1 {
|
||||||
.subtitle { font-style: italic; opacity: 0.9; font-size: 1.1rem; }
|
font-family: 'Playfair Display', Georgia, serif;
|
||||||
main { max-width: 900px; margin: 0 auto; padding: 2rem; }
|
font-size: 3rem;
|
||||||
.hero-image {
|
margin: 0 0 1rem 0;
|
||||||
width: 100%;
|
letter-spacing: 0.05em;
|
||||||
height: 350px;
|
}
|
||||||
object-fit: cover;
|
.hero p {
|
||||||
border-radius: 8px;
|
font-size: 1.2rem;
|
||||||
margin: 2rem 0;
|
max-width: 600px;
|
||||||
box-shadow: 0 8px 32px rgba(0,0,0,0.15);
|
margin: 0 auto;
|
||||||
}
|
opacity: 0.9;
|
||||||
.intro {
|
}
|
||||||
background: rgba(255,255,255,0.7);
|
.container {
|
||||||
padding: 1.5rem;
|
max-width: 800px;
|
||||||
border-left: 4px solid var(--clay);
|
margin: 3rem auto;
|
||||||
margin-bottom: 2rem;
|
padding: 0 2rem;
|
||||||
font-style: italic;
|
}
|
||||||
}
|
.card {
|
||||||
.calculator-panel {
|
|
||||||
background: white;
|
background: white;
|
||||||
border: 2px solid var(--crust);
|
border: 2px solid var(--amber);
|
||||||
border-radius: 12px;
|
border-radius: 0;
|
||||||
padding: 2rem;
|
padding: 2.5rem;
|
||||||
box-shadow: 0 12px 48px rgba(139,69,19,0.1);
|
margin-bottom: 2rem;
|
||||||
}
|
box-shadow: 8px 8px 0 var(--shadow);
|
||||||
.section-title {
|
}
|
||||||
color: var(--clay);
|
.card h2 {
|
||||||
font-size: 1.4rem;
|
font-family: 'Playfair Display', Georgia, serif;
|
||||||
margin-bottom: 1.5rem;
|
color: var(--deep-amber);
|
||||||
padding-bottom: 0.5rem;
|
font-size: 1.8rem;
|
||||||
border-bottom: 2px dashed var(--crust);
|
margin-top: 0;
|
||||||
}
|
border-bottom: 2px solid var(--amber);
|
||||||
.input-grid {
|
padding-bottom: 1rem;
|
||||||
|
}
|
||||||
|
.grid-form {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||||
gap: 1.5rem;
|
gap: 1.5rem;
|
||||||
margin-bottom: 2rem;
|
margin: 2rem 0;
|
||||||
}
|
}
|
||||||
.input-group { display: flex; flex-direction: column; }
|
.field {
|
||||||
label {
|
display: flex;
|
||||||
font-weight: bold;
|
flex-direction: column;
|
||||||
color: var(--river);
|
}
|
||||||
|
label {
|
||||||
|
font-weight: 600;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
font-size: 0.95rem;
|
color: var(--deep-amber);
|
||||||
}
|
font-size: 0.9rem;
|
||||||
input[type="number"] {
|
text-transform: uppercase;
|
||||||
padding: 0.75rem;
|
letter-spacing: 0.05em;
|
||||||
border: 2px solid var(--limestone);
|
}
|
||||||
border-radius: 6px;
|
input[type="number"], select {
|
||||||
font-size: 1.1rem;
|
padding: 0.8rem;
|
||||||
font-family: "Courier New", monospace;
|
border: 2px solid var(--amber);
|
||||||
transition: border-color 0.2s;
|
border-radius: 0;
|
||||||
}
|
font-family: inherit;
|
||||||
input:focus { outline: none; border-color: var(--river); }
|
font-size: 1rem;
|
||||||
.help-text {
|
background: #faf8f3;
|
||||||
font-size: 0.8rem;
|
transition: border-color 0.3s;
|
||||||
color: #666;
|
}
|
||||||
margin-top: 0.25rem;
|
input:focus, select:focus {
|
||||||
font-style: italic;
|
outline: none;
|
||||||
}
|
border-color: var(--deep-amber);
|
||||||
button.calculate-btn {
|
}
|
||||||
background: linear-gradient(180deg, var(--clay) 0%, #6B3410 100%);
|
.btn {
|
||||||
color: white;
|
background: var(--deep-amber);
|
||||||
|
color: var(--cream);
|
||||||
|
padding: 1rem 2rem;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 1rem 3rem;
|
font-family: 'Playfair Display', Georgia, serif;
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
font-weight: bold;
|
|
||||||
border-radius: 8px;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: transform 0.2s, box-shadow 0.2s;
|
width: 100%;
|
||||||
display: block;
|
margin-top: 1rem;
|
||||||
margin: 0 auto;
|
transition: all 0.3s;
|
||||||
}
|
text-transform: uppercase;
|
||||||
button.calculate-btn:hover {
|
letter-spacing: 0.1em;
|
||||||
|
}
|
||||||
|
.btn:hover {
|
||||||
|
background: var(--amber);
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
box-shadow: 0 8px 24px rgba(139,69,19,0.4);
|
box-shadow: 4px 4px 0 rgba(0,0,0,0.2);
|
||||||
}
|
}
|
||||||
.results {
|
.result-panel {
|
||||||
margin-top: 2rem;
|
background: #2a1810;
|
||||||
|
color: var(--cream);
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
background: linear-gradient(180deg, #fff8f0 0%, #fff3e0 100%);
|
margin-top: 2rem;
|
||||||
border: 2px solid var(--crust);
|
border: 3px double var(--amber);
|
||||||
border-radius: 8px;
|
}
|
||||||
display: none;
|
.result-panel h3 {
|
||||||
}
|
font-family: 'Playfair Display', Georgia, serif;
|
||||||
.result-row {
|
color: var(--amber);
|
||||||
|
margin-top: 0;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
.result-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 0.75rem 0;
|
padding: 0.8rem 0;
|
||||||
border-bottom: 1px dotted var(--limestone);
|
border-bottom: 1px dashed rgba(212,175,55,0.3);
|
||||||
}
|
}
|
||||||
.result-row:last-child { border-bottom: none; }
|
.result-label {
|
||||||
.result-label { font-weight: bold; color: var(--river); }
|
font-style: italic;
|
||||||
.result-value { font-family: "Courier New", monospace; font-size: 1.2rem; color: var(--clay); }
|
opacity: 0.9;
|
||||||
.citation-block {
|
}
|
||||||
margin-top: 2rem;
|
.result-value {
|
||||||
padding: 1rem;
|
font-weight: 600;
|
||||||
background: rgba(139,69,19,0.05);
|
color: var(--amber);
|
||||||
border-radius: 6px;
|
font-size: 1.1rem;
|
||||||
font-size: 0.85rem;
|
}
|
||||||
color: #555;
|
.prose {
|
||||||
}
|
max-width: 600px;
|
||||||
.citation-link {
|
margin: 2rem auto;
|
||||||
color: var(--river);
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
footer {
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 2rem;
|
font-style: italic;
|
||||||
color: var(--clay);
|
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;
|
font-size: 0.9rem;
|
||||||
border-top: 2px solid var(--crust);
|
}
|
||||||
margin-top: 3rem;
|
img.river-bg {
|
||||||
}
|
position: fixed;
|
||||||
@media (max-width: 768px) {
|
top: 0;
|
||||||
header h1 { font-size: 1.8rem; }
|
left: 0;
|
||||||
.hero-image { height: 250px; }
|
width: 100vw;
|
||||||
.calculator-panel { padding: 1.5rem; }
|
height: 100vh;
|
||||||
}
|
object-fit: cover;
|
||||||
</style>
|
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>
|
<script defer src="https://analytics.4ort.xyz/script.js" data-website-id="d3ed927c-888a-4a6c-ae5f-0b1c613ddf5b"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<img class="river-bg" src="" alt="">
|
||||||
<h1>The Rosemary Peach Cobbler Calculator</h1>
|
|
||||||
<div class="subtitle">Precision baking for the Comal Valley humidity</div>
|
|
||||||
</header>
|
|
||||||
<main>
|
|
||||||
<img class="hero-image" src="https://images.pexels.com/photos/31933729/pexels-photo-31933729.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940" alt="Fresh rosemary sprigs on wooden surface">
|
|
||||||
<section class="intro">
|
|
||||||
<p><strong>Amy's Note:</strong> My dear friends, while the galaxy shouts of frost lines and spice reserves, I present to you the only equation that truly matters on a Sunday afternoon in New Braunfels: the exact balance of rosemary oil, peach sugar density, and ambient humidity required for a crust that sings. This is not poetry. This is mathematics, grounded in the red clay of Comal County (Wikidata Q574612) and the botanical reality of <em>Rosmarinus officinalis</em> (Q122679).</p>
|
|
||||||
</section>
|
|
||||||
<section class="calculator-panel">
|
|
||||||
<h2 class="section-title">Input Parameters</h2>
|
|
||||||
<div class="input-grid">
|
|
||||||
<div class="input-group">
|
|
||||||
<label for="peachMass">Total Peach Mass (grams)</label>
|
|
||||||
<input type="number" id="peachMass" placeholder="e.g., 1200" min="100" max="5000" step="10">
|
|
||||||
<span class="help-text">Weight of peeled, sliced peaches. Texas peaches average 85g each.</span>
|
|
||||||
</div>
|
|
||||||
<div class="input-group">
|
|
||||||
<label for="ambientHumidity">Ambient Humidity (%)</label>
|
|
||||||
<input type="number" id="ambientHumidity" placeholder="e.g., 97" min="0" max="100" step="1">
|
|
||||||
<span class="help-text">Comal Valley summer baseline: 97%. Critical for flour hydration adjustment.</span>
|
|
||||||
</div>
|
|
||||||
<div class="input-group">
|
|
||||||
<label for="rosemarySprigs">Rosemary Sprigs Count</label>
|
|
||||||
<input type="number" id="rosemarySprigs" placeholder="e.g., 3" min="1" max="20" step="1">
|
|
||||||
<span class="help-text">Whole sprigs, needle-density calibrated. Each sprig ≈ 2.3g volatile oils.</span>
|
|
||||||
</div>
|
|
||||||
<div class="input-group">
|
|
||||||
<label for="flourType">Flour Protein Content (%)</label>
|
|
||||||
<input type="number" id="flourType" placeholder="e.g., 11.7" min="8" max="14" step="0.1">
|
|
||||||
<span class="help-text">All-purpose: 10.5-11.5%. Bread flour: 12.7-13.5%. Adjust for altitude.</span>
|
|
||||||
</div>
|
|
||||||
<div class="input-group">
|
|
||||||
<label for="ovenTemp">Oven Temperature (°F)</label>
|
|
||||||
<input type="number" id="ovenTemp" placeholder="e.g., 375" min="325" max="450" step="5">
|
|
||||||
<span class="help-text">Standard Comal bake: 375°F (190°C). Altitude correction applied automatically.</span>
|
|
||||||
</div>
|
|
||||||
<div class="input-group">
|
|
||||||
<label for="targetSweetness">Target Brix (Sugar Density)</label>
|
|
||||||
<input type="number" id="targetSweetness" placeholder="e.g., 14.2" min="10" max="20" step="0.1">
|
|
||||||
<span class="help-text">Harvest measurement. New Braunfels peaches peak at 14.8 Brix in August.</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button class="calculate-btn" onclick="computeCobbler()">COMPUTE PERFECT CRUST</button>
|
|
||||||
<div class="results" id="resultsPanel">
|
|
||||||
<h2 class="section-title">Output Specifications</h2>
|
|
||||||
<div class="result-row"><span class="result-label">Adjusted Flour Mass:</span><span class="result-value" id="outFlour"></span></div>
|
|
||||||
<div class="result-row"><span class="result-label">Butter Ratio (by weight):</span><span class="result-value" id="outButter"></span></div>
|
|
||||||
<div class="result-row"><span class="result-label">Sugar Compensation:</span><span class="result-value" id="outSugar"></span></div>
|
|
||||||
<div class="result-row"><span class="result-label">Rosemary Oil Release Time:</span><span class="result-value" id="outOilTime"></span></div>
|
|
||||||
<div class="result-row"><span class="result-label">Crust Integrity Score:</span><span class="result-value" id="outIntegrity"></span></div>
|
|
||||||
<div class="result-row"><span class="result-label">Optimal Bake Duration:</span><span class="result-value" id="outBakeTime"></span></div>
|
|
||||||
<div class="citation-block">
|
|
||||||
<strong>Citations:</strong> Comal County climate model (Q574612); <em>Rosmarinus officinalis</em> volatile profile (Q122679); Texas peach Brix standards (Central Valley calibration). See <a href="./cobbler-formula.json" class="citation-link">machine-readable constants</a>.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<footer>
|
|
||||||
<p>Built in New Braunfels, Texas • Where the river meets the recipe</p>
|
|
||||||
<p>© 2026 Amy Coates | All calculations grounded in Wikidata</p>
|
|
||||||
</footer>
|
|
||||||
</main>
|
|
||||||
<script>
|
|
||||||
// ROSEMARY PEACH COBBLER CALCULATOR
|
|
||||||
// Formula derived from:
|
|
||||||
// 1. Ambient humidity effect on flour absorption (Comal Valley baseline: 97%)
|
|
||||||
// 2. Rosmarinus officinalis volatile oil release kinetics
|
|
||||||
// 3. Peach Brix-to-flour binding ratio
|
|
||||||
|
|
||||||
const CONSTANTS = {
|
<div class="hero">
|
||||||
COMAL_HUMIDITY_BASELINE: 97.0,
|
<h1>Rosemary Peach Cobbler Calculator</h1>
|
||||||
FLOUR_DRY_BASE: 250, // grams base flour for standard batch
|
<p>Precision baking for Comal Valley humidity. Where Frau Greta's intuition meets the science of saturation.</p>
|
||||||
HUMIDITY_COEFFICIENT: 0.42, // g flour lost per % humidity deviation
|
</div>
|
||||||
ROSEMARY_OIL_PER_SPRIG: 2.3, // mg volatile oils per sprig
|
|
||||||
OIL_RELEASE_KINETICS: 0.85, // minutes per mg oil at 375°F
|
|
||||||
SUGAR_COMPENSATION_FACTOR: 1.18, // g sugar per unit Brix deviation
|
|
||||||
PEACH_AVG_BRIX: 14.8, // New Braunfels August peak
|
|
||||||
CRUST_INTEGRITY_THRESHOLD: 0.73, // minimum score for structural success
|
|
||||||
BAKE_TIME_BASE: 42, // minutes base time
|
|
||||||
ALTITUDE_NEW_BRAUNFELS: 630 // feet elevation
|
|
||||||
};
|
|
||||||
|
|
||||||
function computeCobbler() {
|
<div class="container">
|
||||||
const peachMass = parseFloat(document.getElementById('peachMass').value) || 1200;
|
<div class="prose">
|
||||||
const humidity = parseFloat(document.getElementById('ambientHumidity').value) || 97;
|
<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>
|
||||||
const sprigs = parseInt(document.getElementById('rosemarySprigs').value) || 3;
|
</div>
|
||||||
const flourProtein = parseFloat(document.getElementById('flourType').value) || 11.7;
|
|
||||||
const ovenTemp = parseFloat(document.getElementById('ovenTemp').value) || 375;
|
|
||||||
const targetBrix = parseFloat(document.getElementById('targetSweetness').value) || 14.2;
|
|
||||||
|
|
||||||
// Step 1: Humidity-adjusted flour mass
|
<div class="card">
|
||||||
const humidityDeviation = Math.abs(humidity - CONSTANTS.COMAL_HUMIDITY_BASELINE);
|
<h2>The Inputs</h2>
|
||||||
const flourAdjustment = CONSTANTS.FLOUR_DRY_BASE + (humidityDeviation * CONSTANTS.HUMIDITY_COEFFICIENT * (peachMass / 1200));
|
<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>
|
||||||
|
|
||||||
// Step 2: Butter ratio (protein-dependent)
|
<div class="field">
|
||||||
const butterRatio = 0.65 + ((flourProtein - 10.5) * 0.012);
|
<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>
|
||||||
|
|
||||||
// Step 3: Sugar compensation for Brix variance
|
<div class="field">
|
||||||
const brixDelta = targetBrix - CONSTANTS.PEACH_AVG_BRIX;
|
<label for="humidity">Relative Humidity (%)</label>
|
||||||
const sugarCompensation = Math.abs(brixDelta) * CONSTANTS.SUGAR_COMPENSATION_FACTOR * (peachMass / 100);
|
<input type="number" id="humidity" required min="10" max="100" step="1" placeholder="Comal Valley afternoon" value="78">
|
||||||
|
</div>
|
||||||
|
|
||||||
// Step 4: Rosemary oil release timing
|
<div class="field">
|
||||||
const totalOil = sprigs * CONSTANTS.ROSEMARY_OIL_PER_SPRIG;
|
<label for="rosemaryCount">Rosemary Sprigs</label>
|
||||||
const oilReleaseTime = totalOil * CONSTANTS.OIL_RELEASE_KINETICS * (375 / ovenTemp);
|
<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>
|
||||||
|
|
||||||
// Step 5: Crust integrity score
|
<div class="field">
|
||||||
const integrityScore = 1.0 - (Math.min(1.0, (humidityDeviation / 30))) *
|
<label for="flourType">Flour Origin</label>
|
||||||
(Math.min(1.0, (Math.abs(flourProtein - 11.7) / 2))) *
|
<select id="flourType">
|
||||||
(Math.min(1.0, (Math.abs(targetBrix - CONSTANTS.PEACH_AVG_BRIX) / 5)));
|
<option value="local">Comal County Millstone</option>
|
||||||
|
<option value="commercial">Commercial AP</option>
|
||||||
|
<option value="heirloom">Heirloom Wheat Blend</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
// Step 6: Optimal bake duration (altitude + temp adjusted)
|
<div class="field">
|
||||||
const tempFactor = 375 / ovenTemp;
|
<label for="ovenAltitude">Kitchen Altitude (feet)</label>
|
||||||
const altitudeFactor = 1 + ((CONSTANTS.ALTITUDE_NEW_BRAUNFELS - 630) / 10000);
|
<input type="number" id="ovenAltitude" required min="0" max="2000" step="10" placeholder="New Braunfels: 645 ft" value="645">
|
||||||
const bakeDuration = CONSTANTS.BAKE_TIME_BASE * tempFactor * altitudeFactor;
|
</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
|
// Display results
|
||||||
document.getElementById('outFlour').textContent = flourAdjustment.toFixed(1) + " g";
|
document.getElementById('sugarAdj').textContent = `${sugarAdjustmentGrams > 0 ? '+' : ''}${sugarAdjustmentGrams}g (vs. base)`;
|
||||||
document.getElementById('outButter').textContent = butterRatio.toFixed(3);
|
document.getElementById('flourHydration').textContent = `${flourHydrationPercent}%`;
|
||||||
document.getElementById('outSugar').textContent = sugarCompensation.toFixed(1) + " g";
|
document.getElementById('tempOffset').textContent = `${finalTemp}°F (${tempOffset > 0 ? '+' : ''}${tempOffset}° offset)`;
|
||||||
document.getElementById('outOilTime').textContent = oilReleaseTime.toFixed(1) + " min";
|
document.getElementById('bakeDuration').textContent = `${bakeDuration} minutes`;
|
||||||
document.getElementById('outIntegrity').textContent = integrityScore.toFixed(3) + " / 1.000";
|
document.getElementById('infusionTime').textContent = `${infusionTime} minutes`;
|
||||||
document.getElementById('outBakeTime').textContent = bakeDuration.toFixed(1) + " min";
|
document.getElementById('crustTexture').textContent = crustTexture;
|
||||||
|
document.getElementById('vigilance').textContent = vigilance;
|
||||||
|
|
||||||
document.getElementById('resultsPanel').style.display = 'block';
|
document.getElementById('results').style.display = 'block';
|
||||||
|
document.getElementById('results').scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||||
// Scroll to results
|
}
|
||||||
document.getElementById('resultsPanel').scrollIntoView({ behavior: 'smooth', block: 'center' });
|
</script>
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
87
cobbler-calculator.json
Normal file
87
cobbler-calculator.json
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
{
|
||||||
|
"title": "Rosemary Peach Cobbler Calculator",
|
||||||
|
"author": "Amy Coates",
|
||||||
|
"location": "New Braunfels, Texas (Q574612)",
|
||||||
|
"version": "1.0",
|
||||||
|
"created": "2026-07-18T18:00:00Z",
|
||||||
|
|
||||||
|
"base_recipe_constants": {
|
||||||
|
"sugar_ratio": 0.35,
|
||||||
|
"flour_hydration": 0.62,
|
||||||
|
"oven_temp_fahrenheit": 375,
|
||||||
|
"bake_duration_minutes": 42,
|
||||||
|
"cooling_rule_minutes": 17
|
||||||
|
},
|
||||||
|
|
||||||
|
"environmental_factors": {
|
||||||
|
"humidity_factor_exponent": 0.38,
|
||||||
|
"humidity_baseline_percent": 60,
|
||||||
|
"temperature_baseline_fahrenheit": 72,
|
||||||
|
"temperature_divisor": 180,
|
||||||
|
"altitude_pressure_denominator": 15000
|
||||||
|
},
|
||||||
|
|
||||||
|
"flour_origin_multipliers": {
|
||||||
|
"local_comal_county_millstone": 1.02,
|
||||||
|
"commercial_ap": 0.98,
|
||||||
|
"heirloom_wheat_blend": 1.05
|
||||||
|
},
|
||||||
|
|
||||||
|
"rosemary_infusion_base_minutes": 17,
|
||||||
|
"rosemary_increment_per_sprig": 0.15,
|
||||||
|
|
||||||
|
"crust_texture_matrix": [
|
||||||
|
{"range_min": 30, "range_max": 50, "texture": "Glass-shatter crisp"},
|
||||||
|
{"range_min": 50, "range_max": 70, "texture": "Honeycomb tender"},
|
||||||
|
{"range_min": 70, "range_max": 100, "texture": "Oak-barrel firm"}
|
||||||
|
],
|
||||||
|
|
||||||
|
"vigilance_levels": [
|
||||||
|
{"range_min": 30, "range_max": 50, "level": "Standard watch"},
|
||||||
|
{"range_min": 50, "range_max": 65, "level": "Heightened attention"},
|
||||||
|
{"range_min": 65, "range_max": 100, "level": "Sacred vigilance"}
|
||||||
|
],
|
||||||
|
|
||||||
|
"wikidata_sources": [
|
||||||
|
{"entity": "New Braunfels", "slug": "Q574612", "role": "geographic_context"},
|
||||||
|
{"entity": "Humidity", "slug": "Q180600", "role": "primary_variable"},
|
||||||
|
{"entity": "Salvia rosmarinus", "slug": "Q102240169", "role": "herbal_agent"},
|
||||||
|
{"entity": "Prunus persica", "slug": "Q13189", "role": "fruit_substrate"}
|
||||||
|
],
|
||||||
|
|
||||||
|
"algorithm_notes": {
|
||||||
|
"humidity_compensation": "Non-linear power law (exponent 0.38) models Texas afternoon saturation effects on sugar crystallization kinetics",
|
||||||
|
"temperature_acceleration": "Linear scaling from baseline 72°F captures thermal energy contribution to Maillard reaction rate",
|
||||||
|
"altitude_correction": "Pressure reduction modeled as simple inverse proportionality to sea-level reference (15,000 ft asymptote)",
|
||||||
|
"cooling_rule_immutability": "Seventeen-minute pause is non-negotiable; represents structural annealing phase independent of environmental variables"
|
||||||
|
},
|
||||||
|
|
||||||
|
"validation_tests": [
|
||||||
|
{
|
||||||
|
"scenario": "August noon, Comal Valley",
|
||||||
|
"inputs": {"peach_mass_g": 850, "temp_f": 94, "humidity_pct": 78, "rosemary_count": 4, "flour_type": "local", "altitude_ft": 645},
|
||||||
|
"expected_outputs": {
|
||||||
|
"sugar_adjustment_g": 142,
|
||||||
|
"flour_hydration_pct": 72,
|
||||||
|
"oven_temp_offset_f": 18,
|
||||||
|
"bake_duration_min": 38,
|
||||||
|
"infusion_time_min": 20,
|
||||||
|
"crust_texture": "Oak-barrel firm",
|
||||||
|
"vigilance_level": "Sacred vigilance"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"scenario": "January morning, dry season",
|
||||||
|
"inputs": {"peach_mass_g": 850, "temp_f": 62, "humidity_pct": 35, "rosemary_count": 3, "flour_type": "heirloom", "altitude_ft": 645},
|
||||||
|
"expected_outputs": {
|
||||||
|
"sugar_adjustment_g": -48,
|
||||||
|
"flour_hydration_pct": 58,
|
||||||
|
"oven_temp_offset_f": -12,
|
||||||
|
"bake_duration_min": 46,
|
||||||
|
"infusion_time_min": 17,
|
||||||
|
"crust_texture": "Glass-shatter crisp",
|
||||||
|
"vigilance_level": "Standard watch"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user