← back to CelebritySignatures
game/murals/wear: render cleaned colored-ink signatures via shared preview
6a759ce3b936f8f294bcb725547a62c499cafe84 · 2026-09-09 14:46:17 -0700 · Steve
Wire the signature-preview.css + signature-preview.js component into the game,
murals, and wear pages so their signatures get the same white-ground / colored-
ink cleanup as the gallery. Depends on the signature-ink.js crash fix.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ikfY9DhpMDXkpNRpb5ufG
Files touched
M public/game.htmlM public/murals.htmlM public/wear.html
Diff
commit 6a759ce3b936f8f294bcb725547a62c499cafe84
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Sep 9 14:46:17 2026 -0700
game/murals/wear: render cleaned colored-ink signatures via shared preview
Wire the signature-preview.css + signature-preview.js component into the game,
murals, and wear pages so their signatures get the same white-ground / colored-
ink cleanup as the gallery. Depends on the signature-ink.js crash fix.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014ikfY9DhpMDXkpNRpb5ufG
---
public/game.html | 14 ++++++++++----
public/murals.html | 15 +++++++++++----
public/wear.html | 14 +++++++++++---
3 files changed, 32 insertions(+), 11 deletions(-)
diff --git a/public/game.html b/public/game.html
index ba1e46e..4996cc4 100644
--- a/public/game.html
+++ b/public/game.html
@@ -45,6 +45,10 @@
.stage .duo { display:flex; gap:16px; align-items:center; justify-content:center; flex-wrap:wrap; }
.duo .d { text-align:center; }
.duo .d img { max-height:110px; max-width:260px; background:#fff; border:1px solid var(--line); border-radius:8px; padding:8px; }
+ /* Colored-ink signatures on clean white via the shared preview component. */
+ .stage .sig.signature-preview { display:inline-flex; height:120px; max-height:120px; }
+ .stage .sig.signature-preview .signature-image { max-height:120px; }
+ .duo .d .signature-preview { height:110px; max-width:260px; background:#fff; border:1px solid var(--line); border-radius:8px; padding:8px; }
.duo .d .lab { font:600 15px 'Playfair Display',Georgia,serif; margin-top:6px; }
.choices { display:grid; grid-template-columns:1fr 1fr; gap:10px; }
.choice { padding:13px 16px; border:1px solid var(--line); border-radius:10px; background:#fff; cursor:pointer; font:inherit; font-size:14.5px; text-align:center; transition:transform .08s; }
@@ -120,6 +124,8 @@
<meta property="og:url" content="https://celebsignatures.com/game">
<meta property="og:image" content="https://celebsignatures.com/assets/og-share.jpg">
<meta name="twitter:card" content="summary_large_image">
+<link rel="stylesheet" href="/assets/signature-preview.css">
+<script type="module" src="/assets/signature-preview.js"></script>
<meta name="twitter:title" content="The Signature Games — how well do you know history’s hands?">
<meta name="twitter:description" content="Six games: name the artist, name the painting, spot the earlier signature, and a 5-second Lightning Round. Play free and climb the leaderboard.">
<meta name="twitter:image" content="https://celebsignatures.com/assets/og-share.jpg">
@@ -413,7 +419,7 @@ function showRound(){
return;
}
if (gameKey==='whose' || gameKey==='century'){
- stage.innerHTML = `<img class="sig" src="${esc(r.answer.signature_image_url)}" alt="Mystery signature">`;
+ stage.innerHTML = `<span class="sig signature-preview" data-signature-src="${esc(r.answer.signature_image_url)}" data-signature-label="Mystery signature" data-signature-tint-key="${esc(r.answer.full_name||'')}"></span>`;
ch.innerHTML = (gameKey==='whose' ? r.options.map((o,i)=>`<button class="choice" data-i="${i}">${esc(o.full_name)}</button>`)
: r.options.map((c,i)=>`<button class="choice" data-i="${i}">${centuryLabel(c)}</button>`)).join('');
}
@@ -422,13 +428,13 @@ function showRound(){
ch.innerHTML = r.options.map((o,i)=>`<button class="choice" data-i="${i}">${esc(o.full_name)}</button>`).join('');
}
else if (gameKey==='match'){
- stage.innerHTML = `<img class="sig" src="${esc(r.answer.signature_image_url)}" alt="Signature of the artist">`;
+ stage.innerHTML = `<span class="sig signature-preview" data-signature-src="${esc(r.answer.signature_image_url)}" data-signature-label="Signature of the artist" data-signature-tint-key="${esc(r.answer.full_name||'')}"></span>`;
ch.innerHTML = r.options.map((o,i)=>`<button class="choice art" data-i="${i}"><img referrerpolicy="no-referrer" src="${esc(o.art.img)}" alt="artwork option"><span class="cl"></span></button>`).join('');
}
else if (gameKey==='early'){
stage.innerHTML = `<div class="duo">
- <div class="d"><img referrerpolicy="no-referrer" src="${esc(r.left.url)}" alt="signature A"><div class="lab">A</div></div>
- <div class="d"><img referrerpolicy="no-referrer" src="${esc(r.right.url)}" alt="signature B"><div class="lab">B</div></div>
+ <div class="d"><span class="signature-preview" data-signature-src="${esc(r.left.url)}" data-signature-label="signature A" data-signature-tint-key="${esc(r.left.full_name||r.left.url||'A')}"></span><div class="lab">A</div></div>
+ <div class="d"><span class="signature-preview" data-signature-src="${esc(r.right.url)}" data-signature-label="signature B" data-signature-tint-key="${esc(r.right.full_name||r.right.url||'B')}"></span><div class="lab">B</div></div>
</div>`;
ch.innerHTML = `<button class="choice" data-i="left">A is earlier</button><button class="choice" data-i="right">B is earlier</button>`;
$('#prompt').textContent = `${r.person} — which is the EARLIER signature?`;
diff --git a/public/murals.html b/public/murals.html
index 19fdee1..7e5f143 100644
--- a/public/murals.html
+++ b/public/murals.html
@@ -138,6 +138,11 @@
.mural .sig-grid{position:absolute;inset:0;display:grid;gap:2px;padding:3px;background:#fff}
.mural .sg-cell{display:flex;align-items:center;justify-content:center;background:#fff;overflow:hidden}
.mural .sg-cell img{max-width:94%;max-height:88%;object-fit:contain;pointer-events:none}
+ /* Colored-ink signatures on clean white via the shared preview component. */
+ .mural .sg-cell .signature-preview{width:100%;height:100%;pointer-events:none}
+ .roster .r-item .signature-preview{height:20px;width:50px;flex:none}
+ .finder .hit .signature-preview{height:26px;width:96px;flex:none}
+ #provSig .signature-preview{height:120px;width:100%}
/* — scene presets in studio — */
.scene-presets{display:flex;gap:6px;flex-wrap:wrap}
.scene-presets button{font:inherit;font-size:12.5px;padding:6px 11px;border:1px solid var(--line);border-radius:8px;background:#fff;cursor:pointer;color:var(--muted)}
@@ -160,6 +165,8 @@
<meta property="og:url" content="https://celebsignatures.com/murals">
<meta property="og:image" content="https://celebsignatures.com/assets/og-share.jpg">
<meta name="twitter:card" content="summary_large_image">
+<link rel="stylesheet" href="/assets/signature-preview.css">
+<script type="module" src="/assets/signature-preview.js"></script>
<meta name="twitter:title" content="Celebrity Signature Murals — made to order">
<meta name="twitter:description" content="A wall of history in their own hand. Custom-size signature murals from $10/sq ft, printed to order.">
<meta name="twitter:image" content="https://celebsignatures.com/assets/og-share.jpg">
@@ -419,7 +426,7 @@ function toggleRoster(code, trig){
`<input class="r-search" type="search" placeholder="Filter ${rows.length} names…" autocomplete="off">
<div class="r-list">` +
rows.map(r => `<button class="r-item" data-sig="${r.category}|${r.rank}" type="button">
- <img loading="lazy" src="${r.signature_image_url}" alt=""><span>${r.full_name}</span></button>`).join('') +
+ <span class="signature-preview" data-signature-src="${r.signature_image_url}" data-signature-label="Signature of ${(r.full_name||'').replace(/"/g,'"')}" data-signature-tint-key="${(r.full_name||'').replace(/"/g,'"')}"></span><span>${r.full_name}</span></button>`).join('') +
`</div>`;
body.dataset.filled = '1';
}
@@ -428,7 +435,7 @@ function toggleRoster(code, trig){
// — provenance modal (consensus #2) —
function openProv(row){
if(!row) return;
- $('#provSig').innerHTML = `<img src="${row.signature_image_url}" alt="${row.full_name} signature">`;
+ $('#provSig').innerHTML = `<span class="signature-preview" data-signature-src="${row.signature_image_url}" data-signature-label="Signature of ${(row.full_name||'').replace(/"/g,'"')}" data-signature-tint-key="${(row.full_name||'').replace(/"/g,'"')}"></span>`;
$('#provName').textContent = row.full_name;
$('#provWhy').textContent = row.reason_for_ranking || '';
const deceased = row.deceased ? ('Deceased' + (row.death_date ? ' ' + String(row.death_date).slice(0,4) : '')) : 'Living';
@@ -453,7 +460,7 @@ function wireFinder(){
const hits = SIGS.filter(r => r.full_name.toLowerCase().includes(q)).slice(0, 8);
if(!hits.length){ res.innerHTML = `<div class="miss">Not in the openly-licensed archive yet — <a href="#order">request a custom mural</a> and we'll source them.</div>`; return; }
res.innerHTML = hits.map(r => { const m = codeOfCat[r.category];
- return `<div class="hit"><img src="${r.signature_image_url}" alt=""> <b>${r.full_name}</b> — on ${m ? `<a href="#" data-go="${m.code}">${m.title}</a>` : r.category}</div>`;
+ return `<div class="hit"><span class="signature-preview" data-signature-src="${r.signature_image_url}" data-signature-label="Signature of ${(r.full_name||'').replace(/"/g,'"')}" data-signature-tint-key="${(r.full_name||'').replace(/"/g,'"')}"></span> <b>${r.full_name}</b> — on ${m ? `<a href="#" data-go="${m.code}">${m.title}</a>` : r.category}</div>`;
}).join('');
});
res.addEventListener('click', e => {
@@ -539,7 +546,7 @@ function muralInnerHTML(pxPerFt){
}
const shown = roster.slice(0, n);
return { n, html: `<div class="sig-grid" style="grid-template-columns:repeat(${cols},1fr);grid-auto-rows:1fr">${
- shown.map(r=>`<div class="sg-cell" title="${(r.full_name||'').replace(/"/g,'"')}"><img loading="lazy" src="${thumbUrl(r.signature_image_url)}" alt="${(r.full_name||'').replace(/"/g,'"')} signature" onerror="this.closest('.sg-cell').style.visibility='hidden'"></div>`).join('')
+ shown.map(r=>`<div class="sg-cell" title="${(r.full_name||'').replace(/"/g,'"')}"><span class="signature-preview" data-signature-src="${r.signature_image_url}" data-signature-label="Signature of ${(r.full_name||'').replace(/"/g,'"')}" data-signature-tint-key="${(r.full_name||'').replace(/"/g,'"')}"></span></div>`).join('')
}</div>` };
}
diff --git a/public/wear.html b/public/wear.html
index 7b6084d..e0fda68 100644
--- a/public/wear.html
+++ b/public/wear.html
@@ -24,7 +24,7 @@
.cart-btn:hover { border-color:var(--ink); }
.cart-badge { display:inline-block; margin-left:4px; background:var(--ink); color:#fff; border-radius:999px; font-size:11px; min-width:16px; padding:1px 5px; text-align:center; }
.cart-item { display:flex; gap:10px; align-items:center; padding:10px 0; border-bottom:1px solid var(--line); }
- .cart-item img { width:44px; height:44px; object-fit:contain; background:#f7f5f0; border-radius:8px; padding:4px; mix-blend-mode:multiply; }
+ .cart-item img { width:44px; height:44px; object-fit:contain; background:#fff; border-radius:8px; padding:4px; mix-blend-mode:multiply; }
.cart-item .ci-info { flex:1; min-width:0; }
.cart-item .ci-name { font-size:14px; font-weight:500; }
.cart-item .ci-meta { font-size:12px; color:#6b6b6b; }
@@ -50,6 +50,9 @@
.card { background:#fff; border:1px solid var(--line); border-radius:12px; padding:14px; cursor:pointer; text-align:center; transition:transform .12s, box-shadow .12s; }
.card:hover { transform:translateY(-2px); box-shadow:0 8px 22px rgba(0,0,0,.07); }
.card img { max-width:100%; height:64px; object-fit:contain; mix-blend-mode:multiply; }
+ /* Colored-ink signatures on clean white via the shared preview component. */
+ .card .signature-preview { height:64px; }
+ .ac-item .signature-preview { width:40px; height:34px; flex:0 0 40px; background:#fff; }
.card .nm { font-size:13px; margin-top:8px; font-weight:500; }
.card .cat { font-size:11px; color:#9a978f; text-transform:uppercase; letter-spacing:.05em; }
.empty { background:#fff; border:1px dashed var(--line); border-radius:12px; padding:44px 24px; text-align:center; color:#6b6b6b; }
@@ -106,6 +109,8 @@
.fine { font-size:12px; color:#9a978f; margin-top:12px; }
.close { position:absolute; top:14px; right:18px; font-size:26px; color:#fff; cursor:pointer; z-index:41; }
</style>
+<link rel="stylesheet" href="/assets/signature-preview.css">
+<script type="module" src="/assets/signature-preview.js"></script>
</head>
<body>
<header>
@@ -191,6 +196,9 @@
<script>
const state = { data:null, sig:null, garment:null, color:null, size:null };
+// Top-level HTML-attribute escape (the autocomplete's own `esc` is scoped to
+// that block, so the grid renderer needs its own).
+const escA = s => String(s==null?'':s).replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c]));
// ---- cart (localStorage-backed; no login required) ----
const CART_KEY = 'wear_cart';
@@ -296,7 +304,7 @@ function render(){
}
grid.style.display='grid'; empty.style.display='none';
grid.innerHTML = sigs.map(s=>`<div class="card" data-qid="${s.qid}">
- <img src="${s.signature_image_url}" alt="${s.full_name}" loading="lazy" onerror="this.style.opacity=.15">
+ <span class="signature-preview" data-signature-src="${escA(s.signature_image_url)}" data-signature-label="Signature of ${escA(s.full_name)}" data-signature-tint-key="${escA(s.full_name)}"></span>
<div class="nm">${s.full_name}</div><div class="cat">${s.category||''}</div></div>`).join('');
grid.querySelectorAll('.card').forEach(c=>c.onclick=()=>openProduct(c.dataset.qid));
}
@@ -703,7 +711,7 @@ document.getElementById('sort').onchange = e => { localStorage.setItem('wear_sor
items = suggest(q);
if (!items.length){ closeAc(); return; }
ac.innerHTML = items.map((s,i)=>`<div class="ac-item${i===active?' active':''}" role="option" data-qid="${s.qid}">
- <img src="${s.signature_image_url}" alt="" loading="lazy" onerror="this.style.visibility='hidden'">
+ <span class="signature-preview" data-signature-src="${s.signature_image_url}" data-signature-label="Signature of ${esc(s.full_name||'')}" data-signature-tint-key="${esc(s.full_name||'')}"></span>
<span class="ac-nm">${hl(s.full_name||'', q)}</span><span class="ac-cat">${esc(s.category||'')}</span></div>`).join('');
ac.style.display='block'; input.setAttribute('aria-expanded','true');
ac.querySelectorAll('.ac-item').forEach(el=>{
← 5499131 signatures: permanently exclude Q352 (brand safety) from eve
·
back to CelebritySignatures
·
signatures: render each signer in a distinct, WCAG-legible c 3a772dc →