diff --git a/assets/css/torchlight.css b/assets/css/torchlight.css index 5def64b..a864f1a 100644 --- a/assets/css/torchlight.css +++ b/assets/css/torchlight.css @@ -126,17 +126,17 @@ opacity: 1; Insert prompt indicators on interactive shells. */ .cmd::before { - color: var(--base07); + color: var(--user-prompt); content: "$ "; } .cmd_root::before { - color: var(--base08); + color: var(--root-prompt); content: "# "; } .cmd_pwsh::before { - color: var(--base07); + color: var(--user-prompt); content: "PS> "; } 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/config/_default/params.toml b/config/_default/params.toml index d990007..5471e29 100644 --- a/config/_default/params.toml +++ b/config/_default/params.toml @@ -110,6 +110,7 @@ taglines = [ "there's no place like $HOME", "time jumped backwards, rotating", "tonight we test in prod", + "unable to decrypt error", "unable to open display", "undefined reference to function", "unexpected token", diff --git a/content/changelog.md b/content/changelog.md index af5241a..9055be2 100644 --- a/content/changelog.md +++ b/content/changelog.md @@ -1,7 +1,7 @@ --- title: "/changelog" date: "2024-05-26T21:19:08Z" -lastmod: "2024-08-04T22:30:43Z" +lastmod: "2024-08-15T21:21:05Z" description: "Maybe I should keep a log of all my site-related tinkering?" featured: false toc: false @@ -10,6 +10,9 @@ categories: slashes --- *Running list of config/layout changes to the site. The full changelog is of course [on GitHub](https://github.com/jbowdre/runtimeterror/commits/main/).* +**2024-08-15:** +- Implemented light/dark theme toggle + **2024-08-04:** - Dynamically build `robots.txt` based on [ai.robots.txt](https://github.com/ai-robots-txt/ai.robots.txt) diff --git a/layouts/_default/single.html b/layouts/_default/single.html index 62d73c2..1c28805 100644 --- a/layouts/_default/single.html +++ b/layouts/_default/single.html @@ -26,7 +26,7 @@ {{- if and (gt $ageDays 365) (not .Params.timeless) -}}
-

Technology keeps moving but this post has not.

+

Technology keeps moving but this post has not.

What you're about to read hasn't been updated in more than a year. The information may be out of date. Let me know if you see anything that needs fixing.
{{- end -}} diff --git a/layouts/partials/about.html b/layouts/partials/about.html index f78bae7..d0e4a3e 100644 --- a/layouts/partials/about.html +++ b/layouts/partials/about.html @@ -1,7 +1,7 @@ {{ with .Site.Params.about }}
{{ with .logo }}{{ end }} -

{{ .title }}  

+

{{ .title }} 

{{ partial "tagline.html" . }}
 {{ site.Params.Author.name }} 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 113b5ee..4691e45 100644 --- a/layouts/partials/head.html +++ b/layouts/partials/head.html @@ -21,8 +21,20 @@ {{ 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 c7c6ddf..41d797f 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'; @@ -30,7 +134,7 @@ /* logo tweaks */ .page__logo { - color: var(--off-fg); + color: var(--logo-text); } .page__logo-inner { @@ -70,59 +174,24 @@ /* Notice CSS Built on hugo-notice by Nicolas Martignoni: https://github.com/martignoni/hugo-notice */ .notice { - --root-color: #444; - --root-background: #eff; - --title-color: #fff; - --title-background: #7bd; - --warning-title: #c33; - --warning-content: #fee; - --info-title: #fb7; - --info-content: #fec; - --note-title: #6be; - --note-content: #e7f2fa; - --tip-title: #5a5; - --tip-content: #efe; -} + --notice-title-color: #fff; + --notice-warn-color: #c33; + --notice-info-color: #fb7; + --notice-note-color: #6be; + --notice-tip-color: #5a5; -@media (prefers-color-scheme: dark) { - .notice { - --root-color: #ddd; - --root-background: #eff; - --title-color: #fff; - --title-background: #7bd; - --warning-title: #800; - --warning-content: #400; - --info-title: #a50; - --info-content: #420; - --note-title: #069; - --note-content: #023; - --tip-title: #363; - --tip-content: #121; - } -} + --notice-padding: 18px; + --notice-line-height: 24px; + --notice-margin-bottom: 24px; + --notice-border-radius: 4px; + --notice-title-margin: 12px; + --notice-bg-opacity: 10%; -body.dark .notice { - --root-color: #ddd; - --root-background: #eff; - --title-color: #fff; - --title-background: #7bd; - --warning-title: #800; - --warning-content: #400; - --info-title: #a50; - --info-content: #420; - --note-title: #069; - --note-content: #023; - --tip-title: #363; - --tip-content: #121; -} - -.notice { - padding: 18px; - line-height: 24px; - margin-bottom: 24px; - border-radius: 4px; - color: var(--root-color); - background: var(--root-background); + padding: var(--notice-padding); + line-height: var(--notice-line-height); + margin-bottom: var(--notice-margin-bottom); + border-radius: var(--notice-border-radius); + color: var(--fg); } .notice p:last-child { @@ -130,45 +199,23 @@ body.dark .notice { } .notice-title { - margin: -18px -18px 12px; - padding: 4px 18px; - border-radius: 4px 4px 0 0; + margin: calc(-1 * var(--notice-padding)); + margin-bottom: var(--notice-title-margin); + padding: 4px var(--notice-padding); + border-radius: var(--notice-border-radius) var(--notice-border-radius) 0 0; font-weight: 700; - color: var(--title-color); - background: var(--title-background); + color: var(--notice-title-color); } -.notice.warning .notice-title { - background: var(--warning-title); -} +.notice.warning .notice-title { background: var(--notice-warning-color); } +.notice.info .notice-title { background: var(--notice-info-color); } +.notice.note .notice-title { background: var(--notice-note-color); } +.notice.tip .notice-title { background: var(--notice-tip-color); } -.notice.warning { - background: var(--warning-content); -} - -.notice.info .notice-title { - background: var(--info-title); -} - -.notice.info { - background: var(--info-content); -} - -.notice.note .notice-title { - background: var(--note-title); -} - -.notice.note { - background: var(--note-content); -} - -.notice.tip .notice-title { - background: var(--tip-title); -} - -.notice.tip { - background: var(--tip-content); -} +.notice.warning { background: color-mix(in srgb, var(--notice-warning-color) var(--notice-bg-opacity), transparent); } +.notice.info { background: color-mix(in srgb, var(--notice-info-color) var(--notice-bg-opacity), transparent); } +.notice.note { background: color-mix(in srgb, var(--notice-note-color) var(--notice-bg-opacity), transparent); } +.notice.tip { background: color-mix(in srgb, var(--notice-tip-color) var(--notice-bg-opacity), transparent); } .icon-notice { display: inline-flex; diff --git a/static/css/palettes/runtimeterror.css b/static/css/palettes/runtimeterror.css index 76e566e..5d6b2a9 100644 --- a/static/css/palettes/runtimeterror.css +++ b/static/css/palettes/runtimeterror.css @@ -2,20 +2,35 @@ */ :root { - --base00: #090909; /* bg */ - --base01: #1c1c1c; /* off-bg */ - --base02: #292929; /* inner-bg */ - --base03: #6d6c6c; /* muted */ - --base04: #abaaaa; /* off-fg */ - --base05: #d8d8d8; /* fg */ - --base06: #75f558; /* code */ - --base07: #5f8700; /* user prompt */ - --base08: #ab4642; /* root prompt */ - --base09: #dc9656; - --base0A: #f7ca88; /* highlight */ - --base0B: #682523; /* logo */ - --base0C: #ab2321; /* hover */ - --base0D: #d36060; /* link */ - --base0E: #ba8baf; - --base0F: #a16946; + /* dark theme colors */ + --dark-base00: #090909; /* bg */ + --dark-base01: #1c1c1c; /* off-bg */ + --dark-base02: #292929; /* inner-bg */ + --dark-base03: #6d6c6c; /* muted */ + --dark-base04: #abaaaa; /* off-fg */ + --dark-base05: #d8d8d8; /* fg */ + --dark-base06: #75f558; /* code */ + --dark-base07: #5f8700; /* user prompt */ + --dark-base08: #ab4642; /* root prompt */ + --dark-base09: #abaaaa; /* logo text */ + --dark-base0A: #f7ca88; /* highlight */ + --dark-base0B: #682523; /* logo */ + --dark-base0C: #ab2321; /* hover */ + --dark-base0D: #d36060; /* link */ + + /* light theme colors */ + --light-base00: #ffffff; /* bg */ + --light-base01: #f0f0f0; /* off-bg */ + --light-base02: #dbdbdb; /* inner-bg */ + --light-base03: #909090; /* muted */ + --light-base04: #707070; /* off-fg */ + --light-base05: #303030; /* fg */ + --light-base06: #2a8f1f; /* code */ + --light-base07: #3f5900; /* user prompt */ + --light-base08: #c23d3d; /* root prompt */ + --light-base09: #f0f0f0; /* logo-text */ + --light-base0A: #d4a960; /* highlight */ + --light-base0B: #af3a37; /* logo */ + --light-base0C: #c62a28; /* hover */ + --light-base0D: #a04545; /* link */ } diff --git a/torchlight.config.js b/torchlight.config.js index bc799f9..8c44c81 100644 --- a/torchlight.config.js +++ b/torchlight.config.js @@ -11,7 +11,7 @@ module.exports = { // Which theme you want to use. You can find all of the themes at // https://torchlight.dev/docs/themes. - theme: 'synthwave-84', + theme: 'material-theme-lighter', // The Host of the API. host: 'https://api.torchlight.dev',