fix(reader): rewrite getCSS with concrete theme colors and aggressive !important

Studied grimmory-tools/grimmory's ebook-reader style.service.ts and adopted
their approach:

- Replace CSS custom property resolution (getComputedStyle) with a hardcoded
  THEME_COLORS map containing concrete fg/bg/link values for all 18 themes
  in both light and dark modes. No more variable resolution failures.

- Font family now targets body + body * with !important, overriding book CSS
  on every element (matching grimmory's approach).

- Colors use grimmory's aggressive pattern:
  html, body { color: ... !important; background: none !important; }
  body * { color: inherit !important; background-color: ... !important; }
  This forces reading theme colors on ALL book elements, overriding inline
  styles and book stylesheets.

- Line height uses !important on p, li, blockquote, dd to override book CSS.

- Removed fragile getComputedStyle calls entirely. getCSS() now receives
  themeName and themeMode parameters for direct color lookup.
This commit is contained in:
2026-04-19 21:01:07 -04:00
parent d68879a6c6
commit 0eb3a7df48
+125 -47
View File
@@ -15,58 +15,147 @@ const FONT_MAP: Record<string, string> = {
"comic-sans": '"Comic Sans MS", "Comic Sans", cursive', "comic-sans": '"Comic Sans MS", "Comic Sans", cursive',
}; };
interface ThemeColors {
fg: string;
bg: string;
link: string;
}
const THEME_COLORS: Record<string, { light: ThemeColors; dark: ThemeColors }> = {
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 = ({ const getCSS = ({
fontFamily, fontFamily,
fontSize, fontSize,
lineHeight, lineHeight,
justify, justify,
hyphenate, hyphenate,
themeName,
themeMode,
}: { }: {
fontFamily: string; fontFamily: string;
fontSize: number; fontSize: number;
lineHeight: number; lineHeight: number;
justify: boolean; justify: boolean;
hyphenate: boolean; hyphenate: boolean;
themeName: string;
themeMode: string;
}) => { }) => {
const el = document.getElementById("reader-viewport"); const themeSet = THEME_COLORS[themeName] ?? THEME_COLORS.light;
const style = getComputedStyle(el ?? document.body); const theme = themeMode === "dark" ? themeSet.dark : themeSet.light;
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 font = FONT_MAP[fontFamily] ?? '"Literata"'; const font = FONT_MAP[fontFamily] ?? '"Literata"';
const fontFamilyRule = fontFamily ? `
body {
font-family: ${font}, serif !important;
}
body * {
font-family: inherit !important;
}` : "";
return ` return `
@namespace epub "http://www.idpf.org/2007/ops"; @namespace epub "http://www.idpf.org/2007/ops";
html { html {
color-scheme: light dark; color-scheme: light dark;
background-color: ${bg} !important;
color: ${text} !important;
font-family: ${font}, serif;
font-size: ${fontSize}px; font-size: ${fontSize}px;
line-height: ${lineHeight}; line-height: ${lineHeight};
} }
::selection { ${fontFamilyRule}
background-color: ${selection}; html, body {
color: ${theme.fg} !important;
background: none !important;
} }
a:link { body * {
color: ${link}; color: inherit !important;
border-color: currentColor !important;
background-color: ${theme.bg} !important;
} }
@media (prefers-color-scheme: dark) { a:any-link {
a:link { color: ${theme.link} !important;
color: ${link}; 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 { p, li, blockquote, dd {
line-height: ${lineHeight}; line-height: ${lineHeight} !important;
text-align: ${justify ? "justify" : "start"}; text-align: ${justify ? "justify" : "start"} !important;
-webkit-hyphens: ${hyphenate ? "auto" : "manual"}; hyphens: ${hyphenate ? "auto" : "none"};
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;
} }
[align="left"] { text-align: left; } [align="left"] { text-align: left; }
[align="right"] { text-align: right; } [align="right"] { text-align: right; }
@@ -74,32 +163,19 @@ const getCSS = ({
[align="justify"] { text-align: justify; } [align="justify"] { text-align: justify; }
pre { pre {
white-space: pre-wrap !important; white-space: pre-wrap !important;
tab-size: 2;
} }
aside[epub|type~="endnote"], ::selection {
aside[epub|type~="footnote"], background-color: rgba(128, 128, 128, 0.3);
aside[epub|type~="note"],
aside[epub|type~="rearnote"] {
display: none;
} }
img, svg, video { img, svg, video {
max-width: 100%; max-width: 100%;
height: auto; height: auto;
display: block;
margin: 0.5em auto;
} }
p { html {
orphans: 3; hanging-punctuation: allow-end last;
widows: 3; orphans: 2;
} widows: 2;
blockquote {
margin: 1em 0;
padding-left: 1em;
border-left: 3px solid ${link};
font-style: italic;
}
a {
color: ${link};
text-decoration: underline;
} }
`; `;
}; };
@@ -346,6 +422,8 @@ document.addEventListener("alpine:init", () => {
lineHeight: this.lineHeight, lineHeight: this.lineHeight,
justify: this.justify, justify: this.justify,
hyphenate: this.hyphenate, hyphenate: this.hyphenate,
themeName: this.readingTheme,
themeMode: this.readingMode,
}); });
}, },
restoreDefaults() { restoreDefaults() {