My Blog Theme

I use a custom Sphinx theme for this site.

Generally, I try and implement as much as I can using just HTML+CSS however, there are a few occasions where some JavaScript gets involved.

CSS Variables

Below are the CSS Variables used throughout the site’s theme.

_static/css/styles.css
:root {
    --bg-main: white;
    --fg-main: black;

    --bg-sidebar: #161b22;
    --fg-sidebar: white;
    --fg-sidebar-dim: #aaa;

    --highlight-color: #f0b100;
    --border-color: #657b83;

    --fg-accent-dim: #064e3b;
    --fg-accent: #059669;
    --fg-accent-bright: #a7f3d0;

    --transition: 300ms;

    --sidebar-min-width: 0.5em;
    --sidebar-max-width: 300px;

    --panel-min-height: 0.5em;
    --panel-max-height: 400px;

    /* Closed by default */
    --left-sidebar-width: var(--sidebar-min-width);
    --right-sidebar-width: var(--sidebar-min-width);
    --panel-height: var(--sidebar-min-width);
}

Which makes implementing a dark theme straightforward

_static/css/styles.css
@media(prefers-color-scheme: dark) {
  :root {
      --bg-main: #0d1117;
      --fg-main: white;

      --border-color: #839496;

      --fg-accent-dim: #064e3b;
      --fg-accent: #059669;
      --fg-accent-bright: #a7f3d0;
  }
}

As well as respecting the prefers-reduced-motion preference

_static/css/styles.css
@media screen and (prefers-reduced-motion: reduce) {
  :root {
    --transition: 0.001ms;
  }
}

A CSS “Reset”

You might have heard of a CSS reset, while I haven’t yet felt the need for a full blown reset stylesheet I do include the following

_static/css/styles.css
* {
  margin: 0;
  box-sizing: border-box;
}

Utility Classes

.guilabel

Used by the :guilabel: role.

_static/css/styles.css
.guilabel {
  color: var(--fg-accent);
  font-style: italic;
  font-weight: bold;
}

.hidden

Easy way to hide elements.

_static/css/styles.css
.hidden { display: none; }

.highlighted

Used by the search system to highlight matched terms.

_static/css/styles.css
.highlighted {
  background: var(--highlight-color);
  color: var(--bg-main);
  padding: 0 0.5em;
  border: solid 1px var(--fg-main);
  border-radius: 3px;
}

.line-through

On some pages I define an ad-hoc role to strikethrough some text.

.. role:: strike
   :class: line-through

Which requires the CSS for the given class name to be defined.

_static/css/styles.css
.line-through { text-decoration: line-through; }

HTML Elements

Trying to work with the CSS cascade rather than against it, it’s good to try and define global rules that apply to all HTML elements

Adding styles to the html element applies them to everything on the page. I also think that by setting the font size here, I can use rem units to set all font sizes relative to this base size.

_static/css/styles.css
html {
  font-size: 13pt;
  font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
  scroll-behavior: smooth;
}

a

It’s nice if links follow the accent color

_static/css/styles.css
a { color: var(--fg-accent);}

Tags

When linking to tags I want the link to be styled like a “pill”.

_static/css/styles.css
a.tag {
  color: var(--fg-accent-bright);
  background: var(--fg-accent-dim);
  border: solid 1px var(--fg-accent);
  border-radius: 3px;
  padding: 0.1em 0.5em;

  span.count {
    color: var(--fg-main);
  }
}

main a.tag {
  color: var(--bg-main);
  background: var(--fg-accent);
  border-color: var(--fg-accent-dim);
}

Lists of tags should be flattened

_static/css/styles.css
ul.taglist {
  display: flex;
  gap: 1em;
  flex-wrap: wrap;

  li {
    list-style: none;
    margin: 0;
  }
}

blockquote

As generated by the .. pull-quote:: directive.

The technology you use impresses no one.

The experience you create with it is everything. – `Sean Gerety`_

_static/css/styles.css
blockquote.pull-quote {
    padding: 0 1em;
    font-style: italic;
    border-left: solid var(--fg-accent);
}

details

Expand...

For additional information.

_static/css/styles.css
details > :not(summary) {
  border-left: solid 1px var(--border-color);
  padding: 0 0 0 1em;
  margin: 0 0.2em;
}

dd & dt

Used by Sphinx/docutils to represent definition lists

dt

Represents the term to be defined.

dd

Contains the definition.

_static/css/styles.css
dt { margin-top: 1em; }

dd {
  margin-left: 1em;
  padding-left: 1em;
  border-left: solid 1px var(--fg-accent);

  p:first-child {
    margin-top: 0;
  }
}

figure

_static/css/styles.css
figure {
  &.align-center img {
    display: block;
    margin: auto;
  }

  figcaption p {
    margin: auto;
    font-size: 0.8rem;
    text-align: center;
  }
}

h1-h6

_static/css/styles.css
h1, h2, h3, h4, h5, h6 {
  font-weight: 500;
}

h1 { font-size:   2rem; }
h2 { font-size: 1.8rem; }
h3 { font-size: 1.7rem; }
h4 { font-size: 1.5rem; }
h5 { font-size: 1.4rem; }
h6 { font-size: 1.2rem; }

img

_static/css/styles.css
img { max-width: 100%; }

kbd

e.g. C-x C-f

_static/css/styles.css
kbd {
  padding: 0 0.15em;
  color: var(--fg-accent);
}

table

A table is composed of many elements.

Element

Description

table

Top-level table element

thead

Contains the table’s header rows

tbody

Contains the table’s rows

tr

Defines a row

th

Defines a header cell

td

Defines a normal cell

_static/css/styles.css
table {
  width: 100%;
  border-collapse: collapse;

  thead {
    tr {
      border-bottom: solid 1px var(--border-color);
    }
  }

  td, th {
    padding: 0 0.5em;
    border-right: 1px solid var(--border-color);
  }

  td:last-child, th:last-child {
    border-right: none;
  }
}

Layout

The layout for this site is divided into the following sections

Header Left Main Right Footer

I want a somewhat dynamic layout for this site, meaning that the sidebars and the footer should be open/closable by the user.

Amazingly, it turns out that the grid-template-columns and grid-template-rows CSS properties can be animated! So as long as you set the transition property and use a few CSS variables

_static/css/styles.css
body { overflow: hidden; }

.grid {
  color: var(--fg-main);
  background: var(--bg-main);

  display: grid;
  grid-template-columns: var(--left-sidebar-width) 1fr var(--right-sidebar-width);
  grid-template-rows: 3em calc(100vh - 3em - var(--panel-height)) var(--panel-height);
  transition: var(--transition);
}

Then the standard checkbox trick can be used to open/close the various elements! For example, assuming that when checked the sidebar should close, the CSS for the left sidebar might look something like this.

#left-sidebar-checkbox {
  &:checked ~ .grid {
    --left-sidebar-width: var(--sidebar-min-width);

    .sidebar {
      opacity: 0;
    }
  }
}

Which, assumes the following HTML structure.

<body>
  <input type="checkbox" id="left-sidebar-checkbox" class="hidden" />
  <div class="grid">
     ...
     <aside>
       <section class="left-sidebar-toggle">
         <label for="left-sidebar-checkbox"></label>
       </section>
       <div class="sidebar">...</div>

However, I think the only way to make this work across screen sizes, is to change the meaning of the checkbox state depending on the screen size. This is bound to lead to some quirks if you resize the screen across a breakpoint, but hopefully, it’s enough of an edge case to not have to worry too much about it! 😅

Main

_static/css/styles.css
body { background: var(--bg-sidebar); }

main {
  min-width: 0;
  max-height: 100%;
  overflow-y: auto;
}

article

I use article elements to contain the main content of the page.

_static/css/styles.css
article {
  padding: 0 2em;
  line-height: 1.5;
  max-width: 100ch;

  footer {
    border-top: solid 1px var(--border-color);
  }
}

On mobile the padding should be reduced a bit to create more space

_static/css/styles.css
@media screen and (max-width: 600px) {
  article { padding: 0 0.5em; }
}

Admonitions

_static/css/styles.css
div.admonition {
  margin: 1em 0;
  border: solid 1px var(--border-color);
  border-radius: 3px;

  & > :not(ol,ul) {
    padding: 0.5em;
    margin: 0;
  }

  .admonition-title {
    margin: -1px -1px 0 -1px;
    color: var(--fg-accent);
    border: solid 2px var(--fg-accent);
    border-left: solid 5px var(--fg-accent);
    border-top-left-radius: 3px;
    border-top-right-radius: 3px;
  }
}

Code

Sphinx/docutils render inline code in <code> tags with the text further wrapped in a <span> tag.

_static/css/styles.css
article {
  code.literal {
    &::before, &&::after {
      content: "`";
    }

    span.pre {
      font-weight: 600;
      font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
    }
  }
}

While code blocks are in a <div class="highlight"> tag.

_static/css/styles.css
div.highlight {
  padding: 0.5em;
  margin: 0.5em 0;
  overflow-x: auto;
  border: solid 1px var(--border-color);
  border-radius: 5px;
}

I use awdur throughout this site to take the contents of code blocks and export them to separate code files (for example all the CSS on this page!). Awdur provides a code block header, indicating where the code will be exported to. This header also needs to be styled to match the look and feel of this site.

_static/css/styles.css
div.awdur-codeblock {
  border: none;

  div.awdur-codeblock-header {
    color: var(--fg-sidebar);
    background: var(--bg-sidebar);
    padding: 0 0.5em;

    border: solid 1px var(--border-color);
    border-top-left-radius: 5px;
    border-top-right-radius: 5px;
    border-bottom: none;
  }

  div.highlight {
    border-top: none;
    border-top-left-radius: 0;
    border-top-right-radius: 0;
    margin-top: 0;
  }
}

Typography

_static/css/styles.css
article {
  section {margin-top: 2em;}

  h1, h2, h3, h4, h5, h6 { color: var(--fg-accent); }

  p { margin: 1em 0; }

  table {
    line-height: unset;
    p { margin: 0;}
  }
}

Post Collections

I quite liked the idea of placing all of my blog posts on a timeline, but it does involve a fair amount of CSS. I keep telling myself that I’m going to expand the type of “events” in the list… someday! 😅

_static/css/styles.css
article.summary {
  display: grid;
  grid-template-columns: 250px auto;

  & > :not(aside) {
    padding: 0 1em;
  }

  header, footer {
    grid-column: 2;
  }

  header {
    position: relative;

    &::before {
      content: '';
      width: 0.75em;
      height: 0.75em;
      background: var(--border-color);
      border-radius: 100%;
      position: absolute;
      top: 1em;
      left: -0.4em;
    }
  }

  aside {
    grid-row: 1 / span 3;
    border-right: solid 1px var(--border-color);
    padding: 0 1em;

    section.post-metadata {
      margin-top: 0.4em;
      align-items: flex-start;

      h5 { display: none; }

      ul.taglist { gap: 0.5em; }
    }
  }

  footer {
    border-top: none;
    text-align: right;
    margin-bottom: 4em;
  }
}

Of course on smaller displays, things need to be rearranged a bit.

_static/css/styles.css
@media screen and (max-width: 1200px) {
  article.summary {
    grid-template-columns: auto;

    header, footer { grid-column: unset; }
    header::before { display: none; }

    aside {
      grid-row: unset;
      border: none;
    }

  }
}

It would be interesting to see if I can replace the above with a container query at some point.

Search Results

The search results page requires its own set of rules.

_static/css/styles.css
section#search-results {
  padding: 2em;

  h2 { color: var(--fg-accent); }

  p.search-summary {
    padding: 1em 0;
  }

  ul.search {
    padding: 0;

    li {
      margin: 1em 0;
      padding: 1em;
      list-style: none;
      border: solid 1px var(--fg-accent-dim);
      border-radius: 3px;
    }

    p.context {
      margin: 0.5em 0;
    }
  }
}

Footer

_static/css/styles.css
footer.site {
  color: var(--fg-sidebar);
  background: var(--bg-sidebar);

  grid-column: span 3;
}