summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--blog/css-only-code-blocks/index.html267
-rw-r--r--blog/index.html12
-rw-r--r--blog/index.xml9
-rw-r--r--index.html26
-rw-r--r--index.xml9
-rw-r--r--pagefind/fragment/unknown_117d485.pf_fragment (renamed from pagefind/fragment/unknown_26a6593.pf_fragment)bin2431 -> 2432 bytes
-rw-r--r--pagefind/fragment/unknown_1ced448.pf_fragmentbin886 -> 0 bytes
-rw-r--r--pagefind/fragment/unknown_1dc17c1.pf_fragmentbin0 -> 1564 bytes
-rw-r--r--pagefind/fragment/unknown_3470936.pf_fragment (renamed from pagefind/fragment/unknown_7aa9d62.pf_fragment)bin6501 -> 6500 bytes
-rw-r--r--pagefind/fragment/unknown_60c6413.pf_fragmentbin0 -> 3210 bytes
-rw-r--r--pagefind/fragment/unknown_67ef208.pf_fragmentbin0 -> 848 bytes
-rw-r--r--pagefind/fragment/unknown_684b5ee.pf_fragmentbin1563 -> 0 bytes
-rw-r--r--pagefind/fragment/unknown_6aca334.pf_fragmentbin848 -> 0 bytes
-rw-r--r--pagefind/fragment/unknown_6b283ed.pf_fragmentbin0 -> 4111 bytes
-rw-r--r--pagefind/fragment/unknown_77ac5be.pf_fragmentbin0 -> 7728 bytes
-rw-r--r--pagefind/fragment/unknown_8a619f9.pf_fragmentbin0 -> 8368 bytes
-rw-r--r--pagefind/fragment/unknown_903d5c1.pf_fragment (renamed from pagefind/fragment/unknown_62c6a43.pf_fragment)bin5046 -> 5046 bytes
-rw-r--r--pagefind/fragment/unknown_a118647.pf_fragmentbin8364 -> 0 bytes
-rw-r--r--pagefind/fragment/unknown_abc2923.pf_fragmentbin1039 -> 0 bytes
-rw-r--r--pagefind/fragment/unknown_bb28932.pf_fragmentbin0 -> 886 bytes
-rw-r--r--pagefind/fragment/unknown_c2c6ec9.pf_fragmentbin7726 -> 0 bytes
-rw-r--r--pagefind/fragment/unknown_c3ee675.pf_fragmentbin0 -> 1038 bytes
-rw-r--r--pagefind/fragment/unknown_f17ff3b.pf_fragment (renamed from pagefind/fragment/unknown_83ecd38.pf_fragment)bin11912 -> 11911 bytes
-rw-r--r--pagefind/fragment/unknown_fd69f69.pf_fragmentbin4108 -> 0 bytes
-rw-r--r--pagefind/index/unknown_1a2698a.pf_indexbin42345 -> 0 bytes
-rw-r--r--pagefind/index/unknown_3133a12.pf_indexbin0 -> 33207 bytes
-rw-r--r--pagefind/index/unknown_3fb8321.pf_indexbin42469 -> 0 bytes
-rw-r--r--pagefind/index/unknown_57b4e5c.pf_indexbin0 -> 42351 bytes
-rw-r--r--pagefind/index/unknown_631b97d.pf_indexbin27916 -> 0 bytes
-rw-r--r--pagefind/index/unknown_94b0ced.pf_indexbin0 -> 42229 bytes
-rw-r--r--pagefind/pagefind-entry.json2
-rw-r--r--pagefind/pagefind.unknown_1722432a8a5b089.pf_metabin305 -> 0 bytes
-rw-r--r--pagefind/pagefind.unknown_a6e7cd42f4ee53b.pf_metabin0 -> 317 bytes
-rw-r--r--sitemap.xml9
-rw-r--r--style.css129
35 files changed, 388 insertions, 75 deletions
diff --git a/blog/css-only-code-blocks/index.html b/blog/css-only-code-blocks/index.html
new file mode 100644
index 0000000..76888b2
--- /dev/null
+++ b/blog/css-only-code-blocks/index.html
@@ -0,0 +1,267 @@
+<!DOCTYPE html>
+<html><head>
+ <meta charset="utf-8">
+ <title>Code listings with nice line wrapping and line numbers from plain CSS | Home</title>
+ <meta name="description" content="">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <meta name="mobile-web-app-capable" content="yes">
+ <meta name="color-scheme" content="dark light">
+ <link rel="stylesheet" href="/style.css">
+
+ <link rel="preload" href="/fonts/roboto_slab/RobotoSlab-VariableFont_wght.ttf" as="font" type="font/ttf" crossorigin />
+ <link rel="preload" href="/fonts/nyght-serif-main/fonts/WEB/NyghtSerif-Regular.woff2" as="font" type="font/woff2" crossorigin />
+ <link rel="preload" href="/fonts/nyght-serif-main/fonts/WEB/NyghtSerif-Bold.woff2" as="font" type="font/woff2" crossorigin />
+ <link rel="preload" href="/fonts/nyght-serif-main/fonts/WEB/NyghtSerif-BoldItalic.woff2" as="font" type="font/woff2" crossorigin />
+</head>
+<body><nav>
+ <div class="internal">
+
+ <a href="/" title="Home">Home</a>
+ <a href="/blog/" title="Blog">Blog</a>
+ <a href="/projects/" title="Projects">Projects</a>
+ <a href="/about/" title="About">About</a>
+ </div>
+ <div class="search">
+ <div id="search"></div>
+ </div>
+ <div class="external">
+ <a href="https://git.jaseg.de/" title="cgit">cgit</a>
+ <a href="https://github.com/jaseg" title="Github">Github</a>
+ <a href="https://gitlab.com/neinseg" title="Gitlab">Gitlab</a>
+ <a href="https://chaos.social/@jaseg" title="Mastodon">Mastodon</a>
+ </span>
+</nav>
+
+ <header>
+ <h1>Code listings with nice line wrapping and line numbers from plain CSS</h1>
+<ul class="breadcrumbs">
+ <li><a href="/">jaseg.de</a></li>
+ <li><a href="/blog/">Blog</a></li><li><a href="/blog/css-only-code-blocks/">Code listings with nice line wrapping and line numbers from plain CSS</a></li>
+</ul>
+ <strong>2025-07-23</strong>
+ </header>
+ <main data-pagefind-body>
+ <div class="document">
+
+
+<p>Code listings in web pages are often a bit of a pain to use. Often, they don't wrap on small screens. Also, copy-pasting
+code from a code listing often copies the line numbers along with the code. Finally, many implementations use
+heavyweight HTML and/or javascript, making them slow to render (looking at you, gitlab).</p>
+<p>For this blog, I wrote an implementation that renders HTML code listings entirely without JavaScript, renders line
+numbers using plain CSS such that they don't get selected with the code, and that works with the browser to wrap in a
+natural way while still supporting the little line continuation arrows that are used to show that a line was soft
+wrapped in text editors.</p>
+<p>This blog is rendered as a static site using <a class="reference external" href="https://gohugo.io/">Hugo</a> from a pile of <a class="reference external" href="https://www.sphinx-doc.org/en/master/usage/restructuredtext/index.html">RestructuredText</a> documents. RestructuredText renders
+code listings using <a class="reference external" href="https://pygments.org/">Pygments</a> by default. Pygments hard-bakes the line numbers into the generated HTML, so I am using a
+<a class="reference external" href="https://en.wikipedia.org/wiki/Monkey_patch">monkey-patched</a> hook that changes the line number rendering to just a bunch of empty <tt class="docutils literal">&lt;span&gt;</tt> elements. The resulting
+HTML for a code block then looks like this:</p>
+<pre class="code html literal-block">
+<span class="lineno"></span><span class="line"><span class="p">&lt;</span><span class="nt">pre</span> <span class="na">class</span><span class="o">=</span><span class="s">&quot;code [language] literal-block&quot;</span><span class="p">&gt;</span></span>
+<span class="lineno"></span><span class="line"> <span class="p">&lt;</span><span class="nt">span</span> <span class="na">class</span><span class="o">=</span><span class="s">&quot;lineno&quot;</span><span class="p">&gt;&lt;/</span><span class="nt">span</span><span class="p">&gt;</span></span>
+<span class="lineno"></span><span class="line"> <span class="p">&lt;</span><span class="nt">span</span> <span class="na">class</span><span class="o">=</span><span class="s">&quot;line&quot;</span><span class="p">&gt;</span></span>
+<span class="lineno"></span><span class="line"> <span class="p">&lt;</span><span class="nt">span</span> <span class="na">class</span><span class="o">=</span><span class="s">&quot;[syntax highlight token]&quot;</span><span class="p">&gt;</span>The <span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;&lt;</span><span class="nt">span</span> <span class="na">class</span><span class="o">=</span><span class="s">&quot;[other syntax highlight token]&quot;</span><span class="p">&gt;</span>code!<span class="p">&lt;</span><span class="nt">span</span><span class="p">&gt;</span></span>
+<span class="lineno"></span><span class="line"> <span class="p">&lt;/</span><span class="nt">span</span><span class="p">&gt;</span></span>
+<span class="lineno"></span><span class="line"> <span class="cm">&lt;!-- ... repeat once for each source line. --&gt;</span></span>
+<span class="lineno"></span><span class="line"><span class="p">&lt;/</span><span class="nt">pre</span><span class="p">&gt;</span>
+</span></pre>
+<p>You can find the (rather short) source of the <tt class="docutils literal">rst2html</tt> wrapper <a class="reference external" href="rst2html_wrapper">below</a>.</p>
+<div class="section" id="the-css">
+<h2>The CSS</h2>
+<p>This modified HTML structure of the code listing gets accompanied by some CSS to make it flow nicely. Here is a listing
+of the complete CSS controlling the listing. The only bit that isn't included here is the actual syntax styling rules
+for the pygments tokens.</p>
+<pre class="code css literal-block">
+<span class="lineno"></span><span class="line"><span class="c">/*****************************************************/</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="c">/* Code block formatting / syntax highlighting rules */</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="c">/*****************************************************/</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="p">.</span><span class="nc">code</span><span class="w"> </span><span class="p">{</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">font-family</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;Fira Code&quot;</span><span class="p">;</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">font-size</span><span class="p">:</span><span class="w"> </span><span class="mi">13</span><span class="kt">px</span><span class="p">;</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">text-align</span><span class="p">:</span><span class="w"> </span><span class="kc">left</span><span class="p">;</span><span class="w"> </span><span class="c">/* Override default content &quot;justify&quot; alignment */</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">white-space</span><span class="p">:</span><span class="w"> </span><span class="kc">pre-wrap</span><span class="p">;</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">word-wrap</span><span class="p">:</span><span class="w"> </span><span class="kc">break-word</span><span class="p">;</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">overflow-x</span><span class="p">:</span><span class="w"> </span><span class="kc">auto</span><span class="p">;</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">display</span><span class="p">:</span><span class="w"> </span><span class="k">grid</span><span class="p">;</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">align-items</span><span class="p">:</span><span class="w"> </span><span class="kc">start</span><span class="p">;</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">grid-template-columns</span><span class="p">:</span><span class="w"> </span><span class="n">min-content</span><span class="w"> </span><span class="mi">1</span><span class="n">fr</span><span class="p">;</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="p">}</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="p">.</span><span class="nc">code</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="p">.</span><span class="nc">line</span><span class="w"> </span><span class="p">{</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">padding-left</span><span class="p">:</span><span class="w"> </span><span class="nb">calc</span><span class="p">(</span><span class="mi">2</span><span class="kt">em</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">5</span><span class="kt">px</span><span class="p">);</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">text-indent</span><span class="p">:</span><span class="w"> </span><span class="mi">-2</span><span class="kt">em</span><span class="p">;</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">padding-top</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="kt">px</span><span class="p">;</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">min-width</span><span class="p">:</span><span class="w"> </span><span class="mi">15</span><span class="kt">em</span><span class="p">;</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="p">}</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="c">/* Make individual syntax tokens wrap anywhere */</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="p">.</span><span class="nc">code</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="p">.</span><span class="nc">line</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="nt">span</span><span class="w"> </span><span class="p">{</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">overflow-wrap</span><span class="p">:</span><span class="w"> </span><span class="n">anywhere</span><span class="p">;</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">white-space</span><span class="p">:</span><span class="w"> </span><span class="kc">pre-wrap</span><span class="p">;</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="p">}</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="c">/* We render line numbers in CSS! */</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="p">.</span><span class="nc">code</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="p">.</span><span class="nc">lineno</span><span class="w"> </span><span class="p">{</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">counter-increment</span><span class="p">:</span><span class="w"> </span><span class="n">lineno</span><span class="p">;</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">word-break</span><span class="p">:</span><span class="w"> </span><span class="n">keep-all</span><span class="p">;</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">margin</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">padding-left</span><span class="p">:</span><span class="w"> </span><span class="mi">15</span><span class="kt">px</span><span class="p">;</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">padding-right</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="kt">px</span><span class="p">;</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">overflow</span><span class="p">:</span><span class="w"> </span><span class="kc">clip</span><span class="p">;</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">position</span><span class="p">:</span><span class="w"> </span><span class="kc">relative</span><span class="p">;</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">text-align</span><span class="p">:</span><span class="w"> </span><span class="kc">right</span><span class="p">;</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">color</span><span class="p">:</span><span class="w"> </span><span class="nf">var</span><span class="p">(</span><span class="nv">--c-text-muted</span><span class="p">);</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">border-right</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="kt">px</span><span class="w"> </span><span class="kc">solid</span><span class="w"> </span><span class="nf">var</span><span class="p">(</span><span class="nv">--c-fg-highlight</span><span class="p">);</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">align-self</span><span class="p">:</span><span class="w"> </span><span class="kc">stretch</span><span class="p">;</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="p">}</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="c">/* We also handle line continuation markers in CSS. */</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="p">.</span><span class="nc">code</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="p">.</span><span class="nc">lineno</span><span class="p">::</span><span class="nd">after</span><span class="w"> </span><span class="p">{</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">position</span><span class="p">:</span><span class="w"> </span><span class="kc">absolute</span><span class="p">;</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">right</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="kt">px</span><span class="p">;</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">content</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳&quot;</span><span class="p">;</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">white-space</span><span class="p">:</span><span class="w"> </span><span class="kc">pre</span><span class="p">;</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">color</span><span class="p">:</span><span class="w"> </span><span class="nf">var</span><span class="p">(</span><span class="nv">--c-text-muted</span><span class="p">);</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="p">}</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="c">/* Insert the actual line number */</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="p">.</span><span class="nc">code</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="p">.</span><span class="nc">lineno</span><span class="p">::</span><span class="nd">before</span><span class="w"> </span><span class="p">{</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">content</span><span class="p">:</span><span class="w"> </span><span class="nb">counter</span><span class="p">(</span><span class="n">lineno</span><span class="p">);</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="p">}</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="p">.</span><span class="nc">code</span><span class="p">::</span><span class="nd">before</span><span class="w"> </span><span class="p">{</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">counter-reset</span><span class="p">:</span><span class="w"> </span><span class="n">lineno</span><span class="p">;</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="p">}</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="nt">footer</span><span class="w"> </span><span class="p">{</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">margin-top</span><span class="p">:</span><span class="w"> </span><span class="mi">30</span><span class="kt">px</span><span class="p">;</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">align-self</span><span class="p">:</span><span class="w"> </span><span class="kc">center</span><span class="p">;</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">margin-bottom</span><span class="p">:</span><span class="w"> </span><span class="mi">10</span><span class="kt">px</span><span class="p">;</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">text-align</span><span class="p">:</span><span class="w"> </span><span class="kc">center</span><span class="p">;</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"> </span><span class="k">padding</span><span class="p">:</span><span class="w"> </span><span class="mi">5</span><span class="kt">px</span><span class="w"> </span><span class="mi">25</span><span class="kt">px</span><span class="w"> </span><span class="mi">5</span><span class="kt">px</span><span class="w"> </span><span class="mi">25</span><span class="kt">px</span><span class="p">;</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="p">}</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="p">.</span><span class="nc">code</span><span class="w"> </span><span class="p">.</span><span class="nc">hll</span><span class="w"> </span><span class="p">{}</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="c">/* Following are about 50 lines that define the styling of each kind of pygments syntax highlight token. These lines</span></span>
+<span class="lineno"></span><span class="line"><span class="c"> all look like the following: */</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="p">.</span><span class="nc">code</span><span class="w"> </span><span class="p">.</span><span class="nc">c</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="k">color</span><span class="p">:</span><span class="w"> </span><span class="nf">var</span><span class="p">(</span><span class="nv">--c-text</span><span class="p">);</span><span class="w"> </span><span class="k">font-weight</span><span class="p">:</span><span class="w"> </span><span class="mi">400</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="c">/* Comment */</span>
+</span></pre>
+<p>This CSS does a few things:</p>
+<blockquote>
+<ol class="arabic simple">
+<li>It renders the <tt class="docutils literal">&lt;pre&gt;</tt> code listing element using a two-column CSS <tt class="docutils literal">display: grid</tt> layout. The left column is
+used for the line numbers, and the right column is used for the code lines.</li>
+<li>It numbers the lines using a <a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/CSS/counter">CSS Counter</a>. CSS counters are meant for things like numbering headings and such, but
+they are a perfect fit for our purpose.</li>
+<li>It inserts the counter value as the line number into the <tt class="docutils literal">&lt;span <span class="pre">class=&quot;lineno&quot;&gt;</span></tt> element's <tt class="docutils literal">::before</tt>
+pseudo-element. A side effect of using the <tt class="docutils literal">::before</tt> pseudo-element is that without doing anything extra, the
+line numbers will remain outside of the normal text selection so they will neither be highlighted when selecting
+listing content, nor will they be copied when copy/pasting the listing content.</li>
+<li>It inserts a string of <tt class="docutils literal"><span class="pre">&quot;\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳&quot;</span></tt> into the line number span's
+<tt class="docutils literal">::after</tt> pseudo-element. This string evaluates to a sequence of unicode arrows separated by line breaks, and
+starting with an empty line. The <tt class="docutils literal">::after</tt> pseudo-element is positioned using <tt class="docutils literal">position: absolute</tt>, and the
+parent <tt class="docutils literal">&lt;span <span class="pre">class=&quot;lineno&quot;&gt;</span></tt> has <tt class="docutils literal">position: relative</tt> set. This way, the arrow pseudo-element gets placed on
+top of the lineno span without affecting the layout at all. By setting <tt class="docutils literal">overflow: clip</tt> on the parent <tt class="docutils literal">&lt;span
+<span class="pre">class=&quot;lineno&quot;&gt;</span></tt>, the arrow pseudo-element gets cut off vertically wherever the parent lineno element naturally
+ends.</li>
+</ol>
+</blockquote>
+<p>The line number span is inserted into the parent <tt class="docutils literal">&lt;pre&gt;</tt> element's CSS grid using <tt class="docutils literal"><span class="pre">align-self:</span> stretch</tt>, which
+causes it to vertically stretch to fill the available space. Since the line number span only contains the line number,
+its minimum height is a single line. As a result, it will stretch higher only when the corresponding code line in the
+right grid column stretches vertically because of line wrapping. When that happens, part of the arrow pseudo-element
+starts showing through from behind the <tt class="docutils literal">overflow: clip</tt> of the line number span, and one arrow gets rendered for each
+wrapped listing line.</p>
+<p>When the page is too narrow, we don't want the code listing's lines to wrapp into a column of single characters. To
+prevent that, we simply set a <tt class="docutils literal"><span class="pre">min-width</span></tt> on the <tt class="docutils literal">&lt;span <span class="pre">class=&quot;line&quot;&gt;</span></tt> in the right column, and set <tt class="docutils literal"><span class="pre">overflow-x:</span>
+auto</tt> on the listing <tt class="docutils literal">&lt;pre&gt;</tt>. This results in a horizontal scroll bar appearing whenever the listing gets too narrow.</p>
+<p>You can try out the line wrapping by resizing this page!</p>
+</div>
+<div class="section" id="rst2html-wrapper">
+<h2>rst2html wrapper</h2>
+<p>Here is the python <tt class="docutils literal">rst2html</tt> wrapper that monkey-patches code rendering. I made hugo invoke this while building the
+page by simply overriding the <tt class="docutils literal">PATH</tt> environment variable.</p>
+<pre class="code python literal-block">
+<span class="lineno"></span><span class="line"><span class="ch">#!/usr/bin/env python3</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="c1"># Based on https://gist.github.com/mastbaum/2655700 for the basic plugin scaffolding</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="kn">import</span><span class="w"> </span><span class="nn">sys</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="kn">import</span><span class="w"> </span><span class="nn">re</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="kn">import</span><span class="w"> </span><span class="nn">docutils.core</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="kn">from</span><span class="w"> </span><span class="nn">docutils.transforms</span><span class="w"> </span><span class="kn">import</span> <span class="n">Transform</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="kn">from</span><span class="w"> </span><span class="nn">docutils.nodes</span><span class="w"> </span><span class="kn">import</span> <span class="n">TextElement</span><span class="p">,</span> <span class="n">Inline</span><span class="p">,</span> <span class="n">Text</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="kn">from</span><span class="w"> </span><span class="nn">docutils.parsers.rst</span><span class="w"> </span><span class="kn">import</span> <span class="n">Directive</span><span class="p">,</span> <span class="n">directives</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="kn">from</span><span class="w"> </span><span class="nn">docutils.writers.html4css1</span><span class="w"> </span><span class="kn">import</span> <span class="n">Writer</span><span class="p">,</span> <span class="n">HTMLTranslator</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="k">class</span><span class="w"> </span><span class="nc">UnfuckedHTMLTranslator</span><span class="p">(</span><span class="n">HTMLTranslator</span><span class="p">):</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span> <span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span> <span class="bp">self</span><span class="o">.</span><span class="n">in_literal_block</span> <span class="o">=</span> <span class="kc">False</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span> <span class="k">def</span><span class="w"> </span><span class="nf">visit_literal_block</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">node</span><span class="p">):</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span> <span class="c1"># Insert an empty &quot;lineno&quot; span before each line. We insert the line numbers using pure CSS in a ::before</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span> <span class="c1"># pseudo-element. This has the added advantage that the line numbers don't get included in text selection.</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span> <span class="c1"># These line number spans are also used to show line continuation markers when a line is wrapped.</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span> <span class="bp">self</span><span class="o">.</span><span class="n">in_literal_block</span> <span class="o">=</span> <span class="kc">True</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span> <span class="bp">self</span><span class="o">.</span><span class="n">body</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">starttag</span><span class="p">(</span><span class="n">node</span><span class="p">,</span> <span class="s1">'pre'</span><span class="p">,</span> <span class="n">CLASS</span><span class="o">=</span><span class="s1">'literal-block'</span><span class="p">))</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span> <span class="bp">self</span><span class="o">.</span><span class="n">body</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s1">'&lt;span class=&quot;lineno&quot;&gt;&lt;/span&gt;&lt;span class=&quot;line&quot;&gt;'</span><span class="p">)</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span> <span class="k">def</span><span class="w"> </span><span class="nf">depart_literal_block</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">node</span><span class="p">):</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span> <span class="bp">self</span><span class="o">.</span><span class="n">in_literal_block</span> <span class="o">=</span> <span class="kc">False</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span> <span class="bp">self</span><span class="o">.</span><span class="n">body</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="s1">'</span><span class="se">\n</span><span class="s1">&lt;/span&gt;&lt;/pre&gt;</span><span class="se">\n</span><span class="s1">'</span><span class="p">)</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span> <span class="k">def</span><span class="w"> </span><span class="nf">visit_Text</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">node</span><span class="p">):</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span> <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">in_literal_block</span><span class="p">:</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span> <span class="k">for</span> <span class="n">match</span> <span class="ow">in</span> <span class="n">re</span><span class="o">.</span><span class="n">finditer</span><span class="p">(</span><span class="s1">'([^</span><span class="se">\n</span><span class="s1">]*)(</span><span class="se">\n</span><span class="s1">|$)'</span><span class="p">,</span> <span class="n">node</span><span class="o">.</span><span class="n">astext</span><span class="p">()):</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span> <span class="n">text</span><span class="p">,</span> <span class="n">end</span> <span class="o">=</span> <span class="n">match</span><span class="o">.</span><span class="n">groups</span><span class="p">()</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span> <span class="k">if</span> <span class="n">text</span><span class="p">:</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">visit_Text</span><span class="p">(</span><span class="n">Text</span><span class="p">(</span><span class="n">text</span><span class="p">))</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span> <span class="k">if</span> <span class="n">end</span> <span class="o">==</span> <span class="s1">'</span><span class="se">\n</span><span class="s1">'</span><span class="p">:</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span> <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">node</span><span class="o">.</span><span class="n">parent</span><span class="p">,</span> <span class="n">Inline</span><span class="p">):</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span> <span class="bp">self</span><span class="o">.</span><span class="n">depart_inline</span><span class="p">(</span><span class="n">node</span><span class="o">.</span><span class="n">parent</span><span class="p">)</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span> <span class="bp">self</span><span class="o">.</span><span class="n">body</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">f</span><span class="s1">'&lt;/span&gt;</span><span class="se">\n</span><span class="s1">&lt;span class=&quot;lineno&quot;&gt;&lt;/span&gt;&lt;span class=&quot;line&quot;&gt;'</span><span class="p">)</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span> <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">node</span><span class="o">.</span><span class="n">parent</span><span class="p">,</span> <span class="n">Inline</span><span class="p">):</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span> <span class="bp">self</span><span class="o">.</span><span class="n">visit_inline</span><span class="p">(</span><span class="n">node</span><span class="o">.</span><span class="n">parent</span><span class="p">)</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span> <span class="k">else</span><span class="p">:</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">visit_Text</span><span class="p">(</span><span class="n">node</span><span class="p">)</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">html_writer</span> <span class="o">=</span> <span class="n">Writer</span><span class="p">()</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">html_writer</span><span class="o">.</span><span class="n">translator_class</span> <span class="o">=</span> <span class="n">UnfuckedHTMLTranslator</span><span class="w"></span></span>
+<span class="lineno"></span><span class="line"><span class="w"></span><span class="n">docutils</span><span class="o">.</span><span class="n">core</span><span class="o">.</span><span class="n">publish_cmdline</span><span class="p">(</span><span class="n">writer</span><span class="o">=</span><span class="n">html_writer</span><span class="p">)</span>
+</span></pre>
+</div>
+</div>
+ </main><footer>
+ Copyright © 2025 Jan Sebastian Götte
+ / <a href="/about/">About</a>
+ / <a href="/imprint/">Imprint</a>
+</footer>
+<script type="text/javascript" src="/pagefind/pagefind-ui.js" defer></script>
+ <script>
+ window.addEventListener('DOMContentLoaded', (event) => {
+ new PagefindUI({element: "#search", showSubResults: true});
+ });
+ </script>
+ <script type="speculationrules">
+ {
+ "prerender": [
+ {
+ "source": "document",
+ "where": {
+ "and": [
+ {"href_matches": "/*"}
+ ]
+ },
+ "eagerness": "moderate"
+ }
+ ]
+ }
+ </script>
+ </body>
+</html>
diff --git a/blog/index.html b/blog/index.html
index 97f5921..3c2bb69 100644
--- a/blog/index.html
+++ b/blog/index.html
@@ -40,6 +40,18 @@
</header>
<main class="cards">
+ <div class="card"><h3><a href="/blog/css-only-code-blocks/">Code listings with nice line wrapping and line numbers from plain CSS</a></h3><strong>2025-07-23</strong>
+
+ <div class="summary">
+ <div class="document">
+
+
+<p>Code listings in web pages are often a bit of a pain to use. Usually, they don't wrap on small screens. Also, copy-pasting code from a code listing often copies the line numbers along with the code. Finally, many implementations use heavyweight HTML and/or javascript, making them slow to render. For this blog, I wrote a little CSS hack that renders nice, wrapping code blocks with line continuation markers in plain CSS without any JS.</p>
+</div>
+ <a href="http://jaseg.de/blog/css-only-code-blocks/">Read more</a>
+ </div>
+</div>
+
<div class="card"><h3><a href="/blog/jupyterlab-notebook-file-oneliner/">Getting the .ipynb Notebook File Location From a Running Jupyter Lab Notebook</a></h3><strong>2025-06-29</strong>
<div class="summary">
diff --git a/blog/index.xml b/blog/index.xml
index 285b3f8..5ce7d35 100644
--- a/blog/index.xml
+++ b/blog/index.xml
@@ -7,9 +7,16 @@
<generator>Hugo</generator>
<language>en-us</language>
<copyright>Jan Sebastian Götte</copyright>
- <lastBuildDate>Sun, 29 Jun 2025 23:42:00 +0100</lastBuildDate>
+ <lastBuildDate>Wed, 23 Jul 2025 23:42:00 +0100</lastBuildDate>
<atom:link href="http://jaseg.de/blog/index.xml" rel="self" type="application/rss+xml" />
<item>
+ <title>Code listings with nice line wrapping and line numbers from plain CSS</title>
+ <link>http://jaseg.de/blog/css-only-code-blocks/</link>
+ <pubDate>Wed, 23 Jul 2025 23:42:00 +0100</pubDate>
+ <guid>http://jaseg.de/blog/css-only-code-blocks/</guid>
+ <description>&lt;div class=&#34;document&#34;&gt;&#xA;&#xA;&#xA;&lt;p&gt;Code listings in web pages are often a bit of a pain to use. Usually, they don&#39;t wrap on small screens. Also, copy-pasting code from a code listing often copies the line numbers along with the code. Finally, many implementations use heavyweight HTML and/or javascript, making them slow to render. For this blog, I wrote a little CSS hack that renders nice, wrapping code blocks with line continuation markers in plain CSS without any JS.&lt;/p&gt;&#xA;&lt;/div&gt;</description>
+ </item>
+ <item>
<title>Getting the .ipynb Notebook File Location From a Running Jupyter Lab Notebook</title>
<link>http://jaseg.de/blog/jupyterlab-notebook-file-oneliner/</link>
<pubDate>Sun, 29 Jun 2025 23:42:00 +0100</pubDate>
diff --git a/index.html b/index.html
index 06daa32..ee0dc89 100644
--- a/index.html
+++ b/index.html
@@ -96,6 +96,19 @@ pages. If you want to learn more about me, head over to the about page.</p>
<h2>Blog</h2>
+ <div class="card"><h3><a href="/blog/css-only-code-blocks/">Code listings with nice line wrapping and line numbers from plain CSS</a></h3><strong>2025-07-23</strong>
+
+ <div class="summary">
+ <div class="document">
+
+
+<p>Code listings in web pages are often a bit of a pain to use. Usually, they don't wrap on small screens. Also, copy-pasting code from a code listing often copies the line numbers along with the code. Finally, many implementations use heavyweight HTML and/or javascript, making them slow to render. For this blog, I wrote a little CSS hack that renders nice, wrapping code blocks with line continuation markers in plain CSS without any JS.</p>
+</div>
+ <a href="http://jaseg.de/blog/css-only-code-blocks/">Read more</a>
+ </div>
+</div>
+
+
<div class="card"><h3><a href="/blog/jupyterlab-notebook-file-oneliner/">Getting the .ipynb Notebook File Location From a Running Jupyter Lab Notebook</a></h3><strong>2025-06-29</strong>
<div class="summary">
@@ -220,19 +233,6 @@ set up your UART to talk to an external hardware USB to serial converter is a ma
</div>
- <div class="card"><h3><a href="/blog/multichannel-led-driver/">32-Channel LED tape driver</a></h3><strong>2018-05-02</strong>
-
- <div class="summary">
- <div class="document">
-
-
-<p>Together, a friend and I outfitted the small staircase at Berlin's Chaos Computer Club with nice, shiny RGB-WW LED tape for ambient lighting. For this installation, I made a 32-channel LED driver that achieves high dynamic range on all 32 channels using a cheap microcontroller by using Binary Code Modulation.</p>
-</div>
- <a href="http://jaseg.de/blog/multichannel-led-driver/">Read more</a>
- </div>
-</div>
-
-
<div class="pagination-links">
diff --git a/index.xml b/index.xml
index 7e5ce20..de6d7e8 100644
--- a/index.xml
+++ b/index.xml
@@ -7,9 +7,16 @@
<generator>Hugo</generator>
<language>en-us</language>
<copyright>Jan Sebastian Götte</copyright>
- <lastBuildDate>Sun, 29 Jun 2025 23:42:00 +0100</lastBuildDate>
+ <lastBuildDate>Wed, 23 Jul 2025 23:42:00 +0100</lastBuildDate>
<atom:link href="http://jaseg.de/index.xml" rel="self" type="application/rss+xml" />
<item>
+ <title>Code listings with nice line wrapping and line numbers from plain CSS</title>
+ <link>http://jaseg.de/blog/css-only-code-blocks/</link>
+ <pubDate>Wed, 23 Jul 2025 23:42:00 +0100</pubDate>
+ <guid>http://jaseg.de/blog/css-only-code-blocks/</guid>
+ <description>&lt;div class=&#34;document&#34;&gt;&#xA;&#xA;&#xA;&lt;p&gt;Code listings in web pages are often a bit of a pain to use. Usually, they don&#39;t wrap on small screens. Also, copy-pasting code from a code listing often copies the line numbers along with the code. Finally, many implementations use heavyweight HTML and/or javascript, making them slow to render. For this blog, I wrote a little CSS hack that renders nice, wrapping code blocks with line continuation markers in plain CSS without any JS.&lt;/p&gt;&#xA;&lt;/div&gt;</description>
+ </item>
+ <item>
<title>Getting the .ipynb Notebook File Location From a Running Jupyter Lab Notebook</title>
<link>http://jaseg.de/blog/jupyterlab-notebook-file-oneliner/</link>
<pubDate>Sun, 29 Jun 2025 23:42:00 +0100</pubDate>
diff --git a/pagefind/fragment/unknown_26a6593.pf_fragment b/pagefind/fragment/unknown_117d485.pf_fragment
index a356c24..c751b98 100644
--- a/pagefind/fragment/unknown_26a6593.pf_fragment
+++ b/pagefind/fragment/unknown_117d485.pf_fragment
Binary files differ
diff --git a/pagefind/fragment/unknown_1ced448.pf_fragment b/pagefind/fragment/unknown_1ced448.pf_fragment
deleted file mode 100644
index e86b6db..0000000
--- a/pagefind/fragment/unknown_1ced448.pf_fragment
+++ /dev/null
Binary files differ
diff --git a/pagefind/fragment/unknown_1dc17c1.pf_fragment b/pagefind/fragment/unknown_1dc17c1.pf_fragment
new file mode 100644
index 0000000..e10c683
--- /dev/null
+++ b/pagefind/fragment/unknown_1dc17c1.pf_fragment
Binary files differ
diff --git a/pagefind/fragment/unknown_7aa9d62.pf_fragment b/pagefind/fragment/unknown_3470936.pf_fragment
index e87ac51..84d52fa 100644
--- a/pagefind/fragment/unknown_7aa9d62.pf_fragment
+++ b/pagefind/fragment/unknown_3470936.pf_fragment
Binary files differ
diff --git a/pagefind/fragment/unknown_60c6413.pf_fragment b/pagefind/fragment/unknown_60c6413.pf_fragment
new file mode 100644
index 0000000..cdeb2f0
--- /dev/null
+++ b/pagefind/fragment/unknown_60c6413.pf_fragment
Binary files differ
diff --git a/pagefind/fragment/unknown_67ef208.pf_fragment b/pagefind/fragment/unknown_67ef208.pf_fragment
new file mode 100644
index 0000000..a4f085a
--- /dev/null
+++ b/pagefind/fragment/unknown_67ef208.pf_fragment
Binary files differ
diff --git a/pagefind/fragment/unknown_684b5ee.pf_fragment b/pagefind/fragment/unknown_684b5ee.pf_fragment
deleted file mode 100644
index 278d844..0000000
--- a/pagefind/fragment/unknown_684b5ee.pf_fragment
+++ /dev/null
Binary files differ
diff --git a/pagefind/fragment/unknown_6aca334.pf_fragment b/pagefind/fragment/unknown_6aca334.pf_fragment
deleted file mode 100644
index 5945f47..0000000
--- a/pagefind/fragment/unknown_6aca334.pf_fragment
+++ /dev/null
Binary files differ
diff --git a/pagefind/fragment/unknown_6b283ed.pf_fragment b/pagefind/fragment/unknown_6b283ed.pf_fragment
new file mode 100644
index 0000000..2edd8ca
--- /dev/null
+++ b/pagefind/fragment/unknown_6b283ed.pf_fragment
Binary files differ
diff --git a/pagefind/fragment/unknown_77ac5be.pf_fragment b/pagefind/fragment/unknown_77ac5be.pf_fragment
new file mode 100644
index 0000000..68e26b7
--- /dev/null
+++ b/pagefind/fragment/unknown_77ac5be.pf_fragment
Binary files differ
diff --git a/pagefind/fragment/unknown_8a619f9.pf_fragment b/pagefind/fragment/unknown_8a619f9.pf_fragment
new file mode 100644
index 0000000..83c8365
--- /dev/null
+++ b/pagefind/fragment/unknown_8a619f9.pf_fragment
Binary files differ
diff --git a/pagefind/fragment/unknown_62c6a43.pf_fragment b/pagefind/fragment/unknown_903d5c1.pf_fragment
index 587431b..aaa5a7a 100644
--- a/pagefind/fragment/unknown_62c6a43.pf_fragment
+++ b/pagefind/fragment/unknown_903d5c1.pf_fragment
Binary files differ
diff --git a/pagefind/fragment/unknown_a118647.pf_fragment b/pagefind/fragment/unknown_a118647.pf_fragment
deleted file mode 100644
index c51f0b3..0000000
--- a/pagefind/fragment/unknown_a118647.pf_fragment
+++ /dev/null
Binary files differ
diff --git a/pagefind/fragment/unknown_abc2923.pf_fragment b/pagefind/fragment/unknown_abc2923.pf_fragment
deleted file mode 100644
index c6d3d46..0000000
--- a/pagefind/fragment/unknown_abc2923.pf_fragment
+++ /dev/null
Binary files differ
diff --git a/pagefind/fragment/unknown_bb28932.pf_fragment b/pagefind/fragment/unknown_bb28932.pf_fragment
new file mode 100644
index 0000000..3cb19a3
--- /dev/null
+++ b/pagefind/fragment/unknown_bb28932.pf_fragment
Binary files differ
diff --git a/pagefind/fragment/unknown_c2c6ec9.pf_fragment b/pagefind/fragment/unknown_c2c6ec9.pf_fragment
deleted file mode 100644
index 08c09f8..0000000
--- a/pagefind/fragment/unknown_c2c6ec9.pf_fragment
+++ /dev/null
Binary files differ
diff --git a/pagefind/fragment/unknown_c3ee675.pf_fragment b/pagefind/fragment/unknown_c3ee675.pf_fragment
new file mode 100644
index 0000000..f41e1e5
--- /dev/null
+++ b/pagefind/fragment/unknown_c3ee675.pf_fragment
Binary files differ
diff --git a/pagefind/fragment/unknown_83ecd38.pf_fragment b/pagefind/fragment/unknown_f17ff3b.pf_fragment
index e7f2639..3ed8fbd 100644
--- a/pagefind/fragment/unknown_83ecd38.pf_fragment
+++ b/pagefind/fragment/unknown_f17ff3b.pf_fragment
Binary files differ
diff --git a/pagefind/fragment/unknown_fd69f69.pf_fragment b/pagefind/fragment/unknown_fd69f69.pf_fragment
deleted file mode 100644
index 8e999f6..0000000
--- a/pagefind/fragment/unknown_fd69f69.pf_fragment
+++ /dev/null
Binary files differ
diff --git a/pagefind/index/unknown_1a2698a.pf_index b/pagefind/index/unknown_1a2698a.pf_index
deleted file mode 100644
index aa0b2b8..0000000
--- a/pagefind/index/unknown_1a2698a.pf_index
+++ /dev/null
Binary files differ
diff --git a/pagefind/index/unknown_3133a12.pf_index b/pagefind/index/unknown_3133a12.pf_index
new file mode 100644
index 0000000..e2381c7
--- /dev/null
+++ b/pagefind/index/unknown_3133a12.pf_index
Binary files differ
diff --git a/pagefind/index/unknown_3fb8321.pf_index b/pagefind/index/unknown_3fb8321.pf_index
deleted file mode 100644
index 15da04a..0000000
--- a/pagefind/index/unknown_3fb8321.pf_index
+++ /dev/null
Binary files differ
diff --git a/pagefind/index/unknown_57b4e5c.pf_index b/pagefind/index/unknown_57b4e5c.pf_index
new file mode 100644
index 0000000..461f5f6
--- /dev/null
+++ b/pagefind/index/unknown_57b4e5c.pf_index
Binary files differ
diff --git a/pagefind/index/unknown_631b97d.pf_index b/pagefind/index/unknown_631b97d.pf_index
deleted file mode 100644
index a2556ed..0000000
--- a/pagefind/index/unknown_631b97d.pf_index
+++ /dev/null
Binary files differ
diff --git a/pagefind/index/unknown_94b0ced.pf_index b/pagefind/index/unknown_94b0ced.pf_index
new file mode 100644
index 0000000..0cbaf1f
--- /dev/null
+++ b/pagefind/index/unknown_94b0ced.pf_index
Binary files differ
diff --git a/pagefind/pagefind-entry.json b/pagefind/pagefind-entry.json
index b83100a..695aeba 100644
--- a/pagefind/pagefind-entry.json
+++ b/pagefind/pagefind-entry.json
@@ -1 +1 @@
-{"version":"1.0.4","languages":{"unknown":{"hash":"unknown_1722432a8a5b089","wasm":null,"page_count":21}}} \ No newline at end of file
+{"version":"1.0.4","languages":{"unknown":{"hash":"unknown_a6e7cd42f4ee53b","wasm":null,"page_count":22}}} \ No newline at end of file
diff --git a/pagefind/pagefind.unknown_1722432a8a5b089.pf_meta b/pagefind/pagefind.unknown_1722432a8a5b089.pf_meta
deleted file mode 100644
index d441ab0..0000000
--- a/pagefind/pagefind.unknown_1722432a8a5b089.pf_meta
+++ /dev/null
Binary files differ
diff --git a/pagefind/pagefind.unknown_a6e7cd42f4ee53b.pf_meta b/pagefind/pagefind.unknown_a6e7cd42f4ee53b.pf_meta
new file mode 100644
index 0000000..f785482
--- /dev/null
+++ b/pagefind/pagefind.unknown_a6e7cd42f4ee53b.pf_meta
Binary files differ
diff --git a/sitemap.xml b/sitemap.xml
index 7cfe3cf..c0965ab 100644
--- a/sitemap.xml
+++ b/sitemap.xml
@@ -3,12 +3,15 @@
xmlns:xhtml="http://www.w3.org/1999/xhtml">
<url>
<loc>http://jaseg.de/blog/</loc>
- <lastmod>2025-06-29T23:42:00+01:00</lastmod>
+ <lastmod>2025-07-23T23:42:00+01:00</lastmod>
</url><url>
- <loc>http://jaseg.de/blog/jupyterlab-notebook-file-oneliner/</loc>
- <lastmod>2025-06-29T23:42:00+01:00</lastmod>
+ <loc>http://jaseg.de/blog/css-only-code-blocks/</loc>
+ <lastmod>2025-07-23T23:42:00+01:00</lastmod>
</url><url>
<loc>http://jaseg.de/</loc>
+ <lastmod>2025-07-23T23:42:00+01:00</lastmod>
+ </url><url>
+ <loc>http://jaseg.de/blog/jupyterlab-notebook-file-oneliner/</loc>
<lastmod>2025-06-29T23:42:00+01:00</lastmod>
</url><url>
<loc>http://jaseg.de/blog/8seg/</loc>
diff --git a/style.css b/style.css
index 8ffcbe1..9d5bce3 100644
--- a/style.css
+++ b/style.css
@@ -267,6 +267,14 @@ h3 {
font-weight: 700;
}
+/* Prevent long literals from breaking the page layout's width */
+span.pre {
+ white-space: pre-wrap;
+ word-wrap: break-word;
+ overflow-x: auto;
+ overflow-wrap: anywhere;
+}
+
body > header {
z-index: 1;
margin-top: 100px;
@@ -471,9 +479,14 @@ img:hover {
filter: none;
}
+/*****************************************************/
+/* Code block formatting / syntax highlighting rules */
+/*****************************************************/
+
.code {
font-family: "Fira Code";
font-size: 13px;
+ text-align: left; /* Override default content "justify" alignment */
white-space: pre-wrap;
word-wrap: break-word;
overflow-x: auto;
@@ -495,6 +508,7 @@ img:hover {
white-space: pre-wrap;
}
+/* We render line numbers in CSS! */
.code > .lineno {
counter-increment: lineno;
word-break: keep-all;
@@ -509,6 +523,7 @@ img:hover {
align-self: stretch;
}
+/* We also handle line continuation markers in CSS. */
.code > .lineno::after {
position: absolute;
right: 5px;
@@ -517,6 +532,7 @@ img:hover {
color: var(--c-text-muted);
}
+/* Insert the actual line number */
.code > .lineno::before {
content: counter(lineno);
}
@@ -533,62 +549,63 @@ footer {
padding: 5px 25px 5px 25px;
}
-body .hll {}
-body .c { color: var(--c-text); font-weight: 400 } /* Comment */
-body .n { color: var(--c-text); font-weight: 400 } /* Name */
-body .o { color: var(--c-text); font-weight: 400 } /* Operator */
-body .cm { color: var(--c-text); font-weight: 400 } /* Comment.Multiline */
-body .cp { color: var(--c-text); font-weight: 400 } /* Comment.Preproc */
-body .c1 { color: var(--c-text); font-weight: 400 } /* Comment.Single */
-body .cs { color: var(--c-text); font-weight: 400 } /* Comment.Special */
-body .nd { color: var(--c-text); font-weight: 400 } /* Name.Decorator */
-body .nn { color: var(--c-text); font-weight: 400 } /* Name.Namespace */
-body .vc { color: var(--c-text); font-weight: 400 } /* Name.Variable.Class */
-body .vg { color: var(--c-text); font-weight: 400 } /* Name.Variable.Global */
-body .vi { color: var(--c-text); font-weight: 400 } /* Name.Variable.Instance */
-body .err { color: var(--c-text-highlight); font-weight: 500 } /* Error */
-body .k { color: var(--c-text-highlight); font-weight: 500 } /* Keyword */
-body .l { color: var(--c-text-highlight); font-weight: 500 } /* Literal */
-body .kc { color: var(--c-text-highlight); font-weight: 500 } /* Keyword.Constant */
-body .kd { color: var(--c-text-highlight); font-weight: 500 } /* Keyword.Declaration */
-body .kn { color: var(--c-text-highlight); font-weight: 500 } /* Keyword.Namespace */
-body .kp { color: var(--c-text-highlight); font-weight: 500 } /* Keyword.Pseudo */
-body .kr { color: var(--c-text-highlight); font-weight: 500 } /* Keyword.Reserved */
-body .kt { color: var(--c-text-highlight); font-weight: 500 } /* Keyword.Type */
-body .na { color: var(--c-text-highlight); font-weight: 500 } /* Name.Attribute */
-body .nb { color: var(--c-text-highlight); font-weight: 500 } /* Name.Builtin */
-body .nc { color: var(--c-text-highlight); font-weight: 500 } /* Name.Class */
-body .no { color: var(--c-text-highlight); font-weight: 500 } /* Name.Constant */
-body .ni { color: var(--c-text-highlight); font-weight: 500 } /* Name.Entity */
-body .ne { color: var(--c-text-highlight); font-weight: 500 } /* Name.Exception */
-body .nf { color: var(--c-text-highlight); font-weight: 500 } /* Name.Function */
-body .nl { color: var(--c-text-highlight); font-weight: 500 } /* Name.Label */
-body .nx { color: var(--c-text-highlight); font-weight: 500 } /* Name.Other */
-body .py { color: var(--c-text-highlight); font-weight: 500 } /* Name.Property */
-body .nt { color: var(--c-text-highlight); font-weight: 500 } /* Name.Tag */
-body .nv { color: var(--c-text-highlight); font-weight: 500 } /* Name.Variable */
-body .ow { color: var(--c-text-highlight); font-weight: 500 } /* Operator.Word */
-body .bp { color: var(--c-text-highlight); font-weight: 500 } /* Name.Builtin.Pseudo */
-body .ld { color: var(--c-text); font-weight: 600 } /* Literal.Date */
-body .m { color: var(--c-text); font-weight: 600 } /* Literal.Number */
-body .s { color: var(--c-text); font-weight: 600 } /* Literal.String */
-body .mb { color: var(--c-text); font-weight: 600 } /* Literal.Number.Bin */
-body .mf { color: var(--c-text); font-weight: 600 } /* Literal.Number.Float */
-body .mh { color: var(--c-text); font-weight: 600 } /* Literal.Number.Hex */
-body .mi { color: var(--c-text); font-weight: 600 } /* Literal.Number.Integer */
-body .mo { color: var(--c-text); font-weight: 600 } /* Literal.Number.Oct */
-body .sb { color: var(--c-text); font-weight: 600 } /* Literal.String.Backtick */
-body .sc { color: var(--c-text); font-weight: 600 } /* Literal.String.Char */
-body .sd { color: var(--c-text); font-weight: 600 } /* Literal.String.Doc */
-body .s2 { color: var(--c-text); font-weight: 600 } /* Literal.String.Double */
-body .se { color: var(--c-text); font-weight: 600 } /* Literal.String.Escape */
-body .sh { color: var(--c-text); font-weight: 600 } /* Literal.String.Heredoc */
-body .si { color: var(--c-text); font-weight: 600 } /* Literal.String.Interpol */
-body .sx { color: var(--c-text); font-weight: 600 } /* Literal.String.Other */
-body .sr { color: var(--c-text); font-weight: 600 } /* Literal.String.Regex */
-body .s1 { color: var(--c-text); font-weight: 600 } /* Literal.String.Single */
-body .ss { color: var(--c-text); font-weight: 600 } /* Literal.String.Symbol */
-body .il { color: var(--c-text); font-weight: 600 } /* Literal.Number.Integer.Long */
+/* Token styling rules for syntax highlighting */
+.code .hll {}
+.code .c { color: var(--c-text); font-weight: 400 } /* Comment */
+.code .n { color: var(--c-text); font-weight: 400 } /* Name */
+.code .o { color: var(--c-text); font-weight: 400 } /* Operator */
+.code .cm { color: var(--c-text); font-weight: 400 } /* Comment.Multiline */
+.code .cp { color: var(--c-text); font-weight: 400 } /* Comment.Preproc */
+.code .c1 { color: var(--c-text); font-weight: 400 } /* Comment.Single */
+.code .cs { color: var(--c-text); font-weight: 400 } /* Comment.Special */
+.code .nd { color: var(--c-text); font-weight: 400 } /* Name.Decorator */
+.code .nn { color: var(--c-text); font-weight: 400 } /* Name.Namespace */
+.code .vc { color: var(--c-text); font-weight: 400 } /* Name.Variable.Class */
+.code .vg { color: var(--c-text); font-weight: 400 } /* Name.Variable.Global */
+.code .vi { color: var(--c-text); font-weight: 400 } /* Name.Variable.Instance */
+.code .err { color: var(--c-text-highlight); font-weight: 500 } /* Error */
+.code .k { color: var(--c-text-highlight); font-weight: 500 } /* Keyword */
+.code .l { color: var(--c-text-highlight); font-weight: 500 } /* Literal */
+.code .kc { color: var(--c-text-highlight); font-weight: 500 } /* Keyword.Constant */
+.code .kd { color: var(--c-text-highlight); font-weight: 500 } /* Keyword.Declaration */
+.code .kn { color: var(--c-text-highlight); font-weight: 500 } /* Keyword.Namespace */
+.code .kp { color: var(--c-text-highlight); font-weight: 500 } /* Keyword.Pseudo */
+.code .kr { color: var(--c-text-highlight); font-weight: 500 } /* Keyword.Reserved */
+.code .kt { color: var(--c-text-highlight); font-weight: 500 } /* Keyword.Type */
+.code .na { color: var(--c-text-highlight); font-weight: 500 } /* Name.Attribute */
+.code .nb { color: var(--c-text-highlight); font-weight: 500 } /* Name.Builtin */
+.code .nc { color: var(--c-text-highlight); font-weight: 500 } /* Name.Class */
+.code .no { color: var(--c-text-highlight); font-weight: 500 } /* Name.Constant */
+.code .ni { color: var(--c-text-highlight); font-weight: 500 } /* Name.Entity */
+.code .ne { color: var(--c-text-highlight); font-weight: 500 } /* Name.Exception */
+.code .nf { color: var(--c-text-highlight); font-weight: 500 } /* Name.Function */
+.code .nl { color: var(--c-text-highlight); font-weight: 500 } /* Name.Label */
+.code .nx { color: var(--c-text-highlight); font-weight: 500 } /* Name.Other */
+.code .py { color: var(--c-text-highlight); font-weight: 500 } /* Name.Property */
+.code .nt { color: var(--c-text-highlight); font-weight: 500 } /* Name.Tag */
+.code .nv { color: var(--c-text-highlight); font-weight: 500 } /* Name.Variable */
+.code .ow { color: var(--c-text-highlight); font-weight: 500 } /* Operator.Word */
+.code .bp { color: var(--c-text-highlight); font-weight: 500 } /* Name.Builtin.Pseudo */
+.code .ld { color: var(--c-text); font-weight: 600 } /* Literal.Date */
+.code .m { color: var(--c-text); font-weight: 600 } /* Literal.Number */
+.code .s { color: var(--c-text); font-weight: 600 } /* Literal.String */
+.code .mb { color: var(--c-text); font-weight: 600 } /* Literal.Number.Bin */
+.code .mf { color: var(--c-text); font-weight: 600 } /* Literal.Number.Float */
+.code .mh { color: var(--c-text); font-weight: 600 } /* Literal.Number.Hex */
+.code .mi { color: var(--c-text); font-weight: 600 } /* Literal.Number.Integer */
+.code .mo { color: var(--c-text); font-weight: 600 } /* Literal.Number.Oct */
+.code .sb { color: var(--c-text); font-weight: 600 } /* Literal.String.Backtick */
+.code .sc { color: var(--c-text); font-weight: 600 } /* Literal.String.Char */
+.code .sd { color: var(--c-text); font-weight: 600 } /* Literal.String.Doc */
+.code .s2 { color: var(--c-text); font-weight: 600 } /* Literal.String.Double */
+.code .se { color: var(--c-text); font-weight: 600 } /* Literal.String.Escape */
+.code .sh { color: var(--c-text); font-weight: 600 } /* Literal.String.Heredoc */
+.code .si { color: var(--c-text); font-weight: 600 } /* Literal.String.Interpol */
+.code .sx { color: var(--c-text); font-weight: 600 } /* Literal.String.Other */
+.code .sr { color: var(--c-text); font-weight: 600 } /* Literal.String.Regex */
+.code .s1 { color: var(--c-text); font-weight: 600 } /* Literal.String.Single */
+.code .ss { color: var(--c-text); font-weight: 600 } /* Literal.String.Symbol */
+.code .il { color: var(--c-text); font-weight: 600 } /* Literal.Number.Integer.Long */
@media (max-width: 40em) {
nav > div {