← back to Domain Landings
server.js
360 lines
// Multi-tenant domain-landing server.
// One process serves a themed one-pager per Host header, each with GA4 + a
// "make an offer" inquiry form that routes to Steve via the George mailer
// (durably logged to data/inquiries.jsonl so a lead is never lost).
const http = require('http');
const fs = require('fs');
const path = require('path');
// minimal .env loader (so secrets live in a 600 file, not the process listing)
try {
const envf = path.join(__dirname, '.env');
if (fs.existsSync(envf)) {
for (const line of fs.readFileSync(envf, 'utf8').split('\n')) {
const m = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*)$/);
if (m && !process.env[m[1]]) process.env[m[1]] = m[2].replace(/^["']|["']$/g, '');
}
}
} catch {}
const PORT = Number(process.env.PORT || readPort() || 9788);
const OFFER_TO = process.env.OFFER_TO || 'steve@designerwallcoverings.com';
const GEORGE = process.env.GEORGE_URL || 'http://127.0.0.1:9850/api/send';
const GEORGE_TOKEN = process.env.GEORGE_TOKEN || ''; // set to enable email
const GEORGE_AUTH_SCHEME = process.env.GEORGE_AUTH_SCHEME || 'Bearer'; // Bearer | Basic | raw
const GEORGE_AUTH_HEADER = process.env.GEORGE_AUTH_HEADER || 'Authorization';
const BASIC_AUTH = process.env.BASIC_AUTH || 'admin:DW2024!'; // admin viewer gate
const META_PIXEL = process.env.META_PIXEL || '1431180262113856'; // DW Meta/Facebook pixel
const CFG = JSON.parse(fs.readFileSync(path.join(__dirname, 'data/domains.json'), 'utf8'));
const INQ = path.join(__dirname, 'data/inquiries.jsonl');
function readPort() { try { return Number(fs.readFileSync(path.join(__dirname, '.port'), 'utf8').trim()); } catch { return 0; } }
const esc = s => String(s == null ? '' : s).replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]));
// Per-brand SVG favicon: an accent-colored tile with the brand's initial.
// Without this, Chrome (and every browser) draws a blank/globe icon for a
// bookmark of the domain — the "my bookmark won't show" symptom.
function faviconSvg(cfg) {
const bg = (cfg && cfg.palette && cfg.palette.bg) || '#0d1117';
const accent = (cfg && cfg.palette && cfg.palette.accent) || '#6ca8ff';
const letter = esc(String((cfg && cfg.title) || 'A').trim().charAt(0).toUpperCase() || 'A');
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" rx="14" fill="${bg}"/><text x="32" y="45" font-family="Helvetica,Arial,sans-serif" font-size="38" font-weight="700" text-anchor="middle" fill="${accent}">${letter}</text></svg>`;
}
function hostOf(req) {
let h = (req.headers['x-forwarded-host'] || req.headers.host || '').split(',')[0].trim().toLowerCase();
h = h.replace(/:\d+$/, '').replace(/^www\./, '');
const u = new URL(req.url, 'http://x');
if (u.searchParams.get('__host')) h = u.searchParams.get('__host').toLowerCase(); // local preview
return h;
}
function page(cfg) {
const { bg, accent } = cfg.palette;
const ga = cfg.ga4 ? `<script async src="https://www.googletagmanager.com/gtag/js?id=${esc(cfg.ga4)}"></script>
<script>window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments)}gtag('js',new Date());gtag('config','${esc(cfg.ga4)}');</script>` : '';
const pixel = META_PIXEL ? `<script>!function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}(window,document,'script','https://connect.facebook.net/en_US/fbevents.js');fbq('init','${esc(META_PIXEL)}');fbq('track','PageView');</script>
<noscript><img height="1" width="1" style="display:none" src="https://www.facebook.com/tr?id=${esc(META_PIXEL)}&ev=PageView&noscript=1"/></noscript>` : '';
return `<!doctype html><html lang="en"><head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>${esc(cfg.title)}</title>
<meta name="description" content="${esc(cfg.tagline)}">
<link rel="icon" href="data:image/svg+xml,${encodeURIComponent(faviconSvg(cfg))}">
${ga}
${pixel}
<style>
:root{--bg:${bg};--accent:${accent}}
*{box-sizing:border-box;margin:0;padding:0}
body{font:16px/1.6 -apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;color:#eef2f6;background:var(--bg);-webkit-font-smoothing:antialiased}
.wrap{max-width:960px;margin:0 auto;padding:0 24px}
header{padding:28px 0;display:flex;justify-content:space-between;align-items:center}
.brand{font-weight:700;letter-spacing:.04em;font-size:18px}
.brand b{color:var(--accent)}
.pill{border:1px solid color-mix(in srgb,var(--accent) 55%,transparent);color:var(--accent);border-radius:999px;padding:8px 16px;font-size:13px;text-decoration:none;font-weight:600;transition:.2s}
.pill:hover{background:var(--accent);color:var(--bg)}
.hero{padding:72px 0 56px;text-align:center}
.kicker{text-transform:uppercase;letter-spacing:.22em;font-size:12px;color:var(--accent);font-weight:700;margin-bottom:20px}
h1{font-size:clamp(40px,7vw,72px);line-height:1.02;font-weight:800;letter-spacing:-.02em}
.lede{font-size:clamp(17px,2.4vw,21px);color:#c3ccd6;max-width:640px;margin:22px auto 0}
.cta{display:inline-flex;gap:12px;margin-top:36px}
.btn{background:var(--accent);color:var(--bg);border:0;border-radius:10px;padding:14px 26px;font-size:15px;font-weight:700;cursor:pointer;text-decoration:none;transition:.2s}
.btn:hover{filter:brightness(1.08);transform:translateY(-1px)}
.btn.ghost{background:transparent;color:#eef2f6;border:1px solid #ffffff30}
.feats{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:18px;padding:24px 0 72px}
.feat{background:#ffffff08;border:1px solid #ffffff12;border-radius:14px;padding:24px}
.feat h3{font-size:16px;margin-bottom:8px}.feat p{color:#aab6c2;font-size:14px}
.sale{border-top:1px solid #ffffff14;padding:56px 0;text-align:center}
.sale h2{font-size:26px;margin-bottom:10px}.sale p{color:#aab6c2;max-width:520px;margin:0 auto 26px}
form{max-width:440px;margin:0 auto;display:grid;gap:12px;text-align:left}
label{font-size:12px;color:#93a1ad;text-transform:uppercase;letter-spacing:.08em}
input,textarea{width:100%;background:#ffffff0d;border:1px solid #ffffff20;border-radius:9px;padding:12px 14px;color:#fff;font:inherit}
input:focus,textarea:focus{outline:0;border-color:var(--accent)}
.ok{color:var(--accent);font-weight:600;padding:14px;text-align:center}
footer{padding:36px 0;color:#67727d;font-size:13px;text-align:center;border-top:1px solid #ffffff10}
</style></head>
<body><div class="wrap">
<header><div class="brand">${esc(cfg.title)}<b>.</b></div>
<a class="pill" href="#offer">Buy this domain</a></header>
<section class="hero">
<div class="kicker">${esc(cfg.kicker)}</div>
<h1>${esc(cfg.title)}</h1>
<p class="lede">${esc(cfg.tagline)}</p>
<div class="cta"><a class="btn" href="#offer">Make an offer</a><a class="btn ghost" href="#about">Learn more</a></div>
</section>
<section class="feats" id="about">
${cfg.features.map(f => `<div class="feat"><h3>${esc(f)}</h3><p>Part of the ${esc(cfg.title)} concept.</p></div>`).join('')}
</section>
<section class="sale" id="offer">
<h2>This domain is available</h2>
<p><strong>${esc(cfg.domain)}</strong> is a brandable domain owned by Abrams. Interested in acquiring it? Send an offer and I'll be in touch.</p>
<form id="f" onsubmit="return send(event)">
<div><label>Your name</label><input name="name" required maxlength="80"></div>
<div><label>Email</label><input name="email" type="email" required maxlength="120"></div>
<div><label>Your offer (USD) & message</label><textarea name="message" rows="3" maxlength="1000" placeholder="I'd like to make an offer of $..."></textarea></div>
<button class="btn" type="submit">Send offer</button>
<div id="msg"></div>
</form>
</section>
<footer>© ${new Date().getFullYear()} Abrams · ${esc(cfg.domain)} · <a style="color:var(--accent)" href="#offer">Domain inquiries</a></footer>
</div>
<script>
async function send(e){e.preventDefault();var f=e.target,m=document.getElementById('msg');
var b={name:f.name.value,email:f.email.value,message:f.message.value};
m.textContent='Sending…';
try{var r=await fetch('/api/inquire',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify(b)});
if(r.ok){f.style.display='none';m.className='ok';m.textContent='Thanks — your offer was sent. I\\'ll be in touch shortly.';}
else{m.className='ok';m.textContent='Something went wrong — email steve@designerwallcoverings.com directly.';}}
catch(_){m.className='ok';m.textContent='Something went wrong — email steve@designerwallcoverings.com directly.';}
return false;}
</script>
</body></html>`;
}
function notFound(host) {
return `<!doctype html><meta charset=utf-8><title>Available domain</title>
<body style="font-family:system-ui;background:#15171c;color:#eef2f6;text-align:center;padding:80px">
<h1>${esc(host || 'This domain')}</h1><p style="color:#9aa7b4">This domain is available. Email <a style="color:#9aa7b4" href="mailto:steve@designerwallcoverings.com">steve@designerwallcoverings.com</a>.</p>`;
}
async function forwardGeorge(payload) {
return new Promise((resolve) => {
try {
const data = JSON.stringify(payload);
const u = new URL(GEORGE);
const headers = { 'content-type': 'application/json', 'content-length': Buffer.byteLength(data) };
if (GEORGE_TOKEN) {
let val;
if (GEORGE_AUTH_SCHEME === 'raw') val = GEORGE_TOKEN;
else if (GEORGE_AUTH_SCHEME === 'Basic') val = 'Basic ' + (GEORGE_TOKEN.includes(':') ? Buffer.from(GEORGE_TOKEN).toString('base64') : GEORGE_TOKEN);
else val = `${GEORGE_AUTH_SCHEME} ${GEORGE_TOKEN}`;
headers[GEORGE_AUTH_HEADER] = val;
}
const req = http.request({ hostname: u.hostname, port: u.port, path: u.pathname, method: 'POST',
headers, timeout: 8000 },
r => { let b = ''; r.on('data', c => b += c); r.on('end', () => resolve({ ok: r.statusCode < 400, code: r.statusCode, body: b.slice(0, 200) })); });
req.on('error', e => resolve({ ok: false, err: String(e.message) }));
req.on('timeout', () => { req.destroy(); resolve({ ok: false, err: 'timeout' }); });
req.write(data); req.end();
} catch (e) { resolve({ ok: false, err: String(e.message) }); }
});
}
function checkBasicAuth(req, res, realm) {
const want = 'Basic ' + Buffer.from(BASIC_AUTH).toString('base64');
if ((req.headers.authorization || '') !== want) {
res.writeHead(401, { 'WWW-Authenticate': `Basic realm="${realm}"` }).end('auth required');
return false;
}
return true;
}
function adminInquiries(req, res) {
if (!checkBasicAuth(req, res, 'offers')) return;
let rows = [];
try {
rows = fs.readFileSync(INQ, 'utf8').split('\n').filter(Boolean).map(l => JSON.parse(l)).reverse();
} catch {}
const fmt = ts => { try { return new Date(ts).toLocaleString('en-US', { year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' }); } catch { return ts; } };
const cards = rows.map(r => `<div class="card">
<div class="dom">${esc(r.domain)}</div>
<div class="when" title="${esc(r.ts)}">🕓 ${esc(fmt(r.ts))}</div>
<div class="who"><b>${esc(r.name)}</b> · <a href="mailto:${esc(r.email)}">${esc(r.email)}</a></div>
<div class="msg">${esc(r.message) || '<i>(no message)</i>'}</div></div>`).join('');
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
res.end(`<!doctype html><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1">
<title>Domain offers (${rows.length})</title>
<style>body{font:15px/1.5 -apple-system,system-ui,sans-serif;background:#0f1216;color:#e6edf3;margin:0;padding:24px}
h1{font-size:20px;margin:0 0 18px}.card{background:#161b22;border:1px solid #2a3038;border-radius:12px;padding:16px 18px;margin:0 0 12px;max-width:720px}
.dom{font-weight:700;color:#58a6ff;font-size:16px}.when{color:#8b949e;font-size:12px;margin:2px 0 8px}
.who{margin-bottom:6px}.who a{color:#58a6ff}.msg{color:#c9d1d9;white-space:pre-wrap}
.empty{color:#8b949e}</style>
<h1>💰 Domain offers — ${rows.length}</h1>
${cards || '<p class="empty">No offers yet.</p>'}`);
}
function adminWall(req, res) {
if (!checkBasicAuth(req, res, 'wall')) return;
let sites = [];
try { sites = JSON.parse(fs.readFileSync(path.join(__dirname, 'data/all-sites.json'), 'utf8')); } catch {}
// mark which domains actually have a thumbnail so the client never requests a missing one (no 404s)
let thumbSet = new Set();
try { thumbSet = new Set(fs.readdirSync(path.join(__dirname, 'data/thumbs')).filter(f => f.endsWith('.jpg')).map(f => f.slice(0, -4))); } catch {}
let frameBlocked = new Set();
try { frameBlocked = new Set(JSON.parse(fs.readFileSync(path.join(__dirname, 'data/_frame_blocked.json'), 'utf8'))); } catch {}
// th = has a thumbnail; fr = frameable (0 = refuses embedding, show "open directly" instead of a blank iframe)
sites = sites.map(s => ({ ...s, th: thumbSet.has(s.domain) ? 1 : 0, fr: frameBlocked.has(s.domain) ? 0 : 1 }));
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
res.end(`<!doctype html><html><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1">
<title>Portfolio wall — ${sites.length} sites</title>
<style>
:root{--cols:4}
*{box-sizing:border-box;margin:0;padding:0}
body{font:14px/1.4 -apple-system,system-ui,sans-serif;background:#0d1117;color:#e6edf3}
header{position:sticky;top:0;z-index:10;background:#0d1117ee;backdrop-filter:blur(8px);border-bottom:1px solid #21262d;padding:12px 18px;display:flex;gap:16px;align-items:center;flex-wrap:wrap}
h1{font-size:16px;font-weight:700}h1 b{color:#58a6ff}
label{font-size:11px;color:#8b949e;text-transform:uppercase;letter-spacing:.06em;margin-right:6px}
select,input[type=range]{vertical-align:middle}
select{background:#161b22;color:#e6edf3;border:1px solid #30363d;border-radius:7px;padding:6px 10px;font:inherit}
input[type=range]{width:150px}
.filter{background:#161b22;color:#e6edf3;border:1px solid #30363d;border-radius:7px;padding:6px 10px}
.grid{display:grid;grid-template-columns:repeat(var(--cols),1fr);gap:14px;padding:18px}
.card{background:#161b22;border:1px solid #21262d;border-radius:12px;overflow:hidden;display:flex;flex-direction:column}
.frame{position:relative;width:100%;aspect-ratio:4/3;overflow:hidden;background:#0d1117;border-bottom:1px solid #21262d;cursor:pointer}
.frame img{width:100%;height:100%;object-fit:cover;object-position:top center;display:block}
.frame iframe{position:absolute;top:0;left:0;width:1280px;height:960px;border:0;transform-origin:0 0;pointer-events:none;background:#fff}
.ph{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:#484f58;font-size:12px;text-align:center;padding:8px}
.live{position:absolute;top:6px;right:6px;background:#1f6febcc;color:#fff;font-size:9px;padding:2px 7px;border-radius:999px;opacity:0;transition:.15s}
.frame:hover .live{opacity:1}
.openbtn{position:absolute;top:6px;left:6px;background:#238636dd;color:#fff;border:0;font-size:9px;font-family:inherit;padding:2px 8px;border-radius:999px;cursor:pointer;opacity:.9;transition:.15s;z-index:3}
.openbtn:hover{background:#2ea043;opacity:1}
.meta a.open{color:#8b949e;text-decoration:none;font-size:12px;padding:0 2px;flex:0 0 auto}
.meta a.open:hover{color:#7ee787}
.meta{padding:9px 11px;display:flex;justify-content:space-between;align-items:center;gap:8px}
.dom{font-weight:600;font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.dom a{color:#e6edf3;text-decoration:none}.dom a:hover{color:#58a6ff}
.badge{font-size:9px;text-transform:uppercase;letter-spacing:.04em;padding:2px 7px;border-radius:999px;white-space:nowrap}
.b-landing{background:#1f6feb33;color:#79c0ff}.b-developed{background:#23863633;color:#7ee787}
.b-other{background:#6e768133;color:#c9d1d9}.b-excluded{background:#f8514933;color:#ff7b72}
.count{color:#8b949e;font-size:12px;margin-left:auto}
</style></head><body>
<header>
<h1>Portfolio wall<b>.</b></h1>
<div><label>Sort</label><select id=sort>
<option value=name>Name A→Z</option><option value=type>Type</option>
<option value=landing>Landings first</option><option value=developed>Developed first</option></select></div>
<div><label>Type</label><select id=type class=filter>
<option value=all>All (${sites.length})</option><option value=landing>Landings</option>
<option value=developed>Developed</option><option value=other>Other</option><option value=excluded>Excluded</option></select></div>
<div><label>Density</label><input type=range id=dens min=2 max=8 step=1></div>
<span class=count id=count></span>
</header>
<div class=grid id=grid></div>
<script>
const SITES=${JSON.stringify(sites)};
const grid=document.getElementById('grid'), sortEl=document.getElementById('sort'),
typeEl=document.getElementById('type'), densEl=document.getElementById('dens'), countEl=document.getElementById('count');
const LS=(k,d)=>localStorage.getItem('wall_'+k)||d;
sortEl.value=LS('sort','name'); typeEl.value=LS('type','all'); densEl.value=LS('dens','4');
function applyDens(){document.documentElement.style.setProperty('--cols',densEl.value);
const cw=grid.querySelector('.frame'); if(cw){const s=cw.clientWidth/1280;
document.querySelectorAll('.frame iframe').forEach(f=>f.style.transform='scale('+s+')');}}
// click a tile -> swap the thumbnail for the LIVE iframe (with blocked-embed detection)
const LIVE=new Set();
function goLive(f){ if(f.dataset.live)return; const dom=f.dataset.dom;
if(f.dataset.fr==='0'){ // known to refuse embedding -> open-directly, don't load a doomed blank iframe
window.open('https://'+dom+'/','_blank','noopener'); return; }
f.dataset.live=1; LIVE.add(dom);
const if_=document.createElement('iframe'); if_.src='https://'+dom+'/';
if_.sandbox='allow-scripts allow-same-origin'; if_.style.transform='scale('+(f.clientWidth/1280)+')';
let loaded=false; if_.addEventListener('load',()=>loaded=true);
f.innerHTML=''; f.appendChild(if_);
// X-Frame-Options / frame-ancestors block => load never fires. Give feedback + a way out.
setTimeout(()=>{ if(!loaded){ f.dataset.live=''; LIVE.delete(dom);
f.innerHTML='<div class=ph>'+dom+'<br><small>⚠ blocks embedding — <a href="https://'+dom+'/" target=_blank rel=noopener style="color:#58a6ff">open directly ↗</a></small></div>'; } },3000); }
function render(){
const t=typeEl.value; let list=SITES.filter(s=>t==='all'||s.type===t);
const sv=sortEl.value;
if(sv==='name')list.sort((a,b)=>a.domain.localeCompare(b.domain));
else if(sv==='type')list.sort((a,b)=>a.type.localeCompare(b.type)||a.domain.localeCompare(b.domain));
else list.sort((a,b)=>(b.type===sv)-(a.type===sv)||a.domain.localeCompare(b.domain));
countEl.textContent=list.length+' shown';
grid.innerHTML=list.map(s=>'<div class=card><div class=frame data-dom="'+s.domain+'" data-fr="'+s.fr+'" title="'+(s.fr?'click for live view':'this site blocks embedding — click to open')+'">'+
(s.th?'<img loading=lazy src="/thumbs/'+s.domain+'.jpg" onerror="this.style.display=\\'none\\';this.nextElementSibling.style.display=\\'flex\\'">':'')+
'<div class=ph'+(s.th?' style=display:none':'')+'>'+s.domain+'<br><small>(no preview — click for live)</small></div>'+
'<span class=live>▶ live</span>'+
'<button class=openbtn data-url="https://'+s.domain+'/" title="open '+s.domain+' in a new window">↗ open</button></div>'+
'<div class=meta><span class=dom><a href="https://'+s.domain+'/" target=_blank rel=noopener>'+s.domain+'</a></span>'+
'<a class=open href="https://'+s.domain+'/" target=_blank rel=noopener title="open in a new window">↗</a>'+
'<span class="badge b-'+s.type+'">'+s.type+'</span></div></div>').join('');
grid.querySelectorAll('.frame').forEach(f=>{f.addEventListener('click',()=>goLive(f));
if(LIVE.has(f.dataset.dom)){f.dataset.live='';goLive(f);}}); // restore live iframes after re-render
// explicit open-in-new-window per chip (bypasses the click-to-iframe hijack; the reliable escape hatch for sites that refuse embedding)
grid.querySelectorAll('.openbtn').forEach(b=>b.addEventListener('click',e=>{e.stopPropagation();window.open(b.dataset.url,'_blank','noopener');}));
applyDens();
}
sortEl.onchange=()=>{localStorage.setItem('wall_sort',sortEl.value);render()};
typeEl.onchange=()=>{localStorage.setItem('wall_type',typeEl.value);render()};
densEl.oninput=()=>{localStorage.setItem('wall_dens',densEl.value);applyDens()};
render(); window.addEventListener('resize',applyDens);
</script></body></html>`);
}
const WALL_HOST = process.env.WALL_HOST || 'wall.agentabrams.com'; // the wall lives ONLY here — never on butler/tenant domains
const server = http.createServer((req, res) => {
const host = hostOf(req);
// Host-scope the admin surface: /admin/* and /thumbs/ answer ONLY on the wall host (plus localhost for dev).
// Every tenant landing domain (818butler.com, beverlyhillsbutler.com, the 200+ others) gets a normal 404 here.
// use the REAL Host header (not the ?__host= preview override) so the admin gate can't be spoofed from a tenant domain
const rawHost = (req.headers['x-forwarded-host'] || req.headers.host || '').split(',')[0].trim().toLowerCase().replace(/:\d+$/, '').replace(/^www\./, '');
const onWallHost = rawHost === WALL_HOST || rawHost === 'localhost' || rawHost === '127.0.0.1';
if (!onWallHost && (req.url.startsWith('/admin/') || req.url.startsWith('/thumbs/'))) {
res.writeHead(404, { 'content-type': 'text/html; charset=utf-8' });
return res.end(notFound(host));
}
if (req.method === 'GET' && req.url.split('?')[0] === '/admin/wall') {
return adminWall(req, res);
}
if (req.method === 'GET' && req.url.startsWith('/thumbs/')) {
// same gate as the wall — the authenticated wall page's <img> requests carry the creds
if (!checkBasicAuth(req, res, 'wall')) return;
const name = path.basename(req.url.split('?')[0]); // <domain>.jpg
if (!/^[a-z0-9.\-]+\.jpg$/i.test(name)) { res.writeHead(400).end(); return; }
const fp = path.join(__dirname, 'data/thumbs', name);
fs.readFile(fp, (e, buf) => {
if (e) { res.writeHead(404).end(); return; }
res.writeHead(200, { 'content-type': 'image/jpeg', 'cache-control': 'public, max-age=86400' }).end(buf);
});
return;
}
if (req.method === 'GET' && req.url.split('?')[0] === '/admin/inquiries') {
return adminInquiries(req, res);
}
if (req.method === 'POST' && req.url.split('?')[0] === '/api/inquire') {
let body = '';
req.on('data', c => { body += c; if (body.length > 1e5) req.destroy(); });
req.on('end', async () => {
let d = {}; try { d = JSON.parse(body); } catch {}
const rec = { ts: new Date().toISOString(), domain: host, name: String(d.name || '').slice(0, 80),
email: String(d.email || '').slice(0, 120), message: String(d.message || '').slice(0, 1000) };
if (!rec.email || !rec.name) { res.writeHead(400).end('{"ok":false}'); return; }
try { fs.appendFileSync(INQ, JSON.stringify(rec) + '\n'); } catch {} // durable first — never lose a lead
const mailBody = `New domain inquiry for ${rec.domain}\n\nName: ${rec.name}\nEmail: ${rec.email}\n\n${rec.message}\n\n(logged ${rec.ts})`;
const mail = { to: OFFER_TO, subject: `Domain offer: ${rec.domain}`, body: mailBody, text: mailBody };
const g = await forwardGeorge(mail);
const debug = req.headers['x-debug'] === '1' ? { mail_code: g.code, mail_note: (g.body || g.err || '').slice(0, 120) } : {};
res.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify({ ok: true, mailed: g.ok, ...debug }));
});
return;
}
if (req.method === 'GET' && req.url.split('?')[0] === '/favicon.ico') {
// real icon so Chrome's automatic /favicon.ico probe stops getting HTML
res.writeHead(200, { 'content-type': 'image/svg+xml', 'cache-control': 'public, max-age=86400' });
return res.end(faviconSvg(CFG[host]));
}
const cfg = CFG[host];
res.writeHead(cfg ? 200 : 404, { 'content-type': 'text/html; charset=utf-8' });
res.end(cfg ? page(cfg) : notFound(host));
});
server.listen(PORT, () => console.log(`domain-landings on :${PORT} · ${Object.keys(CFG).length} domains · offers→${OFFER_TO}`));