← back to Domain Landings
lib/generate.js
94 lines
// Deterministic, $0 per-domain content generator.
// Parses a domain name -> words -> inferred category -> themed one-pager config.
// No LLM, no network: same domain always yields the same page (clean diffs, cache-friendly).
const KNOWN = ['abrams','beverly','hills','butler','boulevard','without','student','debt',
'wallpaper','wallcoverings','wallpapers','rentals','awards','news','daily','atlas','civic',
'directory','guide','index','industries','intel','intelligence','live','local','maps','markets',
'os','protection','space','terminal','vc','videos','crisis','center','hotline','voter','attorney',
'lawyers','forgive','loan','borrower','bills','petition','your','no','worries','cypres','as','seen',
'in','hotels','movies','showrooms','la','best','actor','documentary','films','born','bh','bar',
'stock','lining','blank','bleach','friendly','ai','ask','nonna','all'];
// split a domain label into words (greedy dictionary match, fallback to camel/simple)
function words(label) {
const s = label.toLowerCase();
const out = []; let i = 0;
while (i < s.length) {
let matched = '';
for (const w of KNOWN) {
if (s.startsWith(w, i) && w.length > matched.length) matched = w;
}
if (matched) { out.push(matched); i += matched.length; }
else { // consume until next known word starts, or one char
let j = i + 1;
while (j < s.length && !KNOWN.some(w => s.startsWith(w, j))) j++;
out.push(s.slice(i, j)); i = j;
}
}
return out.filter(Boolean);
}
const CATS = [
{ key: 'concierge', kw: ['butler','concierge'], palette: ['#0e1b2a','#c9a24b'],
kicker: 'Luxury Concierge', blurb: 'A premium on-demand concierge & errand service concept — vetted, discreet, always on.',
features: ['On-demand tasks & errands','Vetted local professionals','Members-first priority scheduling'] },
{ key: 'directory', kw: ['directory','index','guide','atlas','maps','local','markets','intel','intelligence'], palette: ['#10233b','#4aa3df'],
kicker: 'Curated Directory', blurb: 'A curated directory & discovery platform concept — organized, searchable, and trusted.',
features: ['Structured, searchable listings','Verified & ranked results','Local & category coverage'] },
{ key: 'media', kw: ['news','daily','videos','films','documentary','movies','live','allnews'], palette: ['#1a1a1f','#e0533d'],
kicker: 'Media & Stories', blurb: 'An independent media & storytelling concept — timely, original, worth your attention.',
features: ['Original reporting & features','Video-first storytelling','Daily curated briefings'] },
{ key: 'awards', kw: ['awards','best','actor','cypres'], palette: ['#141019','#d4af37'],
kicker: 'Recognition & Awards', blurb: 'An awards & recognition program concept — honoring the best in its field.',
features: ['Juried recognition program','Nominee showcase','Annual honors & events'] },
{ key: 'realestate', kw: ['rentals','born','bh','beverly','hills','boulevard'], palette: ['#0f1e17','#7bb274'],
kicker: 'Property & Living', blurb: 'A local property, rentals & lifestyle concept for a sought-after market.',
features: ['Featured listings','Neighborhood guides','Concierge leasing'] },
{ key: 'wallcovering', kw: ['wallpaper','wallcoverings','wallpapers','bar','stock','lining','bleach'], palette: ['#1c1614','#b8865f'],
kicker: 'Surfaces & Design', blurb: 'A specialty wallcovering & surface-design concept — materials, patterns, and inspiration.',
features: ['Curated pattern library','Trade & designer focus','Samples & specification'] },
{ key: 'tech', kw: ['os','terminal','space','protection','ai','industries','civic','vc','petition','your'], palette: ['#0b1220','#5b8def'],
kicker: 'Platform & Tools', blurb: 'A modern platform concept — built to be fast, secure, and genuinely useful.',
features: ['Purpose-built workflows','Privacy-first by design','Built to scale'] },
];
function categorize(ws) {
for (const c of CATS) if (ws.some(w => c.kw.includes(w))) return c;
return { key: 'brand', kw: [], palette: ['#15171c','#9aa7b4'],
kicker: 'Available Concept', blurb: 'A distinctive, brandable domain ready for its next chapter.',
features: ['Short, memorable, brandable','Clean search & voice presence','Ready to build on'] };
}
const title = ws => ws.map(w => w ? w[0].toUpperCase() + w.slice(1) : w).join(' ');
function build(domain) {
const label = domain.replace(/\.[a-z]+$/i, '');
const ws = words(label);
const cat = categorize(ws);
return {
domain,
title: title(ws) || label,
kicker: cat.kicker,
category: cat.key,
tagline: cat.blurb,
features: cat.features,
palette: { bg: cat.palette[0], accent: cat.palette[1] },
ga4: '', // filled per-domain after GA4 property creation
};
}
module.exports = { build, words, title };
if (require.main === module) {
const fs = require('fs');
const list = JSON.parse(fs.readFileSync(__dirname + '/../data/undeveloped.json', 'utf8'));
const cfg = {};
for (const d of list) cfg[d] = build(d);
fs.writeFileSync(__dirname + '/../data/domains.json', JSON.stringify(cfg, null, 2));
const counts = {};
for (const d in cfg) counts[cfg[d].category] = (counts[cfg[d].category] || 0) + 1;
console.log(`built ${Object.keys(cfg).length} domain configs`);
console.log('by category:', counts);
}