diff --git a/web/src/reader/reader.ts b/web/src/reader/reader.ts index 51d26bd..6ba25d0 100644 --- a/web/src/reader/reader.ts +++ b/web/src/reader/reader.ts @@ -15,58 +15,147 @@ const FONT_MAP: Record = { "comic-sans": '"Comic Sans MS", "Comic Sans", cursive', }; +interface ThemeColors { + fg: string; + bg: string; + link: string; +} + +const THEME_COLORS: Record = { + light: { + light: { fg: "#1a1a1a", bg: "#fafafa", link: "#0066cc" }, + dark: { fg: "#e8e8e8", bg: "#1a1a1a", link: "#60a5fa" }, + }, + paper: { + light: { fg: "#2d2d2d", bg: "#fdfbf7", link: "#0284c7" }, + dark: { fg: "#ebe5dd", bg: "#1c1917", link: "#38bdf8" }, + }, + sepia: { + light: { fg: "#4a3728", bg: "#f4ecd8", link: "#8b4513" }, + dark: { fg: "#d4c4a8", bg: "#2b2420", link: "#d97706" }, + }, + parchment: { + light: { fg: "#5c4033", bg: "#f0e6d3", link: "#92400e" }, + dark: { fg: "#e8dcc8", bg: "#3d2b1f", link: "#b45309" }, + }, + warm: { + light: { fg: "#2b2b2b", bg: "#faf8f0", link: "#d97706" }, + dark: { fg: "#f5e6d3", bg: "#2d2416", link: "#fbbf24" }, + }, + candlelight: { + light: { fg: "#3d2b1f", bg: "#fef3c7", link: "#b45309" }, + dark: { fg: "#fde68a", bg: "#451a03", link: "#fb923c" }, + }, + azure: { + light: { fg: "#262d48", bg: "#cedef5", link: "#2d53e5" }, + dark: { fg: "#babee1", bg: "#282e47", link: "#0ea5e9" }, + }, + sky: { + light: { fg: "#1e3a5f", bg: "#e0f2fe", link: "#0284c7" }, + dark: { fg: "#bae6fd", bg: "#0c4a6e", link: "#0ea5e9" }, + }, + arctic: { + light: { fg: "#1e3a5f", bg: "#f0f9ff", link: "#0284c7" }, + dark: { fg: "#bfdbfe", bg: "#1e293b", link: "#38bdf8" }, + }, + frost: { + light: { fg: "#334155", bg: "#f8fafc", link: "#38bdf8" }, + dark: { fg: "#e0f2fe", bg: "#0f172a", link: "#7dd3fc" }, + }, + dusk: { + light: { fg: "#3d2914", bg: "#fef3e2", link: "#ea580c" }, + dark: { fg: "#f5d5b8", bg: "#2d1f14", link: "#fb923c" }, + }, + sunset: { + light: { fg: "#4a1d1d", bg: "#fff7ed", link: "#f97316" }, + dark: { fg: "#fed7aa", bg: "#431407", link: "#fb923c" }, + }, + twilight: { + light: { fg: "#4c1d95", bg: "#f5f3ff", link: "#8b5cf6" }, + dark: { fg: "#ddd6fe", bg: "#2e1065", link: "#a78bfa" }, + }, + forest: { + light: { fg: "#1a2e1a", bg: "#e8efe8", link: "#15803d" }, + dark: { fg: "#c8dcc8", bg: "#1a2e1a", link: "#22c55e" }, + }, + moss: { + light: { fg: "#14532d", bg: "#dcfce7", link: "#22c55e" }, + dark: { fg: "#86efac", bg: "#052e16", link: "#4ade80" }, + }, + slate: { + light: { fg: "#334155", bg: "#f8fafc", link: "#475569" }, + dark: { fg: "#e2e8f0", bg: "#1e293b", link: "#94a3b8" }, + }, + oled: { + light: { fg: "#000000", bg: "#ffffff", link: "#0066cc" }, + dark: { fg: "#ffffff", bg: "#000000", link: "#3b82f6" }, + }, + solarized: { + light: { fg: "#657b83", bg: "#fdf6e3", link: "#268bd2" }, + dark: { fg: "#839496", bg: "#002b36", link: "#268bd2" }, + }, +}; + const getCSS = ({ fontFamily, fontSize, lineHeight, justify, hyphenate, + themeName, + themeMode, }: { fontFamily: string; fontSize: number; lineHeight: number; justify: boolean; hyphenate: boolean; + themeName: string; + themeMode: string; }) => { - const el = document.getElementById("reader-viewport"); - const style = getComputedStyle(el ?? document.body); - const bg = style.getPropertyValue("--reader-bg").trim() || "#fafafa"; - const text = style.getPropertyValue("--reader-text").trim() || "#1a1a1a"; - const link = style.getPropertyValue("--reader-link").trim() || "#0066cc"; - const selection = - style.getPropertyValue("--reader-selection").trim() || "#b3d9ff"; + const themeSet = THEME_COLORS[themeName] ?? THEME_COLORS.light; + const theme = themeMode === "dark" ? themeSet.dark : themeSet.light; const font = FONT_MAP[fontFamily] ?? '"Literata"'; + const fontFamilyRule = fontFamily ? ` + body { + font-family: ${font}, serif !important; + } + body * { + font-family: inherit !important; + }` : ""; + return ` @namespace epub "http://www.idpf.org/2007/ops"; html { color-scheme: light dark; - background-color: ${bg} !important; - color: ${text} !important; - font-family: ${font}, serif; font-size: ${fontSize}px; line-height: ${lineHeight}; } - ::selection { - background-color: ${selection}; + ${fontFamilyRule} + html, body { + color: ${theme.fg} !important; + background: none !important; } - a:link { - color: ${link}; + body * { + color: inherit !important; + border-color: currentColor !important; + background-color: ${theme.bg} !important; } - @media (prefers-color-scheme: dark) { - a:link { - color: ${link}; - } + a:any-link { + color: ${theme.link} !important; + text-decoration-color: color-mix(in srgb, currentColor 20%, transparent); + text-underline-offset: .1em; + } + svg, img { + background-color: transparent !important; + } + aside[epub|type~="footnote"] { + display: none; } p, li, blockquote, dd { - line-height: ${lineHeight}; - text-align: ${justify ? "justify" : "start"}; - -webkit-hyphens: ${hyphenate ? "auto" : "manual"}; - hyphens: ${hyphenate ? "auto" : "manual"}; - -webkit-hyphenate-limit-before: 3; - -webkit-hyphenate-limit-after: 2; - -webkit-hyphenate-limit-lines: 2; - hanging-punctuation: allow-end last; - widows: 2; + line-height: ${lineHeight} !important; + text-align: ${justify ? "justify" : "start"} !important; + hyphens: ${hyphenate ? "auto" : "none"}; } [align="left"] { text-align: left; } [align="right"] { text-align: right; } @@ -74,32 +163,19 @@ const getCSS = ({ [align="justify"] { text-align: justify; } pre { white-space: pre-wrap !important; + tab-size: 2; } - aside[epub|type~="endnote"], - aside[epub|type~="footnote"], - aside[epub|type~="note"], - aside[epub|type~="rearnote"] { - display: none; + ::selection { + background-color: rgba(128, 128, 128, 0.3); } img, svg, video { max-width: 100%; height: auto; - display: block; - margin: 0.5em auto; } - p { - orphans: 3; - widows: 3; - } - blockquote { - margin: 1em 0; - padding-left: 1em; - border-left: 3px solid ${link}; - font-style: italic; - } - a { - color: ${link}; - text-decoration: underline; + html { + hanging-punctuation: allow-end last; + orphans: 2; + widows: 2; } `; }; @@ -346,6 +422,8 @@ document.addEventListener("alpine:init", () => { lineHeight: this.lineHeight, justify: this.justify, hyphenate: this.hyphenate, + themeName: this.readingTheme, + themeMode: this.readingMode, }); }, restoreDefaults() {