| | |
| | | secondary: string |
| | | tertiary: string |
| | | highlight: string |
| | | textHighlight: string |
| | | } |
| | | |
| | | interface Colors { |
| | |
| | | darkMode: ColorScheme |
| | | } |
| | | |
| | | type FontSpecification = |
| | | | string |
| | | | { |
| | | name: string |
| | | weights?: number[] |
| | | includeItalic?: boolean |
| | | } |
| | | |
| | | export interface Theme { |
| | | typography: { |
| | | header: string |
| | | body: string |
| | | code: string |
| | | header: FontSpecification |
| | | body: FontSpecification |
| | | code: FontSpecification |
| | | } |
| | | cdnCaching: boolean |
| | | colors: Colors |
| | | fontOrigin: "googleFonts" | "local" |
| | | } |
| | | |
| | | export type ThemeKey = keyof Colors |
| | | |
| | | const DEFAULT_SANS_SERIF = |
| | | '-apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif' |
| | | 'system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"' |
| | | const DEFAULT_MONO = "ui-monospace, SFMono-Regular, SF Mono, Menlo, monospace" |
| | | |
| | | export function getFontSpecificationName(spec: FontSpecification): string { |
| | | if (typeof spec === "string") { |
| | | return spec |
| | | } |
| | | |
| | | return spec.name |
| | | } |
| | | |
| | | function formatFontSpecification(type: "header" | "body" | "code", spec: FontSpecification) { |
| | | if (typeof spec === "string") { |
| | | spec = { name: spec } |
| | | } |
| | | |
| | | const defaultIncludeWeights = type === "header" ? [400, 700] : [400, 600] |
| | | const defaultIncludeItalic = type === "body" |
| | | const weights = spec.weights ?? defaultIncludeWeights |
| | | const italic = spec.includeItalic ?? defaultIncludeItalic |
| | | |
| | | const features: string[] = [] |
| | | if (italic) { |
| | | features.push("ital") |
| | | } |
| | | |
| | | if (weights.length > 1) { |
| | | const weightSpec = italic |
| | | ? weights |
| | | .flatMap((w) => [`0,${w}`, `1,${w}`]) |
| | | .sort() |
| | | .join(";") |
| | | : weights.join(";") |
| | | |
| | | features.push(`wght@${weightSpec}`) |
| | | } |
| | | |
| | | if (features.length > 0) { |
| | | return `${spec.name}:${features.join(",")}` |
| | | } |
| | | |
| | | return spec.name |
| | | } |
| | | |
| | | export function googleFontHref(theme: Theme) { |
| | | const { code, header, body } = theme.typography |
| | | return `https://fonts.googleapis.com/css2?family=${code}&family=${header}:wght@400;700&family=${body}:ital,wght@0,400;0,600;1,400;1,600&display=swap` |
| | | const headerFont = formatFontSpecification("header", header) |
| | | const bodyFont = formatFontSpecification("body", body) |
| | | const codeFont = formatFontSpecification("code", code) |
| | | |
| | | return `https://fonts.googleapis.com/css2?family=${bodyFont}&family=${headerFont}&family=${codeFont}&display=swap` |
| | | } |
| | | |
| | | export function joinStyles(theme: Theme, ...stylesheet: string[]) { |
| | |
| | | --secondary: ${theme.colors.lightMode.secondary}; |
| | | --tertiary: ${theme.colors.lightMode.tertiary}; |
| | | --highlight: ${theme.colors.lightMode.highlight}; |
| | | --textHighlight: ${theme.colors.lightMode.textHighlight}; |
| | | |
| | | --headerFont: "${theme.typography.header}", ${DEFAULT_SANS_SERIF}; |
| | | --bodyFont: "${theme.typography.body}", ${DEFAULT_SANS_SERIF}; |
| | | --codeFont: "${theme.typography.code}", ${DEFAULT_MONO}; |
| | | --headerFont: "${getFontSpecificationName(theme.typography.header)}", ${DEFAULT_SANS_SERIF}; |
| | | --bodyFont: "${getFontSpecificationName(theme.typography.body)}", ${DEFAULT_SANS_SERIF}; |
| | | --codeFont: "${getFontSpecificationName(theme.typography.code)}", ${DEFAULT_MONO}; |
| | | } |
| | | |
| | | :root[saved-theme="dark"] { |
| | |
| | | --secondary: ${theme.colors.darkMode.secondary}; |
| | | --tertiary: ${theme.colors.darkMode.tertiary}; |
| | | --highlight: ${theme.colors.darkMode.highlight}; |
| | | --textHighlight: ${theme.colors.darkMode.textHighlight}; |
| | | } |
| | | ` |
| | | } |