mirror of
https://github.com/calibrain/shelfmark.git
synced 2026-09-24 20:50:28 +01:00
feat: implement dark mode with improved UI contrast and full-height layout (#91)
- Add theme toggle (light/dark/auto) - Improve text contrast and readability - Fix viewport height issues - Ensure consistent styling across components
This commit is contained in:
@@ -67,15 +67,15 @@ if len(BOOK_LANGUAGE) == 0:
|
||||
BOOK_LANGUAGE = ['en']
|
||||
|
||||
# Custom script settings
|
||||
CUSTOM_SCRIPT = os.getenv("CUSTOM_SCRIPT", None).strip()
|
||||
CUSTOM_SCRIPT = os.getenv("CUSTOM_SCRIPT", "").strip()
|
||||
# check if the script is valid
|
||||
if CUSTOM_SCRIPT:
|
||||
if not os.path.exists(CUSTOM_SCRIPT):
|
||||
logger.error(f"Custom script {CUSTOM_SCRIPT} does not exist")
|
||||
CUSTOM_SCRIPT = None
|
||||
CUSTOM_SCRIPT = ""
|
||||
elif not os.access(CUSTOM_SCRIPT, os.X_OK):
|
||||
logger.error(f"Custom script {CUSTOM_SCRIPT} is not executable")
|
||||
CUSTOM_SCRIPT = None
|
||||
CUSTOM_SCRIPT = ""
|
||||
|
||||
# API settings
|
||||
FLASK_HOST = os.getenv("FLASK_HOST", "0.0.0.0")
|
||||
|
||||
+204
-3
@@ -1,13 +1,33 @@
|
||||
/* Base styles and CSS reset */
|
||||
:root {
|
||||
/* Light theme variables */
|
||||
--primary-color: #0073e6;
|
||||
--primary-dark: #005bb5;
|
||||
--text-color: #333;
|
||||
--background-color: #fdfdfd;
|
||||
--border-color: #ccc;
|
||||
--background-color: #f8f8f8;
|
||||
--border-color: #e5e5e5;
|
||||
--header-bg: #333;
|
||||
--header-text: #fff;
|
||||
--loading-overlay: rgba(0, 0, 0, 0.5);
|
||||
--card-background: #fff;
|
||||
--table-background: #fff;
|
||||
--table-hover-background: #f8f8f8;
|
||||
--table-border-color: #e5e5e5;
|
||||
--input-background: #fff;
|
||||
--heading-color: #333;
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
/* Dark theme variables with improved contrast */
|
||||
--background-color: #121212;
|
||||
--text-color: #ffffff;
|
||||
--heading-color: #ffffff;
|
||||
--card-background: #1e1e1e;
|
||||
--border-color: #404040;
|
||||
--table-background: #1e1e1e;
|
||||
--table-hover-background: #2d2d2d;
|
||||
--table-border-color: #404040;
|
||||
--input-background: #2d2d2d;
|
||||
}
|
||||
|
||||
* {
|
||||
@@ -17,11 +37,26 @@
|
||||
}
|
||||
|
||||
/* Typography */
|
||||
html, body {
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: var(--text-color);
|
||||
background: var(--background-color);
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
min-height: 100%;
|
||||
background: var(--background-color);
|
||||
}
|
||||
|
||||
/* Layout */
|
||||
@@ -36,9 +71,9 @@ header {
|
||||
footer {
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
margin-top: 2rem;
|
||||
background: var(--header-bg);
|
||||
color: var(--header-text);
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
/* Search Section */
|
||||
@@ -341,3 +376,169 @@ th[data-sort="index"] {
|
||||
text-align: center;
|
||||
padding: 1rem !important;
|
||||
}
|
||||
|
||||
/* Dark mode specific styles */
|
||||
[data-theme="dark"] .uk-card,
|
||||
[data-theme="dark"] .uk-modal-dialog {
|
||||
background-color: var(--card-background);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .uk-table {
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .uk-table-hover tbody tr:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .uk-button-default {
|
||||
background-color: var(--card-background);
|
||||
color: var(--text-color);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .uk-search-input {
|
||||
background-color: var(--card-background);
|
||||
color: var(--text-color);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .uk-select {
|
||||
background-color: var(--card-background);
|
||||
color: var(--text-color);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
/* Table styles */
|
||||
.uk-table {
|
||||
background-color: var(--table-background);
|
||||
color: var(--text-color);
|
||||
border-color: var(--table-border-color);
|
||||
}
|
||||
|
||||
.uk-table th {
|
||||
color: var(--text-color);
|
||||
background-color: var(--table-background);
|
||||
border-bottom-color: var(--table-border-color);
|
||||
}
|
||||
|
||||
.uk-table td {
|
||||
color: var(--text-color);
|
||||
background-color: var(--table-background);
|
||||
border-bottom-color: var(--table-border-color);
|
||||
}
|
||||
|
||||
.uk-table-hover tbody tr:hover,
|
||||
.uk-table-hover tbody tr:hover td {
|
||||
background-color: var(--table-hover-background);
|
||||
}
|
||||
|
||||
/* Input styles */
|
||||
.uk-search-input,
|
||||
.uk-input,
|
||||
.uk-select,
|
||||
.uk-textarea {
|
||||
background-color: var(--input-background);
|
||||
color: var(--text-color);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
/* Button styles */
|
||||
.uk-button-default {
|
||||
background-color: var(--card-background);
|
||||
color: var(--text-color);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
.uk-button-default:hover {
|
||||
background-color: var(--table-hover-background);
|
||||
color: var(--text-color);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
/* Dropdown styles */
|
||||
.uk-dropdown {
|
||||
background-color: var(--card-background);
|
||||
color: var(--text-color);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
.uk-dropdown-nav > li > a {
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.uk-dropdown-nav > li > a:hover {
|
||||
background-color: var(--table-hover-background);
|
||||
}
|
||||
|
||||
/* Modal styles */
|
||||
.modal-overlay {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.details-container {
|
||||
background-color: var(--card-background);
|
||||
color: var(--text-color);
|
||||
border-color: var(--border-color);
|
||||
}
|
||||
|
||||
/* Status colors - ensure they remain visible in dark mode */
|
||||
.status-downloading {
|
||||
color: #4CAF50 !important;
|
||||
}
|
||||
|
||||
.status-available {
|
||||
color: #2196F3 !important;
|
||||
}
|
||||
|
||||
.status-error {
|
||||
color: #f44336 !important;
|
||||
}
|
||||
|
||||
/* Header specific button styles */
|
||||
header .uk-button-default {
|
||||
color: var(--header-text) !important;
|
||||
background-color: transparent;
|
||||
border-color: var(--header-text);
|
||||
}
|
||||
|
||||
header .uk-button-default:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
border-color: var(--header-text);
|
||||
color: var(--header-text) !important;
|
||||
}
|
||||
|
||||
/* Ensure dropdown text is visible when opened */
|
||||
header .uk-dropdown {
|
||||
background-color: var(--card-background);
|
||||
}
|
||||
|
||||
header .uk-dropdown-nav > li > a {
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
header .uk-dropdown-nav > li > a:hover {
|
||||
color: var(--text-color);
|
||||
background-color: var(--table-hover-background);
|
||||
}
|
||||
|
||||
/* Update headings to use heading color */
|
||||
h1, h2, h3, h4, h5, h6,
|
||||
.uk-heading-small,
|
||||
.uk-heading-medium,
|
||||
.uk-heading-large,
|
||||
.uk-heading-xlarge,
|
||||
.uk-heading-2xlarge {
|
||||
color: var(--heading-color) !important;
|
||||
}
|
||||
|
||||
/* Ensure accordion titles are visible */
|
||||
.uk-accordion-title {
|
||||
color: var(--heading-color) !important;
|
||||
}
|
||||
|
||||
/* Ensure search results heading is visible */
|
||||
#results-section-accordion .uk-accordion-title h1 {
|
||||
color: var(--heading-color) !important;
|
||||
}
|
||||
|
||||
+62
-1
@@ -22,7 +22,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
statusTable: document.getElementById('status-table'),
|
||||
statusTableBody: document.querySelector('#status-table tbody'),
|
||||
modalOverlay: document.getElementById('modal-overlay'),
|
||||
detailsContainer: document.getElementById('details-container')
|
||||
detailsContainer: document.getElementById('details-container'),
|
||||
theme: {
|
||||
toggle: document.getElementById('theme-toggle'),
|
||||
text: document.getElementById('theme-text'),
|
||||
dropdown: document.querySelector('[uk-dropdown]'),
|
||||
}
|
||||
};
|
||||
|
||||
// State
|
||||
@@ -596,6 +601,61 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Theme Management
|
||||
const theme = {
|
||||
STORAGE_KEY: 'preferred-theme',
|
||||
|
||||
init() {
|
||||
this.loadTheme();
|
||||
this.setupListeners();
|
||||
},
|
||||
|
||||
loadTheme() {
|
||||
const savedTheme = localStorage.getItem(this.STORAGE_KEY) || 'auto';
|
||||
this.applyThemePreference(savedTheme);
|
||||
this.updateButtonText(savedTheme);
|
||||
},
|
||||
|
||||
applyThemePreference(preference) {
|
||||
if (preference === 'auto') {
|
||||
const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
document.documentElement.setAttribute('data-theme', isDarkMode ? 'dark' : 'light');
|
||||
} else {
|
||||
document.documentElement.setAttribute('data-theme', preference);
|
||||
}
|
||||
},
|
||||
|
||||
setupListeners() {
|
||||
// Listen for system theme changes
|
||||
const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
darkModeMediaQuery.addEventListener('change', (e) => {
|
||||
if (localStorage.getItem(this.STORAGE_KEY) === 'auto') {
|
||||
document.documentElement.setAttribute('data-theme', e.matches ? 'dark' : 'light');
|
||||
}
|
||||
});
|
||||
|
||||
// Theme selection listeners
|
||||
const themeLinks = document.querySelectorAll('.uk-dropdown-nav a[data-theme]');
|
||||
themeLinks.forEach(link => {
|
||||
link.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
const selectedTheme = e.target.getAttribute('data-theme');
|
||||
localStorage.setItem(this.STORAGE_KEY, selectedTheme);
|
||||
this.applyThemePreference(selectedTheme);
|
||||
this.updateButtonText(selectedTheme);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
updateButtonText(currentTheme) {
|
||||
const themeText = currentTheme.charAt(0).toUpperCase() + currentTheme.slice(1);
|
||||
const themeTextElement = document.getElementById('theme-text');
|
||||
if (themeTextElement) {
|
||||
themeTextElement.textContent = themeText;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Event Listeners
|
||||
function setupEventListeners() {
|
||||
// Search events
|
||||
@@ -671,6 +731,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
// Initialize
|
||||
function init() {
|
||||
setupEventListeners();
|
||||
theme.init(); // Initialize theme management
|
||||
status.fetch();
|
||||
setInterval(() => status.fetch(), REFRESH_INTERVAL);
|
||||
}
|
||||
|
||||
+16
-1
@@ -18,7 +18,22 @@
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1 class="uk-heading-medium" style="color: white;">Book Search & Download</h1>
|
||||
<div class="uk-flex uk-flex-between uk-flex-middle">
|
||||
<h1 class="uk-heading-medium" style="color: white;">Book Search & Download</h1>
|
||||
<div class="uk-margin-right">
|
||||
<button class="uk-button uk-button-default" type="button" id="theme-toggle">
|
||||
<span uk-icon="icon: paint-bucket"></span>
|
||||
<span id="theme-text">Theme</span>
|
||||
</button>
|
||||
<div uk-dropdown="mode: click">
|
||||
<ul class="uk-nav uk-dropdown-nav">
|
||||
<li><a href="#" data-theme="light">Light</a></li>
|
||||
<li><a href="#" data-theme="dark">Dark</a></li>
|
||||
<li><a href="#" data-theme="auto">Auto (System)</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
|
||||
Reference in New Issue
Block a user