Spaces:
Running
Running
Download sigsprig/app.js from CyberMax-tools/cybermax: direct link, hf CLI and curl.
- Browser
- Download file 17.2 kB
-
https://huggingface.co/spaces/CyberMax-tools/cybermax/resolve/main/sigsprig/app.js
- Command line
-
hf download hf://spaces/CyberMax-tools/cybermax/sigsprig/app.js
-
curl -L -o app.js https://huggingface.co/spaces/CyberMax-tools/cybermax/resolve/main/sigsprig/app.js
17.2 kB
| /* 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 = '<span class="soc"><img src="icons/dark/' + s[0] + '.png" width="16" height="16" alt=""> ' + s[1] + '</span><input data-s="' + s[0] + '" placeholder="' + (s[0] === 'calendar' ? 'https://cal.com/you' : s[0] === 'whatsapp' ? '+1 555 010 2290' : '@handle or link') + '">'; | |
| 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 = '<b>' + t.name + '</b><span>' + t.desc + '</span>' + (t.free ? '' : '<i class="pro-tag">PRO</i>'); | |
| 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 = '<div class="pro-preview"><span class="pro-tag">PRO</span> Button and banner are added with Pro:</div>' + 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 '<li>' + Sig.esc(m) + '</li>'; }).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 '<li><span><b>' + Sig.esc(s.label) + '</b> <small>' + Sig.esc(tplInfo(s.template).name) + ' · ' + Sig.esc(s.date) + '</small></span><span><button type="button" class="btn ghost small" data-load="' + i + '">Load</button> <button type="button" class="btn ghost small" data-del="' + i + '" aria-label="Delete ' + Sig.esc(s.label) + '">Delete</button></span></li>'; | |
| }).join('') || '<li class="muted">Nothing saved yet.</li>'; | |
| } | |
| $('#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 '<figure class="g-item"><figcaption>' + Sig.esc(s.name) + ' <small>' + Sig.esc(s.file) + '</small></figcaption><div class="g-sig">' + local(s.html) + '</div></figure>'; }).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'; | |
| })(); | |