themes-coffee/email_wrapper.templ
Alex Dunmow 11c6c8c63e initial: theme plugin coffee
Bootstrapped during the 2026-06-06 BlockNinja consolidation. Was previously
an unversioned directory inside ~/src/blockninja-themes/coffee.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-06 14:11:22 +08:00

205 lines
7.5 KiB
Plaintext

package main
import (
"bytes"
"context"
"fmt"
"git.dev.alexdunmow.com/block/core/templates"
)
// CoffeeEmailWrapper renders a single-column 560px cream-and-espresso wrapper
// for transactional and newsletter emails. Table-only layout so it survives
// Outlook; doodle divider rendered inline as SVG so it survives email
// clients that strip styles.
func CoffeeEmailWrapper(body string, emailCtx templates.EmailContext) string {
var buf bytes.Buffer
_ = coffeeEmailTemplate(emailCtx, body).Render(context.Background(), &buf)
return buf.String()
}
templ coffeeEmailTemplate(emailCtx templates.EmailContext, body string) {
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta name="x-apple-disable-message-reformatting"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<title>{ emailCtx.SiteSettings.SiteName }</title>
<style type="text/css">
body, table, td, p, a, li, blockquote {
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
table, td {
mso-table-lspace: 0pt;
mso-table-rspace: 0pt;
}
img {
-ms-interpolation-mode: bicubic;
border: 0;
height: auto;
line-height: 100%;
outline: none;
text-decoration: none;
}
body {
margin: 0 !important;
padding: 0 !important;
width: 100% !important;
}
a[x-apple-data-detectors] {
color: inherit !important;
text-decoration: none !important;
}
h1, h2, h3, h4, h5, h6 {
font-family: "Fraunces", "Playfair Display", Georgia, "Times New Roman", serif;
font-weight: 600;
}
@media only screen and (max-width: 620px) {
.coffee-email-container {
width: 100% !important;
max-width: 100% !important;
}
.coffee-email-padding {
padding-left: 24px !important;
padding-right: 24px !important;
}
}
</style>
</head>
<body style={ fmt.Sprintf("background-color: %s; margin: 0; padding: 0; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;", coffeeBgColor(emailCtx)) }>
if emailCtx.PreviewText != "" {
<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;">
{ emailCtx.PreviewText }
</div>
}
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
<tr>
<td align="center" style={ fmt.Sprintf("padding: 40px 10px; background-color: %s;", coffeeBgColor(emailCtx)) }>
<table role="presentation" class="coffee-email-container" width="560" cellspacing="0" cellpadding="0" border="0" style={ fmt.Sprintf("max-width: 560px; background-color: %s; border: 1px solid %s; border-radius: 4px;", coffeeCardColor(emailCtx), coffeeBorderColor(emailCtx)) }>
<tr>
<td align="center" style={ fmt.Sprintf("padding: 32px 40px 16px; border-bottom: 1px dashed %s;", coffeeBorderColor(emailCtx)) }>
if emailCtx.SiteSettings.LogoURL != "" {
<img src={ emailCtx.SiteSettings.LogoURL } alt={ emailCtx.SiteSettings.SiteName } style="max-height: 48px; width: auto; display: block;"/>
} else if emailCtx.SiteSettings.SiteName != "" {
<h1 style={ fmt.Sprintf("margin: 0; font-size: 24px; letter-spacing: -0.01em; color: %s;", coffeePrimaryColor(emailCtx)) }>
{ emailCtx.SiteSettings.SiteName }
</h1>
}
</td>
</tr>
<tr>
<td class="coffee-email-padding" style={ fmt.Sprintf("padding: 32px 40px; color: %s; font-size: 16px; line-height: 1.65;", coffeeFgColor(emailCtx)) }>
@templ.Raw(body)
</td>
</tr>
<tr>
<td align="center" style="padding: 16px 40px 8px;">
<!-- inline SVG doodle divider, survives Outlook -->
<table role="presentation" cellspacing="0" cellpadding="0" border="0"><tr><td>
<svg xmlns="http://www.w3.org/2000/svg" width="200" height="32" viewBox="0 0 200 32" fill="none" stroke={ coffeeAccentColor(emailCtx) } stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M4 18 L80 18"></path>
<path d="M120 18 L196 18"></path>
<ellipse cx="95" cy="18" rx="6" ry="9"></ellipse>
<path d="M95 9 L95 27"></path>
<ellipse cx="108" cy="18" rx="6" ry="9"></ellipse>
<path d="M108 9 L108 27"></path>
</svg>
</td></tr></table>
</td>
</tr>
<tr>
<td style={ fmt.Sprintf("padding: 16px 40px 32px; color: %s; font-size: 12px; line-height: 1.6;", coffeeMutedFgColor(emailCtx)) }>
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
<tr>
<td align="center">
if emailCtx.SiteSettings.SiteName != "" {
<p style={ fmt.Sprintf("margin: 0 0 6px; font-size: 13px; color: %s;", coffeeFgColor(emailCtx)) }>
{ emailCtx.SiteSettings.SiteName }
</p>
}
<p style="margin: 0 0 6px;">Pull up a seat. Pastries from 7, coffee until late.</p>
if emailCtx.SiteSettings.SiteURL != "" {
<p style="margin: 0 0 8px;">
<a href={ templ.SafeURL(emailCtx.SiteSettings.SiteURL) } style={ fmt.Sprintf("color: %s; text-decoration: none; border-bottom: 1px dashed %s;", coffeeAccentColor(emailCtx), coffeeAccentColor(emailCtx)) }>
{ emailCtx.SiteSettings.SiteURL }
</a>
</p>
}
if emailCtx.UnsubscribeURL != "" {
<p style="margin: 0; font-size: 11px;">
<a href={ templ.SafeURL(emailCtx.UnsubscribeURL) } style={ fmt.Sprintf("color: %s; text-decoration: none;", coffeeMutedFgColor(emailCtx)) }>
Unsubscribe
</a>
</p>
}
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
}
// Coffee email color helpers — cream paper background, espresso ink, terracotta accent.
// EmailColors carries the resolved theme palette; we use Primary for the
// accent fallback because EmailColors has no dedicated Accent field.
func coffeeBgColor(emailCtx templates.EmailContext) string {
if emailCtx.Colors.Background != "" {
return emailCtx.Colors.Background
}
return "#f4ece1"
}
func coffeeCardColor(emailCtx templates.EmailContext) string {
if emailCtx.Colors.Card != "" {
return emailCtx.Colors.Card
}
return "#ece1d0"
}
func coffeeFgColor(emailCtx templates.EmailContext) string {
if emailCtx.Colors.Foreground != "" {
return emailCtx.Colors.Foreground
}
return "#3d2a1a"
}
func coffeePrimaryColor(emailCtx templates.EmailContext) string {
if emailCtx.Colors.Primary != "" {
return emailCtx.Colors.Primary
}
return "#8a4a23"
}
// coffeeAccentColor reuses Primary when no dedicated accent is in scope —
// EmailColors does not expose Accent; the resolved palette still applies via
// Primary which the CMS sets per-email.
func coffeeAccentColor(emailCtx templates.EmailContext) string {
if emailCtx.Colors.Primary != "" {
return emailCtx.Colors.Primary
}
return "#c95b2f"
}
func coffeeMutedFgColor(emailCtx templates.EmailContext) string {
if emailCtx.Colors.MutedForeground != "" {
return emailCtx.Colors.MutedForeground
}
return "#6b5440"
}
func coffeeBorderColor(emailCtx templates.EmailContext) string {
if emailCtx.Colors.Border != "" {
return emailCtx.Colors.Border
}
return "#c9b69e"
}