[object Object]

← back to Wallco Ai

PDP v11-best (live theme): Wallpaper/Wall Mural toggle on every design

bd05114a515e715cea2870d39158add36dca5aaf · 2026-06-01 16:38:41 -0700 · Steve Abrams

The live PDP theme is v11-best.html, which hard-locked format to design.kind.
Added the explicit Wallpaper / Wall Mural segmented toggle above the CTAs;
applyMode() re-renders the sold-by line, last spec row (Roll length<->Panel
size), Buy CTA label, the at-scale preview (tiled <-> one continuous image
across the wall) and the calculator. Default follows kind, both always offered.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit bd05114a515e715cea2870d39158add36dca5aaf
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Jun 1 16:38:41 2026 -0700

    PDP v11-best (live theme): Wallpaper/Wall Mural toggle on every design
    
    The live PDP theme is v11-best.html, which hard-locked format to design.kind.
    Added the explicit Wallpaper / Wall Mural segmented toggle above the CTAs;
    applyMode() re-renders the sold-by line, last spec row (Roll length<->Panel
    size), Buy CTA label, the at-scale preview (tiled <-> one continuous image
    across the wall) and the calculator. Default follows kind, both always offered.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 public/theme-variants/v11-best.html | 90 ++++++++++++++++++++++++++++++++-----
 1 file changed, 80 insertions(+), 10 deletions(-)

diff --git a/public/theme-variants/v11-best.html b/public/theme-variants/v11-best.html
index 8bf1f2f..e643cab 100644
--- a/public/theme-variants/v11-best.html
+++ b/public/theme-variants/v11-best.html
@@ -107,6 +107,16 @@
   .cta.buy { background: var(--accent-dk); color: #f0eada; }
   .cta:hover { filter: brightness(1.04); }
 
+  /* ── format toggle: Wallpaper vs Wall Mural (offered on every design) ── */
+  .fmt-toggle { display: grid; grid-template-columns: 1fr 1fr; gap: 0; border: 1px solid var(--line); border-radius: var(--r); overflow: hidden; }
+  .fmt-tab { background: transparent; border: 0; border-right: 1px solid var(--line); padding: 12px 14px; text-align: left; cursor: pointer; color: var(--ink); }
+  .fmt-tab:last-child { border-right: 0; }
+  .fmt-tab .t { display: block; font: 700 13.5px/1 var(--sans); letter-spacing: .02em; }
+  .fmt-tab .s { display: block; font: 11px/1.3 var(--sans); color: var(--muted); margin-top: 3px; }
+  .fmt-tab.active { background: var(--accent-dk); }
+  .fmt-tab.active .t, .fmt-tab.active .s { color: #f0eada; }
+  .fmt-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
+
   /* tab bar (V3) */
   .tab-bar {
     background: var(--tab-bg);
@@ -298,13 +308,21 @@
   const cat = design.category || 'wallpaper';
   const catDisplay = (cat || '').replace(/^botanical-wonders · /, 'Botanical Wonders — ').replace(/^designer-zoo-calm/, 'Designer Zoo Calm').replace(/^cactus-pine-scenic/, 'Cactus Pine Scenic').replace(/^monterey-mural/, 'Coastal Scenic').replace(/^tree-mural/, 'Full-Room Scenic').replace(/^cactus-11ft-mural/, 'Single-Subject Mural').replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
   const kindLabel = design.kind === 'seamless_tile' ? 'Seamless Tile (24×24")' : design.kind === 'mural_panel' ? 'Mural Panel' : design.kind === 'mural' ? 'Single-Subject Mural' : (design.kind || 'pattern');
-  const isMural = design.kind === 'mural_panel' || design.kind === 'mural';
+  // FORMAT (Steve 2026-06-01): every design is purchasable BOTH as a seamless
+  // Wallpaper (per roll) AND as a single Wall Mural (one image across the whole
+  // wall, printed from the full-size TIF). `mode` is user-selectable via the
+  // toggle below; the default follows the design's native kind, but both are
+  // always offered. applyMode() re-renders the mode-dependent bits live.
+  const nativeMural = design.kind === 'mural_panel' || design.kind === 'mural';
+  let mode = nativeMural ? 'mural' : 'wallpaper';
+  const muralMode = () => mode === 'mural';
+  const soldByText = (m) => (m === 'mural' ? 'Per panel (36" × 11 ft)' : 'Per roll (27 ft)');
   const palette = Array.isArray(design.palette) ? design.palette : [];
   const dominant = design.dominant_hex || (palette[0] && palette[0].hex) || '#888';
   const img = (/^(\/[^"'<>\s]*|https?:\/\/[^"'<>\s]*)$/i.test(String(design.image_url||'')) ? design.image_url : null) || ('/designs/img/by-id/' + id);
   const w = Number(design.width_in) || 24, h = Number(design.height_in) || 24;
   const repeatLabel = w + '"' + (w !== h ? ' × ' + h + '"' : '');
-  const rollOrPanel = isMural ? 'Per panel (36" × 11 ft)' : 'Per roll (27 ft)';
+  const rollOrPanel = soldByText(mode);
   const handle = design.handle || ('wallco-' + id);
   const generator = design.generator || 'wallco.ai';
   const roomMockup = (design.room_mockups && (typeof design.room_mockups === 'object' && !Array.isArray(design.room_mockups) ? Object.keys(design.room_mockups)[0] : design.room_mockups[0])) || null;
@@ -334,7 +352,7 @@
 
       <div class="title-card">
         <div class="name">${escapeHtml(title)}</div>
-        <div class="meta">${escapeHtml(catDisplay)}<span class="sep">·</span>${escapeHtml(kindLabel)}<span class="sep">·</span>${rollOrPanel}</div>
+        <div class="meta">${escapeHtml(catDisplay)}<span class="sep">·</span>${escapeHtml(kindLabel)}<span class="sep">·</span><span id="meta-soldby">${rollOrPanel}</span></div>
         <div class="chips">
           <span class="chip"><span class="dot" style="background:${escapeAttr(dominant)}"></span>${escapeHtml(dominant)}</span>
           <span class="chip">No.${id}</span>
@@ -342,10 +360,20 @@
         </div>
       </div>
 
+      <!-- FORMAT TOGGLE — Wallpaper vs Wall Mural (offered on EVERY design) -->
+      <div class="fmt-toggle" role="tablist" aria-label="Choose format">
+        <button type="button" class="fmt-tab" data-mode="wallpaper" role="tab" aria-selected="false">
+          <span class="t">Wallpaper</span><span class="s">Seamless tile · per roll (27 ft)</span>
+        </button>
+        <button type="button" class="fmt-tab" data-mode="mural" role="tab" aria-selected="false">
+          <span class="t">Wall Mural</span><span class="s">One image across the entire wall</span>
+        </button>
+      </div>
+
       <!-- Primary CTAs always visible (V1) -->
       <div class="cta-row">
         <button class="cta sample" onclick="addSample()">Add sample · $4.25</button>
-        <button class="cta buy" onclick="document.querySelector('[data-tab=buy]').click(); setTimeout(()=>document.getElementById('calc-input')?.focus(),100)">Buy by the roll →</button>
+        <button class="cta buy" id="cta-buy" onclick="document.querySelector('[data-tab=buy]').click(); setTimeout(()=>document.getElementById('calc-input')?.focus(),100)">Buy by the roll →</button>
       </div>
 
       <!-- Tab bar (V3) -->
@@ -401,8 +429,8 @@
             <div class="specs-row"><span class="k">Kind</span><span class="v">${escapeHtml(kindLabel)}</span></div>
             <div class="specs-row"><span class="k">Width</span><span class="v">${w}"</span></div>
             <div class="specs-row"><span class="k">Repeat</span><span class="v">${repeatLabel}</span></div>
-            <div class="specs-row"><span class="k">Sold by</span><span class="v">${rollOrPanel}</span></div>
-            <div class="specs-row"><span class="k">${isMural ? 'Panel size' : 'Roll length'}</span><span class="v">${isMural ? (w + '" × 11 ft') : '27 ft'}</span></div>
+            <div class="specs-row"><span class="k">Sold by</span><span class="v" id="spec-soldby">${rollOrPanel}</span></div>
+            <div class="specs-row"><span class="k" id="spec-lastk">${muralMode() ? 'Panel size' : 'Roll length'}</span><span class="v" id="spec-lastv">${muralMode() ? (w + '" × 11 ft') : '27 ft'}</span></div>
             <div class="specs-row"><span class="k">Material</span><span class="v">Type II vinyl, water-resist</span></div>
             <div class="specs-row"><span class="k">Designed by</span><span class="v">${escapeHtml(generator)}</span></div>
             <div class="specs-row"><span class="k">Pattern No.</span><span class="v">${id}</span></div>
@@ -485,8 +513,8 @@
       ${roomImgUrl ? '' : '<div class="room-placeholder">Room mockup not yet generated</div>'}
     </div>
     <div class="preview atscale" id="atscale-preview" style="background-image:url('${img}')">
-      <span class="badge">At-scale · 10 × 9 ft wall</span>
-      <span class="scale-pill">scale <input type="range" min="120" max="360" value="220" id="scale-slider"></span>
+      <span class="badge" id="atscale-badge">At-scale · 10 × 9 ft wall</span>
+      <span class="scale-pill" id="scale-pill">scale <input type="range" min="120" max="360" value="220" id="scale-slider"></span>
     </div>
   </div>
 
@@ -542,7 +570,7 @@
     const ww = parseFloat(calcW.value) || 0, wh = parseFloat(calcH.value) || 0;
     const rwInches = parseFloat(calcRW.value) || 24;
     if (ww < 1 || wh < 1) { total.textContent = '— rolls · $—'; return; }
-    if (isMural) {
+    if (muralMode()) {
       const panels = Math.ceil((ww * 12) / rwInches); const cost = panels * 245;
       total.innerHTML = '<b>' + panels + '</b> panels · <b>$' + cost.toLocaleString() + '</b>';
     } else {
@@ -558,7 +586,49 @@
   // ── at-scale slider ──
   const scale = document.getElementById('scale-slider');
   const ascale = document.getElementById('atscale-preview');
-  scale.addEventListener('input', () => { ascale.style.backgroundSize = scale.value + 'px ' + scale.value + 'px'; });
+  scale.addEventListener('input', () => { if (!muralMode()) ascale.style.backgroundSize = scale.value + 'px ' + scale.value + 'px'; });
+
+  // ── format toggle: Wallpaper ⇄ Wall Mural ──
+  // Re-renders every mode-dependent surface: meta + specs sold-by line, the
+  // last spec row (Roll length ⇄ Panel size), the Buy CTA label, the at-scale
+  // preview (tiled ⇄ one continuous image across the wall) and the calculator.
+  function applyMode() {
+    const m = mode, isM = (m === 'mural');
+    root.querySelectorAll('.fmt-tab').forEach(t => {
+      const on = t.dataset.mode === m;
+      t.classList.toggle('active', on);
+      t.setAttribute('aria-selected', on ? 'true' : 'false');
+    });
+    const sb = soldByText(m);
+    const set = (sel, txt) => { const el = root.querySelector(sel); if (el) el.textContent = txt; };
+    set('#meta-soldby', sb);
+    set('#spec-soldby', sb);
+    set('#spec-lastk', isM ? 'Panel size' : 'Roll length');
+    set('#spec-lastv', isM ? (w + '" × 11 ft') : '27 ft');
+    const ctaBuy = root.querySelector('#cta-buy'); if (ctaBuy) ctaBuy.textContent = isM ? 'Order wall mural →' : 'Buy by the roll →';
+    const pill = document.getElementById('scale-pill');
+    const asBadge = document.getElementById('atscale-badge');
+    if (ascale) {
+      if (isM) {
+        ascale.style.backgroundRepeat = 'no-repeat';
+        ascale.style.backgroundSize = 'cover';
+        ascale.style.backgroundPosition = 'center';
+        if (pill) pill.style.display = 'none';
+        if (asBadge) asBadge.textContent = 'Wall mural · one image · 10 × 9 ft wall';
+      } else {
+        ascale.style.backgroundRepeat = 'repeat';
+        ascale.style.backgroundSize = (scale ? scale.value : 220) + 'px ' + (scale ? scale.value : 220) + 'px';
+        if (pill) pill.style.display = '';
+        if (asBadge) asBadge.textContent = 'At-scale · 10 × 9 ft wall';
+      }
+    }
+    recalc();
+  }
+  root.querySelectorAll('.fmt-tab').forEach(t => t.addEventListener('click', () => {
+    mode = (t.dataset.mode === 'mural') ? 'mural' : 'wallpaper';
+    applyMode();
+  }));
+  applyMode();
 
   // ── adjust sliders ──
   root.querySelectorAll('.adjust .sl').forEach(row => {

← 5713b85 add read-only color-name vs dominant-hex drift audit (10171  ·  back to Wallco Ai  ·  generator: add Texture component picker (15 real DW natural- 06a0e7c →