← back to CelebritySignatures
wear: drop the cap, real Printify product photos instead of flat SVG (TK-10286)
108381cf33df1b9be57be67472b7bb9041a3ef81 · 2026-09-08 14:15:27 -0700 · Steve Abrams
Steve: "I DONT LIKE THE HATS AND THE TSHIRTS NEED TO LOK REAL FROM PRINTERS
TEMPLATES". Still fully gated off (WEAR_SALES_LIVE unset, checkout returns
comingSoon).
1. Removed the cap/Classic Dad Cap garment entirely (data + the SVG geometry
that only existed for it).
2. Replaced the hand-drawn flat-vector garment illustration (Path2D body
shape + accent paths) with REAL Printify product photography. Captured 7
real blank product photos (one per garment x color: tee white/heather/
sand/sky, polo white/black, pocket-tee white) via a throwaway draft
product with an invisible 1x1-pixel transparent print (so the photo comes
back completely blank), fetched the real mockup image, deleted the
product — same proven create/fetch/delete/verify-404 pattern used all
session. Saved to public/assets/garments/*.jpg (1200x1200, real Printify
photography).
3. New wear-templates.json fields per garment: photoWidth/photoHeight (1200)
and overlayBox (the pixel rectangle where the client composites the
signature onto the real photo) — measured directly against a real
Printify mockup WITH an actual signature printed at that garment's own
printify.printPosition, so the on-site preview and the real order use the
same position. pocket-tee's overlayBox was measured straight off the
blank photo itself (the real stitched pocket is visible with no print at
all). Each color object now carries its own `photo` path instead of just
a flat `hex` (hex/ink kept — still used to decide the multiply-vs-recolor
blend mode).
4. public/wear.html draw() rewritten: loads and caches the current color's
real photo, draws it as the canvas background, then composites the
signature into the measured overlayBox with the same contain-fit +
multiply/recolor-to-light logic as before (unchanged math, real photo
underneath instead of a flat color fill). Removed the now-dead shade()
helper and all Path2D/accent/pocketAccent/pocketHem drawing code.
5. FOUND AND FIXED a real bug while visually verifying: switching garments
via the chip only re-resolved state.color when the color id was NOT found
on the new garment — when both garments shared a color id (e.g. "white"),
it silently kept the OLD garment's color object, which under the old
flat-hex rendering was invisible (same hex value) but under real photos
showed the WRONG garment's photo entirely. Fixed by always re-resolving
to the new garment's own matching color object.
Verified: node --check clean, JSON valid, local E2E shows 3 garments
(cap gone), checkout still comingSoon:true. Playwright screenshots of the
real on-site canvas (tee white/heather, polo white/black, pocket-tee) all
show real photographic garments with the signature correctly composited —
0 console errors.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmxcNKtFg77wP47uZq5Zsm
Files touched
M data/wear-templates.jsonA public/assets/garments/classic-tee-heather.jpgA public/assets/garments/classic-tee-sand.jpgA public/assets/garments/classic-tee-sky.jpgA public/assets/garments/classic-tee-white.jpgA public/assets/garments/pocket-tee-white.jpgA public/assets/garments/polo-black.jpgA public/assets/garments/polo-white.jpgM public/wear.html
Diff
commit 108381cf33df1b9be57be67472b7bb9041a3ef81
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Sep 8 14:15:27 2026 -0700
wear: drop the cap, real Printify product photos instead of flat SVG (TK-10286)
Steve: "I DONT LIKE THE HATS AND THE TSHIRTS NEED TO LOK REAL FROM PRINTERS
TEMPLATES". Still fully gated off (WEAR_SALES_LIVE unset, checkout returns
comingSoon).
1. Removed the cap/Classic Dad Cap garment entirely (data + the SVG geometry
that only existed for it).
2. Replaced the hand-drawn flat-vector garment illustration (Path2D body
shape + accent paths) with REAL Printify product photography. Captured 7
real blank product photos (one per garment x color: tee white/heather/
sand/sky, polo white/black, pocket-tee white) via a throwaway draft
product with an invisible 1x1-pixel transparent print (so the photo comes
back completely blank), fetched the real mockup image, deleted the
product — same proven create/fetch/delete/verify-404 pattern used all
session. Saved to public/assets/garments/*.jpg (1200x1200, real Printify
photography).
3. New wear-templates.json fields per garment: photoWidth/photoHeight (1200)
and overlayBox (the pixel rectangle where the client composites the
signature onto the real photo) — measured directly against a real
Printify mockup WITH an actual signature printed at that garment's own
printify.printPosition, so the on-site preview and the real order use the
same position. pocket-tee's overlayBox was measured straight off the
blank photo itself (the real stitched pocket is visible with no print at
all). Each color object now carries its own `photo` path instead of just
a flat `hex` (hex/ink kept — still used to decide the multiply-vs-recolor
blend mode).
4. public/wear.html draw() rewritten: loads and caches the current color's
real photo, draws it as the canvas background, then composites the
signature into the measured overlayBox with the same contain-fit +
multiply/recolor-to-light logic as before (unchanged math, real photo
underneath instead of a flat color fill). Removed the now-dead shade()
helper and all Path2D/accent/pocketAccent/pocketHem drawing code.
5. FOUND AND FIXED a real bug while visually verifying: switching garments
via the chip only re-resolved state.color when the color id was NOT found
on the new garment — when both garments shared a color id (e.g. "white"),
it silently kept the OLD garment's color object, which under the old
flat-hex rendering was invisible (same hex value) but under real photos
showed the WRONG garment's photo entirely. Fixed by always re-resolving
to the new garment's own matching color object.
Verified: node --check clean, JSON valid, local E2E shows 3 garments
(cap gone), checkout still comingSoon:true. Playwright screenshots of the
real on-site canvas (tee white/heather, polo white/black, pocket-tee) all
show real photographic garments with the signature correctly composited —
0 console errors.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AmxcNKtFg77wP47uZq5Zsm
---
data/wear-templates.json | 185 ++++++-------------------
public/assets/garments/classic-tee-heather.jpg | Bin 0 -> 158780 bytes
public/assets/garments/classic-tee-sand.jpg | Bin 0 -> 59035 bytes
public/assets/garments/classic-tee-sky.jpg | Bin 0 -> 55856 bytes
public/assets/garments/classic-tee-white.jpg | Bin 0 -> 42109 bytes
public/assets/garments/pocket-tee-white.jpg | Bin 0 -> 31263 bytes
public/assets/garments/polo-black.jpg | Bin 0 -> 40295 bytes
public/assets/garments/polo-white.jpg | Bin 0 -> 33325 bytes
public/wear.html | 93 ++++++-------
9 files changed, 86 insertions(+), 192 deletions(-)
diff --git a/data/wear-templates.json b/data/wear-templates.json
index 2b2de98..e1a42e6 100644
--- a/data/wear-templates.json
+++ b/data/wear-templates.json
@@ -1,5 +1,5 @@
{
- "_note": "Garment TEMPLATES for the /wear left-chest signature apparel feature. Each garment is pure geometry: an SVG viewBox + body path (drawn on canvas via new Path2D), optional accent paths (collar/placket/pocket), a set of LIGHT colorways (dark garments deferred — a black signature renders via canvas 'multiply', which needs a light ground; the polo's black exception got a separate renderer fix, see public/wear.html), and the pocketBox: the left-chest placement rectangle. CORRECTED 2026-09-08 — pocketBox is viewer-RIGHT (wearer's LEFT chest, the industry-standard 'left chest' placement), matching the real Printify print position verified that day; it was previously viewer-left, which did not match what actually printed.",
+ "_note": "Garment TEMPLATES for the /wear left-chest signature apparel feature. REBUILT 2026-09-08 to use REAL Printify product photography instead of a flat hand-drawn SVG illustration (Steve: 'the tshirts need to look real from printers templates'). Each color has a real blank product photo (captured via a throwaway draft product with an invisible 1x1 print, deleted immediately — public/assets/garments/*.jpg) at photoWidth x photoHeight pixels; overlayBox is the pixel rectangle (in that photo's own pixel space) where the client-side canvas composites the selected signature on top, measured directly against a real Printify mockup with an actual signature printed at the SAME printPosition used for real orders. The cap/hat garment was dropped per Steve's feedback the same session.",
"placement": "left_chest",
"printAreaInches": {
"w": 4,
@@ -17,44 +17,43 @@
"XL",
"2XL"
],
- "viewBox": [
- 0,
- 0,
- 400,
- 480
- ],
- "bodyPath": "M150 62 L120 72 L58 112 L40 178 L112 168 L112 430 L288 430 L288 168 L360 178 L342 112 L280 72 L250 62 Q200 104 150 62 Z",
- "accents": [],
- "pocketBox": {
- "x": 176,
- "y": 150,
- "w": 96,
- "h": 78
+ "photoWidth": 1200,
+ "photoHeight": 1200,
+ "overlayBox": {
+ "x": 590,
+ "y": 390,
+ "w": 200,
+ "h": 80
},
+ "_note_overlayBox": "Measured 2026-09-08 against a real Printify mockup (throwaway draft product, deleted) printed at this garment's own printify.printPosition (x:0.7/y:0.1/scale:0.16) — the core signature ink landed at px x:651-717/y:421-429 on the 1200x1200 photo; box padded generously beyond that so longer signature names still contain-fit inside it.",
"colors": [
{
"id": "white",
"label": "White",
"hex": "#f6f4ef",
- "ink": "#1a1a1a"
+ "ink": "#1a1a1a",
+ "photo": "/assets/garments/classic-tee-white.jpg"
},
{
"id": "heather",
"label": "Heather Gray",
"hex": "#d8d5cd",
- "ink": "#1a1a1a"
+ "ink": "#1a1a1a",
+ "photo": "/assets/garments/classic-tee-heather.jpg"
},
{
"id": "sand",
"label": "Sand",
"hex": "#e7ddca",
- "ink": "#1a1a1a"
+ "ink": "#1a1a1a",
+ "photo": "/assets/garments/classic-tee-sand.jpg"
},
{
"id": "sky",
"label": "Sky",
"hex": "#dbe6ef",
- "ink": "#16324a"
+ "ink": "#16324a",
+ "photo": "/assets/garments/classic-tee-sky.jpg"
}
],
"printful": {
@@ -71,7 +70,7 @@
"printPosition": { "placeholder": "front", "scale": 0.16, "x": 0.7, "y": 0.1 },
"extraPrintAreas": [
{
- "_note": "Fabric tag at the back collar (Steve, 2026-09-08) — Printify's dedicated 'neck' placeholder, only available on this blueprint (checked: polo/pocket-tee/cap blueprints have no neck-label placeholder). Static asset (not per-order design), same on every order. Verified 2026-09-08 via 4 real throwaway draft products — y:0.0 clips into the collar-binding curve and distorts the text; y:0.08 is the highest clean/legible position.",
+ "_note": "Fabric tag at the back collar (Steve, 2026-09-08) — Printify's dedicated 'neck' placeholder, only available on this blueprint (checked: polo/pocket-tee blueprints have no neck-label placeholder). Static asset (not per-order design), same on every order. Verified 2026-09-08 via 4 real throwaway draft products — y:0.0 clips into the collar-binding curve and distorts the text; y:0.08 is the highest clean/legible position.",
"placeholder": "neck",
"imageUrl": "/assets/wear-tag-celebrity-signatures.png",
"x": 0.5, "y": 0.08, "scale": 0.8
@@ -99,41 +98,29 @@
"XL",
"2XL"
],
- "viewBox": [
- 0,
- 0,
- 400,
- 480
- ],
- "bodyPath": "M156 66 L120 72 L58 112 L40 178 L112 168 L112 430 L288 430 L288 168 L360 178 L342 112 L280 72 L244 66 L200 96 Z",
- "accents": [
- {
- "d": "M156 66 L200 120 L244 66 L226 66 L200 96 L174 66 Z",
- "fillShade": -0.08
- },
- {
- "d": "M193 96 L207 96 L205 150 L195 150 Z",
- "fillShade": -0.05
- }
- ],
- "pocketBox": {
- "x": 180,
- "y": 158,
- "w": 92,
- "h": 74
+ "photoWidth": 1200,
+ "photoHeight": 1200,
+ "overlayBox": {
+ "x": 640,
+ "y": 325,
+ "w": 180,
+ "h": 70
},
+ "_note_overlayBox": "Measured 2026-09-08 against a real Printify mockup (throwaway draft product, deleted) printed at this garment's own printify.printPosition (the dedicated left_chest placeholder, x:0.5/y:0.5/scale:0.85) on the 1200x1200 photo.",
"colors": [
{
"id": "white",
"label": "White",
"hex": "#f6f4ef",
- "ink": "#1a1a1a"
+ "ink": "#1a1a1a",
+ "photo": "/assets/garments/polo-white.jpg"
},
{
"id": "black",
"label": "Black",
"hex": "#181818",
- "ink": "#f5f3ee"
+ "ink": "#f5f3ee",
+ "photo": "/assets/garments/polo-black.jpg"
}
],
"_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.",
@@ -145,11 +132,10 @@
"variants": {}
},
"printify": {
- "_note": "JERZEES 443M Piqué Polo, print provider 99 (Printify Choice), DTG. Mapped 2026-09-08 via read-only catalog GETs. printPosition VERIFIED 2026-09-08 against real Printify mockups (multiple throwaway draft products, each deleted) — iterated through x:0.32/y:0.3/scale:0.25 (wrong, dead-center) -> x:0.7/y:0.15/scale:0.14 -> x:0.7/y:0.13/scale:0.22 (too big) to the final x:0.7/y:0.1/scale:0.16, a standard small left-chest logo size sitting just above pocket-top height. Renders correctly on both white and black. KNOWN ISSUE: on the black colorway, the source signature art (dark ink) prints as low-contrast dark-gray-on-black — Printify does not auto-recolor art for dark garments. Needs a server-side light-ink recolor of design_image_url before order submission for dark colors, or dropping the black colorway, before go-live.",
+ "_note": "JERZEES 443M Piqué Polo, print provider 99 (Printify Choice), DTG. Mapped 2026-09-08 via read-only catalog GETs. printPosition VERIFIED 2026-09-08 against real Printify mockups (multiple throwaway draft products, each deleted) — iterated through x:0.32/y:0.3/scale:0.25 (wrong, dead-center) -> x:0.7/y:0.15/scale:0.14 -> x:0.7/y:0.13/scale:0.22 (too big) -> x:0.7/y:0.1/scale:0.16 (still overlapping the placket even at y:0.0) to the final DEDICATED 'left_chest' placeholder, x:0.5/y:0.5/scale:0.85, a properly-scoped small print area that lands at the correct height automatically. Renders correctly on both white and black. KNOWN ISSUE: on the black colorway, the source signature art (dark ink) prints as low-contrast dark-gray-on-black — Printify does not auto-recolor art for dark garments. Needs a server-side light-ink recolor of design_image_url before order submission for dark colors, or dropping the black colorway, before go-live.",
"blueprintId": 1402,
"printProviderId": 99,
"printPosition": { "placeholder": "left_chest", "scale": 0.85, "x": 0.5, "y": 0.5 },
- "_note_placeholder": "Switched off the manually-tuned 'front' full-torso area (kept overlapping the placket no matter how high y went) to Printify's own DEDICATED 'left_chest' placeholder for this blueprint — a properly-scoped small print area that lands at the correct height automatically. Verified 2026-09-08 via a real throwaway draft product.",
"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
@@ -170,34 +156,22 @@
"2XL"
],
"_note_sizes": "No XL — Printify's own catalog has no White/XL variant for this blueprint's pocket-DTG placeholder (checked both providers, same gap). Real catalog limit, not an oversight.",
- "viewBox": [
- 0,
- 0,
- 400,
- 480
- ],
- "bodyPath": "M150 62 L120 72 L58 112 L40 178 L112 168 L112 430 L288 430 L288 168 L360 178 L342 112 L280 72 L250 62 Q200 104 150 62 Z",
- "accents": [],
- "pocketAccent": {
- "d": "M176 150 L272 150 L272 214 Q272 222 264 222 L184 222 Q176 222 176 214 Z",
- "fillShade": -0.05
- },
- "pocketHem": {
- "d": "M176 160 L272 160 L272 163 L176 163 Z",
- "fillShade": -0.12
- },
- "pocketBox": {
- "x": 184,
- "y": 168,
- "w": 80,
- "h": 46
+ "photoWidth": 1200,
+ "photoHeight": 1200,
+ "overlayBox": {
+ "x": 700,
+ "y": 415,
+ "w": 130,
+ "h": 110
},
+ "_note_overlayBox": "Matches the REAL physical pocket visible in the product photo itself (public/assets/garments/pocket-tee-white.jpg) — measured directly off that photo, not a separate mockup-with-signature render, since the pocket's stitched outline is visible without any print at all.",
"colors": [
{
"id": "white",
"label": "White",
"hex": "#f6f4ef",
- "ink": "#1a1a1a"
+ "ink": "#1a1a1a",
+ "photo": "/assets/garments/pocket-tee-white.jpg"
}
],
"_note_colors": "White-only (Steve asked for a pocket-tee variant, 2026-09-08) — Deep Heather only has S/M in Printify's catalog for this blueprint's pocket placeholder, too gappy to offer alongside white's own S/M/L/2XL gap (no XL). Signature prints directly ON the pocket (Printify's dedicated 'pocket' placeholder), not just near it.",
@@ -220,83 +194,6 @@
"sizeUpchargeUsd": {
"2XL": 4
}
- },
- {
- "id": "cap",
- "label": "Classic Dad Cap",
- "priceUsd": 28,
- "sizes": [
- "One size"
- ],
- "_note_sizes": "Caps are one-size-fits-all (adjustable strap) — no size chips shown for this garment on the storefront.",
- "viewBox": [
- 0,
- 0,
- 400,
- 480
- ],
- "bodyPath": "M90 210 Q90 70 200 64 Q310 70 310 210 Q310 226 200 230 Q90 226 90 210 Z",
- "accents": [
- {
- "d": "M195 68 L205 68 L207 226 L193 226 Z",
- "fillShade": -0.06
- }
- ],
- "pocketAccent": {
- "d": "M60 214 Q40 250 90 268 Q200 288 310 268 Q360 250 340 214 Q320 232 200 236 Q80 232 60 214 Z",
- "fillShade": -0.1
- },
- "_note_pocketAccent": "This is the cap's BRIM (drawn as a shaded curve below the crown), reusing the pocketAccent rendering hook — not a real pocket. Renamed at the code level would be cleaner but this keeps public/wear.html's draw() generic across all 4 garments without a one-off branch.",
- "pocketBox": {
- "x": 150,
- "y": 120,
- "w": 100,
- "h": 55
- },
- "colors": [
- {
- "id": "white",
- "label": "White",
- "hex": "#f6f4ef",
- "ink": "#1a1a1a"
- },
- {
- "id": "stone",
- "label": "Stone",
- "hex": "#cfc9bd",
- "ink": "#1a1a1a"
- },
- {
- "id": "khaki",
- "label": "Khaki",
- "hex": "#c9b896",
- "ink": "#1a1a1a"
- },
- {
- "id": "lightblue",
- "label": "Light Blue",
- "hex": "#a9c4d9",
- "ink": "#16324a"
- }
- ],
- "_note_colors": "Light-only (White/Stone/Khaki/Light Blue), same conservative default as the original tee palette — avoids the known dark-ink-on-dark-fabric contrast problem (see polo's black colorway note) on a third garment before that gets a real fix.",
- "printful": {
- "_note": "Not mapped — Printify only for this garment.",
- "suggestedProduct": "Yupoong 6245CM Classic Dad Cap",
- "productId": null,
- "placementType": "front",
- "variants": {}
- },
- "printify": {
- "_note": "Yupoong 6245CM Classic Dad Cap, print provider 99 (Printify Choice), DTF (not embroidery — a cursive signature's thin strokes would blob out in embroidery; this blueprint offers both, DTF was the deliberate choice). Mapped + VERIFIED 2026-09-08 via a real throwaway draft product — front_dtf placeholder, x:0.5/y:0.5/scale:0.7 centers cleanly on the front panel. Cost $16.12 at time of verification.",
- "blueprintId": 1447,
- "printProviderId": 99,
- "printPosition": { "placeholder": "front_dtf", "scale": 0.7, "x": 0.5, "y": 0.5 },
- "variants": {
- "white/One size": 105381, "stone/One size": 105380, "khaki/One size": 105375, "lightblue/One size": 105376
- }
- },
- "sizeUpchargeUsd": {}
}
]
}
diff --git a/public/assets/garments/classic-tee-heather.jpg b/public/assets/garments/classic-tee-heather.jpg
new file mode 100644
index 0000000..0b71e97
Binary files /dev/null and b/public/assets/garments/classic-tee-heather.jpg differ
diff --git a/public/assets/garments/classic-tee-sand.jpg b/public/assets/garments/classic-tee-sand.jpg
new file mode 100644
index 0000000..93e9691
Binary files /dev/null and b/public/assets/garments/classic-tee-sand.jpg differ
diff --git a/public/assets/garments/classic-tee-sky.jpg b/public/assets/garments/classic-tee-sky.jpg
new file mode 100644
index 0000000..a57d1c7
Binary files /dev/null and b/public/assets/garments/classic-tee-sky.jpg differ
diff --git a/public/assets/garments/classic-tee-white.jpg b/public/assets/garments/classic-tee-white.jpg
new file mode 100644
index 0000000..57adc26
Binary files /dev/null and b/public/assets/garments/classic-tee-white.jpg differ
diff --git a/public/assets/garments/pocket-tee-white.jpg b/public/assets/garments/pocket-tee-white.jpg
new file mode 100644
index 0000000..957995f
Binary files /dev/null and b/public/assets/garments/pocket-tee-white.jpg differ
diff --git a/public/assets/garments/polo-black.jpg b/public/assets/garments/polo-black.jpg
new file mode 100644
index 0000000..661c400
Binary files /dev/null and b/public/assets/garments/polo-black.jpg differ
diff --git a/public/assets/garments/polo-white.jpg b/public/assets/garments/polo-white.jpg
new file mode 100644
index 0000000..9c622dc
Binary files /dev/null and b/public/assets/garments/polo-white.jpg differ
diff --git a/public/wear.html b/public/wear.html
index 7e2c17c..5ff92fd 100644
--- a/public/wear.html
+++ b/public/wear.html
@@ -258,7 +258,16 @@ function openProduct(qid){
function buildOptions(){
const G = document.getElementById('garments'), C = document.getElementById('colors'), Z = document.getElementById('sizes');
G.innerHTML = state.data.garments.map(g=>`<span class="chip ${g.id===state.garment.id?'sel':''}" data-g="${g.id}">${g.label}</span>`).join('');
- G.querySelectorAll('.chip').forEach(el=>el.onclick=()=>{ state.garment = state.data.garments.find(g=>g.id===el.dataset.g); if(!state.garment.colors.find(c=>c.id===state.color.id)) state.color=state.garment.colors[0]; if(!state.garment.sizes.includes(state.size)) state.size=state.garment.sizes[0]; buildOptions(); draw(); });
+ G.querySelectorAll('.chip').forEach(el=>el.onclick=()=>{
+ // Re-resolve to the NEW garment's own color object even when the id
+ // matches (e.g. both offer "white") — each garment's color now carries
+ // its own real product photo, so reusing the old garment's color object
+ // silently showed the wrong garment's photo.
+ state.garment = state.data.garments.find(g=>g.id===el.dataset.g);
+ state.color = state.garment.colors.find(c=>c.id===state.color.id) || state.garment.colors[0];
+ if(!state.garment.sizes.includes(state.size)) state.size=state.garment.sizes[0];
+ buildOptions(); draw();
+ });
C.innerHTML = state.garment.colors.map(c=>`<span class="swatch ${c.id===state.color.id?'sel':''}" data-c="${c.id}" title="${c.label}" style="background:${c.hex}"></span>`).join('');
C.querySelectorAll('.swatch').forEach(el=>el.onclick=()=>{ state.color = state.garment.colors.find(c=>c.id===el.dataset.c); buildOptions(); draw(); });
Z.innerHTML = state.garment.sizes.map(z=>`<span class="chip ${z===state.size?'sel':''}" data-z="${z}">${z}</span>`).join('');
@@ -274,11 +283,6 @@ function loadSigThenDraw(){
sigImg.src = state.sig.signature_image_url;
}
-function shade(hex, amt){ // amt <0 darken, >0 lighten
- const n = parseInt(hex.slice(1),16); let r=(n>>16)&255,g=(n>>8)&255,b=n&255;
- 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){
@@ -299,51 +303,44 @@ function recolorToLight(img){
return off;
}
+// Real garment photos are loaded once per URL and cached — switching sizes
+// (which doesn't change the photo) never re-fetches.
+const photoCache = {};
+function loadPhoto(src, cb){
+ if (photoCache[src] && photoCache[src].complete) { cb(photoCache[src]); return; }
+ const img = photoCache[src] || (photoCache[src] = new Image());
+ img.onload = () => cb(img);
+ img.onerror = () => cb(null);
+ if (!img.src) img.src = src;
+}
+
function draw(){
const cv = document.getElementById('stage'), ctx = cv.getContext('2d');
- const g = state.garment, col = state.color, vb = g.viewBox;
- cv.width = vb[2]; cv.height = vb[3];
- ctx.clearRect(0,0,cv.width,cv.height);
- // garment body
- const body = new Path2D(g.bodyPath);
- ctx.save();
- ctx.shadowColor='rgba(0,0,0,.12)'; ctx.shadowBlur=18; ctx.shadowOffsetY=6;
- ctx.fillStyle = col.hex; ctx.fill(body);
- ctx.restore();
- ctx.lineWidth=1.2; ctx.strokeStyle=shade(col.hex,-0.16); ctx.stroke(body);
- // accents (collar/placket)
- (g.accents||[]).forEach(a=>{ const p=new Path2D(a.d); ctx.fillStyle=shade(col.hex, a.fillShade||-0.06); ctx.fill(p); });
- const pb = g.pocketBox;
- if (g.pocketAccent){
- // a real sewn-on patch pocket (pocket-tee): filled shape + a hem line, the
- // signature prints ON it (matches Printify's dedicated 'pocket' placement).
- ctx.fillStyle=shade(col.hex, g.pocketAccent.fillShade||-0.05); ctx.fill(new Path2D(g.pocketAccent.d));
- ctx.strokeStyle=shade(col.hex,-0.18); ctx.lineWidth=1; ctx.stroke(new Path2D(g.pocketAccent.d));
- if (g.pocketHem) { ctx.fillStyle=shade(col.hex, g.pocketHem.fillShade||-0.12); ctx.fill(new Path2D(g.pocketHem.d)); }
- } else {
- // no real pocket on this garment — just a subtle placement hint
- 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. 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();
- 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);
+ const g = state.garment, col = state.color;
+ cv.width = g.photoWidth; cv.height = g.photoHeight;
+ loadPhoto(col.photo, photoImg => {
+ ctx.clearRect(0, 0, cv.width, cv.height);
+ if (photoImg) ctx.drawImage(photoImg, 0, 0, cv.width, cv.height);
+ // signature into the overlay box, contain-fit, against the REAL product
+ // photo. Light cloth: 'multiply' so the dark ink reads naturally against
+ // the photo (fabric shading still shows through, same as a real print).
+ // Dark cloth: multiply would render invisible (it can only darken), so
+ // recolor the signature to a light ink and composite normally instead.
+ const ob = g.overlayBox;
+ if (sigImg && sigImg.width){
+ const pad = ob.w * 0.08, bw = ob.w - 2*pad, bh = ob.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 = ob.x + (ob.w-w)/2, y = ob.y + (ob.h-h)/2;
+ 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();
}
- 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);
- }
+ });
}
document.getElementById('sort').onchange = render;
← 4173af9 wear: real multi-item shopping cart (own cart, not Shopify's
·
back to CelebritySignatures
·
auto-data-snapshot: 2026-09-08T15:35:04 (1 data files) — pub 8b345a4 →