<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Northlight</title><link>https://mortennordbye.github.io/northlight/</link><description>A short site description, used for meta tags and the RSS feed.</description><language>en</language><lastBuildDate>Mon, 04 May 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://mortennordbye.github.io/northlight/" rel="self" type="application/rss+xml"/><item><title>Measuring Contrast Instead of Guessing</title><link>https://mortennordbye.github.io/northlight/blog/measuring/</link><pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://mortennordbye.github.io/northlight/blog/measuring/</guid><description>Four of the theme's own syntax colours failed the 4.5:1 floor. All four had been calculated by hand and looked fine.</description><category>accessibility</category><category>design</category><enclosure url="https://mortennordbye.github.io/northlight/blog/measuring/cover.png" type="image/png" length="0"/><content:encoded><![CDATA[<p>The design reference for this theme carried contrast ratios for every accent colour, worked
out by hand from the sRGB values. When they were finally measured against the surfaces they
actually sit on, several were wrong — and the ones that were wrong were the ones nobody
would have questioned.</p>
<h2 id="what-failed">What failed<a class="heading-anchor" href="#what-failed" aria-label="Link to this section">#</a>
</h2>
<p>Comments in code blocks measured 3.28:1 in light mode and 3.85:1 in dark, against a floor
of 4.5:1. Light-mode numbers measured between 4.08 and 4.32:1 depending on the palette.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">comment  light  #8a8a92  3.28:1  -&gt;  #727278  4.60:1
</span></span><span class="line"><span class="cl">comment  dark   #71717d  3.85:1  -&gt;  #7e7e89  4.60:1
</span></span><span class="line"><span class="cl">number   light  #b06a1f  4.08:1  -&gt;  #a4631d  4.60:1</span></span></code></pre></div><h2 id="why-hand-calculation-goes-wrong">Why hand calculation goes wrong<a class="heading-anchor" href="#why-hand-calculation-goes-wrong" aria-label="Link to this section">#</a>
</h2>
<p>Relative luminance is not lightness, and the green channel carries 72% of it. Two colours
that look equally dark can differ by a factor of two once weighted, which is exactly the
error that puts a grey at 3.3:1 while it reads as comfortably mid-tone.</p>
<h2 id="measure-against-the-real-surface">Measure against the real surface<a class="heading-anchor" href="#measure-against-the-real-surface" aria-label="Link to this section">#</a>
</h2>
<p>Code sits on <code>--surface</code>, not on the page background. A comment grey measured against white
clears the floor; the same grey measured against the code block it actually sits in does
not. Sample the rendered page, not the palette.</p>
]]></content:encoded></item><item><title>Shipping a Static Site Without a Node Toolchain</title><link>https://mortennordbye.github.io/northlight/blog/shipping-static/</link><pubDate>Sun, 22 Mar 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://mortennordbye.github.io/northlight/blog/shipping-static/</guid><description>Hugo's asset pipeline covers concatenation, minification and fingerprinting on its own. A third post, so related content has more than one candidate to choose from.</description><category>example</category><category>build</category><enclosure url="https://mortennordbye.github.io/northlight/blog/shipping-static/cover.png" type="image/png" length="0"/><content:encoded><![CDATA[<p>The usual static-site build has a Node step in it, and the Node step is where most of the
breakage lives. Hugo&rsquo;s own pipeline covers enough that a content site does not need one.</p>
<h2 id="what-the-pipeline-already-does">What the pipeline already does<a class="heading-anchor" href="#what-the-pipeline-already-does" aria-label="Link to this section">#</a>
</h2>
<p>Concatenation, minification, fingerprinting and subresource integrity are all built in:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go-html-template" data-lang="go-html-template"><span class="line"><span class="cl"><span class="cp">{{</span><span class="w"> </span><span class="nx">$css</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">slice</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="o">(</span><span class="nx">resources</span><span class="na">.Get</span><span class="w"> </span><span class="s">&#34;css/tokens.css&#34;</span><span class="o">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="o">(</span><span class="nx">resources</span><span class="na">.Get</span><span class="w"> </span><span class="s">&#34;css/base.css&#34;</span><span class="o">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="o">|</span><span class="w"> </span><span class="nx">resources</span><span class="na">.Concat</span><span class="w"> </span><span class="s">&#34;css/site.css&#34;</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">minify</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">fingerprint</span><span class="w"> </span><span class="s">&#34;sha512&#34;</span><span class="w"> </span><span class="cp">}}</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">link</span> <span class="na">rel</span><span class="o">=</span><span class="s">&#34;stylesheet&#34;</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;</span><span class="cp">{{</span><span class="w"> </span><span class="nx">$css</span><span class="na">.RelPermalink</span><span class="w"> </span><span class="cp">}}</span><span class="s">&#34;</span> <span class="na">integrity</span><span class="o">=</span><span class="s">&#34;</span><span class="cp">{{</span><span class="w"> </span><span class="nx">$css</span><span class="na">.Data.Integrity</span><span class="w"> </span><span class="cp">}}</span><span class="s">&#34;</span><span class="p">&gt;</span></span></span></code></pre></div><p>That is the whole build. No lockfile, no <code>node_modules</code>, nothing to audit.</p>
<h2 id="what-you-give-up">What you give up<a class="heading-anchor" href="#what-you-give-up" aria-label="Link to this section">#</a>
</h2>
<p>Autoprefixing, and the ability to use a framework that assumes a bundler. Custom properties
and modern CSS cover most of what a preprocessor was for, and the browsers that needed the
prefixes are gone.</p>
<h2 id="what-you-gain">What you gain<a class="heading-anchor" href="#what-you-gain" aria-label="Link to this section">#</a>
</h2>
<p>A build that still works in two years. The failure modes of a Hugo-only build are Hugo&rsquo;s,
and there is exactly one binary to upgrade.</p>
]]></content:encoded></item><item><title>A Post With Two Authors</title><link>https://mortennordbye.github.io/northlight/blog/co-authored/</link><pubDate>Wed, 18 Mar 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://mortennordbye.github.io/northlight/blog/co-authored/</guid><description>Credited to two people, so the byline, the author pages and the author card at the foot all have something real to render.</description><category>writing</category><content:encoded><![CDATA[<p>This post is credited to two people. Its byline names both, each linking to an author
page listing what they wrote, and the card at the foot carries the fuller version with
headlines and links.</p>
<p>Authors come from <code>data/authors/</code>, one file per person, keyed by filename. A post lists
the keys it wants in <code>authors</code>, and a key with no matching file fails the build rather
than quietly dropping somebody&rsquo;s name from their own work.</p>
<p>A post that says nothing about authors falls back to the single <code>[params.author]</code> in
site config, which is what every other post on this site does.</p>
]]></content:encoded></item><item><title>Setting Long-Form Text for the Screen</title><link>https://mortennordbye.github.io/northlight/blog/reading-long-form/</link><pubDate>Sun, 08 Feb 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://mortennordbye.github.io/northlight/blog/reading-long-form/</guid><description>Measure, leading and scale are the whole job. A second post so prev/next, related content and the post index have something real to work with.</description><category>typography</category><category>example</category><enclosure url="https://mortennordbye.github.io/northlight/blog/reading-long-form/cover.png" type="image/png" length="0"/><content:encoded><![CDATA[<p>A page of body copy is mostly one decision repeated: how far the eye travels before it
has to find the next line. Everything else in a reading layout is downstream of that.</p>
<h2 id="measure-comes-first">Measure comes first<a class="heading-anchor" href="#measure-comes-first" aria-label="Link to this section">#</a>
</h2>
<p>Northlight sets prose at 68 characters. Below about 45 the eye reverses too often and the
rhythm breaks; past about 80 it loses the start of the next line on the return sweep. The
range is wide, and 68 sits where a 17px face with generous leading stays comfortable on a
laptop without stranding a column of whitespace beside it.</p>
<p>The measure is set in <code>ch</code> units rather than pixels, so it follows the font size instead of
fighting it.</p>
<h3 id="leading-follows-measure">Leading follows measure<a class="heading-anchor" href="#leading-follows-measure" aria-label="Link to this section">#</a>
</h3>
<p>Longer lines need more leading. At 68 characters, 1.75 keeps the lines distinct without
opening gaps that break the paragraph into stripes.</p>
<blockquote><p>The two settings are one setting. Changing the measure without changing the leading is how
a layout ends up technically correct and unpleasant to read.</p>
</blockquote><h2 id="scale-and-how-little-of-it-you-need">Scale, and how little of it you need<a class="heading-anchor" href="#scale-and-how-little-of-it-you-need" aria-label="Link to this section">#</a>
</h2>
<p>Six sizes cover a blog: a page title, two heading levels, a lede, body, and meta. Anything
more and the steps stop being distinguishable, which defeats the point of having them.</p>
<ul>
<li>Page title, fluid between 30 and 46px</li>
<li>Section heading at 26px</li>
<li>Subsection at 20px</li>
<li>Lede at 19px, body at 17px</li>
<li>Meta at 13.5px</li>
</ul>
<h3 id="a-third-level-so-the-next-one-does-not-skip">A third level, so the next one does not skip<a class="heading-anchor" href="#a-third-level-so-the-next-one-does-not-skip" aria-label="Link to this section">#</a>
</h3>
<p>The scale runs without gaps for the same reason the measure does: skipping a level is a
hole in the document outline, and the outline is how a screen reader navigates.</p>
<h4 id="a-fourth-level-for-completeness">A fourth level, for completeness<a class="heading-anchor" href="#a-fourth-level-for-completeness" aria-label="Link to this section">#</a>
</h4>
<p>It exists, it appears in the table of contents, and it is styled — but four levels of
heading in one post usually means the post wanted to be two posts.</p>
]]></content:encoded></item><item><title>Thumb and background hero</title><link>https://mortennordbye.github.io/northlight/blog/hero-thumb-and-background/</link><pubDate>Thu, 22 Jan 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://mortennordbye.github.io/northlight/blog/hero-thumb-and-background/</guid><description>Demonstrates heroStyle = "thumbAndBackground". Its cover is deliberately textless, because this treatment puts the title on top of the artwork.</description><category>design</category><enclosure url="https://mortennordbye.github.io/northlight/blog/hero-thumb-and-background/cover.svg" type="image/svg+xml" length="0"/><content:encoded><![CDATA[<p>This post exists to render <code>heroStyle = &quot;thumbAndBackground&quot;</code>. Every other post on this site uses the
default, <code>basic</code>.</p>
<p><strong>Its cover has no text in it, and that is the point.</strong> The covers elsewhere on this site
have their title baked into the artwork, which is the right choice for a card but the
wrong one here: an overlay treatment would then show the title twice. Artwork for these
styles should carry no words.</p>
<p>The image is still never cropped. The box keeps the exact 1200×630 ratio and the fit is
<code>contain</code>, so the whole cover is visible and the header sits over a scrim on top of it.</p>
]]></content:encoded></item><item><title>Background hero</title><link>https://mortennordbye.github.io/northlight/blog/hero-background/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://mortennordbye.github.io/northlight/blog/hero-background/</guid><description>Demonstrates heroStyle = "background". Its cover is deliberately textless, because this treatment puts the title on top of the artwork.</description><category>design</category><enclosure url="https://mortennordbye.github.io/northlight/blog/hero-background/cover.svg" type="image/svg+xml" length="0"/><content:encoded><![CDATA[<p>This post exists to render <code>heroStyle = &quot;background&quot;</code>. Every other post on this site uses the
default, <code>basic</code>.</p>
<p><strong>Its cover has no text in it, and that is the point.</strong> The covers elsewhere on this site
have their title baked into the artwork, which is the right choice for a card but the
wrong one here: an overlay treatment would then show the title twice. Artwork for these
styles should carry no words.</p>
<p>The image is still never cropped. The box keeps the exact 1200×630 ratio and the fit is
<code>contain</code>, so the whole cover is visible and the header sits over a scrim on top of it.</p>
]]></content:encoded></item><item><title>Everything the Theme Has to Render</title><link>https://mortennordbye.github.io/northlight/blog/hello-northlight/</link><pubDate>Thu, 15 Jan 2026 00:00:00 +0000</pubDate><guid isPermaLink="true">https://mortennordbye.github.io/northlight/blog/hello-northlight/</guid><description>A kitchen-sink post used as the integration test. If a feature is not exercised here, it is not finished.</description><category>example</category><category>kitchen-sink</category><enclosure url="https://mortennordbye.github.io/northlight/blog/hello-northlight/cover.png" type="image/png" length="0"/><content:encoded><![CDATA[<p>This post exists to exercise every element the theme renders. Keep it in sync with the
build plan: when you add a feature, add the markup that proves it works.</p>
<h2 id="a-second-level-heading">A second-level heading<a class="heading-anchor" href="#a-second-level-heading" aria-label="Link to this section">#</a>
</h2>
<p>Body copy sets the measure at 68ch. Here is an <a href="https://gohugo.io/" rel="noopener" target="_blank">external link</a> and some
<code>inline code</code> in a sentence.</p>
<h3 id="a-third-level-heading">A third-level heading<a class="heading-anchor" href="#a-third-level-heading" aria-label="Link to this section">#</a>
</h3>
<ul>
<li>A list item</li>
<li>Another one, with <code>code</code> inside it</li>
<li>A third</li>
</ul>
<blockquote><p>A blockquote. Used sparingly, and styled with the accent wash rather than a grey box.</p>
</blockquote><h2 id="code-in-the-languages-that-matter">Code, in the languages that matter<a class="heading-anchor" href="#code-in-the-languages-that-matter" aria-label="Link to this section">#</a>
</h2>
<p>Seven languages, because those are the seven the reference blog uses. Every one of them
has to be legible in both colour modes, comments included.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">ConfigMap</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">example</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">default</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">data</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># Comments must clear 4.5:1 against the code background</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">replicas</span><span class="p">:</span><span class="w"> </span><span class="m">3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;value&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">enabled</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span></span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># A comment, which must stay legible in both colour modes</span>
</span></span><span class="line"><span class="cl">find . -name <span class="s1">&#39;*.md&#39;</span> -mtime -7 <span class="p">|</span> sort -u <span class="p">|</span> head -20
</span></span><span class="line"><span class="cl"><span class="nb">export</span> <span class="nv">COUNT</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span>grep -c . &lt; list.txt<span class="k">)</span><span class="s2">&#34;</span></span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-dockerfile" data-lang="dockerfile"><span class="line"><span class="cl"><span class="k">FROM</span><span class="w"> </span><span class="s">alpine:3.20</span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">RUN</span> apk add --no-cache ca-certificates<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">COPY</span> --chown<span class="o">=</span>1000:1000 . /srv<span class="err">
</span></span></span><span class="line"><span class="cl"><span class="k">EXPOSE</span><span class="w"> </span><span class="s">8080</span></span></span></code></pre></div><p>A fence can name the file it came from, which renders a bar above the code:</p>
<figure class="code-figure">
    <figcaption class="code-bar">
      <span class="code-file">/etc/nginx/conf.d/example.conf</span>
    </figcaption>
    <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-nginx" data-lang="nginx"><span class="line"><span class="cl"><span class="k">server</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kn">listen</span>       <span class="mi">443</span> <span class="s">ssl</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kn">server_name</span>  <span class="s">example.com</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># Cache immutable assets for a year
</span></span></span><span class="line"><span class="cl">    <span class="kn">location</span> <span class="p">~</span><span class="sr">*</span> <span class="s">\.(css|js|woff2)</span>$ <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kn">expires</span>    <span class="s">1y</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kn">add_header</span> <span class="s">Cache-Control</span> <span class="s">&#34;public,</span> <span class="s">immutable&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div>
  </figure><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="cl"><span class="k">resource</span> <span class="s2">&#34;aws_s3_bucket&#34; &#34;assets&#34;</span> {
</span></span><span class="line"><span class="cl"><span class="n">  bucket</span> <span class="o">=</span> <span class="s2">&#34;example-assets&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">  tags</span> <span class="o">=</span> {
</span></span><span class="line"><span class="cl"><span class="n">    Environment</span> <span class="o">=</span> <span class="s2">&#34;production&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">    Managed</span>     <span class="o">=</span> <span class="kt">true</span>
</span></span><span class="line"><span class="cl">  }
</span></span><span class="line"><span class="cl">}</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-alloy" data-lang="alloy"><span class="line"><span class="cl"><span class="n">prometheus</span><span class="o">.</span><span class="n">scrape</span><span class="w"> </span><span class="s">&#34;default&#34;</span><span class="w"> </span><span class="o">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">targets</span><span class="w">    </span><span class="o">=</span><span class="w"> </span><span class="n">discovery</span><span class="o">.</span><span class="n">kubernetes</span><span class="o">.</span><span class="n">pods</span><span class="o">.</span><span class="n">targets</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="n">forward_to</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">[</span><span class="n">prometheus</span><span class="o">.</span><span class="n">remote_write</span><span class="o">.</span><span class="n">default</span><span class="o">.</span><span class="n">receiver</span><span class="o">]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">}</span></span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">NAME                     READY   STATUS    RESTARTS   AGE
</span></span><span class="line"><span class="cl">northlight-7d9f8c6b-x2k  1/1     Running   0          14d</span></span></code></pre></div><p>Line numbers and a highlighted line are configured per fence rather than globally, so
both need styling even though the reference blog uses neither:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="hl"><span class="lnt">3
</span></span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">example</span><span class="w">
</span></span></span><span class="line hl"><span class="cl"><span class="w">  </span><span class="nt">namespace</span><span class="p">:</span><span class="w"> </span><span class="l">highlighted</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">labels</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">app</span><span class="p">:</span><span class="w"> </span><span class="l">northlight</span></span></span></code></pre></td></tr></table>
</div>
</div><h2 id="a-table">A table<a class="heading-anchor" href="#a-table" aria-label="Link to this section">#</a>
</h2>
<p>Wide tables scroll inside their own container rather than pushing the page sideways.</p>
<table>
	<thead>
			<tr>
					<th>Token</th>
					<th>Light</th>
					<th>Dark</th>
					<th>Used by</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>--bg</code></td>
					<td><code>#ffffff</code></td>
					<td><code>#0b0b0e</code></td>
					<td>page background</td>
			</tr>
			<tr>
					<td><code>--surface</code></td>
					<td><code>#fafafa</code></td>
					<td><code>#131317</code></td>
					<td>code blocks, cards on hover</td>
			</tr>
			<tr>
					<td><code>--fg</code></td>
					<td><code>#16161a</code></td>
					<td><code>#f2f2f5</code></td>
					<td>body text</td>
			</tr>
			<tr>
					<td><code>--fg-3</code></td>
					<td><code>#6e6e75</code></td>
					<td><code>#868690</code></td>
					<td>meta lines, dates, breadcrumb separators</td>
			</tr>
			<tr>
					<td><code>--accent</code></td>
					<td><code>#4f57c4</code></td>
					<td><code>#a6aef0</code></td>
					<td>links, active TOC item, progress bar</td>
			</tr>
	</tbody>
</table>
<h2 id="raw-html">Raw HTML<a class="heading-anchor" href="#raw-html" aria-label="Link to this section">#</a>
</h2>
<p>The site config enables <code>unsafe</code> HTML, so posts may contain markup like this:</p>
<img src="/images/example.svg" alt="An example image" style="width:70%;" />
<h2 id="fourth-level">Fourth level<a class="heading-anchor" href="#fourth-level" aria-label="Link to this section">#</a>
</h2>
<h3 id="a-third-level-so-the-fourth-does-not-skip-one">A third level, so the fourth does not skip one<a class="heading-anchor" href="#a-third-level-so-the-fourth-does-not-skip-one" aria-label="Link to this section">#</a>
</h3>
<p>A heading level may not be skipped: an <code>h2</code> followed by an <code>h4</code> reads to a screen
reader as a missing section, and this page is the theme&rsquo;s own integration test, so it
should not demonstrate the mistake.</p>
<h4 id="this-is-an-h4">This is an h4<a class="heading-anchor" href="#this-is-an-h4" aria-label="Link to this section">#</a>
</h4>
<p>It should render, and it should appear in the table of contents, which is configured for
levels 2 through 4.</p>
]]></content:encoded></item><item><title>Why This Theme Has No Shortcodes</title><link>https://mortennordbye.github.io/northlight/blog/no-shortcodes/</link><pubDate>Wed, 12 Nov 2025 00:00:00 +0000</pubDate><guid isPermaLink="true">https://mortennordbye.github.io/northlight/blog/no-shortcodes/</guid><description>An audit of the theme this one replaces found roughly two thirds of it unused. Shortcodes were the largest single share of that.</description><category>design</category><category>example</category><enclosure url="https://mortennordbye.github.io/northlight/blog/no-shortcodes/cover.png" type="image/png" length="0"/><content:encoded><![CDATA[<p>The theme Northlight replaces ships about forty-five shortcodes. An audit of every post
on the site using it found that exactly zero appeared in any of them.</p>
<h2 id="unused-surface-is-not-free">Unused surface is not free<a class="heading-anchor" href="#unused-surface-is-not-free" aria-label="Link to this section">#</a>
</h2>
<p>Every shortcode is a template to maintain, a name to document, and a behaviour someone
will eventually depend on. Shipping forty-five of them to cover the handful a given site
might want is a trade that only looks good before the maintenance starts.</p>
<h2 id="what-replaces-them">What replaces them<a class="heading-anchor" href="#what-replaces-them" aria-label="Link to this section">#</a>
</h2>
<p>Markdown, and render hooks for the cases where markdown alone is not enough. A hook
applies automatically to content that is already written, which is the opposite of a
shortcode: nothing has to be sprinkled through the posts to switch it on.</p>
<figure>
    <img class="img-light" src="/northlight/blog/no-shortcodes/render-hooks.png" srcset="/northlight/blog/no-shortcodes/render-hooks_hu_5d721b03ff18a6eb.png 480w, /northlight/blog/no-shortcodes/render-hooks_hu_6ecd3755faff714e.png 720w, /northlight/blog/no-shortcodes/render-hooks_hu_9ccd1486bb434129.png 1080w, /northlight/blog/no-shortcodes/render-hooks_hu_a77ec5aed18a68cb.png 1440w, /northlight/blog/no-shortcodes/render-hooks.png 1600w" sizes="(max-width: 47rem) 100vw, 44.2rem" width="1600" height="470" alt="Markdown passes through a render hook and comes out as themed HTML" loading="lazy" decoding="async"><img class="img-dark" src="/northlight/blog/no-shortcodes/render-hooks-dark.png" srcset="/northlight/blog/no-shortcodes/render-hooks-dark_hu_50fbfdac1d58bf79.png 480w, /northlight/blog/no-shortcodes/render-hooks-dark_hu_bdd0661552b8ca62.png 720w, /northlight/blog/no-shortcodes/render-hooks-dark_hu_1469f9a25808433b.png 1080w, /northlight/blog/no-shortcodes/render-hooks-dark_hu_54347e768dc96044.png 1440w, /northlight/blog/no-shortcodes/render-hooks-dark.png 1600w" sizes="(max-width: 47rem) 100vw, 44.2rem" width="1600" height="470" alt="Markdown passes through a render hook and comes out as themed HTML" loading="lazy" decoding="async">
    <figcaption>The same three steps apply to headings, links, tables, images and code fences.</figcaption>
  </figure><p>The distinction that matters is portability. A shortcode is theme-specific syntax, so
content written with it only renders correctly inside this theme. A render hook enriches
standard markdown, so the same file renders fine anywhere — just plainer.</p>
<h2 id="admonitions">Admonitions<a class="heading-anchor" href="#admonitions" aria-label="Link to this section">#</a>
</h2>
<p>Callouts use GitHub&rsquo;s alert syntax rather than a shortcode, for exactly that reason:</p>
<div class="admonition admonition-note" role="note">
      <p class="admonition-label"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="9"/><path d="M12 16v-5M12 8v.01"/></svg><span>Note</span></p>
      <div class="admonition-body"><p>The source for this block is four ordinary lines of markdown. Paste it into a GitHub
README or a plain markdown previewer and it renders as a blockquote — quieter, but
never broken.</p></div>
    </div><div class="admonition admonition-tip" role="note">
      <p class="admonition-label"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M9 18h6M10 21h4M12 3a6 6 0 0 1 3.6 10.8c-.5.4-.6 1-.6 1.5v.7H9v-.7c0-.5-.1-1.1-.6-1.5A6 6 0 0 1 12 3Z"/></svg><span>Tip</span></p>
      <div class="admonition-body"><p>Give a code fence a <code>{file=&quot;path/to/thing.yaml&quot;}</code> attribute and it grows a filename
bar. Another hook, same idea.</p></div>
    </div><div class="admonition admonition-important" role="note">
      <p class="admonition-label"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M3 11v2a1 1 0 0 0 1 1h3l7 5V5L7 10H4a1 1 0 0 0-1 1ZM18 9a4 4 0 0 1 0 6"/></svg><span>Important</span></p>
      <div class="admonition-body"><p>Five types are recognised: note, tip, important, warning and caution. Their colours sit
outside the palette system, because a caution should be red whether the site is running
periwinkle, sage or clay.</p></div>
    </div><div class="admonition admonition-warning" role="note">
      <p class="admonition-label"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M10.3 4 2.6 17.5A2 2 0 0 0 4.3 20.5h15.4a2 2 0 0 0 1.7-3L13.7 4a2 2 0 0 0-3.4 0Z"/><path d="M12 10v3.5M12 17v.01"/></svg><span>Warning</span></p>
      <div class="admonition-body"><p>An unrecognised alert type is not silently ignored. It emits a build warning and falls
back to a plain blockquote, so a typo surfaces at build time rather than in production.</p></div>
    </div><div class="admonition admonition-caution" role="note">
      <p class="admonition-label"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M8.3 3h7.4L21 8.3v7.4L15.7 21H8.3L3 15.7V8.3Z"/><path d="M12 8v4.5M12 16v.01"/></svg><span>Caution</span></p>
      <div class="admonition-body"><p>Renaming a published config param is a breaking change. Once a theme is public, its
config keys are API.</p></div>
    </div><h2 id="images">Images<a class="heading-anchor" href="#images" aria-label="Link to this section">#</a>
</h2>
<p>The image hook does the unglamorous work. Every local image ships with its intrinsic
width and height, so the article never reflows as pictures arrive, and it generates a
<code>srcset</code> — the prose column is 44.2rem wide, so serving a 2400px screenshot at full size
costs a reader roughly three times the bytes they can actually use.</p>
<p>A markdown title becomes the caption, as on the diagram above.</p>
]]></content:encoded></item><item><title>A Link Post Pointing Elsewhere</title><link>https://mortennordbye.github.io/northlight/blog/a-link-post/</link><pubDate>Thu, 02 Oct 2025 00:00:00 +0000</pubDate><guid isPermaLink="true">https://mortennordbye.github.io/northlight/blog/a-link-post/</guid><description>Set externalUrl in front matter and the listing entry links straight out, with an icon saying so before the click.</description><category>example</category><content:encoded><![CDATA[<p>This body is never reached from a listing, because every card and index row for this post
links to <code>externalUrl</code> instead. The page still builds, so an existing permalink does not
break.</p>
<p><code>excludeFromSearch</code> is set here too, which keeps it out of the ⌘K index without keeping
it out of the site.</p>
]]></content:encoded></item><item><title>Two Modes, Not One Inverted</title><link>https://mortennordbye.github.io/northlight/blog/two-modes/</link><pubDate>Tue, 30 Sep 2025 00:00:00 +0000</pubDate><guid isPermaLink="true">https://mortennordbye.github.io/northlight/blog/two-modes/</guid><description>Dark mode built by flipping the lightness of a light palette looks like a light palette with the lights off. The two are designed separately here.</description><category>design</category><category>colour</category><enclosure url="https://mortennordbye.github.io/northlight/blog/two-modes/cover.png" type="image/png" length="0"/><content:encoded><![CDATA[<p>Inverting a palette is the cheapest way to get a dark mode and the easiest one to spot.
Pure white text on pure black vibrates; mid greys that read as calm on white read as murky
on black; and a saturated accent that was comfortable at 6:1 becomes a glare at 15:1.</p>
<h2 id="what-changes-between-the-modes">What changes between the modes<a class="heading-anchor" href="#what-changes-between-the-modes" aria-label="Link to this section">#</a>
</h2>
<p>Dark sits on <code>#0b0b0e</code> rather than <code>#000000</code>, and text drops to <code>#f2f2f5</code> rather than
<code>#ffffff</code>. Both decisions reduce the contrast between the extremes from punishing to
comfortable without going anywhere near the AA floor.</p>
<h2 id="the-accent-swaps-roles">The accent swaps roles<a class="heading-anchor" href="#the-accent-swaps-roles" aria-label="Link to this section">#</a>
</h2>
<p>Each palette carries two tones. The readable one carries text and links; the soft one
carries fills, hover borders and washes, where contrast rules do not apply. Between light
and dark the two swap places, which is the only reason a genuine pastel can be the primary
colour of a theme at all.</p>
<blockquote><p>A pastel used as link text on white fails contrast every time. Used as a fill behind
dark text, the same colour is fine. The trick is never asking one tone to do both.</p>
</blockquote>]]></content:encoded></item><item><title>A Post With No Description</title><link>https://mortennordbye.github.io/northlight/blog/no-description/</link><pubDate>Tue, 12 Aug 2025 00:00:00 +0000</pubDate><guid isPermaLink="true">https://mortennordbye.github.io/northlight/blog/no-description/</guid><description>This post deliberately has no description in its front matter, which is what makes it useful: it is the one entry in the index that exercises list.showSummary. With that setting off, the listing shows the title and nothing else. With it on, the text you are reading now is stripped of its markup, truncated, and used as the excerpt instead.</description><category>writing</category><content:encoded><![CDATA[<p>This post deliberately has no <code>description</code> in its front matter, which is what makes it
useful: it is the one entry in the index that exercises <code>list.showSummary</code>. With that
setting off, the listing shows the title and nothing else. With it on, the text you are
reading now is stripped of its markup, truncated, and used as the excerpt instead.</p>
<p>A <code>description</code> is still the better thing to write. It is the meta description, the card
summary and the feed entry all at once, so it is worth choosing the words rather than
letting the first two sentences of the body do the job by accident. The fallback exists
for the posts where nobody did, not as a replacement for doing it.</p>
]]></content:encoded></item></channel></rss>