/* Card Generator — frontend */ const $ = (id) => document.getElementById(id); const preview = $("preview-frame"); const status = $("status"); let state = { eyebrow: "RÉALISATION · SYMFONY", title: "pokedwwmm", title_2: "", tagline: "151 Pokémon · Twig Components · JSON", accent: "#ef4444", logo_color:"#ef4444", logo_kind: "preset", // preset | upload | text | none logo_value:"symfony", logo_size: 260, }; let renderTimer = null; let logoLibrary = []; /* ── Wire up form ──────────────────────────────────────────────── */ const inputBindings = { eyebrow: "eyebrow", title: "title", title_2: "title_2", tagline: "tagline", accent: "accent", logo_color: "logo_color", }; for (const [stateKey, inputId] of Object.entries(inputBindings)) { $(inputId).addEventListener("input", (e) => { state[stateKey] = e.target.value; schedule(); }); } $("logo_size").addEventListener("input", (e) => { state.logo_size = +e.target.value; $("logo_size_val").value = `${state.logo_size}px`; schedule(); }); /* Swatches */ document.querySelectorAll(".swatch").forEach(btn => { const c = btn.dataset.color; btn.style.background = c; btn.addEventListener("click", () => { state.accent = c; state.logo_color = c; $("accent").value = c; $("logo_color").value = c; schedule(); }); }); /* Segmented control: logo source */ document.querySelectorAll(".seg-btn").forEach(btn => { btn.addEventListener("click", () => { document.querySelectorAll(".seg-btn").forEach(b => b.classList.remove("active")); btn.classList.add("active"); state.logo_kind = btn.dataset.kind; document.querySelectorAll(".logo-source").forEach(el => { el.hidden = true; }); if (state.logo_kind === "preset") document.querySelector(".preset-source").hidden = false; if (state.logo_kind === "upload") document.querySelector(".upload-source").hidden = false; if (state.logo_kind === "text") document.querySelector(".text-source").hidden = false; schedule(); }); }); /* Text logo input */ $("logo_text").addEventListener("input", (e) => { if (state.logo_kind === "text") state.logo_value = e.target.value; schedule(); }); /* Upload */ $("logo_upload").addEventListener("change", async (e) => { const file = e.target.files[0]; if (!file) return; const dataUrl = await new Promise((res, rej) => { const r = new FileReader(); r.onload = () => res(r.result); r.onerror = rej; r.readAsDataURL(file); }); state.logo_kind = "upload"; state.logo_value = dataUrl; const preview = $("upload_preview"); preview.hidden = false; preview.innerHTML = ``; schedule(); }); /* ── Logo library ──────────────────────────────────────────────── */ async function loadLogoLibrary() { const res = await fetch("/api/logos"); logoLibrary = await res.json(); const grid = $("logo_grid"); grid.innerHTML = ""; for (const slug of logoLibrary) { const tile = document.createElement("div"); tile.className = "logo-tile"; tile.title = slug; if (slug === state.logo_value) tile.classList.add("selected"); tile.dataset.slug = slug; const svgRes = await fetch(`/logos/${slug}.svg`); tile.innerHTML = await svgRes.text(); tile.addEventListener("click", () => { grid.querySelectorAll(".logo-tile").forEach(t => t.classList.remove("selected")); tile.classList.add("selected"); state.logo_kind = "preset"; state.logo_value = slug; // Also update segmented control document.querySelectorAll(".seg-btn").forEach(b => b.classList.toggle("active", b.dataset.kind === "preset")); document.querySelectorAll(".logo-source").forEach(el => { el.hidden = true; }); document.querySelector(".preset-source").hidden = false; schedule(); }); grid.appendChild(tile); } } /* ── Render pipeline ───────────────────────────────────────────── */ function schedule() { if (renderTimer) clearTimeout(renderTimer); status.textContent = "rendu…"; status.className = "status working"; renderTimer = setTimeout(render, 120); } async function render() { try { const res = await fetch("/api/svg", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(state), }); if (!res.ok) throw new Error(`HTTP ${res.status}`); const svgText = await res.text(); preview.innerHTML = svgText; status.textContent = "à jour"; status.className = "status ok"; } catch (e) { console.error(e); status.textContent = "erreur"; status.className = "status err"; } } /* ── Download buttons ──────────────────────────────────────────── */ $("btn-svg").addEventListener("click", async () => { status.textContent = "génération SVG…"; status.className = "status working"; const res = await fetch("/api/svg", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(state), }); const blob = await res.blob(); downloadBlob(blob, `${$("filename").value || "card"}.svg`); status.textContent = "SVG téléchargé"; status.className = "status ok"; }); $("btn-png").addEventListener("click", async () => { status.textContent = "rendu PNG (rsvg-convert)…"; status.className = "status working"; const width = +$("export_width").value; const height = Math.round(width / 1.6); try { const res = await fetch("/api/png", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ ...state, export_width: width, export_height: height, filename: $("filename").value || "card" }), }); if (!res.ok) throw new Error(await res.text()); const blob = await res.blob(); downloadBlob(blob, `${$("filename").value || "card"}.png`); status.textContent = `PNG ${width}×${height} téléchargé`; status.className = "status ok"; } catch (e) { console.error(e); status.textContent = "erreur PNG"; status.className = "status err"; } }); function downloadBlob(blob, filename) { const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url); } /* ── Init ──────────────────────────────────────────────────────── */ loadLogoLibrary().then(render);