Files
moulin-mapper/web/client.html
Flag 7981c449c0 feat(web): présentation client simplifiée (état structurel + plan)
Page non-technique pour le client final : verdict structurel feu tricolore,
plan 2D coté chargé depuis assets/map_2d.csv, coupe hauteur d'eau, livrables.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-07 07:38:50 +00:00

403 lines
18 KiB
HTML
Raw 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>Cartographie de votre chambre de moulin à marée — Rapport client</title>
<style>
:root{
--navy:#0b2545;--navy2:#13315c;--blue:#1d5d9b;--blue-soft:#e7eef6;
--ink:#1a2433;--muted:#5c6b80;--line:#dce3ec;--bg:#f4f7fb;--card:#ffffff;
--green:#1f9d55;--green-bg:#e4f6ec;--orange:#e08a00;--orange-bg:#fdf1dd;
--red:#cc3b34;--red-bg:#fbe5e3;--water:#2a78b8;--water2:#8fc3e8;
--shadow:0 1px 3px rgba(11,37,69,.08),0 6px 20px rgba(11,37,69,.06);
}
*{box-sizing:border-box}
html,body{margin:0;padding:0}
body{
font-family:system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
color:var(--ink);background:var(--bg);line-height:1.55;
}
.wrap{max-width:1040px;margin:0 auto;padding:0 20px}
header.hero{
background:linear-gradient(135deg,var(--navy),var(--blue));
color:#fff;padding:48px 0 56px;
}
header.hero .wrap{display:flex;flex-direction:column;gap:10px}
.badge{align-self:flex-start;background:rgba(255,255,255,.15);border:1px solid rgba(255,255,255,.25);
padding:5px 12px;border-radius:999px;font-size:13px;letter-spacing:.3px}
header.hero h1{margin:6px 0 0;font-size:30px;font-weight:700;line-height:1.2}
header.hero p.sub{margin:0;font-size:17px;color:#dbe7f5;max-width:680px}
header.hero .meta{margin-top:8px;font-size:13px;color:#b9cce3}
section{padding:40px 0}
section:nth-of-type(even){background:#fff}
h2{font-size:22px;margin:0 0 6px;color:var(--navy)}
.lead{color:var(--muted);margin:0 0 24px;max-width:720px}
.grid{display:grid;gap:18px}
.cards4{grid-template-columns:repeat(4,1fr)}
.cards2{grid-template-columns:repeat(2,1fr)}
@media(max-width:880px){.cards4{grid-template-columns:repeat(2,1fr)}}
@media(max-width:560px){.cards4,.cards2{grid-template-columns:1fr}}
.card{background:var(--card);border:1px solid var(--line);border-radius:14px;
padding:20px;box-shadow:var(--shadow)}
.card .ico{width:44px;height:44px;border-radius:11px;background:var(--blue-soft);
display:flex;align-items:center;justify-content:center;margin-bottom:12px}
.card .ico svg{width:24px;height:24px;stroke:var(--blue);fill:none;stroke-width:2;
stroke-linecap:round;stroke-linejoin:round}
.card h3{margin:0 0 4px;font-size:16px}
.card p{margin:0;font-size:14px;color:var(--muted)}
/* plan */
.plan-box{background:var(--card);border:1px solid var(--line);border-radius:14px;
padding:16px;box-shadow:var(--shadow)}
#plan{width:100%;height:auto;display:block;border-radius:8px;background:#fbfdff}
.plan-stats{display:flex;flex-wrap:wrap;gap:14px;margin-top:16px}
.stat{flex:1;min-width:140px;background:var(--blue-soft);border-radius:10px;padding:12px 14px}
.stat .v{font-size:22px;font-weight:700;color:var(--navy)}
.stat .l{font-size:13px;color:var(--muted)}
/* table feu tricolore */
.verdict{display:flex;align-items:center;gap:14px;background:var(--green-bg);
border:1px solid #bfe6cd;border-left:6px solid var(--green);
border-radius:12px;padding:16px 18px;margin-bottom:20px}
.verdict .dot{width:14px;height:14px;border-radius:50%;background:var(--green);flex:0 0 auto}
.verdict strong{color:#13653a}
table.struct{width:100%;border-collapse:collapse;background:#fff;border-radius:12px;
overflow:hidden;box-shadow:var(--shadow)}
table.struct th,table.struct td{padding:13px 16px;text-align:left;font-size:14px;
border-bottom:1px solid var(--line)}
table.struct th{background:var(--navy);color:#fff;font-weight:600}
table.struct tr:last-child td{border-bottom:none}
.pill{display:inline-flex;align-items:center;gap:7px;font-weight:600;font-size:13px;
padding:4px 11px;border-radius:999px}
.pill .d{width:9px;height:9px;border-radius:50%}
.pill.ok{background:var(--green-bg);color:#13653a}.pill.ok .d{background:var(--green)}
.pill.warn{background:var(--orange-bg);color:#8a5500}.pill.warn .d{background:var(--orange)}
.pill.bad{background:var(--red-bg);color:#922b25}.pill.bad .d{background:var(--red)}
.legend{display:flex;gap:18px;flex-wrap:wrap;margin-top:14px;font-size:13px;color:var(--muted)}
.legend span{display:inline-flex;align-items:center;gap:6px}
.legend i{width:11px;height:11px;border-radius:50%;display:inline-block}
/* coupe */
.coupe-box{background:var(--card);border:1px solid var(--line);border-radius:14px;
padding:16px;box-shadow:var(--shadow)}
#coupe{width:100%;height:auto;display:block}
/* livrables */
ul.deliv{list-style:none;padding:0;margin:0;display:grid;gap:12px;
grid-template-columns:repeat(2,1fr)}
@media(max-width:560px){ul.deliv{grid-template-columns:1fr}}
ul.deliv li{background:#fff;border:1px solid var(--line);border-radius:12px;
padding:14px 16px;display:flex;gap:12px;align-items:flex-start;box-shadow:var(--shadow)}
ul.deliv .chk{width:24px;height:24px;border-radius:7px;background:var(--green-bg);
color:var(--green);font-weight:700;display:flex;align-items:center;justify-content:center;flex:0 0 auto}
ul.deliv b{display:block;font-size:15px}
ul.deliv small{color:var(--muted)}
.note{font-size:12px;color:var(--muted);font-style:italic;margin-top:14px}
footer{background:var(--navy);color:#cdd8e8;padding:26px 0;text-align:center;font-size:14px}
footer b{color:#fff}
</style>
</head>
<body>
<header class="hero">
<div class="wrap">
<span class="badge">Rapport client</span>
<h1>Cartographie de votre chambre de moulin à marée</h1>
<p class="sub">Mesure complète réalisée <strong>sans vider l'eau</strong> et <strong>sans aucun travaux</strong>.
Un robot sous-marin a relevé les murs, la profondeur et l'état des surfaces. Voici ce qu'on a trouvé.</p>
<p class="meta">Document de présentation — non technique</p>
</div>
</header>
<!-- CE QU'ON MESURE -->
<section>
<div class="wrap">
<h2>Ce qu'on mesure</h2>
<p class="lead">Quatre relevés simples pour comprendre l'état de votre chambre, en restant à la surface.</p>
<div class="grid cards4">
<div class="card">
<div class="ico"><svg viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 3v18"/></svg></div>
<h3>Le plan de la pièce</h3>
<p>Le contour exact des murs, vu de dessus, avec les longueurs et la surface au sol.</p>
</div>
<div class="card">
<div class="ico"><svg viewBox="0 0 24 24"><path d="M12 3v18M7 7l5-4 5 4M7 17l5 4 5-4"/></svg></div>
<h3>La hauteur d'eau</h3>
<p>La profondeur entre la surface et le fond, mesurée en continu pendant le relevé.</p>
</div>
<div class="card">
<div class="ico"><svg viewBox="0 0 24 24"><path d="M6 3v18M6 21h12M9 6l9 0M9 11l7 0M9 16l9 0"/></svg></div>
<h3>La verticalité des murs</h3>
<p>On vérifie si les murs sont bien droits ou s'ils penchent (le « dévers »).</p>
</div>
<div class="card">
<div class="ico"><svg viewBox="0 0 24 24"><path d="M4 4h16v16H4z"/><path d="M8 4l3 16M14 4l2 16"/></svg></div>
<h3>L'état des surfaces</h3>
<p>Détection des fissures, des bosses et des décrochements sur les parois.</p>
</div>
</div>
</div>
</section>
<!-- PLAN -->
<section>
<div class="wrap">
<h2>Le plan de la pièce</h2>
<p class="lead">Vue de dessus reconstruite à partir du relevé du robot. Chaque point correspond à un mur détecté.</p>
<div class="plan-box">
<canvas id="plan" width="980" height="640"></canvas>
<div class="plan-stats">
<div class="stat"><div class="v" id="st-len"></div><div class="l">Longueur max</div></div>
<div class="stat"><div class="v" id="st-wid"></div><div class="l">Largeur max</div></div>
<div class="stat"><div class="v" id="st-area"></div><div class="l">Surface au sol estimée</div></div>
<div class="stat"><div class="v" id="st-h">2,4 m</div><div class="l">Hauteur d'eau moyenne</div></div>
</div>
<p class="note" id="plan-note">Plan à l'échelle — graduations en mètres.</p>
</div>
</div>
</section>
<!-- ETAT STRUCTUREL -->
<section>
<div class="wrap">
<h2>État structurel</h2>
<p class="lead">Synthèse en code couleur. Vert = conforme, Orange = à surveiller, Rouge = action nécessaire.</p>
<div class="verdict">
<span class="dot"></span>
<div><strong>Structure jugée saine</strong> — aucun défaut majeur détecté. Une surveillance périodique est recommandée.</div>
</div>
<table class="struct">
<thead><tr><th>Critère</th><th>Mesure</th><th>État</th><th>Commentaire</th></tr></thead>
<tbody>
<tr>
<td>Verticalité des murs (dévers)</td><td>1,8° max</td>
<td><span class="pill ok"><span class="d"></span>Conforme</span></td>
<td>Murs globalement droits, sous le seuil de tolérance.</td>
</tr>
<tr>
<td>Planéité des parois</td><td>écart 2,3 cm</td>
<td><span class="pill ok"><span class="d"></span>Conforme</span></td>
<td>Surfaces régulières, pas de bombement notable.</td>
</tr>
<tr>
<td>Fissures détectées</td><td>1 zone mineure</td>
<td><span class="pill warn"><span class="d"></span>À surveiller</span></td>
<td>Microfissure verticale ~30 cm sur mur Est, sans ouverture.</td>
</tr>
<tr>
<td>Affaissement du fond</td><td>1,5 cm</td>
<td><span class="pill ok"><span class="d"></span>Conforme</span></td>
<td>Fond stable, pas de cuvette d'affaissement.</td>
</tr>
<tr>
<td>Intégrité globale</td><td></td>
<td><span class="pill ok"><span class="d"></span>Conforme</span></td>
<td>Aucun désaffleurement ni rupture structurelle observé.</td>
</tr>
</tbody>
</table>
<div class="legend">
<span><i style="background:var(--green)"></i>Conforme</span>
<span><i style="background:var(--orange)"></i>À surveiller</span>
<span><i style="background:var(--red)"></i>Action nécessaire</span>
</div>
<p class="note">Valeurs d'exemple — seront remplacées par les mesures réelles après traitement final du relevé.</p>
</div>
</section>
<!-- COUPE -->
<section>
<div class="wrap">
<h2>Coupe / hauteur d'eau</h2>
<p class="lead">Vue en coupe verticale : la surface, la colonne d'eau et le fond de la chambre.</p>
<div class="coupe-box">
<canvas id="coupe" width="980" height="360"></canvas>
</div>
</div>
</section>
<!-- LIVRABLES -->
<section>
<div class="wrap">
<h2>Ce que vous recevez</h2>
<p class="lead">L'ensemble des documents remis à la fin de la prestation.</p>
<ul class="deliv">
<li><span class="chk"></span><div><b>Plan 2D coté</b><small>Contour de la pièce avec dimensions — formats PDF et DXF (CAO).</small></div></li>
<li><span class="chk"></span><div><b>Nuage de points 3D</b><small>Modèle complet de la chambre — formats .ply / .las.</small></div></li>
<li><span class="chk"></span><div><b>Rapport structurel</b><small>Synthèse de l'état des murs, fissures et intégrité.</small></div></li>
<li><span class="chk"></span><div><b>Tableau de mesures</b><small>Longueurs, surface, hauteur d'eau et dévers des parois.</small></div></li>
</ul>
</div>
</section>
<footer>
<div class="wrap"><b>Silent Flow</b> / <b>Cosma</b> — projet moulin à marée</div>
</footer>
<script>
// ---- Fallback contour (échantillon réel lu dans assets/map_2d.csv) ----
var FALLBACK = [[0.01,4.02],[-0.54,-0.97],[2.65,4.0],[-3.95,1.52],[3.98,0.74],[-0.55,4.05],[2.87,-1.12],[-0.83,3.82],[-0.21,-1.21],[-1.0,4.92],[-3.03,2.79],[-1.44,2.75],[-0.81,5.26],[-3.85,4.42],[4.18,-0.28],[-2.86,6.65],[-3.02,2.52],[-1.97,6.28],[-1.39,2.36],[-2.37,6.0],[-0.61,3.34],[-2.29,5.79],[-3.58,1.36],[-0.69,2.96],[-3.5,3.24],[-0.81,-0.18],[-1.74,2.48],[-2.31,0.6],[0.99,3.57],[-2.77,-1.17],[1.24,3.76],[-3.52,0.65],[3.34,-1.05],[-0.81,2.99],[-0.4,-1.1],[4.29,2.2],[-1.24,-1.08],[4.12,1.69],[-3.82,5.22],[-0.06,-1.16],[3.98,3.52],[-4.01,2.97],[3.97,1.7],[0.54,3.51],[-1.1,0.6],[0.35,3.51],[-3.04,0.54],[4.12,1.61],[-1.78,2.46],[0.44,-1.68]];
function parseCSV(txt){
var pts=[],lines=txt.split(/\r?\n/);
for(var i=1;i<lines.length;i++){
var l=lines[i].trim();if(!l)continue;
var c=l.split(',');if(c.length<2)continue;
var x=parseFloat(c[0]),y=parseFloat(c[1]);
if(isFinite(x)&&isFinite(y))pts.push([x,y]);
}
return pts;
}
// convex hull (Andrew monotone chain) pour estimer surface + cotes
function hull(pts){
var p=pts.slice().sort(function(a,b){return a[0]-b[0]||a[1]-b[1]});
if(p.length<3)return p;
function cross(o,a,b){return (a[0]-o[0])*(b[1]-o[1])-(a[1]-o[1])*(b[0]-o[0]);}
var lo=[],up=[];
for(var i=0;i<p.length;i++){while(lo.length>=2&&cross(lo[lo.length-2],lo[lo.length-1],p[i])<=0)lo.pop();lo.push(p[i]);}
for(var j=p.length-1;j>=0;j--){while(up.length>=2&&cross(up[up.length-2],up[up.length-1],p[j])<=0)up.pop();up.push(p[j]);}
lo.pop();up.pop();return lo.concat(up);
}
function polyArea(poly){var a=0;for(var i=0;i<poly.length;i++){var j=(i+1)%poly.length;a+=poly[i][0]*poly[j][1]-poly[j][0]*poly[i][1];}return Math.abs(a)/2;}
function bbox(pts){
var xn=1e9,xx=-1e9,yn=1e9,yx=-1e9;
for(var i=0;i<pts.length;i++){var p=pts[i];
if(p[0]<xn)xn=p[0];if(p[0]>xx)xx=p[0];if(p[1]<yn)yn=p[1];if(p[1]>yx)yx=p[1];}
return {xn:xn,xx:xx,yn:yn,yx:yx};
}
function fmt(n){return n.toFixed(1).replace('.',',');}
function drawPlan(pts){
var cv=document.getElementById('plan'),ctx=cv.getContext('2d');
var W=cv.width,H=cv.height;
ctx.clearRect(0,0,W,H);ctx.fillStyle='#fbfdff';ctx.fillRect(0,0,W,H);
var b=bbox(pts);
var wx=b.xx-b.xn,wy=b.yx-b.yn;
var pad=70;
var sx=(W-2*pad)/wx,sy=(H-2*pad)/wy;
var s=Math.min(sx,sy);
var ox=(W-wx*s)/2,oy=(H-wy*s)/2;
// y inversé (haut = +y)
function X(x){return ox+(x-b.xn)*s;}
function Y(y){return H-(oy+(y-b.yn)*s);}
// grille metrique
ctx.strokeStyle='#e6edf5';ctx.lineWidth=1;ctx.fillStyle='#9aa9bd';
ctx.font='11px system-ui';ctx.textAlign='center';
for(var gx=Math.ceil(b.xn);gx<=b.xx;gx++){
var px=X(gx);ctx.beginPath();ctx.moveTo(px,oy);ctx.lineTo(px,H-oy);ctx.stroke();
ctx.fillText(gx+' m',px,H-oy+16);
}
ctx.textAlign='right';ctx.textBaseline='middle';
for(var gy=Math.ceil(b.yn);gy<=b.yx;gy++){
var py=Y(gy);ctx.beginPath();ctx.moveTo(ox,py);ctx.lineTo(W-ox,py);ctx.stroke();
ctx.fillText(gy+' m',ox-8,py);
}
ctx.textBaseline='alphabetic';
// hull (contour estimé)
var h=hull(pts);
if(h.length>2){
ctx.beginPath();ctx.moveTo(X(h[0][0]),Y(h[0][1]));
for(var i=1;i<h.length;i++)ctx.lineTo(X(h[i][0]),Y(h[i][1]));
ctx.closePath();
ctx.fillStyle='rgba(29,93,155,.10)';ctx.fill();
ctx.strokeStyle='#1d5d9b';ctx.lineWidth=2.5;ctx.stroke();
}
// points murs
ctx.fillStyle='#0b2545';
for(var k=0;k<pts.length;k++){ctx.fillRect(X(pts[k][0])-1,Y(pts[k][1])-1,2,2);}
// cotes longueur/largeur sur bbox
ctx.strokeStyle='#cc3b34';ctx.lineWidth=1.5;ctx.fillStyle='#922b25';
ctx.font='bold 13px system-ui';ctx.textAlign='center';
// largeur (axe x) en bas
var yb=H-oy+34;
ctx.beginPath();ctx.moveTo(X(b.xn),yb);ctx.lineTo(X(b.xx),yb);ctx.stroke();
ctx.fillText(fmt(wx)+' m',(X(b.xn)+X(b.xx))/2,yb-5);
// longueur (axe y) a droite
var xr=W-ox+34;
ctx.beginPath();ctx.moveTo(xr,Y(b.yn));ctx.lineTo(xr,Y(b.yx));ctx.stroke();
ctx.save();ctx.translate(xr+13,(Y(b.yn)+Y(b.yx))/2);ctx.rotate(-Math.PI/2);
ctx.fillText(fmt(wy)+' m',0,0);ctx.restore();
// echelle / regle 1m bas-gauche
var sl=s;ctx.strokeStyle='#0b2545';ctx.lineWidth=3;
ctx.beginPath();ctx.moveTo(18,H-22);ctx.lineTo(18+sl,H-22);ctx.stroke();
ctx.fillStyle='#0b2545';ctx.font='11px system-ui';ctx.textAlign='left';
ctx.fillText('1 m',18,H-28);
// stats
document.getElementById('st-len').textContent=fmt(wy)+' m';
document.getElementById('st-wid').textContent=fmt(wx)+' m';
var area=h.length>2?polyArea(h):wx*wy;
document.getElementById('st-area').textContent=fmt(area)+' m²';
}
function loadPlan(){
fetch('assets/map_2d.csv').then(function(r){
if(!r.ok)throw new Error('http '+r.status);return r.text();
}).then(function(t){
var pts=parseCSV(t);
if(pts.length<3)throw new Error('vide');
drawPlan(pts);
}).catch(function(e){
document.getElementById('plan-note').textContent=
'Plan à l\'échelle (aperçu hors-ligne) — graduations en mètres.';
drawPlan(FALLBACK);
});
}
function drawCoupe(){
var cv=document.getElementById('coupe'),ctx=cv.getContext('2d');
var W=cv.width,H=cv.height;ctx.clearRect(0,0,W,H);
var mL=70,mR=70,top=50,bot=H-50;
var surfY=top+10, fondY=bot-30;
// ciel
ctx.fillStyle='#fbfdff';ctx.fillRect(0,0,W,H);
// eau
var grad=ctx.createLinearGradient(0,surfY,0,fondY);
grad.addColorStop(0,'#8fc3e8');grad.addColorStop(1,'#2a78b8');
ctx.fillStyle=grad;ctx.fillRect(mL,surfY,W-mL-mR,fondY-surfY);
// fond (hachuré)
ctx.fillStyle='#7a6a55';ctx.fillRect(mL,fondY,W-mL-mR,bot-fondY);
ctx.strokeStyle='#5e5040';ctx.lineWidth=1;
for(var x=mL;x<W-mR;x+=14){ctx.beginPath();ctx.moveTo(x,fondY);ctx.lineTo(x+8,bot);ctx.stroke();}
// surface ligne + ondulation
ctx.strokeStyle='#1b5a8c';ctx.lineWidth=2;ctx.beginPath();
for(var sx=mL;sx<=W-mR;sx+=4){var yy=surfY+Math.sin((sx)/22)*2.5;sx==mL?ctx.moveTo(sx,yy):ctx.lineTo(sx,yy);}
ctx.stroke();
// labels
ctx.fillStyle='#0b2545';ctx.font='13px system-ui';ctx.textAlign='left';
ctx.fillText('Surface',mL+8,surfY-8);
ctx.fillStyle='#fff';ctx.fillText('Colonne d\'eau',mL+8,(surfY+fondY)/2);
ctx.fillStyle='#3a3328';ctx.fillText('Fond de la chambre',mL+8,bot-8);
// cote hauteur d'eau a gauche
ctx.strokeStyle='#cc3b34';ctx.lineWidth=1.5;ctx.fillStyle='#922b25';
var cx=mL-22;
ctx.beginPath();ctx.moveTo(cx,surfY);ctx.lineTo(cx,fondY);ctx.stroke();
function arr(y,d){ctx.beginPath();ctx.moveTo(cx,y);ctx.lineTo(cx-5,y+d);ctx.moveTo(cx,y);ctx.lineTo(cx+5,y+d);ctx.stroke();}
arr(surfY,8);arr(fondY,-8);
ctx.save();ctx.translate(cx-12,(surfY+fondY)/2);ctx.rotate(-Math.PI/2);
ctx.textAlign='center';ctx.font='bold 14px system-ui';ctx.fillText('2,4 m',0,0);ctx.restore();
// tirets cote vers surface/fond
ctx.strokeStyle='#cc3b34';ctx.setLineDash([4,3]);
ctx.beginPath();ctx.moveTo(cx,surfY);ctx.lineTo(mL,surfY);ctx.moveTo(cx,fondY);ctx.lineTo(mL,fondY);ctx.stroke();
ctx.setLineDash([]);
}
loadPlan();
drawCoupe();
</script>
</body>
</html>