← back to Recycledwallpaper
public/dw-header.js
131 lines
/* 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>' : '');
document.body.insertBefore(header, document.body.firstChild);
// hide ANY pre-existing site header (ns-header, .header, etc.) so we never double up
document.querySelectorAll('header').forEach(function (h) {
if (h !== header && h.className.indexOf('dwh') === -1) h.style.display = 'none';
});
// ---- 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'); } });
})();