← back to Dw Fleet Registry
reusable dw-header.js drop-in: hamburgers UL (nav + admin) + centered name, configurable per-site (siteName/accent/navLinks/adminLinks), admin-reveal stub; + hero-dup audit (50 files, only 14 unique)
d8d66292edd772a3be6124a951ff64c32c4c2948 · 2026-06-01 13:20:39 -0700 · Steve Abrams
Files touched
D brand/bleachfriendly.htmlA component/dw-header.jsA component/test.htmlM dw-fleet-registry.json
Diff
commit d8d66292edd772a3be6124a951ff64c32c4c2948
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Jun 1 13:20:39 2026 -0700
reusable dw-header.js drop-in: hamburgers UL (nav + admin) + centered name, configurable per-site (siteName/accent/navLinks/adminLinks), admin-reveal stub; + hero-dup audit (50 files, only 14 unique)
---
brand/bleachfriendly.html | 62 ----------------------
component/dw-header.js | 129 ++++++++++++++++++++++++++++++++++++++++++++++
component/test.html | 5 ++
dw-fleet-registry.json | 47 +----------------
4 files changed, 135 insertions(+), 108 deletions(-)
diff --git a/brand/bleachfriendly.html b/brand/bleachfriendly.html
deleted file mode 100644
index 0d67ea8..0000000
--- a/brand/bleachfriendly.html
+++ /dev/null
@@ -1,62 +0,0 @@
-<!DOCTYPE html><html lang="en"><head><meta charset="utf-8">
-<title>Bleach Friendly — brand board</title>
-<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
-<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:#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}
-.board{width:1456px;margin:0 auto;background:var(--bg)}
-.hero{padding:92px 110px 78px;border-bottom:1px solid var(--rule);position:relative}
-.mono{font-family:var(--serif);font-weight:500;font-size:28px;letter-spacing:.2em;color:var(--accent);margin-bottom:48px;text-transform:uppercase}
-.wordmark{font-family:var(--serif);font-weight:500;font-size:88px;line-height:1.0;letter-spacing:.03em;text-transform:uppercase}
-.wordmark .thin{font-weight:400;color:var(--accent)}
-.tagline{font-family:var(--serif);font-style:italic;font-size:28px;color:var(--muted);margin-top:24px}
-.hairline{width:120px;height:1px;background:var(--accent);margin-top:36px}
-.badge{position:absolute;top:92px;right:110px;font-size:11px;letter-spacing:.3em;color:var(--muted);text-transform:uppercase}
-.row{display:grid;grid-template-columns:1fr 1fr}
-.panel{padding:58px 110px}.panel+.panel{border-left:1px solid var(--rule)}
-.eyebrow{font-size:11px;letter-spacing:.28em;text-transform:uppercase;color:var(--accent);margin-bottom:30px;font-weight:600}
-.tr{display:flex;align-items:baseline;gap:18px;padding:13px 0;border-bottom:1px solid var(--rule)}.tr:last-child{border-bottom:0}
-.tt{font-size:10px;letter-spacing:.1em;color:var(--muted);width:90px;flex-shrink:0;text-transform:uppercase}
-.d{font-family:var(--serif);font-weight:500;font-size:48px;line-height:1}
-.h1{font-family:var(--serif);font-weight:500;font-size:32px}
-.h2{font-family:var(--serif);font-weight:600;font-size:22px;letter-spacing:.03em;text-transform:uppercase}
-.bd{font-family:var(--sans);font-size:16px;line-height:1.5;color:var(--ink);opacity:.82}
-.cap{font-family:var(--sans);font-weight:500;font-size:11px;letter-spacing:.22em;text-transform:uppercase;color:var(--muted)}
-.sw{display:grid;grid-template-columns:repeat(3,1fr);gap:16px}
-.s{border-radius:8px;overflow:hidden;border:1px solid var(--rule)}.s .c{height:88px}.s .m{padding:10px 12px}
-.s .n{font-size:12px;font-weight:600}.s .x{font-size:10px;color:var(--muted);letter-spacing:.06em;margin-top:2px}
-.strip{display:flex;border-top:1px solid var(--rule)}
-.tile{flex:1;height:132px;display:flex;align-items:center;justify-content:center;font-family:var(--serif);font-weight:500;font-size:18px;letter-spacing:.14em;text-transform:uppercase}
-.tile+.tile{border-left:1px solid var(--rule)}
-.ft{padding:26px 110px;display:flex;justify-content:space-between;align-items:center;border-top:1px solid var(--rule)}
-.ft .l{font-family:var(--serif);font-size:16px;letter-spacing:.14em;text-transform:uppercase;color:var(--accent)}
-.ft .r{font-size:11px;color:var(--muted)}
-</style></head><body><div class="board">
-<section class="hero"><div class="badge">DW fleet · bleachfriendly.com</div>
-<div class="mono">DW</div>
-<h1 class="wordmark">Bleach<br><span class="thin">Friendly</span></h1>
-<p class="tagline">Bleach-cleanable performance wallcoverings.</p><div class="hairline"></div></section>
-
-<div class="row">
-<div class="panel"><div class="eyebrow">Type Scale · Cormorant Garamond</div>
-<div class="tr"><span class="tt">Display</span><span class="d">Aa</span><span class="cap">Cormorant Garamond</span></div>
-<div class="tr"><span class="tt">H1 · 32</span><span class="h1">Hand-crafted, to order</span></div>
-<div class="tr"><span class="tt">H2 · 22</span><span class="h2">The Collection</span></div>
-<div class="tr"><span class="tt">Body · 16</span><span class="bd">Free memo samples, expert trade service, hand-holding on every order.</span></div>
-<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 · coastal</div>
-<div class="sw">
-<div class="s"><div class="c" style="background:#e6eef0"></div><div class="m"><div class="n">Ink</div><div class="x">#E6EEF0</div></div></div>
-<div class="s"><div class="c" style="background:#2a7d8a"></div><div class="m"><div class="n">Accent</div><div class="x">#2A7D8A</div></div></div>
-<div class="s"><div class="c" style="background:#1e5a63"></div><div class="m"><div class="n">Accent deep</div><div class="x">#1E5A63</div></div></div>
-<div class="s"><div class="c" style="background:#f0f4f5"></div><div class="m"><div class="n">Ground</div><div class="x">#F0F4F5</div></div></div>
-<div class="s"><div class="c" style="background:#f0f4f5"></div><div class="m"><div class="n">Light</div><div class="x">#F0F4F5</div></div></div>
-<div class="s"><div class="c" style="background:#3bafc1"></div><div class="m"><div class="n">Accent tint</div><div class="x">#3BAFC1</div></div></div>
-</div></div></div>
-<div class="strip"><div class="tile" style="background:#e6eef0;color:#0a1113">vinyl</div><div class="tile" style="background:#2a7d8a;color:#e6eef0">upholstery</div><div class="tile" style="background:#1e5a63;color:#e6eef0">contract</div><div class="tile" style="background:#153f45;color:#e6eef0">commercial</div><div class="tile" style="background:#f0f4f5;color:#0a1113">performance</div></div>
-<div class="ft"><div class="l">Bleach Friendly</div><div class="r">DW hybrid brand · shared skeleton · per-site theme from style guide · coastal/cormorant</div></div>
-</div></body></html>
\ No newline at end of file
diff --git a/component/dw-header.js b/component/dw-header.js
new file mode 100644
index 0000000..0f10a0b
--- /dev/null
+++ b/component/dw-header.js
@@ -0,0 +1,129 @@
+/* dw-header.js — universal DW fleet header (drop-in).
+ *
+ * Spec (Steve 2026-06-01): BOTH hamburgers upper-LEFT, site name CENTERED.
+ * - Public: ☰ (site nav) top-left · centered site name
+ * - Admin (logged in): a SECOND ☰ beside it (accent-tinted) for admin tools
+ *
+ * Drop-in: <script src="/dw-header.js" defer></script>
+ * Per-site override (set BEFORE the script loads):
+ * window.DwHeaderConfig = {
+ * siteName: 'Jute Wallpaper', // default: derived from hostname
+ * accent: '#9B7B4F', // admin-burger tint (default: --accent var or #9B7B4F)
+ * homeUrl: '/',
+ * navLinks: [{name:'Home',url:'/'}, {name:'About',url:'/about'}, ...],
+ * adminLinks: [{name:'Shopify admin', url:'https://admin.shopify.com', external:true}, ...],
+ * };
+ * Admin reveal: ?admin=1 sets localStorage.dwAdmin=1 (stub until real auth wires in).
+ *
+ * Self-styled · idempotent · replaces inline ns-header + the upper-right corner-nav.
+ */
+(function () {
+ 'use strict';
+ if (window.__dwHeaderLoaded) return;
+ window.__dwHeaderLoaded = true;
+
+ var CFG = window.DwHeaderConfig || {};
+ var DOMAIN = location.hostname.replace(/^www\./, '');
+ var STEM = DOMAIN.replace(/\..+$/, '');
+
+ function titleCase(s) {
+ return s.replace(/(wallcoverings?|wallpapers?|walls?|fabric|covering|paper|cover)/gi, ' $1')
+ .replace(/\s+/g, ' ').trim()
+ .replace(/\b\w/g, function (c) { return c.toUpperCase(); });
+ }
+ var SITE_NAME = CFG.siteName || titleCase(STEM);
+ var HOME = CFG.homeUrl || '/';
+ var ACCENT = CFG.accent || (getComputedStyle(document.documentElement).getPropertyValue('--accent').trim()) || '#9B7B4F';
+ var NAV = CFG.navLinks || [
+ { name: 'Home', url: '/' }, { name: 'About', url: '/about' },
+ { name: 'Care', url: '/care.html' }, { name: 'History', url: '/history.html' },
+ ];
+ var ADMIN = CFG.adminLinks || [
+ { name: 'Shopify admin', url: 'https://admin.shopify.com', external: true },
+ { name: 'Edit hero', url: '#' }, { name: 'Analytics', url: '#' },
+ ];
+
+ // admin reveal (stub auth)
+ var isAdmin = false;
+ try {
+ var p = new URLSearchParams(location.search);
+ if (p.get('admin') === '1') localStorage.setItem('dwAdmin', '1');
+ if (p.get('admin') === '0') localStorage.removeItem('dwAdmin');
+ isAdmin = localStorage.getItem('dwAdmin') === '1';
+ } catch (e) {}
+
+ function esc(s) { return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/"/g, '"'); }
+ function linksHTML(arr) {
+ return arr.map(function (l) {
+ if (l.onclick) return '<button type="button" data-act="' + esc(l.onclick) + '">' + esc(l.name) + '</button>';
+ var ext = l.external ? ' target="_blank" rel="noopener noreferrer"' : '';
+ return '<a href="' + esc(l.url) + '"' + ext + '>' + esc(l.name) + '</a>';
+ }).join('');
+ }
+
+ // ---- styles ----
+ var css = document.createElement('style');
+ css.id = 'dw-header-styles';
+ css.textContent = [
+ '.dwh{position:sticky;top:0;z-index:200;display:flex;align-items:center;justify-content:space-between;padding:18px 28px;background:rgba(10,10,10,.92);backdrop-filter:blur(8px);border-bottom:1px solid rgba(245,241,232,.08)}',
+ '.dwh-burgers{display:flex;align-items:center;gap:10px;flex:1 1 0}',
+ '.dwh-spacer{flex:1 1 0}',
+ '.dwh-name{flex:0 0 auto;text-align:center;font-family:"Cormorant Garamond","Recoleta",Georgia,serif;font-style:italic;font-weight:500;font-size:22px;letter-spacing:.01em;color:#f5f1e8;text-decoration:none;white-space:nowrap}',
+ '.dwh-burger{display:flex;flex-direction:column;align-items:center;justify-content:center;width:34px;height:34px;gap:5px;background:transparent;border:0;cursor:pointer;padding:0}',
+ '.dwh-burger span{display:block;width:22px;height:1.5px;background:#f5f1e8;transition:transform .2s,opacity .2s}',
+ '.dwh-burger[aria-expanded="true"] span:nth-child(1){transform:translateY(6.5px) rotate(45deg)}',
+ '.dwh-burger[aria-expanded="true"] span:nth-child(2){opacity:0}',
+ '.dwh-burger[aria-expanded="true"] span:nth-child(3){transform:translateY(-6.5px) rotate(-45deg)}',
+ '.dwh-admin span{background:' + ACCENT + '}',
+ '.dwh-drawer{display:none;position:absolute;top:100%;left:18px;flex-direction:column;align-items:stretch;background:rgba(10,10,10,.97);backdrop-filter:blur(10px);border:1px solid rgba(245,241,232,.12);border-top:0;min-width:240px;padding:8px 0;margin:0;box-shadow:0 16px 40px rgba(0,0,0,.55);z-index:210}',
+ '.dwh-drawer.open{display:flex}',
+ '.dwh-drawer.dwh-admin-drawer{left:60px}',
+ '.dwh-drawer a,.dwh-drawer button{font-family:-apple-system,system-ui,sans-serif;font-size:14px;color:#f5f1e8;text-decoration:none;background:none;border:0;cursor:pointer;padding:12px 22px;text-align:left;width:100%;border-bottom:1px solid rgba(245,241,232,.08);display:block}',
+ '.dwh-drawer a:last-child,.dwh-drawer button:last-child{border-bottom:0}',
+ '.dwh-drawer a:hover,.dwh-drawer button:hover{background:rgba(245,241,232,.06)}',
+ '.dwh-head{padding:10px 22px 8px;font-size:10px;letter-spacing:.22em;text-transform:uppercase;color:' + ACCENT + '}',
+ ].join('\n');
+ document.head.appendChild(css);
+
+ // ---- header markup ----
+ var header = document.createElement('header');
+ header.className = 'dwh';
+ header.innerHTML =
+ '<div class="dwh-burgers">' +
+ '<button type="button" class="dwh-burger" id="dwhNav" aria-label="Menu" aria-controls="dwhNavDrawer" aria-expanded="false"><span></span><span></span><span></span></button>' +
+ (isAdmin ? '<button type="button" class="dwh-burger dwh-admin" id="dwhAdmin" aria-label="Admin tools" aria-controls="dwhAdminDrawer" aria-expanded="false"><span></span><span></span><span></span></button>' : '') +
+ '</div>' +
+ '<a href="' + esc(HOME) + '" class="dwh-name">' + esc(SITE_NAME) + '</a>' +
+ '<div class="dwh-spacer"></div>' +
+ '<nav class="dwh-drawer" id="dwhNavDrawer" aria-label="Primary">' + linksHTML(NAV) + '</nav>' +
+ (isAdmin ? '<nav class="dwh-drawer dwh-admin-drawer" id="dwhAdminDrawer" aria-label="Admin tools"><div class="dwh-head">Admin tools</div>' + linksHTML(ADMIN) + '<button type="button" data-act="logout">Sign out admin</button></nav>' : '');
+
+ // remove any pre-existing inline header so we don't double up
+ var old = document.querySelector('header.ns-header, header.dwh:not(:first-of-type)');
+ if (old && old.className.indexOf('dwh') === -1) old.style.display = 'none';
+
+ document.body.insertBefore(header, document.body.firstChild);
+
+ // ---- behaviour ----
+ function drawer(id) { return document.getElementById(id); }
+ function close(navId, btnId) { var d = drawer(navId), b = document.getElementById(btnId); if (d) d.classList.remove('open'); if (b) b.setAttribute('aria-expanded', 'false'); }
+ function toggle(navId, btnId, otherNav, otherBtn) {
+ close(otherNav, otherBtn);
+ var d = drawer(navId), b = document.getElementById(btnId);
+ if (!d || !b) return;
+ var open = d.classList.toggle('open');
+ b.setAttribute('aria-expanded', open ? 'true' : 'false');
+ }
+ var navBtn = document.getElementById('dwhNav');
+ if (navBtn) navBtn.addEventListener('click', function () { toggle('dwhNavDrawer', 'dwhNav', 'dwhAdminDrawer', 'dwhAdmin'); });
+ var admBtn = document.getElementById('dwhAdmin');
+ if (admBtn) admBtn.addEventListener('click', function () { toggle('dwhAdminDrawer', 'dwhAdmin', 'dwhNavDrawer', 'dwhNav'); });
+
+ header.addEventListener('click', function (e) {
+ var act = e.target && e.target.getAttribute && e.target.getAttribute('data-act');
+ if (!act) return;
+ if (act === 'logout') { try { localStorage.removeItem('dwAdmin'); } catch (x) {} location.reload(); return; }
+ if (typeof window[act] === 'function') { e.preventDefault(); window[act](); }
+ });
+ document.addEventListener('keydown', function (e) { if (e.key === 'Escape') { close('dwhNavDrawer', 'dwhNav'); close('dwhAdminDrawer', 'dwhAdmin'); } });
+})();
diff --git a/component/test.html b/component/test.html
new file mode 100644
index 0000000..84f052b
--- /dev/null
+++ b/component/test.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html><html><head><meta charset="utf-8"><title>dw-header test</title>
+<style>:root{--accent:#9B7B4F}body{margin:0;background:#0e1311;min-height:1200px;font-family:Inter,sans-serif}
+.hero{height:380px;display:flex;align-items:center;justify-content:center;color:#9B7B4F;font-family:'Cormorant Garamond',serif;font-style:italic;font-size:54px;background:linear-gradient(160deg,#1a1410,#0e1311)}</style>
+<script>window.DwHeaderConfig={siteName:'Jute Wallpaper',accent:'#9B7B4F',navLinks:[{name:'Home',url:'/'},{name:'Collections',url:'#'},{name:'About',url:'#'},{name:'Care',url:'#'}],adminLinks:[{name:'Shopify admin',url:'#',external:true},{name:'Edit hero',url:'#'},{name:'Analytics',url:'#'}]};</script>
+</head><body><div class="hero">Jute Wallpaper</div><script src="/dw-header.js"></script></body></html>
diff --git a/dw-fleet-registry.json b/dw-fleet-registry.json
index 57790e3..b811214 100644
--- a/dw-fleet-registry.json
+++ b/dw-fleet-registry.json
@@ -3,7 +3,7 @@
"generatedAt": "2026-06-01T19:45:45.840Z",
"provenance": "DTD verdict 2026-06-01 — consolidate to one canonical registry (control plane only; serving methods pluggable).",
"summary": {
- "totalDomains": 119,
+ "totalDomains": 118,
"localMicrosites": 75,
"manifestDomains": 44,
"overlappingDomains": 0,
@@ -905,51 +905,6 @@
"checkedAt": "2026-06-01T19:45:47.341Z"
}
},
- {
- "domain": "bleachfriendly.com",
- "slug": "bleachfriendly",
- "siteName": "Bleach Friendly",
- "fleets": [
- "domain-fleet-manifest"
- ],
- "overlap": false,
- "servingModes": [
- "shared-multitenant"
- ],
- "canonicalOwner": "domain-fleet-manifest",
- "local": null,
- "manifest": {
- "domain": "bleachfriendly.com",
- "slug": "bleachfriendly",
- "siteName": "Bleach Friendly",
- "servingMode": "shared-multitenant",
- "configPath": "/Users/stevestudio2/Projects/dw-domain-fleet/sites/bleachfriendly.json",
- "port": 10008,
- "siteEmail": "info@bleachfriendly.com",
- "hasLocalDir": false,
- "theme": {
- "accent": "#2a7d8a",
- "bg": "#0a1113",
- "bgLight": "#f0f4f5",
- "ink": "#e6eef0",
- "inkLight": "#10191c",
- "rule": "#bdcdd0",
- "serif": "'Cormorant Garamond','Playfair Display',Georgia,serif",
- "sans": "-apple-system,'Inter',system-ui,sans-serif",
- "palette": "coastal",
- "font": "cormorant"
- }
- },
- "live": {
- "state": "live",
- "status": 200,
- "finalUrl": "https://bleachfriendly.com/",
- "server": "nginx",
- "redirectedOffDomain": null,
- "scheme": "https",
- "checkedAt": "2026-06-01T19:45:47.323Z"
- }
- },
{
"domain": "blockprintedwallpaper.com",
"slug": "blockprintedwallpaper",
← e2ab0c2 fix(grasscloth board): dark --muted/--rule on white ground s
·
back to Dw Fleet Registry
·
fleet fan-out engine + hero ledger: 4 sites rebuilt (jute+me 80203d0 →