Bootstrapped during the 2026-06-06 BlockNinja consolidation. Was previously an unversioned directory inside ~/src/blockninja-themes/noir. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
137 lines
5.2 KiB
Plaintext
137 lines
5.2 KiB
Plaintext
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
|
|
"git.dev.alexdunmow.com/block/core/templates"
|
|
)
|
|
|
|
// NoirEmailWrapper wraps body content in a pure black 600px canvas with a
|
|
// Tenor Sans masthead and a mono caption strip above the footer.
|
|
func NoirEmailWrapper(body string, emailCtx templates.EmailContext) string {
|
|
var buf bytes.Buffer
|
|
_ = noirEmailTemplate(emailCtx, body).Render(context.Background(), &buf)
|
|
return buf.String()
|
|
}
|
|
|
|
// noirEmailTemplate renders the inline-styled HTML email wrapper.
|
|
templ noirEmailTemplate(emailCtx templates.EmailContext, body string) {
|
|
<!DOCTYPE html>
|
|
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
|
|
<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>
|
|
</head>
|
|
<body style={ fmt.Sprintf("background-color: %s; margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;", noirEmailBg(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" style={ fmt.Sprintf("background-color: %s;", noirEmailBg(emailCtx)) }>
|
|
<tr>
|
|
<td align="center" style="padding: 48px 12px;">
|
|
<table role="presentation" width="600" cellspacing="0" cellpadding="0" border="0" style={ fmt.Sprintf("width: 600px; max-width: 600px; background-color: %s; border: 1px solid %s;", noirEmailCard(emailCtx), noirEmailBorder(emailCtx)) }>
|
|
<!-- Masthead -->
|
|
<tr>
|
|
<td align="left" style={ fmt.Sprintf("padding: 28px 32px; border-bottom: 1px solid %s;", noirEmailBorder(emailCtx)) }>
|
|
if emailCtx.SiteSettings.SiteName != "" {
|
|
<div style={ fmt.Sprintf("margin: 0; font-size: 18px; font-weight: 400; font-family: 'Tenor Sans', Georgia, serif; letter-spacing: 0.05em; color: %s;", noirEmailFg(emailCtx)) }>
|
|
{ emailCtx.SiteSettings.SiteName }
|
|
</div>
|
|
}
|
|
</td>
|
|
</tr>
|
|
<!-- Cover image (16:10) -->
|
|
if emailCtx.SiteSettings.LogoURL != "" {
|
|
<tr>
|
|
<td style="padding: 0; line-height: 0;">
|
|
<img src={ emailCtx.SiteSettings.LogoURL } alt={ emailCtx.SiteSettings.SiteName } width="600" height="375" style="display: block; width: 600px; height: 375px; object-fit: cover; max-width: 100%;"/>
|
|
</td>
|
|
</tr>
|
|
}
|
|
<!-- Body -->
|
|
<tr>
|
|
<td style={ fmt.Sprintf("padding: 32px; color: %s; font-size: 16px; line-height: 1.7;", noirEmailFg(emailCtx)) }>
|
|
@templ.Raw(body)
|
|
</td>
|
|
</tr>
|
|
<!-- Mono caption strip -->
|
|
<tr>
|
|
<td style={ fmt.Sprintf("padding: 12px 32px; border-top: 1px solid %s; border-bottom: 1px solid %s; font-family: 'JetBrains Mono', Consolas, monospace; font-size: 10px; letter-spacing: 0.18em; text-transform: uppercase; color: %s;", noirEmailBorder(emailCtx), noirEmailBorder(emailCtx), noirEmailMutedFg(emailCtx)) }>
|
|
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0">
|
|
<tr>
|
|
<td align="left">
|
|
if emailCtx.SiteSettings.SiteName != "" {
|
|
{ "© " + emailCtx.SiteSettings.SiteName }
|
|
}
|
|
</td>
|
|
<td align="right">
|
|
if emailCtx.UnsubscribeURL != "" {
|
|
<a href={ templ.SafeURL(emailCtx.UnsubscribeURL) } style={ fmt.Sprintf("color: %s; text-decoration: none;", noirEmailMutedFg(emailCtx)) }>
|
|
Unsubscribe
|
|
</a>
|
|
}
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
<!-- Footer -->
|
|
<tr>
|
|
<td align="center" style={ fmt.Sprintf("padding: 20px 32px; color: %s; font-family: 'JetBrains Mono', Consolas, monospace; font-size: 10px; letter-spacing: 0.18em; text-transform: uppercase;", noirEmailMutedFg(emailCtx)) }>
|
|
if emailCtx.SiteSettings.SiteURL != "" {
|
|
<a href={ templ.SafeURL(emailCtx.SiteSettings.SiteURL) } style={ fmt.Sprintf("color: %s; text-decoration: none;", noirEmailMutedFg(emailCtx)) }>
|
|
{ emailCtx.SiteSettings.SiteURL }
|
|
</a>
|
|
}
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</body>
|
|
</html>
|
|
}
|
|
|
|
// noirEmailBg returns the canvas colour (pure black per spec) with a fallback
|
|
// derived from the EmailContext.
|
|
func noirEmailBg(emailCtx templates.EmailContext) string {
|
|
if emailCtx.Colors.Background != "" {
|
|
return emailCtx.Colors.Background
|
|
}
|
|
return "#000000"
|
|
}
|
|
|
|
func noirEmailCard(emailCtx templates.EmailContext) string {
|
|
if emailCtx.Colors.Card != "" {
|
|
return emailCtx.Colors.Card
|
|
}
|
|
return "#0a0a0a"
|
|
}
|
|
|
|
func noirEmailFg(emailCtx templates.EmailContext) string {
|
|
if emailCtx.Colors.Foreground != "" {
|
|
return emailCtx.Colors.Foreground
|
|
}
|
|
return "#f5f5f5"
|
|
}
|
|
|
|
func noirEmailMutedFg(emailCtx templates.EmailContext) string {
|
|
if emailCtx.Colors.MutedForeground != "" {
|
|
return emailCtx.Colors.MutedForeground
|
|
}
|
|
return "#8c8c8c"
|
|
}
|
|
|
|
func noirEmailBorder(emailCtx templates.EmailContext) string {
|
|
if emailCtx.Colors.Border != "" {
|
|
return emailCtx.Colors.Border
|
|
}
|
|
return "#1f1f1f"
|
|
}
|