/** * Carries each site style into email, so a digest looks like the site its * reader chose and the other emails like the instance's style. A mail client * takes no stylesheet of ours, so each style is spelled out here: its palettes, * copied from its theme under `src/themes/` and flattened to plain colors, and * the few touches that make it recognisable once inlined. `layout.ts` reads * them. A test fails when a copied color no longer matches its theme's. * * @module */ import type { StyleName } from "../../shared/theme.ts"; /** * The colors of one style in one scheme, by role. Most are the theme's own * tokens; the optional ones carry a style's particular touches. */ export interface EmailPalette { /** The page around the card (`--color-bg`). */ outer: string; /** The card holding the content (`--color-surface`). */ card: string; text: string; sec: string; muted: string; /** Section titles, ranks and button fills (`--color-accent`). */ accent: string; link: string; /** Text on an accent fill. */ onAccent: string; /** Dividers and hairlines. */ rule: string; /** Pills and the reset link's box. */ soft: string; /** Quoted comments and messages. */ quote: string; /** Kanagawa's fujiGray dates; others use `muted`. */ date?: string; /** Kanagawa's oniViolet wordmark; others use `text`. */ brand?: string; /** GeoCities' ridged border. */ border?: string; /** Melange's warm card shadow. */ shadow?: string; /** Melange's spice gradient, left to right. */ spice?: string[]; } /** * How a style draws an email. `layout.ts` builds every block from it, so * adding a style to `shared/theme.ts` means adding its entry here. */ export interface EmailStyle { /** * The font stack. Its first family is a web font only Apple's mail apps * load, through {@link webFont}; other clients fall back along the stack. */ font: string; /** The Google Fonts `family` parameter of the web font, if it is one. */ webFont?: string; /** Multiplies every font size: Times sets its serif larger, Kanagawa its monospace smaller. */ scale: number; /** Corner radii, in pixels. */ radius: { card: number; img: number; btn: number; pill: number; avatar: number; }; /** The heading: its size in pixels, weight, and Brutalist's uppercase and spacing. */ title: { size: number; weight: number; upper?: boolean; spacing?: string; accent?: boolean; }; /** The weight of titles of items, figures and names. */ heavy: number; /** How the card stands out from the page. */ frame: "none" | "hard" | "ridge" | "hairline" | "soft"; /** The divider between sections. */ rule: "hairline" | "thick" | "rainbow"; /** A colored bar along the card's top. */ topBar?: "rainbow" | "spice"; /** Underlines links, for styles whose links are the color of the text. */ linkUnderline?: boolean; light: EmailPalette; dark: EmailPalette; } /** GeoCities' rainbow, as its theme draws its dividers. */ export const RAINBOW = [ "#ff004d", "#ff8a00", "#ffee00", "#00d11a", "#00b3ff", "#b300ff", ]; /** Every style's email look, keyed by its name. */ export const EMAIL_STYLES: Record = { smooth: { font: "Saira, 'Helvetica Neue', Helvetica, Arial, sans-serif", webFont: "Saira:wght@400;600;700", scale: 1, radius: { card: 14, img: 6, btn: 8, pill: 999, avatar: 16 }, title: { size: 30, weight: 600 }, heavy: 600, frame: "none", rule: "hairline", light: { outer: "#faf5e9", card: "#fffdf8", text: "#0e3a42", sec: "#35626b", muted: "#4d7178", accent: "#b54a41", link: "#b54a41", onAccent: "#fffaf0", rule: "#ebe4d4", soft: "#f3ecdd", quote: "#5c7d83", }, dark: { outer: "#01152a", card: "#0a2139", text: "#e9eef2", sec: "#a3b9c7", muted: "#7b93a6", accent: "#f4d58d", link: "#f4d58d", onAccent: "#001427", rule: "#1b3350", soft: "#112a46", quote: "#a3b9c7", }, }, brutalist: { font: "'Space Grotesk', Arial, Helvetica, sans-serif", webFont: "Space+Grotesk:wght@400;700", scale: 1, radius: { card: 0, img: 0, btn: 0, pill: 0, avatar: 0 }, title: { size: 30, weight: 900, upper: true, spacing: "0.03em" }, heavy: 700, frame: "hard", rule: "thick", light: { outer: "#f4f1de", card: "#f8f5e8", text: "#3d405b", sec: "#5a5e80", muted: "#7a7e9a", accent: "#e07a5f", link: "#b85a3a", onAccent: "#f4f1de", rule: "#8a8da8", soft: "#ede9d4", quote: "#5a5e80", }, dark: { outer: "#252838", card: "#2e3145", text: "#f4f1de", sec: "#c8c5b2", muted: "#8e8c80", accent: "#f2cc8f", link: "#f2cc8f", onAccent: "#3d405b", rule: "#4a4e6a", soft: "#222436", quote: "#c8c5b2", }, }, geocities: { font: "'Comic Sans MS', 'Comic Sans', 'Chalkboard SE', 'Comic Neue', cursive", scale: 1, radius: { card: 0, img: 0, btn: 0, pill: 0, avatar: 0 }, title: { size: 30, weight: 700, accent: true }, heavy: 700, frame: "ridge", rule: "rainbow", topBar: "rainbow", linkUnderline: true, light: { outer: "#fff0f8", card: "#ffffff", text: "#2a1a4e", sec: "#aa00aa", muted: "#7a6a9a", accent: "#d000d0", link: "#0000ee", onAccent: "#ffffff", rule: "#f0c0f0", soft: "#fff0fb", quote: "#aa00aa", border: "#d000d0", }, dark: { outer: "#0a0a2e", card: "#1b1b46", text: "#f2f0ff", sec: "#7fdbff", muted: "#a99ad6", accent: "#ff00ff", link: "#00ffff", onAccent: "#000000", rule: "#34346e", soft: "#14143a", quote: "#7fdbff", border: "#00ffff", }, }, nyt: { font: "'Instrument Serif', Georgia, 'Times New Roman', serif", webFont: "Instrument+Serif:ital@0;1", scale: 1.12, radius: { card: 0, img: 0, btn: 0, pill: 0, avatar: 16 }, title: { size: 38, weight: 400 }, heavy: 400, frame: "hairline", rule: "hairline", linkUnderline: true, light: { outer: "#ffffff", card: "#ffffff", text: "#121212", sec: "#5a5a5a", muted: "#767676", accent: "#121212", link: "#1a1a1a", onAccent: "#ffffff", rule: "#e2e2e2", soft: "#f3f3f3", quote: "#5a5a5a", }, dark: { outer: "#121212", card: "#1e1e1e", text: "#e6e6e6", sec: "#ababab", muted: "#8a8a8a", accent: "#ececec", link: "#ececec", onAccent: "#121212", rule: "#3a3a3a", soft: "#2a2a2a", quote: "#ababab", }, }, kanagawa: { font: "'JetBrains Mono', Menlo, Consolas, 'Courier New', monospace", webFont: "JetBrains+Mono:ital,wght@0,400;0,600;1,400", scale: 0.9, radius: { card: 6, img: 4, btn: 4, pill: 3, avatar: 4 }, title: { size: 26, weight: 600 }, heavy: 600, frame: "hairline", rule: "hairline", light: { outer: "#f2ecbc", card: "#e7dfb2", text: "#545464", sec: "#716e61", muted: "#766b90", accent: "#4d699b", link: "#4d699b", onAccent: "#f2ecbc", rule: "#d0c79d", soft: "#dcd5ac", quote: "#6f894e", date: "#8a8980", brand: "#624c83", }, dark: { outer: "#1f1f28", card: "#2a2a37", text: "#dcd7ba", sec: "#c8c093", muted: "#938aa9", accent: "#7e9cd8", link: "#7e9cd8", onAccent: "#16161d", rule: "#43435a", soft: "#16161d", quote: "#98bb6c", date: "#727169", brand: "#957fb8", }, }, melange: { font: "Fraunces, Georgia, serif", webFont: "Fraunces:opsz,wght@9..144,400;9..144,600;9..144,700", scale: 1, radius: { card: 18, img: 10, btn: 12, pill: 999, avatar: 16 }, title: { size: 32, weight: 600 }, heavy: 600, frame: "soft", rule: "hairline", topBar: "spice", light: { outer: "#e9e1db", card: "#f6f2ef", text: "#54433a", sec: "#7d6658", muted: "#a98a78", accent: "#a06d00", link: "#a06d00", onAccent: "#f7f4f1", rule: "#e0d3ca", soft: "#ece4dd", quote: "#7d6658", shadow: "rgba(84,67,58,0.28)", spice: ["#a06d00", "#bc5c00", "#c77b8b", "#be79bb", "#6e9b72"], }, dark: { outer: "#292522", card: "#34302c", text: "#ece1d7", sec: "#c1a78e", muted: "#867462", accent: "#ebc06d", link: "#ebc06d", onAccent: "#292522", rule: "#4a423b", soft: "#403a36", quote: "#c1a78e", shadow: "rgba(0,0,0,0.5)", spice: ["#ebc06d", "#e49b5d", "#bd8183", "#b380b0", "#78997a"], }, }, };