← back to Wallco Ai
PDP theme switcher: working public dropdown (Classic + 17 variants) in .switcher; fix broken ← Classic link (route via ?classic=1)
08fb7a1203fb6f19455f6d9deb33db0c3757fd93 · 2026-06-01 19:53:45 -0700 · Steve Abrams
Files touched
M public/theme-variants/theme-variant-nav.js
Diff
commit 08fb7a1203fb6f19455f6d9deb33db0c3757fd93
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Jun 1 19:53:45 2026 -0700
PDP theme switcher: working public dropdown (Classic + 17 variants) in .switcher; fix broken ← Classic link (route via ?classic=1)
---
public/theme-variants/theme-variant-nav.js | 100 +++++++++++++++++++++++++----
1 file changed, 88 insertions(+), 12 deletions(-)
diff --git a/public/theme-variants/theme-variant-nav.js b/public/theme-variants/theme-variant-nav.js
index 1ca47b5..be201ae 100644
--- a/public/theme-variants/theme-variant-nav.js
+++ b/public/theme-variants/theme-variant-nav.js
@@ -1,24 +1,26 @@
-/* theme-variant-nav.js — admin-only ‹ prev / next › theme stepper, upper-right.
+/* theme-variant-nav.js — PDP theme switchers for the 17 variants.
*
* Drop-in (ABSOLUTE path — variants are served at /design/:id/:slug):
* <script src="/theme-variants/theme-variant-nav.js" defer></script>
- * Admin gate: shows ONLY when localStorage.dwAdmin === '1' (set via ?admin=1).
- * Steps through all 17 PDP theme variants while preserving the design id + query,
- * so you stay on the same design. Marks the LIVE theme (from /data/pdp-theme.json).
- * Self-styled · idempotent · zero deps. Alt+←/→ also step.
+ *
+ * Two switchers:
+ * (1) PUBLIC theme dropdown — turns each variant's static .switcher bar
+ * ("Theme: X ← Classic") into a working <select> of Classic + all 17
+ * variants. Runs for everyone (the bar is already public). Also fixes the
+ * broken "← Classic" link (its inline href points at the bare /design/:id
+ * which serves the LIVE theme, so when a variant is the live default it
+ * loops back to itself — we route Classic through ?classic=1).
+ * (2) ADMIN ‹ prev / next › stepper pill (upper-right) — shows ONLY when
+ * localStorage.dwAdmin === '1' (set via ?admin=1). Alt+←/→ also step.
+ * Marks the LIVE theme (from /api/pdp-theme). Self-styled · idempotent · 0 deps.
*/
(function () {
'use strict';
if (window.__themeVariantNavLoaded) return;
window.__themeVariantNavLoaded = true;
- // admin gate
- try {
- var q = new URLSearchParams(location.search);
- if (q.get('admin') === '1') localStorage.setItem('dwAdmin', '1');
- if (q.get('admin') === '0') localStorage.removeItem('dwAdmin');
- if (localStorage.getItem('dwAdmin') !== '1') return;
- } catch (e) { return; }
+ // NOTE: the admin gate is DOWN below — it wraps ONLY the ‹ › stepper pill.
+ // The public dropdown runs for everyone.
// ordered route slugs (match server /design/:id/:slug + smoke test) → display name
var THEMES = [
@@ -73,6 +75,80 @@
var LIVE_IDX = -1;
+ // ───────────────────────────────────────────────────────────────────────
+ // (1) PUBLIC theme dropdown (everyone). Classic routes through ?classic=1
+ // (server forces the monolith) so it works even when a variant is the live
+ // default; variants route through /design/:id/<slug> (always that variant).
+ // ───────────────────────────────────────────────────────────────────────
+ function classicUrl() {
+ if (mode !== 'route') return '/'; // file-mode dev: no monolith
+ var p = new URLSearchParams(location.search);
+ p.set('classic', '1');
+ return '/design/' + encodeURIComponent(designId) + '?' + p.toString();
+ }
+ function buildDropdown() {
+ var sw = document.querySelector('header .switcher') || document.querySelector('.switcher');
+ if (!sw || sw.querySelector('.tvn-dd')) return; // no bar, or already built
+ if (!document.getElementById('tvn-dd-css')) {
+ var st = document.createElement('style');
+ st.id = 'tvn-dd-css';
+ st.textContent = '.switcher .tvn-dd{font:inherit;font-size:11px;line-height:1.4;color:var(--ink,#1a1611);'
+ + 'background:transparent;border:1px solid rgba(0,0,0,.28);border-radius:6px;padding:2px 6px;'
+ + 'cursor:pointer;max-width:230px;vertical-align:middle}';
+ document.head.appendChild(st);
+ }
+ var sel = document.createElement('select');
+ sel.className = 'tvn-dd';
+ sel.setAttribute('aria-label', 'Switch PDP theme');
+ var oc = document.createElement('option');
+ oc.value = '__classic__';
+ oc.textContent = 'Classic (full PDP)';
+ sel.appendChild(oc);
+ THEMES.forEach(function (t, i) {
+ var o = document.createElement('option');
+ o.value = t[0];
+ o.textContent = 'V' + (i + 1) + ' · ' + t[1] + (i === LIVE_IDX ? ' • live' : '');
+ if (i === idx) o.selected = true;
+ sel.appendChild(o);
+ });
+ sel.addEventListener('change', function () {
+ location.href = (sel.value === '__classic__') ? classicUrl() : urlFor(SLUGS.indexOf(sel.value));
+ });
+ // Repurpose the existing "← Classic" link (inline href is the broken bare
+ // URL). Force it through classicUrl() so it works even if the page's own
+ // inline script resets the href afterwards.
+ var back = sw.querySelector('#back-to-classic');
+ if (back) {
+ back.href = classicUrl();
+ back.addEventListener('click', function (e) { e.preventDefault(); location.href = classicUrl(); });
+ }
+ sw.appendChild(document.createTextNode(' '));
+ sw.appendChild(sel);
+ }
+ function tvnInit() {
+ // Resolve live theme (to flag "• live"), then build. Build regardless of
+ // fetch outcome — the dropdown must work even if /api/pdp-theme is down.
+ fetch('/api/pdp-theme', { credentials: 'same-origin' })
+ .then(function (r) { return r.ok ? r.json() : null; })
+ .then(function (j) {
+ var s = j && typeof j.theme === 'string' ? j.theme.toLowerCase().trim() : '';
+ var li = SLUGS.indexOf(s);
+ if (li !== -1) LIVE_IDX = li;
+ })
+ .catch(function () {})
+ .then(buildDropdown);
+ }
+ if (document.body) tvnInit();
+ else document.addEventListener('DOMContentLoaded', tvnInit);
+
+ // ───────────── (2) ADMIN ‹ prev / next › stepper pill (admin-only) ─────────────
+ try {
+ var q = new URLSearchParams(location.search);
+ if (q.get('admin') === '1') localStorage.setItem('dwAdmin', '1');
+ if (q.get('admin') === '0') localStorage.removeItem('dwAdmin');
+ if (localStorage.getItem('dwAdmin') !== '1') return;
+ } catch (e) { return; }
+
// ---- styles ----
var css = document.createElement('style');
css.textContent = [
← 833bd9f genComfy: COMFY_SAMPLER/COMFY_SCHEDULER env knob (seam inves
·
back to Wallco Ai
·
follow-ups #2 + #3: settlement trigger on all_designs (post- 05e0e01 →