const fields = { text: document.getElementById("mobileText"), tape: document.getElementById("mobileTape"), margin: document.getElementById("mobileMargin"), font: document.getElementById("mobileFont"), copies: document.getElementById("mobileCopies"), align: document.getElementById("mobileAlign"), bold: document.getElementById("mobileBold"), frame: document.getElementById("mobileFrame"), tapeInfo: document.getElementById("mobileTapeInfo"), tapeInfoType: document.getElementById("mobileTapeInfoType"), tapePrintableWidth: document.getElementById("mobileTapePrintableWidth"), tapeBgSwatch: document.getElementById("mobileTapeBgSwatch"), tapeBgText: document.getElementById("mobileTapeBgText"), tapeTextSwatch: document.getElementById("mobileTapeTextSwatch"), tapeTextText: document.getElementById("mobileTapeTextText"), preview: document.getElementById("mobilePreview"), message: document.getElementById("mobileMessage"), }; const steppers = { mobileCopies: { input: fields.copies, output: document.getElementById("mobileCopiesValue"), min: 1, max: 20, step: 1, suffix: "" }, mobileFont: { input: fields.font, output: document.getElementById("mobileFontValue"), min: 2, max: 24, step: 0.5, suffix: " mm" }, mobileMargin: { input: fields.margin, output: document.getElementById("mobileMarginValue"), min: 0, max: 20, step: 0.5, suffix: " mm" }, }; function estimateTextLengthMm(text, fontSize, margin, frame) { const lines = String(text || " ").split("\n"); const longest = lines.reduce((max, line) => Math.max(max, line.length), 0); const textWidth = Math.max(8, longest * fontSize * 0.62); return Math.min(300, Math.max(20, Math.ceil(textWidth + margin * 2 + (frame ? 4 : 0)))); } function clamp(value, min, max) { return Math.min(max, Math.max(min, value)); } function formatStepperValue(value) { return Number.isInteger(value) ? String(value) : value.toFixed(1).replace(".", ","); } function syncStepper(id) { const config = steppers[id]; const value = Number(config.input.value); config.output.textContent = `${formatStepperValue(value)}${config.suffix}`; } function setStepperValue(id, value) { const config = steppers[id]; const rounded = Math.round(value / config.step) * config.step; const next = clamp(rounded, config.min, config.max); config.input.value = String(next); syncStepper(id); mobileDesign(); } function setMessage(text, type = "") { fields.message.textContent = text; fields.message.className = `message ${type}`; } function updateTapeInfo(info) { fields.tapeInfo.hidden = !info; if (!info) return; const media = info.mediaType?.name || "Unbekannt"; const bg = info.tapeColor || { name: "Unbekannt", css: "#d1d5db" }; const text = info.textColor || { name: "Unbekannt", css: "#111111" }; fields.tapeInfoType.textContent = `${info.width} mm ยท ${media}${info.mediaType?.code ? ` (${info.mediaType.code})` : ""}`; fields.tapePrintableWidth.textContent = info.printableWidthPx ? `${info.printableWidthPx} px` : "-"; fields.tapeBgText.textContent = `${bg.name}${bg.code ? ` (${bg.code})` : ""}`; fields.tapeBgSwatch.style.background = bg.css; fields.tapeTextText.textContent = `${text.name}${text.code ? ` (${text.code})` : ""}`; fields.tapeTextSwatch.style.background = text.css; } function mobileDesign() { const tapeWidth = Number(fields.tape.value); const margin = Number(fields.margin.value); const text = fields.text.value.trim() || " "; const fontSize = Number(fields.font.value); const length = estimateTextLengthMm(text, fontSize, margin, fields.frame.checked); const objects = []; if (fields.frame.checked) { objects.push({ id: "frame", type: "shape", x: margin, y: margin, w: Math.max(2, length - margin * 2), h: Math.max(2, tapeWidth - margin * 2), lineMm: 0.25, fill: false, }); } objects.push({ id: "text", type: "text", x: margin + (fields.frame.checked ? 1 : 0), y: margin, w: Math.max(2, length - margin * 2 - (fields.frame.checked ? 2 : 0)), h: Math.max(2, tapeWidth - margin * 2), text, fontSizeMm: fontSize, bold: fields.bold.checked, align: fields.align.value, valign: "middle", }); return { tapeWidthMm: tapeWidth, lengthMm: length, autoLength: true, marginMm: margin, orientation: "landscape", cutMode: "auto", halfcut: "off", objects, }; } async function send(url) { setMessage("Rendere Etikett..."); const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ design: mobileDesign(), copies: Number(fields.copies.value) || 1 }), }); const payload = await response.json(); if (!payload.ok) { setMessage(payload.error || "Fehler", "error"); return; } fields.preview.src = `${payload.image}?t=${Date.now()}`; setMessage(payload.message || "Vorschau erstellt.", "success"); } document.getElementById("mobilePreviewBtn").addEventListener("click", () => send("/api/preview")); document.getElementById("mobilePrintBtn").addEventListener("click", () => send("/api/print")); document.getElementById("mobileDetect").addEventListener("click", async () => { setMessage("Erkenne Band..."); const response = await fetch("/api/tape-info"); const payload = await response.json(); if (!payload.ok) { setMessage(payload.error || "Band konnte nicht erkannt werden.", "error"); return; } fields.tape.value = String(payload.width); updateTapeInfo(payload); const bg = payload.tapeColor?.name ? `, ${payload.tapeColor.name}` : ""; const text = payload.textColor?.name ? ` / ${payload.textColor.name}` : ""; setMessage(`Band erkannt: ${payload.width} mm${bg}${text}`, "success"); }); ["text", "frame", "bold", "align", "tape"].forEach((name) => { fields[name].addEventListener("input", () => { mobileDesign(); }); }); document.querySelectorAll(".stepper").forEach((stepper) => { const id = stepper.dataset.stepper; stepper.querySelectorAll("button").forEach((button) => { button.addEventListener("click", () => { setStepperValue(id, Number(steppers[id].input.value) + Number(button.dataset.step)); }); }); syncStepper(id); }); mobileDesign();