Skip to content

Commit c0bebbb

Browse files
authored
🎨 feat: implement theme toggle functionality and update styles
- Add CSS variables for light and dark themes in style.css - Create theme-toggle.js to manage theme switching and local storage - Update base.html to include theme initialization script - Enhance header.html with a theme toggle button for user interaction - Refactor existing styles to utilize CSS variables for consistency
1 parent 677fb28 commit c0bebbb

File tree

4 files changed

+312
-13
lines changed

4 files changed

+312
-13
lines changed

pythonie/core/static/css/style.css

Lines changed: 201 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,80 @@
1+
:root {
2+
--bg-color: #eaeff2;
3+
--text-color: #111827;
4+
--muted-color: #475569;
5+
--link-color: #0b61d8;
6+
--link-hover-color: #0a58ca;
7+
--card-bg: #ffffff;
8+
--border-color: #d0d7de;
9+
--nav-bg: #ffffff;
10+
--nav-hover-bg: #e5e7eb;
11+
--nav-text: #111827;
12+
--footer-bg: #ffffff;
13+
--button-bg: #e5e7eb;
14+
--button-text: #111827;
15+
--button-border: #cbd5e1;
16+
--focus-ring: #2563eb;
17+
--brand-primary: #336600;
18+
--brand-secondary: #339900;
19+
--heading-color: #111827;
20+
}
21+
22+
html.dark {
23+
--bg-color: #222426; /* page background */
24+
--text-color: #bdbcb2; /* requested body text */
25+
--muted-color: #cbd5e1; /* muted */
26+
--link-color: #facc15; /* emphasized links */
27+
--link-hover-color: #c3c3bc; /* neutral hover to avoid yellow tint */
28+
--card-bg: #1e2021; /* content boxes */
29+
--border-color: #374151; /* subtle mid gray */
30+
--nav-bg: #1c1e1f; /* navbar background */
31+
--nav-hover-bg: #111827;
32+
--nav-text: #bdbcb2;
33+
--footer-bg: #111827;
34+
--button-bg: #1f2937;
35+
--button-text: #e5e7eb;
36+
--button-border: #374151;
37+
--focus-ring: #76b3fa;
38+
--brand-primary: #336600; /* match original logo green */
39+
--brand-secondary: #339900; /* supporting tint */
40+
--heading-color: #c3c3bc;
41+
}
42+
143
body {
2-
background: #EAEFF2;
3-
color: #333;
44+
background: var(--bg-color);
45+
color: var(--text-color);
446
font-size: 16px;
547
font-family: "bree-serif", serif;
648
font-style: normal;
749
font-weight: 300; /* Bree Serif: 700 (bold), 400 (regular), 300 (light), 200 (thin); */
850
}
951

52+
body, p, li {
53+
color: var(--text-color);
54+
}
55+
56+
small,
57+
.text-muted {
58+
color: var(--muted-color);
59+
}
60+
61+
a {
62+
color: var(--link-color);
63+
text-decoration: none;
64+
}
65+
66+
a:hover,
67+
a:focus {
68+
color: var(--link-hover-color);
69+
text-decoration: underline;
70+
}
71+
72+
a:focus-visible,
73+
button:focus-visible {
74+
outline: 2px solid var(--focus-ring);
75+
outline-offset: 2px;
76+
}
77+
1078
.wf-loading {
1179
visibility: hidden;
1280
}
@@ -16,12 +84,14 @@ h2, h3, h4, h5, h6 {
1684
font-family: "bree", sans-serif;
1785
font-style: normal;
1886
font-weight: 400; /* Bree: 700 (bold), 400 (regular), 300 (light), 200 (thin) */
87+
color: var(--heading-color, var(--text-color));
1988
}
2089

2190
h1 {
2291
font-family: "bree", sans-serif;
2392
font-style: normal;
2493
font-weight: 700;
94+
color: var(--heading-color, var(--text-color));
2595
}
2696

2797
.list-group img {
@@ -38,19 +108,19 @@ h1 {
38108
text-align: left;
39109
word-wrap: break-word !important;
40110
white-space: normal;
41-
color: white;
111+
color: #ffffff;
42112
}
43113

44114
#header h1 #pyhead {
45-
color: #336600;
115+
color: var(--brand-primary);
46116
}
47117

48118
#header h1 #iehead {
49-
color: #339900;
119+
color: var(--brand-secondary);
50120
}
51121

52122
#header h1 #yearhead {
53-
color: #66cc33;
123+
color: var(--brand-secondary);
54124
}
55125

56126
#header .well {
@@ -85,9 +155,133 @@ h1 {
85155
font-family: "bree", sans-serif;
86156
font-style: normal;
87157
font-weight: 400;
88-
color: black;
158+
color: var(--text-color);
89159
}
90160

91161
.thumbnail {
92162
padding: 4px 0;
93163
}
164+
165+
.navbar-default {
166+
background-color: var(--nav-bg);
167+
border-color: var(--border-color);
168+
}
169+
170+
.navbar-default .navbar-brand {
171+
color: var(--nav-text);
172+
}
173+
174+
.navbar-default .navbar-brand:hover,
175+
.navbar-default .navbar-brand:focus {
176+
color: var(--link-hover-color);
177+
}
178+
179+
.navbar-default .navbar-nav > li > a {
180+
color: var(--nav-text);
181+
}
182+
183+
.navbar-default .navbar-nav > li > a:hover,
184+
.navbar-default .navbar-nav > li > a:focus {
185+
color: var(--link-hover-color);
186+
background-color: var(--nav-hover-bg);
187+
}
188+
189+
.navbar-default .navbar-toggle .icon-bar {
190+
background-color: var(--nav-text);
191+
}
192+
193+
.well {
194+
background-color: var(--card-bg);
195+
border-color: var(--border-color);
196+
color: var(--text-color);
197+
}
198+
199+
footer .well {
200+
background-color: var(--footer-bg);
201+
}
202+
203+
.panel,
204+
.panel-default,
205+
.panel-body {
206+
background-color: var(--card-bg);
207+
border-color: var(--border-color);
208+
color: var(--text-color);
209+
}
210+
211+
.panel-heading {
212+
background-color: var(--nav-bg);
213+
color: var(--text-color);
214+
}
215+
216+
.list-group-item {
217+
background-color: var(--card-bg);
218+
border-color: var(--border-color);
219+
color: var(--text-color);
220+
}
221+
222+
.header-bar {
223+
position: relative;
224+
}
225+
226+
.header-brand {
227+
text-decoration: none;
228+
color: inherit;
229+
}
230+
231+
.header-actions {
232+
position: absolute;
233+
top: 50%;
234+
right: 0;
235+
transform: translateY(-50%);
236+
}
237+
238+
.theme-toggle {
239+
display: inline-flex;
240+
align-items: center;
241+
gap: 8px;
242+
background: transparent;
243+
border: none;
244+
padding: 4px 0;
245+
color: var(--text-color);
246+
font-size: 14px;
247+
cursor: pointer;
248+
}
249+
250+
.theme-toggle:hover {
251+
color: var(--link-hover-color);
252+
}
253+
254+
.theme-toggle-switch {
255+
position: relative;
256+
width: 48px;
257+
height: 24px;
258+
border-radius: 12px;
259+
background: var(--button-bg);
260+
border: 2px solid var(--button-border);
261+
transition: background 0.2s, border-color 0.2s;
262+
}
263+
264+
.theme-toggle-switch::before {
265+
content: '';
266+
position: absolute;
267+
width: 18px;
268+
height: 18px;
269+
border-radius: 50%;
270+
background: var(--button-text);
271+
top: 1px;
272+
left: 1px;
273+
transition: transform 0.2s;
274+
}
275+
276+
.theme-toggle.is-dark .theme-toggle-switch {
277+
background: var(--nav-hover-bg);
278+
border-color: var(--border-color);
279+
}
280+
281+
.theme-toggle.is-dark .theme-toggle-switch::before {
282+
transform: translateX(24px);
283+
}
284+
285+
.theme-toggle-label {
286+
font-weight: 500;
287+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
(function () {
2+
var storageKey = 'pythonie-theme';
3+
var className = 'dark';
4+
var root = document.documentElement;
5+
var toggleButton;
6+
7+
function getStoredTheme() {
8+
try {
9+
return window.localStorage ? localStorage.getItem(storageKey) : null;
10+
} catch (e) {
11+
return null;
12+
}
13+
}
14+
15+
function setStoredTheme(theme) {
16+
try {
17+
if (window.localStorage) {
18+
localStorage.setItem(storageKey, theme);
19+
}
20+
} catch (e) {
21+
/* ignore storage errors */
22+
}
23+
}
24+
25+
function prefersDark() {
26+
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
27+
}
28+
29+
function getActiveTheme() {
30+
return getStoredTheme() || (prefersDark() ? 'dark' : 'light');
31+
}
32+
33+
function applyTheme(theme) {
34+
if (theme === 'dark') {
35+
root.classList.add(className);
36+
} else {
37+
root.classList.remove(className);
38+
theme = 'light';
39+
}
40+
root.dataset.theme = theme;
41+
updateToggle(theme);
42+
}
43+
44+
function updateToggle(theme) {
45+
if (!toggleButton) return;
46+
var isDark = theme === 'dark';
47+
toggleButton.setAttribute('aria-pressed', isDark);
48+
toggleButton.classList.toggle('is-dark', isDark);
49+
var label = toggleButton.querySelector('[data-theme-label]');
50+
if (label) label.textContent = isDark ? 'Light' : 'Dark';
51+
}
52+
53+
function handleToggle() {
54+
var isDark = root.classList.contains(className);
55+
var nextTheme = isDark ? 'light' : 'dark';
56+
applyTheme(nextTheme);
57+
setStoredTheme(nextTheme);
58+
}
59+
60+
function initThemeToggle() {
61+
toggleButton = document.querySelector('[data-theme-toggle]');
62+
if (!toggleButton) {
63+
return;
64+
}
65+
toggleButton.addEventListener('click', handleToggle);
66+
applyTheme(getActiveTheme());
67+
}
68+
69+
document.addEventListener('DOMContentLoaded', initThemeToggle);
70+
})();

pythonie/core/templates/base.html

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,29 @@
1717
<meta name="description" content=""/>
1818
<meta name="viewport" content="width=device-width, initial-scale=1"/>
1919

20+
<script>
21+
(function () {
22+
var storageKey = 'pythonie-theme';
23+
var className = 'dark';
24+
25+
try {
26+
var stored = window.localStorage ? localStorage.getItem(storageKey) : null;
27+
var prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
28+
var theme = stored || (prefersDark ? 'dark' : 'light');
29+
30+
if (theme === 'dark') {
31+
document.documentElement.classList.add(className);
32+
} else {
33+
document.documentElement.classList.remove(className);
34+
}
35+
document.documentElement.dataset.theme = theme;
36+
} catch (e) {
37+
document.documentElement.classList.remove(className);
38+
document.documentElement.dataset.theme = 'light';
39+
}
40+
})();
41+
</script>
42+
2043
<script type="text/javascript" src="//use.typekit.net/qgj1xay.js"></script>
2144
<script type="text/javascript">try{Typekit.load({async: false});}catch(e){}</script>
2245

@@ -52,7 +75,7 @@
5275
{% include "footer.html" %}
5376

5477
{% compress js %}
55-
<script type="text/javascript" src="{% static 'js/pythonie.js' %}"></script>
78+
<script type="text/javascript" src="{% static 'js/theme-toggle.js' %}"></script>
5679
{% endcompress %}
5780

5881
{% block basejs %}

pythonie/core/templates/header.html

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,24 @@
11
{% load static %}
22

33
<section class="container">
4-
<header class="row" id="header">
4+
<header class="row header-bar" id="header">
55
<!-- Begin grid -->
6-
<a href="/">
7-
<h1><img src="{% static 'img/pythonie.png' %}"
8-
class="logo img-rounded"/>
9-
<span id="pyhead">Python Ireland</span></h1></a>
6+
<a class="header-brand" href="/">
7+
<h1>
8+
<img src="{% static 'img/pythonie.png' %}" class="logo img-rounded"/>
9+
<span id="pyhead">Python Ireland</span>
10+
</h1>
11+
</a>
12+
<div class="header-actions">
13+
<button type="button"
14+
class="theme-toggle"
15+
data-theme-toggle
16+
aria-pressed="false"
17+
aria-label="Toggle dark mode">
18+
<span class="theme-toggle-switch"></span>
19+
<span class="theme-toggle-label" data-theme-label>Dark</span>
20+
</button>
21+
</div>
1022
<!-- End grid -->
1123
</header>
1224
<!-- header -->

0 commit comments

Comments
 (0)