Hero Image


A lightweight, website theme template


A lightweight, website theme template
Scene Mode

Welcome to Custom-Deck!

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.

  1. The ability to dismiss the dropdown navigation menu on touch screens.
  2. An optional light and dark mode button for those who love dark mode.
  3. A basic podcast player that keeps track of where you are in each episode.

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.

Using Custom-Deck

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.

Deck of Cards

Customization and Implementation

Site Details

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:

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.

Implementation Details

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.

Local Storage

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.

Network Usage

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.

Possible Issues

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:

Media Fragments w/ query:

Quirks Mode (YouTube-style) w/ hash or query:


Example Markdown

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, Lists, and Quotes

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.

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

  1. <— This number is 9812.
  2. <— This number is 4.
  3. <— And this number is 0.

This is a nested, ordered list

  1. Item 1
    1. Subitem 1.1
      1. Subsubitem 1.1.1

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.

  1. Another List
  2. 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>.

Heading 1

Heading 2

Heading 3

Heading 4

Heading 5
Heading 6

Non-standard Customization

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": Embed image

Non-standard Customizations

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):

Embeded image with width defined by class="onethird"

Embeded image with width defined by class="onethird"

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.