← back to Marketing Command Center
Add separate Showroom picker area to Make-a-post so showroom-only lines (PJ) stay out of the general pool with correct language
da0f1f854497015b422304dc491b1f64b8a8ed76 · 2026-09-03 11:19:28 -0700 · Steve
Backend (modules/assets/index.js): read the canonical showroom-vendors.json at
runtime (60s memo, default [Phillip Jeffries]); carry vendor + a showroom flag
through both catalog maps; add GET /api/assets/showroom that surfaces showroom
lines via on-site search (they are absent from browse by design); include the
canonical vendor list in catalog responses. Keyed off the list, never a
hardcoded name.
Frontend (quickpost.html/js): new 'Showroom' filter chip; showroom assets are
EXCLUDED from All ideas / DW picks and shown ONLY under Showroom; each showroom
card carries a Showroom pill; a persistent correct-language reminder banner
(invite to the DW showroom / book a visit -- never shop now / buy online) shows
when the Showroom area is active or a showroom picture is selected, and again in
the review step. Grid/List/Details + density + click-to-select preserved.
Verified headless: 18/18 checks (no PJ under All ideas/DW picks; Showroom chip
shows badged PJ cards + banner; 3 views + density; selection; zero console errors).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GDFjK9hKdrY7ExEupvWGr
Files touched
M modules/assets/index.jsM public/panels/quickpost.htmlM public/panels/quickpost.js
Diff
commit da0f1f854497015b422304dc491b1f64b8a8ed76
Author: Steve <steve@designerwallcoverings.com>
Date: Thu Sep 3 11:19:28 2026 -0700
Add separate Showroom picker area to Make-a-post so showroom-only lines (PJ) stay out of the general pool with correct language
Backend (modules/assets/index.js): read the canonical showroom-vendors.json at
runtime (60s memo, default [Phillip Jeffries]); carry vendor + a showroom flag
through both catalog maps; add GET /api/assets/showroom that surfaces showroom
lines via on-site search (they are absent from browse by design); include the
canonical vendor list in catalog responses. Keyed off the list, never a
hardcoded name.
Frontend (quickpost.html/js): new 'Showroom' filter chip; showroom assets are
EXCLUDED from All ideas / DW picks and shown ONLY under Showroom; each showroom
card carries a Showroom pill; a persistent correct-language reminder banner
(invite to the DW showroom / book a visit -- never shop now / buy online) shows
when the Showroom area is active or a showroom picture is selected, and again in
the review step. Grid/List/Details + density + click-to-select preserved.
Verified headless: 18/18 checks (no PJ under All ideas/DW picks; Showroom chip
shows badged PJ cards + banner; 3 views + density; selection; zero console errors).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018GDFjK9hKdrY7ExEupvWGr
---
modules/assets/index.js | 66 ++++++++++++++++++++++++++++++++++---
public/panels/quickpost.html | 16 +++++++--
public/panels/quickpost.js | 78 ++++++++++++++++++++++++++++++++++++--------
3 files changed, 139 insertions(+), 21 deletions(-)
diff --git a/modules/assets/index.js b/modules/assets/index.js
index 667a717..329c8eb 100644
--- a/modules/assets/index.js
+++ b/modules/assets/index.js
@@ -18,6 +18,7 @@
// Both are gitignored runtime state — the library lives wherever the server runs
// and survives code deploys (deploy ships code, not data).
const fs = require('fs');
+const os = require('os');
const path = require('path');
const express = require('express');
const { execFileSync } = require('child_process');
@@ -40,6 +41,32 @@ const CAT_TTL_MS = 6 * 60 * 60 * 1000; // 6h
const DW_PRODUCTS_JSON = 'https://designerwallcoverings.com/products.json';
const DW_SUGGEST = 'https://designerwallcoverings.com/search/suggest.json';
+// ── showroom-only lines (Steve's "addressable but not discoverable" rule) ─────
+// Canonical list of showroom-only vendors — the SINGLE source of truth. These
+// lines (e.g. Phillip Jeffries) stay reachable by direct URL + on-site search
+// but are HIDDEN from every browse/push surface. Here we mark each catalog asset
+// with a `showroom` flag so the "Make a post" picker can keep them OUT of the
+// general image pool and only surface them in a dedicated, correctly-framed
+// Showroom area. Keyed off the LIST, never a hardcoded vendor name. Re-read at
+// runtime (60s memo) so adding a vendor to the JSON flows without a code change.
+const SHOWROOM_VENDORS_FILE = process.env.SHOWROOM_VENDORS_FILE
+ || path.join(os.homedir(), 'Projects', 'fix-live-board', 'config', 'showroom-vendors.json');
+let _svCache = { at: 0, list: null };
+function showroomVendors() {
+ if (_svCache.list && (Date.now() - _svCache.at) < 60 * 1000) return _svCache.list;
+ let list = ['Phillip Jeffries']; // safe default if the file is unreadable
+ try {
+ const a = JSON.parse(fs.readFileSync(SHOWROOM_VENDORS_FILE, 'utf8'));
+ if (Array.isArray(a) && a.length) list = a.map(v => String(v).trim()).filter(Boolean);
+ } catch { /* keep default */ }
+ _svCache = { at: Date.now(), list };
+ return list;
+}
+function isShowroom(vendor) {
+ const v = String(vendor || '').trim().toLowerCase();
+ return !!v && showroomVendors().some(s => s.toLowerCase() === v);
+}
+
const MIME_EXT = {
'image/jpeg': 'jpg', 'image/jpg': 'jpg', 'image/png': 'png', 'image/gif': 'gif',
'image/webp': 'webp', 'image/svg+xml': 'svg', 'image/avif': 'avif',
@@ -245,7 +272,8 @@ async function suggestSearch(q, limit) {
const ps = (d.resources && d.resources.results && d.resources.results.products) || [];
return ps.map(p => {
const img = (p.featured_image && (p.featured_image.url || p.featured_image)) || p.image || '';
- return { title: p.title || p.handle || 'Untitled', handle: p.handle || '', type: p.type || '', url: normImg(img) };
+ const vendor = p.vendor || '';
+ return { title: p.title || p.handle || 'Untitled', handle: p.handle || '', type: p.type || '', vendor, showroom: isShowroom(vendor), url: normImg(img) };
}).filter(p => p.url);
}
@@ -274,10 +302,13 @@ async function fetchCatalog(opts) {
for (const p of prods) {
const img = (p.images || [])[0];
if (!img || !img.src) continue;
+ const vendor = p.vendor || '';
out.push({
title: p.title || p.handle || 'Untitled',
handle: p.handle || '',
type: p.product_type || '',
+ vendor,
+ showroom: isShowroom(vendor),
url: normImg(img.src),
});
}
@@ -444,18 +475,19 @@ module.exports = {
const q = (req.query.q || '').toString().trim();
const limit = Math.max(1, Math.min(200, parseInt(req.query.limit, 10) || 60));
try {
+ const sv = showroomVendors();
if (q) {
try {
const hits = await suggestSearch(q, limit);
- if (hits.length) return res.json({ products: hits.slice(0, limit), total: hits.length, cached: false, source: 'search', fetchedAt: readCatCache()?.fetchedAt || null });
+ if (hits.length) return res.json({ products: hits.slice(0, limit), total: hits.length, cached: false, source: 'search', fetchedAt: readCatCache()?.fetchedAt || null, showroomVendors: sv });
} catch { /* rate-limited / down → fall back to cached browse filter below */ }
const { products, fetchedAt } = await fetchCatalog();
const ql = q.toLowerCase();
- const rows = products.filter(p => (p.title + ' ' + p.type + ' ' + p.handle).toLowerCase().includes(ql));
- return res.json({ products: rows.slice(0, limit), total: rows.length, cached: true, source: 'browse-filter', fetchedAt: fetchedAt || null });
+ const rows = products.filter(p => (p.title + ' ' + p.type + ' ' + p.handle + ' ' + (p.vendor || '')).toLowerCase().includes(ql));
+ return res.json({ products: rows.slice(0, limit), total: rows.length, cached: true, source: 'browse-filter', fetchedAt: fetchedAt || null, showroomVendors: sv });
}
const { products, cached, fetchedAt } = await fetchCatalog();
- res.json({ products: products.slice(0, limit), total: products.length, cached, source: 'browse', fetchedAt: fetchedAt || null });
+ res.json({ products: products.slice(0, limit), total: products.length, cached, source: 'browse', fetchedAt: fetchedAt || null, showroomVendors: sv });
} catch (e) {
res.status(502).json({ error: 'catalog fetch failed: ' + e.message });
}
@@ -472,6 +504,30 @@ module.exports = {
}
});
+ // Showroom-only lines for the "Make a post" picker's dedicated Showroom
+ // area. Showroom vendors (e.g. Phillip Jeffries) are ABSENT from the browse
+ // pages by design ("addressable but not discoverable"), so we surface them
+ // here via on-site search — exactly the reachable-by-search channel the rule
+ // preserves. Every returned product is guaranteed showroom:true (near-match
+ // noise is dropped), and the canonical vendor list rides along so the client
+ // can frame the correct-language reminder without hardcoding a vendor name.
+ router.get('/showroom', async (_req, res) => {
+ const vendors = showroomVendors();
+ const out = [];
+ const seen = new Set();
+ for (const v of vendors) {
+ let hits = [];
+ try { hits = await suggestSearch(v, 10); } catch { /* one vendor down → keep going */ }
+ for (const h of hits) {
+ if (!h.showroom) continue; // drop any non-showroom near-match
+ if (!h.url || seen.has(h.url)) continue;
+ seen.add(h.url);
+ out.push(h);
+ }
+ }
+ res.json({ products: out, total: out.length, source: 'showroom-search', showroomVendors: vendors });
+ });
+
// Persist a catalog image into the library. Body: { name, url }.
router.post('/save-catalog', (req, res) => {
const { name, url } = req.body || {};
diff --git a/public/panels/quickpost.html b/public/panels/quickpost.html
index f1bb9c0..87977a3 100644
--- a/public/panels/quickpost.html
+++ b/public/panels/quickpost.html
@@ -9,11 +9,12 @@
<main class="kp-card">
<section class="kp-pane on" data-step="1">
<div class="kp-number">1</div><div class="kp-copy"><h3>Pick a picture or video</h3><p>Tap one you like. Every DW post needs a picture or video.</p></div>
- <div class="kp-asset-tools"><div class="kp-filter" role="group" aria-label="Picture choices"><button type="button" class="on" data-filter="all">✨ All ideas</button><button type="button" data-filter="catalog">🏷️ DW picks</button><button type="button" data-filter="upload">📤 My uploads</button></div><input id="kp-asset-search" type="search" placeholder="🔎 Find a color or room" aria-label="Find a picture"></div>
+ <div class="kp-asset-tools"><div class="kp-filter" role="group" aria-label="Picture choices"><button type="button" class="on" data-filter="all">✨ All ideas</button><button type="button" data-filter="catalog">🏷️ DW picks</button><button type="button" data-filter="upload">📤 My uploads</button><button type="button" class="kp-filter-showroom" data-filter="showroom">🏛️ Showroom</button></div><input id="kp-asset-search" type="search" placeholder="🔎 Find a color or room" aria-label="Find a picture"></div>
<div class="kp-view-tools" role="group" aria-label="How the pictures are shown">
<div class="kp-views" role="group" aria-label="View"><button type="button" class="on" data-view="grid" aria-pressed="true">▦ Grid</button><button type="button" data-view="list" aria-pressed="false">☰ List</button><button type="button" data-view="kv" aria-pressed="false">⊞ Details</button></div>
<label class="kp-density"><span>Bigger</span><input id="kp-density" type="range" min="2" max="8" step="1" value="4" aria-label="Space between pictures — left is bigger, right fits more"><span>Smaller</span></label>
</div>
+ <div id="kp-showroom-note" class="kp-showroom-note" role="note" hidden></div>
<div id="kp-assets" class="kp-assets view-grid" aria-live="polite"><div class="kp-wait">Loading your pictures…</div></div>
<details class="kp-more"><summary>Paste a picture link instead</summary><input id="kp-media" type="url" placeholder="https://…/picture.jpg" autocomplete="off"></details>
</section>
@@ -64,4 +65,15 @@
.view-kv .kp-asset .kp-thumb{width:70px;height:70px}
.view-list .kp-asset-name,.view-kv .kp-asset-name{display:none}
.view-list .kp-asset.on,.view-kv .kp-asset.on{border-color:var(--green);background:#ecf8f1}
-.view-list .kp-asset.on:after,.view-kv .kp-asset.on:after{top:50%;right:12px;transform:translateY(-50%)}</style>
+.view-list .kp-asset.on:after,.view-kv .kp-asset.on:after{top:50%;right:12px;transform:translateY(-50%)}
+/* Showroom-only lines — separate area, always the correct (invite-to-showroom) language */
+.kp-filter-showroom{border-color:#c9bdf2 !important;color:#5a45b8}
+.kp-filter-showroom.on{background:#6b4bd6 !important;border-color:#6b4bd6 !important;color:#fff !important}
+.kp-badge-showroom{position:absolute;left:7px;top:7px;z-index:2;background:#6b4bd6;color:#fff;font:900 9px/1 inherit;letter-spacing:.07em;text-transform:uppercase;padding:4px 7px;border-radius:999px;pointer-events:none;box-shadow:0 2px 6px rgba(23,34,53,.25)}
+.kp-asset.is-showroom{border-color:#c9bdf2}
+.view-list .kp-badge-showroom,.view-kv .kp-badge-showroom{position:static;display:inline-block;margin-top:6px;box-shadow:none}
+.kp-showroom-note{margin-top:14px;border:2px solid #d7ccf5;background:#f4f0fe;border-radius:16px;padding:13px 15px;color:#3c2f66;line-height:1.5}
+.kp-showroom-note[hidden]{display:none}
+.kp-showroom-note b{display:block;font-size:14px;color:#5a45b8;margin-bottom:3px}
+.kp-showroom-note em{font-style:normal;background:#e6dcff;border-radius:6px;padding:1px 5px;font-weight:700}
+.kp-showroom-note .no{color:#a33c2f;font-weight:800;text-decoration:line-through}</style>
diff --git a/public/panels/quickpost.js b/public/panels/quickpost.js
index fedede0..c6b8bbf 100644
--- a/public/panels/quickpost.js
+++ b/public/panels/quickpost.js
@@ -8,7 +8,7 @@ window.MCC_PANELS.quickpost = {
const $$ = s => [...root.querySelectorAll(s)];
const esc = s => String(s == null ? '' : s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
const icons = {facebook:'📘',instagram:'📸',bluesky:'🦋',linkedin:'💼',tiktok:'🎵',youtube:'▶️',threads:'🧵'};
- const state = { step:1, mediaUrl:'', song:null, channels:[], targets:[], status:{}, view:'grid', cols:4 };
+ const state = { step:1, mediaUrl:'', song:null, channels:[], targets:[], status:{}, view:'grid', cols:4, showroomVendors:[], showroomSelected:false };
let allAssets = [];
const PICKER_KEY = 'mcc.quickpost.picker';
try { const s = JSON.parse(localStorage.getItem(PICKER_KEY) || '{}'); if (['grid','list','kv'].includes(s.view)) state.view = s.view; if (+s.cols >= 2 && +s.cols <= 8) state.cols = +s.cols; } catch (_) {}
@@ -47,42 +47,91 @@ window.MCC_PANELS.quickpost = {
async function loadAssets() {
try {
- const [saved, catalog] = await Promise.all([
+ // Browse pool (general pictures) + saved library + the SEPARATE showroom
+ // area. Showroom-only lines (e.g. Phillip Jeffries) are absent from the
+ // browse pages by design, so we pull them via the dedicated endpoint —
+ // the only place they surface, and always with the correct language.
+ const [saved, catalog, showroom] = await Promise.all([
fetch(O + '/api/assets/list', {credentials:'same-origin'}).then(r => r.json()),
- fetch(O + '/api/assets/catalog?limit=200', {credentials:'same-origin'}).then(r => r.ok ? r.json() : {products:[]}).catch(() => ({products:[]}))
+ fetch(O + '/api/assets/catalog?limit=200', {credentials:'same-origin'}).then(r => r.ok ? r.json() : {products:[]}).catch(() => ({products:[]})),
+ fetch(O + '/api/assets/showroom', {credentials:'same-origin'}).then(r => r.ok ? r.json() : {products:[]}).catch(() => ({products:[]}))
]);
- const savedAssets = (saved.assets || []).filter(a => a.src);
+ // Canonical showroom vendor list rides along with either catalog call —
+ // key off the LIST, never a hardcoded vendor name.
+ state.showroomVendors = catalog.showroomVendors || showroom.showroomVendors || ['Phillip Jeffries'];
+ const svLower = state.showroomVendors.map(s => String(s).toLowerCase());
+ // Saved assets carry no vendor field → infer showroom from the name only.
+ const savedAssets = (saved.assets || []).filter(a => a.src).map(a => {
+ const hay = `${a.name || ''} ${(a.tags || []).join(' ')}`.toLowerCase();
+ return Object.assign({}, a, { showroom: !!a.showroom || svLower.some(s => s && hay.includes(s)) });
+ });
const catalogAssets = (catalog.products || []).filter(p => p.url).map(p => ({
id: `catalog-${p.handle || p.url}`, name: p.title || 'DW catalog image', kind: 'catalog', src: p.url,
- tags: ['dw-catalog', p.type || 'wallcovering'], aiTags: {}
+ tags: ['dw-catalog', p.type || 'wallcovering'], aiTags: {}, vendor: p.vendor || '', showroom: !!p.showroom
+ }));
+ const showroomAssets = (showroom.products || []).filter(p => p.url).map(p => ({
+ id: `showroom-${p.handle || p.url}`, name: p.title || 'Showroom line', kind: 'catalog', src: p.url,
+ tags: ['dw-catalog', p.vendor || '', p.type || ''].filter(Boolean), aiTags: {}, vendor: p.vendor || '', showroom: true
}));
const seen = new Set();
- allAssets = [...savedAssets, ...catalogAssets].filter(a => { const key = a.src; if (seen.has(key)) return false; seen.add(key); return true; });
+ allAssets = [...savedAssets, ...showroomAssets, ...catalogAssets].filter(a => { const key = a.src; if (seen.has(key)) return false; seen.add(key); return true; });
renderAssets();
} catch (_) { $('#kp-assets').innerHTML = '<div class="kp-error">Pictures could not load. Paste a picture link below.</div>'; }
}
function cardInner(a, i) {
const name = esc(a.name || 'DW picture');
const img = `<img loading="lazy" src="${esc(a.src)}" alt="${esc(a.name || 'Wallpaper picture')}">`;
- if (state.view === 'grid') return `${img}<span class="kp-asset-name">${name}</span>`;
- const kind = { catalog:'DW pick', upload:'My upload' }[String(a.kind || '').toLowerCase()] || esc(a.kind || 'Picture');
+ const badge = a.showroom ? '<span class="kp-badge-showroom">🏛️ Showroom</span>' : '';
+ if (state.view === 'grid') return `${badge}${img}<span class="kp-asset-name">${name}</span>`;
+ const kind = a.showroom ? 'Showroom line' : ({ catalog:'DW pick', upload:'My upload' }[String(a.kind || '').toLowerCase()] || esc(a.kind || 'Picture'));
const chips = state.view === 'kv'
? `<span class="kp-kv"><span class="kp-kv-chip"><i>Kind</i>${kind}</span>${(a.tags && a.tags.length) ? `<span class="kp-kv-chip"><i>Tags</i>${esc(a.tags.slice(0,3).join(' · '))}</span>` : ''}</span>`
: '';
- return `<span class="kp-thumb">${img}</span><span class="kp-row-main"><b class="kp-row-name">${name}</b>${chips}</span>`;
+ return `<span class="kp-thumb">${img}</span><span class="kp-row-main"><b class="kp-row-name">${name}</b>${chips}${a.showroom ? badge : ''}</span>`;
}
function renderAssets() {
const filter = root.querySelector('.kp-filter button.on')?.dataset.filter || 'all';
const q = ($('#kp-asset-search')?.value || '').trim().toLowerCase();
- const assets = allAssets.filter(a => (filter === 'all' || String(a.kind || '').toLowerCase() === filter) && (!q || `${a.name || ''} ${(a.tags || []).join(' ')} ${JSON.stringify(a.aiTags || {})}`.toLowerCase().includes(q))).slice(0, 120);
+ const matchesQ = a => !q || `${a.name || ''} ${(a.tags || []).join(' ')} ${a.vendor || ''} ${JSON.stringify(a.aiTags || {})}`.toLowerCase().includes(q);
+ const assets = allAssets.filter(a => {
+ if (!matchesQ(a)) return false;
+ // Showroom-only lines appear ONLY in the dedicated Showroom area — never
+ // in "All ideas" or "DW picks" (addressable-but-not-discoverable).
+ if (filter === 'showroom') return !!a.showroom;
+ if (a.showroom) return false;
+ if (filter === 'all') return true;
+ return String(a.kind || '').toLowerCase() === filter;
+ }).slice(0, 120);
const box = $('#kp-assets');
box.classList.remove('view-grid','view-list','view-kv'); box.classList.add('view-' + state.view);
box.style.setProperty('--cols', state.cols);
- box.innerHTML = assets.length ? assets.map((a,i) => `<button class="kp-asset" type="button" data-url="${esc(new URL(a.src, O).href)}" aria-label="Choose ${esc(a.name || 'picture '+(i+1))}" aria-pressed="false">${cardInner(a,i)}</button>`).join('') : '<div class="kp-wait">No matching pictures yet. Try another word or paste a link below.</div>';
+ const emptyMsg = filter === 'showroom'
+ ? 'No showroom pictures yet. Showroom lines stay reachable by direct link and on-site search.'
+ : 'No matching pictures yet. Try another word or paste a link below.';
+ box.innerHTML = assets.length ? assets.map((a,i) => `<button class="kp-asset${a.showroom ? ' is-showroom' : ''}" type="button" data-url="${esc(new URL(a.src, O).href)}" data-showroom="${a.showroom ? '1' : '0'}" aria-label="Choose ${esc(a.name || 'picture '+(i+1))}${a.showroom ? ' (showroom line)' : ''}" aria-pressed="false">${cardInner(a,i)}</button>`).join('') : `<div class="kp-wait">${emptyMsg}</div>`;
$$('.kp-asset').forEach(b => {
if (b.dataset.url === state.mediaUrl) { b.classList.add('on'); b.setAttribute('aria-pressed','true'); }
- b.onclick = () => { $$('.kp-asset').forEach(x => { x.classList.remove('on'); x.setAttribute('aria-pressed','false'); }); b.classList.add('on'); b.setAttribute('aria-pressed','true'); state.mediaUrl = b.dataset.url; $('#kp-media').value = state.mediaUrl; };
+ b.onclick = () => { $$('.kp-asset').forEach(x => { x.classList.remove('on'); x.setAttribute('aria-pressed','false'); }); b.classList.add('on'); b.setAttribute('aria-pressed','true'); state.mediaUrl = b.dataset.url; state.showroomSelected = b.dataset.showroom === '1'; $('#kp-media').value = state.mediaUrl; updateShowroomNote(); };
});
+ updateShowroomNote();
+ }
+
+ // Persistent correct-language reminder. Shows whenever the Showroom area is
+ // active OR a showroom picture is selected. Framing per the doctrine:
+ // showroom lines may be featured on social/email ONLY to drive people to the
+ // physical showroom — invite the visit, never "shop now / buy online".
+ function updateShowroomNote() {
+ const el = $('#kp-showroom-note'); if (!el) return;
+ const filter = root.querySelector('.kp-filter button.on')?.dataset.filter || 'all';
+ const active = filter === 'showroom' || state.showroomSelected;
+ const vendors = state.showroomVendors && state.showroomVendors.length ? state.showroomVendors : ['Phillip Jeffries'];
+ const list = vendors.length === 1 ? esc(vendors[0])
+ : esc(vendors.slice(0, -1).join(', ')) + ' and ' + esc(vendors[vendors.length - 1]);
+ el.hidden = !active;
+ if (active) el.innerHTML = `<b>🏛️ Showroom-only line — invite people IN, don’t sell online</b>`
+ + `${list} is shown by appointment in our showroom, not on the online store. Feature it to bring people to us — `
+ + `<em>“See it in person at the Designer Wallcoverings showroom”</em> or <em>“Book a showroom visit to feel the samples.”</em> `
+ + `Please don’t write <span class="no">Shop now</span> or <span class="no">Buy online</span> — this line isn’t sold on the site.`;
}
function syncViewUI() {
$$('.kp-views button').forEach(b => { const on = b.dataset.view === state.view; b.classList.toggle('on', on); b.setAttribute('aria-pressed', String(on)); });
@@ -94,7 +143,7 @@ window.MCC_PANELS.quickpost = {
syncViewUI();
$$('.kp-filter button').forEach(b => b.onclick = () => { $$('.kp-filter button').forEach(x => x.classList.remove('on')); b.classList.add('on'); renderAssets(); });
$('#kp-asset-search').addEventListener('input', renderAssets);
- $('#kp-media').addEventListener('input', e => { state.mediaUrl = e.target.value.trim(); $$('.kp-asset').forEach(x => x.classList.toggle('on', x.dataset.url === state.mediaUrl)); });
+ $('#kp-media').addEventListener('input', e => { state.mediaUrl = e.target.value.trim(); let sel = null; $$('.kp-asset').forEach(x => { const on = x.dataset.url === state.mediaUrl; x.classList.toggle('on', on); if (on) sel = x; }); state.showroomSelected = !!(sel && sel.dataset.showroom === '1'); updateShowroomNote(); });
async function loadMusic() {
try {
@@ -137,7 +186,8 @@ window.MCC_PANELS.quickpost = {
const media=state.mediaUrl ? (isVideo?`<video src="${esc(state.mediaUrl)}" controls playsinline></video>`:`<img src="${esc(state.mediaUrl)}" alt="Chosen post picture">`) : '<div class="kp-wait">No picture</div>';
const tags=state.channels.map(c=>`<span class="kp-tag">${icons[c]||'●'} ${esc(c)}</span>`).join('');
const song = state.song ? `${esc(state.song.name)} · ${esc(state.song.duration)}<div class="kp-disclosure">Music suggestion only. It is saved with the post recipe; some social apps require adding the track inside their own app.</div>` : 'No music';
- $('#kp-review').innerHTML=`<div class="kp-preview">${media}</div><div class="kp-summary"><h4>Your words</h4><p>${esc(cap||'No words')}</p><h4>Music suggestion</h4><p>${song}</p><h4>Posting to</h4><div class="kp-tags">${tags||'<span class="kp-tag">None chosen</span>'}</div></div>`;
+ const showroomWarn = state.showroomSelected ? `<div class="kp-showroom-note" style="margin:0 0 14px"><b>🏛️ Showroom-only line</b>Make sure the words invite people to the Designer Wallcoverings showroom (see it in person / book a visit) — not to shop or buy online.</div>` : '';
+ $('#kp-review').innerHTML=`<div class="kp-preview">${media}</div><div class="kp-summary">${showroomWarn}<h4>Your words</h4><p>${esc(cap||'No words')}</p><h4>Music suggestion</h4><p>${song}</p><h4>Posting to</h4><div class="kp-tags">${tags||'<span class="kp-tag">None chosen</span>'}</div></div>`;
$('#kp-confirm').checked=false; $('#kp-publish').disabled=true; $('#kp-result').textContent='';
}
$('#kp-confirm').onchange=e => { $('#kp-publish').disabled=!e.target.checked; };
← d3e2d6d auto-data-snapshot: 2026-09-03T11:10:51 (1 data files) — pub
·
back to Marketing Command Center
·
auto-data-snapshot: 2026-09-03T11:49:57 (1 data files) — pub 5430cac →