← 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
M public/theme-variants/v11-best.html
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 →