Add dark mode and compact chapter links

This commit is contained in:
2026-07-12 10:48:31 -05:00
parent 123cb8dd98
commit 735e247477
7 changed files with 248 additions and 17 deletions
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "libre-study", "name": "libre-study",
"version": "0.1.17", "version": "0.1.18",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "libre-study", "name": "libre-study",
"version": "0.1.17", "version": "0.1.18",
"dependencies": { "dependencies": {
"@tauri-apps/api": "^2.11.1", "@tauri-apps/api": "^2.11.1",
"react": "^19.2.7", "react": "^19.2.7",
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "libre-study", "name": "libre-study",
"private": true, "private": true,
"version": "0.1.17", "version": "0.1.18",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite --host 127.0.0.1", "dev": "vite --host 127.0.0.1",
+1 -1
View File
@@ -77,7 +77,7 @@ checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
[[package]] [[package]]
name = "app" name = "app"
version = "0.1.17" version = "0.1.18"
dependencies = [ dependencies = [
"log", "log",
"rusqlite", "rusqlite",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "app" name = "app"
version = "0.1.17" version = "0.1.18"
description = "A Tauri App" description = "A Tauri App"
authors = ["you"] authors = ["you"]
license = "" license = ""
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"productName": "Libre Study", "productName": "Libre Study",
"version": "0.1.17", "version": "0.1.18",
"identifier": "org.librestudy.desktop", "identifier": "org.librestudy.desktop",
"build": { "build": {
"frontendDist": "../dist", "frontendDist": "../dist",
+209 -11
View File
@@ -386,29 +386,37 @@ textarea:focus-visible {
.chapter-tabs { .chapter-tabs {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: nowrap;
justify-content: center; justify-content: center;
gap: 3px; gap: 0;
max-height: 38px; max-width: 100%;
max-height: 18px;
overflow: hidden; overflow: hidden;
white-space: nowrap;
} }
.chapter-tabs button { .chapter-tabs button {
min-width: 22px; min-width: 0;
min-height: 20px; min-height: 0;
padding: 2px 5px; padding: 0 3px;
border-color: #cfc4ad; border: 0;
background: #f2ead8; border-radius: 0;
color: #4c422f; background: transparent;
color: #6f5a24;
font-size: 10px; font-size: 10px;
line-height: 1; line-height: 1.1;
text-decoration: none;
box-shadow: none;
} }
.chapter-tabs button.active, .chapter-tabs button.active,
.chapter-tabs button:hover, .chapter-tabs button:hover,
.chapter-tabs button:focus-visible { .chapter-tabs button:focus-visible {
background: #d6a84f; background: transparent;
color: #151510; color: #151510;
text-decoration: underline;
text-decoration-color: #d6a84f;
text-underline-offset: 2px;
} }
.page-columns { .page-columns {
@@ -1291,6 +1299,196 @@ textarea:focus-visible {
width: auto; width: auto;
} }
.segmented-control {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 4px;
padding: 3px;
border: 1px solid #d8cfbf;
border-radius: 7px;
background: #fffef8;
}
.segmented-control button {
background: transparent;
color: #3f443d;
}
.segmented-control button.active {
background: #2f6f5e;
color: #fffef8;
}
.theme-dark {
background: #161a18;
color: #eee8d8;
}
.theme-dark .reader {
background: #161a18;
}
.theme-dark .page-system,
.theme-dark .notes-workspace,
.theme-dark .status-panel,
.theme-dark .bible-page,
.theme-dark .page-turner,
.theme-dark .footnote-strip,
.theme-dark .modal-panel,
.theme-dark .study-margin,
.theme-dark .study-top-tools,
.theme-dark .modal-section,
.theme-dark .note-helper,
.theme-dark .note-placeholder,
.theme-dark .segmented-control,
.theme-dark input,
.theme-dark select,
.theme-dark textarea {
border-color: #3f493f;
background: #222820;
color: #eee8d8;
}
.theme-dark .bible-page,
.theme-dark .page-turner,
.theme-dark .footnote-strip {
background: #1d221c;
}
.theme-dark .page-columns {
column-rule-color: #333b32;
}
.theme-dark .page-verse {
color: #f2eadb;
}
.theme-dark .page-verse.selected {
background: #3a3322;
}
.theme-dark .page-verse sup,
.theme-dark .chapter-tabs button,
.theme-dark .footnote-list p span {
color: #ddb763;
}
.theme-dark .chapter-tabs button.active,
.theme-dark .chapter-tabs button:hover,
.theme-dark .chapter-tabs button:focus-visible {
color: #fff7df;
}
.theme-dark .original-language-verse,
.theme-dark .english-strong-line,
.theme-dark .resource-card {
border-color: #3f493f;
background: #20251f;
}
.theme-dark .margin-section-header {
background: #2b332a;
}
.theme-dark .margin-section-header:hover,
.theme-dark .margin-section-header:focus-visible {
background: #344031;
}
.theme-dark .strong-focus {
border-color: #3b5a50;
background: #1f302b;
}
.theme-dark .panel-header {
border-bottom-color: #3f493f;
}
.theme-dark .panel-header span,
.theme-dark .margin-reference,
.theme-dark .translit,
.theme-dark .eyebrow,
.theme-dark .collapse-button,
.theme-dark .mini-settings,
.theme-dark .resource-disabled,
.theme-dark .commentary-list small,
.theme-dark .dictionary-list p strong,
.theme-dark .page-turner span,
.theme-dark .footnote-strip span,
.theme-dark .timeline-canvas time,
.theme-dark .timeline-canvas span,
.theme-dark .timeline-canvas small,
.theme-dark .note-helper small,
.theme-dark .note-placeholder small,
.theme-dark .session-facts dt,
.theme-dark .about-list dt,
.theme-dark .modal-section label,
.theme-dark .page-controls label {
color: #aaa996;
}
.theme-dark .strong-focus h4,
.theme-dark .strong-focus p,
.theme-dark .commentary-list h3,
.theme-dark .dictionary-list h3,
.theme-dark .commentary-list p,
.theme-dark .dictionary-list p,
.theme-dark .linked-line-text,
.theme-dark .lemma-line-text,
.theme-dark .session-facts dd,
.theme-dark .about-list dd,
.theme-dark .about-modal p,
.theme-dark .resource-toggles label,
.theme-dark .checkbox-row,
.theme-dark .segmented-control button {
color: #eee8d8 !important;
}
.theme-dark .segmented-control button.active,
.theme-dark .study-top-tools button,
.theme-dark .pin-notes,
.theme-dark .strong-focus button,
.theme-dark .about-links a {
background: #3a7a69;
color: #fffdf6 !important;
}
.theme-dark .modal-backdrop {
background: rgba(5, 7, 5, 0.68);
}
.theme-dark .close-button,
.theme-dark .tool-rail,
.theme-dark .notes-drawer-grip {
background: #111511;
}
.theme-dark .book-tabs {
background: #161a18;
}
.theme-dark .book-tabs button {
border-color: #4b422f;
background: #312c20;
color: #ddc17f;
}
.theme-dark .book-tabs button:hover,
.theme-dark .book-tabs button:focus-visible,
.theme-dark .book-tabs button.active {
background: #c7963f;
color: #141510;
}
.theme-dark .split-resizer {
background: linear-gradient(90deg, transparent, #4b5448 45%, #4b5448 55%, transparent);
}
.theme-dark .split-resizer:hover,
.theme-dark .split-resizer:focus-visible {
background: #c7963f;
}
.about-modal p { .about-modal p {
margin: 0; margin: 0;
color: #3f443d; color: #3f443d;
+33
View File
@@ -19,6 +19,7 @@ const timelineLimit = 200
type ViewMode = 'page' | 'study' | 'notes' | 'compare' type ViewMode = 'page' | 'study' | 'notes' | 'compare'
type StrongDisplayMode = 'off' | 'highlight' | 'numbers' type StrongDisplayMode = 'off' | 'highlight' | 'numbers'
type PageMotion = 'idle' | 'turn-forward' | 'turn-back' type PageMotion = 'idle' | 'turn-forward' | 'turn-back'
type ThemeMode = 'light' | 'dark'
type TimelineEntryType = 'verse' | 'strong' | 'footnote' | 'search' | 'navigation' | 'view' | 'setting' type TimelineEntryType = 'verse' | 'strong' | 'footnote' | 'search' | 'navigation' | 'view' | 'setting'
type TimelineEntry = { type TimelineEntry = {
@@ -223,6 +224,7 @@ function App() {
const [pageMotion, setPageMotion] = useState<PageMotion>('idle') const [pageMotion, setPageMotion] = useState<PageMotion>('idle')
const [viewMode, setViewMode] = useState<ViewMode>('notes') const [viewMode, setViewMode] = useState<ViewMode>('notes')
const [query, setQuery] = useState('') const [query, setQuery] = useState('')
const [themeMode, setThemeMode] = useState<ThemeMode>('light')
const [libraryVerses, setLibraryVerses] = useState<Verse[]>([]) const [libraryVerses, setLibraryVerses] = useState<Verse[]>([])
const [verses, setVerses] = useState<Verse[]>([]) const [verses, setVerses] = useState<Verse[]>([])
const [selectedId, setSelectedId] = useState<number | null>(null) const [selectedId, setSelectedId] = useState<number | null>(null)
@@ -407,6 +409,7 @@ function App() {
showDictionaryPanel: boolean showDictionaryPanel: boolean
splitPercent: number splitPercent: number
notesPinned: boolean notesPinned: boolean
themeMode: ThemeMode
}> }>
setLemmaFontSize(parsed.lemmaFontSize ?? 17) setLemmaFontSize(parsed.lemmaFontSize ?? 17)
setLinkedFontSize(parsed.linkedFontSize ?? 15) setLinkedFontSize(parsed.linkedFontSize ?? 15)
@@ -422,6 +425,7 @@ function App() {
setShowDictionaryPanel(parsed.showDictionaryPanel ?? true) setShowDictionaryPanel(parsed.showDictionaryPanel ?? true)
setSplitPercent(parsed.splitPercent ?? 50) setSplitPercent(parsed.splitPercent ?? 50)
setNotesPinned(parsed.notesPinned ?? false) setNotesPinned(parsed.notesPinned ?? false)
setThemeMode(parsed.themeMode ?? 'light')
} catch { } catch {
localStorage.removeItem(studySettingsStorageKey) localStorage.removeItem(studySettingsStorageKey)
} }
@@ -493,6 +497,7 @@ function App() {
showDictionaryPanel, showDictionaryPanel,
splitPercent, splitPercent,
notesPinned, notesPinned,
themeMode,
}), }),
) )
}, [ }, [
@@ -510,6 +515,7 @@ function App() {
showLinkedLine, showLinkedLine,
showOriginalLine, showOriginalLine,
splitPercent, splitPercent,
themeMode,
]) ])
useEffect(() => { useEffect(() => {
@@ -782,6 +788,11 @@ function App() {
}) })
} }
function changeThemeMode(nextThemeMode: ThemeMode) {
setThemeMode(nextThemeMode)
recordDisplaySetting(`Theme: ${nextThemeMode}`)
}
function beginSplitResize(event: ReactMouseEvent<HTMLDivElement>) { function beginSplitResize(event: ReactMouseEvent<HTMLDivElement>) {
event.preventDefault() event.preventDefault()
const reader = readerRef.current const reader = readerRef.current
@@ -1278,6 +1289,7 @@ function App() {
key={chapter} key={chapter}
type="button" type="button"
className={chapter === selectedVerse?.chapter ? 'active' : ''} className={chapter === selectedVerse?.chapter ? 'active' : ''}
style={{ fontSize: `${Math.max(8, Math.min(11, 13 - chapters.length / 20))}px` }}
onClick={() => selectedVerse && jumpTo(selectedVerse.book, chapter, 1)} onClick={() => selectedVerse && jumpTo(selectedVerse.book, chapter, 1)}
> >
{chapter} {chapter}
@@ -1733,6 +1745,26 @@ function App() {
</button> </button>
</header> </header>
<div className="settings-grid"> <div className="settings-grid">
<section className="modal-section">
<h3>Theme</h3>
<div className="segmented-control" aria-label="Theme mode">
<button
type="button"
className={themeMode === 'light' ? 'active' : ''}
onClick={() => changeThemeMode('light')}
>
Light
</button>
<button
type="button"
className={themeMode === 'dark' ? 'active' : ''}
onClick={() => changeThemeMode('dark')}
>
Dark
</button>
</div>
</section>
<section className="modal-section"> <section className="modal-section">
<h3>Original Language Line</h3> <h3>Original Language Line</h3>
<label> <label>
@@ -1964,6 +1996,7 @@ function App() {
'app-shell', 'app-shell',
railOpen ? 'menu-open' : '', railOpen ? 'menu-open' : '',
notesPinned ? 'notes-pinned' : '', notesPinned ? 'notes-pinned' : '',
themeMode === 'dark' ? 'theme-dark' : 'theme-light',
].filter(Boolean).join(' ')} ].filter(Boolean).join(' ')}
> >
<aside <aside