[object Object]

← back to Madagascarwallpaper

feat(fleet): theme1/theme2 query-param-gated toggle on catalog page

c25c1f61d04f04294bd55e907a0f0651248ff6b6 · 2026-05-29 13:20:43 -0700 · Steve Abrams

Default: theme1, toggle hidden. Opt-in via ?theme=2 or localStorage
('wallco-page-theme' key shared across the fleet). Same gate pattern
as dw-domain-fleet productPage.

Files touched

Diff

commit c25c1f61d04f04294bd55e907a0f0651248ff6b6
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri May 29 13:20:43 2026 -0700

    feat(fleet): theme1/theme2 query-param-gated toggle on catalog page
    
    Default: theme1, toggle hidden. Opt-in via ?theme=2 or localStorage
    ('wallco-page-theme' key shared across the fleet). Same gate pattern
    as dw-domain-fleet productPage.
---
 public/index.html | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 81 insertions(+), 4 deletions(-)

diff --git a/public/index.html b/public/index.html
index 778a723..8a3dc15 100644
--- a/public/index.html
+++ b/public/index.html
@@ -236,6 +236,74 @@ textarea:focus-visible,
 <!-- End Meta Pixel -->
 </head>
 <body>
+<!-- Theme toggle (theme1 / theme2) — query-param-gated preview surface
+     (Steve 2026-05-29). ?theme=2 in URL flips to theme2 and persists in
+     localStorage. Toggle UI hidden until theme2 mode active. Same key
+     ('wallco-page-theme') as wallco.ai for cross-site continuity. -->
+<script>
+(function(){
+  var qs = new URLSearchParams(location.search);
+  var fromUrl = qs.get('theme');
+  var saved = null; try { saved = localStorage.getItem('wallco-page-theme'); } catch(e){}
+  var theme = null;
+  if (fromUrl === '2' || fromUrl === 'theme2') theme = 'theme2';
+  else if (fromUrl === '1' || fromUrl === 'theme1') theme = 'theme1';
+  else if (saved === 'theme2' || saved === 'theme1') theme = saved;
+  if (theme) {
+    document.documentElement.setAttribute('data-page-theme', theme);
+    if (fromUrl) { try { localStorage.setItem('wallco-page-theme', theme); } catch(e){} }
+  }
+})();
+</script>
+<style id="page-theme-css-fleet">
+  [data-page-theme="theme2"] body{
+    background:#fff !important; color:#0a0a0a !important;
+  }
+  [data-page-theme="theme2"] h1,
+  [data-page-theme="theme2"] h2,
+  [data-page-theme="theme2"] h3{ letter-spacing:-0.01em; font-weight:500; }
+  [data-page-theme="theme2"] .card,
+  [data-page-theme="theme2"] [class*="card"]{
+    background:#fafafa !important; border:1px solid #e5e5e5 !important; box-shadow:none !important;
+  }
+  #page-theme-toggle{ display:none; }
+  [data-page-theme] #page-theme-toggle{ display:inline-flex; }
+  #page-theme-toggle{
+    position:fixed; top:96px; right:14px; z-index:90;
+    gap:0; align-items:center;
+    background:rgba(255,255,255,.92); border:1px solid #d8d2c5;
+    border-radius:999px; padding:3px; backdrop-filter:blur(6px);
+    box-shadow:0 2px 10px rgba(0,0,0,.08);
+    font:600 11px ui-sans-serif,system-ui; letter-spacing:.06em; text-transform:uppercase;
+  }
+  #page-theme-toggle button{
+    border:0; background:transparent; color:#5a5048; cursor:pointer;
+    padding:6px 14px; border-radius:999px; font:inherit; transition:all .15s;
+  }
+  #page-theme-toggle button[aria-pressed="true"]{ background:#1a1714; color:#faf7f2; }
+  #page-theme-toggle button:hover:not([aria-pressed="true"]){ color:#1a1714; }
+</style>
+<div id="page-theme-toggle" role="group" aria-label="Page theme">
+  <button type="button" data-theme="theme1" aria-pressed="false">Theme 1</button>
+  <button type="button" data-theme="theme2" aria-pressed="false">Theme 2</button>
+</div>
+<script>
+(function(){
+  var cur = document.documentElement.getAttribute('data-page-theme') || 'theme1';
+  var btns = document.querySelectorAll('#page-theme-toggle button');
+  function sync(t){ btns.forEach(function(b){ b.setAttribute('aria-pressed', b.dataset.theme === t ? 'true' : 'false'); }); }
+  sync(cur);
+  btns.forEach(function(b){
+    b.addEventListener('click', function(){
+      var t = b.dataset.theme;
+      document.documentElement.setAttribute('data-page-theme', t);
+      try { localStorage.setItem('wallco-page-theme', t); } catch(e){}
+      sync(t);
+    });
+  });
+})();
+</script>
+
 <style id="ns-header-modal-styles">
         .ns-header { position: sticky; top: 0; z-index: 100; display: flex; align-items: center; justify-content: space-between; padding: 18px 36px; background: rgba(10,10,10,0.92) !important; backdrop-filter: blur(8px); border-bottom: 1px solid rgba(245,241,232,0.06); }
         .ns-brand { display: inline-flex; align-items: center; gap: 14px; text-decoration: none; }
@@ -517,6 +585,13 @@ try {
 const LABELS = {"all":"All","texture":"Natural Weave","botanical":"Botanical & Leaf"};
 
 function escAttr(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, c => ({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;' }[c])); }
+// Strip vendor names baked into a sku/handle slug BEFORE displaying it (the raw slug carries
+// supplier names e.g. "versace-...-versace", "hutch-raffia-ivory-lee-jofa-modern"). Display-only;
+// the real p.sku is untouched for cart/links. Enforces "no DW vendor names in customer UI".
+function cleanSku(s){
+  var V=/(?:^|-)(versace|kravet|fentucci|sandberg|harlequin|blithfield|westport|thibaut|koroseal|schumacher|scalamandre|fromental|dedar|carnegie|marburg|fabricut|coordonne|nina-campbell|lee-jofa(?:-modern)?|clarke(?:-and)?-clarke|clarke|brunschwig(?:-and)?(?:-fils)?|designers-guild|graham-(?:and-)?brown|andrew-martin|candice-olson|ronald-redding|jeffrey-stevens|sister-parish|arte-international|wolf-gordon|phillip-jeffries|cole-(?:and-)?son|maya-romanoff|g-p-j-baker|les-ensembliers|breegan(?:-jane)?)(?:-\d+)?(?=-|$)/gi;
+  return String(s||'').replace(V,'').replace(/--+/g,'-').replace(/^-+|-+$/g,'');
+}
 // Image-URL allowlist: only DW Shopify CDN + designerwallcoverings.com (defends against XSS via crafted products.json)
 function safeImg(u) { return /^https:\/\/(?:cdn\.shopify\.com|designerwallcoverings\.com)\//.test(u || '') ? u : '/hero-bg.jpg'; }
 function cardHTML(p) {
@@ -524,7 +599,7 @@ function cardHTML(p) {
   return '<img loading="' + (eager ? 'eager' : 'lazy') + '"' + (eager ? ' fetchpriority="high"' : '') + ' src="' + escAttr(safeImg(p.image_url)) + '" alt="' + escAttr(p.title) + '">'
     + '<div class="overlay">'
     + '<div class="pat">' + escAttr(p.pattern_name || p.title) + '</div>'
-    + '<div class="ven">Designer Wallcoverings · ' + escAttr(p.sku || p.handle || '') + '</div>'
+    + '<div class="ven">Designer Wallcoverings' + (cleanSku(p.sku || p.handle) ? ' · ' + escAttr(cleanSku(p.sku || p.handle)) : '') + '</div>'
     + '<div class="actions">'
     + '<button class="sample-btn" onclick="event.stopPropagation();dwmOpen(\'Sample\',' + JSON.stringify({sku:p.sku||p.handle, title:p.title, image_url:safeImg(p.image_url)}).replace(/"/g,'&quot;') + ')">Sample</button>'
     + '</div></div>';
@@ -538,7 +613,7 @@ function rowHTML(p) {
   const aes = LABELS[p.aesthetic] || p.aesthetic || '—';
   return '<img loading="lazy" src="' + escAttr(safeImg(p.image_url)) + '" alt="' + escAttr(p.title) + '">'
     + '<div class="r-pat">' + escAttr(p.pattern_name || p.title) + '</div>'
-    + '<div class="r-col r-sku">' + escAttr(p.sku || p.handle || '—') + '</div>'
+    + '<div class="r-col r-sku">' + escAttr(cleanSku(p.sku || p.handle) || '—') + '</div>'
     + '<div class="r-col r-aes">' + escAttr(String(aes).replace(/-/g, ' ')) + '</div>'
     + '<div class="r-col r-price">' + price + '</div>';
 }
@@ -563,7 +638,7 @@ function openDetails(p) {
   m.querySelector('[data-d-img]').src = safeP.image_url;
   m.querySelector('[data-d-img]').alt = safeP.title;
   m.querySelector('[data-d-pat]').textContent = safeP.pattern;
-  m.querySelector('[data-d-brand]').textContent = 'Designer Wallcoverings · ' + safeP.sku;
+  m.querySelector('[data-d-brand]').textContent = 'Designer Wallcoverings' + (cleanSku(safeP.sku) ? ' · ' + cleanSku(safeP.sku) : '');
   const priceEl = m.querySelector('[data-d-price]');
   priceEl.textContent = safeP.price != null && !isNaN(safeP.price) ? ('$' + safeP.price.toFixed(2) + ' / roll') : 'Pricing on request';
   const chipsEl = m.querySelector('[data-d-chips]');
@@ -755,7 +830,9 @@ if (tb) tb.addEventListener('click', () => setTheme(document.documentElement.dat
 
 loadFacets();
 loadGridPage();
-renderRailSections();
+// renderRailSections is defined in a later <script> block; defer the call to DOMContentLoaded
+// so the function exists when invoked (fixes "renderRailSections is not defined" on load).
+document.addEventListener('DOMContentLoaded', function(){ try { renderRailSections(); } catch(e){ console.warn('rails defer', e); } });
 </script>
 <!-- ============================================================
      DW UNIVERSAL CONTACT MODULE — fashion-house UX

← 1fa618a scrub vendor name leaks from products.json source (47 produc  ·  back to Madagascarwallpaper  ·  Remove Big Red widget — never UX-worked, collided with Help d6d533c →