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

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

188 lines
7.3 KiB
Plaintext

package main
import (
"bytes"
"context"
"fmt"
"git.dev.alexdunmow.com/block/core/templates"
)
// PastelEmailWrapper wraps body content in the Pastel Dream branded email
// frame: cream paper background, watercolor blob watermark, 560px centered
// frame, Caveat Brush masthead, Nunito body, mint CTA pill, unsubscribe and a
// closing affirmation in the footer.
func PastelEmailWrapper(body string, emailCtx templates.EmailContext) string {
var buf bytes.Buffer
_ = pastelEmailTemplate(emailCtx, body).Render(context.Background(), &buf)
return buf.String()
}
// emailColorOr falls back to the supplied default when the EmailContext has
// not been populated with the preset color (e.g. for raw previews).
// All defaults pull from the blush-morning palette, expressed as hex because
// email clients cannot resolve CSS custom properties.
func emailColorOr(value, fallback string) string {
if value == "" {
return fallback
}
return value
}
func pastelEmailBg(c templates.EmailColors) string {
// blush-morning background: hsl(25 60% 98%) ≈ #fdf7f1
return emailColorOr(c.Background, "#fdf7f1")
}
func pastelEmailCard(c templates.EmailColors) string {
return emailColorOr(c.Card, "#ffffff")
}
func pastelEmailForeground(c templates.EmailColors) string {
// hsl(340 20% 22%) ≈ #432e36
return emailColorOr(c.Foreground, "#432e36")
}
func pastelEmailMutedFg(c templates.EmailColors) string {
// hsl(340 12% 48%) ≈ #80707a
return emailColorOr(c.MutedForeground, "#80707a")
}
func pastelEmailPrimary(c templates.EmailColors) string {
// hsl(350 65% 72%) ≈ #e9a1ad
return emailColorOr(c.Primary, "#e9a1ad")
}
func pastelEmailPrimaryFg(c templates.EmailColors) string {
return emailColorOr(c.PrimaryForeground, "#432e36")
}
func pastelEmailBorder(c templates.EmailColors) string {
return emailColorOr(c.Border, "#f0d8de")
}
// pastelEmailTemplate is the Pastel Dream email body template.
templ pastelEmailTemplate(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 {
-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;
}
.pastel-email-frame {
max-width: 560px;
margin: 0 auto;
}
@media only screen and (max-width: 600px) {
.pastel-email-frame {
width: 100% !important;
max-width: 100% !important;
}
.pastel-pad {
padding-left: 24px !important;
padding-right: 24px !important;
}
}
</style>
</head>
<body style={ fmt.Sprintf("background-color: %s; margin: 0; padding: 0; font-family: 'Nunito', 'Quicksand', Helvetica, Arial, sans-serif;", pastelEmailBg(emailCtx.Colors)) }>
if emailCtx.PreviewText != "" {
<div style="display: none; max-height: 0; overflow: hidden;">
{ emailCtx.PreviewText }
</div>
}
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style={ fmt.Sprintf("background-color: %s;", pastelEmailBg(emailCtx.Colors)) }>
<tr>
<td align="center" style="padding: 40px 12px; position: relative;">
<!-- Watermark blob (top-right). Inline SVG so any client that supports it renders the wash. -->
<div style="position: absolute; top: 0; right: 0; width: 200px; height: 200px; opacity: 0.35; pointer-events: none;">
<svg viewBox="0 0 200 200" width="200" height="200" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path d={ "M100,20 C160,20 180,80 180,120 C180,170 130,180 90,180 C40,180 20,140 20,100 C20,60 50,20 100,20 Z" } fill={ pastelEmailPrimary(emailCtx.Colors) }></path>
</svg>
</div>
<table role="presentation" class="pastel-email-frame" width="560" cellspacing="0" cellpadding="0" border="0" style={ fmt.Sprintf("max-width: 560px; background-color: %s; border-radius: 20px; border: 1px solid %s;", pastelEmailCard(emailCtx.Colors), pastelEmailBorder(emailCtx.Colors)) }>
<!-- Masthead -->
<tr>
<td align="center" class="pastel-pad" style={ fmt.Sprintf("padding: 36px 40px 8px; border-bottom: 1px solid %s;", pastelEmailBorder(emailCtx.Colors)) }>
if emailCtx.SiteSettings.LogoURL != "" {
<img src={ emailCtx.SiteSettings.LogoURL } alt={ emailCtx.SiteSettings.SiteName } style="max-height: 56px; width: auto; display: block;"/>
} else if emailCtx.SiteSettings.SiteName != "" {
<h1 style={ fmt.Sprintf("margin: 0; font-family: 'Caveat Brush', 'Sacramento', cursive; font-size: 36px; font-weight: 400; color: %s;", pastelEmailForeground(emailCtx.Colors)) }>
{ emailCtx.SiteSettings.SiteName }
</h1>
}
</td>
</tr>
<!-- Body -->
<tr>
<td class="pastel-pad" style={ fmt.Sprintf("padding: 32px 40px 24px; color: %s; font-size: 16px; line-height: 1.75;", pastelEmailForeground(emailCtx.Colors)) }>
@templ.Raw(body)
</td>
</tr>
<!-- Footer -->
<tr>
<td class="pastel-pad" align="center" style={ fmt.Sprintf("padding: 24px 40px 36px; border-top: 1px solid %s;", pastelEmailBorder(emailCtx.Colors)) }>
<p style={ fmt.Sprintf("margin: 0 0 12px; font-family: 'Caveat Brush', 'Sacramento', cursive; font-size: 18px; color: %s;", pastelEmailMutedFg(emailCtx.Colors)) }>
Be gentle with yourself today.
</p>
if emailCtx.SiteSettings.SiteURL != "" {
<p style={ fmt.Sprintf("margin: 0 0 12px; font-size: 13px; color: %s;", pastelEmailMutedFg(emailCtx.Colors)) }>
<a href={ templ.SafeURL(emailCtx.SiteSettings.SiteURL) } style={ fmt.Sprintf("color: %s; text-decoration: none;", pastelEmailPrimary(emailCtx.Colors)) }>
{ emailCtx.SiteSettings.SiteURL }
</a>
</p>
}
if emailCtx.UnsubscribeURL != "" {
<p style={ fmt.Sprintf("margin: 0; font-size: 11px; color: %s;", pastelEmailMutedFg(emailCtx.Colors)) }>
<a href={ templ.SafeURL(emailCtx.UnsubscribeURL) } style={ fmt.Sprintf("color: %s; text-decoration: underline;", pastelEmailMutedFg(emailCtx.Colors)) }>
Unsubscribe
</a>
</p>
}
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>
}
// pastelEmailPillStyle returns the inline CSS for the mint-tinted CTA pill
// that templates may use inside the body slot. The wrapper exposes it as a
// reusable token because the body is opaque pre-rendered HTML.
func pastelEmailPillStyle(c templates.EmailColors) string {
primary := pastelEmailPrimary(c)
primaryFg := pastelEmailPrimaryFg(c)
return fmt.Sprintf(
"display: inline-block; padding: 12px 28px; border-radius: 9999px; background-color: %s; color: %s; font-family: 'Nunito', Helvetica, Arial, sans-serif; font-weight: 600; text-decoration: none;",
primary,
primaryFg,
)
}