← back to Lawyer Directory Builder
src/lib/mockup_templates.ts
298 lines
/**
* Three full-page HTML mockup templates for law-firm websites. Stamped with
* firm-specific data (name, phone, address, city) and rendered to a PNG by
* src/enrich/render_mockups.ts. Each template is deliberately distinct in
* type, palette, and tone so the prospect can pick a direction.
*
* VARIANT NAMES (don't change — referenced from DB & UI):
* 'a' — Editorial Noir (Cormorant + Inter, near-black, champagne)
* 'b' — Clean Modern Light (Playfair + Inter, ivory, navy)
* 'c' — Bold Confident (Plus Jakarta Sans, charcoal, oxblood)
*
* NOTE: every template renders a prominent "MOCKUP — verify all info" banner
* at the top so a screenshot pasted into a meeting can never be mistaken for
* a live site. The banner is sticky / above-the-fold.
*/
export type MockupInput = {
firmName: string;
city: string | null;
phone: string | null;
address: string | null;
};
const esc = (s: string | null | undefined): string =>
String(s ?? '').replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]!));
const DISCLAIMER_BANNER = `
<div style="position:sticky;top:0;left:0;right:0;background:linear-gradient(180deg,#2a2010 0%,#1a1611 100%);color:#d4a04a;font-family:-apple-system,BlinkMacSystemFont,'Inter',system-ui,sans-serif;font-size:11px;font-weight:500;letter-spacing:.22em;text-transform:uppercase;padding:11px 22px;text-align:center;z-index:9999;border-bottom:1px solid #4a3a1a">
<span style="color:#e8c98c;font-family:'Cormorant Garamond',Georgia,serif;font-style:italic;font-size:13px;letter-spacing:.04em;text-transform:none;margin-right:10px">Proposal</span>
Mockup design · firm details to be verified · Counsel & Bar
</div>
`;
// ─── Variant A — Editorial Noir ────────────────────────────────────────────
export function variantA(d: MockupInput): string {
const city = d.city || 'California';
return `<!doctype html><html lang="en"><head><meta charset="utf-8">
<title>${esc(d.firmName)} — Attorneys</title>
<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,300;0,400;0,500;1,300;1,400&family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
<style>
*{box-sizing:border-box;margin:0;padding:0}
html,body{font-family:'Inter',-apple-system,system-ui,sans-serif;font-weight:300;color:#f4f1ea;background:#0a0a0c;font-size:15px;line-height:1.6;-webkit-font-smoothing:antialiased}
.serif{font-family:'Cormorant Garamond','Cormorant',Georgia,serif}
header{padding:26px 64px;border-bottom:1px solid #2a2724;display:flex;align-items:center;justify-content:space-between;background:#050507}
header .logo{font-family:'Cormorant Garamond',serif;font-size:24px;font-weight:400;letter-spacing:.005em}
header .logo em{font-style:italic;color:#b89968;padding:0 4px}
nav a{color:#8b857a;font-size:11px;letter-spacing:.18em;text-transform:uppercase;text-decoration:none;margin-left:32px;font-weight:400}
nav a:hover{color:#f4f1ea}
nav a.cta{color:#b89968;border:1px solid #b89968;padding:9px 18px;letter-spacing:.16em}
.hero{padding:120px 64px 100px;background:radial-gradient(ellipse at 30% 0%,#1a1815 0%,#0a0a0c 60%);position:relative}
.eyebrow{font-size:10px;letter-spacing:.32em;text-transform:uppercase;color:#b89968;font-weight:500;margin-bottom:28px}
h1{font-family:'Cormorant Garamond',serif;font-size:74px;font-weight:300;line-height:1.05;letter-spacing:-.02em;max-width:14ch;margin-bottom:32px}
h1 em{font-style:italic;color:#b89968;font-weight:400}
.lede{font-size:18px;color:#d8d2c5;max-width:54ch;font-weight:300;line-height:1.55;margin-bottom:48px}
.cta-row{display:flex;gap:18px;align-items:center}
.btn{display:inline-block;padding:16px 32px;font-size:11px;letter-spacing:.22em;text-transform:uppercase;font-weight:500;text-decoration:none;border:1px solid #b89968;background:#b89968;color:#0a0a0c}
.btn.ghost{background:transparent;color:#b89968}
.creds{display:grid;grid-template-columns:repeat(3,1fr);gap:0;margin:0 64px;border-top:1px solid #2a2724;border-bottom:1px solid #2a2724}
.cred{padding:36px 28px;border-right:1px solid #2a2724}
.cred:last-child{border-right:0}
.cred .num{font-family:'Cormorant Garamond',serif;font-size:46px;font-weight:300;line-height:1;color:#b89968;font-variant-numeric:tabular-nums}
.cred .lbl{font-size:10px;letter-spacing:.22em;text-transform:uppercase;color:#8b857a;margin-top:10px}
.practice{padding:120px 64px}
h2{font-family:'Cormorant Garamond',serif;font-size:44px;font-weight:300;letter-spacing:-.01em;margin-bottom:8px}
h2 em{font-style:italic;color:#b89968;font-weight:400}
.section-eyebrow{font-size:10px;letter-spacing:.32em;text-transform:uppercase;color:#8b857a;font-weight:500;margin-bottom:18px}
.areas{display:grid;grid-template-columns:repeat(4,1fr);gap:1px;background:#2a2724;margin-top:48px;border:1px solid #2a2724}
.area{background:#131316;padding:36px 28px;min-height:180px;display:flex;flex-direction:column;justify-content:flex-end}
.area .n{font-family:'Cormorant Garamond',serif;font-style:italic;font-size:14px;color:#b89968;margin-bottom:14px}
.area .t{font-family:'Cormorant Garamond',serif;font-size:24px;font-weight:400;line-height:1.15;color:#f4f1ea}
.contact{padding:100px 64px;border-top:1px solid #2a2724;background:#050507;display:grid;grid-template-columns:1fr 1fr;gap:64px;align-items:center}
.contact .phone{font-family:'Cormorant Garamond',serif;font-size:54px;font-weight:300;color:#b89968;letter-spacing:-.01em;line-height:1.1}
.contact .addr{color:#d8d2c5;font-size:15px;line-height:1.7}
footer{padding:32px 64px;font-size:11px;color:#8b857a;letter-spacing:.14em;text-transform:uppercase;border-top:1px solid #2a2724}
</style></head><body>
${DISCLAIMER_BANNER}
<header>
<div class="logo">${esc(d.firmName).split(' ')[0]} <em>&</em> Associates</div>
<nav><a href="#">Practice</a><a href="#">Attorneys</a><a href="#">About</a><a href="#" class="cta">Schedule consult</a></nav>
</header>
<section class="hero">
<div class="eyebrow">Attorneys at Law · ${esc(city)}</div>
<h1>${esc(d.firmName)}.<br><em>Counsel built for outcomes.</em></h1>
<p class="lede">A boutique ${esc(city)} practice serving discerning clients across complex civil, family, and business matters. Discreet. Deliberate. Decisive.</p>
<div class="cta-row"><a href="#" class="btn">Request consultation</a><a href="#" class="btn ghost">Our practice areas</a></div>
</section>
<div class="creds">
<div class="cred"><div class="num">—</div><div class="lbl">Years in practice (firm to verify)</div></div>
<div class="cred"><div class="num">—</div><div class="lbl">Total clients served (firm to verify)</div></div>
<div class="cred"><div class="num">—</div><div class="lbl">Public ratings (firm to verify)</div></div>
</div>
<section class="practice">
<div class="section-eyebrow">Practice areas</div>
<h2>Where the firm <em>concentrates.</em></h2>
<div class="areas">
<div class="area"><div class="n">— 01</div><div class="t">Personal Injury</div></div>
<div class="area"><div class="n">— 02</div><div class="t">Family Law</div></div>
<div class="area"><div class="n">— 03</div><div class="t">Business Litigation</div></div>
<div class="area"><div class="n">— 04</div><div class="t">Estate Planning</div></div>
</div>
</section>
<section class="contact">
<div>
<div class="section-eyebrow">Speak with the firm</div>
<div class="phone">${esc(d.phone || '(310) 555-0100')}</div>
</div>
<div class="addr">${esc(d.address || `${city}, California`)}<br>By appointment · evenings & weekends available<br>Free 30-minute consultation</div>
</section>
<footer>© ${new Date().getFullYear()} ${esc(d.firmName)} · Attorney advertising · Prior results do not guarantee a similar outcome</footer>
</body></html>`;
}
// ─── Variant B — Clean Modern Light ────────────────────────────────────────
export function variantB(d: MockupInput): string {
const city = d.city || 'California';
return `<!doctype html><html lang="en"><head><meta charset="utf-8">
<title>${esc(d.firmName)}</title>
<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,500;1,400&family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
<style>
*{box-sizing:border-box;margin:0;padding:0}
html,body{font-family:'Inter',sans-serif;color:#1a2b4a;background:#f9f7f3;font-size:15px;line-height:1.6;font-weight:400;-webkit-font-smoothing:antialiased}
.serif{font-family:'Playfair Display',Georgia,serif}
header{padding:24px 64px;display:flex;align-items:center;justify-content:space-between;background:#fff;border-bottom:1px solid #e8e2d6}
header .logo{font-family:'Playfair Display',serif;font-size:22px;color:#1a2b4a;font-weight:500}
nav a{color:#5a6b85;font-size:13px;text-decoration:none;margin-left:36px;font-weight:500}
nav a:hover{color:#1a2b4a}
nav a.cta{background:#1a2b4a;color:#fff;padding:11px 22px;border-radius:2px;font-weight:500}
.hero{padding:120px 64px;display:grid;grid-template-columns:1.4fr 1fr;gap:80px;align-items:center;background:#fff}
.hero .badge{display:inline-block;font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:#a87935;background:#fbf3e3;padding:6px 14px;border-radius:99px;font-weight:600;margin-bottom:24px}
h1{font-family:'Playfair Display',serif;font-size:64px;font-weight:400;line-height:1.05;letter-spacing:-.02em;color:#1a2b4a;margin-bottom:24px}
h1 em{font-style:italic;color:#a87935;font-weight:400}
.lede{font-size:17px;color:#5a6b85;max-width:48ch;line-height:1.65;margin-bottom:36px;font-weight:400}
.cta-row{display:flex;gap:14px}
.btn{display:inline-block;padding:14px 26px;font-size:14px;font-weight:500;text-decoration:none;border-radius:2px;transition:all 200ms ease}
.btn.solid{background:#1a2b4a;color:#fff;border:1px solid #1a2b4a}
.btn.solid:hover{background:#0f1d36}
.btn.ghost{background:transparent;color:#1a2b4a;border:1px solid #c7cdd9}
.hero-card{background:#1a2b4a;color:#fff;padding:48px 36px;border-radius:4px;box-shadow:0 24px 60px rgba(26,43,74,.18)}
.hero-card h3{font-family:'Playfair Display',serif;font-size:24px;font-weight:400;margin-bottom:18px}
.hero-card .row{display:flex;justify-content:space-between;padding:14px 0;border-bottom:1px solid rgba(255,255,255,.12);font-size:14px}
.hero-card .row:last-child{border-bottom:0}
.hero-card .row b{color:#e8c98c;font-weight:500}
.hero-card .phone-cta{margin-top:24px;text-align:center;background:#a87935;padding:16px;border-radius:2px;font-family:'Playfair Display',serif;font-size:24px;font-weight:500}
.creds{padding:80px 64px;background:#f4f0e8;display:grid;grid-template-columns:repeat(3,1fr);gap:40px}
.cred{text-align:center}
.cred .num{font-family:'Playfair Display',serif;font-size:54px;font-weight:400;color:#a87935;line-height:1;margin-bottom:8px}
.cred .lbl{font-size:13px;color:#5a6b85;font-weight:500}
.practice{padding:100px 64px;background:#fff}
.practice-head{text-align:center;max-width:680px;margin:0 auto 64px}
h2{font-family:'Playfair Display',serif;font-size:42px;font-weight:400;color:#1a2b4a;letter-spacing:-.01em;margin-bottom:14px}
h2 em{font-style:italic;color:#a87935}
.section-sub{color:#5a6b85;font-size:16px}
.areas{display:grid;grid-template-columns:repeat(3,1fr);gap:24px}
.area{padding:36px 28px;border:1px solid #e8e2d6;border-radius:4px;background:#fff;transition:all 200ms ease}
.area:hover{border-color:#a87935;transform:translateY(-2px)}
.area .icon{width:44px;height:44px;background:#fbf3e3;color:#a87935;border-radius:50%;display:flex;align-items:center;justify-content:center;font-family:'Playfair Display',serif;font-size:20px;font-weight:500;margin-bottom:18px}
.area h4{font-family:'Playfair Display',serif;font-size:22px;font-weight:500;color:#1a2b4a;margin-bottom:10px}
.area p{font-size:14px;color:#5a6b85;line-height:1.55}
footer{padding:36px 64px;font-size:13px;color:#7a8aa3;background:#1a2b4a;color:#c7cdd9;text-align:center}
footer b{color:#fff}
</style></head><body>
${DISCLAIMER_BANNER}
<header>
<div class="logo">${esc(d.firmName)}</div>
<nav><a href="#">Attorneys</a><a href="#">Practice</a><a href="#">Results</a><a href="#">Reviews</a><a href="#" class="cta">Free consultation</a></nav>
</header>
<section class="hero">
<div>
<span class="badge">Established · ${esc(city)}</span>
<h1>Trusted counsel for <em>life's</em> defining moments.</h1>
<p class="lede">${esc(d.firmName)} represents individuals and businesses in ${esc(city)} and across California with the precision your matter deserves and the compassion you deserve.</p>
<div class="cta-row"><a href="#" class="btn solid">Schedule a free consult</a><a href="#" class="btn ghost">Read client reviews →</a></div>
</div>
<div class="hero-card">
<h3>What to expect</h3>
<div class="row"><span>First call</span><b>30 min · free</b></div>
<div class="row"><span>Same-day response</span><b>Mon – Sun</b></div>
<div class="row"><span>Fees</span><b>Contingency available</b></div>
<div class="row"><span>Languages</span><b>English · Spanish</b></div>
<div class="phone-cta">${esc(d.phone || '(310) 555-0100')}</div>
</div>
</section>
<section class="creds">
<div class="cred"><div class="num">—</div><div class="lbl">Years in ${esc(city)} (firm to verify)</div></div>
<div class="cred"><div class="num">—</div><div class="lbl">Cases handled (firm to verify)</div></div>
<div class="cred"><div class="num">—</div><div class="lbl">Reviews (firm to verify)</div></div>
</section>
<section class="practice">
<div class="practice-head">
<h2>Where we <em>excel.</em></h2>
<p class="section-sub">Five focused practice areas — every matter handled by an attorney, never paralegal-only.</p>
</div>
<div class="areas">
<div class="area"><div class="icon">PI</div><h4>Personal Injury</h4><p>Auto, slip-and-fall, premises liability — no fee unless we recover.</p></div>
<div class="area"><div class="icon">FL</div><h4>Family Law</h4><p>Divorce, custody, support, prenuptial agreements with discretion.</p></div>
<div class="area"><div class="icon">BL</div><h4>Business Litigation</h4><p>Contract disputes, partnership wind-downs, IP, employment matters.</p></div>
</div>
</section>
<footer>© ${new Date().getFullYear()} <b>${esc(d.firmName)}</b> · ${esc(d.address || city + ', CA')} · Attorney advertising</footer>
</body></html>`;
}
// ─── Variant C — Bold Confident ────────────────────────────────────────────
export function variantC(d: MockupInput): string {
const city = d.city || 'California';
return `<!doctype html><html lang="en"><head><meta charset="utf-8">
<title>${esc(d.firmName)}</title>
<link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<style>
*{box-sizing:border-box;margin:0;padding:0}
html,body{font-family:'Plus Jakarta Sans',-apple-system,system-ui,sans-serif;color:#16181d;background:#fff;font-size:16px;line-height:1.55;font-weight:500;-webkit-font-smoothing:antialiased}
header{padding:22px 64px;background:#16181d;color:#fff;display:flex;align-items:center;justify-content:space-between;position:sticky;top:32px;z-index:100}
header .logo{font-size:20px;font-weight:800;letter-spacing:-.02em}
header .logo span{color:#b8332e}
nav a{color:#cfd1d6;font-size:14px;text-decoration:none;margin-left:32px;font-weight:600}
nav a:hover{color:#fff}
nav a.cta{background:#b8332e;color:#fff;padding:11px 22px;border-radius:6px}
.hero{padding:140px 64px 100px;background:linear-gradient(180deg,#16181d 0%,#1f242c 100%);color:#fff}
.tag{display:inline-block;background:rgba(184,51,46,.18);color:#ff8a85;padding:7px 14px;border-radius:99px;font-size:12px;font-weight:700;letter-spacing:.04em;margin-bottom:24px;border:1px solid rgba(184,51,46,.4)}
h1{font-size:84px;font-weight:800;line-height:.98;letter-spacing:-.035em;max-width:18ch;margin-bottom:28px}
h1 .accent{color:#b8332e}
.lede{font-size:20px;color:#a8acb8;max-width:56ch;font-weight:500;line-height:1.5;margin-bottom:40px}
.cta-row{display:flex;gap:14px;align-items:center;flex-wrap:wrap}
.btn{display:inline-flex;align-items:center;gap:10px;padding:18px 30px;font-size:15px;font-weight:700;text-decoration:none;border-radius:8px;transition:all 200ms ease}
.btn.solid{background:#b8332e;color:#fff}
.btn.solid:hover{background:#9c2a26;transform:translateY(-1px)}
.btn.ghost{background:rgba(255,255,255,.06);color:#fff;border:1px solid rgba(255,255,255,.18)}
.creds{padding:60px 64px;background:#16181d;display:grid;grid-template-columns:repeat(4,1fr);gap:40px;border-top:1px solid #2a2f38}
.cred{color:#fff}
.cred .num{font-size:42px;font-weight:800;letter-spacing:-.025em;line-height:1;margin-bottom:6px}
.cred .num .accent{color:#b8332e}
.cred .lbl{font-size:13px;color:#8e94a3;font-weight:600}
.practice{padding:120px 64px;background:#fff}
.practice-head{margin-bottom:64px;display:grid;grid-template-columns:1fr 1fr;align-items:end;gap:40px}
.section-eyebrow{font-size:13px;font-weight:700;color:#b8332e;letter-spacing:.08em;text-transform:uppercase;margin-bottom:16px}
h2{font-size:54px;font-weight:800;letter-spacing:-.025em;line-height:1.05;color:#16181d}
.practice-head .lede2{color:#5a6168;font-size:17px;font-weight:500}
.areas{display:grid;grid-template-columns:repeat(2,1fr);gap:1px;background:#e8eaee;border:1px solid #e8eaee;border-radius:14px;overflow:hidden}
.area{background:#fff;padding:48px;display:grid;grid-template-columns:auto 1fr;gap:28px;align-items:start}
.area .nbr{font-size:46px;font-weight:800;color:#b8332e;letter-spacing:-.04em;line-height:.9}
.area h4{font-size:22px;font-weight:700;margin-bottom:10px;letter-spacing:-.015em}
.area p{font-size:14px;color:#5a6168;font-weight:500;line-height:1.55}
.cta-band{padding:80px 64px;background:#b8332e;color:#fff;display:flex;justify-content:space-between;align-items:center;gap:60px}
.cta-band h3{font-size:38px;font-weight:800;letter-spacing:-.02em;line-height:1.15}
.cta-band .phone{font-size:42px;font-weight:800;letter-spacing:-.025em}
.cta-band .phone .lbl{display:block;font-size:13px;font-weight:600;opacity:.8;letter-spacing:.06em;margin-bottom:6px}
footer{padding:36px 64px;background:#0e1014;color:#6e7383;font-size:13px;font-weight:500;text-align:center}
footer b{color:#fff}
</style></head><body>
${DISCLAIMER_BANNER}
<header>
<div class="logo">${esc(d.firmName.split(' ')[0])}<span>.</span></div>
<nav><a href="#">Practice</a><a href="#">Wins</a><a href="#">About</a><a href="#" class="cta">Free case review →</a></nav>
</header>
<section class="hero">
<span class="tag">★ Trial-tested · ${esc(city)}</span>
<h1>We don't <span class="accent">settle.</span><br>We <span class="accent">win.</span></h1>
<p class="lede">${esc(d.firmName)} fights for ${esc(city)} clients in personal injury, civil litigation, and contested family matters. Aggressive when you need it. Strategic when it matters.</p>
<div class="cta-row"><a href="#" class="btn solid">Get my free case review →</a><a href="#" class="btn ghost">Watch results video</a></div>
</section>
<section class="creds">
<div class="cred"><div class="num">—</div><div class="lbl">Total recovered (firm to verify)</div></div>
<div class="cred"><div class="num">—</div><div class="lbl">Win/settlement rate (firm to verify)</div></div>
<div class="cred"><div class="num">—</div><div class="lbl">Response window (firm to set)</div></div>
<div class="cred"><div class="num">—</div><div class="lbl">Fee structure (firm to set)</div></div>
</section>
<section class="practice">
<div class="practice-head">
<div>
<div class="section-eyebrow">What we fight for</div>
<h2>Five practice areas. One standard: <em style="font-style:italic">excellence.</em></h2>
</div>
<p class="lede2">From the first call to the final verdict, your case is handled by an attorney who knows the courthouse, the judges, and the opposition's playbook.</p>
</div>
<div class="areas">
<div class="area"><div class="nbr">01</div><div><h4>Personal Injury</h4><p>Auto accidents, premises, product liability — for ${esc(city)} clients. Specifics and case results to be added by the firm.</p></div></div>
<div class="area"><div class="nbr">02</div><div><h4>Civil Litigation</h4><p>Contract disputes, partnership disputes, employment matters — courtroom-ready from day one.</p></div></div>
<div class="area"><div class="nbr">03</div><div><h4>Family Law</h4><p>Contested divorce, custody, support modifications. Discreet, decisive, prepared.</p></div></div>
<div class="area"><div class="nbr">04</div><div><h4>Criminal Defense</h4><p>DUI, white-collar, felony defense. Former prosecutors who know how the other side thinks.</p></div></div>
</div>
</section>
<section class="cta-band">
<h3>Free case review.<br>Speak with an attorney today.</h3>
<div class="phone"><span class="lbl">Call now · 24/7</span>${esc(d.phone || '(310) 555-0100')}</div>
</section>
<footer>© ${new Date().getFullYear()} <b>${esc(d.firmName)}</b> · ${esc(d.address || city + ', CA')} · Attorney advertising · Prior results do not guarantee a similar outcome</footer>
</body></html>`;
}
export const VARIANTS: Array<{ key: 'a' | 'b' | 'c'; label: string; render: (d: MockupInput) => string }> = [
{ key: 'a', label: 'Editorial Noir', render: variantA },
{ key: 'b', label: 'Clean Modern Light', render: variantB },
{ key: 'c', label: 'Bold Confident', render: variantC },
];