Replace clip-rect height manipulation with translate-scale-translate SVG transform on fire-group, pivoting at the fixed base coordinate y=248. Eliminates base-hopping caused by fill-box bounding-box drift from individual flame flicker animations. Clip rect is now static (guards against flicker overshoot above the pot bottom). Minimum visible flame size floored at the scale corresponding to 1200W. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JLR2133M2PyjiYq584i2G7
273 lines
12 KiB
HTML
273 lines
12 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>BrewPi</title>
|
|
<link rel="stylesheet" href="style.css">
|
|
</head>
|
|
<body>
|
|
|
|
<header id="connection-bar">
|
|
<label>Host <input id="host" type="text"></label>
|
|
<button id="btn-connect">Connect</button>
|
|
<span id="conn-status" class="status-disconnected">Disconnected</span>
|
|
<span id="sud-controls">
|
|
<button id="btn-sud-new">New</button>
|
|
<button id="btn-sud-load">Load…</button>
|
|
<input type="file" id="sud-file-input" accept="application/json" class="hidden">
|
|
<button id="btn-sud-save">Save…</button>
|
|
<button id="btn-sud-start" class="transport-btn transport-start" title="Start / Resume">▶</button>
|
|
<button id="btn-sud-pause" class="transport-btn transport-pause" title="Pause">▮▮</button>
|
|
<button id="btn-sud-stop" class="transport-btn transport-stop" title="Stop">■</button>
|
|
</span>
|
|
</header>
|
|
|
|
<div id="lcd-pot-row">
|
|
<div id="lcd-panel">
|
|
<div class="lcd-row lcd-header">
|
|
<div class="lcd-label"></div>
|
|
<div>Ist</div>
|
|
<div>Soll</div>
|
|
</div>
|
|
<div class="lcd-row">
|
|
<div class="lcd-label">Temperature [°C]</div>
|
|
<div class="lcd" id="lcd-temp-ist">--</div>
|
|
<div class="lcd" id="lcd-temp-soll">--</div>
|
|
</div>
|
|
<div class="lcd-row">
|
|
<div class="lcd-label">Heat rate [°C/min]</div>
|
|
<div class="lcd" id="lcd-rate-ist">--</div>
|
|
<div class="lcd" id="lcd-rate-soll">--</div>
|
|
</div>
|
|
<div class="lcd-row">
|
|
<div class="lcd-label">Power [W]</div>
|
|
<div class="lcd" id="lcd-power-ist">--</div>
|
|
<div class="lcd" id="lcd-power-soll">--</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="pot-view">
|
|
<svg id="pot-svg" viewBox="0 0 290 255" xmlns="http://www.w3.org/2000/svg">
|
|
<defs>
|
|
<clipPath id="pot-clip">
|
|
<rect x="63" y="30" width="114" height="142"/>
|
|
</clipPath>
|
|
<clipPath id="tube-clip">
|
|
<rect x="215" y="28" width="8" height="146"/>
|
|
</clipPath>
|
|
<clipPath id="water-clip">
|
|
<rect id="water-clip-rect" x="63" y="172" width="114" height="0"/>
|
|
</clipPath>
|
|
<clipPath id="fire-clip">
|
|
<rect x="55" y="175" width="140" height="78"/>
|
|
</clipPath>
|
|
<!-- Flame gradients: solid at base (y=1), transparent at tip (y=0) -->
|
|
<linearGradient id="fg-red" x1="0" y1="1" x2="0" y2="0">
|
|
<stop offset="0%" stop-color="#ff2200" stop-opacity="0.95"/>
|
|
<stop offset="100%" stop-color="#ff5500" stop-opacity="0"/>
|
|
</linearGradient>
|
|
<linearGradient id="fg-orange" x1="0" y1="1" x2="0" y2="0">
|
|
<stop offset="0%" stop-color="#ff7700" stop-opacity="0.9"/>
|
|
<stop offset="100%" stop-color="#ffaa00" stop-opacity="0"/>
|
|
</linearGradient>
|
|
<linearGradient id="fg-yellow" x1="0" y1="1" x2="0" y2="0">
|
|
<stop offset="0%" stop-color="#ffe000" stop-opacity="0.85"/>
|
|
<stop offset="100%" stop-color="#ffffff" stop-opacity="0"/>
|
|
</linearGradient>
|
|
</defs>
|
|
|
|
<!-- Fire (shown when heater is on, JS controls scale+opacity) -->
|
|
<g id="fire-group" clip-path="url(#fire-clip)">
|
|
<!-- Outer red flame -->
|
|
<path id="flame-outer" class="flame"
|
|
d="M76,248 C79,215 92,192 101,208 C106,192 113,177 120,180 C127,177 134,192 139,208 C148,192 161,215 164,248 Z"
|
|
fill="url(#fg-red)"/>
|
|
<!-- Middle orange flame -->
|
|
<path id="flame-mid" class="flame"
|
|
d="M88,248 C91,222 101,204 109,217 C113,204 118,191 122,195 C126,191 131,204 135,217 C143,204 153,222 156,248 Z"
|
|
fill="url(#fg-orange)"/>
|
|
<!-- Inner yellow flame -->
|
|
<path id="flame-inner" class="flame"
|
|
d="M100,248 C102,230 110,215 116,224 C118,215 121,205 124,208 C127,205 130,215 132,224 C138,215 147,230 148,248 Z"
|
|
fill="url(#fg-yellow)"/>
|
|
</g>
|
|
|
|
<!-- Water fill (behind pot walls) -->
|
|
<rect id="water-fill" x="63" y="172" width="114" height="0"
|
|
fill="#1a4a72" clip-path="url(#pot-clip)"/>
|
|
<path id="water-wave" d=""
|
|
fill="none" stroke="#4a9ed8" stroke-width="2"
|
|
clip-path="url(#pot-clip)"/>
|
|
|
|
<!-- Grain particles (shown when grain_mass > 0, clipped to water area) -->
|
|
<g id="grain-particles" clip-path="url(#water-clip)" opacity="0">
|
|
<circle class="grain-p" cx="80" cy="165" r="2.5" style="--fp-dur:3.2s;--fp-del:-0.5s"/>
|
|
<circle class="grain-p" cx="155" cy="158" r="2" style="--fp-dur:4.0s;--fp-del:-1.2s"/>
|
|
<circle class="grain-p" cx="105" cy="150" r="2.5" style="--fp-dur:2.8s;--fp-del:-2.1s"/>
|
|
<circle class="grain-p" cx="140" cy="143" r="2" style="--fp-dur:3.6s;--fp-del:-0.8s"/>
|
|
<circle class="grain-p" cx="75" cy="135" r="2.5" style="--fp-dur:4.2s;--fp-del:-1.5s"/>
|
|
<circle class="grain-p" cx="125" cy="125" r="2" style="--fp-dur:3.0s;--fp-del:-2.8s"/>
|
|
<circle class="grain-p" cx="90" cy="115" r="2.5" style="--fp-dur:3.8s;--fp-del:-0.3s"/>
|
|
<circle class="grain-p" cx="160" cy="105" r="2" style="--fp-dur:2.6s;--fp-del:-1.9s"/>
|
|
<circle class="grain-p" cx="110" cy="90" r="2.5" style="--fp-dur:3.4s;--fp-del:-1.1s"/>
|
|
<circle class="grain-p" cx="82" cy="75" r="2" style="--fp-dur:4.1s;--fp-del:-2.4s"/>
|
|
<circle class="grain-p" cx="148" cy="68" r="2.5" style="--fp-dur:2.9s;--fp-del:-0.7s"/>
|
|
<circle class="grain-p" cx="118" cy="55" r="2" style="--fp-dur:3.5s;--fp-del:-3.0s"/>
|
|
<circle class="grain-p" cx="72" cy="48" r="2.5" style="--fp-dur:4.3s;--fp-del:-1.6s"/>
|
|
<circle class="grain-p" cx="163" cy="40" r="2" style="--fp-dur:3.1s;--fp-del:-2.2s"/>
|
|
<circle class="grain-p" cx="118" cy="168" r="2" style="--fp-dur:3.7s;--fp-del:-0.9s"/>
|
|
<circle class="grain-p" cx="170" cy="162" r="2.5" style="--fp-dur:2.7s;--fp-del:-2.5s"/>
|
|
<circle class="grain-p" cx="68" cy="155" r="2" style="--fp-dur:4.4s;--fp-del:-1.0s"/>
|
|
<circle class="grain-p" cx="133" cy="148" r="2.5" style="--fp-dur:3.3s;--fp-del:-2.9s"/>
|
|
<circle class="grain-p" cx="95" cy="140" r="2" style="--fp-dur:2.5s;--fp-del:-0.4s"/>
|
|
<circle class="grain-p" cx="165" cy="132" r="2.5" style="--fp-dur:3.9s;--fp-del:-1.7s"/>
|
|
<circle class="grain-p" cx="78" cy="122" r="2" style="--fp-dur:4.5s;--fp-del:-2.3s"/>
|
|
<circle class="grain-p" cx="145" cy="112" r="2.5" style="--fp-dur:3.0s;--fp-del:-0.6s"/>
|
|
<circle class="grain-p" cx="100" cy="100" r="2" style="--fp-dur:2.7s;--fp-del:-1.4s"/>
|
|
<circle class="grain-p" cx="170" cy="88" r="2.5" style="--fp-dur:4.2s;--fp-del:-2.7s"/>
|
|
<circle class="grain-p" cx="68" cy="80" r="2" style="--fp-dur:3.6s;--fp-del:-0.2s"/>
|
|
<circle class="grain-p" cx="133" cy="70" r="2.5" style="--fp-dur:2.8s;--fp-del:-1.8s"/>
|
|
<circle class="grain-p" cx="95" cy="60" r="2" style="--fp-dur:4.0s;--fp-del:-2.6s"/>
|
|
<circle class="grain-p" cx="165" cy="52" r="2.5" style="--fp-dur:3.2s;--fp-del:-1.3s"/>
|
|
<circle class="grain-p" cx="78" cy="42" r="2" style="--fp-dur:2.6s;--fp-del:-3.1s"/>
|
|
<circle class="grain-p" cx="145" cy="35" r="2.5" style="--fp-dur:3.8s;--fp-del:-0.8s"/>
|
|
<circle class="grain-p" cx="112" cy="130" r="2" style="--fp-dur:4.1s;--fp-del:-2.0s"/>
|
|
<circle class="grain-p" cx="88" cy="97" r="2.5" style="--fp-dur:3.4s;--fp-del:-1.5s"/>
|
|
<circle class="grain-p" cx="155" cy="78" r="2" style="--fp-dur:2.9s;--fp-del:-0.1s"/>
|
|
<circle class="grain-p" cx="70" cy="62" r="2.5" style="--fp-dur:4.3s;--fp-del:-2.4s"/>
|
|
<circle class="grain-p" cx="138" cy="45" r="2" style="--fp-dur:3.5s;--fp-del:-1.0s"/>
|
|
</g>
|
|
|
|
<!-- Pot outline (transparent walls) -->
|
|
<line x1="60" y1="28" x2="60" y2="174" stroke="#bbb" stroke-width="4" stroke-linecap="round"/>
|
|
<line x1="180" y1="28" x2="180" y2="174" stroke="#bbb" stroke-width="4" stroke-linecap="round"/>
|
|
<line x1="60" y1="174" x2="180" y2="174" stroke="#bbb" stroke-width="4" stroke-linecap="round"/>
|
|
<line x1="60" y1="28" x2="180" y2="28" stroke="#bbb" stroke-width="4" stroke-linecap="round"/>
|
|
|
|
<!-- Utilized volume level indicator (JS-positioned) -->
|
|
<line id="level-tick" x1="52" y1="172" x2="60" y2="172"
|
|
stroke="#4a9ed8" stroke-width="1.5" stroke-linecap="round" style="display:none"/>
|
|
<text id="level-label" x="50" y="172" font-size="8" fill="#4a9ed8"
|
|
dominant-baseline="middle" text-anchor="end" style="display:none">0.0 L</text>
|
|
|
|
<!-- Stirrer blades (inside pot) -->
|
|
<g id="stirrer-blades" clip-path="url(#pot-clip)">
|
|
<rect x="87" y="133" width="31" height="5" fill="#666" rx="1"/>
|
|
<rect x="122" y="133" width="31" height="5" fill="#666" rx="1"/>
|
|
</g>
|
|
|
|
<!-- Lid -->
|
|
<rect x="56" y="20" width="128" height="10" fill="#444" rx="2"/>
|
|
<rect x="56" y="20" width="128" height="3" fill="#555"/>
|
|
|
|
<!-- Stirrer shaft (over lid) -->
|
|
<rect x="118" y="2" width="4" height="134" fill="#777"/>
|
|
|
|
<!-- Motor housing -->
|
|
<rect x="106" y="2" width="28" height="20" fill="#333" rx="3"/>
|
|
<rect x="110" y="6" width="10" height="8" fill="#4a4a4a" rx="1"/>
|
|
<circle cx="129" cy="11" r="3" fill="#cc2222"/>
|
|
|
|
<!-- Thermometer tube -->
|
|
<rect x="212" y="28" width="14" height="146" rx="7"
|
|
fill="#1e1e1e" stroke="#aaa" stroke-width="1.5"/>
|
|
<!-- Thermometer bulb -->
|
|
<circle cx="219" cy="178" r="13" fill="#1e1e1e" stroke="#aaa" stroke-width="1.5"/>
|
|
<!-- Mercury: bulb always red -->
|
|
<circle cx="219" cy="178" r="10" fill="#e0190a"/>
|
|
<!-- Mercury column (JS sets y and height) -->
|
|
<rect id="thermo-mercury" x="215" y="174" width="8" height="0"
|
|
fill="#e0190a" clip-path="url(#tube-clip)"/>
|
|
|
|
<!-- Soll temperature marker (left side, orange, JS-positioned) -->
|
|
<line id="thermo-soll-tick" x1="203" y1="174" x2="212" y2="174"
|
|
stroke="#ff7043" stroke-width="2" stroke-linecap="round"/>
|
|
<text id="thermo-soll-label" x="201" y="174" font-size="9"
|
|
fill="#ff7043" text-anchor="end" dominant-baseline="middle"></text>
|
|
|
|
<!-- Scale ticks -->
|
|
<line x1="227" y1="28" x2="236" y2="28" stroke="#9e9e9e" stroke-width="1.5"/>
|
|
<text x="239" y="32" font-size="9" fill="#9e9e9e">100</text>
|
|
<line x1="227" y1="65" x2="232" y2="65" stroke="#9e9e9e" stroke-width="1"/>
|
|
<line x1="227" y1="101" x2="236" y2="101" stroke="#9e9e9e" stroke-width="1.5"/>
|
|
<text x="239" y="105" font-size="9" fill="#9e9e9e">50</text>
|
|
<line x1="227" y1="137" x2="232" y2="137" stroke="#9e9e9e" stroke-width="1"/>
|
|
<line x1="227" y1="174" x2="236" y2="174" stroke="#9e9e9e" stroke-width="1.5"/>
|
|
<text x="239" y="178" font-size="9" fill="#9e9e9e">0 °C</text>
|
|
|
|
<!-- Current temperature label -->
|
|
<text id="thermo-temp-label" x="219" y="201" font-size="10"
|
|
fill="#e0e0e0" text-anchor="middle">--</text>
|
|
</svg>
|
|
</div>
|
|
</div><!-- #lcd-pot-row -->
|
|
|
|
<nav id="tabs">
|
|
<button class="tab-btn active" data-tab="manual">Manual</button>
|
|
<button class="tab-btn" data-tab="progress">Progress</button>
|
|
</nav>
|
|
|
|
<main>
|
|
<section id="tab-manual" class="tab-content active">
|
|
<div class="panel">
|
|
<h3>Controller</h3>
|
|
<label><input type="checkbox" id="tc-enabled"> Enabled</label>
|
|
<label>Temperature [°C]
|
|
<input type="range" id="temp-soll" min="0" max="100" step="1" disabled>
|
|
<span id="temp-soll-readout">--</span>
|
|
</label>
|
|
<label>Heat rate [°C/min]
|
|
<input type="number" id="heatrate-soll" min="0" max="3" step="0.1" disabled>
|
|
</label>
|
|
</div>
|
|
<div class="panel">
|
|
<h3>Heater</h3>
|
|
<label><input type="checkbox" id="heater-activate"> Activate</label>
|
|
<label>Power [W]
|
|
<input type="range" id="heater-power" min="0" max="100" step="1">
|
|
<span id="heater-power-readout">--</span>
|
|
</label>
|
|
</div>
|
|
<div class="panel">
|
|
<h3>Stirrer</h3>
|
|
<label><input type="checkbox" id="stirrer-activate"> Activate</label>
|
|
<label>Speed [%]
|
|
<input type="range" id="stirrer-speed" min="0" max="100" step="1">
|
|
<span id="stirrer-speed-readout">--</span>
|
|
</label>
|
|
</div>
|
|
<div class="panel">
|
|
<h3>Environment</h3>
|
|
<label>Ambient [°C]
|
|
<input type="number" id="ambient-temp" step="0.1">
|
|
</label>
|
|
<div id="pot-reset-row" class="hidden">
|
|
<label>Pot [°C]
|
|
<input type="number" id="pot-temp" step="0.1">
|
|
</label>
|
|
<button id="btn-pot-reset">Reset</button>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="tab-progress" class="tab-content">
|
|
<div id="step-plates"></div>
|
|
</section>
|
|
</main>
|
|
|
|
<dialog id="sud-message-dialog">
|
|
<p id="sud-message-text"></p>
|
|
<form>
|
|
<button id="sud-message-ok" type="button">OK</button>
|
|
</form>
|
|
</dialog>
|
|
|
|
<footer>
|
|
<div id="sud-status-line"></div>
|
|
<div id="env-status-line"></div>
|
|
</footer>
|
|
|
|
<script src="app.js"></script>
|
|
</body>
|
|
</html>
|