/* Sigsprig app: form -> live preview -> copy / download / save / team ZIP. All data stays in this browser. * (c) 2026 CyberMax. All rights reserved. */ (function () { 'use strict'; var Sig = window.Sig, Pro = window.CMXPro; var APP = 'https://cybermax-tools-cybermax.static.hf.space/sigsprig/'; var LOCAL = new URL('./', location.href).href; var K_DRAFT = 'sigsprig:draft', K_SAVED = 'sigsprig:saved', K_TEAM = 'sigsprig:team'; var FREE_SAVES = 1; var $ = function (s, r) { return (r || document).querySelector(s); }; var $$ = function (s, r) { return Array.prototype.slice.call((r || document).querySelectorAll(s)); }; var state = { template: 'classic', data: null }; var last = null; function load(k, d) { try { var v = JSON.parse(localStorage.getItem(k) || 'null'); return v == null ? d : v; } catch (e) { return d; } } function store(k, v) { try { localStorage.setItem(k, JSON.stringify(v)); } catch (e) { /* private mode */ } } function toast(m) { Pro.toast(m); } function isPro() { return Pro.isPro(); } function tplInfo(id) { return Sig.TEMPLATES.filter(function (t) { return t.id === id; })[0] || Sig.TEMPLATES[0]; } function locked(id) { return !isPro() && !tplInfo(id).free; } function local(html) { return html.split(APP).join(LOCAL); } // ---------- build the form ---------- var fontSel = $('#f-font'); Object.keys(Sig.FONTS).forEach(function (f) { var o = document.createElement('option'); o.value = o.textContent = f; o.style.fontFamily = Sig.FONTS[f]; fontSel.appendChild(o); }); var sf = $('#social-fields'); Sig.SOCIALS.forEach(function (s) { var l = document.createElement('label'); l.innerHTML = ' ' + s[1] + ''; sf.appendChild(l); }); var tl = $('#tpl-list'); Sig.TEMPLATES.forEach(function (t) { var b = document.createElement('button'); b.type = 'button'; b.className = 'tpl'; b.setAttribute('role', 'radio'); b.dataset.tpl = t.id; b.innerHTML = '' + t.name + '' + t.desc + '' + (t.free ? '' : 'PRO'); b.onclick = function () { state.template = t.id; render(); save(); }; tl.appendChild(b); }); function collect() { var d = { socials: {} }; $$('[data-k]').forEach(function (el) { d[el.dataset.k] = el.value; }); $$('[data-s]').forEach(function (el) { d.socials[el.dataset.s] = el.value; }); d.fontSize = Number(d.fontSize); d.photoSize = Number(d.photoSize); d.logoWidth = Number(d.logoWidth); return d; } function fill(d) { d = d || {}; $$('[data-k]').forEach(function (el) { var v = d[el.dataset.k]; if (el.type === 'color') el.value = /^#[0-9a-f]{6}$/i.test(v || '') ? v.toLowerCase() : el.defaultValue; else if (el.tagName === 'SELECT') el.value = v != null && v !== '' ? String(v) : el.options[el.dataset.k === 'fontSize' ? 2 : 0].value; else if (el.type === 'range') el.value = v || el.defaultValue; else el.value = v == null ? '' : v; }); $$('[data-s]').forEach(function (el) { el.value = (d.socials || {})[el.dataset.s] || ''; }); } // ---------- render ---------- function render() { var data = collect(); state.data = data; var pro = isPro(); var b = Sig.build(data, { template: state.template, pro: pro }); last = b; $('#preview').innerHTML = local(b.html); // CTA preview for free users who filled it in var cp = $('#cta-preview'); if (!pro && Sig.hasCta(Sig.normalize(data))) { cp.hidden = false; cp.innerHTML = '
PRO Button and banner are added with Pro:
' + local(Sig.ctaPreview(data)); } else { cp.hidden = true; cp.innerHTML = ''; } var chk = Sig.check(b.html); var line = $('#check-line'); line.textContent = (chk.ok ? 'Email-safe ✓ · ' : 'Check ✗ · ') + chk.size.toLocaleString('en-US') + ' / 10,000 characters'; line.className = 'chip ' + (chk.ok && !chk.warnings.length ? 'good' : chk.ok ? 'warn' : 'bad'); $('#issues').innerHTML = chk.errors.concat(chk.warnings).map(function (m) { return '
  • ' + Sig.esc(m) + '
  • '; }).join(''); $$('.tpl').forEach(function (el) { var on = el.dataset.tpl === state.template; el.setAttribute('aria-checked', on ? 'true' : 'false'); el.classList.toggle('on', on); el.classList.toggle('locked', locked(el.dataset.tpl)); }); $('#tpl-note').textContent = locked(state.template) ? '"' + tplInfo(state.template).name + '" is a Pro template: preview it freely; copying and downloading need Pro.' : (pro ? 'All 12 templates unlocked.' : '4 of 12 templates are free. PRO templates can be previewed.'); $('#photoSize-out').textContent = data.photoSize; $('#logoWidth-out').textContent = data.logoWidth; $('#embed-warn').hidden = !/^data:/.test(data.photo || ''); renderSaved(); renderTeam(); } var timer; function save() { clearTimeout(timer); timer = setTimeout(function () { store(K_DRAFT, { template: state.template, data: collect() }); }, 250); } $('#editor').addEventListener('input', function () { render(); save(); }); $('#editor').addEventListener('change', function () { render(); save(); }); // ---------- outputs ---------- function gate(action) { if (locked(state.template)) { Pro.upsell('"' + tplInfo(state.template).name + '" is a Pro template. Pick a free one (Classic, Stacked, Compact, Bold bar) or unlock all 12.', 'template'); return false; } return true; } function note() { return last && last.ctaDropped ? ' (Without the button/banner: that is a Pro extra.)' : ''; } function copyRich(html, text) { if (navigator.clipboard && window.ClipboardItem && window.isSecureContext) { return navigator.clipboard.write([new ClipboardItem({ 'text/html': new Blob([html], { type: 'text/html' }), 'text/plain': new Blob([text], { type: 'text/plain' }) })]) .then(function () { return true; }, function () { return legacyCopy(html, true); }); } return Promise.resolve(legacyCopy(html, true)); } function copyText(text) { if (navigator.clipboard && window.isSecureContext) return navigator.clipboard.writeText(text).then(function () { return true; }, function () { return legacyCopy(text, false); }); return Promise.resolve(legacyCopy(text, false)); } function legacyCopy(content, rich) { var el = document.createElement(rich ? 'div' : 'textarea'); el.setAttribute('aria-hidden', 'true'); el.style.cssText = 'position:fixed;left:-9999px;top:0;background:#fff'; if (rich) { el.contentEditable = 'true'; el.innerHTML = content; } else el.value = content; document.body.appendChild(el); var ok = false; try { if (rich) { var r = document.createRange(); r.selectNodeContents(el); var s = getSelection(); s.removeAllRanges(); s.addRange(r); } else el.select(); ok = document.execCommand('copy'); } catch (e) { ok = false; } el.remove(); if (getSelection) getSelection().removeAllRanges(); return ok; } $('#copy-rich').onclick = function () { if (!gate()) return; copyRich(last.html, last.text).then(function (ok) { if (ok) { toast('Signature copied. Paste it into your email settings (guides below).' + note()); firstResult(); } else toast('Copy was blocked by the browser: select the preview and press Ctrl+C.'); }); }; $('#copy-html').onclick = function () { if (!gate()) return; copyText(last.html).then(function (ok) { toast(ok ? 'HTML source copied.' + note() : 'Copy was blocked by the browser.'); if (ok) firstResult(); }); }; $('#dl-htm').onclick = function () { if (!gate()) return; var d = Sig.normalize(state.data); Pro.download(Sig.fileName(d.name || 'signature', {}), Sig.doc(last.html, (d.name || 'Email') + ' signature'), 'text/html;charset=utf-8'); toast('Downloaded. Open it in a browser to copy, or use it with Outlook or Thunderbird.' + note()); firstResult(); }; function firstResult() { document.body.classList.add('has-result'); } // ---------- saved signatures ---------- function saved() { return load(K_SAVED, []); } function renderSaved() { var list = saved(), pro = isPro(); $('#limit-line').textContent = pro ? list.length + ' saved · unlimited with Pro' : list.length + ' of ' + FREE_SAVES + ' free saved signature used' + (list.length >= FREE_SAVES ? '. Pro saves unlimited signatures.' : '.'); $('#saved-list').innerHTML = list.map(function (s, i) { return '
  • ' + Sig.esc(s.label) + ' ' + Sig.esc(tplInfo(s.template).name) + ' · ' + Sig.esc(s.date) + '
  • '; }).join('') || '
  • Nothing saved yet.
  • '; } $('#save-sig').onclick = function () { var list = saved(); if (!isPro() && list.length >= FREE_SAVES) { Pro.upsell('The free plan keeps 1 saved signature. Pro saves as many as you like.', 'saves'); return; } var d = collect(); list.push({ label: (d.name || 'Signature') + (d.company ? ' · ' + d.company : ''), template: state.template, data: d, date: new Date().toISOString().slice(0, 10) }); store(K_SAVED, list); renderSaved(); toast('Saved in this browser.'); }; $('#saved-list').onclick = function (e) { var t = e.target, list = saved(); if (t.dataset.load != null) { var s = list[+t.dataset.load]; if (s) { state.template = s.template; fill(s.data); render(); save(); toast('Loaded "' + s.label + '".'); } } if (t.dataset.del != null) { list.splice(+t.dataset.del, 1); store(K_SAVED, list); renderSaved(); } }; // ---------- photo embed ---------- $('#photo-file').onchange = function () { var f = this.files && this.files[0]; if (!f) return; var fr = new FileReader(); fr.onload = function () { var im = new Image(); im.onload = function () { var px = Math.min(200, Math.max(96, (Number(collect().photoSize) || 84) * 2)); var cv = document.createElement('canvas'); cv.width = cv.height = px; var s = Math.min(im.width, im.height), cx = cv.getContext('2d'); cx.fillStyle = '#fff'; cx.fillRect(0, 0, px, px); cx.drawImage(im, (im.width - s) / 2, (im.height - s) / 2, s, s, 0, 0, px, px); $('#f-photo').value = cv.toDataURL('image/jpeg', 0.8); render(); save(); toast('Photo embedded (cropped square, resized to ' + px + ' px). It stays in this browser.'); }; im.src = fr.result; }; fr.readAsDataURL(f); }; // ---------- team mode ---------- var SAMPLE_TEAM = 'name,title,email,phone,photo\nMaya Torres,Founder & Lead Designer,maya@northfield.example,+1 (555) 014-2290,' + APP + 'sample/photo.png\nAna Ruiz,Operations Lead,ana@northfield.example,+1 (555) 014-2291,\nBo Chen,Account Manager,bo@northfield.example,+1 (555) 014-2292,\n"Okafor, Dee",Studio Assistant,dee@northfield.example,,'; var teamCache = []; function renderTeam() { var txt = $('#team-csv').value; var g = $('#team-gallery'), st = $('#team-status'); if (!txt.trim()) { g.innerHTML = ''; st.textContent = 'Paste a CSV or load the sample to see everyone\'s signature.'; teamCache = []; return; } var p = Sig.people(txt); var pro = isPro(); teamCache = Sig.team(state.data || collect(), p.people, { template: state.template, pro: pro }); var show = pro ? teamCache : teamCache.slice(0, 3); st.textContent = p.people.length + ' ' + (p.people.length === 1 ? 'person' : 'people') + ' · template ' + tplInfo(state.template).name + (pro ? '' : ' · free preview shows the first 3; the ZIP download is Pro') + (p.errors.length ? ' · ' + p.errors.join(' ') : ''); g.innerHTML = show.map(function (s) { return '
    ' + Sig.esc(s.name) + ' ' + Sig.esc(s.file) + '
    ' + local(s.html) + '
    '; }).join(''); } $('#team-csv').addEventListener('input', function () { store(K_TEAM, this.value); renderTeam(); }); $('#team-sample').onclick = function () { $('#team-csv').value = SAMPLE_TEAM; store(K_TEAM, SAMPLE_TEAM); renderTeam(); }; $('#team-file').onchange = function () { var f = this.files && this.files[0]; if (!f) return; f.text().then(function (t) { $('#team-csv').value = t; store(K_TEAM, t); renderTeam(); }); }; $('#team-zip').onclick = function () { if (!teamCache.length) { toast('Add your team first: paste a CSV or load the sample.'); return; } if (!isPro()) { Pro.upsell('Team mode downloads are part of Pro and Team. The Team licence covers up to 50 people.', 'team'); return; } if (locked(state.template)) return gate(); var files = teamCache.map(function (s) { return { name: s.file, data: Sig.doc(s.html, s.name + ' signature') }; }); files.unshift({ name: 'index.htm', data: Sig.gallery(teamCache, Sig.normalize(state.data).company) }); Pro.download('team-signatures.zip', new Blob([Sig.zip(files)], { type: 'application/zip' })); toast('ZIP with ' + teamCache.length + ' signatures downloaded.'); firstResult(); }; // ---------- backup ---------- $('#export-json').onclick = function () { var b = { app: 'sigsprig', version: 1, exported: new Date().toISOString(), draft: { template: state.template, data: collect() }, saved: saved(), team: $('#team-csv').value }; Pro.download('sigsprig-backup.json', JSON.stringify(b, null, 1), 'application/json'); }; $('#import-json').onchange = function () { var f = this.files && this.files[0]; if (!f) return; f.text().then(function (t) { var b; try { b = JSON.parse(t); } catch (e) { b = null; } if (!b || b.app !== 'sigsprig') { toast('That is not a Sigsprig backup file.'); return; } var list = Array.isArray(b.saved) ? b.saved : []; if (!isPro() && list.length > FREE_SAVES) { toast('Imported the first saved signature (free keeps 1; Pro keeps all).'); list = list.slice(0, FREE_SAVES); } store(K_SAVED, list); if (b.draft) { state.template = b.draft.template || 'classic'; fill(b.draft.data); } if (typeof b.team === 'string') { $('#team-csv').value = b.team; store(K_TEAM, b.team); } render(); save(); toast('Backup imported.'); }); }; $('#reset').onclick = function () { if (!confirm('Clear the form? Saved signatures are kept.')) return; state.template = 'classic'; fill({ photoShape: 'circle', iconStyle: 'dark', font: 'Arial', fontSize: 13, color: '#1F7A5A', textColor: '#1F2328' }); render(); save(); }; $('#load-example').onclick = function () { state.template = 'classic'; fill(Sig.EXAMPLE); render(); save(); $('#app').scrollIntoView({ behavior: 'smooth', block: 'start' }); }; // ---------- install guide tabs ---------- var tabs = $$('#install-tabs [role=tab]'); function pick(t) { tabs.forEach(function (x) { var on = x === t; x.setAttribute('aria-selected', on); x.tabIndex = on ? 0 : -1; $('#' + x.getAttribute('aria-controls')).hidden = !on; }); } tabs.forEach(function (t, i) { t.onclick = function () { pick(t); }; t.onkeydown = function (e) { var n = e.key === 'ArrowRight' ? 1 : e.key === 'ArrowLeft' ? -1 : 0; if (n) { var x = tabs[(i + n + tabs.length) % tabs.length]; pick(x); x.focus(); e.preventDefault(); } }; }); pick(tabs[/Mac|iPhone|iPad/.test(navigator.userAgent) && /iPhone|iPad/.test(navigator.userAgent) ? 4 : 0]); // ---------- Pro state ---------- Pro.onChange(function (on) { var pill = $('#pro-pill'); pill.textContent = on ? 'Pro ✓' : 'Pro'; pill.classList.toggle('on', on); $('#midcta').hidden = on; $('#free-line').textContent = on ? 'Pro is on: all templates, no credit link.' : 'Free: 4 templates · 1 saved signature · small credit link'; if (state.data) render(); }); // ---------- PWA ---------- var deferred = null; window.addEventListener('beforeinstallprompt', function (e) { e.preventDefault(); deferred = e; $('#install-btn').hidden = false; }); $('#install-btn').onclick = function () { if (!deferred) return; deferred.prompt(); deferred.userChoice.then(function () { deferred = null; $('#install-btn').hidden = true; }); }; if ('serviceWorker' in navigator && (location.protocol === 'https:' || location.hostname === 'localhost' || location.hostname === '127.0.0.1')) { navigator.serviceWorker.register('sw.js').catch(function () { /* offline cache is optional */ }); } // ---------- start ---------- var draft = load(K_DRAFT, null); if (draft && draft.data) { state.template = draft.template || 'classic'; fill(draft.data); } else fill(Sig.EXAMPLE); $('#team-csv').value = load(K_TEAM, '') || ''; render(); document.body.dataset.ready = '1'; })();