/** * Export themes. A theme is a self-contained CSS stylesheet plus the font and * colour tokens exporters use to set defaults (EPUB reading-mode overrides, * DOCX/ODT font families, PDF margins). * * Four themes ship by default: * - Ink — neutral serif, light (the default trade trim size look) * - Parchment — warm cream paper, dark text * - Nocturne — dark mode, light text * - Terminal — monospace, terminal aesthetic */ export interface ThemeColors { text: string; background: string; accent: string; muted: string; heading: string; } export interface Theme { id: string; name: string; /** Body font family. */ fontFamily: string; /** Heading font family (falls back to fontFamily when null). */ headingFont: string | null; /** Base body font size (CSS length). */ fontSize: string; /** Body line height. */ lineHeight: string; /** Paragraph indent. */ paragraphIndent: string; colors: ThemeColors; /** Full stylesheet, scoped under `.ps-doc`, ready to inline in HTML/EPUB. */ css: string; } const baseRules = (t: Theme): string => ` .ps-doc { font-family: ${t.fontFamily}; font-size: ${t.fontSize}; line-height: ${t.lineHeight}; color: ${t.colors.text}; background: ${t.colors.background}; } .ps-doc h1, .ps-doc h2, .ps-doc h3 { font-family: ${t.headingFont ?? t.fontFamily}; color: ${t.colors.heading}; line-height: 1.25; margin: 1.4em 0 0.6em; } .ps-doc h1 { font-size: 2em; } .ps-doc h2 { font-size: 1.5em; } .ps-doc h3 { font-size: 1.2em; } .ps-doc p { margin: 0; text-indent: ${t.paragraphIndent}; orphans: 2; widows: 2; } .ps-doc p:first-of-type { text-indent: none; } .ps-doc blockquote { margin: 1em 0; padding: 0.2em 1em; border-left: 3px solid ${t.colors.accent}; color: ${t.colors.muted}; font-style: italic; } .ps-doc pre { white-space: pre-wrap; word-wrap: break-word; background: ${t.colors.background}; border: 1px solid ${t.colors.muted}; border-radius: 4px; padding: 0.6em 0.8em; font-size: 0.9em; } .ps-doc a { color: ${t.colors.accent}; text-decoration: underline; } .ps-doc .scene-break { text-align: center; margin: 1.4em 0; color: ${t.colors.muted}; } `; const makeTheme = (spec: Omit): Theme => ({ ...spec, css: baseRules(spec as Theme), }); const ink: Theme = makeTheme({ id: "ink", name: "Ink", fontFamily: '"Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif', headingFont: '"Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif', fontSize: "11pt", lineHeight: "1.55", paragraphIndent: "1.4em", colors: { text: "#1a1a1a", background: "#ffffff", accent: "#7a2e2e", muted: "#6b6b6b", heading: "#101010", }, }); const parchment: Theme = makeTheme({ id: "parchment", name: "Parchment", fontFamily: '"Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif', headingFont: '"Garamond", "EB Garamond", Georgia, serif', fontSize: "11pt", lineHeight: "1.6", paragraphIndent: "1.4em", colors: { text: "#3a3226", background: "#f4ecd8", accent: "#8a5a2b", muted: "#7a6f5c", heading: "#4a3a22", }, }); const nocturne: Theme = makeTheme({ id: "nocturne", name: "Nocturne", fontFamily: '"Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif', headingFont: '"Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif', fontSize: "11pt", lineHeight: "1.6", paragraphIndent: "1.4em", colors: { text: "#d8d8d8", background: "#14161a", accent: "#7fb3ff", muted: "#9aa0aa", heading: "#ffffff", }, }); const terminal: Theme = makeTheme({ id: "terminal", name: "Terminal", fontFamily: '"JetBrains Mono", "Fira Code", "SF Mono", Menlo, Consolas, monospace', headingFont: '"JetBrains Mono", "Fira Code", "SF Mono", Menlo, Consolas, monospace', fontSize: "9.5pt", lineHeight: "1.5", paragraphIndent: "0", colors: { text: "#d6ffdb", background: "#0b0f10", accent: "#7dff9c", muted: "#7fae86", heading: "#b6ffbf", }, }); /** All available themes, keyed by id. */ export const THEMES: Record = { ink, parchment, nocturne, terminal, }; /** The default theme id. */ export const DEFAULT_THEME_ID = "ink"; /** Look up a theme by id, falling back to the default. */ export function getTheme(id?: string): Theme { return THEMES[id ?? DEFAULT_THEME_ID] ?? ink; }