<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>MeltLogic — Spooner, WI</title>
  <style>
    :root {
      --brand:      #E97239;
      --brand-dark: #c05a22;
      --bg:         #f5f7fa;
      --card-bg:    #ffffff;
      --border:     #e2e6ea;
      --text:       #1a1a2e;
      --muted:      #6c7a8a;
      --on-green:   #16a34a;
      --off-red:    #dc2626;
      --radius:     10px;
    }
    * { box-sizing: border-box; margin: 0; padding: 0; }
    body {
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif;
      background: var(--bg); color: var(--text); font-size: 14px; line-height: 1.5;
    }
    .site-header {
      background: var(--text); color: #fff; padding: 16px 24px;
      display: flex; align-items: center; gap: 16px;
      border-bottom: 3px solid var(--brand);
    }
    .logo-mark {
      width: 38px; height: 38px; background: var(--brand); border-radius: 8px;
      display: flex; align-items: center; justify-content: center;
      font-size: 22px; flex-shrink: 0;
    }
    .logo-text    { font-size: 22px; font-weight: 700; letter-spacing: -0.5px; }
    .logo-tagline { font-size: 12px; color: #8fa3b8; margin-top: 1px; }
    .header-meta  { margin-left: auto; text-align: right; font-size: 12px; color: #8fa3b8; line-height: 1.6; }
    .version-badge {
      display: inline-block; background: var(--brand); color: #fff;
      font-size: 10px; font-weight: 700; padding: 2px 7px; border-radius: 4px; letter-spacing: 0.5px;
    }
    .container { max-width: 1200px; margin: 0 auto; padding: 20px 24px; }
    .section {
      background: var(--card-bg); border: 1px solid var(--border);
      border-radius: var(--radius); padding: 18px 20px; margin-bottom: 20px;
    }
    .section h2 { font-size: 16px; font-weight: 700; margin-bottom: 14px; }
    .devices-row { display: flex; gap: 14px; flex-wrap: wrap; }
    .device-card {
      border: 1px solid var(--border); border-radius: var(--radius); padding: 14px 16px;
      flex: 1 1 220px; background: var(--card-bg); border-top: 3px solid var(--brand);
    }
    .device-card.state-on  { border-top-color: var(--on-green); }
    .device-card.state-off { border-top-color: #ccc; }
    .device-title { font-size: 15px; font-weight: 700; }
    .device-sub   { font-size: 12px; color: var(--muted); margin-top: 1px; }
    .pills { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0; }
    .pill {
      display: inline-block; padding: 2px 10px; border-radius: 999px;
      font-size: 11px; font-weight: 600; border: 1.5px solid #ccc; white-space: nowrap;
    }
    .pill.on  { border-color: var(--on-green); color: var(--on-green); }
    .pill.off { border-color: var(--off-red);  color: var(--off-red);  }
    .depth-line { font-size: 15px; margin: 6px 0 2px; }
    .meta-line  { font-size: 11px; color: var(--muted); margin-bottom: 2px; }
    .obs-form { margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border); }
    .obs-row  { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
    .obs-label { font-size: 12px; font-weight: 600; white-space: nowrap; }
    .obs-input {
      width: 80px; padding: 4px 6px; border: 1px solid var(--border);
      border-radius: 5px; font-size: 13px;
    }
    .obs-btn {
      padding: 4px 14px; background: var(--brand); color: #fff;
      border: none; border-radius: 5px; cursor: pointer; font-size: 13px; font-weight: 600;
    }
    .obs-btn:hover { background: var(--brand-dark); }
    .obs-msg  { font-size: 12px; margin-top: 5px; color: var(--muted); min-height: 16px; }
    .last-on  { font-size: 11px; color: var(--muted); margin-top: 8px; }
    .chart-wrap {
      background: #0f1117; border-radius: var(--radius); padding: 20px;
    }
    .chart-wrap + .chart-wrap { margin-top: 12px; }
    .chart-header { margin-bottom: 12px; }
    .chart-title  { color: #e0e6f0; font-size: 15px; font-weight: 700; }
    .chart-sub    { color: #6c7a8a; font-size: 12px; margin-top: 3px; }
    .savings-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 12px; }
    .savings-label { font-size: 12px; color: var(--muted); }
    .savings-value { font-size: 20px; font-weight: 700; margin-top: 2px; }
    .savings-value.green { color: var(--on-green); }
    .savings-value.brand { color: var(--brand); }
    .events-table { width: 100%; border-collapse: collapse; font-size: 13px; }
    .events-table th {
      text-align: left; padding: 8px; border-bottom: 2px solid var(--border);
      font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.5px;
    }
    .events-table td { padding: 7px 8px; border-bottom: 1px solid var(--border); vertical-align: top; }
    .events-table tr:last-child td { border-bottom: none; }
    .sdi-sub { font-size: 11px; color: var(--muted); margin-top: 2px; }
    .site-footer {
      text-align: center; padding: 16px; font-size: 12px; color: var(--muted);
      border-top: 1px solid var(--border); background: var(--card-bg);
    }
    @media (max-width: 600px) {
      .devices-row { flex-direction: column; }
      .savings-grid { grid-template-columns: 1fr 1fr; }
    }
  </style>
</head>
<body>

<header class="site-header">
  <div class="logo-mark">❄</div>
  <div>
    <div class="logo-text">MeltLogic <span class="version-badge">V2.2</span></div>
    <div class="logo-tagline">Weather-responsive roof cable control</div>
  </div>
  <div class="header-meta">
    <div>Spooner, WI &nbsp;·&nbsp; ZIP 54801</div>
    <div>Polls every 1h &nbsp;·&nbsp; 4 devices simulated</div>
  </div>
</header>

<div class="container">

<!-- ── Devices ── -->
<div class="section">
  <h2>Devices</h2>
  <div class="devices-row">
      <div class="device-card state-off">
      <div class="device-title">SOUTH (S, 100 ft)</div>
      <div class="device-sub">ZIP 54801</div>
      <div class="pills">
        <span class="pill off">OFF</span>
        <span class="pill">HIBERNATION</span>
      </div>
      <div class="depth-line">Est. snow depth: <strong>0 in</strong></div>
      <div class="meta-line">SDI: 0 &nbsp;·&nbsp; Orient. mult: 1.3× (Jun) &nbsp;·&nbsp; Season: 1×</div>
      <div class="meta-line">
        Decay multiplier: 0.6×
        &nbsp;↑ slower (roof holds snow)
              </div>
            <div class="meta-line">Last report: 0.4" obs / 0" est</div>
            <div class="obs-form">
        <form onsubmit="submitObs(event,'south')">
          <div class="obs-row">
            <span class="obs-label">Update snow depth:</span>
            <input class="obs-input" type="number" name="observed_inches" min="0" max="72" step="0.5" placeholder="e.g. 0">
            <button class="obs-btn" type="submit">Update</button>
          </div>
        </form>
        <div id="obs-msg-south" class="obs-msg"></div>
      </div>
            <div class="last-on">Last ON: Apr 4, 11:00 PM CT</div>
          </div>
      <div class="device-card state-off">
      <div class="device-title">EAST (E, 100 ft)</div>
      <div class="device-sub">ZIP 54801</div>
      <div class="pills">
        <span class="pill off">OFF</span>
        <span class="pill">HIBERNATION</span>
      </div>
      <div class="depth-line">Est. snow depth: <strong>0 in</strong></div>
      <div class="meta-line">SDI: 0 &nbsp;·&nbsp; Orient. mult: 1.05× (Jun) &nbsp;·&nbsp; Season: 1×</div>
      <div class="meta-line">
        Decay multiplier: 0.3×
        &nbsp;↑ slower (roof holds snow)
              </div>
            <div class="meta-line">Last report: 5" obs / 0" est</div>
            <div class="obs-form">
        <form onsubmit="submitObs(event,'east')">
          <div class="obs-row">
            <span class="obs-label">Update snow depth:</span>
            <input class="obs-input" type="number" name="observed_inches" min="0" max="72" step="0.5" placeholder="e.g. 0">
            <button class="obs-btn" type="submit">Update</button>
          </div>
        </form>
        <div id="obs-msg-east" class="obs-msg"></div>
      </div>
            <div class="last-on">Last ON: May 8, 9:00 AM CT</div>
          </div>
      <div class="device-card state-off">
      <div class="device-title">NORTH (N, 100 ft)</div>
      <div class="device-sub">ZIP 54801</div>
      <div class="pills">
        <span class="pill off">OFF</span>
        <span class="pill">HIBERNATION</span>
      </div>
      <div class="depth-line">Est. snow depth: <strong>0 in</strong></div>
      <div class="meta-line">SDI: 0 &nbsp;·&nbsp; Orient. mult: 0.65× (Jun) &nbsp;·&nbsp; Season: 1×</div>
      <div class="meta-line">
        Decay multiplier: 0.51×
        &nbsp;↑ slower (roof holds snow)
              </div>
            <div class="meta-line">Last report: 7" obs / 5.2" est</div>
            <div class="obs-form">
        <form onsubmit="submitObs(event,'north')">
          <div class="obs-row">
            <span class="obs-label">Update snow depth:</span>
            <input class="obs-input" type="number" name="observed_inches" min="0" max="72" step="0.5" placeholder="e.g. 0">
            <button class="obs-btn" type="submit">Update</button>
          </div>
        </form>
        <div id="obs-msg-north" class="obs-msg"></div>
      </div>
            <div class="last-on">Last ON: Apr 4, 11:00 PM CT</div>
          </div>
      <div class="device-card state-off">
      <div class="device-title">WEST (W, 100 ft)</div>
      <div class="device-sub">ZIP 54801</div>
      <div class="pills">
        <span class="pill off">OFF</span>
        <span class="pill">HIBERNATION</span>
      </div>
      <div class="depth-line">Est. snow depth: <strong>0 in</strong></div>
      <div class="meta-line">SDI: 0 &nbsp;·&nbsp; Orient. mult: 1.15× (Jun) &nbsp;·&nbsp; Season: 1×</div>
      <div class="meta-line">
        Decay multiplier: 0.47×
        &nbsp;↑ slower (roof holds snow)
              </div>
            <div class="meta-line">Last report: 7" obs / 5" est</div>
            <div class="obs-form">
        <form onsubmit="submitObs(event,'west')">
          <div class="obs-row">
            <span class="obs-label">Update snow depth:</span>
            <input class="obs-input" type="number" name="observed_inches" min="0" max="72" step="0.5" placeholder="e.g. 0">
            <button class="obs-btn" type="submit">Update</button>
          </div>
        </form>
        <div id="obs-msg-west" class="obs-msg"></div>
      </div>
            <div class="last-on">Last ON: Apr 4, 11:00 PM CT</div>
          </div>
    </div>
</div>

<!-- ── Charts ── -->
<div class="section">
  <h2>SDI &amp; Conditions (Last 24h)</h2>

  <!-- Chart 1: SDI Accumulation -->
  <div class="chart-wrap">
    <div class="chart-header">
      <div class="chart-title">MeltLogic — SDI Accumulation (Last 24h)</div>
      <div class="chart-sub">
        Spooner, WI (54801)
        &nbsp;·&nbsp; CT times &nbsp;·&nbsp; Generated 2026-06-09 06:55 UTC
      </div>
    </div>
    <canvas id="sdiChart" style="max-height:320px"></canvas>
  </div>

  <!-- Chart 2: Conditions & Multipliers -->
  <div class="chart-wrap" style="margin-top:12px">
    <div class="chart-header">
      <div class="chart-title">Conditions &amp; Multipliers (Last 24h)</div>
      <div class="chart-sub">
        Temperature · Liquid equivalent precip · Season / Orientation / Rain / Decay multipliers
      </div>
    </div>
    <canvas id="conditionsChart" style="max-height:280px"></canvas>
  </div>
</div>

<!-- ── Savings + Carbon ── -->
<div class="section">
  <h2>Savings + Carbon</h2>
  <div class="savings-grid">
    <div><div class="savings-label">Baseline kWh</div><div class="savings-value">3056.63</div></div>
    <div><div class="savings-label">MeltLogic kWh</div><div class="savings-value">598.5</div></div>
    <div><div class="savings-label">Energy saved</div><div class="savings-value green">2458.13 kWh</div></div>
    <div><div class="savings-label">Cost avoided (@ $0.15/kWh)</div><div class="savings-value brand">$368.72</div></div>
    <div><div class="savings-label">CO₂ avoided (@ 0.7 lbs/kWh)</div><div class="savings-value">1720.69 lbs</div></div>
  </div>
</div>

<!-- ── Tools ── -->
<div class="section">
  <h2>Tools &amp; Simulators</h2>
  <div style="display:flex;gap:14px;flex-wrap:wrap;">
    <a href="/sdi-simulator.html" style="
      display:flex;align-items:center;gap:12px;
      padding:14px 18px;
      background:var(--card-bg);
      border:1px solid var(--border);
      border-top:3px solid var(--brand);
      border-radius:var(--radius);
      text-decoration:none;
      color:var(--text);
      flex:0 1 280px;
      transition:box-shadow 0.15s,border-top-color 0.15s;
    " onmouseover="this.style.boxShadow='0 4px 16px rgba(233,114,57,0.2)';this.style.borderTopColor='var(--brand-dark)'"
       onmouseout="this.style.boxShadow='';this.style.borderTopColor='var(--brand)'">
      <div style="font-size:2rem;line-height:1;">🛰️</div>
      <div>
        <div style="font-weight:700;font-size:14px;">SDI Simulator</div>
        <div style="font-size:12px;color:var(--muted);margin-top:2px;">Model snow depth index scenarios</div>
      </div>
      <div style="margin-left:auto;color:var(--brand);font-size:18px;">→</div>
    </a>
  </div>
</div>


<!-- ── Recent Events ── -->
<div class="section">
  <h2>Recent Events (last 10 ticks)</h2>
  <div style="overflow-x:auto">
  <table class="events-table">
    <thead>
      <tr>
        <th>Time (CT)</th><th>Temp</th><th>Cloud</th>
        <th>South</th><th>East</th><th>North</th><th>West</th>        <th>Changed</th>
      </tr>
    </thead>
    <tbody>
          <tr>
        <td style="white-space:nowrap">Jun 9, 1:00 AM</td>
        <td style="white-space:nowrap">65.4°F</td>
        <td style="white-space:nowrap">2%</td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                <td style="font-size:12px">
          —        </td>
      </tr>
          <tr>
        <td style="white-space:nowrap">Jun 9, 12:00 AM</td>
        <td style="white-space:nowrap">65.7°F</td>
        <td style="white-space:nowrap">64%</td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                <td style="font-size:12px">
          —        </td>
      </tr>
          <tr>
        <td style="white-space:nowrap">Jun 8, 11:00 PM</td>
        <td style="white-space:nowrap">66.4°F</td>
        <td style="white-space:nowrap">100%</td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                <td style="font-size:12px">
          —        </td>
      </tr>
          <tr>
        <td style="white-space:nowrap">Jun 8, 10:00 PM</td>
        <td style="white-space:nowrap">67°F</td>
        <td style="white-space:nowrap">100%</td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                <td style="font-size:12px">
          —        </td>
      </tr>
          <tr>
        <td style="white-space:nowrap">Jun 8, 9:00 PM</td>
        <td style="white-space:nowrap">68.9°F</td>
        <td style="white-space:nowrap">97%</td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                <td style="font-size:12px">
          —        </td>
      </tr>
          <tr>
        <td style="white-space:nowrap">Jun 8, 8:00 PM</td>
        <td style="white-space:nowrap">71.5°F</td>
        <td style="white-space:nowrap">100%</td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                <td style="font-size:12px">
          —        </td>
      </tr>
          <tr>
        <td style="white-space:nowrap">Jun 8, 7:00 PM</td>
        <td style="white-space:nowrap">73.7°F</td>
        <td style="white-space:nowrap">100%</td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                <td style="font-size:12px">
          —        </td>
      </tr>
          <tr>
        <td style="white-space:nowrap">Jun 8, 6:00 PM</td>
        <td style="white-space:nowrap">75°F</td>
        <td style="white-space:nowrap">100%</td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                <td style="font-size:12px">
          —        </td>
      </tr>
          <tr>
        <td style="white-space:nowrap">Jun 8, 5:00 PM</td>
        <td style="white-space:nowrap">75.2°F</td>
        <td style="white-space:nowrap">100%</td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                <td style="font-size:12px">
          —        </td>
      </tr>
          <tr>
        <td style="white-space:nowrap">Jun 8, 4:00 PM</td>
        <td style="white-space:nowrap">72.4°F</td>
        <td style="white-space:nowrap">100%</td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                  <td>
            <span class="pill off" style="font-size:11px">OFF</span>
            <span class="pill" style="font-size:11px">HIBERNATION</span>
            <div class="sdi-sub">SDI 0 · 0&quot;</div>
          </td>
                <td style="font-size:12px">
          —        </td>
      </tr>
        </tbody>
  </table>
  </div>
  <p style="color:var(--muted);font-size:11px;margin-top:10px">
    Timestamps in America/Chicago (CT). Run <code>php cron.php</code> to force an update.
  </p>
</div>
</div><!-- /container -->

<footer class="site-footer">
  MeltLogic V2.2 &nbsp;·&nbsp; Spooner, WI &nbsp;·&nbsp;
  ZIP 54801 &nbsp;·&nbsp;
  Weather: <a href="https://open-meteo.com" style="color:var(--brand)">Open-Meteo</a>
</footer>

<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<script>
// ============================================================================
// Observation form
// ============================================================================
function submitObs(e, deviceId) {
  e.preventDefault();
  const form  = e.target;
  const msgEl = document.getElementById('obs-msg-' + deviceId);
  const val   = form.observed_inches.value;
  if (val === '' || isNaN(parseFloat(val))) {
    msgEl.style.color = '#dc2626'; msgEl.textContent = 'Please enter a depth.'; return;
  }
  msgEl.style.color = '#6c7a8a'; msgEl.textContent = 'Saving…';
  fetch('observation.php', { method: 'POST', body: new URLSearchParams({ device_id: deviceId, observed_inches: val }) })
    .then(r => r.json()).then(d => {
      if (d.status === 'standby') {
        msgEl.style.color = '#16a34a'; msgEl.textContent = d.message;
      } else if (d.status === 'updated') {
        msgEl.style.color = '#16a34a';
        msgEl.textContent = `Updated! Decay ×${d.decay_multiplier} · Est depth ${d.new_depth}"`;
      } else {
        msgEl.style.color = '#dc2626'; msgEl.textContent = d.error || 'Unknown error.';
      }
      form.reset(); setTimeout(() => location.reload(), 1800);
    }).catch(() => { msgEl.style.color='#dc2626'; msgEl.textContent='Network error.'; });
}

// ============================================================================
// Chart data (from PHP)
// ============================================================================
const raw = {"labels":["06-08 02:00","06-08 03:00","06-08 04:00","06-08 05:00","06-08 06:00","06-08 07:00","06-08 08:00","06-08 09:00","06-08 10:00","06-08 11:00","06-08 12:00","06-08 13:00","06-08 14:00","06-08 15:00","06-08 16:00","06-08 17:00","06-08 18:00","06-08 19:00","06-08 20:00","06-08 21:00","06-08 22:00","06-08 23:00","06-09 00:00","06-09 01:00"],"devices":{"south":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"east":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"north":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"west":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},"precip":[0,0,0,0,3.39,0,0,0,0,0,0,0,0.16,0.28,0,0,0,0,0,0,0,0,0,0],"precipTypes":["none","none","none","none","rain","none","none","none","none","none","none","none","rain","rain","none","none","none","none","none","none","none","none","none","none"],"stormBands":[{"index":0,"active":false,"imminent":false,"on":true,"reason":""},{"index":1,"active":false,"imminent":false,"on":true,"reason":""},{"index":2,"active":false,"imminent":false,"on":true,"reason":""},{"index":3,"active":false,"imminent":false,"on":true,"reason":""},{"index":4,"active":false,"imminent":false,"on":true,"reason":""},{"index":5,"active":false,"imminent":false,"on":true,"reason":""},{"index":6,"active":false,"imminent":false,"on":true,"reason":""},{"index":7,"active":false,"imminent":false,"on":true,"reason":""},{"index":8,"active":false,"imminent":false,"on":true,"reason":""},{"index":9,"active":false,"imminent":false,"on":true,"reason":""},{"index":10,"active":false,"imminent":false,"on":true,"reason":""},{"index":11,"active":false,"imminent":false,"on":true,"reason":""},{"index":12,"active":false,"imminent":false,"on":true,"reason":""},{"index":13,"active":false,"imminent":false,"on":true,"reason":""},{"index":14,"active":false,"imminent":false,"on":true,"reason":""},{"index":15,"active":false,"imminent":false,"on":true,"reason":""},{"index":16,"active":false,"imminent":false,"on":true,"reason":""},{"index":17,"active":false,"imminent":false,"on":true,"reason":""},{"index":18,"active":false,"imminent":false,"on":true,"reason":""},{"index":19,"active":false,"imminent":false,"on":true,"reason":""},{"index":20,"active":false,"imminent":false,"on":true,"reason":""},{"index":21,"active":false,"imminent":false,"on":true,"reason":""},{"index":22,"active":false,"imminent":false,"on":true,"reason":""},{"index":23,"active":false,"imminent":false,"on":true,"reason":""}],"sdiResets":[],"apiFails":[],"temp":[71.6,70.5,70.1,69.6,65.5,66.4,65.9,64.9,69.6,70.7,73.5,74.5,73.5,73.9,72.4,75.2,75,73.7,71.5,68.9,67,66.4,65.7,65.4],"precipIn":[0,0,0,0,0.339,0,0,0,0,0,0,0,0.016,0.028,0,0,0,0,0,0,0,0,0,0],"seasonMult":[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],"orientMult":{"south":[1.3,1.3,1.3,1.3,1.3,1.3,1.3,1.3,1.3,1.3,1.3,1.3,1.3,1.3,1.3,1.3,1.3,1.3,1.3,1.3,1.3,1.3,1.3,1.3],"east":[1.05,1.05,1.05,1.05,1.05,1.05,1.05,1.05,1.05,1.05,1.05,1.05,1.05,1.05,1.05,1.05,1.05,1.05,1.05,1.05,1.05,1.05,1.05,1.05],"north":[0.65,0.65,0.65,0.65,0.65,0.65,0.65,0.65,0.65,0.65,0.65,0.65,0.65,0.65,0.65,0.65,0.65,0.65,0.65,0.65,0.65,0.65,0.65,0.65],"west":[1.15,1.15,1.15,1.15,1.15,1.15,1.15,1.15,1.15,1.15,1.15,1.15,1.15,1.15,1.15,1.15,1.15,1.15,1.15,1.15,1.15,1.15,1.15,1.15]},"rainMult":[1,1,1,1,1.086,1,1,1,1,1,1,1,1.004,1.007,1,1,1,1,1,1,1,1,1,1],"decayMult":{"south":[0.6,0.6,0.6,0.6,0.6,0.6,0.6,0.6,0.6,0.6,0.6,0.6,0.6,0.6,0.6,0.6,0.6,0.6,0.6,0.6,0.6,0.6,0.6,0.6],"east":[0.3,0.3,0.3,0.3,0.3,0.3,0.3,0.3,0.3,0.3,0.3,0.3,0.3,0.3,0.3,0.3,0.3,0.3,0.3,0.3,0.3,0.3,0.3,0.3],"north":[0.512,0.512,0.512,0.512,0.512,0.512,0.512,0.512,0.512,0.512,0.512,0.512,0.512,0.512,0.512,0.512,0.512,0.512,0.512,0.512,0.512,0.512,0.512,0.512],"west":[0.47,0.47,0.47,0.47,0.47,0.47,0.47,0.47,0.47,0.47,0.47,0.47,0.47,0.47,0.47,0.47,0.47,0.47,0.47,0.47,0.47,0.47,0.47,0.47]},"deviceColors":{"south":"#E97239","east":"#4fa3e0","north":"#9b7fd4","west":"#4caf87"},"deviceLabels":{"south":"South","east":"East","north":"North","west":"West"},"deviceOrder":["south","east","north","west"]};
if (raw.labels && raw.labels.length > 0) {

// ---------------------------------------------------------------------------
// Shared helpers
// ---------------------------------------------------------------------------
const DC  = raw.deviceColors;
const DL  = raw.deviceLabels;
const DO_ = raw.deviceOrder;

// Common axis styling
const darkGrid  = { color: 'rgba(255,255,255,0.07)' };
const mutedTick = { color: '#6c7a8a', font: { size: 10 } };
const axisTitle = (text, color) => ({
  display: true, text, color: color || '#8fa3b8', font: { size: 11 }
});

// ============================================================================
// CHART 1 — SDI Accumulation
// ============================================================================

// ── Custom plugin: storm shading + labels, SDI reset lines, API fail circles ──
const chart1Plugin = {
  id: 'chart1Overlays',
  afterDatasetsDraw(chart) {
    const { ctx, chartArea: { left, right, top, bottom }, scales } = chart;
    const x = scales.x, ySdi = scales.ySdi;
    if (!x || !ySdi) return;
    const n = raw.labels.length;

    // 1. Storm shading bands
    for (let i = 0; i < n; i++) {
      const b  = raw.stormBands[i];
      const x0 = x.getPixelForValue(i);
      const x1 = (i + 1 < n) ? x.getPixelForValue(i + 1) : right;
      if (b.active) {
        ctx.save(); ctx.fillStyle = 'rgba(72,187,120,0.10)';
        ctx.fillRect(x0, top, x1 - x0, bottom - top); ctx.restore();
      } else if (b.imminent) {
        ctx.save(); ctx.fillStyle = 'rgba(215,179,72,0.11)';
        ctx.fillRect(x0, top, x1 - x0, bottom - top); ctx.restore();
      }
    }

    // 2. Heater ON labels -- all active reasons shown as contiguous run labels
    const reasonMeta = {
      'STORM_ACTIVE':   { color: '#48bb78', label: 'Active Storm' },
      'STORM_IMMINENT': { color: '#c9a227', label: 'Storm Imminent' },
      'THAW_GUARD':     { color: '#63b3ed', label: 'Thaw Guard' },
      'SOLAR_THAW':     { color: '#f6ad55', label: 'Solar Thaw' },
      'RAIN_HAZARD':    { color: '#76e4f7', label: 'Rain Hazard' },
      'FREEZE_TAIL':    { color: '#b794f4', label: 'Freeze Tail' },
      'SCHEDULED_RUN':  { color: '#a0aec0', label: 'Scheduled' },
    };
    function drawHeaterLabel(startIdx, endIdx, reason) {
      const meta  = reasonMeta[reason];
      if (!meta) return;
      const x0    = x.getPixelForValue(startIdx);
      const x1    = x.getPixelForValue(Math.min(endIdx, n - 1));
      const midPx = (x0 + x1) / 2;
      const spanW = x1 - x0;
      ctx.save();
      ctx.globalAlpha = 0.18;
      ctx.fillStyle   = meta.color;
      ctx.fillRect(x0, top, spanW, bottom - top);
      ctx.globalAlpha = 1.0;
      ctx.fillStyle   = meta.color;
      ctx.font        = 'bold 10px -apple-system,sans-serif';
      ctx.textAlign   = 'center';
      ctx.fillText(meta.label, midPx, top + 15);
      ctx.restore();
    }
    let runStart = null, runReason = null;
    for (let i = 0; i <= n; i++) {
      const b      = raw.stormBands[i];
      const reason = (b && b.on && b.reason) ? b.reason : null;
      if (reason !== runReason) {
        if (runReason !== null && runStart !== null) drawHeaterLabel(runStart, i - 1, runReason);
        runStart = reason ? i : null; runReason = reason;
      }
    }

    // 3. SDI reset vertical lines (from OBSERVATION events)
    for (const reset of (raw.sdiResets || [])) {
      const idx = raw.labels.indexOf(reset.time);
      // If exact label not found, find nearest by prefix (handles slight time skew)
      let xPos = (idx !== -1) ? x.getPixelForValue(idx) : null;
      if (xPos === null) continue;
      ctx.save();
      ctx.strokeStyle = '#e53e3e';
      ctx.lineWidth   = 1.5;
      ctx.setLineDash([5, 4]);
      ctx.beginPath(); ctx.moveTo(xPos, top); ctx.lineTo(xPos, bottom); ctx.stroke();
      ctx.setLineDash([]);
      ctx.fillStyle = '#e53e3e';
      ctx.font      = 'bold 9px monospace';
      ctx.textAlign = 'left';
      ctx.fillText('↓ SDI Reset', xPos + 4, top + 14);
      if (reset.device) ctx.fillText(reset.device, xPos + 4, top + 25);
      ctx.restore();
    }

    // 4. API failure hollow circles (at bottom of chart)
    for (const fail of (raw.apiFails || [])) {
      const idx = raw.labels.indexOf(fail.time);
      if (idx === -1) continue;
      const xPos = x.getPixelForValue(idx);
      const yPos = bottom - 12;
      ctx.save();
      ctx.strokeStyle = '#a0aec0';
      ctx.fillStyle   = '#0f1117';
      ctx.lineWidth   = 1.5;
      ctx.beginPath(); ctx.arc(xPos, yPos, 5, 0, 2 * Math.PI);
      ctx.fill(); ctx.stroke(); ctx.restore();
    }
  }
};

// ── SDI line datasets ──
const sdiDatasets = DO_.map(did => ({
  label:                deviceLabelFor(did),
  data:                 raw.devices[did] || [],
  borderColor:          DC[did] || '#999',
  backgroundColor:      'transparent',
  borderWidth:          2,
  pointRadius:          2.5,
  pointHoverRadius:     5,
  pointBackgroundColor: DC[did] || '#999',
  tension:              0.3,
  yAxisID:              'ySdi',
  order:                1,
  spanGaps:             true,
  segment: {
    borderDash: ctx => {
      const d = raw.devices[did] || [];
      const i = ctx.p0DataIndex;
      if (d[i] === null || d[i + 1] === null) return [4, 4];
      return [];
    },
    borderColor: ctx => {
      const d = raw.devices[did] || [];
      const i = ctx.p0DataIndex;
      if (d[i] === null || d[i + 1] === null) return 'rgba(120,130,150,0.35)';
      return DC[did] || '#999';
    },
  },
}));

// ── Precip bar dataset ──
const precipBars = {
  label:           'Precip (×10 in)',
  data:            raw.precip,
  backgroundColor: raw.precipTypes.map(t =>
    t === 'snow' ? 'rgba(72,187,120,0.65)' :
    t === 'rain' ? 'rgba(99,179,237,0.55)' : 'rgba(0,0,0,0)'
  ),
  borderWidth: 0,
  type:        'bar',
  yAxisID:     'yPrecip',
  order:       2,
};

new Chart(document.getElementById('sdiChart'), {
  type:    'line',
  plugins: [chart1Plugin],
  data:    { labels: raw.labels, datasets: [...sdiDatasets, precipBars] },
  options: {
    responsive: true,
    animation:  false,
    interaction: { mode: 'index', intersect: false },
    plugins: {
      legend: {
        display: true, position: 'bottom',
        labels: {
          color: '#8fa3b8', font: { size: 11 }, padding: 16, boxWidth: 20,
          filter: item => item.datasetIndex < DO_.length, // hide precip bar from legend
        }
      },
      // Append legend entries manually for storm shading and markers
      tooltip: {
        backgroundColor: 'rgba(10,12,20,0.96)',
        titleColor: '#e0e6f0', bodyColor: '#b0bec5',
        borderColor: '#2d3748', borderWidth: 1,
        callbacks: {
          title: items => {
            const i   = items[0]?.dataIndex;
            const b   = raw.stormBands[i] || {};
            const lbl = raw.labels[i] || '';
            const rMeta = {
              'STORM_ACTIVE':'STORM_ACTIVE','STORM_IMMINENT':'STORM_IMMINENT',
              'THAW_GUARD':'THAW_GUARD','SOLAR_THAW':'SOLAR_THAW',
              'RAIN_HAZARD':'RAIN_HAZARD','FREEZE_TAIL':'FREEZE_TAIL',
              'SCHEDULED_RUN':'SCHEDULED',
            };
            const state = b.on && b.reason && rMeta[b.reason] ? ' -- ' + rMeta[b.reason] : '';
            return lbl + state;
          },
          afterBody(items) {
            const i     = items[0]?.dataIndex;
            if (i == null) return [];
            const t     = raw.precipTypes[i];
            const precipVal = raw.precip[i] ?? 0;
            const pIn   = (precipVal / 10).toFixed(3);
            const lines = [];
            if (precipVal > 0) lines.push(`Precip: ${pIn}" (${t})`);
            const resets = (raw.sdiResets || []).filter(r => raw.labels.indexOf(r.time) === i);
            resets.forEach(r => lines.push(`↓ SDI Reset (${r.device}): obs ${r.observed}"→ SDI ${r.new_sdi}`));
            const fails  = (raw.apiFails || []).filter(f => raw.labels.indexOf(f.time) === i);
            if (fails.length) lines.push('⚠ API timeout (504)');
            return lines;
          }
        }
      }
    },
    scales: {
      x: { ticks: { ...mutedTick, maxTicksLimit: 24, autoSkip: false, maxRotation: 45 }, grid: darkGrid },
      ySdi: {
        type: 'linear', position: 'left',
        title: axisTitle('SDI (inches equivalent)'),
        min: 0,
        ticks: mutedTick, grid: darkGrid,
      },
      yPrecip: {
        type: 'linear', position: 'right',
        min: 0, max: 3,
        ticks: { display: false }, grid: { display: false },
      }
    }
  }
});

// custom legend supplement for chart 1 overlays
// (handled by plugin labels directly on canvas)

// ============================================================================
// CHART 2 — Conditions & Multipliers
// ============================================================================

// Dashed + dotted styles
const dashLong   = [8, 4];
const dashDot    = [3, 3];

const condDatasets = [
  // Temperature — left axis, prominent white line
  {
    label:           'Temperature (°F)',
    data:            raw.temp,
    borderColor:     '#e0e6f0',
    backgroundColor: 'transparent',
    borderWidth:     2,
    pointRadius:     2,
    tension:         0.3,
    yAxisID:         'yTemp',
    order:           1,
  },

  // Precip bars — left axis (small), same colors as chart 1
  {
    label:           'Precip liq. equiv. (in)',
    data:            raw.precipIn.map(v => v * 10), // scale for visibility
    backgroundColor: raw.precipTypes.map(t =>
      t === 'snow' ? 'rgba(72,187,120,0.55)' :
      t === 'rain' ? 'rgba(99,179,237,0.50)' : 'rgba(0,0,0,0)'
    ),
    borderWidth: 0,
    type:        'bar',
    yAxisID:     'yPrecipC2',
    order:       5,
  },

  // Season multiplier — single dashed gray line (same for all devices)
  {
    label:           'Season mult.',
    data:            raw.seasonMult,
    borderColor:     '#a0aec0',
    borderDash:      dashLong,
    backgroundColor: 'transparent',
    borderWidth:     1.5,
    pointRadius:     0,
    tension:         0.2,
    yAxisID:         'yMult',
    order:           2,
  },

  // Rain multiplier — spikes when raining, otherwise flat at 1.0
  {
    label:           'Rain mult.',
    data:            raw.rainMult,
    borderColor:     '#63b3ed',
    borderDash:      dashDot,
    backgroundColor: 'transparent',
    borderWidth:     1.5,
    pointRadius:     0,
    tension:         0.2,
    yAxisID:         'yMult',
    order:           2,
  },

  // Per-device orientation multipliers (solid thin colored lines)
  ...DO_.map(did => ({
    label:           `Orient. mult. (${DL[did] || did})`,
    data:            raw.orientMult[did] || [],
    borderColor:     DC[did] || '#999',
    borderDash:      dashLong,
    backgroundColor: 'transparent',
    borderWidth:     1.5,
    pointRadius:     0,
    tension:         0.1,
    yAxisID:         'yMult',
    order:           3,
  })),

  // Per-device decay multipliers (dotted — changes on observations)
  ...DO_.map(did => ({
    label:           `Decay mult. (${DL[did] || did})`,
    data:            raw.decayMult[did] || [],
    borderColor:     DC[did] || '#999',
    borderDash:      dashDot,
    backgroundColor: 'transparent',
    borderWidth:     1,
    pointRadius:     0,
    tension:         0.1,
    yAxisID:         'yMult',
    order:           4,
  })),
];

new Chart(document.getElementById('conditionsChart'), {
  type: 'line',
  data: { labels: raw.labels, datasets: condDatasets },
  options: {
    responsive:  true,
    animation:   false,
    interaction: { mode: 'index', intersect: false },
    plugins: {
      legend: {
        display:  true,
        position: 'bottom',
        labels: {
          color: '#8fa3b8', font: { size: 10 }, padding: 12, boxWidth: 18,
          // Hide precip bar from legend
          filter: item => item.datasetIndex !== 1,
        }
      },
      tooltip: {
        backgroundColor: 'rgba(10,12,20,0.96)',
        titleColor: '#e0e6f0', bodyColor: '#b0bec5',
        borderColor: '#2d3748', borderWidth: 1,
        callbacks: {
          afterBody(items) {
            const i = items[0]?.dataIndex;
            if (i == null) return [];
            const pIn = (raw.precipIn[i] || 0).toFixed(4);
            const t   = raw.precipTypes[i];
            return pIn > 0 ? [`Precip: ${pIn}" (${t})`] : [];
          }
        }
      }
    },
    scales: {
      x: { ticks: { ...mutedTick, maxTicksLimit: 24, autoSkip: false, maxRotation: 45 }, grid: darkGrid },
      yTemp: {
        type:     'linear',
        position: 'left',
        title:    axisTitle('Temperature (°F)'),
        ticks:    mutedTick,
        grid:     darkGrid,
      },
      yMult: {
        type:     'linear',
        position: 'right',
        min:      0,
        max:      3,
        title:    axisTitle('Multiplier value', '#8fa3b8'),
        ticks:    { ...mutedTick, stepSize: 0.5 },
        grid:     { display: false },
      },
      yPrecipC2: {
        type: 'linear', position: 'right',
        min: 0, max: 5,
        ticks: { display: false }, grid: { display: false },
      }
    }
  }
});

// ============================================================================
// Helpers
// ============================================================================
function deviceLabelFor(did) {
  const orient = { south: 'S', east: 'E', north: 'N', west: 'W' };
  const omArr  = raw.orientMult[did];
  const lastNonNull = omArr ? [...omArr].reverse().find(v => v !== null) : null;
  const lastOM = lastNonNull != null ? lastNonNull.toFixed(2) : '?';
  return `${(DL[did]||did)} (E=${lastOM}×)`;
}

} // end if labels.length > 0
</script>
</body>
</html>
