← back to CelebritySignatures
public/upload-signature.html
178 lines
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Upload Your Signature — Celebrity Signatures</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,600;1,400&display=swap" rel="stylesheet">
<style>
:root { --ink:#1a1a1a; --muted:#6b6b6b; --line:#e6e3dc; --bg:#f7f5f0; --card:#fff; --green:#1d7a36; }
* { box-sizing:border-box; }
body { margin:0; font:15px/1.55 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif; color:var(--ink); background:var(--bg); }
header { padding:26px 28px 18px; border-bottom:1px solid var(--line); background:#fff; }
h1 { margin:0; font:italic 600 24px/1.2 "Playfair Display", Georgia, serif; }
h1 a { color:var(--muted); text-decoration:none; font-size:14px; font-weight:400; margin-left:10px; }
main { max-width:760px; margin:0 auto; padding:28px 20px 80px; }
.card { background:var(--card); border:1px solid var(--line); border-radius:14px; padding:22px 24px; margin-bottom:22px; }
.card h2 { margin:0 0 8px; font:600 19px/1.3 "Playfair Display", Georgia, serif; }
.sub { color:var(--muted); font-size:13.5px; }
label { display:block; font-size:12px; text-transform:uppercase; letter-spacing:.06em; color:var(--muted); margin:14px 0 5px; }
input[type=text], input[type=email], input[type=password] { width:100%; padding:9px 12px; border:1px solid var(--line); border-radius:8px; font:inherit; }
input[type=file] { font:inherit; }
.btn { display:inline-block; margin-top:14px; padding:9px 20px; border-radius:999px; border:1px solid var(--ink); background:var(--ink); color:#fff; font:inherit; cursor:pointer; }
.btn:disabled { opacity:.5; cursor:default; }
.btn.ghost { background:#fff; color:var(--ink); }
.msg { margin-top:10px; font-size:13.5px; }
.msg.err { color:#b3261e; } .msg.ok { color:var(--green); }
.attest { display:flex; gap:9px; align-items:flex-start; margin-top:14px; font-size:13.5px; color:var(--muted); }
.attest input { margin-top:3px; }
table { width:100%; border-collapse:collapse; margin-top:10px; font-size:13.5px; }
th, td { text-align:left; padding:7px 8px; border-bottom:1px solid var(--line); }
th { font-size:11px; text-transform:uppercase; letter-spacing:.05em; color:var(--muted); }
.st { font-size:11px; padding:2px 9px; border-radius:999px; font-weight:600; }
.st-pending { background:#fdf1d8; color:#9a6b00; } .st-approved { background:#e3f3e6; color:#1d7a36; } .st-rejected { background:#fae0df; color:#b3261e; }
.total { font-size:15px; margin-top:12px; }
.total b { color:var(--green); }
.privacy { font-size:12.5px; color:var(--muted); border-top:1px dashed var(--line); margin-top:14px; padding-top:12px; }
#preview { max-height:90px; max-width:100%; object-fit:contain; margin-top:10px; display:none; background:#fff; border:1px solid var(--line); border-radius:8px; padding:6px; }
</style>
<!-- Google Analytics 4 (G-2HEVP6TD0J) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-2HEVP6TD0J"></script>
<script>window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);}gtag('js',new Date());gtag('config','G-2HEVP6TD0J');</script>
<script src="/assets/ga-events.js" defer></script>
<link rel="canonical" href="https://celebsignatures.com/upload">
<meta name="description" content="Own your signature. Upload it, keep it private, and earn a commission on every download.">
<link rel="apple-touch-icon" href="/assets/favicon.png">
<link rel="icon" type="image/png" href="/assets/favicon.png">
<meta property="og:type" content="website">
<meta property="og:site_name" content="Celebrity Signatures">
<meta property="og:title" content="Upload Your Signature — earn a commission">
<meta property="og:description" content="Own your signature. Upload it, keep it private, and earn a commission on every download.">
<meta property="og:url" content="https://celebsignatures.com/upload">
<meta property="og:image" content="https://celebsignatures.com/assets/og-share.jpg">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Upload Your Signature — earn a commission">
<meta name="twitter:description" content="Own your signature. Upload it, keep it private, and earn a commission on every download.">
<meta name="twitter:image" content="https://celebsignatures.com/assets/og-share.jpg">
</head>
<body>
<header><h1>Upload Your Signature <a href="/">← back to the gallery</a></h1></header>
<main>
<div class="card">
<h2>Your signature. Your account. Your commission.</h2>
<div style="display:flex;gap:26px;align-items:center;margin:14px 0 10px">
<div><div style="font-size:40px;font-weight:700;color:var(--green);line-height:1">$10</div>
<div class="sub">earned by you, per download</div></div>
<div><div style="font-size:40px;font-weight:700;line-height:1">50%</div>
<div class="sub">your cut of every $20 download</div></div>
</div>
<div class="sub">Upload your own signature as a digital file. It is tied to your account and stored privately —
the file is <b>never shared, resold, or publicly exposed</b>. Every single download is tracked to your account
and earns you a commission. Payouts are reconciled from the download ledger.</div>
</div>
<div class="card" id="authCard">
<h2 id="authTitle">Sign in or create an account</h2>
<div id="authForms">
<label>Email</label><input type="email" id="aEmail" autocomplete="email">
<label>Password</label><input type="password" id="aPw" autocomplete="current-password">
<label>Name (for new accounts)</label><input type="text" id="aName" autocomplete="name">
<button class="btn" id="loginBtn">Sign in</button>
<button class="btn ghost" id="signupBtn">Create account</button>
<div class="msg" id="authMsg"></div>
</div>
<div id="whoami" style="display:none">
<div class="sub">Signed in as <b id="whoEmail"></b> · <a href="#" id="logoutLink">sign out</a></div>
</div>
</div>
<div class="card" id="uploadCard" style="display:none">
<h2>Upload a signature</h2>
<label>Your name (as it should appear)</label>
<input type="text" id="celebName" placeholder="e.g. Jane Q. Celebrity">
<label>Signature file — PNG, JPEG, or SVG (dark ink on light/transparent background)</label>
<input type="file" id="sigFile" accept="image/png,image/jpeg,image/svg+xml">
<img id="preview" alt="preview">
<div class="attest"><input type="checkbox" id="attest">
<span>I attest this is <b>my own signature</b> (or I am legally authorized to license it), and I grant
Celebrity Signatures the right to sell tracked downloads with the commission split shown above.
Identity is reviewed before anything goes live.</span></div>
<button class="btn" id="uploadBtn">Submit for review</button>
<div class="msg" id="upMsg"></div>
<div class="privacy">Privacy: the raw file lives outside the public web root; it can only leave the server
through the tracked download endpoint, which writes a ledger entry crediting your account first.</div>
</div>
<div class="card" id="dashCard" style="display:none">
<h2>My signatures & earnings</h2>
<table id="dashTable"><thead><tr><th>Name</th><th>Status</th><th>Downloads</th><th>Price</th><th>Cut</th><th>Earned</th></tr></thead><tbody></tbody></table>
<div class="total">Total earned: <b id="totalEarned">$0.00</b></div>
</div>
</main>
<script>
const $ = s => document.querySelector(s);
const api = (p, opts) => fetch(p, { headers:{'Content-Type':'application/json'}, ...opts }).then(r => r.json());
async function refresh() {
const me = await api('/api/auth/me');
const on = !!me.user;
$('#authForms').style.display = on ? 'none' : '';
$('#whoami').style.display = on ? '' : 'none';
$('#authTitle').textContent = on ? 'Account' : 'Sign in or create an account';
$('#uploadCard').style.display = on ? '' : 'none';
$('#dashCard').style.display = on ? '' : 'none';
if (on) {
$('#whoEmail').textContent = me.user.email;
const d = await api('/api/my-signatures');
if (d.ok) {
$('#dashTable tbody').innerHTML = d.uploads.length ? d.uploads.map(u => `<tr>
<td>${u.celebrity_name}</td>
<td><span class="st st-${u.status}">${u.status}</span></td>
<td>${u.downloads}</td><td>$${u.priceUsd}</td><td>${u.commissionPct}%</td>
<td>$${u.earnedUsd.toFixed(2)}</td></tr>`).join('')
: '<tr><td colspan="6" class="sub">no uploads yet</td></tr>';
$('#totalEarned').textContent = '$' + d.totalEarnedUsd.toFixed(2);
}
}
}
$('#loginBtn').onclick = async () => {
const r = await api('/api/auth/login', { method:'POST', body: JSON.stringify({ email:$('#aEmail').value, password:$('#aPw').value }) });
$('#authMsg').className = 'msg ' + (r.ok ? 'ok' : 'err'); $('#authMsg').textContent = r.ok ? 'signed in' : (r.error || 'failed');
if (r.ok) refresh();
};
$('#signupBtn').onclick = async () => {
const r = await api('/api/auth/signup', { method:'POST', body: JSON.stringify({ email:$('#aEmail').value, password:$('#aPw').value, name:$('#aName').value }) });
$('#authMsg').className = 'msg ' + (r.ok ? 'ok' : 'err'); $('#authMsg').textContent = r.ok ? 'account created' : (r.error || 'failed');
if (r.ok) refresh();
};
$('#logoutLink').onclick = async (e) => { e.preventDefault(); await api('/api/auth/logout', { method:'POST' }); refresh(); };
let dataUrl = null;
$('#sigFile').onchange = () => {
const f = $('#sigFile').files[0];
if (!f) return;
const rd = new FileReader();
rd.onload = () => { dataUrl = rd.result; $('#preview').src = dataUrl; $('#preview').style.display = 'block'; };
rd.readAsDataURL(f);
};
$('#uploadBtn').onclick = async () => {
$('#upMsg').textContent = '';
if (!dataUrl) { $('#upMsg').className='msg err'; $('#upMsg').textContent='choose a signature file first'; return; }
try { const bytes=atob(dataUrl.split(',')[1]||'').length; if(bytes<500||bytes>3e6){ $('#upMsg').className='msg err'; $('#upMsg').textContent='file must be 0.5KB–3MB (yours is '+(bytes<500?'too small':'too large')+')'; return; } } catch {}
if (!$('#attest').checked) { $('#upMsg').className='msg err'; $('#upMsg').textContent='the attestation checkbox is required'; return; }
$('#uploadBtn').disabled = true;
const r = await api('/api/signature-upload', { method:'POST', body: JSON.stringify({
celebrity_name: $('#celebName').value, dataUrl, attest: $('#attest').checked === true }) });
$('#uploadBtn').disabled = false;
$('#upMsg').className = 'msg ' + (r.ok ? 'ok' : 'err');
$('#upMsg').textContent = r.ok ? ('submitted — status: ' + r.status) : (r.error || 'failed');
if (r.ok) { $('#celebName').value=''; $('#sigFile').value=''; $('#preview').style.display='none'; dataUrl=null; $('#attest').checked=false; refresh(); }
};
refresh();
</script>
</body>
</html>