Sprucing Up the Blog - Themes, Toggles, and Reflex Flexibility 🎨
· Categories: jekyll, css, javascript, dark-mode, github-actions, reflexes, ai-collaboration
It started, as these things often do, with a simple request: “The landing page could be a bit cooler”. Little did we know this would take us on a fun journey through Jekyll theming, CSS variables, JavaScript debugging, and ultimately, showcase the power of a consistent build environment thanks to Reflexes.
✨ Giving the Homepage Some Flair
The default Minima theme homepage is functional, but a bit bare. We wanted more!
- Custom Index: First, we ditched the theme’s default index by adding
index.markdownto_config.yml’sexcludelist. - Our Own Index: We created
blog/content/index.html, using thedefaultlayout but adding our own loop ({% for post in site.posts %}) to display not just titles, but also dates, categories, and crucially, post excerpts ({{ post.excerpt }}). This gives visitors a much better preview of the content. - Styling: Of course, new HTML needs new CSS. We added styles to
blog/content/assets/main.scss(initially namedstyle.scss, more on that later!) for the.post-preview,.post-excerpt,.read-morelink, and category links within the.post-metablock.
🌙 Embracing the Dark Side (Mode)
Static themes are so last season. We needed dark mode!
- Initial Attempt (
prefers-color-scheme): Our first thought was the simple CSS media query@media (prefers-color-scheme: dark). We refactored the SCSS to use CSS variables (--text-color,--background-color, etc.) defined in:root, and then redefined these variables within the media query for dark mode. We replaced direct SCSS variable usage ($text-color) withvar(--text-color). - The Toggle Request: Simple automatic dark mode wasn’t enough – a manual toggle (☀️/🌙) was requested! This meant ditching
prefers-color-schemein favor of a JavaScript-controlled approach. - JS +
data-theme:- We created
blog/content/assets/js/theme-toggle.js. - This script checks
localStoragefor a saved theme preference. If none exists, it defaults to the user’s system preference (window.matchMedia('(prefers-color-scheme: dark)').matches). - It adds an event listener to a button (
#theme-toggle). - On click, it toggles the
data-themeattribute on an HTML element between ‘light’ and ‘dark’ and saves the new preference tolocalStorage.
- We created
- CSS Update: We changed the CSS variables to be scoped by the attribute:
:root, html[data-theme="light"] { ... }andhtml[data-theme="dark"] { ... }. - HTML Integration: We added the toggle button (
<button id="theme-toggle">...) to_includes/header.htmland the script tag (<script src="...">) to_includes/footer.html. We also added a small inline script to_includes/head.htmlto set the initialdata-themeon the<html>tag before the main CSS loads, preventing a “flash of unstyled content” (FOUC) or flash of the wrong theme.
🐛 Debugging the Toggle - A Classic Tale
It wouldn’t be software development without debugging!
- Problem 1: Toggle Didn’t Work: Initially, clicking the button did nothing, and both icons showed. Reason: The JS was setting
data-themeondocument.body, but the CSS was looking for it onhtml[...]. Fix: Updated the JS (theme-toggle.js) to get/set the attribute ondocument.documentElementinstead. - Problem 2: CSS Not Applying: Even with the JS fixed, the styles weren’t right. Reason: Our custom styles were in
assets/css/style.scss, but Jekyll (using the Minima theme) expects the main stylesheet entry point to beassets/main.scss. Our custom styles weren’t being included in the build output (/tmp_jekyll_output/assets/main.css). Fix: Renamedstyle.scsstomain.scssand added@import "minima";at the top to ensure the base theme styles were included first, followed by our overrides and additions.
🚀 CI/CD Consistency with Reflexes
Okay, great, it works locally! But how do we ensure it builds correctly in our GitHub Actions workflow?
- Initial Workflow Check: The existing workflow (
.github/workflows/blog.frison.ca.yaml) used a different base image (frison/simple-sites:example) and different volume mount paths than our local Reflex setup. - Aligning the Build: We updated the workflow to use the Reflex approach:
- Build Dependencies: We realized the
reflexes/generate/jekyll-siteDockerfile depended oncortex/ruby:localandreflexes--base-tools:latest. We added steps to build these first within the CI job using the project’s standard methods (cd ./cortex && make rubyand./reflexes/bin/build reflexes/.base-tools). - Build Target: Added a step to build the
reflexes/generate/jekyll-siteimage itself using./reflexes/bin/build reflexes/generate/jekyll-site. - Run with Correct Mounts: Updated the
docker runcommand to use the locally builtreflexes-generate-jekyll-site:latestimage and map the volumes to the paths expected by that image (/app/input_content,/app/input_config,/app/output_static_site).
- Build Dependencies: We realized the
✨ Looking Forward
This little adventure highlights the beauty of the Reflex pattern. By defining our build and execution environment in a containerized “reflex,” we could develop and debug locally with high confidence. When it came time for CI/CD, we simply replicated the exact same build steps in the workflow. No more “works on my machine” issues caused by differing environments! The build process defined by the Reflexes (cortex/ruby -> base-tools -> jekyll-site) became the single source of truth, ensuring consistency from local development to automated deployment. Now, about those SCSS deprecation warnings from the Minima theme… maybe next time! 😉
Generated by an AI assistant and reviewed by human. Commit history for this post: e68a32af969c9a3fbcadc7af14e9b7f215609d3e