[object Object]

← back to Dw Photo Capture

DW Pattern Photo Capture: mobile-first (iPhone/iPad) camera worklist for 179 photo-less Fentucci patterns; snaps save locally + attach to Shopify draft (filename=DW SKU, leak-safe); basic-auth, sort+density, client-side downscale

2b07e4959a8c3bf5ef0bf47ec4cd68453097c3c0 · 2026-06-24 15:02:43 -0700 · steve@designerwallcoverings.com

Files touched

Diff

commit 2b07e4959a8c3bf5ef0bf47ec4cd68453097c3c0
Author: steve@designerwallcoverings.com <steve@designerwallcoverings.com>
Date:   Wed Jun 24 15:02:43 2026 -0700

    DW Pattern Photo Capture: mobile-first (iPhone/iPad) camera worklist for 179 photo-less Fentucci patterns; snaps save locally + attach to Shopify draft (filename=DW SKU, leak-safe); basic-auth, sort+density, client-side downscale
---
 .gitignore        |    6 +
 data/queue.json   | 1613 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 public/index.html |  167 ++++++
 server.js         |  148 +++++
 4 files changed, 1934 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..043ae50
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+node_modules/
+.env*
+photos/
+data/progress.json
+*.log
+.DS_Store
diff --git a/data/queue.json b/data/queue.json
new file mode 100644
index 0000000..e5bfa0e
--- /dev/null
+++ b/data/queue.json
@@ -0,0 +1,1613 @@
+[
+  {
+    "product_id": "7867414544435",
+    "title": "Modica Grasscloth Wallcovering | Fentucci",
+    "dw_sku": "DWNAT-100154",
+    "mfr": "SG6019",
+    "status": "DRAFT",
+    "price": "29.70",
+    "done": false
+  },
+  {
+    "product_id": "7822225440819",
+    "title": "20232-27 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "20232-27",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822225473587",
+    "title": "20232-30 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "20232-30",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822225539123",
+    "title": "20232-31 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "20232-31",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822225571891",
+    "title": "6863-44 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "6863-44",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822225604659",
+    "title": "6863-52 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "6863-52",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822225670195",
+    "title": "BA206 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "BA206",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822225702963",
+    "title": "BA244T2 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "BA244T2",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822225735731",
+    "title": "BA401 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "BA401",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822225768499",
+    "title": "BA403 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "BA403",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822225834035",
+    "title": "BA405 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "BA405",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822225866803",
+    "title": "BA427 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "BA427",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822225899571",
+    "title": "BA452 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "BA452",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822225932339",
+    "title": "BA466 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "BA466",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822225965107",
+    "title": "BA469 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "BA469",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822226030643",
+    "title": "C3325T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "C3325T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822226063411",
+    "title": "C3348T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "C3348T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822226096179",
+    "title": "C3542T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "C3542T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822226128947",
+    "title": "C4512T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "C4512T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822226194483",
+    "title": "C4522T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "C4522T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822226227251",
+    "title": "C4526T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "C4526T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822226292787",
+    "title": "C4544T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "C4544T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822226358323",
+    "title": "EG503 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "EG503",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822226391091",
+    "title": "EG542 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "EG542",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822226423859",
+    "title": "EG542CT Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "EG542CT",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822226456627",
+    "title": "EG543 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "EG543",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822226489395",
+    "title": "EG548 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "EG548",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822226554931",
+    "title": "EG549 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "EG549",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822226587699",
+    "title": "EG559 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "EG559",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822226653235",
+    "title": "FAB047 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "FAB047",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822226686003",
+    "title": "FAB257 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "FAB257",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822226718771",
+    "title": "FAB258 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "FAB258",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822226751539",
+    "title": "FAB259 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "FAB259",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822226784307",
+    "title": "FAB265 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "FAB265",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822226849843",
+    "title": "FAB267 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "FAB267",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822232453171",
+    "title": "Fentucci Abaca Grasscloth Wallcovering",
+    "dw_sku": "",
+    "mfr": "WNR1213",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822233993267",
+    "title": "Fentucci Abaca Grasscloth Wallcovering \u2014 Blue Cream",
+    "dw_sku": "",
+    "mfr": "ZG1537",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822233862195",
+    "title": "Fentucci Abaca Grasscloth Wallcovering \u2014 Cream And Ice Blue",
+    "dw_sku": "",
+    "mfr": "ZG1530",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822233567283",
+    "title": "Fentucci Burlap Grasscloth Wallcovering \u2014 Black",
+    "dw_sku": "",
+    "mfr": "WS5030",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7664585080883",
+    "title": "Fentucci Damask - Pink Wallcovering | DW Bespoke Studios",
+    "dw_sku": "-25",
+    "mfr": "",
+    "status": "DRAFT",
+    "price": "4.25",
+    "done": false
+  },
+  {
+    "product_id": "7822232715315",
+    "title": "Fentucci Grasscloth Wallcovering",
+    "dw_sku": "",
+    "mfr": "WNR2116",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822233010227",
+    "title": "Fentucci Herringbone Grasscloth Wallcovering",
+    "dw_sku": "",
+    "mfr": "WP7572",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822233042995",
+    "title": "Fentucci Herringbone Grasscloth Wallcovering",
+    "dw_sku": "",
+    "mfr": "WP7573",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822233075763",
+    "title": "Fentucci Herringbone Grasscloth Wallcovering",
+    "dw_sku": "",
+    "mfr": "WP7574",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822233174067",
+    "title": "Fentucci Herringbone Grasscloth Wallcovering",
+    "dw_sku": "",
+    "mfr": "WP7577",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822233829427",
+    "title": "Fentucci Mica Grasscloth Wallcovering",
+    "dw_sku": "",
+    "mfr": "ZG1502",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822230618163",
+    "title": "Fentucci Paperweave Grasscloth Wallcovering",
+    "dw_sku": "",
+    "mfr": "TSJ4785",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822233534515",
+    "title": "Fentucci Paperweave Grasscloth Wallcovering",
+    "dw_sku": "",
+    "mfr": "WS4748T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822232748083",
+    "title": "Fentucci Raffia Grasscloth Wallcovering",
+    "dw_sku": "",
+    "mfr": "WNR2122",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822229962803",
+    "title": "Fentucci Sisal Grasscloth Wallcovering",
+    "dw_sku": "",
+    "mfr": "SH5059T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822233501747",
+    "title": "Fentucci Sisal Grasscloth Wallcovering",
+    "dw_sku": "",
+    "mfr": "WS4701",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822233731123",
+    "title": "Fentucci Sisal Grasscloth Wallcovering \u2014 Brilliant Red",
+    "dw_sku": "",
+    "mfr": "WS5126",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822233698355",
+    "title": "Fentucci Sisal Grasscloth Wallcovering \u2014 Deep Denim Blue",
+    "dw_sku": "",
+    "mfr": "WS5115",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822233600051",
+    "title": "Fentucci Sisal Grasscloth Wallcovering \u2014 Light Blue",
+    "dw_sku": "",
+    "mfr": "WS5040",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822233894963",
+    "title": "Fentucci Sisal Grasscloth Wallcovering \u2014 Light Denim Blue",
+    "dw_sku": "",
+    "mfr": "ZG1535",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822233632819",
+    "title": "Fentucci Sisal Grasscloth Wallcovering \u2014 Light Grey",
+    "dw_sku": "",
+    "mfr": "WS5043",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822233206835",
+    "title": "Fentucci Small Diamond Grasscloth Wallcovering",
+    "dw_sku": "",
+    "mfr": "WP7583",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822233239603",
+    "title": "Fentucci Small Diamond Grasscloth Wallcovering",
+    "dw_sku": "",
+    "mfr": "WP7584",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822233305139",
+    "title": "Fentucci Small Diamond Grasscloth Wallcovering",
+    "dw_sku": "",
+    "mfr": "WP7586",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822226915379",
+    "title": "GR1039 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "GR1039",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822226980915",
+    "title": "GR1042 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "GR1042",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822227013683",
+    "title": "GR1047 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "GR1047",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822227046451",
+    "title": "GR1049 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "GR1049",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822227079219",
+    "title": "GR1050 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "GR1050",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822227111987",
+    "title": "GT3901T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "GT3901T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822227177523",
+    "title": "GT3931T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "GT3931T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822227243059",
+    "title": "GT3940T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "GT3940T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822227275827",
+    "title": "GT3944T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "GT3944T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822227341363",
+    "title": "GT3977T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "GT3977T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822227374131",
+    "title": "GT3980T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "GT3980T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822227406899",
+    "title": "HBD1002T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "HBD1002T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822227439667",
+    "title": "HBD1006T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "HBD1006T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822227505203",
+    "title": "HBD1018T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "HBD1018T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822227537971",
+    "title": "HBD1024T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "HBD1024T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822227570739",
+    "title": "HBD1094T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "HBD1094T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822227603507",
+    "title": "HBD1101T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "HBD1101T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822227669043",
+    "title": "HBD1114T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "HBD1114T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822227701811",
+    "title": "HBD9020T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "HBD9020T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822227734579",
+    "title": "M5552 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "M5552",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822227800115",
+    "title": "MD4002T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "MD4002T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822227832883",
+    "title": "MD4033T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "MD4033T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822227898419",
+    "title": "MD5010T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "MD5010T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822227931187",
+    "title": "MD5020T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "MD5020T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822227963955",
+    "title": "MD5069T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "MD5069T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822228029491",
+    "title": "MD5070 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "MD5070",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822228062259",
+    "title": "MD5074T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "MD5074T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822228095027",
+    "title": "MD5076T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "MD5076T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822228127795",
+    "title": "MD5077 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "MD5077",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822228193331",
+    "title": "MD5082 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "MD5082",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822228226099",
+    "title": "MG1071T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "MG1071T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822228291635",
+    "title": "MG2008 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "MG2008",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822228324403",
+    "title": "MG2009 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "MG2009",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822228357171",
+    "title": "MG2015 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "MG2015",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822228389939",
+    "title": "MG2024 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "MG2024",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822228422707",
+    "title": "NAT4104 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "NAT4104",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822228455475",
+    "title": "ON2501T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "ON2501T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822228488243",
+    "title": "ON2506T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "ON2506T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822228521011",
+    "title": "PG109RSP Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "PG109RSP",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822228553779",
+    "title": "RD2557 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "RD2557",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822228586547",
+    "title": "RH1215T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "RH1215T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822228619315",
+    "title": "RH6013 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "RH6013",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822228652083",
+    "title": "RH6036 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "RH6036",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822228717619",
+    "title": "S3042T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "S3042T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822228750387",
+    "title": "SG2232 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "SG2232",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822228783155",
+    "title": "SG5605T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "SG5605T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822228815923",
+    "title": "SG6001 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "SG6001",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822228979763",
+    "title": "SG6019 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "SG6019",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822229110835",
+    "title": "SG6045 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "SG6045",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822229209139",
+    "title": "SG6081 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "SG6081",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822229340211",
+    "title": "SG6096 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "SG6096",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822229438515",
+    "title": "SG6107 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "SG6107",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822229569587",
+    "title": "SG6114 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "SG6114",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822229700659",
+    "title": "SG6115 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "SG6115",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822229798963",
+    "title": "SG6119 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "SG6119",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822229897267",
+    "title": "SG6139 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "SG6139",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822229930035",
+    "title": "SG6141 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "SG6141",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822229995571",
+    "title": "TR223T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TR223T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822230028339",
+    "title": "TR245T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TR245T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822230061107",
+    "title": "TR254T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TR254T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822230093875",
+    "title": "TR256T Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TR256T",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822230126643",
+    "title": "TS4207 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TS4207",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822230159411",
+    "title": "TSJ3354 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TSJ3354",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822230192179",
+    "title": "TSJ3363 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TSJ3363",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822230224947",
+    "title": "TSJ3395 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TSJ3395",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822230257715",
+    "title": "TSJ3396 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TSJ3396",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822230290483",
+    "title": "TSJ4213 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TSJ4213",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822230323251",
+    "title": "TSJ4219 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TSJ4219",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822230421555",
+    "title": "TSJ4224 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TSJ4224",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822230454323",
+    "title": "TSJ4225 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TSJ4225",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822230487091",
+    "title": "TSJ4241 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TSJ4241",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822230519859",
+    "title": "TSJ4246 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TSJ4246",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822230552627",
+    "title": "TSJ4248 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TSJ4248",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822230683699",
+    "title": "TSJ4786 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TSJ4786",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822230716467",
+    "title": "TSJ4792 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TSJ4792",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822230782003",
+    "title": "TSJ7549 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TSJ7549",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822230814771",
+    "title": "TSJ7550 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TSJ7550",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822230847539",
+    "title": "TSJ7551 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TSJ7551",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822230913075",
+    "title": "TSJ7555 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TSJ7555",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822230978611",
+    "title": "TSJ7562 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TSJ7562",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822231011379",
+    "title": "TW21002 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TW21002",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822231044147",
+    "title": "TW21003 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TW21003",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822231109683",
+    "title": "TW21004 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TW21004",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822231142451",
+    "title": "TW21006 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TW21006",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822231207987",
+    "title": "TW60703 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TW60703",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822231273523",
+    "title": "TW68025 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TW68025",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822231306291",
+    "title": "TW68051 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TW68051",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822231339059",
+    "title": "TW68056 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TW68056",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822231371827",
+    "title": "TW68063 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TW68063",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822231404595",
+    "title": "TW68065 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TW68065",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822231437363",
+    "title": "TW68069 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TW68069",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822231502899",
+    "title": "TW68070 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TW68070",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822231535667",
+    "title": "TW68071 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TW68071",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822231568435",
+    "title": "TW68072 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TW68072",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822231633971",
+    "title": "TW68074 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TW68074",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822231666739",
+    "title": "TW68076 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TW68076",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822231699507",
+    "title": "TW68077 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TW68077",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822231765043",
+    "title": "TW68080 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TW68080",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822231797811",
+    "title": "TW68089 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TW68089",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822231830579",
+    "title": "TW68090 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TW68090",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822231896115",
+    "title": "TW68091 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TW68091",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822231928883",
+    "title": "TW68098 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TW68098",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822231961651",
+    "title": "TW68101 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TW68101",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822232059955",
+    "title": "TW68103 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TW68103",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822232092723",
+    "title": "TW68104 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TW68104",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822232125491",
+    "title": "TW68106 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "TW68106",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822232158259",
+    "title": "WAS1558 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "WAS1558",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822232223795",
+    "title": "WNR1104 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "WNR1104",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822232256563",
+    "title": "WNR1108 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "WNR1108",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822232289331",
+    "title": "WNR1115 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "WNR1115",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822232485939",
+    "title": "WNR2003 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "WNR2003",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822232518707",
+    "title": "WNR2005 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "WNR2005",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822232551475",
+    "title": "WNR2020 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "WNR2020",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822232682547",
+    "title": "WNR2113 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "WNR2113",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822233108531",
+    "title": "WP7575 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "WP7575",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822233370675",
+    "title": "WP7593 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "WP7593",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822233403443",
+    "title": "WP7594 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "WP7594",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822233468979",
+    "title": "WP7596 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "WP7596",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822233763891",
+    "title": "WS5128 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "WS5128",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  },
+  {
+    "product_id": "7822233960499",
+    "title": "ZG1536 Wallcovering | TWIL",
+    "dw_sku": "",
+    "mfr": "ZG1536",
+    "status": "DRAFT",
+    "price": null,
+    "done": false
+  }
+]
\ No newline at end of file
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 0000000..4195e12
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,167 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, maximum-scale=1">
+<meta name="apple-mobile-web-app-capable" content="yes">
+<meta name="apple-mobile-web-app-title" content="DW Photos">
+<title>DW Pattern Photo Capture</title>
+<style>
+  :root{ --cols: 1; --bg:#0f0e0c; --card:#1b1916; --ink:#f3efe7; --muted:#9a9184; --line:#2e2a25;
+         --gold:#c8a24a; --green:#3fa06a; --red:#c0563f; }
+  *{box-sizing:border-box}
+  body{margin:0;background:var(--bg);color:var(--ink);font:16px/1.4 -apple-system,BlinkMacSystemFont,"SF Pro Text",Segoe UI,Roboto,sans-serif;-webkit-text-size-adjust:100%}
+  header{position:sticky;top:0;z-index:10;background:rgba(15,14,12,.96);backdrop-filter:blur(8px);border-bottom:1px solid var(--line);padding:max(10px,env(safe-area-inset-top)) 14px 10px}
+  h1{font:600 17px/1.1 "SF Pro Display",sans-serif;letter-spacing:.04em;margin:0 0 8px;text-transform:uppercase}
+  h1 span{color:var(--gold)}
+  .bar{height:8px;border-radius:99px;background:#2a261f;overflow:hidden;margin:6px 0}
+  .bar i{display:block;height:100%;background:linear-gradient(90deg,var(--gold),#e7c878);width:0;transition:width .3s}
+  .meta{display:flex;justify-content:space-between;font-size:13px;color:var(--muted)}
+  .controls{display:flex;gap:8px;align-items:center;margin-top:10px;flex-wrap:wrap}
+  input[type=search],select{background:#16140f;border:1px solid var(--line);color:var(--ink);border-radius:10px;padding:9px 11px;font-size:15px}
+  input[type=search]{flex:1;min-width:140px}
+  .chips{display:flex;gap:6px}
+  .chip{padding:7px 13px;border-radius:99px;border:1px solid var(--line);background:#16140f;color:var(--muted);font-size:13px;cursor:pointer}
+  .chip.on{background:var(--gold);color:#1b1407;border-color:var(--gold);font-weight:600}
+  .dens{display:flex;align-items:center;gap:6px;color:var(--muted);font-size:12px}
+  input[type=range]{accent-color:var(--gold)}
+  main{padding:14px;display:grid;grid-template-columns:repeat(var(--cols),1fr);gap:12px;max-width:1200px;margin:0 auto}
+  .card{background:var(--card);border:1px solid var(--line);border-radius:16px;overflow:hidden;display:flex;flex-direction:column}
+  .card.done{border-color:var(--green)}
+  .thumb{aspect-ratio:4/3;background:#0c0b09 center/cover no-repeat;display:flex;align-items:center;justify-content:center;color:#4a443b;font-size:34px;position:relative}
+  .badge{position:absolute;top:8px;right:8px;font-size:11px;padding:3px 8px;border-radius:99px;font-weight:700;letter-spacing:.03em}
+  .b-need{background:#3a2a14;color:#e7c878} .b-done{background:var(--green);color:#fff} .b-err{background:var(--red);color:#fff}
+  .body{padding:11px 12px 13px;display:flex;flex-direction:column;gap:6px;flex:1}
+  .ttl{font:600 14px/1.25 "SF Pro Display",sans-serif}
+  .sku{font:700 15px/1 ui-monospace,Menlo,monospace;color:var(--gold);letter-spacing:.02em}
+  .sub{font-size:11.5px;color:var(--muted);display:flex;gap:8px;flex-wrap:wrap;align-items:center}
+  .price{color:var(--green);font-weight:700}
+  .price.no{color:var(--muted)}
+  .when{font-size:11px;color:var(--muted)}
+  .actions{display:flex;gap:8px;margin-top:auto;padding-top:6px}
+  .shoot{flex:1;position:relative;overflow:hidden;background:var(--gold);color:#1b1407;border:none;border-radius:12px;padding:13px;font-weight:700;font-size:15px;text-align:center;cursor:pointer}
+  .shoot input{position:absolute;inset:0;opacity:0;font-size:200px;cursor:pointer}
+  .skip{background:#16140f;border:1px solid var(--line);color:var(--muted);border-radius:12px;padding:13px 14px;font-size:14px;cursor:pointer}
+  .card.done .shoot{background:#16140f;color:var(--gold);border:1px solid var(--gold)}
+  .empty{grid-column:1/-1;text-align:center;color:var(--muted);padding:40px 10px}
+  .toast{position:fixed;left:50%;bottom:24px;transform:translateX(-50%);background:#16140f;border:1px solid var(--gold);color:var(--ink);padding:11px 18px;border-radius:12px;font-size:14px;z-index:50;opacity:0;transition:opacity .2s;pointer-events:none}
+  .toast.show{opacity:1}
+</style>
+</head>
+<body>
+<header>
+  <h1>DW Pattern Photo <span>Capture</span></h1>
+  <div class="bar"><i id="prog"></i></div>
+  <div class="meta"><span id="count">…</span><span id="remain"></span></div>
+  <div class="controls">
+    <input type="search" id="q" placeholder="Search pattern or SKU…">
+    <select id="sort">
+      <option value="ready">Priced first</option>
+      <option value="title">Title A→Z</option>
+      <option value="sku">SKU A→Z</option>
+    </select>
+  </div>
+  <div class="controls">
+    <div class="chips">
+      <div class="chip on" data-f="need">Needs photo</div>
+      <div class="chip" data-f="done">Done</div>
+      <div class="chip" data-f="all">All</div>
+    </div>
+    <div class="dens">Size <input type="range" id="dens" min="1" max="4" value="1"></div>
+  </div>
+</header>
+<main id="grid"></main>
+<div class="toast" id="toast"></div>
+<script>
+const $=s=>document.querySelector(s), grid=$('#grid');
+let ITEMS=[], filter='need';
+const LS=k=>localStorage.getItem('dwphoto_'+k), setLS=(k,v)=>localStorage.setItem('dwphoto_'+k,v);
+
+// restore prefs
+if(LS('dens')){$('#dens').value=LS('dens');document.documentElement.style.setProperty('--cols',LS('dens'));}
+if(LS('sort'))$('#sort').value=LS('sort');
+if(LS('filter')){filter=LS('filter');document.querySelectorAll('.chip').forEach(c=>c.classList.toggle('on',c.dataset.f===filter));}
+
+function toast(m){const t=$('#toast');t.textContent=m;t.classList.add('show');clearTimeout(t._t);t._t=setTimeout(()=>t.classList.remove('show'),2200);}
+function fmt(ts){if(!ts)return'';try{return new Date(ts).toLocaleString(undefined,{month:'short',day:'numeric',hour:'numeric',minute:'2-digit'})}catch(e){return''}}
+
+async function load(){
+  const r=await fetch('/api/queue'); const d=await r.json();
+  ITEMS=d.items; render();
+}
+function priced(x){return x.price && parseFloat(x.price)>4.25;}
+function render(){
+  const q=($('#q').value||'').toLowerCase(), sort=$('#sort').value;
+  let list=ITEMS.filter(x=>{
+    if(filter==='need'&&(x.done||x.skipped))return false;
+    if(filter==='done'&&!x.done)return false;
+    if(q&&!((x.title||'').toLowerCase().includes(q)||(x.dw_sku||'').toLowerCase().includes(q)||(x.mfr||'').toLowerCase().includes(q)))return false;
+    return true;
+  });
+  list.sort((a,b)=> sort==='title'?(a.title||'').localeCompare(b.title||'')
+    : sort==='sku'?(a.dw_sku||'').localeCompare(b.dw_sku||'')
+    : (priced(b)-priced(a))||(a.title||'').localeCompare(b.title||''));
+  const done=ITEMS.filter(x=>x.done).length, total=ITEMS.length;
+  $('#prog').style.width=(total?done/total*100:0)+'%';
+  $('#count').textContent=`${done} / ${total} photographed`;
+  $('#remain').textContent=`${total-done} remaining`;
+  grid.innerHTML = list.length? '' : '<div class="empty">Nothing here — switch filter or clear search.</div>';
+  for(const x of list){
+    const el=document.createElement('div'); el.className='card'+(x.done?' done':'');
+    const badge=x.push_err?'<span class="badge b-err">PUSH ERR</span>':x.done?'<span class="badge b-done">✓ DONE</span>':'<span class="badge b-need">NEEDS</span>';
+    const thumb=x.photo?`style="background-image:url('${x.photo}')"`:'';
+    el.innerHTML=`
+      <div class="thumb" ${thumb}>${x.photo?'':'▦'}${badge}</div>
+      <div class="body">
+        <div class="ttl">${(x.title||'').replace(/ \\| Fentucci$/,'')}</div>
+        <div class="sku">${x.dw_sku||'—'}</div>
+        <div class="sub">
+          <span>mfr: ${x.mfr||'—'}</span>
+          <span class="price ${priced(x)?'':'no'}">${priced(x)?'$'+x.price:'no price'}</span>
+          <span>${x.status}</span>
+        </div>
+        ${x.ts?`<div class="when">🕓 ${fmt(x.ts)}${x.shopify_pushed?' · on Shopify':''}</div>`:''}
+        ${x.push_err?`<div class="when" style="color:var(--red)">${x.push_err}</div>`:''}
+        <div class="actions">
+          <label class="shoot">${x.done?'Retake':'📷 Photograph'}<input type="file" accept="image/*" capture="environment"></label>
+          ${x.done?'':'<button class="skip">Skip</button>'}
+        </div>
+      </div>`;
+    el.querySelector('.shoot input').addEventListener('change',e=>shoot(x,e.target.files[0]));
+    const sk=el.querySelector('.skip'); if(sk) sk.addEventListener('click',()=>skip(x));
+    grid.appendChild(el);
+  }
+}
+function downscale(file,max=2000){return new Promise((res)=>{
+  const img=new Image(), url=URL.createObjectURL(file);
+  img.onload=()=>{
+    let{width:w,height:h}=img; const s=Math.min(1,max/Math.max(w,h)); // never upscale
+    const c=document.createElement('canvas'); c.width=Math.round(w*s); c.height=Math.round(h*s);
+    c.getContext('2d').drawImage(img,0,0,c.width,c.height); URL.revokeObjectURL(url);
+    res(c.toDataURL('image/jpeg',0.9));
+  };
+  img.src=url;
+});}
+async function shoot(x,file){
+  if(!file)return; toast('Uploading '+x.dw_sku+'…');
+  const dataUrl=await downscale(file);
+  try{
+    const r=await fetch('/api/photo',{method:'POST',headers:{'Content-Type':'application/json'},
+      body:JSON.stringify({dw_sku:x.dw_sku,product_id:x.product_id,dataUrl})});
+    const d=await r.json();
+    if(d.ok){ Object.assign(x,{done:true,photo:d.photo,ts:new Date().toISOString(),shopify_pushed:d.shopify_pushed,push_err:d.push_err});
+      toast(d.shopify_pushed?('✓ '+x.dw_sku+' → Shopify'):('Saved '+x.dw_sku+(d.push_err?' (Shopify push failed)':''))); render();
+    } else toast('Error: '+(d.err||'failed'));
+  }catch(e){toast('Upload failed — check connection');}
+}
+async function skip(x){await fetch('/api/skip',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({dw_sku:x.dw_sku,skipped:true})});x.skipped=true;render();}
+
+$('#q').addEventListener('input',render);
+$('#sort').addEventListener('change',e=>{setLS('sort',e.target.value);render();});
+$('#dens').addEventListener('input',e=>{document.documentElement.style.setProperty('--cols',e.target.value);setLS('dens',e.target.value);});
+document.querySelectorAll('.chip').forEach(c=>c.addEventListener('click',()=>{
+  document.querySelectorAll('.chip').forEach(z=>z.classList.remove('on'));c.classList.add('on');filter=c.dataset.f;setLS('filter',filter);render();}));
+load(); setInterval(load, 60000);
+</script>
+</body>
+</html>
diff --git a/server.js b/server.js
new file mode 100644
index 0000000..e5970a9
--- /dev/null
+++ b/server.js
@@ -0,0 +1,148 @@
+#!/usr/bin/env node
+/**
+ * DW Pattern Photo Capture — mobile-first (iPhone + iPad) camera worklist.
+ * Walk physical sample books, snap each pattern that has no product photo,
+ * and the image is saved locally + attached to its Shopify DRAFT product
+ * (filename = DW SKU, never the mfr# — honors the private-label leak rule).
+ *
+ * Pure Node http (zero npm deps). Basic-auth gated. Binds 0.0.0.0 for LAN.
+ *   PORT=9890 node server.js
+ */
+const http = require('http');
+const https = require('https');
+const fs = require('fs');
+const path = require('path');
+
+const ROOT = __dirname;
+const DATA = path.join(ROOT, 'data');
+const PHOTOS = path.join(ROOT, 'photos');
+const QUEUE_FILE = path.join(DATA, 'queue.json');
+const PROGRESS_FILE = path.join(DATA, 'progress.json');
+const PORT = process.env.PORT || 9890;
+const AUTH_USER = process.env.AUTH_USER || 'admin';
+const AUTH_PASS = process.env.AUTH_PASS || 'DW2024!';
+
+// Shopify (for pushing the photo onto the draft product). Token from secrets .env.
+const SHOP = 'designer-laboratory-sandbox.myshopify.com';
+const API = '2024-10';
+let TOKEN = process.env.SHOPIFY_ADMIN_TOKEN || '';
+if (!TOKEN) {
+  try {
+    for (const l of fs.readFileSync(path.join(process.env.HOME, 'Projects/secrets-manager/.env'), 'utf8').split('\n'))
+      if (l.startsWith('SHOPIFY_ADMIN_TOKEN=')) { TOKEN = l.split('=').slice(1).join('=').trim(); break; }
+  } catch (e) {}
+}
+
+fs.mkdirSync(PHOTOS, { recursive: true });
+const loadJSON = (f, d) => { try { return JSON.parse(fs.readFileSync(f, 'utf8')); } catch (e) { return d; } };
+const saveJSON = (f, o) => fs.writeFileSync(f, JSON.stringify(o, null, 2));
+
+// progress.json: { "<dw_sku>": { done, skipped, photo, ts, shopify_pushed, push_err } }
+let progress = loadJSON(PROGRESS_FILE, {});
+const saveProgress = () => saveJSON(PROGRESS_FILE, progress);
+
+function shopifyAttachImage(productId, dwSku, base64) {
+  return new Promise((resolve) => {
+    if (!TOKEN) return resolve({ ok: false, err: 'no SHOPIFY_ADMIN_TOKEN' });
+    const safe = (dwSku || ('SKU' + productId)).replace(/[^A-Za-z0-9._-]/g, '');
+    const body = JSON.stringify({ image: { attachment: base64, filename: `${safe}.jpg` } });
+    const req = https.request({
+      host: SHOP, path: `/admin/api/${API}/products/${productId}/images.json`, method: 'POST',
+      headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) }
+    }, (res) => {
+      let d = ''; res.on('data', c => d += c);
+      res.on('end', () => {
+        if (res.statusCode >= 200 && res.statusCode < 300) { try { const j = JSON.parse(d); resolve({ ok: true, image_id: j.image && j.image.id }); } catch (e) { resolve({ ok: true }); } }
+        else resolve({ ok: false, err: `HTTP ${res.statusCode}: ${d.slice(0, 180)}` });
+      });
+    });
+    req.on('error', e => resolve({ ok: false, err: e.message }));
+    req.write(body); req.end();
+  });
+}
+
+function checkAuth(req) {
+  const h = req.headers.authorization || '';
+  if (!h.startsWith('Basic ')) return false;
+  const [u, p] = Buffer.from(h.slice(6), 'base64').toString().split(':');
+  return u === AUTH_USER && p === AUTH_PASS;
+}
+
+function buildQueue() {
+  const q = loadJSON(QUEUE_FILE, []);
+  return q.map(item => {
+    const pr = progress[item.dw_sku] || {};
+    return { ...item, done: !!pr.done, skipped: !!pr.skipped, photo: pr.photo || null, ts: pr.ts || null,
+             shopify_pushed: !!pr.shopify_pushed, push_err: pr.push_err || null };
+  });
+}
+
+function send(res, code, body, headers = {}) {
+  res.writeHead(code, { 'Content-Type': 'application/json', ...headers });
+  res.end(typeof body === 'string' ? body : JSON.stringify(body));
+}
+
+const server = http.createServer((req, res) => {
+  // auth (allow /healthz open)
+  if (req.url !== '/healthz' && !checkAuth(req)) {
+    res.writeHead(401, { 'WWW-Authenticate': 'Basic realm="DW Photo Capture"' });
+    return res.end('Auth required');
+  }
+  const u = new URL(req.url, `http://${req.headers.host}`);
+
+  if (u.pathname === '/healthz') return send(res, 200, { ok: true });
+
+  if (u.pathname === '/' || u.pathname === '/index.html') {
+    return res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }),
+           res.end(fs.readFileSync(path.join(ROOT, 'public/index.html')));
+  }
+
+  if (u.pathname === '/api/queue' && req.method === 'GET') {
+    const q = buildQueue();
+    return send(res, 200, { total: q.length, done: q.filter(x => x.done).length, items: q });
+  }
+
+  if (u.pathname.startsWith('/photos/')) {
+    const f = path.join(PHOTOS, path.basename(u.pathname));
+    if (fs.existsSync(f)) { res.writeHead(200, { 'Content-Type': 'image/jpeg' }); return res.end(fs.readFileSync(f)); }
+    return send(res, 404, { err: 'not found' });
+  }
+
+  if (u.pathname === '/api/photo' && req.method === 'POST') {
+    let body = '';
+    req.on('data', c => { body += c; if (body.length > 25 * 1024 * 1024) req.destroy(); });
+    req.on('end', async () => {
+      let p; try { p = JSON.parse(body); } catch (e) { return send(res, 400, { err: 'bad json' }); }
+      const { dw_sku, product_id, dataUrl, push } = p;
+      if (!dw_sku || !dataUrl) return send(res, 400, { err: 'dw_sku + dataUrl required' });
+      const b64 = dataUrl.replace(/^data:image\/\w+;base64,/, '');
+      const safe = String(dw_sku).replace(/[^A-Za-z0-9._-]/g, '');
+      const fname = `${safe}-${Date.now()}.jpg`;
+      fs.writeFileSync(path.join(PHOTOS, fname), Buffer.from(b64, 'base64'));
+      const entry = progress[dw_sku] || {};
+      entry.done = true; entry.skipped = false; entry.photo = `/photos/${fname}`; entry.ts = new Date().toISOString();
+      // push to Shopify draft (default on)
+      if (push !== false && product_id) {
+        const r = await shopifyAttachImage(product_id, dw_sku, b64);
+        entry.shopify_pushed = r.ok; entry.push_err = r.ok ? null : r.err;
+      }
+      progress[dw_sku] = entry; saveProgress();
+      return send(res, 200, { ok: true, dw_sku, photo: entry.photo, shopify_pushed: entry.shopify_pushed, push_err: entry.push_err });
+    });
+    return;
+  }
+
+  if (u.pathname === '/api/skip' && req.method === 'POST') {
+    let body = ''; req.on('data', c => body += c);
+    req.on('end', () => {
+      let p; try { p = JSON.parse(body); } catch (e) { return send(res, 400, { err: 'bad json' }); }
+      const e = progress[p.dw_sku] || {}; e.skipped = !!p.skipped; progress[p.dw_sku] = e; saveProgress();
+      return send(res, 200, { ok: true });
+    });
+    return;
+  }
+
+  send(res, 404, { err: 'not found' });
+});
+
+server.listen(PORT, '0.0.0.0', () => console.log(`DW Photo Capture on http://0.0.0.0:${PORT}  (Shopify push: ${TOKEN ? 'ON' : 'OFF — no token'})`));

(oldest)  ·  back to Dw Photo Capture  ·  auto-save: 2026-06-24T15:27:54 (2 files) — public/index.html ea8d5f1 →