197 lines
6.8 KiB
JavaScript
197 lines
6.8 KiB
JavaScript
/* 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 = `<img src="${dataUrl}" alt="">`;
|
||
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);
|