Files
digiserver-v2/app/templates/base.html
T
ske087 4dc8506c9b Fix all remaining dark-theme colour bugs across the templates
Follow-up to the player-modal / maintenance-page fix: the same root cause --
colours hard-coded for the light theme -- affected the whole app, in two
places.

1) Inline style attributes (inline outranks the theme rules, so the light
   value survives dark mode and the inherited text colour turns
   light-on-light). base.html now carries a single, property-scoped
   dark-mode override layer that maps every light literal found in the
   templates to the theme variables: light backgrounds/whites -> dark
   surfaces, semantic pastels -> dark equivalents, dark text -> light text,
   light borders -> dark borders, plus .back-link. Scoped to
   body.dark-mode, so the light theme is untouched. This covers all
   templates, including ones not edited here and any added later.

2) Page-level <style> blocks with no dark support:
   * admin/https_config.html hard-coded a light-only palette (blue-tint
     panels, dark labels) -- added a body.dark-mode block for it.
   * dashboard.html / help.html / manage_player.html coloured text links
     with --primary-color (#7c3aed in dark = too dim). Introduced a
     dedicated --link-color (#667eea light / #a78bfa dark) and used it for
     text links, leaving --primary-color for button/background use.

Verified with an automated luminance scan of all 18 pages in both themes:
dark mode has 0 light backgrounds and 0 dark-on-dark text (the only
remaining light surfaces are the intentional white buttons on the admin
gradient cards), and light mode is unchanged.
2026-10-14 22:45:09 +03:00

634 lines
25 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}DigiServer v2{% endblock %}</title>
<style>
/* Ensure emoji font support */
@supports (font-family: "Apple Color Emoji") {
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji";
}
}
:root {
/* Light Mode Colors */
--primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--primary-color: #667eea;
--primary-dark: #5568d3;
--secondary-color: #764ba2;
/* Colour for text links. Kept separate from --primary-color so the
primary colour can stay dark enough for button backgrounds while
links remain legible on the matching background. */
--link-color: #667eea;
--bg-color: #f5f7fa;
--card-bg: #ffffff;
--text-color: #2d3748;
--text-secondary: #718096;
--border-color: #e2e8f0;
--header-bg: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--shadow: 0 4px 6px rgba(0, 0, 0, 0.07);
--shadow-lg: 0 10px 25px rgba(0, 0, 0, 0.1);
}
body.dark-mode {
/* Dark Mode Colors */
--primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--primary-color: #7c3aed;
--primary-dark: #6d28d9;
--secondary-color: #8b5cf6;
/* Dark background needs a lighter link colour (#7c3aed is too dim
on a dark surface). */
--link-color: #a78bfa;
--bg-color: #1a202c;
--card-bg: #2d3748;
--text-color: #e2e8f0;
--text-secondary: #a0aec0;
--border-color: #4a5568;
--header-bg: linear-gradient(135deg, #7c3aed 0%, #8b5cf6 100%);
--shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
--shadow-lg: 0 10px 25px rgba(0, 0, 0, 0.4);
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
line-height: 1.6;
color: var(--text-color);
background: var(--bg-color);
transition: background 0.3s, color 0.3s;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
background: var(--header-bg);
color: white;
padding: 1rem 0;
margin-bottom: 2rem;
box-shadow: var(--shadow);
}
header .container {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 1rem;
}
header h1 {
font-size: 1.5rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
nav {
display: flex;
align-items: center;
gap: 0.5rem;
flex-wrap: wrap;
}
nav a {
color: white;
text-decoration: none;
padding: 0.5rem 1rem;
border-radius: 6px;
transition: all 0.3s;
font-weight: 500;
display: flex;
align-items: center;
gap: 0.5rem;
}
nav a:hover {
background: rgba(255,255,255,0.2);
transform: translateY(-2px);
}
nav a img {
width: 18px;
height: 18px;
filter: brightness(0) invert(1);
}
/* Mobile Responsive */
@media (max-width: 768px) {
header .container {
flex-direction: column;
align-items: stretch;
padding: 1rem;
}
header h1 {
font-size: 1.25rem;
justify-content: center;
text-align: center;
}
nav {
flex-direction: column;
gap: 0.5rem;
width: 100%;
}
nav a {
justify-content: center;
width: 100%;
padding: 0.75rem 1rem;
}
.dark-mode-toggle {
margin-left: 0;
width: 100%;
padding: 0.75rem;
}
.container {
padding: 15px;
}
.card {
padding: 1rem;
margin-bottom: 1rem;
}
.card-header {
padding: 15px;
margin: -1rem -1rem 1rem -1rem;
}
}
@media (max-width: 480px) {
header h1 {
font-size: 1.1rem;
}
nav a {
font-size: 0.9rem;
padding: 0.6rem 0.8rem;
}
nav a img {
width: 16px;
height: 16px;
}
}
.dark-mode-toggle {
background: rgba(255,255,255,0.2);
border: 2px solid rgba(255,255,255,0.3);
color: white;
cursor: pointer;
padding: 0.5rem;
border-radius: 6px;
transition: all 0.3s;
margin-left: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
}
.dark-mode-toggle:hover {
background: rgba(255,255,255,0.3);
transform: scale(1.1);
}
.dark-mode-toggle img {
width: 20px;
height: 20px;
filter: brightness(0) invert(1);
}
/* Emoji fallback for systems without emoji fonts */
.emoji-fallback::before {
content: attr(data-emoji);
font-family: 'Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji', sans-serif;
}
.alert {
padding: 1rem;
margin-bottom: 1rem;
border-radius: 8px;
border-left: 4px solid;
transition: all 0.3s;
}
.alert-success {
background: #d4edda;
border-color: #28a745;
color: #155724;
}
body.dark-mode .alert-success {
background: rgba(40, 167, 69, 0.2);
border-color: #28a745;
color: #7ce3a3;
}
.alert-danger {
background: #f8d7da;
border-color: #dc3545;
color: #721c24;
}
body.dark-mode .alert-danger {
background: rgba(220, 53, 69, 0.2);
border-color: #dc3545;
color: #f88f9a;
}
.alert-warning {
background: #fff3cd;
border-color: #ffc107;
color: #856404;
}
body.dark-mode .alert-warning {
background: rgba(255, 193, 7, 0.2);
border-color: #ffc107;
color: #ffd454;
}
.alert-info {
background: #d1ecf1;
border-color: #17a2b8;
color: #0c5460;
}
body.dark-mode .alert-info {
background: rgba(23, 162, 184, 0.2);
border-color: #17a2b8;
color: #7dd3e0;
}
.card {
background: var(--card-bg);
border-radius: 12px;
padding: 1.5rem;
margin-bottom: 1.5rem;
box-shadow: var(--shadow);
transition: all 0.3s;
border: 1px solid var(--border-color);
}
.card:hover {
box-shadow: var(--shadow-lg);
transform: translateY(-2px);
}
.card h2 {
margin-bottom: 1rem;
color: var(--text-color);
font-weight: 600;
}
.card h3 {
color: var(--text-color);
}
/* Card with gradient header */
.card-header {
background: var(--primary-gradient);
color: white;
padding: 20px;
border-radius: 12px 12px 0 0;
margin: -1.5rem -1.5rem 1.5rem -1.5rem;
}
.card-header h2 {
margin: 0;
color: white;
}
.btn {
display: inline-block;
padding: 0.5rem 1rem;
background: var(--primary-color);
color: white;
text-decoration: none;
border-radius: 6px;
border: none;
cursor: pointer;
transition: all 0.3s;
font-weight: 500;
}
.btn:hover {
background: var(--primary-dark);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
.btn-primary {
background: var(--primary-gradient);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
}
.btn-danger { background: #e74c3c; }
.btn-danger:hover {
background: #c0392b;
box-shadow: 0 4px 12px rgba(231, 76, 60, 0.4);
}
.btn-success { background: #27ae60; }
.btn-success:hover {
background: #229954;
box-shadow: 0 4px 12px rgba(39, 174, 96, 0.4);
}
.btn-info {
background: #3498db;
}
.btn-info:hover {
background: #2980b9;
box-shadow: 0 4px 12px rgba(52, 152, 219, 0.4);
}
.btn-sm {
padding: 0.375rem 0.75rem;
font-size: 0.875rem;
}
/* Form Inputs */
input, textarea, select {
background: var(--card-bg);
color: var(--text-color);
border: 1px solid var(--border-color);
transition: all 0.3s;
}
input:focus, textarea:focus, select:focus {
border-color: var(--primary-color);
outline: none;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
body.dark-mode input:focus,
body.dark-mode textarea:focus,
body.dark-mode select:focus {
box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.3);
}
/* Tables */
table {
background: var(--card-bg);
color: var(--text-color);
}
th {
background: var(--bg-color);
color: var(--text-color);
}
tr {
border-bottom: 1px solid var(--border-color);
}
/* Code blocks */
code, pre {
background: var(--bg-color);
color: var(--text-color);
border: 1px solid var(--border-color);
}
footer {
margin-top: 2rem;
padding: 1rem 0;
text-align: center;
color: var(--text-secondary);
font-size: 0.9rem;
}
/* ================================================================
Dark-mode fixes for INLINE colours
----------------------------------------------------------------
A recurring pattern across the templates is to set colours with
inline style="..." attributes (a few JS blocks build nodes the
same way). An inline declaration outranks every normal rule --
including the body.dark-mode rules above -- so those literals keep
their LIGHT value in dark mode. The text, which inherits the (now
light) --text-color, then ends up light-on-light: unreadable.
Inline styles can only be beaten with !important, so the light
literals are mapped to the theme variables here, centrally, with
the property scoped so a colour used for two things (e.g. #1a202c
is both a dark surface and dark text) is only changed where it is
wrong. This fixes every template at once -- including any future
page that repeats the pattern -- instead of patching each file.
Everything is scoped to body.dark-mode, so the LIGHT theme is
completely untouched.
Ordering matters: selectors have equal specificity, so later rules
win. The bare "#fff" rule is therefore placed BEFORE the "#fff3cd"
/"#fff8e1" ones, because "#fff" is a string prefix of them.
================================================================ */
/* --- White / near-white surfaces -> dark card --------------------- */
body.dark-mode [style*="background: white"], body.dark-mode [style*="background-color: white"],
body.dark-mode [style*="background: #fff"], body.dark-mode [style*="background-color: #fff"],
body.dark-mode [style*="background: #ffffff"], body.dark-mode [style*="background-color: #ffffff"],
body.dark-mode [style*="background: #fefefe"], body.dark-mode [style*="background-color: #fefefe"],
body.dark-mode [style*="background: #f5f7fa"], body.dark-mode [style*="background-color: #f5f7fa"],
body.dark-mode [style*="background: #e9ecef"], body.dark-mode [style*="background-color: #e9ecef"],
body.dark-mode [style*="background: #e0e0e0"], body.dark-mode [style*="background-color: #e0e0e0"],
body.dark-mode [style*="background: #f0f2ff"], body.dark-mode [style*="background-color: #f0f2ff"],
body.dark-mode [style*="background: #e8ebff"], body.dark-mode [style*="background-color: #e8ebff"],
body.dark-mode [style*="background: #e7e9ff"], body.dark-mode [style*="background-color: #e7e9ff"],
body.dark-mode [style*="background: #ede9fe"], body.dark-mode [style*="background-color: #ede9fe"] {
background: var(--card-bg) !important;
}
/* --- Light grey panels -> dark page background -------------------- */
body.dark-mode [style*="background: #f8f9fa"], body.dark-mode [style*="background-color: #f8f9fa"],
body.dark-mode [style*="background: #f0f0f0"], body.dark-mode [style*="background-color: #f0f0f0"],
body.dark-mode [style*="background: #f5f5f5"], body.dark-mode [style*="background-color: #f5f5f5"],
body.dark-mode [style*="background: #f4f4f4"], body.dark-mode [style*="background-color: #f4f4f4"],
body.dark-mode [style*="background: #f8fafc"], body.dark-mode [style*="background-color: #f8fafc"] {
background: var(--bg-color) !important;
}
/* --- Semantic pastel backgrounds -> dark equivalents -------------- */
/* info / blue */
body.dark-mode [style*="background: #d1ecf1"], body.dark-mode [style*="background-color: #d1ecf1"],
body.dark-mode [style*="background: #cce5ff"], body.dark-mode [style*="background-color: #cce5ff"],
body.dark-mode [style*="background: #e7f3ff"], body.dark-mode [style*="background-color: #e7f3ff"],
body.dark-mode [style*="background: #f0f7ff"], body.dark-mode [style*="background-color: #f0f7ff"] {
background: #1e3a5f !important;
}
/* success / green */
body.dark-mode [style*="background: #d4edda"], body.dark-mode [style*="background-color: #d4edda"],
body.dark-mode [style*="background: #d1f0e0"], body.dark-mode [style*="background-color: #d1f0e0"],
body.dark-mode [style*="background: #c6f6d5"], body.dark-mode [style*="background-color: #c6f6d5"] {
background: #1a4d2e !important;
}
/* warning / amber (must stay AFTER the "#fff" rule above) */
body.dark-mode [style*="background: #fff3cd"], body.dark-mode [style*="background-color: #fff3cd"],
body.dark-mode [style*="background: #fff8e1"], body.dark-mode [style*="background-color: #fff8e1"],
body.dark-mode [style*="background: #ffeaa7"], body.dark-mode [style*="background-color: #ffeaa7"],
body.dark-mode [style*="background: #fef3c7"], body.dark-mode [style*="background-color: #fef3c7"],
body.dark-mode [style*="background: #feebc8"], body.dark-mode [style*="background-color: #feebc8"] {
background: #4a3800 !important;
}
/* danger / red */
body.dark-mode [style*="background: #f8d7da"], body.dark-mode [style*="background-color: #f8d7da"],
body.dark-mode [style*="background: #fed7d7"], body.dark-mode [style*="background-color: #fed7d7"] {
background: #4a1a1a !important;
}
/* mid-grey chips / knobs */
body.dark-mode [style*="background: #ccc"], body.dark-mode [style*="background-color: #ccc"],
body.dark-mode [style*="background: #6c757d"], body.dark-mode [style*="background-color: #6c757d"] {
background: #4a5568 !important;
}
/* --- Dark text -> light text -------------------------------------- */
body.dark-mode [style*="color: black"] {
color: var(--text-color) !important;
}
body.dark-mode [style*="color: #6c757d"], body.dark-mode [style*="color:#6c757d"],
body.dark-mode [style*="color: #666"], body.dark-mode [style*="color: #888"],
body.dark-mode [style*="color: #999"], body.dark-mode [style*="color: #aaa"],
body.dark-mode [style*="color: #495057"], body.dark-mode [style*="color: #718096"],
body.dark-mode [style*="color: #7f8c8d"], body.dark-mode [style*="color: #5a6a78"],
body.dark-mode [style*="color: #4a5568"] {
color: var(--text-secondary) !important;
}
body.dark-mode [style*="color: #555"], body.dark-mode [style*="color: #475569"],
body.dark-mode [style*="color: #64748b"] {
color: #cbd5e1 !important;
}
body.dark-mode [style*="color: #333"], body.dark-mode [style*="color: #2c3e50"],
body.dark-mode [style*="color: #000"], body.dark-mode [style*="color: #1a202c"],
body.dark-mode [style*="color: #2d3748"] {
color: var(--text-color) !important;
}
/* links / info blue */
body.dark-mode [style*="color: #0066cc"], body.dark-mode [style*="color: #007bff"],
body.dark-mode [style*="color: #0056b3"], body.dark-mode [style*="color: #0c5460"],
body.dark-mode [style*="color: #004085"], body.dark-mode [style*="color: #3498db"] {
color: #90cdf4 !important;
}
/* success text */
body.dark-mode [style*="color: #155724"], body.dark-mode [style*="color: #156b2e"],
body.dark-mode [style*="color: #28a745"], body.dark-mode [style*="color: #27ae60"],
body.dark-mode [style*="color: #2f855a"] {
color: #86efac !important;
}
/* warning text */
body.dark-mode [style*="color: #856404"], body.dark-mode [style*="color: #92400e"],
body.dark-mode [style*="color: #c05621"], body.dark-mode [style*="color: #e67e22"] {
color: #fbbf24 !important;
}
/* danger text */
body.dark-mode [style*="color: #721c24"], body.dark-mode [style*="color: #dc3545"],
body.dark-mode [style*="color: #e74c3c"], body.dark-mode [style*="color: #e53e3e"],
body.dark-mode [style*="color:#e53e3e"], body.dark-mode [style*="color: #c53030"],
body.dark-mode [style*="color: #b91c1c"] {
color: #fca5a5 !important;
}
/* purple accents */
body.dark-mode [style*="color: #5b21b6"] {
color: #a78bfa !important;
}
/* --- Light borders -> dark borders ------------------------------- */
body.dark-mode [style*="border-top-color: white"] {
border-top-color: var(--border-color) !important;
}
body.dark-mode [style*="#e2e8f0"], body.dark-mode [style*="#dee2e6"],
body.dark-mode [style*="#ddd"], body.dark-mode [style*="#ccc"],
body.dark-mode [style*="#ced4da"], body.dark-mode [style*="#e0e0e0"],
body.dark-mode [style*="#eee"], body.dark-mode [style*="#e9ecef"],
body.dark-mode [style*="#f0f0f0"], body.dark-mode [style*="#f0f2ff"],
body.dark-mode [style*="#888"], body.dark-mode [style*="#999"] {
border-color: #4a5568 !important;
}
body.dark-mode [style*="#c3e6cb"], body.dark-mode [style*="#d1f0e0"] {
border-color: #2f855a !important;
}
body.dark-mode [style*="#f5c6cb"] {
border-color: #c53030 !important;
}
body.dark-mode [style*="#bee5eb"], body.dark-mode [style*="#cce5ff"] {
border-color: #2c5282 !important;
}
/* --- "Back" links (class used across the admin pages) ------------- */
body.dark-mode .back-link {
color: #90cdf4 !important;
}
</style>
{% block extra_css %}{% endblock %}
</head>
<body>
<header>
<div class="container">
<h1>
<img src="{{ url_for('static', filename='uploads/header_logo.png') }}?v={{ logo_version }}" alt="DigiServer" style="height: 32px; width: auto; margin-right: 8px;" onerror="this.style.display='none';" onload="this.style.display='inline';">
DigiServer
</h1>
<nav>
{% if current_user.is_authenticated %}
<a href="{{ url_for('main.dashboard') }}">
<img src="{{ url_for('static', filename='icons/home.svg') }}" alt="">
Dashboard
</a>
<a href="{{ url_for('players.list') }}">
<img src="{{ url_for('static', filename='icons/monitor.svg') }}" alt="">
Players
</a>
<a href="{{ url_for('content.content_list') }}">
<img src="{{ url_for('static', filename='icons/playlist.svg') }}" alt="">
Playlists
</a>
<a href="{{ url_for('main.help_page') }}">
<img src="{{ url_for('static', filename='icons/help.svg') }}" alt="" onerror="this.style.display='none';">
Ajutor
</a>
<a href="{{ url_for('admin.admin_panel') }}">Admin</a>
<a href="{{ url_for('auth.logout') }}">Logout ({{ current_user.username }})</a>
<button class="dark-mode-toggle" onclick="toggleDarkMode()" title="Toggle Dark Mode">
<img id="theme-icon" src="{{ url_for('static', filename='icons/moon.svg') }}" alt="Toggle theme">
</button>
{% else %}
<a href="{{ url_for('auth.login') }}">Login</a>
<a href="{{ url_for('auth.register') }}">Register</a>
{% endif %}
</nav>
</div>
</header>
<div class="container">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}">
{{ message }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</div>
<footer>
<div class="container">
DigiServer v2.0.0-alpha | Blueprint Architecture | {{ server_version if server_version else 'Development' }}
</div>
</footer>
<script>
// Dark Mode Toggle
function toggleDarkMode() {
const body = document.body;
const themeIcon = document.getElementById('theme-icon');
body.classList.toggle('dark-mode');
// Update icon
if (body.classList.contains('dark-mode')) {
themeIcon.src = "{{ url_for('static', filename='icons/sun.svg') }}";
localStorage.setItem('darkMode', 'enabled');
} else {
themeIcon.src = "{{ url_for('static', filename='icons/moon.svg') }}";
localStorage.setItem('darkMode', 'disabled');
}
}
// Load saved theme preference
document.addEventListener('DOMContentLoaded', function() {
const darkMode = localStorage.getItem('darkMode');
const themeIcon = document.getElementById('theme-icon');
if (darkMode === 'enabled') {
document.body.classList.add('dark-mode');
if (themeIcon) {
themeIcon.src = "{{ url_for('static', filename='icons/sun.svg') }}";
}
}
});
</script>
{% block extra_js %}{% endblock %}
</body>
</html>