← back to CelebritySignatures
wear v1 soft-launch: browsable catalog live, sales gated behind WEAR_SALES_LIVE (coming-soon/notify-me buy button; no test-checkout exposed to real visitors)
5ed1ce734a6273227ce27ad9f48662c3aee989db · 2026-08-10 10:59:15 -0700 · Steve Abrams
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
M public/wear.htmlM server.js
Diff
commit 5ed1ce734a6273227ce27ad9f48662c3aee989db
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Aug 10 10:59:15 2026 -0700
wear v1 soft-launch: browsable catalog live, sales gated behind WEAR_SALES_LIVE (coming-soon/notify-me buy button; no test-checkout exposed to real visitors)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
public/wear.html | 27 ++++++++++++++-------------
server.js | 5 +++++
2 files changed, 19 insertions(+), 13 deletions(-)
diff --git a/public/wear.html b/public/wear.html
index 00e1fdb..4adbba1 100644
--- a/public/wear.html
+++ b/public/wear.html
@@ -118,13 +118,9 @@ async function boot(){
const r = await fetch('/api/wear/signatures').then(r=>r.json());
state.data = r;
const d = document.getElementById('disclosure');
- if (r.disclosure.screenComplete) {
- d.className = 'disclosure';
- d.innerHTML = `<b>Trademark-screened.</b> ${r.count.toLocaleString()} signatures are legally cleared for sale — screened against the USPTO register${r.disclosure.screenedAt? ' ('+new Date(r.disclosure.screenedAt).toLocaleDateString()+')':''}. Names with a live apparel trademark are excluded.`;
- } else {
- d.className = 'disclosure warn';
- d.innerHTML = `<b>Trademark screening in progress.</b> The current screen is a sample, not the full USPTO register, so <b>no signatures are cleared for sale yet</b>. Only legally-clear signatures will ever be offered here.`;
- }
+ d.className = 'disclosure' + (r.disclosure.purchasable ? '' : ' warn');
+ d.innerHTML = `<b>${(r.count||0).toLocaleString()} historic signatures</b> — public-domain figures (deceased on or before ${r.disclosure.cutoffDeathYear||1900}) on left-chest apparel.` +
+ (r.disclosure.purchasable ? '' : ' <b>Sales open soon</b> — browse now; add your email to be notified.');
render();
}
@@ -135,12 +131,10 @@ function render(){
if (sort==='name') sigs.sort((a,b)=>a.full_name.localeCompare(b.full_name));
else if (sort==='cat') sigs.sort((a,b)=>(a.category||'').localeCompare(b.category||'')||a.full_name.localeCompare(b.full_name));
else if (sort==='shuffle') sigs.sort(()=>Math.random()-0.5);
- document.getElementById('count').textContent = sigs.length ? `${sigs.length.toLocaleString()} cleared` : '';
+ document.getElementById('count').textContent = sigs.length ? `${sigs.length.toLocaleString()} available` : '';
if (!sigs.length){
grid.style.display='none'; empty.style.display='block';
- empty.innerHTML = state.data.disclosure.screenComplete
- ? 'No signatures matched the eligibility gate.'
- : 'Nothing is cleared for sale yet — the full trademark screen has not been run. Once it is, cleared signatures appear here automatically.';
+ empty.innerHTML = 'No signatures matched the eligibility gate.';
return;
}
grid.style.display='grid'; empty.style.display='none';
@@ -160,6 +154,11 @@ function openProduct(qid){
document.getElementById('pCat').textContent = s.category||'';
document.getElementById('email').value = '';
document.getElementById('msg').textContent = '';
+ const live = !!(state.data.disclosure && state.data.disclosure.purchasable);
+ const bb = document.getElementById('buy'); bb.disabled=false; bb.textContent = live ? 'Add to cart' : 'Notify me when live';
+ const fine = document.querySelector('.fine'); if(fine) fine.textContent = live
+ ? 'Signature placed at the left chest. Fulfilled by our print partner.'
+ : 'Signature placed at the left chest. Apparel sales open soon — add your email and we’ll notify you the moment it goes live.';
buildOptions();
loadSigThenDraw();
document.getElementById('scrim').classList.add('on');
@@ -226,17 +225,19 @@ document.getElementById('close').onclick = ()=>document.getElementById('scrim').
document.getElementById('scrim').onclick = e => { if(e.target.id==='scrim') document.getElementById('scrim').classList.remove('on'); };
document.getElementById('buy').onclick = async ()=>{
+ const live = !!(state.data && state.data.disclosure && state.data.disclosure.purchasable);
const email = document.getElementById('email').value.trim();
const msg = document.getElementById('msg'); msg.style.color='#c0392b';
if(!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email)){ msg.textContent='Enter a valid email.'; return; }
- const btn = document.getElementById('buy'); btn.disabled=true; btn.textContent='Starting checkout…';
+ const btn = document.getElementById('buy'); btn.disabled=true; btn.textContent = live ? 'Starting checkout…' : 'Saving…';
try{
const r = await fetch('/api/wear-checkout',{method:'POST',headers:{'Content-Type':'application/json'},
body:JSON.stringify({ qid:state.sig.qid, garment:state.garment.id, color:state.color.id, size:state.size, email })}).then(r=>r.json());
if(r.ok && r.url){ location.href = r.url; return; }
+ if(r.comingSoon){ msg.style.color='#2e7d32'; msg.textContent = `You're on the list — we'll email ${email} the moment ${state.sig.full_name} apparel goes live.`; btn.textContent='On the list ✓'; return; }
msg.textContent = r.error || 'Checkout unavailable.';
}catch(e){ msg.textContent='Network error.'; }
- btn.disabled=false; btn.textContent='Add to cart';
+ btn.disabled=false; btn.textContent = live ? 'Add to cart' : 'Notify me when live';
};
// restore density pref
diff --git a/server.js b/server.js
index 4580f5e..cb17d18 100644
--- a/server.js
+++ b/server.js
@@ -774,6 +774,7 @@ ${paid ? `<div class="ok">✓</div><h1>Order confirmed</h1>
garments: tpl.garments || [], placement: tpl.placement || 'left_chest',
disclosure: {
basis: 'public-domain-historical', cutoffDeathYear: WEAR_PD_DEATH_CUTOFF,
+ purchasable: envVal('WEAR_SALES_LIVE') === '1',
note: 'Only public-domain historical signatures (figures deceased on or before ' + WEAR_PD_DEATH_CUTOFF + ') are offered — no living or modern figures. Live USPTO trademark screening is deferred.',
},
count: sigs.length, signatures: sigs,
@@ -786,6 +787,10 @@ ${paid ? `<div class="ok">✓</div><h1>Order confirmed</h1>
// payment, a POD-order draft is appended to data/pod-order-drafts.jsonl; NO POD
// API call is made here.
if (path === '/api/wear-checkout' && M === 'POST') {
+ // v1 soft-launch: catalog is public + browsable, but real sales stay OFF until Steve
+ // flips WEAR_SALES_LIVE=1 (paired with live Stripe + a wired POD). Until then, no visitor
+ // is sent to a test-mode checkout — they get a friendly "opening soon".
+ if (envVal('WEAR_SALES_LIVE') !== '1') return sendJSON(res, 200, { ok: false, comingSoon: true, error: 'Apparel sales open soon — thanks for your interest!' });
if (!STRIPE_DOWNLOAD_KEY) return sendJSON(res, 503, { ok: false, error: 'apparel not purchasable yet (awaiting Stripe test key)' });
const b = await readBody(req);
const email = String(b.email || '').trim();
← 96c795b wear v1 (TK-10286, Steve GO): ship public-domain historical
·
back to CelebritySignatures
·
wear/POD: wire Printful sender + go-live path (gated OFF) — 0fa8dbf →