[object Object]

← 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 →