Christian Heilmann

You are currently browsing the Christian Heilmann blog archives for January, 2026.

Archive for January, 2026

Monky Business: Creating a Cistercian Numerals Generator

Tuesday, January 13th, 2026

In the 13th century Cistercian monks came up with a way to show the numbers from 1 to 9999 as a single character.

The cistercian numerals showing numbers 1 - 9 and 10x multiples of those as different characters

The way it works is to add the lines of different characters to each other until the number is reached. So, if you want to show 161, you take the 1, the 60 and the 100 and add them together:

Showing the correct numeral for 161 by showing the ones for 1, 60 and 100 and adding them to the same image

Same with 1312 as 1000 + 300 + 10 + 2:

Showing the correct numeral for 1312 by showing the ones for 2, 10, 300 and 1000 and adding them to the same image

Which is pretty much incredible, so I thought it would be fun to create a generator for those characters. And here it is:

Screen recording of the generator in action

And while we’re at it, why not have a Cistercian Clock ?

How to use the generator

Open it in your browser and enter the numbers you want to generate. You can also get the source code, download it and use it offline. You can generate numerals as PNG or as SVG, click them to download the images and click the X buttons to remove them.

How to use the code in your own products

The generator is based on a script I wrote to generate the numerals, all available on the GitHub Repo. There are two flavours, a simple Node based one that returns SVG strings and a more advanced one that allows for in-browser PNG and SVG generation and customisation.

toCistercian.js – Node or browser number to Cistercian numeral converted in SVG

You can use this on the command line using:

node toCistercian.js {number}

For example `node toCistercian.js 161` results in the following SVG:

<svg width="120" height="180" xmlns="http://www.w3.org/2000/svg">
    <title>Cistercian numeral for 161</title>
    <line x1="60" y1="20" x2="60" y2="160" stroke="#000" stroke-linecap="square" stroke-width="4"/>
    <line x1="60" y1="20" x2="100" y2="20" stroke="#000" stroke-linecap="square" stroke-width="4"/>
    <line x1="60" y1="20" x2="60" y2="160" stroke="#000" stroke-linecap="square" stroke-width="4"/>
    <line x1="20" y1="20" x2="20" y2="60" stroke="#000" stroke-linecap="square" stroke-width="4"/>
    <line x1="60" y1="20" x2="60" y2="160" stroke="#000" stroke-linecap="square" stroke-width="4"/>
    <line x1="100" y1="160" x2="60" y2="160" stroke="#000" stroke-linecap="square" stroke-width="4"/>
</svg>

You can also use this in a browser as shown in the simple example:

<output></output>
<script src="toCistercian.js">
</script>
<script>
    const svg = toCistercian(1312);
    document.querySelector('output').innerHTML = svg;
</script>

Cistercian.js – convert to svg/png/canvas with customisation

The generator uses the more detailed cistercian.js version, which allows you to generate numerals in various versions and formats.

Usage is in JavaScript and a browser environment.

const converter = new Cistercian();
converter.rendernumber(1312);

This would add an `output` element to the body and render the numeral with a text representation and a button to remove it again.
You can configure it to change the look and feel and what gets rendered by calling the `configure` method. See the advanced example for that.

If you want, for example, to render the numeral inside the element with the ID `mycanvas` as SVG with a `width` of `400`, lines 10 pixels thick and in the colour `peachpuff` and without any text display or button to delete, you can do the following:

<div id="mycanvas"></div>

myConverter.configure({
    renderer: 'svg',
    canvas: { width: 400 },
    stroke: { colour: 'peachpuff', width: 10 },
    addtext: false,
    addinteraction: false,
    outputcontainer: document.getElementById('mycanvas')
});
myConverter.rendernumber(1312);

How I built the thing

As with many things I code for fun, this started offline, with me thinking how to approach this issue. In essence, all I had was an image of the numerals. When I got home, I thought I should give this to Copilot to vibe code like all the cool kids do. I asked it to take this image of numerals and create SVG versions for each of them (so I could link to them). The result was fast, immediate, confident and utter garbage.

Generated SVG for 1-9 of the numerals, all wrong

So I went back to analysing the numerals and instead of creating them as SVGs, I created them as a dataset. In essence, these are characters on a 3 by 5 grid. I numbered the points and wrote them down as coordinates:

my glyph cheatsheet

this.points = [
    [10,10],[30,10],[50,10],
    [10,30],[30,30],[50,30],
    [10,50],[30,50],[50,50],
    [10,60],[30,60],[50,60],
    [10,80],[30,80],[50,80]
];

The middle line is never used in the real numerals, but hey, why not?

Then I looked at the numerals and noted down which points are connected for each of them. 1 and 13 are always there as this is a vertical line in the middle. This gave me the dataset to use with Canvas or generate SVG from. Here are the indices of the points array that describe all the glyphs:

this.glyphs = {
    0: [[1,13]],
    1: [[1,2]], 10: [[0,1]], 100: [[14,13]], 1000: [[12,13]],
    2: [[4,5]], 20: [[3,4]], 200: [[10,11]], 2000: [[9,10]],
    3: [[1,5]], 30: [[1,3]], 300: [[13,11]], 3000: [[13,9]],
    4: [[4,2]], 40: [[4,0]], 400: [[10,14]], 4000: [[10,12]], 
    5: [[1,2],[2,4]], 50: [[0,1],[0,4]], 500: [[13,14],[14,10]], 5000: [[13,12],[12,10]],
    6: [[2,5]], 60: [[0,3]], 600: [[14,11]], 6000: [[12,9]],
    7: [[1,2],[2,5]], 70: [[0,1],[0,3]], 700: [[13,14],[14,11]], 7000: [[13,12],[12,9]],
    8: [[4,5],[5,2]], 80: [[4,3],[3,0]], 800: [[10,11],[11,14]], 8000: [[12,9],[9,10]],
    9: [[1,2],[2,5],[5,4]], 90: [[0,1],[0,3],[3,4]], 900: [[13,14],[14,11],[11,10]], 9000: [[13,12],[12,9],[9,10]]
};

The rest was just comparing and looping over this array.

The logic of adding to the final numeral was not too taxing either. When the number wasn’t defined in the glyphs array, I turn it into a string and loop over it from the end to the start. Each number then gets the added zeroes to allow for the lookup:

let chunks = number.toString().split('').reverse();
chunks.forEach((chunk, index) => {
let value = chunk + '0'.repeat(index);

So, for 1312, this would become 1312 and on each loop iteration I get the data:

  • 2
  • 10
  • 300
  • 1000

Feel free to check the source of the script for some more fun bits. And yes, I did use Copilot to help with some of the cruft code I didn’t feel like writing by hand, especially turning functions into methods and such.

I had fun, I hope you find it interesting, too.

You are already behind by not having read this post.

Friday, January 2nd, 2026

A bunch of people running frantically with their laptops and a clock in the background

Lately, I have found an incredibly annoying pattern in social media—especially LinkedIn posts: the “you are already behind” posts, claiming that by not using product $XYZ you have already been beaten by the competition. These incendiary headlines are often followed up by a testimonial that the author used $XYZ to deliver 10-23x the amount of work he (yes, most of the time “he”) used to deliver. And, of course, the post ends with a special discount to try out product $XYZ.

I utterly despise this narrative; it is insincere, plays on people’s worries, and doesn’t actually deliver any solution. It’s plain and simply an ad for a product or a crowbar approach to paint the original poster as a thought leader who knows what the future holds.

Fun fact: in my whole career, I’ve always been told that I am behind the pack for not embracing certain products or technologies, and many passed me by without affecting my career or the products I built and sold at all.

  • I didn’t replace web development with Flash/Silverlight.
  • I didn’t go from the web into Second Life/Metaverse.
  • I didn’t bet exclusively on Android or iOS.
  • I didn’t build apps for Facebook/Myspace/WeChat exclusively.
  • I didn’t bet on Crypto or Blockchain.

And yet, here I am, having had a good career and some money. I didn’t burn out and don’t really feel like I should have been one of the super early adopters failing to make an impact in the long run. I celebrate the successes of other people and fast implementers, but I also spent far too much time fixing what others innovated to work in production. There’s always a “but $PERSON uses $XYZ and delivers much faster and simpler than you do.” Well, let them. Remember that you are a professional, and you want to deliver great work, which takes time, effort, and quality thinking. So, demand that from your customers and especially from yourself.

So here’s my advice: don’t interact with posts like these, avoid people who post these, and instead find your own pace and peace of mind. You will be most effective when you are happy in achieving what you want to, not what a made-up market or competition demands from you. If I am already behind the people running against a wall, that’s actually a good thing.