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>
188 lines
7.3 KiB
Plaintext
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,
|
|
)
|
|
}
|