← back to Designer Wallcoverings
auto-save: 2026-07-09T09:39:56 (5 files) — pending-approval/boost-filter-consolidation-2026-06-25 pending-approval/color-dot-index-10pct-tolerance-2026-07-09/MEMO.md pending-approval/color-dot-index-10pct-tolerance-2026-07-09/push-to-live.sh shopify/theme-LIVE-591/snippets/color-palette.liquid vendor-scrapers/china-seas-refresh
f4e6d315741b560eaac1487e5d667949127bcb71 · 2026-07-09 09:39:59 -0700 · Steve Abrams
Files touched
D pending-approval/color-dot-index-10pct-tolerance-2026-07-09/MEMO.mdD pending-approval/color-dot-index-10pct-tolerance-2026-07-09/push-to-live.shM shopify/theme-LIVE-591/snippets/color-palette.liquid
Diff
commit f4e6d315741b560eaac1487e5d667949127bcb71
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Jul 9 09:39:59 2026 -0700
auto-save: 2026-07-09T09:39:56 (5 files) — pending-approval/boost-filter-consolidation-2026-06-25 pending-approval/color-dot-index-10pct-tolerance-2026-07-09/MEMO.md pending-approval/color-dot-index-10pct-tolerance-2026-07-09/push-to-live.sh shopify/theme-LIVE-591/snippets/color-palette.liquid vendor-scrapers/china-seas-refresh
---
.../MEMO.md | 136 -------------
.../push-to-live.sh | 55 ------
.../theme-LIVE-591/snippets/color-palette.liquid | 212 ++++++++++++++++++++-
3 files changed, 202 insertions(+), 201 deletions(-)
diff --git a/pending-approval/color-dot-index-10pct-tolerance-2026-07-09/MEMO.md b/pending-approval/color-dot-index-10pct-tolerance-2026-07-09/MEMO.md
deleted file mode 100644
index 87e26f84..00000000
--- a/pending-approval/color-dot-index-10pct-tolerance-2026-07-09/MEMO.md
+++ /dev/null
@@ -1,136 +0,0 @@
-# PENDING APPROVAL — Color-dot click → in-page INDEX of many products within 10% tolerance
-
-**Date:** 2026-07-09
-**Owner:** vp-dw-commerce
-**Status:** committed locally, verified, STAGED — NOT pushed. Two Steve-gated steps.
-**Supersedes behavior of:** `DONE-color-dot-specific-product-deeplink-2026-07-09/`
-(the single-product deeplink, commit 6ba5016). This replaces "click → ONE product"
-with "click → GRID of MANY products in that color."
-
-## Steve's feedback (verbatim)
-> "bring up an index of many images, not just that exact hex. tolerance 10% for color to pull more"
-
-## What it does (before → after)
-- **BEFORE (6ba5016, live):** clicking a color dot deep-linked to the ONE nearest
- specific product+variant of that hue and navigated away.
-- **AFTER (staged):** clicking a color dot opens an **in-page expanding grid**
- right below the palette — an index of MANY products (up to 36) whose dominant
- color is within a perceptual **10% tolerance (CIELAB ΔE76)** of the clicked hex,
- drawn **CATALOG-WIDE** (not one collection). Header shows the clicked color +
- a close (×); cards are image + pattern + vendor, each linking to
- `/products/<handle>` (the roll variant — **never** the $4.25 Sample). Graceful
- empty/error states. No navigation away (matches the CLIP-strip pattern).
-- **Unchanged / intact:** the swatch hex fill, the interpolated spectrum, the
- legend + proportion bar, and the separate CLIP **"More Wallcoverings Like This"**
- strip. Family-collection href kept as the plain `href` so middle-click / new-tab
- / crawlers still get a valid destination.
-
-## Data source (proof) + why
-**Catalog-wide color-index endpoint** (the source the task explicitly prefers),
-NOT the narrow one-collection products.json client sample.
-
-- New endpoint `POST /apps/color-index` on the dwphoto app (:9912), alongside the
- existing `/apps/similar`. Same public-but-tightly-bounded posture (CORS
- allowlist to the storefront origins, input allowlist `{hex,k}` only, output
- allowlist of public catalog fields).
-- Backed by a static `data/color-index.json` — **one dominant color per ACTIVE
- product with a PRECOMPUTED CIELAB LAB** — built from `dw_unified.product_colors`
- (which already stores `lab_l/lab_a/lab_b`) by
- `dw-photo-capture/scripts/build-color-index.cjs`. **60,960 products** in the
- index. No DB at request time; the endpoint loads the index once and hot-reloads
- on mtime change. `$0 (local)`.
-- Why static-JSON not live-PG: the dwphoto prod app carries no `pg` driver and is
- intentionally dependency-light; the static index keeps it that way while still
- being genuinely catalog-wide. The generator runs where PG lives (or with
- `NODE_PATH` pointed at a node_modules that has `pg`).
-
-## The 10% perceptual metric (named tunable constant)
-- ONE knob: `COLOR_INDEX_TOLERANCE_PCT = 0.10` (in `server.js`).
-- Mapped to a **ΔE76 ceiling** = `TOLERANCE_PCT × 100 = 10`, where 100 is the
- practical perceptual ΔE range for two clearly-different colors. So "10%" = a
- ΔE76 ceiling of 10 — tight enough to stay same-family, loose enough to pull
- MANY products. Retune by changing the one constant; the ceiling derives from it.
-- Perceptual (CIELAB), not raw hex/RGB math, as required. Product LAB is
- precomputed in `product_colors`; the clicked hex is converted sRGB→LAB in JS
- (`hexToLab`, matching the upstream computation).
-
-## Local verification (all $0 local)
-- `node --check` on `server.js`, `build-color-index.cjs`, and BOTH extracted
- `<script>` blocks of the snippet → all OK.
-- `shopify theme check` on `color-palette.liquid` → **5 warnings, all pre-existing**
- (OrphanedSnippet, 3× VariableName on the CLIP block's liquid vars, DeprecatedFilter
- `img_url` on the untouched featured_image line). **0 new offenses, 0 errors** —
- same baseline as the prior deeplink task.
-- Endpoint smoke: forest `#3C5B49`→97 in tol, blush `#DE5D83`→80, gold `#D4AF37`→41,
- navy `#1B2A4A`→235; bad-input/405/CORS-preflight all correct.
-- **Headless click proof (puppeteer, real snippet markup+JS over http):** clicked
- the forest-green base dot on a rendered palette → the in-page grid opened with
- **36 product cards**, ALL linking to `/products/<handle>` (roll, no Sample),
- spanning 7+ vendors (Phillipe Romano, Boråstapeter, Sandberg, Hollywood, Kravet,
- Coordonné, Rebel Walls, Lee Jofa Modern). Zero console errors in the flow.
-
-### Tolerance proof (clicked hex → resolved product hexes + ΔE + count)
-Clicked **#3C5B49** → `total_in_tolerance=97` (ΔE≤10), grid shows nearest-first:
-
-| product hex | ΔE from clicked | vendor | title |
-|---|---|---|---|
-| #3c5b49 | 0.0 | Phillipe Romano | Crushed Coral - Forest |
-| #3f5f4d | 1.6 | Boråstapeter | Umbrella Leaves |
-| #375f50 | 3.6 | Sandberg | Igor jade |
-| #445d4f | 3.8 | Hollywood | Ashbourne Type II Vinyl |
-| #436354 | 3.9 | Sandberg | Raphael green |
-| #3f594d | 4.3 | Kravet Design | W3863-30 Sage |
-| #3a5447 | 4.4 | Coordonné | Cherry Blossom Emerald |
-| #35543c | 5.0 | Rebel Walls | Opulence, Jade |
-| … | … | … | (max ΔE among 36 returned = 6.3, all ≤ ceiling 10) |
-
-## Commit hashes
-- dwphoto (endpoint + generator): `602bb7f` in `~/Projects/dw-photo-capture`
- (`server.js`, `scripts/build-color-index.cjs`, `.gitignore`). The 16MB generated
- `data/color-index.json` is gitignored — rebuild it, don't commit it.
-- shopify theme (snippet): `7b0ae3d` in `~/Projects/Designer-Wallcoverings/shopify`
- (`theme-LIVE-591-freshpull-20260709/snippets/color-palette.liquid` +
- `theme-LIVE-591/snippets/color-palette.liquid`).
-
-## TWO Steve-gated ship steps
-
-### STEP 1 (required first) — deploy the color-index endpoint publicly
-The theme grid fetches `https://photo.designerwallcoverings.com/apps/color-index`.
-That endpoint is built + verified LOCAL only. To go live it must (a) exist on the
-public dwphoto host and (b) have a freshly generated index there:
-
-```bash
-# on the box that serves photo.designerwallcoverings.com (dwphoto :9912):
-# 1. pull the new server.js + scripts/build-color-index.cjs (commit 602bb7f)
-# 2. generate the index where PG (dw_unified) is reachable:
-cd <dwphoto-dir> && node scripts/build-color-index.cjs # writes data/color-index.json
-# 3. restart dwphoto so the new /apps/color-index route + index load:
-pm2 restart dwphoto # (or the process manager in use)
-# 4. smoke test:
-curl -s -X POST https://photo.designerwallcoverings.com/apps/color-index \
- -H 'Content-Type: application/json' -d '{"hex":"#3C5B49","k":6}' | head
-```
-Also schedule `build-color-index.cjs` (e.g. nightly) so the index tracks catalog
-changes. Keep loopback-only services loopback; this endpoint is the only public
-surface added.
-
-### STEP 2 — push the theme snippet to the live theme
-Only ship AFTER Step 1 is live (otherwise the grid shows the graceful
-"could not load matches" empty state). Equivalent to the prior deeplink push:
-
-```bash
-cd ~/Projects/Designer-Wallcoverings/shopify/theme-LIVE-591-freshpull-20260709
-SHOPIFY_CLI_THEME_TOKEN=$SHOPIFY_THEME_TOKEN \
- shopify theme push --allow-live --theme 144076931123 \
- --store designer-laboratory-sandbox.myshopify.com \
- --only snippets/color-palette.liquid
-```
-Or run the ready script (backs up the current live snippet to
-`shopify/theme-backups/` first, then PUTs via Asset API):
-```bash
-bash ~/Projects/Designer-Wallcoverings/pending-approval/color-dot-index-10pct-tolerance-2026-07-09/push-to-live.sh
-```
-
-**Revert:** PUT the `theme-backups/color-palette.liquid.LIVE-144076931123.<ts>.bak`
-back to `snippets/color-palette.liquid` on theme 144076931123 (restores the
-current live deeplink behavior).
diff --git a/pending-approval/color-dot-index-10pct-tolerance-2026-07-09/push-to-live.sh b/pending-approval/color-dot-index-10pct-tolerance-2026-07-09/push-to-live.sh
deleted file mode 100755
index 2fddc4c8..00000000
--- a/pending-approval/color-dot-index-10pct-tolerance-2026-07-09/push-to-live.sh
+++ /dev/null
@@ -1,55 +0,0 @@
-#!/usr/bin/env bash
-# STEVE-GATED — STEP 2 of 2. Push the color-palette "index of many" fix to LIVE.
-# Ships snippets/color-palette.liquid ONLY. Backs up the current live version first.
-#
-# ⚠️ RUN STEP 1 FIRST: the /apps/color-index endpoint must be LIVE on
-# photo.designerwallcoverings.com (deploy dwphoto commit 602bb7f + generate the
-# index + restart) or the in-page grid shows the graceful empty state. See MEMO.md.
-#
-# Change: a color-dot click now opens an IN-PAGE grid of MANY products whose
-# dominant color is within 10% (CIELAB deltaE76) of the clicked hex, catalog-wide,
-# instead of deep-linking to ONE product. (Steve 2026-07-09b)
-set -euo pipefail
-
-STORE="designer-laboratory-sandbox.myshopify.com"
-API="2024-10"
-TID="144076931123" # live theme 5.9.1 [Dev] hero-appt-CTA
-SRC="$HOME/Projects/Designer-Wallcoverings/shopify/theme-LIVE-591-freshpull-20260709/snippets/color-palette.liquid"
-SECRETS="$HOME/Projects/secrets-manager/.env"
-
-# quick pre-flight: warn (don't block) if the public endpoint isn't answering yet
-if ! curl -s -m 8 -X POST "https://photo.designerwallcoverings.com/apps/color-index" \
- -H 'Content-Type: application/json' -d '{"hex":"#3C5B49","k":1}' | grep -q '"ok"'; then
- echo "⚠️ WARNING: /apps/color-index is not returning ok yet — run STEP 1 (deploy dwphoto) first,"
- echo " or the grid will show the graceful empty state. Continuing in 5s (Ctrl-C to abort)…"
- sleep 5
-fi
-
-TOK="$(grep -hE '^SHOPIFY_THEME_TOKEN=' "$SECRETS" | head -1 | cut -d= -f2- | tr -d '"'"'"'')"
-[ -z "$TOK" ] && { echo "no theme token"; exit 1; }
-[ -f "$SRC" ] || { echo "source snippet missing: $SRC"; exit 1; }
-echo "token …${TOK: -4} | live theme $TID"
-
-BK="$HOME/Projects/Designer-Wallcoverings/shopify/theme-backups/color-palette.liquid.LIVE-$TID.$(date +%Y%m%d-%H%M%S).bak"
-mkdir -p "$(dirname "$BK")"
-curl -s "https://$STORE/admin/api/$API/themes/$TID/assets.json?asset%5Bkey%5D=snippets/color-palette.liquid" \
- -H "X-Shopify-Access-Token: $TOK" \
- | python3 -c "import sys,json;open('$BK','w').write(json.load(sys.stdin)['asset']['value'])"
-echo "backup: $BK ($(wc -c < "$BK") bytes)"
-
-# PUT the new snippet
-python3 - "$STORE" "$API" "$TID" "$TOK" "$SRC" <<'PY'
-import sys, json, urllib.request
-store, api, tid, tok, src = sys.argv[1:6]
-val = open(src).read()
-body = json.dumps({"asset": {"key": "snippets/color-palette.liquid", "value": val}}).encode()
-r = urllib.request.Request(f"https://{store}/admin/api/{api}/themes/{tid}/assets.json",
- data=body, method="PUT",
- headers={"X-Shopify-Access-Token": tok, "Content-Type": "application/json"})
-resp = urllib.request.urlopen(r)
-print("PUT", resp.status, "OK", f"({len(val)} bytes)")
-PY
-
-echo "verify on any PDP with a color palette:"
-echo " click a color dot → an in-page grid of many products in that color opens below the palette"
-echo "revert: PUT $BK back to snippets/color-palette.liquid on theme $TID"
diff --git a/shopify/theme-LIVE-591/snippets/color-palette.liquid b/shopify/theme-LIVE-591/snippets/color-palette.liquid
index 33057aac..1ac4d6b5 100644
--- a/shopify/theme-LIVE-591/snippets/color-palette.liquid
+++ b/shopify/theme-LIVE-591/snippets/color-palette.liquid
@@ -1,9 +1,20 @@
{% comment %}
color-palette — "Colors In This Pattern" swatch spectrum.
Shows the pattern's real colors AND ~20 interpolated colors between each pair,
- laid out as uniform swatches centered in even rows. Every swatch (real OR
- interpolated) links to its color-FAMILY collection, derived from the measured
- hex — so there is never a dead/unlinked swatch. (Steve 2026-07-08)
+ laid out as uniform swatches centered in even rows.
+
+ DOT CLICK TARGET (Steve 2026-07-09b): "bring up an index of many images, not
+ just that exact hex. tolerance 10% for color to pull more." A swatch click now
+ opens an IN-PAGE expanding grid of MANY products whose dominant color is within
+ a perceptual 10% tolerance (CIELAB ΔE76) of the clicked hex — CATALOG-WIDE, not
+ one collection. The grid is served by the color-index endpoint
+ POST https://photo.designerwallcoverings.com/apps/color-index {hex, k}
+ (dwphoto → data/color-index.json, one dominant color per ACTIVE product with
+ precomputed LAB). Cards link to /products/<handle> (the roll variant, never the
+ $4.25 Sample). The family-collection href stays as the plain href so
+ middle-click / new-tab / crawlers still get a valid destination, and is the
+ graceful fallback if the endpoint is unreachable.
+ (was: Steve 2026-07-09a, a click deep-linked to the ONE nearest product+variant.)
Data tiers (mirrors color-dots.liquid, kept in sync):
Tier 1 custom.color_dots_json / custom.color_details (enriched JSON: [{hex,name,pct}])
@@ -39,6 +50,19 @@
>
<p class="dw-color-palette__title">Colors In This Pattern</p>
<div class="dw-color-palette__cards" data-dw-color-palette-cards aria-live="polite"></div>
+
+ {%- comment -%} In-page color-index grid the dot brings up (Steve 2026-07-09b).
+ Hidden until a swatch is clicked; then filled with the catalog-wide products
+ within 10% (CIELAB ΔE76) of the clicked hex. {%- endcomment -%}
+ <div class="dw-color-index" data-dw-color-index hidden
+ data-endpoint="https://photo.designerwallcoverings.com/apps/color-index">
+ <div class="dw-color-index__head">
+ <span class="dw-color-index__swatch" data-dw-ci-swatch></span>
+ <span class="dw-color-index__label" data-dw-ci-label aria-live="polite"></span>
+ <button type="button" class="dw-color-index__close" data-dw-ci-close aria-label="Close color index">×</button>
+ </div>
+ <div class="dw-color-index__grid" data-dw-ci-grid aria-live="polite"></div>
+ </div>
</div>
<style>
@@ -66,6 +90,24 @@
.dw-swatch{width:15px;height:15px;}
.dw-swatch--base{width:19px;height:19px;}
}
+ /* ── In-page color-index grid the dot brings up ── */
+ .dw-color-index{max-width:660px;margin:16px auto 4px;text-align:left;}
+ .dw-color-index[hidden]{display:none;}
+ .dw-color-index__head{display:flex;align-items:center;gap:9px;margin:0 0 12px;}
+ .dw-color-index__swatch{width:20px;height:20px;border-radius:4px;flex:0 0 auto;box-shadow:0 0 0 1px rgba(0,0,0,.14);}
+ .dw-color-index__label{font-family:Lora,serif;font-size:13px;color:#3D4246;flex:1 1 auto;}
+ .dw-color-index__label b{font-weight:600;}
+ .dw-color-index__close{flex:0 0 auto;background:none;border:1px solid #e0ded9;border-radius:50%;width:24px;height:24px;line-height:1;font-size:16px;color:#6b6b6b;cursor:pointer;padding:0;}
+ .dw-color-index__close:hover{color:#000;border-color:#b8b3a8;}
+ .dw-color-index__grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:12px;}
+ .dw-color-index__card{display:block;text-decoration:none;color:inherit;}
+ .dw-color-index__card img{width:100%;aspect-ratio:1;object-fit:cover;border:1px solid #e5e5e5;border-radius:3px;background:#f4f2ee;}
+ .dw-color-index__cap{display:block;font-family:Lora,serif;font-size:11px;color:#3D4246;margin-top:5px;line-height:1.25;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;text-transform:capitalize;}
+ .dw-color-index__cap i{display:block;font-style:normal;color:#8a8577;font-size:10px;text-transform:none;}
+ .dw-color-index__empty,.dw-color-index__loading{font-family:Lora,serif;font-size:12px;color:#8a8577;padding:8px 0;}
+ @media (max-width:600px){
+ .dw-color-index__grid{grid-template-columns:repeat(auto-fill,minmax(96px,1fr));gap:8px;}
+ }
</style>
<script>
@@ -149,14 +191,95 @@
return 'pink';
}
var PRODUCT_STYLE = (root.dataset.productStyle||'').trim();
- function familyHref(hex){
+ /* The color-FAMILY collection handle for a hex (used both as the search space
+ for the nearest-specific-product resolver AND as the graceful fallback). */
+ function familyHandle(hex){
var fam=familyFromHex(hex); if(!fam) return null;
- var handle=FAM_COLL[fam]; if(!handle) return null;
+ return FAM_COLL[fam] || null;
+ }
+ /* Fallback collection URL — only used if we can't resolve a single specific
+ product of that hue (edge case). (Steve 2026-07-09: DEFAULT must be a
+ specific product+variant, this is the graceful floor.) */
+ function familyHref(hex){
+ var handle=familyHandle(hex); if(!handle) return null;
var url='/collections/'+handle+'?sort_by=created-descending';
if(PRODUCT_STYLE){ url+='&filter.p.m.custom.style='+encodeURIComponent(PRODUCT_STYLE); }
return url;
}
+ /* ── hex → IN-PAGE INDEX of MANY products within 10% color tolerance ───────
+ Steve 2026-07-09b: "bring up an index of many images, not just that exact
+ hex. tolerance 10% for color to pull more." A dot is a palette-extracted
+ hue; clicking it opens the in-page grid (below the palette) of products
+ whose dominant color is within a perceptual 10% tolerance (CIELAB ΔE76) of
+ the clicked hex, drawn CATALOG-WIDE from the color-index endpoint. Results
+ are cached per hex so re-clicking a swatch is instant. */
+ var CI = root.querySelector('[data-dw-color-index]');
+ var CI_ENDPOINT = (CI && CI.dataset.endpoint) || 'https://photo.designerwallcoverings.com/apps/color-index';
+ var CI_K = 36; // cards to show (endpoint caps at 60)
+ var _ciCache = {}; // hex -> Promise<{results,total,tolerance_pct}>
+ function fetchColorIndex(hex){
+ if(_ciCache[hex]) return _ciCache[hex];
+ _ciCache[hex]=fetch(CI_ENDPOINT,{
+ method:'POST', mode:'cors', headers:{'Content-Type':'application/json'},
+ body:JSON.stringify({hex:hex, k:CI_K})
+ }).then(function(r){ return r.ok?r.json():{results:[]}; })
+ .catch(function(){ return {results:[], _err:true}; });
+ return _ciCache[hex];
+ }
+ function ciEl(sel){ return CI ? CI.querySelector(sel) : null; }
+ function closeIndex(){ if(CI){ CI.hidden=true; } }
+ if(CI){
+ var _cx=ciEl('[data-dw-ci-close]');
+ if(_cx) _cx.addEventListener('click', closeIndex);
+ }
+ /* Open the in-page index for a clicked hex. */
+ function openIndex(hex, name){
+ if(!CI) return;
+ var sw=ciEl('[data-dw-ci-swatch]'), lbl=ciEl('[data-dw-ci-label]'), grid=ciEl('[data-dw-ci-grid]');
+ var fam=familyFromHex(hex)||'';
+ if(sw) sw.style.background=hex;
+ if(lbl) lbl.innerHTML='Wallcoverings in this color — <b>'+(name||fam||hex)+'</b>';
+ if(grid) grid.innerHTML='<div class="dw-color-index__loading">Finding matches…</div>';
+ CI.hidden=false;
+ CI.scrollIntoView({behavior:'smooth', block:'nearest'});
+ fetchColorIndex(hex).then(function(d){
+ if(!grid) return;
+ var res=(d&&d.results)||[];
+ if(!res.length){
+ grid.innerHTML='';
+ var empty=document.createElement('div'); empty.className='dw-color-index__empty';
+ empty.textContent = d && d._err
+ ? 'Could not load matches right now — try another color.'
+ : 'No close matches for this color yet.';
+ grid.appendChild(empty);
+ return;
+ }
+ grid.innerHTML='';
+ var frag=document.createDocumentFragment();
+ res.forEach(function(p){
+ if(!p || !p.handle) return;
+ var a=document.createElement('a'); a.className='dw-color-index__card'; a.rel='nofollow';
+ // link straight to the PDP handle — the roll/first-available variant,
+ // never the $4.25 Sample (Sample is never the default variant).
+ a.href='/products/'+encodeURIComponent(p.handle);
+ var img=document.createElement('img'); img.loading='lazy';
+ img.src=p.image||''; img.alt=p.title||'';
+ var cap=document.createElement('span'); cap.className='dw-color-index__cap';
+ cap.textContent=(p.title||'').replace(/\s*\|\s*.*$/,''); // pattern part
+ if(p.vendor){ var v=document.createElement('i'); v.textContent=p.vendor; cap.appendChild(v); }
+ a.appendChild(img); a.appendChild(cap); frag.appendChild(a);
+ });
+ grid.appendChild(frag);
+ });
+ }
+ /* Click handler shared by every swatch: open the in-page index for that hue.
+ Modified clicks (new tab) fall through to the plain family-collection href. */
+ function onSwatchClick(hex, ev, name){
+ ev.preventDefault();
+ openIndex(hex, name);
+ }
+
/* ── color math: normalize + interpolate ── */
function normHex(hex){
var rgb=hexToRgb(hex); if(!rgb) return null;
@@ -169,20 +292,30 @@
}
var STEPS = 20; /* interpolated colors served BETWEEN each pair (Steve 2026-07-08) */
- /* Build one swatch. Every swatch links to its hex-derived family collection. */
+ /* Build one swatch. Left-click opens the IN-PAGE index of many products
+ within 10% of that hue (onSwatchClick → openIndex); the href is the family
+ collection so middle-click / new-tab / crawlers still get a valid
+ destination. (Steve 2026-07-09b: "bring up an index of many images.") */
function makeSwatch(hex, opts){
opts=opts||{};
var href=familyHref(hex);
var el=document.createElement(href?'a':'span');
el.className='dw-swatch'+(opts.base?' dw-swatch--base':'');
el.style.background=hex;
- if(href) el.href=href;
+ if(href){
+ el.href=href;
+ el.addEventListener('click', (function(nm){ return function(ev){
+ // let modified clicks (new tab / new window) use the plain-collection href
+ if(ev.metaKey||ev.ctrlKey||ev.shiftKey||ev.altKey||ev.button!==0) return;
+ onSwatchClick(hex, ev, nm);
+ }; })(opts.name));
+ }
var fam=familyFromHex(hex);
if(opts.base){
var lbl=(opts.name?opts.name:(fam||''));
- el.title='Shop '+(fam||'this')+' wallcoverings'+(lbl&&lbl!==fam?' — '+lbl:'')+' ('+hex+(opts.pct!=null?', '+opts.pct+'% here':'')+')';
+ el.title='Shop this '+(fam||'')+' color'+(lbl&&lbl!==fam?' — '+lbl:'')+' ('+hex+(opts.pct!=null?', '+opts.pct+'% here':'')+')';
} else {
- el.title='Shop '+(fam||'this')+' wallcoverings ('+hex+')';
+ el.title='Shop this '+(fam||'')+' color ('+hex+')';
}
return el;
}
@@ -223,7 +356,13 @@
named.forEach(function(c){
var href=familyHref(c.hex);
var it=document.createElement(href?'a':'span');
- if(href) it.href=href;
+ if(href){
+ it.href=href;
+ (function(hx, nm){ it.addEventListener('click', function(ev){
+ if(ev.metaKey||ev.ctrlKey||ev.shiftKey||ev.altKey||ev.button!==0) return;
+ onSwatchClick(hx, ev, nm);
+ }); })(c.hex, c.name);
+ }
it.innerHTML='<i style="background:'+c.hex+'"></i>'+c.name+(c.pct!=null?' <b style="font-weight:600;color:#8a8577">'+c.pct+'%</b>':'');
leg.appendChild(it);
});
@@ -295,3 +434,56 @@
if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',init);}else{init();}
})();
</script>
+
+{%- comment -%} CLIP "More like this" — PDP color-dot CLIP visual-similarity row.
+ Calls the SAME-BEHAVIOR public proxy /apps/similar (dwphoto → loopback CLIP :9914),
+ which returns the visually-nearest patterns to THIS product, softly re-ranked by the
+ same hex→color-family bucket + style overlap the swatches use. Additive: the swatch
+ spectrum + its color-family links above are unchanged. dw_sku / hex / style come from
+ the product's REAL fields. (Steve "ship the CLIP dots" 2026-07-08) {%- endcomment -%}
+{%- assign _dw_sku = product.selected_or_first_available_variant.sku | default: product.variants.first.sku | remove: '-Sample' -%}
+{%- assign _dw_hex = product.metafields.custom.primary_color_hex.value | default: product.metafields.custom.color_hex.value | default: product.metafields.dwc.dominant_hex.value -%}
+{%- assign _dw_style = product.metafields.custom.style.value | default: product.metafields.global.Style.value | default: product.type -%}
+{%- if _dw_sku != blank -%}
+<div id="dw-similar" class="dw-similar"
+ data-dw-sku="{{ _dw_sku | escape }}"
+ data-hex="{{ _dw_hex | escape }}"
+ data-style="{{ _dw_style | escape }}"
+ data-endpoint="https://photo.designerwallcoverings.com/apps/similar">
+ <h3 class="dw-similar__h">More Wallcoverings Like This</h3>
+ <div class="dw-similar__grid" aria-live="polite"></div>
+</div>
+<script>
+(function(){
+ var root=document.getElementById('dw-similar'); if(!root) return;
+ var sku=(root.dataset.dwSku||'').trim().replace(/-Sample$/i,''); // base SKU keys the CLIP index; the Sample variant SKU (usually first-available) has no embedding
+ if(!sku){ root.style.display='none'; return; }
+ var ep=root.dataset.endpoint||'https://photo.designerwallcoverings.com/apps/similar';
+ fetch(ep,{method:'POST',mode:'cors',headers:{'Content-Type':'application/json'},
+ body:JSON.stringify({dw_sku:sku, hex:root.dataset.hex||'', style:root.dataset.style||'', k:8})})
+ .then(function(r){return r.ok?r.json():{results:[]};})
+ .then(function(d){
+ var g=root.querySelector('.dw-similar__grid');
+ if(!d.results||!d.results.length){ root.style.display='none'; return; }
+ d.results.forEach(function(p){
+ var a=document.createElement('a'); a.className='dw-similar__card'; a.rel='nofollow';
+ // real PDP link when we have a storefront handle; else fall back to a SKU search (never a name-search)
+ a.href = p.handle ? ('/products/'+encodeURIComponent(p.handle))
+ : ('/search?q='+encodeURIComponent(p.dw_sku||'')+'&type=product');
+ var img=document.createElement('img'); img.loading='lazy'; img.src=p.image||''; img.alt=p.pattern||'';
+ var cap=document.createElement('span'); cap.className='dw-similar__cap'; cap.textContent=p.pattern||'';
+ a.appendChild(img); a.appendChild(cap); g.appendChild(a);
+ });
+ }).catch(function(){ root.style.display='none'; });
+})();
+</script>
+<style>
+.dw-similar{margin:20px auto 8px;max-width:660px;text-align:center}
+.dw-similar__h{font-family:Lora,serif;font-size:15px;font-weight:500;color:#3D4246;letter-spacing:-0.01em;margin:0 0 12px}
+.dw-similar__grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:10px}
+.dw-similar__card{display:block;text-decoration:none;color:inherit}
+.dw-similar__card img{width:100%;aspect-ratio:1;object-fit:cover;border:1px solid #e5e5e5;border-radius:3px}
+.dw-similar__cap{display:block;font-family:Lora,serif;font-size:11px;color:#3D4246;margin-top:5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-transform:capitalize}
+.dw-similar:empty{display:none}
+</style>
+{%- endif -%}
← 0da7a73d ship: color-dot 10% CIELAB tolerance grid — STEP 1 (dwphoto
·
back to Designer Wallcoverings
·
auto-save: 2026-07-09T10:10:01 (5 files) — pending-approval/ 3816d8b2 →