/* UTM Builder UI. © 2026 CyberMax. All rights reserved. */ (function () { "use strict"; var U = window.UtmCore; var el = function (id) { return document.getElementById(id); }; var KEY = "cybermax-utm:v1:"; function load(k, d) { try { var v = localStorage.getItem(KEY + k); return v ? JSON.parse(v) : d; } catch (e) { return d; } } function save(k, v) { try { localStorage.setItem(KEY + k, JSON.stringify(v)); } catch (e) { /* ignore */ } } function esc(s) { return String(s).replace(/[&<>"]/g, function (c) { return { "&": "&", "<": "<", ">": ">", '"': """ }[c]; }); } var toastT; function toast(m) { var t = el("toast"); t.textContent = m; t.classList.add("show"); clearTimeout(toastT); toastT = setTimeout(function () { t.classList.remove("show"); }, 1600); } function copy(text, msg) { var done = function () { toast(msg || "Copied"); }; if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(text).then(done, function () { fallback(text); done(); }); else { fallback(text); done(); } } function fallback(text) { var ta = document.createElement("textarea"); ta.value = text; document.body.appendChild(ta); ta.select(); try { document.execCommand("copy"); } catch (e) { /* ignore */ } ta.remove(); } function download(name, text, type) { var a = document.createElement("a"); a.href = URL.createObjectURL(new Blob([text], { type: type })); a.download = name; document.body.appendChild(a); a.click(); a.remove(); setTimeout(function () { URL.revokeObjectURL(a.href); }, 1000); } // ---------- tabs ---------- function showTab(name) { document.querySelectorAll(".tab").forEach(function (t) { t.setAttribute("aria-selected", String(t.dataset.tab === name)); }); document.querySelectorAll(".panel").forEach(function (p) { p.hidden = p.id !== "panel-" + name; }); if (name === "history") renderHistory(); } document.querySelectorAll(".tab").forEach(function (t) { t.onclick = function () { showTab(t.dataset.tab); }; }); // ---------- builder ---------- var fields = ["url"].concat(U.KEYS); function params() { var p = {}; U.KEYS.forEach(function (k) { p[k] = el("f-" + k).value; }); return p; } function opts() { return { lower: el("o-lower").checked, space: el("o-space").value }; } var current = ""; var presets = el("presets"); Object.keys(U.PRESETS).forEach(function (name) { var b = document.createElement("button"); b.type = "button"; b.className = "chip"; b.textContent = name; b.onclick = function () { var p = U.PRESETS[name]; for (var k in p) el("f-" + k).value = p[k]; presets.querySelectorAll(".chip").forEach(function (c) { c.classList.toggle("on", c === b); }); update(); }; presets.appendChild(b); }); function update() { var r = U.build(el("f-url").value, params(), opts()); var out = el("out"); if (r.error || !r.tagged) { current = ""; out.textContent = el("f-url").value.trim() ? (r.error || "Add a source, medium and campaign.") : "Fill in the URL, source, medium and campaign."; } else { current = r.url; out.textContent = r.url; } el("warn").innerHTML = (r.warnings || []).filter(function () { return !!current; }).map(function (w) { return "⚠ " + esc(w); }).join("
"); el("btn-copy").disabled = !current; el("btn-qr").disabled = !current; var open = el("btn-open"); if (current) { open.href = current; open.removeAttribute("aria-disabled"); } else { open.removeAttribute("href"); open.setAttribute("aria-disabled", "true"); } drawQr(current); var state = {}; fields.forEach(function (f) { state[f] = el("f-" + f).value; }); state.lower = el("o-lower").checked; state.space = el("o-space").value; save("form", state); } function drawQr(text) { var c = el("qr"), ctx = c.getContext("2d"); ctx.fillStyle = "#fff"; ctx.fillRect(0, 0, c.width, c.height); if (!text) { ctx.fillStyle = "#9aa4b6"; ctx.font = "14px system-ui, sans-serif"; ctx.textAlign = "center"; ctx.fillText("QR code appears here", c.width / 2, c.height / 2); return; } try { var qr = window.qrcode(0, "M"); qr.addData(text); qr.make(); var n = qr.getModuleCount(), quiet = 4, size = Math.floor(c.width / (n + quiet * 2)); var off = Math.floor((c.width - size * n) / 2); ctx.fillStyle = "#000"; for (var r = 0; r < n; r++) for (var col = 0; col < n; col++) if (qr.isDark(r, col)) ctx.fillRect(off + col * size, off + r * size, size, size); } catch (e) { ctx.fillStyle = "#b25e00"; ctx.font = "13px system-ui, sans-serif"; ctx.textAlign = "center"; ctx.fillText("Link too long for a QR code", c.width / 2, c.height / 2); } } function qrPng() { // Re-render at 1024px for print quality. var qr = window.qrcode(0, "M"); qr.addData(current); qr.make(); var n = qr.getModuleCount(), cell = Math.floor(1024 / (n + 8)), size = cell * (n + 8); var c = document.createElement("canvas"); c.width = c.height = size; var ctx = c.getContext("2d"); ctx.fillStyle = "#fff"; ctx.fillRect(0, 0, size, size); ctx.fillStyle = "#000"; for (var r = 0; r < n; r++) for (var col = 0; col < n; col++) if (qr.isDark(r, col)) ctx.fillRect((col + 4) * cell, (r + 4) * cell, cell, cell); var a = document.createElement("a"); a.href = c.toDataURL("image/png"); var camp = (el("f-utm_campaign").value || "link").toLowerCase().replace(/[^a-z0-9_-]+/g, "-"); a.download = "qr-" + camp + ".png"; document.body.appendChild(a); a.click(); a.remove(); } fields.forEach(function (f) { el("f-" + f).addEventListener("input", update); }); el("o-lower").addEventListener("change", update); el("o-space").addEventListener("change", update); el("btn-copy").onclick = function () { if (!current) return; copy(current, "Link copied"); addHistory(current); }; el("btn-qr").onclick = function () { if (current) qrPng(); }; el("btn-reset").onclick = function () { fields.forEach(function (f) { el("f-" + f).value = ""; }); presets.querySelectorAll(".chip").forEach(function (c) { c.classList.remove("on"); }); update(); el("f-url").focus(); }; // ---------- bulk ---------- var bulkRows = []; el("btn-bulk").onclick = function () { bulkRows = U.bulk(el("bulk-in").value, params(), opts()); var tb = el("bulk-table").querySelector("tbody"); tb.innerHTML = bulkRows.map(function (r, i) { return "" + (i + 1) + "" + esc(r.url || r.input) + "" + (r.error ? "" + esc(r.error) + "" : esc(r.warnings.filter(function (w) { return !/^Added https/.test(w); }).slice(0, 2).join(" "))) + ""; }).join(""); el("bulk-table").hidden = !bulkRows.length; var okN = bulkRows.filter(function (r) { return !r.error; }).length; el("btn-bulk-copy").disabled = el("btn-bulk-csv").disabled = !okN; toast(bulkRows.length ? okN + " of " + bulkRows.length + " links built" : "Paste some URLs first"); }; el("btn-bulk-copy").onclick = function () { copy(bulkRows.filter(function (r) { return !r.error; }).map(function (r) { return r.url; }).join("\n"), "All links copied"); }; el("btn-bulk-csv").onclick = function () { download("utm-links.csv", U.toCsv(bulkRows), "text/csv"); }; // ---------- decode ---------- var decoded = null; el("dec-in").addEventListener("input", function () { var v = el("dec-in").value.trim(), tb = el("dec-table").querySelector("tbody"); decoded = v ? U.parse(v) : null; if (!decoded || decoded.error) { tb.innerHTML = v ? "" + esc(decoded.error) + "" : ""; el("dec-clean").textContent = ""; return; } var keys = Object.keys(decoded.utm); tb.innerHTML = keys.length ? keys.map(function (k) { return "" + esc(k) + "" + esc(decoded.utm[k]) + ""; }).join("") : "No UTM parameters in this link."; el("dec-clean").textContent = decoded.clean; }); el("btn-dec-edit").onclick = function () { if (!decoded || decoded.error) return; el("f-url").value = decoded.clean; U.KEYS.forEach(function (k) { el("f-" + k).value = decoded.utm[k] || ""; }); showTab("single"); update(); }; // ---------- history ---------- function addHistory(url) { var h = load("history", []).filter(function (x) { return x.url !== url; }); h.unshift({ url: url, at: new Date().toISOString().slice(0, 16).replace("T", " ") }); save("history", h.slice(0, (window.CMXPro && CMXPro.isPro()) ? 500 : 25)); // Pro (pro.js): 500 } function renderHistory() { var h = load("history", []), tb = el("hist-table").querySelector("tbody"); tb.innerHTML = h.length ? h.map(function (x, i) { return "" + esc(x.at) + "" + esc(x.url) + ""; }).join("") : "Links you copy will appear here."; tb.querySelectorAll("button").forEach(function (b) { b.onclick = function () { copy(h[+b.dataset.i].url, "Link copied"); }; }); } el("btn-hist-clear").onclick = function () { save("history", []); renderHistory(); }; // ---------- restore ---------- var st = load("form", null); if (st) { fields.forEach(function (f) { if (st[f]) el("f-" + f).value = st[f]; }); el("o-lower").checked = st.lower !== false; if (st.space) el("o-space").value = st.space; } update(); window.UtmApp = { update: update, current: function () { return current; } }; if ("serviceWorker" in navigator && location.protocol === "https:") navigator.serviceWorker.register("sw.js").catch(function () {}); })();