From ed85306526aa6521e25a24d23a9838fd6a89a46d Mon Sep 17 00:00:00 2001 From: Brian Love Date: Tue, 12 May 2026 17:31:58 -0700 Subject: [PATCH 1/2] =?UTF-8?q?refactor(website):=20email=20brand=20pass?= =?UTF-8?q?=20=E2=80=94=20drop=20gradient=20header,=20use=20new=20palette?= =?UTF-8?q?=20(Group=20B.1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migrates the shared wrapEmail() and lead-notification templates off the legacy aesthetic (pastel gradient header band + zinc-200/400 palette) to the Statusbrew-shaped tokens used by the marketing site: - email-wrapper.ts: drops the 4-stop pastel linear-gradient header band for a white header with a hairline #e6e8ee bottom border. Body background switches from #e8eaf0 to #f4f6fb (surfaceTinted). Card drops the box-shadow in favor of a 1px #e6e8ee border with border-radius bumped 12px โ†’ 14px. Footer divider + text colors unified to #e6e8ee + #8b8fa3. - lead-notification.ts: same palette swap (3 references). - /api/email-preview dev route: matching swap so the preview wrapper doesn't read like two different brands. All 7 dev preview endpoints (whitepaper-download, newsletter-welcome, lead-notification, drip-day-{2,5,10,20}) return HTTP 200 and a grep of the rendered HTML for the legacy palette returns zero matches. Per-library accent colors (#004090 LangGraph blue, #5a00c8 chat purple, #1a7a40 render green, #DD0031 angular red) intentionally preserved in template bodies โ€” those are brand signals, not chrome. Whitepaper-PDF covers are Group B.2 (separate PR โ€” requires figuring out the PDF build pipeline). Co-Authored-By: Claude Opus 4.7 --- apps/website/emails/email-wrapper.ts | 21 +++++++++++-------- apps/website/emails/lead-notification.ts | 6 +++--- .../src/app/api/email-preview/route.ts | 6 +++--- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/apps/website/emails/email-wrapper.ts b/apps/website/emails/email-wrapper.ts index b12324734..5fcb20f05 100644 --- a/apps/website/emails/email-wrapper.ts +++ b/apps/website/emails/email-wrapper.ts @@ -1,22 +1,25 @@ /** * Shared HTML wrapper for all email templates. - * Gradient header band with logo, white body, footer. + * + * Brand pass: drops the pastel gradient header band (legacy aesthetic) for + * a clean white card with hairline borders, matching the Statusbrew-inspired + * marketing surface. Inline-only styles for cross-client compatibility. */ export function wrapEmail(opts: { body: string; showUnsubscribe?: boolean; }): string { return ` - -
-
-
๐Ÿ›ฉ๏ธ Angular Agent Framework
+ +
+
+
๐Ÿ›ฉ๏ธ Angular Agent Framework
-
+
${opts.body} -
-

Angular Agent Framework โ€” Signal-native streaming for LangGraph.

- ${opts.showUnsubscribe ? '

Unsubscribe

' : ''} +
+

Angular Agent Framework โ€” Signal-native streaming for LangGraph.

+ ${opts.showUnsubscribe ? '

Unsubscribe

' : ''}
diff --git a/apps/website/emails/lead-notification.ts b/apps/website/emails/lead-notification.ts index b5475dd96..0be62275f 100644 --- a/apps/website/emails/lead-notification.ts +++ b/apps/website/emails/lead-notification.ts @@ -14,9 +14,9 @@ export function leadNotificationHtml({ name, email, company, message, ts }: Lead

New Lead

${esc(name)}

${esc(email)}${company ? ` โ€” ${esc(company)}` : ''}

- ${message ? `

${esc(message)}

` : ''} -
-

Received ${esc(ts)}

+ ${message ? `

${esc(message)}

` : ''} +
+

Received ${esc(ts)}

`, }); diff --git a/apps/website/src/app/api/email-preview/route.ts b/apps/website/src/app/api/email-preview/route.ts index f0a51e98f..bae3cc39a 100644 --- a/apps/website/src/app/api/email-preview/route.ts +++ b/apps/website/src/app/api/email-preview/route.ts @@ -64,10 +64,10 @@ export async function GET(req: NextRequest) { // Wrap in a preview frame showing subject line const preview = `Preview: ${subject} - -
+ +
- Subject: + Subject: ${subject}
โ† All templates From 0705baa65faa01b4575b32637ccf2a2f9b8cb572 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Tue, 12 May 2026 17:34:18 -0700 Subject: [PATCH 2/2] refactor(website): whitepaper PDF cover brand pass (Group B.2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updates apps/website/scripts/generate-whitepaper.ts so the next regen of the 4 whitepapers produces covers matching the Statusbrew-shaped marketing aesthetic. - coverGradient per paper: 4-stop pastel rainbows replaced with subtle two-stop gradients in the new palette. Each paper carries a light hint of its library's brand accent: - whitepaper.pdf (agent): #fafbfc โ†’ #eaf3ff (LangGraph blue) - angular.pdf: #fafbfc โ†’ #eaf3ff (LangGraph blue) - render.pdf: #fafbfc โ†’ #e8f5e9 (render green) - chat.pdf: #fafbfc โ†’ #f3e8ff (chat purple) - Cover footer cacheplane.ai text: #888 โ†’ #8b8fa3 (textMuted) - TOC row border + meta color: rgba(0,0,0,.06)/#444 โ†’ #e6e8ee/#555770 (border + textSecondary) The committed PDF artifacts under apps/website/public/whitepaper*.pdf and public/whitepapers/*.pdf are unchanged โ€” they regenerate next time someone runs the whitepaper script (requires ANTHROPIC_API_KEY). Until then the deployed PDFs continue to use the legacy cover; the source of truth for the new cover lives in this script. Co-Authored-By: Claude Opus 4.7 --- apps/website/scripts/generate-whitepaper.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/website/scripts/generate-whitepaper.ts b/apps/website/scripts/generate-whitepaper.ts index 38470b8c3..01deec9db 100644 --- a/apps/website/scripts/generate-whitepaper.ts +++ b/apps/website/scripts/generate-whitepaper.ts @@ -70,7 +70,7 @@ const WHITEPAPERS: Record = { title: 'From Prototype to Production', subtitle: 'The Angular Agent Readiness Guide', eyebrow: '@ngaf/langgraph ยท Production Readiness Guide', - coverGradient: 'linear-gradient(135deg,#fef0f3 0%,#f4f0ff 45%,#eaf3ff 70%,#e6f4ff 100%)', + coverGradient: 'linear-gradient(135deg, #fafbfc 0%, #eaf3ff 100%)', outputPdf: 'apps/website/public/whitepaper.pdf', outputHtml: 'apps/website/public/whitepaper-preview.html', chapters: [ @@ -196,7 +196,7 @@ Tone: Direct, technical, peer-to-peer. No fluff. Audience is senior Angular engi title: 'The Enterprise Guide to Agent Streaming in Angular', subtitle: 'Ship LangGraph agents in Angular โ€” without building the plumbing', eyebrow: '@ngaf/langgraph ยท Enterprise Guide', - coverGradient: 'linear-gradient(135deg, #eaf3ff 0%, #e6f4ff 45%, #f4f0ff 70%, #fef0f3 100%)', + coverGradient: 'linear-gradient(135deg, #fafbfc 0%, #eaf3ff 100%)', outputPdf: 'apps/website/public/whitepapers/angular.pdf', outputHtml: 'apps/website/public/whitepapers/angular-preview.html', chapters: [ @@ -323,7 +323,7 @@ Tone: Direct, technical, peer-to-peer. No fluff. Audience is senior Angular engi title: 'The Enterprise Guide to Generative UI in Angular', subtitle: 'Agents that render UI โ€” without coupling to your frontend', eyebrow: '@ngaf/render ยท Enterprise Guide', - coverGradient: 'linear-gradient(135deg, #e8f5e9 0%, #eaf3ff 45%, #f4f0ff 70%, #fef0f3 100%)', + coverGradient: 'linear-gradient(135deg, #fafbfc 0%, #e8f5e9 100%)', outputPdf: 'apps/website/public/whitepapers/render.pdf', outputHtml: 'apps/website/public/whitepapers/render-preview.html', chapters: [ @@ -433,7 +433,7 @@ Tone: Direct, technical, peer-to-peer. No fluff. Audience is senior Angular engi title: 'The Enterprise Guide to Agent Chat Interfaces in Angular', subtitle: 'Production agent chat UI in days, not sprints', eyebrow: '@ngaf/chat ยท Enterprise Guide', - coverGradient: 'linear-gradient(135deg, #f3e8ff 0%, #f4f0ff 45%, #eaf3ff 70%, #e6f4ff 100%)', + coverGradient: 'linear-gradient(135deg, #fafbfc 0%, #f3e8ff 100%)', outputPdf: 'apps/website/public/whitepapers/chat.pdf', outputHtml: 'apps/website/public/whitepapers/chat-preview.html', chapters: [ @@ -564,7 +564,7 @@ function buildHTML( config: WhitepaperConfig, ): string { const tocHTML = chapters.map((ch, i) => ` -
+
${String(i + 1).padStart(2, '0')} ${ch.title}
`).join(''); @@ -601,7 +601,7 @@ function buildHTML(
${config.eyebrow}

${config.title.replace(/ /g, '
')}

${config.subtitle}

-
cacheplane.ai ยท ${new Date().getFullYear()}
+
cacheplane.ai ยท ${new Date().getFullYear()}