Tutorial: Publishing your own Github blog (Part 2)

Part 2: setting up and customizing your own blog template

Hugo FerreiraHugo Ferreira

Introduction

In the first part of this series of articles we looked at how to setup a JBake site using the Mill build tool (Mill documentation) to develop and execute the required scripts to generate and publish the content. The goal now is to prepare the JBake Future Imperfect template that is not one of the default templates of JBake. After reading this article, you will able to use other templates that are freely available in the Internet. In this article, I assume you have read and worked through the previous post. In particular, you have a private Github repository that holds your website's sources, a public Github repository configured with its own Github pages URL and a Mill build.sc script with the site module and respective Mill targets to copy, "bake" and deploy your website's sources.

Getting started

We will first initialize our source repository to hold a copy of the JBake Future Imperfect template executing the following command:

~/VSCodeProjects/blog$ ./mill -i site.jbakeGitTemplate

Output

[3/3] site.jbakeGitTemplate 
jbakeGitTemplate: Git source at "https://github.com/manikmagar/jbake-future-imperfect-template.git"
jbakeGitTemplate: check if Git repo at /home/user/VSCodeProjects/blog
Git command at "/home/user/VSCodeProjects/blog/site" remove "/home/user/VSCodeProjects/blog/site/src"
Git clone command at "/home/user/VSCodeProjects/blog/site" with relative destination to "src"
Cloning into 'src'...
remote: Enumerating objects: 775, done.
remote: Total 775 (delta 0), reused 0 (delta 0), pack-reused 775
Receiving objects: 100% (775/775), 2.19 MiB | 2.06 MiB/s, done.
Resolving deltas: 100% (470/470), done.

We can then serve the website's pages locally and see the site's content with this command:

~/VSCodeProjects/blog$ ./mill -i site.jbakeServe

Next we bake and publish the content to the public Github repository as follows:

~/VSCodeProjects/blog$ ./mill -i site.jbakeDeploy

Output:

[9/10] site.jbakeCopy 
Copying site from '/home/user/VSCodeProjects/blog/out/site/jbake.dest' to '/home/user/VSCodeProjects/tech4rd/docs'
[10/10] site.jbakeDeploy 
Git command at /home/user/VSCodeProjects/tech4rd
pushSite: commit message = '2022-05-21T14:37:16.591502@/home/user/VSCodeProjects/blog/site/build.sc/pushSite}: '
pushSite: add
pushSite: commit
pushSite: push
Enumerating objects: 47, done.
Counting objects: 100% (47/47), done.
Delta compression using up to 12 threads
Compressing objects: 100% (20/20), done.
Writing objects: 100% (24/24), 3.19 KiB | 1.06 MiB/s, done.
Total 24 (delta 16), reused 0 (delta 0)
remote: Resolving deltas: 100% (16/16), completed with 15 local objects.
To https://github.com/hmf/tech4rd.git
   c4254b3..10424e6  main -> main

and view them in the remote Github pages. It is best to modify and check your website's content locally. Only when all is correct, do we publish to the remote public site. I will use the following workflow:

  1. Execute the ./mill -i site.jbakeServe;
  2. Open your browser at the local URL:
  3. Make sure to clear the browser's cache to see the latest changes;
  4. Check that the website content is correct;
  5. If the content is not correct, change the site's sources. These will be automatically compiled and copied to the local HTTP browser's folder. The web server will detect the changes and serve the new content;
  6. Repeat the above process until you have what you want. At this point you can execute the ./mill -i site.jbakeDeploy command;
  7. You can now confirm that the content has been correctly published in the remote public website.

Just a note on cleaning the cache. I use the Firefox browser. To clear the cache, I select the striped menu icon on the top right corner of the window. I then select history, followed by clear recent history. A dialogue box will open. Select the option time range to clear to last hour and click Ok. You will have to repeat this process every time you make changes to the website's content.

We are now ready to configure and contribute content to the site.

Initial housekeeping

The template comes with a number of assets such as icons, images, cascading style sheets (CSS) and FreeMarker templates that gives the site its look and feel. It also provides a set of menu options and default entries, including social media links and an "about" page that need to be changed, so that, it contains the information relevant to the website's content. We must first change these in order to "brand" our site with a specific look and feel.

Icons and avatars

The first step in branding your site is to change the icons. For my public site, I searched for an icon of my liking in several free online resources. I found the Iron Man icon by Icons8 to be a good choice. You should select or design you own icons with a size of at least 128 x 128 pixels. There are 3 icons one should change. The first is a favicon that is located in the site/src/assets/img/favicon/favicon.png file.

Originally is was a 75 x 75 PNG image. I downloaded the icon, scaled it from 512 x 512 pixels down to a 75 x 75 pixel image and replaced the original file indicated above. If you refresh your browser, you should see the new icon, shown below, in your browser's tab:

site/src/assets/img/main/favicon.png

The next icon is used for the the small image located on the top right of each post. The source image is found in the site/src/assets/img/main/icon.png file. This is also a 75 x 75 PNG image, so I simply removed the original, copied my favicon to the folder and renamed it to avatar.png. You should now see the icon below appear on each post (remember to clear the cache first):

site/src/assets/img/main/avatar.png

The final image is the larger avatar found in the left sidebar just above the blog's title. This was originally a 128 x 128 PNG image. I took the same icon, scaled it from 512 x 512 pixels down to a 128 x 128 pixel image and replaced the original file named icon.png. If you refresh your browser, you should see the new icon, shown below, in your browser's tab (remember to clear the cache first):

site/src/assets/img/main/icon.png

Our next stop is the JBake configuration file:

site/src/jbake.properties

The template uses many of the Jbake default properties found in this file. This configuration file also includes many other properties used by the FreeMarker templates. Changes to the Jbake configuration (properties) file are not detected by the JBake serve command. Whenever you make changes to this file you have to restart the jbakeServe command. Use the Ctrl-C key combination to stop the HTTP server and repeat the JBake serve command.

We will start off by changing the sites URL, title, author name and avatar icon as shown below:

site.host=https://hmf.github.io/tech4rd/
site.title=Tech 4 R&D

site.author=Hugo Ferreira
site.author.avatar=img/main/avatar.png

Note that:

  • site.host is automatically replaced when local serving is used with the http://localhost:8820/ URL.
  • site.title is the title that appears on the top left corner (left of the menu option).

We don't change any of the menu's or their respective Fontawesome icons. For example, the user icon is used in the "about" menu option.

The side bar icon (icon.png) referred to above, can be placed either in a circle or a hexagonal shape. One can set the JBake properties to use one of these shapes or none, by changing several variables. These variables, shown in the section below, are used in the sidebar.ftl template. Here are the values I set:

sidebar.intro.pic.src = img/main/icon.png
# modify your picture in the shape of a circle or
# future imperfect's hexagonal shape
sidebar.intro.pic.circle    = false
sidebar.intro.pic.imperfect = false
sidebar.intro.pic.width     = 75px
sidebar.intro.pic.alt       = Tech 4 R&D

If we opt to use either one of these shapes, then the icon is set as a link to the site's root. If not, then only the image is used. The sidebar.ftl uses, for example, the intro-circle property that is defined in the add-on.css CSS file. This property sets the border-radius. Note that besides the circle's background colour, an SVG image of a hexagon (future imperfect's hexagonal shape) is also set behind the icon. When setting the colours we can also set both these background colours. Below, I provide additional information on this. However, for a simple dark mode colour scheme, I have opted to use no background shapes.

I did notice the following though:

  1. The picture width has no effect if either or both pic.circle and pic.imperfect are set to true (see #intro .logo img in main.css);
  2. If you set pic.circle to true then pic.imperfect will automatically be activated, even if you set it to false;
  3. Because you cannot change the icon size, if the image covers most of the square, then the background can make the image less aesthetically pleasing.

The icon's size is set in the main.css file via the following CSS snippet:

		#intro .logo img {
			display: block;
			margin-left: -0.25em;
			width: 4.5em;
		}

One could of course tweak the size by using a variable for width, but at this point I left it as it was.

Below the icon we have a title, subtitle and "about" blurb that can also be set in the configuration file. Here are the values I have set:

sidebar.intro.header =Tech 4 R&D
sidebar.intro.summary =A technical blog on all things software and hardware.
sidebar.intro.about =A blog with technical articles on programming\\, software engineering\\, artificial intelligence and electronics.
sidebar.intro.about.learnMore=about.html

Note that we need to escape the commas with the double backslash (\\,), otherwise the parsing fails.

The next step is to fill in your "about" page, which is a raw HTML file. As per the JBake documentation, all raw HTML or Markdown files must have a metadata header that looks something like this:

title=Weekly Links #2
date=2013-02-01
type=post
tags=weekly links, java
status=published

Note that we must have at least the status and type fields in the header. If you have defined a default status value in your configuration file, then you need not add it to the header. Here is an example of the header I used:

title=About
date=2022-05-25
type=page
status=published

A small note on the content of the "about" file. Because it is raw HTML (we could have used another format such as Markdown or AsciiDoc by simply changing the sidebar.intro.about.learnMore configuration value), the contents may be any HTML snippet you want. The original was just text. I opted to write several paragraphs and so used the following HTML snippet to correctly justify the paragraphs:

<p style="text-align: justify;">
  Example text in a formatted paragraph.
</p>

Social media

The port of the Future Imperfect's HTML5 UP template also provides links with icons to several well-known social media outlets that can be set via the configuration file. Here are the values I used in my case:

sidebar.social.github          = hmf
sidebar.social.medium          = hugo6ferreira
sidebar.social.linkedin        = hugo-ferreira-289a4251
sidebar.social.stackoverflow   = 2051561
sidebar.social.reddit          =
sidebar.social.twitter         = 

Note that we should only provide the user's identifier. The correct URL will be used by the template.

Color scheme

Setting up your colour scheme or pallet for your site is important for branding. It sets the look and feel of the website. If you look through the CSS files in the assets folder, you will see many colours defined by value. This makes adapting the current template difficult and time consuming. So the first step was to define a set of colours in a separate CSS file named theme.css with all the existing colours found in the other CSS files. These were defined using CSS variables. I then replaced each and every colour in the main.css and add-on.css files, with the corresponding variables of the theme.css file. Here is an example of the variables that I used:

:root {
    --red         : #ff0000;
    --persian-red : #d1342fff;
    --green       : #00ff00;
    --greenyellow : #ADFF2F;


    /* text in posts, about, arquive list                */
    --foreground           : var(--blue_primary_text);
    /* back to top button on left bottom (#back-to-top), side bar footer (#footer .copyright), next and previous buttons */
    --mid_ground           : var(--blue_secondary_text);
    /* Top menu + content area - facebook background     */
    --background           : var(--blue_background);    
    /* Side bar with icon, title, social media links, recent posts, tags, about */
    --background_body      : var(--blue_card);          
    /* ?                                                 */
    --dark_gray            : var(--blue_hover);         
    /* bold, strong for titles and subtitles in side bar */
    --dark_gray_2          : var(--blue_secondary_text);
    /* borders and separators                            */
    --dark_gray_alpha      : var(--blue_hover);          
    /* background of search boxes                        */
    --dark_gray_alpha_2    : var(--blue_hover);          
    /* share icon buttons - .share-btn:active            */
    --active_button        : var(--blue_hover);          
    /* border below share buttons - box-shadow           */
    --share_btn            : var(--blue_hover);          
    /* links and author name                             */
    --content_border       : var(--mask_b_red_1);        
    /* selection background of button pressed            */
    --hover_alpha          : var(--blue_hover);          
    /* button and link hover, text area borders          */
    --hover                : var(--mask_b_red_2);        

    /* share links */
    --btn_twitter          : #55acee;
    --btn_google-plus      : #dd4b39;
    --btn_facebook         : #3B5998;
    --btn_linkedin         : #4875B4;
    --btn_stumbleupon      : #EB4823;
    --btn_reddit           : #ff5700;
    --btn_email            : #444444;
    --btn_twitter_hover    : #4c9ad6; 
    --btn_google-plus_hover: #c64333;
    --btn_facebook_hover   : #2f4779;
    --btn_linkedin_hover   : #4069a2;
    --btn_stumbleupon_hover: #d3401f;
    --btn_reddit_hover     : #e54e00;
    --btn_email_hover      : #363636;
    
    /* Icon mask + Dark reader colours */
    --mask_red_0           : #e494fc;
    --mask_red_1           : #cf3eff;
    --mask_red_2           : #9e41ef;
    --mask_red_3           : #7d4dfb;
    --mask_red_4           : #525afb;
    --mask_red_5           : #2e72ff;
    --mask_red_6           : #3262f6;
    --mask_red_7           : #23438f;
    --light_background     : #1e2122;
    --dark_background_0    : #181a1b;
    --dark_background_1    : #191b1c;
    --light_foreground_0   : #e8e6e3;
    --light_foreground_1   : #cdcbc9;
    --dark_foreground      : #979796;

    /* Blue pallet */
    /* 5 */
    --blue_background         : #bfd9ec; 
    --blue_card               : #8fb9d7; 
    --blue_hover              : #a3bacb; 
    --blue_primary_text       : #153f6f; 
    --blue_secondary_text     : #0c4480; 
    /* +2 */
    --mask_b_red_0           : #c3848f;
    --mask_b_red_1           : #b46572;
    --mask_b_red_2           : #a84858;
}

Note that some of the variables are defined indirectly via another set of variables. More concretely one can observe, for example, that the --foreground variable that is used in the main.css file, is set to the value of the variable --blue_primary_text. This has allowed me use simple colour schemes of 5 + 2 colours defined in the theme.css and map only those to a larger set of colours that were used in the original template. To set up your own pallet, just define and map those variables to the colour combination of your choice. Note that the "+2" colours set the borders that signal selected components via clicks (--content_border) or hovering (--hover). I kept the original colours of the "share link" icons.

One functionality I added to the template, was the automated selection of a "dark mode" colour scheme based on the user's preference. A problem with this, is that the colours visibility and user's perception of these colours will change based on the contrast provided by the selected colour schemes. Even though it is advisable to change several other properties to solve possible issues with the "dark mode" colour pallets, I only encountered problems with the underlining of the links and author name. To make these more visible I simply increase the line's thickness from 1px to 2px in the main.css file. Here are the changes I made:

		.author .name {
			-moz-transition: border-bottom-color 0.2s ease;
			-webkit-transition: border-bottom-color 0.2s ease;
			-ms-transition: border-bottom-color 0.2s ease;
			transition: border-bottom-color 0.2s ease;
			border-bottom: dotted 2px var(--content_border);
			display: block;
			margin: 0 1.5em 0 0;
		}


	a {
		-moz-transition: color 0.2s ease, border-bottom-color 0.2s ease;
		-webkit-transition: color 0.2s ease, border-bottom-color 0.2s ease;
		-ms-transition: color 0.2s ease, border-bottom-color 0.2s ease;
		transition: color 0.2s ease, border-bottom-color 0.2s ease;
		border-bottom: dotted 2px var(--content_border);
		color: inherit;
		text-decoration: none;
	}

Recall that we can set the side bar icon (icon.png) via the Jbake properties file. This allows us to place either a circle or a hexagonal shape under the icon. The colours of the shape are defined in two places. The first is set by the .intro-circle class that is configured in the add-on.css as follows:

#intro .intro-circle {
    border-radius: 50%;
}

#intro .intro-circle {
    border-radius: 50%;
    background-color: var(--intro_logo_color);
}

In this case I created and used the --intro_logo_color variable. The second is set by the .logo:before class defined in the main.css file as follows:

		#intro .logo:before {
			background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100px' height='100px' viewBox='0 0 100 100' preserveAspectRatio='none' zoomAndPan='disable'%3E%3Cpolygon points='0,0 100,0 100,25 50,0 0,25' style='fill:%23f4f4f4' /%3E%3Cpolygon points='0,100 100,100 100,75 50,100 0,75' style='fill:%23f4f4f4' /%3E%3C/svg%3E");
			background-position: top left;
			background-repeat: no-repeat;
			background-size: 100% 100%;
			content: '';
			display: block;
			height: 100%;
			left: 0;
			position: absolute;
			top: 0;
			width: 100%;
		}

Note that the hexagon's background-image colour is hardcoded to #f4f4f4. To be able to change the colour dynamically, I defined the --intro_logo_shape variable in the theme.css file as follows:

  :root{

      --intro_logo_shape: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='100px' height='100px' viewBox='0 0 100 100' preserveAspectRatio='none' zoomAndPan='disable'%3E%3Cpolygon points='0,0 100,0 100,25 50,0 0,25' style='fill:%230000ff' /%3E%3Cpolygon points='0,100 100,100 100,75 50,100 0,75' style='fill:%230000ff' /%3E%3C/svg%3E");

  }

and then used that variable in the in the main.css file as follows:

		#intro .logo:before {
			background-image: var(--intro_logo_shape);
			background-position: top left;
			background-repeat: no-repeat;
			background-size: 100% 100%;
			content: '';
			display: block;
			height: 100%;
			left: 0;
			position: absolute;
			top: 0;
			width: 100%;
		}

So now, by setting the appropriate colours in the theme.css file, we can have the desired background behind the logo. I finally decided not to do it because: a) I could not find a good combination of colours for the pallets I selected and b) the hexagon shape is too "tight" and the result is less aesthetically appealing (maybe I could increase the background-size to solve this). However, you are free to change the template as you wish.

Dark Mode

Dark mode allows one to select a colour pallet that uses a dark background and light foreground. A user may opt for the dark mode in the following 3 ways:

  1. Select the dark mode in operating system;
  2. Select the dark mode in the web browser;
  3. Select the dark mode in the web application;

The web browser (also referred to as an agent) can detect whether the dark mode has been selected in the operating system or in the web browser. This option can be detected via the CSS media feature prefers-color-scheme. With this feature we can automatically activate the dark mode according to the user's choice. So how does this work? This solution is based on some other examples I came across in the Internet (example1, example2. See Jemima Abu's good article for more details.

Let's work work through a simple example. Assume we have the following HTML snippet that we want to show via light mode or dark mode:

<button class="btn-toggle">Toggle Dark-Mode</button>
<h1>Hey there! This is just a title</h2>
  <p>I am just a boring text, existing here solely for the purpose of this demo</p>
  <p>And I am just another one like the one above me, because two is better than having only one</p>

To be able to select the dark mode we need only use the following CSS file:

:root {
  --text-color: #222;
  --bkg-color: #fff;
}


@media (prefers-color-scheme: dark) {
  :root {
    --text-color: #eee;
    --bkg-color: #121212;
  }
}

* {
  font-family: Arial, Helvetica, sans-serif;
}

body {
  background: var(--bkg-color);
}

h1,
p {
  color: var(--text-color);
}

The :root is a pseudo-class that matches the root element of the document. This means that :root represents the <html> elements with a html selector. Because it has the highest specificity, all elements within the <html> element will be selected unless overridden by a more specific selector. This selector is also visible to all other selectors in the CSS files. So in the case above, the variables --text-color and --bkg-color will be assigned to the body, h1 and p selectors. As a result, all of the matched elements in the <html> element and its descendants, will be selected, and hence the colours defined by these variables will be used by default.

If a user selects the dark mode, we need only redefine the :root pseudo-class with new content. The @media (prefers-color-scheme: dark) simply checks if the dark mode has been selected and redefines the colour variables. The selectors thereafter will automatically use the new dark mode colour scheme. So the HTML file is always rendered with the :root variables, but the values of those variables will changed according to the user's preference in the operating system or web browser.

In the theme.css file I initially used this strategy. However, I later added the possibility for the user to interactively set this option in the web application - the static website. This required some minor changes and is explained in the next section. Here is an example of overriding the colour scheme to use dark mode when selected by the OS or web browser:

/* Dark mode */
@media (prefers-color-scheme: dark) {
  :root {

    /* text in posts, about, arquive list                */
    --foreground           : var(--facebook_primary_text);   
    /* back to top button on left bottom (#back-to-top), side bar footer (#footer .copyright), next and previous buttons */
    --mid_ground           : var(--facebook_secondary_text); 
    /* Top menu + content area - facebook background     */
    --background           : var(--facebook_background);    
     /* Side bar with icon, title, social media links, recent posts, tags, about */ 
    --background_body      : var(--facebook_card);          
    /* ?                                                 */
    --dark_gray            : var(--facebook_hover);          
    /* bold, strong for titles and subtitles in side bar */
    --dark_gray_2          : var(--facebook_secondary_text); 
    /* borders and separators                            */
    --dark_gray_alpha      : var(--facebook_hover);          
    /* background of search boxes                        */
    --dark_gray_alpha_2    : var(--facebook_hover);          
    /* share icon buttons - .share-btn:active            */
    --active_button        : var(--facebook_hover);          
    /* border below share buttons - box-shadow           */
    --share_btn            : var(--facebook_hover);          
    /* links and author name                             */
    --content_border       : var(--mask_red_1);              
    /* selection background of button pressed            */
    --hover_alpha          : var(--facebook_hover);          
    /* button and link hover                             */
    --hover                : var(--mask_red_0);              
  }
}

Adding a Dark Mode application selector

Some users are not aware of the dark mode option, don't want to have the trouble to change it, or simply want to use the dark mode at the application level. To change this at the application level, I added a checkbox on the top menu and a script, which checks for events on this checkbox and changes the colour pallet accordingly. All of the JS scripts used by the website are added by the footer.ftl template file. The idea is to load the script when the rest of the page content has loaded. The user will have the perception that the page has finished loading, when indeed it is still loading the scripts. Here is the line I added:

		<script src="<#if (content.rootpath)??>${content.rootpath}<#else></#if>js/darkmode.js"></script>

Then I added the following code to the darkmode.js script:

$(function(){
	const btn = document.getElementById("mode_checkbox");
	const btn = document.getElementById("mode_checkbox");
	const prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)");
	
	const currentTheme = localStorage.getItem("theme");
	if (currentTheme == "dark") {
		document.documentElement.className = "dark-theme";
	} else if (currentTheme == "light") {
		document.documentElement.className = "light-theme";
	}
	else {
		// default
		if (prefersDarkScheme.matches)
			document.documentElement.className = "dark-theme";  
		else
		  document.documentElement.className = "light-theme";  
	}
	
	btn.addEventListener("click", function () {
		// Toggle
		var theme = (document.documentElement.className == "light-theme")
				? "dark"
				: "light";
		// Set new class
		if (theme == "light")
			document.documentElement.className = "light-theme";
		else
			document.documentElement.className = "dark-theme";

	  // Store mode
		localStorage.setItem("theme", theme);
	});
		
});

The next step was to add the checkbox to the static website. The menu.ftl template sets up the top menu bar of the website. I changed one of the nav elements to include this checkbox. Here is a snippet of the code I changed (id=="mode_checkbox"):

    <nav class="main">
        <ul>
            <li>
                <div>
                    <input type="checkbox" class="checkbox" id="mode_checkbox">
                    <label for="mode_checkbox" class="label">
                        <div class='ball'>
                    </label>
                </div>
            </li>
            <#if content.shareNav??>
            <li id="share-nav" class="share-menu" style="display:none;">
                <a class="fa-share-alt" href="#share-menu">Share</a>
            </li>
            </#if>
            <li class="search">
                <a class="fa-search" href="#search">Search</a>
                <form id="search" method="get" action="//google.com/search">
                    <input type="text" name="q" placeholder="Search" />
                    <input type="hidden" name="q" value="site:${config.site_host}">
                </form>
            </li>
            <li class="menu">
                <a class="fa-bars" href="#menu">Menu</a>
            </li>
        </ul>
    </nav>

As a side note, I was unable to use the checkbox with the light/dark icons due to spacing and formatting issues. For the same reasons I was also unable to add a label. In addition to this, the checkbox was not centred correctly. I ended up leaving the default tick icon and changed the main.css file slightly by removing the margin-right property of the input[type="checkbox"] selector. Here is a snippet with the changes made:

	input[type="checkbox"],
	input[type="radio"] {
		-moz-appearance: none;
		-webkit-appearance: none;
		-ms-appearance: none;
		appearance: none;
		display: block;
		float: left;
		/*margin-right: -2em;*/
		opacity: 0;
		width: 1em;
		z-index: -1;
	}

As a result here is a preliminary version of the website with the original example content and the light color scheme that I selected. Note how the checkbox on the menu's right is selected:

light mode

This is the same website using the alternate dark mode colour pallet I selected. Note how the checkbox on the menu's right is not selected:

dark mode

We will look at the details of how the script works by extending the simple example we used above. To recap, we want to change the colour scheme of the following HTML document:

<button class="btn-toggle">Toggle Dark-Mode</button>
<h1>Hey there! This is just a title</h2>
  <p>I am just a boring text, existing here solely for the purpose of this demo</p>
  <p>And I am just another one like the one above me, because two is better than having only one</p>

We now change the previous CSS file by adding the following two new :root pseudo-classes:

  • :root.light-theme
  • :root.dark-theme

and have:

:root,
:root.light-theme {
  --text-color: #222;
  --bkg-color: #fff;
}


@media (prefers-color-scheme: dark) {
  :root.dark-theme {
    --text-color: #eee;
    --bkg-color: #121212;
  }
}

* {
  font-family: Arial, Helvetica, sans-serif;
}

body {
  background: var(--bkg-color);
}

h1,
p {
  color: var(--text-color);
}

Note that we still keep the default :root so that even with no root class name, we still have a colour scheme.

Essentially, I have added a "selector" to the pseudo-class. Note that the :root selects the <html> root and all its descendants. The :root.filter simply adds filter as the class of the root <html> element. The code below allows the user to toggle between light and dark mode. Lets look at how it works.


const btn = document.querySelector(".btn-toggle");
	const btn = document.getElementById("mode_checkbox");
	const prefersDarkScheme = window.matchMedia("(prefers-color-scheme: dark)");
	
	const currentTheme = localStorage.getItem("theme");
	if (currentTheme == "dark") {
		document.documentElement.className = "dark-theme";
	} else if (currentTheme == "light") {
		document.documentElement.className = "light-theme";
	}
	else {
		// default
		if (prefersDarkScheme.matches)
			document.documentElement.className = "dark-theme";  
		else
		  document.documentElement.className = "light-theme";  
	}

btn.addEventListener("click", function () {
  // Toggle
  var theme = (document.documentElement.className == "light-theme")
      ? "dark"
      : "light";
  // Set new class
  if (theme == "light")
    document.documentElement.className = "light-theme";
  else
    document.documentElement.className = "dark-theme";

  // Store mode
  localStorage.setItem("theme", theme);
});

I have assigned a higher priority to the user's colour mode selection for the web application than that of the OS and browser options. The script first obtains the preferred colour scheme from the OS or browser. It then checks if the user had previously set a colour scheme option using the checkbox. If this was so, the <html> class name is either set to "dark-theme" or "light-theme" depending on the saved option. If no option was saved, it either sets the <html> class name to "dark-theme" (if this was set at the OS or browser level) or "light-theme" (if otherwise).

The second part of the code sets up an event listener for the checkbox. It simply toggles the <html> class name to the opposite colour scheme and saves the new option. If you run the example above and toggle back and forth, you will see how the document root's class name changed. Here is an example of the light mode selected:

<html class="dark-theme" lang="en"><head>

  <meta charset="UTF-8">
  <title>CodePen Demo</title>

  <meta name="robots" content="noindex">
  
  <body>
    <button class="btn-toggle">Toggle Dark-Mode</button>
  <h1>Hey there! This is just a title</h1>
    <p>I am just a boring text, existing here solely for the purpose of this demo</p>
    <p>And I am just another one like the one above me, because two is better than having only one</p>

  </body>
</html>

Here is an example of the dark mode selected:

<html class="light-theme" lang="en"><head>

  <meta charset="UTF-8">
  <title>CodePen Demo</title>

  <meta name="robots" content="noindex">

  <body>
    <button class="btn-toggle">Toggle Dark-Mode</button>
  <h1>Hey there! This is just a title</h1>
    <p>I am just a boring text, existing here solely for the purpose of this demo</p>
    <p>And I am just another one like the one above me, because two is better than having only one</p>


  </body>
</html>

Conclusion

This concludes our second part of the series. With this information you can quickly brand the JBake Future Imperfect template to suite your needs. This includes setting up social medial links, logo and colour scheme. Of course, you are free to change the templates further for more customization. In the next part of this series, we will look at how to create and publish posts and pages using the JBake Future Imperfect template, the Mill JBake plugin in Github and additional Mill targets I have prepared.

References

  1. Markdown
  2. AsciiDoc site
  3. AsciiDoc Wikipedia
  4. ReStructuredText
  5. Freemarker
  6. ThymeLeaf
  7. Google Analytics
  8. MathJax
  9. MathJS
  10. KaTex
  11. Mermaid-Js
  12. Flowchart.js
  13. ChartJS
  14. Plotly
  15. Chartist-Js
  16. RawGraphs.io
  17. d3js
  18. c3js
  19. nvd3
  20. Mill Github
  21. Mill Github pages
  22. FeatherIcons
  23. IonIcons
  24. FontAwesome
  25. Tabler
  26. css.gg
  27. Docusaurus.io
  28. Docusaurus Github
  29. Jekyll
  30. Jekyll Github
  31. JBake github
  32. JBake
  33. Laika
  34. Laika Github
  35. Scala-lang docs
  36. Scala3 ScalaDoc
  37. Scala3 ScalaDoc Guide
  38. Scala3 ScalaDoc Usage
  39. Scala3/Dotty - ScalaDocs github
  40. Javadoc
  41. Sbt-site Github
  42. Sbt-site
  43. sphinx-doc
  44. Pamflet
  45. Pamflet Github
  46. Nanoc
  47. GitBook
  48. Paradox
  49. Github Paradox
  50. GoHugo
  51. AsciiDoctor
  52. Mill Build tool
  53. Mill Docusaurus plugin
  54. Mill JBake plugin
  55. Mill MDoc plugin
  56. MDoc
  57. MDoc in Github
  58. Mill JBake plugin in Github
  59. Github pages
  60. Gitlab pages
  61. Github Flavoured Markdown
  62. Gitlab Flavoured Markdown
  63. John Gruber’s canonical description of Markdown’s syntax
  64. Commonmark variant
  65. Markdown Guide
  66. Markdown guide extended-syntax
  67. R Markdown variant
  68. JBake documentation
  69. Ammonite
  70. Coursier
  71. JBake Author template
  72. JBake Future Imperfect template