from pathlib import Path
import base64, json, textwrap, zipfile, os

base=Path('/home/agent/erne_presentation_review')
assets=base/'original_html_assets'
out=base/'ERNE_Beschaffung_3D_Prozess_HTML_Prototyp_v2.html'
slides=[]
for i,p in enumerate(sorted(assets.glob('slide-*.jpg')),1):
    b64=base64.b64encode(p.read_bytes()).decode('ascii')
    slides.append({'n':i,'img':f'data:image/jpeg;base64,{b64}'})

meta=[
 {'title':'Beschaffung und Backoffice','phase':'Opening','note':'CI-Originalfolie mit 3D-Raumwirkung als Einstieg.'},
 {'title':'Ziel der Präsentation','phase':'Zielbild','note':'Die Prozesslogik soll hängen bleiben – die Folien dienen später als Nachschlagewerk.'},
 {'title':'Grundsätze','phase':'Preis · Termin · Qualität','note':'Drei Achsen bleiben als 3D-Kompass sichtbar.'},
 {'title':'Basic@ERNE','phase':'Leitfaden','note':'Vom Basic in die operative Vergabe.'},
 {'title':'Praxishebel','phase':'Pareto / Preishebel','note':'Fokus auf die Positionen mit Wirkung.'},
 {'title':'Prozess Ausschreibung','phase':'Prozessstart','note':'Ziel, Risiken und Chancen der Vergabe.'},
 {'title':'Grundlagen','phase':'P1','note':'Intern/extern, Grundlagen, Termine, Leistungsbeschrieb.'},
 {'title':'Kontrollfragen','phase':'P2','note':'Grundsätze, Flussdiagramm, Informationsquellen.'},
 {'title':'Anfragen','phase':'P3','note':'Mindestanzahl Unternehmer und Marktbreite.'},
 {'title':'Varianten / Verhandlung','phase':'P4','note':'Varianten erkennen, nachverhandeln, zweite Abgebote.'},
 {'title':'Auswertung / Zusage','phase':'P5','note':'Vergabegespräch, Protokoll, Bonität, Zusage.'},
 {'title':'Absage / Werkvertrag','phase':'P6','note':'Sauber abschliessen, fair absagen, Vertrag sichern.'},
 {'title':'Weitere Prozesse','phase':'Übergang','note':'Weitere Basics als Nachschlagewerk.'},
 {'title':'Backoffice-Unterstützung','phase':'Backoffice','note':'Zentrale Abwicklung, Entlastung, Synergien.'},
 {'title':'Abgrenzung','phase':'B1','note':'Wer macht was, bis wann, in welcher Qualität?'},
 {'title':'Bearbeitungskette','phase':'B2','note':'LV → Kontrolle → Vergleich → Versand → Abgebote → Werkvertrag.'},
 {'title':'Prozessdetail','phase':'B3','note':'Detailprozess als Referenzfolie.'},
 {'title':'Abschluss','phase':'B4','note':'Digitaler Abschluss / Signatur / Freigabe.'},
 {'title':'Werkverträge','phase':'Sicherheiten','note':'Garantien und Risikoabsicherung früh prüfen.'},
]
process_nodes=[
 {'slide':7,'label':'P1 Grundlagen','x':12,'y':50,'info':'Start: Grundlagen, Termine und Leistungsbeschrieb klären.'},
 {'slide':8,'label':'P2 Kontrollfragen','x':28,'y':36,'info':'Kontrollfragen und Grundsätze prüfen.'},
 {'slide':9,'label':'P3 Anfragen','x':43,'y':50,'info':'Geeignete Unternehmer und Mindestanzahl Anfragen bestimmen.'},
 {'slide':10,'label':'P4 Varianten','x':58,'y':35,'info':'Varianten, technische Bereinigung und Nachverhandlung führen.'},
 {'slide':11,'label':'P5 Vergabe','x':74,'y':50,'info':'Auswertung, Bonität, Vergabegespräch und Zusage.'},
 {'slide':12,'label':'P6 Vertrag','x':89,'y':40,'info':'Absagen, Werkvertrag, Abschluss.'},
]
backoffice_nodes=[
 {'slide':15,'label':'B1 Rollen','x':18,'y':43,'info':'Abgrenzung: wer macht was bis wann?'},
 {'slide':16,'label':'B2 Bearbeitung','x':43,'y':48,'info':'LV-Bearbeitung, Kontrolle, Offertenvergleich, Versand.'},
 {'slide':17,'label':'B3 Detail','x':66,'y':42,'info':'Detailprozess als Nachschlagefolie.'},
 {'slide':18,'label':'B4 Abschluss','x':84,'y':48,'info':'Digitale Freigabe und Abschluss.'},
]

html=f'''<!doctype html>
<html lang="de"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>ERNE Beschaffung – 3D Prozess-Prototyp v2</title>
<style>
:root{{--erne:#005091;--erne2:#0070b8;--orange:#f28c28;--ink:#16324a;--muted:#617389;--glass:rgba(255,255,255,.78);--darkglass:rgba(0,30,60,.70)}}
*{{box-sizing:border-box}} html,body{{margin:0;height:100%;overflow:hidden;background:radial-gradient(circle at 30% 20%,#ffffff 0,#eaf2f8 38%,#cfdce8 100%);font-family:Aptos,Segoe UI,Arial,sans-serif;color:var(--ink)}}
#webgl{{position:fixed;inset:0;width:100vw;height:100vh;display:block;opacity:.92}}
.app{{position:fixed;inset:0;display:grid;grid-template-columns:86px 1fr 310px;gap:18px;padding:18px;perspective:1600px}}
.rail,.panel{{border:1px solid rgba(255,255,255,.65);background:var(--glass);box-shadow:0 24px 60px rgba(0,44,80,.18);backdrop-filter:blur(18px);border-radius:24px}}
.rail{{display:flex;flex-direction:column;align-items:center;padding:14px 8px;gap:10px;z-index:6}} .brand{{font-weight:950;color:var(--erne);letter-spacing:.08em;font-size:18px;margin:8px 0 14px;writing-mode:vertical-rl;transform:rotate(180deg)}}
.navdot{{width:42px;height:28px;border:0;border-radius:12px;background:#fff;color:#6a7d91;font-weight:900;cursor:pointer;box-shadow:0 4px 14px rgba(0,0,0,.08);transition:.22s}} .navdot:hover{{transform:translateX(3px);background:#eaf6ff}} .navdot.active{{background:var(--erne);color:#fff;transform:translateX(6px) scale(1.05)}}
.stageWrap{{display:grid;place-items:center;position:relative;min-width:0}}
.stage{{width:min(100%,calc((100vh - 36px)*16/9));aspect-ratio:16/9;position:relative;transform-style:preserve-3d;transition:transform .55s cubic-bezier(.2,.8,.2,1);filter:drop-shadow(0 35px 70px rgba(0,42,80,.32))}}
.slide{{position:absolute;inset:0;border-radius:22px;overflow:hidden;background:#fff;box-shadow:0 0 0 1px rgba(255,255,255,.7) inset;opacity:0;transform:translateZ(-120px) rotateY(10deg) scale(.93);transition:opacity .45s ease,transform .65s cubic-bezier(.16,.84,.28,1);pointer-events:none}}
.slide.active{{opacity:1;transform:translateZ(0) rotateY(0) scale(1);pointer-events:auto;z-index:3}} .slide.prev{{opacity:.20;transform:translateZ(-180px) rotateY(-18deg) translateX(-9%) scale(.88);z-index:1}} .slide.next{{opacity:.20;transform:translateZ(-180px) rotateY(18deg) translateX(9%) scale(.88);z-index:1}}
.slide img{{width:100%;height:100%;object-fit:cover;display:block}}
.hud{{position:absolute;left:22px;right:22px;bottom:18px;display:flex;align-items:center;gap:14px;z-index:10;pointer-events:none}} .chip{{padding:9px 14px;border-radius:999px;background:var(--darkglass);color:white;font-weight:800;font-size:14px;box-shadow:0 10px 30px rgba(0,40,80,.22)}} .progress{{flex:1;height:7px;border-radius:99px;background:rgba(255,255,255,.72);overflow:hidden;box-shadow:0 0 0 1px rgba(0,80,145,.15) inset}} .bar{{height:100%;background:linear-gradient(90deg,var(--erne),var(--orange));width:0;transition:.35s}}
.overlay{{position:absolute;inset:0;z-index:8;pointer-events:none}} .spot{{position:absolute;width:170px;height:170px;border-radius:50%;transform:translate(-50%,-50%);background:radial-gradient(circle,rgba(242,140,40,.52) 0,rgba(242,140,40,.18) 38%,rgba(242,140,40,0) 72%);filter:blur(1px);animation:pulse 1.6s ease-in-out infinite}} @keyframes pulse{{50%{{transform:translate(-50%,-50%) scale(1.18)}}}}
.route{{position:absolute;left:6%;right:6%;bottom:10%;height:90px;border-radius:26px;background:rgba(0,46,84,.70);box-shadow:0 18px 50px rgba(0,35,70,.28);backdrop-filter:blur(10px);display:flex;align-items:center;padding:0 30px;gap:12px;pointer-events:auto;transform:translateZ(70px)}}
.routeNode{{position:relative;min-width:54px;height:54px;border-radius:50%;background:white;border:3px solid #c9ddeb;display:grid;place-items:center;font-weight:950;color:var(--erne);cursor:pointer;transition:.22s}} .routeNode.on{{background:var(--orange);border-color:#fff;color:#fff;box-shadow:0 0 0 8px rgba(242,140,40,.25),0 0 34px rgba(242,140,40,.55);transform:translateY(-8px) scale(1.12)}} .routeLine{{height:4px;flex:1;background:linear-gradient(90deg,#fff,var(--erne2));border-radius:9px;opacity:.95}}
.floatingCard{{position:absolute;right:5%;top:11%;width:350px;padding:18px 20px;border-radius:20px;background:rgba(255,255,255,.88);backdrop-filter:blur(16px);box-shadow:0 18px 50px rgba(0,50,90,.25);border:1px solid rgba(255,255,255,.75);transform:translateZ(95px);pointer-events:auto}} .floatingCard b{{display:block;color:var(--erne);font-size:19px;margin-bottom:7px}} .floatingCard p{{margin:0;color:#4b5d70;line-height:1.35;font-size:15px}}
.compass{{position:absolute;left:5%;top:12%;display:flex;gap:14px;transform:translateZ(80px)}} .axis{{width:124px;height:124px;border-radius:26px;background:rgba(255,255,255,.85);display:grid;place-items:center;text-align:center;font-weight:950;color:var(--erne);box-shadow:0 16px 40px rgba(0,50,90,.18);animation:float 4s ease-in-out infinite}} .axis:nth-child(2){{animation-delay:.35s;color:var(--orange)}} .axis:nth-child(3){{animation-delay:.7s;color:#258459}} @keyframes float{{50%{{transform:translateY(-13px) rotateX(8deg)}}}}
.panel{{padding:18px 18px 16px;z-index:5;display:flex;flex-direction:column;gap:16px;min-width:0}} .panel h2{{margin:0;color:var(--erne);font-size:22px;letter-spacing:-.02em}} .phase{{font-size:13px;text-transform:uppercase;letter-spacing:.12em;color:var(--orange);font-weight:950}} .note{{font-size:15px;line-height:1.45;color:#40546a;background:white;border-radius:16px;padding:14px;box-shadow:inset 0 0 0 1px #e1ebf2}} .btnrow{{display:grid;grid-template-columns:1fr 1fr;gap:10px}} button.ctrl{{border:0;background:var(--erne);color:white;border-radius:14px;padding:12px;font-weight:900;cursor:pointer}} button.ctrl.alt{{background:white;color:var(--erne);box-shadow:inset 0 0 0 1px #d6e3ee}} .toggle{{display:flex;align-items:center;justify-content:space-between;gap:12px;background:white;border-radius:16px;padding:12px 14px;color:#40546a;font-weight:800}}
.timeline{{display:flex;flex-direction:column;gap:8px;overflow:auto;padding-right:4px}} .tl{{display:flex;gap:10px;align-items:center;background:white;border:0;border-radius:14px;padding:9px 10px;text-align:left;cursor:pointer;color:#40546a}} .tl span{{min-width:30px;height:24px;border-radius:9px;display:grid;place-items:center;background:#edf5fb;color:var(--erne);font-weight:950}} .tl.active{{background:#e6f4ff;box-shadow:inset 0 0 0 2px rgba(0,80,145,.25)}} .tl.process span{{background:#fff0df;color:#b65b00}}
.tip{{font-size:12px;color:#617389;line-height:1.35;margin-top:auto}} .kbd{{font-family:ui-monospace,Consolas,monospace;background:white;border:1px solid #d5e1ea;border-bottom-width:3px;border-radius:8px;padding:2px 6px;color:#29445d}}
body.clean .panel, body.clean .rail, body.clean .hud{{opacity:0;pointer-events:none}} body.clean .stage{{width:min(100%,calc(100vh*16/9));}} body.no3d #webgl{{display:none}} body.no3d .stage, body.no3d .floatingCard, body.no3d .route, body.no3d .compass{{transform:none!important}}
@media(max-width:1100px){{.app{{grid-template-columns:1fr;padding:10px}}.rail,.panel{{display:none}}.stage{{width:100%}}}}
</style></head><body>
<canvas id="webgl"></canvas>
<div class="app">
  <nav class="rail"><div class="brand">ERNE</div><div id="dots"></div></nav>
  <section class="stageWrap"><div class="stage" id="stage"><div id="slides"></div><div class="hud"><span class="chip" id="counter">1 / 19</span><div class="progress"><div class="bar" id="bar"></div></div><span class="chip" id="modeChip">3D Prozessmodus</span></div></div></section>
  <aside class="panel"><div class="phase" id="phase"></div><h2 id="ptitle"></h2><div class="note" id="note"></div><div class="btnrow"><button class="ctrl" id="prev">Zurück</button><button class="ctrl" id="next">Weiter</button></div><div class="toggle"><span>Clean Presenter Mode</span><button class="ctrl alt" id="clean">Toggle</button></div><div class="toggle"><span>3D reduzieren</span><button class="ctrl alt" id="reduce">Toggle</button></div><div class="timeline" id="timeline"></div><div class="tip"><b>Shortcuts:</b> <span class="kbd">←</span>/<span class="kbd">→</span> navigieren, <span class="kbd">O</span> Übersicht, <span class="kbd">C</span> Clean Mode, <span class="kbd">F</span> Fullscreen. Prozessfolien zeigen aktive Schritte entlang des Flussdiagramms.</div></aside>
</div>
<script>
const slides={json.dumps(slides, ensure_ascii=False)};
const meta={json.dumps(meta, ensure_ascii=False)};
const processNodes={json.dumps(process_nodes, ensure_ascii=False)};
const backofficeNodes={json.dumps(backoffice_nodes, ensure_ascii=False)};
let current=Number(localStorage.getItem('erne3d-current')||0); let overview=false;
const slidesEl=document.getElementById('slides'), dotsEl=document.getElementById('dots'), timeline=document.getElementById('timeline'), stage=document.getElementById('stage');
function overlayFor(i){{
  const n=i+1; const p=processNodes.find(x=>x.slide===n); const b=backofficeNodes.find(x=>x.slide===n);
  let html='<div class="overlay">';
  if(i===2) html+='<div class="compass"><div class="axis">Preis</div><div class="axis">Termin</div><div class="axis">Qualität</div></div>';
  if(p){{ html+=`<div class="spot" style="left:${{p.x}}%;top:${{p.y}}%"></div><div class="floatingCard"><b>${{p.label}}</b><p>${{p.info}}</p></div><div class="route">`; processNodes.forEach((node,idx)=>{{html+=`<div class="routeNode ${{node.slide===n?'on':''}}" data-goto="${{node.slide-1}}">${{idx+1}}</div>${{idx<processNodes.length-1?'<div class="routeLine"></div>':''}}`;}}); html+='</div>'; }}
  if(b){{ html+=`<div class="spot" style="left:${{b.x}}%;top:${{b.y}}%"></div><div class="floatingCard"><b>${{b.label}}</b><p>${{b.info}}</p></div><div class="route">`; backofficeNodes.forEach((node,idx)=>{{html+=`<div class="routeNode ${{node.slide===n?'on':''}}" data-goto="${{node.slide-1}}">B${{idx+1}}</div>${{idx<backofficeNodes.length-1?'<div class="routeLine"></div>':''}}`;}}); html+='</div>'; }}
  html+='</div>'; return html;
}}
function build(){{
 slidesEl.innerHTML=slides.map((s,i)=>`<article class="slide" data-i="${{i}}"><img src="${{s.img}}" alt="Folie ${{i+1}}">${{overlayFor(i)}}</article>`).join('');
 dotsEl.innerHTML=slides.map((s,i)=>`<button class="navdot" data-goto="${{i}}">${{i+1}}</button>`).join('');
 timeline.innerHTML=meta.map((m,i)=>`<button class="tl ${{(i>=6&&i<=11)||(i>=14&&i<=17)?'process':''}}" data-goto="${{i}}"><span>${{i+1}}</span><div><b>${{m.phase}}</b><br>${{m.title}}</div></button>`).join('');
 document.querySelectorAll('[data-goto]').forEach(el=>el.addEventListener('click',()=>go(Number(el.dataset.goto))));
}}
function update(){{
 document.querySelectorAll('.slide').forEach((el,i)=>{{el.classList.toggle('active',i===current); el.classList.toggle('prev',i===current-1); el.classList.toggle('next',i===current+1); if(overview){{const col=i%5,row=Math.floor(i/5); el.style.opacity='.95'; el.style.pointerEvents='auto'; el.style.transform=`translate3d(${{(col-2)*22}}%,${{(row-1.5)*22}}%,${{-260-row*45}}px) rotateY(${{(col-2)*-12}}deg) scale(.22)`;}} else el.style.transform=''; }});
 document.querySelectorAll('.navdot').forEach((el,i)=>el.classList.toggle('active',i===current)); document.querySelectorAll('.tl').forEach((el,i)=>el.classList.toggle('active',i===current));
 document.getElementById('counter').textContent=`${{current+1}} / ${{slides.length}}`; document.getElementById('bar').style.width=`${{((current+1)/slides.length)*100}}%`;
 document.getElementById('phase').textContent=meta[current].phase; document.getElementById('ptitle').textContent=meta[current].title; document.getElementById('note').textContent=meta[current].note;
 document.getElementById('modeChip').textContent=((current>=6&&current<=11)||(current>=14&&current<=17))?'Animierter Prozesspfad':'3D CI-Modus'; localStorage.setItem('erne3d-current',current);
}}
function go(i){{overview=false; current=(i+slides.length)%slides.length; update();}}
document.getElementById('prev').onclick=()=>go(current-1); document.getElementById('next').onclick=()=>go(current+1); document.getElementById('clean').onclick=()=>document.body.classList.toggle('clean'); document.getElementById('reduce').onclick=()=>document.body.classList.toggle('no3d');
addEventListener('keydown',e=>{{if(e.key==='ArrowRight'||e.key===' ')go(current+1); if(e.key==='ArrowLeft')go(current-1); if(e.key.toLowerCase()==='c')document.body.classList.toggle('clean'); if(e.key.toLowerCase()==='f')document.documentElement.requestFullscreen?.(); if(e.key.toLowerCase()==='o'){{overview=!overview; update();}} if(e.key==='Escape'){{overview=false;update();}} }});
addEventListener('mousemove',e=>{{if(document.body.classList.contains('no3d')) return; const x=(e.clientX/innerWidth-.5), y=(e.clientY/innerHeight-.5); stage.style.transform=`rotateY(${{x*7}}deg) rotateX(${{-y*5}}deg)`;}});
build(); update();
// lightweight 3D canvas engine: rotating procurement network / construction grid, no external CDN
const canvas=document.getElementById('webgl'),ctx=canvas.getContext('2d'); let W,H,DPR,pts=[];
function resize(){{DPR=devicePixelRatio||1; W=canvas.width=innerWidth*DPR; H=canvas.height=innerHeight*DPR; pts=[]; for(let z=-8; z<=8; z++) for(let x=-9; x<=9; x+=2) pts.push({{x,y:Math.sin(x*.7+z)*.55,z}});}} resize(); addEventListener('resize',resize);
function project(p,t){{const cy=Math.cos(t*.18),sy=Math.sin(t*.18), cx=Math.cos(.75),sx=Math.sin(.75); let x=p.x*cy-p.z*sy, z=p.x*sy+p.z*cy+18, y=p.y*cx-z*sx*.05; const f=820*DPR/(z+12); return {{x:W/2+x*f,y:H/2+y*f+40*DPR,f,z}}}}
function draw(t){{t*=.001; ctx.clearRect(0,0,W,H); ctx.lineWidth=1.1*DPR; const pp=pts.map(p=>project(p,t)); for(let i=0;i<pp.length;i++){{for(let j=i+1;j<pp.length;j++){{const a=pp[i],b=pp[j],dx=a.x-b.x,dy=a.y-b.y,d=Math.hypot(dx,dy); if(d<105*DPR){{ctx.globalAlpha=Math.max(0,.22-d/(105*DPR)*.18); ctx.strokeStyle='#005091'; ctx.beginPath(); ctx.moveTo(a.x,a.y); ctx.lineTo(b.x,b.y); ctx.stroke();}}}}}} ctx.globalAlpha=1; for(const a of pp){{ctx.fillStyle='rgba(0,80,145,.42)'; ctx.beginPath(); ctx.arc(a.x,a.y,Math.max(1.5,a.f*.035),0,Math.PI*2); ctx.fill();}} requestAnimationFrame(draw)}} requestAnimationFrame(draw);
</script></body></html>'''
out.write_text(html, encoding='utf-8')
print(out)
print('size_mb', round(out.stat().st_size/1024/1024,2))
zip_path=base/'ERNE_Beschaffung_3D_HTML_Prototyp_v2.zip'
with zipfile.ZipFile(zip_path,'w',zipfile.ZIP_DEFLATED) as z:
    z.write(out, out.name)
print(zip_path)
print('zip_mb', round(zip_path.stat().st_size/1024/1024,2))
