← back to Quadrille Showroom
Tighter arc (86deg span, ~75% overlap = thinner slivers) + warmer cream plaster + calmer exposure to match PJ photo; gates hold (senior 50/69, 2min 69/71, 0 err)
cbd8fb95cd5047ef0218b7f626d32109749d4841 · 2026-06-28 08:09:07 -0700 · Steve
Files touched
A data/enrichment.jsonM package-lock.jsonM package.jsonA proto/shot-colorriver.cjsA proto/shots/v10-concierge.pngA proto/shots/v6-colorriver.pngA proto/shots/v8-swipe-filled.pngA proto/shots/v8-swipe.pngA proto/shots/v9-walkin-angle2.pngA proto/shots/v9-walkin.pngA proto/v10-concierge.htmlA proto/v6-colorriver.htmlA proto/v8-swipe.htmlA proto/v9-walkin.htmlA proto/verify-v10.cjsA proto/verify-v8.cjsM public/js/showroom.jsA scripts/enrich.js
Diff
commit cbd8fb95cd5047ef0218b7f626d32109749d4841
Author: Steve <steve@designerwallcoverings.com>
Date: Sun Jun 28 08:09:07 2026 -0700
Tighter arc (86deg span, ~75% overlap = thinner slivers) + warmer cream plaster + calmer exposure to match PJ photo; gates hold (senior 50/69, 2min 69/71, 0 err)
---
data/enrichment.json | 1 +
package-lock.json | 585 ++++++++++++++++++++++++++++++++++++-
package.json | 3 +-
proto/shot-colorriver.cjs | 88 ++++++
proto/shots/v10-concierge.png | Bin 0 -> 724853 bytes
proto/shots/v6-colorriver.png | Bin 0 -> 867221 bytes
proto/shots/v8-swipe-filled.png | Bin 0 -> 1052236 bytes
proto/shots/v8-swipe.png | Bin 0 -> 859543 bytes
proto/shots/v9-walkin-angle2.png | Bin 0 -> 1473500 bytes
proto/shots/v9-walkin.png | Bin 0 -> 1016219 bytes
proto/v10-concierge.html | 561 ++++++++++++++++++++++++++++++++++++
proto/v6-colorriver.html | 547 +++++++++++++++++++++++++++++++++++
proto/v8-swipe.html | 602 +++++++++++++++++++++++++++++++++++++++
proto/v9-walkin.html | 367 ++++++++++++++++++++++++
proto/verify-v10.cjs | 80 ++++++
proto/verify-v8.cjs | 73 +++++
public/js/showroom.js | 14 +-
scripts/enrich.js | 56 ++++
18 files changed, 2969 insertions(+), 8 deletions(-)
diff --git a/data/enrichment.json b/data/enrichment.json
new file mode 100644
index 0000000..dde277b
--- /dev/null
+++ b/data/enrichment.json
@@ -0,0 +1 @@
+{"built_at":"2026-06-28T15:07:17.707Z","hue_done":true,"hue_filled":878,"hue_failed":5,"enrich":{"DWCW-700146":{"patternBase":"Aga Apple","room":null,"hue":66,"color_bucket":"yellow","familySkus":[]},"DWCW-700246":{"patternBase":"Aga Jungle Green","room":null,"hue":59,"color_bucket":"yellow","familySkus":[]},"DWCW-700346":{"patternBase":"Aga Jungle","room":null,"hue":60,"color_bucket":"yellow","familySkus":[]},"DWCW-700446":{"patternBase":"Aga New Navy Almost White","room":null,"hue":217,"color_bucket":"blue","familySkus":[]},"DWCW-700546":{"patternBase":"Aga Pistachio","room":null,"hue":85,"color_bucket":"green","familySkus":[]},"DWCW-700646":{"patternBase":"Aga Purple","room":null,"hue":320,"color_bucket":"magenta","familySkus":[]},"DWCW-700746":{"patternBase":"Aga Reverse Apple Green","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Aga-Reverse-fabric-group_39f22a7b-0bbe-4098-b3be-1775ba205742.jpg","hue":74,"color_bucket":"green","familySkus":[]},"DWCW-700846":{"patternBase":"Aga Reverse Brown","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Aga-Reverse-fabric-group_e4b46742-2238-4774-8301-be3f94a15bbc.jpg","hue":18,"color_bucket":"orange","familySkus":[]},"DWCW-700946":{"patternBase":"Aga Reverse New Navy","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Aga-Reverse-fabric-group_697a0328-c768-4406-bf8d-0f26211d564c.jpg","hue":216,"color_bucket":"blue","familySkus":[]},"DWCW-701046":{"patternBase":"Aga Reverse Watermelon","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Aga-Reverse-fabric-group_e0c86671-d3af-4e07-92a8-d9459aa46796.jpg","hue":343,"color_bucket":"red","familySkus":[]},"DWCW-701146":{"patternBase":"Aga Turquoise","room":null,"hue":183,"color_bucket":"teal","familySkus":[]},"DWCW-701246":{"patternBase":"Aga Watermelon","room":null,"hue":344,"color_bucket":"red","familySkus":[]},"DWCW-702246":{"patternBase":"Antionette Black_onwhite","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Antoinette_Black_onWhite_CP1040W-08.jpg","hue":153,"color_bucket":"green","familySkus":[]},"DWCW-701946":{"patternBase":"Antionette Burnt_Orange_on_White","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Barbados-Batik-Orange-Burnt-Orange-on-Almost-White-8250-05WP.jpg","hue":16,"color_bucket":"orange","familySkus":[]},"DWCW-702346":{"patternBase":"Antionette Cognac_on_white","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Antoinette_Cognac_on_White_CP1040W-06.jpg","hue":26,"color_bucket":"orange","familySkus":[]},"DWCW-702046":{"patternBase":"Antionette Lavender_on_white","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Obi-II-Lavender-on-Almost-White-6650WP-04.jpg","hue":278,"color_bucket":"purple","familySkus":[]},"DWCW-702446":{"patternBase":"Antionette Navy_on_white","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Antoinette_Navy_on_White_CP1040W-07.jpg","hue":211,"color_bucket":"blue","familySkus":[]},"DWCW-702146":{"patternBase":"Antionette Turquoise_on_white","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Aqua-IV-Yellow-on-White-7240-02WP_cf9b842c-c2b3-470b-ac7f-f2fe14a05178.jpg","hue":194,"color_bucket":"teal","familySkus":[]},"DWCW-702546":{"patternBase":"Aqua Iv Aqua","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Aqua-IV-fabric-group_34512a11-eabe-48a4-96de-a5007932ba21.jpg","hue":160,"color_bucket":"green","familySkus":[]},"DWCW-702646":{"patternBase":"Aqua Iv Brown","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Aqua-IV-fabric-group_73a617c1-1351-4d6a-b965-5a9bc5f689e4.jpg","hue":37,"color_bucket":"orange","familySkus":[]},"DWCW-702746":{"patternBase":"Aqua Iv Camel","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Aqua-IV-fabric-group_f68643fb-323e-451e-b798-4ec7c41adb71.jpg","hue":37,"color_bucket":"orange","familySkus":[]},"DWCW-702846":{"patternBase":"Aqua Iv French Blue","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Aqua-IV-fabric-group_c73055e6-60a5-4f58-8359-008f7cb475af.jpg","hue":208,"color_bucket":"blue","familySkus":[]},"DWCW-702946":{"patternBase":"Aqua Iv Jungle Green","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Aqua-IV-fabric-group_b0342750-5240-46ca-9526-6dfa2b625454.jpg","hue":83,"color_bucket":"green","familySkus":[]},"DWCW-703046":{"patternBase":"Aqua Iv Magenta","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Aqua-IV-fabric-group_6e75ac9f-414e-47b0-aa0f-3dc45f47a0a1.jpg","hue":341,"color_bucket":"red","familySkus":[]},"DWCW-703146":{"patternBase":"Aqua Iv Navy","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Aqua-IV-fabric-group_4adae4e3-dc9f-4450-9ebd-a3472ebe17bb.jpg","hue":218,"color_bucket":"blue","familySkus":["DWCW-703246"]},"DWCW-703246":{"patternBase":"Aqua Iv Navy","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Aqua-IV-fabric-group_70ef6106-4b82-4821-9f99-f6f4a94c5d77.jpg","hue":221,"color_bucket":"blue","familySkus":["DWCW-703146"]},"DWCW-703346":{"patternBase":"Aqua Iv Rust","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Aqua-IV-fabric-group_d233f236-6a8a-468d-81e1-b63792b9e5bd.jpg","hue":10,"color_bucket":"red","familySkus":[]},"DWCW-703446":{"patternBase":"Aqua Iv Turquoise","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Aqua-IV-fabric-group_2560515e-f66f-4d45-b49d-cff267f63afa.jpg","hue":179,"color_bucket":"teal","familySkus":[]},"DWCW-703546":{"patternBase":"Aqua Iv Yellow","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Aqua-IV-fabric-group_8b364a2a-0977-49b1-bcfb-411e969735c3.jpg","hue":41,"color_bucket":"orange","familySkus":[]},"DWCW-703646":{"patternBase":"Arbre de Matisse Black","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Arbre-de-Matisse-fabric-group_623e5507-e083-46e9-b743-932e57e437d9.jpg","hue":49,"color_bucket":"yellow","familySkus":[]},"DWCW-703746":{"patternBase":"Arbre De Matisse Brown","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Arbre-de-Matisse-fabric-group_65c8ef0d-dba4-41e7-8e3d-7e1d42461d9e.jpg","hue":20,"color_bucket":"orange","familySkus":["DWCW-703846"]},"DWCW-703846":{"patternBase":"Arbre De Matisse Brown","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Arbre-de-Matisse-fabric-group_ee634196-f04f-4b5b-b860-659843bb87c2.jpg","hue":22,"color_bucket":"orange","familySkus":["DWCW-703746"]},"DWCW-703946":{"patternBase":"Arbre De Matisse China Blue","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Arbre-de-Matisse-fabric-group_ba7e3c65-1593-4f89-be76-9fc720720742.jpg","hue":218,"color_bucket":"blue","familySkus":[]},"DWCW-704046":{"patternBase":"Arbre De Matisse Gold Metallic","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Arbre-de-Matisse-fabric-group_82896203-7452-496a-b8ba-fdbea0844c16.jpg","hue":45,"color_bucket":"orange","familySkus":[]},"DWCW-704146":{"patternBase":"Arbre De Matisse Jungle Green","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Arbre-de-Matisse-fabric-group_2be773d7-ab1c-4143-b2fd-5dd3dd26ad75.jpg","hue":74,"color_bucket":"green","familySkus":[]},"DWCW-704246":{"patternBase":"Arbre De Matisse Reverse Brown","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Arbre-de-Matisse-Reverse-fabric-group_4a5f674f-5c22-4e7e-a69e-575371b5356c.jpg","hue":10,"color_bucket":"red","familySkus":[]},"DWCW-704346":{"patternBase":"Arbre De Matisse Reverse China Blue","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Arbre-de-Matisse-Reverse-fabric-group_17e8d75f-8900-4f5f-89ac-d6d4a409415c.jpg","hue":213,"color_bucket":"blue","familySkus":[]},"DWCW-704446":{"patternBase":"Arbre De Matisse Reverse French Blue","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Arbre-de-Matisse-Reverse-fabric-group_6942d1b2-54d4-47f5-a0bb-0cee6de9877c.jpg","hue":213,"color_bucket":"blue","familySkus":[]},"DWCW-704546":{"patternBase":"Arbre De Matisse Reverse Inca Gold","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Arbre-de-Matisse-Reverse-fabric-group_4b574217-419d-4b84-9d03-407c90bdf37f.jpg","hue":61,"color_bucket":"yellow","familySkus":[]},"DWCW-704646":{"patternBase":"Arbre De Matisse Reverse Jungle Green","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Arbre-de-Matisse-Reverse-fabric-group_e660d59b-b047-4203-873a-b0a96eb54cde.jpg","hue":70,"color_bucket":"yellow","familySkus":[]},"DWCW-704746":{"patternBase":"Arbre De Matisse Reverse Shrimp","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Arbre-de-Matisse-Reverse-fabric-group_5041a8c3-0d80-45e4-b167-cae562ebb93f.jpg","hue":11,"color_bucket":"red","familySkus":[]},"DWCS-894046":{"patternBase":"Arbre de Matisse Reverse Soft Aqua","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Arbre-de-Matisse-Reverse-fabric-group_6aec8444-8cbe-4dd8-8e71-52f0b6bfa403.jpg","hue":132,"color_bucket":"green","familySkus":[]},"DWCW-704846":{"patternBase":"Arbre De Matisse Reverse White","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Arbre-de-Matisse-Reverse-fabric-group_5ea45d97-1162-4a7b-809b-1e3889d6420e.jpg","hue":48,"color_bucket":"yellow","familySkus":[]},"DWCW-704946":{"patternBase":"Arbre De Matisse Silver Metallic","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Arbre-de-Matisse-fabric-group_5982aff7-e6a4-43ee-b6a9-0e9ac002d848.jpg","hue":57,"color_bucket":"yellow","familySkus":[]},"DWCW-705046":{"patternBase":"Arbre De Matisse White","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Arbre-de-Matisse-fabric-group_d3f3ff09-d9c9-4756-9906-8518e5f5a630.jpg","hue":49,"color_bucket":"yellow","familySkus":["DWCW-705146"]},"DWCW-705146":{"patternBase":"Arbre De Matisse White","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Arbre-de-Matisse-fabric-group_ab124c48-ca5b-4bef-9d1a-86e9825b6eec.jpg","hue":203,"color_bucket":"blue","familySkus":["DWCW-705046"]},"DWCW-705246":{"patternBase":"Bali Diamond Coral on_Almost White","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Bali-Diamond-fabric-group_98b88d83-2a6a-45af-8496-92b30de22caa.jpg","hue":5,"color_bucket":"red","familySkus":[]},"DWCW-705346":{"patternBase":"Bali Diamond Lilac","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Bali-Diamond-fabric-group_74e0bf80-4ecd-4baa-a9b2-0e743debf2bf.jpg","hue":290,"color_bucket":"purple","familySkus":[]},"DWCW-705446":{"patternBase":"Bali Diamond Multi Greys","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Bali-Diamond-fabric-group_270eab72-add0-412e-aef7-4c894b0d275a.jpg","hue":54,"color_bucket":"yellow","familySkus":[]},"DWCW-705546":{"patternBase":"Bali Hai Blues","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/bali-hai-group_d63546a3-4dc6-4ce0-8be1-9334c2fd4393.png","hue":214,"color_bucket":"blue","familySkus":[]},"DWCW-705646":{"patternBase":"Bali Hai Grays","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/bali-hai-group_7f57c5f8-64f1-475f-8c03-5916863cd38e.png","hue":54,"color_bucket":"yellow","familySkus":[]},"DWCW-705746":{"patternBase":"Bali Hai Greens","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/bali-hai-group_e180a448-a86d-4a36-960c-75e77d0a8151.png","hue":96,"color_bucket":"green","familySkus":["DWCW-705846"]},"DWCW-705846":{"patternBase":"Bali Hai Greens","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/bali-hai-group_7519a43f-02f4-4f18-89d0-199f71ebf4f8.png","hue":101,"color_bucket":"green","familySkus":["DWCW-705746"]},"DWCW-705946":{"patternBase":"Bali Hai Multi Blues","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/bali-hai-group_7bc519e7-fe8b-4d53-ba17-46f2320a6753.png","hue":214,"color_bucket":"blue","familySkus":[]},"DWCW-706046":{"patternBase":"Bali Hai Purple","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/bali-hai-group_036075aa-e0a3-4dc1-8f43-b9569f9d5446.png","hue":300,"color_bucket":"magenta","familySkus":[]},"DWCW-706146":{"patternBase":"Bali Hai Reds","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/bali-hai-group_ae3368b2-46a6-4897-87c2-2d66a180f427.png","hue":359,"color_bucket":"red","familySkus":["DWCW-706246"]},"DWCW-706246":{"patternBase":"Bali Hai Reds","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/bali-hai-group_220b07d3-c17c-4eae-b209-ef43eed3156c.png","hue":357,"color_bucket":"red","familySkus":["DWCW-706146"]},"DWCW-706346":{"patternBase":"Bali Hai Salmon","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/bali-hai-group_f7989c3e-e5b5-4e7e-a027-cf341739488a.png","hue":10,"color_bucket":"red","familySkus":[]},"DWCW-706446":{"patternBase":"Bali Hai Turquoise","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/bali-hai-group_15fb2607-f017-434f-bddc-1bebe34cb3a5.png","hue":190,"color_bucket":"teal","familySkus":[]},"DWCW-706546":{"patternBase":"Bali Ii Brown","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Bali-II-fabric-group_1d381389-96c9-4fc4-9338-acf6753e7a12.jpg","hue":21,"color_bucket":"orange","familySkus":[]},"DWCW-706646":{"patternBase":"Bali Ii French Blue","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Bali-II-fabric-group_0eadac73-ffb4-4f3e-9e6b-8d9c3eebfdc1.jpg","hue":208,"color_bucket":"blue","familySkus":[]},"DWCW-706746":{"patternBase":"Bali Ii Lime Green","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Bali-II-fabric-group_50552a7e-d953-44d9-84c4-a1bb61b6b208.jpg","hue":76,"color_bucket":"green","familySkus":[]},"DWCW-706846":{"patternBase":"Bali II New Brown","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Bali-II-fabric-group_f7cb5afa-4588-4f0a-91be-b2b798fb1292.jpg","hue":20,"color_bucket":"orange","familySkus":[]},"DWCW-706946":{"patternBase":"Bali Ii Orange","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Bali-II-fabric-group_fe100100-83a2-4906-b6cd-2d5b66dc30e4.jpg","hue":18,"color_bucket":"orange","familySkus":[]},"DWCW-707046":{"patternBase":"Bali Ii Red","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Bali-II-fabric-group_900dab96-204b-4b69-b633-4ba1001d6756.jpg","hue":349,"color_bucket":"red","familySkus":[]},"DWCW-707146":{"patternBase":"Bali II Salmon II","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Bali-II-fabric-group_63262f42-55ad-4fa6-bae3-dec794d4233f.jpg","hue":6,"color_bucket":"red","familySkus":[]},"DWCW-707246":{"patternBase":"Bali II Turquoise","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Bali-II-fabric-group_9c1a1858-a5e4-4ac0-961b-aeebcb5b3412.jpg","hue":167,"color_bucket":"green","familySkus":[]},"DWCW-707346":{"patternBase":"Bali Isle French Blue","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Bali-Isle-fabric-group_0bc601de-0999-4610-a538-ecf0c614e058.jpg","hue":209,"color_bucket":"blue","familySkus":["DWCW-707446"]},"DWCW-707446":{"patternBase":"Bali Isle French Blue","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Bali-Isle-fabric-group_4bde3466-dd06-4589-9a59-f7911d3e7ed6.jpg","hue":218,"color_bucket":"blue","familySkus":["DWCW-707346"]},"DWCW-707546":{"patternBase":"Bali Isle Magenta","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Bali-Isle-fabric-group_afdc655d-32c6-4544-a559-7e8eb45212a4.jpg","hue":348,"color_bucket":"red","familySkus":[]},"DWCW-707646":{"patternBase":"Bali Isle New Navy","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Bali-Isle-fabric-group_ba7fa5ba-1ff3-471f-a295-8c22611410f8.jpg","hue":219,"color_bucket":"blue","familySkus":[]},"DWCW-707746":{"patternBase":"Bali Isle Orange","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Bali-Isle-fabric-group_4d6af991-06d2-4d36-bd65-b2ed8a49f23f.jpg","hue":16,"color_bucket":"orange","familySkus":[]},"DWCW-707846":{"patternBase":"Bali Isle Salmon","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Bali-Isle-fabric-group_f08102f1-d4d6-4696-8e72-4637d6ef5f9e.jpg","hue":8,"color_bucket":"red","familySkus":[]},"DWCW-707946":{"patternBase":"Bali Isle Turquoise","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Bali-Isle-fabric-group_41085ff5-b898-4817-a456-f1096f97d1a7.jpg","hue":169,"color_bucket":"green","familySkus":[]},"DWCW-708046":{"patternBase":"Balinese Batik Magenta Cream","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Balinese-Batik-fabric-group_6b00daf4-a49e-4fa6-b73c-ca73eec0ed11.jpg","hue":354,"color_bucket":"red","familySkus":[]},"DWCW-708146":{"patternBase":"Balinese Batik Navy Cream","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Balinese-Batik-fabric-group_6c805de4-750f-42bf-a027-b1a74bfb50e0.jpg","hue":212,"color_bucket":"blue","familySkus":[]},"DWCW-708246":{"patternBase":"Balinese Batik Shrimp Cream","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Balinese-Batik-fabric-group_fb5e9c72-0734-4ca4-9e28-06a48b376f1c.jpg","hue":6,"color_bucket":"red","familySkus":[]},"DWCW-708346":{"patternBase":"Ballon de Gonesse Fuschia Citron","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Ballon-de-Gonesse-Fuschia-Citron-2232-207.jpg","hue":37,"color_bucket":"orange","familySkus":[]},"DWCW-708446":{"patternBase":"Barbados Batik Orange Burnt Orange","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Barbados-Batik-Group_e29bc704-275d-4364-bc98-3e40255a3342.jpg","hue":10,"color_bucket":"red","familySkus":[]},"DWCW-708546":{"patternBase":"Barbados Batik Purple Medium Purple","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Barbados-Batik-Group_0176fd2e-38da-4b34-94f2-35e97b06fe71.jpg","hue":273,"color_bucket":"purple","familySkus":[]},"DWCW-708646":{"patternBase":"Beau Rivage French Blue","room":null,"hue":211,"color_bucket":"blue","familySkus":[]},"DWCW-708746":{"patternBase":"Beau Rivage Magenta","room":null,"hue":346,"color_bucket":"red","familySkus":[]},"DWCW-708846":{"patternBase":"Beau Rivage Navy","room":null,"hue":211,"color_bucket":"blue","familySkus":[]},"DWCW-708946":{"patternBase":"Beau Rivage Orange","room":null,"hue":14,"color_bucket":"red","familySkus":[]},"DWCW-709046":{"patternBase":"Beau Rivage Turquoise","room":null,"hue":168,"color_bucket":"green","familySkus":[]},"DWCW-709146":{"patternBase":"Beau Rivage Yellow","room":null,"hue":46,"color_bucket":"yellow","familySkus":[]},"DWCW-709246":{"patternBase":"Bijou Stripe Jungle Green New Shrimp Brown","room":null,"hue":22,"color_bucket":"orange","familySkus":[]},"DWCW-709346":{"patternBase":"Birds Ii New Blue","room":null,"hue":182,"color_bucket":"teal","familySkus":[]},"DWCW-709446":{"patternBase":"Birds Ii Pink","room":null,"hue":338,"color_bucket":"red","familySkus":[]},"DWCW-709546":{"patternBase":"Birds Lilac Lilac","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Birds-Lilac-on-White-5050-04WP.jpg","hue":338,"color_bucket":"red","familySkus":[]},"DWCW-709646":{"patternBase":"Bonaparte Aqua Dark Aqua Almost White","room":null,"hue":163,"color_bucket":"green","familySkus":[]},"DWCW-709746":{"patternBase":"Bonaparte French Blue Navy Almost White","room":null,"hue":212,"color_bucket":"blue","familySkus":[]},"DWCW-709846":{"patternBase":"Bonaparte Gold Orange Almost White","room":null,"hue":24,"color_bucket":"orange","familySkus":[]},"DWCW-709946":{"patternBase":"Bonaparte Lilac Purple Almost White","room":null,"hue":305,"color_bucket":"magenta","familySkus":[]},"DWCW-710046":{"patternBase":"Bonaparte Lime Forest Green Almost White","room":null,"hue":81,"color_bucket":"green","familySkus":[]},"DWCW-710146":{"patternBase":"Bonaparte Pink Dark Pink Almost White","room":null,"hue":349,"color_bucket":"red","familySkus":[]},"DWCW-710446":{"patternBase":"Brighton New Navy","room":null,"hue":216,"color_bucket":"blue","familySkus":[]},"DWCW-710546":{"patternBase":"Brighton Orange","room":null,"hue":4,"color_bucket":"red","familySkus":[]},"DWCW-710646":{"patternBase":"Brighton Pistachio","room":null,"hue":58,"color_bucket":"yellow","familySkus":[]},"DWCW-710746":{"patternBase":"Brighton Purple","room":null,"hue":294,"color_bucket":"magenta","familySkus":[]},"DWCW-710846":{"patternBase":"Brighton Red","room":null,"hue":359,"color_bucket":"red","familySkus":[]},"DWCW-710346":{"patternBase":"Brighton Reverse Turquoise","room":null,"hue":187,"color_bucket":"teal","familySkus":[]},"DWCW-710946":{"patternBase":"Brighton Turquoise","room":null,"hue":184,"color_bucket":"teal","familySkus":[]},"DWCW-711046":{"patternBase":"Brighton Vapor","room":null,"hue":90,"color_bucket":"green","familySkus":[]},"DWCW-711146":{"patternBase":"Camelot Black_on_white","room":null,"hue":128,"color_bucket":"green","familySkus":[]},"DWCW-711246":{"patternBase":"Camelot Cognac_on_white","room":null,"hue":27,"color_bucket":"orange","familySkus":[]},"DWCW-711346":{"patternBase":"Camelot Gray_on_white","room":null,"hue":63,"color_bucket":"yellow","familySkus":[]},"DWCW-711446":{"patternBase":"Camelot Light_blue_on_white","room":null,"hue":203,"color_bucket":"blue","familySkus":[]},"DWCW-711546":{"patternBase":"Camelot Pale_green_on_white","room":null,"hue":119,"color_bucket":"green","familySkus":[]},"DWCW-711646":{"patternBase":"Camelot Pink_on_white","room":null,"hue":6,"color_bucket":"red","familySkus":[]},"DWCW-711746":{"patternBase":"Camelot Purple_on_white","room":null,"hue":294,"color_bucket":"magenta","familySkus":[]},"DWCW-711846":{"patternBase":"Camelot Turquoise_on_white","room":null,"hue":194,"color_bucket":"teal","familySkus":[]},"DWCW-711946":{"patternBase":"Cap Ferrat Jungle Green Taupe Black","room":null,"hue":71,"color_bucket":"green","familySkus":[]},"DWCW-712046":{"patternBase":"Cap Ferrat Navy Blue Red","room":null,"hue":258,"color_bucket":"purple","familySkus":[]},"DWCW-712146":{"patternBase":"Capri Black_on_off_white","room":null,"hue":50,"color_bucket":"yellow","familySkus":[]},"DWCW-712246":{"patternBase":"Capri Coral_on_off_white","room":null,"hue":11,"color_bucket":"red","familySkus":[]},"DWCW-712346":{"patternBase":"Capri Gold_Metallic_on_Off_White","room":null,"hue":47,"color_bucket":"yellow","familySkus":[]},"DWCW-712446":{"patternBase":"Capri Soft_pink_on_off_white","room":null,"hue":13,"color_bucket":"red","familySkus":[]},"DWCW-712546":{"patternBase":"Capri Turquoise_on_off_white","room":null,"hue":63,"color_bucket":"yellow","familySkus":[]},"DWCW-712646":{"patternBase":"Carlo II Denim Blue","room":null,"hue":198,"color_bucket":"teal","familySkus":[]},"DWCW-712746":{"patternBase":"Carlo Ii Gray","room":null,"hue":52,"color_bucket":"yellow","familySkus":[]},"DWCW-712846":{"patternBase":"Carlo II New Apple","room":null,"hue":62,"color_bucket":"yellow","familySkus":[]},"DWCW-712946":{"patternBase":"Carlo Ii Pumice","room":null,"hue":31,"color_bucket":"orange","familySkus":[]},"DWCW-713046":{"patternBase":"Carlo II Soft French Green","room":null,"hue":77,"color_bucket":"green","familySkus":[]},"DWCW-713146":{"patternBase":"Carlo II Soft Lavender","room":null,"hue":340,"color_bucket":"red","familySkus":[]},"DWCW-713246":{"patternBase":"Carlo II Soft Windsor Blue","room":null,"hue":157,"color_bucket":"green","familySkus":[]},"DWCW-713346":{"patternBase":"Carlo Ii Taupe","room":null,"hue":32,"color_bucket":"orange","familySkus":[]},"DWCW-713446":{"patternBase":"Carlo Ii Teal","room":null,"hue":131,"color_bucket":"green","familySkus":[]},"DWCW-713546":{"patternBase":"Carlo II Venice Blue","room":null,"hue":173,"color_bucket":"teal","familySkus":[]},"DWCW-714546":{"patternBase":"Ceylon Batik Gray","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Ceylon-Batik-Group_38811dea-cb38-415c-a7bb-6ef31e068a3b.jpg","hue":54,"color_bucket":"yellow","familySkus":[]},"DWCW-714646":{"patternBase":"Ceylon Batik Jungle Green","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Ceylon-Batik-Group_dd49b523-3fc9-44c7-9bad-83225404e69f.jpg","hue":75,"color_bucket":"green","familySkus":[]},"DWCW-714746":{"patternBase":"Ceylon Batik Magenta","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Ceylon-Batik-Group_6ffb4aca-de58-43bf-b53a-8756749204f9.jpg","hue":351,"color_bucket":"red","familySkus":[]},"DWCW-714846":{"patternBase":"Ceylon Batik Medium Blue","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Ceylon-Batik-Group_0bef74fe-8c99-4dc6-9bdb-7eabf170ca51.jpg","hue":205,"color_bucket":"blue","familySkus":[]},"DWCW-714946":{"patternBase":"Ceylon Batik Navy","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Ceylon-Batik-Group_756bc8dd-edc2-45d6-a8c7-0818b91dfd29.jpg","hue":202,"color_bucket":"blue","familySkus":[]},"DWCW-715046":{"patternBase":"Ceylon Batik Orange","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Ceylon-Batik-Group_e0d5bf0d-6694-4b2f-af24-419ee8c4dc84.jpg","hue":13,"color_bucket":"red","familySkus":[]},"DWCW-715146":{"patternBase":"Ceylon Batik Purple","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Ceylon-Batik-Group_64bbca5b-f3af-4ce2-b435-4169cc396c33.jpg","hue":328,"color_bucket":"magenta","familySkus":[]},"DWCW-715246":{"patternBase":"Ceylon Batik Red","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Ceylon-Batik-Group_f200cce5-7ff8-4cbf-8c7b-eb675799f93e.jpg","hue":7,"color_bucket":"red","familySkus":[]},"DWCW-715346":{"patternBase":"Ceylon Batik Turquoise","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Ceylon-Batik-Group_96aba18c-d30c-4bdf-a979-cf4252dff4e3.jpg","hue":167,"color_bucket":"green","familySkus":[]},"DWCW-715446":{"patternBase":"Chantilly Stripe Beige","room":null,"hue":40,"color_bucket":"orange","familySkus":[]},"DWCW-715546":{"patternBase":"Chantilly Stripe Black","room":null,"hue":54,"color_bucket":"yellow","familySkus":[]},"DWCW-715646":{"patternBase":"Chantilly Stripe Metallic Gold","room":null,"hue":36,"color_bucket":"orange","familySkus":[]},"DWCW-715746":{"patternBase":"Chantilly Stripe Slate Blue","room":null,"hue":221,"color_bucket":"blue","familySkus":[]},"DWCW-715846":{"patternBase":"Chantilly Stripe Soft Teal","room":null,"hue":161,"color_bucket":"green","familySkus":[]},"DWCW-715946":{"patternBase":"Chantilly Stripe Soft Windsor","room":null,"hue":167,"color_bucket":"green","familySkus":[]},"DWCW-716046":{"patternBase":"Charleston_ii Black_on_white","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Charleston_II_Black_on_White_300869WPV.jpg","hue":59,"color_bucket":"yellow","familySkus":[]},"DWCW-716146":{"patternBase":"Charleston_II Gold_Metallic_on_White","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Charleston_II_Gold_Metallic_on_White_300863WPV.jpg","hue":50,"color_bucket":"yellow","familySkus":[]},"DWCW-716246":{"patternBase":"Charleston_II Navy_on_Off_White","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Charleston_II_Navy_on_Off_White_300868WPV.jpg","hue":223,"color_bucket":"blue","familySkus":[]},"DWCW-716346":{"patternBase":"Charleston_II New_Blue_on_Off_White","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Charleston_II_New_Blue_on_Off_White_300864W.jpg","hue":196,"color_bucket":"teal","familySkus":[]},"DWCW-716446":{"patternBase":"Charleston_II New_Brown_on_Off_White","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Charleston_II_New_Brown_on_Off_White_300867WPV.jpg","hue":35,"color_bucket":"orange","familySkus":[]},"DWCW-716546":{"patternBase":"Charleston_II Orange_on_Off_White","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Charleston_II_Orange_on_Off_White_300866W.jpg","hue":14,"color_bucket":"red","familySkus":[]},"DWCW-716646":{"patternBase":"Charleston_II Purple_on_Off_White","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Charleston_II_Purple_on_Off_White_300865W.jpg","hue":314,"color_bucket":"magenta","familySkus":[]},"DWCW-716746":{"patternBase":"Charleston_II Silver_Metallic_on_White","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Charleston_II_Silver_Metallic_on_White_300862WPV.jpg","hue":190,"color_bucket":"teal","familySkus":[]},"DWCW-716846":{"patternBase":"Charleston_ii White_on_white","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Charleston_II_Orange_on_Off_White_300866W_e7b9772a-c602-46e5-9331-0d79f31c776e.jpg","hue":203,"color_bucket":"blue","familySkus":[]},"DWCW-716946":{"patternBase":"Cherry Branch","room":null,"hue":6,"color_bucket":"red","familySkus":["DWCW-717046","DWCW-717146"]},"DWCW-717046":{"patternBase":"Cherry Branch","room":null,"hue":11,"color_bucket":"red","familySkus":["DWCW-716946","DWCW-717146"]},"DWCW-717146":{"patternBase":"Cherry Branch","room":null,"hue":5,"color_bucket":"red","familySkus":["DWCW-716946","DWCW-717046"]},"DWCW-717246":{"patternBase":"Cirebon Flores Blue","room":null,"hue":44,"color_bucket":"orange","familySkus":[]},"DWCW-717346":{"patternBase":"Cirebon II French Blue","room":null,"hue":213,"color_bucket":"blue","familySkus":[]},"DWCW-717446":{"patternBase":"Cirebon Reverse Old Pink","room":null,"hue":3,"color_bucket":"red","familySkus":[]},"DWCW-717546":{"patternBase":"Clementine All Over Camel Beige Brown","room":null,"hue":41,"color_bucket":"orange","familySkus":[]},"DWCW-717646":{"patternBase":"Clementine All Over Charcoal Gray Blue","room":null,"hue":50,"color_bucket":"yellow","familySkus":[]},"DWCW-717746":{"patternBase":"Clementine All Over Green Brown","room":null,"hue":103,"color_bucket":"green","familySkus":[]},"DWCW-717846":{"patternBase":"Clementine All Over Lilac Purple Brown","room":null,"hue":43,"color_bucket":"orange","familySkus":[]},"DWCW-717946":{"patternBase":"Clementine All Over Peach Pink Brown","room":null,"hue":16,"color_bucket":"orange","familySkus":[]},"DWCW-718046":{"patternBase":"Clementine All Over Windsor Royal Navy","room":null,"hue":218,"color_bucket":"blue","familySkus":[]},"DWCW-718146":{"patternBase":"Club Cane Cream Forest","room":null,"hue":61,"color_bucket":"yellow","familySkus":[]},"DWCW-718346":{"patternBase":"Club Cane Cream Taupe Brown","room":null,"hue":27,"color_bucket":"orange","familySkus":[]},"DWCW-718446":{"patternBase":"Club Cane Cream Taupe Eggplant","room":null,"hue":17,"color_bucket":"orange","familySkus":[]},"DWCW-718546":{"patternBase":"Club Cane Cream Taupe Orange","room":null,"hue":29,"color_bucket":"orange","familySkus":[]},"DWCW-718646":{"patternBase":"Club Cane Cream Taupe Peach","room":null,"hue":21,"color_bucket":"orange","familySkus":[]},"DWCW-718746":{"patternBase":"Coliseum Creams","room":null,"hue":26,"color_bucket":"orange","familySkus":[]},"DWCW-718846":{"patternBase":"Coliseum Grays","room":null,"hue":52,"color_bucket":"yellow","familySkus":[]},"DWCW-718946":{"patternBase":"Crawford Multi Blues","room":null,"hue":188,"color_bucket":"teal","familySkus":[]},"DWCW-719046":{"patternBase":"Crawford Multi Greens","room":null,"hue":65,"color_bucket":"yellow","familySkus":[]},"DWCW-719146":{"patternBase":"Crawford Multi Purple","room":null,"hue":9,"color_bucket":"red","familySkus":[]},"DWCW-719346":{"patternBase":"Criss Cross Black","room":null,"hue":269,"color_bucket":"purple","familySkus":[]},"DWCW-719446":{"patternBase":"Criss Cross Brown","room":null,"hue":38,"color_bucket":"orange","familySkus":["DWCW-719546"]},"DWCW-719546":{"patternBase":"Criss Cross Brown","room":null,"hue":13,"color_bucket":"red","familySkus":["DWCW-719446"]},"DWCW-719646":{"patternBase":"Criss Cross Camel II","room":null,"hue":22,"color_bucket":"orange","familySkus":[]},"DWCW-719746":{"patternBase":"Criss Cross Green","room":null,"hue":85,"color_bucket":"green","familySkus":[]},"DWCW-719846":{"patternBase":"Criss Cross Inca Gold","room":null,"hue":31,"color_bucket":"orange","familySkus":[]},"DWCW-719946":{"patternBase":"Criss Cross Navy","room":null,"hue":212,"color_bucket":"blue","familySkus":["DWCW-720046","DWCW-720146"]},"DWCW-720046":{"patternBase":"Criss Cross Navy","room":null,"hue":216,"color_bucket":"blue","familySkus":["DWCW-719946","DWCW-720146"]},"DWCW-720146":{"patternBase":"Criss Cross Navy","room":null,"hue":207,"color_bucket":"blue","familySkus":["DWCW-719946","DWCW-720046"]},"DWCW-720246":{"patternBase":"Criss Cross New Shrimp","room":null,"hue":8,"color_bucket":"red","familySkus":[]},"DWCW-720346":{"patternBase":"Criss Cross Slate Blue","room":null,"hue":214,"color_bucket":"blue","familySkus":["DWCW-720446"]},"DWCW-720446":{"patternBase":"Criss Cross Slate Blue","room":null,"hue":212,"color_bucket":"blue","familySkus":["DWCW-720346"]},"DWCW-720546":{"patternBase":"Criss Cross Swiss Blue","room":null,"hue":206,"color_bucket":"blue","familySkus":[]},"DWCW-720646":{"patternBase":"Criss Cross Taupe","room":null,"hue":35,"color_bucket":"orange","familySkus":[]},"DWCW-720746":{"patternBase":"Criss Cross Turquoise","room":null,"hue":185,"color_bucket":"teal","familySkus":[]},"DWCW-720846":{"patternBase":"Criss Cross Yellow","room":null,"hue":50,"color_bucket":"yellow","familySkus":[]},"DWCW-721246":{"patternBase":"Cumberland on","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Cumberland-fabric-group_eb64ed01-b283-40b3-8d4a-cd6ee386bdef.jpg","hue":60,"color_bucket":"yellow","familySkus":["DWCW-721346","DWCW-721446","DWCW-721546"]},"DWCW-721346":{"patternBase":"Cumberland on","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Cumberland-fabric-group_47aaecb6-d1f4-4ea6-83a4-2dfe0a441adc.jpg","hue":51,"color_bucket":"yellow","familySkus":["DWCW-721246","DWCW-721446","DWCW-721546"]},"DWCW-721446":{"patternBase":"Cumberland on","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Cumberland-fabric-group_0db54161-f509-443e-9559-7452fa50e2c5.jpg","hue":53,"color_bucket":"yellow","familySkus":["DWCW-721246","DWCW-721346","DWCW-721546"]},"DWCW-721546":{"patternBase":"Cumberland on","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Cumberland-fabric-group_b889c833-0fea-4052-81ce-f74af19c5871.jpg","hue":141,"color_bucket":"green","familySkus":["DWCW-721246","DWCW-721346","DWCW-721446"]},"DWCW-720946":{"patternBase":"Cumberland One","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Cumberland-fabric-group_efc0a994-ccbb-410e-84f9-ad345b2f74c2.jpg","hue":62,"color_bucket":"yellow","familySkus":["DWCW-721046","DWCW-721146"]},"DWCW-721046":{"patternBase":"Cumberland One","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Cumberland-fabric-group_fa238df1-5d65-46a5-b3b6-f343b632ffd8.jpg","hue":52,"color_bucket":"yellow","familySkus":["DWCW-720946","DWCW-721146"]},"DWCW-721146":{"patternBase":"Cumberland One","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Cumberland-fabric-group_5b450daa-829f-46b8-a509-49f12bd1c03c.jpg","hue":53,"color_bucket":"yellow","familySkus":["DWCW-720946","DWCW-721046"]},"DWCW-721646":{"patternBase":"Deauville Marine Blue","room":null,"hue":209,"color_bucket":"blue","familySkus":[]},"DWCW-721746":{"patternBase":"Deauville Navy","room":null,"hue":219,"color_bucket":"blue","familySkus":[]},"DWCW-721846":{"patternBase":"Des Gardes Black","room":null,"hue":75,"color_bucket":"green","familySkus":[]},"DWCW-721946":{"patternBase":"Des Gardes Burgundy","room":null,"hue":354,"color_bucket":"red","familySkus":[]},"DWCW-722046":{"patternBase":"Des Gardes Gold Metallic","room":null,"hue":36,"color_bucket":"orange","familySkus":[]},"DWCW-722146":{"patternBase":"Des Gardes Gray","room":null,"hue":49,"color_bucket":"yellow","familySkus":[]},"DWCW-722246":{"patternBase":"Des Gardes New Navy","room":null,"hue":212,"color_bucket":"blue","familySkus":[]},"DWCW-722346":{"patternBase":"Des Gardes Pumice","room":null,"hue":42,"color_bucket":"orange","familySkus":[]},"DWCW-722446":{"patternBase":"Des Gardes Reverse Black Off White","room":null,"hue":21,"color_bucket":"orange","familySkus":[]},"DWCW-722546":{"patternBase":"Des Gardes Reverse Burgundy Off White","room":null,"hue":354,"color_bucket":"red","familySkus":[]},"DWCW-722646":{"patternBase":"Des Gardes Reverse Gold Metallic Off White","room":null,"hue":40,"color_bucket":"orange","familySkus":[]},"DWCW-722746":{"patternBase":"Des Gardes Reverse Gray","room":null,"hue":49,"color_bucket":"yellow","familySkus":[]},"DWCW-722846":{"patternBase":"Des Gardes Reverse New Navy Off White","room":null,"hue":211,"color_bucket":"blue","familySkus":[]},"DWCW-722946":{"patternBase":"Des Gardes Reverse Pumice","room":null,"hue":44,"color_bucket":"orange","familySkus":[]},"DWCW-723046":{"patternBase":"Des Gardes Reverse Silver Metallic Off White","room":null,"hue":202,"color_bucket":"blue","familySkus":[]},"DWCW-723146":{"patternBase":"Des Gardes Reverse Soft French Green","room":null,"hue":63,"color_bucket":"yellow","familySkus":[]},"DWCW-723246":{"patternBase":"Des Gardes Reverse Soft Lavender","room":null,"hue":13,"color_bucket":"red","familySkus":[]},"DWCW-723346":{"patternBase":"Des Gardes Reverse Soft Pink","room":null,"hue":12,"color_bucket":"red","familySkus":[]},"DWCW-723446":{"patternBase":"Des Gardes Reverse Soft Windsor Blue","room":null,"hue":85,"color_bucket":"green","familySkus":[]},"DWCW-723546":{"patternBase":"Des Gardes Reverse Terracotta","room":null,"hue":16,"color_bucket":"orange","familySkus":[]},"DWCW-723646":{"patternBase":"Des Gardes Reverse Turquoise","room":null,"hue":170,"color_bucket":"green","familySkus":[]},"DWCW-723746":{"patternBase":"Des Gardes Silver Metallic","room":null,"hue":269,"color_bucket":"purple","familySkus":[]},"DWCW-723846":{"patternBase":"Des Gardes Soft French Green","room":null,"hue":69,"color_bucket":"yellow","familySkus":[]},"DWCW-723946":{"patternBase":"Des Gardes Soft Lavender","room":null,"hue":350,"color_bucket":"red","familySkus":[]},"DWCW-724046":{"patternBase":"Des Gardes Soft Pink","room":null,"hue":4,"color_bucket":"red","familySkus":[]},"DWCW-724146":{"patternBase":"Des Gardes Soft Windsor Blue","room":null,"hue":170,"color_bucket":"green","familySkus":[]},"DWCW-724246":{"patternBase":"Des Gardes Terracotta","room":null,"hue":22,"color_bucket":"orange","familySkus":[]},"DWCW-724346":{"patternBase":"Des Gardes Turquoise","room":null,"hue":169,"color_bucket":"green","familySkus":[]},"DWCW-724446":{"patternBase":"Edo Aqua","room":null,"hue":180,"color_bucket":"teal","familySkus":["DWCW-724546"]},"DWCW-724546":{"patternBase":"Edo Aqua","room":null,"hue":181,"color_bucket":"teal","familySkus":["DWCW-724446"]},"DWCW-724646":{"patternBase":"Edo Beige","room":null,"hue":40,"color_bucket":"orange","familySkus":[]},"DWCW-724746":{"patternBase":"Edo Brown","room":null,"hue":30,"color_bucket":"orange","familySkus":[]},"DWCW-724846":{"patternBase":"Edo Camel Ii","room":null,"hue":27,"color_bucket":"orange","familySkus":[]},"DWCW-724946":{"patternBase":"Edo Celadon","room":null,"hue":135,"color_bucket":"green","familySkus":[]},"DWCW-725046":{"patternBase":"Edo French Blue","room":null,"hue":211,"color_bucket":"blue","familySkus":[]},"DWCW-725146":{"patternBase":"Edo Gray","room":null,"hue":29,"color_bucket":"orange","familySkus":[]},"DWCW-725246":{"patternBase":"Edo II French Blue","room":null,"hue":214,"color_bucket":"blue","familySkus":[]},"DWCW-725346":{"patternBase":"Edo II New Shrimp","room":null,"hue":16,"color_bucket":"orange","familySkus":[]},"DWCW-725446":{"patternBase":"Edo II New Yellow","room":null,"hue":31,"color_bucket":"orange","familySkus":[]},"DWCW-725546":{"patternBase":"Edo Ii Salmon","room":null,"hue":14,"color_bucket":"red","familySkus":[]},"DWCW-725646":{"patternBase":"Edo Inca Gold","room":null,"hue":32,"color_bucket":"orange","familySkus":[]},"DWCW-725746":{"patternBase":"Edo Navy","room":null,"hue":213,"color_bucket":"blue","familySkus":["DWCW-725846"]},"DWCW-725846":{"patternBase":"Edo Navy","room":null,"hue":216,"color_bucket":"blue","familySkus":["DWCW-725746"]},"DWCW-725946":{"patternBase":"Edo New Shrimp","room":null,"hue":16,"color_bucket":"orange","familySkus":[]},"DWCW-726046":{"patternBase":"Edo Turquoise","room":null,"hue":146,"color_bucket":"green","familySkus":[]},"DWCW-726146":{"patternBase":"Edo Watermelon","room":null,"hue":348,"color_bucket":"red","familySkus":[]},"DWCW-726246":{"patternBase":"Edo White","room":null,"hue":52,"color_bucket":"yellow","familySkus":[]},"DWCW-726346":{"patternBase":"Edo Windsor Blue","room":null,"hue":162,"color_bucket":"green","familySkus":[]},"DWCW-726446":{"patternBase":"El Cid Black","room":null,"hue":49,"color_bucket":"yellow","familySkus":[]},"DWCW-726546":{"patternBase":"El Cid French Blue","room":null,"hue":203,"color_bucket":"blue","familySkus":[]},"DWCW-726646":{"patternBase":"El Cid Kelly Green","room":null,"hue":104,"color_bucket":"green","familySkus":[]},"DWCW-726746":{"patternBase":"El Cid New Melon","room":null,"hue":3,"color_bucket":"red","familySkus":[]},"DWCW-726846":{"patternBase":"El Cid New Navy","room":null,"hue":220,"color_bucket":"blue","familySkus":[]},"DWCW-726946":{"patternBase":"El Cid New Yellow","room":null,"hue":34,"color_bucket":"orange","familySkus":[]},"DWCW-727046":{"patternBase":"El Cid Turquoise","room":null,"hue":186,"color_bucket":"teal","familySkus":[]},"DWCW-727146":{"patternBase":"Fez Background Black","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Fez-Background-fabric-group_204b6a80-d54c-4738-ab4b-29b2f9224d66.jpg","hue":49,"color_bucket":"yellow","familySkus":[]},"DWCW-727246":{"patternBase":"Fez Background Brown","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Fez-Background-fabric-group_a575524d-a47e-441f-8c4b-c5401b03a3bf.jpg","hue":39,"color_bucket":"orange","familySkus":[]},"DWCW-727346":{"patternBase":"Fez Background Camel","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Fez-Background-fabric-group_fa073f98-352e-4c2d-a01c-973363a98a0b.jpg","hue":35,"color_bucket":"orange","familySkus":[]},"DWCW-727446":{"patternBase":"Fez Background Gold Metallic Off White","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Fez-Background-fabric-group_a60e1ff2-5878-4bfa-8b77-44baddb755d1.jpg","hue":44,"color_bucket":"orange","familySkus":[]},"DWCW-727546":{"patternBase":"Fez Ii Black","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Fez-II-fabric-group_7a3a340a-bf4f-4b0c-8a34-7993d9108fda.jpg","hue":42,"color_bucket":"orange","familySkus":[]},"DWCW-727646":{"patternBase":"Fez II Silver Metallic Off White","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Fez-II-fabric-group_d91ee337-d1ff-4cb5-8bef-630867e28856.jpg","hue":66,"color_bucket":"yellow","familySkus":[]},"DWCW-727746":{"patternBase":"Fiorentina Aqua","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Fiorentina-fabric-group_619c88ac-3278-4054-95a3-e2204b5e9993.jpg","hue":160,"color_bucket":"green","familySkus":[]},"DWCS-915246":{"patternBase":"Fiorentina Dark Yellow","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Fiorentina-fabric-group_0b7bd0b5-4869-4fd9-b853-85932d34e0a4.jpg","hue":35,"color_bucket":"orange","familySkus":[]},"DWCW-727846":{"patternBase":"Fiorentina Gray","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Fiorentina-fabric-group_938c9140-d906-4f8c-8b31-060cc88e67a7.jpg","hue":71,"color_bucket":"green","familySkus":[]},"DWCW-727946":{"patternBase":"Fiorentina Greige Almost White","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Fiorentina-fabric-group_26d51368-fb8a-4ea6-b2ab-1b3d12db91ba.jpg","hue":37,"color_bucket":"orange","familySkus":[]},"DWCW-728046":{"patternBase":"Fiorentina New Apple","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Fiorentina-fabric-group_16cf27de-1916-4285-8d20-8c05e4ee6930.jpg","hue":68,"color_bucket":"yellow","familySkus":[]},"DWCS-916046":{"patternBase":"Fiorentina Terracotta","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Fiorentina-fabric-group_1d34ddd1-342a-4792-b0be-736b49ac7b71.jpg","hue":20,"color_bucket":"orange","familySkus":[]},"DWCW-728146":{"patternBase":"Fiorentina Two Color Lavender Purple Almost White","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Fiorentina-Two-Color-fabric-group_d5cea41f-229f-44c4-b7ea-6b8892686223.jpg","hue":309,"color_bucket":"magenta","familySkus":[]},"DWCW-728246":{"patternBase":"Fiorentina White","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Fiorentina-fabric-group_e05f4208-1613-4bfd-8e2e-e28c462edbd7.jpg","hue":50,"color_bucket":"yellow","familySkus":[]},"DWCW-728346":{"patternBase":"Fiorentina Windsor Blue Off White","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Fiorentina-fabric-group_9d5dd61c-7ebb-4acf-a284-d26b9391a024.jpg","hue":81,"color_bucket":"green","familySkus":[]},"DWCW-728446":{"patternBase":"Fiorentina Windsor Blue White Patent Vinyl","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Fiorentina-fabric-group_5220e29d-c857-4628-a150-59ad309c7609.jpg","hue":168,"color_bucket":"green","familySkus":[]},"DWCW-728546":{"patternBase":"Flores Multi Brown Cream","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/FLORES_20MULTI_204060M-09WP.jpg","hue":19,"color_bucket":"orange","familySkus":[]},"DWCW-728646":{"patternBase":"Flores Multi French Blue Cream","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/FLORES_20MULTI_204060M-02WP.jpg","hue":214,"color_bucket":"blue","familySkus":[]},"DWCW-728746":{"patternBase":"Flores Multi Gray Cream","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/FLORES_20MULTI_204060M-06WP.jpg","hue":54,"color_bucket":"yellow","familySkus":[]},"DWCW-728846":{"patternBase":"Flores Multi Inca Gold Cream","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/FLORES_20MULTI_204060M-06WP_88986417-bf69-4ef3-ad4c-957dd032bdee.jpg","hue":41,"color_bucket":"orange","familySkus":[]},"DWCW-728946":{"patternBase":"Flores Multi Navy Cream","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/FLORES_20MULTI_204060M-06WP_003f85db-c71e-4179-87d2-b8156264a4ab.jpg","hue":213,"color_bucket":"blue","familySkus":[]},"DWCW-729046":{"patternBase":"Flores Multi New Blue Cream","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Zig-Zag-New-Shrimp-on-Almost-White-AP302-06.jpg","hue":172,"color_bucket":"teal","familySkus":[]},"DWCW-729146":{"patternBase":"Flores Multi New Green Cream","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/FLORES_20MULTI_204060M-03WP.jpg","hue":64,"color_bucket":"yellow","familySkus":[]},"DWCW-729246":{"patternBase":"Flores Multi Orange Cream","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/FLORES_20MULTI_204060M-04WP.jpg","hue":14,"color_bucket":"red","familySkus":[]},"DWCW-729346":{"patternBase":"Flores Multi Shrimp Cream","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/FLORES_20MULTI_204060M-07WP.jpg","hue":1,"color_bucket":"red","familySkus":[]},"DWCS-921946":{"patternBase":"Gate House Reverse One Color Beige","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Lyford-Trellis-One-Color-Wallpaper-Forest-Clover-Green-on-White-6021W-20-sm-thumb_106e6f2e-85f2-4ad0-85bf-3f16862b5128.jpg","hue":35,"color_bucket":"orange","familySkus":["DWCS-922046"]},"DWCS-922046":{"patternBase":"Gate House Reverse One Color Beige","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Lyford-Trellis-One-Color-Wallpaper-Forest-Clover-Green-on-White-6021W-20-sm-thumb_fdddb45b-1f1c-446e-becd-405b75871bfb.jpg","hue":35,"color_bucket":"orange","familySkus":["DWCS-921946"]},"DWCS-922146":{"patternBase":"Gate House Reverse One Color French Blue","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sigourney-Reverse-Small-Scale-Royal-Blue-on-White-2485WP-13.jpg","hue":220,"color_bucket":"blue","familySkus":["DWCS-922246"]},"DWCS-922246":{"patternBase":"Gate House Reverse One Color French Blue","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sigourney-Reverse-Small-Scale-Royal-Blue-on-White-2485WP-13_0300f9d2-5bed-4d28-8e12-45ec7679c975.jpg","hue":220,"color_bucket":"blue","familySkus":["DWCS-922146"]},"DWCS-922346":{"patternBase":"Gate House Reverse One Color Gray","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sigourney-Reverse-Large-Scale-Gray-on-White-Paper-2480-13WP.jpg","hue":41,"color_bucket":"orange","familySkus":["DWCS-922446"]},"DWCS-922446":{"patternBase":"Gate House Reverse One Color Gray","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sigourney-Reverse-Large-Scale-Gray-on-White-Paper-2480-13WP_949994de-e5c2-448c-a6a4-638e840707f3.jpg","hue":41,"color_bucket":"orange","familySkus":["DWCS-922346"]},"DWCS-922546":{"patternBase":"Gate House Reverse One Color Inca Gold","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Maze-Reverse-Two-Color-Light-Orange-New-Shrimp-2520-05OWP_9cc7bce5-eb43-4ac2-b49c-733d75512d79.jpg","hue":41,"color_bucket":"orange","familySkus":["DWCS-922646"]},"DWCS-922646":{"patternBase":"Gate House Reverse One Color Inca Gold","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Maze-Reverse-Two-Color-Light-Orange-New-Shrimp-2520-05OWP_5fdde781-ab03-4a4e-8da8-83553cfdfc7c.jpg","hue":41,"color_bucket":"orange","familySkus":["DWCS-922546"]},"DWCS-922746":{"patternBase":"Gate House Reverse One Color Lilac","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/MAZE_WALLPAPER_2510-0BWP_BEIGE_0da25784-b1c7-45c6-bc77-48dfd0888d84.jpg","hue":324,"color_bucket":"magenta","familySkus":["DWCS-922846"]},"DWCS-922846":{"patternBase":"Gate House Reverse One Color Lilac","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/MAZE_WALLPAPER_2510-0BWP_BEIGE_0735cc88-24bd-4365-8579-d312d61a4295.jpg","hue":324,"color_bucket":"magenta","familySkus":["DWCS-922746"]},"DWCS-922946":{"patternBase":"Gate House Reverse One Color New Navy","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Maze-Reverse-Two-Color-Light-Orange-New-Shrimp-2520-05OWP_9947e17c-4ec7-49d0-a1c0-a44862035d68.jpg","hue":224,"color_bucket":"blue","familySkus":["DWCS-923046"]},"DWCS-923046":{"patternBase":"Gate House Reverse One Color New Navy","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Maze-Reverse-Two-Color-Light-Orange-New-Shrimp-2520-05OWP_924e9245-fe8c-4ea3-bddb-ec735ba174a0.jpg","hue":224,"color_bucket":"blue","familySkus":["DWCS-922946"]},"DWCS-923146":{"patternBase":"Gate House Reverse One Color Sage Green","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Lyford-Trellis-One-Color-Wallpaper-Forest-Clover-Green-on-White-6021W-20-sm-thumb_62c057c8-d641-4842-b93f-5607e6a6269d.jpg","hue":59,"color_bucket":"yellow","familySkus":["DWCS-923246"]},"DWCS-923246":{"patternBase":"Gate House Reverse One Color Sage Green","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Lyford-Trellis-One-Color-Wallpaper-Forest-Clover-Green-on-White-6021W-20-sm-thumb_90493c9f-4ef0-4a0f-9d91-1a4aa1f4f1ee.jpg","hue":59,"color_bucket":"yellow","familySkus":["DWCS-923146"]},"DWCS-923346":{"patternBase":"Gate House Reverse One Color Windsor Blue","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Des-Gardes-Reverse-Soft-Windsor-Blue-on-Off-White-306581W.jpg","hue":211,"color_bucket":"blue","familySkus":["DWCS-923446"]},"DWCS-923446":{"patternBase":"Gate House Reverse One Color Windsor Blue","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Des-Gardes-Reverse-Soft-Windsor-Blue-on-Off-White-306581W_cde75e9c-4760-471c-918a-0931a55c0ae0.jpg","hue":211,"color_bucket":"blue","familySkus":["DWCS-923346"]},"DWCW-729446":{"patternBase":"Ginza Brown Camel Aqua","room":null,"hue":71,"color_bucket":"green","familySkus":[]},"DWCW-729546":{"patternBase":"Ginza Inca Aqua Jungle","room":null,"hue":77,"color_bucket":"green","familySkus":[]},"DWCW-729646":{"patternBase":"Ginza Magenta Plum Pink","room":null,"hue":356,"color_bucket":"red","familySkus":[]},"DWCW-729746":{"patternBase":"Gorrivan Fretwork Camel","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Gorrivan-Fretwork-Camel-on-Beige-Grasscloth-6300R-CAMEL_678b7641-adb1-4289-b6df-2c2556e0e0ac.jpg","hue":36,"color_bucket":"orange","familySkus":[]},"DWCW-729846":{"patternBase":"Happy Garden Background Black","room":null,"hue":182,"color_bucket":"teal","familySkus":[]},"DWCW-729946":{"patternBase":"Happy Garden Background Gray","room":null,"hue":59,"color_bucket":"yellow","familySkus":[]},"DWCW-730046":{"patternBase":"Happy Garden Background Grey","room":null,"hue":111,"color_bucket":"green","familySkus":[]},"DWCW-730146":{"patternBase":"Happy Garden Background Jungle Green","room":null,"hue":81,"color_bucket":"green","familySkus":[]},"DWCW-730246":{"patternBase":"Happy Garden Background Lavender","room":null,"hue":306,"color_bucket":"magenta","familySkus":[]},"DWCW-730346":{"patternBase":"Happy Garden Background Magenta","room":null,"hue":341,"color_bucket":"red","familySkus":[]},"DWCW-730446":{"patternBase":"Happy Garden Background Navy","room":null,"hue":225,"color_bucket":"blue","familySkus":[]},"DWCW-730546":{"patternBase":"Happy Garden Background Rust Orange","room":null,"hue":7,"color_bucket":"red","familySkus":[]},"DWCW-730646":{"patternBase":"Happy Garden Background Windsor Blue","room":null,"hue":198,"color_bucket":"teal","familySkus":[]},"DWCW-730746":{"patternBase":"Happy Garden Grey","room":null,"hue":203,"color_bucket":"blue","familySkus":[]},"DWCW-730846":{"patternBase":"Happy Garden Lavender","room":null,"hue":258,"color_bucket":"purple","familySkus":[]},"DWCW-730946":{"patternBase":"Happy Garden Navy","room":null,"hue":219,"color_bucket":"blue","familySkus":[]},"DWCW-731046":{"patternBase":"Happy Garden Rust Orange","room":null,"hue":11,"color_bucket":"red","familySkus":[]},"DWCW-731146":{"patternBase":"Happy Garden Turquoise","room":null,"hue":188,"color_bucket":"teal","familySkus":[]},"DWCW-731246":{"patternBase":"Hawthorne French Blue","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Hawthorne-fabric-group.jpg","hue":209,"color_bucket":"blue","familySkus":[]},"DWCW-731346":{"patternBase":"Hawthorne Magenta","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Hawthorne-fabric-group_0eff2bac-cdbb-47b4-b943-e70181c96480.jpg","hue":344,"color_bucket":"red","familySkus":[]},"DWCW-731446":{"patternBase":"Hawthorne Pale Aqua","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Hawthorne-fabric-group_711901fc-dd1f-4cee-ae54-65e7d5af0e17.jpg","hue":172,"color_bucket":"teal","familySkus":[]},"DWCW-731546":{"patternBase":"Henriot Floral Peach Yellows","room":null,"hue":46,"color_bucket":"yellow","familySkus":[]},"DWCW-731646":{"patternBase":"Henriot Floral Turquoise","room":null,"hue":188,"color_bucket":"teal","familySkus":[]},"DWCS-926446":{"patternBase":"Hulai Batik","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/hulai-batik-fabric-group-2024_221a3644-e7f5-43ef-b8b2-781de1b58810.png","hue":40,"color_bucket":"orange","familySkus":[]},"DWCS-927046":{"patternBase":"II Blue","room":null,"hue":206,"color_bucket":"blue","familySkus":[]},"DWCW-731746":{"patternBase":"Indramayu Reverse Bali Blue","room":null,"hue":190,"color_bucket":"teal","familySkus":[]},"DWCW-731846":{"patternBase":"Indramayu Reverse French Blue","room":null,"hue":217,"color_bucket":"blue","familySkus":[]},"DWCW-731946":{"patternBase":"Indramayu Reverse Grassy Green White Matte","room":null,"hue":131,"color_bucket":"green","familySkus":[]},"DWCW-732046":{"patternBase":"Indramayu Reverse Royal Blue","room":null,"hue":224,"color_bucket":"blue","familySkus":[]},"DWCW-732146":{"patternBase":"Indramayu Reverse Tan","room":null,"hue":44,"color_bucket":"orange","familySkus":[]},"DWCW-732246":{"patternBase":"Interweave Dark_grey","room":null,"hue":188,"color_bucket":"teal","familySkus":[]},"DWCW-732346":{"patternBase":"Interweave Dark_turquoise","room":null,"hue":196,"color_bucket":"teal","familySkus":[]},"DWCW-732446":{"patternBase":"Interweave Inca","room":null,"hue":41,"color_bucket":"orange","familySkus":[]},"DWCW-732546":{"patternBase":"Interweave Jungle_green","room":null,"hue":80,"color_bucket":"green","familySkus":[]},"DWCW-732646":{"patternBase":"Interweave Light_blue","room":null,"hue":212,"color_bucket":"blue","familySkus":[]},"DWCW-732746":{"patternBase":"Interweave Navy","room":null,"hue":225,"color_bucket":"blue","familySkus":[]},"DWCW-732846":{"patternBase":"Interweave Periwinkle","room":null,"hue":233,"color_bucket":"blue","familySkus":[]},"DWCW-732946":{"patternBase":"Interweave Purple","room":null,"hue":308,"color_bucket":"magenta","familySkus":[]},"DWCW-733046":{"patternBase":"Interweave Soft_pink","room":null,"hue":342,"color_bucket":"red","familySkus":[]},"DWCW-733146":{"patternBase":"Isfahan Melon Green Brown","room":null,"hue":25,"color_bucket":"orange","familySkus":[]},"DWCW-733246":{"patternBase":"Isfahan Multi Celeste Blue","room":null,"hue":208,"color_bucket":"blue","familySkus":["DWCW-733346"]},"DWCW-733346":{"patternBase":"Isfahan Multi Celeste Blue","room":null,"hue":203,"color_bucket":"blue","familySkus":["DWCW-733246"]},"DWCW-733446":{"patternBase":"Isfahan Multi Turquoise Celadon Teal","room":null,"hue":162,"color_bucket":"green","familySkus":[]},"DWCW-733546":{"patternBase":"Isfahan Purple Lilac Turquoise","room":null,"hue":19,"color_bucket":"orange","familySkus":[]},"DWCW-733646":{"patternBase":"Island Ikat","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Island-Ikat-fabric-group_30fee875-cfd3-4b9e-8a27-802304416a2b.jpg","hue":56,"color_bucket":"yellow","familySkus":["DWCW-733746","DWCW-733846","DWCW-733946","DWCW-734046"]},"DWCW-733746":{"patternBase":"Island Ikat","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Island-Ikat-fabric-group_56c08949-c2d0-4ba3-b1b1-fdba0b45b3f3.jpg","hue":158,"color_bucket":"green","familySkus":["DWCW-733646","DWCW-733846","DWCW-733946","DWCW-734046"]},"DWCW-733846":{"patternBase":"Island Ikat","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Island-Ikat-fabric-group_7be576b4-f539-4b9e-9672-92427690cd30.jpg","hue":62,"color_bucket":"yellow","familySkus":["DWCW-733646","DWCW-733746","DWCW-733946","DWCW-734046"]},"DWCW-733946":{"patternBase":"Island Ikat","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Island-Ikat-fabric-group_58a87fe5-8a7c-40cc-90f9-bf6a6a983003.jpg","hue":68,"color_bucket":"yellow","familySkus":["DWCW-733646","DWCW-733746","DWCW-733846","DWCW-734046"]},"DWCW-734046":{"patternBase":"Island Ikat","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Island-Ikat-fabric-group_128c17d8-9fc2-40a2-b780-02ca5c184566.jpg","hue":212,"color_bucket":"blue","familySkus":["DWCW-733646","DWCW-733746","DWCW-733846","DWCW-733946"]},"DWCW-735446":{"patternBase":"Java Brown","room":null,"hue":27,"color_bucket":"orange","familySkus":[]},"DWCW-735746":{"patternBase":"Java Camel II","room":null,"hue":24,"color_bucket":"orange","familySkus":[]},"DWCW-734146":{"patternBase":"Java Grande Camel","room":null,"hue":25,"color_bucket":"orange","familySkus":["DWCW-734246"]},"DWCW-734246":{"patternBase":"Java Grande Camel","room":null,"hue":22,"color_bucket":"orange","familySkus":["DWCW-734146"]},"DWCW-734346":{"patternBase":"Java Grande French Blue","room":null,"hue":206,"color_bucket":"blue","familySkus":[]},"DWCW-734446":{"patternBase":"Java Grande Magenta","room":null,"hue":352,"color_bucket":"red","familySkus":[]},"DWCW-734546":{"patternBase":"Java Grande Navy","room":null,"hue":222,"color_bucket":"blue","familySkus":["DWCS-931946"]},"DWCS-931946":{"patternBase":"Java Grande Navy","room":null,"hue":222,"color_bucket":"blue","familySkus":["DWCW-734546"]},"DWCS-932046":{"patternBase":"Java Grande New Blue","room":null,"hue":195,"color_bucket":"teal","familySkus":[]},"DWCW-734646":{"patternBase":"Java Grande New Brown","room":null,"hue":27,"color_bucket":"orange","familySkus":[]},"DWCW-734746":{"patternBase":"Java Grande Tan","room":null,"hue":42,"color_bucket":"orange","familySkus":[]},"DWCW-734846":{"patternBase":"Java Grande Taupe","room":null,"hue":34,"color_bucket":"orange","familySkus":[]},"DWCW-734946":{"patternBase":"Java Grande Turquoise","room":null,"hue":189,"color_bucket":"teal","familySkus":[]},"DWCS-932646":{"patternBase":"Java Grande Turquoise_on White","room":null,"hue":189,"color_bucket":"teal","familySkus":[]},"DWCW-735046":{"patternBase":"Java Java Aqua","room":null,"hue":189,"color_bucket":"teal","familySkus":[]},"DWCW-735146":{"patternBase":"Java Java Bali Blue","room":null,"hue":189,"color_bucket":"teal","familySkus":[]},"DWCW-735246":{"patternBase":"Java Java Bali Green","room":null,"hue":71,"color_bucket":"green","familySkus":[]},"DWCW-735346":{"patternBase":"Java Java Beige","room":null,"hue":46,"color_bucket":"yellow","familySkus":[]},"DWCW-735546":{"patternBase":"Java Java Brown","room":null,"hue":28,"color_bucket":"orange","familySkus":["DWCW-735646"]},"DWCW-735646":{"patternBase":"Java Java Brown","room":null,"hue":16,"color_bucket":"orange","familySkus":["DWCW-735546"]},"DWCW-735846":{"patternBase":"Java Java Celadon","room":null,"hue":110,"color_bucket":"green","familySkus":[]},"DWCW-735946":{"patternBase":"Java Java French Blue","room":null,"hue":213,"color_bucket":"blue","familySkus":[]},"DWCW-736046":{"patternBase":"Java Java Lilac","room":null,"hue":345,"color_bucket":"red","familySkus":[]},"DWCW-736246":{"patternBase":"Java Java Navy","room":null,"hue":216,"color_bucket":"blue","familySkus":[]},"DWCW-736446":{"patternBase":"Java Java New Yellow","room":null,"hue":43,"color_bucket":"orange","familySkus":[]},"DWCW-736646":{"patternBase":"Java Java Pale Pink","room":null,"hue":17,"color_bucket":"orange","familySkus":[]},"DWCW-736746":{"patternBase":"Java Java Pale Yellow","room":null,"hue":49,"color_bucket":"yellow","familySkus":[]},"DWCW-736846":{"patternBase":"Java Java Pearl","room":null,"hue":41,"color_bucket":"orange","familySkus":[]},"DWCW-736946":{"patternBase":"Java Java Pink","room":null,"hue":354,"color_bucket":"red","familySkus":[]},"DWCW-737146":{"patternBase":"Java Java Taupe","room":null,"hue":35,"color_bucket":"orange","familySkus":[]},"DWCW-737246":{"patternBase":"Java Java White","room":null,"hue":53,"color_bucket":"yellow","familySkus":[]},"DWCW-737346":{"patternBase":"Java Java Yellow","room":null,"hue":43,"color_bucket":"orange","familySkus":[]},"DWCW-736546":{"patternBase":"Java Pacific Blue Almost White","room":null,"hue":207,"color_bucket":"blue","familySkus":[]},"DWCW-737446":{"patternBase":"Java Petite Almond","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Java-Petite-fabric-group_db71cf48-63c5-41b5-afbc-27e89960f25c.jpg","hue":42,"color_bucket":"orange","familySkus":[]},"DWCW-737546":{"patternBase":"Java Petite Aqua","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Java-Petite-fabric-group_4ef2efa8-6cff-4ebf-8e54-69f7776c207d.jpg","hue":185,"color_bucket":"teal","familySkus":[]},"DWCW-737646":{"patternBase":"Java Petite Blue","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Java-Petite-fabric-group_67c44c7e-2442-4fbc-ab38-2b3e553e7738.jpg","hue":211,"color_bucket":"blue","familySkus":[]},"DWCW-737746":{"patternBase":"Java Petite Camel Ii","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Java-Petite-fabric-group_48db5ad3-b6c8-4bb2-8c79-578d809d4014.jpg","hue":25,"color_bucket":"orange","familySkus":[]},"DWCW-737846":{"patternBase":"Java Petite Celadon","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Java-Petite-fabric-group_dfa793cd-6186-49b7-a452-d680c6b09c08.jpg","hue":66,"color_bucket":"yellow","familySkus":[]},"DWCW-737946":{"patternBase":"Java Petite French Blue","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Java-Petite-fabric-group_be546c36-1479-46a2-bf38-de10adbd2f91.jpg","hue":206,"color_bucket":"blue","familySkus":[]},"DWCW-738046":{"patternBase":"Java Petite Greige","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Java-Petite-fabric-group_3db40b1a-c4f4-435f-b8f8-54e6e3c9657a.jpg","hue":49,"color_bucket":"yellow","familySkus":[]},"DWCW-738146":{"patternBase":"Java Petite Taupe","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Java-Petite-fabric-group_eb577739-19f7-444e-bf02-4925bf46bbe4.jpg","hue":35,"color_bucket":"orange","familySkus":[]},"DWCW-737046":{"patternBase":"Java Salmon Orange","room":null,"hue":15,"color_bucket":"red","familySkus":[]},"DWCW-738246":{"patternBase":"Jave Petite Cadet","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Java-Grande-French-Blue-on-White-6620-20WWP.jpg","hue":190,"color_bucket":"teal","familySkus":[]},"DWCW-738346":{"patternBase":"Jave Petite Lime","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/JAVA_GRANDE_WALLPAPER_6620-06AWP_NEW_SHRIMP.jpg","hue":87,"color_bucket":"green","familySkus":[]},"DWCW-738446":{"patternBase":"Jave Petite Periwinkle","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/EDO_20II_206990WP-19PV.jpg","hue":221,"color_bucket":"blue","familySkus":[]},"DWCW-738546":{"patternBase":"Jeanne Medallion Gold","room":null,"hue":48,"color_bucket":"yellow","familySkus":[]},"DWCW-738646":{"patternBase":"Jeanne Medallion Silver","room":null,"hue":57,"color_bucket":"yellow","familySkus":[]},"DWCW-738746":{"patternBase":"Juan Les Pins Turquoise","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Juan-Les-Pins-Turquoise-on-White-Grasscloth-AP115GC-05.jpg","hue":171,"color_bucket":"teal","familySkus":[]},"DWCW-738846":{"patternBase":"Kalamkari All Over Celeste Gray Charcoal","room":null,"hue":202,"color_bucket":"blue","familySkus":[]},"DWCW-738946":{"patternBase":"Kalamkari All Over Lavender Green Navy","room":null,"hue":107,"color_bucket":"green","familySkus":[]},"DWCW-739046":{"patternBase":"Kalamkari All Over Red Burgundy Purple","room":null,"hue":343,"color_bucket":"red","familySkus":[]},"DWCW-739146":{"patternBase":"Kalamkari All Over Slate Royal Sky Denim","room":null,"hue":221,"color_bucket":"blue","familySkus":[]},"DWCW-739346":{"patternBase":"Kalamkari Border Celeste Gray Charcoal","room":null,"hue":203,"color_bucket":"blue","familySkus":["DWCW-739246"]},"DWCW-739246":{"patternBase":"Kalamkari Border Celeste Gray Charcoal","room":null,"hue":202,"color_bucket":"blue","familySkus":["DWCW-739346"]},"DWCW-739546":{"patternBase":"Kalamkari Border Red Burgundy Purple","room":null,"hue":341,"color_bucket":"red","familySkus":["DWCW-739446"]},"DWCW-739446":{"patternBase":"Kalamkari Border Red Burgundy Purple","room":null,"hue":344,"color_bucket":"red","familySkus":["DWCW-739546"]},"DWCW-739646":{"patternBase":"Kalamkari Border Slate Royal Sky Denim","room":null,"hue":221,"color_bucket":"blue","familySkus":[]},"DWCW-739746":{"patternBase":"Kalamkari Floral Blues","room":null,"hue":222,"color_bucket":"blue","familySkus":[]},"DWCW-740346":{"patternBase":"Kazak Black Metallic Silver","room":null,"hue":271,"color_bucket":"purple","familySkus":[]},"DWCW-740746":{"patternBase":"Kazak Blue Metallic Gold","room":null,"hue":201,"color_bucket":"blue","familySkus":[]},"DWCW-740446":{"patternBase":"Kazak Coral New Yellow","room":null,"hue":10,"color_bucket":"red","familySkus":[]},"DWCW-740546":{"patternBase":"Kazak Magenta Lime","room":null,"hue":9,"color_bucket":"red","familySkus":[]},"DWCW-740646":{"patternBase":"Kazak New Navy Denim","room":null,"hue":216,"color_bucket":"blue","familySkus":[]},"DWCW-740246":{"patternBase":"Kazak_wallcovering Aqua_lilac","room":null,"hue":191,"color_bucket":"teal","familySkus":[]},"DWCW-739946":{"patternBase":"Kazak_Wallcovering Coral_New_Yellow","room":null,"hue":16,"color_bucket":"orange","familySkus":[]},"DWCW-739846":{"patternBase":"Kazak_wallcovering Magenta_lime","room":null,"hue":55,"color_bucket":"yellow","familySkus":[]},"DWCW-740046":{"patternBase":"Kazak_Wallcovering New_Navy_Denim_Blue","room":null,"hue":230,"color_bucket":"blue","familySkus":[]},"DWCW-740146":{"patternBase":"Kazak_wallcovering Orange_pink","room":null,"hue":9,"color_bucket":"red","familySkus":[]},"DWCW-740846":{"patternBase":"Kediri Batik","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Kediri-Batik-fabric-group_15140074-e353-454f-9c5f-860174abccdd.jpg","hue":51,"color_bucket":"yellow","familySkus":["DWCS-936246"]},"DWCS-936246":{"patternBase":"Kediri Batik","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Kediri-Batik-fabric-group_cfabd99b-05b1-492c-ae40-88e45408340a.jpg","hue":56,"color_bucket":"yellow","familySkus":["DWCW-740846"]},"DWCS-937746":{"patternBase":"Kyoto Two","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Kyoto-Two-Colors-fabric-group_5ec0296d-23b7-4e36-80f8-fbe2de4b98e7.jpg","hue":187,"color_bucket":"teal","familySkus":[]},"DWCW-740946":{"patternBase":"Labyrinth Greige_on_almost_white_HC","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Labyrinth_Twilight_on_almost_white_HC1520WP-15.jpg","hue":45,"color_bucket":"orange","familySkus":[]},"DWCW-741046":{"patternBase":"Labyrinth Twilight_on_almost_white_HC","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Labyrinth_Twilight_on_almost_white_HC1520WP-15_80e8cc24-c4ac-42c4-9d4e-7b374f5f3438.jpg","hue":125,"color_bucket":"green","familySkus":[]},"DWCW-741146":{"patternBase":"Labyrinth Vapor","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Labyrinth_Twilight_on_almost_white_HC1520WP-15_c0611b2a-5074-47ab-9c87-482f78e37a9d.jpg","hue":177,"color_bucket":"teal","familySkus":[]},"DWCW-741246":{"patternBase":"Lascaux Black","room":null,"hue":74,"color_bucket":"green","familySkus":[]},"DWCW-741346":{"patternBase":"Lascaux White","room":null,"hue":39,"color_bucket":"orange","familySkus":[]},"DWCS-937846":{"patternBase":"Lila Stripe","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Lila-Stripe-fabric-group.jpg","hue":201,"color_bucket":"blue","familySkus":["DWCS-937946","DWCS-938046","DWCS-938146","DWCS-938246","DWCS-938346"]},"DWCS-937946":{"patternBase":"Lila Stripe","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Lila-Stripe-fabric-group_f16fb269-d201-414f-a632-579d10e35126.jpg","hue":217,"color_bucket":"blue","familySkus":["DWCS-937846","DWCS-938046","DWCS-938146","DWCS-938246","DWCS-938346"]},"DWCS-938046":{"patternBase":"Lila Stripe","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Lila-Stripe-fabric-group_7628be82-b317-40c9-b45b-ecab3484916c.jpg","hue":60,"color_bucket":"yellow","familySkus":["DWCS-937846","DWCS-937946","DWCS-938146","DWCS-938246","DWCS-938346"]},"DWCS-938146":{"patternBase":"Lila Stripe","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Lila-Stripe-fabric-group_34ddf92d-a78e-40a2-924a-91c829de1a5a.jpg","hue":48,"color_bucket":"yellow","familySkus":["DWCS-937846","DWCS-937946","DWCS-938046","DWCS-938246","DWCS-938346"]},"DWCS-938246":{"patternBase":"Lila Stripe","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Lila-Stripe-fabric-group_d6c358d3-19a8-441c-9a38-53735898f95d.jpg","hue":284,"color_bucket":"purple","familySkus":["DWCS-937846","DWCS-937946","DWCS-938046","DWCS-938146","DWCS-938346"]},"DWCS-938346":{"patternBase":"Lila Stripe","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Lila-Stripe-fabric-group_0c032f54-712b-472d-b11c-eeb5824513d6.jpg","hue":1,"color_bucket":"red","familySkus":["DWCS-937846","DWCS-937946","DWCS-938046","DWCS-938146","DWCS-938246"]},"DWCW-741446":{"patternBase":"Lim Diagonal Multi French Blue Navy","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Lim-Diagonal-group_ccee1e40-a446-4c1c-bcde-87df315389ed.jpg","hue":223,"color_bucket":"blue","familySkus":[]},"DWCW-741546":{"patternBase":"Links II Multi Pinks","room":null,"hue":5,"color_bucket":"red","familySkus":[]},"DWCW-741646":{"patternBase":"Links II Multi Purple","room":null,"hue":7,"color_bucket":"red","familySkus":[]},"DWCW-741746":{"patternBase":"Longfellow Black Grey","room":null,"hue":74,"color_bucket":"green","familySkus":[]},"DWCW-741846":{"patternBase":"Longfellow Moss Green Taupe","room":null,"hue":59,"color_bucket":"yellow","familySkus":["DWCW-741946"]},"DWCW-741946":{"patternBase":"Longfellow Moss Green Taupe","room":null,"hue":62,"color_bucket":"yellow","familySkus":["DWCW-741846"]},"DWCW-742046":{"patternBase":"Longfellow Navy French Blue","room":null,"hue":200,"color_bucket":"teal","familySkus":["DWCW-742146"]},"DWCW-742146":{"patternBase":"Longfellow Navy French Blue","room":null,"hue":208,"color_bucket":"blue","familySkus":["DWCW-742046"]},"DWCW-742246":{"patternBase":"Longfellow Orange Yellow","room":null,"hue":24,"color_bucket":"orange","familySkus":[]},"DWCW-742346":{"patternBase":"Longfellow Purple Lilac","room":null,"hue":291,"color_bucket":"magenta","familySkus":["DWCW-742446"]},"DWCW-742446":{"patternBase":"Longfellow Purple Lilac","room":null,"hue":293,"color_bucket":"magenta","familySkus":["DWCW-742346"]},"DWCW-742546":{"patternBase":"Longfellow Royal New Blue","room":null,"hue":216,"color_bucket":"blue","familySkus":[]},"DWCW-742646":{"patternBase":"Longfellow Strawberry","room":null,"hue":355,"color_bucket":"red","familySkus":["DWCW-742746"]},"DWCW-742746":{"patternBase":"Longfellow Strawberry","room":null,"hue":349,"color_bucket":"red","familySkus":["DWCW-742646"]},"DWCW-742846":{"patternBase":"Longfellow Turquoise","room":null,"hue":192,"color_bucket":"teal","familySkus":["DWCW-742946"]},"DWCW-742946":{"patternBase":"Longfellow Turquoise","room":null,"hue":196,"color_bucket":"teal","familySkus":["DWCW-742846"]},"DWCS-940946":{"patternBase":"Lulu Stripe","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Lulu-Stripe-fabric-group.jpg","hue":114,"color_bucket":"green","familySkus":["DWCS-941046","DWCS-941146","DWCS-941246","DWCS-941346","DWCS-941446","DWCS-941546"]},"DWCS-941046":{"patternBase":"Lulu Stripe","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Lulu-Stripe-fabric-group_83a82637-fba9-4537-8baa-141ca1429f00.jpg","hue":201,"color_bucket":"blue","familySkus":["DWCS-940946","DWCS-941146","DWCS-941246","DWCS-941346","DWCS-941446","DWCS-941546"]},"DWCS-941146":{"patternBase":"Lulu Stripe","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Lulu-Stripe-fabric-group_e62b9555-badf-4a7d-a9b0-a8e4d5643695.jpg","hue":217,"color_bucket":"blue","familySkus":["DWCS-940946","DWCS-941046","DWCS-941246","DWCS-941346","DWCS-941446","DWCS-941546"]},"DWCS-941246":{"patternBase":"Lulu Stripe","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Lulu-Stripe-fabric-group_deb533ca-9a38-4e4b-b20e-aba27ca52e5a.jpg","hue":61,"color_bucket":"yellow","familySkus":["DWCS-940946","DWCS-941046","DWCS-941146","DWCS-941346","DWCS-941446","DWCS-941546"]},"DWCS-941346":{"patternBase":"Lulu Stripe","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Lulu-Stripe-fabric-group_31b0023c-dcc6-4c1f-b7b3-53760d34c1a7.jpg","hue":58,"color_bucket":"yellow","familySkus":["DWCS-940946","DWCS-941046","DWCS-941146","DWCS-941246","DWCS-941446","DWCS-941546"]},"DWCS-941446":{"patternBase":"Lulu Stripe","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Lulu-Stripe-fabric-group_572a5f3f-9639-435d-8671-3829216d53f6.jpg","hue":286,"color_bucket":"purple","familySkus":["DWCS-940946","DWCS-941046","DWCS-941146","DWCS-941246","DWCS-941346","DWCS-941546"]},"DWCS-941546":{"patternBase":"Lulu Stripe","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Lulu-Stripe-fabric-group_f00bc833-4b4e-4113-b092-1dd1f64f4328.jpg","hue":358,"color_bucket":"red","familySkus":["DWCS-940946","DWCS-941046","DWCS-941146","DWCS-941246","DWCS-941346","DWCS-941446"]},"DWCW-743146":{"patternBase":"Lyford Background Light Turquoise Almost White","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Lyford-Background-fabric-group_176cb220-1bc3-4137-81b3-1f948f8c932b.jpg","hue":172,"color_bucket":"teal","familySkus":[]},"DWCW-743246":{"patternBase":"Lyford Background Magenta","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Lyford-Background-fabric-group_129559e9-9fdf-498e-859b-9bda2d43ae37.jpg","hue":338,"color_bucket":"red","familySkus":[]},"DWCW-743346":{"patternBase":"Lyford Background Navy Almost White","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Lyford-Background-fabric-group_97688e5a-0733-4f0d-b600-aaf925590299.jpg","hue":213,"color_bucket":"blue","familySkus":[]},"DWCW-743446":{"patternBase":"Lyford Background Navy","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Lyford-Background-fabric-group_65ebabad-8e96-457e-b66e-539ad938442b.jpg","hue":212,"color_bucket":"blue","familySkus":[]},"DWCW-743546":{"patternBase":"Lyford Diamond Bamboo Navy","room":null,"hue":66,"color_bucket":"yellow","familySkus":[]},"DWCW-743646":{"patternBase":"Lyford Diamond Bamboo New Shrimp","room":null,"hue":359,"color_bucket":"red","familySkus":[]},"DWCW-743746":{"patternBase":"Lyford Pagoda Navy","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Lyford-Pagoda-fabric-group_dc774ba3-00d8-40e7-8070-fb6cb184d86f.jpg","hue":214,"color_bucket":"blue","familySkus":[]},"DWCW-743846":{"patternBase":"Lyford Pagoda Petite Navy","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Lyford-Pagoda-Petite-fabric-group_1e350052-1d34-4a91-9841-e8c6fa485bb9.jpg","hue":216,"color_bucket":"blue","familySkus":[]},"DWCW-743946":{"patternBase":"Lyford Pagoda Pink","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Lyford-Pagoda-fabric-group_36d0dbfb-ffd7-43ed-a030-7e50c413874a.jpg","hue":1,"color_bucket":"red","familySkus":[]},"DWCW-744046":{"patternBase":"Lyford Trellis Beige Salmon","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Lyford-Trellis-fabric-group_3ce1da5a-0e04-46e0-acba-47ccdbf4502c.png","hue":37,"color_bucket":"orange","familySkus":[]},"DWCW-744146":{"patternBase":"Lyford Trellis Beiges Green","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Lyford-Trellis-fabric-group_8658e44e-020a-431d-8ca5-8d1b93faef2a.png","hue":76,"color_bucket":"green","familySkus":[]},"DWCW-744246":{"patternBase":"Lyford Trellis Brown Beige Taupe Pink","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Lyford-Trellis-fabric-group_0893a1e9-ebae-41f6-a771-b6336b451987.png","hue":22,"color_bucket":"orange","familySkus":[]},"DWCW-744346":{"patternBase":"Lyford Trellis Light Blue French Blue Brown","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Lyford-Trellis-fabric-group_d89de6d4-b1ae-413e-819b-ec7f88bed4d4.png","hue":195,"color_bucket":"teal","familySkus":[]},"DWCW-744446":{"patternBase":"Lyford Trellis Navy New Blue Terracotta","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Lyford-Trellis-fabric-group_1d80c92c-68c8-4531-b6e4-001d8d735a77.png","hue":221,"color_bucket":"blue","familySkus":[]},"DWCW-744546":{"patternBase":"Lyford Trellis Navy","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Lyford-Trellis-fabric-group_99566a35-156c-4239-ae69-cff6766498da.png","hue":221,"color_bucket":"blue","familySkus":[]},"DWCW-744846":{"patternBase":"Lyford Trellis New Blue Navy Terracotta","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Lyford-Trellis-fabric-group_9ce52c1e-123a-485d-ab1d-c4b2869af245.png","hue":209,"color_bucket":"blue","familySkus":["DWCW-744646","DWCW-744746"]},"DWCW-744646":{"patternBase":"Lyford Trellis New Blue Navy Terracotta","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Lyford-Trellis-fabric-group_2d2bfc6a-bcf4-498f-a6ba-0051b6a6eb91.png","hue":209,"color_bucket":"blue","familySkus":["DWCW-744846","DWCW-744746"]},"DWCW-744746":{"patternBase":"Lyford Trellis New Blue Navy Terracotta","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Lyford-Trellis-fabric-group_b33ce292-6b04-4452-99ad-b5d6b880ef02.png","hue":209,"color_bucket":"blue","familySkus":["DWCW-744846","DWCW-744646"]},"DWCW-743046":{"patternBase":"Lyford Trellis","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Lyford-Trellis-fabric-group_dbb70135-2eed-415c-acd4-9162a0e0683d.png","hue":144,"color_bucket":"green","familySkus":[]},"DWCW-744946":{"patternBase":"Lyford Trellis Slate Cream","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Lyford-Trellis-fabric-group_df114e80-cb89-4366-9c5f-082440ee7b72.png","hue":205,"color_bucket":"blue","familySkus":[]},"DWCW-745046":{"patternBase":"Lysette Chartreuse","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/lysette-fabric-group-2024-edited.png","hue":62,"color_bucket":"yellow","familySkus":[]},"DWCW-745146":{"patternBase":"Lysette French Blue","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/lysette-fabric-group-2024-edited_6a1a2df4-5a70-42f8-a248-95400a8aa161.png","hue":212,"color_bucket":"blue","familySkus":[]},"DWCW-745246":{"patternBase":"Lysette Navy","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/lysette-fabric-group-2024-edited_026fcb51-db2d-4089-8a7d-e1ffd261f210.png","hue":221,"color_bucket":"blue","familySkus":[]},"DWCW-745346":{"patternBase":"Lysette New Shrimp","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/lysette-fabric-group-2024-edited_0d055580-5256-4522-b89c-81881e682798.png","hue":9,"color_bucket":"red","familySkus":[]},"DWCW-745446":{"patternBase":"Lysette Windsor Blue","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/lysette-fabric-group-2024-edited_886664d2-f19c-4a05-842e-6f6d9f076aca.png","hue":225,"color_bucket":"blue","familySkus":[]},"DWCW-745546":{"patternBase":"Macoco Ii","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Macoco-II-fabric-group_bae30319-6733-4cc1-8317-47872261e6ee.jpg","hue":169,"color_bucket":"green","familySkus":[]},"DWCW-745646":{"patternBase":"Macoco Reverse","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Macoco-Reverse-fabric-group_395f051b-9523-42a1-9530-96d6facc0d9b.jpg","hue":44,"color_bucket":"orange","familySkus":[]},"DWCW-745746":{"patternBase":"Madagascar_backed Natural","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Madagascar_Backed_Natural_300345W.jpg","hue":37,"color_bucket":"orange","familySkus":[]},"DWCW-745846":{"patternBase":"Madame Butterfly Greens","room":null,"hue":126,"color_bucket":"green","familySkus":[]},"DWCW-745946":{"patternBase":"Madame Butterfly Pinks","room":null,"hue":351,"color_bucket":"red","familySkus":[]},"DWCW-746046":{"patternBase":"Magic Garden","room":null,"hue":48,"color_bucket":"yellow","familySkus":[]},"DWCW-746146":{"patternBase":"Malaya Black_on_almost_white","room":null,"hue":62,"color_bucket":"yellow","familySkus":[]},"DWCW-746246":{"patternBase":"Malaya Celadon_on_almost_white","room":null,"hue":65,"color_bucket":"yellow","familySkus":[]},"DWCW-746346":{"patternBase":"Malaya Gray_on_almost_white","room":null,"hue":55,"color_bucket":"yellow","familySkus":[]},"DWCW-746446":{"patternBase":"Malaya Jungle_Green_on_almost_white","room":null,"hue":69,"color_bucket":"yellow","familySkus":[]},"DWCW-746546":{"patternBase":"Malaya New_Blue_on_almost_white","room":null,"hue":213,"color_bucket":"blue","familySkus":[]},"DWCW-746646":{"patternBase":"Malaya New_Brown_on_almost_white","room":null,"hue":29,"color_bucket":"orange","familySkus":[]},"DWCW-746746":{"patternBase":"Malaya Red_on_almost_white","room":null,"hue":359,"color_bucket":"red","familySkus":[]},"DWCW-746846":{"patternBase":"Malaya Zibby_Blue_on_almost_white","room":null,"hue":209,"color_bucket":"blue","familySkus":[]},"DWCW-746946":{"patternBase":"Maze Navy","room":null,"hue":215,"color_bucket":"blue","familySkus":[]},"DWCW-747046":{"patternBase":"Maze Reverse","room":null,"hue":16,"color_bucket":"orange","familySkus":["DWCW-747146"]},"DWCW-747146":{"patternBase":"Maze Reverse","room":null,"hue":299,"color_bucket":"magenta","familySkus":["DWCW-747046"]},"DWCW-747346":{"patternBase":"Maze Salmon","room":null,"hue":12,"color_bucket":"red","familySkus":[]},"DWCW-747446":{"patternBase":"Maze White","room":null,"hue":200,"color_bucket":"teal","familySkus":[]},"DWCW-747746":{"patternBase":"Medallion Brown Navy","room":null,"hue":34,"color_bucket":"orange","familySkus":[]},"DWCW-747546":{"patternBase":"Medallion Fig Cream","room":null,"hue":52,"color_bucket":"yellow","familySkus":[]},"DWCW-747646":{"patternBase":"Medallion Melon Camel","room":null,"hue":19,"color_bucket":"orange","familySkus":[]},"DWCW-747846":{"patternBase":"Melanie Ii Camel New Brown","room":null,"hue":38,"color_bucket":"orange","familySkus":[]},"DWCW-747946":{"patternBase":"Meloire Reverse White","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Meloire-Reverse-White-on-Off-White-AP809-01WP.jpg","hue":48,"color_bucket":"yellow","familySkus":[]},"DWCW-748046":{"patternBase":"Melong Batik Black","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Melong-Batik-fabric-group_3ed93cc0-a9fd-4db9-96ad-d6a23cdd5554.jpg","hue":51,"color_bucket":"yellow","familySkus":[]},"DWCW-748146":{"patternBase":"Melong Batik","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Melong-Batik-fabric-group_bc6108e4-8304-4c63-8b0d-b8d258844cdf.jpg","hue":31,"color_bucket":"orange","familySkus":[]},"DWCW-748246":{"patternBase":"Melong Batik Brown","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Melong-Batik-fabric-group_5c1b2254-e40a-46d2-ad2f-bf5b319b9b99.jpg","hue":13,"color_bucket":"red","familySkus":[]},"DWCW-748346":{"patternBase":"Melong Batik French Blue Off White","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Melong-Batik-fabric-group_40cc9be9-2283-4c49-8b67-6a38090eea80.jpg","hue":99,"color_bucket":"green","familySkus":[]},"DWCW-748446":{"patternBase":"Melong Batik French Blue","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Melong-Batik-fabric-group_640f5511-b9b3-41cd-851e-c176d11cc563.jpg","hue":212,"color_bucket":"blue","familySkus":["DWCW-748546"]},"DWCW-748546":{"patternBase":"Melong Batik French Blue","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Melong-Batik-fabric-group_8a655f32-fd5c-498b-835c-a7c5ad67ab47.jpg","hue":212,"color_bucket":"blue","familySkus":["DWCW-748446"]},"DWCW-748646":{"patternBase":"Melong Batik Jungle Green","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Melong-Batik-fabric-group_82146e24-d6d4-4fa6-8e05-d06cd5f3f407.jpg","hue":65,"color_bucket":"yellow","familySkus":[]},"DWCW-748746":{"patternBase":"Melong Batik Navy","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Melong-Batik-fabric-group_41fff4ce-d93c-41a0-98d5-324ea240ef65.jpg","hue":79,"color_bucket":"green","familySkus":["DWCW-748846"]},"DWCW-748846":{"patternBase":"Melong Batik Navy","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Melong-Batik-fabric-group_5a9d3478-605f-46a0-bc50-e030a55c6523.jpg","hue":215,"color_bucket":"blue","familySkus":["DWCW-748746"]},"DWCW-748946":{"patternBase":"Melong Batik New Navy","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Melong-Batik-fabric-group_6afabaf0-d5f4-4ea7-9125-c68c12420ec2.jpg","hue":56,"color_bucket":"yellow","familySkus":[]},"DWCW-749046":{"patternBase":"Melong Batik Reverse Navy","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Melong-Batik-Reverse-fabric-group_b405dad8-72bc-45df-ae7b-beee925eb614.jpg","hue":209,"color_bucket":"blue","familySkus":[]},"DWCW-749146":{"patternBase":"Melong Batik Turquoise","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Melong-Batik-fabric-group_90e5b1af-4c21-45c3-8e0f-44c8f5a314f1.jpg","hue":177,"color_bucket":"teal","familySkus":[]},"DWCW-749246":{"patternBase":"Mojave Pink","room":null,"hue":352,"color_bucket":"red","familySkus":[]},"DWCW-749346":{"patternBase":"Mojave White","room":null,"hue":53,"color_bucket":"yellow","familySkus":[]},"DWCW-749446":{"patternBase":"Montecito_Bamboo Cognac_on_Off_White_CP","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Montecito_Bamboo_Cognac_on_Off_White_CP1060W-05.jpg","hue":27,"color_bucket":"orange","familySkus":[]},"null":{"patternBase":"Nitik II New Yellow Salmon","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Nitik-II-fabric-group_249d74a0-4cee-4844-a074-4834c02b57aa.jpg","hue":43,"color_bucket":"orange","familySkus":["DWCW-754546"]},"DWCW-750546":{"patternBase":"Monty on","room":null,"hue":27,"color_bucket":"orange","familySkus":["null","null","null","null","null","DWCW-750646","DWCW-750746","DWCW-750846","DWCW-750946"]},"DWCW-750646":{"patternBase":"Monty on","room":null,"hue":183,"color_bucket":"teal","familySkus":["null","null","null","null","null","DWCW-750546","DWCW-750746","DWCW-750846","DWCW-750946"]},"DWCW-750746":{"patternBase":"Monty on","room":null,"hue":73,"color_bucket":"green","familySkus":["null","null","null","null","null","DWCW-750546","DWCW-750646","DWCW-750846","DWCW-750946"]},"DWCW-750846":{"patternBase":"Monty on","room":null,"hue":61,"color_bucket":"yellow","familySkus":["null","null","null","null","null","DWCW-750546","DWCW-750646","DWCW-750746","DWCW-750946"]},"DWCW-750946":{"patternBase":"Monty on","room":null,"hue":54,"color_bucket":"yellow","familySkus":["null","null","null","null","null","DWCW-750546","DWCW-750646","DWCW-750746","DWCW-750846"]},"DWCW-751046":{"patternBase":"Monty White","room":null,"hue":195,"color_bucket":"teal","familySkus":[]},"DWCW-751146":{"patternBase":"Nairobi Beige","room":null,"hue":42,"color_bucket":"orange","familySkus":[]},"DWCW-751246":{"patternBase":"Nairobi French","room":null,"hue":71,"color_bucket":"green","familySkus":[]},"DWCW-751346":{"patternBase":"Nairobi Pale","room":null,"hue":63,"color_bucket":"yellow","familySkus":["DWCW-751446"]},"DWCW-751446":{"patternBase":"Nairobi Pale","room":null,"hue":358,"color_bucket":"red","familySkus":["DWCW-751346"]},"DWCW-752546":{"patternBase":"Nitik Grande Camel Brown","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Nitik-Grande-fabric-group_bff57ad2-5a25-4577-812a-074353d641c8.jpg","hue":32,"color_bucket":"orange","familySkus":[]},"DWCW-752646":{"patternBase":"Nitik Grande Jungle Green Brown","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Nitik-Grande-fabric-group_25256a72-1bdc-43f0-a26d-24b11476e715.jpg","hue":80,"color_bucket":"green","familySkus":[]},"DWCW-752746":{"patternBase":"Nitik Grande Pink Navy","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Nitik-Grande-fabric-group_58702332-e100-473e-be52-74b1b8320a24.jpg","hue":2,"color_bucket":"red","familySkus":[]},"DWCW-752846":{"patternBase":"Nitik Grande Sky Navy","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Nitik-Grande-fabric-group_12aa92e1-e44f-41e0-ad07-88c6fdc6f726.jpg","hue":181,"color_bucket":"teal","familySkus":[]},"DWCW-753046":{"patternBase":"Nitik II Black Navy","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Nitik-II-fabric-group_b7dc79d2-ed06-4a49-bb77-50b6bcd031f9.jpg","hue":294,"color_bucket":"magenta","familySkus":[]},"DWCW-753146":{"patternBase":"Nitik II Blue Floresblue","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Nitik-II-fabric-group_8d89dd8d-3aa5-439e-a984-b861d7fa5a4b.jpg","hue":210,"color_bucket":"blue","familySkus":[]},"DWCW-753246":{"patternBase":"Nitik II French Blue Navy Almost White","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Nitik-II-fabric-group_3f6f7053-3af7-4ce1-90ff-a327b1d3d804.jpg","hue":216,"color_bucket":"blue","familySkus":[]},"DWCW-753346":{"patternBase":"Nitik II Gray Brown Almost White","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Nitik-II-fabric-group_8b671dfa-e1ec-4d04-b232-8c7a5079800e.jpg","hue":76,"color_bucket":"green","familySkus":[]},"DWCW-753446":{"patternBase":"Nitik II Jungle Green Brown Almost White","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Nitik-II-fabric-group_d2b97ecf-7fc8-4fd6-996a-e1a8d8039fab.jpg","hue":67,"color_bucket":"yellow","familySkus":[]},"DWCW-754546":{"patternBase":"Nitik II New Yellow Salmon","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Nitik-II-fabric-group_0d9064b5-3d37-4d6b-b52e-c99a2431a0a9.jpg","hue":32,"color_bucket":"orange","familySkus":["null"]},"DWCW-754646":{"patternBase":"Nitik II Orange Magenta Almost White","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Nitik-II-fabric-group_8f018436-2a0d-4a01-948b-a11b08caf653.jpg","hue":17,"color_bucket":"orange","familySkus":[]},"DWCW-754746":{"patternBase":"Nitik II Orange","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Nitik-II-fabric-group_42630493-b1c8-4c85-92d6-72215e1d5d48.jpg","hue":17,"color_bucket":"orange","familySkus":[]},"DWCW-754846":{"patternBase":"Nitik II Polar Gray Cream","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Nitik-II-fabric-group_4a08599b-788c-4efb-989a-2ae5b2a65fd0.jpg","hue":54,"color_bucket":"yellow","familySkus":[]},"DWCW-754946":{"patternBase":"Nitik Ii Sky Blue","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Nitik-II-fabric-group_e0e1e023-cda0-47a3-bdee-48db57832856.jpg","hue":202,"color_bucket":"blue","familySkus":[]},"DWCW-755046":{"patternBase":"Nitik Ii Taupe Brown","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Nitik-II-fabric-group_abb8be3f-43d3-402f-8faa-1bce3f71dec8.jpg","hue":36,"color_bucket":"orange","familySkus":[]},"DWCW-755146":{"patternBase":"Obi Ii Black","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Obi-II-fabric-group_ed9c2789-2227-4b36-822f-acab7d80f3e1.jpg","hue":47,"color_bucket":"yellow","familySkus":[]},"DWCW-755246":{"patternBase":"Obi II Lavender","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Obi-II-fabric-group_19cb53bd-17dd-48e5-b62c-41f0e11b8fcf.jpg","hue":289,"color_bucket":"purple","familySkus":[]},"DWCW-755346":{"patternBase":"Obi Ii Navy","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Obi-II-fabric-group_2cb976bd-7b25-430c-ab4e-233175d5a0c6.jpg","hue":217,"color_bucket":"blue","familySkus":[]},"DWCW-755446":{"patternBase":"Obi II Reverse Aqua","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Obi-II-Reverse-fabric-group_5bd9ed18-3a22-4abe-93f0-91b49b0ccf2e.jpg","hue":189,"color_bucket":"teal","familySkus":[]},"DWCW-755546":{"patternBase":"Obi II Reverse Black","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Obi-II-Reverse-fabric-group_2bd52a5d-eb17-4736-9a59-99be8673ccf8.jpg","hue":346,"color_bucket":"red","familySkus":[]},"DWCW-755646":{"patternBase":"Obi II Reverse Camel","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Obi-II-Reverse-fabric-group_7fcdb10f-7369-430b-88b9-4402d270d25f.jpg","hue":29,"color_bucket":"orange","familySkus":[]},"DWCW-755746":{"patternBase":"Obi II Reverse Jungle Green","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Obi-II-Reverse-fabric-group_b4dfb590-e6db-4603-88b7-595d90d404d3.jpg","hue":75,"color_bucket":"green","familySkus":[]},"DWCW-755846":{"patternBase":"Obi II Reverse Magenta","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Obi-II-Reverse-fabric-group_190bc64d-3561-4f11-a9b3-429b23b45f87.jpg","hue":349,"color_bucket":"red","familySkus":[]},"DWCW-755946":{"patternBase":"Obi II Reverse Navy","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Obi-II-Reverse-fabric-group_2e83eb99-7f7c-4027-8a95-08265ebf401e.jpg","hue":216,"color_bucket":"blue","familySkus":[]},"DWCW-756046":{"patternBase":"Obi II Reverse New Shrimp","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Obi-II-Reverse-fabric-group_403142ff-5fdd-4ad2-b8c9-9842917e80ad.jpg","hue":8,"color_bucket":"red","familySkus":[]},"DWCW-756146":{"patternBase":"Obi II Reverse Orange","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Obi-II-Reverse-fabric-group_ad28d32d-62b9-4203-b221-777af43e14c8.jpg","hue":14,"color_bucket":"red","familySkus":[]},"DWCW-756246":{"patternBase":"Obi II Reverse Turquoise","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Obi-II-Reverse-fabric-group_5150d317-e047-4474-a741-b7c205a8f38f.jpg","hue":172,"color_bucket":"teal","familySkus":[]},"DWCW-756346":{"patternBase":"Obi II Turquoise","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Obi-II-fabric-group_10f405d4-2eb9-48db-b534-e127e4fa1f42.jpg","hue":171,"color_bucket":"teal","familySkus":[]},"DWCW-756446":{"patternBase":"Pacific Jute","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Pacific-Jute-Hay-7010-02GC.jpg","hue":33,"color_bucket":"orange","familySkus":["DWCW-756546","DWCW-756646"]},"DWCW-756546":{"patternBase":"Pacific Jute","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Pacific-Jute-Natural-7010-01GC.jpg","hue":43,"color_bucket":"orange","familySkus":["DWCW-756446","DWCW-756646"]},"DWCW-756646":{"patternBase":"Pacific Jute","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Pacific-Jute-Straw-7010-03GC.jpg","hue":27,"color_bucket":"orange","familySkus":["DWCW-756446","DWCW-756546"]},"DWCW-756746":{"patternBase":"Pacific Paperweave","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Pacific-Paperweave-Natural-7000-02GC.jpg","hue":38,"color_bucket":"orange","familySkus":["DWCW-756846","DWCW-756946"]},"DWCW-756846":{"patternBase":"Pacific Paperweave","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Pacific-Paperweave-Straw-7000-03GC.jpg","hue":35,"color_bucket":"orange","familySkus":["DWCW-756746","DWCW-756946"]},"DWCW-756946":{"patternBase":"Pacific Paperweave","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Pacific-Paperweave-White-7000-01GC.jpg","hue":57,"color_bucket":"yellow","familySkus":["DWCW-756746","DWCW-756846"]},"DWCW-757046":{"patternBase":"Pacific Sisal","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Pacific-Sisal-Cream-7020-04GC.jpg","hue":18,"color_bucket":"orange","familySkus":["DWCW-757146","DWCW-757246","DWCW-757346","DWCW-757446"]},"DWCW-757146":{"patternBase":"Pacific Sisal","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Pacific-Sisal-Cream-7020-04GC_3c16b743-2cce-4971-96d5-2c204a328d44.jpg","hue":42,"color_bucket":"orange","familySkus":["DWCW-757046","DWCW-757246","DWCW-757346","DWCW-757446"]},"DWCW-757246":{"patternBase":"Pacific Sisal","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Pacific-Sisal-Cream-7020-04GC_1ec1683a-e308-41ea-ada3-478e8cd1ba75.jpg","hue":218,"color_bucket":"blue","familySkus":["DWCW-757046","DWCW-757146","DWCW-757346","DWCW-757446"]},"DWCW-757346":{"patternBase":"Pacific Sisal","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/CHINA_SEAS_WALLPAPER_DWC_757300_Pacific-Sisal-Silver-7020-06GC-2400_jpg_b4862627-7db8-4809-a9b9-9402275a6a80.jpg","hue":37,"color_bucket":"orange","familySkus":["DWCW-757046","DWCW-757146","DWCW-757246","DWCW-757446"]},"DWCW-757446":{"patternBase":"Pacific Sisal","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Pacific-Sisal-Straw-7020-02GC.jpg","hue":37,"color_bucket":"orange","familySkus":["DWCW-757046","DWCW-757146","DWCW-757246","DWCW-757346"]},"DWCW-757546":{"patternBase":"Paradise Background Black","room":null,"hue":279,"color_bucket":"purple","familySkus":["DWCW-757646"]},"DWCW-757646":{"patternBase":"Paradise Background Black","room":null,"hue":262,"color_bucket":"purple","familySkus":["DWCW-757546"]},"DWCW-757746":{"patternBase":"Paradise Background","room":null,"hue":57,"color_bucket":"yellow","familySkus":[]},"DWCW-757846":{"patternBase":"Paradise Background Lilac","room":null,"hue":344,"color_bucket":"red","familySkus":[]},"DWCW-757946":{"patternBase":"Paradise Background Orange","room":null,"hue":12,"color_bucket":"red","familySkus":[]},"DWCW-758046":{"patternBase":"Paradise Background Turquoise","room":null,"hue":179,"color_bucket":"teal","familySkus":[]},"DWCW-758146":{"patternBase":"Parquetry Gray Charcoal","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Parquetry-fabric-group_9699a4e4-8a6b-46ab-8d75-c31bc1d8e002.jpg","hue":155,"color_bucket":"green","familySkus":[]},"DWCW-758246":{"patternBase":"Parquetry Gray Silver","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Parquetry-fabric-group_1c6cbf49-a5a0-46ce-8226-41f7e3c6f958.jpg","hue":61,"color_bucket":"yellow","familySkus":[]},"DWCW-758346":{"patternBase":"Parquetry Taupe Brown","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Parquetry-fabric-group_236b0ee2-2402-4fff-8f8c-da9b419b8aec.jpg","hue":33,"color_bucket":"orange","familySkus":[]},"DWCW-758446":{"patternBase":"Parquetry Teal Charcoal","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Parquetry-fabric-group_3620280e-6d93-438a-85ab-f12f2a236cf5.jpg","hue":159,"color_bucket":"green","familySkus":[]},"DWCW-758546":{"patternBase":"Passy Black","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Passy-Black-on-Off-White-8210WP-10OWP.jpg","hue":52,"color_bucket":"yellow","familySkus":[]},"DWCW-758646":{"patternBase":"Passy White","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Passy-White-on-Off-White-8210WP-01OWP.jpg","hue":48,"color_bucket":"yellow","familySkus":[]},"DWCW-758746":{"patternBase":"Peacock Background","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Peacock-Background-Batik-Group_7f4c391c-7434-4804-96bd-eea49a60834a.jpg","hue":53,"color_bucket":"yellow","familySkus":[]},"DWCW-758946":{"patternBase":"Persepolis Brown Navy","room":null,"hue":39,"color_bucket":"orange","familySkus":["DWCW-759046"]},"DWCW-759046":{"patternBase":"Persepolis Brown Navy","room":null,"hue":331,"color_bucket":"red","familySkus":["DWCW-758946"]},"DWCW-759146":{"patternBase":"Persepolis Celeste Blue","room":null,"hue":187,"color_bucket":"teal","familySkus":["DWCW-759246"]},"DWCW-759246":{"patternBase":"Persepolis Celeste Blue","room":null,"hue":194,"color_bucket":"teal","familySkus":["DWCW-759146"]},"DWCW-759346":{"patternBase":"Persepolis Fig Cream","room":null,"hue":56,"color_bucket":"yellow","familySkus":[]},"DWCW-759446":{"patternBase":"Persepolis Fig Taupe","room":null,"hue":56,"color_bucket":"yellow","familySkus":[]},"DWCW-759546":{"patternBase":"Persepolis Melon Camel","room":null,"hue":26,"color_bucket":"orange","familySkus":[]},"DWCW-759646":{"patternBase":"Persepolis Navy French Blue","room":null,"hue":212,"color_bucket":"blue","familySkus":["DWCW-759746"]},"DWCW-759746":{"patternBase":"Persepolis Navy French Blue","room":null,"hue":217,"color_bucket":"blue","familySkus":["DWCW-759646"]},"DWCW-759846":{"patternBase":"Persia Black_on_almost_white","room":null,"hue":66,"color_bucket":"yellow","familySkus":[]},"DWCW-759946":{"patternBase":"Persia Camel_0n_almost_white","room":null,"hue":46,"color_bucket":"yellow","familySkus":[]},"DWCW-760046":{"patternBase":"Persia Flamingo_on_Almost_White","room":null,"hue":340,"color_bucket":"red","familySkus":[]},"DWCW-760146":{"patternBase":"Persia Lilac_on_almost_white","room":null,"hue":288,"color_bucket":"purple","familySkus":[]},"DWCW-760246":{"patternBase":"Persia Navy_on_almost_white","room":null,"hue":150,"color_bucket":"green","familySkus":[]},"DWCW-760346":{"patternBase":"Persia New_Blue_on_Almost_White","room":null,"hue":210,"color_bucket":"blue","familySkus":[]},"DWCW-760446":{"patternBase":"Persia Orange_on_almost_white","room":null,"hue":18,"color_bucket":"orange","familySkus":[]},"DWCW-760546":{"patternBase":"Persia Turquoise_on_Almost_White","room":null,"hue":158,"color_bucket":"green","familySkus":[]},"DWCW-760646":{"patternBase":"Persia White_on_tan","room":null,"hue":50,"color_bucket":"yellow","familySkus":[]},"DWCW-760746":{"patternBase":"Petite Zig Zag Blue","room":null,"hue":203,"color_bucket":"blue","familySkus":[]},"DWCW-760846":{"patternBase":"Petite Zig Zag Brown","room":null,"hue":38,"color_bucket":"orange","familySkus":["DWCW-760946"]},"DWCW-760946":{"patternBase":"Petite Zig Zag Brown","room":null,"hue":21,"color_bucket":"orange","familySkus":["DWCW-760846"]},"DWCW-761046":{"patternBase":"Petite Zig Zag Camel II","room":null,"hue":38,"color_bucket":"orange","familySkus":[]},"DWCW-761146":{"patternBase":"Petite Zig Zag French Blue","room":null,"hue":207,"color_bucket":"blue","familySkus":["DWCW-761246"]},"DWCW-761246":{"patternBase":"Petite Zig Zag French Blue","room":null,"hue":216,"color_bucket":"blue","familySkus":["DWCW-761146"]},"DWCW-761346":{"patternBase":"Petite Zig Zag Green","room":null,"hue":92,"color_bucket":"green","familySkus":[]},"DWCW-761446":{"patternBase":"Petite Zig Zag Inca Gold","room":null,"hue":41,"color_bucket":"orange","familySkus":["DWCW-761546"]},"DWCW-761546":{"patternBase":"Petite Zig Zag Inca Gold","room":null,"hue":40,"color_bucket":"orange","familySkus":["DWCW-761446"]},"DWCW-761646":{"patternBase":"Petite Zig Zag Jungle Green","room":null,"hue":66,"color_bucket":"yellow","familySkus":["DWCW-761746"]},"DWCW-761746":{"patternBase":"Petite Zig Zag Jungle Green","room":null,"hue":70,"color_bucket":"yellow","familySkus":["DWCW-761646"]},"DWCW-761846":{"patternBase":"Petite Zig Zag Light Blue","room":null,"hue":178,"color_bucket":"teal","familySkus":[]},"DWCW-761946":{"patternBase":"Petite Zig Zag Navy","room":null,"hue":216,"color_bucket":"blue","familySkus":["DWCW-762046","DWCW-762146"]},"DWCW-762046":{"patternBase":"Petite Zig Zag Navy","room":null,"hue":211,"color_bucket":"blue","familySkus":["DWCW-761946","DWCW-762146"]},"DWCW-762146":{"patternBase":"Petite Zig Zag Navy","room":null,"hue":228,"color_bucket":"blue","familySkus":["DWCW-761946","DWCW-762046"]},"DWCW-762246":{"patternBase":"Petite Zig Zag New Navy","room":null,"hue":216,"color_bucket":"blue","familySkus":["DWCW-762346"]},"DWCW-762346":{"patternBase":"Petite Zig Zag New Navy","room":null,"hue":213,"color_bucket":"blue","familySkus":["DWCW-762246"]},"DWCW-762446":{"patternBase":"Petite Zig Zag New Shrimp","room":null,"hue":4,"color_bucket":"red","familySkus":["DWCW-762546"]},"DWCW-762546":{"patternBase":"Petite Zig Zag New Shrimp","room":null,"hue":15,"color_bucket":"red","familySkus":["DWCW-762446"]},"DWCW-762646":{"patternBase":"Petite Zig Zag Pacific Blue","room":null,"hue":212,"color_bucket":"blue","familySkus":[]},"DWCW-762746":{"patternBase":"Petite Zig Zag Peach","room":null,"hue":20,"color_bucket":"orange","familySkus":[]},"DWCW-762846":{"patternBase":"Petite Zig Zag Pink","room":null,"hue":350,"color_bucket":"red","familySkus":[]},"DWCW-762946":{"patternBase":"Petite Zig Zag Red","room":null,"hue":359,"color_bucket":"red","familySkus":[]},"DWCW-763046":{"patternBase":"Petite Zig Zag Salmon","room":null,"hue":25,"color_bucket":"orange","familySkus":[]},"DWCW-763146":{"patternBase":"Petite Zig Zag Sand","room":null,"hue":35,"color_bucket":"orange","familySkus":[]},"DWCW-763246":{"patternBase":"Petite Zig Zag Slate Blue","room":null,"hue":208,"color_bucket":"blue","familySkus":["DWCW-763346"]},"DWCW-763346":{"patternBase":"Petite Zig Zag Slate Blue","room":null,"hue":210,"color_bucket":"blue","familySkus":["DWCW-763246"]},"DWCW-763446":{"patternBase":"Petite Zig Zag Taupe","room":null,"hue":41,"color_bucket":"orange","familySkus":["DWCW-763546"]},"DWCW-763546":{"patternBase":"Petite Zig Zag Taupe","room":null,"hue":42,"color_bucket":"orange","familySkus":["DWCW-763446"]},"DWCW-763646":{"patternBase":"Petite Zig Zag Turquoise","room":null,"hue":168,"color_bucket":"green","familySkus":[]},"DWCW-763746":{"patternBase":"Petite Zig Zag Watermelon","room":null,"hue":351,"color_bucket":"red","familySkus":["DWCW-763846","DWCW-763946"]},"DWCW-763846":{"patternBase":"Petite Zig Zag Watermelon","room":null,"hue":343,"color_bucket":"red","familySkus":["DWCW-763746","DWCW-763946"]},"DWCW-763946":{"patternBase":"Petite Zig Zag Watermelon","room":null,"hue":352,"color_bucket":"red","familySkus":["DWCW-763746","DWCW-763846"]},"DWCW-764046":{"patternBase":"Petite Zig Zag White","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Petite-Zig-Zag-Watermelon-on-White-Vinyl-AP303-07PV.jpg","hue":169,"color_bucket":"green","familySkus":[]},"DWCW-764146":{"patternBase":"Pina Dark Turquoise","room":null,"hue":169,"color_bucket":"green","familySkus":[]},"DWCW-764246":{"patternBase":"Pina Jungle Green Off White","room":null,"hue":80,"color_bucket":"green","familySkus":[]},"DWCW-764346":{"patternBase":"Pina Tomato","room":null,"hue":11,"color_bucket":"red","familySkus":[]},"DWCW-764446":{"patternBase":"Potalla Background Camel II Off White","room":null,"hue":22,"color_bucket":"orange","familySkus":[]},"DWCW-764546":{"patternBase":"Potalla Background French Blue","room":null,"hue":217,"color_bucket":"blue","familySkus":[]},"DWCW-764746":{"patternBase":"Potalla Multi Blues","room":null,"hue":177,"color_bucket":"teal","familySkus":[]},"DWCW-764846":{"patternBase":"Potalla Multi Greens Rust","room":null,"hue":35,"color_bucket":"orange","familySkus":[]},"DWCW-764946":{"patternBase":"Potalla Outline Black","room":null,"hue":48,"color_bucket":"yellow","familySkus":["DWCW-765046"]},"DWCW-765046":{"patternBase":"Potalla Outline Black","room":null,"hue":241,"color_bucket":"blue","familySkus":["DWCW-764946"]},"DWCW-765146":{"patternBase":"Potalla Outline French Blue","room":null,"hue":52,"color_bucket":"yellow","familySkus":[]},"DWCW-765246":{"patternBase":"Potalla Outline French Blue White","room":null,"hue":206,"color_bucket":"blue","familySkus":[]},"DWCW-765346":{"patternBase":"Potalla Outline Jungle Green Off White","room":null,"hue":54,"color_bucket":"yellow","familySkus":[]},"DWCW-765446":{"patternBase":"Potalla Outline Jungle Green","room":null,"hue":67,"color_bucket":"yellow","familySkus":[]},"DWCW-765546":{"patternBase":"Potalla Outline New Shrimp Off White","room":null,"hue":40,"color_bucket":"orange","familySkus":[]},"DWCW-765646":{"patternBase":"Potalla Outline New Shrimp White","room":null,"hue":3,"color_bucket":"red","familySkus":[]},"DWCW-765746":{"patternBase":"Potalla Salmons Yellow","room":null,"hue":14,"color_bucket":"red","familySkus":[]},"DWCW-765846":{"patternBase":"Puccini Classic Blue","room":null,"hue":116,"color_bucket":"green","familySkus":[]},"DWCW-765946":{"patternBase":"Puccini French Blue","room":null,"hue":205,"color_bucket":"blue","familySkus":[]},"DWCW-766046":{"patternBase":"Puccini Gray","room":null,"hue":50,"color_bucket":"yellow","familySkus":[]},"DWCW-766146":{"patternBase":"Puccini Inca","room":null,"hue":41,"color_bucket":"orange","familySkus":[]},"DWCW-766246":{"patternBase":"Puccini Lavender","room":null,"hue":352,"color_bucket":"red","familySkus":[]},"DWCW-766346":{"patternBase":"Puccini Light Pink","room":null,"hue":23,"color_bucket":"orange","familySkus":[]},"DWCW-766446":{"patternBase":"Puccini New Apple","room":null,"hue":69,"color_bucket":"yellow","familySkus":[]},"DWCW-766546":{"patternBase":"Puccini New Navy","room":null,"hue":216,"color_bucket":"blue","familySkus":[]},"DWCW-766646":{"patternBase":"Puccini Pink","room":null,"hue":341,"color_bucket":"red","familySkus":[]},"DWCW-766746":{"patternBase":"Puccini Slate","room":null,"hue":133,"color_bucket":"green","familySkus":[]},"DWCW-766846":{"patternBase":"Puccini Tomato","room":null,"hue":11,"color_bucket":"red","familySkus":[]},"DWCW-766946":{"patternBase":"Puccini Venice Blue","room":null,"hue":150,"color_bucket":"green","familySkus":[]},"DWCW-767046":{"patternBase":"Puccini White","room":null,"hue":46,"color_bucket":"yellow","familySkus":[]},"DWCW-767146":{"patternBase":"Raffles New","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Raffles-fabric-group_3e4f3e8b-83f0-42c8-8e5d-aeebad8e6640.jpg","hue":181,"color_bucket":"teal","familySkus":[]},"DWCW-767246":{"patternBase":"Rio French Blue","room":null,"hue":209,"color_bucket":"blue","familySkus":[]},"DWCW-767346":{"patternBase":"Rio Royal Blue","room":null,"hue":207,"color_bucket":"blue","familySkus":[]},"DWCW-767446":{"patternBase":"Safari Beige","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Safari-Jungle-Green-on-Almost-White-AP855-06.jpg","hue":40,"color_bucket":"orange","familySkus":[]},"DWCW-767546":{"patternBase":"Safari Black","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Safari-Jungle-Green-on-Almost-White-AP855-06_5e69f6ec-ffc7-4f90-90c9-8c23f04ae9e5.jpg","hue":118,"color_bucket":"green","familySkus":[]},"DWCW-767646":{"patternBase":"Safari Embroidery Black","room":null,"hue":57,"color_bucket":"yellow","familySkus":[]},"DWCW-767746":{"patternBase":"Safari Embroidery Caramel","room":null,"hue":37,"color_bucket":"orange","familySkus":[]},"DWCW-767846":{"patternBase":"Safari Embroidery French Blue","room":null,"hue":216,"color_bucket":"blue","familySkus":[]},"DWCW-767946":{"patternBase":"Safari Embroidery Inca Gold","room":null,"hue":46,"color_bucket":"yellow","familySkus":[]},"DWCW-768046":{"patternBase":"Safari Embroidery Jungle Green","room":null,"hue":74,"color_bucket":"green","familySkus":[]},"DWCW-768246":{"patternBase":"Safari Embroidery Lime","room":null,"hue":81,"color_bucket":"green","familySkus":[]},"DWCW-768346":{"patternBase":"Safari Embroidery Medium Grey","room":null,"hue":58,"color_bucket":"yellow","familySkus":[]},"DWCW-768446":{"patternBase":"Safari Embroidery Medium Turquoise","room":null,"hue":189,"color_bucket":"teal","familySkus":[]},"DWCW-768546":{"patternBase":"Safari Embroidery Melon","room":null,"hue":0,"color_bucket":"red","familySkus":[]},"DWCW-768646":{"patternBase":"Safari Embroidery New Navy","room":null,"hue":220,"color_bucket":"blue","familySkus":[]},"DWCW-768746":{"patternBase":"Safari Embroidery Pale Grey","room":null,"hue":44,"color_bucket":"orange","familySkus":[]},"DWCW-768846":{"patternBase":"Safari Embroidery Royal Blue","room":null,"hue":216,"color_bucket":"blue","familySkus":[]},"DWCW-768946":{"patternBase":"Safari Embroidery Soft Lavender","room":null,"hue":21,"color_bucket":"orange","familySkus":[]},"DWCW-769046":{"patternBase":"Safari Embroidery Soft Windsor","room":null,"hue":136,"color_bucket":"green","familySkus":[]},"DWCW-769146":{"patternBase":"Safari Embroidery White","room":null,"hue":216,"color_bucket":"blue","familySkus":[]},"DWCW-769246":{"patternBase":"Safari French Blue","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Safari-French-Blue-on-Almost-White-Paper-AP855-05.jpg","hue":215,"color_bucket":"blue","familySkus":[]},"DWCW-769346":{"patternBase":"Safari Jungle Green","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Safari-Jungle-Green-on-Almost-White-AP855-06_2ab7de15-0e07-42ac-9848-1d8aa770eab4.jpg","hue":76,"color_bucket":"green","familySkus":[]},"DWCW-769446":{"patternBase":"Safari Light Gray","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Safari-Light-Gray-on-Almost-White-AP855-16.jpg","hue":65,"color_bucket":"yellow","familySkus":[]},"DWCW-769546":{"patternBase":"Safari Lime","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Safari-Jungle-Green-on-Almost-White-AP855-06_850b5720-7016-40e4-9af0-71e44d02defd.jpg","hue":84,"color_bucket":"green","familySkus":[]},"DWCW-769646":{"patternBase":"Safari New Navy","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Safari-Jungle-Green-on-Almost-White-AP855-06_75ff0134-5560-4c74-a443-4b916b25f10b.jpg","hue":221,"color_bucket":"blue","familySkus":[]},"DWCW-769746":{"patternBase":"Safari Pale Grey","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Safari-Jungle-Green-on-Almost-White-AP855-06_95043332-eeee-4e77-acff-e70013e6a879.jpg","hue":45,"color_bucket":"orange","familySkus":[]},"DWCW-769846":{"patternBase":"Safari Pumice","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Safari-Jungle-Green-on-Almost-White-AP855-06_84edf9e7-f428-42d0-8c36-6bef28cd5770.jpg","hue":45,"color_bucket":"orange","familySkus":[]},"DWCW-769946":{"patternBase":"Safari Soft Lavender","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Safari-Soft-Lavender-on-Almost-White-Paper-AP855-04.jpg","hue":304,"color_bucket":"magenta","familySkus":[]},"DWCW-770046":{"patternBase":"Safari Soft Windsor","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Safari-Soft-Lavender-on-Almost-White-Paper-AP855-04_f201b38b-df12-48f3-a6d3-da36a824c89e.jpg","hue":185,"color_bucket":"teal","familySkus":[]},"DWCW-770246":{"patternBase":"San Marco Arctic Blue","room":null,"hue":153,"color_bucket":"green","familySkus":[]},"DWCW-770346":{"patternBase":"San Marco Celadon","room":null,"hue":58,"color_bucket":"yellow","familySkus":[]},"DWCW-770446":{"patternBase":"San Marco Chartreuse","room":null,"hue":57,"color_bucket":"yellow","familySkus":[]},"DWCW-770546":{"patternBase":"San Marco Coral","room":null,"hue":15,"color_bucket":"red","familySkus":[]},"DWCW-770646":{"patternBase":"San Marco Cream","room":null,"hue":29,"color_bucket":"orange","familySkus":[]},"DWCW-770746":{"patternBase":"San Marco Gray","room":null,"hue":62,"color_bucket":"yellow","familySkus":["DWCW-770846","DWCW-770946"]},"DWCW-770846":{"patternBase":"San Marco Gray","room":null,"hue":59,"color_bucket":"yellow","familySkus":["DWCW-770746","DWCW-770946"]},"DWCW-770946":{"patternBase":"San Marco Gray","room":null,"hue":56,"color_bucket":"yellow","familySkus":["DWCW-770746","DWCW-770846"]},"DWCW-771046":{"patternBase":"San Marco Langdon Yellow","room":null,"hue":59,"color_bucket":"yellow","familySkus":[]},"DWCW-771146":{"patternBase":"San Marco Reverse Black","room":null,"hue":299,"color_bucket":"magenta","familySkus":[]},"DWCW-771246":{"patternBase":"San Marco Reverse Brown","room":null,"hue":6,"color_bucket":"red","familySkus":[]},"DWCW-771346":{"patternBase":"San Marco Reverse Coral","room":null,"hue":353,"color_bucket":"red","familySkus":[]},"DWCW-771446":{"patternBase":"San Marco Reverse Langdon Yellow","room":null,"hue":55,"color_bucket":"yellow","familySkus":[]},"DWCW-771546":{"patternBase":"San Marco Reverse Prune","room":null,"hue":3,"color_bucket":"red","familySkus":[]},"DWCW-771646":{"patternBase":"San Marco Reverse Turquoise","room":null,"hue":168,"color_bucket":"green","familySkus":[]},"DWCW-771746":{"patternBase":"San Marco Reverse White","room":null,"hue":206,"color_bucket":"blue","familySkus":[]},"DWCW-771846":{"patternBase":"San Marco Zibby Blue","room":null,"hue":206,"color_bucket":"blue","familySkus":[]},"DWCW-771946":{"patternBase":"Sauvage_Reverse Black_on_Almost_White_CP","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sauvage_Reverse_Black_on_Almost_White_CP1025W-09.jpg","hue":115,"color_bucket":"green","familySkus":[]},"DWCW-772046":{"patternBase":"Sauvage_Reverse Brown_on_Almost_White_CP","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sauvage_Reverse_Brown_on_Almost_White_CP1025W-08.jpg","hue":32,"color_bucket":"orange","familySkus":[]},"DWCW-772146":{"patternBase":"Sauvage_Reverse Butter_Yellow_on_Almost_White_CP","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sauvage_Reverse_Butter_Yellow_on_Almost_White_CP1025W-02.jpg","hue":43,"color_bucket":"orange","familySkus":[]},"DWCW-772246":{"patternBase":"Sauvage_Reverse Camel_II_on_Almost_White_CP","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sauvage_Reverse_Camel_II_on_Almost_White_CP1025W-06.jpg","hue":38,"color_bucket":"orange","familySkus":[]},"DWCW-772346":{"patternBase":"Sauvage_Reverse Dark_Lilac_on_Almost_White_CP","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sauvage_Reverse_Dark_Lilac_on_Almost_White_CP1025W-06.jpg","hue":276,"color_bucket":"purple","familySkus":[]},"DWCW-772446":{"patternBase":"Sauvage_Reverse Dark_Turquoise_on_Almost_White_CP","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sauvage_Reverse_Dark_Turquoise_on_Almost_White_CP1025W-03.jpg","hue":181,"color_bucket":"teal","familySkus":[]},"DWCW-772546":{"patternBase":"Sauvage_Reverse Jungle_Green_on_Almost_White_CP","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sauvage_Reverse_Jungle_Green_on_Almost_White_CP1025W-04.jpg","hue":81,"color_bucket":"green","familySkus":[]},"DWCW-772646":{"patternBase":"Sauvage_Reverse Red on_Almost_White_CP","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sauvage_Reverse_Red-on_Almost_White_CP1025W-07.jpg","hue":1,"color_bucket":"red","familySkus":[]},"DWCW-772746":{"patternBase":"Saya Gata French Blue","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Saya-Gata-Lines-Navy-Almost-White-AP207-16.jpg","hue":184,"color_bucket":"teal","familySkus":[]},"DWCW-772846":{"patternBase":"Saya Gata Lines Brown","room":null,"hue":40,"color_bucket":"orange","familySkus":[]},"DWCW-772946":{"patternBase":"Saya Gata Lines Kelly Green","room":null,"hue":98,"color_bucket":"green","familySkus":[]},"DWCW-773046":{"patternBase":"Saya Gata Lines Magenta","room":null,"hue":345,"color_bucket":"red","familySkus":[]},"DWCW-773146":{"patternBase":"Saya Gata Lines Navy Almost White","room":null,"hue":94,"color_bucket":"green","familySkus":[]},"DWCW-773246":{"patternBase":"Saya Gata Lines New Shrimp Off White","room":null,"hue":10,"color_bucket":"red","familySkus":[]},"DWCW-773346":{"patternBase":"Saya Gata Lines New Shrimp","room":null,"hue":16,"color_bucket":"orange","familySkus":[]},"DWCW-773446":{"patternBase":"Saya Gata Lines Orange","room":null,"hue":21,"color_bucket":"orange","familySkus":[]},"DWCW-773546":{"patternBase":"Saya Gata Lines Slate Blue Almost White","room":null,"hue":213,"color_bucket":"blue","familySkus":[]},"DWCW-773646":{"patternBase":"Saya Gata Lines Taxi Cab Yellow","room":null,"hue":35,"color_bucket":"orange","familySkus":[]},"DWCW-773746":{"patternBase":"Saya Gata Lines Turquoise","room":null,"hue":157,"color_bucket":"green","familySkus":["DWCW-773846"]},"DWCW-773846":{"patternBase":"Saya Gata Lines Turquoise","room":null,"hue":173,"color_bucket":"teal","familySkus":["DWCW-773746"]},"DWCW-773946":{"patternBase":"Saya Gata Lines Watermelon Almost White","room":null,"hue":348,"color_bucket":"red","familySkus":[]},"DWCW-774046":{"patternBase":"Seya Black Gray","room":null,"hue":265,"color_bucket":"purple","familySkus":[]},"DWCW-774146":{"patternBase":"Shanghai Dark Grays","room":null,"hue":65,"color_bucket":"yellow","familySkus":[]},"DWCW-774246":{"patternBase":"Shanghai Lilac","room":null,"hue":292,"color_bucket":"magenta","familySkus":[]},"DWCW-774346":{"patternBase":"Shanghai Navy Windsor","room":null,"hue":215,"color_bucket":"blue","familySkus":[]},"DWCW-774446":{"patternBase":"Shanghai Pinks","room":null,"hue":359,"color_bucket":"red","familySkus":[]},"DWCW-774546":{"patternBase":"Sigourney Black","room":null,"hue":270,"color_bucket":"purple","familySkus":[]},"DWCW-774646":{"patternBase":"Sigourney Mustard","room":null,"hue":43,"color_bucket":"orange","familySkus":[]},"DWCW-774746":{"patternBase":"Sigourney Navy","room":null,"hue":219,"color_bucket":"blue","familySkus":[]},"DWCW-774846":{"patternBase":"Sigourney Razzle Dazzle","room":null,"hue":338,"color_bucket":"red","familySkus":[]},"DWCW-774946":{"patternBase":"Sigourney Reverse Large Scale Gray","room":null,"hue":76,"color_bucket":"green","familySkus":[]},"DWCW-775046":{"patternBase":"Sigourney Reverse Large Scale Lavender","room":null,"hue":286,"color_bucket":"purple","familySkus":[]},"DWCW-775146":{"patternBase":"Sigourney Reverse Small Scale Black","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sigourney-Reverse-Small-Scale-Group_694ca8ac-d87a-4f3a-a57d-51a75e69d820.jpg","hue":99,"color_bucket":"green","familySkus":[]},"DWCW-775246":{"patternBase":"Sigourney Reverse Small Scale French Blue","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sigourney-Reverse-Small-Scale-Group_10efdc5c-3474-4646-a163-62709b4bc069.jpg","hue":216,"color_bucket":"blue","familySkus":[]},"DWCW-775346":{"patternBase":"Sigourney Reverse Small Scale Gray","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sigourney-Reverse-Small-Scale-Group_3864ebc6-917a-4ecf-affc-3ad126335690.jpg","hue":71,"color_bucket":"green","familySkus":[]},"DWCW-775446":{"patternBase":"Sigourney Reverse Small Scale Jungle Green","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sigourney-Reverse-Small-Scale-Group_5b49730d-9999-4e79-b375-e3f18297aa05.jpg","hue":78,"color_bucket":"green","familySkus":[]},"DWCW-775546":{"patternBase":"Sigourney Reverse Small Scale Lavender","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sigourney-Reverse-Small-Scale-Group_1186733e-0fc4-4258-b198-097dd9585672.jpg","hue":294,"color_bucket":"magenta","familySkus":[]},"DWCW-775646":{"patternBase":"Sigourney Reverse Small Scale New Blue","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sigourney-Reverse-Small-Scale-Group_b3559a71-85c5-46bd-9a27-cdaefc43be2a.jpg","hue":200,"color_bucket":"teal","familySkus":[]},"DWCW-775746":{"patternBase":"Sigourney Reverse Small Scale New Navy","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sigourney-Reverse-Small-Scale-Group_0b5cf86c-f392-452d-84a0-b257ae5e4733.jpg","hue":222,"color_bucket":"blue","familySkus":[]},"DWCW-775846":{"patternBase":"Sigourney Reverse Small Scale Orange","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sigourney-Reverse-Small-Scale-Group_0deafb76-44f6-4a58-9726-5ba808a108bd.jpg","hue":17,"color_bucket":"orange","familySkus":[]},"DWCW-775946":{"patternBase":"Sigourney Reverse Small Scale Red","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sigourney-Reverse-Small-Scale-Group_a86319e0-0637-486b-8a98-e815696785dd.jpg","hue":352,"color_bucket":"red","familySkus":[]},"DWCW-776046":{"patternBase":"Sigourney Reverse Small Scale Royal Blue","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sigourney-Reverse-Small-Scale-Group_506a63c0-e3d8-4362-affa-9cbfcffd5c32.jpg","hue":219,"color_bucket":"blue","familySkus":[]},"DWCW-776146":{"patternBase":"Sigourney Reverse Small Scale Turquoise","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sigourney-Reverse-Small-Scale-Group_ab93b2a4-d14d-433b-a2be-e1dbfb033c40.jpg","hue":186,"color_bucket":"teal","familySkus":[]},"DWCW-776246":{"patternBase":"Sigourney Reverse Small Scale Yellow","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sigourney-Reverse-Small-Scale-Group_3ed6f850-37a5-47f3-ab3f-8eca2d9d28fa.jpg","hue":44,"color_bucket":"orange","familySkus":[]},"DWCW-776346":{"patternBase":"Sigourney Small Scale Chartreuse","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sigourney-Small-Scale-Group_9ae83fae-d151-413a-9307-9c01295ab359.jpg","hue":58,"color_bucket":"yellow","familySkus":[]},"DWCW-776446":{"patternBase":"Sigourney Small Scale French Blue","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sigourney-Small-Scale-Group_fa1eb9da-07ce-4441-b08f-4cac9772897f.jpg","hue":217,"color_bucket":"blue","familySkus":[]},"DWCW-776546":{"patternBase":"Sigourney Small Scale Gray","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sigourney-Small-Scale-Group_156c3015-043a-45ba-8f3e-a03ca2998399.jpg","hue":66,"color_bucket":"yellow","familySkus":[]},"DWCW-776646":{"patternBase":"Sigourney Small Scale Jungle Green","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sigourney-Small-Scale-Group_817ce314-d7b8-4b45-b33f-aa28a23ee251.jpg","hue":79,"color_bucket":"green","familySkus":[]},"DWCW-776746":{"patternBase":"Sigourney Small Scale Lavender","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sigourney-Small-Scale-Group_413275f5-e4eb-4de1-ac07-7946f35a13d4.jpg","hue":293,"color_bucket":"magenta","familySkus":[]},"DWCW-776846":{"patternBase":"Sigourney Small Scale New Blue","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sigourney-Small-Scale-Group_6140dcc0-f2cd-4d87-bd77-eaa9dc2ce1d9.jpg","hue":197,"color_bucket":"teal","familySkus":[]},"DWCW-776946":{"patternBase":"Sigourney Small Scale New Navy","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sigourney-Small-Scale-Group_682eca05-eb09-4c12-8139-70d6a086744f.jpg","hue":222,"color_bucket":"blue","familySkus":[]},"DWCW-777046":{"patternBase":"Sigourney Small Scale Orange","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sigourney-Small-Scale-Group_184fad37-9f52-4057-ad2f-087d71446c38.jpg","hue":16,"color_bucket":"orange","familySkus":[]},"DWCW-777146":{"patternBase":"Sigourney Small Scale Red","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sigourney-Small-Scale-Group_4534349a-40df-402e-a4c8-e03a5fbb3faf.jpg","hue":351,"color_bucket":"red","familySkus":[]},"DWCW-777246":{"patternBase":"Sigourney Small Scale Royal Blue","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sigourney-Small-Scale-Group_cb22abd5-6052-4ad2-bda7-2b1b32a27370.jpg","hue":220,"color_bucket":"blue","familySkus":[]},"DWCW-777346":{"patternBase":"Sigourney Small Scale Taupe","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sigourney-Small-Scale-Group_4f91f526-69a2-4fd3-bdf7-a3e4ad58cb64.jpg","hue":37,"color_bucket":"orange","familySkus":[]},"DWCW-777446":{"patternBase":"Sigourney Small Scale Turquoise","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sigourney-Small-Scale-Group_878a1876-00cd-41d9-ad24-5bb6ad0ea59c.jpg","hue":188,"color_bucket":"teal","familySkus":[]},"DWCW-777546":{"patternBase":"Sigourney Small Scale Yellow","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sigourney-Small-Scale-Group_85231284-f421-4d1a-a0e1-9262f7856c1e.jpg","hue":43,"color_bucket":"orange","familySkus":[]},"DWCW-777646":{"patternBase":"Sigourney Terracotta","room":null,"hue":10,"color_bucket":"red","familySkus":[]},"DWCW-777746":{"patternBase":"Sigourney Zibby Blue","room":null,"hue":206,"color_bucket":"blue","familySkus":[]},"DWCW-777846":{"patternBase":"Soho Jungle Green Off White","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Soho-Turquoise-Navy-on-White-AP209-06.jpg","hue":69,"color_bucket":"yellow","familySkus":[]},"DWCW-777946":{"patternBase":"Soho Turquoise Navy","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Soho-Turquoise-Navy-on-White-AP209-06_77e2d05a-ac27-4677-89fb-5e1335be5139.jpg","hue":178,"color_bucket":"teal","familySkus":[]},"DWCW-778046":{"patternBase":"Sunnyjim All Over Classic Blues","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sunnyjim-All-Over-fabric-group_7e52edc4-d507-41cb-ba7d-5e06c112cc32.jpg","hue":136,"color_bucket":"green","familySkus":[]},"DWCW-778146":{"patternBase":"Sunnyjim All Over French Blue","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sunnyjim-All-Over-fabric-group_710ecd15-7ceb-4011-b536-c9f38f73770a.jpg","hue":207,"color_bucket":"blue","familySkus":[]},"DWCW-778246":{"patternBase":"Sunnyjim All Over Pinks","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sunnyjim-All-Over-fabric-group_2f2e82a9-47be-4a8c-a1ed-d79f804e5adb.jpg","hue":12,"color_bucket":"red","familySkus":[]},"DWCW-778346":{"patternBase":"Sunnyjim All Over Turquoise","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sunnyjim-All-Over-fabric-group_0d454488-9ab5-4d75-87db-10c55cc580d2.jpg","hue":162,"color_bucket":"green","familySkus":[]},"DWCW-778446":{"patternBase":"Sunnyjim Batik Multi Charcoal Gray","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sunnyjim-Batik-fabric-group_6d58ff43-7ac1-4684-9488-d8622fe66af0.jpg","hue":54,"color_bucket":"yellow","familySkus":[]},"DWCW-778546":{"patternBase":"Sunnyjim Batik Multi Greens Tan","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sunnyjim-Batik-fabric-group_0a324796-0fe0-4dd3-bc14-340c2488241e.jpg","hue":56,"color_bucket":"yellow","familySkus":[]},"DWCW-778646":{"patternBase":"Sunnyjim Batik Multi Navy French Blue","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Sunnyjim-Batik-fabric-group_ac79f503-14c5-4f5d-b861-6c79bb7874c4.jpg","hue":9,"color_bucket":"red","familySkus":[]},"DWCW-778746":{"patternBase":"Tableau Sky","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Tableau-Sky-on-White-Matte-619-20.jpg","hue":211,"color_bucket":"blue","familySkus":[]},"DWCW-779346":{"patternBase":"Taj Melon","room":null,"hue":10,"color_bucket":"red","familySkus":[]},"DWCW-779446":{"patternBase":"Taj Navy","room":null,"hue":201,"color_bucket":"blue","familySkus":[]},"DWCW-779546":{"patternBase":"Taj New Shrimp","room":null,"hue":13,"color_bucket":"red","familySkus":[]},"DWCW-779646":{"patternBase":"Taj Tobacco","room":null,"hue":31,"color_bucket":"orange","familySkus":[]},"DWCW-779746":{"patternBase":"Tate Dark Grey","room":null,"hue":53,"color_bucket":"yellow","familySkus":[]},"DWCW-779846":{"patternBase":"Tate Jungle Green","room":null,"hue":60,"color_bucket":"yellow","familySkus":[]},"DWCW-779946":{"patternBase":"Tate Lavender","room":null,"hue":29,"color_bucket":"orange","familySkus":[]},"DWCW-780046":{"patternBase":"Tate Orange","room":null,"hue":25,"color_bucket":"orange","familySkus":[]},"DWCW-780146":{"patternBase":"Tate Pink","room":null,"hue":16,"color_bucket":"orange","familySkus":[]},"DWCW-780246":{"patternBase":"Tate Sky Blue","room":null,"hue":89,"color_bucket":"green","familySkus":[]},"DWCW-780346":{"patternBase":"Tate Turquoise","room":null,"hue":86,"color_bucket":"green","familySkus":[]},"DWCW-780446":{"patternBase":"Tate Yellow","room":null,"hue":53,"color_bucket":"yellow","familySkus":["DWCW-780546"]},"DWCW-780546":{"patternBase":"Tate Yellow","room":null,"hue":53,"color_bucket":"yellow","familySkus":["DWCW-780446"]},"DWCW-780646":{"patternBase":"Terrace New","room":null,"hue":63,"color_bucket":"yellow","familySkus":[]},"DWCW-780746":{"patternBase":"Tie Dye White","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Tie-Dye-White-on-Off-White-AP930-00.jpg","hue":49,"color_bucket":"yellow","familySkus":[]},"DWCW-780846":{"patternBase":"Trellis Background Camel II Off White","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Trellis-Background-fabric-group.jpg","hue":30,"color_bucket":"orange","familySkus":[]},"DWCW-780946":{"patternBase":"Trellis Background Navy","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Trellis-Background-fabric-group_6928c49b-90b2-42c0-bb2d-3fbf2c6cf3b2.jpg","hue":219,"color_bucket":"blue","familySkus":[]},"DWCW-781046":{"patternBase":"Trellis Background New Brown","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Trellis-Background-fabric-group_9770db5e-de32-44e6-a84c-a3ac21c28321.jpg","hue":17,"color_bucket":"orange","familySkus":[]},"DWCW-781146":{"patternBase":"Trellis Background Orange Almost White","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Trellis-Background-fabric-group_4cd19f1c-836a-42c6-a3ef-715867c7d6b6.jpg","hue":17,"color_bucket":"orange","familySkus":[]},"DWCW-781246":{"patternBase":"Trellis Background Orange Off White","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Trellis-Background-fabric-group_a0502d5f-8194-46fc-b794-f4bdc67c3178.jpg","hue":21,"color_bucket":"orange","familySkus":[]},"DWCW-781346":{"patternBase":"Trellis Background Turquoise Off White","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Trellis-Background-fabric-group_a2fdd30a-5977-46c4-aa5c-54b17f4666b9.jpg","hue":170,"color_bucket":"green","familySkus":[]},"DWCW-781446":{"patternBase":"Trellis Outline Black","room":null,"hue":59,"color_bucket":"yellow","familySkus":[]},"DWCW-781546":{"patternBase":"Trilby Jungle Green Pink","room":null,"hue":81,"color_bucket":"green","familySkus":[]},"DWCW-781646":{"patternBase":"Tulip Multicolor Lilac Turquoise Green White","room":null,"hue":91,"color_bucket":"green","familySkus":[]},"DWCW-781746":{"patternBase":"Tulip Multicolor Multi Pink Green","room":null,"hue":15,"color_bucket":"red","familySkus":[]},"DWCW-781846":{"patternBase":"Twigs Soft","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Twigs-fabric-group_561fbb58-ee45-47f3-89a1-f06f6132e43b.jpg","hue":69,"color_bucket":"yellow","familySkus":[]},"DWCW-781946":{"patternBase":"Uzbek Multi Blues Greens Royal","room":null,"hue":125,"color_bucket":"green","familySkus":[]},"DWCW-782046":{"patternBase":"Uzbek Multi Green Pink Blues","room":null,"hue":2,"color_bucket":"red","familySkus":[]},"DWCW-782246":{"patternBase":"Uzbek Multi Red Teal Blues","room":null,"hue":174,"color_bucket":"teal","familySkus":[]},"DWCW-782346":{"patternBase":"Vacances French Blue","room":null,"hue":208,"color_bucket":"blue","familySkus":[]},"DWCW-782446":{"patternBase":"Vacances Inca Gold","room":null,"hue":38,"color_bucket":"orange","familySkus":[]},"DWCW-782546":{"patternBase":"Vacances Jungle Green","room":null,"hue":84,"color_bucket":"green","familySkus":[]},"DWCW-782646":{"patternBase":"Vacances Lilac","room":null,"hue":292,"color_bucket":"magenta","familySkus":[]},"DWCW-782746":{"patternBase":"Vacances Melon","room":null,"hue":351,"color_bucket":"red","familySkus":[]},"DWCW-782846":{"patternBase":"Vacances New Navy","room":null,"hue":207,"color_bucket":"blue","familySkus":[]},"DWCW-782946":{"patternBase":"Vacances Turquoise","room":null,"hue":184,"color_bucket":"teal","familySkus":[]},"DWCW-783046":{"patternBase":"Veneto 2 Across Denim Blue","room":null,"hue":200,"color_bucket":"teal","familySkus":[]},"DWCW-783146":{"patternBase":"Veneto 2 Across Maize","room":null,"hue":43,"color_bucket":"orange","familySkus":[]},"DWCW-783246":{"patternBase":"Veneto 2 Across New Apple","room":null,"hue":65,"color_bucket":"yellow","familySkus":[]},"DWCW-783346":{"patternBase":"Veneto 2 Across Terracotta","room":null,"hue":21,"color_bucket":"orange","familySkus":[]},"DWCW-783446":{"patternBase":"Veneto 2 Across Venice Blue","room":null,"hue":173,"color_bucket":"teal","familySkus":[]},"DWCW-783546":{"patternBase":"Veneto 2 Across White","room":null,"hue":61,"color_bucket":"yellow","familySkus":[]},"DWCW-783646":{"patternBase":"Veneto Beige","room":null,"hue":40,"color_bucket":"orange","familySkus":[]},"DWCW-783746":{"patternBase":"Veneto Gold Metallic","room":null,"hue":49,"color_bucket":"yellow","familySkus":[]},"DWCW-783846":{"patternBase":"Veneto Gray","room":null,"hue":55,"color_bucket":"yellow","familySkus":[]},"DWCW-783946":{"patternBase":"Veneto Langdon Yellow","room":null,"hue":56,"color_bucket":"yellow","familySkus":[]},"DWCW-784046":{"patternBase":"Veneto Plum","room":null,"hue":342,"color_bucket":"red","familySkus":[]},"DWCW-784146":{"patternBase":"Veneto Pumice","room":null,"hue":34,"color_bucket":"orange","familySkus":[]},"DWCW-784246":{"patternBase":"Veneto Soft French Green","room":null,"hue":72,"color_bucket":"green","familySkus":[]},"DWCW-784346":{"patternBase":"Veneto Soft Lavender","room":null,"hue":0,"color_bucket":"red","familySkus":[]},"DWCW-784446":{"patternBase":"Veneto Soft Pink","room":null,"hue":7,"color_bucket":"red","familySkus":[]},"DWCW-784646":{"patternBase":"Veneto Windsor Blue","room":null,"hue":149,"color_bucket":"green","familySkus":[]},"DWCW-784746":{"patternBase":"Victoria French","room":null,"hue":63,"color_bucket":"yellow","familySkus":[]},"DWCW-784846":{"patternBase":"Volpi Gray","room":null,"hue":52,"color_bucket":"yellow","familySkus":[]},"DWCW-784946":{"patternBase":"Volpi Langdon Yellow","room":null,"hue":58,"color_bucket":"yellow","familySkus":[]},"DWCW-785046":{"patternBase":"Volpi New Navy","room":null,"hue":226,"color_bucket":"blue","familySkus":[]},"DWCW-785146":{"patternBase":"Wildflowers Ii Black","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Wildflowers-II-fabric-group_e4bed0d0-6259-49bb-895d-cc7268351bd1.jpg","hue":173,"color_bucket":"teal","familySkus":[]},"DWCW-785246":{"patternBase":"Wildflowers Ii Brown","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Wildflowers-II-fabric-group_51b674f8-21b5-466d-bf67-de959b7fe9f2.jpg","hue":31,"color_bucket":"orange","familySkus":[]},"DWCW-785346":{"patternBase":"Wildflowers II Camel II","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Wildflowers-II-fabric-group_1ff74e65-5179-42c0-bf11-6cc0e6a027e9.jpg","hue":29,"color_bucket":"orange","familySkus":[]},"DWCW-785446":{"patternBase":"Wildflowers Ii Gray","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Wildflowers-II-fabric-group_c39c22ab-5b13-45bc-a40d-0c12bded7075.jpg","hue":60,"color_bucket":"yellow","familySkus":[]},"DWCW-785546":{"patternBase":"Wildflowers Ii Purple","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Wildflowers-II-fabric-group_ac71657a-9340-4a55-b4bf-cccf0c0fc93b.jpg","hue":328,"color_bucket":"magenta","familySkus":[]},"DWCW-785646":{"patternBase":"Wildflowers II Royal Blue","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Wildflowers-II-fabric-group_28fe581a-80b7-410a-93bd-ce3db3fb9e60.jpg","hue":218,"color_bucket":"blue","familySkus":[]},"DWCW-787246":{"patternBase":"Zig Zag Black","room":null,"hue":300,"color_bucket":"magenta","familySkus":[]},"DWCW-785946":{"patternBase":"Zig Zag Leaf","room":null,"hue":106,"color_bucket":"green","familySkus":[]},"DWCW-786046":{"patternBase":"Zig Zag Multi Aqua Lt Turquoise Almost White","room":null,"hue":170,"color_bucket":"green","familySkus":[]},"DWCW-786146":{"patternBase":"Zig Zag Multi Lavender Purple Almost White","room":null,"hue":336,"color_bucket":"red","familySkus":[]},"DWCW-786246":{"patternBase":"Zig Zag Multi Lime Yellow Almost White","room":null,"hue":48,"color_bucket":"yellow","familySkus":[]},"DWCW-786346":{"patternBase":"Zig Zag Multi New Blue Navy Almost White","room":null,"hue":201,"color_bucket":"blue","familySkus":[]},"DWCW-786446":{"patternBase":"Zig Zag Multi Turquoise Fuschia Almost White","room":null,"hue":344,"color_bucket":"red","familySkus":[]},"DWCW-786546":{"patternBase":"Zig Zag Multi Turquoise Yellow Almost White","room":null,"hue":52,"color_bucket":"yellow","familySkus":[]},"DWCW-786646":{"patternBase":"Zig Zag Navy","room":null,"hue":216,"color_bucket":"blue","familySkus":["DWCW-786746"]},"DWCW-786746":{"patternBase":"Zig Zag Navy","room":null,"hue":220,"color_bucket":"blue","familySkus":["DWCW-786646"]},"DWCW-786846":{"patternBase":"Zig Zag New Shrimp","room":null,"hue":6,"color_bucket":"red","familySkus":[]},"DWCW-787346":{"patternBase":"Zig Zag Orange","room":null,"hue":17,"color_bucket":"orange","familySkus":[]},"DWCW-786946":{"patternBase":"Zig Zag Slate Blue","room":null,"hue":209,"color_bucket":"blue","familySkus":[]},"DWCW-787046":{"patternBase":"Zig Zag Taupe","room":null,"hue":36,"color_bucket":"orange","familySkus":[]},"DWCW-787146":{"patternBase":"Zig Zag White","room":null,"hue":195,"color_bucket":"teal","familySkus":[]},"DWCW-785746":{"patternBase":"Ziggurat Jungle","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Ziggurat-fabric-group_9de4631e-96c8-4278-976b-9cd8792c5ac1.jpg","hue":67,"color_bucket":"yellow","familySkus":[]},"DWCW-785846":{"patternBase":"Ziggurat Lavender","room":"https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Ziggurat-fabric-group_d1d9f57e-1fdb-44ae-b6c4-a0c5e50590e3.jpg","hue":330,"color_bucket":"magenta","familySkus":[]},"303815G-10GRAS":{"patternBase":"Enchanted Bamboo","room":"https://quadrillefabrics.com/Grasscloth/Enchanted-Bamboo-One-Color-on-Grasscloth-Black-on-Cream-Grasscloth-303815G-10GRAS-sm-thumb.jpg","hue":null,"color_bucket":null,"familySkus":["303815G-15GRAS","303815G-06GRAS","303815G-04GRAS","303815G-05GRAS"]},"303815G-15GRAS":{"patternBase":"Enchanted Bamboo","room":"https://quadrillefabrics.com/Grasscloth/Enchanted-Bamboo-One-Color-on-Grasscloth-Black-On-Hay-Grasscloth-303815G-15GRAS-sm-thumb.jpg","hue":null,"color_bucket":null,"familySkus":["303815G-10GRAS","303815G-06GRAS","303815G-04GRAS","303815G-05GRAS"]},"303815G-06GRAS":{"patternBase":"Enchanted Bamboo","room":"https://quadrillefabrics.com/Grasscloth/Enchanted-Bamboo-One-Color-on-Grasscloth-Inca-Gold-on-Cream-Grasscloth-303815G-06GRAS-sm-thumb.jpg","hue":null,"color_bucket":null,"familySkus":["303815G-10GRAS","303815G-15GRAS","303815G-04GRAS","303815G-05GRAS"]},"303815G-04GRAS":{"patternBase":"Enchanted Bamboo","room":"https://quadrillefabrics.com/Grasscloth/Enchanted-Bamboo-One-Color-on-Grasscloth-Jungle-Green-on-Cream-Grasscloth-303815G-04GRAS-sm-thumb.jpg","hue":null,"color_bucket":null,"familySkus":["303815G-10GRAS","303815G-15GRAS","303815G-06GRAS","303815G-05GRAS"]},"303815G-05GRAS":{"patternBase":"Enchanted Bamboo","room":"https://quadrillefabrics.com/Grasscloth/Enchanted-Bamboo-One-Color-on-Grasscloth-Navy-on-Cream-Grasscloth-303815G-05GRAS-sm-thumb.jpg","hue":null,"color_bucket":null,"familySkus":["303815G-10GRAS","303815G-15GRAS","303815G-06GRAS","303815G-04GRAS"]}}}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index dc27995..561f503 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,7 +11,518 @@
"dependencies": {
"compression": "^1.8.1",
"express": "^5.2.1",
- "helmet": "^8.1.0"
+ "helmet": "^8.1.0",
+ "sharp": "^0.35.2"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz",
+ "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@img/colour": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
+ "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@img/sharp-darwin-arm64": {
+ "version": "0.35.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.2.tgz",
+ "integrity": "sha512-eEieHsMksAW4IiO5NzauESRl2D2qz3J/kwUxUrSfV06A93eEaRfMpHXyUb1mAqrR7i8U9A0GRqE9pjn6u1Jjpg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-arm64": "1.3.1"
+ }
+ },
+ "node_modules/@img/sharp-darwin-x64": {
+ "version": "0.35.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.2.tgz",
+ "integrity": "sha512-BaktuGPCeHJMARpodR8jK4uKiZrPAy9WrfQW0sdI37clracq8Bp01AYS3SZgi5FS/y5twa9t4+LIuuxQjqRrWw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-x64": "1.3.1"
+ }
+ },
+ "node_modules/@img/sharp-freebsd-wasm32": {
+ "version": "0.35.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.2.tgz",
+ "integrity": "sha512-YoAxdnd8hPUkvLHd3bWY+YA8nw3xM/RyRopYucNsWHVSan8NLVM3X2volsfoRDcXdUJPg6tXahSd7HXPK7lRnw==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "dependencies": {
+ "@img/sharp-wasm32": "0.35.2"
+ },
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-arm64": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.1.tgz",
+ "integrity": "sha512-4V/M3roRMTYjiwZY9IOVQOE8OyeCxFAkYmyZDrZl51uOKjibm3oeEJ4WAmLxutAfzFbC9jqUiPs2gbnGflH+7g==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-x64": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.1.tgz",
+ "integrity": "sha512-c0/DxItpJv2+dGhgycJBBgotdqruGYDvA79drdh0MD1dFpy7JzJ/PlXwi1H4rFf0eTy8tgbI91aHDnZIceY3jQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.1.tgz",
+ "integrity": "sha512-aGGy9aWzXgHBG7HNyQPWorZthlp7+x6fDRoPAQbGO3ThcttuTyKIx3NuSHb6zb4gBNq6/yNn9f1cy9nFKS/Vmg==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm64": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.1.tgz",
+ "integrity": "sha512-JznefmcK9j1JKPz8AkQDh89kjojubyfOasWBPKfzMIhPwsgDy9evpE/naJTXXXmghS1iFwR8u/kTwh/I2/+GCw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-ppc64": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.1.tgz",
+ "integrity": "sha512-1EkwGNCZk6iWNCMWqrvdJ+r1j0PT1zIz60CNPhYnJlK/zyeWqlsPZIe+ocBVqPF8k/Ssee/NCk+tE9Ryrko6ng==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-riscv64": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.1.tgz",
+ "integrity": "sha512-Ilays+w2bXdnxzxtQdmXR62u8o8GYa3eL4+Gr+1KiE4xperMZUslRaVPJwwPkzlHEjGfXAfRVAa/7CYCtSqsBw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-s390x": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.1.tgz",
+ "integrity": "sha512-VfBwVHQTbRoj4XlpA/KLZ7ltgMpz+4WSejFzQ+GnoImjo1PtEJ59QB2qR1xQEeRPYIkNrPIm2L4cICMvz4C2ew==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-x64": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.1.tgz",
+ "integrity": "sha512-+c8ukgwU62DS54nCAjw7keOfHUkmr0B5QHEdcOqRnodF/MNXJbVI8Eopoj4B/0H8Asr65I+A4Amrn7a85/md6A==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.1.tgz",
+ "integrity": "sha512-qlKb/pwbkAi1WMsJrYHk7CuDrd12s27U2QnRhFYUoJNrRCmkosMTttuRFat/DDB3IlDm5qE1TJgZ4JDnHX8Ldw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-x64": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.1.tgz",
+ "integrity": "sha512-yO21HwoUVLN8Qa+/SBjQLMYwBWAVJjeGPNe+hc0OUeMeifEtJqu5a1c4HayE1nNpDih9y3/KkoltfkDodmKAlg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm": {
+ "version": "0.35.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.2.tgz",
+ "integrity": "sha512-SE4kzF2mepn6z+6E7L6lsV8FzuLL6IPQdyX8ZiwROAG/G8td+hP/m7FsFPwidtrF19gvajuC9l6TxAVcsA4S7A==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm": "1.3.1"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm64": {
+ "version": "0.35.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.2.tgz",
+ "integrity": "sha512-af12Pnd0ZGu2HfP8NayB0kk6eC/lrfbQE6HlR4jD+34wdJ1Vw9TF6TMn6ZvffT+WgqVsl0hRbmNvz2u/23VmwA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm64": "1.3.1"
+ }
+ },
+ "node_modules/@img/sharp-linux-ppc64": {
+ "version": "0.35.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.2.tgz",
+ "integrity": "sha512-hYSBm7zcNtDCozCxQHYZJiu63b/bXsgRZuOxCIBZsStMM9Vap47iFHdbX4kCvQsblPB/k+clhELpdQJHQLSHvg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-ppc64": "1.3.1"
+ }
+ },
+ "node_modules/@img/sharp-linux-riscv64": {
+ "version": "0.35.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.2.tgz",
+ "integrity": "sha512-qQt0Kc13+Hoan/Awq/qMSQw3L+RI1NCRPgD5cUJ/1WSSmIoysLOc72jlRM3E0OHN9Yr313jgeQ2T+zW+F03QFA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-riscv64": "1.3.1"
+ }
+ },
+ "node_modules/@img/sharp-linux-s390x": {
+ "version": "0.35.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.2.tgz",
+ "integrity": "sha512-E4fLLfRPzDLlEeDaTzI98OFLcv++WL5ChLLMwPoVd0CIoZQqupBSNbOisPL5am9XsbQ9T84+iiMpUvbFtkunbA==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-s390x": "1.3.1"
+ }
+ },
+ "node_modules/@img/sharp-linux-x64": {
+ "version": "0.35.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.2.tgz",
+ "integrity": "sha512-gi0zFJJRLswfCZmHtJdikXPOc5u7qamSOS3NHedLqLd4W8Q0NqjdBr6TTRIgsfFjqfTsHFgdfvJ9LwqSgcHiAA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-x64": "1.3.1"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-arm64": {
+ "version": "0.35.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.2.tgz",
+ "integrity": "sha512-siWbOW1u6HFnFLrp0waKyW7VEf7jYvcDWdrXEFa8AkdAQgEvuu5Fz8/Y70w9EeqAdwDtfU012BhEHHaDqvQNzg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-arm64": "1.3.1"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-x64": {
+ "version": "0.35.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.2.tgz",
+ "integrity": "sha512-YBqMMcjDi4QGYiSn4vNOYBhmlC4z5AXqkOUUqI2e0AFA4urNv4ESgOgwNl3K+4etQhha0twXlzeF20bbULm9Yg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-x64": "1.3.1"
+ }
+ },
+ "node_modules/@img/sharp-wasm32": {
+ "version": "0.35.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.2.tgz",
+ "integrity": "sha512-Mrv4JQNYVQ94xH+jzZ9r+gowleN8mv2FTgKT+PI6bx5C0G8TdNYndu161pg2i7uoBwxy2ImPMHrJOM2LZef7Bw==",
+ "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/runtime": "^1.11.1"
+ },
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-webcontainers-wasm32": {
+ "version": "0.35.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.2.tgz",
+ "integrity": "sha512-QNV27pxs9wpApEiCfvHM1RDoP1w1+2KrUWWDPEhEwg+latvOrfuhWrHWZKwdSFwU6jh3myjw/yOCRsUIuOft3g==",
+ "cpu": [
+ "wasm32"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "@img/sharp-wasm32": "0.35.2"
+ },
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-arm64": {
+ "version": "0.35.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.2.tgz",
+ "integrity": "sha512-BiVRYc/t6/Vl3e1hBx0hugG4oN9Pydf4fgMSpxTQJmwGUg/YoXTWHiFeRymHfCZzifxu4F4rpk/I67D0LQ20wQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-ia32": {
+ "version": "0.35.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.2.tgz",
+ "integrity": "sha512-YYEhx9PImCC7T0tI8JDMi4DB9LwLCXCU5OWNYEXAxh5Q1ShKkyC6byxzoBJ3gEFDnH2lQckWuDe70G7mB2XJog==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-x64": {
+ "version": "0.35.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.2.tgz",
+ "integrity": "sha512-imoOyBcoM/iiUr4J6VPpCNjPnjvP/Gks95898yB8YqoGGYmHYbOyCuNv9FMhFgtaiHFGbHW8bxKqRV6VjtXThQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
}
},
"node_modules/accepts": {
@@ -209,6 +720,15 @@
"node": ">= 0.8"
}
},
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -807,6 +1327,18 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
+ "node_modules/semver": {
+ "version": "7.8.5",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
+ "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/send": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
@@ -881,6 +1413,50 @@
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC"
},
+ "node_modules/sharp": {
+ "version": "0.35.2",
+ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.2.tgz",
+ "integrity": "sha512-FVtFjtBCMiJS6yb5CX7Sop45WFMpeGw6oRKuJnXYgf/f1ms/D7LE/ZUSNxnW7rZ/dbslQWYkoqFHGPaDBtaK4w==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@img/colour": "^1.1.0",
+ "detect-libc": "^2.1.2",
+ "semver": "^7.8.4"
+ },
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-darwin-arm64": "0.35.2",
+ "@img/sharp-darwin-x64": "0.35.2",
+ "@img/sharp-freebsd-wasm32": "0.35.2",
+ "@img/sharp-libvips-darwin-arm64": "1.3.1",
+ "@img/sharp-libvips-darwin-x64": "1.3.1",
+ "@img/sharp-libvips-linux-arm": "1.3.1",
+ "@img/sharp-libvips-linux-arm64": "1.3.1",
+ "@img/sharp-libvips-linux-ppc64": "1.3.1",
+ "@img/sharp-libvips-linux-riscv64": "1.3.1",
+ "@img/sharp-libvips-linux-s390x": "1.3.1",
+ "@img/sharp-libvips-linux-x64": "1.3.1",
+ "@img/sharp-libvips-linuxmusl-arm64": "1.3.1",
+ "@img/sharp-libvips-linuxmusl-x64": "1.3.1",
+ "@img/sharp-linux-arm": "0.35.2",
+ "@img/sharp-linux-arm64": "0.35.2",
+ "@img/sharp-linux-ppc64": "0.35.2",
+ "@img/sharp-linux-riscv64": "0.35.2",
+ "@img/sharp-linux-s390x": "0.35.2",
+ "@img/sharp-linux-x64": "0.35.2",
+ "@img/sharp-linuxmusl-arm64": "0.35.2",
+ "@img/sharp-linuxmusl-x64": "0.35.2",
+ "@img/sharp-webcontainers-wasm32": "0.35.2",
+ "@img/sharp-win32-arm64": "0.35.2",
+ "@img/sharp-win32-ia32": "0.35.2",
+ "@img/sharp-win32-x64": "0.35.2"
+ }
+ },
"node_modules/side-channel": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
@@ -971,6 +1547,13 @@
"node": ">=0.6"
}
},
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD",
+ "optional": true
+ },
"node_modules/type-is": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz",
diff --git a/package.json b/package.json
index a048fe0..42bbcd5 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
"dependencies": {
"compression": "^1.8.1",
"express": "^5.2.1",
- "helmet": "^8.1.0"
+ "helmet": "^8.1.0",
+ "sharp": "^0.35.2"
}
}
diff --git a/proto/shot-colorriver.cjs b/proto/shot-colorriver.cjs
new file mode 100644
index 0000000..1e739d4
--- /dev/null
+++ b/proto/shot-colorriver.cjs
@@ -0,0 +1,88 @@
+#!/usr/bin/env node
+/* Headless verifier for proto/v6-colorriver.html
+ - boots the proto static+proxy server (proxy-server.cjs) on a temp port
+ - loads /v6-colorriver.html in chromium
+ - waits for swatches to load, captures console errors
+ - asserts swatches rendered AND sorted by hue
+ - writes proto/shots/v6-colorriver.png
+ Run: NODE_PATH=$HOME/.npm-global/lib/node_modules node proto/shot-colorriver.cjs
+*/
+const { spawn } = require("child_process");
+const path = require("path");
+const http = require("http");
+
+const PROTO_DIR = __dirname;
+const PORT = 7711;
+const URL = `http://127.0.0.1:${PORT}/v6-colorriver.html`;
+
+function waitForServer(port, tries=40){
+ return new Promise((resolve,reject)=>{
+ const probe=(n)=>{
+ const req=http.get({host:"127.0.0.1",port,path:"/v6-colorriver.html"},(r)=>{r.destroy();resolve();});
+ req.on("error",()=>{ if(n<=0) return reject(new Error("server never came up")); setTimeout(()=>probe(n-1),150); });
+ };
+ probe(tries);
+ });
+}
+
+(async()=>{
+ let pw;
+ try { pw = require("playwright"); } catch(e){ console.error("NO playwright:",e.message); process.exit(2); }
+
+ // start proto server
+ const srv = spawn("node", [path.join(PROTO_DIR,"proxy-server.cjs")], {
+ env:{ ...process.env, PROTO_PORT:String(PORT) }, stdio:"inherit"
+ });
+ let exitCode = 1;
+ try {
+ await waitForServer(PORT);
+ const browser = await pw.chromium.launch({ headless:true });
+ const page = await browser.newPage({ viewport:{ width:1440, height:900 }, deviceScaleFactor:2 });
+
+ const errors = [];
+ page.on("console", m=>{ if(m.type()==="error") errors.push(m.text()); });
+ page.on("pageerror", e=>errors.push("PAGEERROR: "+e.message));
+
+ await page.goto(URL, { waitUntil:"domcontentloaded", timeout:30000 });
+
+ // wait until full load+sort completes (or 35s)
+ await page.waitForFunction(()=>window.__RIVER__ && window.__RIVER__.done(), { timeout:35000 })
+ .catch(()=>{});
+
+ // let textures settle + boot fade
+ await page.waitForTimeout(2000);
+
+ const count = await page.evaluate(()=>window.__RIVER__ ? window.__RIVER__.count() : 0);
+ const sorted = await page.evaluate(()=>window.__RIVER__ ? window.__RIVER__.sortedByHue() : false);
+ const spatial = await page.evaluate(()=>window.__RIVER__ ? window.__RIVER__.spatialSorted() : false);
+ const hueSample = await page.evaluate(()=>{
+ const h = window.__RIVER__ ? window.__RIVER__.hues() : [];
+ return { first:h.slice(0,5), last:h.slice(-5), n:h.length };
+ });
+
+ const shotPath = path.join(PROTO_DIR,"shots","v6-colorriver.png");
+ await page.screenshot({ path:shotPath });
+
+ console.log("\n================ COLOR RIVER VERIFY ================");
+ console.log("swatches rendered :", count);
+ console.log("sorted by hue :", sorted);
+ console.log("spatial X by hue :", spatial);
+ console.log("hue range (sorted):", hueSample.first, "...", hueSample.last);
+ console.log("console errors :", errors.length);
+ if(errors.length) errors.slice(0,10).forEach(e=>console.log(" !", e));
+ console.log("screenshot :", shotPath);
+ console.log("===================================================\n");
+
+ await browser.close();
+
+ const ok = count >= 40 && sorted === true && spatial === true && errors.length === 0;
+ exitCode = ok ? 0 : 1;
+ console.log(ok ? "VERDICT: PASS" : "VERDICT: FAIL");
+ } catch(e){
+ console.error("verify error:", e);
+ exitCode = 3;
+ } finally {
+ srv.kill("SIGTERM");
+ setTimeout(()=>process.exit(exitCode), 300);
+ }
+})();
diff --git a/proto/shots/v10-concierge.png b/proto/shots/v10-concierge.png
new file mode 100644
index 0000000..f919725
Binary files /dev/null and b/proto/shots/v10-concierge.png differ
diff --git a/proto/shots/v6-colorriver.png b/proto/shots/v6-colorriver.png
new file mode 100644
index 0000000..283804b
Binary files /dev/null and b/proto/shots/v6-colorriver.png differ
diff --git a/proto/shots/v8-swipe-filled.png b/proto/shots/v8-swipe-filled.png
new file mode 100644
index 0000000..b802042
Binary files /dev/null and b/proto/shots/v8-swipe-filled.png differ
diff --git a/proto/shots/v8-swipe.png b/proto/shots/v8-swipe.png
new file mode 100644
index 0000000..1218eb6
Binary files /dev/null and b/proto/shots/v8-swipe.png differ
diff --git a/proto/shots/v9-walkin-angle2.png b/proto/shots/v9-walkin-angle2.png
new file mode 100644
index 0000000..604a819
Binary files /dev/null and b/proto/shots/v9-walkin-angle2.png differ
diff --git a/proto/shots/v9-walkin.png b/proto/shots/v9-walkin.png
new file mode 100644
index 0000000..4a755bc
Binary files /dev/null and b/proto/shots/v9-walkin.png differ
diff --git a/proto/v10-concierge.html b/proto/v10-concierge.html
new file mode 100644
index 0000000..7ff8a57
--- /dev/null
+++ b/proto/v10-concierge.html
@@ -0,0 +1,561 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>The Quadrille House — Concierge Nook</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,500;0,600;1,400;1,500&family=Jost:wght@300;400;500&display=swap" rel="stylesheet">
+<style>
+ :root{
+ /* Art-direction palette */
+ --plaster:#e7e1d6;
+ --stone:#cfc7b8;
+ --greige:#b8afa0;
+ --oak:#9a7a52;
+ --oak-dark:#6f5536;
+ --brass:#b9933f;
+ --brass-lt:#c9a96e;
+ --ink:#1a1206;
+ --ink-soft:#473c2c;
+ --cream:#f0e6cf;
+ --charcoal:#1c1c22;
+ --felt:#3a352c; /* table felt */
+ --pane:#f5f0e7;
+ }
+ *{box-sizing:border-box;margin:0;padding:0}
+ html,body{height:100%}
+ body{
+ font-family:'Jost',sans-serif;
+ color:var(--ink);
+ background:
+ radial-gradient(120% 90% at 50% -10%, #2c2820 0%, #211d16 45%, #16130d 100%);
+ overflow:hidden;
+ -webkit-font-smoothing:antialiased;
+ }
+ /* ===== Room shell: POV sitting at the consultation table ===== */
+ .room{
+ position:fixed; inset:0;
+ display:grid;
+ grid-template-rows: 1fr auto;
+ perspective:1400px;
+ perspective-origin:50% 38%;
+ }
+
+ /* Back wall — limewash plaster with a brass picture rail + soft picture-light glow */
+ .backwall{
+ position:absolute; inset:0 0 28% 0;
+ background:
+ radial-gradient(80% 55% at 50% 4%, rgba(201,169,110,.18), transparent 60%),
+ linear-gradient(180deg, #efe9dd 0%, #e3dccf 55%, #d6cdbc 100%);
+ box-shadow: inset 0 -40px 80px rgba(28,28,34,.18);
+ }
+ .backwall::after{ /* faint plaster mottle */
+ content:""; position:absolute; inset:0;
+ background:
+ radial-gradient(60% 40% at 22% 30%, rgba(0,0,0,.03), transparent 60%),
+ radial-gradient(50% 38% at 78% 22%, rgba(0,0,0,.025), transparent 60%);
+ mix-blend-mode:multiply;
+ }
+ .rail{ position:absolute; top:13%; left:0; right:0; height:5px;
+ background:linear-gradient(180deg,var(--brass-lt),var(--brass) 60%,#7e6526);
+ box-shadow:0 3px 9px rgba(0,0,0,.28), 0 1px 0 rgba(255,255,255,.35) inset;}
+
+ /* The consultation TABLE — a tilted oak surface receding from the viewer (the POV) */
+ .table-zone{ position:absolute; inset:0; overflow:hidden; }
+ .table{
+ position:absolute; left:50%; bottom:-6%;
+ width:175%; height:122%;
+ transform:translateX(-50%) rotateX(58deg);
+ transform-origin:50% 100%;
+ background:
+ linear-gradient(115deg, rgba(255,255,255,.05) 0%, transparent 30%),
+ repeating-linear-gradient(91deg, #a07d50 0 60px, #98774b 60px 122px),
+ linear-gradient(180deg,#a98a5c,#7c6038 70%,#5f4a2c);
+ border-radius:14px 14px 0 0;
+ box-shadow: 0 -2px 0 rgba(255,255,255,.18) inset, 0 40px 120px rgba(0,0,0,.55);
+ }
+ .table::before{ /* warm sheen / picture-light grazing the felt runner */
+ content:""; position:absolute; left:50%; top:6%; transform:translateX(-50%);
+ width:62%; height:70%;
+ background:
+ radial-gradient(60% 60% at 50% 0%, rgba(231,225,214,.14), transparent 70%);
+ border-radius:8px;
+ background-color:var(--felt);
+ box-shadow:inset 0 0 60px rgba(0,0,0,.5), 0 0 0 1px rgba(201,169,110,.25);
+ opacity:.9;
+ }
+
+ /* ===== Concierge assist panel (left, fixed) ===== */
+ .concierge{
+ position:fixed; top:0; left:0; bottom:0; width:380px;
+ background:linear-gradient(180deg, rgba(245,240,231,.97), rgba(238,232,221,.98));
+ border-right:1px solid rgba(185,147,63,.35);
+ box-shadow: 8px 0 40px rgba(0,0,0,.4);
+ display:flex; flex-direction:column;
+ z-index:20;
+ }
+ .c-head{ padding:26px 26px 16px; border-bottom:1px solid rgba(185,147,63,.25); }
+ .eyebrow{ font-size:11px; letter-spacing:2.4px; text-transform:uppercase; color:var(--brass);
+ font-weight:500; }
+ .wordmark{ font-family:'Cormorant Garamond',serif; font-weight:600; font-size:30px;
+ line-height:1.05; color:var(--ink); margin-top:4px; }
+ .wordmark em{ font-style:italic; color:var(--oak-dark); }
+ .seat{ font-size:12.5px; color:var(--ink-soft); margin-top:9px; line-height:1.5; }
+
+ /* conversation transcript */
+ .convo{ flex:1; overflow-y:auto; padding:18px 22px; display:flex; flex-direction:column; gap:14px;}
+ .convo::-webkit-scrollbar{width:7px} .convo::-webkit-scrollbar-thumb{background:rgba(185,147,63,.35);border-radius:4px}
+ .msg{ max-width:92%; font-size:14px; line-height:1.55; }
+ .msg.assist{ align-self:flex-start; }
+ .msg.assist .who{ font-size:10px; letter-spacing:2px; text-transform:uppercase; color:var(--brass); margin-bottom:4px; }
+ .msg.assist .body{ background:#fff; border:1px solid rgba(185,147,63,.22); border-radius:3px 14px 14px 14px;
+ padding:11px 14px; color:var(--ink); box-shadow:0 2px 10px rgba(0,0,0,.06); }
+ .msg.you{ align-self:flex-end; }
+ .msg.you .body{ background:var(--oak-dark); color:var(--cream); border-radius:14px 3px 14px 14px;
+ padding:9px 14px; font-family:'Cormorant Garamond',serif; font-size:17px; }
+ .msg .body em{ color:var(--oak-dark); font-style:italic; }
+ .msg.you .body em{ color:var(--brass-lt); }
+
+ /* chip palette */
+ .chips-wrap{ padding:14px 20px 8px; border-top:1px solid rgba(185,147,63,.25); }
+ .chips-label{ font-size:10px; letter-spacing:2px; text-transform:uppercase; color:var(--ink-soft); margin-bottom:10px; }
+ .chips{ display:flex; flex-wrap:wrap; gap:8px; }
+ .chip{
+ font-family:'Cormorant Garamond',serif; font-size:17px;
+ padding:9px 16px; border-radius:30px; cursor:pointer;
+ background:#fff; color:var(--ink);
+ border:1px solid rgba(185,147,63,.4);
+ transition:.18s; user-select:none; line-height:1;
+ }
+ .chip:hover{ background:var(--brass); color:#fff; border-color:var(--brass); transform:translateY(-1px); }
+ .chip:active{ transform:translateY(0); }
+ .chip .sw{ display:inline-block; width:11px; height:11px; border-radius:50%; margin-right:7px;
+ vertical-align:-1px; border:1px solid rgba(0,0,0,.15); }
+
+ /* text input bar */
+ .ask{ padding:16px 20px 20px; border-top:1px solid rgba(185,147,63,.25); }
+ .ask form{ display:flex; gap:8px; }
+ .ask input{
+ flex:1; font-family:'Cormorant Garamond',serif; font-size:18px;
+ padding:12px 15px; border-radius:30px; border:1px solid rgba(185,147,63,.45);
+ background:#fff; color:var(--ink); outline:none;
+ }
+ .ask input::placeholder{ color:#a99; font-style:italic; }
+ .ask input:focus{ border-color:var(--brass); box-shadow:0 0 0 3px rgba(185,147,63,.15); }
+ .ask button{ border:none; background:var(--oak-dark); color:var(--cream); cursor:pointer;
+ border-radius:50%; width:46px; height:46px; font-size:18px; flex:0 0 auto; transition:.18s; }
+ .ask button:hover{ background:var(--brass); }
+
+ /* ===== Boards gliding onto the table ===== */
+ .stage{ position:fixed; left:380px; right:0; top:0; bottom:0; z-index:10;
+ display:flex; align-items:center; justify-content:center; pointer-events:none; }
+ .boards{
+ pointer-events:auto;
+ display:flex; gap:30px; flex-wrap:wrap; justify-content:center; align-content:center;
+ max-width:1100px; max-height:88vh; overflow-y:auto; padding:40px 30px 20px;
+ perspective:1200px;
+ }
+ .boards::-webkit-scrollbar{width:8px} .boards::-webkit-scrollbar-thumb{background:rgba(201,169,110,.4);border-radius:4px}
+
+ .board{
+ width:210px; flex:0 0 auto;
+ transform-origin:50% 100%;
+ animation:glide .7s cubic-bezier(.18,.8,.28,1) backwards;
+ cursor:pointer;
+ }
+ @keyframes glide{
+ 0%{ opacity:0; transform:translateY(70px) rotateX(28deg) scale(.86); }
+ 100%{ opacity:1; transform:translateY(0) rotateX(0) scale(1); }
+ }
+ /* brass-framed sample board with gallery placard */
+ .board .frame{
+ position:relative; border-radius:4px;
+ padding:9px; background:linear-gradient(155deg,#d8c79c,var(--brass) 55%,#7e6526);
+ box-shadow:0 22px 44px rgba(0,0,0,.5), 0 2px 0 rgba(255,255,255,.3) inset;
+ transition:transform .35s cubic-bezier(.2,.7,.3,1), box-shadow .35s;
+ }
+ .board:hover .frame{ transform:translateY(-10px) rotateX(-4deg); box-shadow:0 34px 60px rgba(0,0,0,.6); }
+ .board .swatch{
+ width:100%; aspect-ratio:1/1; border-radius:2px; object-fit:cover;
+ background:var(--stone); display:block; box-shadow:0 0 0 1px rgba(0,0,0,.25) inset;
+ }
+ /* engraved-look placard */
+ .placard{ margin-top:9px; background:linear-gradient(180deg,#23201a,#171410);
+ border:1px solid var(--brass); border-radius:3px; padding:9px 11px 10px;
+ box-shadow:0 5px 14px rgba(0,0,0,.4); }
+ .placard .pname{ font-family:'Cormorant Garamond',serif; font-size:16px; color:var(--cream);
+ line-height:1.15; }
+ .placard .pmeta{ font-size:10px; letter-spacing:1.4px; text-transform:uppercase; color:var(--brass-lt);
+ margin-top:4px; display:flex; gap:6px; align-items:center; }
+ .placard .pmeta .dot{ width:9px;height:9px;border-radius:50%;border:1px solid rgba(255,255,255,.3); }
+ .placard a{ color:var(--brass-lt); text-decoration:none; font-size:10px; letter-spacing:1px;
+ text-transform:uppercase; display:inline-block; margin-top:7px; border-bottom:1px solid rgba(201,169,110,.4); }
+ .placard a:hover{ color:#fff; }
+
+ /* empty / welcome state floating over the table */
+ .welcome{ position:absolute; left:50%; top:34%; transform:translateX(-50%);
+ text-align:center; color:var(--cream); max-width:420px; pointer-events:none; }
+ .welcome .wm{ font-family:'Cormorant Garamond',serif; font-size:34px; font-weight:500; line-height:1.2;
+ text-shadow:0 4px 24px rgba(0,0,0,.6); }
+ .welcome .wm em{ font-style:italic; color:var(--brass-lt); }
+ .welcome .sub{ font-size:13px; letter-spacing:.4px; margin-top:12px; opacity:.78; line-height:1.6; }
+
+ /* result count ribbon */
+ .ribbon{ position:fixed; top:20px; right:30px; z-index:15;
+ background:rgba(28,28,34,.82); color:var(--cream); border:1px solid rgba(201,169,110,.4);
+ border-radius:30px; padding:8px 18px; font-size:11px; letter-spacing:2px; text-transform:uppercase;
+ backdrop-filter:blur(8px); display:none; }
+ .ribbon b{ color:var(--brass-lt); font-weight:500; }
+
+ @media (max-width:920px){
+ .concierge{ width:300px; } .stage{ left:300px; }
+ .board{ width:160px; }
+ }
+</style>
+</head>
+<body>
+ <!-- POV room -->
+ <div class="room">
+ <div class="table-zone">
+ <div class="backwall"></div>
+ <div class="rail"></div>
+ <div class="table"></div>
+ </div>
+ </div>
+
+ <div class="ribbon" id="ribbon"></div>
+
+ <!-- Boards stage -->
+ <div class="stage">
+ <div class="welcome" id="welcome">
+ <div class="wm">Take a seat at the <em>consultation table.</em></div>
+ <div class="sub">Tell me what you're after — a colour, a feeling, a pattern you already love —
+ and I'll pull the boards for you, one at a time, right here in front of you.</div>
+ </div>
+ <div class="boards" id="boards"></div>
+ </div>
+
+ <!-- Concierge assist panel -->
+ <aside class="concierge">
+ <div class="c-head">
+ <div class="eyebrow">The Quadrille House</div>
+ <div class="wordmark">Concierge <em>Nook</em></div>
+ <div class="seat">Your private appointment. I'm pulling from the China Seas atelier —
+ just say the word.</div>
+ </div>
+
+ <div class="convo" id="convo"></div>
+
+ <div class="chips-wrap">
+ <div class="chips-label">A few things people often ask for</div>
+ <div class="chips" id="chips"></div>
+ </div>
+
+ <div class="ask">
+ <form id="askForm" autocomplete="off">
+ <input id="askInput" type="text" placeholder="e.g. show me blues… or “like Aga but darker”" aria-label="Ask the concierge">
+ <button type="submit" title="Ask">→</button>
+ </form>
+ </div>
+ </aside>
+
+<script>
+/* ============================================================
+ CONCIERGE NOOK — client-side matching engine (no LLM)
+ Color/hue/bucket fields are null in the feed, so all signal
+ is derived from pattern_name + collection text.
+ ============================================================ */
+/* Relative path so it's same-origin whether served behind the proto proxy
+ (proto/proxy-server.cjs :7699 → backend :7690) or later from the app's public/.
+ Falls back to the absolute backend URL if same-origin fetch fails. */
+const API = '/api/showroom/products?limit=200';
+const API_FALLBACK = 'http://127.0.0.1:7690/api/showroom/products?limit=200';
+let PRODUCTS = [];
+
+/* ---- Color lexicon: word -> {bucket, hex (for the chip swatch)} ---- */
+const COLOR_WORDS = {
+ // blues
+ navy:['blue','#1f2d52'], blue:['blue','#3a5fa0'], blues:['blue','#3a5fa0'],
+ aqua:['blue','#5fb8c4'], turquoise:['blue','#2fb5b0'], capri:['blue','#3aa0c8'],
+ // greens
+ green:['green','#4f7a3a'], greens:['green','#4f7a3a'], jungle:['green','#2f5d33'],
+ apple:['green','#7aa340'], lime:['green','#9bc23a'], pistachio:['green','#bcd49a'],
+ // warm / neutrals
+ cream:['neutral','#efe7d2'], white:['neutral','#f3eee2'], 'off-white':['neutral','#ece5d4'],
+ beige:['neutral','#d8ccb0'], gold:['warm','#c9a44a'], metallic:['warm','#c9b072'],
+ cognac:['warm','#9a5a2c'], brown:['warm','#6f4a2c'], burnt:['warm','#8a4326'],
+ shrimp:['warm','#e08e72'], salmon:['warm','#e89274'], coral:['warm','#e26d52'],
+ orange:['warm','#d2752c'], yellow:['warm','#e0bb46'],
+ // reds / pinks / purples
+ red:['red','#a4322b'], reds:['red','#a4322b'], watermelon:['red','#c8455a'],
+ magenta:['purple','#a63072'], pink:['pink','#dd86a4'], lilac:['purple','#b79ec8'],
+ purple:['purple','#6b4a8a'], plum:['purple','#5a2f52'],
+ // dark / black
+ black:['dark','#1c1a16'], charcoal:['dark','#2b2926']
+};
+
+/* ---- Style / pattern keywords (matched against pattern_name) ---- */
+const STYLE_WORDS = {
+ stripe:['stripe','striped','bali','batik'],
+ trellis:['trellis','diamond','lattice'],
+ floral:['floral','flower','bouquet','arbre','rivage'],
+ bird:['bird','birds','aviary'],
+ geometric:['geometric','diamond','batik','isle']
+};
+
+/* ---- bucket -> warm description for the concierge to speak ---- */
+const BUCKET_PHRASE = {
+ blue:'cool blues — from soft aqua to deep navy',
+ green:'fresh greens — jungle, apple, pistachio',
+ warm:'warm neutrals and earthy tones',
+ neutral:'quiet creams and whites',
+ red:'rich reds and watermelons',
+ pink:'soft pinks',
+ purple:'purples and lilacs',
+ dark:'darker, more dramatic grounds'
+};
+
+/* ---- family = first phrase of pattern_name (before " on ") ---- */
+function familyOf(p){
+ const base = (p.pattern_name||'').split(/ on /i)[0];
+ // family root = first 1–2 words (e.g. "Aga", "Bali Hai", "Arbre de Matisse")
+ return base.replace(/\s*(screen printed|reverse|new).*/i,'').trim();
+}
+function familyRoot(p){ return (familyOf(p).split(/\s+/)[0]||'').toLowerCase(); }
+
+/* lightness heuristic from ground/color words (for "darker"/"lighter") */
+function lightness(p){
+ const n=(p.pattern_name||'').toLowerCase();
+ let score=0.5;
+ if(/white|cream|almost white|off white|pale|soft|light|pistachio|lilac/.test(n)) score+=0.32;
+ if(/navy|black|charcoal|jungle|brown|burnt|cognac|plum|dark|deep/.test(n)) score-=0.34;
+ if(/aqua|apple|lime|pink|shrimp|gold|yellow|salmon/.test(n)) score+=0.1;
+ return Math.max(0,Math.min(1,score));
+}
+
+/* ---- normalize a query into match intent ---- */
+function tokenize(q){ return q.toLowerCase().replace(/[^a-z' ]/g,' ').split(/\s+/).filter(Boolean); }
+
+function interpret(query){
+ const toks = tokenize(query);
+ const intent = { colors:new Set(), buckets:new Set(), styles:new Set(),
+ families:new Set(), darker:false, lighter:false, raw:query };
+ // direction words
+ if(toks.some(t=>/dark|deep|moody|richer|bolder/.test(t))) intent.darker=true;
+ if(toks.some(t=>/light|pale|soft|airy|brighter|paler/.test(t))) intent.lighter=true;
+
+ // color & bucket words
+ for(const t of toks){
+ if(COLOR_WORDS[t]){ intent.colors.add(t); intent.buckets.add(COLOR_WORDS[t][0]); }
+ if(t==='neutral'||t==='neutrals') intent.buckets.add('neutral').add('warm');
+ if(t==='warm') intent.buckets.add('warm');
+ if(t==='cool') intent.buckets.add('blue');
+ }
+ // style words (tolerate plurals: "stripes"->"stripe", "florals"->"floral")
+ const stem=t=>t.replace(/s$/,'');
+ for(const [style,words] of Object.entries(STYLE_WORDS)){
+ const want=new Set([style,...words].map(stem));
+ if(toks.some(t=>want.has(stem(t)))) intent.styles.add(style);
+ }
+ // "like X" family references — match any token against known family roots
+ const famRoots = new Set(PRODUCTS.map(familyRoot));
+ for(const t of toks){ if(famRoots.has(t)) intent.families.add(t); }
+
+ return intent;
+}
+
+/* ---- score a product against an intent ---- */
+function scoreProduct(p, intent){
+ const n=(p.pattern_name||'').toLowerCase();
+ let s=0, hits=0;
+ // color words present in name
+ for(const c of intent.colors){ if(n.includes(c)){ s+=6; hits++; } }
+ // bucket: any color word in name that maps to a wanted bucket
+ if(intent.buckets.size){
+ for(const [w,[bk]] of Object.entries(COLOR_WORDS)){
+ if(intent.buckets.has(bk) && n.includes(w)){ s+=3; hits++; }
+ }
+ }
+ // style
+ for(const st of intent.styles){
+ if(STYLE_WORDS[st].some(w=>n.includes(w))){ s+=4; hits++; }
+ }
+ // family ("like Aga")
+ if(intent.families.size){
+ const fr=familyRoot(p);
+ if(intent.families.has(fr)){ s+=5; hits++; }
+ }
+ if(hits===0) return -1; // no positive signal at all -> exclude
+ // darker/lighter nudge
+ const L=lightness(p);
+ if(intent.darker) s += (1-L)*5;
+ if(intent.lighter) s += L*5;
+ // "like X but darker": family match + direction => big boost when both
+ if(intent.families.size && (intent.darker||intent.lighter)) s+=2;
+ return s;
+}
+
+/* ---- run a query: return sorted matches + a spoken concierge line ---- */
+function runQuery(query){
+ const intent = interpret(query);
+ let scored = PRODUCTS.map(p=>({p,score:scoreProduct(p,intent)}))
+ .filter(x=>x.score>=0)
+ .sort((a,b)=>b.score-a.score);
+
+ // If query was a "like family but darker" and family match yielded few, broaden to bucket of that family
+ let results = scored.map(x=>x.p);
+
+ // Fallback: if nothing matched but they typed *something*, do a loose substring across name
+ if(!results.length){
+ const toks=tokenize(query);
+ results = PRODUCTS.filter(p=>{const n=(p.pattern_name||'').toLowerCase();return toks.some(t=>t.length>2&&n.includes(t));});
+ }
+ results = results.slice(0,24);
+
+ return { results, line: concierge(query,intent,results) };
+}
+
+/* ---- concierge's warm spoken response ---- */
+function concierge(query,intent,results){
+ if(!results.length){
+ return `Hm — I don't have anything quite like “${esc(query)}” on the floor today. Try a colour like <em>blues</em> or <em>warm neutrals</em>, or name a pattern such as <em>Aga</em> or <em>Bali Hai</em>.`;
+ }
+ const bits=[];
+ if(intent.families.size){
+ const fam=[...intent.families][0];
+ if(intent.darker) bits.push(`the <em>${cap(fam)}</em> family, leaning to the darker, moodier grounds`);
+ else if(intent.lighter) bits.push(`the <em>${cap(fam)}</em> family in its lighter, airier colourways`);
+ else bits.push(`everything in the <em>${cap(fam)}</em> family`);
+ } else if(intent.buckets.size){
+ const ph=[...intent.buckets].map(b=>BUCKET_PHRASE[b]).filter(Boolean)[0];
+ bits.push(ph?`some lovely <em>${ph}</em>`:`a few in that palette`);
+ } else if(intent.styles.size){
+ bits.push(`pieces with a <em>${[...intent.styles][0]}</em> feel`);
+ } else if(intent.darker) bits.push(`the darker, more dramatic boards`);
+ else if(intent.lighter) bits.push(`the softest, lightest grounds`);
+ else bits.push(`a curated handful for you`);
+
+ const n=results.length;
+ const lead=['Of course.','Lovely choice.','With pleasure.','Beautifully said.','Right this way.'][Math.floor(Math.random()*5)];
+ return `${lead} I'm bringing out ${bits.join(', ')} — ${n} board${n>1?'s':''} gliding onto the table now.`;
+}
+
+/* ---- render boards onto the table ---- */
+function presentBoards(results){
+ const wrap=document.getElementById('boards');
+ const welcome=document.getElementById('welcome');
+ const ribbon=document.getElementById('ribbon');
+ welcome.style.display = results.length ? 'none':'block';
+ ribbon.style.display = results.length ? 'block':'none';
+ ribbon.innerHTML = `<b>${results.length}</b> boards on the table`;
+ wrap.innerHTML='';
+ results.forEach((p,i)=>{
+ const fam=familyOf(p);
+ const ground=(p.pattern_name||'').split(/ on /i)[1]||'';
+ const dot=swatchHex(p);
+ const el=document.createElement('div');
+ el.className='board';
+ el.style.animationDelay=(i*70)+'ms';
+ el.innerHTML=`
+ <div class="frame">
+ <img class="swatch" loading="lazy" src="${p.image}" alt="${esc(p.pattern_name)}"
+ onerror="this.style.opacity=.4;this.alt='image unavailable'">
+ </div>
+ <div class="placard">
+ <div class="pname">${esc(fam)}</div>
+ <div class="pmeta"><span class="dot" style="background:${dot}"></span>${esc((ground||p.vendor||'China Seas').trim())}</div>
+ <a href="${p.store_url||'#'}" target="_blank" rel="noopener">View this design →</a>
+ </div>`;
+ wrap.appendChild(el);
+ });
+ wrap.scrollTop=0;
+}
+
+/* pick a representative swatch hex for a product from its color words */
+function swatchHex(p){
+ const n=(p.pattern_name||'').toLowerCase();
+ for(const [w,[,hex]] of Object.entries(COLOR_WORDS)){ if(n.includes(w)) return hex; }
+ return '#cfc7b8';
+}
+
+/* ---- transcript ---- */
+function addMsg(role,html){
+ const c=document.getElementById('convo');
+ const m=document.createElement('div'); m.className='msg '+role;
+ if(role==='assist') m.innerHTML=`<div class="who">Concierge</div><div class="body">${html}</div>`;
+ else m.innerHTML=`<div class="body">${html}</div>`;
+ c.appendChild(m); c.scrollTop=c.scrollHeight;
+}
+
+/* ---- handle an ask ---- */
+function ask(query){
+ query=(query||'').trim(); if(!query) return;
+ addMsg('you', esc(query));
+ // tiny "pulling boards" beat
+ setTimeout(()=>{
+ const {results,line}=runQuery(query);
+ addMsg('assist', line);
+ presentBoards(results);
+ }, 260);
+}
+
+/* ---- chips ---- */
+const CHIPS=[
+ {label:'Show me blues', q:'show me blues', c:'#3a5fa0'},
+ {label:'Fresh greens', q:'fresh greens', c:'#4f7a3a'},
+ {label:'Warm neutrals', q:'warm neutrals', c:'#d8ccb0'},
+ {label:'Something with stripes', q:'something with stripes', c:'#9a7a52'},
+ {label:'Soft & pale', q:'soft pale and light', c:'#efe7d2'},
+ {label:'Dark & dramatic', q:'dark and dramatic', c:'#2b2926'},
+ {label:'Like Aga, but darker', q:'like Aga but darker', c:'#2f5d33'},
+ {label:'Pinks & corals', q:'pink coral and shrimp', c:'#e08e72'},
+ {label:'Bali Hai', q:'Bali Hai', c:'#5fb8c4'}
+];
+function buildChips(){
+ const wrap=document.getElementById('chips');
+ CHIPS.forEach(ch=>{
+ const b=document.createElement('div'); b.className='chip';
+ b.innerHTML=`<span class="sw" style="background:${ch.c}"></span>${ch.label}`;
+ b.onclick=()=>ask(ch.q);
+ wrap.appendChild(b);
+ });
+}
+
+/* ---- utils ---- */
+function esc(s){return (s||'').replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c]));}
+function cap(s){return (s||'').charAt(0).toUpperCase()+(s||'').slice(1);}
+
+/* ---- boot ---- */
+async function boot(){
+ buildChips();
+ addMsg('assist', `Welcome — make yourself comfortable. I'm your design concierge for the
+ <em>China Seas</em> atelier. Tell me a colour, a mood, or a pattern you love, and I'll pull the boards.`);
+ try{
+ let r;
+ try{ r=await fetch(API); if(!r.ok) throw new Error(r.status); }
+ catch(_){ r=await fetch(API_FALLBACK); } // dev fallback when not behind the proxy
+ const d=await r.json();
+ PRODUCTS=(d.products||d).filter(p=>p&&p.image);
+ addMsg('assist', `I've ${PRODUCTS.length} boards ready behind me. Where shall we begin?`);
+ }catch(e){
+ addMsg('assist', `I'm having trouble reaching the sample room just now — please try again in a moment.`);
+ console.error('feed error',e);
+ }
+}
+
+document.getElementById('askForm').addEventListener('submit',e=>{
+ e.preventDefault();
+ const inp=document.getElementById('askInput');
+ ask(inp.value); inp.value='';
+});
+
+/* expose for verification harness */
+window.runQuery=runQuery; window.interpret=interpret;
+Object.defineProperty(window,'PRODUCTS',{get:()=>PRODUCTS});
+
+boot();
+</script>
+</body>
+</html>
diff --git a/proto/v6-colorriver.html b/proto/v6-colorriver.html
new file mode 100644
index 0000000..ff9225d
--- /dev/null
+++ b/proto/v6-colorriver.html
@@ -0,0 +1,547 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="utf-8" />
+<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
+<title>COLOR RIVER · China Seas</title>
+<style>
+ :root { --ink:#0d0c0f; --paper:#f4f1ea; }
+ * { box-sizing:border-box; margin:0; padding:0; }
+ html,body { height:100%; overflow:hidden; background:#07060a; color:var(--paper);
+ font-family:"Helvetica Neue",Helvetica,Arial,sans-serif; -webkit-font-smoothing:antialiased; }
+ #app { position:fixed; inset:0; }
+ canvas { display:block; }
+
+ /* ---- chrome / overlay ---- */
+ .masthead { position:fixed; top:0; left:0; right:0; z-index:20; padding:22px 30px 40px;
+ pointer-events:none;
+ background:linear-gradient(180deg, rgba(7,6,10,.85) 0%, rgba(7,6,10,0) 100%); }
+ .masthead .kicker { font-size:11px; letter-spacing:.42em; text-transform:uppercase;
+ color:#bdb7c8; font-weight:600; }
+ .masthead h1 { font-size:clamp(28px,5vw,52px); font-weight:300; letter-spacing:.02em;
+ line-height:.96; margin-top:6px; }
+ .masthead h1 b { font-weight:700; }
+ .masthead .sub { margin-top:8px; font-size:12.5px; letter-spacing:.04em; color:#9b96a6;
+ max-width:520px; }
+
+ /* hue navigation rail */
+ #rail { position:fixed; left:0; right:0; bottom:0; z-index:20; height:64px;
+ display:flex; align-items:stretch; pointer-events:auto; cursor:pointer;
+ background:linear-gradient(0deg, rgba(7,6,10,.92), rgba(7,6,10,.35) 70%, transparent); }
+ #rail .band { flex:1; position:relative; }
+ #rail .tick { position:absolute; bottom:8px; left:50%; transform:translateX(-50%);
+ font-size:9.5px; letter-spacing:.18em; text-transform:uppercase; color:rgba(255,255,255,.62);
+ white-space:nowrap; font-weight:600; text-shadow:0 1px 3px rgba(0,0,0,.8); }
+ #railhandle { position:absolute; top:0; height:100%; width:3px; background:#fff;
+ box-shadow:0 0 16px 3px rgba(255,255,255,.7); pointer-events:none; transition:left .08s linear; }
+
+ .hint { position:fixed; bottom:74px; left:50%; transform:translateX(-50%); z-index:20;
+ font-size:11px; letter-spacing:.28em; text-transform:uppercase; color:rgba(255,255,255,.5);
+ pointer-events:none; transition:opacity .5s; }
+
+ /* loading */
+ #boot { position:fixed; inset:0; z-index:50; display:flex; flex-direction:column;
+ align-items:center; justify-content:center; background:#07060a; transition:opacity .8s; }
+ #boot .ring { width:46px; height:46px; border-radius:50%;
+ border:2px solid rgba(255,255,255,.15); border-top-color:#fff; animation:spin 1s linear infinite; }
+ #boot .lbl { margin-top:18px; font-size:11px; letter-spacing:.34em; text-transform:uppercase; color:#8c8796; }
+ @keyframes spin { to { transform:rotate(360deg); } }
+
+ /* focus detail card */
+ #detail { position:fixed; z-index:30; right:34px; top:50%; transform:translateY(-50%) translateX(40px);
+ width:min(340px,40vw); background:rgba(15,13,18,.74); backdrop-filter:blur(18px);
+ -webkit-backdrop-filter:blur(18px); border:1px solid rgba(255,255,255,.1);
+ border-radius:4px; padding:24px 24px 26px; opacity:0; pointer-events:none;
+ transition:opacity .45s ease, transform .45s cubic-bezier(.2,.8,.2,1); }
+ #detail.show { opacity:1; transform:translateY(-50%) translateX(0); pointer-events:auto; }
+ #detail .swatchchip { width:38px; height:38px; border-radius:50%; border:1px solid rgba(255,255,255,.2);
+ margin-bottom:14px; box-shadow:0 4px 18px rgba(0,0,0,.5); }
+ #detail .pname { font-size:21px; font-weight:600; line-height:1.12; letter-spacing:.01em; }
+ #detail .pcollection { margin-top:4px; font-size:11px; letter-spacing:.26em; text-transform:uppercase; color:#a7a1b3; }
+ #detail .specrow { display:flex; justify-content:space-between; padding:9px 0;
+ border-bottom:1px solid rgba(255,255,255,.08); font-size:12.5px; }
+ #detail .specrow:first-of-type { margin-top:16px; border-top:1px solid rgba(255,255,255,.08); }
+ #detail .specrow .k { color:#8e8898; letter-spacing:.04em; }
+ #detail .specrow .v { color:#efece5; text-align:right; }
+ #detail a.cta { display:block; margin-top:18px; text-align:center; padding:12px;
+ background:var(--paper); color:#0d0c0f; font-size:11px; letter-spacing:.22em; text-transform:uppercase;
+ font-weight:700; text-decoration:none; border-radius:2px; transition:transform .15s, background .15s; }
+ #detail a.cta:hover { transform:translateY(-1px); background:#fff; }
+ #detail .closex { position:absolute; top:12px; right:14px; font-size:18px; color:#9b96a6;
+ cursor:pointer; line-height:1; }
+ #detail .closex:hover { color:#fff; }
+
+ @media (max-width:720px){
+ #detail { right:14px; left:14px; width:auto; top:auto; bottom:84px; transform:translateY(20px); }
+ #detail.show { transform:translateY(0); }
+ .masthead .sub { display:none; }
+ }
+</style>
+</head>
+<body>
+<div id="app"></div>
+
+<div class="masthead">
+ <div class="kicker">Designer Wallcoverings · Shop by Color</div>
+ <h1>COLOR <b>RIVER</b></h1>
+ <div class="sub">Every China Seas pattern set adrift on a single current of hue — from warm corals through
+ jungle greens into deep indigos. Scroll or drag to travel downstream. Tap a swatch to surface it.</div>
+</div>
+
+<div id="rail" title="Jump to a hue"></div>
+<div id="railhandle"></div>
+<div class="hint">scroll · drag · click a swatch</div>
+
+<div id="detail">
+ <div class="closex">×</div>
+ <div class="swatchchip" id="d_chip"></div>
+ <div class="pname" id="d_name">—</div>
+ <div class="pcollection" id="d_coll">China Seas</div>
+ <div class="specrow"><span class="k">Dimensions</span><span class="v" id="d_dim">27" × 27"</span></div>
+ <div class="specrow"><span class="k">Repeat</span><span class="v" id="d_rep">—</span></div>
+ <div class="specrow"><span class="k">Match</span><span class="v" id="d_match">—</span></div>
+ <div class="specrow"><span class="k">Finish</span><span class="v" id="d_finish">—</span></div>
+ <div class="specrow"><span class="k">Material</span><span class="v" id="d_mat">—</span></div>
+ <a class="cta" id="d_cta" href="#" target="_blank" rel="noopener noreferrer">View Pattern →</a>
+</div>
+
+<div id="boot"><div class="ring"></div><div class="lbl" id="bootlbl">Gathering the current…</div></div>
+
+<script src="./three.min.js"></script>
+<script>
+"use strict";
+(function () {
+ const API = "/api/showroom/products?limit=120";
+ // ---- helpers -----------------------------------------------------------
+ const TAU = Math.PI * 2;
+ const clamp = (v,a,b)=>Math.max(a,Math.min(b,v));
+ const lerp = (a,b,t)=>a+(b-a)*t;
+
+ // rgb -> hsl, returns [h(0..1), s(0..1), l(0..1)]
+ function rgb2hsl(r,g,b){
+ r/=255; g/=255; b/=255;
+ const mx=Math.max(r,g,b), mn=Math.min(r,g,b); let h=0,s=0; const l=(mx+mn)/2;
+ if(mx!==mn){ const d=mx-mn; s=l>0.5?d/(2-mx-mn):d/(mx+mn);
+ switch(mx){ case r:h=(g-b)/d+(g<b?6:0);break; case g:h=(b-r)/d+2;break; default:h=(r-g)/d+4; }
+ h/=6; }
+ return [h,s,l];
+ }
+ function hsl2css(h,s,l){
+ // h 0..1 -> css hsl string
+ return `hsl(${(h*360).toFixed(0)} ${(s*100).toFixed(0)}% ${(l*100).toFixed(0)}%)`;
+ }
+
+ // ---- extract a representative hue from an image ------------------------
+ // Strategy: downscale to 24x24, bin pixels into hue buckets weighted by
+ // saturation (so neutral pinstripe grounds don't dominate). The "color" of a
+ // China Seas pattern is its INK, not its near-white paper ground.
+ const huecanvas = document.createElement("canvas");
+ huecanvas.width = huecanvas.height = 24;
+ const hctx = huecanvas.getContext("2d", { willReadFrequently:true });
+
+ function analyzeImage(img){
+ try {
+ hctx.clearRect(0,0,24,24);
+ hctx.drawImage(img,0,0,24,24);
+ const data = hctx.getImageData(0,0,24,24).data;
+ const BINS = 36;
+ const hueWeight = new Float64Array(BINS);
+ const hueSatSum = new Float64Array(BINS);
+ const hueLightSum = new Float64Array(BINS);
+ let satWeightTotal = 0, lAcc = 0, n = 0, neutralL = 0, neutralW = 0;
+ for(let i=0;i<data.length;i+=4){
+ const r=data[i],g=data[i+1],b=data[i+2];
+ const [h,s,l]=rgb2hsl(r,g,b);
+ n++; lAcc += l;
+ if(s < 0.12){ neutralL += l; neutralW++; continue; } // skip near-neutral for hue
+ // weight chromatic pixels by saturation, de-emphasise the near-white ground
+ const w = s * (1 - Math.abs(l-0.5)*0.7);
+ const bin = Math.min(BINS-1, Math.floor(h*BINS));
+ hueWeight[bin] += w;
+ hueSatSum[bin] += s*w;
+ hueLightSum[bin] += l*w;
+ satWeightTotal += w;
+ }
+ // pick dominant chromatic hue bin
+ let best=-1, bestW=0;
+ for(let k=0;k<BINS;k++){ if(hueWeight[k]>bestW){ bestW=hueWeight[k]; best=k; } }
+ const avgL = n? lAcc/n : 0.6;
+ if(best<0 || satWeightTotal < 0.02){
+ // genuinely neutral pattern (greys / black & white). Park it as low-sat,
+ // hue mapped from lightness so neutrals form their own greyscale eddy at the river's tail.
+ const nl = neutralW? neutralL/neutralW : avgL;
+ return { hue: 0, sat: 0.0, light: nl, neutral:true };
+ }
+ const hue = (best + 0.5)/BINS; // bin centre
+ const sat = clamp(hueSatSum[best]/bestW, 0, 1);
+ const light = clamp(hueLightSum[best]/bestW, 0, 1);
+ return { hue, sat, light, neutral:false };
+ } catch(e){
+ return { hue: 0, sat: 0, light: 0.6, neutral:true };
+ }
+ }
+
+ // ---- THREE scene -------------------------------------------------------
+ const app = document.getElementById("app");
+ const W = ()=>window.innerWidth, H = ()=>window.innerHeight;
+
+ const renderer = new THREE.WebGLRenderer({ antialias:true, alpha:false });
+ renderer.setPixelRatio(Math.min(window.devicePixelRatio||1, 2));
+ renderer.setSize(W(), H());
+ renderer.setClearColor(0x07060a, 1);
+ app.appendChild(renderer.domElement);
+
+ const scene = new THREE.Scene();
+ scene.fog = new THREE.FogExp2(0x07060a, 0.018);
+
+ const camera = new THREE.PerspectiveCamera(52, W()/H(), 0.1, 400);
+ camera.position.set(0, 0.4, 18);
+
+ scene.add(new THREE.AmbientLight(0xffffff, 0.92));
+ const key = new THREE.DirectionalLight(0xffffff, 0.55); key.position.set(4,8,12); scene.add(key);
+
+ // --- the riverbed: a long horizontal gradient plane spanning the full spectrum ---
+ const RIVER_LEN = 132; // world units the ribbon spans in X (tighter = denser current)
+ const X0 = -RIVER_LEN/2;
+ function hueToX(h){ return X0 + h*RIVER_LEN; }
+ // undulation of the current — bigger amplitude so the flow visibly courses
+ function flowY(h){ return Math.sin(h*TAU*2.4)*3.6 + Math.sin(h*TAU*5.1+1.3)*1.5; }
+
+ (function buildRiverbed(){
+ const seg = 160;
+ const geo = new THREE.PlaneGeometry(RIVER_LEN+30, 22, seg, 1);
+ const cols = [];
+ const pos = geo.attributes.position;
+ const col = new THREE.Color();
+ for(let i=0;i<pos.count;i++){
+ const x = pos.getX(i);
+ const t = clamp((x - X0)/RIVER_LEN, 0, 1);
+ const yTop = pos.getY(i) > 0;
+ // riverbed glows the spectrum hue, darker top/bottom for depth
+ col.setHSL(t, 0.55, yTop?0.05:0.14);
+ cols.push(col.r,col.g,col.b);
+ }
+ geo.setAttribute("color", new THREE.Float32BufferAttribute(cols,3));
+ const mat = new THREE.MeshBasicMaterial({ vertexColors:true });
+ const mesh = new THREE.Mesh(geo, mat);
+ mesh.position.set(0, 0, -8);
+ scene.add(mesh);
+
+ // a brighter thin "waterline" gradient strip behind the swatches
+ const geo2 = new THREE.PlaneGeometry(RIVER_LEN+30, 7.5, seg, 1);
+ const cols2=[];
+ const p2=geo2.attributes.position;
+ for(let i=0;i<p2.count;i++){
+ const x=p2.getX(i); const t=clamp((x-X0)/RIVER_LEN,0,1);
+ col.setHSL(t,0.7,0.42); cols2.push(col.r,col.g,col.b);
+ }
+ geo2.setAttribute("color", new THREE.Float32BufferAttribute(cols2,3));
+ const m2 = new THREE.Mesh(geo2, new THREE.MeshBasicMaterial({vertexColors:true, transparent:true, opacity:0.5}));
+ m2.position.set(0,0,-5.5);
+ scene.add(m2);
+ })();
+
+ // group that holds all swatches and pans in X
+ const flow = new THREE.Group();
+ scene.add(flow);
+
+ const swatchGeo = new THREE.PlaneGeometry(1,1);
+ const loader = new THREE.TextureLoader();
+ loader.crossOrigin = "anonymous";
+
+ const swatches = []; // { mesh, base{x,y,z,scale}, data, hue }
+ let focused = null;
+
+ // raycast picking
+ const ray = new THREE.Raycaster();
+ const ndc = new THREE.Vector2();
+ const pickables = [];
+
+ // ---- build a swatch mesh ----
+ function addSwatch(d, analysis, tex){
+ const { hue, sat, light, neutral } = analysis;
+ // X by hue; neutrals pushed to the far warm end as a greyscale eddy
+ const hpos = neutral ? 0.985 : hue;
+ const x = hueToX(hpos) + (Math.random()-0.5)*1.3; // jitter so equal hues don't z-fight
+ const baseY = flowY(hpos);
+ // saturation lifts the swatch up onto the crest; lightness pushes depth
+ const y = baseY + (sat-0.4)*2.6 + (Math.random()-0.5)*1.1;
+ const z = (light-0.5)*3.2 + (Math.random()-0.5)*1.4;
+ const scale = lerp(1.7, 2.7, sat) ; // punchier hues bigger
+
+ const mat = new THREE.MeshBasicMaterial({ map:tex, toneMapped:false });
+ const mesh = new THREE.Mesh(swatchGeo, mat);
+ mesh.position.set(x, y, z);
+ mesh.scale.setScalar(scale);
+ mesh.userData.idx = swatches.length;
+ flow.add(mesh);
+ pickables.push(mesh);
+
+ swatches.push({ mesh, base:{x,y,z,scale}, data:d, hue:hpos, sat, light });
+ }
+
+ // ---- data load + texture pipeline -------------------------------------
+ const bootlbl = document.getElementById("bootlbl");
+ let loadedCount = 0, totalCount = 0;
+
+ function imgURL(u){
+ // CDN sends ACAO:* so direct load works & is canvas-readable; fall back to proxy for non-cdn.
+ if(/cdn\.shopify\.com/.test(u)) return u;
+ return "/api/proxy/image?url=" + encodeURIComponent(u);
+ }
+
+ function loadOne(d){
+ return new Promise((resolve)=>{
+ const url = imgURL(d.image);
+ const img = new Image();
+ img.crossOrigin = "anonymous";
+ img.onload = ()=>{
+ const analysis = analyzeImage(img);
+ const tex = new THREE.Texture(img);
+ tex.colorSpace = THREE.SRGBColorSpace || undefined;
+ tex.needsUpdate = true;
+ tex.anisotropy = 4;
+ addSwatch(d, analysis, tex);
+ loadedCount++;
+ bootlbl.textContent = `Sorting ${loadedCount} / ${totalCount} swatches by hue…`;
+ resolve();
+ };
+ img.onerror = ()=>{ loadedCount++; resolve(); };
+ img.src = url;
+ });
+ }
+
+ async function start(){
+ let products = [];
+ try {
+ const res = await fetch(API);
+ const json = await res.json();
+ products = json.products || [];
+ } catch(e){
+ bootlbl.textContent = "Could not reach the catalog.";
+ console.error("fetch failed", e);
+ return;
+ }
+ totalCount = products.length;
+ // load with limited concurrency so we don't stall the main thread
+ const POOL = 8;
+ let cursor = 0;
+ async function worker(){
+ while(cursor < products.length){
+ const d = products[cursor++];
+ await loadOne(d);
+ }
+ }
+ await Promise.all(Array.from({length:POOL}, worker));
+
+ // sort swatches array by hue so rail navigation maps cleanly
+ swatches.sort((a,b)=>a.hue - b.hue);
+
+ // open the river at its DENSEST stretch so the first frame is full of color,
+ // not a half-empty warm head. Sliding window over hue density.
+ if(swatches.length){
+ const win = 0.09; // ~10% of the spectrum visible at once
+ let bestH = swatches[Math.floor(swatches.length/2)].hue, bestN = 0;
+ for(const s of swatches){
+ let n=0; for(const o of swatches){ if(Math.abs(o.hue - s.hue) <= win) n++; }
+ if(n>bestN){ bestN=n; bestH=s.hue; }
+ }
+ pan = targetPan = clamp(-hueToX(bestH), PAN_MIN, PAN_MAX);
+ }
+ window.__RIVER_DONE = true;
+
+ finishBoot();
+ }
+
+ function finishBoot(){
+ const boot = document.getElementById("boot");
+ boot.style.opacity = "0";
+ setTimeout(()=>boot.remove(), 850);
+ buildRail();
+ }
+
+ // ---- hue rail (navigation) --------------------------------------------
+ const HUE_BANDS = [
+ [0.00,"Coral"],[0.06,"Terracotta"],[0.12,"Gold"],[0.22,"Citron"],
+ [0.30,"Jungle"],[0.42,"Viridian"],[0.50,"Teal"],[0.58,"Sky"],
+ [0.66,"Indigo"],[0.74,"Violet"],[0.82,"Plum"],[0.92,"Magenta"]
+ ];
+ function buildRail(){
+ const rail = document.getElementById("rail");
+ rail.innerHTML = "";
+ HUE_BANDS.forEach(([h,name],i)=>{
+ const band = document.createElement("div");
+ band.className = "band";
+ const c1 = hsl2css(h,0.65,0.42), c2 = hsl2css((HUE_BANDS[i+1]?HUE_BANDS[i+1][0]:1),0.65,0.42);
+ band.style.background = `linear-gradient(90deg, ${c1}, ${c2})`;
+ const tk = document.createElement("div"); tk.className="tick"; tk.textContent=name;
+ band.appendChild(tk); rail.appendChild(band);
+ });
+ rail.addEventListener("pointerdown", e=>{
+ const r = rail.getBoundingClientRect();
+ const t = clamp((e.clientX - r.left)/r.width, 0, 1);
+ targetPan = -hueToX(t); // center that hue
+ });
+ }
+
+ // ---- camera pan / travel ----------------------------------------------
+ // We pan the flow group in X. pan ranges so first/last swatch stay reachable.
+ let pan = 0, targetPan = -hueToX(0.0); // start at the warm head of the river
+ const PAN_MIN = -hueToX(1.0) , PAN_MAX = -hueToX(0.0);
+ function clampPan(p){ return clamp(p, PAN_MIN, PAN_MAX); }
+
+ // wheel
+ window.addEventListener("wheel", (e)=>{
+ e.preventDefault();
+ const dom = (Math.abs(e.deltaX) > Math.abs(e.deltaY)) ? e.deltaX : e.deltaY;
+ targetPan = clampPan(targetPan - dom*0.045);
+ if(focused) defocus();
+ }, { passive:false });
+
+ // drag
+ let dragging=false, dragStartX=0, panStart=0, moved=0;
+ renderer.domElement.addEventListener("pointerdown",(e)=>{
+ dragging=true; moved=0; dragStartX=e.clientX; panStart=targetPan;
+ renderer.domElement.setPointerCapture(e.pointerId);
+ });
+ renderer.domElement.addEventListener("pointermove",(e)=>{
+ if(!dragging) return;
+ const dx = e.clientX - dragStartX; moved += Math.abs(dx);
+ targetPan = clampPan(panStart + dx*0.06);
+ });
+ renderer.domElement.addEventListener("pointerup",(e)=>{
+ dragging=false;
+ if(moved < 6){ tryPick(e); } // treat as click
+ });
+
+ // touch fallback handled by pointer events above
+
+ // ---- picking / focus ---------------------------------------------------
+ function tryPick(e){
+ const r = renderer.domElement.getBoundingClientRect();
+ ndc.x = ((e.clientX-r.left)/r.width)*2 - 1;
+ ndc.y = -((e.clientY-r.top)/r.height)*2 + 1;
+ ray.setFromCamera(ndc, camera);
+ const hits = ray.intersectObjects(pickables, false);
+ if(hits.length){
+ const mesh = hits[0].object;
+ const sw = swatches.find(s=>s.mesh===mesh);
+ if(sw) focus(sw);
+ } else if(focused){ defocus(); }
+ }
+
+ function focus(sw){
+ focused = sw;
+ // bring its hue to center
+ targetPan = clampPan(-sw.base.x);
+ showDetail(sw);
+ }
+ function defocus(){
+ focused = null;
+ hideDetail();
+ }
+
+ // detail card
+ const detail = document.getElementById("detail");
+ document.querySelector("#detail .closex").addEventListener("click", defocus);
+ function showDetail(sw){
+ const d = sw.data;
+ document.getElementById("d_name").textContent = d.pattern_name || d.title || "Untitled";
+ document.getElementById("d_coll").textContent = (d.collection || d.vendor || "China Seas");
+ const w = d.width||27;
+ document.getElementById("d_dim").textContent = `${w}" × ${w}"`;
+ document.getElementById("d_rep").textContent = d.repeat ? `${d.repeat}"` : "—";
+ document.getElementById("d_match").textContent = d.match_type || "—";
+ document.getElementById("d_finish").textContent = d.finish || "—";
+ document.getElementById("d_mat").textContent = d.material || "—";
+ const chip = document.getElementById("d_chip");
+ chip.style.background = sw.sat>0.02 ? hsl2css(sw.hue,sw.sat,sw.light) : hsl2css(0,0,sw.light||0.5);
+ const cta = document.getElementById("d_cta");
+ if(d.store_url){ cta.href = d.store_url; cta.style.display="block"; }
+ else cta.style.display="none";
+ detail.classList.add("show");
+ }
+ function hideDetail(){ detail.classList.remove("show"); }
+
+ // ---- resize ------------------------------------------------------------
+ window.addEventListener("resize", ()=>{
+ camera.aspect = W()/H(); camera.updateProjectionMatrix();
+ renderer.setSize(W(), H());
+ });
+
+ // ---- render loop -------------------------------------------------------
+ const railhandle = document.getElementById("railhandle");
+ const hint = document.querySelector(".hint");
+ let hintFaded = false;
+ let t0 = performance.now();
+
+ function tick(now){
+ const dt = Math.min((now - t0)/1000, 0.05); t0 = now;
+ pan += (targetPan - pan) * Math.min(1, dt*6.5);
+ flow.position.x = pan;
+
+ // gentle drift of the whole current so it feels alive
+ const breathe = Math.sin(now*0.0004)*0.15;
+ flow.position.y = breathe;
+
+ // billboard + focus animation
+ for(const sw of swatches){
+ const m = sw.mesh;
+ m.lookAt(camera.position.x, camera.position.y, m.position.z + 10); // face camera (yaw only-ish)
+ m.rotation.x = 0; // keep upright
+ let targetScale = sw.base.scale;
+ let tz = sw.base.z, ty = sw.base.y;
+ if(sw === focused){
+ targetScale = 6.4;
+ tz = sw.base.z + 9.5; // float toward camera
+ ty = sw.base.y*0.3 + 0.6;
+ }
+ m.scale.x += (targetScale - m.scale.x)*Math.min(1,dt*8);
+ m.scale.y = m.scale.x;
+ m.position.z += (tz - m.position.z)*Math.min(1,dt*8);
+ m.position.y += (ty - m.position.y)*Math.min(1,dt*8);
+ // dim non-focused when something is focused
+ const wantOp = (focused && sw!==focused) ? 0.28 : 1.0;
+ if(m.material.opacity===undefined) m.material.opacity=1;
+ m.material.transparent = wantOp<1;
+ m.material.opacity += (wantOp - m.material.opacity)*Math.min(1,dt*8);
+ }
+
+ // rail handle position = current center hue
+ const centerHue = clamp((-pan - X0)/RIVER_LEN, 0, 1);
+ railhandle.style.left = (centerHue*100).toFixed(2)+"%";
+
+ if(!hintFaded && Math.abs(targetPan - PAN_MAX) > 4){
+ hint.style.opacity = "0"; hintFaded = true;
+ }
+
+ renderer.render(scene, camera);
+ requestAnimationFrame(tick);
+ }
+ requestAnimationFrame(tick);
+
+ // expose a tiny probe for headless verification
+ window.__RIVER__ = {
+ ready:()=>swatches.length,
+ done:()=>!!window.__RIVER_DONE,
+ sortedByHue:()=>{
+ // visual sort: swatch world-X must increase monotonically with hue
+ const arr = swatches.slice().sort((a,b)=>a.hue-b.hue);
+ for(let i=1;i<arr.length;i++){ if(arr[i].hue < arr[i-1].hue-0.001) return false; }
+ return true;
+ },
+ // proves layout is spatial-by-hue: mesh X strictly tracks hue
+ spatialSorted:()=>{
+ const arr = swatches.slice().sort((a,b)=>a.base.x-b.base.x);
+ for(let i=1;i<arr.length;i++){ if(arr[i].hue < arr[i-1].hue - 0.06) return false; }
+ return true;
+ },
+ hues:()=>swatches.slice().sort((a,b)=>a.hue-b.hue).map(s=>+s.hue.toFixed(3)),
+ count:()=>swatches.length
+ };
+
+ start();
+})();
+</script>
+</body>
+</html>
diff --git a/proto/v8-swipe.html b/proto/v8-swipe.html
new file mode 100644
index 0000000..036ac6e
--- /dev/null
+++ b/proto/v8-swipe.html
@@ -0,0 +1,602 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8" />
+<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
+<title>Swipe Tower — Curated Wallcovering Discovery</title>
+<style>
+ /* ─────────────────────────── TOKENS ─────────────────────────── */
+ :root{
+ --bg: #f4f1ec; /* warm gallery linen */
+ --bg-deep: #ebe6dd;
+ --ink: #1c1a17; /* near-black warm ink */
+ --ink-soft: #6b655c;
+ --line: #d8d1c4;
+ --card: #ffffff;
+ --keep: #2f6b4f; /* deep botanical green */
+ --keep-tint: #e7f0ea;
+ --skip: #8a8178; /* muted stone */
+ --skip-tint: #efece6;
+ --gold: #b08a4f;
+ --shadow: 0 24px 60px -24px rgba(28,26,23,.42), 0 4px 14px -6px rgba(28,26,23,.22);
+ --serif: "Hoefler Text", "Iowan Old Style", "Palatino Linotype", Georgia, "Times New Roman", serif;
+ --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
+ }
+ *{ box-sizing:border-box; -webkit-tap-highlight-color:transparent; }
+ html,body{ height:100%; margin:0; }
+ body{
+ background:
+ radial-gradient(120% 80% at 50% -10%, var(--bg) 0%, var(--bg-deep) 70%, #e3ddd2 100%);
+ color:var(--ink);
+ font-family:var(--sans);
+ overflow:hidden;
+ -webkit-font-smoothing:antialiased;
+ }
+
+ /* ─────────────────────────── LAYOUT ─────────────────────────── */
+ .app{
+ height:100dvh;
+ display:grid;
+ grid-template-columns: 1fr 300px;
+ grid-template-rows: auto 1fr;
+ grid-template-areas:
+ "head head"
+ "stage tray";
+ max-width:1240px; margin:0 auto;
+ }
+ @media (max-width: 860px){
+ .app{ grid-template-columns:1fr; grid-template-rows:auto 1fr auto; grid-template-areas:"head" "stage" "tray"; }
+ }
+
+ /* ─────────────────────────── HEADER ─────────────────────────── */
+ header{
+ grid-area:head;
+ padding:22px 30px 14px;
+ display:flex; align-items:baseline; justify-content:space-between; gap:18px;
+ border-bottom:1px solid var(--line);
+ }
+ .brand{ display:flex; flex-direction:column; gap:3px; }
+ .brand h1{
+ font-family:var(--serif); font-weight:500; font-size:26px; letter-spacing:.5px;
+ margin:0; line-height:1;
+ }
+ .brand .sub{
+ font-size:11px; letter-spacing:.32em; text-transform:uppercase; color:var(--ink-soft);
+ }
+ .counter{ text-align:right; font-size:13px; color:var(--ink-soft); line-height:1.5; }
+ .counter b{ color:var(--ink); font-variant-numeric:tabular-nums; }
+
+ /* ─────────────────────────── STAGE ─────────────────────────── */
+ .stage{
+ grid-area:stage;
+ position:relative;
+ display:flex; flex-direction:column; align-items:center; justify-content:center;
+ padding:24px 20px 18px;
+ min-height:0;
+ }
+ .deck{
+ position:relative;
+ width:min(78vmin, 460px);
+ aspect-ratio:1/1;
+ flex:0 0 auto;
+ }
+
+ /* card */
+ .card{
+ position:absolute; inset:0;
+ background:var(--card);
+ border-radius:14px;
+ box-shadow:var(--shadow);
+ overflow:hidden;
+ display:flex; flex-direction:column;
+ transform-origin:center bottom;
+ will-change:transform, opacity;
+ user-select:none;
+ }
+ .card.behind-1{ transform:translateY(14px) scale(.955); }
+ .card.behind-2{ transform:translateY(26px) scale(.91); }
+ .card .swatch{
+ position:relative;
+ flex:1 1 auto;
+ background:var(--bg-deep);
+ background-size:cover; background-position:center;
+ min-height:0;
+ }
+ .card .swatch::after{ /* hairline frame inside swatch */
+ content:""; position:absolute; inset:0;
+ box-shadow: inset 0 0 0 1px rgba(28,26,23,.06);
+ pointer-events:none;
+ }
+ .card .meta{
+ flex:0 0 auto;
+ padding:14px 18px 16px;
+ border-top:1px solid var(--line);
+ background:linear-gradient(180deg,#fff, #fbf9f5);
+ }
+ .card .pname{
+ font-family:var(--serif); font-size:21px; line-height:1.15; font-weight:500;
+ margin:0 0 4px; color:var(--ink);
+ display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;
+ }
+ .card .pvendor{ font-size:12px; letter-spacing:.16em; text-transform:uppercase; color:var(--gold); margin-bottom:8px; }
+ .card .spec{ display:flex; gap:14px; font-size:12.5px; color:var(--ink-soft); flex-wrap:wrap; }
+ .card .spec span{ display:inline-flex; align-items:center; gap:5px; }
+ .card .spec .dot{ width:5px; height:5px; border-radius:50%; background:var(--line); }
+
+ /* swipe verdict stamps */
+ .stamp{
+ position:absolute; top:26px; padding:8px 18px;
+ font-family:var(--serif); font-size:24px; letter-spacing:.06em;
+ border:2.5px solid currentColor; border-radius:8px;
+ opacity:0; transition:opacity .08s; pointer-events:none;
+ background:rgba(255,255,255,.78); backdrop-filter:blur(2px);
+ }
+ .stamp.keep{ left:24px; color:var(--keep); transform:rotate(-11deg); }
+ .stamp.skip{ right:24px; color:var(--skip); transform:rotate(11deg); }
+
+ /* empty state */
+ .empty{
+ position:absolute; inset:0; display:none; flex-direction:column;
+ align-items:center; justify-content:center; text-align:center; gap:14px; padding:30px;
+ }
+ .empty.show{ display:flex; }
+ .empty h2{ font-family:var(--serif); font-weight:500; font-size:24px; margin:0; }
+ .empty p{ color:var(--ink-soft); font-size:14px; margin:0; max-width:32ch; line-height:1.5; }
+ .empty button{
+ margin-top:8px; font:inherit; font-size:14px; letter-spacing:.04em;
+ border:1px solid var(--ink); background:none; color:var(--ink);
+ padding:11px 24px; border-radius:30px; cursor:pointer;
+ }
+ .empty button:hover{ background:var(--ink); color:#fff; }
+
+ /* ─────────────────────────── ACTION BAR ─────────────────────────── */
+ .actions{
+ margin-top:22px; display:flex; align-items:center; gap:26px;
+ }
+ .act{
+ --c:var(--ink);
+ width:80px; height:80px; border-radius:50%;
+ border:2px solid var(--line); background:#fff;
+ display:flex; align-items:center; justify-content:center;
+ cursor:pointer; transition:transform .12s ease, box-shadow .2s, border-color .2s, background .2s;
+ box-shadow:0 6px 18px -8px rgba(28,26,23,.3);
+ position:relative;
+ }
+ .act svg{ width:34px; height:34px; }
+ .act:hover{ transform:translateY(-3px); }
+ .act:active{ transform:scale(.92); }
+ .act .lbl{
+ position:absolute; bottom:-24px; left:50%; transform:translateX(-50%);
+ font-size:10.5px; letter-spacing:.22em; text-transform:uppercase; color:var(--ink-soft);
+ white-space:nowrap;
+ }
+ .act.skip{ color:var(--skip); }
+ .act.skip:hover{ border-color:var(--skip); background:var(--skip-tint); }
+ .act.keep{ color:var(--keep); width:96px; height:96px; }
+ .act.keep svg{ width:42px; height:42px; }
+ .act.keep:hover{ border-color:var(--keep); background:var(--keep-tint); }
+ .act.undo{ width:58px; height:58px; color:var(--ink-soft); }
+ .act.undo svg{ width:24px; height:24px; }
+ .act.undo:disabled{ opacity:.32; cursor:default; transform:none; }
+ .hint{ margin-top:34px; font-size:11.5px; letter-spacing:.04em; color:var(--ink-soft); text-align:center; }
+ .hint kbd{
+ font-family:var(--sans); font-size:11px; background:#fff; border:1px solid var(--line);
+ border-bottom-width:2px; border-radius:5px; padding:1px 7px; color:var(--ink);
+ }
+
+ /* ─────────────────────────── FAVORITES TRAY ─────────────────────────── */
+ .tray{
+ grid-area:tray;
+ border-left:1px solid var(--line);
+ background:linear-gradient(180deg, #faf8f3, #f3efe7);
+ display:flex; flex-direction:column; min-height:0;
+ }
+ @media (max-width:860px){ .tray{ border-left:none; border-top:1px solid var(--line); max-height:38vh; } }
+ .tray-head{
+ padding:18px 22px 12px; display:flex; align-items:baseline; justify-content:space-between;
+ border-bottom:1px solid var(--line);
+ }
+ .tray-head h2{ font-family:var(--serif); font-weight:500; font-size:17px; margin:0; letter-spacing:.02em; }
+ .tray-head .ct{
+ font-size:12px; color:#fff; background:var(--keep);
+ min-width:24px; height:24px; padding:0 8px; border-radius:12px;
+ display:inline-flex; align-items:center; justify-content:center; font-variant-numeric:tabular-nums;
+ }
+ .tray-head .ct.zero{ background:var(--line); color:var(--ink-soft); }
+ .tray-list{ flex:1 1 auto; overflow-y:auto; padding:12px 14px 18px; display:flex; flex-direction:column; gap:10px; }
+ .tray-empty{ color:var(--ink-soft); font-size:13px; line-height:1.6; padding:18px 8px; text-align:center; }
+ .tray-empty .heart{ font-size:26px; display:block; margin-bottom:8px; opacity:.4; }
+
+ .fav{
+ display:flex; gap:11px; align-items:center;
+ background:#fff; border:1px solid var(--line); border-radius:10px;
+ padding:8px; position:relative;
+ animation:favIn .42s cubic-bezier(.2,.9,.25,1) both;
+ }
+ @keyframes favIn{ from{ opacity:0; transform:translateX(16px) scale(.96);} to{opacity:1; transform:none;} }
+ .fav .thumb{ width:54px; height:54px; border-radius:7px; background:var(--bg-deep) center/cover; flex:0 0 auto; box-shadow:inset 0 0 0 1px rgba(0,0,0,.05); }
+ .fav .fmeta{ min-width:0; flex:1 1 auto; }
+ .fav .fname{ font-family:var(--serif); font-size:14px; line-height:1.2; margin:0 0 2px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
+ .fav .fvendor{ font-size:10px; letter-spacing:.12em; text-transform:uppercase; color:var(--gold); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
+ .fav .fspec{ font-size:10.5px; color:var(--ink-soft); margin-top:2px; }
+ .fav .rm{
+ position:absolute; top:6px; right:6px; width:20px; height:20px; border-radius:50%;
+ border:none; background:var(--skip-tint); color:var(--skip); cursor:pointer;
+ font-size:13px; line-height:1; display:flex; align-items:center; justify-content:center;
+ opacity:0; transition:opacity .15s;
+ }
+ .fav:hover .rm{ opacity:1; }
+ .tray-foot{ flex:0 0 auto; padding:14px 18px; border-top:1px solid var(--line); }
+ .tray-foot button{
+ width:100%; font:inherit; font-size:13.5px; letter-spacing:.1em; text-transform:uppercase;
+ background:var(--ink); color:#fff; border:none; border-radius:30px; padding:13px; cursor:pointer;
+ transition:opacity .2s; display:flex; align-items:center; justify-content:center; gap:8px;
+ }
+ .tray-foot button:disabled{ opacity:.3; cursor:default; }
+ .tray-foot button:not(:disabled):hover{ background:var(--keep); }
+ .tray-foot .note{ font-size:11px; color:var(--ink-soft); text-align:center; margin:9px 0 0; line-height:1.4; }
+
+ .loading{ position:absolute; inset:0; display:flex; align-items:center; justify-content:center; color:var(--ink-soft); font-size:14px; letter-spacing:.1em; }
+ @media (prefers-reduced-motion: reduce){ *{ animation-duration:.001s !important; transition-duration:.001s !important; } }
+</style>
+</head>
+<body>
+<div class="app">
+ <header>
+ <div class="brand">
+ <h1>Swipe Tower</h1>
+ <div class="sub">Curated Wallcovering Discovery</div>
+ </div>
+ <div class="counter">
+ <div>Reviewed <b id="seenCt">0</b></div>
+ <div><b id="leftCt">0</b> remaining</div>
+ </div>
+ </header>
+
+ <section class="stage">
+ <div class="deck" id="deck">
+ <div class="loading" id="loading">Curating your collection…</div>
+ <div class="empty" id="empty">
+ <h2 id="emptyTitle">That's the collection</h2>
+ <p id="emptyMsg">You've seen every swatch in this curation. Your favorites are saved in the tray.</p>
+ <button id="restartBtn">Start over</button>
+ </div>
+ </div>
+
+ <div class="actions" id="actions" style="visibility:hidden">
+ <button class="act undo" id="undoBtn" title="Undo last (Backspace)" disabled aria-label="Undo last">
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7v6h6"/><path d="M3 13a9 9 0 1 0 3-7.7L3 8"/></svg>
+ </button>
+ <button class="act skip" id="skipBtn" title="Skip (← arrow)" aria-label="Skip this swatch">
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"><path d="M6 6l12 12M18 6L6 18"/></svg>
+ <span class="lbl">Skip</span>
+ </button>
+ <button class="act keep" id="keepBtn" title="Keep (→ arrow)" aria-label="Keep this swatch">
+ <svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 21s-7.5-4.7-10-9.2C.3 8.4 1.9 4.6 5.4 4.1 7.6 3.8 9.5 5 12 7.6 14.5 5 16.4 3.8 18.6 4.1c3.5.5 5.1 4.3 3.4 7.7C19.5 16.3 12 21 12 21z"/></svg>
+ <span class="lbl">Keep</span>
+ </button>
+ </div>
+ <div class="hint" id="hint" style="visibility:hidden">
+ Drag a swatch, tap a button, or use <kbd>←</kbd> skip <kbd>→</kbd> keep <kbd>↩</kbd> undo
+ </div>
+ </section>
+
+ <aside class="tray">
+ <div class="tray-head">
+ <h2>Your Favorites</h2>
+ <span class="ct zero" id="favCt">0</span>
+ </div>
+ <div class="tray-list" id="favList">
+ <div class="tray-empty" id="trayEmpty">
+ <span class="heart">♥</span>
+ Swatches you keep collect here.<br>Build a palette, then request samples.
+ </div>
+ </div>
+ <div class="tray-foot">
+ <button id="sampleBtn" disabled>Request <span id="sampleCt">0</span> Samples</button>
+ <p class="note" id="sampleNote">No order minimum on memo samples.</p>
+ </div>
+ </aside>
+</div>
+
+<script>
+const API = "http://127.0.0.1:7690/api/showroom/products?limit=120";
+const PLACEHOLDER = "data:image/svg+xml;utf8," + encodeURIComponent(
+ '<svg xmlns="http://www.w3.org/2000/svg" width="80" height="80"><rect width="80" height="80" fill="#ebe6dd"/></svg>');
+
+/* Offline fallback so the mechanic is demonstrable even when the showroom API
+ is unreachable (e.g. opened as a bare file:// with CORS blocking the fetch).
+ Generated swatch tiles via inline SVG so no external image dependency. */
+function swatchSVG(bg, fg, motif){
+ const m = {
+ trellis:`<path d="M0 50 L50 0 L100 50 L50 100 Z" fill="none" stroke="${fg}" stroke-width="3"/><path d="M50 0 L100 50 L50 100 L0 50 Z" fill="none" stroke="${fg}" stroke-width="3"/><circle cx="50" cy="50" r="6" fill="${fg}"/>`,
+ leaf:`<path d="M50 8 C30 30 30 70 50 92 C70 70 70 30 50 8 Z" fill="${fg}" opacity=".85"/><path d="M50 8 L50 92" stroke="${bg}" stroke-width="2"/>`,
+ floral:`<g fill="${fg}"><circle cx="50" cy="32" r="11"/><circle cx="32" cy="55" r="11"/><circle cx="68" cy="55" r="11"/><circle cx="50" cy="72" r="11"/><circle cx="50" cy="52" r="7" fill="${bg}"/></g>`,
+ stripe:`<rect x="14" width="14" height="100" fill="${fg}" opacity=".8"/><rect x="58" width="14" height="100" fill="${fg}" opacity=".8"/>`,
+ geo:`<rect x="20" y="20" width="60" height="60" fill="none" stroke="${fg}" stroke-width="4"/><rect x="35" y="35" width="30" height="30" fill="${fg}" opacity=".7"/>`
+ }[motif] || '';
+ const svg=`<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200" viewBox="0 0 100 100"><rect width="100" height="100" fill="${bg}"/>${m}</svg>`;
+ return "data:image/svg+xml;utf8,"+encodeURIComponent(svg);
+}
+const FALLBACK = [
+ ["Aga Jungle on Off-White","China Seas","#f3efe6","#2f6b4f","leaf","Jungle Green"],
+ ["Lyford Trellis — Indigo","Quadrille","#1f3a5f","#e8edf4","trellis","Indigo"],
+ ["Fiorentina — Coral","China Seas","#fbeee6","#c4674e","floral","Coral"],
+ ["Henriot Stripe — Oatmeal","Quadrille","#efe9dc","#8a7a5c","stripe","Oatmeal"],
+ ["Veneto — Charcoal","China Seas","#2b2926","#d8cfbf","geo","Charcoal"],
+ ["Arbre de Matisse — Celadon","Quadrille","#e7efe4","#5e8a6a","leaf","Celadon"],
+ ["Bali Hai — Aqua","China Seas","#e2f1f0","#3a8f93","floral","Aqua"],
+ ["Cap Ferrat — Rose","Quadrille","#f7e9ec","#b06a82","trellis","Rose"],
+ ["Sigourney — Mustard","China Seas","#f4ead0","#c79a3a","stripe","Mustard"],
+ ["Persia — Ink","Quadrille","#1c1f2b","#cfc6b4","geo","Ink"],
+ ["Lulu — Blush","China Seas","#fbeef0","#d398a6","floral","Blush"],
+ ["Kazak — Terracotta","Quadrille","#f0e4dc","#b15a3c","trellis","Terracotta"],
+].map((r,i)=>({
+ pattern_name:r[0], vendor:r[1], collection:r[1],
+ image:swatchSVG(r[2],r[3],r[4]), color_name:r[5],
+ width:27, repeat:27, sku:"DEMO-"+(100+i)
+}));
+
+let queue = []; // remaining products (head = top card)
+let history = []; // {item, verdict} for undo
+let favorites = []; // kept items
+let seen = 0;
+
+const $ = id => document.getElementById(id);
+const deck = $("deck");
+
+/* ── derive a short color/colorway word from pattern_name (hue is null in data) ── */
+const COLORWORDS = ["white","off-white","ivory","cream","oatmeal","alabaster","greige","grey","gray","charcoal","black","ink","navy","blue","indigo","teal","aqua","celadon","sage","green","jungle","olive","moss","gold","ochre","mustard","yellow","apricot","coral","blush","pink","rose","red","crimson","terracotta","rust","brown","taupe","tan","sand","bronze","copper","silver","plum","purple","lavender","aubergine"];
+function colorwordFor(p){
+ if(p.color_name) return p.color_name;
+ const name = (p.pattern_name||"").toLowerCase();
+ const hit = COLORWORDS.find(c => name.includes(c));
+ if(hit) return hit.replace(/\b\w/g, m=>m.toUpperCase());
+ if(p.color) return p.color;
+ return null;
+}
+function specLine(p){
+ const w = p.width || 27, r = p.repeat || 27;
+ const parts = [`${w}″ wide`];
+ if(r) parts.push(`${r}″ repeat`);
+ return parts;
+}
+
+/* ─────────────────────────── CARD RENDER ─────────────────────────── */
+function makeCard(p, depth){
+ const card = document.createElement("div");
+ card.className = "card" + (depth===1?" behind-1":depth===2?" behind-2":"");
+ card.dataset.depth = depth;
+
+ const swatch = document.createElement("div");
+ swatch.className = "swatch";
+ const img = new Image();
+ img.onload = ()=>{ swatch.style.backgroundImage = `url("${p.image}")`; };
+ img.onerror = ()=>{ swatch.style.backgroundImage = `url("${PLACEHOLDER}")`; };
+ img.src = p.image || PLACEHOLDER;
+
+ const stampKeep = el("div","stamp keep","Keep");
+ const stampSkip = el("div","stamp skip","Skip");
+ swatch.append(stampKeep, stampSkip);
+
+ const meta = document.createElement("div");
+ meta.className = "meta";
+ const cw = colorwordFor(p);
+ meta.innerHTML = `
+ <p class="pname">${esc(cleanName(p.pattern_name))}</p>
+ <div class="pvendor">${esc(p.vendor || p.collection || "Designer Wallcoverings")}</div>
+ <div class="spec">
+ ${specLine(p).map(s=>`<span>${esc(s)}</span>`).join('<span class="dot"></span>')}
+ ${cw?`<span class="dot"></span><span>${esc(cw)}</span>`:""}
+ </div>`;
+
+ card.append(swatch, meta);
+ return card;
+}
+function el(t,c,txt){ const e=document.createElement(t); e.className=c; e.textContent=txt; return e; }
+function esc(s){ return String(s==null?"":s).replace(/[&<>"]/g, m=>({'&':'&','<':'<','>':'>','"':'"'}[m])); }
+function cleanName(s){ return (s||"Untitled").replace(/\s*-\s*[^-]+$/, m => m.length<22 ? m : ""); }
+
+/* ─────────────────────────── DECK STATE ─────────────────────────── */
+function renderDeck(){
+ // remove existing cards (keep loading/empty nodes)
+ deck.querySelectorAll(".card").forEach(c=>c.remove());
+ const top3 = queue.slice(0,3);
+ // render back-to-front so top card is last (highest z)
+ for(let i=top3.length-1; i>=0; i--){
+ const card = makeCard(top3[i], i);
+ deck.appendChild(card);
+ if(i===0) attachDrag(card);
+ }
+ updateCounters();
+ const done = queue.length===0;
+ $("empty").classList.toggle("show", done);
+ $("actions").style.visibility = done ? "hidden":"visible";
+ $("hint").style.visibility = done ? "hidden":"visible";
+ $("undoBtn").disabled = history.length===0;
+ $("emptyTitle").textContent = favorites.length ? "That's the collection" : "All caught up";
+}
+
+function updateCounters(){
+ $("seenCt").textContent = seen;
+ $("leftCt").textContent = queue.length;
+ const n = favorites.length;
+ const fc = $("favCt"); fc.textContent = n; fc.classList.toggle("zero", n===0);
+ $("sampleCt").textContent = n;
+ $("sampleBtn").disabled = n===0;
+}
+
+/* ─────────────────────────── VERDICT / THROW ─────────────────────────── */
+let busy = false;
+function topCard(){ return deck.querySelector('.card[data-depth="0"]'); }
+
+function decide(verdict, fromDrag){
+ if(busy || queue.length===0) return;
+ busy = true;
+ const card = topCard();
+ const item = queue[0];
+ const dir = verdict==="keep" ? 1 : -1;
+
+ // show stamp
+ const stamp = card.querySelector(verdict==="keep" ? ".stamp.keep" : ".stamp.skip");
+ if(stamp) stamp.style.opacity = "1";
+
+ // throw animation
+ const throwX = dir * (window.innerWidth*0.9 + 200);
+ const rot = dir * 18;
+ card.style.transition = "transform .42s cubic-bezier(.36,.07,.4,1), opacity .42s";
+ card.style.transform = `translate(${throwX}px, -40px) rotate(${rot}deg)`;
+ card.style.opacity = "0";
+
+ // promote remaining cards immediately so the stack feels live
+ const b1 = deck.querySelector('.card[data-depth="1"]');
+ const b2 = deck.querySelector('.card[data-depth="2"]');
+ if(b1){ b1.style.transition="transform .42s cubic-bezier(.2,.9,.25,1)"; b1.className="card"; b1.dataset.depth="0"; }
+ if(b2){ b2.style.transition="transform .42s cubic-bezier(.2,.9,.25,1)"; b2.className="card behind-1"; b2.dataset.depth="1"; }
+
+ // commit state
+ queue.shift();
+ seen++;
+ history.push({item, verdict});
+ if(verdict==="keep"){ favorites.unshift(item); addFav(item); }
+
+ setTimeout(()=>{
+ busy = false;
+ renderDeck(); // rebuilds stack cleanly + brings in next back card + wires drag
+ }, 430);
+}
+
+function undo(){
+ if(busy || history.length===0) return;
+ const last = history.pop();
+ queue.unshift(last.item);
+ seen = Math.max(0, seen-1);
+ if(last.verdict==="keep"){
+ favorites = favorites.filter(f=>f!==last.item);
+ removeFavNode(last.item);
+ }
+ renderDeck();
+}
+
+/* ─────────────────────────── FAVORITES TRAY ─────────────────────────── */
+const favList = $("favList");
+function addFav(p){
+ $("trayEmpty").style.display = "none";
+ const row = document.createElement("div");
+ row.className = "fav";
+ row._item = p;
+ const cw = colorwordFor(p);
+ row.innerHTML = `
+ <div class="thumb" style="background-image:url('${(p.image||PLACEHOLDER).replace(/'/g,"%27")}')"></div>
+ <div class="fmeta">
+ <p class="fname">${esc(cleanName(p.pattern_name))}</p>
+ <div class="fvendor">${esc(p.vendor||p.collection||"")}</div>
+ <div class="fspec">${(p.width||27)}″ · ${cw?esc(cw):"swatch"}</div>
+ </div>
+ <button class="rm" title="Remove" aria-label="Remove">×</button>`;
+ row.querySelector(".rm").onclick = ()=> removeFav(p);
+ favList.prepend(row);
+ updateCounters();
+}
+function removeFav(p){
+ favorites = favorites.filter(f=>f!==p);
+ removeFavNode(p);
+ updateCounters();
+}
+function removeFavNode(p){
+ [...favList.querySelectorAll(".fav")].forEach(r=>{ if(r._item===p) r.remove(); });
+ if(favorites.length===0) $("trayEmpty").style.display = "";
+}
+
+/* ─────────────────────────── DRAG TO SWIPE ─────────────────────────── */
+function attachDrag(card){
+ let startX=0, startY=0, dx=0, dy=0, dragging=false;
+ const threshold = 110;
+
+ const onDown = e=>{
+ if(busy) return;
+ dragging = true;
+ const pt = e.touches?e.touches[0]:e;
+ startX = pt.clientX; startY = pt.clientY; dx=dy=0;
+ card.style.transition = "none";
+ window.addEventListener("mousemove", onMove);
+ window.addEventListener("mouseup", onUp);
+ window.addEventListener("touchmove", onMove, {passive:false});
+ window.addEventListener("touchend", onUp);
+ };
+ const onMove = e=>{
+ if(!dragging) return;
+ const pt = e.touches?e.touches[0]:e;
+ dx = pt.clientX-startX; dy = pt.clientY-startY;
+ if(e.cancelable && Math.abs(dx)>6) e.preventDefault();
+ const rot = dx/22;
+ card.style.transform = `translate(${dx}px, ${dy*0.4}px) rotate(${rot}deg)`;
+ const ks = card.querySelector(".stamp.keep"), ss = card.querySelector(".stamp.skip");
+ if(ks) ks.style.opacity = dx>20 ? Math.min(1, dx/threshold).toFixed(2):0;
+ if(ss) ss.style.opacity = dx<-20 ? Math.min(1, -dx/threshold).toFixed(2):0;
+ };
+ const onUp = ()=>{
+ if(!dragging) return;
+ dragging = false;
+ window.removeEventListener("mousemove", onMove);
+ window.removeEventListener("mouseup", onUp);
+ window.removeEventListener("touchmove", onMove);
+ window.removeEventListener("touchend", onUp);
+ if(Math.abs(dx) > threshold){
+ decide(dx>0 ? "keep":"skip", true);
+ } else {
+ card.style.transition = "transform .3s cubic-bezier(.2,.9,.25,1)";
+ card.style.transform = "";
+ const ks=card.querySelector(".stamp.keep"), ss=card.querySelector(".stamp.skip");
+ if(ks) ks.style.opacity=0; if(ss) ss.style.opacity=0;
+ }
+ };
+ card.addEventListener("mousedown", onDown);
+ card.addEventListener("touchstart", onDown, {passive:true});
+}
+
+/* ─────────────────────────── WIRING ─────────────────────────── */
+$("keepBtn").onclick = ()=> decide("keep");
+$("skipBtn").onclick = ()=> decide("skip");
+$("undoBtn").onclick = undo;
+$("restartBtn").onclick = ()=> { history=[]; favorites.forEach(removeFavNode); favorites=[]; load(true); };
+$("sampleBtn").onclick = ()=>{
+ if(!favorites.length) return;
+ const note = $("sampleNote");
+ note.textContent = `${favorites.length} sample${favorites.length>1?"s":""} queued — a stylist will confirm your memo set.`;
+ note.style.color = "var(--keep)";
+};
+
+document.addEventListener("keydown", e=>{
+ if(e.key==="ArrowRight"){ e.preventDefault(); decide("keep"); }
+ else if(e.key==="ArrowLeft"){ e.preventDefault(); decide("skip"); }
+ else if(e.key==="Backspace"){ e.preventDefault(); undo(); }
+});
+
+/* ─────────────────────────── LOAD ─────────────────────────── */
+async function load(reshuffle){
+ $("loading").style.display = "flex";
+ $("empty").classList.remove("show");
+ let items = [];
+ try{
+ const r = await fetch(API);
+ if(!r.ok) throw new Error("HTTP "+r.status);
+ const j = await r.json();
+ items = (j.products || j || []).filter(p=>p && p.image);
+ }catch(err){
+ // Standalone / offline: fall back to the embedded sample set so the
+ // mechanic is always usable. (Same-origin from :7690 the live fetch works.)
+ console.warn("[swipe-tower] live API unavailable, using offline sample set:", err.message);
+ items = FALLBACK.slice();
+ }
+ if(!items.length) items = FALLBACK.slice();
+ if(reshuffle){ items = items.slice().sort(()=>Math.random()-0.5); }
+ queue = items;
+ history = []; seen = 0;
+ $("loading").style.display = "none";
+ renderDeck();
+}
+load();
+</script>
+</body>
+</html>
diff --git a/proto/v9-walkin.html b/proto/v9-walkin.html
new file mode 100644
index 0000000..4d5fd8b
--- /dev/null
+++ b/proto/v9-walkin.html
@@ -0,0 +1,367 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="utf-8" />
+<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no" />
+<title>WALK-IN ROOM — China Seas</title>
+<style>
+ :root { --ink:#2b2622; --paper:#f4efe7; }
+ * { box-sizing:border-box; }
+ html,body { margin:0; height:100%; overflow:hidden; background:#1a1714;
+ font-family:"Hoefler Text", Georgia, "Times New Roman", serif; color:var(--paper); }
+ #app { position:fixed; inset:0; }
+ canvas { display:block; }
+
+ /* top brand bar */
+ .topbar { position:fixed; top:0; left:0; right:0; z-index:10;
+ display:flex; align-items:center; justify-content:space-between;
+ padding:14px 22px; pointer-events:none;
+ background:linear-gradient(to bottom, rgba(20,17,14,.55), rgba(20,17,14,0)); }
+ .brand { letter-spacing:.32em; font-size:12px; text-transform:uppercase; opacity:.9; }
+ .now { text-align:right; font-size:11px; letter-spacing:.06em; line-height:1.5; opacity:.85; max-width:46%; }
+ .now b { display:block; letter-spacing:.04em; font-size:13px; }
+
+ /* bottom design switcher strip */
+ .strip { position:fixed; left:0; right:0; bottom:0; z-index:10;
+ padding:14px 16px 18px; display:flex; gap:12px; align-items:flex-end;
+ overflow-x:auto; overflow-y:hidden;
+ background:linear-gradient(to top, rgba(20,17,14,.82), rgba(20,17,14,0)); }
+ .strip::-webkit-scrollbar { height:6px; }
+ .strip::-webkit-scrollbar-thumb { background:rgba(244,239,231,.25); border-radius:3px; }
+ .swatch { flex:0 0 auto; width:78px; cursor:pointer; text-align:center;
+ opacity:.72; transition:opacity .2s, transform .2s; }
+ .swatch:hover { opacity:1; transform:translateY(-3px); }
+ .swatch.active { opacity:1; }
+ .swatch img { width:78px; height:78px; object-fit:cover; border-radius:6px;
+ border:2px solid transparent; background:#3a342d; display:block; }
+ .swatch.active img { border-color:var(--paper); box-shadow:0 6px 18px rgba(0,0,0,.5); }
+ .swatch span { display:block; margin-top:5px; font-size:9.5px; line-height:1.25;
+ letter-spacing:.02em; opacity:.85; max-height:24px; overflow:hidden;
+ text-transform:uppercase; }
+
+ .hint { position:fixed; left:50%; top:54%; transform:transl(-50%,-50%);
+ transform:translateX(-50%); z-index:9; pointer-events:none;
+ font-size:11px; letter-spacing:.18em; text-transform:uppercase;
+ opacity:.0; transition:opacity 1s; text-shadow:0 1px 6px rgba(0,0,0,.6); }
+ .hint.show { opacity:.55; }
+
+ .loading { position:fixed; inset:0; z-index:20; display:flex;
+ align-items:center; justify-content:center; flex-direction:column; gap:14px;
+ background:#1a1714; transition:opacity .6s; }
+ .loading.gone { opacity:0; pointer-events:none; }
+ .ring { width:34px; height:34px; border:2px solid rgba(244,239,231,.2);
+ border-top-color:var(--paper); border-radius:50%; animation:spin 1s linear infinite; }
+ @keyframes spin { to { transform:rotate(360deg); } }
+ .loading p { letter-spacing:.28em; text-transform:uppercase; font-size:11px; opacity:.7; }
+</style>
+</head>
+<body>
+ <div id="app"></div>
+
+ <div class="topbar">
+ <div class="brand">China Seas · Walk-In Room</div>
+ <div class="now" id="now"><b>—</b><span id="meta"></span></div>
+ </div>
+
+ <div class="hint" id="hint">Drag to look around the room</div>
+
+ <div class="strip" id="strip"></div>
+
+ <div class="loading" id="loading"><div class="ring"></div><p>Papering the room…</p></div>
+
+ <script src="./three.min.js"></script>
+ <script src="./OrbitControls.js"></script>
+ <script>
+ (function(){
+ "use strict";
+ const FT = 0.3048; // 1 ft in metres
+ const REPEAT_IN = 27; // architectural wallpaper repeat (inches)
+ const REPEAT_M = REPEAT_IN * 0.0254; // ~0.6858 m
+
+ // Room dimensions (a real-feeling living room): 16ft W x 13ft D x 9ft H
+ const W = 16 * FT, D = 13 * FT, H = 9 * FT;
+ const EYE = 1.62; // standing eye height
+
+ const app = document.getElementById('app');
+
+ // ---- renderer ----
+ const renderer = new THREE.WebGLRenderer({ antialias:true });
+ renderer.setPixelRatio(Math.min(window.devicePixelRatio||1, 2));
+ renderer.setSize(window.innerWidth, window.innerHeight);
+ renderer.shadowMap.enabled = true;
+ renderer.shadowMap.type = THREE.PCFSoftShadowMap;
+ if ('outputColorSpace' in renderer) renderer.outputColorSpace = THREE.SRGBColorSpace;
+ else if ('outputEncoding' in renderer) renderer.outputEncoding = THREE.sRGBEncoding;
+ renderer.toneMapping = THREE.ACESFilmicToneMapping;
+ renderer.toneMappingExposure = 1.05;
+ app.appendChild(renderer.domElement);
+
+ const scene = new THREE.Scene();
+ scene.background = new THREE.Color(0x2a2520);
+
+ // ---- camera INSIDE the room, near the back wall, looking across ----
+ const camera = new THREE.PerspectiveCamera(72, window.innerWidth/window.innerHeight, 0.05, 100);
+ camera.position.set(0, EYE, D*0.30); // standing a bit back from centre
+
+ // OrbitControls used as a "look around from inside" rig: target sits just
+ // in front of the eye, so dragging rotates the view, not an exterior orbit.
+ const controls = new THREE.OrbitControls(camera, renderer.domElement);
+ controls.target.set(-W*0.32, EYE, -D*0.5); // look toward far wall + window corner
+ controls.enablePan = false;
+ controls.enableZoom = false; // it's a room, not a model — no zoom
+ controls.rotateSpeed = -0.32; // negative = natural "look" direction
+ controls.minPolarAngle = THREE.MathUtils.degToRad(58);
+ controls.maxPolarAngle = THREE.MathUtils.degToRad(118);
+ controls.enableDamping = true;
+ controls.dampingFactor = 0.08;
+ // keep the pivot pinned to the eye every frame (first-person look)
+ function lockPivot(){
+ const dir = new THREE.Vector3();
+ camera.getWorldDirection(dir);
+ controls.target.copy(camera.position).add(dir.multiplyScalar(0.6));
+ }
+
+ // ================= LIGHTING (warm, soft) =================
+ const hemi = new THREE.HemisphereLight(0xfff3e0, 0x3a2f26, 0.42);
+ scene.add(hemi);
+ const ambient = new THREE.AmbientLight(0xffe8cf, 0.20);
+ scene.add(ambient);
+
+ // Daylight pouring through the window (key light), warm + directional so the
+ // walls carry a light->shadow gradient (depth cue: you're in a real lit room)
+ const sun = new THREE.DirectionalLight(0xfff1da, 1.55);
+ sun.position.set(-W*0.55, H*0.9, D*0.55);
+ sun.castShadow = true;
+ sun.shadow.mapSize.set(1024,1024);
+ sun.shadow.camera.near = 0.5; sun.shadow.camera.far = 30;
+ sun.shadow.camera.left = -W; sun.shadow.camera.right = W;
+ sun.shadow.camera.top = H; sun.shadow.camera.bottom = -H;
+ sun.shadow.bias = -0.0004;
+ scene.add(sun);
+
+ // soft warm fill bounce from the room interior
+ const fill = new THREE.PointLight(0xffd9a8, 0.45, 18, 2);
+ fill.position.set(W*0.25, H*0.7, D*0.1);
+ scene.add(fill);
+ // gentle lamp glow near the sofa
+ const lamp = new THREE.PointLight(0xffcaa0, 0.55, 9, 2);
+ lamp.position.set(W*0.32, 1.4, -D*0.30);
+ scene.add(lamp);
+ // bright daylight spill right at the window so that wall glows (falloff = depth)
+ const dayspill = new THREE.PointLight(0xfff4e0, 0.85, 7, 2);
+ dayspill.position.set(-W*0.5 + 0.4, 1.4, -D*0.10);
+ scene.add(dayspill);
+
+ // ================= ROOM SHELL =================
+ const tex = { loader: new THREE.TextureLoader() };
+ tex.loader.crossOrigin = 'anonymous';
+
+ // wall material starts neutral; texture swapped in on load
+ const wallMat = new THREE.MeshStandardMaterial({ color:0xece4d6, roughness:0.92, metalness:0.0, side:THREE.FrontSide });
+
+ function wall(w, h){
+ const g = new THREE.PlaneGeometry(w, h);
+ const m = wallMat.clone();
+ m.userData.wallW = w; // physical width of this wall (m)
+ return new THREE.Mesh(g, m);
+ }
+
+ const walls = [];
+ // back wall (-Z)
+ let m = wall(W, H); m.position.set(0, H/2, -D/2); walls.push(m);
+ // front wall (+Z) faces inward
+ m = wall(W, H); m.position.set(0, H/2, D/2); m.rotation.y = Math.PI; walls.push(m);
+ // left wall (-X)
+ m = wall(D, H); m.position.set(-W/2, H/2, 0); m.rotation.y = Math.PI/2; walls.push(m);
+ // right wall (+X)
+ m = wall(D, H); m.position.set(W/2, H/2, 0); m.rotation.y = -Math.PI/2; walls.push(m);
+ walls.forEach(w => { w.receiveShadow = true; scene.add(w); });
+
+ // ceiling — soft off-white
+ const ceil = new THREE.Mesh(new THREE.PlaneGeometry(W, D),
+ new THREE.MeshStandardMaterial({ color:0xf3ece0, roughness:1 }));
+ ceil.rotation.x = Math.PI/2; ceil.position.y = H; ceil.receiveShadow = true; scene.add(ceil);
+
+ // floor — warm wood tone
+ const floor = new THREE.Mesh(new THREE.PlaneGeometry(W, D),
+ new THREE.MeshStandardMaterial({ color:0x6b4a31, roughness:0.78 }));
+ floor.rotation.x = -Math.PI/2; floor.position.y = 0; floor.receiveShadow = true; scene.add(floor);
+
+ // baseboard + crown trim (white) to frame the paper like a real install
+ const trimMat = new THREE.MeshStandardMaterial({ color:0xf6f1e8, roughness:0.6 });
+ function trimRing(y, h){
+ const grp = new THREE.Group();
+ const t = 0.02;
+ const spec = [
+ [W, 0, -D/2, 0], [W, 0, D/2, 0],
+ [D, -W/2, 0, Math.PI/2], [D, W/2, 0, Math.PI/2],
+ ];
+ spec.forEach(([len,x,z,ry])=>{
+ const b = new THREE.Mesh(new THREE.BoxGeometry(len, h, t+0.01), trimMat);
+ b.position.set(x, y, z); b.rotation.y = ry; b.castShadow=false; grp.add(b);
+ });
+ return grp;
+ }
+ scene.add(trimRing(0.05, 0.10)); // baseboard
+ scene.add(trimRing(H-0.06, 0.12)); // crown
+
+ // ================= WINDOW (light source, on left wall) =================
+ (function(){
+ const ww = 1.6, wh = 1.5, wy = 1.25, wx = -W/2 + 0.012;
+ // bright daylight panel (emissive so it reads as a window)
+ const glass = new THREE.Mesh(new THREE.PlaneGeometry(ww, wh),
+ new THREE.MeshStandardMaterial({ color:0xfdf6e8, emissive:0xfff2d6, emissiveIntensity:1.2, roughness:1 }));
+ glass.position.set(wx, wy, -D*0.10); glass.rotation.y = Math.PI/2; scene.add(glass);
+ // white frame
+ const fr = new THREE.Mesh(new THREE.PlaneGeometry(ww+0.16, wh+0.16),
+ new THREE.MeshStandardMaterial({ color:0xf6f1e8, roughness:0.5 }));
+ fr.position.set(wx+0.002, wy, -D*0.10); fr.rotation.y = Math.PI/2; scene.add(fr);
+ // mullions
+ const mull = new THREE.MeshStandardMaterial({ color:0xeae3d6 });
+ const v = new THREE.Mesh(new THREE.BoxGeometry(0.03, wh, 0.03), mull);
+ v.position.set(wx-0.01, wy, -D*0.10); scene.add(v);
+ const hbar = new THREE.Mesh(new THREE.BoxGeometry(0.03, 0.03, ww), mull);
+ hbar.position.set(wx-0.01, wy, -D*0.10); scene.add(hbar);
+ })();
+
+ // ================= FURNITURE silhouette =================
+ (function sofa(){
+ const grp = new THREE.Group();
+ const mat = new THREE.MeshStandardMaterial({ color:0xcfc4b0, roughness:0.95 });
+ const base = new THREE.Mesh(new THREE.BoxGeometry(2.2, 0.45, 0.95), mat);
+ base.position.y = 0.32; base.castShadow = true; base.receiveShadow=true; grp.add(base);
+ const back = new THREE.Mesh(new THREE.BoxGeometry(2.2, 0.7, 0.22), mat);
+ back.position.set(0, 0.62, -0.36); back.castShadow=true; grp.add(back);
+ const seat = new THREE.Mesh(new THREE.BoxGeometry(2.05, 0.18, 0.78), new THREE.MeshStandardMaterial({color:0xe7ddc8, roughness:1}));
+ seat.position.set(0, 0.58, 0.04); seat.castShadow=true; grp.add(seat);
+ [-1.0,1.0].forEach(x=>{ const a=new THREE.Mesh(new THREE.BoxGeometry(0.2,0.6,0.95),mat); a.position.set(x,0.45,0); a.castShadow=true; grp.add(a); });
+ grp.position.set(0, 0, -D/2 + 0.78); // against the back wall
+ grp.rotation.y = 0;
+ scene.add(grp);
+ // round rug
+ const rug = new THREE.Mesh(new THREE.CircleGeometry(1.7, 48),
+ new THREE.MeshStandardMaterial({ color:0xb9a88c, roughness:1 }));
+ rug.rotation.x = -Math.PI/2; rug.position.set(0, 0.012, -D/2 + 1.7); rug.receiveShadow=true; scene.add(rug);
+ // a floor lamp by the sofa (gives the lamp glow a body)
+ const pole = new THREE.Mesh(new THREE.CylinderGeometry(0.02,0.02,1.5,12), new THREE.MeshStandardMaterial({color:0x3a3026}));
+ pole.position.set(W*0.32, 0.75, -D*0.30); scene.add(pole);
+ const shade = new THREE.Mesh(new THREE.CylinderGeometry(0.18,0.24,0.28,20),
+ new THREE.MeshStandardMaterial({color:0xfff1d8, emissive:0xffe6c0, emissiveIntensity:0.6, roughness:1}));
+ shade.position.set(W*0.32, 1.5, -D*0.30); scene.add(shade);
+ })();
+
+ // ================= WALLPAPER TEXTURE =================
+ let designs = [];
+ let active = -1;
+
+ function applyTexture(t){
+ t.wrapS = t.wrapT = THREE.RepeatWrapping;
+ if ('colorSpace' in t) t.colorSpace = THREE.SRGBColorSpace;
+ else if ('encoding' in t) t.encoding = THREE.sRGBEncoding;
+ t.anisotropy = renderer.capabilities.getMaxAnisotropy();
+ // The tile image is one horizontal repeat (27") wide; its physical HEIGHT
+ // is 27" * (imgH/imgW). Respect that aspect so the pattern isn't squashed.
+ const iw = (t.image && t.image.width) || 2000;
+ const ih = (t.image && t.image.height) || 2500;
+ const tileW = REPEAT_M; // 27" wide
+ const tileH = REPEAT_M * (ih / iw); // proportional height
+ walls.forEach(w=>{
+ const tt = t.clone(); tt.needsUpdate = true;
+ tt.wrapS = tt.wrapT = THREE.RepeatWrapping;
+ if ('colorSpace' in tt) tt.colorSpace = THREE.SRGBColorSpace;
+ tt.anisotropy = t.anisotropy;
+ // repeats = wall_dimension / physical_tile_dimension
+ tt.repeat.set(w.material.userData.wallW / tileW, H / tileH);
+ w.material.map = tt;
+ w.material.color.set(0xffffff);
+ w.material.needsUpdate = true;
+ });
+ }
+
+ function pickDesign(i){
+ const d = designs[i]; if(!d) return;
+ active = i;
+ document.querySelectorAll('.swatch').forEach((el,k)=> el.classList.toggle('active', k===i));
+ document.getElementById('now').querySelector('b').textContent = d.pattern_name || d.sku;
+ document.getElementById('meta').textContent =
+ (d.collection || 'China Seas') + (d.repeat ? ' · ' + d.repeat + '" repeat' : '');
+ const url = d.tile || d.image;
+ tex.loader.load(url, applyTexture, undefined, (e)=>{
+ // fallback to the flat product image if tile fails
+ if (url !== d.image && d.image) tex.loader.load(d.image, applyTexture);
+ else console.warn('texture load failed', url, e);
+ });
+ }
+
+ function buildStrip(){
+ const strip = document.getElementById('strip');
+ strip.innerHTML = '';
+ designs.forEach((d,i)=>{
+ const el = document.createElement('div');
+ el.className = 'swatch';
+ el.innerHTML = '<img alt="" src="'+(d.tile||d.image)+'"><span>'+
+ ((d.pattern_name||d.sku).split(/[-–]/)[0].trim())+'</span>';
+ el.addEventListener('click', ()=> pickDesign(i));
+ strip.appendChild(el);
+ });
+ }
+
+ // ---- boot ----
+ fetch('/api/showroom/products?limit=24')
+ .then(r=>r.json())
+ .then(j=>{
+ const list = (j.products||j||[]).filter(p=> p.tile || p.image);
+ designs = list.slice(0, 18);
+ if (!designs.length) throw new Error('no designs');
+ buildStrip();
+ // preload first then reveal
+ const first = designs[0];
+ tex.loader.load(first.tile || first.image, (t)=>{
+ applyTexture(t);
+ active = 0;
+ pickDesign(0);
+ reveal();
+ }, undefined, ()=>{ pickDesign(0); reveal(); });
+ })
+ .catch(err=>{
+ console.error('boot error', err);
+ // still show the room with the neutral wall so it's not a black screen
+ reveal();
+ });
+
+ function reveal(){
+ const ld = document.getElementById('loading');
+ ld.classList.add('gone');
+ const hint = document.getElementById('hint');
+ hint.classList.add('show');
+ setTimeout(()=> hint.classList.remove('show'), 4200);
+ setTimeout(()=> ld.remove(), 700);
+ }
+
+ // ---- resize ----
+ window.addEventListener('resize', ()=>{
+ camera.aspect = window.innerWidth/window.innerHeight;
+ camera.updateProjectionMatrix();
+ renderer.setSize(window.innerWidth, window.innerHeight);
+ });
+
+ // ---- render loop ----
+ let t0 = performance.now();
+ function tick(){
+ requestAnimationFrame(tick);
+ controls.update();
+ lockPivot(); // keep first-person pivot at the eye
+ renderer.render(scene, camera);
+ }
+ tick();
+
+ // expose for verification
+ window.__walkin = {
+ ready: ()=> active >= 0 && walls[0].material.map != null,
+ designs: ()=> designs.length,
+ active: ()=> active,
+ };
+ })();
+ </script>
+</body>
+</html>
diff --git a/proto/verify-v10.cjs b/proto/verify-v10.cjs
new file mode 100644
index 0000000..2da3949
--- /dev/null
+++ b/proto/verify-v10.cjs
@@ -0,0 +1,80 @@
+const { chromium } = require('playwright');
+const path = require('path');
+const fs = require('fs');
+
+(async () => {
+ const shotsDir = path.join(__dirname, 'shots');
+ fs.mkdirSync(shotsDir, { recursive: true });
+ // Serve the proto same-origin (proxies /api -> backend :7690) so CORS isn't an issue,
+ // exactly as it'll behave behind proto/proxy-server.cjs in real use.
+ const { spawn } = require('child_process');
+ const PORT = 7693;
+ const srv = spawn(process.execPath, [path.join(__dirname, 'proxy-server.cjs')],
+ { env: { ...process.env, PROTO_PORT: String(PORT) }, stdio: 'inherit' });
+ await new Promise(r => setTimeout(r, 700));
+ const fileUrl = `http://127.0.0.1:${PORT}/v10-concierge.html`;
+
+ const browser = await chromium.launch({ headless: true });
+ const ctx = await browser.newContext({ viewport: { width: 1440, height: 900 } });
+ const page = await ctx.newPage();
+
+ const errors = [];
+ page.on('console', m => { if (m.type() === 'error') errors.push(m.text()); });
+ page.on('pageerror', e => errors.push('PAGEERROR: ' + e.message));
+
+ await page.goto(fileUrl, { waitUntil: 'networkidle' });
+
+ // wait for feed to load (concierge announces board count)
+ await page.waitForFunction(() => window.PRODUCTS && window.PRODUCTS.length > 0, { timeout: 8000 })
+ .catch(() => {});
+ const total = await page.evaluate(() => (window.PRODUCTS || []).length);
+ console.log('Products loaded:', total);
+
+ // ---- Query 1: "show me blues" via API ----
+ const q1 = await page.evaluate(() => {
+ const { results } = window.runQuery('show me blues');
+ const allBlue = results.every(p => /aqua|blue|navy|turquoise|capri/i.test(p.pattern_name));
+ return { n: results.length, allBlue, sample: results.slice(0,3).map(p=>p.pattern_name) };
+ });
+ console.log('Q1 "show me blues":', q1.n, 'results, all blue-named:', q1.allBlue);
+ console.log(' sample:', q1.sample);
+
+ // ---- Query 2: "like Aga but darker" ----
+ const q2 = await page.evaluate(() => {
+ const { results } = window.runQuery('like Aga but darker');
+ const allAga = results.every(p => /^aga/i.test(p.pattern_name));
+ return { n: results.length, allAga, sample: results.slice(0,4).map(p=>p.pattern_name) };
+ });
+ console.log('Q2 "like Aga but darker":', q2.n, 'results, all Aga family:', q2.allAga);
+ console.log(' sample:', q2.sample);
+
+ // ---- Query 3: stripes (style) ----
+ const q3 = await page.evaluate(() => {
+ const { results } = window.runQuery('something with stripes');
+ return { n: results.length, sample: results.slice(0,3).map(p=>p.pattern_name) };
+ });
+ console.log('Q3 "stripes":', q3.n, 'results; sample:', q3.sample);
+
+ // ---- Drive the UI: click a chip and confirm boards render in DOM ----
+ await page.click('text=Warm neutrals');
+ await page.waitForTimeout(900);
+ const boardsRendered = await page.evaluate(() => document.querySelectorAll('.board').length);
+ console.log('Warm neutrals chip -> boards in DOM:', boardsRendered);
+
+ // type into the box for the screenshot beauty shot
+ await page.fill('#askInput', 'like Aga but darker');
+ await page.click('.ask button');
+ await page.waitForTimeout(1000);
+ const finalBoards = await page.evaluate(() => document.querySelectorAll('.board').length);
+ console.log('Final on-table boards:', finalBoards);
+
+ await page.screenshot({ path: path.join(shotsDir, 'v10-concierge.png'), fullPage: false });
+ console.log('Screenshot -> shots/v10-concierge.png');
+
+ console.log('\nConsole errors:', errors.length);
+ errors.forEach(e => console.log(' ✗', e));
+
+ await browser.close();
+ srv.kill();
+ process.exit(errors.length ? 2 : 0);
+})();
diff --git a/proto/verify-v8.cjs b/proto/verify-v8.cjs
new file mode 100644
index 0000000..4b2893a
--- /dev/null
+++ b/proto/verify-v8.cjs
@@ -0,0 +1,73 @@
+const { chromium } = require('playwright');
+const path = require('path');
+const fs = require('fs');
+
+(async () => {
+ const shotsDir = path.join(__dirname, 'shots');
+ fs.mkdirSync(shotsDir, { recursive: true });
+ const fileUrl = 'file://' + path.join(__dirname, 'v8-swipe.html');
+
+ const browser = await chromium.launch();
+ const ctx = await browser.newContext({ viewport: { width: 1280, height: 860 }, deviceScaleFactor: 2 });
+ const page = await ctx.newPage();
+
+ const errors = [];
+ page.on('console', m => { if (m.type() === 'error') errors.push(m.text()); });
+ page.on('pageerror', e => errors.push('PAGEERROR: ' + e.message));
+
+ // Fulfill the live-API request with REAL data fetched server-side (Node),
+ // proving the page renders the actual showroom payload — bypasses the
+ // file:// CORS wall that only exists for the standalone testing case.
+ let liveOk = false, liveCount = 0;
+ await page.route('**/api/showroom/products**', async route => {
+ try {
+ const res = await fetch('http://127.0.0.1:7690/api/showroom/products?limit=120');
+ const body = await res.text();
+ liveOk = true;
+ try { liveCount = (JSON.parse(body).products || []).length; } catch(_) {}
+ route.fulfill({ status: 200, contentType: 'application/json', body });
+ } catch (e) {
+ route.abort();
+ }
+ });
+
+ await page.goto(fileUrl, { waitUntil: 'networkidle' });
+ // wait for first card to render
+ await page.waitForSelector('.card[data-depth="0"]', { timeout: 8000 });
+ await page.waitForTimeout(900); // let swatch images load
+
+ // initial screenshot
+ await page.screenshot({ path: path.join(shotsDir, 'v8-swipe.png') });
+
+ // drive: keep, keep, skip, keep, skip -> expect 3 favorites
+ const seq = ['keep','keep','skip','keep','skip'];
+ for (const v of seq) {
+ await page.click(v === 'keep' ? '#keepBtn' : '#skipBtn');
+ await page.waitForTimeout(520);
+ }
+
+ const favCount = await page.$eval('#favCt', e => e.textContent.trim());
+ const seenCount = await page.$eval('#seenCt', e => e.textContent.trim());
+ const favRows = await page.$$eval('.fav', n => n.length);
+
+ // test undo
+ await page.click('#undoBtn');
+ await page.waitForTimeout(400);
+ const seenAfterUndo = await page.$eval('#seenCt', e => e.textContent.trim());
+
+ // test keyboard arrow keep
+ await page.keyboard.press('ArrowRight');
+ await page.waitForTimeout(520);
+
+ // final screenshot with favorites tray filled
+ await page.screenshot({ path: path.join(shotsDir, 'v8-swipe-filled.png') });
+
+ console.log(JSON.stringify({
+ liveApiUsed: liveOk, liveApiCount: liveCount,
+ favCount, seenCount, favRows, seenAfterUndo,
+ consoleErrors: errors,
+ pass: errors.length === 0 && favRows > 0 && liveOk
+ }, null, 2));
+
+ await browser.close();
+})().catch(e => { console.error('VERIFY FAILED:', e); process.exit(1); });
diff --git a/public/js/showroom.js b/public/js/showroom.js
index 753d5b6..691b598 100644
--- a/public/js/showroom.js
+++ b/public/js/showroom.js
@@ -20,7 +20,7 @@ const CONFIG = {
// brass rail, each hinged on its LEFT vertical edge, raked so packed they show only
// a thin vertical SLIVER of each pattern (occlusion gives the sliver for free).
arcRadius: 3.3, // radius of the curved rail (m)
- arcSpanDeg: 112, // total angular sweep of the arc (wraps the corner/bay)
+ arcSpanDeg: 86, // tighter sweep → boards overlap ~75% → THIN slivers
arcCenter: { x: -0.3, z: -1.2 }, // arc centre in room space (boards face inward → viewer)
sliverPitchScale: 1.0 // multiplier on the angular pitch (reveal slider can thin)
},
@@ -112,7 +112,7 @@ function init() {
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 1.5));
if (THREE.sRGBEncoding) renderer.outputEncoding = THREE.sRGBEncoding; // correct gamma → no washed-out look
renderer.toneMapping = THREE.ACESFilmicToneMapping;
- renderer.toneMappingExposure = 1.12; // brighter clean gallery exposure
+ renderer.toneMappingExposure = 1.02; // calm warm gallery exposure (was blowing out the plaster)
renderer.shadowMap.enabled = true; // contact shadows = the #1 realism lever
// Soft PCF — penumbra blur on the wing/furniture shadows reads far more believable than
// hard PCF. Cost is modest at one shadow-casting light; the adaptive guard protects FPS.
@@ -284,7 +284,7 @@ function initMaterials() {
normalMap: loadDataTex('wall-limewash-normal', 2, 1.4),
normalScale: new THREE.Vector2(0.2, 0.2),
roughnessMap: loadDataTex('wall-limewash-rough', 2, 1.4),
- color: 0xfbf7ef, roughness: 1.0, metalness: 0.0, envMapIntensity: 0.3
+ color: 0xefe6d4, roughness: 1.0, metalness: 0.0, envMapIntensity: 0.3
});
MAT.chrome = new THREE.MeshStandardMaterial({ color: 0xc9a96e, roughness: 0.25, metalness: 0.9, envMapIntensity: 1.0 }); // brushed brass
MAT.wood = new THREE.MeshStandardMaterial({ color: 0x6b4a32, roughness: 0.5, metalness: 0.0, envMapIntensity: 0.5 }); // satin walnut
@@ -306,7 +306,7 @@ function initMaterials() {
normalMap: loadDataTex('wall-limewash-normal', 1.6, 1.6),
normalScale: new THREE.Vector2(0.2, 0.2),
roughnessMap: loadDataTex('wall-limewash-rough', 1.6, 1.6),
- color: 0xfbf7ef, roughness: 1.0, metalness: 0.0, envMapIntensity: 0.3
+ color: 0xefe6d4, roughness: 1.0, metalness: 0.0, envMapIntensity: 0.3
});
}
@@ -748,8 +748,10 @@ function buildLighting() {
// wallcovering swatches read crisp and true-colour, not greyed by moody low-key shadow.
// Committed to ONE dominant key (below) for photographic modelling; fill kept high
// enough that the wallcovering swatch colour stays true + legible.
- scene.add(new THREE.AmbientLight(0xffffff, 0.5));
- scene.add(new THREE.HemisphereLight(0xfffdf8, 0xe6ded2, 0.45)); // sky/ground bounce — warm, even
+ // Warmer, slightly calmer wash than a pure white-box so the plaster reads CREAM (like
+ // the PJ Dallas photo) instead of blown-out white — the swatches still read true.
+ scene.add(new THREE.AmbientLight(0xfff3e2, 0.42));
+ scene.add(new THREE.HemisphereLight(0xfff1e0, 0xe4d8c4, 0.42)); // warm sky / warm-stone ground bounce
// KEY light — soft, gentle shadow (clean contact, not dramatic)
const key = new THREE.DirectionalLight(0xfff6ea, 1.05); // dominant warm key — clear light direction
diff --git a/scripts/enrich.js b/scripts/enrich.js
new file mode 100644
index 0000000..5d6dae6
--- /dev/null
+++ b/scripts/enrich.js
@@ -0,0 +1,56 @@
+#!/usr/bin/env node
+/**
+ * enrich.js — RESEARCH.md data wins for the version mechanics. Produces
+ * data/enrichment.json: sku -> { patternBase, family colorways, room, hue, color_bucket }.
+ * - patternBase + family: split pattern_name on " on " (instant, powers pairs-well-with).
+ * - room: surface the existing room image (~41% have one).
+ * - hue + color_bucket: dominant colour per swatch via sharp (powers Color River / Fan).
+ * Writes patternBase/room/family immediately, then fills hue in the background pass.
+ */
+const fs = require('fs');
+const path = require('path');
+const https = require('https');
+
+const DATA = path.join(__dirname, '..', 'data', 'showroom-products.json');
+const OUT = path.join(__dirname, '..', 'data', 'enrichment.json');
+const products = JSON.parse(fs.readFileSync(DATA, 'utf8')).products;
+
+// ---- 1+2: patternBase / family / room (instant) ----
+const baseOf = (name) => (name || '').split(/\s+on\s+/i)[0].replace(/\s+screen\s*print.*$/i, '').trim() || (name || 'Pattern');
+const enrich = {};
+const families = {};
+products.forEach(p => {
+ const base = baseOf(p.pattern_name || p.name);
+ enrich[p.sku] = { patternBase: base, room: p.room || null, hue: null, color_bucket: null };
+ (families[base] = families[base] || []).push(p.sku);
+});
+products.forEach(p => { enrich[p.sku].familySkus = families[enrich[p.sku].patternBase].filter(s => s !== p.sku).slice(0, 12); });
+fs.writeFileSync(OUT, JSON.stringify({ built_at: new Date().toISOString(), hue_done: false, enrich }, null, 0));
+console.log(`[enrich] patternBase/room/family written for ${products.length} skus; ${Object.keys(families).length} families`);
+
+// ---- 3: dominant hue via sharp (background) ----
+let sharp; try { sharp = require('sharp'); } catch { console.log('[enrich] sharp not installed — run: npm i sharp (hue skipped)'); process.exit(0); }
+const BUCKETS = [[15,'red'],[45,'orange'],[70,'yellow'],[170,'green'],[200,'teal'],[255,'blue'],[290,'purple'],[330,'magenta'],[360,'red']];
+const rgb2hue = (r,g,b)=>{r/=255;g/=255;b/=255;const mx=Math.max(r,g,b),mn=Math.min(r,g,b),d=mx-mn;let h=0;if(d){if(mx===r)h=((g-b)/d)%6;else if(mx===g)h=(b-r)/d+2;else h=(r-g)/d+4;h*=60;if(h<0)h+=360;}const s=mx===0?0:d/mx;return {h:Math.round(h),s,v:mx};};
+const fetchBuf = (url)=>new Promise((res,rej)=>{https.get(url,r=>{if(r.statusCode>=400){r.resume();return rej(new Error(r.statusCode));}const c=[];r.on('data',d=>c.push(d));r.on('end',()=>res(Buffer.concat(c)));}).on('error',rej);});
+
+(async()=>{
+ let done=0, fail=0;
+ for (const p of products) {
+ const url = p.image; if (!url || !/^https/.test(url)) { fail++; continue; }
+ try {
+ const buf = await fetchBuf(url);
+ const { data } = await sharp(buf).resize(16,16,{fit:'fill'}).removeAlpha().raw().toBuffer({resolveWithObject:true});
+ // average only reasonably-saturated pixels so the hue reflects the design, not the ground
+ let r=0,g=0,b=0,n=0;
+ for(let i=0;i<data.length;i+=3){const hsv=rgb2hue(data[i],data[i+1],data[i+2]);if(hsv.s>0.18){r+=data[i];g+=data[i+1];b+=data[i+2];n++;}}
+ if(!n){for(let i=0;i<data.length;i+=3){r+=data[i];g+=data[i+1];b+=data[i+2];n++;}}
+ const {h}=rgb2hue(r/n,g/n,b/n);
+ enrich[p.sku].hue=h; enrich[p.sku].color_bucket=BUCKETS.find(x=>h<=x[0])[1];
+ done++;
+ if(done%100===0){fs.writeFileSync(OUT,JSON.stringify({built_at:new Date().toISOString(),hue_done:false,enrich},null,0));console.log(`[enrich] hue ${done}/${products.length}`);}
+ } catch(e){ fail++; }
+ }
+ fs.writeFileSync(OUT,JSON.stringify({built_at:new Date().toISOString(),hue_done:true,hue_filled:done,hue_failed:fail,enrich},null,0));
+ console.log(`[enrich] DONE hue: ${done} filled, ${fail} failed -> ${OUT}`);
+})();
← 72bd476 proto v7: mood-board table — drag swatches, build palette, p
·
back to Quadrille Showroom
·
proto: COLOR RIVER — shop China Seas by hue as a flowing Thr 597fb5b →