← 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
M data/wear-templates.jsonM public/wear.htmlM server.js
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 →