[object Object]

← 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">&middot;</span>` +
+    `<span class="promo-name">${esc(it.t)}</span>` +
+    `<span class="promo-arrow" aria-hidden="true">&rarr;</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">&times;</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 →