[object Object]

← back to Dw Fleet Registry

read-only template-drift audit: 37 mapped sites vs assigned fashion templates (30 match, 7 drift)

8886e6674b41becfc82ee87a6c12c6a41cc58b5a · 2026-06-01 13:08:14 -0700 · Steve Abrams

Files touched

Diff

commit 8886e6674b41becfc82ee87a6c12c6a41cc58b5a
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Jun 1 13:08:14 2026 -0700

    read-only template-drift audit: 37 mapped sites vs assigned fashion templates (30 match, 7 drift)
---
 audit-templates.mjs         | 111 ++++++++++++++
 brand/asseeninhotels.html   |   4 +-
 brand/bleachfriendly.html   |   2 +-
 brand/build-themes.mjs      |  36 ++++-
 brand/corkwallcovering.html |   2 +-
 brand/etciemurals.html      |   4 +-
 brand/linenwallpaper.html   |   2 +-
 brand/silkwallpaper.html    |   8 +-
 template-audit.json         | 343 ++++++++++++++++++++++++++++++++++++++++++++
 template-audit.md           |  45 ++++++
 10 files changed, 541 insertions(+), 16 deletions(-)

diff --git a/audit-templates.mjs b/audit-templates.mjs
new file mode 100644
index 0000000..6af9475
--- /dev/null
+++ b/audit-templates.mjs
@@ -0,0 +1,111 @@
+#!/usr/bin/env node
+// READ-ONLY fleet template-drift audit (DTD verdict B, 2026-06-01: dw-fashion-templates
+// is the authoritative style guide). For each mapped DW site, compare its ACTUAL
+// :root --accent / colour scheme against its ASSIGNED fashion template's signature.
+// Writes template-audit.json + template-audit.md. Touches NOTHING on any site.
+
+import fs from 'node:fs';
+import os from 'node:os';
+import path from 'node:path';
+
+const PROJECTS = path.join(os.homedir(), 'Projects');
+const OUT = path.dirname(new URL(import.meta.url).pathname);
+
+// site -> assigned template (from dw-fashion-templates SKILL.md mapping table)
+const MAP = {
+  corkwallcovering:'hermes', silkwallpaper:'saint_laurent', linenwallpaper:'loewe',
+  handcraftedwallpaper:'bottega', '1890swallpaper':'gucci', '1900swallpaper':'gucci',
+  '1920swallpaper':'chanel', '1930swallpaper':'chanel', '1960swallpaper':'prada',
+  '1970swallpaper':'prada', '1980swallpaper':'balenciaga', metallicwallpaper:'dior',
+  mylarwallpaper:'dior', silverleafwallpaper:'valentino', glitterwalls:'valentino',
+  naturalwallcoverings:'celine', recycledwallpaper:'celine', jutewallpaper:'loewe',
+  raffiawalls:'loewe', raffiawallcoverings:'loewe', museumwallpaper:'therow',
+  restaurantwallpaper:'burberry', hotelwallcoverings:'burberry', hospitalitywallpaper:'burberry',
+  contractwallpaper:'toteme', healthcarewallpaper:'toteme', madagascarwallpaper:'khaite',
+  suedewallpaper:'khaite', screenprintedwallpaper:'margiela', blockprintedwallpaper:'margiela',
+  embroideredwallpaper:'miu_miu', textilewallpaper:'miu_miu', selfadhesivewallpaper:'allbirds',
+  apartmentwallpaper:'allbirds', vinylwallpaper:'phoebe_philo', fabricwallpaper:'phoebe_philo',
+  wallpapersback:'reformation',
+};
+
+// template -> expected accent signature. scheme: 'color' (hue-matched) | 'mono' | 'neutral'
+const TPL = {
+  hermes:{primary:'#FF4F00',scheme:'color'}, bottega:{primary:'#107A3B',scheme:'color'},
+  saint_laurent:{scheme:'mono'}, loewe:{primary:'#9B7B4F',scheme:'color'},
+  prada:{scheme:'mono'}, chanel:{scheme:'mono'}, gucci:{primary:'#006837',scheme:'color'},
+  balenciaga:{primary:'#FF4F00',scheme:'color'}, dior:{primary:'#A98B5B',scheme:'color'},
+  valentino:{primary:'#D70000',scheme:'color'}, celine:{scheme:'mono'},
+  toteme:{scheme:'neutral'}, khaite:{primary:'#3A3530',scheme:'neutral'},
+  burberry:{primary:'#A47C5A',scheme:'color'}, miu_miu:{primary:'#FFB6C1',scheme:'color'},
+  margiela:{scheme:'mono'}, allbirds:{scheme:'neutral'}, reformation:{scheme:'neutral'},
+  phoebe_philo:{scheme:'mono'}, therow:{scheme:'neutral'},
+};
+
+function hsl(hex) {
+  const h = String(hex).replace('#', '');
+  const f = h.length === 3 ? h.split('').map(c => c + c).join('') : h;
+  if (!/^[0-9a-f]{6}$/i.test(f)) return null;
+  let r = parseInt(f.slice(0,2),16)/255, g = parseInt(f.slice(2,4),16)/255, b = parseInt(f.slice(4,6),16)/255;
+  const mx=Math.max(r,g,b), mn=Math.min(r,g,b), d=mx-mn, l=(mx+mn)/2;
+  let hh=0, s=0;
+  if (d) { s = d/(1-Math.abs(2*l-1)); if(mx===r)hh=((g-b)/d)%6; else if(mx===g)hh=(b-r)/d+2; else hh=(r-g)/d+4; hh=Math.round(hh*60+360)%360; }
+  return { h: hh, s, l };
+}
+const hueDiff = (a,b) => { const d = Math.abs(a-b)%360; return d>180?360-d:d; };
+
+// pull the FIRST :root --accent (dark default) + --bg from a site's index.html
+function readTokens(slug) {
+  const file = path.join(PROJECTS, slug, 'public', 'index.html');
+  if (!fs.existsSync(file)) return null;
+  const html = fs.readFileSync(file, 'utf8').slice(0, 40000);
+  const acc = html.match(/--accent\s*:\s*(#[0-9a-fA-F]{3,8}|rgb[^;]+)/i);
+  const bg  = html.match(/--bg\s*:\s*(#[0-9a-fA-F]{3,8}|rgb[^;]+)/i);
+  const tmpl = html.match(/template[^*\n]{0,40}/i);   // the "Saint Laurent template" comment, if any
+  return { accent: acc ? acc[1].trim() : null, bg: bg ? bg[1].trim() : null, note: tmpl ? tmpl[0].trim() : null };
+}
+
+function verdict(actualAccent, tpl) {
+  if (!actualAccent || !actualAccent.startsWith('#')) return { v: 'UNKNOWN', why: 'no hex --accent token found' };
+  const a = hsl(actualAccent);
+  if (!a) return { v: 'UNKNOWN', why: 'unparseable accent' };
+  const sat = a.s;
+  if (tpl.scheme === 'mono' || tpl.scheme === 'neutral') {
+    return sat < 0.20
+      ? { v: 'MATCH', why: `${tpl.scheme} template, accent is neutral (sat ${sat.toFixed(2)})` }
+      : { v: 'DRIFT', why: `${tpl.scheme} template expects neutral accent, but accent is saturated (${actualAccent}, sat ${sat.toFixed(2)})` };
+  }
+  // color template — hue must match
+  const exp = hsl(tpl.primary);
+  if (sat < 0.15) return { v: 'DRIFT', why: `template expects ${tpl.primary}, but accent is desaturated (${actualAccent})` };
+  const dh = hueDiff(a.h, exp.h);
+  return dh <= 28
+    ? { v: 'MATCH', why: `accent ${actualAccent} hue ~${a.h}° matches template ${tpl.primary} hue ${exp.h}° (Δ${dh}°)` }
+    : { v: 'DRIFT', why: `accent ${actualAccent} hue ${a.h}° ≠ template ${tpl.primary} hue ${exp.h}° (Δ${dh}°)` };
+}
+
+const rows = [];
+for (const [slug, tplKey] of Object.entries(MAP)) {
+  const tpl = TPL[tplKey];
+  const tok = readTokens(slug);
+  if (!tok) { rows.push({ slug, template: tplKey, status: 'NO-LOCAL-DIR' }); continue; }
+  const ver = verdict(tok.accent, tpl);
+  rows.push({ slug, template: tplKey, expected: tpl.primary || tpl.scheme, actualAccent: tok.accent, actualBg: tok.bg, status: ver.v, why: ver.why });
+}
+
+const counts = rows.reduce((m, r) => (m[r.status] = (m[r.status]||0)+1, m), {});
+const report = { auditedAt: new Date().toISOString(), authority: 'dw-fashion-templates (DTD verdict B)', counts, sites: rows };
+fs.writeFileSync(path.join(OUT, 'template-audit.json'), JSON.stringify(report, null, 2) + '\n');
+
+// markdown
+let md = `# DW Fleet — Template-Drift Audit\n\n_Authority: dw-fashion-templates (DTD verdict B). Read-only._\n\n`;
+md += `**Counts:** ` + Object.entries(counts).map(([k,v])=>`${k} ${v}`).join(' · ') + `\n\n`;
+md += `| Site | Template | Expected | Actual accent | Verdict | Note |\n|---|---|---|---|---|---|\n`;
+for (const r of rows.sort((a,b)=>(a.status>b.status?1:-1)||a.slug.localeCompare(b.slug))) {
+  md += `| ${r.slug} | ${r.template} | ${r.expected||'—'} | ${r.actualAccent||'—'} | ${r.status} | ${r.why||''} |\n`;
+}
+fs.writeFileSync(path.join(OUT, 'template-audit.md'), md);
+
+console.log('Template-drift audit —', Object.entries(counts).map(([k,v])=>`${k}: ${v}`).join('  '));
+console.log('DRIFT sites:');
+for (const r of rows.filter(r=>r.status==='DRIFT')) console.log(`  ${r.slug.padEnd(24)} ${r.template.padEnd(14)} expected ${String(r.expected).padEnd(10)} actual ${r.actualAccent}`);
+console.log(`\nWrote template-audit.json + template-audit.md`);
diff --git a/brand/asseeninhotels.html b/brand/asseeninhotels.html
index 49efc47..8b611eb 100644
--- a/brand/asseeninhotels.html
+++ b/brand/asseeninhotels.html
@@ -4,7 +4,7 @@
 <link href="https://fonts.googleapis.com/css2?family=Bodoni+Moda:ital,opsz,wght@0,6..96,400;0,6..96,500;1,6..96,400&family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
 <script src="https://mcp.figma.com/mcp/html-to-design/capture.js" async></script>
 <style>
-:root{--ink:#f2ecd8;--bg:#f7f3e8;--paper:#f7f3e8;--accent:#b08923;--accent-deep:#7f6319;--rule:#d8caa0;
+:root{--ink:#f2ecd8;--bg:#0d0b06;--paper:#0d0b06;--accent:#b08923;--accent-deep:#7f6319;--rule:rgba(255,255,255,.12);
 --serif:'Bodoni Moda',Didot,'Times New Roman',serif;--sans:'Inter',system-ui,sans-serif;--muted:rgba(255,255,255,.45)}
 *{box-sizing:border-box;margin:0;padding:0}
 body{background:var(--bg);font-family:var(--sans);color:var(--ink);-webkit-font-smoothing:antialiased}
@@ -57,6 +57,6 @@ body{background:var(--bg);font-family:var(--sans);color:var(--ink);-webkit-font-
 <div class="s"><div class="c" style="background:#f7f3e8"></div><div class="m"><div class="n">Light</div><div class="x">#F7F3E8</div></div></div>
 <div class="s"><div class="c" style="background:#f6c031"></div><div class="m"><div class="n">Accent tint</div><div class="x">#F6C031</div></div></div>
 </div></div></div>
-<div class="strip"><div class="tile" style="background:#f2ecd8;color:#f7f3e8">hospitality</div><div class="tile" style="background:#b08923;color:#f7f3e8">hotel</div><div class="tile" style="background:#7f6319;color:#f7f3e8">contract</div><div class="tile" style="background:#584512;color:#f7f3e8">commercial</div></div>
+<div class="strip"><div class="tile" style="background:#f2ecd8;color:#0a1113">hospitality</div><div class="tile" style="background:#b08923;color:#0a1113">hotel</div><div class="tile" style="background:#7f6319;color:#f0f4f5">contract</div><div class="tile" style="background:#584512;color:#f0f4f5">commercial</div></div>
 <div class="ft"><div class="l">As Seen In Hotels</div><div class="r">DW hybrid brand · shared skeleton · per-site theme from style guide · gilt/bodoni</div></div>
 </div></body></html>
\ No newline at end of file
diff --git a/brand/bleachfriendly.html b/brand/bleachfriendly.html
index 2d8ae57..0d67ea8 100644
--- a/brand/bleachfriendly.html
+++ b/brand/bleachfriendly.html
@@ -4,7 +4,7 @@
 <link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,500;0,600;1,400&family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
 <script src="https://mcp.figma.com/mcp/html-to-design/capture.js" async></script>
 <style>
-:root{--ink:#e6eef0;--bg:#f0f4f5;--paper:#f0f4f5;--accent:#2a7d8a;--accent-deep:#1e5a63;--rule:#bdcdd0;
+:root{--ink:#e6eef0;--bg:#0a1113;--paper:#0a1113;--accent:#2a7d8a;--accent-deep:#1e5a63;--rule:rgba(255,255,255,.12);
 --serif:'Cormorant Garamond',Georgia,serif;--sans:'Inter',system-ui,sans-serif;--muted:rgba(255,255,255,.45)}
 *{box-sizing:border-box;margin:0;padding:0}
 body{background:var(--bg);font-family:var(--sans);color:var(--ink);-webkit-font-smoothing:antialiased}
diff --git a/brand/build-themes.mjs b/brand/build-themes.mjs
index 7192f78..fe85f2d 100644
--- a/brand/build-themes.mjs
+++ b/brand/build-themes.mjs
@@ -39,6 +39,25 @@ function darken(hex, amt = 0.32) {
   const b = Math.round((n & 255) * (1 - amt));
   return '#' + [r, g, b].map(x => x.toString(16).padStart(2, '0')).join('');
 }
+// WCAG relative luminance (0=black .. 1=white) from a hex color
+function lum(hex) {
+  const h = String(hex).replace('#', '');
+  const f = h.length === 3 ? h.split('').map(c => c + c).join('') : h;
+  if (f.length < 6) return 0.5;
+  const n = parseInt(f.slice(0, 6), 16);
+  const ch = [(n >> 16) & 255, (n >> 8) & 255, n & 255].map(v => {
+    v /= 255; return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
+  });
+  return 0.2126 * ch[0] + 0.7152 * ch[1] + 0.0722 * ch[2];
+}
+// pick the legible ink (near-black or near-white) for a given background —
+// whichever yields the higher WCAG contrast ratio against that background.
+const inkOn = (bg, dark = '#0a1113', light = '#f0f4f5') => {
+  const L = lum(bg);
+  const cDark = (L + 0.05) / (lum(dark) + 0.05);   // contrast using dark text
+  const cLight = (lum(light) + 0.05) / (L + 0.05); // contrast using light text
+  return cDark >= cLight ? dark : light;
+};
 const esc = s => String(s || '').replace(/&/g, '&amp;').replace(/</g, '&lt;');
 
 const LIVE = process.argv.includes('--live');
@@ -83,10 +102,17 @@ async function fetchLive(domain) {
 function board(site, heroImage) {
   const t = site.theme || {};
   const font = FONT_FAMILY[t.font] || FONT_FAMILY.cormorant;
-  const ink = t.ink || '#f0ede6', bg = t.bg || '#0e0e0c', accent = t.accent || '#a8884a';
-  const paper = darken(bg, -0.06) === bg ? bg : bg; // keep bg
+  let ink = t.ink || '#f0ede6';
+  const bg = t.bg || '#0e0e0c', accent = t.accent || '#a8884a';
+  // Contrast invariant: ground + ink must sit on OPPOSITE luminance sides, else
+  // the board washes out (live-fetched tokens can hand us a light bg AND light ink).
+  if ((lum(ink) > 0.42) === (lum(bg) > 0.42)) ink = inkOn(bg);
+  const darkGround = lum(bg) <= 0.42;
+  const paper = bg;
   const accentDeep = darken(accent, 0.28);
-  const rule = t.rule || 'rgba(255,255,255,.12)';
+  // muted text + hairlines must track ground polarity (was hardcoded white → invisible on a light ground)
+  const muted = darkGround ? 'rgba(255,255,255,.45)' : 'rgba(0,0,0,.5)';
+  const rule = t.rule || (darkGround ? 'rgba(255,255,255,.12)' : 'rgba(0,0,0,.14)');
   const name = (site.siteName || site.slug);
   const words = name.split(' ');
   const wTop = words.length > 1 ? words.slice(0, -1).join(' ') : name;
@@ -103,7 +129,7 @@ ${gfont}
 <script src="https://mcp.figma.com/mcp/html-to-design/capture.js" async></script>
 <style>
 :root{--ink:${ink};--bg:${bg};--paper:${paper};--accent:${accent};--accent-deep:${accentDeep};--rule:${rule};
---serif:${font.css};--sans:'Inter',system-ui,sans-serif;--muted:rgba(255,255,255,.45)}
+--serif:${font.css};--sans:'Inter',system-ui,sans-serif;--muted:${muted}}
 *{box-sizing:border-box;margin:0;padding:0}
 body{background:var(--bg);font-family:var(--sans);color:var(--ink);-webkit-font-smoothing:antialiased}
 .board{width:1456px;margin:0 auto;background:var(--bg)}
@@ -158,7 +184,7 @@ ${heroImage ? `<div style="position:relative;border-bottom:1px solid var(--rule)
 <div class="strip">${tiles.map((x, i) => {
     const cols = [ink, accent, accentDeep, darken(accent, 0.5), t.bgLight || '#f3f1ea'];
     const c = cols[i % cols.length];
-    const fg = i === 4 ? ink : bg;
+    const fg = inkOn(c); // legible text per-tile, computed from each tile's own background
     return `<div class="tile" style="background:${c};color:${fg}">${esc(x)}</div>`;
   }).join('')}</div>
 <div class="ft"><div class="l">${esc(name)}</div><div class="r">DW hybrid brand · shared skeleton · per-site theme from style guide · ${esc(t.palette || '')}/${esc(t.font || '')}</div></div>
diff --git a/brand/corkwallcovering.html b/brand/corkwallcovering.html
index d511da4..ecf0182 100644
--- a/brand/corkwallcovering.html
+++ b/brand/corkwallcovering.html
@@ -57,6 +57,6 @@ body{background:var(--bg);font-family:var(--sans);color:var(--ink);-webkit-font-
 <div class="s"><div class="c" style="background:#f3f1ea"></div><div class="m"><div class="n">Light</div><div class="x">#F3F1EA</div></div></div>
 <div class="s"><div class="c" style="background:#1656f00"></div><div class="m"><div class="n">Accent tint</div><div class="x">#1656F00</div></div></div>
 </div></div></div>
-<div class="strip"><div class="tile" style="background:#f0ede6;color:#1a1410">Silk</div><div class="tile" style="background:#FF4F00;color:#1a1410">Grasscloth</div><div class="tile" style="background:#b83900;color:#1a1410">Cork</div><div class="tile" style="background:#802800;color:#1a1410">Linen</div></div>
+<div class="strip"><div class="tile" style="background:#f0ede6;color:#0a1113">Silk</div><div class="tile" style="background:#FF4F00;color:#0a1113">Grasscloth</div><div class="tile" style="background:#b83900;color:#f0f4f5">Cork</div><div class="tile" style="background:#802800;color:#f0f4f5">Linen</div></div>
 <div class="ft"><div class="l">Cork Wallcovering</div><div class="r">DW hybrid brand · shared skeleton · per-site theme from style guide · /</div></div>
 </div></body></html>
\ No newline at end of file
diff --git a/brand/etciemurals.html b/brand/etciemurals.html
index d09b453..7f35cba 100644
--- a/brand/etciemurals.html
+++ b/brand/etciemurals.html
@@ -4,7 +4,7 @@
 <link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,500;0,600;1,400&family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
 <script src="https://mcp.figma.com/mcp/html-to-design/capture.js" async></script>
 <style>
-:root{--ink:#ece4ee;--bg:#f3eff3;--paper:#f3eff3;--accent:#6a3a7a;--accent-deep:#4c2a58;--rule:#cdc1d0;
+:root{--ink:#ece4ee;--bg:#0e0a10;--paper:#0e0a10;--accent:#6a3a7a;--accent-deep:#4c2a58;--rule:rgba(255,255,255,.12);
 --serif:'Playfair Display',Georgia,serif;--sans:'Inter',system-ui,sans-serif;--muted:rgba(255,255,255,.45)}
 *{box-sizing:border-box;margin:0;padding:0}
 body{background:var(--bg);font-family:var(--sans);color:var(--ink);-webkit-font-smoothing:antialiased}
@@ -57,6 +57,6 @@ body{background:var(--bg);font-family:var(--sans);color:var(--ink);-webkit-font-
 <div class="s"><div class="c" style="background:#f3eff3"></div><div class="m"><div class="n">Light</div><div class="x">#F3EFF3</div></div></div>
 <div class="s"><div class="c" style="background:#9451ab"></div><div class="m"><div class="n">Accent tint</div><div class="x">#9451AB</div></div></div>
 </div></div></div>
-<div class="strip"><div class="tile" style="background:#ece4ee;color:#f3eff3">mural</div><div class="tile" style="background:#6a3a7a;color:#f3eff3">scenic</div><div class="tile" style="background:#4c2a58;color:#f3eff3">panoramic</div></div>
+<div class="strip"><div class="tile" style="background:#ece4ee;color:#0a1113">mural</div><div class="tile" style="background:#6a3a7a;color:#f0f4f5">scenic</div><div class="tile" style="background:#4c2a58;color:#f0f4f5">panoramic</div></div>
 <div class="ft"><div class="l">Et Cie Murals</div><div class="r">DW hybrid brand · shared skeleton · per-site theme from style guide · plum/playfair</div></div>
 </div></body></html>
\ No newline at end of file
diff --git a/brand/linenwallpaper.html b/brand/linenwallpaper.html
index 398a28f..a558da1 100644
--- a/brand/linenwallpaper.html
+++ b/brand/linenwallpaper.html
@@ -57,6 +57,6 @@ body{background:var(--bg);font-family:var(--sans);color:var(--ink);-webkit-font-
 <div class="s"><div class="c" style="background:#f3f1ea"></div><div class="m"><div class="n">Light</div><div class="x">#F3F1EA</div></div></div>
 <div class="s"><div class="c" style="background:#d9ac6f"></div><div class="m"><div class="n">Accent tint</div><div class="x">#D9AC6F</div></div></div>
 </div></div></div>
-<div class="strip"><div class="tile" style="background:#f0ede6;color:#2C1F0F">Silk</div><div class="tile" style="background:#9B7B4F;color:#2C1F0F">Grasscloth</div><div class="tile" style="background:#705939;color:#2C1F0F">Cork</div><div class="tile" style="background:#4e3e28;color:#2C1F0F">Linen</div></div>
+<div class="strip"><div class="tile" style="background:#f0ede6;color:#0a1113">Silk</div><div class="tile" style="background:#9B7B4F;color:#0a1113">Grasscloth</div><div class="tile" style="background:#705939;color:#f0f4f5">Cork</div><div class="tile" style="background:#4e3e28;color:#f0f4f5">Linen</div></div>
 <div class="ft"><div class="l">linenwallpaper</div><div class="r">DW hybrid brand · shared skeleton · per-site theme from style guide · /</div></div>
 </div></body></html>
\ No newline at end of file
diff --git a/brand/silkwallpaper.html b/brand/silkwallpaper.html
index 02ad23d..c2203ff 100644
--- a/brand/silkwallpaper.html
+++ b/brand/silkwallpaper.html
@@ -4,8 +4,8 @@
 <link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,500;0,600;1,400&family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
 <script src="https://mcp.figma.com/mcp/html-to-design/capture.js" async></script>
 <style>
-:root{--ink:#f0ede6;--bg:#FFF;--paper:#FFF;--accent:#c8a878;--accent-deep:#907956;--rule:rgba(255,255,255,.12);
---serif:'Cormorant Garamond',Georgia,serif;--sans:'Inter',system-ui,sans-serif;--muted:rgba(255,255,255,.45)}
+:root{--ink:#0a1113;--bg:#FFF;--paper:#FFF;--accent:#c8a878;--accent-deep:#907956;--rule:rgba(0,0,0,.14);
+--serif:'Cormorant Garamond',Georgia,serif;--sans:'Inter',system-ui,sans-serif;--muted:rgba(0,0,0,.5)}
 *{box-sizing:border-box;margin:0;padding:0}
 body{background:var(--bg);font-family:var(--sans);color:var(--ink);-webkit-font-smoothing:antialiased}
 .board{width:1456px;margin:0 auto;background:var(--bg)}
@@ -50,13 +50,13 @@ body{background:var(--bg);font-family:var(--sans);color:var(--ink);-webkit-font-
 <div class="tr"><span class="tt">Caption</span><span class="cap">Free memo samples · Trade pricing</span></div></div>
 <div class="panel"><div class="eyebrow">Palette · </div>
 <div class="sw">
-<div class="s"><div class="c" style="background:#f0ede6"></div><div class="m"><div class="n">Ink</div><div class="x">#F0EDE6</div></div></div>
+<div class="s"><div class="c" style="background:#0a1113"></div><div class="m"><div class="n">Ink</div><div class="x">#0A1113</div></div></div>
 <div class="s"><div class="c" style="background:#c8a878"></div><div class="m"><div class="n">Accent</div><div class="x">#C8A878</div></div></div>
 <div class="s"><div class="c" style="background:#907956"></div><div class="m"><div class="n">Accent deep</div><div class="x">#907956</div></div></div>
 <div class="s"><div class="c" style="background:#FFF"></div><div class="m"><div class="n">Ground</div><div class="x">#FFF</div></div></div>
 <div class="s"><div class="c" style="background:#f3f1ea"></div><div class="m"><div class="n">Light</div><div class="x">#F3F1EA</div></div></div>
 <div class="s"><div class="c" style="background:#118eba8"></div><div class="m"><div class="n">Accent tint</div><div class="x">#118EBA8</div></div></div>
 </div></div></div>
-<div class="strip"><div class="tile" style="background:#f0ede6;color:#FFF">Silk</div><div class="tile" style="background:#c8a878;color:#FFF">Grasscloth</div><div class="tile" style="background:#907956;color:#FFF">Cork</div><div class="tile" style="background:#64543c;color:#FFF">Linen</div></div>
+<div class="strip"><div class="tile" style="background:#0a1113;color:#f0f4f5">Silk</div><div class="tile" style="background:#c8a878;color:#0a1113">Grasscloth</div><div class="tile" style="background:#907956;color:#0a1113">Cork</div><div class="tile" style="background:#64543c;color:#f0f4f5">Linen</div></div>
 <div class="ft"><div class="l">silkwallpaper</div><div class="r">DW hybrid brand · shared skeleton · per-site theme from style guide · /</div></div>
 </div></body></html>
\ No newline at end of file
diff --git a/template-audit.json b/template-audit.json
new file mode 100644
index 0000000..e8c5c4e
--- /dev/null
+++ b/template-audit.json
@@ -0,0 +1,343 @@
+{
+  "auditedAt": "2026-06-01T20:08:14.125Z",
+  "authority": "dw-fashion-templates (DTD verdict B)",
+  "counts": {
+    "MATCH": 30,
+    "DRIFT": 7
+  },
+  "sites": [
+    {
+      "slug": "corkwallcovering",
+      "template": "hermes",
+      "expected": "#FF4F00",
+      "actualAccent": "#FF4F00",
+      "actualBg": "#FFFEFA",
+      "status": "MATCH",
+      "why": "accent #FF4F00 hue ~19° matches template #FF4F00 hue 19° (Δ0°)"
+    },
+    {
+      "slug": "silkwallpaper",
+      "template": "saint_laurent",
+      "expected": "mono",
+      "actualAccent": "#FFF",
+      "actualBg": "#000",
+      "status": "MATCH",
+      "why": "mono template, accent is neutral (sat 0.00)"
+    },
+    {
+      "slug": "linenwallpaper",
+      "template": "loewe",
+      "expected": "#9B7B4F",
+      "actualAccent": "#9B7B4F",
+      "actualBg": "#F2EDE2",
+      "status": "MATCH",
+      "why": "accent #9B7B4F hue ~35° matches template #9B7B4F hue 35° (Δ0°)"
+    },
+    {
+      "slug": "handcraftedwallpaper",
+      "template": "bottega",
+      "expected": "#107A3B",
+      "actualAccent": "#107A3B",
+      "actualBg": "#F4F0E6",
+      "status": "MATCH",
+      "why": "accent #107A3B hue ~144° matches template #107A3B hue 144° (Δ0°)"
+    },
+    {
+      "slug": "1890swallpaper",
+      "template": "gucci",
+      "expected": "#006837",
+      "actualAccent": "#006837",
+      "actualBg": "#FFFEF7",
+      "status": "MATCH",
+      "why": "accent #006837 hue ~152° matches template #006837 hue 152° (Δ0°)"
+    },
+    {
+      "slug": "1900swallpaper",
+      "template": "gucci",
+      "expected": "#006837",
+      "actualAccent": "#006837",
+      "actualBg": "#FFFEF7",
+      "status": "MATCH",
+      "why": "accent #006837 hue ~152° matches template #006837 hue 152° (Δ0°)"
+    },
+    {
+      "slug": "1920swallpaper",
+      "template": "chanel",
+      "expected": "mono",
+      "actualAccent": "#000",
+      "actualBg": "#FFF",
+      "status": "MATCH",
+      "why": "mono template, accent is neutral (sat 0.00)"
+    },
+    {
+      "slug": "1930swallpaper",
+      "template": "chanel",
+      "expected": "mono",
+      "actualAccent": "#000",
+      "actualBg": "#FFF",
+      "status": "MATCH",
+      "why": "mono template, accent is neutral (sat 0.00)"
+    },
+    {
+      "slug": "1960swallpaper",
+      "template": "prada",
+      "expected": "mono",
+      "actualAccent": "#000",
+      "actualBg": "#F5F2EB",
+      "status": "MATCH",
+      "why": "mono template, accent is neutral (sat 0.00)"
+    },
+    {
+      "slug": "1970swallpaper",
+      "template": "prada",
+      "expected": "mono",
+      "actualAccent": "#000",
+      "actualBg": "#F5F2EB",
+      "status": "MATCH",
+      "why": "mono template, accent is neutral (sat 0.00)"
+    },
+    {
+      "slug": "1980swallpaper",
+      "template": "balenciaga",
+      "expected": "#FF4F00",
+      "actualAccent": "#FF4F00",
+      "actualBg": "#000",
+      "status": "MATCH",
+      "why": "accent #FF4F00 hue ~19° matches template #FF4F00 hue 19° (Δ0°)"
+    },
+    {
+      "slug": "metallicwallpaper",
+      "template": "dior",
+      "expected": "#A98B5B",
+      "actualAccent": "#1A1A1A",
+      "actualBg": "#FAF9F6",
+      "status": "DRIFT",
+      "why": "template expects #A98B5B, but accent is desaturated (#1A1A1A)"
+    },
+    {
+      "slug": "mylarwallpaper",
+      "template": "dior",
+      "expected": "#A98B5B",
+      "actualAccent": "#1A1A1A",
+      "actualBg": "#FAF9F6",
+      "status": "DRIFT",
+      "why": "template expects #A98B5B, but accent is desaturated (#1A1A1A)"
+    },
+    {
+      "slug": "silverleafwallpaper",
+      "template": "valentino",
+      "expected": "#D70000",
+      "actualAccent": "#D70000",
+      "actualBg": "#FFF",
+      "status": "MATCH",
+      "why": "accent #D70000 hue ~0° matches template #D70000 hue 0° (Δ0°)"
+    },
+    {
+      "slug": "glitterwalls",
+      "template": "valentino",
+      "expected": "#D70000",
+      "actualAccent": "#D70000",
+      "actualBg": "#000",
+      "status": "MATCH",
+      "why": "accent #D70000 hue ~0° matches template #D70000 hue 0° (Δ0°)"
+    },
+    {
+      "slug": "naturalwallcoverings",
+      "template": "celine",
+      "expected": "mono",
+      "actualAccent": "#000",
+      "actualBg": "#FFF",
+      "status": "MATCH",
+      "why": "mono template, accent is neutral (sat 0.00)"
+    },
+    {
+      "slug": "recycledwallpaper",
+      "template": "celine",
+      "expected": "mono",
+      "actualAccent": "#000",
+      "actualBg": "#FFF",
+      "status": "MATCH",
+      "why": "mono template, accent is neutral (sat 0.00)"
+    },
+    {
+      "slug": "jutewallpaper",
+      "template": "loewe",
+      "expected": "#9B7B4F",
+      "actualAccent": "#107A3B",
+      "actualBg": "#F4F0E6",
+      "status": "DRIFT",
+      "why": "accent #107A3B hue 144° ≠ template #9B7B4F hue 35° (Δ109°)"
+    },
+    {
+      "slug": "raffiawalls",
+      "template": "loewe",
+      "expected": "#9B7B4F",
+      "actualAccent": "#9B7B4F",
+      "actualBg": "#F2EDE2",
+      "status": "MATCH",
+      "why": "accent #9B7B4F hue ~35° matches template #9B7B4F hue 35° (Δ0°)"
+    },
+    {
+      "slug": "raffiawallcoverings",
+      "template": "loewe",
+      "expected": "#9B7B4F",
+      "actualAccent": "#9B7B4F",
+      "actualBg": "#F2EDE2",
+      "status": "MATCH",
+      "why": "accent #9B7B4F hue ~35° matches template #9B7B4F hue 35° (Δ0°)"
+    },
+    {
+      "slug": "museumwallpaper",
+      "template": "therow",
+      "expected": "neutral",
+      "actualAccent": "#1A1A1A",
+      "actualBg": "#F8F6F1",
+      "status": "MATCH",
+      "why": "neutral template, accent is neutral (sat 0.00)"
+    },
+    {
+      "slug": "restaurantwallpaper",
+      "template": "burberry",
+      "expected": "#A47C5A",
+      "actualAccent": "#A47C5A",
+      "actualBg": "#FFF",
+      "status": "MATCH",
+      "why": "accent #A47C5A hue ~28° matches template #A47C5A hue 28° (Δ0°)"
+    },
+    {
+      "slug": "hotelwallcoverings",
+      "template": "burberry",
+      "expected": "#A47C5A",
+      "actualAccent": "#A47C5A",
+      "actualBg": "#FFF",
+      "status": "MATCH",
+      "why": "accent #A47C5A hue ~28° matches template #A47C5A hue 28° (Δ0°)"
+    },
+    {
+      "slug": "hospitalitywallpaper",
+      "template": "burberry",
+      "expected": "#A47C5A",
+      "actualAccent": "#A47C5A",
+      "actualBg": "#FFF",
+      "status": "MATCH",
+      "why": "accent #A47C5A hue ~28° matches template #A47C5A hue 28° (Δ0°)"
+    },
+    {
+      "slug": "contractwallpaper",
+      "template": "toteme",
+      "expected": "neutral",
+      "actualAccent": "#FFF",
+      "actualBg": "#000",
+      "status": "MATCH",
+      "why": "neutral template, accent is neutral (sat 0.00)"
+    },
+    {
+      "slug": "healthcarewallpaper",
+      "template": "toteme",
+      "expected": "neutral",
+      "actualAccent": "#1A1A1A",
+      "actualBg": "#F5F2ED",
+      "status": "MATCH",
+      "why": "neutral template, accent is neutral (sat 0.00)"
+    },
+    {
+      "slug": "madagascarwallpaper",
+      "template": "khaite",
+      "expected": "#3A3530",
+      "actualAccent": "#3A3530",
+      "actualBg": "#F2EFE9",
+      "status": "MATCH",
+      "why": "neutral template, accent is neutral (sat 0.09)"
+    },
+    {
+      "slug": "suedewallpaper",
+      "template": "khaite",
+      "expected": "#3A3530",
+      "actualAccent": "#3A3530",
+      "actualBg": "#F2EFE9",
+      "status": "MATCH",
+      "why": "neutral template, accent is neutral (sat 0.09)"
+    },
+    {
+      "slug": "screenprintedwallpaper",
+      "template": "margiela",
+      "expected": "mono",
+      "actualAccent": "#FFF",
+      "actualBg": "#FAFAFA",
+      "status": "MATCH",
+      "why": "mono template, accent is neutral (sat 0.00)"
+    },
+    {
+      "slug": "blockprintedwallpaper",
+      "template": "margiela",
+      "expected": "mono",
+      "actualAccent": "#FFF",
+      "actualBg": "#FAFAFA",
+      "status": "MATCH",
+      "why": "mono template, accent is neutral (sat 0.00)"
+    },
+    {
+      "slug": "embroideredwallpaper",
+      "template": "miu_miu",
+      "expected": "#FFB6C1",
+      "actualAccent": "#FFB6C1",
+      "actualBg": "#FFF8F2",
+      "status": "MATCH",
+      "why": "accent #FFB6C1 hue ~351° matches template #FFB6C1 hue 351° (Δ0°)"
+    },
+    {
+      "slug": "textilewallpaper",
+      "template": "miu_miu",
+      "expected": "#FFB6C1",
+      "actualAccent": "#FFB6C1",
+      "actualBg": "#FFF8F2",
+      "status": "MATCH",
+      "why": "accent #FFB6C1 hue ~351° matches template #FFB6C1 hue 351° (Δ0°)"
+    },
+    {
+      "slug": "selfadhesivewallpaper",
+      "template": "allbirds",
+      "expected": "neutral",
+      "actualAccent": "#1A2B40",
+      "actualBg": "#F4F1EC",
+      "status": "DRIFT",
+      "why": "neutral template expects neutral accent, but accent is saturated (#1A2B40, sat 0.42)"
+    },
+    {
+      "slug": "apartmentwallpaper",
+      "template": "allbirds",
+      "expected": "neutral",
+      "actualAccent": "#1A2B40",
+      "actualBg": "#F4F1EC",
+      "status": "DRIFT",
+      "why": "neutral template expects neutral accent, but accent is saturated (#1A2B40, sat 0.42)"
+    },
+    {
+      "slug": "vinylwallpaper",
+      "template": "phoebe_philo",
+      "expected": "mono",
+      "actualAccent": "#000",
+      "actualBg": "#F5F5F0",
+      "status": "MATCH",
+      "why": "mono template, accent is neutral (sat 0.00)"
+    },
+    {
+      "slug": "fabricwallpaper",
+      "template": "phoebe_philo",
+      "expected": "mono",
+      "actualAccent": "#FFB6C1",
+      "actualBg": "#FFF8F2",
+      "status": "DRIFT",
+      "why": "mono template expects neutral accent, but accent is saturated (#FFB6C1, sat 1.00)"
+    },
+    {
+      "slug": "wallpapersback",
+      "template": "reformation",
+      "expected": "neutral",
+      "actualAccent": "#A86B5C",
+      "actualBg": "#FAEDE3",
+      "status": "DRIFT",
+      "why": "neutral template expects neutral accent, but accent is saturated (#A86B5C, sat 0.30)"
+    }
+  ]
+}
diff --git a/template-audit.md b/template-audit.md
new file mode 100644
index 0000000..5c6b166
--- /dev/null
+++ b/template-audit.md
@@ -0,0 +1,45 @@
+# DW Fleet — Template-Drift Audit
+
+_Authority: dw-fashion-templates (DTD verdict B). Read-only._
+
+**Counts:** MATCH 30 · DRIFT 7
+
+| Site | Template | Expected | Actual accent | Verdict | Note |
+|---|---|---|---|---|---|
+| wallpapersback | reformation | neutral | #A86B5C | DRIFT | neutral template expects neutral accent, but accent is saturated (#A86B5C, sat 0.30) |
+| fabricwallpaper | phoebe_philo | mono | #FFB6C1 | DRIFT | mono template expects neutral accent, but accent is saturated (#FFB6C1, sat 1.00) |
+| apartmentwallpaper | allbirds | neutral | #1A2B40 | DRIFT | neutral template expects neutral accent, but accent is saturated (#1A2B40, sat 0.42) |
+| selfadhesivewallpaper | allbirds | neutral | #1A2B40 | DRIFT | neutral template expects neutral accent, but accent is saturated (#1A2B40, sat 0.42) |
+| jutewallpaper | loewe | #9B7B4F | #107A3B | DRIFT | accent #107A3B hue 144° ≠ template #9B7B4F hue 35° (Δ109°) |
+| mylarwallpaper | dior | #A98B5B | #1A1A1A | DRIFT | template expects #A98B5B, but accent is desaturated (#1A1A1A) |
+| metallicwallpaper | dior | #A98B5B | #1A1A1A | DRIFT | template expects #A98B5B, but accent is desaturated (#1A1A1A) |
+| vinylwallpaper | phoebe_philo | mono | #000 | MATCH | mono template, accent is neutral (sat 0.00) |
+| textilewallpaper | miu_miu | #FFB6C1 | #FFB6C1 | MATCH | accent #FFB6C1 hue ~351° matches template #FFB6C1 hue 351° (Δ0°) |
+| embroideredwallpaper | miu_miu | #FFB6C1 | #FFB6C1 | MATCH | accent #FFB6C1 hue ~351° matches template #FFB6C1 hue 351° (Δ0°) |
+| blockprintedwallpaper | margiela | mono | #FFF | MATCH | mono template, accent is neutral (sat 0.00) |
+| screenprintedwallpaper | margiela | mono | #FFF | MATCH | mono template, accent is neutral (sat 0.00) |
+| suedewallpaper | khaite | #3A3530 | #3A3530 | MATCH | neutral template, accent is neutral (sat 0.09) |
+| madagascarwallpaper | khaite | #3A3530 | #3A3530 | MATCH | neutral template, accent is neutral (sat 0.09) |
+| healthcarewallpaper | toteme | neutral | #1A1A1A | MATCH | neutral template, accent is neutral (sat 0.00) |
+| contractwallpaper | toteme | neutral | #FFF | MATCH | neutral template, accent is neutral (sat 0.00) |
+| hospitalitywallpaper | burberry | #A47C5A | #A47C5A | MATCH | accent #A47C5A hue ~28° matches template #A47C5A hue 28° (Δ0°) |
+| hotelwallcoverings | burberry | #A47C5A | #A47C5A | MATCH | accent #A47C5A hue ~28° matches template #A47C5A hue 28° (Δ0°) |
+| restaurantwallpaper | burberry | #A47C5A | #A47C5A | MATCH | accent #A47C5A hue ~28° matches template #A47C5A hue 28° (Δ0°) |
+| museumwallpaper | therow | neutral | #1A1A1A | MATCH | neutral template, accent is neutral (sat 0.00) |
+| raffiawallcoverings | loewe | #9B7B4F | #9B7B4F | MATCH | accent #9B7B4F hue ~35° matches template #9B7B4F hue 35° (Δ0°) |
+| raffiawalls | loewe | #9B7B4F | #9B7B4F | MATCH | accent #9B7B4F hue ~35° matches template #9B7B4F hue 35° (Δ0°) |
+| recycledwallpaper | celine | mono | #000 | MATCH | mono template, accent is neutral (sat 0.00) |
+| naturalwallcoverings | celine | mono | #000 | MATCH | mono template, accent is neutral (sat 0.00) |
+| glitterwalls | valentino | #D70000 | #D70000 | MATCH | accent #D70000 hue ~0° matches template #D70000 hue 0° (Δ0°) |
+| silverleafwallpaper | valentino | #D70000 | #D70000 | MATCH | accent #D70000 hue ~0° matches template #D70000 hue 0° (Δ0°) |
+| 1980swallpaper | balenciaga | #FF4F00 | #FF4F00 | MATCH | accent #FF4F00 hue ~19° matches template #FF4F00 hue 19° (Δ0°) |
+| 1970swallpaper | prada | mono | #000 | MATCH | mono template, accent is neutral (sat 0.00) |
+| 1960swallpaper | prada | mono | #000 | MATCH | mono template, accent is neutral (sat 0.00) |
+| 1930swallpaper | chanel | mono | #000 | MATCH | mono template, accent is neutral (sat 0.00) |
+| 1920swallpaper | chanel | mono | #000 | MATCH | mono template, accent is neutral (sat 0.00) |
+| 1900swallpaper | gucci | #006837 | #006837 | MATCH | accent #006837 hue ~152° matches template #006837 hue 152° (Δ0°) |
+| 1890swallpaper | gucci | #006837 | #006837 | MATCH | accent #006837 hue ~152° matches template #006837 hue 152° (Δ0°) |
+| handcraftedwallpaper | bottega | #107A3B | #107A3B | MATCH | accent #107A3B hue ~144° matches template #107A3B hue 144° (Δ0°) |
+| linenwallpaper | loewe | #9B7B4F | #9B7B4F | MATCH | accent #9B7B4F hue ~35° matches template #9B7B4F hue 35° (Δ0°) |
+| silkwallpaper | saint_laurent | mono | #FFF | MATCH | mono template, accent is neutral (sat 0.00) |
+| corkwallcovering | hermes | #FF4F00 | #FF4F00 | MATCH | accent #FF4F00 hue ~19° matches template #FF4F00 hue 19° (Δ0°) |

← 41cdb66 live-data boards: scrape theme tokens from served CSS + embe  ·  back to Dw Fleet Registry  ·  fix(grasscloth board): dark --muted/--rule on white ground s e2ab0c2 →