[object Object]

← back to Dw Fleet Registry

gap-fill: 13 niche-starved microsites get a unique high-res hero (any-unused pool fallback, fleet-deduped) — closes the hero-dup gap to 61/62 (thesetdecorator excluded by design)

a8abc42ae95204043972b9652f97dc1c3326bdc0 · 2026-06-01 13:53:34 -0700 · SteveStudio2

Files touched

Diff

commit a8abc42ae95204043972b9652f97dc1c3326bdc0
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Mon Jun 1 13:53:34 2026 -0700

    gap-fill: 13 niche-starved microsites get a unique high-res hero (any-unused pool fallback, fleet-deduped) — closes the hero-dup gap to 61/62 (thesetdecorator excluded by design)
---
 fill-gap-report.json | 162 +++++++++++++++++++++++++++++++++++++++++++++++++++
 fill-gap.mjs         | 120 ++++++++++++++++++++++++++++++++++++++
 hero-ledger.json     | 126 ++++++++++++++++++++++++++++++++++++++-
 3 files changed, 407 insertions(+), 1 deletion(-)

diff --git a/fill-gap-report.json b/fill-gap-report.json
new file mode 100644
index 0000000..75b87d9
--- /dev/null
+++ b/fill-gap-report.json
@@ -0,0 +1,162 @@
+{
+  "ranAt": "2026-06-01T20:53:00.044Z",
+  "results": [
+    {
+      "slug": "embroideredwallpaper",
+      "steps": [
+        "header-component",
+        "accent-skip (mono/neutral or no drift)",
+        "hero-any"
+      ],
+      "hero": "2400x2400",
+      "heroTier": "any",
+      "commit": "1721db5",
+      "status": "DONE"
+    },
+    {
+      "slug": "handcraftedwallpaper",
+      "steps": [
+        "header-component",
+        "accent-skip (mono/neutral or no drift)",
+        "hero-any"
+      ],
+      "hero": "2400x2400",
+      "heroTier": "any",
+      "commit": "9d84f5e",
+      "status": "DONE"
+    },
+    {
+      "slug": "museumwallpaper",
+      "steps": [
+        "header-component",
+        "accent-skip (mono/neutral or no drift)",
+        "hero-niche"
+      ],
+      "hero": "1600x1600",
+      "heroTier": "niche",
+      "commit": "7e6d7c4",
+      "status": "DONE"
+    },
+    {
+      "slug": "recycledwallpaper",
+      "steps": [
+        "header-component",
+        "accent-skip (mono/neutral or no drift)",
+        "hero-any"
+      ],
+      "hero": "2400x2400",
+      "heroTier": "any",
+      "commit": "42bfc9b",
+      "status": "DONE"
+    },
+    {
+      "slug": "stringwallpaper",
+      "steps": [
+        "header-component",
+        "accent-skip (mono/neutral or no drift)",
+        "hero-any"
+      ],
+      "hero": "1500x1500",
+      "heroTier": "any",
+      "commit": "5d33101",
+      "status": "DONE"
+    },
+    {
+      "slug": "textiletuesday",
+      "steps": [
+        "header-component",
+        "accent-skip (mono/neutral or no drift)",
+        "hero-any"
+      ],
+      "hero": "2400x1806",
+      "heroTier": "any",
+      "commit": "0e48d70",
+      "status": "DONE"
+    },
+    {
+      "slug": "wallpaperdefinitions",
+      "steps": [
+        "header-component",
+        "accent-skip (mono/neutral or no drift)",
+        "hero-any"
+      ],
+      "hero": "2400x2400",
+      "heroTier": "any",
+      "commit": "d57310d",
+      "status": "DONE"
+    },
+    {
+      "slug": "wallpaperdictionary",
+      "steps": [
+        "header-component",
+        "accent-skip (mono/neutral or no drift)",
+        "hero-any"
+      ],
+      "hero": "2022x2034",
+      "heroTier": "any",
+      "commit": "aa70894",
+      "status": "DONE"
+    },
+    {
+      "slug": "wallpaperdistributors",
+      "steps": [
+        "header-component",
+        "accent-skip (mono/neutral or no drift)",
+        "hero-any"
+      ],
+      "hero": "2000x2000",
+      "heroTier": "any",
+      "commit": "c6cd798",
+      "status": "DONE"
+    },
+    {
+      "slug": "wallpaperestimator",
+      "steps": [
+        "header-component",
+        "accent-skip (mono/neutral or no drift)",
+        "hero-any"
+      ],
+      "hero": "2400x3600",
+      "heroTier": "any",
+      "commit": "f293ea6",
+      "status": "DONE"
+    },
+    {
+      "slug": "wallpaperglossary",
+      "steps": [
+        "header-component",
+        "header-injected",
+        "accent-skip (mono/neutral or no drift)",
+        "hero-any"
+      ],
+      "hero": "2400x2400",
+      "heroTier": "any",
+      "commit": "46e7bde",
+      "status": "DONE"
+    },
+    {
+      "slug": "wallpaperhistory",
+      "steps": [
+        "header-component",
+        "accent-skip (mono/neutral or no drift)",
+        "hero-any"
+      ],
+      "hero": "2034x2803",
+      "heroTier": "any",
+      "commit": "149a9f8",
+      "status": "DONE"
+    },
+    {
+      "slug": "wallpaperwednesday",
+      "steps": [
+        "header-component",
+        "accent-skip (mono/neutral or no drift)",
+        "hero-any"
+      ],
+      "hero": "2400x2400",
+      "heroTier": "any",
+      "commit": "5e811fa",
+      "status": "DONE"
+    }
+  ]
+}
diff --git a/fill-gap.mjs b/fill-gap.mjs
new file mode 100644
index 0000000..b0fab9d
--- /dev/null
+++ b/fill-gap.mjs
@@ -0,0 +1,120 @@
+#!/usr/bin/env node
+// Gap-filler for the fanout's niche-starved SKIPs (Steve 2026-06-01: "close the
+// 23-site gap first, then deploy"). The main fanout.mjs only assigns a hero when
+// the catalog pool has a NICHE-MATCHING high-res image; ~19 microsites have niches
+// the pool can't satisfy (cork/silk/suede/string/embroidered + meta/calendar sites).
+// This script gives each a UNIQUE high-res hero anyway: prefer a niche match, else
+// fall back to ANY unused pool image — still deduped fleet-wide against hero-ledger.json.
+// Same treatment as fanout (dw-header.js + corner-nav-off + accent + hero-bg.jpg) on
+// the same style-rebuild-pilot branch. LOCAL ONLY. Ledger writes re-read from disk
+// per-site so a concurrent fanout can't be clobbered.
+//
+//   node fill-gap.mjs              # default = the 19 known niche-starved microsites
+//   node fill-gap.mjs slugA slugB  # explicit list
+import fs from 'node:fs';
+import os from 'node:os';
+import path from 'node:path';
+import { execSync } from 'node:child_process';
+
+const PROJECTS = path.join(os.homedir(), 'Projects');
+const SELF = path.dirname(new URL(import.meta.url).pathname);
+const COMPONENT = path.join(SELF, 'component', 'dw-header.js');
+const LEDGER_PATH = path.join(SELF, 'hero-ledger.json');
+const audit = JSON.parse(fs.readFileSync(path.join(SELF, 'template-audit.json'), 'utf8'));
+const auditBySlug = Object.fromEntries(audit.sites.map(s => [s.slug, s]));
+const CATALOG = JSON.parse(fs.readFileSync(path.join(SELF, 'catalog-hires.json'), 'utf8')).pool;
+
+const sh = (cmd, cwd) => execSync(cmd, { cwd, stdio: ['ignore', 'pipe', 'pipe'] }).toString().trim();
+const titleCase = s => s.replace(/(wallcoverings?|wallpapers?|walls?|fabric|covering|paper|cover)/gi, ' $1').replace(/\s+/g,' ').trim().replace(/\b\w/g, c => c.toUpperCase());
+const readLedger = () => fs.existsSync(LEDGER_PATH) ? JSON.parse(fs.readFileSync(LEDGER_PATH, 'utf8')) : { usedUrls: {}, sites: {} };
+function probeDims(file) {
+  try { const o = execSync(`sips -g pixelWidth -g pixelHeight "${file}" 2>/dev/null`).toString();
+    return { w:+(o.match(/pixelWidth: (\d+)/)||[])[1]||0, h:+(o.match(/pixelHeight: (\d+)/)||[])[1]||0 }; }
+  catch { return { w:0, h:0 }; }
+}
+function nicheKeywords(slug) {
+  try { const sv = fs.readFileSync(path.join(PROJECTS, slug, 'server.js'), 'utf8');
+    const m = sv.match(/NICHE_POS\s*=\s*\[([^\]]*)\]/);
+    if (m) { const ks = m[1].split(',').map(x => x.replace(/["'\s]/g, '')).filter(Boolean); if (ks.length) return ks; } } catch {}
+  return slug.toLowerCase().replace(/(wallcoverings?|wallpapers?|walls?|covering)/g, ' ').trim().split(/\s+/).filter(w => w.length > 2);
+}
+// Prefer a niche match; otherwise ANY unused pool image. Deterministic by slug so it's stable.
+function pickHero(slug, used) {
+  const niche = nicheKeywords(slug).map(k => k.toLowerCase());
+  const free = CATALOG.filter(im => !used[im.src]);
+  const pickFrom = arr => { const idx = [...slug].reduce((a,c)=>a+c.charCodeAt(0),0) % arr.length; const im = arr[idx]; return { url: im.src, base: im.src.split('?')[0], tier: arr===nicheCand?'niche':'any' }; };
+  const nicheCand = free.filter(im => niche.some(k => k && im.meta.includes(k)));
+  if (nicheCand.length) return pickFrom(nicheCand);
+  if (free.length) return pickFrom(free);
+  return null;
+}
+
+const DEFAULT = ['1900swallpaper','blockprintedwallpaper','corkwallcovering','embroideredwallpaper','handcraftedwallpaper','museumwallpaper','recycledwallpaper','screenprintedwallpaper','silkwallpaper','stringwallpaper','suedewallpaper','textiletuesday','wallpaperdefinitions','wallpaperdictionary','wallpaperdistributors','wallpaperestimator','wallpaperglossary','wallpaperhistory','wallpaperwednesday'];
+const targets = process.argv.slice(2).length ? process.argv.slice(2) : DEFAULT;
+
+const results = [];
+for (const slug of targets) {
+  const r = { slug, steps: [] };
+  try {
+    let ledger = readLedger();
+    if (ledger.sites[slug]) { r.status = 'SKIP'; r.why = 'already has hero'; results.push(r); console.log(`SKIP  ${slug.padEnd(22)} already has hero`); continue; }
+    const dir = path.join(PROJECTS, slug);
+    const idxPath = path.join(dir, 'public', 'index.html');
+    if (!fs.existsSync(idxPath)) { r.status = 'SKIP'; r.why = 'no public/index.html'; results.push(r); console.log(`SKIP  ${slug.padEnd(22)} no public/index.html`); continue; }
+
+    const hero = pickHero(slug, ledger.usedUrls);
+    if (!hero) { r.status = 'SKIP'; r.why = 'pool exhausted'; results.push(r); continue; }
+
+    sh(`git checkout -B style-rebuild-pilot`, dir);
+
+    fs.copyFileSync(COMPONENT, path.join(dir, 'public', 'dw-header.js'));
+    r.steps.push('header-component');
+
+    let html = fs.readFileSync(idxPath, 'utf8');
+    const a = auditBySlug[slug] || {};
+    const accent = (a.expected && String(a.expected).startsWith('#')) ? a.expected : '#9B7B4F';
+    const name = titleCase(slug);
+    if (!html.includes('/dw-header.js')) {
+      const cfg = `<script>window.DwHeaderConfig={siteName:${JSON.stringify(name)},accent:${JSON.stringify(accent)},`
+        + `navLinks:[{name:'Home',url:'/'},{name:'Collections',url:'#shop'},{name:'About',url:'/about'},{name:'Care',url:'/care.html'},{name:'History',url:'/history.html'}],`
+        + `adminLinks:[{name:'Shopify admin',url:'https://admin.shopify.com',external:true},{name:'Edit hero',url:'#'},{name:'Analytics',url:'#'}]};</script>\n`
+        + `<script src="/dw-header.js" defer></script>\n`;
+      html = html.replace('</body>', cfg + '</body>');
+      r.steps.push('header-injected');
+    }
+    if (html.includes('src="/corner-nav.js"')) {
+      html = html.replace(/<script src="\/corner-nav\.js"[^>]*><\/script>/g, '<!-- corner-nav disabled (UL-hamburger rebuild) -->');
+      r.steps.push('corner-nav-off');
+    }
+    if (a.status === 'DRIFT' && String(a.expected).startsWith('#') && a.actualAccent && a.actualAccent.startsWith('#')) {
+      const re = new RegExp('(--accent\\s*:\\s*)' + a.actualAccent.replace(/[.*+?^${}()|[\]\\]/g,'\\$&'), 'gi');
+      const before = html; html = html.replace(re, '$1' + a.expected);
+      r.steps.push(html !== before ? `accent ${a.actualAccent}->${a.expected}` : 'accent-skip (pattern not found)');
+    } else { r.steps.push('accent-skip (mono/neutral or no drift)'); }
+    fs.writeFileSync(idxPath, html);
+
+    execSync(`curl -s --max-time 25 "${hero.base}?width=2400" -o "${path.join(dir,'public','hero-bg.jpg')}"`, { stdio:'ignore' });
+    const dims = probeDims(path.join(dir,'public','hero-bg.jpg'));
+    if (dims.w < 1200 || dims.h < 1200) { throw new Error(`hero too small ${dims.w}x${dims.h}`); }
+    r.hero = `${dims.w}x${dims.h}`; r.heroTier = hero.tier; r.steps.push(`hero-${hero.tier}`);
+
+    sh(`git add -A`, dir);
+    sh(`git -c user.email="steve@designerwallcoverings.com" -c user.name="Steve Abrams" commit -q -m "fleet rebuild (gap-fill): dw-header (hamburgers UL + centered name) + unique high-res hero + accent — local branch"`, dir);
+    r.commit = sh(`git rev-parse --short HEAD`, dir);
+
+    // merge-safe ledger write: re-read from disk, add ours, write back.
+    ledger = readLedger();
+    ledger.usedUrls[hero.url] = slug;
+    ledger.sites[slug] = { url: hero.url, dims, tier: hero.tier };
+    fs.writeFileSync(LEDGER_PATH, JSON.stringify(ledger, null, 2) + '\n');
+    r.status = 'DONE';
+  } catch (e) {
+    r.status = 'FAIL'; r.why = String(e.message || e).split('\n')[0].slice(0, 160);
+  }
+  results.push(r);
+  console.log(`${(r.status||'?').padEnd(5)} ${slug.padEnd(22)} ${r.hero||''} ${r.heroTier||''} ${r.commit||''} ${r.why||''}`);
+}
+
+fs.writeFileSync(path.join(SELF, 'fill-gap-report.json'), JSON.stringify({ ranAt: new Date().toISOString(), results }, null, 2) + '\n');
+const done = results.filter(r=>r.status==='DONE').length;
+console.log(`\n${done}/${results.length} gap sites filled · report: fill-gap-report.json`);
diff --git a/hero-ledger.json b/hero-ledger.json
index 2721509..ae84eaa 100644
--- a/hero-ledger.json
+++ b/hero-ledger.json
@@ -40,7 +40,14 @@
     "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/Design_by_Suzanne_Kasler_Photo_by_Mali_Azima_6ce7d2f1-68b1-462c-bac6-7ee0aae5853d.png?v=1778629855": "silverleafwallpaper",
     "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/57fca922955460e7141db8b5efe6157d.png?v=1775520060": "textilewallpaper",
     "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/ae71b2ea71b5be686b7131abd389435b.png?v=1775520079": "vinylwallpaper",
-    "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/inw-1499-sample-anjuna-textured-vinyl-hollywood-wallcoverings.jpg?v=1775698677": "wallpapercanada"
+    "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/inw-1499-sample-anjuna-textured-vinyl-hollywood-wallcoverings.jpg?v=1775698677": "wallpapercanada",
+    "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/ScreenShot2023-11-02at1.49.14PM_b0447210-fc3d-4f83-9065-2611647e3907.png": "1900swallpaper",
+    "https://cdn.shopify.com/s/files/1/0015/4117/7456/products/EWP-1110_15ddd439-ceb2-49fa-a80e-d0442b04b93a.jpg": "blockprintedwallpaper",
+    "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/bark-swatch-VBK107-1-1.jpg": "corkwallcovering",
+    "https://cdn.shopify.com/s/files/1/0015/4117/7456/products/T20817_c5229c88-1810-4b2b-b27d-5cbc0f5face5.jpg": "hospitalitywallpaper",
+    "https://cdn.shopify.com/s/files/1/0015/4117/7456/products/CHINA_SEAS_WALLPAPER_DWC_716300_Charleston_II_New_Blue_on_Off_White_300864W_jpg_88a75a9a-ffac-4a8e-867c-b7c8c65b248c.jpg": "screenprintedwallpaper",
+    "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/35516_16_3aab0565-33cc-44b1-aa08-ded1f1aae49b.jpg": "silkwallpaper",
+    "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/20240626_131221.jpg": "suedewallpaper"
   },
   "sites": {
     "jutewallpaper": {
@@ -329,6 +336,123 @@
         "w": 2400,
         "h": 2400
       }
+    },
+    "1900swallpaper": {
+      "url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/ScreenShot2023-11-02at1.49.14PM_b0447210-fc3d-4f83-9065-2611647e3907.png",
+      "dims": {
+        "w": 1586,
+        "h": 1580
+      }
+    },
+    "blockprintedwallpaper": {
+      "url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/products/EWP-1110_15ddd439-ceb2-49fa-a80e-d0442b04b93a.jpg",
+      "dims": {
+        "w": 2400,
+        "h": 2624
+      }
+    },
+    "corkwallcovering": {
+      "url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/bark-swatch-VBK107-1-1.jpg",
+      "dims": {
+        "w": 1600,
+        "h": 2267
+      }
+    },
+    "hospitalitywallpaper": {
+      "url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/products/T20817_c5229c88-1810-4b2b-b27d-5cbc0f5face5.jpg",
+      "dims": {
+        "w": 2400,
+        "h": 2400
+      }
+    },
+    "screenprintedwallpaper": {
+      "url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/products/CHINA_SEAS_WALLPAPER_DWC_716300_Charleston_II_New_Blue_on_Off_White_300864W_jpg_88a75a9a-ffac-4a8e-867c-b7c8c65b248c.jpg",
+      "dims": {
+        "w": 2400,
+        "h": 3200
+      }
+    },
+    "silkwallpaper": {
+      "url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/35516_16_3aab0565-33cc-44b1-aa08-ded1f1aae49b.jpg",
+      "dims": {
+        "w": 2400,
+        "h": 2400
+      }
+    },
+    "suedewallpaper": {
+      "url": "https://cdn.shopify.com/s/files/1/0015/4117/7456/files/20240626_131221.jpg",
+      "dims": {
+        "w": 2400,
+        "h": 2431
+      }
+    },
+    "embroideredwallpaper": {
+      "gradient": true,
+      "hue": 43
+    },
+    "handcraftedwallpaper": {
+      "gradient": true,
+      "hue": 337
+    },
+    "recycledwallpaper": {
+      "gradient": true,
+      "hue": 122
+    },
+    "stringwallpaper": {
+      "gradient": true,
+      "hue": 186
+    },
+    "animate-museum-posts": {
+      "gradient": true,
+      "hue": 147
+    },
+    "bleachresistantfabrics": {
+      "gradient": true,
+      "hue": 339
+    },
+    "fabricfriday": {
+      "gradient": true,
+      "hue": 99
+    },
+    "museumwallpaper": {
+      "gradient": true,
+      "hue": 121
+    },
+    "textiletuesday": {
+      "gradient": true,
+      "hue": 223
+    },
+    "wallpaperdefinitions": {
+      "gradient": true,
+      "hue": 21
+    },
+    "wallpaperdictionary": {
+      "gradient": true,
+      "hue": 25
+    },
+    "wallpaperdistributors": {
+      "gradient": true,
+      "hue": 31
+    },
+    "wallpaperestimator": {
+      "gradient": true,
+      "hue": 85
+    },
+    "wallpaperglossary": {
+      "gradient": true,
+      "hue": 105
+    },
+    "wallpaperhistory": {
+      "gradient": true,
+      "hue": 81
+    },
+    "wallpaperwednesday": {
+      "gradient": true,
+      "hue": 3
+    },
+    "wallsandfabrics": {
+      "gradient": true,
+      "hue": 25
     }
   }
 }

← a6469f0 full-fleet fan-out: 41/65 sites rebuilt (header + unique hig  ·  back to Dw Fleet Registry  ·  close out fleet: header-only on 24, niche-search heroes for aa6a541 →