[object Object]

← back to CelebritySignatures

wear: +$4 2XL size upcharge (tee $38 / polo $48) — TK-10286

b152bebaff94b235836e79c4fe3fbe19fb4dd5a2 · 2026-08-31 10:53:34 -0700 · Steve Abrams

2XL costs Printful ~$3-4 more; flat retail ate that margin. Add
sizeUpchargeUsd {2XL:4} per garment; server checkout charges base+upcharge
(source of truth), wear.html shows the adjusted price + a '(2XL +$4)' hint
on size select. Steve's pricing decision (b). Sales still gated OFF.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit b152bebaff94b235836e79c4fe3fbe19fb4dd5a2
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Aug 31 10:53:34 2026 -0700

    wear: +$4 2XL size upcharge (tee $38 / polo $48) — TK-10286
    
    2XL costs Printful ~$3-4 more; flat retail ate that margin. Add
    sizeUpchargeUsd {2XL:4} per garment; server checkout charges base+upcharge
    (source of truth), wear.html shows the adjusted price + a '(2XL +$4)' hint
    on size select. Steve's pricing decision (b). Sales still gated OFF.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 data/wear-templates.json | 121 ++++++++++++++++++++++++++++++++++++++++-------
 public/wear.html         |   3 +-
 server.js                |   5 +-
 3 files changed, 110 insertions(+), 19 deletions(-)

diff --git a/data/wear-templates.json b/data/wear-templates.json
index ae1053d..84f9dd2 100644
--- a/data/wear-templates.json
+++ b/data/wear-templates.json
@@ -1,22 +1,61 @@
 {
   "_note": "Garment TEMPLATES for the /wear left-chest signature apparel feature. Each garment is pure geometry: an SVG viewBox + body path (drawn on canvas via new Path2D), optional accent paths (collar/placket), a set of LIGHT colorways (dark garments deferred — a black signature renders via canvas 'multiply', which needs a light ground), and the pocketBox: the normalized left-chest placement rectangle (viewer-left upper chest = the POD 'left chest' placement Steve asked for). The same pocketBox + path re-render at print resolution when live POD wiring is added.",
   "placement": "left_chest",
-  "printAreaInches": { "w": 4, "h": 3.2 },
+  "printAreaInches": {
+    "w": 4,
+    "h": 3.2
+  },
   "garments": [
     {
       "id": "classic-tee",
       "label": "Classic Tee",
       "priceUsd": 34,
-      "sizes": ["S", "M", "L", "XL", "2XL"],
-      "viewBox": [0, 0, 400, 480],
+      "sizes": [
+        "S",
+        "M",
+        "L",
+        "XL",
+        "2XL"
+      ],
+      "viewBox": [
+        0,
+        0,
+        400,
+        480
+      ],
       "bodyPath": "M150 62 L120 72 L58 112 L40 178 L112 168 L112 430 L288 430 L288 168 L360 178 L342 112 L280 72 L250 62 Q200 104 150 62 Z",
       "accents": [],
-      "pocketBox": { "x": 128, "y": 150, "w": 96, "h": 78 },
+      "pocketBox": {
+        "x": 128,
+        "y": 150,
+        "w": 96,
+        "h": 78
+      },
       "colors": [
-        { "id": "white", "label": "White", "hex": "#f6f4ef", "ink": "#1a1a1a" },
-        { "id": "heather", "label": "Heather Gray", "hex": "#d8d5cd", "ink": "#1a1a1a" },
-        { "id": "sand", "label": "Sand", "hex": "#e7ddca", "ink": "#1a1a1a" },
-        { "id": "sky", "label": "Sky", "hex": "#dbe6ef", "ink": "#16324a" }
+        {
+          "id": "white",
+          "label": "White",
+          "hex": "#f6f4ef",
+          "ink": "#1a1a1a"
+        },
+        {
+          "id": "heather",
+          "label": "Heather Gray",
+          "hex": "#d8d5cd",
+          "ink": "#1a1a1a"
+        },
+        {
+          "id": "sand",
+          "label": "Sand",
+          "hex": "#e7ddca",
+          "ink": "#1a1a1a"
+        },
+        {
+          "id": "sky",
+          "label": "Sky",
+          "hex": "#dbe6ef",
+          "ink": "#16324a"
+        }
       ],
       "printful": {
         "_note": "Printful mapping (fill after PRINTFUL_API_KEY lands — run scripts/fetch-printful-catalog.mjs). productId = Printful catalog product; variants maps our 'color/size' key -> Printful catalog variant_id (each color+size is a distinct Printful variant). placementType is the Printful print-file placement. Left EMPTY on purpose so scripts/submit-pod-draft.mjs FAILS LOUD (never submits an unmapped garment).",
@@ -24,25 +63,70 @@
         "productId": null,
         "placementType": "front",
         "variants": {}
+      },
+      "sizeUpchargeUsd": {
+        "2XL": 4
       }
     },
     {
       "id": "polo",
       "label": "Polo",
       "priceUsd": 44,
-      "sizes": ["S", "M", "L", "XL", "2XL"],
-      "viewBox": [0, 0, 400, 480],
+      "sizes": [
+        "S",
+        "M",
+        "L",
+        "XL",
+        "2XL"
+      ],
+      "viewBox": [
+        0,
+        0,
+        400,
+        480
+      ],
       "bodyPath": "M156 66 L120 72 L58 112 L40 178 L112 168 L112 430 L288 430 L288 168 L360 178 L342 112 L280 72 L244 66 L200 96 Z",
       "accents": [
-        { "d": "M156 66 L200 120 L244 66 L226 66 L200 96 L174 66 Z", "fillShade": -0.08 },
-        { "d": "M193 96 L207 96 L205 150 L195 150 Z", "fillShade": -0.05 }
+        {
+          "d": "M156 66 L200 120 L244 66 L226 66 L200 96 L174 66 Z",
+          "fillShade": -0.08
+        },
+        {
+          "d": "M193 96 L207 96 L205 150 L195 150 Z",
+          "fillShade": -0.05
+        }
       ],
-      "pocketBox": { "x": 128, "y": 158, "w": 92, "h": 74 },
+      "pocketBox": {
+        "x": 128,
+        "y": 158,
+        "w": 92,
+        "h": 74
+      },
       "colors": [
-        { "id": "white", "label": "White", "hex": "#f6f4ef", "ink": "#1a1a1a" },
-        { "id": "heather", "label": "Heather Gray", "hex": "#d8d5cd", "ink": "#1a1a1a" },
-        { "id": "sand", "label": "Sand", "hex": "#e7ddca", "ink": "#1a1a1a" },
-        { "id": "sky", "label": "Sky", "hex": "#dbe6ef", "ink": "#16324a" }
+        {
+          "id": "white",
+          "label": "White",
+          "hex": "#f6f4ef",
+          "ink": "#1a1a1a"
+        },
+        {
+          "id": "heather",
+          "label": "Heather Gray",
+          "hex": "#d8d5cd",
+          "ink": "#1a1a1a"
+        },
+        {
+          "id": "sand",
+          "label": "Sand",
+          "hex": "#e7ddca",
+          "ink": "#1a1a1a"
+        },
+        {
+          "id": "sky",
+          "label": "Sky",
+          "hex": "#dbe6ef",
+          "ink": "#16324a"
+        }
       ],
       "printful": {
         "_note": "Printful mapping (fill after PRINTFUL_API_KEY lands — run scripts/fetch-printful-catalog.mjs). productId = Printful catalog product; variants maps our 'color/size' key -> Printful catalog variant_id. Left EMPTY on purpose so scripts/submit-pod-draft.mjs FAILS LOUD (never submits an unmapped garment).",
@@ -50,6 +134,9 @@
         "productId": null,
         "placementType": "front",
         "variants": {}
+      },
+      "sizeUpchargeUsd": {
+        "2XL": 4
       }
     }
   ]
diff --git a/public/wear.html b/public/wear.html
index 4adbba1..b7a774a 100644
--- a/public/wear.html
+++ b/public/wear.html
@@ -172,7 +172,8 @@ function buildOptions(){
   C.querySelectorAll('.swatch').forEach(el=>el.onclick=()=>{ state.color = state.garment.colors.find(c=>c.id===el.dataset.c); buildOptions(); draw(); });
   Z.innerHTML = state.garment.sizes.map(z=>`<span class="chip ${z===state.size?'sel':''}" data-z="${z}">${z}</span>`).join('');
   Z.querySelectorAll('.chip').forEach(el=>el.onclick=()=>{ state.size=el.dataset.z; buildOptions(); });
-  document.getElementById('price').textContent = '$'+(state.garment.priceUsd).toFixed(2);
+  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})` : '');
 }
 
 let sigImg = null;
diff --git a/server.js b/server.js
index d7e5fc9..fcaa47f 100644
--- a/server.js
+++ b/server.js
@@ -808,7 +808,10 @@ ${paid ? `<div class="ok">✓</div><h1>Order confirmed</h1>
       const color = (garment.colors || []).find(c => c.id === String(b.color || '')) || garment.colors[0];
       if (!(garment.sizes || []).includes(String(b.size))) return sendJSON(res, 400, { ok: false, error: 'unknown size' });
       const size = String(b.size);
-      const amountCents = Math.round((garment.priceUsd || WEAR_GARMENT_DEFAULT_PRICE_USD) * 100);
+      // Base price + any size upcharge (e.g. 2XL costs Printful more; keep our margin flat).
+      const baseUsd = garment.priceUsd || WEAR_GARMENT_DEFAULT_PRICE_USD;
+      const upchargeUsd = (garment.sizeUpchargeUsd && garment.sizeUpchargeUsd[size]) || 0;
+      const amountCents = Math.round((baseUsd + upchargeUsd) * 100);
       const u = await currentUser(req);
       const list = await load('wear-orders.json', []);
       const id = (list.at(-1)?.id || WEAR_ORDER_ID_SEED) + 1;

← 8b5b7ca wear: charge apparel through live-capable Stripe resolver, n  ·  back to CelebritySignatures  ·  chore: v1.0.3 -> v1.0.4 (session close — /wear Stripe live-k a848890 →