diff --git a/pythonie/core/static/css/style.css b/pythonie/core/static/css/style.css
index 4c69160..0a7e75e 100644
--- a/pythonie/core/static/css/style.css
+++ b/pythonie/core/static/css/style.css
@@ -1,12 +1,80 @@
+:root {
+ --bg-color: #eaeff2;
+ --text-color: #111827;
+ --muted-color: #475569;
+ --link-color: #0b61d8;
+ --link-hover-color: #0a58ca;
+ --card-bg: #ffffff;
+ --border-color: #d0d7de;
+ --nav-bg: #ffffff;
+ --nav-hover-bg: #e5e7eb;
+ --nav-text: #111827;
+ --footer-bg: #ffffff;
+ --button-bg: #e5e7eb;
+ --button-text: #111827;
+ --button-border: #cbd5e1;
+ --focus-ring: #2563eb;
+ --brand-primary: #336600;
+ --brand-secondary: #339900;
+ --heading-color: #111827;
+}
+
+html.dark {
+ --bg-color: #222426; /* page background */
+ --text-color: #bdbcb2; /* requested body text */
+ --muted-color: #cbd5e1; /* muted */
+ --link-color: #facc15; /* emphasized links */
+ --link-hover-color: #c3c3bc; /* neutral hover to avoid yellow tint */
+ --card-bg: #1e2021; /* content boxes */
+ --border-color: #374151; /* subtle mid gray */
+ --nav-bg: #1c1e1f; /* navbar background */
+ --nav-hover-bg: #111827;
+ --nav-text: #bdbcb2;
+ --footer-bg: #111827;
+ --button-bg: #1f2937;
+ --button-text: #e5e7eb;
+ --button-border: #374151;
+ --focus-ring: #76b3fa;
+ --brand-primary: #336600; /* match original logo green */
+ --brand-secondary: #339900; /* supporting tint */
+ --heading-color: #c3c3bc;
+}
+
body {
- background: #EAEFF2;
- color: #333;
+ background: var(--bg-color);
+ color: var(--text-color);
font-size: 16px;
font-family: "bree-serif", serif;
font-style: normal;
font-weight: 300; /* Bree Serif: 700 (bold), 400 (regular), 300 (light), 200 (thin); */
}
+body, p, li {
+ color: var(--text-color);
+}
+
+small,
+.text-muted {
+ color: var(--muted-color);
+}
+
+a {
+ color: var(--link-color);
+ text-decoration: none;
+}
+
+a:hover,
+a:focus {
+ color: var(--link-hover-color);
+ text-decoration: underline;
+}
+
+a:focus-visible,
+button:focus-visible {
+ outline: 2px solid var(--focus-ring);
+ outline-offset: 2px;
+}
+
.wf-loading {
visibility: hidden;
}
@@ -16,12 +84,14 @@ h2, h3, h4, h5, h6 {
font-family: "bree", sans-serif;
font-style: normal;
font-weight: 400; /* Bree: 700 (bold), 400 (regular), 300 (light), 200 (thin) */
+ color: var(--heading-color, var(--text-color));
}
h1 {
font-family: "bree", sans-serif;
font-style: normal;
font-weight: 700;
+ color: var(--heading-color, var(--text-color));
}
.list-group img {
@@ -38,19 +108,19 @@ h1 {
text-align: left;
word-wrap: break-word !important;
white-space: normal;
- color: white;
+ color: #ffffff;
}
#header h1 #pyhead {
- color: #336600;
+ color: var(--brand-primary);
}
#header h1 #iehead {
- color: #339900;
+ color: var(--brand-secondary);
}
#header h1 #yearhead {
- color: #66cc33;
+ color: var(--brand-secondary);
}
#header .well {
@@ -85,9 +155,133 @@ h1 {
font-family: "bree", sans-serif;
font-style: normal;
font-weight: 400;
- color: black;
+ color: var(--text-color);
}
.thumbnail {
padding: 4px 0;
}
+
+.navbar-default {
+ background-color: var(--nav-bg);
+ border-color: var(--border-color);
+}
+
+.navbar-default .navbar-brand {
+ color: var(--nav-text);
+}
+
+.navbar-default .navbar-brand:hover,
+.navbar-default .navbar-brand:focus {
+ color: var(--link-hover-color);
+}
+
+.navbar-default .navbar-nav > li > a {
+ color: var(--nav-text);
+}
+
+.navbar-default .navbar-nav > li > a:hover,
+.navbar-default .navbar-nav > li > a:focus {
+ color: var(--link-hover-color);
+ background-color: var(--nav-hover-bg);
+}
+
+.navbar-default .navbar-toggle .icon-bar {
+ background-color: var(--nav-text);
+}
+
+.well {
+ background-color: var(--card-bg);
+ border-color: var(--border-color);
+ color: var(--text-color);
+}
+
+footer .well {
+ background-color: var(--footer-bg);
+}
+
+.panel,
+.panel-default,
+.panel-body {
+ background-color: var(--card-bg);
+ border-color: var(--border-color);
+ color: var(--text-color);
+}
+
+.panel-heading {
+ background-color: var(--nav-bg);
+ color: var(--text-color);
+}
+
+.list-group-item {
+ background-color: var(--card-bg);
+ border-color: var(--border-color);
+ color: var(--text-color);
+}
+
+.header-bar {
+ position: relative;
+}
+
+.header-brand {
+ text-decoration: none;
+ color: inherit;
+}
+
+.header-actions {
+ position: absolute;
+ top: 50%;
+ right: 0;
+ transform: translateY(-50%);
+}
+
+.theme-toggle {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ background: transparent;
+ border: none;
+ padding: 4px 0;
+ color: var(--text-color);
+ font-size: 14px;
+ cursor: pointer;
+}
+
+.theme-toggle:hover {
+ color: var(--link-hover-color);
+}
+
+.theme-toggle-switch {
+ position: relative;
+ width: 48px;
+ height: 24px;
+ border-radius: 12px;
+ background: var(--button-bg);
+ border: 2px solid var(--button-border);
+ transition: background 0.2s, border-color 0.2s;
+}
+
+.theme-toggle-switch::before {
+ content: '';
+ position: absolute;
+ width: 18px;
+ height: 18px;
+ border-radius: 50%;
+ background: var(--button-text);
+ top: 1px;
+ left: 1px;
+ transition: transform 0.2s;
+}
+
+.theme-toggle.is-dark .theme-toggle-switch {
+ background: var(--nav-hover-bg);
+ border-color: var(--border-color);
+}
+
+.theme-toggle.is-dark .theme-toggle-switch::before {
+ transform: translateX(24px);
+}
+
+.theme-toggle-label {
+ font-weight: 500;
+}
diff --git a/pythonie/core/static/js/theme-toggle.js b/pythonie/core/static/js/theme-toggle.js
new file mode 100644
index 0000000..d952f74
--- /dev/null
+++ b/pythonie/core/static/js/theme-toggle.js
@@ -0,0 +1,70 @@
+(function () {
+ var storageKey = 'pythonie-theme';
+ var className = 'dark';
+ var root = document.documentElement;
+ var toggleButton;
+
+ function getStoredTheme() {
+ try {
+ return window.localStorage ? localStorage.getItem(storageKey) : null;
+ } catch (e) {
+ return null;
+ }
+ }
+
+ function setStoredTheme(theme) {
+ try {
+ if (window.localStorage) {
+ localStorage.setItem(storageKey, theme);
+ }
+ } catch (e) {
+ /* ignore storage errors */
+ }
+ }
+
+ function prefersDark() {
+ return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
+ }
+
+ function getActiveTheme() {
+ return getStoredTheme() || (prefersDark() ? 'dark' : 'light');
+ }
+
+ function applyTheme(theme) {
+ if (theme === 'dark') {
+ root.classList.add(className);
+ } else {
+ root.classList.remove(className);
+ theme = 'light';
+ }
+ root.dataset.theme = theme;
+ updateToggle(theme);
+ }
+
+ function updateToggle(theme) {
+ if (!toggleButton) return;
+ var isDark = theme === 'dark';
+ toggleButton.setAttribute('aria-pressed', isDark);
+ toggleButton.classList.toggle('is-dark', isDark);
+ var label = toggleButton.querySelector('[data-theme-label]');
+ if (label) label.textContent = isDark ? 'Light' : 'Dark';
+ }
+
+ function handleToggle() {
+ var isDark = root.classList.contains(className);
+ var nextTheme = isDark ? 'light' : 'dark';
+ applyTheme(nextTheme);
+ setStoredTheme(nextTheme);
+ }
+
+ function initThemeToggle() {
+ toggleButton = document.querySelector('[data-theme-toggle]');
+ if (!toggleButton) {
+ return;
+ }
+ toggleButton.addEventListener('click', handleToggle);
+ applyTheme(getActiveTheme());
+ }
+
+ document.addEventListener('DOMContentLoaded', initThemeToggle);
+})();
diff --git a/pythonie/core/templates/base.html b/pythonie/core/templates/base.html
index a989652..dbc01c2 100644
--- a/pythonie/core/templates/base.html
+++ b/pythonie/core/templates/base.html
@@ -17,6 +17,29 @@
+
+
@@ -52,7 +75,7 @@
{% include "footer.html" %}
{% compress js %}
-
+
{% endcompress %}
{% block basejs %}
diff --git a/pythonie/core/templates/header.html b/pythonie/core/templates/header.html
index ac1cafe..ece3bdf 100644
--- a/pythonie/core/templates/header.html
+++ b/pythonie/core/templates/header.html
@@ -1,12 +1,24 @@
{% load static %}