Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
ba89f13
feat(chat): ship-readiness polish — Tailwind, auto-scroll, markdown, …
blove Apr 6, 2026
2dccb0a
Rebrand to Angular Stream Resource (#28)
blove Apr 6, 2026
65f13df
feat(website): add narrative sections, pilot-to-prod page, and rebran…
blove Apr 6, 2026
1c9e7b6
fix(website): replace unsourced stats with verified Gartner and Stack…
blove Apr 6, 2026
16d37f7
merge: resolve conflicts with main
blove Apr 6, 2026
40a967b
docs: add website audit and lead generation specs
blove Apr 6, 2026
5e52aeb
docs: add implementation plans for lead gen and website audit
blove Apr 6, 2026
b8e8ba2
chore: install resend and react-email dependencies
blove Apr 6, 2026
e53d86d
feat: add shared resend module with audience helper
blove Apr 6, 2026
fd13886
feat: add lead notification email template
blove Apr 6, 2026
7f16a19
feat: add whitepaper download email template
blove Apr 6, 2026
e41dd9f
feat: add newsletter welcome email template
blove Apr 6, 2026
c948d1a
feat: wire /api/leads to Resend email + audience
blove Apr 6, 2026
c33e2e1
feat: wire /api/whitepaper-signup to Resend email delivery
blove Apr 6, 2026
4b5d310
feat: add /api/newsletter route with Resend welcome email
blove Apr 6, 2026
5c700cf
fix: convert email templates to plain HTML to avoid React dual-instan…
blove Apr 6, 2026
fba8c65
fix: lazy-init Resend client to gracefully handle missing API key
blove Apr 6, 2026
3092ca4
fix: make ChatFeaturesSection responsive on mobile
blove Apr 6, 2026
150ca98
fix: stack FairComparisonSection rows vertically on mobile
blove Apr 6, 2026
1aaaec2
fix: increase touch targets to meet WCAG 44px minimum
blove Apr 6, 2026
5975f8f
fix: enforce 12px minimum font size on progress bar labels
blove Apr 6, 2026
b65d9e2
feat: add social proof badge strip below stats
blove Apr 6, 2026
fcfe07b
feat: add newsletter signup form to footer
blove Apr 6, 2026
5f62f73
feat: restructure white paper section with soft gate
blove Apr 6, 2026
32b2221
feat: add OpenGraph and Twitter Card meta tags
blove Apr 6, 2026
888701c
merge: resolve ProblemSection conflict with main (keep our stat + fon…
blove Apr 6, 2026
8f6ec67
feat: rebrand from streamResource to agent() — @cacheplane/angular
blove Apr 7, 2026
5dbeb85
merge: resolve conflicts with main (keep renamed versions)
blove Apr 7, 2026
7353ae7
merge: pull latest main
blove Apr 7, 2026
e439b03
fix: complete rebrand audit — nav bug, hero copy, templates, footer
blove Apr 7, 2026
b96574c
fix: remove $20k from pilot program, include with app license, annual…
blove Apr 7, 2026
0b4e127
fix: resolve API docs slug mismatch after rename
blove Apr 7, 2026
615898d
Merge remote-tracking branch 'origin/main' into claude/zealous-jones
blove Apr 7, 2026
f7a90e3
refactor: rename stream-resource lib to agent in Nx project structure
blove Apr 7, 2026
aca9d65
Merge remote-tracking branch 'origin/main' into claude/zealous-jones
blove Apr 7, 2026
8aec369
Merge remote-tracking branch 'origin/main' into claude/zealous-jones
blove Apr 7, 2026
58a0e11
feat: add airplane emoji favicon and logo to header/footer
blove Apr 7, 2026
db10a06
fix: prevent FullStackSection connector animations from overlapping c…
blove Apr 7, 2026
24c4326
fix: add remark-gfm to enable markdown table rendering in docs
blove Apr 7, 2026
b0305da
fix: update developer seat feature text to "12-month license"
blove Apr 7, 2026
8ee2ec8
feat: add dismissible whitepaper announcement toast
blove Apr 7, 2026
f3923da
feat: add lead capture form to announcement toast
blove Apr 7, 2026
b225874
merge: resolve AnnouncementToast conflict (keep form version)
blove Apr 7, 2026
7e8ded9
feat: replace social proof badges with animated logo scroll strip
blove Apr 7, 2026
aa1697a
Merge remote-tracking branch 'origin/main' into claude/zealous-jones
blove Apr 7, 2026
fce1b7b
Merge remote-tracking branch 'origin/main' into claude/zealous-jones
blove Apr 7, 2026
d94f93f
feat: add Loops.so integration for drip email campaigns
blove Apr 7, 2026
4ba5f38
feat: replace Loops with self-hosted drip via Resend scheduled_at
blove Apr 7, 2026
7bd0a37
merge: resolve whitepaper-signup conflict (keep drip version)
blove Apr 7, 2026
558676f
feat: unified gradient-header email template design
blove Apr 7, 2026
7064f3f
merge: resolve drip template conflict (keep gradient-header version)
blove Apr 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 46 additions & 32 deletions apps/website/emails/drip-whitepaper-followup.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,66 @@
export function dripWhitepaperFollowupHtml(day: number): { subject: string; html: string } {
const unsubUrl = 'https://stream-resource.dev/api/unsubscribe';
import { wrapEmail } from './email-wrapper';

export function dripWhitepaperFollowupHtml(day: number): { subject: string; html: string } {
if (day === 2) {
return {
subject: 'Did you get a chance to read Chapter 3?',
html: wrapEmail(`
<p style="font-size:14px;color:#3f3f46;line-height:1.6;margin:0 0 16px">Chapter 3 covers <strong>tool-call rendering</strong> — how to surface agent actions as real UI instead of raw JSON. It's the chapter most teams bookmark first.</p>
<p style="font-size:14px;color:#3f3f46;line-height:1.6;margin:0 0 24px">If you haven't downloaded it yet, <a href="https://stream-resource.dev/whitepaper.pdf" style="color:#004090;text-decoration:underline">grab it here</a>.</p>
`),
html: wrapEmail({
body: `
<p style="font-size:11px;font-family:monospace;text-transform:uppercase;letter-spacing:0.08em;color:#004090;font-weight:700;margin:0 0 8px">Whitepaper Follow-up</p>
<p style="font-size:20px;font-weight:700;color:#1a1a2e;margin:0 0 14px;line-height:1.3">Did you get a chance to read Chapter 3?</p>
<p style="font-size:14px;color:#555770;line-height:1.7;margin:0 0 24px">Chapter 3 covers <strong>tool-call rendering</strong> — how to surface agent actions as real UI instead of raw JSON. It's the chapter most teams bookmark first.</p>
<a href="https://stream-resource.dev/whitepaper.pdf" style="display:inline-block;background-color:#004090;color:#fff;padding:12px 28px;border-radius:8px;font-size:14px;font-weight:700;text-decoration:none">Read the Guide →</a>
`,
showUnsubscribe: true,
}),
};
}

if (day === 5) {
return {
subject: 'The gap between demo and production',
html: wrapEmail(`
<p style="font-size:14px;color:#3f3f46;line-height:1.6;margin:0 0 16px">Half of GenAI projects die after proof of concept. The gap isn't the model — it's the frontend production path: streaming state, thread persistence, human approval flows, and deterministic testing.</p>
<p style="font-size:14px;color:#3f3f46;line-height:1.6;margin:0 0 24px">That's exactly what the Angular Agent Framework solves. <a href="https://stream-resource.dev/docs" style="color:#004090;text-decoration:underline">See how it works →</a></p>
`),
html: wrapEmail({
body: `
<p style="font-size:11px;font-family:monospace;text-transform:uppercase;letter-spacing:0.08em;color:#004090;font-weight:700;margin:0 0 8px">Production Readiness</p>
<p style="font-size:20px;font-weight:700;color:#1a1a2e;margin:0 0 14px;line-height:1.3">The gap between demo and production</p>
<p style="font-size:14px;color:#555770;line-height:1.7;margin:0 0 24px">Half of GenAI projects die after proof of concept. The gap isn't the model — it's the frontend production path: streaming state, thread persistence, human approval flows, and deterministic testing.</p>
<a href="https://stream-resource.dev/docs" style="display:inline-block;background-color:#004090;color:#fff;padding:12px 28px;border-radius:8px;font-size:14px;font-weight:700;text-decoration:none">See How It Works →</a>
`,
showUnsubscribe: true,
}),
};
}

if (day === 10) {
return {
subject: 'The pilot program is included with every app license',
html: wrapEmail(`
<p style="font-size:14px;color:#3f3f46;line-height:1.6;margin:0 0 16px">Every app deployment license includes a <strong>3-month co-pilot engagement</strong> — we work alongside your Angular team to ship your first agent to production.</p>
<p style="font-size:14px;color:#3f3f46;line-height:1.6;margin:0 0 8px">Week 1: Integration & first stream<br/>Month 1: First agent in staging<br/>Month 3: Production deployment</p>
<p style="font-size:14px;color:#3f3f46;line-height:1.6;margin:16px 0 24px"><a href="https://stream-resource.dev/pilot-to-prod" style="color:#004090;text-decoration:underline">Learn about the pilot program →</a></p>
`),
html: wrapEmail({
body: `
<p style="font-size:11px;font-family:monospace;text-transform:uppercase;letter-spacing:0.08em;color:#004090;font-weight:700;margin:0 0 8px">Pilot Program</p>
<p style="font-size:20px;font-weight:700;color:#1a1a2e;margin:0 0 14px;line-height:1.3">The pilot program is included with every app license</p>
<p style="font-size:14px;color:#555770;line-height:1.7;margin:0 0 14px">Every app deployment license includes a <strong>3-month co-pilot engagement</strong> — we work alongside your Angular team to ship your first agent to production.</p>
<div style="background:#f8f9fc;border-radius:8px;padding:16px 18px;margin:0 0 24px;border:1px solid rgba(0,64,144,0.08)">
<p style="font-size:13px;color:#555770;margin:0 0 4px;line-height:1.6"><strong style="color:#004090">Week 1</strong> · Integration &amp; first stream</p>
<p style="font-size:13px;color:#555770;margin:0 0 4px;line-height:1.6"><strong style="color:#004090">Month 1</strong> · First agent in staging</p>
<p style="font-size:13px;color:#555770;margin:0;line-height:1.6"><strong style="color:#004090">Month 3</strong> · Production deployment</p>
</div>
<a href="https://stream-resource.dev/pilot-to-prod" style="display:inline-block;background-color:#004090;color:#fff;padding:12px 28px;border-radius:8px;font-size:14px;font-weight:700;text-decoration:none">Learn About the Pilot →</a>
`,
showUnsubscribe: true,
}),
};
}

// day === 20
return {
subject: 'Ready to ship your agent? Let\'s talk.',
html: wrapEmail(`
<p style="font-size:14px;color:#3f3f46;line-height:1.6;margin:0 0 16px">If your team is evaluating how to take an Angular + LangGraph agent to production, I'd love to hear what you're building.</p>
<p style="font-size:14px;color:#3f3f46;line-height:1.6;margin:0 0 24px">Reply to this email or <a href="mailto:hello@cacheplane.io" style="color:#004090;text-decoration:underline">schedule a conversation</a> — no pitch, just a technical discussion about your use case.</p>
`),
subject: "Ready to ship your agent? Let's talk.",
html: wrapEmail({
body: `
<p style="font-size:11px;font-family:monospace;text-transform:uppercase;letter-spacing:0.08em;color:#004090;font-weight:700;margin:0 0 8px">Let's Connect</p>
<p style="font-size:20px;font-weight:700;color:#1a1a2e;margin:0 0 14px;line-height:1.3">Ready to ship your agent? Let's talk.</p>
<p style="font-size:14px;color:#555770;line-height:1.7;margin:0 0 24px">If your team is evaluating how to take an Angular + LangGraph agent to production, I'd love to hear what you're building. Reply to this email or <a href="mailto:hello@cacheplane.io" style="color:#004090;text-decoration:underline">schedule a conversation</a> — no pitch, just a technical discussion about your use case.</p>
`,
showUnsubscribe: true,
}),
};
}

function wrapEmail(body: string): string {
return `<!DOCTYPE html><html><head></head>
<body style="font-family:Inter,Arial,sans-serif;background-color:#f4f4f5;padding:40px 0;margin:0">
<div style="max-width:520px;margin:0 auto;background-color:#fff;border-radius:12px;padding:32px 40px;border:1px solid #e4e4e7">
<p style="font-size:11px;font-family:monospace;text-transform:uppercase;letter-spacing:0.08em;color:#004090;font-weight:700;margin:0 0 16px">Angular Agent Framework</p>
${body}
<hr style="border:none;border-top:1px solid #e4e4e7;margin:24px 0 16px"/>
<p style="font-size:11px;color:#a1a1aa;line-height:1.5;margin:0">Angular Agent Framework — Signal-native streaming for LangGraph.</p>
<p style="font-size:10px;color:#d4d4d8;margin:8px 0 0"><a href="https://stream-resource.dev/api/unsubscribe?email=RECIPIENT" style="color:#d4d4d8;text-decoration:underline">Unsubscribe</a></p>
</div></body></html>`;
}
28 changes: 28 additions & 0 deletions apps/website/emails/email-wrapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* Shared HTML wrapper for all email templates.
* Gradient header band with logo, white body, footer.
*/
export function wrapEmail(opts: {
body: string;
showUnsubscribe?: boolean;
}): string {
return `<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"></head>
<body style="font-family:Inter,Arial,sans-serif;background-color:#e8eaf0;padding:40px 0;margin:0">
<div style="max-width:520px;margin:0 auto;border-radius:12px;overflow:hidden;box-shadow:0 2px 12px rgba(0,0,0,0.08)">
<div style="background:linear-gradient(135deg, #fef0f3 0%, #f4f0ff 45%, #eaf3ff 70%, #e6f4ff 100%);padding:28px 32px 20px;border-bottom:1px solid rgba(0,64,144,0.1)">
<div style="font-size:13px;font-weight:700;color:#1a1a2e;letter-spacing:-0.01em">🛩️ Angular Agent Framework</div>
</div>
<div style="background:#fff;padding:28px 32px 32px">
${opts.body}
<div style="border-top:1px solid #e4e4e7;margin-top:28px;padding-top:16px">
<p style="font-size:11px;color:#a1a1aa;line-height:1.5;margin:0">Angular Agent Framework — Signal-native streaming for LangGraph.</p>
${opts.showUnsubscribe ? '<p style="font-size:10px;color:#d4d4d8;margin:6px 0 0"><a href="https://stream-resource.dev/api/unsubscribe?email=RECIPIENT" style="color:#d4d4d8;text-decoration:underline">Unsubscribe</a></p>' : ''}
</div>
</div>
</div>
</body></html>`;
}

export function esc(s: string): string {
return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
27 changes: 13 additions & 14 deletions apps/website/emails/lead-notification.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { wrapEmail, esc } from './email-wrapper';

interface LeadNotificationProps {
name: string;
email: string;
Expand All @@ -7,18 +9,15 @@ interface LeadNotificationProps {
}

export function leadNotificationHtml({ name, email, company, message, ts }: LeadNotificationProps): string {
return `<!DOCTYPE html><html><head></head>
<body style="font-family:Inter,Arial,sans-serif;background-color:#f4f4f5;padding:40px 0;margin:0">
<div style="max-width:520px;margin:0 auto;background-color:#fff;border-radius:12px;padding:32px 40px;border:1px solid #e4e4e7">
<p style="font-size:11px;font-family:monospace;text-transform:uppercase;letter-spacing:0.08em;color:#004090;font-weight:700;margin:0 0 8px">New Lead</p>
<p style="font-size:20px;font-weight:700;color:#1a1a2e;margin:8px 0 4px">${esc(name)}</p>
<p style="font-size:14px;color:#71717a;margin:0 0 16px">${esc(email)}${company ? ` — ${esc(company)}` : ''}</p>
${message ? `<hr style="border:none;border-top:1px solid #e4e4e7;margin:16px 0"/><p style="font-size:14px;color:#3f3f46;line-height:1.6;margin:0">${esc(message)}</p>` : ''}
<hr style="border:none;border-top:1px solid #e4e4e7;margin:16px 0"/>
<p style="font-size:11px;color:#a1a1aa;margin:0">Received ${esc(ts)}</p>
</div></body></html>`;
}

function esc(s: string): string {
return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
return wrapEmail({
body: `
<p style="font-size:11px;font-family:monospace;text-transform:uppercase;letter-spacing:0.08em;color:#DD0031;font-weight:700;margin:0 0 8px">New Lead</p>
<p style="font-size:20px;font-weight:700;color:#1a1a2e;margin:0 0 4px">${esc(name)}</p>
<p style="font-size:14px;color:#8b8fa3;margin:0 0 16px">${esc(email)}${company ? ` — ${esc(company)}` : ''}</p>
${message ? `<div style="border-top:1px solid #e4e4e7;padding-top:14px;margin-bottom:4px"><p style="font-size:14px;color:#555770;line-height:1.7;margin:0">${esc(message)}</p></div>` : ''}
<div style="border-top:1px solid #e4e4e7;padding-top:14px;margin-top:14px">
<p style="font-size:11px;color:#a1a1aa;margin:0">Received ${esc(ts)}</p>
</div>
`,
});
}
19 changes: 9 additions & 10 deletions apps/website/emails/newsletter-welcome.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { wrapEmail } from './email-wrapper';

export function newsletterWelcomeHtml(): string {
return `<!DOCTYPE html><html><head></head>
<body style="font-family:Inter,Arial,sans-serif;background-color:#f4f4f5;padding:40px 0;margin:0">
<div style="max-width:520px;margin:0 auto;background-color:#fff;border-radius:12px;padding:32px 40px;border:1px solid #e4e4e7">
<p style="font-size:11px;font-family:monospace;text-transform:uppercase;letter-spacing:0.08em;color:#004090;font-weight:700;margin:0 0 12px">Angular Agent Framework</p>
<p style="font-size:22px;font-weight:700;color:#1a1a2e;margin:0 0 8px">Welcome to Angular Agent Framework updates</p>
<p style="font-size:14px;color:#3f3f46;line-height:1.6;margin:0 0 24px">You'll receive updates on new capabilities, production patterns, and Angular agent best practices. We keep it focused and infrequent — no spam.</p>
<a href="https://stream-resource.dev/docs" style="background-color:#004090;color:#fff;padding:12px 28px;border-radius:10px;font-size:14px;font-weight:700;text-decoration:none;display:inline-block">Explore the Docs</a>
<hr style="border:none;border-top:1px solid #e4e4e7;margin:24px 0 16px"/>
<p style="font-size:12px;color:#a1a1aa;line-height:1.5;margin:0">Angular Agent Framework — Signal-native streaming for LangGraph.</p>
</div></body></html>`;
return wrapEmail({
body: `
<p style="font-size:20px;font-weight:700;color:#1a1a2e;margin:0 0 8px;line-height:1.3">Welcome to Angular Agent Framework updates</p>
<p style="font-size:14px;color:#555770;line-height:1.7;margin:0 0 24px">You'll receive updates on new capabilities, production patterns, and Angular agent best practices. We keep it focused and infrequent — no spam.</p>
<a href="https://stream-resource.dev/docs" style="display:inline-block;background-color:#004090;color:#fff;padding:12px 28px;border-radius:8px;font-size:14px;font-weight:700;text-decoration:none">Explore the Docs</a>
`,
});
}
27 changes: 11 additions & 16 deletions apps/website/emails/whitepaper-download.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
import { wrapEmail, esc } from './email-wrapper';

const DOWNLOAD_URL = 'https://stream-resource.dev/whitepaper.pdf';

export function whitepaperDownloadHtml(name?: string): string {
return `<!DOCTYPE html><html><head></head>
<body style="font-family:Inter,Arial,sans-serif;background-color:#f4f4f5;padding:40px 0;margin:0">
<div style="max-width:520px;margin:0 auto;background-color:#fff;border-radius:12px;padding:32px 40px;border:1px solid #e4e4e7">
<p style="font-size:11px;font-family:monospace;text-transform:uppercase;letter-spacing:0.08em;color:#004090;font-weight:700;margin:0 0 12px">Angular Agent Framework</p>
<p style="font-size:22px;font-weight:700;color:#1a1a2e;margin:0 0 8px">Your Angular Agent Readiness Guide</p>
<p style="font-size:14px;color:#3f3f46;line-height:1.6;margin:0 0 24px">${name ? `Hi ${esc(name)}, t` : 'T'}he guide covers six production-readiness dimensions: streaming state, thread persistence, tool-call rendering, human approval flows, generative UI, and deterministic testing.</p>
<div style="text-align:center;margin:0 0 24px">
<a href="${DOWNLOAD_URL}" style="background-color:#004090;color:#fff;padding:14px 32px;border-radius:10px;font-size:14px;font-weight:700;text-decoration:none;display:inline-block">Download the Guide</a>
</div>
<hr style="border:none;border-top:1px solid #e4e4e7;margin:16px 0"/>
<p style="font-size:12px;color:#a1a1aa;line-height:1.5;margin:0">Angular Agent Framework — Signal-native streaming for LangGraph.</p>
</div></body></html>`;
}

function esc(s: string): string {
return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
return wrapEmail({
body: `
<p style="font-size:20px;font-weight:700;color:#1a1a2e;margin:0 0 8px;line-height:1.3">Your Angular Agent Readiness Guide</p>
<p style="font-size:14px;color:#555770;line-height:1.7;margin:0 0 24px">${name ? `Hi ${esc(name)}, t` : 'T'}he guide covers six production-readiness dimensions: streaming state, thread persistence, tool-call rendering, human approval flows, generative UI, and deterministic testing.</p>
<div style="text-align:center;margin:0 0 4px">
<a href="${DOWNLOAD_URL}" style="display:inline-block;background-color:#004090;color:#fff;padding:12px 32px;border-radius:8px;font-size:14px;font-weight:700;text-decoration:none">Download the Guide</a>
</div>
`,
});
}
81 changes: 81 additions & 0 deletions apps/website/src/app/api/email-preview/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/**
* Dev-only email template preview route.
* Visit /api/email-preview?template=whitepaper-download to preview a template.
* Available templates: whitepaper-download, newsletter-welcome, lead-notification,
* drip-day-2, drip-day-5, drip-day-10, drip-day-20
*/
import { NextRequest, NextResponse } from 'next/server';
import { whitepaperDownloadHtml } from '../../../../emails/whitepaper-download';
import { newsletterWelcomeHtml } from '../../../../emails/newsletter-welcome';
import { leadNotificationHtml } from '../../../../emails/lead-notification';
import { dripWhitepaperFollowupHtml } from '../../../../emails/drip-whitepaper-followup';

const TEMPLATES: Record<string, () => { subject: string; html: string }> = {
'whitepaper-download': () => ({
subject: 'Your Angular Agent Readiness Guide',
html: whitepaperDownloadHtml('Brian'),
}),
'newsletter-welcome': () => ({
subject: 'Welcome to Angular Agent Framework updates',
html: newsletterWelcomeHtml(),
}),
'lead-notification': () => ({
subject: 'New lead: Brian at Cacheplane',
html: leadNotificationHtml({
name: 'Brian Love',
email: 'brian@cacheplane.io',
company: 'Cacheplane',
message: 'Interested in the pilot program for our Angular + LangGraph project.',
ts: new Date().toISOString(),
}),
}),
'drip-day-2': () => dripWhitepaperFollowupHtml(2),
'drip-day-5': () => dripWhitepaperFollowupHtml(5),
'drip-day-10': () => dripWhitepaperFollowupHtml(10),
'drip-day-20': () => dripWhitepaperFollowupHtml(20),
};

export async function GET(req: NextRequest) {
const template = req.nextUrl.searchParams.get('template');

// Index page — show all templates
if (!template) {
const links = Object.keys(TEMPLATES).map(
(t) => `<a href="/api/email-preview?template=${t}" style="display:block;padding:8px 0;color:#004090;font-size:14px">${t}</a>`
).join('');

return new NextResponse(
`<!DOCTYPE html><html><head><meta charset="utf-8"><title>Email Previews</title></head>
<body style="font-family:Inter,Arial,sans-serif;max-width:600px;margin:40px auto;padding:0 20px">
<h1 style="font-size:24px;font-weight:700;color:#1a1a2e;margin:0 0 8px">Email Template Previews</h1>
<p style="font-size:14px;color:#71717a;margin:0 0 24px">Click a template to preview it as rendered HTML.</p>
${links}
</body></html>`,
{ headers: { 'Content-Type': 'text/html' } }
);
}

const factory = TEMPLATES[template];
if (!factory) {
return new NextResponse(`Unknown template: ${template}`, { status: 404 });
}

const { subject, html } = factory();

// Wrap in a preview frame showing subject line
const preview = `<!DOCTYPE html><html><head><meta charset="utf-8"><title>Preview: ${subject}</title></head>
<body style="margin:0;padding:0;background:#e4e4e7">
<div style="background:#fff;padding:12px 24px;border-bottom:1px solid #e4e4e7;font-family:Inter,Arial,sans-serif;display:flex;align-items:center;justify-content:space-between">
<div>
<span style="font-size:11px;color:#71717a;text-transform:uppercase;letter-spacing:0.06em">Subject:</span>
<span style="font-size:14px;font-weight:600;color:#1a1a2e;margin-left:8px">${subject}</span>
</div>
<a href="/api/email-preview" style="font-size:12px;color:#004090;text-decoration:none">← All templates</a>
</div>
<div style="padding:20px 0">
${html}
</div>
</body></html>`;

return new NextResponse(preview, { headers: { 'Content-Type': 'text/html' } });
}