← back to Dw Domain Fleet
scripts/gen-configs.js
241 lines
#!/usr/bin/env node
/**
* gen-configs.js — generate sites/<slug>.json for all 66 fleet domains.
*
* Each domain gets a DISTINCT visual identity (palette + font pair + niche
* filter + copy) so the fleet is NOT 66 identical templates (doorway-page
* SEO risk). Identity is derived from a per-domain spec table below.
*/
const fs = require('fs');
const path = require('path');
const SITES_DIR = path.join(__dirname, '..', 'sites');
fs.mkdirSync(SITES_DIR, { recursive: true });
// ---- palette library (distinct visual directions) ----
const PALETTES = {
ivory: { bgLight:'#faf7f2', inkLight:'#1a1714', bg:'#14110d', ink:'#f3efe8', rule:'#d8cec0', accent:'#7d3c00' },
noir: { bgLight:'#f4f2ee', inkLight:'#15140f', bg:'#0a0a09', ink:'#f0ede6', rule:'#cfcabf', accent:'#a8884a' },
sage: { bgLight:'#f3f4ee', inkLight:'#1c1f17', bg:'#10120c', ink:'#eef0e6', rule:'#cdd0c0', accent:'#5a6f3e' },
sand: { bgLight:'#f6f1e7', inkLight:'#211c12', bg:'#16120a', ink:'#f1ead9', rule:'#d6c9ad', accent:'#9c6b30' },
slate: { bgLight:'#eef0f2', inkLight:'#161a1e', bg:'#0c0e11', ink:'#e8eaee', rule:'#c4cad0', accent:'#3a5f8a' },
bordeaux: { bgLight:'#f6f0ee', inkLight:'#1d1310', bg:'#120a08', ink:'#f0e6e2', rule:'#d4c2bb', accent:'#7a2230' },
coastal: { bgLight:'#f0f4f5', inkLight:'#10191c', bg:'#0a1113', ink:'#e6eef0', rule:'#bdcdd0', accent:'#2a7d8a' },
gilt: { bgLight:'#f7f3e8', inkLight:'#1c1810', bg:'#0d0b06', ink:'#f2ecd8', rule:'#d8caa0', accent:'#b08923' },
blush: { bgLight:'#f8f1f0', inkLight:'#1f1614', bg:'#130c0b', ink:'#f1e6e3', rule:'#dcc8c4', accent:'#a85a5f' },
ink: { bgLight:'#f1f1f0', inkLight:'#121211', bg:'#080807', ink:'#ededec', rule:'#c8c8c5', accent:'#444440' },
forest: { bgLight:'#eef1ec', inkLight:'#13170f', bg:'#0a0d07', ink:'#e8ece2', rule:'#c2c9b8', accent:'#2f5d3a' },
plum: { bgLight:'#f3eff3', inkLight:'#1a131c', bg:'#0e0a10', ink:'#ece4ee', rule:'#cdc1d0', accent:'#6a3a7a' }
};
const FONTS = {
cormorant: { serif:"'Cormorant Garamond','Playfair Display',Georgia,serif", sans:"-apple-system,'Inter',system-ui,sans-serif" },
playfair: { serif:"'Playfair Display',Georgia,serif", sans:"-apple-system,'Inter',system-ui,sans-serif" },
bodoni: { serif:"'Bodoni Moda',Didot,Georgia,serif", sans:"-apple-system,'Helvetica Neue',Arial,sans-serif" },
georgia: { serif:"Georgia,'Times New Roman',serif", sans:"-apple-system,'Inter',system-ui,sans-serif" }
};
// niche filter shorthand → {pos,neg,types}
function W(t){return ['Wallcovering'];}
// ---- the 44 BUILD domains: slug, palette, font, niche, copy ----
//
// SCOPE: a dtd debate-panel ruled SPLIT (Option B) on 2026-05-18. Of the
// original 66 fleet domains, 22 near-duplicate/typo-variant domains stay as
// 301 redirects to designerwallcoverings.com (doorway-page SEO risk if built
// out) and are NOT in this table. The 44 below have genuinely distinct search
// intent and get standalone sites.
//
// niche.pos = any-match keywords; neg = exclude; types = product_type filter.
// SEO MUST-DO #1: every site serves a CURATED SUBSET (niche filter) with
// original per-site copy — never the full shared catalog rendered identically.
// Domains whose intent is geographic/editorial rather than product-category
// (asseeninla, roomsettings, etc.) are differentiated by original landing copy
// + per-site title/meta + rel=canonical, not by a product filter.
const SPEC = [
['1800wallcoverings', 'ivory', 'cormorant', {types:W()}, 'Designer wallcoverings for every room.'],
['asseeninhotels', 'gilt', 'bodoni', {pos:['hospitality','hotel','contract','commercial']}, 'Wallcoverings seen in the world\'s finest hotels.'],
['asseeninla', 'noir', 'playfair', {types:W()}, 'The wallcoverings of Los Angeles design.'],
['asseeninmovies', 'noir', 'bodoni', {pos:['dramatic','glamour','scenic','statement','mural','metallic']}, 'Wallcoverings seen on the silver screen.'],
['asseeninshowrooms', 'slate', 'cormorant', {types:W()}, 'Showroom-grade wallcoverings, direct to the trade.'],
['barwallpaper', 'bordeaux','bodoni', {pos:['bar','hospitality','dark','dramatic','jewel']}, 'Dramatic wallcoverings for bars and lounges.'],
['blankstocklining', 'ink', 'georgia', {pos:['liner','blank','lining','solid','paper'],neg:['display']}, 'Liner and blankstock for a flawless finish.'],
['carmelwallpaper', 'sage', 'cormorant', {pos:['grasscloth','sisal','jute','organic modern','botanical']}, 'Coastal-calm wallcoverings, Carmel to Monterey.'],
['cfafabrics', 'blush', 'playfair', {types:['Fabric']}, 'Designer fabrics for the trade.'],
['classawallcovering', 'slate', 'cormorant', {pos:['class a','fire rated','commercial','contract']}, 'Class A fire-rated wallcoverings.'],
['commercialsalesreps', 'ink', 'georgia', {pos:['commercial','contract']}, 'Commercial wallcoverings and trade representation.'],
['commercialwallcovering','slate', 'cormorant', {pos:['commercial','contract','class a']}, 'Contract-grade commercial wallcoverings.'],
['designermagnetics', 'slate', 'bodoni', {pos:['magnetic','metallic','mica','metal']}, 'Magnetic and metallic wallcoverings.'],
['etciemurals', 'plum', 'playfair', {pos:['mural','scenic','panoramic'],types:['Mural','Wallcovering']}, 'Statement murals for interiors.'],
['fabricfridays', 'blush', 'playfair', {types:['Fabric']}, 'New designer fabrics every Friday.'],
['fireratedwallcovering','slate', 'georgia', {pos:['fire rated','class a','commercial','contract']}, 'Fire-rated wallcoverings for code compliance.'],
['flocked', 'bordeaux','bodoni', {pos:['flock','velvet','damask','traditional']}, 'Flocked velvet wallcoverings.'],
['grassclothwallcovering','sage', 'cormorant', {pos:['grasscloth','sisal','jute','arrowroot']}, 'Hand-crafted grasscloth wallcoverings.'],
['handmadewallcovering','sand', 'cormorant', {pos:['grasscloth','sisal','jute','hand screen','handcraft','artisan']}, 'Hand-made wallcoverings, artisan-crafted.'],
['hollywoodwallcovering','gilt', 'bodoni', {pos:['hollywood','glamour','metallic','dramatic']}, 'Hollywood-glamour wallcoverings.'],
['hospitalitysalesagency','gilt', 'georgia', {pos:['hospitality','hotel','contract','commercial']}, 'Hospitality wallcoverings and sales representation.'],
['malibuwallpaper', 'coastal', 'cormorant', {pos:['grasscloth','sisal','jute','organic modern','botanical']}, 'Coastal wallcoverings, Malibu style.'],
['montereywallpaper', 'sage', 'cormorant', {pos:['grasscloth','sisal','jute','organic modern']}, 'Monterey-coast wallcoverings.'],
['naturalwalltextures', 'sage', 'cormorant', {pos:['grasscloth','sisal','jute','arrowroot','madagascar','cork','mica']}, 'Natural-fiber wall textures.'],
['patterndesignlab', 'plum', 'playfair', {pos:['pattern','geometric','floral','abstract']}, 'A laboratory of pattern design.'],
['philliperomano', 'noir', 'bodoni', {pos:['phillipe romano']}, 'The Phillipe Romano collection.'],
['printmurals', 'plum', 'playfair', {pos:['mural','scenic','panoramic','print'],types:['Mural','Wallcovering']}, 'Printed murals for every wall.'],
['restaurantfabrics', 'bordeaux','playfair', {types:['Fabric']}, 'Designer fabrics for restaurants.'],
['restaurantmurals', 'bordeaux','bodoni', {pos:['mural','scenic','panoramic'],types:['Mural','Wallcovering']}, 'Murals for restaurant interiors.'],
['roomsettings', 'ivory', 'cormorant', {types:W()}, 'Wallcoverings, seen in the room.'],
['sheltermagazines', 'noir', 'playfair', {types:W()}, 'The wallcoverings of shelter editorial.'],
['specifywallpaper', 'slate', 'georgia', {pos:['commercial','contract','class a','fire rated']}, 'Specify commercial wallcoverings with confidence.'],
['stevenabramsphotography','ink', 'playfair', {types:W()}, 'Steven Abrams — photography and design.'],
['thehotelwallpaper', 'gilt', 'bodoni', {pos:['hospitality','hotel','contract','commercial']}, 'Wallcoverings for the modern hotel.'],
['traditionalwhimsy', 'blush', 'playfair', {pos:['traditional','floral','toile','botanical','whimsical']}, 'Traditional wallcoverings with a whimsical turn.'],
['unitedstateswallpaper','slate', 'georgia', {types:W()}, 'American designer wallcoverings.'],
['wallcovering', 'ivory', 'cormorant', {types:W()}, 'Wallcovering — the catalog.'],
['wallpaperexports', 'sand', 'georgia', {types:W()}, 'Designer wallcoverings for export.'],
['wallpaperfromthe80s', 'plum', 'bodoni', {pos:['retro','vintage','geometric','abstract','1980']}, 'Retro wallcoverings with 80s spirit.'],
['wallpaperny', 'ink', 'bodoni', {types:W()}, 'New York designer wallcoverings.'],
['wallpaperpurchasing', 'slate', 'georgia', {pos:['commercial','contract']}, 'Commercial wallcovering purchasing.'],
['wallpaperweekly', 'ivory', 'playfair', {types:W()}, 'New designer wallcoverings, weekly.'],
['wc01wallcoverings', 'slate', 'georgia', {pos:['commercial','contract','class a']}, 'WC-01 commercial wallcoverings.']
];
// ---- the 22 REDIRECT-ONLY domains (NOT built — 301 → designerwallcoverings.com) ----
// Kept here for documentation / DNS-audit reference only; gen-configs does not
// emit a site config for these. Near-duplicate typo/variant spellings or
// redundant siblings of designerwallcoverings.com.
const REDIRECT_ONLY = [
'designerswallcovering.com','designerswallcoverings.com','designerswallpapers.com',
'designerwallcover.com','designerwallcovering.com','designwallcoverings.com',
'designerwallpaints.com','designerwalltiles.com','dwlosangeles.com',
'designerlaboratory.com','thedesignerlaboratory.com','handmadewallpapers.com',
'handscreenedwallpaper.com','hospitalitysalesagents.com','hospitalitysalesreps.com',
'commercialwalls.com','1800wallpapers.com','800wallpaper.com','wallpapernyc.com',
'wc01wallpaper.com','wc-01.com','steveabramsdesigns.com'
];
// domain overrides where slug != slug+'.com'
const DOMAIN_OVERRIDE = {
'flocked': 'flocked.org',
'wallcovering': 'wallcovering.net',
'wc-01': 'wc-01.com'
};
// explicit display-name overrides — for compound slugs the regex titleCase
// reads as a slug. The brand display is still the domain stem, just spaced.
const NAME_OVERRIDE = {
'dwlosangeles': 'DW Los Angeles',
'asseeninla': 'As Seen In LA',
'asseeninhotels': 'As Seen In Hotels',
'asseeninmovies': 'As Seen In Movies',
'asseeninshowrooms': 'As Seen In Showrooms',
'wallpaperfromthe80s': 'Wallpaper From The 80s',
'steveabramsdesigns': 'Steve Abrams Designs',
'stevenabramsphotography': 'Steven Abrams Photography',
'thedesignerlaboratory': 'The Designer Laboratory',
'thehotelwallpaper': 'The Hotel Wallpaper',
'cfafabrics': 'CFA Fabrics',
'hospitalitysalesagency': 'Hospitality Sales Agency',
'hospitalitysalesagents': 'Hospitality Sales Agents',
'hospitalitysalesreps': 'Hospitality Sales Reps',
'commercialsalesreps': 'Commercial Sales Reps',
'unitedstateswallpaper': 'United States Wallpaper',
'patterndesignlab': 'Pattern Design Lab',
'designerlaboratory': 'Designer Laboratory',
'naturalwalltextures': 'Natural Wall Textures',
'handscreenedwallpaper': 'Hand-Screened Wallpaper',
'sheltermagazines': 'Shelter Magazines',
'roomsettings': 'Room Settings',
'traditionalwhimsy': 'Traditional Whimsy',
'designermagnetics': 'Designer Magnetics',
'designerwalltiles': 'Designer Wall Tiles',
'designerwallpaints': 'Designer Wall Paints',
'wallpaperexports': 'Wallpaper Exports',
'wallpaperpurchasing': 'Wallpaper Purchasing',
'wallpaperweekly': 'Wallpaper Weekly',
'fabricfridays': 'Fabric Fridays',
'restaurantfabrics': 'Restaurant Fabrics',
'restaurantmurals': 'Restaurant Murals',
'printmurals': 'Print Murals',
'etciemurals': 'Et Cie Murals',
'barwallpaper': 'Bar Wallpaper',
'carmelwallpaper': 'Carmel Wallpaper',
'malibuwallpaper': 'Malibu Wallpaper',
'montereywallpaper': 'Monterey Wallpaper',
'philliperomano': 'Phillipe Romano',
'flocked': 'Flocked',
'blankstocklining': 'Blankstock Lining',
'fireratedwallcovering': 'Fire-Rated Wallcovering',
'classawallcovering': 'Class A Wallcovering',
'specifywallpaper': 'Specify Wallpaper',
'handmadewallcovering': 'Hand-Made Wallcovering',
'handmadewallpapers': 'Hand-Made Wallpapers',
'hollywoodwallcovering': 'Hollywood Wallcovering',
'grassclothwallcovering': 'Grasscloth Wallcovering',
'commercialwallcovering': 'Commercial Wallcovering',
'commercialwalls': 'Commercial Walls',
'designwallcoverings': 'Design Wallcoverings',
'designerwallcover': 'Designer Wallcover',
'designerwallcovering': 'Designer Wallcovering',
'designerswallcovering': 'Designers Wallcovering',
'designerswallcoverings': 'Designers Wallcoverings',
'designerswallpapers': 'Designers Wallpapers',
'wallpaperny': 'Wallpaper NY',
'wallpapernyc': 'Wallpaper NYC',
'wc-01': 'WC-01',
'wc01wallcoverings': 'WC-01 Wallcoverings',
'wc01wallpaper': 'WC-01 Wallpaper',
'1800wallcoverings': '1-800 Wallcoverings',
'1800wallpapers': '1-800 Wallpapers',
'800wallpaper': '800 Wallpaper',
'wallcovering': 'Wallcovering'
};
function titleCase(slug){
let s = slug
.replace(/wc-?0?1/i,'WC-01 ')
.replace(/(wallcoverings?|wallpapers?|walls|fabrics?|magnetics|paints|tiles|murals|laboratory|exports|purchasing|weekly|agency|agents|reps|photography|designs|settings|magazines|whimsy)/gi,' $1 ')
.replace(/(\d{3,4}s?)/g,' $1 ')
.replace(/\b(nyc|ny|la)\b/gi,' $1 ')
.replace(/\s+/g,' ').trim()
.replace(/\b\w/g,c=>c.toUpperCase());
// city/state abbreviations stay uppercase
return s.replace(/\bNyc\b/g,'NYC').replace(/\bNy\b/g,'NY').replace(/\bLa\b/g,'LA');
}
let start = 9901;
const manifest = [];
SPEC.forEach((row, i) => {
const [slug, pal, font, niche, tagline] = row;
const palette = PALETTES[pal];
const fonts = FONTS[font];
const domain = DOMAIN_OVERRIDE[slug] || (slug + '.com');
const siteName = NAME_OVERRIDE[slug] || titleCase(slug);
const port = start + i;
const nicheLabel = niche.types && niche.types[0] === 'Fabric' ? 'designer fabrics'
: niche.types && niche.types[0] === 'Mural' ? 'designer murals'
: 'designer wallcoverings';
const cfg = {
slug, domain, siteName, port,
siteEmail: 'info@' + domain,
tagline,
heroHeadline: siteName,
heroSub: 'Explore the collection',
metaDesc: `${siteName} — ${tagline} Free memo samples, expert trade service, and hand-holding on every order from Designer Wallcoverings.`,
nicheLabel,
aboutCopy: `${siteName} curates ${nicheLabel} for designers, architects, and discerning homeowners. ${tagline} Every selection is backed by Designer Wallcoverings' decades of trade experience.`,
niche: { pos: niche.pos || [], neg: niche.neg || [], types: niche.types || [] },
theme: {
accent: palette.accent,
bg: palette.bg, bgLight: palette.bgLight,
ink: palette.ink, inkLight: palette.inkLight,
rule: palette.rule,
serif: fonts.serif, sans: fonts.sans,
palette: pal, font
}
};
fs.writeFileSync(path.join(SITES_DIR, slug + '.json'), JSON.stringify(cfg, null, 2));
manifest.push({ slug, domain, port, palette: pal, font, niche: niche });
});
fs.writeFileSync(path.join(__dirname, '..', 'data', 'manifest.json'), JSON.stringify(manifest, null, 2));
console.log(`Generated ${manifest.length} site configs → sites/`);
console.log(`Ports ${start}–${start + manifest.length - 1}`);