← back to Marketing Command Center
composer: Banners tab renders a LIVE banner preview (real /api/layouts/render HTML in the middle zone) instead of a dead deep-link
01d65e1dfd4f729737b286eb653376e0dc6e4224 · 2026-07-17 08:14:18 -0700 · Steve Abrams
Files touched
M public/panels/composer.js
Diff
commit 01d65e1dfd4f729737b286eb653376e0dc6e4224
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Jul 17 08:14:18 2026 -0700
composer: Banners tab renders a LIVE banner preview (real /api/layouts/render HTML in the middle zone) instead of a dead deep-link
---
public/panels/composer.js | 32 +++++++++++++++++++++++++++-----
1 file changed, 27 insertions(+), 5 deletions(-)
diff --git a/public/panels/composer.js b/public/panels/composer.js
index 9471cfe..59f97a5 100644
--- a/public/panels/composer.js
+++ b/public/panels/composer.js
@@ -54,14 +54,19 @@ window.MCC_PANELS['composer'] = {
grid.appendChild(d1);
});
} else {
+ // Banners: click a template → render it LIVE (real HTML from /api/layouts/render)
+ // into the middle preview, using the current caption as the headline and the
+ // selected asset (if any) as the banner's product photo. This is a genuine
+ // designed preview. Posting a banner AS AN IMAGE (HTML→PNG) is the next step,
+ // drafted for Steve — see pending-approval/mcc-composer-banner-image.md.
const d = await jget('/api/layouts/templates');
const items = (d && d.templates) || [];
- grid.innerHTML = `<div class="bd-empty" style="grid-column:1/-1">Banners are built in the <a href="#layouts">Layouts</a> panel (they render to a designed HTML card). Pick one there, export the image, then add it to Assets to post it here.</div>`;
- // still list the template names as quick links
+ grid.innerHTML = `<div class="bd-empty" style="grid-column:1/-1;font-size:11.5px">Click a template to preview it here → the caption becomes the headline, a selected asset becomes the photo. <b>Preview only</b> for now; one-click "post as image" is coming.</div>`;
items.forEach(t => {
- const b = document.createElement('a');
- b.href = '#layouts'; b.className = 'cmp-tgt'; b.style.textDecoration = 'none'; b.style.gridColumn = '1/-1';
- b.innerHTML = `<span class="ico">🎏</span><span class="nm">${esc(t.name || t.title || t.id || t)}</span><span class="st on">open →</span>`;
+ const b = document.createElement('div');
+ b.className = 'cmp-tgt'; b.style.gridColumn = '1/-1'; b.style.cursor = 'pointer';
+ b.innerHTML = `<span class="ico">🎏</span><span class="nm">${esc(t.name || t.id)}</span><span class="st on" style="text-transform:none">${esc(t.aspect || t.kind || '')}</span>`;
+ b.addEventListener('click', () => renderBanner(t.id, b));
grid.appendChild(b);
});
}
@@ -71,8 +76,25 @@ window.MCC_PANELS['composer'] = {
el.classList.add('sel');
state.mediaUrl = url;
const img = $('#cmp-img'); img.src = url; img.style.display = 'block';
+ const bf = $('#cmp-banner'); if (bf) bf.style.display = 'none'; // asset chosen → hide any banner preview
$('#cmp-preview').querySelector('.cmp-ph').style.display = 'none';
}
+ // Render a Layouts template LIVE into the preview via /api/layouts/render (returns HTML).
+ async function renderBanner(templateId, el) {
+ root.querySelectorAll('.cmp-tgt.sel').forEach(x => x.classList.remove('sel')); if (el) el.classList.add('sel');
+ const caption = ($('#cmp-caption').value || '').trim();
+ const headline = caption.split('\n')[0].slice(0, 80) || 'Designer Wallcoverings';
+ const r = await jpost('/api/layouts/render', {
+ template: templateId, headline, subhead: 'New arrivals', cta: 'Shop the collection',
+ productName: 'Designer Wallcoverings', imageUrl: state.mediaUrl || '',
+ });
+ if (!r || !r.html) return;
+ const img = $('#cmp-img'); img.style.display = 'none';
+ $('#cmp-preview').querySelector('.cmp-ph').style.display = 'none';
+ let f = $('#cmp-banner');
+ if (!f) { f = document.createElement('iframe'); f.id = 'cmp-banner'; f.setAttribute('sandbox', 'allow-same-origin'); f.style.cssText = 'width:100%;min-height:320px;border:0;background:#fff;border-radius:12px'; $('#cmp-preview').appendChild(f); }
+ f.style.display = 'block'; f.srcdoc = r.html;
+ }
root.querySelectorAll('.cmp-tab').forEach(tab => tab.addEventListener('click', () => {
root.querySelectorAll('.cmp-tab').forEach(t => t.classList.remove('on'));
tab.classList.add('on'); state.src = tab.dataset.src; loadSource(state.src);
← a4a92b1 layouts: fix malformed placeholder SKUs — DW-GL-001 → DWGL-1
·
back to Marketing Command Center
·
composer: banner preview respects template aspect-ratio (#2) 7877a92 →