This is a public version of the custom theme I built myself for my own website, zyrxvo.github.io. A demo of this theme can be explored at zyrxvo.github.io/custom-deck.
I created it because I wanted to explore how to do front-end web development but I didn't want to load large libraries just to present a static site. Thus, the theme can be used to create very lightweight sites that are almost exclusively HTML
and CSS
. This theme could easily be modified to remove all JavaScript
. However, with JavaScript
it has a few additional features.
The name Custom-Deck
come from the design of the site. Everything is predominantly divided into separate cards to display information. These cards can be arranged however you like them to build a site that works for you.
Much of what I learned in order to build this site I received from W3 Schools, Stack Overflow, MDN and more recently ChatGPT because sometimes I don't even know how to ask the questions I have. However, this theme does include the small and fantastic work of TimeJump.
Almost all of the images on this demo-site were generated using DiffusionBee. If you have any questions or concerns, feel free to reach out to me.
You are free to use and customize this theme provided you follow the licensing agreement and the licensing agreement of TimeJump. See the Customization and Implementation below for more details regarding customization, but the template requires running a Python
script website.py
in order to build and 'dynamically pre-load' the site's content into the site's structure. The script requires a couple libraries (Python-Markdown, BeautifulSoup, and FeedParser) all given in requirements.txt
. It also uses joblib
to process the build in parallel. I suggest making a Python
virtual environment and pip
installing them.
I made this repo and demo site as a working example of how to use the theme. Feel free to reach out to me if you have any questions or issues.
The philosophy behind this theme is to make it as lightweight and fast as possible. To do this there is a focus on using only HTML
and CSS
. JavaScript
is used sparingly to provide functionality for a few key features:
Python
is used to dynamically pre-load content into the index.html
files of your site as HMTL
. You can do this by running python3 website.py build
from the root directory of your site. If you want to remove the content from the HTML
of your site you can do this running python3 website.py clean
.
In the top-level folder there is a config.json
file that provides some customization options for the theme. They don't change very much of the site, but it does allow some of the components of the site to be altered without digging into the HTML
, CSS
, JavaScript
, or Python
.
Changing the main layout and content of the site is done by editing the HTML
files themselves. The basic card
class can be used as full-width, half-width, two-thirds-width, and one-third-width cards with nested combinations, in case you wanted quarter-width, etc.
The content can then be dynamically pre-loaded into the HTML
before publication. Using the following HTML
attributes you can customize how what content is loaded and how it is loaded:
customdeck-include-markdown
customdeck-include-html
customdeck-include-blog
customdeck-include-rss
customdeck-include-rss-episode
You can more quickly create content by writing in markdown and simply inserting the markdown using the markdown rendering engine, see the Example Markdown below.
Some of the other components of the CSS
that can be easily customized are located at the top of the main.css
file. These include font-size and white space adjustments. The site colours for both light and dark schemes are also located at the top of the file. There is only one set of light mode colours, but make sure that when you edit the dark mode colours that you have the same colour defined for both :root.dark
and @media (prefers-color-scheme: dark)
.
The fundamental use of JavaScript for this theme are to be able to dismiss the dropdown menu on mobile, toggle between light and dark themes, and enable podcast playback. Some customizations can be easily made to the JavaScript files located in the js/main.js
and js/podcast.js
. Additionally, for the light and dark schemes, the GitHub logo in the footer is dynamically changed between black and white because a single image doesn't work with both schemes. Any additional images like this can be added in a similar way to the scene
logic. Or the logic can be entirely removed if it is unnecessary. Also see below how TimeJump is used to start playback at a particular time stamp.
This homepage is an example of a normal page in this theme. As mentioned before, the content can either be inserted directly into the HTML
or it can be written in markdown and dynamically pre-loaded. See the markdown example below for the features and quirks of the markdown engine.
Blog posts are written in markdown. The location of the posts and associated images are defined in the config.json
file along with the list of files to load (remember to define the location to the posts folder and list of files relatively with respect to the blog
page). The python script website.py
uses the config information to dynamically build and insert the blog posts into the HTML
of the main blog page. It checks for images with the same file name as the blog post (one image per post). It also checks for a full resolution image to link to. The current format is to label each blog post file as the date of the post (YEAR-MONTH-DAY.md
) to sort the posts reverse chronologically. Multiple posts on the same day can have appended alphabetical indicators, (2023-03-30.md
, 2023-03-30a.md
, etc.). You can easily change the filenames after it's already been published if need be. The images would then be labelled 2023-03-30.jpeg
, 2023-03-30a.jpeg
, etc. If there is not an associated image it will just render the post without it.
Almost all of the podcast customization is contained within the RSS file. If you're unfamiliar with how RSS feeds work, I suggest this guide from Apple as a starting point and use the podcast/rss.rss
file as a template. As far as the RSS feed goes, I believe that an email address is optional now, in case you would like to have less spam in your inbox because you
will
get spam to that email address.
On the podcast
landing page, the badges/logos to different podcast players can be customized to link to your specific show using the config.json
file. If you want to link to a different player or list you should be able to easily customize the HTML
and config file to suit your needs.
MP3 chapters are a common feature of modern podcast players. In order to indicate chapters in your MP3 simply include a file named chapters.md
in the same folder as your podcast episode page. A custom markdown rendering engine takes a list of chapter titles (with timestamps) and dynamically generates a set up buttons that will automatically seek to the timestamp given. This means that you don't even need to embed chapters within your MP3 for this feature to work. You just need to list the times in a chapters.md
file and js/podcast.js
does the rest. If you don't include a chapters.md
file then nothing happens.
Here are some of the details regarding how the site is implemented so that if you go digging around, it might be easier for you to customize it to your liking.
There are two main pieces of data that are stored in localStorage
: the current scene
and the current playback information for podcast episodes. The current scene
flag determines whether the site should be displayed in light
or dark
mode. Saving this setting here allows the preference to persist across browsing sessions.
The podcast episode playback information is also saved so that visitors can resume listening to an episode when the come back (assuming they return using the same browser they originally visited with). Data identifying how much of each episode each visitor has listened to is also saved to localStorage
. This allows for the ability to collect statistics on how much of each episode visitors listen to. However, this data is kept local in each visitor's browser unless it is captured somewhere else. I personally use Matomo to do this and host a Matomo server myself.
Building the site 'dynamically' and pre-loading the content before publishing not makes managing your content easier, but serving everything as HTML
and CSS
with minimal JavaScript
will make your site more accessible and load more quickly (provided everything isn't massive media files).
Almost all of the images are given the loading="lazy"
flag to reduce network usage, especially when loading the blog posts. This flag helps to initially load pages more quickly by waiting until the images come close to or above the 'fold' of the page. The 'hero images' are given the loading="eager"
flag in an attempt to reduce the content from jumping around while fetch requests are being made.
If a visitor loads an episode page using a TimeJump hash, it will overwrite their previously saved progress for the episode. This is because it is equivalent to them manually seeking through the episode themselves. This is intended behaviour but may be surprising, especially if the visitor refreshes the page with an old TimeJump has still in the URL.
This theme uses a slightly customized version of TimeJump which adds deep-linking to HTML5 audio
and video
podcasts. TimeJump works behind-the-scenes to create a standardized API for seeking, based off the YouTube deep-linking syntax.
How it works. It auto-detects the t
(time) parameter in your URL and attempt to fast-forward listeners to that timestamp. It usually works, but doesn't not work on iOS.
Supported URL formats include: Media Fragments w/ hash:
http://mypodca.st/first-episode/#t=1:23:45
http://mypodca.st/first-episode/#t=23:45
http://mypodca.st/first-episode/#t=234
Media Fragments w/ query:
http://mypodca.st/first-episode/?t=1:23:45
http://mypodca.st/first-episode/?t=23:45
http://mypodca.st/first-episode/?t=234
Quirks Mode (YouTube-style) w/ hash or query:
http://mypodca.st/first-episode/?t=1h23m45s
http://mypodca.st/first-episode/#t=1m23s
Markdown is designed to "write using an easy-to-read, easy-to-write plain text format, then convert it to structurally valid XHTML (or HTML)." The Custom-Deck
theme uses a customized version of the Python-Markdown engine by implementing a couple custom features using regular expressions for convenience. The official syntax for the rendering engine is based off of the original markdown syntax. However, I added a couple customizations. As such, it is unlikely to, but it may result in errors or unintended behaviour. HTML
used within the Markdown will remain unchanged and render as HTML
unless it is placed within <code>
blocks using `backticks`
. The most notable exceptions to this are when non-standard syntax is used. The following are some examples of the what is rendered.
Text is just text. Paragraphs and new lines are wrapped in the HTML
paragraph tags <p>
.
Quotes are made if a line begins with >
then it will be wrapped in <blockquote>
tags.
This is a single line/paragraph quote. Additional lines that are not separated by an empty line will appear on the previous line, even if they don't begin with an angle bracket
>
.
But, see how fancy it is with its automatic indentation! The style of the quote can be customized using CSS
.
Lists are rendered if a line begins with a hyphen followed by a space, '-
', then the text on those lines will be converted into an unordered list. Successive lines will be included in the same list. For example,
- This is a list with only one line.
And
Depending on where additional whitespace is used you can change how the list renders.
This is a list
with multiple lines
where each line is wrapped in <p>
tags.
Also, if you have 4 spaces before the list identifier, then you can get nested lists.
This is also true for an ordered list, except you must begin the line with a whole number followed by a period followed by a space, '1.
'. However, it doesn't matter what number you start with because the ordered list will always begin with 1.
. For example,
1. This is a single element in an ordered list.
And
This is a nested, ordered list
Lists should work with block quotes too.
This is a multi-line quote that also contains two different kinds of lists, some emphasis and some
code
. Note again that each new line is kept within the same paragraph tag.Paragraph breaks can be maintained so long as there is a newline gap that begins with a right angle bracket,
>
.My added customization is that the custom list rendering also works inside block quotes.
- See! This is
- a multi-line list!
So long as you don't have two different kinds of lists back to back, you can have ordered or unordered lists.
- Another List
- Within the quote!
Finally this quote is attributed to no one.
Headings from <h1>
to <h6>
are made by using the hashtag #
. Simply use as many as you need to get from <h1>
to <h6>
.
I've added an additional rendering effect where headers are given anchor IDs with the same name, except where spaces are replaced with dashes and commas are removed.
Basic emphasis is available using asterisks or underscores around words or phrases. One is emphasized, two is bold, and
three
is
both
. Basic code
emphasis is made using single, surrounding `backticks`
. Combinations of these emphasizers may not render as expected.
With markdown you can easily make links using closing square brackets []
followed immediately by closing round parentheses ()
. For example, [this is a link](README.md)
and links to an image or file and renders as: this is a link , [another page](blog)
renders as a link to another page on the site, and [an external URL](https://www.example.com)
is an external URL.
The most important consideration with relative links is that the link will be make with respect to where the markdown page will be rendered not where it is saved!
With an exclamation point at the start, you can ![Embed an image](homepage/images/landscape.jpeg "With an optional title")
to embed an image with alt-text "Embed an image":
By adding a vertical bar after the link, but before closing the parentheses, you can ![embed an image with a caption and specify the class]
(homepage/images/landscape.jpeg|onethird)
, to embed an image with a caption and specify the width (by using a CSS
class):
And by using the inverted exclamation mark '¡' (Option+1 on a Mac), this ¡[Button Link] (/podcast/5/#t=0:47)
renders as a button link:
Finally, by using a question mark, it will create an linked information box. Meaning ?[<h5>Information Box</h5><p>This is a custom information box to highlight some key information and provide an easy link to somewhere you would like to direct a visitor to go. It's like an advanced button link. In this case, I'm directing visitors to my website.</p>] (https://zyrxvo.github.io/)
becomes:
Again, the customizations to the Markdown rendering engine uses basic regular expressions. So if something isn't working like you expected it to, then maybe you expected too much of it.