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 %}
-