fix(reader): inject reader-fonts.css into iframe, remove foliate-themes.css

Fonts weren't loading because the paginator renders inside a sandboxed
iframe. @font-face declarations in the parent page's CSS are invisible
to the iframe's document. Even injecting @font-face rules via
setStyles() may not trigger font loading in sandboxed iframes.

Fix: inject a <link> to reader-fonts.css directly into the iframe's
document on each section load, so @font-face declarations are parsed
in the iframe's own document context where font-family rules can
reference them.

Also:
- Remove foliate-themes.css entirely (no longer needed)
- Set viewport background color directly via JS using THEME_COLORS map
- Remove reading theme CSS classes from viewport element
This commit is contained in:
2026-04-19 21:23:55 -04:00
parent 4f5825529a
commit 0007b4d1d0
4 changed files with 17 additions and 292 deletions
-1
View File
@@ -11,7 +11,6 @@ templ Reader(user User, metadata ReaderMetadata, progress ReadingProgress, bookm
<title>{ metadata.Title } - Bookhoard Reader</title>
<link rel="manifest" href="/static/manifest.json"/>
<link href="/static/reader-fonts.css" rel="stylesheet"/>
<link href="/static/foliate-themes.css" rel="stylesheet"/>
<script src="/static/htmx.min.js"></script>
<script type="module" src="/static/reader.js"></script>
<link href="/static/style.css" rel="stylesheet"/>
+7 -7
View File
@@ -44,14 +44,14 @@ func Reader(user User, metadata ReaderMetadata, progress ReadingProgress, bookma
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " - Bookhoard Reader</title><link rel=\"manifest\" href=\"/static/manifest.json\"><link href=\"/static/reader-fonts.css\" rel=\"stylesheet\"><link href=\"/static/foliate-themes.css\" rel=\"stylesheet\"><script src=\"/static/htmx.min.js\"></script><script type=\"module\" src=\"/static/reader.js\"></script><link href=\"/static/style.css\" rel=\"stylesheet\"></head><body x-data=\"readerShell\" x-init=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " - Bookhoard Reader</title><link rel=\"manifest\" href=\"/static/manifest.json\"><link href=\"/static/reader-fonts.css\" rel=\"stylesheet\"><script src=\"/static/htmx.min.js\"></script><script type=\"module\" src=\"/static/reader.js\"></script><link href=\"/static/style.css\" rel=\"stylesheet\"></head><body x-data=\"readerShell\" x-init=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf(`initReader({mediaItemId:'%s',fileUrl:'%s',formatGroup:'%s',readingDirection:'%s',mangaType:'%s'})`, metadata.MediaItemID, metadata.FileURL, metadata.FormatGroup, metadata.ReadingDirection, metadata.MangaType))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 21, Col: 233}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 20, Col: 233}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
@@ -141,7 +141,7 @@ func ReaderChrome(user User, metadata ReaderMetadata, progress ReadingProgress)
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(metadata.Title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 62, Col: 54}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 61, Col: 54}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
@@ -154,7 +154,7 @@ func ReaderChrome(user User, metadata ReaderMetadata, progress ReadingProgress)
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d/%d", progress.CurrentPage, progress.TotalPages))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 147, Col: 70}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 146, Col: 70}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
@@ -293,7 +293,7 @@ func ReaderBookmarksPanel(bookmarks []Bookmark) templ.Component {
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(bookmark.CfiPosition)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 359, Col: 38}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 358, Col: 38}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
@@ -306,7 +306,7 @@ func ReaderBookmarksPanel(bookmarks []Bookmark) templ.Component {
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(bookmark.Title)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 362, Col: 49}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 361, Col: 49}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
@@ -319,7 +319,7 @@ func ReaderBookmarksPanel(bookmarks []Bookmark) templ.Component {
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(bookmark.Position)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 364, Col: 27}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `templates/reader.templ`, Line: 363, Col: 27}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {
+10 -9
View File
@@ -358,10 +358,9 @@ document.addEventListener("alpine:init", () => {
}
}
const viewport = document.getElementById("reader-viewport")!;
viewport.classList.add(`theme-${this.readingTheme}`);
if (this.readingMode === "dark") {
viewport.classList.add("dark");
}
const themeSet = THEME_COLORS[this.readingTheme] ?? THEME_COLORS.light;
const themeColors = this.readingMode === "dark" ? themeSet.dark : themeSet.light;
viewport.style.backgroundColor = themeColors.bg;
this.view = document.getElementById("reader-view") as any;
const resp = await fetch(config.fileUrl, {
headers: { Authorization: `Bearer ${getToken()}` },
@@ -383,6 +382,10 @@ document.addEventListener("alpine:init", () => {
}
this.view.addEventListener("load", (e: any) => {
const { doc } = e.detail;
const link = doc.createElement("link");
link.rel = "stylesheet";
link.href = "/static/reader-fonts.css";
doc.head.append(link);
doc.addEventListener("keydown", (ev: KeyboardEvent) =>
this.handleKeydown(ev),
);
@@ -514,11 +517,9 @@ document.addEventListener("alpine:init", () => {
},
applyTheme() {
const viewport = document.getElementById("reader-viewport")!;
viewport.className = "absolute inset-x-0 top-[52px] bottom-[52px]";
viewport.classList.add(`theme-${this.readingTheme}`);
if (this.readingMode === "dark") {
viewport.classList.add("dark");
}
const themeSet = THEME_COLORS[this.readingTheme] ?? THEME_COLORS.light;
const themeColors = this.readingMode === "dark" ? themeSet.dark : themeSet.light;
viewport.style.backgroundColor = themeColors.bg;
this.applyStyles();
saveSettings({
reading_theme: this.readingTheme,
-275
View File
@@ -1,275 +0,0 @@
/* ==========================================
Bookhoard Reading Themes - 18 Themes
Organized by Category for User Convenience
========================================== */
/* ==========================================
CATEGORY 1: CLASSIC READING (6 themes)
Time-tested, comfortable for long sessions
========================================== */
#reader-viewport {
background-color: var(--reader-bg);
color: var(--reader-text);
font-family: var(--reader-font, "Literata", serif);
font-size: var(--reader-font-size, 16px);
line-height: var(--reader-line-height, 1.6);
}
#reader-viewport ::selection {
background-color: var(--reader-selection);
}
#reader-viewport a {
color: var(--reader-link);
text-decoration: underline;
}
/* Light - Default theme */
.theme-light {
--reader-bg: #fafafa;
--reader-text: #1a1a1a;
--reader-link: #0066cc;
--reader-selection: #b3d9ff;
}
.theme-light.dark {
--reader-bg: #1a1a1a;
--reader-text: #e8e8e8;
--reader-link: #60a5fa;
--reader-selection: #1e3a8a;
}
/* Paper - Warmer, softer */
.theme-paper {
--reader-bg: #fdfbf7;
--reader-text: #2d2d2d;
--reader-link: #0284c7;
--reader-selection: #bae6fd;
}
.theme-paper.dark {
--reader-bg: #1c1917;
--reader-text: #ebe5dd;
--reader-link: #38bdf8;
--reader-selection: #0c4a6e;
}
/* Sepia - Classic e-reader */
.theme-sepia {
--reader-bg: #f4ecd8;
--reader-text: #4a3728;
--reader-link: #8b4513;
--reader-selection: #d4c4a8;
}
.theme-sepia.dark {
--reader-bg: #2b2420;
--reader-text: #d4c4a8;
--reader-link: #d97706;
--reader-selection: #451a03;
}
/* Parchment - Aged document feel */
.theme-parchment {
--reader-bg: #f0e6d3;
--reader-text: #5c4033;
--reader-link: #92400e;
--reader-selection: #e7c9a8;
}
.theme-parchment.dark {
--reader-bg: #3d2b1f;
--reader-text: #e8dcc8;
--reader-link: #b45309;
--reader-selection: #292524;
}
/* Warm - Cozy evening */
.theme-warm {
--reader-bg: #faf8f0;
--reader-text: #2b2b2b;
--reader-link: #d97706;
--reader-selection: #fef3c7;
}
.theme-warm.dark {
--reader-bg: #2d2416;
--reader-text: #f5e6d3;
--reader-link: #fbbf24;
--reader-selection: #451a03;
}
/* Candlelight - Intimate glow */
.theme-candlelight {
--reader-bg: #fef3c7;
--reader-text: #3d2b1f;
--reader-link: #b45309;
--reader-selection: #fde68a;
}
.theme-candlelight.dark {
--reader-bg: #451a03;
--reader-text: #fde68a;
--reader-link: #fb923c;
--reader-selection: #78350f;
}
/* ==========================================
CATEGORY 2: SKY & ATMOSPHERE (4 themes)
Open, airy, contemplative
========================================== */
/* Azure - Open sky */
.theme-azure {
--reader-bg: #cedef5;
--reader-text: #262d48;
--reader-link: #2d53e5;
--reader-selection: #a5b4fc;
}
.theme-azure.dark {
--reader-bg: #282e47;
--reader-text: #babee1;
--reader-link: #0ea5e9;
--reader-selection: #1e3a8a;
}
/* Sky - Clear day */
.theme-sky {
--reader-bg: #e0f2fe;
--reader-text: #1e3a5f;
--reader-link: #0284c7;
--reader-selection: #7dd3fc;
}
.theme-sky.dark {
--reader-bg: #0c4a6e;
--reader-text: #bae6fd;
--reader-link: #0ea5e9;
--reader-selection: #075985;
}
/* Arctic - Icy cool */
.theme-arctic {
--reader-bg: #f0f9ff;
--reader-text: #1e3a5f;
--reader-link: #0284c7;
--reader-selection: #bae6fd;
}
.theme-arctic.dark {
--reader-bg: #1e293b;
--reader-text: #bfdbfe;
--reader-link: #38bdf8;
--reader-selection: #0c4a6e;
}
/* Frost - Crisp winter */
.theme-frost {
--reader-bg: #f8fafc;
--reader-text: #334155;
--reader-link: #38bdf8;
--reader-selection: #e0f2fe;
}
.theme-frost.dark {
--reader-bg: #0f172a;
--reader-text: #e0f2fe;
--reader-link: #7dd3fc;
--reader-selection: #1e293b;
}
/* ==========================================
CATEGORY 3: SUNSET & WARMTH (3 themes)
Warm, energizing, relaxation
========================================== */
/* Dusk - Evening transition */
.theme-dusk {
--reader-bg: #fef3e2;
--reader-text: #3d2914;
--reader-link: #ea580c;
--reader-selection: #fed7aa;
}
.theme-dusk.dark {
--reader-bg: #2d1f14;
--reader-text: #f5d5b8;
--reader-link: #fb923c;
--reader-selection: #431407;
}
/* Sunset - Peak colors */
.theme-sunset {
--reader-bg: #fff7ed;
--reader-text: #4a1d1d;
--reader-link: #f97316;
--reader-selection: #ffedd5;
}
.theme-sunset.dark {
--reader-bg: #431407;
--reader-text: #fed7aa;
--reader-link: #fb923c;
--reader-selection: #7c2d12;
}
/* Twilight - Magical hour */
.theme-twilight {
--reader-bg: #f5f3ff;
--reader-text: #4c1d95;
--reader-link: #8b5cf6;
--reader-selection: #ddd6fe;
}
.theme-twilight.dark {
--reader-bg: #2e1065;
--reader-text: #ddd6fe;
--reader-link: #a78bfa;
--reader-selection: #4c1d95;
}
/* ==========================================
CATEGORY 4: NATURE & EARTH (3 themes)
Grounded, natural, calming
========================================== */
/* Forest - Deep woods */
.theme-forest {
--reader-bg: #e8efe8;
--reader-text: #1a2e1a;
--reader-link: #15803d;
--reader-selection: #bbf7d0;
}
.theme-forest.dark {
--reader-bg: #1a2e1a;
--reader-text: #c8dcc8;
--reader-link: #22c55e;
--reader-selection: #14532d;
}
/* Moss - Fresh spring */
.theme-moss {
--reader-bg: #dcfce7;
--reader-text: #14532d;
--reader-link: #22c55e;
--reader-selection: #86efac;
}
.theme-moss.dark {
--reader-bg: #052e16;
--reader-text: #86efac;
--reader-link: #4ade80;
--reader-selection: #064e3b;
}
/* Slate - Stone stability */
.theme-slate {
--reader-bg: #f8fafc;
--reader-text: #334155;
--reader-link: #475569;
--reader-selection: #e2e8f0;
}
.theme-slate.dark {
--reader-bg: #1e293b;
--reader-text: #e2e8f0;
--reader-link: #94a3b8;
--reader-selection: #334155;
}
/* ==========================================
CATEGORY 5: HIGH PERFORMANCE (2 themes)
Optimized for specific use cases
========================================== */
/* OLED - Battery saving, max contrast */
.theme-oled {
--reader-bg: #ffffff;
--reader-text: #000000;
--reader-link: #0066cc;
--reader-selection: #ffff00;
}
.theme-oled.dark {
--reader-bg: #000000;
--reader-text: #ffffff;
--reader-link: #3b82f6;
--reader-selection: #1e3a8a;
}
/* Solarized - Precision designed */
.theme-solarized {
--reader-bg: #fdf6e3;
--reader-text: #657b83;
--reader-link: #268bd2;
--reader-selection: #eee8d5;
}
.theme-solarized.dark {
--reader-bg: #002b36;
--reader-text: #839496;
--reader-link: #268bd2;
--reader-selection: #073642;
}
#reader-viewport {
background-color: var(--reader-bg);
}