Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 51 additions & 5 deletions static/css/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@

/* override colors for dark theme */
@media (prefers-color-scheme: dark) {
:root {
:root:not([data-theme]) {
--light: #000000; /* inverted from --dark */
--dark: #f8f9fa; /* inverted from --light */
--gray-dark: #9aa0a6; /* inverted from --gray-light */
Expand All @@ -50,11 +50,26 @@
--logo: url('/images/logo-dark.svg');
}

.logo {
:root:not([data-theme]) .logo {
content: url('/images/logo-dark.svg');
}
}

/* Forced dark theme: applied when the toggle is set to Dark, or when the
inline <head> script resolved "System" against a dark OS. That script
always writes a concrete data-theme, so the prefers-color-scheme media
queries above only act as a no-JS fallback. */
:root[data-theme="dark"] {
--light: #000000;
--dark: #f8f9fa;
--gray-dark: #9aa0a6;
--gray-light: #343a40;
--gray-lightest: #252525;
--bg-green: #46b292;
--bg-red: #a44244;
--logo: url('/images/logo-dark.svg');
}

/* *************************************** */
/* Fonts and typography */
/* *************************************** */
Expand Down Expand Up @@ -116,11 +131,15 @@ a.external::after {
}

@media (prefers-color-scheme: dark) {
a.external::after {
:root:not([data-theme]) a.external::after {
content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKAgMAAADwXCcuAAAAAXNSR0IB2cksfwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAxQTFRFAAAA////////////OMA7qAAAAAR0Uk5TAP9gIFOYGRgAAAAqSURBVAjXY2BgDWAIZQpgcOCcwOCgJsDgMIOBwTmBgcHhgAMDDIaGOgAAgeUG8U0ghIsAAAAASUVORK5CYII=);
}
}

:root[data-theme="dark"] a.external::after {
content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKAgMAAADwXCcuAAAAAXNSR0IB2cksfwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAxQTFRFAAAA////////////OMA7qAAAAAR0Uk5TAP9gIFOYGRgAAAAqSURBVAjXY2BgDWAIZQpgcOCcwOCgJsDgMIOBwTmBgcHhgAMDDIaGOgAAgeUG8U0ghIsAAAAASUVORK5CYII=);
}

.logo-light {
content: url('/images/logo-light.svg');
}
Expand Down Expand Up @@ -176,6 +195,24 @@ header #header-nav a:hover {
text-decoration: none;
}

#theme-toggle-btn {
background: none;
border: none;
color: var(--dark);
cursor: pointer;
font-size: 28px;
line-height: 1;
padding: 0 8px;
margin-left: 8px;
vertical-align: middle;
}

#theme-toggle-btn:hover,
#theme-toggle-btn:focus {
color: var(--primary);
outline: 0;
}

header #dropdown-toggle-btn {
display: none;
}
Expand Down Expand Up @@ -478,11 +515,15 @@ nav a {
}

@media (prefers-color-scheme: dark) {
.login-logo {
:root:not([data-theme]) .login-logo {
content: url('/images/logo-dark.svg');
}
}

:root[data-theme="dark"] .login-logo {
content: url('/images/logo-dark.svg');
}

.login-button {
display: block;
padding: 12px 64px;
Expand All @@ -495,12 +536,17 @@ nav a {
}

@media (prefers-color-scheme: dark) {
.login-button {
:root:not([data-theme]) .login-button {
border: solid 1px white;
color: white;
}
}

:root[data-theme="dark"] .login-button {
border: solid 1px white;
color: white;
}

.login-button:hover,
.login-button:focus {
text-decoration: none;
Expand Down
43 changes: 43 additions & 0 deletions templates/base.njk
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{ title | e if title }}{{ " > " + subtitle | e if subtitle }}{{ " | " if title or subtitle }}Codamhero v2</title>
<!-- Resolve colour theme before first paint to avoid a flash of the wrong theme.
Mode is one of 'system' | 'light' | 'dark'; we always write a concrete
data-theme so the CSS never depends on a possibly-wrong browser report. -->
<script>
(function () {
try {
var mode = localStorage.getItem('codamhero-theme') || 'system';
var resolved = mode === 'system'
? ((window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) ? 'dark' : 'light')
: mode;
document.documentElement.setAttribute('data-theme', resolved);
document.documentElement.setAttribute('data-theme-mode', mode);
} catch (e) { /* storage may be blocked - fall back to CSS media query */ }
})();
</script>
<link rel="stylesheet" href="/css/base.css" />
<link href="https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined" rel="stylesheet">

Expand All @@ -26,6 +41,7 @@
{% endif %}
{% endif %}
<a id="contribute" class="main-navigation" href="https://github.com/codam-coding-college/codamhero-v2">CONTRIBUTE</a>
<button id="theme-toggle-btn" class="material-symbols-outlined" type="button" title="Toggle colour theme" aria-label="Toggle colour theme">brightness_auto</button>
</nav>
{% if not nodropdown %}
<button id="dropdown-toggle-btn" class="container-icon material-symbols-outlined" title="Open navigation menu" aria-label="Open navigation menu" aria-controls="dropdown-nav" aria-expanded="false" aria-haspopup="true">menu</button>
Expand Down Expand Up @@ -114,6 +130,33 @@
});
});
</script>
<script>
(function () {
var KEY = 'codamhero-theme';
var btn = document.getElementById('theme-toggle-btn');
if (!btn) return;
var mql = window.matchMedia('(prefers-color-scheme: dark)');
var ICON = { system: 'brightness_auto', light: 'light_mode', dark: 'dark_mode' };
var LABEL = { system: 'System', light: 'Light', dark: 'Dark' };
function currentMode() { return document.documentElement.getAttribute('data-theme-mode') || 'system'; }
function apply(mode) {
var resolved = mode === 'system' ? (mql.matches ? 'dark' : 'light') : mode;
document.documentElement.setAttribute('data-theme', resolved);
document.documentElement.setAttribute('data-theme-mode', mode);
btn.textContent = ICON[mode];
btn.title = 'Theme: ' + LABEL[mode] + ' (click to change)';
btn.setAttribute('aria-label', 'Colour theme: ' + LABEL[mode] + '. Click to change.');
}
apply(currentMode());
btn.addEventListener('click', function () {
var order = ['system', 'light', 'dark'];
var next = order[(order.indexOf(currentMode()) + 1) % order.length];
try { localStorage.setItem(KEY, next); } catch (e) { /* ignore */ }
apply(next);
});
mql.addEventListener('change', function () { if (currentMode() === 'system') apply('system'); });
})();
</script>
<main class="{{ 'noheader' if noheader }}">
{% block content %}{% endblock %}
</main>
Expand Down
Loading