Générateur de page publique figée

Sortie : index_public.html (config + chapitres + logo embarqués)
Le logo est embarqu\u00e9 (data URL) dans la page publique.
Format attendu : [{"t":1296,"title":"...","meta":"..."}]
Principe : tu paramètres ici, puis tu publies une page figée.
La sortie index_public.html ne contient aucune UI admin et ne dépend d'aucun fichier (logo/chapitres).
`; } function previewLoaded(){ const box = document.getElementById("chaptersPreview"); if(!chapters){ box.value = "Aucun fichier chargé…"; return; } const first = chapters.slice(0, 12).map(c=>`${c.t}s — ${c.title}${c.meta?(" | "+c.meta):""}`).join("\n"); box.value = `OK (${chapters.length} chapitres)\n` + first + (chapters.length>12 ? "\n…" : ""); } function setLogoPreview(){ const box = document.getElementById("logoBox"); box.innerHTML = ""; if(!logoDataUrl){ const s = document.createElement("span"); s.className = "note"; s.textContent = "—"; box.appendChild(s); return; } const img = document.createElement("img"); img.src = logoDataUrl; box.appendChild(img); } document.getElementById("fileLogo").addEventListener("change", async (e)=>{ const f = e.target.files && e.target.files[0]; if(!f) return; // Read as DataURL (works for png/jpg/svg) const r = new FileReader(); r.onload = ()=>{ logoDataUrl = r.result; setLogoPreview(); }; r.onerror = ()=>{ alert("Lecture logo impossible."); }; r.readAsDataURL(f); e.target.value = ""; }); document.getElementById("btnClearLogo").addEventListener("click", ()=>{ logoDataUrl = ""; setLogoPreview(); }); document.getElementById("fileChapters").addEventListener("change", async (e)=>{ const f = e.target.files && e.target.files[0]; if(!f) return; try{ const text = await f.text(); const data = JSON.parse(text); if(!Array.isArray(data)) throw new Error("JSON non conforme : tableau attendu"); data.forEach((c,i)=>{ if(typeof c.t !== "number") throw new Error("t manquant ou non numérique (ligne "+(i+1)+")"); }); chapters = data; previewLoaded(); }catch(err){ alert("Impossible de charger chapters.json : " + err.message); chapters = null; previewLoaded(); } e.target.value = ""; }); document.getElementById("btnLoadDemo").addEventListener("click", ()=>{ document.getElementById("title").value = "Conseil municipal"; document.getElementById("date").value = "18/12/2025"; document.getElementById("vimeoId").value = "1147755932"; document.getElementById("vimeoHash").value = ""; document.getElementById("branding").value = "Ville de Nice"; chapters = DEMO; previewLoaded(); }); document.getElementById("btnBuild").addEventListener("click", ()=>{ const cfg = { title: (document.getElementById("title").value || "").trim() || "Conseil municipal", date: (document.getElementById("date").value || "").trim() || "—", branding: (document.getElementById("branding").value || "").trim(), vimeo: (document.getElementById("vimeoId").value || "").trim(), hash: (document.getElementById("vimeoHash").value || "").trim() }; if(!cfg.vimeo){ alert("ID Vimeo obligatoire."); return; } if(!chapters){ alert("Charge d'abord un chapters.json (ou clique 'Charger démo')."); return; } const html = buildPublicHtml(cfg, chapters, logoDataUrl); download("index_public.html", html, "text/html"); }); previewLoaded(); setLogoPreview();