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>
205 lines
7.5 KiB
Plaintext
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"
|
|
}
|