[object Object]

← back to Coming Soon Template

coming-soon template: Gucci-aesthetic Editorial landing + manifest for 21 domains + shared email-signup endpoint

d3214b02fb3a96525ff13d6b0d5722a4ce37365b · 2026-05-13 09:42:05 -0700 · SteveStudio2

Files touched

Diff

commit d3214b02fb3a96525ff13d6b0d5722a4ce37365b
Author: SteveStudio2 <steve@designerwallcoverings.com>
Date:   Wed May 13 09:42:05 2026 -0700

    coming-soon template: Gucci-aesthetic Editorial landing + manifest for 21 domains + shared email-signup endpoint
---
 .gitignore             |   6 +
 email-signup-server.js | 153 +++++++++++++++++++++++++
 manifest.json          | 244 ++++++++++++++++++++++++++++++++++++++++
 scripts/build.js       |  89 +++++++++++++++
 template.html          | 298 +++++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 790 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c4dce82
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+node_modules/
+.env*
+tmp/
+*.log
+.DS_Store
+out/
diff --git a/email-signup-server.js b/email-signup-server.js
new file mode 100644
index 0000000..69d32cf
--- /dev/null
+++ b/email-signup-server.js
@@ -0,0 +1,153 @@
+#!/usr/bin/env node
+/**
+ * Shared email-signup endpoint for the 21 Gucci-coming-soon pages.
+ *
+ * Listens at POST /api/email-signup
+ *   body: { email, source, timestamp }
+ *   returns: 200 { ok:true } | 400 { error } | 429 { error } | 500
+ *
+ * Storage: append-only JSONL at ~/data/email-signups.jsonl (or DATA_FILE env).
+ * Rate-limit: max 1 signup per IP per email per 60s (in-memory window).
+ * CORS: allows the 21 known domains + agentabrams.com itself.
+ *
+ * Designed to run on Kamatera behind nginx at agentabrams.com/api/email-signup.
+ * Zero deps. Bind defaults to 127.0.0.1:9697 (override with PORT) so nginx
+ * proxies in; never expose this port publicly.
+ *
+ * Future: ELEVENLABS-free Steve-voice confirmation, webhook to Postgres, etc.
+ */
+
+'use strict';
+
+const http = require('http');
+const fs = require('fs');
+const path = require('path');
+
+const PORT = parseInt(process.env.PORT, 10) || 9697;
+const HOST = process.env.HOST || '127.0.0.1';
+const DATA_FILE = process.env.DATA_FILE || path.join(process.env.HOME || '/root', 'data', 'email-signups.jsonl');
+
+const ALLOWED_ORIGINS = new Set([
+  'https://agentabrams.com',
+  // abrams.* cluster
+  'https://abramsos.com', 'https://abramsintel.com', 'https://abramsintelligence.com',
+  'https://abramsspace.com', 'https://abramsterminal.com', 'https://abramsvc.com',
+  'https://abramsindustries.com', 'https://abramsprotection.com', 'https://abramscivic.com',
+  'https://abramslive.com', 'https://abramsatlas.com', 'https://abramsdirectory.com',
+  'https://abramsguide.com', 'https://abramsindex.com', 'https://abramslocal.com',
+  'https://abramsmaps.com', 'https://abramsmarkets.com',
+  // butler cluster
+  'https://818butler.com', 'https://beverlyhillsbutler.com',
+  'https://bhbutler.com', 'https://boulevardbutler.com',
+]);
+
+// Rate-limit window (in-memory)
+const WINDOW_MS = 60_000;
+const seen = new Map();   // key = ip|email → expiresAt
+
+function readBody(req, max = 4096) {
+  return new Promise((resolve, reject) => {
+    let total = 0;
+    const chunks = [];
+    req.on('data', c => {
+      total += c.length;
+      if (total > max) { req.destroy(); reject(new Error('payload too large')); return; }
+      chunks.push(c);
+    });
+    req.on('end', () => {
+      try { resolve(JSON.parse(Buffer.concat(chunks).toString('utf8') || '{}')); }
+      catch { reject(new Error('invalid json')); }
+    });
+    req.on('error', reject);
+  });
+}
+
+function ipOf(req) {
+  return (req.headers['x-forwarded-for'] || '').split(',')[0].trim()
+      || (req.socket && req.socket.remoteAddress) || 'unknown';
+}
+
+const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+
+function send(res, status, body, extraHeaders) {
+  const headers = Object.assign({
+    'Content-Type': 'application/json',
+    'Cache-Control': 'no-store',
+    'X-Content-Type-Options': 'nosniff',
+  }, extraHeaders || {});
+  res.writeHead(status, headers);
+  res.end(typeof body === 'string' ? body : JSON.stringify(body));
+}
+
+function corsHeaders(origin) {
+  if (origin && ALLOWED_ORIGINS.has(origin)) {
+    return {
+      'Access-Control-Allow-Origin': origin,
+      'Access-Control-Allow-Methods': 'POST, OPTIONS',
+      'Access-Control-Allow-Headers': 'Content-Type',
+      'Access-Control-Max-Age': '600',
+      'Vary': 'Origin',
+    };
+  }
+  return { 'Vary': 'Origin' };
+}
+
+function ensureDataDir() {
+  const dir = path.dirname(DATA_FILE);
+  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
+}
+ensureDataDir();
+
+const server = http.createServer(async (req, res) => {
+  const origin = req.headers.origin;
+  const cors = corsHeaders(origin);
+
+  if (req.method === 'OPTIONS') { send(res, 204, '', cors); return; }
+
+  if (req.method === 'GET' && req.url === '/api/health') {
+    return send(res, 200, { ok: true, signups_file: DATA_FILE }, cors);
+  }
+
+  if (req.method !== 'POST' || req.url !== '/api/email-signup') {
+    return send(res, 404, { error: 'not found' }, cors);
+  }
+
+  let body;
+  try { body = await readBody(req); }
+  catch (e) { return send(res, 400, { error: e.message }, cors); }
+
+  const email = String(body.email || '').trim().toLowerCase();
+  const source = String(body.source || '').trim().toLowerCase();
+  const ts = body.timestamp ? String(body.timestamp).slice(0, 40) : new Date().toISOString();
+
+  if (!email || !EMAIL_RE.test(email)) return send(res, 400, { error: 'invalid email' }, cors);
+  if (!source) return send(res, 400, { error: 'missing source' }, cors);
+  if (email.length > 320 || source.length > 80) return send(res, 400, { error: 'fields too long' }, cors);
+
+  const ip = ipOf(req);
+  const key = ip + '|' + email;
+  const now = Date.now();
+  // Sweep expired entries cheaply (every ~100 requests this loops; otherwise just delete on hit)
+  if (seen.has(key) && seen.get(key) > now) {
+    return send(res, 429, { error: 'duplicate within window' }, cors);
+  }
+  seen.set(key, now + WINDOW_MS);
+  if (seen.size > 5000) {
+    for (const [k, exp] of seen) if (exp <= now) seen.delete(k);
+  }
+
+  const row = { email, source, timestamp: ts, ip, ua: String(req.headers['user-agent'] || '').slice(0, 200) };
+  try {
+    fs.appendFileSync(DATA_FILE, JSON.stringify(row) + '\n');
+  } catch (e) {
+    console.error('append failed:', e.message);
+    return send(res, 500, { error: 'storage failure' }, cors);
+  }
+
+  send(res, 200, { ok: true }, cors);
+});
+
+server.listen(PORT, HOST, () => {
+  console.log(`email-signup listening on http://${HOST}:${PORT}/api/email-signup`);
+  console.log(`writing to ${DATA_FILE}`);
+});
diff --git a/manifest.json b/manifest.json
new file mode 100644
index 0000000..0cbc5d2
--- /dev/null
+++ b/manifest.json
@@ -0,0 +1,244 @@
+{
+  "_comment": "Per-domain Gucci-coming-soon template instance data. Hero URLs are Unsplash CC0 editorial photos (per Steve's standing-rule: public-domain/CC0 only). Replace any hero with a Steve-owned photo by swapping the hero_url field. WORDMARK and DOMAIN must match what's bought; tagline is the editorial line under it. ACCENT_HEX is the chrome accent (keep neutral or match hero).",
+  "endpoint": "https://agentabrams.com/api/email-signup",
+  "deploy_root": "/var/www/{{DOMAIN}}/index.html",
+  "year": "2026",
+  "domains": [
+    {
+      "domain": "abramsos.com",
+      "wordmark": "abramsos",
+      "wordmark_initial": "a",
+      "tagline": "the operating system",
+      "page_title": "abramsos — coming soon",
+      "hero_url": "https://images.unsplash.com/photo-1518770660439-4636190af475?auto=format&fit=crop&w=2560&q=85",
+      "hero_alt": "Editorial silver-grey architectural detail",
+      "accent_hex": "#3a3a3a",
+      "category": "thin-landing"
+    },
+    {
+      "domain": "abramsintel.com",
+      "wordmark": "abramsintel",
+      "wordmark_initial": "a",
+      "tagline": "briefings",
+      "page_title": "abramsintel — coming soon",
+      "hero_url": "https://images.unsplash.com/photo-1486312338219-ce68d2c6f44d?auto=format&fit=crop&w=2560&q=85",
+      "hero_alt": "Backlit study with paper documents in soft monochrome",
+      "accent_hex": "#2c2a26",
+      "category": "thin-landing"
+    },
+    {
+      "domain": "abramsintelligence.com",
+      "wordmark": "abramsintelligence",
+      "wordmark_initial": "a",
+      "tagline": "briefings",
+      "page_title": "abramsintelligence — coming soon",
+      "hero_url": "https://images.unsplash.com/photo-1486312338219-ce68d2c6f44d?auto=format&fit=crop&w=2560&q=85",
+      "hero_alt": "Backlit study with paper documents in soft monochrome",
+      "accent_hex": "#2c2a26",
+      "category": "thin-landing",
+      "note": "301-canonical to abramsintel.com — page is fallback for direct hits"
+    },
+    {
+      "domain": "abramsspace.com",
+      "wordmark": "abramsspace",
+      "wordmark_initial": "a",
+      "tagline": "workspace",
+      "page_title": "abramsspace — coming soon",
+      "hero_url": "https://images.unsplash.com/photo-1497366216548-37526070297c?auto=format&fit=crop&w=2560&q=85",
+      "hero_alt": "Empty modern workspace with morning light",
+      "accent_hex": "#4a463d",
+      "category": "thin-landing"
+    },
+    {
+      "domain": "abramsterminal.com",
+      "wordmark": "abramsterminal",
+      "wordmark_initial": "a",
+      "tagline": "command line access",
+      "page_title": "abramsterminal — coming soon",
+      "hero_url": "https://images.unsplash.com/photo-1517694712202-14dd9538aa97?auto=format&fit=crop&w=2560&q=85",
+      "hero_alt": "Dark terminal with monospace code, editorial low-key",
+      "accent_hex": "#1a1a1a",
+      "category": "thin-landing"
+    },
+    {
+      "domain": "abramsvc.com",
+      "wordmark": "abramsvc",
+      "wordmark_initial": "a",
+      "tagline": "venture portfolio",
+      "page_title": "abramsvc — coming soon",
+      "hero_url": "https://images.unsplash.com/photo-1521737711867-e3b97375f902?auto=format&fit=crop&w=2560&q=85",
+      "hero_alt": "Aerial board-meeting framing in soft warm tones",
+      "accent_hex": "#3d362a",
+      "category": "thin-landing"
+    },
+    {
+      "domain": "abramsindustries.com",
+      "wordmark": "abramsindustries",
+      "wordmark_initial": "a",
+      "tagline": "the umbrella",
+      "page_title": "abramsindustries — coming soon",
+      "hero_url": "https://images.unsplash.com/photo-1503387762-592deb58ef4e?auto=format&fit=crop&w=2560&q=85",
+      "hero_alt": "Industrial skyline in muted dawn",
+      "accent_hex": "#2e2c28",
+      "category": "thin-landing"
+    },
+    {
+      "domain": "abramsprotection.com",
+      "wordmark": "abramsprotection",
+      "wordmark_initial": "a",
+      "tagline": "guardian layer",
+      "page_title": "abramsprotection — coming soon",
+      "hero_url": "https://images.unsplash.com/photo-1550745165-9bc0b252726f?auto=format&fit=crop&w=2560&q=85",
+      "hero_alt": "Quiet vault interior in monochrome",
+      "accent_hex": "#1f1d1b",
+      "category": "thin-landing"
+    },
+    {
+      "domain": "abramscivic.com",
+      "wordmark": "abramscivic",
+      "wordmark_initial": "a",
+      "tagline": "directories for the public good",
+      "page_title": "abramscivic — coming soon",
+      "hero_url": "https://images.unsplash.com/photo-1486325212027-8081e485255e?auto=format&fit=crop&w=2560&q=85",
+      "hero_alt": "Civic colonnade in even daylight",
+      "accent_hex": "#3a342c",
+      "category": "thin-landing"
+    },
+    {
+      "domain": "abramslive.com",
+      "wordmark": "abramslive",
+      "wordmark_initial": "a",
+      "tagline": "live builds, in public",
+      "page_title": "abramslive — coming soon",
+      "hero_url": "https://images.unsplash.com/photo-1531297484001-80022131f5a1?auto=format&fit=crop&w=2560&q=85",
+      "hero_alt": "Late-night studio screen-glow, soft and grainy",
+      "accent_hex": "#1b1c1f",
+      "category": "thin-landing"
+    },
+    {
+      "domain": "abramsatlas.com",
+      "wordmark": "abramsatlas",
+      "wordmark_initial": "a",
+      "tagline": "the map",
+      "page_title": "abramsatlas — coming soon",
+      "hero_url": "https://images.unsplash.com/photo-1524661135-423995f22d0b?auto=format&fit=crop&w=2560&q=85",
+      "hero_alt": "Antique atlas spread, sepia editorial crop",
+      "accent_hex": "#4a3f2a",
+      "category": "redirect-soon",
+      "note": "Page is short-lived — 301s to agentabrams.com once site is live"
+    },
+    {
+      "domain": "abramsdirectory.com",
+      "wordmark": "abramsdirectory",
+      "wordmark_initial": "a",
+      "tagline": "the index",
+      "page_title": "abramsdirectory — coming soon",
+      "hero_url": "https://images.unsplash.com/photo-1481627834876-b7833e8f5570?auto=format&fit=crop&w=2560&q=85",
+      "hero_alt": "Library card catalog drawers, cool monochrome",
+      "accent_hex": "#2a261f",
+      "category": "redirect-soon"
+    },
+    {
+      "domain": "abramsguide.com",
+      "wordmark": "abramsguide",
+      "wordmark_initial": "a",
+      "tagline": "the guide",
+      "page_title": "abramsguide — coming soon",
+      "hero_url": "https://images.unsplash.com/photo-1495446815901-a7297e633e8d?auto=format&fit=crop&w=2560&q=85",
+      "hero_alt": "Open atlas with margin notes in warm light",
+      "accent_hex": "#3d352a",
+      "category": "redirect-soon"
+    },
+    {
+      "domain": "abramsindex.com",
+      "wordmark": "abramsindex",
+      "wordmark_initial": "a",
+      "tagline": "the ledger",
+      "page_title": "abramsindex — coming soon",
+      "hero_url": "https://images.unsplash.com/photo-1457369804613-52c61a468e7d?auto=format&fit=crop&w=2560&q=85",
+      "hero_alt": "Stacked books in editorial low-light",
+      "accent_hex": "#2c241c",
+      "category": "redirect-soon"
+    },
+    {
+      "domain": "abramslocal.com",
+      "wordmark": "abramslocal",
+      "wordmark_initial": "a",
+      "tagline": "the neighborhood",
+      "page_title": "abramslocal — coming soon",
+      "hero_url": "https://images.unsplash.com/photo-1502672023488-70e25813eb80?auto=format&fit=crop&w=2560&q=85",
+      "hero_alt": "Quiet residential street, sunrise palette",
+      "accent_hex": "#3d3a30",
+      "category": "redirect-soon"
+    },
+    {
+      "domain": "abramsmaps.com",
+      "wordmark": "abramsmaps",
+      "wordmark_initial": "a",
+      "tagline": "the cartographer",
+      "page_title": "abramsmaps — coming soon",
+      "hero_url": "https://images.unsplash.com/photo-1524661135-423995f22d0b?auto=format&fit=crop&w=2560&q=85",
+      "hero_alt": "Antique map detail with compass rose",
+      "accent_hex": "#4a3f2a",
+      "category": "redirect-soon"
+    },
+    {
+      "domain": "abramsmarkets.com",
+      "wordmark": "abramsmarkets",
+      "wordmark_initial": "a",
+      "tagline": "the floor",
+      "page_title": "abramsmarkets — coming soon",
+      "hero_url": "https://images.unsplash.com/photo-1559526324-4b87b5e36e44?auto=format&fit=crop&w=2560&q=85",
+      "hero_alt": "Marketplace stalls in early-morning low light",
+      "accent_hex": "#322a1e",
+      "category": "redirect-soon"
+    },
+
+    {
+      "domain": "818butler.com",
+      "wordmark": "818butler",
+      "wordmark_initial": "b",
+      "tagline": "white-glove service · 818",
+      "page_title": "818butler — coming soon",
+      "hero_url": "https://images.unsplash.com/photo-1597714026720-8f74c62310ba?auto=format&fit=crop&w=2560&q=85",
+      "hero_alt": "Interior of a chauffeured town car, warm leather",
+      "accent_hex": "#2a221a",
+      "category": "thin-landing"
+    },
+    {
+      "domain": "beverlyhillsbutler.com",
+      "wordmark": "beverlyhillsbutler",
+      "wordmark_initial": "b",
+      "tagline": "white-glove service · Beverly Hills",
+      "page_title": "beverlyhillsbutler — coming soon",
+      "hero_url": "https://images.unsplash.com/photo-1564013799919-ab600027ffc6?auto=format&fit=crop&w=2560&q=85",
+      "hero_alt": "Quiet private residence porte-cochère at dusk",
+      "accent_hex": "#1f1c17",
+      "category": "thin-landing",
+      "note": "Already has its own pm2 process — may keep that one as canonical"
+    },
+    {
+      "domain": "bhbutler.com",
+      "wordmark": "bhbutler",
+      "wordmark_initial": "b",
+      "tagline": "white-glove service",
+      "page_title": "bhbutler — coming soon",
+      "hero_url": "https://images.unsplash.com/photo-1564013799919-ab600027ffc6?auto=format&fit=crop&w=2560&q=85",
+      "hero_alt": "Quiet private residence porte-cochère at dusk",
+      "accent_hex": "#1f1c17",
+      "category": "thin-landing",
+      "note": "Short-form alias of beverlyhillsbutler.com — may 301 to it"
+    },
+    {
+      "domain": "boulevardbutler.com",
+      "wordmark": "boulevardbutler",
+      "wordmark_initial": "b",
+      "tagline": "white-glove service · the boulevards",
+      "page_title": "boulevardbutler — coming soon",
+      "hero_url": "https://images.unsplash.com/photo-1503376780353-7e6692767b70?auto=format&fit=crop&w=2560&q=85",
+      "hero_alt": "Empty boulevard at first light, soft warm grade",
+      "accent_hex": "#322a1e",
+      "category": "thin-landing"
+    }
+  ]
+}
diff --git a/scripts/build.js b/scripts/build.js
new file mode 100644
index 0000000..599b3da
--- /dev/null
+++ b/scripts/build.js
@@ -0,0 +1,89 @@
+#!/usr/bin/env node
+/**
+ * Instantiates template.html per-domain from manifest.json → out/<domain>/index.html
+ *
+ * Usage:
+ *   node scripts/build.js                    # build all
+ *   node scripts/build.js abramsos.com       # build one
+ *   node scripts/build.js --deploy           # also rsync to Kamatera /var/www/<domain>/
+ *
+ * Swap points (case-sensitive, double-curly):
+ *   {{WORDMARK}} {{WORDMARK_INITIAL}} {{TAGLINE}} {{HERO_URL}} {{HERO_ALT}}
+ *   {{DOMAIN}} {{PAGE_TITLE}} {{ACCENT_HEX}} {{YEAR}}
+ */
+
+'use strict';
+
+const fs = require('fs');
+const path = require('path');
+const { execSync } = require('child_process');
+
+const ROOT = path.resolve(__dirname, '..');
+const TEMPLATE = path.join(ROOT, 'template.html');
+const MANIFEST = path.join(ROOT, 'manifest.json');
+const OUT = path.join(ROOT, 'out');
+
+const args = process.argv.slice(2);
+const deploy = args.includes('--deploy');
+const onlyDomain = args.find(a => !a.startsWith('--'));
+
+if (!fs.existsSync(TEMPLATE)) { console.error('template.html missing'); process.exit(1); }
+if (!fs.existsSync(MANIFEST)) { console.error('manifest.json missing'); process.exit(1); }
+
+const tpl = fs.readFileSync(TEMPLATE, 'utf8');
+const manifest = JSON.parse(fs.readFileSync(MANIFEST, 'utf8'));
+const year = manifest.year || new Date().getFullYear();
+
+function instantiate(entry) {
+  let out = tpl;
+  const subs = {
+    WORDMARK: entry.wordmark,
+    WORDMARK_INITIAL: entry.wordmark_initial || entry.wordmark.slice(0, 1),
+    TAGLINE: entry.tagline,
+    HERO_URL: entry.hero_url,
+    HERO_ALT: entry.hero_alt || (entry.wordmark + ' coming soon'),
+    DOMAIN: entry.domain,
+    PAGE_TITLE: entry.page_title || (entry.wordmark + ' — coming soon'),
+    ACCENT_HEX: entry.accent_hex || '#3a3a3a',
+    YEAR: String(year),
+  };
+  for (const [k, v] of Object.entries(subs)) {
+    out = out.replaceAll('{{' + k + '}}', v);
+  }
+  return out;
+}
+
+const targets = onlyDomain
+  ? manifest.domains.filter(d => d.domain === onlyDomain)
+  : manifest.domains;
+
+if (!targets.length) {
+  console.error(onlyDomain ? `no manifest entry for ${onlyDomain}` : 'no domains in manifest');
+  process.exit(1);
+}
+
+let built = 0;
+for (const entry of targets) {
+  const html = instantiate(entry);
+  const dir = path.join(OUT, entry.domain);
+  fs.mkdirSync(dir, { recursive: true });
+  fs.writeFileSync(path.join(dir, 'index.html'), html);
+  built++;
+  process.stdout.write(`  built  ${entry.domain.padEnd(28)}  ${entry.tagline}\n`);
+}
+console.log(`\n✓ ${built} page${built === 1 ? '' : 's'} built into ${OUT}/`);
+
+if (deploy) {
+  const REMOTE = 'root@45.61.58.125';
+  console.log(`\n→ rsync to ${REMOTE} (/var/www/<domain>/)…`);
+  for (const entry of targets) {
+    const src = path.join(OUT, entry.domain, 'index.html');
+    const dest = REMOTE + ':/var/www/' + entry.domain + '/index.html';
+    try {
+      execSync(`rsync -az --mkpath "${src}" "${dest}"`, { stdio: 'inherit' });
+    } catch (e) {
+      console.error(`  ✗ ${entry.domain} — ${e.message}`);
+    }
+  }
+  console.log('✓ deploy complete (vhosts must point at these paths)');
+}
diff --git a/template.html b/template.html
new file mode 100644
index 0000000..e574ab7
--- /dev/null
+++ b/template.html
@@ -0,0 +1,298 @@
+<!doctype html>
+<html lang="en" data-theme="light">
+<head>
+<!--
+  Coming-soon template — Gucci-aesthetic editorial landing.
+  Per-domain swap points (case-sensitive, double-curly):
+    {{WORDMARK}}     — domain stem, no TLD          → e.g. "abramsos"
+    {{TAGLINE}}      — italic line under wordmark    → e.g. "the operating system"
+    {{HERO_URL}}     — 2560×1600 editorial photo URL → CC0 or Steve-owned
+    {{HERO_ALT}}     — accessible alt text
+    {{DOMAIN}}       — full domain incl. TLD         → posted as `source` to /api/email-signup
+    {{PAGE_TITLE}}   — <title> + og:title            → e.g. "abramsos — coming soon"
+    {{ACCENT_HEX}}   — single accent hex for chrome  → keep neutral or pull from hero
+
+  Paper-design specs:
+    container max-width: 1440px center, 24px gutters
+    grid: hero 100vh, wordmark zone 480px below
+    type scale (modular 1.25): 11→14→18→22→28→36→48 (px)
+    wordmark: clamp(56px, 9vw, 128px), Cormorant Garamond display, weight 300
+    tagline: 12px, sans, +0.06em tracked, uppercase, opacity .6
+    email input: 14px monospace, hairline 1px underline, no border
+    breakpoints: <=720 mobile (wordmark→clamp 44px,12vw,96px; hero 70vh)
+
+  Standing rules honored:
+    - Logo upper-left (initial only)
+    - Hamburger upper-right (decorative — no menu wired)
+    - Sun/moon toggle next to hamburger (anti-flash inline script)
+    - Wordmark BELOW the hero photo, never overlapping
+    - Viewport meta required (mobile @media depends on it)
+    - No AI-vendor names visible
+-->
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width,initial-scale=1">
+<meta name="robots" content="noindex,nofollow">
+<title>{{PAGE_TITLE}}</title>
+<meta property="og:title" content="{{PAGE_TITLE}}">
+<meta property="og:image" content="{{HERO_URL}}">
+<meta name="theme-color" content="#faf8f3">
+<link rel="preload" as="image" href="{{HERO_URL}}">
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,500;1,300&family=Inter:wght@300;400&family=JetBrains+Mono:wght@300;400&display=swap">
+<script>
+  // Anti-flash theme hydration — must run before paint
+  (function(){
+    try {
+      var t = localStorage.getItem('cs-theme');
+      if (t === 'dark' || (t === null && window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
+        document.documentElement.setAttribute('data-theme', 'dark');
+      }
+    } catch(e) {}
+  })();
+</script>
+<style>
+  :root {
+    --bg: #faf8f3;
+    --ink: #0f0e0c;
+    --ink-soft: #57544c;
+    --ink-faint: #9b958a;
+    --line: #e5dfd0;
+    --card: #ffffff;
+    --accent: {{ACCENT_HEX}};
+    --serif: 'Cormorant Garamond', Georgia, 'Times New Roman', serif;
+    --sans: 'Inter', -apple-system, system-ui, sans-serif;
+    --mono: 'JetBrains Mono', ui-monospace, Menlo, monospace;
+  }
+  html[data-theme="dark"] {
+    --bg: #0f0e0c;
+    --ink: #faf8f3;
+    --ink-soft: #b5ab98;
+    --ink-faint: #6c6557;
+    --line: #2a2820;
+    --card: #18170f;
+  }
+  * { box-sizing: border-box; }
+  html, body { margin: 0; padding: 0; background: var(--bg); color: var(--ink); }
+  body { font-family: var(--sans); font-weight: 300; -webkit-font-smoothing: antialiased; line-height: 1.5; min-height: 100vh; }
+  a { color: inherit; text-decoration: none; }
+
+  /* HEADER — fixed bar, transparent over hero, fades to bg on scroll */
+  header.gh {
+    position: fixed; top: 0; left: 0; right: 0; z-index: 10;
+    display: flex; justify-content: space-between; align-items: center;
+    padding: 22px 28px;
+    pointer-events: none;
+  }
+  header.gh > * { pointer-events: auto; }
+  .glyph {
+    font-family: var(--serif); font-weight: 300; font-size: 32px; line-height: 1;
+    color: var(--ink); letter-spacing: -0.02em;
+    text-shadow: 0 1px 12px rgba(0,0,0,.22);
+  }
+  html[data-theme="dark"] .glyph { text-shadow: 0 1px 12px rgba(0,0,0,.55); }
+  .top-right { display: flex; align-items: center; gap: 18px; }
+  .theme-toggle, .ham {
+    background: transparent; border: 0; padding: 6px; cursor: pointer;
+    color: var(--ink); width: 32px; height: 32px;
+    display: flex; align-items: center; justify-content: center;
+    filter: drop-shadow(0 1px 8px rgba(0,0,0,.22));
+  }
+  .ham svg, .theme-toggle svg { width: 22px; height: 22px; }
+
+  /* HERO — full-bleed editorial photo */
+  .hero {
+    width: 100%; height: 100vh; min-height: 560px;
+    background: var(--card) center/cover no-repeat;
+    background-image: url('{{HERO_URL}}');
+    position: relative;
+  }
+  .hero::after {
+    /* Subtle bottom-fade so the wordmark zone reads cleanly when content peeks */
+    content: ''; position: absolute; inset: auto 0 0 0; height: 24%;
+    background: linear-gradient(to bottom, transparent, var(--bg));
+    pointer-events: none;
+  }
+
+  /* WORDMARK ZONE — center column under hero */
+  .meta {
+    max-width: 720px; margin: 0 auto; padding: 64px 24px 96px;
+    text-align: center;
+  }
+  .wordmark {
+    font-family: var(--serif); font-weight: 300; font-style: normal;
+    font-size: clamp(56px, 9vw, 128px); line-height: 1.02;
+    letter-spacing: -0.01em; color: var(--ink);
+    margin: 0 0 14px;
+  }
+  .tagline {
+    font-family: var(--serif); font-style: italic; font-weight: 300;
+    font-size: clamp(16px, 2.4vw, 22px);
+    color: var(--ink-soft); margin: 0 0 4px;
+  }
+  .eyebrow {
+    font-family: var(--sans); font-size: 11px; font-weight: 400;
+    text-transform: uppercase; letter-spacing: 0.18em;
+    color: var(--ink-faint); margin: 0 0 24px;
+  }
+
+  /* EMAIL CAPTURE — hairline, monospace, link-style submit */
+  form.signup {
+    margin: 40px auto 0; max-width: 420px;
+    display: flex; align-items: baseline; gap: 18px;
+    border-bottom: 1px solid var(--line);
+    padding-bottom: 6px;
+  }
+  form.signup input[type=email] {
+    flex: 1; min-width: 0;
+    background: transparent; border: 0; outline: none;
+    font-family: var(--mono); font-size: 14px; font-weight: 400;
+    color: var(--ink); padding: 10px 0;
+  }
+  form.signup input[type=email]::placeholder { color: var(--ink-faint); }
+  form.signup button {
+    background: transparent; border: 0; padding: 0; cursor: pointer;
+    font-family: var(--sans); font-size: 11px; font-weight: 400;
+    text-transform: uppercase; letter-spacing: 0.18em;
+    color: var(--ink); border-bottom: 1px solid var(--ink);
+    padding-bottom: 2px;
+    transition: opacity .15s ease;
+  }
+  form.signup button:hover { opacity: 0.6; }
+  form.signup button:disabled { color: var(--ink-faint); border-color: var(--ink-faint); cursor: progress; }
+  .signup-msg {
+    margin: 14px auto 0; max-width: 420px;
+    font-family: var(--sans); font-size: 12px; color: var(--ink-soft);
+    min-height: 18px;
+    text-align: center;
+  }
+  .signup-msg.err { color: #c0392b; }
+  .signup-msg.ok { color: #1e6b3a; }
+
+  /* FOOTER */
+  footer.gf {
+    margin-top: 40px; padding: 28px 24px 36px;
+    border-top: 1px solid var(--line);
+    text-align: center;
+    font-family: var(--sans); font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase;
+    color: var(--ink-faint);
+  }
+  footer.gf a {
+    color: var(--ink-soft); border-bottom: 1px solid transparent;
+    transition: border-color .15s ease;
+  }
+  footer.gf a:hover { border-color: var(--ink-soft); }
+  .gf-domain {
+    font-family: var(--serif); font-style: italic; font-size: 14px;
+    text-transform: none; letter-spacing: 0; color: var(--ink-soft);
+    margin-right: 14px;
+  }
+
+  /* Mobile */
+  @media (max-width: 720px) {
+    .hero { height: 70vh; min-height: 480px; }
+    .meta { padding: 48px 20px 72px; }
+    header.gh { padding: 16px 18px; }
+    .glyph { font-size: 26px; }
+  }
+
+  /* Print — keep wordmark + tagline readable; suppress chrome */
+  @media print {
+    header.gh, .theme-toggle, .ham, form.signup, .signup-msg { display: none; }
+    .hero { height: 60vh; }
+  }
+</style>
+</head>
+<body>
+
+<header class="gh">
+  <a href="/" class="glyph" aria-label="{{WORDMARK}} home">{{WORDMARK_INITIAL}}</a>
+  <div class="top-right">
+    <button class="theme-toggle" id="themeToggle" aria-label="Toggle light/dark theme">
+      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4">
+        <circle cx="12" cy="12" r="4"/>
+        <path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/>
+      </svg>
+    </button>
+    <button class="ham" aria-label="Menu (coming soon)">
+      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.4">
+        <path d="M3 7h18M3 12h18M3 17h18"/>
+      </svg>
+    </button>
+  </div>
+</header>
+
+<section class="hero" role="img" aria-label="{{HERO_ALT}}"></section>
+
+<main class="meta">
+  <p class="eyebrow">Coming soon</p>
+  <h1 class="wordmark">{{WORDMARK}}</h1>
+  <p class="tagline">{{TAGLINE}}</p>
+
+  <form class="signup" id="signupForm" novalidate>
+    <input type="email" name="email" id="emailInput" placeholder="your@email.com" required autocomplete="email" inputmode="email">
+    <button type="submit" id="signupBtn">Notify me</button>
+  </form>
+  <div class="signup-msg" id="signupMsg" role="status" aria-live="polite"></div>
+</main>
+
+<footer class="gf">
+  <a href="https://agentabrams.com" class="gf-domain">agentabrams.com</a>
+  <span>©&nbsp;{{YEAR}}&nbsp;&middot;&nbsp;{{DOMAIN}}</span>
+</footer>
+
+<script>
+  // Theme toggle
+  (function(){
+    var btn = document.getElementById('themeToggle');
+    btn.addEventListener('click', function(){
+      var cur = document.documentElement.getAttribute('data-theme') || 'light';
+      var next = cur === 'dark' ? 'light' : 'dark';
+      document.documentElement.setAttribute('data-theme', next);
+      try { localStorage.setItem('cs-theme', next); } catch(e){}
+    });
+  })();
+
+  // Email capture
+  (function(){
+    var form = document.getElementById('signupForm');
+    var input = document.getElementById('emailInput');
+    var btn = document.getElementById('signupBtn');
+    var msg = document.getElementById('signupMsg');
+    var ENDPOINT = 'https://agentabrams.com/api/email-signup';
+    var SOURCE = '{{DOMAIN}}';
+
+    function setMsg(text, cls) {
+      msg.textContent = text || '';
+      msg.classList.remove('err', 'ok');
+      if (cls) msg.classList.add(cls);
+    }
+    form.addEventListener('submit', async function(e){
+      e.preventDefault();
+      var email = (input.value || '').trim();
+      if (!email || !/.+@.+\..+/.test(email)) {
+        setMsg('Please enter a valid email.', 'err'); return;
+      }
+      btn.disabled = true; setMsg('');
+      try {
+        var r = await fetch(ENDPOINT, {
+          method: 'POST',
+          headers: { 'Content-Type': 'application/json' },
+          body: JSON.stringify({ email: email, source: SOURCE, timestamp: new Date().toISOString() })
+        });
+        if (r.ok) {
+          form.style.display = 'none';
+          setMsg('Thank you. We will be in touch.', 'ok');
+        } else {
+          setMsg('Could not save right now — please try again shortly.', 'err');
+          btn.disabled = false;
+        }
+      } catch (err) {
+        setMsg('Network error — please try again shortly.', 'err');
+        btn.disabled = false;
+      }
+    });
+  })();
+</script>
+</body>
+</html>

(oldest)  ·  back to Coming Soon Template  ·  snapshot: backup uncommitted work (1 files) 298fd1e →