OvdW

β€œLive, Laugh, Love”




^top

How this website works

This page explains how this website was created. Hopefully, it may be a source of inspiration for creating your own website! I make use of Eleventy (11ty)β€”a simple and fast static site generatorβ€”for making this website. You might want to check out the Eleventy Getting Started page to start with your own website. Afterwards, you can check out this document for some inspiration on how to personalize it further.

β›° Overview

The basic premise of Eleventy (and static site generators more generally) is thatβ€”with some configurationβ€”you can generate a website from a collection of text files. These text files are often in markdown, but I personally use Org-mode instead (hence the README.org instead of README.md) :) Using a static site generator makes writing new posts much easier, because you don't have to do that in HTML directly! Moreover, you can even create web pages from a JSON file in Eleventy (I do so for the art page, which is generated from a file called art.json containng a list of images I want to include).

When you initialize your project following the Getting Started I referred to above, you will see a file structure in your project directory similar to this one (though I have adapted this structure to my liking):

β”œβ”€β”€ _includes
    // Contains the layout/template files
β”œβ”€β”€ _11ty
    // Contains some extra functions for my website (e.g., for loading images for my art page)
β”‚   β”œβ”€β”€ shortcodes.js
β”œβ”€β”€ _data
    // Contains json files, one for describing some metadata and another for generating my art page
β”‚   β”œβ”€β”€ art.json
β”‚   β”œβ”€β”€ metadata.json
β”œβ”€β”€ _site
β”‚   // Here you find the exported website you can upload to the web
β”œβ”€β”€ assets
    // Here you can store your assets, like your images and other static files you may want to have on your website
β”œβ”€β”€ posts
    // Contains my blog posts as text files
β”œβ”€β”€ pages
    // Here I put my web pages that are not blog posts
β”œβ”€β”€ README.org
β”œβ”€β”€ index.org
β”œβ”€β”€ marx.min.css
β”œβ”€β”€ extra.css
β”œβ”€β”€ .eleventy.js
└── .eleventyignore

In the following sections, I'll explain:

  1. the main configuration file (.eleventy.js), where you define your plugins, filters, and static files;
  2. the structure of a post and how to define its layout (and as a result, how your website is visually structured); and
  3. the theme of the website using some CSS.

I also provide some tips on some more specific things, like how I added a navigation bar and a gallery to this website. This document ends with a Makefile that also serves as a cheat sheet.

🌽 Configuration: plugins, filters, and moving files

The main configuration file for your Eleventy website is .eleventy.js.

πŸ“ .eleventy.js

Your .eleventy.js is the main file for configuring your website. It basically consists of two parts: in the beginning you define your plugins (like import in Python or \usepackage{} in LaTeX) and later your actual configuration in function(eleventyConfig) {}.

πŸ“¦ Plugins and packages

Here are some packages I found useful (don't forget to install them first!):

// Add RSS support
const pluginRss = require("@11ty/eleventy-plugin-rss");
// Add syntax highlighting in your codeblocks
const syntaxHighlight = require("@11ty/eleventy-plugin-syntaxhighlight");
// Add a navigation plugin (useful for links at the top for navigating your website)
const eleventyNavigationPlugin = require("@11ty/eleventy-navigation");
// For pretty date formatting
const { DateTime } = require("luxon");

βš™ Functions

Now we define the main configuration in the module.exports = function(eleventyConfig) {} block.

We start with adding our 11ty plugins:

module.exports = function(eleventyConfig) {
// Add plugins

    // Add syntax highlighting
    eleventyConfig.addPlugin(syntaxHighlight);
    // Add RSS support
    eleventyConfig.addPlugin(pluginRss);
    // Add navigation plugin
    eleventyConfig.addPlugin(eleventyNavigationPlugin);

Here we copy all the static files we want to include on our website:

// Copy files you need for your website

    // If you store your images in `assets/`,
    // you want to copy this directory to your website
    eleventyConfig.addPassthroughCopy("assets");
    // Add your css files to your website
    eleventyConfig.addPassthroughCopy("marx.min.css");
    eleventyConfig.addPassthroughCopy("extra.css");

Lastly, we add some filters that we can use in our templates to transform certain data into nicer textual representations. For example, these two filters create an excerpt for a post's content (maybe in the future I could use a summarization language model?) and prettify the date string:

// Define your filters

    // Define a filter for showing summaries of your posts
    eleventyConfig.addFilter('excerpt', (post) => {
        const content = post.replace(/(<([^>]+)>)/gi, '');
        return content.substr(0, content.lastIndexOf(' ', 200)) + '...';
    });
    // Add pretty dates support
    eleventyConfig.addFilter("asPostDate", (dateObj) => {
        return DateTime.fromJSDate(dateObj).toLocaleString(DateTime.DATE_MED)
   });
}

πŸ—Ί Layouts: Individual posts and the emergence of structure

I store most of my posts and pages as text files in posts/ and pages/, respectively. Each of these files is simply in the format you expect (markdown, orgmode), with the exception of some information at the top of the file: Here you provide information like the title, date, but also which layout file should be used, whether there should be a link in your navigation bar, etc.

For example, this file has the following information at the top:

---
title: "How this website works"
layout: post.njk
eleventyNavigation:
  key: init.el
  parent: Pinned
  order: 4
---

Layout

In _includes/, you have your templates that define how your markdown/orgmode files should be translated into actual HTML files. Eleventy supports multiple "templating languages", such as HTML (.html) or nunjucks (.njk). I use nunjucks. At the top of your posts you define which layout file must be used, of which I currently a base.njk shared by all pages, and one for my home page (home.njk) and all other pages (post.njk).

πŸ“ base.njk

This template is the basis for all of my web pages. Here I define the title of the page, the CSS styling, the navigation bar, the footer, etc. Typically, a HTML file consists of a head and a body.

  1. Head

    <!doctype html>
    <html lang="en">
    <head>
      <meta charset="utf-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>OvdW - {{ title }}</title>
    
      <link href="https://unpkg.com/[email protected]/themes/prism-okaidia.css" rel="stylesheet">
    
      <!-- Tracking users on the web -->
      <script async src="https://discreet-raccoon.pikapod.net/script.js" data-website-id="0671c728-4dac-4247-a62c-8e5750662841"></script>
    
      <!-- Marx CSS -->
      <link rel="stylesheet" href="/marx.min.css">
      <link rel="stylesheet" href="/extra.css">
      <style>
        :root {
            --primary: #008512;
            --link-color: #1a9f60;
        }
      </style>
    
      <!-- Check if tags exists and whether it contains 'vega' or 'post' to load relevant scripts/css -->
      {% if tags %}
        {% if tags.includes('post') %}
          <link href="https://unpkg.com/[email protected]/themes/prism-okaidia.css" rel="stylesheet">
        {% endif %}
        {% if tags.includes('vega') %}
          <script src="https://cdn.jsdelivr.net/npm//vega@5"></script>
          <script src="https://cdn.jsdelivr.net/npm//vega-lite@5"></script>
          <script src="https://cdn.jsdelivr.net/npm//vega-embed@6"></script>
        {% endif %}
    
      {% endif %}
    </head>
    
  2. Body

    <body>
      <!-- <div style="clear: both" class="background sticky"> -->
        <!-- <h1 class="logo" style="float: left"><a href="/">OvdW</a></h1> -->
    <div class="wrapper">
    <h1 class="logo">OvdW</h1>
    <p class="top-quote"><i>β€œLive, Laugh, Love”</i></p>
    <div class="top-right"></div>
    </div>
    <br>
    <main>
      <div class="background">
        <hr />
        <nav>
          {{ collections.all | eleventyNavigation("Pinned") | eleventyNavigationToHtml | safe }}
        </nav>
    <!--     <form style="float: right"> -->
    <!-- <input type="BUTTON" value="Back to top of page" onclick="window.location.href='#top'"> -->
    <!-- </form> -->
        <hr />
        </div>
        <!-- <center class=hero> <h1>{{ title }}</h1></center> -->
      <div class="sticky" style="position: fixed; top: 0; right: 0; border: 0; padding: 10px;"><a href="#top">^top</a></div>
          <article>
        <h1 class="title" style="text-align: center; float: center">{{ title }}</h1>
        {{ content | safe }}
        </article>
        <footer> <p>&copy; 2023 &middot; <a href="https://github.com/oskarvanderwal/">Oskar van der Wal</a></p></footer>
      </main>
    <script src="https://unpkg.com/[email protected]/components/prism-core.min.js"></script>
    <script src="https://unpkg.com/[email protected]/plugins/autoloader/prism-autoloader.min.js"></script>
    </body>
    </html>
    

πŸ“ home.njk

---
"layout": "base.njk"
"title": "Home"
---

{{ content | safe }}

{% for post in collections.post | reverse %}
{% if loop.index0 < 2 %}
<h2><a href="{{ post.url }}">{{ post.data.title }}</a></h2>
<p><b>[{{ post.date | asPostDate }}]</b>&mdash;{{ post.templateContent | excerpt }}</p>
<a href="{{ post.url }}">read more</a>
{% endif %}
{% endfor %}

πŸ“ post.njk

---
"layout": "base.njk"
---

{{ content | safe }}

✨ Themes: CSS files for creating a visual leitmotif

For styling my website, I use Marx CSS, the classless CSS reset. But it is now that you can be really creative, and you should find a theme that suits your website!

My process is very simple: I simply download a CSS file, marx.min.css, and place it in the root folder. Then I add an extra CSS file, extra.css, for adding some additional styling where needed.

πŸ“ extra.css

@media screen and (min-width: 800px) {
* {
    /* Here we set the margin and padding  0 */
    margin: 100;

    padding: 100;
}

    body {
        padding-left: 68px;
        padding-right: 68px;
    }
}

article {
    width: 100%
}


nav {
    /*it specifies the mouse cursor to be displayed
    when it is pointed over the element */
    cursor: pointer;
}

nav ul {
    flex-flow: row wrap;
    display: flex;
    justify-content: space-evenly;
}

.background {
    background: rgba(255, 255, 255, 0.95);
}

.sticky {
    /* This is used to make the navbar sticky, So that the
    navbar stays at the top part even if we scroll */
    position: sticky;
    /* align-items: center; */
    /* justify-content: center; */
    /* top: 0px; */
}

.wrapper{
    display: flex;
    justify-content: center;
    align-items: baseline;
    flex-flow: row wrap;
    /* gap: 10px; */
}

.logo {
    flex: 1;
    font-family: Palatino;
    color: black;
    /* font-size: rem; */
    /* color: white; */
    /* background-color: green; */
}

.top-quote {
    flex: 1;
    font-family: Palatino;
    font-size: 24px;
    text-align: center;
    /* width: 100%; */
    /* max-width: 50%; */
    /* min-width: 50%; */
}

.top-right {
    flex: 1;
}

.back-to-top {
    position: absolute;
    bottom: 0;
    text-align: right;
    height: 100%;
    position: sticky;
}

.back-to-top a {
    position: sticky;
    top: 88vh;
    cursor: pointer;
    font-size: 20px;
}

.back-to-top:before {
    content: '';
}

.title {
    text-align:center;
}

img {
    max-width: 100%;
    height: auto;}

.gallery {
    display: grid;
    gap: 1rem;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}

.gallery img {
    display: block;
    object-fit: cover;
}

.gallery li{
    display: flex;
    max-width: 50%;
}

.gallery figcaption{
    text-align: center;
}

πŸ¦„ Org-mode: Adding support for my life support

The following packages add org-mode file support to my 11ty setup, but if you only use markdown, you can leave this out of course!

At the start of .eleventy.js:

// For adding orgmode file support
const nodePandoc_ = require('node-pandoc');
const util = require('util');
const nodePandoc = util.promisify(nodePandoc_)

Within the module.exports = function(eleventyConfig) {} block of .eleventy.js:

// Optional: Add support for orgmode files
    eleventyConfig.addTemplateFormats('org');
    eleventyConfig.addExtension('org', {
        compile: async (inputContent, inputPath) => {
            const org2html = await nodePandoc(inputContent, "-f org -t html --shift-heading-level-by=1 --no-highlight")
            // Replace any instances of "sourceCode " with "language-"
            //let output = org2html.replace(/sourceCode /gi, "language-");
            // let output = org2html.replace(/(.*<pre class=")(\w*)(" \S*\n\S*)(<code>)/, "$1$2$3<code class=$2>")
            let output = org2html.replaceAll(/(<pre.*?class=")(\w*)(".*?)(<code>)/gs, "$1language-$2$3<code class=\"language-$2\">");
            return async () => {
                return output
            }
        },
    });

Using the eleventyNavigationPlugin, you can add a page to your navigation bar. Make sure to install @11ty/eleventy-navigation first!

Then add eleventyNavigation: to the top of your pages that you want in the navbar:

---
title: "How this website works"
layout: post.njk
eleventyNavigation:
  key: init.el
  parent: Pinned
  order: 4
---

Here, I have defined the current post in the category of Pinned posts, order 4, and key init.el (shown in the navbar). Check the base.njk template file to see how I use this to add pinned pages to the navbar.

🎨 Galleries: L'Art pour l'art(ificial intelligence)

Adding images to your website is easy. However, doing it nicelyβ€”such as resizing it depending on the screen sizeβ€”is more difficult. While 11ty has its own image plugin, I have instead chosen to use a file hosting & processing service called cloudinary.

πŸ“ _11ty/shortcodes.js

const CLOUDNAME = "dgpbva9o1";
const FOLDER = "v1682085335/Art/";
const BASE_URL = `https://res.cloudinary.com/${CLOUDNAME}/image/upload/`;
const FALLBACK_WIDTHS = [300, 600, 680, 1360];
const FALLBACK_WIDTH = 680;

function getSrcset(file, widths) {
  const widthSet = widths ? widths : FALLBACK_WIDTHS;
  return widthSet
    .map((width) => {
      return `${getSrc(file, width)} ${width}w`;
    })
    .join(", ");
}

function getSrc(file, width) {
  return `${BASE_URL}q_auto,f_auto,w_${
    width ? width : FALLBACK_WIDTH
  }/${FOLDER}${file}`;
}

function getThumbnail(thumbnail, alt) {
    return `<img src="${BASE_URL}c_thumb,w_200,g_face/${FOLDER}${thumbnail}" alt="Link to ${alt}">`;
}

function getImage(file, alt, width) {
    src = getSrc(file, width);
    return `<img src="${src}" alt="${alt}">`;
}

// thumbnail is already taken, so defining small_image instead

module.exports = {
  srcset: (file, widths) => getSrcset(file, widths),
  src: (file, width) => getSrc(file, width),
  defaultSizes: () => "(min-width: 980px) 928px, calc(95.15vw + 15px)",
  small_image: (image, alt) => getThumbnail(image, alt),
  image: (file, alt) => getImage(file, alt, 680)
};

Add to the top of .eleventy.js:

// Shortcodes
const shortcodes = require("./_11ty/shortcodes.js");

Add to the top of the .eleventy.js function block:

// Add the functions defined in shortcodes
    Object.keys(shortcodes).forEach((shortcodeName) => {
        eleventyConfig.addShortcode(shortcodeName, shortcodes[shortcodeName]);
    });

πŸ”¬ Analytics: Measuring simple user statistics

Of course it is nice to know who visits your website! While back in the day, having a guestbook (and lots of animated emoticons) on your website was cool, now we simple use a tracking tool to see where people are from. Be aware though that your user-tracking should be GDPR-compliant!

I use the simple self-hosted tool Umami, which provides you with a simple javascript to hide on your website. See if you can find the discreet raccoon in my base.njk?

πŸ±β€πŸ’» Makefile: your sysadmin and cheat-sheet

πŸ“ Makefile

help:
    @echo 'Makefile for my Website                                                   '
    @echo '                                                                          '
    @echo 'Usage:                                                                    '
    @echo '   make install           install 11ty and all plugins                    '
    @echo '   make clean             remove the generated files in _site             '
    @echo '   make publish           generate and sync using production settings     '
    @echo '   make serve             serve site at http://localhost:8080             '
    @echo '   make github            upload the web site to git submodule of _site   '
    @echo '                                                                          '

install-11ty:
    npm init -y
    npm install @11ty/eleventy --save-dev

install-plugins:
    npm install @11ty/eleventy-img --save-dev
    npm install @11ty/eleventy-plugin-syntaxhighlight --save-dev
    npm install @11ty/eleventy-navigation --save-dev

install:
    install-11ty
    install-plugins

clean:
    [ ! -d '_site' ] || rm -rf '_site'

serve:
    npx @11ty/eleventy --serve

publish:
    npx @11ty/eleventy
    bash run_sync.sh

github:
    cd '_site' && git add . && git commit -m "Update website" && git push

Some notes:

Running website for development

The following will host your website at localhost:8080 and reloads automatically when you make any changes:

npx @11ty/eleventy --serve

Installing a plugin

Run the following in your project root to add a plugin, e.g., for eleventy-img:

npm install @11ty/eleventy-img --save-dev