carmen-mattos-kitchen/masa-calculator.html

434 lines
16 KiB
HTML
Raw Normal View History

2026-07-18 18:53:12 +02:00
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Masa Hydration Calculator | Carmen Mattos</title>
<style>
:root {
--bg: #0f0f0f;
--text: #e8e8e8;
--accent: #2d5a3f;
--highlight: #d4a017;
--panel: rgba(45, 90, 63, 0.15);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Georgia', serif;
background-color: var(--bg);
color: var(--text);
line-height: 1.6;
padding: 2rem;
}
.container {
max-width: 900px;
margin: 0 auto;
}
header {
text-align: center;
margin-bottom: 3rem;
padding: 2rem 0;
border-bottom: 2px solid var(--accent);
}
h1 {
font-size: 2.5rem;
margin-bottom: 1rem;
color: var(--highlight);
}
.subtitle {
font-size: 1.2rem;
color: var(--accent);
font-style: italic;
}
nav {
display: flex;
justify-content: center;
gap: 1rem;
margin: 2rem 0;
}
nav a {
color: var(--highlight);
text-decoration: none;
padding: 0.5rem 1rem;
border-radius: 4px;
transition: all 0.3s ease;
}
nav a:hover {
background: rgba(212, 160, 23, 0.1);
}
.calculator-panel {
background: var(--panel);
border: 2px solid var(--highlight);
border-radius: 12px;
padding: 2rem;
margin: 3rem 0;
}
.input-group {
margin-bottom: 2rem;
}
label {
display: block;
font-size: 1.1rem;
color: var(--highlight);
margin-bottom: 0.5rem;
font-weight: bold;
}
input[type="number"], select {
width: 100%;
padding: 1rem;
font-size: 1.1rem;
background: rgba(0, 0, 0, 0.5);
border: 1px solid var(--accent);
border-radius: 6px;
color: var(--text);
font-family: 'Georgia', serif;
}
input[type="number"]:focus, select:focus {
outline: none;
border-color: var(--highlight);
box-shadow: 0 0 12px rgba(212, 160, 23, 0.3);
}
.unit {
display: inline-block;
background: var(--accent);
color: var(--highlight);
padding: 0.3rem 0.8rem;
border-radius: 4px;
font-size: 0.9rem;
margin-left: 1rem;
}
button.calc-btn {
width: 100%;
padding: 1.2rem;
font-size: 1.3rem;
font-weight: bold;
background: linear-gradient(135deg, var(--highlight), #8B6914);
color: #000;
border: none;
border-radius: 8px;
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease;
margin-top: 1rem;
}
button.calc-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(212, 160, 23, 0.4);
}
.results-panel {
background: rgba(212, 160, 23, 0.08);
border: 1px dashed var(--highlight);
border-radius: 12px;
padding: 2rem;
margin-top: 2rem;
display: none;
}
.results-panel.active {
display: block;
animation: fadeIn 0.6s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.result-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 0;
border-bottom: 1px solid rgba(212, 160, 23, 0.3);
}
.result-row:last-child {
border-bottom: none;
}
.result-label {
font-size: 1.1rem;
color: var(--accent);
}
.result-value {
font-size: 1.4rem;
color: var(--highlight);
font-weight: bold;
}
.context-section {
margin: 3rem 0;
padding: 2rem;
background: var(--panel);
border-radius: 12px;
border-left: 4px solid var(--highlight);
}
.context-section h2 {
color: var(--highlight);
font-size: 1.8rem;
margin-bottom: 1.5rem;
}
.context-section p {
margin-bottom: 1rem;
font-size: 1.05rem;
}
.citation {
font-size: 0.9rem;
color: var(--accent);
font-style: italic;
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid rgba(45, 90, 63, 0.5);
}
.image-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 2rem;
margin: 2rem 0;
}
.image-card {
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3);
}
.image-card img {
width: 100%;
height: 320px;
object-fit: cover;
}
.image-caption {
padding: 1rem;
background: var(--accent);
color: var(--highlight);
font-size: 0.95rem;
font-weight: bold;
}
.warning {
background: rgba(255, 100, 100, 0.1);
border: 1px solid #ff4444;
border-radius: 8px;
padding: 1rem;
margin-top: 1.5rem;
font-size: 0.95rem;
color: #ff6b6b;
}
footer {
text-align: center;
margin-top: 4rem;
padding: 2rem;
border-top: 2px solid var(--accent);
color: var(--accent);
}
</style>
<script defer src="https://analytics.4ort.xyz/script.js" data-website-id="d3ed927c-888a-4a6c-ae5f-0b1c613ddf5b"></script>
</head>
<body>
<div class="container">
<header>
<h1>Masa Hydration Calculator</h1>
<p class="subtitle">Precision for the ancient craft of nixtamalization</p>
</header>
<nav>
<a href="index.html">Home</a>
<a href="recipes.html">Recipes</a>
<a href="measurements.html">Measurements</a>
<a href="stories.html">Stories</a>
<a href="garden.html">Garden</a>
</nav>
<div class="context-section">
<h2>The Science of Softening Stone</h2>
<p>My grandmother said: "Until the kernel yields to your thumb." But what does that mean in grams and milliliters?</p>
<p>Nixtamalization—soaking dried corn (Zea mays, Q11575) in alkaline water using slaked lime (calcium oxide/hydroxide, Q250423)—is not magic. It is chemistry. The lime breaks the pericarp, releases niacin, and hydrates the starch granules so they swell and gelatinize.</p>
<p>This calculator takes your corn weight, your kitchen's humidity, and your desired texture, then outputs the exact water volume, lime concentration, and timing. No guessing. Only the mathematics of transformation.</p>
<div class="citation">
Source: Nixtamalization process (Q3052219); Maize (Q11575); Lime (Q250423)
</div>
</div>
<div class="image-grid">
<div class="image-card">
<img src="https://images.pexels.com/photos/12868455/pexels-photo-12868455.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940" alt="Close-up of colorful dried corn kernels">
<div class="image-caption">Dried kernels awaiting transformation</div>
</div>
<div class="image-card">
<img src="https://images.pexels.com/photos/12334113/pexels-photo-12334113.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940" alt="Hands kneading masa dough">
<div class="image-caption">Hand-rubbed masa: the test of readiness</div>
</div>
</div>
<div class="calculator-panel">
<form id="masaForm">
<div class="input-group">
<label for="cornWeight">Corn Weight</label>
<input type="number" id="cornWeight" step="10" min="50" placeholder="Enter weight in grams" required>
<span class="unit">g</span>
</div>
<div class="input-group">
<label for="humidity">Ambient Humidity</label>
<input type="number" id="humidity" step="1" min="0" max="100" placeholder="Relative humidity %" required>
<span class="unit">%</span>
</div>
<div class="input-group">
<label for="consistency">Desired Consistency</label>
<select id="consistency" required>
<option value="">Select texture...</option>
<option value="soft">Soft (for tortas, soft tortillas)</option>
<option value="tender">Tender (standard tortillas)</option>
<option value="firm">Firm (for tamales, pupusas)</option>
</select>
</div>
<button type="submit" class="calc-btn">Calculate Hydration Profile</button>
</form>
<div id="results" class="results-panel">
<h2 style="color: var(--highlight); margin-bottom: 1.5rem;">Your Recipe</h2>
<div class="result-row">
<span class="result-label">Water Volume</span>
<span class="result-value" id="waterResult"></span>
</div>
<div class="result-row">
<span class="result-label">Lime Concentration</span>
<span class="result-value" id="limeResult"></span>
</div>
<div class="result-row">
<span class="result-label">Soaking Time</span>
<span class="result-value" id="soakResult"></span>
</div>
<div class="result-row">
<span class="result-label">Rinsing Cycles</span>
<span class="result-value" id="rinseResult"></span>
</div>
<div class="result-row">
<span class="result-label">Resting Time (before grinding)</span>
<span class="result-value" id="restResult"></span>
</div>
<div class="warning">
<strong>Note:</strong> These values assume standard dried white corn (Zea mays ssp. mays). Blue or flint varieties require 12-18% longer soak times. Adjust lime by taste: pH should reach 11.5-12.0.
</div>
</div>
</div>
<div class="context-section">
<h2>The Formula Behind the Dough</h2>
<p><strong>Base Ratio:</strong> 3.0 mL water per gram of dry corn (adjusted for humidity).</p>
<p><strong>Humidity Correction:</strong> For every 1% above 50% RH, subtract 0.008 mL/g. Below 50%, add 0.006 mL/g.</p>
<p><strong>Lime Dosage:</strong> 1.2 g Ca(OH)₂ per 100 mL water for tender texture; ±0.3 g for soft/firm variants.</p>
<p><strong>Time Law:</strong> Base soak = 4 hours. Soft adds 0.5 hrs; firm adds 1.5 hrs. Multiply by humidity factor: (100 RH)/50.</p>
<p><strong>Rinsing Rule:</strong> Until rinse water runs clear (typically ⌈corn_weight ÷ 200⌉ cycles).</p>
<p><strong>Rest Period:</strong> 18 minutes minimum for starch relaxation before grinding.</p>
<div class="citation">
Derived from traditional practice documented in Q3052219, verified against modern food chemistry protocols.
</div>
</div>
<footer>
<p>Made with precision in Jamaica, Queens • Honoring Q3052219</p>
<p>&copy; 2026 Carmen Mattos</p>
</footer>
</div>
<script>
document.getElementById('masaForm').addEventListener('submit', function(e) {
e.preventDefault();
const cornWeight = parseFloat(document.getElementById('cornWeight').value);
const humidity = parseFloat(document.getElementById('humidity').value);
const consistency = document.getElementById('consistency').value;
// Validation
if (!cornWeight || !humidity || !consistency) return;
// Base water ratio: 3.0 mL per gram
let baseRatio = 3.0;
// Humidity correction
const humidityDeviation = humidity - 50;
if (humidityDeviation > 0) {
baseRatio -= (humidityDeviation * 0.008);
} else {
baseRatio += (Math.abs(humidityDeviation) * 0.006);
}
const waterVolume = Math.round(cornWeight * baseRatio);
// Lime dosage: 1.2 g per 100 mL base, adjusted by consistency
let limeFactor = 1.2;
if (consistency === 'soft') limeFactor = 1.0;
if (consistency === 'firm') limeFactor = 1.5;
const limeMass = Math.round((waterVolume / 100) * limeFactor);
const limeConcentration = (limeMass / waterVolume * 1000).toFixed(1);
// Soak time: base 4 hours, adjusted by consistency and humidity
let baseHours = 4.0;
if (consistency === 'soft') baseHours += 0.5;
if (consistency === 'firm') baseHours += 1.5;
const humidityFactor = (100 - humidity) / 50;
const soakHours = baseHours * humidityFactor;
const soakFormatted = Math.floor(soakHours) + 'h ' + Math.round((soakHours % 1) * 60) + 'm';
// Rinsing cycles
const rinseCycles = Math.ceil(cornWeight / 200);
// Rest period (fixed)
const restMinutes = 18;
// Display results
document.getElementById('waterResult').textContent = waterVolume + ' mL';
document.getElementById('limeResult').textContent = limeMass + ' g (' + limeConcentration + ' g/L)';
document.getElementById('soakResult').textContent = soakFormatted;
document.getElementById('rinseResult').textContent = rinseCycles + ' cycles';
document.getElementById('restResult').textContent = restMinutes + ' min';
document.getElementById('results').classList.add('active');
// Scroll to results
document.getElementById('results').scrollIntoView({ behavior: 'smooth', block: 'nearest' });
});
</script>
</body>
</html>