[object Object]

← back to Wallco Ai

pdp variants: clamp prev link to id>=1 (10 variants) + scheme/quote-sanitize image_url before <img src> across all 17 — codex review pass 2 hardening

3e32b5f9537660b179ecb353a8c9cfb5042b5311 · 2026-05-31 19:22:02 -0700 · Steve Abrams

Files touched

Diff

commit 3e32b5f9537660b179ecb353a8c9cfb5042b5311
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sun May 31 19:22:02 2026 -0700

    pdp variants: clamp prev link to id>=1 (10 variants) + scheme/quote-sanitize image_url before <img src> across all 17 — codex review pass 2 hardening
---
 public/theme-variants/v1-compact.html     | 6 +++---
 public/theme-variants/v10-stack.html      | 4 ++--
 public/theme-variants/v11-best.html       | 4 ++--
 public/theme-variants/v12-roomsplit.html  | 2 +-
 public/theme-variants/v13-roomdrawer.html | 2 +-
 public/theme-variants/v14-roomzine.html   | 2 +-
 public/theme-variants/v15-roomsticky.html | 2 +-
 public/theme-variants/v16-roommin.html    | 2 +-
 public/theme-variants/v17-roomshow.html   | 2 +-
 public/theme-variants/v2-bento.html       | 4 ++--
 public/theme-variants/v3-tabs.html        | 4 ++--
 public/theme-variants/v4-hero.html        | 6 +++---
 public/theme-variants/v5-vertical.html    | 2 +-
 public/theme-variants/v6-drawer.html      | 4 ++--
 public/theme-variants/v7-editorial.html   | 4 ++--
 public/theme-variants/v8-spec.html        | 4 ++--
 public/theme-variants/v9-config.html      | 4 ++--
 17 files changed, 29 insertions(+), 29 deletions(-)

diff --git a/public/theme-variants/v1-compact.html b/public/theme-variants/v1-compact.html
index 9ca1118..8b2fc9b 100644
--- a/public/theme-variants/v1-compact.html
+++ b/public/theme-variants/v1-compact.html
@@ -374,7 +374,7 @@
   const isMural = design.kind === 'mural_panel' || design.kind === 'mural';
   const palette = Array.isArray(design.palette) ? design.palette : [];
   const dominant = design.dominant_hex || (palette[0] && palette[0].hex) || '#888';
-  const img = design.image_url || ('/designs/img/by-id/' + id);
+  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;
   const h = Number(design.height_in) || 24;
   const repeatLabel = w + '"' + (w !== h ? ' × ' + h + '"' : '');
@@ -397,7 +397,7 @@
   <a href="/designs?cat=${encodeURIComponent(cat)}">${escapeHtml(catDisplay)}</a>
   <span class="grow"></span>
   <span class="nav">
-    <a href="/design/${id - 2}?theme=compact">← prev</a>
+    <a href="/design/${Math.max(1, id - 2)}?theme=compact">← prev</a>
     <a href="/design/${id + 2}?theme=compact">next →</a>
   </span>
 </div>
@@ -422,7 +422,7 @@
     </div>
 
     <div class="prevnext">
-      <a href="/design/${id - 2}?theme=compact">← prev</a>
+      <a href="/design/${Math.max(1, id - 2)}?theme=compact">← prev</a>
       <a href="/design/${id + 2}?theme=compact">next →</a>
       <span class="grow"></span>
       <span>Pattern No.${id}</span>
diff --git a/public/theme-variants/v10-stack.html b/public/theme-variants/v10-stack.html
index b24f3de..db8331f 100644
--- a/public/theme-variants/v10-stack.html
+++ b/public/theme-variants/v10-stack.html
@@ -185,7 +185,7 @@
   const isMural = design.kind === 'mural_panel' || design.kind === 'mural';
   const palette = Array.isArray(design.palette) ? design.palette : [];
   const dominant = design.dominant_hex || (palette[0] && palette[0].hex) || '#888';
-  const img = design.image_url || ('/designs/img/by-id/' + id);
+  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)';
@@ -200,7 +200,7 @@
 <div class="crumb">
   <a href="/designs">Designs</a><span>›</span><a href="/designs?cat=${encodeURIComponent(cat)}">${escapeHtml(catDisplay)}</a>
   <span class="grow"></span>
-  <span><a href="/design/${id - 2}?theme=stack">← prev</a> · <a href="/design/${id + 2}?theme=stack">next →</a></span>
+  <span><a href="/design/${Math.max(1, id - 2)}?theme=stack">← prev</a> · <a href="/design/${id + 2}?theme=stack">next →</a></span>
 </div>
 
 <main class="stack">
diff --git a/public/theme-variants/v11-best.html b/public/theme-variants/v11-best.html
index 336a1d3..8bf1f2f 100644
--- a/public/theme-variants/v11-best.html
+++ b/public/theme-variants/v11-best.html
@@ -301,7 +301,7 @@
   const isMural = design.kind === 'mural_panel' || design.kind === 'mural';
   const palette = Array.isArray(design.palette) ? design.palette : [];
   const dominant = design.dominant_hex || (palette[0] && palette[0].hex) || '#888';
-  const img = design.image_url || ('/designs/img/by-id/' + id);
+  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)';
@@ -316,7 +316,7 @@
 <div class="crumb">
   <a href="/designs">Designs</a><span>›</span><a href="/designs?cat=${encodeURIComponent(cat)}">${escapeHtml(catDisplay)}</a>
   <span class="grow"></span>
-  <span class="nav"><a href="/design/${id - 2}?theme=best">← prev</a> · <a href="/design/${id + 2}?theme=best">next →</a></span>
+  <span class="nav"><a href="/design/${Math.max(1, id - 2)}?theme=best">← prev</a> · <a href="/design/${id + 2}?theme=best">next →</a></span>
 </div>
 
 <main class="best">
diff --git a/public/theme-variants/v12-roomsplit.html b/public/theme-variants/v12-roomsplit.html
index a5f16f9..a90cedb 100644
--- a/public/theme-variants/v12-roomsplit.html
+++ b/public/theme-variants/v12-roomsplit.html
@@ -201,7 +201,7 @@
   const kindLabel = design.kind === 'seamless_tile' ? 'Seamless Tile (24×24")' : isMural ? 'Mural Panel' : (design.kind || 'pattern');
   const palette = Array.isArray(design.palette) ? design.palette : [];
   const dominant = design.dominant_hex || (palette[0] && palette[0].hex) || '#999';
-  const img = design.image_url || ('/designs/img/by-id/' + id);
+  const img = (/^(\/[^"'<>\s]*|https?:\/\/[^"'<>\s]*)$/i.test(String(design.image_url||'')) ? design.image_url : null) || ('/designs/img/by-id/' + id);
   const handle = design.handle || ('wallco-' + id);
   const generator = design.generator || 'wallco.ai';
   const created = design.created_at ? new Date(design.created_at) : null;
diff --git a/public/theme-variants/v13-roomdrawer.html b/public/theme-variants/v13-roomdrawer.html
index a04f180..2548b1c 100644
--- a/public/theme-variants/v13-roomdrawer.html
+++ b/public/theme-variants/v13-roomdrawer.html
@@ -201,7 +201,7 @@
   const kindLabel = design.kind === 'seamless_tile' ? 'Seamless Tile (24×24")' : isMural ? 'Mural Panel' : (design.kind || 'pattern');
   const palette = Array.isArray(design.palette) ? design.palette : [];
   const dominant = design.dominant_hex || (palette[0] && palette[0].hex) || '#999';
-  const img = design.image_url || ('/designs/img/by-id/' + id);
+  const img = (/^(\/[^"'<>\s]*|https?:\/\/[^"'<>\s]*)$/i.test(String(design.image_url||'')) ? design.image_url : null) || ('/designs/img/by-id/' + id);
   const handle = design.handle || ('wallco-' + id);
   const generator = design.generator || 'wallco.ai';
   const created = design.created_at ? new Date(design.created_at) : null;
diff --git a/public/theme-variants/v14-roomzine.html b/public/theme-variants/v14-roomzine.html
index e3d3a60..30bddc1 100644
--- a/public/theme-variants/v14-roomzine.html
+++ b/public/theme-variants/v14-roomzine.html
@@ -201,7 +201,7 @@
   const kindLabel = design.kind === 'seamless_tile' ? 'Seamless Tile (24×24")' : isMural ? 'Mural Panel' : (design.kind || 'pattern');
   const palette = Array.isArray(design.palette) ? design.palette : [];
   const dominant = design.dominant_hex || (palette[0] && palette[0].hex) || '#999';
-  const img = design.image_url || ('/designs/img/by-id/' + id);
+  const img = (/^(\/[^"'<>\s]*|https?:\/\/[^"'<>\s]*)$/i.test(String(design.image_url||'')) ? design.image_url : null) || ('/designs/img/by-id/' + id);
   const handle = design.handle || ('wallco-' + id);
   const generator = design.generator || 'wallco.ai';
   const created = design.created_at ? new Date(design.created_at) : null;
diff --git a/public/theme-variants/v15-roomsticky.html b/public/theme-variants/v15-roomsticky.html
index d1b58c2..a2481e5 100644
--- a/public/theme-variants/v15-roomsticky.html
+++ b/public/theme-variants/v15-roomsticky.html
@@ -201,7 +201,7 @@
   const kindLabel = design.kind === 'seamless_tile' ? 'Seamless Tile (24×24")' : isMural ? 'Mural Panel' : (design.kind || 'pattern');
   const palette = Array.isArray(design.palette) ? design.palette : [];
   const dominant = design.dominant_hex || (palette[0] && palette[0].hex) || '#999';
-  const img = design.image_url || ('/designs/img/by-id/' + id);
+  const img = (/^(\/[^"'<>\s]*|https?:\/\/[^"'<>\s]*)$/i.test(String(design.image_url||'')) ? design.image_url : null) || ('/designs/img/by-id/' + id);
   const handle = design.handle || ('wallco-' + id);
   const generator = design.generator || 'wallco.ai';
   const created = design.created_at ? new Date(design.created_at) : null;
diff --git a/public/theme-variants/v16-roommin.html b/public/theme-variants/v16-roommin.html
index 5369b06..0e25271 100644
--- a/public/theme-variants/v16-roommin.html
+++ b/public/theme-variants/v16-roommin.html
@@ -201,7 +201,7 @@
   const kindLabel = design.kind === 'seamless_tile' ? 'Seamless Tile (24×24")' : isMural ? 'Mural Panel' : (design.kind || 'pattern');
   const palette = Array.isArray(design.palette) ? design.palette : [];
   const dominant = design.dominant_hex || (palette[0] && palette[0].hex) || '#999';
-  const img = design.image_url || ('/designs/img/by-id/' + id);
+  const img = (/^(\/[^"'<>\s]*|https?:\/\/[^"'<>\s]*)$/i.test(String(design.image_url||'')) ? design.image_url : null) || ('/designs/img/by-id/' + id);
   const handle = design.handle || ('wallco-' + id);
   const generator = design.generator || 'wallco.ai';
   const created = design.created_at ? new Date(design.created_at) : null;
diff --git a/public/theme-variants/v17-roomshow.html b/public/theme-variants/v17-roomshow.html
index dc2ebab..694eb3d 100644
--- a/public/theme-variants/v17-roomshow.html
+++ b/public/theme-variants/v17-roomshow.html
@@ -201,7 +201,7 @@
   const kindLabel = design.kind === 'seamless_tile' ? 'Seamless Tile (24×24")' : isMural ? 'Mural Panel' : (design.kind || 'pattern');
   const palette = Array.isArray(design.palette) ? design.palette : [];
   const dominant = design.dominant_hex || (palette[0] && palette[0].hex) || '#999';
-  const img = design.image_url || ('/designs/img/by-id/' + id);
+  const img = (/^(\/[^"'<>\s]*|https?:\/\/[^"'<>\s]*)$/i.test(String(design.image_url||'')) ? design.image_url : null) || ('/designs/img/by-id/' + id);
   const handle = design.handle || ('wallco-' + id);
   const generator = design.generator || 'wallco.ai';
   const created = design.created_at ? new Date(design.created_at) : null;
diff --git a/public/theme-variants/v2-bento.html b/public/theme-variants/v2-bento.html
index 93cfaea..8c305a3 100644
--- a/public/theme-variants/v2-bento.html
+++ b/public/theme-variants/v2-bento.html
@@ -353,7 +353,7 @@
   const isMural = design.kind === 'mural_panel' || design.kind === 'mural';
   const palette = Array.isArray(design.palette) ? design.palette : [];
   const dominant = design.dominant_hex || (palette[0] && palette[0].hex) || '#888';
-  const img = design.image_url || ('/designs/img/by-id/' + id);
+  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;
   const h = Number(design.height_in) || 24;
   const repeatLabel = w + '"' + (w !== h ? ' × ' + h + '"' : '');
@@ -377,7 +377,7 @@
   <a href="/designs?cat=${encodeURIComponent(cat)}">${escapeHtml(catDisplay)}</a>
   <span class="grow"></span>
   <span class="nav">
-    <a href="/design/${id - 2}?theme=bento">← prev</a>
+    <a href="/design/${Math.max(1, id - 2)}?theme=bento">← prev</a>
     <a href="/design/${id + 2}?theme=bento">next →</a>
   </span>
 </div>
diff --git a/public/theme-variants/v3-tabs.html b/public/theme-variants/v3-tabs.html
index bea61cd..b0115a1 100644
--- a/public/theme-variants/v3-tabs.html
+++ b/public/theme-variants/v3-tabs.html
@@ -377,7 +377,7 @@
   const isMural = design.kind === 'mural_panel' || design.kind === 'mural';
   const palette = Array.isArray(design.palette) ? design.palette : [];
   const dominant = design.dominant_hex || (palette[0] && palette[0].hex) || '#888';
-  const img = design.image_url || ('/designs/img/by-id/' + id);
+  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;
   const h = Number(design.height_in) || 24;
   const repeatLabel = w + '"' + (w !== h ? ' × ' + h + '"' : '');
@@ -400,7 +400,7 @@
   <a href="/designs?cat=${encodeURIComponent(cat)}">${escapeHtml(catDisplay)}</a>
   <span class="grow"></span>
   <span class="nav">
-    <a href="/design/${id - 2}?theme=tabs">← prev</a>
+    <a href="/design/${Math.max(1, id - 2)}?theme=tabs">← prev</a>
     <a href="/design/${id + 2}?theme=tabs">next →</a>
   </span>
 </div>
diff --git a/public/theme-variants/v4-hero.html b/public/theme-variants/v4-hero.html
index 3f309e0..15054b0 100644
--- a/public/theme-variants/v4-hero.html
+++ b/public/theme-variants/v4-hero.html
@@ -316,7 +316,7 @@
   const isMural = design.kind === 'mural_panel' || design.kind === 'mural';
   const palette = Array.isArray(design.palette) ? design.palette : [];
   const dominant = design.dominant_hex || (palette[0] && palette[0].hex) || '#888';
-  const img = design.image_url || ('/designs/img/by-id/' + id);
+  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;
   const h = Number(design.height_in) || 24;
   const repeatLabel = w + '"' + (w !== h ? ' × ' + h + '"' : '');
@@ -357,7 +357,7 @@
   <a href="/designs?cat=${encodeURIComponent(cat)}">${escapeHtml(catDisplay)}</a>
   <span class="grow"></span>
   <span class="nav">
-    <a href="/design/${id - 2}?theme=hero">← prev</a>
+    <a href="/design/${Math.max(1, id - 2)}?theme=hero">← prev</a>
     <a href="/design/${id + 2}?theme=hero">next →</a>
   </span>
 </div>
@@ -527,7 +527,7 @@
 </main>
 
 <div class="prevnext-band">
-  <a href="/design/${id - 2}?theme=hero">← previous pattern</a>
+  <a href="/design/${Math.max(1, id - 2)}?theme=hero">← previous pattern</a>
   <span class="grow"></span>
   <a href="/design/${id + 2}?theme=hero">next pattern →</a>
 </div>
diff --git a/public/theme-variants/v5-vertical.html b/public/theme-variants/v5-vertical.html
index f813035..71394aa 100644
--- a/public/theme-variants/v5-vertical.html
+++ b/public/theme-variants/v5-vertical.html
@@ -219,7 +219,7 @@
   const isMural = design.kind === 'mural_panel' || design.kind === 'mural';
   const palette = Array.isArray(design.palette) ? design.palette : [];
   const dominant = design.dominant_hex || (palette[0] && palette[0].hex) || '#888';
-  const img = design.image_url || ('/designs/img/by-id/' + id);
+  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)';
diff --git a/public/theme-variants/v6-drawer.html b/public/theme-variants/v6-drawer.html
index 0e9088b..4f8d8ea 100644
--- a/public/theme-variants/v6-drawer.html
+++ b/public/theme-variants/v6-drawer.html
@@ -220,7 +220,7 @@
   const isMural = design.kind === 'mural_panel' || design.kind === 'mural';
   const palette = Array.isArray(design.palette) ? design.palette : [];
   const dominant = design.dominant_hex || (palette[0] && palette[0].hex) || '#888';
-  const img = design.image_url || ('/designs/img/by-id/' + id);
+  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)';
@@ -235,7 +235,7 @@
 <div class="crumb">
   <a href="/designs">Designs</a><span>›</span><a href="/designs?cat=${encodeURIComponent(cat)}">${escapeHtml(catDisplay)}</a>
   <span class="grow"></span>
-  <span><a href="/design/${id - 2}?theme=drawer">← prev</a> · <a href="/design/${id + 2}?theme=drawer">next →</a></span>
+  <span><a href="/design/${Math.max(1, id - 2)}?theme=drawer">← prev</a> · <a href="/design/${id + 2}?theme=drawer">next →</a></span>
 </div>
 
 <main class="minimal">
diff --git a/public/theme-variants/v7-editorial.html b/public/theme-variants/v7-editorial.html
index a5c2ceb..18995c1 100644
--- a/public/theme-variants/v7-editorial.html
+++ b/public/theme-variants/v7-editorial.html
@@ -211,7 +211,7 @@
   const isMural = design.kind === 'mural_panel' || design.kind === 'mural';
   const palette = Array.isArray(design.palette) ? design.palette : [];
   const dominant = design.dominant_hex || (palette[0] && palette[0].hex) || '#888';
-  const img = design.image_url || ('/designs/img/by-id/' + id);
+  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)';
@@ -244,7 +244,7 @@
 <div class="crumb">
   <a href="/designs">Designs</a><span>›</span><a href="/designs?cat=${encodeURIComponent(cat)}">${escapeHtml(catDisplay)}</a>
   <span class="grow"></span>
-  <span><a href="/design/${id - 2}?theme=editorial">← prev</a> · <a href="/design/${id + 2}?theme=editorial">next →</a></span>
+  <span><a href="/design/${Math.max(1, id - 2)}?theme=editorial">← prev</a> · <a href="/design/${id + 2}?theme=editorial">next →</a></span>
 </div>
 
 <main class="editorial">
diff --git a/public/theme-variants/v8-spec.html b/public/theme-variants/v8-spec.html
index 705ddb9..c10e073 100644
--- a/public/theme-variants/v8-spec.html
+++ b/public/theme-variants/v8-spec.html
@@ -186,7 +186,7 @@
   const isMural = design.kind === 'mural_panel' || design.kind === 'mural';
   const palette = Array.isArray(design.palette) ? design.palette : [];
   const dominant = design.dominant_hex || (palette[0] && palette[0].hex) || '#888';
-  const img = design.image_url || ('/designs/img/by-id/' + id);
+  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)';
@@ -202,7 +202,7 @@
 <div class="crumb">
   <a href="/designs">Designs</a><span>›</span><a href="/designs?cat=${encodeURIComponent(cat)}">${escapeHtml(catDisplay)}</a>
   <span class="grow"></span>
-  <span><a href="/design/${id - 2}?theme=spec">← prev</a> · <a href="/design/${id + 2}?theme=spec">next →</a></span>
+  <span><a href="/design/${Math.max(1, id - 2)}?theme=spec">← prev</a> · <a href="/design/${id + 2}?theme=spec">next →</a></span>
 </div>
 
 <main class="specsheet">
diff --git a/public/theme-variants/v9-config.html b/public/theme-variants/v9-config.html
index 7ae6963..52cac45 100644
--- a/public/theme-variants/v9-config.html
+++ b/public/theme-variants/v9-config.html
@@ -190,7 +190,7 @@
   const isMural = design.kind === 'mural_panel' || design.kind === 'mural';
   const palette = Array.isArray(design.palette) ? design.palette : [];
   const dominant = design.dominant_hex || (palette[0] && palette[0].hex) || '#888';
-  const img = design.image_url || ('/designs/img/by-id/' + id);
+  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)';
@@ -205,7 +205,7 @@
 <div class="crumb">
   <a href="/designs">Designs</a><span>›</span><a href="/designs?cat=${encodeURIComponent(cat)}">${escapeHtml(catDisplay)}</a>
   <span class="grow"></span>
-  <span><a href="/design/${id - 2}?theme=config">← prev</a> · <a href="/design/${id + 2}?theme=config">next →</a></span>
+  <span><a href="/design/${Math.max(1, id - 2)}?theme=config">← prev</a> · <a href="/design/${id + 2}?theme=config">next →</a></span>
 </div>
 
 <main class="config">

← 50ab4a2 Harden pattern-generation engine per Claude-Codex debate: ga  ·  back to Wallco Ai  ·  Generate + publish GRASSCLOTH/LINEN/SILK drunk-animals sets 724ffb9 →