[object Object]

← back to CelebritySignatures

Add three hat previews and socks with guarded purchase and fulfillment paths

f44f65bb9f2341b8f0d4bb8f2bc6df1bb8493b02 · 2026-09-09 08:51:11 -0700 · Steve Abrams

Files touched

Diff

commit f44f65bb9f2341b8f0d4bb8f2bc6df1bb8493b02
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Sep 9 08:51:11 2026 -0700

    Add three hat previews and socks with guarded purchase and fulfillment paths
---
 data/wear-templates.json                     | 193 +++++-
 public/assets/garments/bucket-hat-army.jpg   | Bin 0 -> 579691 bytes
 public/assets/garments/crew-socks-white.jpg  | Bin 0 -> 838785 bytes
 public/assets/garments/dad-cap-white.jpg     | Bin 0 -> 544773 bytes
 public/assets/garments/trucker-cap-white.jpg | Bin 0 -> 929525 bytes
 public/wear.html                             |  42 +-
 scripts/submit-pod-draft-printify.mjs        |  18 +-
 server.js                                    |  13 +-
 test/wear-accessories.test.mjs               | 127 ++++
 verification/e2e-proof.json                  |  97 ++-
 verification/gallery-shopping.e2e.cjs        |  23 +-
 verification/hat-sock-catalog.json           | 847 +++++++++++++++++++++++++++
 verification/hat-sock-draft-plan.json        | 179 ++++++
 verification/hats-socks-approval.md          |  40 ++
 verification/hats-socks-decision.md          |  51 ++
 15 files changed, 1583 insertions(+), 47 deletions(-)

diff --git a/data/wear-templates.json b/data/wear-templates.json
index dd85be7..5a834c8 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. 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.",
+  "_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. Three hat styles and crew socks were added as sale-held previews on 2026-09-09 at Steve's request.",
   "placement": "left_chest",
   "printAreaInches": {
     "w": 4,
@@ -194,6 +194,197 @@
       "sizeUpchargeUsd": {
         "2XL": 4
       }
+    },
+    {
+      "id": "dad-cap",
+      "label": "Dad Cap",
+      "priceUsd": null,
+      "saleEnabled": false,
+      "placement": "hat_front",
+      "placementLabel": "Signature printed on the front of the cap.",
+      "sizes": [
+        "One size"
+      ],
+      "photoWidth": 2048,
+      "photoHeight": 2048,
+      "overlayBox": {
+        "x": 865,
+        "y": 705,
+        "w": 520,
+        "h": 150
+      },
+      "colors": [
+        {
+          "id": "white",
+          "label": "White",
+          "hex": "#f6f4ef",
+          "photo": "/assets/garments/dad-cap-white.jpg"
+        }
+      ],
+      "_note": "TK-10286, 2026-09-09: catalog GET verified blueprint/provider/variant/decoration. Official blank Printify lifestyle photo. Overlay and print scale are local preview estimates, not printer-mockup proof. saleEnabled stays false and priceUsd null until real cost, actual printer placement, and release approval are verified.",
+      "photoSource": "https://images.printify.com/66c4719a26c12b30ed07acc2",
+      "printify": {
+        "blueprintId": 1447,
+        "printProviderId": 99,
+        "printPosition": {
+          "placeholder": "front_dtf",
+          "x": 0.5,
+          "y": 0.5,
+          "scale": 0.7
+        },
+        "variants": {
+          "white/One size": 105381
+        }
+      },
+      "catalogVerifiedAt": "2026-09-09T15:37:19.296Z"
+    },
+    {
+      "id": "trucker-cap",
+      "label": "Trucker Cap",
+      "priceUsd": null,
+      "saleEnabled": false,
+      "placement": "hat_front",
+      "placementLabel": "Signature printed on the front of the cap.",
+      "sizes": [
+        "One size"
+      ],
+      "photoWidth": 2048,
+      "photoHeight": 2048,
+      "overlayBox": {
+        "x": 765,
+        "y": 560,
+        "w": 570,
+        "h": 160
+      },
+      "colors": [
+        {
+          "id": "white",
+          "label": "White",
+          "hex": "#f6f4ef",
+          "photo": "/assets/garments/trucker-cap-white.jpg"
+        }
+      ],
+      "_note": "TK-10286, 2026-09-09: catalog GET verified blueprint/provider/variant/decoration. Official blank Printify lifestyle photo. Overlay and print scale are local preview estimates, not printer-mockup proof. saleEnabled stays false and priceUsd null until real cost, actual printer placement, and release approval are verified.",
+      "photoSource": "https://images.printify.com/694951405b10cff51502ed62",
+      "printify": {
+        "blueprintId": 1692,
+        "printProviderId": 99,
+        "printPosition": {
+          "placeholder": "front",
+          "x": 0.5,
+          "y": 0.5,
+          "scale": 0.7
+        },
+        "variants": {
+          "white/One size": 116762
+        }
+      },
+      "catalogVerifiedAt": "2026-09-09T15:37:19.306Z"
+    },
+    {
+      "id": "bucket-hat",
+      "label": "Bucket Hat",
+      "priceUsd": null,
+      "saleEnabled": false,
+      "placement": "hat_front",
+      "placementLabel": "Signature printed on the front of the hat.",
+      "sizes": [
+        "One size"
+      ],
+      "photoWidth": 2048,
+      "photoHeight": 2048,
+      "overlayBox": {
+        "x": 860,
+        "y": 310,
+        "w": 480,
+        "h": 135,
+        "rotation": 9
+      },
+      "colors": [
+        {
+          "id": "army",
+          "label": "Army",
+          "hex": "#60684b",
+          "photo": "/assets/garments/bucket-hat-army.jpg"
+        }
+      ],
+      "_note": "TK-10286, 2026-09-09: catalog GET verified blueprint/provider/variant/decoration. Official blank Printify lifestyle photo. Overlay and print scale are local preview estimates, not printer-mockup proof. saleEnabled stays false and priceUsd null until real cost, actual printer placement, and release approval are verified.",
+      "photoSource": "https://images.printify.com/693146b0182c66341c092922",
+      "printify": {
+        "blueprintId": 1698,
+        "printProviderId": 99,
+        "printPosition": {
+          "placeholder": "front",
+          "x": 0.5,
+          "y": 0.5,
+          "scale": 0.7
+        },
+        "variants": {
+          "army/One size": 116653
+        }
+      },
+      "catalogVerifiedAt": "2026-09-09T15:37:19.317Z"
+    },
+    {
+      "id": "crew-socks",
+      "label": "Crew Socks",
+      "priceUsd": null,
+      "saleEnabled": false,
+      "placement": "sock_legs",
+      "placementLabel": "Signature printed on both socks.",
+      "sizes": [
+        "One size"
+      ],
+      "sizeNote": "One size: US men's 6–10 (EU 34–42.5).",
+      "photoWidth": 2048,
+      "photoHeight": 2048,
+      "overlayBoxes": [
+        {
+          "x": 850,
+          "y": 1130,
+          "w": 300,
+          "h": 100
+        },
+        {
+          "x": 1100,
+          "y": 720,
+          "w": 300,
+          "h": 100,
+          "rotation": -40
+        }
+      ],
+      "colors": [
+        {
+          "id": "white",
+          "label": "White / black heel and toe",
+          "hex": "#f6f4ef",
+          "photo": "/assets/garments/crew-socks-white.jpg"
+        }
+      ],
+      "_note": "TK-10286, 2026-09-09: catalog GET verified blueprint/provider/variant/decoration. Official blank Printify lifestyle photo. Overlay and print scale are local preview estimates, not printer-mockup proof. saleEnabled stays false and priceUsd null until real cost, actual printer placement, and release approval are verified.",
+      "photoSource": "https://images.printify.com/69b954a46002a5644e04d4d2",
+      "printify": {
+        "blueprintId": 2941,
+        "printProviderId": 29,
+        "printPositions": [
+          {
+            "placeholder": "left_leg",
+            "x": 0.5,
+            "y": 0.28,
+            "scale": 0.45
+          },
+          {
+            "placeholder": "right_leg",
+            "x": 0.5,
+            "y": 0.28,
+            "scale": 0.45
+          }
+        ],
+        "variants": {
+          "white/One size": 155679
+        }
+      },
+      "catalogVerifiedAt": "2026-09-09T15:37:19.327Z"
     }
   ]
 }
diff --git a/public/assets/garments/bucket-hat-army.jpg b/public/assets/garments/bucket-hat-army.jpg
new file mode 100644
index 0000000..1c508b7
Binary files /dev/null and b/public/assets/garments/bucket-hat-army.jpg differ
diff --git a/public/assets/garments/crew-socks-white.jpg b/public/assets/garments/crew-socks-white.jpg
new file mode 100644
index 0000000..8a13fcd
Binary files /dev/null and b/public/assets/garments/crew-socks-white.jpg differ
diff --git a/public/assets/garments/dad-cap-white.jpg b/public/assets/garments/dad-cap-white.jpg
new file mode 100644
index 0000000..fb0d4af
Binary files /dev/null and b/public/assets/garments/dad-cap-white.jpg differ
diff --git a/public/assets/garments/trucker-cap-white.jpg b/public/assets/garments/trucker-cap-white.jpg
new file mode 100644
index 0000000..b140002
Binary files /dev/null and b/public/assets/garments/trucker-cap-white.jpg differ
diff --git a/public/wear.html b/public/wear.html
index fd035d9..4244715 100644
--- a/public/wear.html
+++ b/public/wear.html
@@ -95,7 +95,7 @@
 
 <div class="wrap">
   <h1>Wear a Signature</h1>
-  <p class="lede">A historic signature, printed where a chest pocket would sit. Made to order.</p>
+  <p class="lede">Historic signatures on shirts, hats and socks. Made to order.</p>
 
   <div id="disclosure" class="disclosure">Loading trademark screen…</div>
   <p id="selectionNotice" class="disclosure warn" role="status" hidden></p>
@@ -129,14 +129,14 @@
     <div class="buy">
       <h2 id="pName">—</h2>
       <div class="who" id="pCat"></div>
-      <div class="row"><div class="lab">Garment</div><div class="chips" id="garments"></div></div>
+      <div class="row"><div class="lab">Product</div><div class="chips" id="garments"></div></div>
       <div class="row"><div class="lab">Color</div><div class="chips" id="colors"></div></div>
-      <div class="row"><div class="lab">Size</div><div class="chips" id="sizes"></div></div>
+      <div class="row"><div class="lab">Size</div><div class="chips" id="sizes"></div><div class="fine" id="sizeNote"></div></div>
       <div class="price" id="price"></div>
       <div class="row"><input id="email" type="email" placeholder="your@email.com"></div>
       <button class="btn" id="buy">Add to cart</button>
       <div class="msg" id="msg"></div>
-      <div class="fine">Signature placed at the left chest. This is a TEST checkout — no card is charged and no print order is submitted; each order is drafted for review.</div>
+      <div class="fine" id="productNote"></div>
     </div>
   </div>
 </div>
@@ -224,7 +224,7 @@ async function boot(){
   state.data = r;
   const d = document.getElementById('disclosure');
   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.` +
+  d.innerHTML = `<b>${(r.count||0).toLocaleString()} historic signatures</b> — public-domain figures (deceased on or before ${r.disclosure.cutoffDeathYear||1900}) on signature apparel.` +
     (r.disclosure.purchasable ? '' : ' <b>Sales open soon</b> — browse now; add your email to be notified.');
   renderCartBadge();
   render();
@@ -274,12 +274,6 @@ 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';
-  document.getElementById('email').closest('.row').style.display = live ? 'none' : '';
-  const fine = document.querySelector('#scrim .fine'); if(fine) fine.textContent = live
-    ? 'Signature placed at the left chest. Fulfilled by our print partner. Added items go to your cart — checkout when ready.'
-    : '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');
@@ -303,7 +297,15 @@ function buildOptions(){
   Z.innerHTML = state.garment.sizes.map(z=>`<span class="chip ${z===state.size?'sel':''}" data-z="${z}">${z}</span>`).join('');
   Z.querySelectorAll('.chip').forEach(el=>el.onclick=()=>{ state.size=el.dataset.z; buildOptions(); });
   const _up = (state.garment.sizeUpchargeUsd && state.garment.sizeUpchargeUsd[state.size]) || 0;
-  document.getElementById('price').textContent = '$'+(state.garment.priceUsd + _up).toFixed(2) + (_up ? ` (${state.size} +$${_up})` : '');
+  const held = state.garment.saleEnabled === false;
+  const live = !!state.data.disclosure.purchasable;
+  document.getElementById('price').textContent = held ? 'Coming soon' : '$'+(state.garment.priceUsd + _up).toFixed(2) + (_up ? ` (${state.size} +$${_up})` : '');
+  document.getElementById('sizeNote').textContent = state.garment.sizeNote || '';
+  const buy = document.getElementById('buy'); buy.disabled = held;
+  buy.textContent = held ? 'Coming soon' : live ? 'Add to cart' : 'Notify me when live';
+  document.getElementById('email').closest('.row').style.display = live || held ? 'none' : '';
+  document.getElementById('productNote').textContent = (state.garment.placementLabel || 'Signature placed at the left chest.') +
+    (held ? ' Placement preview — this product is coming soon.' : live ? ' Made to order by our print partner.' : ' Apparel sales open soon.');
 }
 
 let sigImg = null;
@@ -447,6 +449,8 @@ function draw(){
   if (has3D && in3DMode) update3DGarment();
   cv.width = g.photoWidth; cv.height = g.photoHeight;
   loadPhoto(col.photo, photoImg => {
+    // A late photo must not repaint a different product selected meanwhile.
+    if (state.garment !== g || state.color !== col) return;
     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
@@ -454,12 +458,17 @@ function draw(){
     // 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){
+    for (const ob of g.overlayBoxes || [g.overlayBox]) {
+    if (ob && 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 (ob.rotation) {
+        ctx.translate(ob.x + ob.w/2, ob.y + ob.h/2);
+        ctx.rotate(ob.rotation * Math.PI / 180);
+        ctx.translate(-(ob.x + ob.w/2), -(ob.y + ob.h/2));
+      }
       if (luminance(col.hex) < 0.4){
         ctx.drawImage(recolorToLight(sigImg), x, y, w, h);
       } else {
@@ -468,6 +477,7 @@ function draw(){
       }
       ctx.restore();
     }
+    }
   });
 }
 
@@ -477,6 +487,7 @@ 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 ()=>{
+  if (state.garment?.saleEnabled === false) return;
   const live = !!(state.data && state.data.disclosure && state.data.disclosure.purchasable);
   const msg = document.getElementById('msg'); msg.style.color='#c0392b';
   const btn = document.getElementById('buy');
@@ -490,7 +501,8 @@ document.getElementById('buy').onclick = async ()=>{
       unitPriceUsd: cartUnitPrice(state.garment, state.size), thumb: state.sig.signature_image_url,
     });
     msg.style.color='#2e7d32'; msg.textContent = 'Added to cart ✓';
-    btn.textContent = 'Added ✓'; setTimeout(()=>{ btn.textContent='Add to cart'; }, 1200);
+    const addedGarment = state.garment;
+    btn.textContent = 'Added ✓'; setTimeout(()=>{ if(state.garment === addedGarment) buildOptions(); }, 1200);
     return;
   }
   // Not live yet: keep the existing per-signature "notify me" email capture.
diff --git a/scripts/submit-pod-draft-printify.mjs b/scripts/submit-pod-draft-printify.mjs
index dddb41c..487a634 100644
--- a/scripts/submit-pod-draft-printify.mjs
+++ b/scripts/submit-pod-draft-printify.mjs
@@ -38,7 +38,7 @@ function resolveProduct(tpl, d) {
   const key = `${d.color}/${d.size}`;
   if (!map.blueprintId || !map.printProviderId || !map.variants?.[key]) throw new Error(`order #${d.orderId}: ${d.garment} ${key} is not mapped to a Printify blueprint/provider/variant`);
   return { blueprintId: map.blueprintId, printProviderId: map.printProviderId, variantId: map.variants[key],
-    position: map.printPosition || { placeholder: 'front', scale: 0.25, x: 0.32, y: 0.3 },
+    positions: map.printPositions || [map.printPosition || { placeholder: 'front', scale: 0.25, x: 0.32, y: 0.3 }],
     // Extra STATIC print areas (e.g. the 'Celebrity Signatures' collar tag) —
     // same image on every order for this garment, not the per-order design.
     extraPrintAreas: map.extraPrintAreas || [] };
@@ -48,21 +48,25 @@ function splitName(name) {
   return { first_name: parts.shift() || '', last_name: parts.join(' ') || '-' };
 }
 export function buildPayload(tpl, d) {
-  const { blueprintId, printProviderId, variantId, position, extraPrintAreas } = resolveProduct(tpl, d);
+  const { blueprintId, printProviderId, variantId, positions, extraPrintAreas } = resolveProduct(tpl, d);
   const r = d.recipient || {};
   const missing = ['name', 'address1', 'city', 'state_code', 'country_code', 'zip'].filter(k => !r[k]);
   if (missing.length) throw new Error(`order #${d.orderId}: missing shipping fields ${missing.join(',')}`);
   if (!d.design_image_url) throw new Error(`order #${d.orderId}: no design_image_url`);
   const name = splitName(r.name);
-  const print_areas = { [position.placeholder || 'front']: [{ src: d.design_image_url, scale: position.scale,
-    x: position.x, y: position.y, angle: 0 }] };
+  if (!positions.length) throw new Error(`order #${d.orderId}: no print positions configured`);
+  const print_areas = {};
+  for (const position of positions) {
+    print_areas[position.placeholder || 'front'] = [{ src: d.design_image_url, scale: position.scale,
+      x: position.x, y: position.y, angle: position.angle || 0 }];
+  }
   for (const area of extraPrintAreas) {
     if (!area.placeholder || !area.imageUrl) continue;
     print_areas[area.placeholder] = [{ src: `${SITE_ORIGIN}${area.imageUrl}`, scale: area.scale, x: area.x, y: area.y, angle: 0 }];
   }
   return {
     external_id: `celebsig-wear-${d.orderId}`,
-    label: `${d.signature_name} signature — left-chest ${d.garment}`,
+    label: `${d.signature_name} signature — ${d.garment}`,
     line_items: [{ print_provider_id: printProviderId, blueprint_id: blueprintId, variant_id: variantId, quantity: 1,
       external_id: `celebsig-wear-${d.orderId}-1`,
       print_areas }],
@@ -100,6 +104,10 @@ async function main() {
     try { payload = buildPayload(tpl, d); } catch (e) { console.log(`  SKIP: ${e.message}`); continue; }
     console.log(JSON.stringify(redactPayload(payload), null, 2));
     if (!willApply) continue;
+    if (tpl.garments.find(g => g.id === d.garment)?.saleEnabled === false) {
+      console.log(`  HELD: ${d.garment} is not approved for sale or fulfillment.`);
+      continue;
+    }
     const resp = await fetch(`${API}/shops/${shopId}/orders.json`, { method: 'POST',
       headers: { Authorization: `Bearer ${token}`, 'User-Agent': 'CelebritySignatures/1.0', 'Content-Type': 'application/json' },
       body: JSON.stringify(payload) });
diff --git a/server.js b/server.js
index 877cf81..30b6b8d 100644
--- a/server.js
+++ b/server.js
@@ -184,7 +184,8 @@ function wearPodReady(tpl, provider) {
     if (!envVal('PRINTIFY_SHOP_ID')) return false;
     if (envVal('PRINTIFY_MANUAL_APPROVAL_CONFIRMED') !== '1') return false;
   }
-  return (tpl.garments || []).every(g => {
+  const offered = (tpl.garments || []).filter(g => g.saleEnabled !== false);
+  return offered.length > 0 && offered.every(g => {
     const mapping = g[provider] || {};
     if (provider === 'printify' && (!mapping.blueprintId || !mapping.printProviderId)) return false;
     return (g.colors || []).every(c => (g.sizes || []).every(size => mapping.variants?.[`${c.id}/${size}`]));
@@ -921,6 +922,7 @@ ${paid ? `<div class="ok">✓</div><h1>Order confirmed</h1>
         if (!sig || !wearEligible(sig, clr)) return sendJSON(res, 403, { ok: false, error: `${it.qid || 'a signature'} is not cleared for sale` });
         const garment = (tpl.garments || []).find(g => g.id === String(it.garment || ''));
         if (!garment) return sendJSON(res, 400, { ok: false, error: 'unknown garment' });
+        if (garment.saleEnabled === false) return sendJSON(res, 503, { ok: false, error: `${garment.label} is coming soon` });
         const color = (garment.colors || []).find(c => c.id === String(it.color || '')) || garment.colors[0];
         if (!(garment.sizes || []).includes(String(it.size))) return sendJSON(res, 400, { ok: false, error: 'unknown size' });
         const size = String(it.size);
@@ -929,7 +931,8 @@ ${paid ? `<div class="ok">✓</div><h1>Order confirmed</h1>
         const upchargeUsd = (garment.sizeUpchargeUsd && garment.sizeUpchargeUsd[size]) || 0;
         const amountCents = Math.round((baseUsd + upchargeUsd) * 100);
         resolvedItems.push({ qid: qidOf(sig), signature_name: sig.full_name, garment: garment.id, garment_label: garment.label,
-          color: color.id, color_label: color.label, size, placement: tpl.placement || 'left_chest', amountCents });
+          color: color.id, color_label: color.label, size, placement: garment.placement || tpl.placement || 'left_chest',
+          placementLabel: garment.placementLabel || 'Signature printed at the left chest.', amountCents });
       }
       const u = await currentUser(req);
       const list = await load('wear-orders.json', []);
@@ -955,8 +958,8 @@ ${paid ? `<div class="ok">✓</div><h1>Order confirmed</h1>
         params.set(`line_items[${i}][quantity]`, '1');
         params.set(`line_items[${i}][price_data][currency]`, 'usd');
         params.set(`line_items[${i}][price_data][unit_amount]`, String(it.amountCents));
-        params.set(`line_items[${i}][price_data][product_data][name]`, `${it.signature_name} signature — left-chest ${it.garment_label} (${it.color_label}, ${it.size})`);
-        params.set(`line_items[${i}][price_data][product_data][description]`, `Signature printed at the left chest; made to order.${WEAR_STRIPE_MODE === 'test' ? ' TEST checkout.' : ''}`);
+        params.set(`line_items[${i}][price_data][product_data][name]`, `${it.signature_name} signature — ${it.garment_label} (${it.color_label}, ${it.size})`);
+        params.set(`line_items[${i}][price_data][product_data][description]`, `${it.placementLabel} Made to order.${WEAR_STRIPE_MODE === 'test' ? ' TEST checkout.' : ''}`);
       });
       params.set('metadata[order_id]', String(id));
       try {
@@ -1044,7 +1047,7 @@ ${paid ? `<div class="ok">✓</div><h1>Order confirmed</h1>
 .ok{font-size:52px}a.btn{display:inline-block;margin-top:22px;padding:12px 26px;border-radius:999px;background:#1a1a1a;color:#fff;text-decoration:none}
 .sub{color:#6b6b6b}</style></head><body><div class="w">
 ${paid ? `<div class="ok">✓</div><h1>Order received</h1>
-<p>${order ? wearOrderItems(order).map(it => `${esc(it.signature_name)} — left-chest ${esc(it.garment_label)} (${esc(it.color_label || it.color)}, ${esc(it.size)})`).join('<br>') : 'Your order'} ${wearOrderItems(order || {}).length > 1 ? 'are' : 'is'} queued. (TEST order #${order ? order.id : ''}.)</p>
+<p>${order ? wearOrderItems(order).map(it => `${esc(it.signature_name)} — ${esc(it.garment_label)} (${esc(it.color_label || it.color)}, ${esc(it.size)})`).join('<br>') : 'Your order'} ${wearOrderItems(order || {}).length > 1 ? 'are' : 'is'} queued. (TEST order #${order ? order.id : ''}.)</p>
 <p class="sub">This is a test checkout; nothing was charged and no print order has been submitted yet.</p>`
 : `<h1>Order not completed</h1><p class="sub">No payment was captured.</p>`}
 <a class="btn" href="/wear">Back to the shop</a></div></body></html>`;
diff --git a/test/wear-accessories.test.mjs b/test/wear-accessories.test.mjs
new file mode 100644
index 0000000..237db87
--- /dev/null
+++ b/test/wear-accessories.test.mjs
@@ -0,0 +1,127 @@
+import test from 'node:test';
+import assert from 'node:assert/strict';
+import { mkdtemp, mkdir, readFile, writeFile, copyFile, rm, realpath } from 'node:fs/promises';
+import { tmpdir } from 'node:os';
+import { join } from 'node:path';
+import { spawn, execFile } from 'node:child_process';
+import { promisify } from 'node:util';
+import { createServer } from 'node:net';
+import { once } from 'node:events';
+import { buildPayload } from '../scripts/submit-pod-draft-printify.mjs';
+
+const root = new URL('..', import.meta.url);
+const templates = JSON.parse(await readFile(new URL('data/wear-templates.json', root), 'utf8'));
+const catalog = JSON.parse(await readFile(new URL('verification/hat-sock-catalog.json', root), 'utf8'));
+const ids = ['dad-cap', 'trucker-cap', 'bucket-hat', 'crew-socks'];
+const draft = { orderId: 'fixture.0', provider: 'printify', status: 'DRAFT_UNSENT',
+  signature_name: 'Fixture', design_image_url: 'https://fixture.invalid/signature.png',
+  recipient_email: 'fixture@example.invalid', recipient: { name: 'Fixture Buyer',
+    address1: '1 Fixture Lane', city: 'Fixture', state_code: 'CA', country_code: 'US', zip: '90001' } };
+const item = id => { const g = templates.garments.find(g => g.id === id);
+  return { qid: 'QFixture', garment: id, color: g.colors[0].id, size: g.sizes[0] }; };
+const env = { PATH: process.env.PATH, WEAR_SALES_LIVE: '1', WEAR_POD_PROVIDER: 'printify',
+  PRINTIFY_API_TOKEN: 'fixture-token', PRINTIFY_SHOP_ID: 'fixture-shop', PRINTIFY_MANUAL_APPROVAL_CONFIRMED: '1',
+  STRIPE_TEST_SECRET_KEY: 'sk_test_fixture', WEAR_STRIPE_LIVE_ENABLED: '0', STRIPE_LIVE_ENABLED: '0' };
+
+test('all four accessory payloads match real catalog variants and raster print areas', () => {
+  for (const id of ids) {
+    const g = templates.garments.find(g => g.id === id);
+    const source = catalog.products.find(p => p.id === id);
+    const payload = buildPayload(templates, { ...draft, ...item(id) }).line_items[0];
+    assert.equal(payload.blueprint_id, source.blueprint.id);
+    assert.equal(payload.print_provider_id, source.catalog.id);
+    const variant = source.catalog.variants.find(v => v.id === payload.variant_id);
+    assert.ok(variant, 'variant exists in provider response');
+    assert.equal(variant.options.size, g.sizes[0]);
+    for (const [position, images] of Object.entries(payload.print_areas)) {
+      const supported = variant.placeholders.find(p => p.position === position);
+      assert.ok(supported, 'printer supports '+position);
+      assert.ok(['dtf', 'dtg'].includes(supported.decoration_method), 'raster decoration method');
+      assert.equal(images[0].src, draft.design_image_url);
+    }
+    assert.equal(g.saleEnabled, false, 'requires cost/mockup approval');
+    assert.equal(g.priceUsd, null, 'no invented price');
+  }
+  const socks = buildPayload(templates, { ...draft, ...item('crew-socks') }).line_items[0];
+  assert.deepEqual(Object.keys(socks.print_areas).sort(), ['left_leg', 'right_leg']);
+  assert.deepEqual(socks.print_areas.left_leg, socks.print_areas.right_leg);
+});
+
+async function fixture(t) {
+  const work = await realpath(await mkdtemp(join(tmpdir(), 'tk10286-accessories-')));
+  t.after(() => rm(work, { recursive: true, force: true }));
+  await mkdir(join(work, 'data'));
+  await mkdir(join(work, 'scripts'));
+  const put = (name, data) => writeFile(join(work, 'data', name), JSON.stringify(data));
+  await put('wear-templates.json', templates);
+  await put('wear-orders.json', []);
+  await put('celebrity_signatures.json', [{ wikidata: 'https://www.wikidata.org/wiki/QFixture',
+    full_name: 'Fixture', category: 'Politics', signature_image_url: draft.design_image_url,
+    usable_in_commercial_collage: 'yes', risk_level: 'low', death_date: '1800-01-01' }]);
+  const interceptor = join(work, 'intercept.mjs');
+  await writeFile(interceptor, `
+import { appendFileSync } from 'node:fs';
+globalThis.fetch = async (input, options = {}) => {
+  appendFileSync(${JSON.stringify(join(work, 'requests.jsonl'))}, JSON.stringify({url:String(input),method:options.method,body:options.body})+'\\n');
+  if (String(input) === 'https://api.stripe.com/v1/checkout/sessions' && options.method === 'POST')
+    return Response.json({id:'cs_test_fixture',url:'https://fixture.invalid/checkout'});
+  throw new Error('External request blocked by fixture');
+};
+`);
+  return { work, put, interceptor };
+}
+
+test('direct and mixed-cart requests cannot buy accessories; original shirts still checkout', { timeout: 20000 }, async t => {
+  const { work, put, interceptor } = await fixture(t);
+  // A held item without a mapping must not disable the three existing shirts.
+  const local = structuredClone(templates);
+  local.garments.filter(g => g.saleEnabled === false).forEach(g => { delete g.printify; });
+  await put('wear-templates.json', local);
+  await copyFile(new URL('server.js', root), join(work, 'server.mjs'));
+  const socket = createServer().listen(0, '127.0.0.1');
+  await once(socket, 'listening');
+  const port = socket.address().port;
+  await new Promise(resolve => socket.close(resolve));
+  const child = spawn(process.execPath, ['--import', interceptor, join(work, 'server.mjs')],
+    { env: { ...env, PORT: String(port) }, stdio: ['ignore', 'pipe', 'pipe'] });
+  t.after(async () => { if (child.exitCode === null) { const stopped = once(child, 'exit'); child.kill(); await stopped; } });
+  await new Promise((resolve, reject) => {
+    const timer = setTimeout(() => reject(new Error('startup timeout')), 10000);
+    child.stdout.on('data', c => { if (String(c).includes('CelebritySignatures →')) { clearTimeout(timer); resolve(); } });
+    child.once('error', e => { clearTimeout(timer); reject(e); });
+    child.once('exit', c => { clearTimeout(timer); reject(new Error('server exited '+c)); });
+  });
+  const buy = items => fetch(`http://127.0.0.1:${port}/api/wear-checkout`, { method: 'POST',
+    headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: 'fixture@example.invalid', items }) });
+  for (const id of ids) {
+    for (const items of [[item(id)], [item('classic-tee'), item(id)]]) {
+      const res = await buy(items);
+      assert.equal(res.status, 503);
+      assert.match((await res.json()).error, /coming soon/);
+    }
+  }
+  assert.deepEqual(JSON.parse(await readFile(join(work, 'data/wear-orders.json'), 'utf8')), []);
+  await assert.rejects(readFile(join(work, 'requests.jsonl')), { code: 'ENOENT' });
+  const res = await buy([item('classic-tee')]);
+  assert.equal(res.status, 200);
+  assert.equal((await res.json()).url, 'https://fixture.invalid/checkout');
+  const requests = (await readFile(join(work, 'requests.jsonl'), 'utf8')).trim().split('\n').map(JSON.parse);
+  assert.equal(requests.length, 1);
+  assert.equal(requests[0].url, 'https://api.stripe.com/v1/checkout/sessions');
+  const order = JSON.parse(await readFile(join(work, 'data/wear-orders.json'), 'utf8'))[0];
+  assert.equal(order.mode, 'test');
+  assert.equal(order.items[0].garment, 'classic-tee');
+});
+
+test('sender holds accessories even with --apply and all account gates set', async t => {
+  const { work, interceptor } = await fixture(t);
+  const script = join(work, 'scripts/submit-pod-draft-printify.mjs');
+  await copyFile(new URL('scripts/submit-pod-draft-printify.mjs', root), script);
+  const queue = ids.map((id, i) => JSON.stringify({ ...draft, ...item(id), orderId: `fixture.${i}` })).join('\n')+'\n';
+  const file = join(work, 'data/pod-order-drafts.jsonl');
+  await writeFile(file, queue);
+  const { stdout } = await promisify(execFile)(process.execPath, ['--import', interceptor, script, '--apply'], { env, timeout: 10000 });
+  assert.equal((stdout.match(/HELD:/g) || []).length, 4, stdout);
+  assert.equal(await readFile(file, 'utf8'), queue);
+  await assert.rejects(readFile(join(work, 'requests.jsonl')), { code: 'ENOENT' });
+});
diff --git a/verification/e2e-proof.json b/verification/e2e-proof.json
index 38a0f1c..d494ffd 100644
--- a/verification/e2e-proof.json
+++ b/verification/e2e-proof.json
@@ -1,18 +1,25 @@
 {
-  "intent": "TK-10286: make mural and clothing shopping options discoverable on the Politics gallery and carry the chosen signature into each destination; independently verify black-polo fulfillment artwork.",
-  "riskTier": "R2 local UI; R3 isolated fulfillment fixtures; production deployment held for approval",
-  "timestamp": "2026-09-09T15:23:50.599Z",
+  "intent": "TK-10286: expose mural/clothing shopping from Politics; add Dad Cap, Trucker Cap, Bucket Hat and Crew Socks with real-photo previews and gated sales; preserve shirt checkout and black-polo fulfillment artwork.",
+  "riskTier": "R2 local UI; R3 isolated checkout/fulfillment fixtures; R4 provider writes and release remain gated",
+  "timestamp": "2026-09-09T15:50:32.070Z",
   "buildIdentity": {
-    "base": "2dd7c66",
+    "base": "f19730e",
     "artworkFix": "c9d72f3",
     "artworkRegressionTest": "c90a83c",
     "files": {
       "public/index.html": "ca359040925874615ceec01debe93de33cd34a4fd682c012bb79cfa5ec732a1c",
       "public/murals.html": "92882d5dc157e218e19b7522501f8cffcac2be00e78faca78d64a47f6a1ca3fc",
-      "public/wear.html": "c0627f6b104c608e91dbab355fde4c8457edfc591cdf3be346cc616511fb1445",
-      "server.js": "a205a957da764da9efb1fb13489687fd0364bd7bd28f518990359da33d1d0a1b",
+      "public/wear.html": "9146b83644733ae0d9418a240a00abec632e11ba36e0b30eecdd6ad583da5b6a",
+      "server.js": "5d0d9a788d57e74ea6814199563a5ba4dfaa74ac94110f7baf77b9a7c71ba9bb",
       "scripts/reconcile-wear-orders.mjs": "d943e7eb33471aaa74da9cb96ae010d3c263b74623088ecc4463898ecff27312",
-      "scripts/submit-pod-draft-printify.mjs": "9c94dd5c0b8528ac14d9b66d38e2ef54ab36ffe9f887ad4f6affa480acae3d7f"
+      "scripts/submit-pod-draft-printify.mjs": "8075be352ae941ca10f1b67acf7d6a4ca741a499d2c16164e8a319d465b0aa5f",
+      "data/wear-templates.json": "7a54f46cc406d01774c4a8ea146139533468d92fde2a3cab16ab910b9429b3d3",
+      "public/assets/garments/dad-cap-white.jpg": "d25c6efef1c1978c30c1d0081c0c137bc0d8b9fe0fa86767202773e9293b6333",
+      "public/assets/garments/trucker-cap-white.jpg": "e69b73dace31b34a61a0bf89e11f65a18bc3e03553e6e872c7c4e5513fcfc56e",
+      "public/assets/garments/bucket-hat-army.jpg": "14561d80699747c3ebca019e51a93f3280d8d918f29d6e5b4ae1fa4b40cc81dc",
+      "public/assets/garments/crew-socks-white.jpg": "7b2aacf78ae91b0f45e67d9f75ca9d86ce487a7c987bd7cab37ef3fd7e9d024a",
+      "test/wear-accessories.test.mjs": "0fa6b42d359a9ba615661b0cd2e7ba739987174fbb1847e69941cda80d362aea",
+      "verification/gallery-shopping.e2e.cjs": "1a7a3fa39594d210fc966470d4de72444b981750e1c2d935a9256ca5a1183ad3"
     }
   },
   "environment": {
@@ -23,7 +30,7 @@
   "baseline": {
     "cleanAtSessionStart": "f2976c0",
     "productionState": "Peer enabled public checkout in Stripe TEST mode during this session; live GET at 15:16Z reports purchasable=true. Local GET reports false. Real payment activation and paid order proof remain peer-owned gates.",
-    "concurrentChanges": "Other session owns compact-card styling/availability icons and payment configuration. This session preserved those changes and owns shopping links/deep-link handlers plus evidence."
+    "concurrentChanges": "Peer claude-run-10286 deployed f19730e shopping links + compact cards under separate Steve approval; owns original payment launch handoff. This session owns four accessories, held-item checkout/sender guards, dual-leg payloads and evidence. No accessory deploy authorization received."
   },
   "commands": [
     "node --test test/printify-pod.test.mjs test/wear-artwork-flow.test.mjs",
@@ -31,7 +38,11 @@
     "node /Users/macstudio3/.agents/skills/3x/run.js --url http://127.0.0.1:9956/?cat=Politics --expect \"Shop clothing\" --selector \".card .shop-clothing\" --no-open",
     "Inline script parse with node:vm for public/index.html, public/murals.html, public/wear.html",
     "git diff --check",
-    "Read-only production sha256sum of server.js, reconcile-wear-orders.mjs, submit-pod-draft-printify.mjs; command -v convert"
+    "Read-only production sha256sum of server.js, reconcile-wear-orders.mjs, submit-pod-draft-printify.mjs; command -v convert",
+    "node --test test/printify-pod.test.mjs test/wear-artwork-flow.test.mjs test/wear-accessories.test.mjs",
+    "GET Printify catalog blueprints and variants via tmp/printify-read.mjs; official photo GETs and sips JPEG encoding",
+    "Read-only remote sha256sum public/index.html public/wear.html public/murals.html",
+    "node /Users/macstudio3/.agents/skills/3x/run.js --url http://127.0.0.1:9956/wear?qid=Q91 --expect \"Historic signatures on shirts, hats and socks.\" --selector \"#garments [data-g=crew-socks]\" --no-open"
   ],
   "assertions": [
     {
@@ -41,7 +52,7 @@
     },
     {
       "boundary": "UI -> clothing",
-      "check": "Click Lincoln clothing: Q91 carried in URL, Abraham Lincoln configurator opens, 3 garment choices, black polo 2XL price $48; back returns to Politics.",
+      "check": "Desktop/mobile Lincoln Clothing -> Q91 configurator, seven product choices, black polo 2XL $48; each of four accessory photos loads, One size selected, price/button Coming soon, purchase disabled, inaccurate generic tee 3D toggle hidden, socks size note intact; switch back to tee restores purchase control; back returns to Politics.",
       "verdict": "PASS"
     },
     {
@@ -56,7 +67,7 @@
     },
     {
       "boundary": "browser",
-      "check": "Both viewport journeys: zero page JavaScript errors. 3x: HTTP, headless render, Playwright render, Chrome parity all pass (4/4 attempted).",
+      "check": "Final desktop/mobile journey: zero JavaScript errors. Final accessories 3x with corrected static expectation: HTTP contract, headless render, Playwright socks-selector render, Chrome parity all PASS (4/4 attempted). Safari/Firefox SKIP.",
       "verdict": "PASS"
     },
     {
@@ -77,29 +88,77 @@
     },
     {
       "boundary": "deployed runtime",
-      "check": "Remote server/reconciler/sender SHA256 exactly match local verified sources; /usr/bin/convert present.",
+      "check": "Before accessories edits, remote runtime hashes matched the previously verified artwork fix. Later read-only SSH confirms deployed home/wear/murals source SHA256 equals f19730e. Raw public HTML adds runtime ads/pixel/footer and is not byte-identical to static source. New accessory backend/template/UI changes remain local.",
       "verdict": "PASS"
     },
     {
       "boundary": "public shopping links",
-      "check": "Deployment of this session shopping-link HTML changes.",
-      "verdict": "SKIP",
-      "reason": "Customer-facing deployment requires user approval under ticket instructions; no deploy run by this session."
+      "check": "Peer deployed f19730e under separate Steve approval. Independently read remote files and confirmed SHA256 matches home ca359040925874615ceec01debe93de33cd34a4fd682c012bb79cfa5ec732a1c, wear c0627f6b104c608e91dbab355fde4c8457edfc591cdf3be346cc616511fb1445, murals 92882d5dc157e218e19b7522501f8cffcac2be00e78faca78d64a47f6a1ca3fc; all public GETs return 200.",
+      "verdict": "PASS"
     },
     {
       "boundary": "real purchase/fulfillment",
       "check": "Completed live-site Stripe TEST purchase and Printify order acceptance/production.",
       "verdict": "SKIP",
       "reason": "Current active peer owns launch handoff; this session has no permission for provider order submission or real charges."
+    },
+    {
+      "boundary": "printer catalog -> payload",
+      "check": "Real saved catalog GET records prove every accessory blueprint/provider/variant and raster print area. Dad front_dtf; trucker/bucket front; socks left_leg plus right_leg carry same design URL. No real provider acceptance claim.",
+      "verdict": "PASS"
+    },
+    {
+      "boundary": "UI -> checkout -> persisted state",
+      "check": "Actual server in temporary fixture: four accessories directly and mixed with shirt -> 503 Coming soon; no order records or intercepted Stripe calls. Existing tee then creates one fake Stripe TEST session and persists correct order. Held items lacking mappings do not disable shirts.",
+      "verdict": "PASS"
+    },
+    {
+      "boundary": "queue -> sender -> external writes",
+      "check": "Actual Printify sender --apply in isolated fixture with all five account gates true: four HELD results; no intercepted fetch call, no SUBMITTED marker, unchanged queue bytes.",
+      "verdict": "PASS"
+    },
+    {
+      "boundary": "visual placement",
+      "check": "Inspected all four rendered real-photo composites. Initial bucket overlay too low; corrected to crown and visually verified final desktop screenshot. Both socks carry signature on cloth. These are provisional previews, not manufacturing proof.",
+      "verdict": "PASS"
+    },
+    {
+      "boundary": "new accessory cost and physical print placement",
+      "check": "Actual Printify product creation/cost retrieval/generated mockup verification.",
+      "verdict": "SKIP",
+      "reason": "External writes and cleanup require Steve approval per ticket. Exact four-product request plan and scoped approval memo prepared; no requests executed."
+    },
+    {
+      "boundary": "new accessory publication and purchases",
+      "check": "Deploy previews, set verified prices and release the four items for sale.",
+      "verdict": "SKIP",
+      "reason": "Customer-facing deployment remains gated. All four retain saleEnabled=false, priceUsd=null. Original payment launch is peer-owned."
+    },
+    {
+      "boundary": "test suite",
+      "check": "node --test test/printify-pod.test.mjs test/wear-artwork-flow.test.mjs test/wear-accessories.test.mjs: 9/9 PASS on final source.",
+      "verdict": "PASS"
     }
   ],
   "artifacts": {
-    "shopping": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk10286-shopping-bCF6Aw",
+    "shopping": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk10286-shopping-PuNAnF",
     "gallery3x": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/3x-TMkf1D",
     "wear3x": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/3x-YB6Clw",
     "repeatableShoppingRunner": "verification/gallery-shopping.e2e.cjs",
-    "repeatableFulfillmentTest": "test/wear-artwork-flow.test.mjs"
+    "repeatableFulfillmentTest": "test/wear-artwork-flow.test.mjs",
+    "previousShopping": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk10286-shopping-bCF6Aw",
+    "catalog": "verification/hat-sock-catalog.json",
+    "printerRequestPlan": "verification/hat-sock-draft-plan.json",
+    "approval": "verification/hats-socks-approval.md",
+    "decision": "verification/hats-socks-decision.md",
+    "accessoryIntegration": "test/wear-accessories.test.mjs",
+    "accessory3x": "/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/3x-6kSOm3"
   },
-  "cleanup": "Synthetic payment fixture projects removed; test servers stopped; browser contexts closed. No real orders, credentials, payment flags, provider settings, external orders or production files changed by this session. Browser runner blocks all non-GET/HEAD requests.",
-  "verdict": "PARTIAL: local shopping fix and isolated artwork integration verified; publication and real purchase/fulfillment remain gated."
+  "cleanup": "Isolated fixture projects removed and servers stopped. Browser contexts closed; artifacts retained. Only catalog/photo GETs to Printify, no real credentials printed, no real payment/provider writes, deploy, orders, or settings changed by this session. Browser runner blocks all non-GET/HEAD requests.",
+  "verdict": "PARTIAL: local accessories and integration proof pass; gallery shopping links deployed by peer and independently confirmed. Printer cost/mockup verification, accessory publication and purchase release require approval; ticket not marked done.",
+  "correctedVerificationIssues": [
+    "macOS /var symlink meant fixture sender main guard was not exercised; fixture canonical path fixed and four actual HELD results asserted.",
+    "Initial 3x HTTP check expected dynamically rendered Dad Cap in static HTML; 3/4 passed, HTTP assertion failed. Corrected static lede expectation, retained socks browser selector.",
+    "Premature peer DM claiming raw HTTP source equality corrected immediately; actual remote file SHA256 subsequently verifies deployed commit."
+  ]
 }
diff --git a/verification/gallery-shopping.e2e.cjs b/verification/gallery-shopping.e2e.cjs
index 6a033f4..38e36b1 100644
--- a/verification/gallery-shopping.e2e.cjs
+++ b/verification/gallery-shopping.e2e.cjs
@@ -31,18 +31,37 @@ const checks = [];
       assert.ok(await card.locator('.shop-clothing').isVisible());
       assert.equal(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), true);
       await card.scrollIntoViewIfNeeded();
+      await card.locator('.sig img').evaluate(img => img.complete && img.naturalWidth ? true : new Promise((resolve, reject) => { img.onload=resolve; img.onerror=()=>reject(new Error('Signature image unavailable')); }));
       await page.screenshot({ path:path.join(artifacts, `${name}-card.png`) });
       await card.locator('.shop-clothing').click();
       await page.waitForURL('**/wear?**');
       await page.locator('#scrim.on').waitFor();
       assert.equal(await page.locator('#pName').textContent(), 'Abraham Lincoln');
       assert.equal(new URL(page.url()).searchParams.get('qid'), 'Q91');
-      assert.equal(await page.locator('#garments .chip').count(), 3);
+      assert.equal(await page.locator('#garments .chip').count(), 7);
       await page.locator('#garments [data-g="polo"]').click();
       await page.locator('#colors [data-c="black"]').click();
       await page.locator('#sizes [data-z="2XL"]').click();
       assert.match(await page.locator('#price').textContent(), /48\.00/);
+      await page.waitForFunction(() => photoCache[state.color.photo]?.naturalWidth > 0 && sigImg?.naturalWidth > 0);
       await page.screenshot({ path:path.join(artifacts, `${name}-clothing.png`) });
+      for (const id of ['dad-cap', 'trucker-cap', 'bucket-hat', 'crew-socks']) {
+        await page.locator(`#garments [data-g="${id}"]`).click();
+        await page.waitForFunction(id => state.garment.id === id && photoCache[state.color.photo]?.naturalWidth > 0, id);
+        assert.equal(await page.locator('#sizes .chip').textContent(), 'One size');
+        assert.equal(await page.locator('#buy').isDisabled(), true);
+        assert.equal(await page.locator('#buy').textContent(), 'Coming soon');
+        assert.equal(await page.locator('#price').textContent(), 'Coming soon');
+        assert.equal(await page.locator('#view3dToggle').isVisible(), false);
+        assert.match(await page.locator('#productNote').textContent(), /Placement preview/);
+        if (id === 'crew-socks') assert.match(await page.locator('#sizeNote').textContent(), /6–10/);
+        await page.locator('#stage').screenshot({ path:path.join(artifacts, `${name}-${id}-preview.png`) });
+        await page.locator('#buy').scrollIntoViewIfNeeded();
+        await page.screenshot({ path:path.join(artifacts, `${name}-${id}-options.png`) });
+      }
+      await page.locator('#garments [data-g="classic-tee"]').click();
+      assert.equal(await page.locator('#buy').isDisabled(), false);
+      assert.equal(await page.locator('#view3dToggle').isVisible(), true);
       await page.goBack({ waitUntil:'domcontentloaded' });
       assert.equal(new URL(page.url()).searchParams.get('cat'), 'Politics');
       await card.locator('.sig').click();
@@ -66,7 +85,7 @@ const checks = [];
       assert.equal(await page.locator('#scrim.on').count(), 0);
       assert.ok(await page.locator('#grid .card').count() > 0);
       assert.deepEqual(errors, []);
-      checks.push({ viewport:name, verdict:'PASS', paths:['Politics card -> selected clothing', 'Politics details -> matching mural finder', 'unknown clothing signature rejected'], pageErrors:errors });
+      checks.push({ viewport:name, verdict:'PASS', paths:['Politics card -> selected clothing', 'four accessory photos, sizes and sale holds', 'switch back to shirt restores purchase control', 'Politics details -> matching mural finder', 'unknown clothing signature rejected'], pageErrors:errors });
       await context.close();
     }
   } finally { await browser.close(); }
diff --git a/verification/hat-sock-catalog.json b/verification/hat-sock-catalog.json
new file mode 100644
index 0000000..8008061
--- /dev/null
+++ b/verification/hat-sock-catalog.json
@@ -0,0 +1,847 @@
+{
+  "timestamp": "2026-09-09T15:37:19.328Z",
+  "source": "https://api.printify.com/v1/catalog",
+  "products": [
+    {
+      "id": "dad-cap",
+      "blueprint": {
+        "id": 1447,
+        "title": "Classic Dad Cap",
+        "description": "Introducing the classic dad cap, a timeless accessory crafted for both style and comfort. Made from 100% cotton (Green Camo variant: 65% polyester / 35% cotton), this cap offers a soft and breathable feel that's perfect for all-day wear. Its unstructured design and low-profile fit give it a laid-back vibe, while the antique brass buckle closure adds a touch of vintage charm. The matching undervisor and four-row stitching on the visor provide subtle yet stylish details. With six panels and a Permacurv® visor, this cap offers a perfect blend of durability and classic aesthetics.<div>.:100% cotton (Green Camo is 65% polyester, 35% cotton)</div><br /><div>.:Closure: Self-fabric hideaway strap with antique brass buckle and grommet</div><br /><div>.:Sewn-in label</div><br /><div>.:Brand sticker on the visor</div>",
+        "brand": "Yupoong",
+        "model": "6245CM",
+        "images": [
+          "https://images.printify.com/66c4719a26c12b30ed07acc2",
+          "https://images.printify.com/66c47195bb474d73730ec5f2",
+          "https://images.printify.com/690c3f0c960e8a60510fac83",
+          "https://images.printify.com/690c3e47960e8a60510fac82",
+          "https://images.printify.com/66c471a2df4dc38b5b08c8b2",
+          "https://images.printify.com/683da14c86f92c37460220d2",
+          "https://images.printify.com/683da154e8fc6ac35907dd02",
+          "https://images.printify.com/683da160eeb4194383079eb2",
+          "https://images.printify.com/6620e034e30a1c1a5e0b6e13",
+          "https://images.printify.com/6620e094592fe11d60031d54",
+          "https://images.printify.com/6620e0ecd4820dc03b0350f2",
+          "https://images.printify.com/6630a99d60cb72053e050682",
+          "https://images.printify.com/6630a99ee5b4152fd00c8ed2",
+          "https://images.printify.com/663c7d44ed007caa6d001eb2"
+        ]
+      },
+      "catalog": {
+        "id": 99,
+        "title": "Printify Choice",
+        "variants": [
+          {
+            "id": 105372,
+            "title": "Black / One size",
+            "options": {
+              "size": "One size",
+              "color": "Black"
+            },
+            "placeholders": [
+              {
+                "position": "back_hat_embroidery",
+                "decoration_method": "embroidery",
+                "width": 600,
+                "height": 300
+              },
+              {
+                "position": "right_hat_embroidery",
+                "decoration_method": "embroidery",
+                "width": 600,
+                "height": 300
+              },
+              {
+                "position": "left_hat_embroidery",
+                "decoration_method": "embroidery",
+                "width": 600,
+                "height": 300
+              },
+              {
+                "position": "front_dtf",
+                "decoration_method": "dtf",
+                "width": 1500,
+                "height": 600
+              },
+              {
+                "position": "front",
+                "decoration_method": "embroidery",
+                "width": 1200,
+                "height": 675
+              }
+            ],
+            "decoration_methods": [
+              "dtf",
+              "embroidery"
+            ]
+          },
+          {
+            "id": 105373,
+            "title": "Dark Grey / One size",
+            "options": {
+              "color": "Dark Grey",
+              "size": "One size"
+            },
+            "placeholders": [
+              {
+                "position": "back_hat_embroidery",
+                "decoration_method": "embroidery",
+                "width": 600,
+                "height": 300
+              },
+              {
+                "position": "right_hat_embroidery",
+                "decoration_method": "embroidery",
+                "width": 600,
+                "height": 300
+              },
+              {
+                "position": "left_hat_embroidery",
+                "decoration_method": "embroidery",
+                "width": 600,
+                "height": 300
+              },
+              {
+                "position": "front_dtf",
+                "decoration_method": "dtf",
+                "width": 1500,
+                "height": 600
+              },
+              {
+                "position": "front",
+                "decoration_method": "embroidery",
+                "width": 1200,
+                "height": 675
+              }
+            ],
+            "decoration_methods": [
+              "dtf",
+              "embroidery"
+            ]
+          },
+          {
+            "id": 105374,
+            "title": "Green Camo / One size",
+            "options": {
+              "size": "One size",
+              "color": "Green Camo"
+            },
+            "placeholders": [
+              {
+                "position": "back_hat_embroidery",
+                "decoration_method": "embroidery",
+                "width": 600,
+                "height": 300
+              },
+              {
+                "position": "right_hat_embroidery",
+                "decoration_method": "embroidery",
+                "width": 600,
+                "height": 300
+              },
+              {
+                "position": "left_hat_embroidery",
+                "decoration_method": "embroidery",
+                "width": 600,
+                "height": 300
+              },
+              {
+                "position": "front_dtf",
+                "decoration_method": "dtf",
+                "width": 1500,
+                "height": 600
+              },
+              {
+                "position": "front",
+                "decoration_method": "embroidery",
+                "width": 1200,
+                "height": 675
+              }
+            ],
+            "decoration_methods": [
+              "dtf",
+              "embroidery"
+            ]
+          },
+          {
+            "id": 105375,
+            "title": "Khaki / One size",
+            "options": {
+              "size": "One size",
+              "color": "Khaki"
+            },
+            "placeholders": [
+              {
+                "position": "back_hat_embroidery",
+                "decoration_method": "embroidery",
+                "width": 600,
+                "height": 300
+              },
+              {
+                "position": "right_hat_embroidery",
+                "decoration_method": "embroidery",
+                "width": 600,
+                "height": 300
+              },
+              {
+                "position": "left_hat_embroidery",
+                "decoration_method": "embroidery",
+                "width": 600,
+                "height": 300
+              },
+              {
+                "position": "front_dtf",
+                "decoration_method": "dtf",
+                "width": 1500,
+                "height": 600
+              },
+              {
+                "position": "front",
+                "decoration_method": "embroidery",
+                "width": 1200,
+                "height": 675
+              }
+            ],
+            "decoration_methods": [
+              "dtf",
+              "embroidery"
+            ]
+          },
+          {
+            "id": 105376,
+            "title": "Light Blue / One size",
+            "options": {
+              "color": "Light Blue",
+              "size": "One size"
+            },
+            "placeholders": [
+              {
+                "position": "back_hat_embroidery",
+                "decoration_method": "embroidery",
+                "width": 600,
+                "height": 300
+              },
+              {
+                "position": "right_hat_embroidery",
+                "decoration_method": "embroidery",
+                "width": 600,
+                "height": 300
+              },
+              {
+                "position": "left_hat_embroidery",
+                "decoration_method": "embroidery",
+                "width": 600,
+                "height": 300
+              },
+              {
+                "position": "front_dtf",
+                "decoration_method": "dtf",
+                "width": 1500,
+                "height": 600
+              },
+              {
+                "position": "front",
+                "decoration_method": "embroidery",
+                "width": 1200,
+                "height": 675
+              }
+            ],
+            "decoration_methods": [
+              "dtf",
+              "embroidery"
+            ]
+          },
+          {
+            "id": 105377,
+            "title": "Navy / One size",
+            "options": {
+              "color": "Navy",
+              "size": "One size"
+            },
+            "placeholders": [
+              {
+                "position": "back_hat_embroidery",
+                "decoration_method": "embroidery",
+                "width": 600,
+                "height": 300
+              },
+              {
+                "position": "right_hat_embroidery",
+                "decoration_method": "embroidery",
+                "width": 600,
+                "height": 300
+              },
+              {
+                "position": "left_hat_embroidery",
+                "decoration_method": "embroidery",
+                "width": 600,
+                "height": 300
+              },
+              {
+                "position": "front_dtf",
+                "decoration_method": "dtf",
+                "width": 1500,
+                "height": 600
+              },
+              {
+                "position": "front",
+                "decoration_method": "embroidery",
+                "width": 1200,
+                "height": 675
+              }
+            ],
+            "decoration_methods": [
+              "dtf",
+              "embroidery"
+            ]
+          },
+          {
+            "id": 105378,
+            "title": "Pink / One size",
+            "options": {
+              "color": "Pink",
+              "size": "One size"
+            },
+            "placeholders": [
+              {
+                "position": "back_hat_embroidery",
+                "decoration_method": "embroidery",
+                "width": 600,
+                "height": 300
+              },
+              {
+                "position": "right_hat_embroidery",
+                "decoration_method": "embroidery",
+                "width": 600,
+                "height": 300
+              },
+              {
+                "position": "left_hat_embroidery",
+                "decoration_method": "embroidery",
+                "width": 600,
+                "height": 300
+              },
+              {
+                "position": "front_dtf",
+                "decoration_method": "dtf",
+                "width": 1500,
+                "height": 600
+              },
+              {
+                "position": "front",
+                "decoration_method": "embroidery",
+                "width": 1200,
+                "height": 675
+              }
+            ],
+            "decoration_methods": [
+              "dtf",
+              "embroidery"
+            ]
+          },
+          {
+            "id": 105379,
+            "title": "Spruce / One size",
+            "options": {
+              "color": "Spruce",
+              "size": "One size"
+            },
+            "placeholders": [
+              {
+                "position": "back_hat_embroidery",
+                "decoration_method": "embroidery",
+                "width": 600,
+                "height": 300
+              },
+              {
+                "position": "right_hat_embroidery",
+                "decoration_method": "embroidery",
+                "width": 600,
+                "height": 300
+              },
+              {
+                "position": "left_hat_embroidery",
+                "decoration_method": "embroidery",
+                "width": 600,
+                "height": 300
+              },
+              {
+                "position": "front_dtf",
+                "decoration_method": "dtf",
+                "width": 1500,
+                "height": 600
+              },
+              {
+                "position": "front",
+                "decoration_method": "embroidery",
+                "width": 1200,
+                "height": 675
+              }
+            ],
+            "decoration_methods": [
+              "dtf",
+              "embroidery"
+            ]
+          },
+          {
+            "id": 105380,
+            "title": "Stone / One size",
+            "options": {
+              "color": "Stone",
+              "size": "One size"
+            },
+            "placeholders": [
+              {
+                "position": "back_hat_embroidery",
+                "decoration_method": "embroidery",
+                "width": 600,
+                "height": 300
+              },
+              {
+                "position": "right_hat_embroidery",
+                "decoration_method": "embroidery",
+                "width": 600,
+                "height": 300
+              },
+              {
+                "position": "left_hat_embroidery",
+                "decoration_method": "embroidery",
+                "width": 600,
+                "height": 300
+              },
+              {
+                "position": "front_dtf",
+                "decoration_method": "dtf",
+                "width": 1500,
+                "height": 600
+              },
+              {
+                "position": "front",
+                "decoration_method": "embroidery",
+                "width": 1200,
+                "height": 675
+              }
+            ],
+            "decoration_methods": [
+              "dtf",
+              "embroidery"
+            ]
+          },
+          {
+            "id": 105381,
+            "title": "White / One size",
+            "options": {
+              "color": "White",
+              "size": "One size"
+            },
+            "placeholders": [
+              {
+                "position": "back_hat_embroidery",
+                "decoration_method": "embroidery",
+                "width": 600,
+                "height": 300
+              },
+              {
+                "position": "right_hat_embroidery",
+                "decoration_method": "embroidery",
+                "width": 600,
+                "height": 300
+              },
+              {
+                "position": "left_hat_embroidery",
+                "decoration_method": "embroidery",
+                "width": 600,
+                "height": 300
+              },
+              {
+                "position": "front_dtf",
+                "decoration_method": "dtf",
+                "width": 1500,
+                "height": 600
+              },
+              {
+                "position": "front",
+                "decoration_method": "embroidery",
+                "width": 1200,
+                "height": 675
+              }
+            ],
+            "decoration_methods": [
+              "dtf",
+              "embroidery"
+            ]
+          },
+          {
+            "id": 117642,
+            "title": "Cranberry / One size",
+            "options": {
+              "color": "Cranberry",
+              "size": "One size"
+            },
+            "placeholders": [
+              {
+                "position": "back_hat_embroidery",
+                "decoration_method": "embroidery",
+                "width": 600,
+                "height": 300
+              },
+              {
+                "position": "right_hat_embroidery",
+                "decoration_method": "embroidery",
+                "width": 600,
+                "height": 300
+              },
+              {
+                "position": "left_hat_embroidery",
+                "decoration_method": "embroidery",
+                "width": 600,
+                "height": 300
+              },
+              {
+                "position": "front_dtf",
+                "decoration_method": "dtf",
+                "width": 1500,
+                "height": 600
+              },
+              {
+                "position": "front",
+                "decoration_method": "embroidery",
+                "width": 1200,
+                "height": 675
+              }
+            ],
+            "decoration_methods": [
+              "dtf",
+              "embroidery"
+            ]
+          }
+        ]
+      }
+    },
+    {
+      "id": "trucker-cap",
+      "blueprint": {
+        "id": 1692,
+        "title": "Trucker Retro Hat",
+        "description": "<p>Travel back in time with Trucker Retro Hat. The nostalgic trucker shape is ramped up with craftsmanship and quality, resulting in a top-seller trucker cap that is beloved by many regardless of time. The cap comes with premium mesh panels and hard buckram, and it is structured. The cap has a matching plastic snapback closure and a matching undervisor.</p><div>.:Material: 74% polyester, 26% cotton</div><br /><div>.:Mid profile - six panel structured snapback</div><br /><div>.:Permacurv® visor</div><br /><div>.:Mesh back</div>",
+        "brand": "Yupoong",
+        "model": "6606",
+        "images": [
+          "https://images.printify.com/694951405b10cff51502ed62",
+          "https://images.printify.com/694a7bff2056efc42a007622",
+          "https://images.printify.com/69495142532c7f5bdf0c5c92",
+          "https://images.printify.com/694a73130b6266923a0439f2"
+        ]
+      },
+      "catalog": {
+        "id": 99,
+        "title": "Printify Choice",
+        "variants": [
+          {
+            "id": 116761,
+            "title": "One size / Black",
+            "options": {
+              "size": "One size",
+              "color": "Black"
+            },
+            "placeholders": [
+              {
+                "position": "front",
+                "decoration_method": "dtf",
+                "width": 1650,
+                "height": 750
+              }
+            ],
+            "decoration_methods": [
+              "dtf"
+            ]
+          },
+          {
+            "id": 116762,
+            "title": "One size / White",
+            "options": {
+              "size": "One size",
+              "color": "White"
+            },
+            "placeholders": [
+              {
+                "position": "front",
+                "decoration_method": "dtf",
+                "width": 1650,
+                "height": 750
+              }
+            ],
+            "decoration_methods": [
+              "dtf"
+            ]
+          },
+          {
+            "id": 116763,
+            "title": "One size / Black/White",
+            "options": {
+              "size": "One size",
+              "color": "Black/White"
+            },
+            "placeholders": [
+              {
+                "position": "front",
+                "decoration_method": "dtf",
+                "width": 1650,
+                "height": 750
+              }
+            ],
+            "decoration_methods": [
+              "dtf"
+            ]
+          },
+          {
+            "id": 116764,
+            "title": "One size / Charcoal",
+            "options": {
+              "size": "One size",
+              "color": "Charcoal"
+            },
+            "placeholders": [
+              {
+                "position": "front",
+                "decoration_method": "dtf",
+                "width": 1650,
+                "height": 750
+              }
+            ],
+            "decoration_methods": [
+              "dtf"
+            ]
+          },
+          {
+            "id": 116765,
+            "title": "One size / Heather Grey/Black",
+            "options": {
+              "size": "One size",
+              "color": "Heather Grey/Black"
+            },
+            "placeholders": [
+              {
+                "position": "front",
+                "decoration_method": "dtf",
+                "width": 1650,
+                "height": 750
+              }
+            ],
+            "decoration_methods": [
+              "dtf"
+            ]
+          }
+        ]
+      }
+    },
+    {
+      "id": "bucket-hat",
+      "blueprint": {
+        "id": 1698,
+        "title": "Bucket Hat",
+        "description": "Meet the AS Colour Bucket Hat, crafted from light-mid weight 100% cotton for unmatched comfort. It boasts a reinforced brim with stitching, side eyelets, and a tear-out label, ensuring both style and functionality. One size fits all.<div>.:Material: 100% cotton</div><br /><div>.:Reinforced brim</div><br /><div>.:One size</div><br /><div>.:Tear away label</div>",
+        "brand": "AS Colour",
+        "model": "AS1117",
+        "images": [
+          "https://images.printify.com/693146b0182c66341c092922",
+          "https://images.printify.com/6932f87d30328d4d000009e2",
+          "https://images.printify.com/6932f87f3ce6018682099702",
+          "https://images.printify.com/6932ccdc35bd4f5462075bb2",
+          "https://images.printify.com/693146aae36b7890ea06b1c2",
+          "https://images.printify.com/693146ae531c1a6af7038732"
+        ]
+      },
+      "catalog": {
+        "id": 99,
+        "title": "Printify Choice",
+        "variants": [
+          {
+            "id": 116653,
+            "title": "One size / Army",
+            "options": {
+              "size": "One size",
+              "color": "Army"
+            },
+            "placeholders": [
+              {
+                "position": "front",
+                "decoration_method": "dtf",
+                "width": 1500,
+                "height": 705
+              }
+            ],
+            "decoration_methods": [
+              "dtf"
+            ]
+          },
+          {
+            "id": 116654,
+            "title": "One size / Black",
+            "options": {
+              "size": "One size",
+              "color": "Black"
+            },
+            "placeholders": [
+              {
+                "position": "front",
+                "decoration_method": "dtf",
+                "width": 1500,
+                "height": 705
+              }
+            ],
+            "decoration_methods": [
+              "dtf"
+            ]
+          },
+          {
+            "id": 116655,
+            "title": "One size / Bone",
+            "options": {
+              "size": "One size",
+              "color": "Bone"
+            },
+            "placeholders": [
+              {
+                "position": "front",
+                "decoration_method": "dtf",
+                "width": 1500,
+                "height": 705
+              }
+            ],
+            "decoration_methods": [
+              "dtf"
+            ]
+          },
+          {
+            "id": 116659,
+            "title": "One size / Ecru",
+            "options": {
+              "size": "One size",
+              "color": "Ecru"
+            },
+            "placeholders": [
+              {
+                "position": "front",
+                "decoration_method": "dtf",
+                "width": 1500,
+                "height": 705
+              }
+            ],
+            "decoration_methods": [
+              "dtf"
+            ]
+          },
+          {
+            "id": 116661,
+            "title": "One size / Orchid",
+            "options": {
+              "size": "One size",
+              "color": "Orchid"
+            },
+            "placeholders": [
+              {
+                "position": "front",
+                "decoration_method": "dtf",
+                "width": 1500,
+                "height": 705
+              }
+            ],
+            "decoration_methods": [
+              "dtf"
+            ]
+          },
+          {
+            "id": 116662,
+            "title": "One size / Storm",
+            "options": {
+              "size": "One size",
+              "color": "Storm"
+            },
+            "placeholders": [
+              {
+                "position": "front",
+                "decoration_method": "dtf",
+                "width": 1500,
+                "height": 705
+              }
+            ],
+            "decoration_methods": [
+              "dtf"
+            ]
+          },
+          {
+            "id": 116663,
+            "title": "One size / Walnut",
+            "options": {
+              "size": "One size",
+              "color": "Walnut"
+            },
+            "placeholders": [
+              {
+                "position": "front",
+                "decoration_method": "dtf",
+                "width": 1500,
+                "height": 705
+              }
+            ],
+            "decoration_methods": [
+              "dtf"
+            ]
+          }
+        ]
+      }
+    },
+    {
+      "id": "crew-socks",
+      "blueprint": {
+        "id": 2941,
+        "title": "Unisex Padded Crew Socks (DTG)",
+        "description": "<p>Made for everyday wear and bold graphics, these crew socks use DTG printing for a true full-bleed and wrap-around design. The long ribbed top and ribbed sole keep the fit secure, while light cushioning at the toe and heel adds all-day comfort. While the rest of the sock is fully customizable, the toe and heel are black.<br><br>Please note: Each pair is packaged with a generic header card that includes the product description.</p><div>.:Material: 86% Polyester, 10% Nylon, 4% Spandex</div><br /><div>.:One size: (US Men's 6 - 10 ((EU 34 - 42.5))</div><br /><div>.:Thick and cushioned structure</div><br /><div>.:Long ribbed top and ribbed sole</div><br /><div>.:Decoration method: DTG (Direct-to-Garment)</div><br /><div>.:Please note: Each pair is packaged with a generic header card that includes the product description</div>",
+        "brand": "Generic brand",
+        "model": "",
+        "images": [
+          "https://images.printify.com/69b954a46002a5644e04d4d2",
+          "https://images.printify.com/69b94e55e63a8fd488086090",
+          "https://images.printify.com/69b9314ad6db2ffd290a0072",
+          "https://images.printify.com/69b931489b56ff6fb805be92",
+          "https://images.printify.com/69b93146440bc5faea0225c2",
+          "https://images.printify.com/69b9314492584af9ec02c475",
+          "https://images.printify.com/69b954a03c48f49b10039122"
+        ]
+      },
+      "catalog": {
+        "id": 29,
+        "title": "Monster Digital",
+        "variants": [
+          {
+            "id": 155679,
+            "title": "One size / White",
+            "options": {
+              "size": "One size",
+              "color": "White"
+            },
+            "placeholders": [
+              {
+                "position": "left_leg",
+                "decoration_method": "dtg",
+                "width": 3050,
+                "height": 5258
+              },
+              {
+                "position": "right_leg",
+                "decoration_method": "dtg",
+                "width": 3050,
+                "height": 5258
+              }
+            ],
+            "decoration_methods": [
+              "dtg"
+            ]
+          }
+        ]
+      }
+    }
+  ],
+  "status": "Catalog verified; prices and printer mockups not verified; sales held."
+}
diff --git a/verification/hat-sock-draft-plan.json b/verification/hat-sock-draft-plan.json
new file mode 100644
index 0000000..ac0f68f
--- /dev/null
+++ b/verification/hat-sock-draft-plan.json
@@ -0,0 +1,179 @@
+{
+  "status": "DRAFT_ONLY_REQUIRES_APPROVAL",
+  "shopId": 496929,
+  "note": "Not executed. Two uploads, four unpublished temporary products, GET evidence, approved cleanup of only created records. API-required temporary price 10000 cents is not retail pricing or a cost estimate.",
+  "requests": [
+    {
+      "garment": "dad-cap",
+      "method": "POST",
+      "endpoint": "https://api.printify.com/v1/shops/496929/products.json",
+      "body": {
+        "title": "TEMP-TK10286-VERIFY-dad-cap",
+        "description": "Unpublished temporary cost and print-placement verification; do not publish or order.",
+        "blueprint_id": 1447,
+        "print_provider_id": 99,
+        "variants": [
+          {
+            "id": 105381,
+            "price": 10000,
+            "is_enabled": true
+          }
+        ],
+        "print_areas": [
+          {
+            "variant_ids": [
+              105381
+            ],
+            "placeholders": [
+              {
+                "position": "front_dtf",
+                "images": [
+                  {
+                    "id": "__DARK_SIGNATURE_UPLOAD_ID__",
+                    "x": 0.5,
+                    "y": 0.5,
+                    "scale": 0.7,
+                    "angle": 0
+                  }
+                ]
+              }
+            ]
+          }
+        ],
+        "visible": false
+      }
+    },
+    {
+      "garment": "trucker-cap",
+      "method": "POST",
+      "endpoint": "https://api.printify.com/v1/shops/496929/products.json",
+      "body": {
+        "title": "TEMP-TK10286-VERIFY-trucker-cap",
+        "description": "Unpublished temporary cost and print-placement verification; do not publish or order.",
+        "blueprint_id": 1692,
+        "print_provider_id": 99,
+        "variants": [
+          {
+            "id": 116762,
+            "price": 10000,
+            "is_enabled": true
+          }
+        ],
+        "print_areas": [
+          {
+            "variant_ids": [
+              116762
+            ],
+            "placeholders": [
+              {
+                "position": "front",
+                "images": [
+                  {
+                    "id": "__DARK_SIGNATURE_UPLOAD_ID__",
+                    "x": 0.5,
+                    "y": 0.5,
+                    "scale": 0.7,
+                    "angle": 0
+                  }
+                ]
+              }
+            ]
+          }
+        ],
+        "visible": false
+      }
+    },
+    {
+      "garment": "bucket-hat",
+      "method": "POST",
+      "endpoint": "https://api.printify.com/v1/shops/496929/products.json",
+      "body": {
+        "title": "TEMP-TK10286-VERIFY-bucket-hat",
+        "description": "Unpublished temporary cost and print-placement verification; do not publish or order.",
+        "blueprint_id": 1698,
+        "print_provider_id": 99,
+        "variants": [
+          {
+            "id": 116653,
+            "price": 10000,
+            "is_enabled": true
+          }
+        ],
+        "print_areas": [
+          {
+            "variant_ids": [
+              116653
+            ],
+            "placeholders": [
+              {
+                "position": "front",
+                "images": [
+                  {
+                    "id": "__LIGHT_SIGNATURE_UPLOAD_ID__",
+                    "x": 0.5,
+                    "y": 0.5,
+                    "scale": 0.7,
+                    "angle": 0
+                  }
+                ]
+              }
+            ]
+          }
+        ],
+        "visible": false
+      }
+    },
+    {
+      "garment": "crew-socks",
+      "method": "POST",
+      "endpoint": "https://api.printify.com/v1/shops/496929/products.json",
+      "body": {
+        "title": "TEMP-TK10286-VERIFY-crew-socks",
+        "description": "Unpublished temporary cost and print-placement verification; do not publish or order.",
+        "blueprint_id": 2941,
+        "print_provider_id": 29,
+        "variants": [
+          {
+            "id": 155679,
+            "price": 10000,
+            "is_enabled": true
+          }
+        ],
+        "print_areas": [
+          {
+            "variant_ids": [
+              155679
+            ],
+            "placeholders": [
+              {
+                "position": "left_leg",
+                "images": [
+                  {
+                    "id": "__DARK_SIGNATURE_UPLOAD_ID__",
+                    "x": 0.5,
+                    "y": 0.28,
+                    "scale": 0.45,
+                    "angle": 0
+                  }
+                ]
+              },
+              {
+                "position": "right_leg",
+                "images": [
+                  {
+                    "id": "__DARK_SIGNATURE_UPLOAD_ID__",
+                    "x": 0.5,
+                    "y": 0.28,
+                    "scale": 0.45,
+                    "angle": 0
+                  }
+                ]
+              }
+            ]
+          }
+        ],
+        "visible": false
+      }
+    }
+  ]
+}
diff --git a/verification/hats-socks-approval.md b/verification/hats-socks-approval.md
new file mode 100644
index 0000000..761be96
--- /dev/null
+++ b/verification/hats-socks-approval.md
@@ -0,0 +1,40 @@
+# TK-10286: release three hats and socks
+
+Owner: codex-run-10286. Prepared 2026-09-09. Status: awaiting Steve's approval.
+
+## Implemented and verified locally
+
+The signature picker now offers Dad Cap, Trucker Cap, Bucket Hat, and Crew Socks alongside the three existing shirts. Official blank product photos show provisional signature placements. The socks carry the same signature on both legs. Each addition has `saleEnabled: false` and `priceUsd: null`; the UI says Coming soon and both checkout and the Printify sender reject it. Existing shirt checkout still passes the isolated integration test.
+
+The Politics gallery's Mural and Clothing links were separately deployed by claude-run-10286 under Steve's prior approval. Read-only remote source hashes match commit f19730e. That authorization does not cover these additions.
+
+## Approval requested: printer verification
+
+Authorize at most two signature-image uploads and four unpublished temporary products in Printify shop **496929**, followed by retrieval of costs and generated mockups, then deletion of only those temporary records created by this verification. No orders, production, charges, publishing, checkout flags, or unrelated records are included.
+
+Use a wear-eligible historical signature in dark ink for the white products and light ink for the Army bucket hat. Upload bytes rather than relying on Printify to fetch Wikimedia. The generated request plan is in `hat-sock-draft-plan.json`; upload IDs are resolved from those two responses.
+
+| Product | Blueprint / provider | Variant | Print area(s) | Proposed x / y / scale |
+|---|---|---|---|---|
+| White Dad Cap, one size | 1447 / 99 | 105381 | front_dtf | 0.5 / 0.5 / 0.7 |
+| White Trucker Cap, one size | 1692 / 99 | 116762 | front | 0.5 / 0.5 / 0.7 |
+| Army Bucket Hat, one size | 1698 / 99 | 116653 | front | 0.5 / 0.5 / 0.7 |
+| White Crew Socks, one size | 2941 / 29 | 155679 | left_leg, right_leg | 0.5 / 0.28 / 0.45 each |
+
+Catalog GET evidence proves these variants and raster decoration areas exist. It does **not** prove cost, shipping, print quality or placement. Inspect actual generated printer mockups and record every variant cost before setting retail prices. Request plan price is a temporary API-required value, never a storefront price or cost claim. Retain images and IDs in the evidence bundle before deleting temporary products; verify deletion by GET returning 404. If uploads cannot be deleted, record their IDs and leave them for explicit follow-up rather than deleting anything else. Keep every new product held if verification fails.
+
+## Separate approval requested: deploy the previews
+
+Deploy only the reviewed changes to `data/wear-templates.json`, `public/wear.html`, `server.js`, `scripts/submit-pod-draft-printify.mjs`, and four named `public/assets/garments/*` JPEGs. Preserve production orders, queues, env files and payment/provider settings. Back up the four existing files first; the new files are dad-cap-white.jpg, trucker-cap-white.jpg, bucket-hat-army.jpg, and crew-socks-white.jpg. Restart only the existing CelebritySignatures process, then verify the seven choices, all four Coming soon states, both shopping links, and existing shirt behavior. Restore the backed-up code/template files and restart that same process if checks fail; unused new photo assets may remain.
+
+Deploying this version exposes previews only. Enabling purchases requires verified cost/placement, reviewed retail prices and Steve's release approval. This request does not enable real Stripe payments or send POD orders. The other ticket owner still owns the original shirt purchase/launch handoff.
+
+## Evidence
+
+- `test/wear-accessories.test.mjs`: real catalog/payload checks; all four direct and mixed-cart rejections without order/payment writes; original shirt checkout through intercepted Stripe; sender remains held with --apply and all five account gates enabled.
+- Existing Printify and black-polo artwork regression tests: combined suite 9/9 PASS.
+- `gallery-shopping.e2e.cjs`: desktop 1280×900 and mobile 390×844, all four previews and product controls, return to shirts, Politics-to-clothing and mural journeys, unavailable-signature rejection; zero JavaScript errors. All non-GET/HEAD browser requests blocked.
+- Final screenshot/video bundle: `/var/folders/rq/j8g1f7nn6jv6_lr1cfmqym6w0000gn/T/tk10286-shopping-PuNAnF`.
+- `e2e-proof.json` records exact source hashes and remaining skipped production boundaries. No production readiness or completed ticket claim is made.
+
+Gate source: Steve's ticket instruction requires customer-facing, destructive, spend, publish and external writes to be drafted to pending-approval and stopped before execution.
diff --git a/verification/hats-socks-decision.md b/verification/hats-socks-decision.md
new file mode 100644
index 0000000..71a95b5
--- /dev/null
+++ b/verification/hats-socks-decision.md
@@ -0,0 +1,51 @@
+# DTD decision: hats and socks
+
+ZERO_COST_REQUIRED; no paid model fallback. Choice A: three raster-capable hats plus DTG socks, local previews held until cost/mockup verification and release approval. Choice B: substitute an embroidery beanie needing another artwork process.
+
+## claude
+
+[claude disabled: DTD_ZERO_COST=1]
+
+
+## codex
+
+VERDICT: A
+
+Implement four local mapped previews: Dad Cap bp1447/provider99 (`front_dtf`), Trucker Retro Hat bp1692/provider99 (`dtf`), Bucket Hat bp1698/provider99 (`dtf`), and Crew Socks bp2941/provider29 (DTG `left_leg` and `right_leg`). This satisfies exactly three hat types plus socks using the existing raster artwork pipeline. Verify both sock-leg payloads and retain each item’s sale gate until cost and actual printer mockup placement are proven and explicit approval is received. External product writes, spend, publishing, and deployment remain gated.
+
+## grok
+
+[grok unavailable]
+
+
+## kimi
+
+[kimi unavailable]
+
+
+## muse
+
+[muse unavailable]
+
+
+## qwen
+
+VERDICT: A  
+Implementing the four local mapped previews aligns with the existing raster-based artwork pipeline, ensuring consistency without introducing unverified embroidery processes. Verifying the sock-leg DTG payloads confirms print accuracy, while retaining the per-item sale gate safeguards against unapproved costs or mockup discrepancies. This approach respects the current workflow, avoids unnecessary digitization overhead, and maintains control over external approvals, ensuring compliance with the shop’s operational requirements.
+
+
+## codex-debate
+
+**PROSECUTOR:** A treats specific blueprint IDs, providers, and print-area names as established mappings, but the supplied record contains no provider specifications or successful payload tests. Two agreeing models do not independently validate those details. Local previews could look convincing while depicting placements the printer cannot produce. Sale gates prevent transactions; they do not prevent building the wrong integration or mistaking a speculative preview for manufacturing evidence.
+
+**DEFENDER:** That objection defeats a claim of production readiness, but A authorizes four local previews and explicitly withholds sale until costs and actual printer placement are proven. Its scope matches three hat types plus socks and uses the existing raster pipeline. Unverified mappings are a reason to test these candidates locally. They are not sufficient grounds to reject that bounded implementation. Both sock-leg payloads must be verified; local imagery cannot substitute for printer evidence.
+
+**JUDGE:** The prosecutor establishes that the mappings lack substantiation in this record, but does not establish that they are wrong or that local implementation requires production certainty. The defender prevails because A separates reversible development from commercial authorization.
+
+FINAL: KEEP
+
+The controlling argument is that A authorizes bounded local development while reserving commercial readiness for subsequent evidence and approval. Treat the listed mappings as candidates requiring validation, and local previews as provisional; neither model agreement nor attractive rendering proves printer compatibility. A remains justified within that scope, with each item’s sale gate retained until its cost and actual printer placement are verified.
+
+## Applied verdict and limits
+
+A; post-decision KEEP. Actual catalog GET data in hat-sock-catalog.json and executable payload tests, rather than model agreement, establish variant/area support. Trucker/bucket decoration method is dtf but placeholder name is front. Dad uses front_dtf. Neither payload tests nor previews prove physical print quality; actual printer mockups and costs remain gated.

← f19730e homepage: tighten gallery card UI + wire real mural/clothing  ·  back to CelebritySignatures  ·  Center the product preview text and options e546ddf →