From 74de3ddee2c13782f9932c61b988fd583dce564c Mon Sep 17 00:00:00 2001 From: Ivo Oskamp Date: Mon, 1 Jun 2026 14:00:46 +0200 Subject: [PATCH] Reader: add Sepia reading theme Add a Dark/Sepia theme toggle to the reader's reading-settings drawer for easier long-form reading. Sepia uses a warm paper background with dark brown text via a :root[data-theme="sepia"] palette in theme.css. Text colour is now stored per theme with theme-specific swatch sets; the old single key migrates into the dark slot. An inline head script applies the saved theme before paint to avoid a flash. Co-Authored-By: Claude Opus 4.8 (1M context) --- containers/novela/static/theme.css | 18 +++++ containers/novela/templates/reader.html | 94 +++++++++++++++++++++---- docs/changelog-develop.md | 25 +++++++ 3 files changed, 125 insertions(+), 12 deletions(-) diff --git a/containers/novela/static/theme.css b/containers/novela/static/theme.css index cc83c0f..48f5195 100644 --- a/containers/novela/static/theme.css +++ b/containers/novela/static/theme.css @@ -13,8 +13,26 @@ --success: #6baa6b; --warning: #c8a03a; --error: #c85a3a; + --shadow-ring: rgba(255,255,255,0.1); --radius: 6px; --sidebar: 220px; --mono: 'DM Mono', monospace; --serif: 'Libre Baskerville', Georgia, serif; } + +/* ── Sepia reading theme (reader only) ──────────────────────────────────── */ +:root[data-theme="sepia"] { + --bg: #f4ecd8; + --surface: #ece2cb; + --surface2: #e3d7bb; + --border: #d8cbac; + --accent: #c17d12; + --accent2: #a9690c; + --text: #5b4636; + --text-dim: #7c6a55; + --text-faint: #ab9a82; + --success: #4e8a4e; + --warning: #9a7714; + --error: #b04a2c; + --shadow-ring: rgba(0,0,0,0.12); +} diff --git a/containers/novela/templates/reader.html b/containers/novela/templates/reader.html index accfd32..16ab208 100644 --- a/containers/novela/templates/reader.html +++ b/containers/novela/templates/reader.html @@ -71,7 +71,7 @@ .btn-header-bm:hover { background: rgba(255,162,14,0.08); border-color: var(--accent); } .btn-header-series { display: none; - color: var(--text-faint); border-color: rgba(255,255,255,0.08); + color: var(--text-faint); border-color: var(--border); padding: 0.3rem 0.5rem; } .btn-header-series.active { @@ -177,11 +177,26 @@ width: 24px; height: 24px; border-radius: 50%; border: 2px solid transparent; cursor: pointer; transition: border-color 0.12s, transform 0.1s; - box-shadow: 0 0 0 1px rgba(255,255,255,0.1); + box-shadow: 0 0 0 1px var(--shadow-ring); padding: 0; } .colour-swatch:hover { transform: scale(1.15); } .colour-swatch.active { border-color: var(--accent); } + .colour-swatches.hidden { display: none; } + + /* ── Theme toggle ── */ + .theme-options { display: flex; gap: 0.5rem; } + .theme-btn { + flex: 1; + padding: 0.4rem 0.6rem; + background: var(--surface2); border: 1px solid var(--border); + border-radius: var(--radius); + font-family: var(--mono); font-size: 0.7rem; + color: var(--text-dim); cursor: pointer; + transition: color 0.12s, border-color 0.12s; + } + .theme-btn:hover { color: var(--text); border-color: var(--text-faint); } + .theme-btn.active { color: var(--accent); border-color: var(--accent); } /* ── Viewer ── */ #viewer { @@ -300,6 +315,16 @@ } @keyframes spin { to { transform: rotate(360deg); } } + @@ -327,6 +352,13 @@
Reading settings
+
+
Theme
+
+ + +
+
Content width @@ -345,13 +377,20 @@
Text colour
-
+
+
@@ -452,18 +491,49 @@ applyWidth(saved); } - // ── Text colour ──────────────────────────────────────────────── - function applyTextColour(hex) { - document.documentElement.style.setProperty('--text', hex); - localStorage.setItem('reader-text-colour', hex); - document.querySelectorAll('.colour-swatch').forEach(el => { - el.classList.toggle('active', el.dataset.colour === hex); + // ── Theme (Dark / Sepia) ─────────────────────────────────────── + const THEME_TEXT_DEFAULT = { dark: '#e8e2d9', sepia: '#5b4636' }; + + function currentTheme() { + return document.documentElement.getAttribute('data-theme') === 'sepia' ? 'sepia' : 'dark'; + } + + function applyTheme(theme) { + if (theme !== 'sepia') theme = 'dark'; + if (theme === 'dark') { + document.documentElement.removeAttribute('data-theme'); + } else { + document.documentElement.setAttribute('data-theme', theme); + } + localStorage.setItem('reader-theme', theme); + document.querySelectorAll('.theme-btn').forEach(el => { + el.classList.toggle('active', el.dataset.theme === theme); }); + document.querySelectorAll('.colour-swatches').forEach(el => { + el.classList.toggle('hidden', el.dataset.theme !== theme); + }); + loadTextColour(); + } + + function loadTheme() { + applyTheme(localStorage.getItem('reader-theme') || 'dark'); + } + + // ── Text colour (per theme) ──────────────────────────────────── + function applyTextColour(hex) { + const theme = currentTheme(); + document.documentElement.style.setProperty('--text', hex); + localStorage.setItem('reader-text-colour-' + theme, hex); + document.querySelectorAll('.colour-swatches[data-theme="' + theme + '"] .colour-swatch') + .forEach(el => el.classList.toggle('active', el.dataset.colour === hex)); } function loadTextColour() { - const saved = localStorage.getItem('reader-text-colour') || '#e8e2d9'; - applyTextColour(saved); + const theme = currentTheme(); + let saved = localStorage.getItem('reader-text-colour-' + theme); + // Migrate the pre-theme single key into the dark slot. + if (!saved && theme === 'dark') saved = localStorage.getItem('reader-text-colour'); + applyTextColour(saved || THEME_TEXT_DEFAULT[theme]); } // ── Font size ────────────────────────────────────────────────── @@ -639,7 +709,7 @@ // ── Init ─────────────────────────────────────────────────────── async function init() { loadWidth(); - loadTextColour(); + loadTheme(); loadFontSize(); loadSeriesNav(); diff --git a/docs/changelog-develop.md b/docs/changelog-develop.md index 5c8d147..eacafe6 100644 --- a/docs/changelog-develop.md +++ b/docs/changelog-develop.md @@ -1,5 +1,30 @@ # Develop Changelog +## 2026-06-01 — Reader: Sepia reading theme + +### Added +- The reader now offers a **Sepia** theme alongside the existing Dark theme, for easier long-form reading (warm paper background with dark brown text instead of light-on-black). New **Theme** toggle (Dark / Sepia) at the top of the reading settings drawer. + - `static/theme.css`: added a `:root[data-theme="sepia"]` palette overriding `--bg`, `--surface`, `--surface2`, `--border`, `--accent`, `--accent2`, `--text`, `--text-dim`, `--text-faint`, `--success`, `--warning`, `--error`. Also added a `--shadow-ring` variable (light hairline in dark, dark hairline in sepia) so swatch rings read correctly in both themes. + - `templates/reader.html`: theme toggle buttons + `.theme-btn` styling; an inline head script applies the saved theme before paint to avoid a flash; the Text colour row now has two theme-specific swatch sets (light tints for dark, dark brown tints for sepia), toggled by visibility. + - JS: `applyTheme()`/`loadTheme()` set the `data-theme` attribute and persist to `localStorage` (`reader-theme`). Text colour is now stored per theme (`reader-text-colour-dark` / `reader-text-colour-sepia`); the old single `reader-text-colour` key is migrated into the dark slot. `init()` calls `loadTheme()` (which loads the active theme's text colour). + - Replaced two hard-coded `rgba(255,255,255,...)` values in the reader CSS (swatch ring, series-button border) with theme variables so they don't show as white halos in sepia. + +## 2026-05-31 — Build version in the sidebar + +### Added +- The sidebar now shows the running build version at the bottom (e.g. `v0.2.11` for releases, `v0.2.11.3` for dev builds), linking to the changelog page. + - New `containers/novela/version.py` exposes `display_version()`. The semantic release version stays the single source in `changelog.py` (`CHANGELOG[0]["version"]`); a `BUILD` segment is appended for dev builds. + - `shared_templates.py` registers `app_version` as a Jinja global; `_sidebar.html` renders it as a `.sidebar-version` link styled in `sidebar.css`. + - `main.py` adds a `/api/version` endpoint returning `{"version": display_version()}`. + +## 2026-05-31 — Find & Replace: scope option + +### Added +- Editor Find & Replace: new **Current chapter only** checkbox in the modal options. When checked, the search/replace runs against the currently open chapter instead of every chapter in the book. + - `templates/editor.html`: added the `rp-current` checkbox next to Regex/Case sensitive; modal title changed from "Find & Replace — all chapters" to "Find & Replace" since scope is now selectable. + - `static/editor.js` (`replaceInAllChapters()`): reads `rp-current`; builds a `targets` list of either just `currentCh()` or all `chapters`, and iterates that. Shows an error ("No chapter open.") if Current-chapter-only is selected with no open chapter. Result message reads "… in current chapter" for the single-chapter case. + - Default is unchecked, so the existing all-chapters behaviour is preserved. + ## 2026-05-10 - Reader: subheading/chat styling now also wins when the wrapper contains block elements with their own color rule (e.g. `

`) - Previously the `

`/`` rules in `reader.html` (`#chapter-content p { color: var(--text); }`, etc.) had the same specificity as `.subheading` and applied more directly to the inner element, so the parent's color was effectively overridden — the wrapped paragraph stayed in the default text color