← back to Japan Enrich
greenland: serve colorway swatches locally (/img/greenland/), self-contained china.
6f12a631d3cb2012b873d2a649e8ae12d8280f42 · 2026-07-01 10:35:13 -0700 · Steve Abrams
- /img/greenland/<sku> route serves the downscaled swatch from images/greenland/<normSku>.jpg
(path-guarded); greenSwatches Set built at load + refreshed on reload.
- greenland cards prefer the local swatch, falling back to the remote downloadFile CDN url
for the ~717 cover-only/failed patterns. 120/120 first-page cards now serve locally.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
Diff
commit 6f12a631d3cb2012b873d2a649e8ae12d8280f42
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Jul 1 10:35:13 2026 -0700
greenland: serve colorway swatches locally (/img/greenland/), self-contained china.
- /img/greenland/<sku> route serves the downscaled swatch from images/greenland/<normSku>.jpg
(path-guarded); greenSwatches Set built at load + refreshed on reload.
- greenland cards prefer the local swatch, falling back to the remote downloadFile CDN url
for the ~717 cover-only/failed patterns. 120/120 first-page cards now serve locally.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
viewer-local/server.js | 23 ++++++++++++++++++++---
1 file changed, 20 insertions(+), 3 deletions(-)
diff --git a/viewer-local/server.js b/viewer-local/server.js
index a07af28..07b6b0e 100644
--- a/viewer-local/server.js
+++ b/viewer-local/server.js
@@ -18,6 +18,7 @@ const SANG = path.join(DIR, 'sangetsu-staging.jsonl');
const GREEN = path.join(DIR, 'greenland-staging.jsonl'); // china. scope: Greenland vendor (pattern→colorways)
const PUBLIC = path.join(__dirname, 'public');
const HENRY_IMG = '/Volumes/Henry/dw-lily-images'; // selectively-fetched pattern swatches
+const GREEN_IMG = path.join(__dirname, '..', 'images', 'greenland'); // downscaled colorway swatches (enrich_greenland.py)
const readJsonl = (f) => (fs.existsSync(f)
? fs.readFileSync(f, 'utf8').trim().split('\n').filter(Boolean).map((l) => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean)
@@ -38,6 +39,16 @@ try {
if (/\.(jpe?g|png)$/i.test(fn)) { const k = normSku(fn.split('_')[0]); if (!allcIndex[k]) allcIndex[k] = path.join(ALLC_DIR, fn); }
}
} catch {}
+// Greenland downscaled swatches are saved as images/greenland/<normSku>.jpg by
+// enrich_greenland.py — a Set of which SKUs have a local swatch, so cards can serve
+// /img/greenland/<sku> (self-contained) and fall back to the remote CDN url otherwise.
+let greenSwatches = new Set();
+const loadGreenSwatches = () => {
+ const s = new Set();
+ try { if (fs.existsSync(GREEN_IMG)) for (const fn of fs.readdirSync(GREEN_IMG)) if (/\.jpe?g$/i.test(fn)) s.add(fn.replace(/\.jpe?g$/i, '')); } catch {}
+ greenSwatches = s;
+};
+loadGreenSwatches();
const ENRICH_FILES = [
process.env.ENRICH_FILE, // explicit override
path.join(DIR, 'lilycolor-enriched.jsonl'), // post-deploy: beside staging
@@ -206,8 +217,8 @@ function buildRows() {
fire_class: null,
price_yen: null,
price_band: null,
- image: (isCw ? imgs[sku] : r.cover_img) || r.cover_img || null,
- image_state: ((isCw ? imgs[sku] : r.cover_img)) ? 'Has swatch' : 'No image',
+ image: (greenSwatches.has(normSku(sku)) ? `/img/greenland/${sku}` : ((isCw ? imgs[sku] : r.cover_img) || r.cover_img)) || null,
+ image_state: ((greenSwatches.has(normSku(sku))) || (isCw ? imgs[sku] : r.cover_img)) ? 'Has swatch' : 'No image',
url: r.source_url || null,
is_sample: false,
colorway_count: isCw ? skus.length : 1,
@@ -228,7 +239,7 @@ function buildRows() {
}
let ROWS = buildRows();
-const reload = () => { ENRICH = loadEnrich(); ROWS = buildRows(); };
+const reload = () => { loadGreenSwatches(); ENRICH = loadEnrich(); ROWS = buildRows(); };
fs.watchFile(SANG, { interval: 4000 }, reload); // pick up Sangetsu staging as it grows
fs.watchFile(LILY, { interval: 8000 }, reload);
try { if (fs.existsSync(GREEN)) fs.watchFile(GREEN, { interval: 8000 }, reload); } catch {}
@@ -378,6 +389,12 @@ const server = http.createServer((req, res) => {
if (f2 && fs.existsSync(f2)) { res.writeHead(200, { 'Content-Type': 'image/jpeg', 'Cache-Control': 'max-age=86400' }); return res.end(fs.readFileSync(f2)); }
res.writeHead(404); return res.end('no image');
}
+ if (u.pathname.startsWith('/img/greenland/')) {
+ const sku = decodeURIComponent(u.pathname.slice('/img/greenland/'.length));
+ const f = path.join(GREEN_IMG, `${normSku(sku)}.jpg`);
+ if (f.startsWith(GREEN_IMG) && fs.existsSync(f)) { res.writeHead(200, { 'Content-Type': 'image/jpeg', 'Cache-Control': 'max-age=86400' }); return res.end(fs.readFileSync(f)); }
+ res.writeHead(404); return res.end('no image');
+ }
// static
let p = u.pathname === '/' ? '/index.html' : u.pathname;
const file = path.join(PUBLIC, path.normalize(p).replace(/^(\.\.[/\\])+/, ''));
← 1f25bc6 sangetsu: backfill colorways/images for 254 imageless patter
·
back to Japan Enrich
·
greenland: clean material facet — scope to fiber taxonomy, d 9f83f10 →