Content width
@@ -345,13 +377,20 @@
@@ -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