← back to Dw Domain Fleet
Tier A promo strip: text-only rotating 'New · <product> →' new-arrivals banner across the 44 multitenant DW sites (promoStrip() in render.js, per-site cfg.promoBanner default on); verified live on carmelwallpaper
e0e0d83ce6ff4cac1364ec96944fad84c2cb6a1d · 2026-06-19 16:30:48 -0700 · Steve
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Files touched
Diff
commit e0e0d83ce6ff4cac1364ec96944fad84c2cb6a1d
Author: Steve <steve@designerwallcoverings.com>
Date: Fri Jun 19 16:30:48 2026 -0700
Tier A promo strip: text-only rotating 'New · <product> →' new-arrivals banner across the 44 multitenant DW sites (promoStrip() in render.js, per-site cfg.promoBanner default on); verified live on carmelwallpaper
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---
shared/render.js | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 100 insertions(+), 3 deletions(-)
diff --git a/shared/render.js b/shared/render.js
index eefd1f0..d12cd0e 100644
--- a/shared/render.js
+++ b/shared/render.js
@@ -197,6 +197,42 @@ a{color:inherit;text-decoration:none}
img{display:block;max-width:100%}
.wrap{max-width:1480px;margin:0 auto;padding:0 32px}
+/* ---- promo strip (text-only rotating "new arrivals") --------------------
+ SHAREABLE SNIPPET: this CSS block + the promoStrip() markup/JS in render.js
+ are self-contained (depend only on the --accent / --sans theme vars) so they
+ lift cleanly into the standalone-tier sister sites. Text-only, no images.
+ White ink on the per-site --accent (same contrast contract as .nsm-cta). */
+.promo-strip{position:relative;z-index:96;width:100%;min-height:40px;
+ background:var(--accent);color:#fff;font-family:var(--sans);
+ display:flex;align-items:center;justify-content:center;
+ padding:9px 46px 9px 16px;text-align:center}
+.promo-strip .promo-track{max-width:100%;overflow:hidden}
+.promo-strip a.promo-item{color:#fff;display:none;align-items:center;gap:8px;
+ max-width:92vw;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
+ font-size:13px;letter-spacing:.03em}
+.promo-strip a.promo-item.on{display:inline-flex}
+.promo-strip a.promo-item:hover{text-decoration:underline}
+.promo-strip .promo-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
+.promo-strip .promo-dot{opacity:.6;flex:none}
+.promo-strip .promo-tag{font-weight:600;letter-spacing:.16em;text-transform:uppercase;
+ font-size:11px;opacity:.92;flex:none}
+.promo-strip .promo-arrow{opacity:.85;flex:none}
+.promo-x{position:absolute;right:12px;top:50%;transform:translateY(-50%);
+ background:transparent;border:0;color:#fff;opacity:.7;cursor:pointer;
+ font-size:18px;line-height:1;padding:4px 6px}
+.promo-x:hover{opacity:1}
+/* when the in-flow promo strip is present, drop the FIXED chrome below it so
+ the topbar/menu/toggle don't overlap the strip (body.promo-on set server-side
+ to avoid a layout flash; the dismiss JS removes it). */
+body.promo-on .topbar{top:40px}
+body.promo-on .gucci-menu,body.promo-on .theme-toggle{top:62px}
+@media(max-width:640px){
+ .promo-strip{padding:8px 40px 8px 12px}
+ .promo-strip a.promo-item{font-size:12px}
+ body.promo-on .topbar{top:38px}
+ body.promo-on .gucci-menu,body.promo-on .theme-toggle{top:58px}
+}
+
/* top bar */
.topbar{position:fixed;top:0;left:0;right:0;z-index:90;display:flex;justify-content:space-between;
align-items:center;padding:20px 36px;mix-blend-mode:normal}
@@ -332,6 +368,63 @@ function chrome(cfg) {
</nav>`;
}
+/* ---------- promo strip (rotating "new arrivals") ----------
+ * SHAREABLE SNIPPET (pairs with the .promo-strip CSS in css()). Text-only,
+ * no images. Shows "NEW · <title> →" rotating through the 3 NEWEST products.
+ *
+ * Newest signal: `products` is already sorted newest-first by the server
+ * (sortProducts(POOL,'newest') = created_at desc — the schema has no
+ * published_at), so the first 3 ARE the 3 newest. Link target is the fleet's
+ * OWN vendor-safe /product/<slug> route (productSlug strips vendor tokens) —
+ * never an external vendor URL (settlement vendor-confidentiality, the same
+ * rule the cards / buy route already honour).
+ *
+ * Per-site flag: cfg.promoBanner. Defaults ON; only an explicit `false`
+ * disables it. Returns '' (no strip, no body.promo-on offset) when disabled or
+ * when there are no products to show. Self-contained: depends only on esc /
+ * clean / scrubVendor / productSlug already in this module.
+ */
+function promoStrip(cfg, products) {
+ if (cfg.promoBanner === false) return '';
+ const items = (Array.isArray(products) ? products : []).slice(0, 3).map(p => {
+ const t = scrubVendor(clean(p.title).split('|')[0].trim())
+ || scrubVendor((p.sku || p.handle || '').replace(/-Sample$/i, ''))
+ || 'New Design';
+ return { t, href: '/product/' + encodeURIComponent(productSlug(p)) };
+ }).filter(x => x.t);
+ if (!items.length) return '';
+ const links = items.map((it, i) =>
+ `<a class="promo-item${i === 0 ? ' on' : ''}" href="${esc(it.href)}">` +
+ `<span class="promo-tag">New</span><span class="promo-dot" aria-hidden="true">·</span>` +
+ `<span class="promo-name">${esc(it.t)}</span>` +
+ `<span class="promo-arrow" aria-hidden="true">→</span></a>`
+ ).join('');
+ return `
+<div class="promo-strip" id="promoStrip" role="region" aria-label="New arrivals">
+ <div class="promo-track">${links}</div>
+ <button class="promo-x" id="promoX" type="button" aria-label="Dismiss new-arrivals banner">×</button>
+</div>
+<script>
+(function(){
+ var k='${cfg.slug}_promo_dismiss';
+ var strip=document.getElementById('promoStrip');
+ if(!strip)return;
+ try{ if(localStorage.getItem(k)==='1'){ strip.style.display='none';
+ document.body.classList.remove('promo-on'); return; } }catch(e){}
+ var items=strip.querySelectorAll('.promo-item'),i=0;
+ if(items.length>1){ setInterval(function(){
+ if(document.hidden)return; // don't advance while the tab is backgrounded
+ items[i].classList.remove('on'); i=(i+1)%items.length; items[i].classList.add('on');
+ },4200); }
+ var x=document.getElementById('promoX');
+ x&&x.addEventListener('click',function(){
+ strip.style.display='none'; document.body.classList.remove('promo-on');
+ try{ localStorage.setItem(k,'1'); }catch(e){}
+ });
+})();
+</script>`;
+}
+
function script(cfg) {
return `
<script>
@@ -359,7 +452,7 @@ function script(cfg) {
* site's own URL. Product pages pass the SKU's canonical home on the main
* store so shared SKUs never compete as duplicate content (panel SEO must-do).
*/
-function head(cfg, title, desc, path, canonicalOverride) {
+function head(cfg, title, desc, path, canonicalOverride, bodyClass) {
const url = 'https://' + cfg.domain + (path || '');
const canonical = canonicalOverride || url;
return `<!doctype html><html lang="en"><head>
@@ -376,7 +469,7 @@ function head(cfg, title, desc, path, canonicalOverride) {
<style>:root{--top-fg:#fff}</style>
<script>(function(){var s=localStorage.getItem('${cfg.slug}_theme');if(s)document.documentElement.dataset.theme=s;})();</script>
<style>${css(cfg)}</style>
-</head><body>`;
+</head><body${bodyClass ? ` class="${esc(bodyClass)}"` : ''}>`;
}
function jsonld(cfg) {
@@ -433,7 +526,11 @@ function homePage(cfg, heroImgs, featured) {
const heroHtml = slides.length >= 3
? slides.map((s,i)=>`<div class="cinema-slide${i===0?' on':''}" style="background-image:url('${esc(s)}')"></div>`).join('')
: `<div class="cinema-grid">${slides.concat(slides).slice(0,4).map(s=>`<div style="background-image:url('${esc(s)}')"></div>`).join('')}</div>`;
- return head(cfg, title, desc, '/') + jsonld(cfg) + chrome(cfg) + `
+ // Rotating "new arrivals" strip — first element in <body>, above topbar+hero.
+ // featured is already newest-first (created_at desc); promoStrip takes the 3
+ // newest. body.promo-on (set server-side) offsets the fixed chrome below it.
+ const promo = promoStrip(cfg, featured);
+ return head(cfg, title, desc, '/', null, promo ? 'promo-on' : '') + promo + jsonld(cfg) + chrome(cfg) + `
<header class="cinema" data-hero-mode="huge">
${heroHtml}
<div class="hero-cta">
← 1b277d8 snapshot before task-52 verification (standalone hero alloca
·
back to Dw Domain Fleet
·
Add Tier-A promo-banner desktop verification screenshot from 4936c4d →