[object Object]

← back to Wallco Ai

pdp: room-size mural preview block (web previews + API field + PDP section)

b4013ff3b617e0983c7b0d275d83eb4dee312954 · 2026-06-03 21:16:15 -0700 · Steve Abrams

Surfaces built 12x11ft scenic-mural masters as a panelized wall product on the
design PDP. mural-web-preview.py downscales each Henry master to a ~234KB web JPG
+ a sidecar JSON (dims/panels) — both small, in-repo, ship to prod (the 500MB TIF
stays Mac2-local). server.js: static route /designs/mural-preview, a 'mural' field
on /api/design/:id, and a conditional 'Room-size mural' block on the bare PDP
(preview image + 12x11ft + 6in install-bleed copy + 24/36/54in panel chips). Renders
only for designs with a master; hidden otherwise. node -c clean; real-browser
validated (block + 1400px preview render on #27151, absent on #54099). 28 previews
generated. NOT deployed — prod push is Steve-gated.

Files touched

Diff

commit b4013ff3b617e0983c7b0d275d83eb4dee312954
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Jun 3 21:16:15 2026 -0700

    pdp: room-size mural preview block (web previews + API field + PDP section)
    
    Surfaces built 12x11ft scenic-mural masters as a panelized wall product on the
    design PDP. mural-web-preview.py downscales each Henry master to a ~234KB web JPG
    + a sidecar JSON (dims/panels) — both small, in-repo, ship to prod (the 500MB TIF
    stays Mac2-local). server.js: static route /designs/mural-preview, a 'mural' field
    on /api/design/:id, and a conditional 'Room-size mural' block on the bare PDP
    (preview image + 12x11ft + 6in install-bleed copy + 24/36/54in panel chips). Renders
    only for designs with a master; hidden otherwise. node -c clean; real-browser
    validated (block + 1400px preview render on #27151, absent on #54099). 28 previews
    generated. NOT deployed — prod push is Steve-gated.
---
 data/mural-previews/10557.jpg  | Bin 0 -> 134918 bytes
 data/mural-previews/10557.json |   1 +
 data/mural-previews/11308.jpg  | Bin 0 -> 153496 bytes
 data/mural-previews/11308.json |   1 +
 data/mural-previews/12599.jpg  | Bin 0 -> 147574 bytes
 data/mural-previews/12599.json |   1 +
 data/mural-previews/12635.jpg  | Bin 0 -> 94163 bytes
 data/mural-previews/12635.json |   1 +
 data/mural-previews/14136.jpg  | Bin 0 -> 249138 bytes
 data/mural-previews/14136.json |   1 +
 data/mural-previews/14450.jpg  | Bin 0 -> 218515 bytes
 data/mural-previews/14450.json |   1 +
 data/mural-previews/14681.jpg  | Bin 0 -> 161301 bytes
 data/mural-previews/14681.json |   1 +
 data/mural-previews/16204.jpg  | Bin 0 -> 149231 bytes
 data/mural-previews/16204.json |   1 +
 data/mural-previews/20213.jpg  | Bin 0 -> 135372 bytes
 data/mural-previews/20213.json |   1 +
 data/mural-previews/20774.jpg  | Bin 0 -> 291787 bytes
 data/mural-previews/20774.json |   1 +
 data/mural-previews/21079.jpg  | Bin 0 -> 339407 bytes
 data/mural-previews/21079.json |   1 +
 data/mural-previews/21976.jpg  | Bin 0 -> 173908 bytes
 data/mural-previews/21976.json |   1 +
 data/mural-previews/25045.jpg  | Bin 0 -> 245088 bytes
 data/mural-previews/25045.json |   1 +
 data/mural-previews/25046.jpg  | Bin 0 -> 252890 bytes
 data/mural-previews/25046.json |   1 +
 data/mural-previews/25051.jpg  | Bin 0 -> 252118 bytes
 data/mural-previews/25051.json |   1 +
 data/mural-previews/25052.jpg  | Bin 0 -> 240079 bytes
 data/mural-previews/25052.json |   1 +
 data/mural-previews/25059.jpg  | Bin 0 -> 247987 bytes
 data/mural-previews/25059.json |   1 +
 data/mural-previews/27151.jpg  | Bin 0 -> 309820 bytes
 data/mural-previews/27151.json |   1 +
 data/mural-previews/31169.jpg  | Bin 0 -> 240018 bytes
 data/mural-previews/31169.json |   1 +
 data/mural-previews/31866.jpg  | Bin 0 -> 294073 bytes
 data/mural-previews/31866.json |   1 +
 data/mural-previews/33427.jpg  | Bin 0 -> 239797 bytes
 data/mural-previews/33427.json |   1 +
 data/mural-previews/37412.jpg  | Bin 0 -> 323624 bytes
 data/mural-previews/37412.json |   1 +
 data/mural-previews/37417.jpg  | Bin 0 -> 176375 bytes
 data/mural-previews/37417.json |   1 +
 data/mural-previews/37613.jpg  | Bin 0 -> 229971 bytes
 data/mural-previews/37613.json |   1 +
 data/mural-previews/37709.jpg  | Bin 0 -> 273883 bytes
 data/mural-previews/37709.json |   1 +
 data/mural-previews/37715.jpg  | Bin 0 -> 288429 bytes
 data/mural-previews/37715.json |   1 +
 data/mural-previews/37769.jpg  | Bin 0 -> 232858 bytes
 data/mural-previews/37769.json |   1 +
 data/mural-previews/37893.jpg  | Bin 0 -> 289023 bytes
 data/mural-previews/37893.json |   1 +
 scripts/mural-web-preview.py   |  79 +++++++++++++++++++++++++++++++++++++++++
 server.js                      |  49 +++++++++++++++++++++++++
 58 files changed, 156 insertions(+)

diff --git a/data/mural-previews/10557.jpg b/data/mural-previews/10557.jpg
new file mode 100644
index 0000000..547c3ab
Binary files /dev/null and b/data/mural-previews/10557.jpg differ
diff --git a/data/mural-previews/10557.json b/data/mural-previews/10557.json
new file mode 100644
index 0000000..63b729c
--- /dev/null
+++ b/data/mural-previews/10557.json
@@ -0,0 +1 @@
+{"print_ft": 4.0, "height_ft": 4.0, "master_px": [7200, 7200], "dpi": 150, "bleed_in": 6.0, "panels": {"24in": 2, "36in": 2, "54in": 1}}
\ No newline at end of file
diff --git a/data/mural-previews/11308.jpg b/data/mural-previews/11308.jpg
new file mode 100644
index 0000000..c5713b5
Binary files /dev/null and b/data/mural-previews/11308.jpg differ
diff --git a/data/mural-previews/11308.json b/data/mural-previews/11308.json
new file mode 100644
index 0000000..db41901
--- /dev/null
+++ b/data/mural-previews/11308.json
@@ -0,0 +1 @@
+{"print_ft": 12.0, "height_ft": 11.0, "master_px": [21600, 19800], "dpi": 150, "bleed_in": 6.0, "panels": {"24in": 6, "36in": 4, "54in": 3}}
\ No newline at end of file
diff --git a/data/mural-previews/12599.jpg b/data/mural-previews/12599.jpg
new file mode 100644
index 0000000..290fab6
Binary files /dev/null and b/data/mural-previews/12599.jpg differ
diff --git a/data/mural-previews/12599.json b/data/mural-previews/12599.json
new file mode 100644
index 0000000..db41901
--- /dev/null
+++ b/data/mural-previews/12599.json
@@ -0,0 +1 @@
+{"print_ft": 12.0, "height_ft": 11.0, "master_px": [21600, 19800], "dpi": 150, "bleed_in": 6.0, "panels": {"24in": 6, "36in": 4, "54in": 3}}
\ No newline at end of file
diff --git a/data/mural-previews/12635.jpg b/data/mural-previews/12635.jpg
new file mode 100644
index 0000000..03a1aa6
Binary files /dev/null and b/data/mural-previews/12635.jpg differ
diff --git a/data/mural-previews/12635.json b/data/mural-previews/12635.json
new file mode 100644
index 0000000..db41901
--- /dev/null
+++ b/data/mural-previews/12635.json
@@ -0,0 +1 @@
+{"print_ft": 12.0, "height_ft": 11.0, "master_px": [21600, 19800], "dpi": 150, "bleed_in": 6.0, "panels": {"24in": 6, "36in": 4, "54in": 3}}
\ No newline at end of file
diff --git a/data/mural-previews/14136.jpg b/data/mural-previews/14136.jpg
new file mode 100644
index 0000000..51a0857
Binary files /dev/null and b/data/mural-previews/14136.jpg differ
diff --git a/data/mural-previews/14136.json b/data/mural-previews/14136.json
new file mode 100644
index 0000000..db41901
--- /dev/null
+++ b/data/mural-previews/14136.json
@@ -0,0 +1 @@
+{"print_ft": 12.0, "height_ft": 11.0, "master_px": [21600, 19800], "dpi": 150, "bleed_in": 6.0, "panels": {"24in": 6, "36in": 4, "54in": 3}}
\ No newline at end of file
diff --git a/data/mural-previews/14450.jpg b/data/mural-previews/14450.jpg
new file mode 100644
index 0000000..1faed28
Binary files /dev/null and b/data/mural-previews/14450.jpg differ
diff --git a/data/mural-previews/14450.json b/data/mural-previews/14450.json
new file mode 100644
index 0000000..db41901
--- /dev/null
+++ b/data/mural-previews/14450.json
@@ -0,0 +1 @@
+{"print_ft": 12.0, "height_ft": 11.0, "master_px": [21600, 19800], "dpi": 150, "bleed_in": 6.0, "panels": {"24in": 6, "36in": 4, "54in": 3}}
\ No newline at end of file
diff --git a/data/mural-previews/14681.jpg b/data/mural-previews/14681.jpg
new file mode 100644
index 0000000..6ad8990
Binary files /dev/null and b/data/mural-previews/14681.jpg differ
diff --git a/data/mural-previews/14681.json b/data/mural-previews/14681.json
new file mode 100644
index 0000000..db41901
--- /dev/null
+++ b/data/mural-previews/14681.json
@@ -0,0 +1 @@
+{"print_ft": 12.0, "height_ft": 11.0, "master_px": [21600, 19800], "dpi": 150, "bleed_in": 6.0, "panels": {"24in": 6, "36in": 4, "54in": 3}}
\ No newline at end of file
diff --git a/data/mural-previews/16204.jpg b/data/mural-previews/16204.jpg
new file mode 100644
index 0000000..061c179
Binary files /dev/null and b/data/mural-previews/16204.jpg differ
diff --git a/data/mural-previews/16204.json b/data/mural-previews/16204.json
new file mode 100644
index 0000000..db41901
--- /dev/null
+++ b/data/mural-previews/16204.json
@@ -0,0 +1 @@
+{"print_ft": 12.0, "height_ft": 11.0, "master_px": [21600, 19800], "dpi": 150, "bleed_in": 6.0, "panels": {"24in": 6, "36in": 4, "54in": 3}}
\ No newline at end of file
diff --git a/data/mural-previews/20213.jpg b/data/mural-previews/20213.jpg
new file mode 100644
index 0000000..6c0029c
Binary files /dev/null and b/data/mural-previews/20213.jpg differ
diff --git a/data/mural-previews/20213.json b/data/mural-previews/20213.json
new file mode 100644
index 0000000..db41901
--- /dev/null
+++ b/data/mural-previews/20213.json
@@ -0,0 +1 @@
+{"print_ft": 12.0, "height_ft": 11.0, "master_px": [21600, 19800], "dpi": 150, "bleed_in": 6.0, "panels": {"24in": 6, "36in": 4, "54in": 3}}
\ No newline at end of file
diff --git a/data/mural-previews/20774.jpg b/data/mural-previews/20774.jpg
new file mode 100644
index 0000000..fb3f271
Binary files /dev/null and b/data/mural-previews/20774.jpg differ
diff --git a/data/mural-previews/20774.json b/data/mural-previews/20774.json
new file mode 100644
index 0000000..db41901
--- /dev/null
+++ b/data/mural-previews/20774.json
@@ -0,0 +1 @@
+{"print_ft": 12.0, "height_ft": 11.0, "master_px": [21600, 19800], "dpi": 150, "bleed_in": 6.0, "panels": {"24in": 6, "36in": 4, "54in": 3}}
\ No newline at end of file
diff --git a/data/mural-previews/21079.jpg b/data/mural-previews/21079.jpg
new file mode 100644
index 0000000..0afaa63
Binary files /dev/null and b/data/mural-previews/21079.jpg differ
diff --git a/data/mural-previews/21079.json b/data/mural-previews/21079.json
new file mode 100644
index 0000000..db41901
--- /dev/null
+++ b/data/mural-previews/21079.json
@@ -0,0 +1 @@
+{"print_ft": 12.0, "height_ft": 11.0, "master_px": [21600, 19800], "dpi": 150, "bleed_in": 6.0, "panels": {"24in": 6, "36in": 4, "54in": 3}}
\ No newline at end of file
diff --git a/data/mural-previews/21976.jpg b/data/mural-previews/21976.jpg
new file mode 100644
index 0000000..f6d50b4
Binary files /dev/null and b/data/mural-previews/21976.jpg differ
diff --git a/data/mural-previews/21976.json b/data/mural-previews/21976.json
new file mode 100644
index 0000000..db41901
--- /dev/null
+++ b/data/mural-previews/21976.json
@@ -0,0 +1 @@
+{"print_ft": 12.0, "height_ft": 11.0, "master_px": [21600, 19800], "dpi": 150, "bleed_in": 6.0, "panels": {"24in": 6, "36in": 4, "54in": 3}}
\ No newline at end of file
diff --git a/data/mural-previews/25045.jpg b/data/mural-previews/25045.jpg
new file mode 100644
index 0000000..799e860
Binary files /dev/null and b/data/mural-previews/25045.jpg differ
diff --git a/data/mural-previews/25045.json b/data/mural-previews/25045.json
new file mode 100644
index 0000000..db41901
--- /dev/null
+++ b/data/mural-previews/25045.json
@@ -0,0 +1 @@
+{"print_ft": 12.0, "height_ft": 11.0, "master_px": [21600, 19800], "dpi": 150, "bleed_in": 6.0, "panels": {"24in": 6, "36in": 4, "54in": 3}}
\ No newline at end of file
diff --git a/data/mural-previews/25046.jpg b/data/mural-previews/25046.jpg
new file mode 100644
index 0000000..c8d2aff
Binary files /dev/null and b/data/mural-previews/25046.jpg differ
diff --git a/data/mural-previews/25046.json b/data/mural-previews/25046.json
new file mode 100644
index 0000000..db41901
--- /dev/null
+++ b/data/mural-previews/25046.json
@@ -0,0 +1 @@
+{"print_ft": 12.0, "height_ft": 11.0, "master_px": [21600, 19800], "dpi": 150, "bleed_in": 6.0, "panels": {"24in": 6, "36in": 4, "54in": 3}}
\ No newline at end of file
diff --git a/data/mural-previews/25051.jpg b/data/mural-previews/25051.jpg
new file mode 100644
index 0000000..60f69b8
Binary files /dev/null and b/data/mural-previews/25051.jpg differ
diff --git a/data/mural-previews/25051.json b/data/mural-previews/25051.json
new file mode 100644
index 0000000..db41901
--- /dev/null
+++ b/data/mural-previews/25051.json
@@ -0,0 +1 @@
+{"print_ft": 12.0, "height_ft": 11.0, "master_px": [21600, 19800], "dpi": 150, "bleed_in": 6.0, "panels": {"24in": 6, "36in": 4, "54in": 3}}
\ No newline at end of file
diff --git a/data/mural-previews/25052.jpg b/data/mural-previews/25052.jpg
new file mode 100644
index 0000000..f08802a
Binary files /dev/null and b/data/mural-previews/25052.jpg differ
diff --git a/data/mural-previews/25052.json b/data/mural-previews/25052.json
new file mode 100644
index 0000000..db41901
--- /dev/null
+++ b/data/mural-previews/25052.json
@@ -0,0 +1 @@
+{"print_ft": 12.0, "height_ft": 11.0, "master_px": [21600, 19800], "dpi": 150, "bleed_in": 6.0, "panels": {"24in": 6, "36in": 4, "54in": 3}}
\ No newline at end of file
diff --git a/data/mural-previews/25059.jpg b/data/mural-previews/25059.jpg
new file mode 100644
index 0000000..ba85e2c
Binary files /dev/null and b/data/mural-previews/25059.jpg differ
diff --git a/data/mural-previews/25059.json b/data/mural-previews/25059.json
new file mode 100644
index 0000000..db41901
--- /dev/null
+++ b/data/mural-previews/25059.json
@@ -0,0 +1 @@
+{"print_ft": 12.0, "height_ft": 11.0, "master_px": [21600, 19800], "dpi": 150, "bleed_in": 6.0, "panels": {"24in": 6, "36in": 4, "54in": 3}}
\ No newline at end of file
diff --git a/data/mural-previews/27151.jpg b/data/mural-previews/27151.jpg
new file mode 100644
index 0000000..0da832f
Binary files /dev/null and b/data/mural-previews/27151.jpg differ
diff --git a/data/mural-previews/27151.json b/data/mural-previews/27151.json
new file mode 100644
index 0000000..db41901
--- /dev/null
+++ b/data/mural-previews/27151.json
@@ -0,0 +1 @@
+{"print_ft": 12.0, "height_ft": 11.0, "master_px": [21600, 19800], "dpi": 150, "bleed_in": 6.0, "panels": {"24in": 6, "36in": 4, "54in": 3}}
\ No newline at end of file
diff --git a/data/mural-previews/31169.jpg b/data/mural-previews/31169.jpg
new file mode 100644
index 0000000..4ef3d87
Binary files /dev/null and b/data/mural-previews/31169.jpg differ
diff --git a/data/mural-previews/31169.json b/data/mural-previews/31169.json
new file mode 100644
index 0000000..db41901
--- /dev/null
+++ b/data/mural-previews/31169.json
@@ -0,0 +1 @@
+{"print_ft": 12.0, "height_ft": 11.0, "master_px": [21600, 19800], "dpi": 150, "bleed_in": 6.0, "panels": {"24in": 6, "36in": 4, "54in": 3}}
\ No newline at end of file
diff --git a/data/mural-previews/31866.jpg b/data/mural-previews/31866.jpg
new file mode 100644
index 0000000..0c294d0
Binary files /dev/null and b/data/mural-previews/31866.jpg differ
diff --git a/data/mural-previews/31866.json b/data/mural-previews/31866.json
new file mode 100644
index 0000000..db41901
--- /dev/null
+++ b/data/mural-previews/31866.json
@@ -0,0 +1 @@
+{"print_ft": 12.0, "height_ft": 11.0, "master_px": [21600, 19800], "dpi": 150, "bleed_in": 6.0, "panels": {"24in": 6, "36in": 4, "54in": 3}}
\ No newline at end of file
diff --git a/data/mural-previews/33427.jpg b/data/mural-previews/33427.jpg
new file mode 100644
index 0000000..030a8e7
Binary files /dev/null and b/data/mural-previews/33427.jpg differ
diff --git a/data/mural-previews/33427.json b/data/mural-previews/33427.json
new file mode 100644
index 0000000..db41901
--- /dev/null
+++ b/data/mural-previews/33427.json
@@ -0,0 +1 @@
+{"print_ft": 12.0, "height_ft": 11.0, "master_px": [21600, 19800], "dpi": 150, "bleed_in": 6.0, "panels": {"24in": 6, "36in": 4, "54in": 3}}
\ No newline at end of file
diff --git a/data/mural-previews/37412.jpg b/data/mural-previews/37412.jpg
new file mode 100644
index 0000000..4e010bf
Binary files /dev/null and b/data/mural-previews/37412.jpg differ
diff --git a/data/mural-previews/37412.json b/data/mural-previews/37412.json
new file mode 100644
index 0000000..db41901
--- /dev/null
+++ b/data/mural-previews/37412.json
@@ -0,0 +1 @@
+{"print_ft": 12.0, "height_ft": 11.0, "master_px": [21600, 19800], "dpi": 150, "bleed_in": 6.0, "panels": {"24in": 6, "36in": 4, "54in": 3}}
\ No newline at end of file
diff --git a/data/mural-previews/37417.jpg b/data/mural-previews/37417.jpg
new file mode 100644
index 0000000..33d74f3
Binary files /dev/null and b/data/mural-previews/37417.jpg differ
diff --git a/data/mural-previews/37417.json b/data/mural-previews/37417.json
new file mode 100644
index 0000000..db41901
--- /dev/null
+++ b/data/mural-previews/37417.json
@@ -0,0 +1 @@
+{"print_ft": 12.0, "height_ft": 11.0, "master_px": [21600, 19800], "dpi": 150, "bleed_in": 6.0, "panels": {"24in": 6, "36in": 4, "54in": 3}}
\ No newline at end of file
diff --git a/data/mural-previews/37613.jpg b/data/mural-previews/37613.jpg
new file mode 100644
index 0000000..c71c291
Binary files /dev/null and b/data/mural-previews/37613.jpg differ
diff --git a/data/mural-previews/37613.json b/data/mural-previews/37613.json
new file mode 100644
index 0000000..db41901
--- /dev/null
+++ b/data/mural-previews/37613.json
@@ -0,0 +1 @@
+{"print_ft": 12.0, "height_ft": 11.0, "master_px": [21600, 19800], "dpi": 150, "bleed_in": 6.0, "panels": {"24in": 6, "36in": 4, "54in": 3}}
\ No newline at end of file
diff --git a/data/mural-previews/37709.jpg b/data/mural-previews/37709.jpg
new file mode 100644
index 0000000..1aa3c21
Binary files /dev/null and b/data/mural-previews/37709.jpg differ
diff --git a/data/mural-previews/37709.json b/data/mural-previews/37709.json
new file mode 100644
index 0000000..db41901
--- /dev/null
+++ b/data/mural-previews/37709.json
@@ -0,0 +1 @@
+{"print_ft": 12.0, "height_ft": 11.0, "master_px": [21600, 19800], "dpi": 150, "bleed_in": 6.0, "panels": {"24in": 6, "36in": 4, "54in": 3}}
\ No newline at end of file
diff --git a/data/mural-previews/37715.jpg b/data/mural-previews/37715.jpg
new file mode 100644
index 0000000..9f9f7a1
Binary files /dev/null and b/data/mural-previews/37715.jpg differ
diff --git a/data/mural-previews/37715.json b/data/mural-previews/37715.json
new file mode 100644
index 0000000..db41901
--- /dev/null
+++ b/data/mural-previews/37715.json
@@ -0,0 +1 @@
+{"print_ft": 12.0, "height_ft": 11.0, "master_px": [21600, 19800], "dpi": 150, "bleed_in": 6.0, "panels": {"24in": 6, "36in": 4, "54in": 3}}
\ No newline at end of file
diff --git a/data/mural-previews/37769.jpg b/data/mural-previews/37769.jpg
new file mode 100644
index 0000000..e251c0d
Binary files /dev/null and b/data/mural-previews/37769.jpg differ
diff --git a/data/mural-previews/37769.json b/data/mural-previews/37769.json
new file mode 100644
index 0000000..db41901
--- /dev/null
+++ b/data/mural-previews/37769.json
@@ -0,0 +1 @@
+{"print_ft": 12.0, "height_ft": 11.0, "master_px": [21600, 19800], "dpi": 150, "bleed_in": 6.0, "panels": {"24in": 6, "36in": 4, "54in": 3}}
\ No newline at end of file
diff --git a/data/mural-previews/37893.jpg b/data/mural-previews/37893.jpg
new file mode 100644
index 0000000..99fc08a
Binary files /dev/null and b/data/mural-previews/37893.jpg differ
diff --git a/data/mural-previews/37893.json b/data/mural-previews/37893.json
new file mode 100644
index 0000000..db41901
--- /dev/null
+++ b/data/mural-previews/37893.json
@@ -0,0 +1 @@
+{"print_ft": 12.0, "height_ft": 11.0, "master_px": [21600, 19800], "dpi": 150, "bleed_in": 6.0, "panels": {"24in": 6, "36in": 4, "54in": 3}}
\ No newline at end of file
diff --git a/scripts/mural-web-preview.py b/scripts/mural-web-preview.py
new file mode 100644
index 0000000..91a7c6a
--- /dev/null
+++ b/scripts/mural-web-preview.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python3
+"""
+mural-web-preview.py — web-sized previews of room-size mural masters for the PDP.
+
+The 12x11ft masters are 500MB TIFs on Henry (not servable to shoppers). This
+makes a small JPG (~1400px wide, ~150KB) of each so a design's product page can
+show "available as a 12x11ft panelized mural" with a real preview. Previews land
+in data/mural-previews/<id>.jpg — small, in-repo, ship to prod with the deploy.
+
+NO spend, NO DB. Pure local PIL.
+
+Usage:
+  python3 scripts/mural-web-preview.py            # all built masters missing a preview
+  python3 scripts/mural-web-preview.py <id>...    # specific ids
+  python3 scripts/mural-web-preview.py --force     # rebuild existing previews
+"""
+import json
+import sys
+from pathlib import Path
+from PIL import Image
+Image.MAX_IMAGE_PIXELS = None
+
+ROOT = Path(__file__).resolve().parents[1]
+_HENRY = Path("/Volumes/Henry/wallco-ai-archive/mural-masters")
+MURAL_DIR = _HENRY if _HENRY.parent.is_dir() else ROOT / "data" / "mural-masters"
+OUT_DIR = ROOT / "data" / "mural-previews"
+OUT_DIR.mkdir(parents=True, exist_ok=True)
+PREVIEW_W = 1400
+
+
+def built_ids():
+    ids = []
+    for d in sorted(MURAL_DIR.iterdir()) if MURAL_DIR.is_dir() else []:
+        if (d / "summary.json").exists():
+            ids.append(d.name)
+    return ids
+
+
+def make_preview(design_id, force=False):
+    d = MURAL_DIR / str(design_id)
+    master = next((p for p in d.glob("*.tif")), None)
+    if not master:
+        print(f"  {design_id}: no master tif", file=sys.stderr); return False
+    out = OUT_DIR / f"{design_id}.jpg"
+    side = OUT_DIR / f"{design_id}.json"
+    # Sidecar JSON (dims + panel counts) ships to prod alongside the jpg, so the
+    # PDP can show the panel breakdown without the Henry-only summary.json.
+    if not side.exists() or force:
+        try:
+            summ = json.loads((d / "summary.json").read_text())
+            panels = {k: (v or {}).get("count", 0) for k, v in (summ.get("panels") or {}).items()}
+            side.write_text(json.dumps({
+                "print_ft": summ.get("print_ft"), "height_ft": summ.get("height_ft"),
+                "master_px": summ.get("master_px"), "dpi": summ.get("dpi"),
+                "bleed_in": summ.get("bleed_in"), "panels": panels,
+            }))
+        except Exception as e:
+            print(f"  {design_id}: sidecar skipped ({e})", file=sys.stderr)
+    if out.exists() and not force:
+        return True
+    im = Image.open(master).convert("RGB")
+    w, h = im.size
+    prev = im.resize((PREVIEW_W, round(PREVIEW_W * h / w)), Image.LANCZOS)
+    prev.save(out, format="JPEG", quality=86, optimize=True)
+    print(f"  {design_id}: {w}x{h} -> {prev.size[0]}x{prev.size[1]} jpg ({out.stat().st_size//1024} KB)")
+    return True
+
+
+def main():
+    args = [a for a in sys.argv[1:] if not a.startswith("--")]
+    force = "--force" in sys.argv
+    ids = args or built_ids()
+    print(f"generating web previews for {len(ids)} masters -> {OUT_DIR}")
+    ok = sum(1 for i in ids if make_preview(i, force))
+    print(f"done: {ok}/{len(ids)} previews")
+
+
+if __name__ == "__main__":
+    main()
diff --git a/server.js b/server.js
index 28150c1..2b69442 100644
--- a/server.js
+++ b/server.js
@@ -207,6 +207,13 @@ app.use('/designs/room', express.static(path.join(__dirname, 'data', 'rooms'), {
   maxAge: '7d',
   setHeaders: (res) => res.setHeader('Cache-Control', 'public, max-age=604800')
 }));
+// Web-sized previews of room-size mural masters (data/mural-previews/<id>.jpg +
+// <id>.json sidecar). Small + in-repo so they ship to prod; the 500MB TIF
+// masters stay Mac2-local. Powers the PDP "room-size mural" block.
+app.use('/designs/mural-preview', express.static(path.join(__dirname, 'data', 'mural-previews'), {
+  maxAge: '1d',
+  setHeaders: (res) => res.setHeader('Cache-Control', 'public, max-age=86400')
+}));
 
 // ── Dual-path room URL resolver (2026-05-30) ──────────────────────────────
 // A room render lives in ONE of two places: the canonical
@@ -526,6 +533,22 @@ app.get('/api/design/:id', (req, res) => {
     const cid = parseInt(c, 10);
     if (Number.isFinite(cid) && cid > 0 && cid !== id) merged.smartfix_child = cid;
   } catch { /* PG hiccup — omit the link */ }
+  // Room-size mural: if a 12x11ft master was built for this design, surface the
+  // web preview + panel breakdown so the PDP can offer it as a panelized mural.
+  try {
+    const pvJpg = path.join(__dirname, 'data', 'mural-previews', id + '.jpg');
+    if (fs.existsSync(pvJpg)) {
+      let info = {};
+      try { info = JSON.parse(fs.readFileSync(path.join(__dirname, 'data', 'mural-previews', id + '.json'), 'utf-8')); } catch {}
+      merged.mural = {
+        available: true,
+        preview_url: '/designs/mural-preview/' + id + '.jpg',
+        print_ft: info.print_ft || 12, height_ft: info.height_ft || 11,
+        bleed_in: info.bleed_in || 6,
+        panels: info.panels || { '24in': 6, '36in': 4, '54in': 3 },
+      };
+    }
+  } catch { /* non-fatal — PDP just won't show the mural block */ }
   res.json({ ok: true, design: merged });
 });
 
@@ -13081,6 +13104,31 @@ app.get('/design/:id', (req, res) => {
   const _resolvedRooms = resolveDesignRooms(id);
   const roomUrlMap = Object.fromEntries(_resolvedRooms.map(r => [r.room_type, r.url]));
   const haveRooms = new Set(_resolvedRooms.map(r => r.room_type));
+
+  // Room-size mural block — shown when a 12x11ft master/preview was built for
+  // this design (data/mural-previews/<id>.jpg + sidecar; both ship to prod, the
+  // 500MB TIF master stays Mac2-local). Surfaces the scenic mural as a panelized
+  // wall product on the PDP.
+  let muralBlock = '';
+  try {
+    if (fs.existsSync(path.join(__dirname, 'data', 'mural-previews', id + '.jpg'))) {
+      let _mi = {};
+      try { _mi = JSON.parse(fs.readFileSync(path.join(__dirname, 'data', 'mural-previews', id + '.json'), 'utf-8')); } catch {}
+      const _pn = _mi.panels || { '24in': 6, '36in': 4, '54in': 3 };
+      const _ft = _mi.print_ft || 12, _ht = _mi.height_ft || 11;
+      const _chips = Object.keys(_pn).map(k =>
+        `<span style="padding:3px 10px;background:#f3efe6;border:1px solid #e3ddd0;border-radius:999px;font-size:12px;color:#3a2f1f"><b style="color:#7a5c2e">${_pn[k]}</b> &times; ${k.replace('in', '&quot;')}</span>`).join('');
+      muralBlock = `
+      <details class="collapse-pane" open>
+        <summary>Room-size mural <span class="cp-sub">${_ft}ft &times; ${_ht}ft &middot; panelized</span></summary>
+        <div class="collapse-body">
+          <img src="/designs/mural-preview/${id}.jpg" alt="room-size scenic mural at ${_ft}ft x ${_ht}ft" loading="lazy" style="width:100%;border-radius:8px;display:block;margin:6px 0 10px">
+          <p style="font-size:13px;line-height:1.6;color:#5a4a33;margin:0 0 10px">Available as a full-wall scenic mural &mdash; one continuous ${_ft}ft &times; ${_ht}ft scene at 150&nbsp;DPI with a 6&quot; install bleed top &amp; bottom, split into vertical panels (pick your width):</p>
+          <div style="display:flex;gap:8px;flex-wrap:wrap">${_chips}</div>
+        </div>
+      </details>` ;
+    }
+  } catch (e) { /* non-fatal — PDP just omits the mural block */ }
   const roomCtx = ROOM_CONTEXTS
     .map(c => { const room = c.candidates.find(r => haveRooms.has(r)); return room ? { id: c.id, label: c.label, room } : null; })
     .filter(Boolean);
@@ -17333,6 +17381,7 @@ ${(() => {
       </script>
       ` : ''}
 
+      ${muralBlock}
       ${roomCtx.length ? `
       <!-- Room mockup with context toggle (residential / commercial / hospitality) -->
       <details class="collapse-pane">

← 2fed713 yolo loop: stop (done) — queue reconciled, clean slate  ·  back to Wallco Ai  ·  fix: digital-for-sale also unpublishes spoon_all_designs (pr 5dc7d6a →