← back to CelebritySignatures
wear: fix dark-garment preview + map Printify blueprints/variants (TK-10286)
ba30f903f3af2e24ee179092f67be86cd0c41f0c · 2026-09-08 09:32:15 -0700 · Steve Abrams
Still fully gated off (WEAR_SALES_LIVE unset, checkout returns comingSoon).
1. public/wear.html: the left-chest signature preview used canvas 'multiply'
blend unconditionally — correct for light garments (dark ink darkens light
cloth) but multiply can only ever darken, so a dark garment rendered the
signature completely invisible. Added a luminance check: dark colorways now
recolor the signature to a light ink and composite normally instead.
2. data/wear-templates.json: polo colorways restricted to white/black (Steve's
call, 2026-09-08) — the mapped JERZEES 443M Piqué Polo blueprint only offers
6 colors, no sand/sky equivalent; black needed fix #1 above to render.
3. Filled in real Printify blueprint/print-provider/variant mappings for both
garments via read-only catalog GETs (no account write): tee = Bella+Canvas
3001 (blueprint 12), polo = JERZEES 443M Piqué (blueprint 1402), both DTG
via print provider 99 (Printify Choice). Shop id 496929 confirmed by Steve
(Designer Wallcoverings and Fabrics — the new products.write-scoped token
resolves to a different shop id than the old token's discovery, corrected).
4. New scripts/printify-cost-check.mjs (not yet run) — creates one throwaway
unpublished draft product per garment to read real per-unit cost, deletes
it immediately, verifies the delete with a follow-up GET. Blocked from
autonomous execution by the harness's own safety classifier (external
account write); handed to Steve to run directly.
Verified: node --check clean, wear-templates.json valid JSON, local E2E shows
2955 signatures, polo colors=[white,black], tee/polo variant maps populated
(20 + 10 entries), checkout still returns comingSoon:true for every request.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmxcNKtFg77wP47uZq5Zsm
Files touched
M data/wear-templates.jsonM public/wear.htmlA scripts/printify-cost-check.mjs
Diff
commit ba30f903f3af2e24ee179092f67be86cd0c41f0c
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Sep 8 09:32:15 2026 -0700
wear: fix dark-garment preview + map Printify blueprints/variants (TK-10286)
Still fully gated off (WEAR_SALES_LIVE unset, checkout returns comingSoon).
1. public/wear.html: the left-chest signature preview used canvas 'multiply'
blend unconditionally — correct for light garments (dark ink darkens light
cloth) but multiply can only ever darken, so a dark garment rendered the
signature completely invisible. Added a luminance check: dark colorways now
recolor the signature to a light ink and composite normally instead.
2. data/wear-templates.json: polo colorways restricted to white/black (Steve's
call, 2026-09-08) — the mapped JERZEES 443M Piqué Polo blueprint only offers
6 colors, no sand/sky equivalent; black needed fix #1 above to render.
3. Filled in real Printify blueprint/print-provider/variant mappings for both
garments via read-only catalog GETs (no account write): tee = Bella+Canvas
3001 (blueprint 12), polo = JERZEES 443M Piqué (blueprint 1402), both DTG
via print provider 99 (Printify Choice). Shop id 496929 confirmed by Steve
(Designer Wallcoverings and Fabrics — the new products.write-scoped token
resolves to a different shop id than the old token's discovery, corrected).
4. New scripts/printify-cost-check.mjs (not yet run) — creates one throwaway
unpublished draft product per garment to read real per-unit cost, deletes
it immediately, verifies the delete with a follow-up GET. Blocked from
autonomous execution by the harness's own safety classifier (external
account write); handed to Steve to run directly.
Verified: node --check clean, wear-templates.json valid JSON, local E2E shows
2955 signatures, polo colors=[white,black], tee/polo variant maps populated
(20 + 10 entries), checkout still returns comingSoon:true for every request.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmxcNKtFg77wP47uZq5Zsm
---
data/wear-templates.json | 47 +++++++++++++---------------
public/wear.html | 33 ++++++++++++++++++--
scripts/printify-cost-check.mjs | 68 +++++++++++++++++++++++++++++++++++++++++
3 files changed, 120 insertions(+), 28 deletions(-)
diff --git a/data/wear-templates.json b/data/wear-templates.json
index fe2b86c..a375c86 100644
--- a/data/wear-templates.json
+++ b/data/wear-templates.json
@@ -65,11 +65,16 @@
"variants": {}
},
"printify": {
- "_note": "Printify dynamic-order mapping (fill after PRINTIFY_API_TOKEN lands — run scripts/fetch-printify-catalog.mjs). blueprintId + printProviderId select the blank garment; variants maps our color/size key to its variant_id. The paid order's signature image is supplied per order with left-chest positioning. Empty mappings fail loud and keep checkout disabled.",
- "blueprintId": null,
- "printProviderId": null,
+ "_note": "Bella+Canvas 3001 Unisex Jersey Short-Sleeve Tee, print provider 99 (Printify Choice), DTG. Mapped 2026-09-08 via read-only catalog GETs (no account write). printPosition is still a GEOMETRIC ESTIMATE, unverified against a real Printify mockup — do NOT go live before checking it renders correctly on an actual print preview.",
+ "blueprintId": 12,
+ "printProviderId": 99,
"printPosition": { "placeholder": "front", "scale": 0.25, "x": 0.32, "y": 0.3 },
- "variants": {}
+ "variants": {
+ "white/S": 18540, "white/M": 18541, "white/L": 18542, "white/XL": 18543, "white/2XL": 18544,
+ "heather/S": 18076, "heather/M": 18077, "heather/L": 18078, "heather/XL": 18079, "heather/2XL": 18080,
+ "sand/S": 67513, "sand/M": 67514, "sand/L": 67515, "sand/XL": 67516, "sand/2XL": 67517,
+ "sky/S": 18084, "sky/M": 18085, "sky/L": 18086, "sky/XL": 18087, "sky/2XL": 18088
+ }
},
"sizeUpchargeUsd": {
"2XL": 4
@@ -117,37 +122,29 @@
"ink": "#1a1a1a"
},
{
- "id": "heather",
- "label": "Heather Gray",
- "hex": "#d8d5cd",
- "ink": "#1a1a1a"
- },
- {
- "id": "sand",
- "label": "Sand",
- "hex": "#e7ddca",
- "ink": "#1a1a1a"
- },
- {
- "id": "sky",
- "label": "Sky",
- "hex": "#dbe6ef",
- "ink": "#16324a"
+ "id": "black",
+ "label": "Black",
+ "hex": "#181818",
+ "ink": "#f5f3ee"
}
],
+ "_note_colors": "Polo colorways restricted to white/black (Steve, 2026-09-08) — the JERZEES 443M Piqué Polo blueprint (Printify bp 1402, provider 99) only offers White/Athletic Heather/Black/Navy/Royal/True Red, no sand or sky-blue equivalent. Black needed a renderer fix (public/wear.html draw()) since the preview's 'multiply' blend can only darken — dark grounds now recolor the signature to a light ink instead.",
"printful": {
"_note": "Printful mapping (fill after PRINTFUL_API_KEY lands — run scripts/fetch-printful-catalog.mjs). productId = Printful catalog product; variants maps our 'color/size' key -> Printful catalog variant_id. Left EMPTY on purpose so scripts/submit-pod-draft.mjs FAILS LOUD (never submits an unmapped garment).",
- "suggestedProduct": "Bella+Canvas 3001 Unisex Jersey Short-Sleeve Tee (or a polo equiv, e.g. Cotton Heritage / Adidas)",
+ "suggestedProduct": "JERZEES 443M Pique Polo (or equivalent)",
"productId": null,
"placementType": "front",
"variants": {}
},
"printify": {
- "_note": "Printify dynamic-order mapping (fill after PRINTIFY_API_TOKEN lands — run scripts/fetch-printify-catalog.mjs). blueprintId + printProviderId select the blank garment; variants maps our color/size key to its variant_id. The paid order's signature image is supplied per order with left-chest positioning. Empty mappings fail loud and keep checkout disabled.",
- "blueprintId": null,
- "printProviderId": null,
+ "_note": "JERZEES 443M Piqué Polo, print provider 99 (Printify Choice), DTG. Mapped 2026-09-08 via read-only catalog GETs (no account write). printPosition is still a GEOMETRIC ESTIMATE, unverified against a real Printify mockup — do NOT go live before checking it renders correctly on an actual print preview.",
+ "blueprintId": 1402,
+ "printProviderId": 99,
"printPosition": { "placeholder": "front", "scale": 0.25, "x": 0.32, "y": 0.3 },
- "variants": {}
+ "variants": {
+ "white/S": 104647, "white/M": 104648, "white/L": 104649, "white/XL": 104650, "white/2XL": 104651,
+ "black/S": 104623, "black/M": 104624, "black/L": 104625, "black/XL": 104626, "black/2XL": 104627
+ }
},
"sizeUpchargeUsd": {
"2XL": 4
diff --git a/public/wear.html b/public/wear.html
index b7a774a..64fd3c0 100644
--- a/public/wear.html
+++ b/public/wear.html
@@ -188,6 +188,25 @@ function shade(hex, amt){ // amt <0 darken, >0 lighten
const f = x => Math.max(0,Math.min(255, Math.round(x + amt*255)));
return '#'+[f(r),f(g),f(b)].map(x=>x.toString(16).padStart(2,'0')).join('');
}
+// Perceived luminance (0=black, 1=white) — decides whether 'multiply' (needs a
+// light ground) or a recolored-to-light rendition (for a dark ground) is used.
+function luminance(hex){
+ const n = parseInt(hex.slice(1),16), r=(n>>16)&255, g=(n>>8)&255, b=n&255;
+ return (0.299*r + 0.587*g + 0.114*b) / 255;
+}
+// Most signature source art is a dark stroke on a transparent ground — great
+// for 'multiply' on light cloth, invisible via 'multiply' on dark cloth (multiply
+// can only ever darken). For a dark garment, recolor the signature's opaque
+// pixels to a light ink and composite normally instead.
+function recolorToLight(img){
+ const off = document.createElement('canvas'); off.width = img.width; off.height = img.height;
+ const octx = off.getContext('2d');
+ octx.drawImage(img, 0, 0);
+ octx.globalCompositeOperation = 'source-in';
+ octx.fillStyle = '#f5f3ee';
+ octx.fillRect(0, 0, off.width, off.height);
+ return off;
+}
function draw(){
const cv = document.getElementById('stage'), ctx = cv.getContext('2d');
@@ -207,13 +226,21 @@ function draw(){
const pb = g.pocketBox;
ctx.strokeStyle=shade(col.hex,-0.10); ctx.setLineDash([4,4]); ctx.lineWidth=1;
ctx.strokeRect(pb.x,pb.y,pb.w,pb.h); ctx.setLineDash([]);
- // signature into the pocket box, contain-fit, 'multiply' so ink shows on light cloth
+ // signature into the pocket box, contain-fit. Light cloth: 'multiply' so the
+ // dark ink shows through. Dark cloth: multiply would render invisible (it can
+ // only darken), so recolor the signature to a light ink and composite normally.
if (sigImg && sigImg.width){
const pad=8, bw=pb.w-2*pad, bh=pb.h-2*pad;
const ar = sigImg.width/sigImg.height; let w=bw, h=w/ar; if(h>bh){ h=bh; w=h*ar; }
const x = pb.x + (pb.w-w)/2, y = pb.y + (pb.h-h)/2;
- ctx.save(); ctx.globalCompositeOperation='multiply';
- ctx.drawImage(sigImg, x, y, w, h); ctx.restore();
+ ctx.save();
+ if (luminance(col.hex) < 0.4){
+ ctx.drawImage(recolorToLight(sigImg), x, y, w, h);
+ } else {
+ ctx.globalCompositeOperation='multiply';
+ ctx.drawImage(sigImg, x, y, w, h);
+ }
+ ctx.restore();
} else {
ctx.fillStyle=shade(col.hex,-0.2); ctx.font='italic 16px Georgia'; ctx.textAlign='center';
ctx.fillText(state.sig.full_name.split(' ').pop(), pb.x+pb.w/2, pb.y+pb.h/2);
diff --git a/scripts/printify-cost-check.mjs b/scripts/printify-cost-check.mjs
new file mode 100644
index 0000000..24f03ad
--- /dev/null
+++ b/scripts/printify-cost-check.mjs
@@ -0,0 +1,68 @@
+#!/usr/bin/env node
+// ONE-OFF, throwaway cost-discovery helper for TK-10286. Creates ONE minimal,
+// single-variant, unpublished (visible:false) draft product per garment on
+// shop 496929 (Designer Wallcoverings and Fabrics) purely to read Printify's
+// real per-unit cost, then IMMEDIATELY deletes it and verifies the delete
+// with a follow-up GET (expects 404). Logs created→deleted id pairs so
+// nothing is left dangling. Creates NOTHING else — no order, no charge.
+import { readFileSync } from 'node:fs';
+import { fileURLToPath } from 'node:url';
+import { join } from 'node:path';
+
+const ROOT = fileURLToPath(new URL('..', import.meta.url));
+const API = 'https://api.printify.com/v1';
+const SHOP_ID = 496929; // Designer Wallcoverings and Fabrics — confirmed by Steve 2026-09-08
+
+function envVal(name) {
+ if (process.env[name]) return process.env[name];
+ for (const p of [join(ROOT, '.env'), join(ROOT, '..', 'secrets-manager', '.env')]) {
+ try { const m = readFileSync(p, 'utf8').match(new RegExp('^' + name + '=(.+)$', 'm')); if (m) return m[1].trim(); } catch {}
+ }
+ return null;
+}
+const token = envVal('PRINTIFY_API_TOKEN');
+if (!token) { console.error('PRINTIFY_API_TOKEN not found'); process.exit(1); }
+
+async function req(method, path, body) {
+ const r = await fetch(API + path, {
+ method, headers: { Authorization: `Bearer ${token}`, 'User-Agent': 'CelebritySignatures/1.0', 'Content-Type': 'application/json' },
+ body: body ? JSON.stringify(body) : undefined,
+ });
+ const j = await r.json().catch(() => ({}));
+ return { status: r.status, ok: r.ok, body: j };
+}
+
+// blueprint_id, print_provider_id, one representative variant id (White/M),
+// matching what's now mapped in data/wear-templates.json (bp12/provider99 for
+// the tee, bp1402/provider99 for the polo — both DTG, Printify Choice).
+const CANDIDATES = [
+ { garment: 'classic-tee', blueprintId: 12, printProviderId: 99, variantId: 18541, label: 'Bella+Canvas 3001 White/M (DTG, provider 99 Printify Choice)' },
+ { garment: 'polo', blueprintId: 1402, printProviderId: 99, variantId: 104648, label: 'JERZEES 443M Piqué Polo White/M (DTG, provider 99 Printify Choice)' },
+];
+
+async function main() {
+ for (const c of CANDIDATES) {
+ console.log(`\n— ${c.garment}: ${c.label} —`);
+ const created = await req('POST', `/shops/${SHOP_ID}/products.json`, {
+ title: `TEMP-COST-CHECK-DELETE-ME (TK-10286, ${c.garment})`,
+ description: 'Throwaway — created only to read Printify cost, deleted immediately by this script.',
+ blueprint_id: c.blueprintId,
+ print_provider_id: c.printProviderId,
+ variants: [{ id: c.variantId, price: 3400, is_enabled: true }],
+ visible: false,
+ });
+ if (!created.ok) { console.log(` CREATE FAILED (${created.status}):`, JSON.stringify(created.body).slice(0, 500)); continue; }
+ const productId = created.body.id;
+ const variant = (created.body.variants || [])[0] || {};
+ console.log(` created product id=${productId}`);
+ console.log(` cost: $${((variant.cost || 0) / 100).toFixed(2)} suggested retail: $${((variant.price || 0) / 100).toFixed(2)}`);
+
+ const del = await req('DELETE', `/shops/${SHOP_ID}/products/${productId}.json`);
+ console.log(` delete: ${del.ok ? 'OK' : 'FAILED ' + del.status}`);
+
+ const verify = await req('GET', `/shops/${SHOP_ID}/products/${productId}.json`);
+ console.log(` verify-deleted (expect 404): got ${verify.status} — ${verify.status === 404 ? 'CONFIRMED GONE' : 'STILL PRESENT — MANUAL CLEANUP NEEDED'}`);
+ console.log(` LOG: created=${productId} deleted=${del.ok} verified_gone=${verify.status === 404}`);
+ }
+}
+main().catch(e => { console.error(e.message); process.exit(1); });
← f197cfc wear/POD: fix 4 correctness bugs from codex's 2026-09-04 rev
·
back to CelebritySignatures
·
wear: extend printify-cost-check.mjs to also verify left-che 2e8f61b →