← back to Wallco Ai
feat(scenic+studio): sub-brand rename — product_line column + UI chips + /scenic + /studio routes
b0577b9d8f1ff84180104250babcaee3152f07c7 · 2026-05-21 20:01:58 -0700 · Steve Abrams
Per Steve approval 2026-05-21 of the sub-brand proposal at
docs/SUB-BRAND-PROPOSAL-SCENIC-VS-STUDIO.md. Names locked: Scenic +
Studio. Trade-pricing gate deferred (separate decision).
Schema (both local + Kamatera):
- ALTER TABLE all_designs ADD COLUMN IF NOT EXISTS product_line TEXT
- Index on product_line WHERE NOT NULL
- Backfill:
cactus-11ft-mural → 'Scenic'
tree-mural → 'Scenic'
cactus-pine-scenic → 'Studio'
- Local view spoon_all_designs recreated to expose the new column
Server.js:
- New productLine(d) helper near designHasImage() — reads PG column when
present, falls back to the category→line mapping in-memory. Source of
truth is PG; helper is the in-memory mirror for the JSON-backed cache.
- /designs accepts ?line=Scenic|Studio (case-insensitive) and filters the
catalog accordingly. Combines cleanly with existing cat/motif/hue/q
filters.
- New routes /scenic and /studio — 302 redirect to /designs?line=Scenic
and ?line=Studio respectively. SEO-stable per-line entry points,
bookmarkable, social-share-friendly.
- PDP gets a pill chip immediately under the title: "wallco / Scenic —
Photoreal hospitality mural" or "wallco / Studio — Hand-painted scenic
register". Chip links to the line's landing URL.
- Grid card gets a smaller line chip in the card-tags row alongside the
Curated-Designed + style badges. Color-coded: indigo for Studio,
burnt-umber for Scenic. Same palette as #39055.
Live counts (prod):
Scenic: 121 designs, 90 published
Studio: 44 designs, 24 published
Smoke verified:
/scenic → 302 → /designs?line=Scenic page=200
/studio → 302 → /designs?line=Studio page=200
/design/39055 page=200, "wallco / Studio" chip in HTML
/design/38842 page=200, "wallco / Scenic" chip in HTML
Not in scope (carried for future):
- Per-line landing-page hero copy ("Photoreal hospitality murals —
Phillip Jeffries quality, AI-scale catalog" / "Hand-painted-silk
scenic in the Gracie / de Gournay register at 60% of the price")
- Trade-pricing gate on Studio products
- Sub-tags inside Scenic by hospitality brand fit (Aman/Auberge/Four
Seasons style packs)
- DESIGNS.json refresh to include product_line column on each row (the
helper covers this in-memory; refreshing the JSON is a one-line PG
export script + restart, can do whenever)
Files touched
Diff
commit b0577b9d8f1ff84180104250babcaee3152f07c7
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu May 21 20:01:58 2026 -0700
feat(scenic+studio): sub-brand rename — product_line column + UI chips + /scenic + /studio routes
Per Steve approval 2026-05-21 of the sub-brand proposal at
docs/SUB-BRAND-PROPOSAL-SCENIC-VS-STUDIO.md. Names locked: Scenic +
Studio. Trade-pricing gate deferred (separate decision).
Schema (both local + Kamatera):
- ALTER TABLE all_designs ADD COLUMN IF NOT EXISTS product_line TEXT
- Index on product_line WHERE NOT NULL
- Backfill:
cactus-11ft-mural → 'Scenic'
tree-mural → 'Scenic'
cactus-pine-scenic → 'Studio'
- Local view spoon_all_designs recreated to expose the new column
Server.js:
- New productLine(d) helper near designHasImage() — reads PG column when
present, falls back to the category→line mapping in-memory. Source of
truth is PG; helper is the in-memory mirror for the JSON-backed cache.
- /designs accepts ?line=Scenic|Studio (case-insensitive) and filters the
catalog accordingly. Combines cleanly with existing cat/motif/hue/q
filters.
- New routes /scenic and /studio — 302 redirect to /designs?line=Scenic
and ?line=Studio respectively. SEO-stable per-line entry points,
bookmarkable, social-share-friendly.
- PDP gets a pill chip immediately under the title: "wallco / Scenic —
Photoreal hospitality mural" or "wallco / Studio — Hand-painted scenic
register". Chip links to the line's landing URL.
- Grid card gets a smaller line chip in the card-tags row alongside the
Curated-Designed + style badges. Color-coded: indigo for Studio,
burnt-umber for Scenic. Same palette as #39055.
Live counts (prod):
Scenic: 121 designs, 90 published
Studio: 44 designs, 24 published
Smoke verified:
/scenic → 302 → /designs?line=Scenic page=200
/studio → 302 → /designs?line=Studio page=200
/design/39055 page=200, "wallco / Studio" chip in HTML
/design/38842 page=200, "wallco / Scenic" chip in HTML
Not in scope (carried for future):
- Per-line landing-page hero copy ("Photoreal hospitality murals —
Phillip Jeffries quality, AI-scale catalog" / "Hand-painted-silk
scenic in the Gracie / de Gournay register at 60% of the price")
- Trade-pricing gate on Studio products
- Sub-tags inside Scenic by hospitality brand fit (Aman/Auberge/Four
Seasons style packs)
- DESIGNS.json refresh to include product_line column on each row (the
helper covers this in-memory; refreshing the JSON is a one-line PG
export script + restart, can do whenever)
---
server.js | 49 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 49 insertions(+)
diff --git a/server.js b/server.js
index 04f8cc3..0bc789b 100644
--- a/server.js
+++ b/server.js
@@ -3506,6 +3506,11 @@ app.post('/login', express.urlencoded({ extended: false, limit: '2kb' }), (req,
res.redirect('/login');
});
+// Sub-brand entry points — clean URLs that redirect into /designs?line=…
+// so search engines + bookmarks have a stable path per product line.
+app.get('/scenic', (req, res) => res.redirect('/designs?line=Scenic'));
+app.get('/studio', (req, res) => res.redirect('/designs?line=Studio'));
+
app.get('/designs', (req, res) => {
// 2026-05-20 Steve directive: NO hard server gate. Allow 1 page browsing,
// then a client-side scroll-triggered modal fades in requiring login.
@@ -3634,6 +3639,14 @@ app.get('/designs', (req, res) => {
// to public /designs (live on /library admin-only instead).
const _VENDOR_DENY = /\b(thibaut|koroseal|schumacher|maya[ -]romanoff|scalamandre|arte[ -]international|cole[ -]?(and|&)[ -]?son|phillip[ -]jeffries|osborne[ -]?(and|&)?[ -]?little|de[ -]gournay|fromental|sister[ -]parish|dedar|brewster|mind[ -]the[ -]gap|hygge|designtex|wolf[ -]gordon|carnegie|coordonn|daisy[ -]bennett|china[ -]seas|york|marburg|romo|fabricut|stout|knoll|arteriors|timorous[ -]beasties|hangups)\b/i;
let filtered = DESIGNS.filter(d => designHasImage(d) && !_VENDOR_DENY.test(String(d.category || '')));
+ // Sub-brand filter — `?line=Scenic` or `?line=Studio`. Set automatically
+ // when the customer hits /scenic or /studio (those routes redirect here
+ // with the matching query). Case-insensitive accept.
+ const lineQ = String(req.query.line || '').trim();
+ if (lineQ) {
+ const want = lineQ.toLowerCase();
+ filtered = filtered.filter(d => String(productLine(d) || '').toLowerCase() === want);
+ }
if (cat) filtered = filtered.filter(d => d.category === cat);
if (motifQ) filtered = filtered.filter(d => (d.motifs || []).includes(motifQ));
if (hueQ) filtered = filtered.filter(d => hueBucketOf(d.dominant_hex) === hueQ);
@@ -3767,6 +3780,14 @@ app.get('/designs', (req, res) => {
<span class="card-tags">
<span class="card-ai-badge" title="Curated-designed pattern">✦ Curated-Designed</span>
<span class="card-style">${styleTag}</span>
+ ${(() => {
+ // Sub-brand chip on grid card. Only renders for Scenic + Studio
+ // designs; legacy categories stay unchipped.
+ const _line = productLine(d);
+ if (!_line) return '';
+ const _bg = _line === 'Studio' ? '#1E3550' : '#7A4A28';
+ return `<span class="card-line-chip" title="wallco / ${_line}" style="font-size:9px;letter-spacing:.16em;text-transform:uppercase;padding:2px 8px;background:${_bg};color:#F2EADB;border-radius:999px;font-weight:600">${_line}</span>`;
+ })()}
</span>
</div>
</a>
@@ -8345,6 +8366,19 @@ ${htmlHeader('/designs')}
</script>
<h1 class="detail-title">${design.title}</h1>
+ ${(() => {
+ // Product-line chip — Scenic (photoreal hospitality murals) or
+ // Studio (flat hand-painted-silk Gracie register). Renders only
+ // for designs in those two lines; legacy categories stay unchipped.
+ const _line = productLine(design);
+ if (!_line) return '';
+ const _hue = _line === 'Studio'
+ ? { bg: '#1E3550', fg: '#F2EADB' } // indigo on cream — Studio inky-luxe
+ : { bg: '#7A4A28', fg: '#F2EADB' }; // burnt-umber on cream — Scenic earthy
+ const _tag = _line === 'Studio' ? 'Hand-painted scenic register' : 'Photoreal hospitality mural';
+ return `<a href="/${_line.toLowerCase()}" class="product-line-chip" title="${_tag}" style="display:inline-flex;align-items:center;gap:8px;margin:6px 0 8px;padding:5px 12px 5px 10px;background:${_hue.bg};color:${_hue.fg};border-radius:999px;text-decoration:none;font:11px var(--sans,system-ui);font-weight:600;letter-spacing:.10em;text-transform:uppercase">wallco / ${_line} <span style="opacity:.7;font-weight:400;text-transform:none;letter-spacing:.02em">— ${_tag}</span></a>`;
+ })()}
+
<div class="provenance-badge">
${_isAdmin ? `<span class="ai-badge">Curated-Original</span>` : ''}
Pattern No.${String(design.id).padStart(4,'0')} · ${(design.category || 'mixed').charAt(0).toUpperCase()+(design.category || 'mixed').slice(1)} · ${design.kind === 'seamless_tile' ? 'Seamless Tile' : (design.kind || 'pattern')}
@@ -12245,6 +12279,21 @@ function imageFilesOnDisk() {
}
return _imgExistsCache;
}
+// Product-line classifier — Steve directive 2026-05-21. Maps a design's
+// category to its sub-brand: 'Scenic' (photoreal hospitality murals) or
+// 'Studio' (flat hand-painted-silk Gracie/de Gournay register). Designs
+// outside these two categories return null and render with no line chip.
+// Source of truth is the product_line column in PG; this helper is the
+// in-memory mirror used by the JSON-file-backed DESIGNS array.
+function productLine(d) {
+ if (!d) return null;
+ if (d.product_line) return d.product_line;
+ const c = String(d.category || '');
+ if (c === 'cactus-11ft-mural' || c === 'tree-mural') return 'Scenic';
+ if (c === 'cactus-pine-scenic') return 'Studio';
+ return null;
+}
+
function designHasImage(d) {
if (!d) return false;
if (BLANK_IDS.has(d.id)) return false; // failed-generation blank — never show
← b6510d8 feat(studio): 12 new Gracie-language scenic compositions (St
·
back to Wallco Ai
·
generator: wire product_line column on INSERT + backfill des 28ef4d5 →