etm-powersync-app/docs/mockups/etm-powersync-dashboard-v4.html
Patrick Schurig 0ebbde49da chore(docs): range specs/contrats/refs dans docs/, track l'autorité
- specs -> docs/ (DASHBOARD_SPEC compagnon du contrat)
- ref introspection nymea-jsonRPC -> docs/reference/
- mockups HTML -> docs/mockups/ ; briefs consommés -> docs/archive/
- track AGENTS.md + INTERFACE_etmvariableload.md (étaient untracked)
- INTERFACE_*: marqué miroir, canonique = repo plugin
2026-06-28 12:49:57 +02:00

240 lines
23 KiB
HTML

<!DOCTYPE html>
<html lang="fr" data-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ETM PowerSync — dashboard v4</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=IBM+Plex+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
:root{
--sans:'IBM Plex Sans',system-ui,sans-serif; --mono:'IBM Plex Mono',ui-monospace,monospace;
--brand:#31a3dd; --solar:#fec113; --eco:#28a06a; --heat:#ef8b2a; --water:#31a3dd; --grid-c:#8a98a3; --import:#e8806f;
--c-house:#b8c34d; --c-ecs:#31a3dd; --c-ve:#28a06a; --c-bcharge:#7fcf72; --c-bdis:#2f6fb0; --c-imp:#e8806f; --c-exp:#fec113;
--radius:20px;
}
html[data-theme="light"]{--bg:#eef2f5;--bg2:#e4eaee;--surface:#fff;--ink:#0d2b3b;--muted:#5d6f7b;--faint:#8b9aa4;
--line:rgba(13,43,59,.10);--line-strong:rgba(13,43,59,.16);--heos-tint:rgba(40,160,106,.09);--track:#e7edf1;--futurebg:rgba(13,43,59,.035);--veil:#fff;--shadow:0 1px 2px rgba(13,43,59,.04)}
html[data-theme="dark"]{--bg:#06141d;--bg2:#0a1f2b;--surface:#0d2b3b;--ink:#eaf3f7;--muted:#9bb0bc;--faint:#6c8493;
--line:rgba(255,255,255,.09);--line-strong:rgba(255,255,255,.16);--heos-tint:rgba(40,160,106,.15);--track:rgba(255,255,255,.06);--futurebg:rgba(255,255,255,.04);--veil:#0d2b3b;--shadow:none;--eco:#33b87a;--solar:#ffcb33}
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:var(--sans);background:var(--bg2);color:var(--ink);min-height:100vh;display:flex;justify-content:center;align-items:flex-start;padding:24px 12px;-webkit-font-smoothing:antialiased}
body,.surface,.theme-toggle,.card,.kpi,.node,.tabbar{transition:background .35s,color .35s,border-color .35s}
.phone{width:100%;max-width:392px;background:var(--bg);border-radius:34px;border:1px solid var(--line);overflow:hidden;position:relative;padding-bottom:74px}
.topbar{display:flex;align-items:center;justify-content:space-between;padding:18px 20px 8px}
.wordmark b{font-size:17px;font-weight:700;letter-spacing:-.01em;display:block;line-height:1.1}
.wordmark span{font-family:var(--mono);font-size:10px;color:var(--faint)}
.theme-toggle{width:58px;height:30px;border-radius:30px;border:1px solid var(--line-strong);background:var(--surface);position:relative;cursor:pointer;display:flex;align-items:center;padding:0 6px;justify-content:space-between}
.theme-toggle svg{width:14px;height:14px;color:var(--faint);z-index:1}
.theme-toggle .knob{position:absolute;top:3px;left:3px;width:22px;height:22px;border-radius:50%;background:var(--brand);transition:transform .32s cubic-bezier(.34,1.56,.64,1)}
html[data-theme="dark"] .theme-toggle .knob{transform:translateX(28px);background:var(--solar)}
.scroll{padding:6px 16px 16px}
/* flux croix */
.flowcard{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);padding:14px 16px 6px;margin-bottom:14px}
.flowhead{display:flex;align-items:center;justify-content:space-between;margin-bottom:2px}
.flowhead h2{font-size:13px;font-weight:600}
.flowhead .live{display:flex;align-items:center;gap:6px;font-family:var(--mono);font-size:11px;color:var(--muted)}
.flowhead .dot{width:7px;height:7px;border-radius:50%;background:var(--eco);animation:pulse 2.4s infinite}
@keyframes pulse{0%{box-shadow:0 0 0 0 rgba(40,160,106,.5)}70%{box-shadow:0 0 0 7px rgba(40,160,106,0)}100%{box-shadow:0 0 0 0 rgba(40,160,106,0)}}
.flow{position:relative;width:100%;aspect-ratio:1/1;max-width:320px;margin:0 auto}
.flow svg.lines{position:absolute;inset:0;width:100%;height:100%;overflow:visible}
.node{position:absolute;transform:translate(-50%,-50%);width:72px;height:72px;border-radius:50%;background:var(--surface);border:1.5px solid var(--line);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1px;text-align:center}
.node .ico{width:21px;height:21px} .node .val{font-family:var(--mono);font-size:11.5px;font-weight:600;line-height:1} .node .lab{font-size:8.5px;color:var(--muted)}
.node.hub{width:90px;height:90px;border-width:2px} .node.hub .val{font-size:14px}
.node.solar{left:50%;top:12%;border-color:rgba(254,193,19,.45)} .node.batt{left:88%;top:50%;border-color:rgba(40,160,106,.4)} .node.grid{left:12%;top:50%} .node.loads{left:50%;top:88%;border-color:rgba(49,163,221,.4)} .node.hub{left:50%;top:50%}
.ico.solar{color:var(--solar)}.ico.batt{color:var(--eco)}.ico.grid{color:var(--grid-c)}.ico.loads{color:var(--water)}.ico.hub{color:var(--ink)}
.heos-ring{position:absolute;inset:-5px;border-radius:50%;border:1.5px solid var(--eco);animation:ring 3s ease-in-out infinite}
@keyframes ring{0%,100%{opacity:.25}50%{opacity:.6}}
.flowline{fill:none;stroke:var(--line-strong);stroke-width:.7}
/* KPI */
.kpis{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:14px}
.kpi{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);padding:13px 14px;display:flex;align-items:center;gap:12px}
.kpi .ring{width:50px;height:50px;flex:0 0 auto} .kpi .ring circle{fill:none;stroke-width:5;stroke-linecap:round} .kpi .ring .bg{stroke:var(--track)} .kpi .ring .fg{transition:stroke-dashoffset 1s cubic-bezier(.4,0,.2,1);transform:rotate(-90deg);transform-origin:50% 50%}
.kpi .tx .v{font-family:var(--mono);font-size:18px;font-weight:600;line-height:1} .kpi .tx .l{font-size:11px;color:var(--muted);margin-top:3px}
.kpi.small .ic{width:30px;height:30px;border-radius:9px;display:flex;align-items:center;justify-content:center;flex:0 0 auto} .kpi.small .ic svg{width:16px;height:16px}
.ic.exp{background:rgba(40,160,106,.12);color:var(--eco)} .ic.imp{background:rgba(232,128,111,.14);color:var(--import)}
.kpi.small .v{font-family:var(--mono);font-size:16px;font-weight:600;line-height:1} .kpi.small .l{font-size:10.5px;color:var(--muted);margin-top:3px}
/* carte Héos */
.card{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);margin-bottom:14px;overflow:hidden}
.card.heos{border-color:rgba(40,160,106,.3)}
.heos-hd{display:flex;align-items:center;gap:9px;padding:15px 16px 12px}
.heos-hd .mark{width:30px;height:30px;border-radius:9px;background:var(--heos-tint);display:flex;align-items:center;justify-content:center;color:var(--eco)} .heos-hd .mark svg{width:17px;height:17px}
.heos-hd .t{flex:1} .heos-hd .t b{font-size:14px;font-weight:600;display:block} .heos-hd .t small{font-size:11px;color:var(--muted);font-family:var(--mono)}
.heos-hd .glow{width:8px;height:8px;border-radius:50%;background:var(--eco);animation:pulse 2.4s infinite}
.summary{padding:0 16px 14px;font-size:14px;line-height:1.45} .summary .hl{color:var(--eco);font-weight:600}
.acts{padding:0 16px 8px}
.act{display:flex;align-items:flex-start;gap:12px;padding:11px 0;border-top:1px solid var(--line)}
.act .ai{width:34px;height:34px;border-radius:10px;display:flex;align-items:center;justify-content:center;flex:0 0 auto} .act .ai svg{width:18px;height:18px}
.ai-car{background:rgba(40,160,106,.12);color:var(--eco)} .ai-water{background:rgba(49,163,221,.12);color:var(--water)} .ai-batt{background:rgba(40,160,106,.12);color:var(--eco)}
.act .ax{flex:1;min-width:0} .act .ax .top{display:flex;justify-content:space-between;align-items:baseline;gap:8px} .act .ax .top b{font-size:13.5px;font-weight:600} .act .ax .top .kw{font-family:var(--mono);font-size:12px;font-weight:600;color:var(--eco)} .act .ax .why{font-size:12px;color:var(--muted);margin-top:2px;line-height:1.35}
.seemore{display:flex;align-items:center;justify-content:center;gap:6px;padding:13px;border-top:1px solid var(--line);font-size:13px;font-weight:600;color:var(--brand);cursor:pointer} .seemore svg{width:15px;height:15px}
/* plan de la journée */
.card-hd{display:flex;align-items:center;justify-content:space-between;padding:15px 16px 2px} .card-hd b{font-size:14px;font-weight:600} .card-hd small{font-size:11px;font-family:var(--mono);color:var(--muted)}
.chartwrap{padding:4px 8px 0} svg.viz{width:100%;height:auto;display:block;overflow:visible}
.axlab{font-family:var(--mono);font-size:7.5px;fill:var(--faint)} .ylab{font-family:var(--mono);font-size:7.5px;fill:var(--faint);text-anchor:end}
.trk-name{font-size:9px;fill:var(--ink);font-weight:500} .trk-sub{font-size:7px;fill:var(--faint);font-family:var(--mono)}
.temp{font-family:var(--mono);font-size:8px;fill:var(--muted);text-anchor:middle}
.now-tab{font-family:var(--mono);font-size:8px;fill:#fff;font-weight:600}
.legend{display:flex;flex-wrap:wrap;gap:7px 12px;padding:8px 16px 4px} .lg{display:flex;align-items:center;gap:6px;font-size:10px;color:var(--muted)} .lg i{width:9px;height:9px;border-radius:2px;display:inline-block}
.gains{display:flex;align-items:center;gap:12px;padding:13px 16px} .gains .g{flex:1} .gains .g .v{font-family:var(--mono);font-size:15px;font-weight:600} .gains .g .l{font-size:10px;color:var(--muted);margin-top:2px} .gains .sep{width:1px;align-self:stretch;background:var(--line)} .gains .v.pos{color:var(--eco)}
.tabbar{position:absolute;left:0;right:0;bottom:0;height:66px;background:var(--surface);border-top:1px solid var(--line);display:flex;align-items:center;justify-content:space-around;padding-bottom:4px}
.tab{display:flex;flex-direction:column;align-items:center;gap:3px;font-size:10px;color:var(--faint);cursor:pointer;border:0;background:transparent} .tab svg{width:21px;height:21px} .tab.active{color:var(--brand)}
@media (prefers-reduced-motion:reduce){*{animation:none!important;transition:none!important}}
</style>
</head>
<body>
<div class="phone">
<div class="topbar">
<div class="wordmark"><b>ETM PowerSync</b><span>Dashboard</span></div>
<button class="theme-toggle" id="themeBtn" aria-label="Basculer le thème">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M2 12h2M20 12h2M5 5l1.4 1.4M17.6 17.6L19 19M19 5l-1.4 1.4M6.4 17.6L5 19"/></svg>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12.8A9 9 0 1111.2 3 7 7 0 0021 12.8z"/></svg>
<span class="knob"></span>
</button>
</div>
<div class="scroll">
<!-- FLUX CROIX -->
<div class="flowcard">
<div class="flowhead"><h2>Flux énergétique</h2><div class="live"><span class="dot"></span> en direct</div></div>
<div class="flow">
<svg class="lines" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet">
<path id="ls" class="flowline" d="M50 22 L50 39"/><path id="lb" class="flowline" d="M61 50 L78 50"/><path id="lg" class="flowline" d="M22 50 L39 50"/><path id="ll" class="flowline" d="M50 61 L50 78"/>
<g fill="#fec113"><circle r="1.5"><animateMotion dur="1.5s" repeatCount="indefinite"><mpath href="#ls"/></animateMotion></circle><circle r="1.5"><animateMotion dur="1.5s" begin="-.5s" repeatCount="indefinite"><mpath href="#ls"/></animateMotion></circle><circle r="1.5"><animateMotion dur="1.5s" begin="-1s" repeatCount="indefinite"><mpath href="#ls"/></animateMotion></circle></g>
<g fill="#28a06a"><circle r="1.4"><animateMotion dur="2.3s" repeatCount="indefinite"><mpath href="#lb"/></animateMotion></circle><circle r="1.4"><animateMotion dur="2.3s" begin="-1.15s" repeatCount="indefinite"><mpath href="#lb"/></animateMotion></circle></g>
<g fill="#8a98a3"><circle r="1.2"><animateMotion dur="3.6s" repeatCount="indefinite"><mpath href="#lg"/></animateMotion></circle></g>
<g fill="#31a3dd"><circle r="1.4"><animateMotion dur="2s" repeatCount="indefinite"><mpath href="#ll"/></animateMotion></circle><circle r="1.4"><animateMotion dur="2s" begin="-1s" repeatCount="indefinite"><mpath href="#ll"/></animateMotion></circle></g>
</svg>
<div class="node solar"><svg class="ico solar" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M2 12h2M20 12h2M5 5l1.4 1.4M17.6 17.6L19 19M19 5l-1.4 1.4M6.4 17.6L5 19"/></svg><div class="val" data-kw="3.8">0.0 kW</div><div class="lab">Solaire</div></div>
<div class="node batt"><svg class="ico batt" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="4" y="8" width="16" height="11" rx="2"/><path d="M9 8V6h6v2M8 13l3-2v4l3-2"/></svg><div class="val">87 %</div><div class="lab">+0.4 kW</div></div>
<div class="node grid"><svg class="ico grid" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 21V9l7-6 7 6v12M9 21v-6h6v6"/></svg><div class="val" data-kw="0.2">0.0 kW</div><div class="lab">Réseau</div></div>
<div class="node loads"><svg class="ico loads" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 2v6M15 2v6M7 8h10v3a5 5 0 01-10 0zM12 16v6"/></svg><div class="val" data-kw="2.1">0.0 kW</div><div class="lab">Pilotés</div></div>
<div class="node hub"><div class="heos-ring"></div><svg class="ico hub" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 11l9-7 9 7M5 10v10h14V10M10 20v-6h4v6"/></svg><div class="val" data-kw="1.1">0.0 kW</div><div class="lab">Maison</div></div>
</div>
</div>
<!-- KPI -->
<div class="kpis">
<div class="kpi"><svg class="ring" viewBox="0 0 50 50"><circle class="bg" cx="25" cy="25" r="20"/><circle class="fg" cx="25" cy="25" r="20" stroke="var(--solar)" data-pct="87"/></svg><div class="tx"><div class="v" data-pct="87">0 %</div><div class="l">Autoconsommation</div></div></div>
<div class="kpi"><svg class="ring" viewBox="0 0 50 50"><circle class="bg" cx="25" cy="25" r="20"/><circle class="fg" cx="25" cy="25" r="20" stroke="var(--eco)" data-pct="94"/></svg><div class="tx"><div class="v" data-pct="94">0 %</div><div class="l">Autonomie</div></div></div>
<div class="kpi small"><div class="ic exp"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14M12 5l-7 7 7 7"/></svg></div><div><div class="v">6.2 kWh</div><div class="l">Vers réseau · aujourd'hui</div></div></div>
<div class="kpi small"><div class="ic imp"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 12H5M12 5l7 7-7 7"/></svg></div><div><div class="v">1.1 kWh</div><div class="l">Depuis réseau · aujourd'hui</div></div></div>
</div>
<!-- HÉOS -->
<div class="card heos">
<div class="heos-hd"><div class="mark"><svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l1.8 5.4L19 9l-5.2 1.6L12 16l-1.8-5.4L5 9l5.2-1.6z"/></svg></div><div class="t"><b>Héos pilote en ce moment</b><small>optimise sur 24 h · maj 22:54</small></div><span class="glow"></span></div>
<div class="summary">Le soleil couvre toute la maison. <span class="hl">Le surplus part dans l'eau chaude et la voiture</span> plutôt que d'être revendu à bas prix.</div>
<div class="acts">
<div class="act"><div class="ai ai-water"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3s6 6.5 6 11a6 6 0 01-12 0c0-4.5 6-11 6-11z"/></svg></div><div class="ax"><div class="top"><b>Eau chaude — chauffe maintenant</b><span class="kw">1.2 kW</span></div><div class="why">Le surplus baisse après 17 h. Mieux vaut monter le ballon à 60 °C tant que c'est gratuit.</div></div></div>
<div class="act"><div class="ai ai-car"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 13l1.5-5h11L19 13M5 13h14v5H5zM7 18v2M17 18v2"/></svg></div><div class="ax"><div class="top"><b>Voiture — charge sur surplus</b><span class="kw">0.9 kW</span></div><div class="why">Énergie solaire gratuite. La charge s'ajuste seule si un nuage passe.</div></div></div>
<div class="act"><div class="ai ai-batt"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="4" y="8" width="16" height="11" rx="2"/><path d="M9 8V6h6v2"/></svg></div><div class="ax"><div class="top"><b>Batterie — se réserve pour ce soir</b><span class="kw">+0.4 kW</span></div><div class="why">Charge lente jusqu'à 87 %, pour alimenter la maison après 18 h sans toucher au réseau.</div></div></div>
</div>
<div class="seemore">Voir le plan de la journée <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 6l6 6-6 6"/></svg></div>
</div>
<!-- ===== PLAN DE LA JOURNÉE (météo + flux + appareils) ===== -->
<div class="card">
<div class="card-hd"><b>Plan de la journée</b><small>météo · flux · appareils</small></div>
<div class="chartwrap"><svg class="viz" id="plansvg" viewBox="0 0 340 178" preserveAspectRatio="xMidYMid meet"></svg></div>
<div class="legend">
<div class="lg"><i style="background:var(--c-house)"></i>maison</div>
<div class="lg"><i style="background:var(--c-ecs)"></i>eau</div>
<div class="lg"><i style="background:var(--c-ve)"></i>voiture</div>
<div class="lg"><i style="background:var(--c-bcharge)"></i>batterie</div>
<div class="lg"><i style="background:var(--c-exp)"></i>réseau</div>
<div class="lg"><i style="background:var(--c-bdis)"></i>batt.→maison</div>
</div>
</div>
</div>
<nav class="tabbar">
<button class="tab active"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 11l9-7 9 7M5 10v10h14V10"/></svg>Dashboard</button>
<button class="tab"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 20V10M12 20V4M19 20v-7"/></svg>Énergie</button>
<button class="tab"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="6" cy="6" r="2.5"/><circle cx="18" cy="6" r="2.5"/><circle cx="12" cy="18" r="2.5"/><path d="M6 8v3a3 3 0 003 3h6a3 3 0 003-3V8"/></svg>Things</button>
<button class="tab"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2v20M5 9l7-7 7 7M5 15l7 7 7-7"/></svg>A/C</button>
<button class="tab"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3l2.5 6 6.5.5-5 4.3 1.6 6.4L12 17l-5.6 3.2L8 13.8 3 9.5 9.5 9z"/></svg>Favoris</button>
</nav>
</div>
<script>
const reduce=window.matchMedia('(prefers-reduced-motion:reduce)').matches;
document.getElementById('themeBtn').addEventListener('click',()=>{const h=document.documentElement;h.dataset.theme=h.dataset.theme==='dark'?'light':'dark';});
function up(el,target,suffix,dur){const dec=suffix==' kW'?1:0;if(reduce){el.textContent=target.toFixed(dec)+suffix;return;}
let t0=null;function s(ts){t0=t0||ts;const p=Math.min((ts-t0)/dur,1),e=1-Math.pow(1-p,3);el.textContent=(target*e).toFixed(dec)+suffix;if(p<1)requestAnimationFrame(s);}requestAnimationFrame(s);}
document.querySelectorAll('[data-kw]').forEach(el=>up(el,parseFloat(el.dataset.kw),' kW',900));
const C=2*Math.PI*20;
document.querySelectorAll('.ring .fg').forEach(c=>{c.style.strokeDasharray=C;c.style.strokeDashoffset=reduce?C*(1-(+c.dataset.pct)/100):C;});
document.querySelectorAll('.tx .v').forEach(el=>up(el,+el.dataset.pct,' %',1100));
window.requestAnimationFrame(()=>setTimeout(()=>{document.querySelectorAll('.ring .fg').forEach(c=>{c.style.strokeDashoffset=C*(1-(+c.dataset.pct)/100);});},reduce?0:120));
/* ===== plan de la journée ===== */
const PAD_L=62,PAD_R=12,W=340,plotW=W-PAD_L-PAD_R,NOW=14.5;
const x=h=>PAD_L+(h/24)*plotW;
const NS="http://www.w3.org/2000/svg";
const E=(t,a,p)=>{const e=document.createElementNS(NS,t);for(const k in a)e.setAttribute(k,a[k]);p&&p.appendChild(e);return e;};
const svg=document.getElementById('plansvg');
const METEO_TOP=8, base=150, posK=17, negK=12, BOTTOM=172;
// [solH, solEcs, solVe, solBatt, solGrid, battH, gridH]
const D=[
[0,0,0,0,0,.35,.05],[0,0,0,0,0,.35,.05],[0,0,0,0,0,.3,0],[0,0,0,0,0,.3,0],[0,0,0,0,0,.3,0],[0,0,0,0,0,.35,.1],
[.3,0,0,0,0,0,.05],[.55,0,0,.35,0,0,0],[.7,0,0,1.1,0,0,0],[.6,0,0,2.2,0,0,0],[.6,0,0,2.0,.9,0,0],
[.6,1.2,0,1.5,.55,0,0],[.7,1.2,0,1.0,1.1,0,0],[.6,0,1.8,.8,.7,0,0],[.6,0,2.2,.5,.2,0,0],
[.6,0,2.0,.3,0,0,0],[.6,0,1.5,0,0,0,0],[.7,0,.6,0,0,0,0],[.6,0,0,0,0,.55,0],[.2,0,0,0,0,1.0,0],
[0,0,0,0,0,1.2,0],[0,0,0,0,0,1.0,0],[0,0,0,0,0,.8,0],[0,0,0,0,0,.5,0]
];
const POS=[[0,'var(--c-house)'],[1,'var(--c-ecs)'],[2,'var(--c-ve)'],[3,'var(--c-bcharge)'],[4,'var(--c-exp)']];
const NEG=[[5,'var(--c-bdis)'],[6,'var(--c-imp)']];
const bw=plotW/24*0.62;
// fond futur (toute hauteur)
E('rect',{x:x(NOW),y:METEO_TOP-4,width:x(24)-x(NOW),height:BOTTOM-(METEO_TOP-4),fill:'var(--futurebg)'},svg);
// gridlines verticales discrètes
[6,12,18].forEach(h=>E('line',{x1:x(h),y1:METEO_TOP-2,x2:x(h),y2:BOTTOM,stroke:'var(--line)','stroke-width':.5},svg));
// ===== météo =====
function sun(cx,cy,c){const g=E('g',{},svg);E('circle',{cx,cy,r:3.4,fill:c},g);for(let i=0;i<8;i++){const a=i*Math.PI/4;E('line',{x1:cx+Math.cos(a)*5,y1:cy+Math.sin(a)*5,x2:cx+Math.cos(a)*6.8,y2:cy+Math.sin(a)*6.8,stroke:c,'stroke-width':1.3,'stroke-linecap':'round'},g);}}
function moon(cx,cy,c){const g=E('g',{},svg);E('path',{d:`M${cx+3.5},${cy-4} a5,5 0 1 0 0,8 a4,4 0 1 1 0,-8 z`,fill:c},g);}
function cloudSun(cx,cy,c){sun(cx-2,cy-1,c);E('path',{d:`M${cx-1},${cy+4} a3,3 0 0 1 6,0 h1 a2.6,2.6 0 0 1 0,5 h-8 a2.6,2.6 0 0 1 1-5 z`,fill:'var(--faint)'},svg);}
const METEO=[[2,'moon',15],[6,'sun',17],[10,'sun',23],[14,'sun',28],[18,'cloudsun',25],[22,'moon',18]];
METEO.forEach(([h,t,temp])=>{const cx=x(h),cy=METEO_TOP+6;
if(t==='moon')moon(cx,cy,'var(--brand)');else if(t==='cloudsun')cloudSun(cx,cy,'var(--solar)');else sun(cx,cy,'var(--solar)');
const tx=E('text',{x:cx,y:METEO_TOP+22,class:'temp'},svg);tx.textContent=temp+'°';});
// ===== grille Y + barres =====
[0,2,4].forEach(v=>{const yy=base-v*posK;E('line',{x1:PAD_L,y1:yy,x2:W-PAD_R,y2:yy,stroke:'var(--line)','stroke-width':v===0?1:.5},svg);
const t=E('text',{x:PAD_L-6,y:yy+3,class:'ylab'},svg);t.textContent=v;});
const yl=E('text',{x:PAD_L-6,y:base-4*posK-4,class:'ylab'},svg);yl.textContent='kWh';
D.forEach((row,h)=>{const cx=x(h+.5),fut=(h+.5)>NOW;
let yt=base;POS.forEach(([i,c])=>{const v=row[i];if(v<=0)return;const hh=v*posK;yt-=hh;E('rect',{x:cx-bw/2,y:yt,width:bw,height:hh,fill:c,rx:1},svg);});
let yb=base;NEG.forEach(([i,c])=>{const v=row[i];if(v<=0)return;const hh=v*negK;E('rect',{x:cx-bw/2,y:yb,width:bw,height:hh,fill:c,rx:1},svg);yb+=hh;});});
// ===== pistes appareils : retirées du Dashboard (gardées dans le plein écran Plan) =====
// ===== voile hachuré futur (toute hauteur) =====
const defs=E('defs',{},svg);const pat=E('pattern',{id:'hatch',width:5,height:5,patternUnits:'userSpaceOnUse',patternTransform:'rotate(45)'},defs);
E('line',{x1:0,y1:0,x2:0,y2:5,stroke:'var(--veil)','stroke-width':2.4,'stroke-opacity':.5},pat);
E('rect',{x:x(NOW),y:METEO_TOP-4,width:x(24)-x(NOW),height:BOTTOM-(METEO_TOP-4),fill:'url(#hatch)'},svg);
// ===== axe X (partagé) =====
[0,6,12,18,24].forEach(h=>{const t=E('text',{x:x(h),y:base+18,class:'axlab','text-anchor':'middle'},svg);t.textContent=h+'h';});
// ===== ligne NOW (traverse tout) =====
E('rect',{x:x(NOW)-1,y:METEO_TOP-2,width:2,height:BOTTOM-(METEO_TOP-2),fill:'var(--ink)','fill-opacity':.2},svg);
E('rect',{x:x(NOW)-16,y:METEO_TOP-6,width:32,height:13,rx:6,fill:'var(--ink)'},svg);
const nt=E('text',{x:x(NOW),y:METEO_TOP+3.5,class:'now-tab','text-anchor':'middle'},svg);nt.textContent='14:30';
</script>
</body>
</html>