← back to Designerwallcoverings
scripts/brands-page/build-brands.cjs
136 lines
const fs=require('fs'),path=require('path');
const HOME=process.env.HOME;
const VL=HOME+'/Projects/Designer-Wallcoverings/vendor-logos';
const rows=JSON.parse(fs.readFileSync(__dirname+'/brands-map.json','utf8'));
const probed=JSON.parse(fs.readFileSync(__dirname+'/probed-logos.json','utf8'));
const cdn=JSON.parse(fs.readFileSync(VL+'/_cdn-urls.json','utf8'));
const roomPath=fs.existsSync(__dirname+'/room-final.json')?__dirname+'/room-final.json':'/tmp/room-final.json';
const room=fs.existsSync(roomPath)?JSON.parse(fs.readFileSync(roomPath,'utf8')):{};
const DROP=new Set(['WallQuest','Nicolette Mayer Wallpaper','Graduate Collection UK','Roberto Cavalli Wallpaper','1838 Wallcoverings']);
const BANNED=/wallquest|chesapeake|brewster|nextwall|seabrook|nicolette/i;
const HOUSE=new Set(['British Walls','LEED Walls','LA Walls','Primo Leathers','Surface Stick','Novasuede','Ultrasuede','DW Bespoke Studio','DW Exclusive Wallpaper','Steve Abrams Studios','PR Faux Leather','Designer Wallcoverings','Zeuxis Parrhasius at DW']);
const FORCE_WM=new Set([]);
const SANS=new Set(['Backdrop','Glitter Walls','Retro Walls','Wallpaper NYC','LEED Walls','LA Walls','Surface Stick','British Walls','Atomic 50 Ceilings','Contrado','Fentucci','SUGARBOO','OLIVIA AND POPP','Versa Designed Surfaces','PR Faux Leather','AS Creation','Hollywood Wallcoverings','Malibu Wallpaper','Marimekko','Artmura']);
const SRC={'FallingStar Studio':{f:'fallingstar.png'},'Artmura':{f:'artmura.png'},'Contrado':{f:'contrado-src.svg'},'Coordonné':{f:'coordonne-src.svg'},'Daisy Bennett':{f:'daisy-bennett-src.svg'},'Emiliana Parati':{f:'emiliana-parati-src.png'},'Laura Ashley Wallpaper':{f:'laura-ashley-src.png'},'Sandberg':{f:'sandberg-src.webp'},'Tres Tintas':{f:'tres-tintas-src.svg'},'Holland and Sherry':{f:'holland-and-sherry-src.svg'},'Kelly Wearstler':{f:'kelly-wearstler-src.svg'},'NLXL':{f:'nlxl-src.jpg'},'Gaston Y Daniela':{f:'gaston-y-daniela-v2-src.png'},'Brand McKenzie':{f:'brand-mckenzie-src.svg',dark:1},'Marburg':{f:'marburg-src.webp',dark:1},'Villa Nova':{f:'villa-nova-src.svg',dark:1}};
// Generated luxe wordmark SVGs (gen-wordmarks.cjs) — one tasteful chip per brand
// with no sourceable mark (DTD verdict B, 2026-06-14). Filenames are wm-<slug>.svg.
const WORDMARK=['AS Creation','Atomic 50 Ceilings','Backdrop','British Walls','Clarence House','Dedar','Designer Wallcoverings','Dolce & Gabbana','DW Bespoke Studio','DW Exclusive Wallpaper','Fentucci','Glitter Walls','LA Walls','LEED Walls','Novasuede','OLIVIA AND POPP','Peg Norriss','PR Faux Leather','Primo Leathers','Retro Walls','Sacha Walckhoff','Sancar','Steve Abrams Studios','SUGARBOO','Surface Stick','Ultrasuede','Wallpaper NYC','Zeuxis Parrhasius at DW','Phillipe Romano','Hollywood Wallcoverings','Malibu Wallpaper','Jeffrey Stevens','Marimekko'];
const wmSlug=s=>s.toLowerCase().normalize('NFD').replace(/[^a-z0-9]+/g,'-').replace(/(^-|-$)/g,'');
for(const b of WORDMARK)SRC[b]={f:'wm-'+wmSlug(b)+'.svg'};
const mime={svg:'image/svg+xml',png:'image/png',jpg:'image/jpeg',jpeg:'image/jpeg',webp:'image/webp'};
function dataURI(file){const p=path.join(VL,file);if(!fs.existsSync(p))return null;const ext=file.split('.').pop().toLowerCase();return `data:${mime[ext]||'image/png'};base64,${fs.readFileSync(p).toString('base64')}`;}
const FEATURED=['Schumacher','Thibaut','Cole & Son','Ralph Lauren','Kravet'];
const FEATURED_FABRICS=['Pierre Frey','Lee Jofa','Romo','Designers Guild','Holland and Sherry'];
const decode=s=>s.replace(/&/g,'&');
const deAcc=s=>s.normalize('NFD').replace(/[̀-ͯ]/g,'');
const norm=s=>deAcc(s).replace(/&|&/g,' and ').toLowerCase().replace(/[^a-z0-9]+/g,'-').replace(/(^-|-$)/g,'');
const idx={};Object.keys(cdn).forEach(k=>idx[norm(k)]=k);
const ALIAS={'schumacher':'schumacher-shopify','missoni-wallpaper':'missoni-home','william-morris':'william-morris-and-co','morris-and-company':'morris-and-co','arte-international':'arte-specialty','newmor-wallcoverings':'newmor-logo','innovations-usa':'innovations','versa-designed-surfaces':'versa','koroseal':'arbor-wood-wallcoverings','kravet':'kravet-logo','graduate-collection':'graduate-collection-shopify','1838-wallcoverings':'1838-wallcoverings-shopify','fabricut':'fabricut-stroheim','elitis':'elitis-panoramic','scalamandre-wallpaper':'scalamandre','scalamandre-fabrics':'scalamandre','roberto-cavalli':'roberto-cavalli','osborne-and-little':'osborne-little','versace':'versace-home-shopify','nina-campbell':'nina-campbell-shopify','milton-and-king':'milton-king','gp-and-j-baker':'gp-j-baker','brunschwig-and-fils':'brunschwig-fils','mulberry':'mulberry-home','versace':'versace-home-shopify'};
function cdnLogo(title){const b=norm(decode(title));if(ALIAS[b]&&cdn[ALIAS[b]])return cdn[ALIAS[b]].url;const c=[b,norm(decode(title).replace(/\b(wallpaper|wallcoverings?|at dw|usa|uk|home|international|fabrics?)\b/gi,'')),b+'-shopify',b+'-home',b+'-and-co'];for(const x of c)if(idx[x])return cdn[idx[x]].url;return null;}
function logoFor(r,title){
const src=SRC[r.title]||SRC[title];
if(src){const u=dataURI(src.f);if(u)return {logo:u,dark:!!src.dark};}
if(!HOUSE.has(r.title)&&!FORCE_WM.has(title)){
const lg=cdnLogo(title)||r.logo||probed[r.title]||'';if(lg)return {logo:lg,dark:false};
// factory bake: drop a logo file named by the collection slug (vendor-logos/<slug>.<ext>)
// when onboarding a vendor and it auto-inlines here — no per-vendor code edit needed.
const slug=(r.href||'').split('/').filter(Boolean).pop();
if(slug)for(const ext of ['svg','png','webp','jpg','jpeg']){const u=dataURI(slug+'.'+ext);if(u)return {logo:u,dark:false};}
}
return {logo:'',dark:false};
}
function resolve(r){
const title=decode(r.title);
if(BANNED.test(title))return null;
const {logo,dark}=logoFor(r,title);
const bg=room[title]||room[r.title]||'';
return {title,href:r.href,logo,dark,bg};
}
let items=rows.filter(r=>!DROP.has(r.title)).map(resolve).filter(Boolean);
const byHref={};for(const it of items){const k=it.href;if(!byHref[k]||((!byHref[k].logo&&it.logo)||(!byHref[k].bg&&it.bg)))byHref[k]=it;}
items=Object.values(byHref).sort((a,b)=>a.title.localeCompare(b.title));
const featuredItems=FEATURED.map(name=>items.find(it=>it.title===name)).filter(Boolean);
const featuredFabricsItems=FEATURED_FABRICS.map(name=>items.find(it=>it.title===name)).filter(Boolean);
const esc=s=>s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
function card(it){
const label=esc(it.title);
const dn=label.toLowerCase().replace(/&/g,'&');
const chipInner = it.logo
? `<img loading="lazy" alt="${label}" src="${it.logo}">`
: `<span class="${SANS.has(it.title)?'nm nm-sans':'nm nm-serif'}">${label}</span>`;
if(it.bg){
return `<a href="${it.href}" class="dw-brand-card dw-has-bg${it.dark?' dw-dark':''}" title="${label}" data-name="${dn}"><span class="dw-bg"><img loading="lazy" alt="${label}" src="${it.bg}"></span><span class="dw-chip">${chipInner}</span></a>`;
}
if(it.logo){
return `<a href="${it.href}" class="dw-brand-card${it.dark?' dw-dark':''}" title="${label}" data-name="${dn}"><span class="dw-brand-logo"><img loading="lazy" alt="${label}" src="${it.logo}"></span></a>`;
}
return `<a href="${it.href}" class="dw-brand-card dw-wmcard" title="${label}" data-name="${dn}"><span class="${SANS.has(it.title)?'nm nm-sans':'nm nm-serif'}">${label}</span></a>`;
}
const nbg=items.filter(i=>i.bg).length, nlogo=items.filter(i=>i.logo).length, nwm=items.filter(i=>!i.logo).length;
const css=`<style>
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@500;600&family=Montserrat:wght@500;600&display=swap');
.page-content.rte{max-width:100%!important;padding:0!important;margin:0!important}
.template-page .main-content,.template-page .page-width,.template-page .main-content .grid{max-width:100%!important;padding:0!important}
.dw-brand-head{text-align:center;padding:34px 20px 8px}
.dw-brand-head h1{font-family:'Playfair Display',Georgia,serif;font-size:clamp(26px,3vw,36px);font-weight:600;color:#1a1a1a;margin:0 0 6px;letter-spacing:-.01em}
.dw-brand-head p{font-family:-apple-system,'Segoe UI',Roboto,sans-serif;font-size:13px;color:#8a8a8a;margin:0}
.dw-brand-grid{display:grid!important;grid-template-columns:repeat(5,1fr)!important;gap:10px!important;padding:14px 22px 52px!important;max-width:1400px!important;margin:0 auto!important}
.dw-brand-card{position:relative;display:flex;align-items:center;justify-content:center;aspect-ratio:1/1;background:#fff;border:1px solid #e7e7e7;border-radius:12px;overflow:hidden;text-decoration:none;transition:transform .18s,box-shadow .18s}
.dw-brand-card:hover{transform:translateY(-3px);box-shadow:0 14px 32px rgba(0,0,0,.16)}
.dw-bg{position:absolute;inset:0;display:block}
.dw-bg img{width:100%;height:100%;object-fit:cover;object-position:center;display:block;transition:transform .45s ease}
.dw-brand-card:hover .dw-bg img{transform:scale(1.05)}
.dw-chip{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:66%;min-width:120px;min-height:52px;background:rgba(255,255,255,.96);border-radius:10px;padding:12px 14px;box-shadow:0 8px 22px rgba(0,0,0,.16),0 2px 6px rgba(0,0,0,.10),0 1px 0 rgba(255,255,255,.65) inset;display:flex;align-items:center;justify-content:center;box-sizing:border-box}
.dw-chip img{max-width:100%;max-height:44px;object-fit:contain;display:block;filter:drop-shadow(0 1px 1.5px rgba(0,0,0,.18))}
.dw-dark .dw-chip{background:rgba(22,22,22,.92)}
.dw-brand-logo{display:flex;align-items:center;justify-content:center;width:100%;height:100%;padding:22px 20px}
.dw-brand-logo img{max-width:100%;max-height:100%;object-fit:contain;filter:drop-shadow(0 1px 1px rgba(0,0,0,.10))}
.dw-dark:not(.dw-has-bg){background:#1b1b1b;border-color:#1b1b1b}
.dw-wmcard{background:#fcfbf9;border-color:#ece5d9}
.nm{text-align:center;line-height:1.12;color:#2a2622}
.nm-serif{font-family:'Playfair Display',Georgia,serif;font-size:clamp(15px,1.7vw,19px);font-weight:600}
.nm-sans{font-family:'Montserrat',-apple-system,sans-serif;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.1em;color:#3a3a3a}
.dw-chip .nm-serif{font-size:clamp(14px,1.5vw,18px)}
@media(max-width:1100px){.dw-brand-grid{grid-template-columns:repeat(3,1fr)}}
@media(max-width:680px){.dw-brand-grid{grid-template-columns:repeat(2,1fr);gap:10px;padding:10px 14px 40px}.dw-chip img{max-height:36px}.nm-serif{font-size:14px}.nm-sans{font-size:11px}}
.dw-eyebrow{text-align:center;font-family:'Montserrat',-apple-system,sans-serif;font-size:11px;font-weight:600;letter-spacing:.2em;text-transform:uppercase;color:#a89a87;margin:20px 0 10px}
.dw-featured-grid{display:grid!important;grid-template-columns:repeat(5,1fr)!important;gap:14px!important;padding:0 24px 6px!important;max-width:1400px!important;margin:0 auto!important}
.dw-featured-grid .dw-brand-card{box-shadow:0 4px 16px rgba(0,0,0,.09)}
.dw-divider{max-width:1320px;margin:22px auto 2px;border:0;border-top:1px solid #ececec}
@media(max-width:1100px){.dw-featured-grid{grid-template-columns:repeat(3,1fr)}}
@media(max-width:680px){.dw-featured-grid{grid-template-columns:repeat(2,1fr);gap:10px;padding:0 14px 6px}}
.dw-intro{max-width:760px;margin:2px auto 14px;padding:0 20px;text-align:center;font-family:-apple-system,'Segoe UI',Roboto,sans-serif;font-size:14px;line-height:1.6;color:#6b6b6b}
.dw-intro a{color:#3a3a3a;text-decoration:underline;text-underline-offset:2px}
.dw-brand-tools{max-width:520px;margin:0 auto 6px;padding:0 18px}
#dwBrandSearch{width:100%;box-sizing:border-box;padding:11px 18px;border:1px solid #dcdcdc;border-radius:24px;font:15px/1 -apple-system,'Segoe UI',Roboto,sans-serif;color:#222;background:#fff;outline:none;transition:border-color .15s}
#dwBrandSearch:focus{border-color:#9a9a9a}
</style>`;
const ld=`<script type="application/ld+json">${JSON.stringify({"@context":"https://schema.org","@type":"ItemList","name":"Designer Wallcovering & Fabric Brands","numberOfItems":items.length,"itemListElement":items.map((it,i)=>({"@type":"ListItem",position:i+1,name:decode(it.title),url:/^https?:/.test(it.href)?it.href:"https://www.designerwallcoverings.com"+it.href}))})}</script>`;
const html=`${css}${ld}
<div class="dw-brand-head"><h1>Shop by Brand</h1><p>From English heritage archives to modern American ateliers — the design houses behind every wall we cover.</p></div>
<p class="dw-intro">Designer Wallcoverings represents the premier wallcovering & fabric houses, from <a href="/collections/schumacher-wallpaper">Schumacher</a> and <a href="/collections/thibaut-wallcoverings">Thibaut</a> to <a href="/collections/ralph-lauren">Ralph Lauren</a> and beyond — available to the trade and retail.</p>
<div class="dw-eyebrow">Featured Wallcoverings</div>
<div class="dw-featured-grid">
${featuredItems.map(card).join('\n')}
</div>
<div class="dw-eyebrow">Featured Fabrics</div>
<div class="dw-featured-grid">
${featuredFabricsItems.map(card).join('\n')}
</div>
<hr class="dw-divider">
<div class="dw-eyebrow">All Brands A\u2013Z</div>
<div class="dw-brand-tools"><input id="dwBrandSearch" type="search" placeholder="Search brands\u2026" aria-label="Search brands" autocomplete="off"></div>
<div class="dw-brand-grid">
${items.map(card).join('\n')}
</div>
<script>(function(){var i=document.getElementById('dwBrandSearch');if(!i)return;var c=[].slice.call(document.querySelectorAll('.dw-brand-grid .dw-brand-card'));i.addEventListener('input',function(){var q=this.value.trim().toLowerCase();for(var k=0;k<c.length;k++){var n=c[k].getAttribute('data-name')||'';c[k].style.display=(!q||n.indexOf(q)>-1)?'':'none';}});})();</script>`;
if(BANNED.test(html.replace(/data:[^"]+/g,''))){console.error('!!! LEAK');process.exit(1);}
fs.writeFileSync('/tmp/page-brands-new.html',html);
fs.writeFileSync(__dirname+'/page-brands-new.html',html);
console.log('tiles:',items.length,'| room-bg:',nbg,'| with-logo:',nlogo,'| wordmark-only:',nwm,'| bytes:',html.length);