diff --git a/assets/js/set-theme.js b/assets/js/set-theme.js
new file mode 100644
index 0000000..5ee4ac4
--- /dev/null
+++ b/assets/js/set-theme.js
@@ -0,0 +1,40 @@
+const toggleButton = document.getElementById('themeToggle');
+const htmlElement = document.documentElement;
+
+function setTheme(theme) {
+ htmlElement.setAttribute('data-theme', theme);
+ localStorage.setItem('theme', theme);
+ updateToggleButton(theme);
+}
+
+function updateToggleButton(theme) {
+ toggleButton.setAttribute('aria-checked', theme === 'dark');
+}
+
+function getPreferredTheme() {
+ const storedTheme = localStorage.getItem('theme');
+ if (storedTheme) {
+ return storedTheme;
+ }
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
+}
+
+// Ensure the toggle button state is correct on load
+document.addEventListener('DOMContentLoaded', () => {
+ updateToggleButton(getPreferredTheme());
+});
+
+// Listen for toggle button clicks
+toggleButton.addEventListener('click', () => {
+ const currentTheme = htmlElement.getAttribute('data-theme') || getPreferredTheme();
+ const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
+ setTheme(newTheme);
+});
+
+// Listen for system preference changes
+window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
+ if (!localStorage.getItem('theme')) {
+ const newTheme = e.matches ? 'dark' : 'light';
+ setTheme(newTheme);
+ }
+});
\ No newline at end of file
diff --git a/layouts/partials/footer.html b/layouts/partials/footer.html
index 9f713ba..d1ba2ac 100644
--- a/layouts/partials/footer.html
+++ b/layouts/partials/footer.html
@@ -15,3 +15,7 @@
{{ $jsCopy := resources.Get "js/code-copy-button.js" | minify }}
{{ end }}
+
+
+{{ $themeToggle := resources.Get "js/set-theme.js" | minify }}
+
diff --git a/layouts/partials/head.html b/layouts/partials/head.html
index bcf7dc5..4691e45 100644
--- a/layouts/partials/head.html
+++ b/layouts/partials/head.html
@@ -21,6 +21,18 @@
{{ partialCached "favicon" . }}
{{ partial "opengraph" . }}
+
+
+
diff --git a/layouts/partials/header.html b/layouts/partials/header.html
index 45c46ce..c3435de 100644
--- a/layouts/partials/header.html
+++ b/layouts/partials/header.html
@@ -7,4 +7,8 @@
{{ end }}
-
+
\ No newline at end of file
diff --git a/static/css/custom.css b/static/css/custom.css
index ddabad1..0398121 100644
--- a/static/css/custom.css
+++ b/static/css/custom.css
@@ -1,9 +1,113 @@
-/* color and font overrides */
+/* define fonts */
:root {
- --code: var(--base06);
--font-monospace: 'Berkeley Mono', 'IBM Plex Mono', 'Cascadia Mono', 'Roboto Mono', 'Source Code Pro', 'Fira Mono', 'Courier New', monospace;
}
+/* dark/light theming */
+:root{
+ /* Default dark theme */
+ --bg: var(--dark-base00);
+ --code: var(--dark-base06);
+ --fg: var(--dark-base05);
+ --highlight: var(--dark-base0A);
+ --hover: var(--dark-base0C);
+ --inner-bg: var(--dark-base02);
+ --link: var(--dark-base0D);
+ --logo-text: var(--dark-base09);
+ --logo: var(--dark-base0B);
+ --muted: var(--dark-base03);
+ --off-bg: var(--dark-base01);
+ --off-fg: var(--dark-base04);
+ --root-prompt: var(--dark-base08);
+ --user-prompt: var(--dark-base07);
+}
+
+:root[data-theme="light"] {
+ --bg: var(--light-base00);
+ --off-bg: var(--light-base01);
+ --inner-bg: var(--light-base02);
+ --muted: var(--light-base03);
+ --off-fg: var(--light-base04);
+ --fg: var(--light-base05);
+ --code: var(--light-base06);
+ --user-prompt: var(--light-base07);
+ --root-prompt: var(--light-base08);
+ --logo-text: var(--light-base09);
+ --highlight: var(--light-base0A);
+ --logo: var(--light-base0B);
+ --hover: var(--light-base0C);
+ --link: var(--light-base0D);
+}
+
+@media (prefers-color-scheme: light) {
+ :root:not([data-theme="dark"]) {
+ --bg: var(--light-base00);
+ --off-bg: var(--light-base01);
+ --inner-bg: var(--light-base02);
+ --muted: var(--light-base03);
+ --off-fg: var(--light-base04);
+ --fg: var(--light-base05);
+ --code: var(--light-base06);
+ --user-prompt: var(--light-base07);
+ --root-prompt: var(--light-base08);
+ --logo-text: var(--light-base09);
+ --highlight: var(--light-base0A);
+ --logo: var(--light-base0B);
+ --hover: var(--light-base0C);
+ --link: var(--light-base0D);
+ }
+}
+
+body {
+ transition: background-color 0.3s ease, color 0.3s ease;
+}
+
+.theme-toggle {
+ position: absolute;
+ top: 1rem;
+ right: 1rem;
+ background: none;
+ border: none;
+ cursor: pointer;
+ padding: 0;
+ width: 2rem;
+ height: 2rem;
+ z-index: 1000;
+ transition: opacity 0.3s ease;
+}
+
+.theme-toggle:hover {
+ opacity: 0.7;
+}
+
+.theme-toggle:focus {
+ outline: none;
+}
+
+.theme-toggle:focus-visible {
+ outline: 2px solid var(--hover);
+ outline-offset: 2px;
+}
+
+.theme-toggle__icon {
+ width: 100%;
+ height: 100%;
+ transition: transform 0.3s ease, fill 0.3s ease;
+ fill: var(--off-fg);
+}
+
+.theme-toggle:hover .theme-toggle__icon {
+ fill: var(--hover);
+}
+
+[data-theme="dark"] .theme-toggle__icon {
+ transform: rotate(180deg);
+}
+
+.page__nav ul {
+ padding-right: 3rem;
+}
+
/* load preferred font */
@font-face {
font-family: 'Berkeley Mono';