← back to Dw Photo Capture
add /apps/color-widen route: tunable ΔE band (default 15%) for the Shop-by-Color wheel at /pages/colors
e2c9e0167391d8439014c601a8ca7636f914b517 · 2026-07-16 11:03:10 -0700 · Steve
Files touched
Diff
commit e2c9e0167391d8439014c601a8ca7636f914b517
Author: Steve <steve@designerwallcoverings.com>
Date: Thu Jul 16 11:03:10 2026 -0700
add /apps/color-widen route: tunable ΔE band (default 15%) for the Shop-by-Color wheel at /pages/colors
---
server.js | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 58 insertions(+), 1 deletion(-)
diff --git a/server.js b/server.js
index 41e2236..def8fff 100644
--- a/server.js
+++ b/server.js
@@ -901,7 +901,7 @@ function pipeUpstreamImage(src, res, headers) {
const appHandler = (req, res) => {
// public (no-auth) paths: health + the home-screen-install assets iOS fetches without creds
- const PUBLIC = ['/healthz', '/icon-180.png', '/icon-512.png', '/apple-touch-icon.png', '/manifest.webmanifest', '/apps/similar', '/apps/color-index'];
+ const PUBLIC = ['/healthz', '/icon-180.png', '/icon-512.png', '/apple-touch-icon.png', '/manifest.webmanifest', '/apps/similar', '/apps/color-index', '/apps/color-widen'];
const _p = req.url.split('?')[0];
if (!PUBLIC.includes(_p) && !checkAuth(req)) {
res.writeHead(401, { 'WWW-Authenticate': 'Basic realm="DW Photo Capture"' });
@@ -1028,6 +1028,63 @@ const appHandler = (req, res) => {
return;
}
+ // ── Shop-by-Color WHEEL "wider band" index (designerwallcoverings.com/pages/colors) ──
+ // Same in-memory color index + ΔE76 math as /apps/color-index, but with a wider,
+ // caller-tunable tolerance (default 15% ≈ ΔE 15) and a higher result cap, so the
+ // public color-wheel page can surface a BIG set of matches per hue (the PDP dots
+ // stay on the tighter 10%/k60 path — that route is untouched). Read-only, public,
+ // same CORS posture. Input allowlist: ONLY {hex, k, ceiling}.
+ if (u.pathname === '/apps/color-widen') {
+ const ALLOWED_ORIGINS = new Set([
+ 'https://designerwallcoverings.com',
+ 'https://www.designerwallcoverings.com',
+ 'https://designer-laboratory-sandbox.myshopify.com'
+ ]);
+ const origin = req.headers.origin || '';
+ const corsOrigin = ALLOWED_ORIGINS.has(origin) ? origin : 'https://designerwallcoverings.com';
+ const CORS = {
+ 'Access-Control-Allow-Origin': corsOrigin,
+ 'Vary': 'Origin',
+ 'Access-Control-Allow-Methods': 'POST, OPTIONS',
+ 'Access-Control-Allow-Headers': 'Content-Type'
+ };
+ if (req.method === 'OPTIONS') { res.writeHead(204, CORS); return res.end(); }
+ if (req.method !== 'POST') return send(res, 405, { err: 'POST required' }, CORS);
+ let body = '';
+ req.on('data', c => { body += c; if (body.length > 4096) req.destroy(); });
+ req.on('end', () => {
+ let p; try { p = JSON.parse(body || '{}'); } catch (e) { return send(res, 400, { err: 'bad json' }, CORS); }
+ const rawHex = (typeof p.hex === 'string') ? p.hex.trim() : '';
+ const hex = /^#?[0-9a-fA-F]{6}$/.test(rawHex) ? ('#' + rawHex.replace('#', '').toLowerCase()) : '';
+ if (!hex) return send(res, 400, { err: 'hex required (#rrggbb)' }, CORS);
+ const k = Math.max(1, Math.min(parseInt(p.k, 10) || 240, 400));
+ const ceil = Math.max(5, Math.min(Number(p.ceiling) || 15, 25)); // % ≈ ΔE76 ceiling
+ try {
+ const idx = loadColorIndex(); // cached, loaded once
+ const target = hexToLab(hex);
+ const within = [];
+ for (let i = 0; i < idx.length; i++) {
+ const it = idx[i];
+ const dl = it.l - target.l, da = it.a - target.a, db = it.b - target.b;
+ const de = Math.sqrt(dl * dl + da * da + db * db);
+ if (de <= ceil) within.push({ it, de });
+ }
+ within.sort((x, y) => x.de - y.de); // nearest first
+ const results = within.slice(0, k).map(w => ({
+ handle: w.it.h, title: w.it.t, vendor: w.it.v,
+ hex: w.it.x, image: w.it.i, delta_e: Math.round(w.de * 10) / 10
+ }));
+ return send(res, 200, {
+ ok: true, hex, tolerance_pct: ceil / 100,
+ delta_e_ceiling: ceil, total_in_tolerance: within.length, results
+ }, CORS);
+ } catch (e) {
+ return send(res, 503, { ok: false, err: 'color index unavailable', results: [] }, CORS);
+ }
+ });
+ return;
+ }
+
// ── Remote-shutter pairing routes ──
// SSE stream for the PHONE cam page. Registers the cam role; listens for `shoot`.
if (u.pathname === '/cam/events') {
← 5d305fe color-index: exclude sample-only ($4.25) products — require
·
back to Dw Photo Capture
·
color-widen: add min param — guarantee >=N results via neare 0d6c355 →