package mail import ( "html" "strings" ) // Shared visual tokens for Silo's branded emails, mirroring the web UI's // default "Midnight Cinema" theme (web/src/app.css): a near-black canvas, // monochrome type, and a white primary action. Feature packages compose body // fragments with these tokens and wrap them with RenderLayout so every email // the server sends looks like it came from the same product. // // Email-client constraints shape everything here: styles must be inline, // layout must be tables, and colors must be explicit on every element (no // inheritance through client-rewritten DOM). Web fonts don't load in most // clients, so the stacks lead with the brand font and degrade to common // system faces. const ( EmailFont = "'Outfit','Avenir Next','Segoe UI',Helvetica,Arial,sans-serif" EmailFontMono = "'SF Mono',SFMono-Regular,Menlo,Consolas,'Liberation Mono',monospace" EmailColorCanvas = "#141417" // page background EmailColorCard = "#1c1c20" // content card surface EmailColorBorder = "#2e2e35" // card outline EmailColorText = "#e8e8ec" // primary text EmailColorMuted = "#9696a0" // secondary text, badges, footer EmailColorRule = "#26262c" // hairline row separators EmailColorAction = "#e8e8ec" // primary button background (white-on-dark) EmailColorOnAct = "#141417" // primary button label ) // LayoutOptions is the content RenderLayout places into the branded shell. type LayoutOptions struct { // Preheader is the hidden inbox-preview snippet shown next to the subject // line. Plain text; optional. Preheader string // Title is the headline at the top of the card. Plain text; optional. Title string // BodyHTML is the card content below the title. Trusted HTML — callers // must escape any user-controlled values before building it. BodyHTML string // FooterHTML is the fine print under the card. Trusted HTML; optional. FooterHTML string } // RenderLayout wraps content in Silo's dark branded email shell: wordmark, // content card, and footer. It adds no links of its own, so an email whose // options carry no hrefs renders fully link-free (some features require // that when no external URL is configured). func RenderLayout(opts LayoutOptions) string { preheader := "" if opts.Preheader != "" { // The trailing zwnj/nbsp run pads the preview so clients don't pull // body markup into the snippet after the real preheader text. preheader = `
` + html.EscapeString(opts.Preheader) + strings.Repeat(" ‌", 40) + `
` + "\n" } title := "" if opts.Title != "" { title = `

` + html.EscapeString(opts.Title) + `

` + "\n" } footer := "" if opts.FooterHTML != "" { footer = `` + opts.FooterHTML + `` + "\n" } return strings.NewReplacer( "{{preheader}}", preheader, "{{title}}", title, "{{body}}", opts.BodyHTML, "{{footer}}", footer, "{{font}}", EmailFont, "{{canvas}}", EmailColorCanvas, "{{card}}", EmailColorCard, "{{border}}", EmailColorBorder, "{{text}}", EmailColorText, ).Replace(emailShell) } // EmailButton renders the primary call-to-action: a white pill on the dark // card, matching the web UI's primary action style. Both arguments are // escaped here. The wrapping table keeps the button shape in Outlook, which // ignores padding on anchors. func EmailButton(label, href string) string { return `` + `
` + `` + html.EscapeString(label) + `` + `
` } // EmailParagraph renders one body paragraph in the standard text style, // escaping the given plain text. func EmailParagraph(text string) string { return `

` + html.EscapeString(text) + `

` } // emailShell is the document skeleton. The color-scheme meta plus explicit // bgcolor attributes keep dark-mode-aware clients from inverting the design; // the small stylesheet only tightens padding on narrow screens (supported by // Gmail/Apple Mail, harmlessly ignored elsewhere). const emailShell = ` {{preheader}}
{{footer}}
▸︎  SILO
{{title}}{{body}}
`