etm-powersync-app/docs/mockups/installations_mockup.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

233 lines
12 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ETM PowerSync — Installations (multi-HEMS)</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=Poppins:wght@500;600;700&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
<style>
:root{
--bg:#081f2b; --panel:#0d2b3b; --card:#12384a; --card2:#0f3242;
--line:rgba(255,255,255,.08); --line2:rgba(255,255,255,.14);
--blue:#31a3dd; --yellow:#fec113; --green:#35c98a; --red:#e07a7a; --grey:#6f8b9a;
--txt:#e8f1f6; --muted:#90a8b6; --muted2:#6f8b9a; --r:16px;
}
*{box-sizing:border-box}
body{margin:0;background:
radial-gradient(900px 500px at 15% -10%, #0e3145 0%, transparent 60%),
radial-gradient(900px 600px at 110% 10%, #0b2535 0%, transparent 55%),
var(--bg);
font-family:'Inter',system-ui,sans-serif;color:var(--txt);min-height:100vh;padding:34px 20px 60px}
.head{max-width:880px;margin:0 auto 26px}
.head h1{font-family:'Poppins';font-weight:700;font-size:20px;margin:0 0 6px}
.head p{margin:0;color:var(--muted);font-size:13.5px;line-height:1.55;max-width:700px}
.head code{color:#9fd4ee}
.stage{display:flex;gap:34px;justify-content:center;flex-wrap:wrap;align-items:flex-start}
.col{display:flex;flex-direction:column;align-items:center;gap:12px}
.cap{color:var(--muted2);font-size:12px;font-family:'Poppins';font-weight:500;letter-spacing:.4px;text-transform:uppercase}
.phone{width:392px;background:var(--panel);border:1px solid var(--line2);border-radius:30px;overflow:hidden;
box-shadow:0 30px 80px -30px rgba(0,0,0,.7);display:flex;flex-direction:column;height:812px}
.appbar{display:flex;align-items:center;gap:12px;padding:18px 16px 14px;border-bottom:1px solid var(--line);
background:linear-gradient(#102f40,#0d2b3b)}
.appbar .ic{width:26px;height:26px;display:grid;place-items:center;color:var(--muted);font-size:18px}
.appbar h2{font-family:'Poppins';font-weight:600;font-size:16px;margin:0;flex:1;text-align:center}
.scroll{overflow:auto;padding:15px 15px 26px}
.scroll::-webkit-scrollbar{width:0}
.sec{display:flex;align-items:center;gap:8px;margin:16px 4px 10px}
.sec h3{font-family:'Poppins';font-weight:600;font-size:11.5px;letter-spacing:1.3px;text-transform:uppercase;
color:var(--blue);margin:0}
.sec .scan{margin-left:auto;display:flex;align-items:center;gap:6px;color:var(--muted2);font-size:11px}
.pulse{width:8px;height:8px;border-radius:50%;background:var(--blue);box-shadow:0 0 0 0 rgba(49,163,221,.5);
animation:p 1.6s infinite}
@keyframes p{0%{box-shadow:0 0 0 0 rgba(49,163,221,.5)}70%{box-shadow:0 0 0 7px rgba(49,163,221,0)}100%{box-shadow:0 0 0 0 rgba(49,163,221,0)}}
.inst{display:flex;align-items:center;gap:12px;background:var(--card);border:1px solid var(--line);
border-radius:var(--r);padding:14px;margin-bottom:11px;cursor:pointer;transition:border-color .15s}
.inst:hover{border-color:var(--line2)}
.inst.active{border-color:rgba(53,201,138,.4);background:linear-gradient(rgba(53,201,138,.05),transparent)}
.inst .dot{width:34px;height:34px;border-radius:10px;display:grid;place-items:center;font-size:17px;flex:none;
background:rgba(255,255,255,.05)}
.inst .body{flex:1;min-width:0}
.inst .name{font-family:'Poppins';font-weight:600;font-size:14.5px;display:flex;align-items:center;gap:8px}
.inst .url{color:var(--muted);font-size:12px;margin-top:2px;font-family:'Inter'}
.inst .uuid{color:var(--muted2);font-size:10.5px;margin-top:3px;font-family:ui-monospace,monospace}
.state{font-size:10.5px;font-weight:700;padding:3px 9px;border-radius:20px;white-space:nowrap}
.state.on{background:rgba(53,201,138,.14);color:var(--green)}
.state.av{background:rgba(49,163,221,.12);color:var(--blue)}
.state.off{background:rgba(255,255,255,.05);color:var(--grey)}
.sdot{width:8px;height:8px;border-radius:50%;flex:none}
.sdot.on{background:var(--green)}.sdot.off{background:var(--grey)}
.chevron{color:var(--muted2);font-size:18px}
.discover{border-style:dashed;border-color:rgba(49,163,221,.35);background:rgba(49,163,221,.04)}
.addchip{font-size:11px;font-weight:600;color:var(--blue);background:rgba(49,163,221,.12);
border:1px solid rgba(49,163,221,.3);border-radius:20px;padding:5px 12px}
.addbtn{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;margin-top:8px;
background:transparent;border:1px solid var(--line2);color:var(--txt);font-family:'Poppins';font-weight:600;
font-size:13px;padding:13px;border-radius:12px;cursor:pointer}
.demo{display:block;text-align:center;margin-top:16px;color:var(--muted2);font-size:12px;text-decoration:none}
.demo b{color:var(--yellow)}
/* auth */
.idhdr{display:flex;align-items:center;gap:12px;padding:14px;background:var(--card);border:1px solid var(--line);
border-radius:var(--r);margin-bottom:18px}
.idhdr .dot{width:38px;height:38px;border-radius:10px;background:rgba(49,163,221,.12);display:grid;place-items:center;
font-size:18px;color:var(--blue);flex:none}
.idhdr .name{font-family:'Poppins';font-weight:600;font-size:15px}
.idhdr .url{color:var(--muted);font-size:12px;margin-top:2px}
.idhdr .uuid{color:var(--muted2);font-size:10px;margin-top:2px;font-family:ui-monospace,monospace}
.seg{display:flex;background:var(--card2);border:1px solid var(--line);border-radius:11px;padding:4px;gap:4px;margin-bottom:6px}
.seg button{flex:1;border:0;background:transparent;color:var(--muted);font-family:'Poppins';font-weight:600;
font-size:12.5px;padding:10px;border-radius:8px;cursor:pointer}
.seg button.on{background:var(--blue);color:#04222f}
.seg-hint{font-size:11px;color:var(--muted2);margin:9px 2px 4px;line-height:1.45}
.inp{margin-top:13px}
.inp label{display:block;font-family:'Poppins';font-weight:600;font-size:12px;color:var(--muted);margin-bottom:7px}
.inp .box{display:flex;align-items:center;gap:10px;background:var(--card2);border:1px solid var(--line2);
border-radius:11px;padding:12px 13px}
.inp .box span{color:var(--muted2);font-size:15px}
.inp .box i{font-style:normal;color:var(--txt);font-size:13.5px;flex:1}
.inp .box i.ph{color:var(--muted2)}
.cta{width:100%;margin-top:20px;background:var(--blue);color:#04222f;border:0;border-radius:12px;padding:14px;
font-family:'Poppins';font-weight:600;font-size:14px;cursor:pointer}
.detect{margin-top:14px;padding:10px 12px;border-radius:11px;background:rgba(254,193,19,.06);
border:1px solid rgba(254,193,19,.18);color:#d8c79a;font-size:11px;line-height:1.5}
.detect b{color:var(--yellow)}
.hide{display:none}
</style>
</head>
<body>
<div class="head">
<h1>Installations — multi-HEMS</h1>
<p>Identité stable = <b>Server UUID</b> (pas l'IP, qui change en DHCP). Découverte <b>mDNS</b> en haut, <b>installations enregistrées</b> en dessous (token stocké par UUID), <b>ajout manuel</b> en repli. Une seule connexion <b>active</b> à la fois ; basculer = reconnecter. À droite, l'auth à deux branches : <b>créer</b> l'admin si la box est neuve, <b>se connecter</b> sinon.</p>
</div>
<div class="stage">
<!-- ÉCRAN 1 — INSTALLATIONS -->
<div class="col">
<div class="phone">
<div class="appbar"><div class="ic"></div><h2>Installations</h2><div class="ic"></div></div>
<div class="scroll">
<div class="sec"><h3>Détectées sur le réseau</h3><div class="scan"><span class="pulse"></span>recherche…</div></div>
<div class="inst discover">
<div class="dot">📡</div>
<div class="body">
<div class="name">nymea</div>
<div class="url">192.168.1.75 : 4444</div>
</div>
<div class="addchip">+ Ajouter</div>
</div>
<div class="sec"><h3>Mes installations</h3></div>
<div class="inst active">
<div class="dot"><span class="sdot on"></span></div>
<div class="body">
<div class="name">Site démo <span class="state on">actif</span></div>
<div class="url">nymea://192.168.1.75 : 4444</div>
<div class="uuid">3943203a-dc47-4d40-8a00-ff16cff1f46c</div>
</div>
</div>
<div class="inst">
<div class="dot"><span class="sdot on"></span></div>
<div class="body">
<div class="name">Client Kutzenhausen <span class="state av">disponible</span></div>
<div class="url">nymea://192.168.10.20 : 4444</div>
<div class="uuid">a1b2c3d4-…-9f8e</div>
</div>
<div class="chevron"></div>
</div>
<div class="inst">
<div class="dot"><span class="sdot off"></span></div>
<div class="body">
<div class="name">Atelier ETM <span class="state off">hors-ligne</span></div>
<div class="url">nymea://192.168.1.40 : 4444</div>
<div class="uuid">7c6b5a49-…-1d2e</div>
</div>
<div class="chevron"></div>
</div>
<button class="addbtn"> Ajouter manuellement</button>
<a href="#" class="demo">ou continuer en <b>mode démo</b></a>
</div>
</div>
<div class="cap">Liste · découverte · bascule</div>
</div>
<!-- ÉCRAN 2 — CONNEXION / AUTH -->
<div class="col">
<div class="phone">
<div class="appbar"><div class="ic"></div><h2>Connexion</h2><div class="ic"></div></div>
<div class="scroll">
<div class="idhdr">
<div class="dot">🔌</div>
<div style="flex:1">
<div class="name">nymea</div>
<div class="url">192.168.1.75 : 4444</div>
<div class="uuid">3943203a-dc47-4d40-8a00-ff16cff1f46c</div>
</div>
</div>
<div class="seg">
<button id="bNew" class="on" onclick="branch('new')">Nouvelle</button>
<button id="bExist" onclick="branch('exist')">Existante</button>
</div>
<div class="seg-hint" id="hint">Box neuve : aucun administrateur n'existe encore. Crée le compte qui pilotera cette installation.</div>
<!-- branche NOUVELLE -->
<div id="formNew">
<div class="inp"><label>Utilisateur (e-mail)</label>
<div class="box"><span></span><i class="ph">installateur@etm-schurig.eu</i></div></div>
<div class="inp"><label>Mot de passe</label>
<div class="box"><span>🔒</span><i class="ph">••••••••</i></div></div>
<div class="inp"><label>Confirmer le mot de passe</label>
<div class="box"><span>🔒</span><i class="ph">••••••••</i></div></div>
<button class="cta">Créer le compte administrateur</button>
</div>
<!-- branche EXISTANTE -->
<div id="formExist" class="hide">
<div class="inp"><label>Utilisateur</label>
<div class="box"><span></span><i class="ph">installateur@etm-schurig.eu</i></div></div>
<div class="inp"><label>Mot de passe</label>
<div class="box"><span>🔒</span><i class="ph">••••••••</i></div></div>
<button class="cta">Se connecter</button>
</div>
<div class="detect"><b>Détection auto.</b> L'app interroge la box au handshake : si un administrateur existe déjà, elle ouvre directement « Existante ». Le segmenteur ci-dessus n'est qu'un repli manuel. Le token obtenu est stocké <b>sous l'UUID</b> de cette box.</div>
</div>
</div>
<div class="cap">Auth · créer ou se connecter</div>
</div>
</div>
<script>
function branch(which){
const isNew = which==='new';
document.getElementById('bNew').classList.toggle('on', isNew);
document.getElementById('bExist').classList.toggle('on', !isNew);
document.getElementById('formNew').classList.toggle('hide', !isNew);
document.getElementById('formExist').classList.toggle('hide', isNew);
document.getElementById('hint').textContent = isNew
? "Box neuve : aucun administrateur n'existe encore. Crée le compte qui pilotera cette installation."
: "Box déjà initialisée : connecte-toi avec un compte existant.";
}
</script>
</body>
</html>