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',
};
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 = ({
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() {