← back to Jutewallpaper
admin: add dw-theme-nav 20-theme navigator (upper-right, admin-only)
3ed1cdd8c23ea0b236e1a2c4235818dfa8d64ae6 · 2026-06-01 16:38:40 -0700 · Steve Abrams
Files touched
A public/dw-theme-nav.jsM public/index.html
Diff
commit 3ed1cdd8c23ea0b236e1a2c4235818dfa8d64ae6
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Jun 1 16:38:40 2026 -0700
admin: add dw-theme-nav 20-theme navigator (upper-right, admin-only)
---
public/dw-theme-nav.js | 123 +++++++++++++++++++++++++++++++++++++++++++++++++
public/index.html | 1 +
2 files changed, 124 insertions(+)
diff --git a/public/dw-theme-nav.js b/public/dw-theme-nav.js
new file mode 100644
index 0000000..5edf346
--- /dev/null
+++ b/public/dw-theme-nav.js
@@ -0,0 +1,123 @@
+/* dw-theme-nav.js — admin-only theme navigator (drop-in).
+ *
+ * Lets a logged-in admin step ‹ back / next › through a library of ~20 color
+ * themes, live, from a pill in the UPPER-RIGHT corner. Each theme rewrites the
+ * site's CSS custom properties (--bg/--paper/--muted/--line/--accent/--bg-soft),
+ * so it previews any palette instantly. Choice persists per-site in localStorage.
+ *
+ * Drop-in: <script src="/dw-theme-nav.js" defer></script>
+ * Admin gate: shows ONLY when localStorage.dwAdmin === '1' (same gate as
+ * dw-header.js; set via ?admin=1). Public visitors never see it.
+ * Optional override BEFORE load:
+ * window.DwThemeNavConfig = { themes: [...], extraVars: {'--card':'#..'} }
+ *
+ * Idempotent · self-styled · zero deps.
+ */
+(function () {
+ 'use strict';
+ if (window.__dwThemeNavLoaded) return;
+ window.__dwThemeNavLoaded = true;
+
+ // admin gate (mirror dw-header.js)
+ 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) {}
+ if (!isAdmin) return;
+
+ var CFG = window.DwThemeNavConfig || {};
+ var KEY = 'dwTheme:' + location.hostname.replace(/^www\./, '');
+
+ // vars order: [bg, paper(text), muted, line, accent, bg-soft]
+ var THEMES = CFG.themes || [
+ { n: 'Classic Cream', v: ['#f5f2eb', '#1c1a16', '#8b7866', 'rgba(28,26,22,.12)', '#9c7a4d', '#ece6d8'] },
+ { n: 'Victorian Oxblood', v: ['#f4ece0', '#2a1714', '#8a6f5e', 'rgba(42,23,20,.12)', '#7b2d26', '#ece0cf'] },
+ { n: 'Belle Époque Sage', v: ['#f1ede2', '#2f2c22', '#877f63', 'rgba(47,44,34,.12)', '#6f7d57', '#e6e0cd'] },
+ { n: 'Edwardian Pale', v: ['#f7f3ec', '#34302a', '#978b78', 'rgba(52,48,42,.10)', '#9aa489', '#eee7d8'] },
+ { n: 'Art Deco Gold', v: ['#f3eee2', '#15140f', '#8a7c55', 'rgba(21,20,15,.14)', '#b8932e', '#e7ddc4'] },
+ { n: 'Streamline Teal', v: ['#eef1f2', '#1c2429', '#7c8990', 'rgba(28,36,41,.12)', '#3f6e7d', '#dde5e8'] },
+ { n: 'Atomic Turquoise', v: ['#f6f2e9', '#26241f', '#8c8275', 'rgba(38,36,31,.12)', '#2f9e96', '#ece4d3'] },
+ { n: 'Mod Orange', v: ['#f7f2e6', '#221d18', '#8a7e66', 'rgba(34,29,24,.13)', '#e2622c', '#efe6d2'] },
+ { n: 'Earthy 70s', v: ['#f3ebdb', '#281f17', '#8a7a5e', 'rgba(40,31,23,.13)', '#b5651d', '#e9ddc4'] },
+ { n: 'Memphis Pink', v: ['#faf7f2', '#14110f', '#8a8a8a', 'rgba(20,17,15,.14)', '#ff2d8b', '#efeae3'] },
+ { n: 'Noir', v: ['#0f0f0f', '#f4f4f2', '#9a9a9a', 'rgba(255,255,255,.14)', '#e8e8e8', '#1a1a1a'] },
+ { n: 'Forest', v: ['#f1f3ee', '#1a241c', '#7d8a7e', 'rgba(26,36,28,.12)', '#2f6b45', '#e0e8e0'] },
+ { n: 'Royal Navy', v: ['#eef0f5', '#14192a', '#7e8499', 'rgba(20,25,42,.12)', '#27407a', '#dde2ee'] },
+ { n: 'Burgundy', v: ['#f5efee', '#241416', '#8c7376', 'rgba(36,20,22,.12)', '#7a263a', '#ece0e0'] },
+ { n: 'Slate Modern', v: ['#eef0f1', '#1b2024', '#7f8890', 'rgba(27,32,36,.12)', '#52606b', '#dee2e5'] },
+ { n: 'Desert Sand', v: ['#f6efe3', '#2c2419', '#9a8a6e', 'rgba(44,36,25,.12)', '#c08a45', '#ece0c9'] },
+ { n: 'Emerald Luxe', v: ['#101814', '#eef3ee', '#84a394', 'rgba(235,250,242,.12)', '#3fd09a', '#1a2720'] },
+ { n: 'Plum', v: ['#f4f0f4', '#241827', '#897689', 'rgba(36,24,39,.12)', '#7b4a8a', '#e9e0ec'] },
+ { n: 'Copper', v: ['#f5efe9', '#241a14', '#8c7a6a', 'rgba(36,26,20,.12)', '#b5651d', '#ece0d4'] },
+ { n: 'Ocean', v: ['#eef3f5', '#15252b', '#7d949c', 'rgba(21,37,43,.12)', '#1f7d96', '#dde9ed'] },
+ ];
+
+ var VARS = ['--bg', '--paper', '--muted', '--line', '--accent', '--bg-soft'];
+
+ function apply(i) {
+ var t = THEMES[(i % THEMES.length + THEMES.length) % THEMES.length];
+ var root = document.documentElement.style;
+ for (var k = 0; k < VARS.length; k++) root.setProperty(VARS[k], t.v[k]);
+ if (CFG.extraVars) for (var ev in CFG.extraVars) root.setProperty(ev, CFG.extraVars[ev]);
+ var lbl = document.getElementById('dwtn-label');
+ if (lbl) lbl.textContent = t.n + ' · ' + ((i % THEMES.length + THEMES.length) % THEMES.length + 1) + '/' + THEMES.length;
+ }
+
+ var idx = 0;
+ try { var s = parseInt(localStorage.getItem(KEY), 10); if (!isNaN(s)) idx = s; } catch (e) {}
+
+ function go(d) {
+ idx = (idx + d % THEMES.length + THEMES.length) % THEMES.length;
+ apply(idx);
+ try { localStorage.setItem(KEY, String(idx)); } catch (e) {}
+ }
+
+ // ---- styles ----
+ var css = document.createElement('style');
+ css.textContent = [
+ '#dwtn{position:fixed;top:14px;right:18px;z-index:9999;display:flex;align-items:center;gap:2px;',
+ 'background:rgba(10,10,10,.82);backdrop-filter:blur(8px);border:1px solid rgba(245,241,232,.18);',
+ 'border-radius:999px;padding:3px 4px;font-family:-apple-system,system-ui,sans-serif;box-shadow:0 6px 20px rgba(0,0,0,.4)}',
+ '#dwtn button{display:flex;align-items:center;justify-content:center;width:26px;height:26px;border:0;background:transparent;',
+ 'color:#f5f1e8;font-size:15px;line-height:1;cursor:pointer;border-radius:50%;transition:background .15s}',
+ '#dwtn button:hover{background:rgba(245,241,232,.14)}',
+ '#dwtn #dwtn-label{font-size:11px;letter-spacing:.04em;color:#f5f1e8;min-width:120px;text-align:center;padding:0 6px;white-space:nowrap;opacity:.92}',
+ '#dwtn .dwtn-reset{font-size:12px;opacity:.6}',
+ ].join('');
+ document.head.appendChild(css);
+
+ // ---- widget ----
+ function build() {
+ var el = document.createElement('div');
+ el.id = 'dwtn';
+ el.setAttribute('aria-label', 'Theme navigator (admin)');
+ el.innerHTML =
+ '<button type="button" id="dwtn-prev" aria-label="Previous theme" title="Previous theme (←)">‹</button>' +
+ '<span id="dwtn-label">—</span>' +
+ '<button type="button" id="dwtn-next" aria-label="Next theme" title="Next theme (→)">›</button>' +
+ '<button type="button" class="dwtn-reset" id="dwtn-reset" aria-label="Reset to site default" title="Reset to site default">↺</button>';
+ document.body.appendChild(el);
+ document.getElementById('dwtn-prev').addEventListener('click', function () { go(-1); });
+ document.getElementById('dwtn-next').addEventListener('click', function () { go(1); });
+ document.getElementById('dwtn-reset').addEventListener('click', function () {
+ try { localStorage.removeItem(KEY); } catch (e) {}
+ var root = document.documentElement.style;
+ for (var k = 0; k < VARS.length; k++) root.removeProperty(VARS[k]);
+ idx = 0;
+ var lbl = document.getElementById('dwtn-label'); if (lbl) lbl.textContent = 'Site default';
+ });
+ document.addEventListener('keydown', function (e) {
+ if (e.target && /input|textarea|select/i.test(e.target.tagName)) return;
+ if (e.key === 'ArrowLeft' && e.altKey) { go(-1); }
+ if (e.key === 'ArrowRight' && e.altKey) { go(1); }
+ });
+ // restore saved theme on load
+ try { if (localStorage.getItem(KEY) !== null) apply(idx); else { var l = document.getElementById('dwtn-label'); if (l) l.textContent = 'Site default'; } } catch (e) {}
+ }
+
+ if (document.body) build();
+ else document.addEventListener('DOMContentLoaded', build);
+})();
diff --git a/public/index.html b/public/index.html
index 2e9eb6f..0485327 100644
--- a/public/index.html
+++ b/public/index.html
@@ -858,6 +858,7 @@ document.addEventListener('DOMContentLoaded', function(){ try { renderRailSectio
<!-- ============================================================
DW UNIVERSAL CONTACT MODULE — fashion-house UX
Inject before <script src="/hero-4grid.js" defer></script>
+<script src="/dw-theme-nav.js" defer></script>
</body> in every DW-family sister site.
Self-contained: CSS + 4 modals + JS submission handlers.
Uses host site's CSS vars (--bg, --fg, --gold, --rule).
← 738552d catalog: fill from Shopify (jutewallpaper niche)
·
back to Jutewallpaper
·
a11y: add aria-label to contact/sample form inputs (zero vis 493720b →