main
   1<!DOCTYPE html>
   2<html lang="en">
   3<head>
   4  <meta charset="utf-8">
   5  <meta name="viewport" content="width=device-width, initial-scale=1">
   6  <title>Flux — Design Sandbox</title>
   7  <link rel="stylesheet" href="style.css">
   8</head>
   9<body>
  10  <div class="site-controls"><button class="theme-toggle" id="theme-toggle" title="Toggle dark/light mode">☀️</button></div>
  11  <article>
  12
  13    <!-- ============================================================
  14         SECTION 1: TYPOGRAPHY & DESIGN FUNDAMENTALS
  15         ============================================================ -->
  16
  17    <h1>Flux — Design Sandbox</h1>
  18    <p class="subtitle">Exploring typography, rhythm, and stream design for vincent.demeester.fr</p>
  19
  20    <nav class="nav-toc">
  21      <a href="#typography">Typography</a> ·
  22      <a href="#rhythm">Rhythm</a> ·
  23      <a href="#components">Components</a> ·
  24      <a href="#stream">Stream</a> ·
  25      <a href="#tufte">Tufte</a> ·
  26      <a href="#colors">Colors</a>
  27    </nav>
  28
  29    <h2 id="typography"><a class="anchor" href="#typography">§</a>Typography Specimen</h2>
  30
  31    <div class="specimen specimen-serif">
  32      <div class="label">Body — Serif (Iowan Old Style / Palatino)</div>
  33      <p class="text-base">The quick brown fox jumps over the lazy dog. 0123456789</p>
  34      <p class="text-base text-italic">The quick brown fox jumps over the lazy dog. — italic</p>
  35      <p class="text-base text-bold">The quick brown fox jumps over the lazy dog. — bold</p>
  36    </div>
  37
  38    <div class="specimen specimen-sans">
  39      <div class="label">Headings — Sans (Seravek / Gill Sans)</div>
  40      <p class="text-base">The quick brown fox jumps over the lazy dog. 0123456789</p>
  41      <p class="text-base text-semibold">The quick brown fox jumps over the lazy dog. — semibold</p>
  42    </div>
  43
  44    <div class="specimen specimen-mono">
  45      <div class="label">Code — Mono (Cascadia Code / Source Code Pro)</div>
  46      <p class="text-sm">func main() { fmt.Println("Hello, flux!") } // 0xDEAD</p>
  47    </div>
  48
  49    <h3>Heading Sizes</h3>
  50
  51    <div class="specimen specimen-headings">
  52      <h1>H1 — Page Title (2rem)</h1>
  53      <h2>H2 — Section Heading (1.4rem)</h2>
  54      <h3>H3 — Subsection (1.15rem, italic)</h3>
  55      <p>Body text at 18px base size, 1.6 line-height = 28.8px rhythm unit.</p>
  56    </div>
  57
  58    <h3>Dropcap</h3>
  59
  60    <p class="dropcap">Gardens can be very personal and full of whimsy or a garden can be a source of food and substance. This is my personal space on the World Wide Web. It is meant to be simple, <em>modest</em> and <strong>persistent</strong> — by persistent, it means that I am trying to not break URIs. The list below is a "selection" of some content.</p>
  61
  62    <h3>Small Caps</h3>
  63
  64    <p>This website uses <span class="smallcaps">Tufte CSS</span> as its foundation, taking cues from <span class="smallcaps">Edward Tufte</span>'s principles of data-ink ratio and information density. The <span class="smallcaps">HTML</span> is semantic and the <span class="smallcaps">CSS</span> is minimal.</p>
  65
  66    <!-- ============================================================
  67         SECTION 2: LINKS
  68         ============================================================ -->
  69
  70    <h2 id="links"><a class="anchor" href="#links">§</a>Link Styles</h2>
  71
  72    <p>Links are styled following <a href="https://adactio.com/journal/22084">Adactio's guidance</a>: subtle underline offset, thin decoration, translucent color. Hover reveals full underline. Compare:</p>
  73
  74    <ul>
  75      <li><a href="#">Default styled link</a><code>text-underline-offset: 0.15em</code></li>
  76      <li><a href="#" class="link-thick">Thicker underline</a><code>text-decoration-thickness: 2px</code></li>
  77      <li><a href="#" class="link-offset">More offset</a><code>text-underline-offset: 0.3em</code></li>
  78      <li><a href="#" class="link-accent">Accent-colored underline</a><code>text-decoration-color: accent</code></li>
  79      <li><a href="#" class="link-dotted">Dotted underline</a><code>text-decoration-style: dotted</code></li>
  80    </ul>
  81
  82    <!-- ============================================================
  83         SECTION 3: VERTICAL RHYTHM
  84         ============================================================ -->
  85
  86    <h2 id="rhythm"><a class="anchor" href="#rhythm">§</a>Vertical Rhythm</h2>
  87
  88    <p>All spacing derives from <code>--lh</code> (one line-height unit = 28.8px at 18px/1.6). Following <a href="https://pawelgrzybek.com/vertical-rhythm-using-css-lh-and-rlh-units/">Pawel Grzybek's approach</a> with <code>lh</code> units, and <a href="https://christiantietze.de">Christian Tietze's</a> CSS custom properties for consistency.</p>
  89
  90    <p>Paragraph spacing: <code>1lh</code> (one line). Heading top margin: <code>2lh</code>. This keeps text on a consistent baseline grid, which your eye perceives as calm and ordered even without consciously noticing it.</p>
  91
  92    <p><a href="https://practicaltypography.com/line-spacing.html">Butterick says</a>: optimal line spacing is 120–145% of point size. We use 160% (1.6) — generous but not loose, good for long-form reading on screens.</p>
  93
  94    <!-- ============================================================
  95         SECTION 4: BLOCK ELEMENTS
  96         ============================================================ -->
  97
  98    <h2 id="components"><a class="anchor" href="#components">§</a>Block Elements</h2>
  99
 100    <h3>Blockquote — Standard</h3>
 101
 102    <blockquote>
 103      <p>While not everybody has or works in a dirt garden, we all share a familiarity with the idea of what a garden is. A garden is usually a place where things grow.</p>
 104      <cite>Joel Hooks</cite>
 105    </blockquote>
 106
 107    <h3>Blockquote — Epigraph</h3>
 108
 109    <blockquote class="epigraph">
 110      <p>“The phrase “digital garden” is a metaphor for thinking about writing and creating that focuses less on the resulting “content”, and more on the process, care, and craft it takes to get there.”</p>
 111      <cite>Joel Hooks</cite>
 112    </blockquote>
 113
 114    <h3>Blockquote — Pullquote</h3>
 115
 116    <blockquote class="pullquote">
 117      <p>If everything is highlighted, nothing is highlighted.</p>
 118      <cite>Nikita Tonsky</cite>
 119    </blockquote>
 120
 121    <h3>Blockquote — Nested / Multi-paragraph</h3>
 122
 123    <blockquote>
 124      <p>Minimalism helps one focus on the content. Anything besides the content is distraction and not design.</p>
 125      <p>‘Attention!’, as Ikkyu would say.</p>
 126      <cite>Gwern</cite>
 127    </blockquote>
 128
 129    <h3>Code — Tonsky-style Syntax Highlighting</h3>
 130
 131    <p>Following <a href="https://tonsky.me/blog/syntax-highlighting/">Tonsky’s principles</a>: only 4 semantic categories get color. <span class="hl-pill hl-pill-string">Strings (green bg)</span>, <span class="hl-pill hl-pill-comment">comments (warm bg)</span>, <span class="hl-pill hl-pill-def">definitions (blue bg)</span>, and <span class="hl-pill-constant">constants (purple text)</span>. Everything else stays default. No bold.</p>
 132
 133    <h4>Go</h4>
 134
 135    <pre><code><span class="cmt">// Entry represents a single item in the flux stream.</span>
 136<span class="cmt">// It can be a GitHub PR, a bookmark, a note, etc.</span>
 137<span class="k">type</span> <span class="fn-def">Entry</span> <span class="k">struct</span> {
 138    ID     <span class="typ">string</span>    <span class="str">`json:"id"`</span>
 139    Kind   <span class="typ">string</span>    <span class="str">`json:"kind"`</span>
 140    Title  <span class="typ">string</span>    <span class="str">`json:"title"`</span>
 141    URL    <span class="typ">string</span>    <span class="str">`json:"url"`</span>
 142    Date   <span class="typ">time.Time</span> <span class="str">`json:"date"`</span>
 143    Tags   []<span class="typ">string</span>  <span class="str">`json:"tags"`</span>
 144    Body   <span class="typ">string</span>    <span class="str">`json:"body,omitempty"`</span>
 145}
 146
 147<span class="k">func</span> <span class="fn-def">NewEntry</span>(kind <span class="typ">string</span>, title <span class="typ">string</span>, url <span class="typ">string</span>) *<span class="typ">Entry</span> {
 148    <span class="k">return</span> &amp;<span class="typ">Entry</span>{
 149        ID:    generateID(kind, url),
 150        Kind:  kind,
 151        Title: title,
 152        URL:   url,
 153        Date:  time.Now(),
 154    }
 155}
 156
 157<span class="k">const</span> (
 158    KindGitHubPR    = <span class="str">"github-pr"</span>
 159    KindBookmark    = <span class="str">"bookmark"</span>
 160    MaxRetries      = <span class="num">3</span>
 161    DefaultTimeout  = <span class="num">30</span> * time.Second
 162    EnableCache     = <span class="const">true</span>
 163)</code></pre>
 164
 165    <h4>Python</h4>
 166
 167    <pre><code><span class="k">import</span> json
 168<span class="k">from</span> pathlib <span class="k">import</span> Path
 169<span class="k">from</span> datetime <span class="k">import</span> datetime
 170
 171<span class="cmt"># Tonsky: comments deserve bright color.</span>
 172<span class="cmt"># Good comments ADD to code.</span>
 173
 174<span class="k">class</span> <span class="fn-def">FluxAggregator</span>:
 175    <span class="str">"""Merge entries from all sources into a single feed."""</span>
 176
 177    <span class="k">def</span> <span class="fn-def">__init__</span>(self, cache_dir: <span class="typ">Path</span>, max_entries: <span class="typ">int</span> = <span class="num">500</span>):
 178        self.cache_dir = cache_dir
 179        self.max_entries = max_entries
 180        self.sources: list[<span class="typ">Source</span>] = []
 181        self._last_run: <span class="typ">datetime</span> | <span class="const">None</span> = <span class="const">None</span>
 182
 183    <span class="k">def</span> <span class="fn-def">generate</span>(self) -&gt; list[<span class="typ">dict</span>]:
 184        <span class="str">"""Fetch from all sources, merge, deduplicate, sort."""</span>
 185        entries = []
 186        <span class="k">for</span> source <span class="k">in</span> self.sources:
 187            new = source.fetch(since=self._last_run)
 188            entries.extend(new)
 189
 190        <span class="cmt"># Sort reverse-chronological, deduplicate by ID</span>
 191        seen = set()
 192        result = []
 193        <span class="k">for</span> e <span class="k">in</span> sorted(entries, key=<span class="k">lambda</span> x: x[<span class="str">"date"</span>], reverse=<span class="const">True</span>):
 194            <span class="k">if</span> e[<span class="str">"id"</span>] <span class="k">not</span> <span class="k">in</span> seen:
 195                seen.add(e[<span class="str">"id"</span>])
 196                result.append(e)
 197
 198        <span class="k">return</span> result[:<span class="num">500</span>]</code></pre>
 199
 200    <h4>Nix</h4>
 201
 202    <pre><code>{ pkgs, lib, config, ... }:
 203
 204<span class="k">let</span>
 205  <span class="cmt"># Build the flux binary from source</span>
 206  <span class="fn-def">flux</span> = pkgs.buildGoModule <span class="k">rec</span> {
 207    pname = <span class="str">"flux"</span>;
 208    version = <span class="str">"0.1.0"</span>;
 209    src = ../.;
 210    vendorHash = <span class="str">"sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="</span>;
 211  };
 212<span class="k">in</span> {
 213  systemd.services.flux-generate = {
 214    description = <span class="str">"Generate flux stream"</span>;
 215    serviceConfig = {
 216      Type = <span class="str">"oneshot"</span>;
 217      ExecStart = <span class="str">"${flux}/bin/flux generate"</span>;
 218      User = <span class="str">"www"</span>;
 219    };
 220  };
 221
 222  systemd.timers.flux-generate = {
 223    wantedBy = [ <span class="str">"timers.target"</span> ];
 224    timerConfig.OnCalendar = <span class="str">"hourly"</span>;
 225    timerConfig.Persistent = <span class="const">true</span>;
 226  };
 227}</code></pre>
 228
 229    <div class="callout">
 230      <div class="callout-title">Tonsky’s 4-color rule</div>
 231      <p>Notice how scannable the code becomes. Your eye immediately finds strings, comments, definitions, and constants. Keywords like <code>func</code>, <code>if</code>, <code>return</code> are just code — you never search for a keyword, you search for the name after it.</p>
 232    </div>
 233
 234    <h3>Callouts</h3>
 235
 236    <div class="callout callout-note">
 237      <div class="callout-title">Note</div>
 238      <p>A general note or annotation. Use for supplementary information the reader might find useful but isn’t essential to the main flow.</p>
 239    </div>
 240
 241    <div class="callout callout-tip">
 242      <div class="callout-title">Tip</div>
 243      <p>Use <code>flux generate --dry-run</code> to preview what entries would be added without writing any files. Handy when adding a new source.</p>
 244    </div>
 245
 246    <div class="callout callout-info">
 247      <div class="callout-title">Info</div>
 248      <p>The JSON Feed spec v1.1 allows an <code>_extension</code> namespace for custom fields. We use <code>_flux</code> to store entry kind and source metadata.</p>
 249    </div>
 250
 251    <div class="callout callout-warning">
 252      <div class="callout-title">Warning</div>
 253      <p>GitHub’s search API is limited to 30 requests/minute even with authentication. The cache mechanism avoids hitting this on every run, but a full re-fetch (<code>flux cache clear</code>) will be slow.</p>
 254    </div>
 255
 256    <div class="callout callout-danger">
 257      <div class="callout-title">Danger</div>
 258      <p>Never commit your <code>GITHUB_TOKEN</code> to the repository. Use environment variables or a secrets manager. The token has read access to all your public activity.</p>
 259    </div>
 260
 261    <div class="callout callout-design">
 262      <div class="callout-title">Design Note</div>
 263      <p>This callout style is borrowed from <a href="https://craftinginterpreters.com">Crafting Interpreters</a>. Each type has a left border, subtle background wash, and icon prefix. Colors follow the same CSS variable system — they adapt to light and dark mode automatically.</p>
 264    </div>
 265
 266    <h3>Table</h3>
 267
 268    <table>
 269      <thead>
 270        <tr>
 271          <th>Reference</th>
 272          <th>Key Takeaway</th>
 273          <th>Applied Here</th>
 274        </tr>
 275      </thead>
 276      <tbody>
 277        <tr>
 278          <td>Gwern</td>
 279          <td>Aesthetically-pleasing minimalism</td>
 280          <td>Grayscale palette, dropcaps</td>
 281        </tr>
 282        <tr>
 283          <td>Tufte CSS</td>
 284          <td>Margins are for thinking</td>
 285          <td>Sidenotes in margin</td>
 286        </tr>
 287        <tr>
 288          <td>Crafting Interpreters</td>
 289          <td>Book-quality web typography</td>
 290          <td>Serif body, § anchors</td>
 291        </tr>
 292        <tr>
 293          <td>Miessler</td>
 294          <td>Warm parchment, premium fonts</td>
 295          <td>Color palette</td>
 296        </tr>
 297        <tr>
 298          <td>Tietze</td>
 299          <td>CSS custom props for rhythm</td>
 300          <td><code>--lh</code> system</td>
 301        </tr>
 302        <tr>
 303          <td>Larlet</td>
 304          <td>Seasonal list markers</td>
 305          <td><code>body:has(time)</code> trick</td>
 306        </tr>
 307      </tbody>
 308    </table>
 309
 310    <h3>Figures &amp; Images</h3>
 311
 312    <h4>Standard figure with caption</h4>
 313
 314    <figure>
 315      <img src="placeholder-wide.svg" alt="A landscape placeholder showing layout elements">
 316      <figcaption><strong>Figure 1.</strong> A standard figure occupies the full content width. The caption uses the heading font, italic, with a bold label prefix. Good for diagrams, charts, and photographs.</figcaption>
 317    </figure>
 318
 319    <h4>Margin figure (Tufte-style)</h4>
 320
 321    <figure class="margin">
 322      <img src="placeholder-square.svg" alt="A small square placeholder">
 323      <figcaption><strong>Fig. 2.</strong> A margin figure floats in the right margin alongside the text, just like sidenotes. On mobile, it becomes full-width.</figcaption>
 324    </figure>
 325
 326    <p>This paragraph has a margin figure floating next to it. The margin figure is a Tufte signature — small supporting images placed in the margin where the reader’s eye can find them without breaking the reading flow. They share the same space as sidenotes, so you should avoid placing both on the same paragraph.</p>
 327
 328    <p>The margin figure works especially well for small diagrams, icons, logos, or thumbnail images that support but don’t dominate the text. On narrow screens, it collapses inline.</p>
 329
 330    <h4>Bordered figure (framed)</h4>
 331
 332    <figure class="bordered">
 333      <img src="placeholder-wide.svg" alt="A framed placeholder">
 334      <figcaption><strong>Figure 3.</strong> A bordered figure adds a subtle frame — useful for screenshots or images that blend into the background without a border.</figcaption>
 335    </figure>
 336
 337    <h4>Screenshot (drop shadow)</h4>
 338
 339    <figure class="screenshot">
 340      <img src="placeholder-wide.svg" alt="A screenshot-style placeholder">
 341      <figcaption><strong>Figure 4.</strong> Screenshots get a drop shadow and rounded corners, making them feel like floating windows. The shadow adapts to light/dark mode.</figcaption>
 342    </figure>
 343
 344    <h4>Full-width figure</h4>
 345
 346    <figure class="fullwidth">
 347      <img src="placeholder-wide.svg" alt="A full-width placeholder">
 348      <figcaption><strong>Figure 5.</strong> A full-width figure stretches across the content column <em>and</em> the margin area. Good for panoramic images, wide diagrams, or data visualizations that need horizontal space.</figcaption>
 349    </figure>
 350
 351    <h4>Figure grid — 2 columns</h4>
 352
 353    <div class="figure-grid cols-2">
 354      <figure>
 355        <img src="placeholder-square.svg" alt="Grid image 1">
 356        <figcaption>Before</figcaption>
 357      </figure>
 358      <figure>
 359        <img src="placeholder-square.svg" alt="Grid image 2">
 360        <figcaption>After</figcaption>
 361      </figure>
 362    </div>
 363
 364    <h4>Figure grid — 3 columns</h4>
 365
 366    <div class="figure-grid cols-3">
 367      <figure>
 368        <img src="placeholder-tall.svg" alt="Grid image A">
 369        <figcaption>v1.0</figcaption>
 370      </figure>
 371      <figure>
 372        <img src="placeholder-tall.svg" alt="Grid image B">
 373        <figcaption>v2.0</figcaption>
 374      </figure>
 375      <figure>
 376        <img src="placeholder-tall.svg" alt="Grid image C">
 377        <figcaption>v3.0 (current)</figcaption>
 378      </figure>
 379    </div>
 380
 381    <h4>No caption (bare image)</h4>
 382
 383    <figure>
 384      <img src="placeholder-wide.svg" alt="An image without a caption">
 385    </figure>
 386
 387    <div class="callout callout-design">
 388      <div class="callout-title">Design Note</div>
 389      <p>Org-mode exports images as <code>&lt;figure&gt;</code> + <code>&lt;figcaption&gt;</code> via <code>#+CAPTION:</code>. The margin figure maps to Tufte’s <code>#+ATTR_HTML: :class margin</code>. The grid layout would need manual HTML or a Soupault transform.</p>
 390    </div>
 391
 392    <h3>Lists — Marker Options</h3>
 393
 394    <p>Pick a list marker style. Current is ✧ (white star).</p>
 395
 396    <ul>
 397      <li>This is the current marker (✧ white star)</li>
 398      <li>A second item to see the rhythm</li>
 399      <li>And a third with <a href="#">a link</a> and <code>inline code</code></li>
 400    </ul>
 401
 402    <h4>Alternatives</h4>
 403
 404    <ul class="marker-reference">
 405      <li>Reference mark (※)</li>
 406      <li>Japanese-influenced</li>
 407      <li>Unusual but elegant</li>
 408    </ul>
 409
 410    <ul class="marker-hyphen">
 411      <li>Hyphen bullet (⁃)</li>
 412      <li>Like a dash but shorter</li>
 413      <li>Understated</li>
 414    </ul>
 415
 416    <ul class="marker-endash">
 417      <li>En dash (–)</li>
 418      <li>Typographically clean</li>
 419      <li>Common in European typography</li>
 420    </ul>
 421
 422    <ul class="marker-hedera">
 423      <li>Hedera (❧)</li>
 424      <li>Typographic fleuron</li>
 425      <li>Distinctive, bookish</li>
 426    </ul>
 427
 428    <ol>
 429      <li>Ordered lists stay numbered</li>
 430      <li>Nothing fancy here</li>
 431      <li>Just clean spacing</li>
 432    </ol>
 433
 434    <!-- ============================================================
 435         SECTION 5: TUFTE SIDENOTES
 436         ============================================================ -->
 437
 438    <h2 id="tufte"><a class="anchor" href="#tufte">§</a>Tufte Sidenotes</h2>
 439
 440    <p>
 441      Edward Tufte's distinctive style places supplementary information in the margin
 442      rather than in footnotes at the bottom of the page.<span class="sidenote-number"></span>
 443      <span class="sidenote">This is a numbered sidenote. On wide screens, it floats in the right margin. On narrow screens, it becomes an inline block below the paragraph. The approach follows <a href="https://gwern.net/sidenote">Gwern's sidenote analysis</a>.</span>
 444      This keeps the reader's eye on the page instead of bouncing back and forth.
 445      The idea is simple: if something is worth saying, say it nearby.
 446    </p>
 447
 448    <p>
 449      Margin notes are similar but unnumbered — useful for brief asides
 450      or contextual links.<span class="marginnote">This is an unnumbered margin note. Kenneth Reitz calls this "margins are for thinking." These work beautifully with Tufte CSS and ox-tufte.</span>
 451      They provide a visual rhythm to the page, filling what would otherwise
 452      be empty whitespace with relevant context.
 453    </p>
 454
 455    <p>
 456      A paragraph without sidenotes, for contrast. Notice how the text column stays at a comfortable reading width of about 640px (roughly 65 characters per line), which typographers consider optimal.<span class="sidenote-number"></span>
 457      <span class="sidenote">Robert Bringhurst recommends 45–75 characters per line. We aim for ~65. This is also what Gwern targets.</span>
 458      The margin area is a bonus, not a crutch.
 459    </p>
 460
 461    <!-- ============================================================
 462         SECTION 6: COLORS
 463         ============================================================ -->
 464
 465    <h2 id="colors"><a class="anchor" href="#colors">§</a>Color Palette</h2>
 466
 467    <p>Light mode draws from Daniel Miessler's warm parchment. Dark mode from Beat's stream. Both shift the same CSS variables.</p>
 468
 469    <h3>Accent Color Picker</h3>
 470
 471    <p>Click a swatch to preview it as the accent color across the whole page — dropcaps, blockquote borders, callout labels, pullquotes, release cards, sidenote numbers, and § anchors all update live.</p>
 472
 473    <div class="accent-picker">
 474
 475      <div class="accent-group">
 476        <div class="accent-group-label">Greens — Deep</div>
 477        <div class="palette">
 478          <div class="accent-swatch" data-light="#1b4332" data-dark="#40916c" title="Evergreen"></div>
 479          <div class="accent-swatch" data-light="#2d6a4f" data-dark="#52b788" title="Forest"></div>
 480          <div class="accent-swatch" data-light="#245e3a" data-dark="#4a9e6a" title="Pine"></div>
 481          <div class="accent-swatch" data-light="#2b5f3a" data-dark="#5aad70" title="Spruce"></div>
 482          <div class="accent-swatch" data-light="#1a5c3a" data-dark="#3da870" title="Hunter"></div>
 483          <div class="accent-swatch" data-light="#2e4a3e" data-dark="#5a8a72" title="Holly"></div>
 484        </div>
 485      </div>
 486
 487      <div class="accent-group">
 488        <div class="accent-group-label">Greens — Mid</div>
 489        <div class="palette">
 490          <div class="accent-swatch" data-light="#4a7c59" data-dark="#6aab7b" title="Sage"></div>
 491          <div class="accent-swatch" data-light="#588157" data-dark="#7dba6d" title="Fern"></div>
 492          <div class="accent-swatch" data-light="#3a7d5e" data-dark="#5cb88a" title="Jade"></div>
 493          <div class="accent-swatch" data-light="#4d7a4d" data-dark="#72b572" title="Moss"></div>
 494          <div class="accent-swatch" data-light="#527a52" data-dark="#7ab87a" title="Clover"></div>
 495          <div class="accent-swatch" data-light="#457b5e" data-dark="#68b088" title="Basil"></div>
 496          <div class="accent-swatch" data-light="#3d7c47" data-dark="#60b06a" title="Leaf"></div>
 497          <div class="accent-swatch" data-light="#4f7942" data-dark="#7ab568" title="Ivy"></div>
 498        </div>
 499      </div>
 500
 501      <div class="accent-group">
 502        <div class="accent-group-label">Greens — Soft</div>
 503        <div class="palette">
 504          <div class="accent-swatch" data-light="#6b8f71" data-dark="#95b89a" title="Lichen"></div>
 505          <div class="accent-swatch" data-light="#7a9e7a" data-dark="#a0c8a0" title="Celadon"></div>
 506          <div class="accent-swatch" data-light="#6a8e6a" data-dark="#90b890" title="Laurel"></div>
 507          <div class="accent-swatch" data-light="#698b69" data-dark="#8cb88c" title="Pistachio"></div>
 508          <div class="accent-swatch" data-light="#789878" data-dark="#a2c2a2" title="Mint"></div>
 509          <div class="accent-swatch" data-light="#839e76" data-dark="#aac89a" title="Willow"></div>
 510        </div>
 511      </div>
 512
 513      <div class="accent-group">
 514        <div class="accent-group-label">Teal &amp; Cyan</div>
 515        <div class="palette">
 516          <div class="accent-swatch" data-light="#006d5b" data-dark="#40b89a" title="Teal"></div>
 517          <div class="accent-swatch" data-light="#005f73" data-dark="#4aabb8" title="Petrol"></div>
 518          <div class="accent-swatch" data-light="#2a7a6a" data-dark="#52baa5" title="Verdigris"></div>
 519          <div class="accent-swatch" data-light="#0a6c6c" data-dark="#42a8a8" title="Deep Teal"></div>
 520          <div class="accent-swatch" data-light="#3a8a7a" data-dark="#60c0aa" title="Seafoam"></div>
 521          <div class="accent-swatch" data-light="#1a7a7a" data-dark="#50b5b5" title="Lagoon"></div>
 522          <div class="accent-swatch" data-light="#2b6b6b" data-dark="#5aadad" title="Patina"></div>
 523          <div class="accent-swatch" data-light="#3d8888" data-dark="#6abfbf" title="Aqua"></div>
 524        </div>
 525      </div>
 526
 527      <div class="accent-group">
 528        <div class="accent-group-label">Blues — Cool</div>
 529        <div class="palette">
 530          <div class="accent-swatch" data-light="#2a6496" data-dark="#7daacd" title="Steel"></div>
 531          <div class="accent-swatch" data-light="#3a5a8c" data-dark="#7a9abf" title="Denim"></div>
 532          <div class="accent-swatch" data-light="#4a6fa5" data-dark="#8aadd0" title="Wedgwood"></div>
 533          <div class="accent-swatch" data-light="#2c5282" data-dark="#63a0d4" title="Navy"></div>
 534          <div class="accent-swatch" data-light="#3f578f" data-dark="#7a92c2" title="Slate"></div>
 535          <div class="accent-swatch" data-light="#1a5276" data-dark="#5a98c0" title="Marine"></div>
 536        </div>
 537      </div>
 538
 539      <div class="accent-group">
 540        <div class="accent-group-label">Blues — Warm</div>
 541        <div class="palette">
 542          <div class="accent-swatch" data-light="#3b6b8a" data-dark="#70a5c5" title="Fjord"></div>
 543          <div class="accent-swatch" data-light="#4a7a95" data-dark="#80acc5" title="Sky"></div>
 544          <div class="accent-swatch" data-light="#3a7ca5" data-dark="#6aafcf" title="Cornflower"></div>
 545          <div class="accent-swatch" data-light="#456a82" data-dark="#7aa0b8" title="Horizon"></div>
 546          <div class="accent-swatch" data-light="#506a7e" data-dark="#85a0b5" title="Storm"></div>
 547          <div class="accent-swatch" data-light="#4a6878" data-dark="#80a0b2" title="Rainy"></div>
 548        </div>
 549      </div>
 550
 551      <div class="accent-group">
 552        <div class="accent-group-label">Warm Earth — Gold &amp; Amber</div>
 553        <div class="palette">
 554          <div class="accent-swatch" data-light="#6f5500" data-dark="#d4aa3c" title="Gold"></div>
 555          <div class="accent-swatch" data-light="#9a7b2a" data-dark="#d4aa3c" title="Ochre"></div>
 556          <div class="accent-swatch" data-light="#8a6d3b" data-dark="#c4a265" title="Honey"></div>
 557          <div class="accent-swatch" data-light="#7a6520" data-dark="#c0a040" title="Brass"></div>
 558          <div class="accent-swatch" data-light="#8a7030" data-dark="#c8a850" title="Wheat"></div>
 559          <div class="accent-swatch" data-light="#a08028" data-dark="#d8b848" title="Saffron"></div>
 560          <div class="accent-swatch" data-light="#7a6838" data-dark="#b8a060" title="Khaki"></div>
 561        </div>
 562      </div>
 563
 564      <div class="accent-group">
 565        <div class="accent-group-label">Warm Earth — Brown &amp; Umber</div>
 566        <div class="palette">
 567          <div class="accent-swatch" data-light="#7a5c3a" data-dark="#b89060" title="Umber"></div>
 568          <div class="accent-swatch" data-light="#6b4c30" data-dark="#a88058" title="Walnut"></div>
 569          <div class="accent-swatch" data-light="#8b4513" data-dark="#c0763a" title="Saddle"></div>
 570          <div class="accent-swatch" data-light="#704828" data-dark="#a87850" title="Bark"></div>
 571          <div class="accent-swatch" data-light="#5c3d2e" data-dark="#9a7560" title="Chestnut"></div>
 572          <div class="accent-swatch" data-light="#6a5040" data-dark="#a58870" title="Truffle"></div>
 573        </div>
 574      </div>
 575
 576      <div class="accent-group">
 577        <div class="accent-group-label">Orange &amp; Terracotta</div>
 578        <div class="palette">
 579          <div class="accent-swatch" data-light="#c05a30" data-dark="#e08858" title="Terracotta"></div>
 580          <div class="accent-swatch" data-light="#a0522d" data-dark="#d48a5a" title="Sienna"></div>
 581          <div class="accent-swatch" data-light="#b86830" data-dark="#e09858" title="Copper"></div>
 582          <div class="accent-swatch" data-light="#c46a28" data-dark="#e8a050" title="Tangerine"></div>
 583          <div class="accent-swatch" data-light="#a05040" data-dark="#d08070" title="Clay"></div>
 584          <div class="accent-swatch" data-light="#9a5535" data-dark="#cc8860" title="Cinnamon"></div>
 585          <div class="accent-swatch" data-light="#b8602a" data-dark="#e09050" title="Pumpkin"></div>
 586        </div>
 587      </div>
 588
 589      <div class="accent-group">
 590        <div class="accent-group-label">Reds — Deep</div>
 591        <div class="palette">
 592          <div class="accent-swatch" data-light="#8a3a3a" data-dark="#c07070" title="Wine"></div>
 593          <div class="accent-swatch" data-light="#b44" data-dark="#dc755c" title="Rust"></div>
 594          <div class="accent-swatch" data-light="#a0413a" data-dark="#d47a6a" title="Brick"></div>
 595          <div class="accent-swatch" data-light="#7a2e2e" data-dark="#b86060" title="Garnet"></div>
 596          <div class="accent-swatch" data-light="#8b3040" data-dark="#c06878" title="Cranberry"></div>
 597          <div class="accent-swatch" data-light="#6b2a2a" data-dark="#a86060" title="Maroon"></div>
 598          <div class="accent-swatch" data-light="#943838" data-dark="#cc7070" title="Carmine"></div>
 599        </div>
 600      </div>
 601
 602      <div class="accent-group">
 603        <div class="accent-group-label">Reds — Soft &amp; Rose</div>
 604        <div class="palette">
 605          <div class="accent-swatch" data-light="#9a5a60" data-dark="#c89098" title="Rose"></div>
 606          <div class="accent-swatch" data-light="#8a5068" data-dark="#c08898" title="Dusty Rose"></div>
 607          <div class="accent-swatch" data-light="#a06070" data-dark="#d098a0" title="Blush"></div>
 608          <div class="accent-swatch" data-light="#906060" data-dark="#c09090" title="Mauve Rose"></div>
 609          <div class="accent-swatch" data-light="#985a68" data-dark="#c89098" title="Peony"></div>
 610        </div>
 611      </div>
 612
 613      <div class="accent-group">
 614        <div class="accent-group-label">Purples</div>
 615        <div class="palette">
 616          <div class="accent-swatch" data-light="#6c5b7b" data-dark="#a090b0" title="Mauve"></div>
 617          <div class="accent-swatch" data-light="#5b4a8a" data-dark="#8a7ab8" title="Iris"></div>
 618          <div class="accent-swatch" data-light="#7a5080" data-dark="#b080b8" title="Plum"></div>
 619          <div class="accent-swatch" data-light="#531ab6" data-dark="#b6a0ff" title="Violet"></div>
 620          <div class="accent-swatch" data-light="#6a4c8a" data-dark="#9a80b8" title="Amethyst"></div>
 621          <div class="accent-swatch" data-light="#5a3a7a" data-dark="#9070b0" title="Grape"></div>
 622          <div class="accent-swatch" data-light="#7a5a8a" data-dark="#aa90b8" title="Lavender"></div>
 623          <div class="accent-swatch" data-light="#604880" data-dark="#9580b5" title="Wisteria"></div>
 624        </div>
 625      </div>
 626
 627      <div class="accent-group">
 628        <div class="accent-group-label">Neutrals &amp; Greys</div>
 629        <div class="palette">
 630          <div class="accent-swatch" data-light="#5a6a6a" data-dark="#90a0a0" title="Graphite"></div>
 631          <div class="accent-swatch" data-light="#4a5568" data-dark="#8090a0" title="Pewter"></div>
 632          <div class="accent-swatch" data-light="#5f6a60" data-dark="#90a090" title="Sage Grey"></div>
 633          <div class="accent-swatch" data-light="#606068" data-dark="#9898a0" title="Charcoal"></div>
 634          <div class="accent-swatch" data-light="#5a5a68" data-dark="#9090a0" title="Flint"></div>
 635          <div class="accent-swatch" data-light="#555" data-dark="#999" title="Iron"></div>
 636        </div>
 637      </div>
 638
 639      <p class="accent-current">Current accent: <span id="accent-label">#4a7c59</span></p>
 640
 641    </div>
 642
 643    <h3>Light Mode</h3>
 644
 645    <div class="palette">
 646      <div class="swatch" data-bg="#f4f1eb">bg</div>
 647      <div class="swatch" data-bg="#eae7e0">bg-alt</div>
 648      <div class="swatch" data-bg="#fff">card</div>
 649      <div class="swatch" data-bg="#2c2c2c">text</div>
 650      <div class="swatch" data-bg="#6b6b6b">muted</div>
 651      <div class="swatch" data-bg="#999">faint</div>
 652      <div class="swatch" data-bg="#4a7c59">accent</div>
 653      <div class="swatch" data-bg="#2a6496">link</div>
 654      <div class="swatch" data-bg="#2d8a56">new</div>
 655      <div class="swatch" data-bg="#7a6c2a">updated</div>
 656    </div>
 657
 658    <h3>Dark Mode</h3>
 659
 660    <div class="palette">
 661      <div class="swatch" data-bg="#1a1a1f">bg</div>
 662      <div class="swatch" data-bg="#222228">bg-alt</div>
 663      <div class="swatch" data-bg="rgb(51,51,57)">card</div>
 664      <div class="swatch" data-bg="#d4d4d8">text</div>
 665      <div class="swatch" data-bg="#9ca3af">muted</div>
 666      <div class="swatch" data-bg="#6b7280">faint</div>
 667      <div class="swatch" data-bg="#6aab7b">accent</div>
 668      <div class="swatch" data-bg="#7daacd">link</div>
 669      <div class="swatch" data-bg="#4ade80">new</div>
 670      <div class="swatch" data-bg="#d4aa3c">updated</div>
 671    </div>
 672
 673    <div class="callout">
 674      <div class="callout-title">Solarized Alternative</div>
 675      <p>Christian Tietze uses Solarized colors: <code>#002b36</code> dark, <code>#fdf5e6</code> cream, <code>#268bd2</code> blue. Could be an alternative palette — more muted, less warm than the Miessler-inspired one above.</p>
 676    </div>
 677
 678    <!-- ============================================================
 679         SECTION 7: FLUX STREAM — THE MAIN EVENT
 680         ============================================================ -->
 681
 682    <hr>
 683
 684    <div class="flux">
 685      <div class="flux-header">
 686        <h1 id="stream">Flux</h1>
 687        <p class="description">What I'm working on, reading, and thinking about.</p>
 688        <div class="flux-feed-links">
 689          <a href="#">JSON Feed</a>
 690          <a href="#">Atom</a>
 691        </div>
 692      </div>
 693
 694      <div class="year-separator">2026</div>
 695
 696      <!-- === Release Card === -->
 697
 698      <div class="release-card">
 699        <span class="release-project">tektoncd/pipeline</span>
 700        <span class="release-version">v1.11.0</span>
 701        <span class="release-date"> · March 30, 2026</span>
 702      </div>
 703
 704      <!-- === Stream Card (note/TIL) === -->
 705
 706      <div class="stream-card" id="00115">
 707        <a class="card-anchor" href="#00115">§</a>
 708        <div class="card-date">
 709          <time datetime="2026-03-28T14:30:00Z">March 28, 2026</time>
 710        </div>
 711        <div class="card-title">Nix flakes and reproducible Go builds</div>
 712        <div class="card-body">
 713          <p>Today I spent some time figuring out how to pin Go module dependencies
 714            inside a Nix flake. The trick is using <code>buildGoModule</code> with
 715            <code>vendorHash</code> — it fetches and vendors dependencies deterministically.</p>
 716          <p>The <a href="https://nixos.wiki/wiki/Go">NixOS wiki Go page</a> is helpful
 717            but incomplete. Had to read the nixpkgs source to understand
 718            <code>proxyVendor</code>.</p>
 719        </div>
 720        <div class="tags">
 721          <span class="tag">NixOS</span>
 722          <span class="tag">Go</span>
 723          <span class="tag">Flakes</span>
 724        </div>
 725      </div>
 726
 727      <!-- === Page Updates === -->
 728
 729      <a class="page-entry" href="#">
 730        <span class="marker new">+</span>
 731        <span class="page-title">Screen Freezes on Framework 16 with AMD and GNOME</span>
 732        <span class="page-date">Mar 26</span>
 733        <span class="arrow"></span>
 734      </a>
 735
 736      <a class="page-entry" href="#">
 737        <span class="marker updated">~</span>
 738        <span class="page-title">Setting Up A Repo In Under 5 Minutes With Nix</span>
 739        <span class="page-date">Mar 25</span>
 740        <span class="arrow"></span>
 741      </a>
 742
 743      <!-- === Another Stream Card === -->
 744
 745      <div class="stream-card" id="00114">
 746        <a class="card-anchor" href="#00114">§</a>
 747        <div class="card-date">
 748          <time datetime="2026-03-22T10:00:00Z">March 22, 2026</time>
 749        </div>
 750        <div class="card-title">selfhostblocks</div>
 751        <div class="card-body">
 752          <p>Found <a href="https://github.com/ibizaman/selfhostblocks">selfhostblocks</a>
 753            while browsing the Clan gitea. An interesting alternative approach to NixOS
 754            service modules — more opinionated, batteries-included.</p>
 755          <p>Though I'm pretty convinced by Clan so far.</p>
 756        </div>
 757        <div class="tags">
 758          <span class="tag">NixOS</span>
 759          <span class="tag">Selfhosting</span>
 760          <span class="tag">Clan</span>
 761        </div>
 762      </div>
 763
 764      <!-- === GitHub Entries === -->
 765
 766      <div class="stream-card stream-card-compact">
 767        <div class="card-date">
 768          <time datetime="2026-03-20">March 20, 2026</time> · GitHub
 769        </div>
 770
 771        <div class="github-entry">
 772          <span class="gh-icon pr"></span>
 773          <div class="gh-content">
 774            <div class="gh-title"><a href="#">Add matrix parameters support in PipelineRuns</a></div>
 775            <div class="gh-repo">tektoncd/pipeline#8234</div>
 776          </div>
 777          <div class="gh-date">merged</div>
 778        </div>
 779
 780        <div class="github-entry">
 781          <span class="gh-icon pr"></span>
 782          <div class="gh-content">
 783            <div class="gh-title"><a href="#">Fix dashboard routing for nested resources</a></div>
 784            <div class="gh-repo">tektoncd/dashboard#3102</div>
 785          </div>
 786          <div class="gh-date">merged</div>
 787        </div>
 788
 789        <div class="github-entry">
 790          <span class="gh-icon issue"></span>
 791          <div class="gh-content">
 792            <div class="gh-title"><a href="#">Catalog v2 resolver: support for remote bundles</a></div>
 793            <div class="gh-repo">tektoncd/pipeline#8240</div>
 794          </div>
 795          <div class="gh-date">opened</div>
 796        </div>
 797      </div>
 798
 799      <!-- === Bookmark Entry === -->
 800
 801      <div class="stream-card stream-card-compact">
 802        <div class="card-date">
 803          <time datetime="2026-03-18">March 18, 2026</time> · Bookmarks
 804        </div>
 805
 806        <div class="bookmark-entry">
 807          <div class="bm-title"><a href="https://stagex.tools">StageX — Minimal, reproducible container base images</a></div>
 808          <div class="bm-note">Interesting approach to base images, similar philosophy to distroless but with better reproducibility.</div>
 809          <div class="bm-meta">stagex.tools</div>
 810        </div>
 811
 812        <div class="bookmark-entry">
 813          <div class="bm-title"><a href="#">The Bitter Lesson — Rich Sutton</a></div>
 814          <div class="bm-note">The essay that keeps coming back: general methods that leverage computation win over domain-specific engineering.</div>
 815          <div class="bm-meta">incompleteideas.net</div>
 816        </div>
 817      </div>
 818
 819      <!-- === More page updates === -->
 820
 821      <a class="page-entry" href="#">
 822        <span class="marker new">+</span>
 823        <span class="page-title">Domain Name System</span>
 824        <span class="page-date">Mar 15</span>
 825        <span class="arrow"></span>
 826      </a>
 827
 828      <a class="page-entry" href="#">
 829        <span class="marker updated">~</span>
 830        <span class="page-title">(Doom) Emacs Config Bits</span>
 831        <span class="page-date">Mar 14</span>
 832        <span class="arrow"></span>
 833      </a>
 834
 835      <a class="page-entry" href="#">
 836        <span class="marker updated">~</span>
 837        <span class="page-title">Window Switching Made Simple</span>
 838        <span class="page-date">Mar 12</span>
 839        <span class="arrow"></span>
 840      </a>
 841
 842      <!-- === Another note === -->
 843
 844      <div class="stream-card" id="00113">
 845        <a class="card-anchor" href="#00113">§</a>
 846        <div class="card-date">
 847          <time datetime="2026-03-10T18:00:00Z">March 10, 2026</time>
 848        </div>
 849        <div class="card-title">Using <code>dig</code></div>
 850        <div class="card-body">
 851          <p>Just had a small DNS dispute again, and remembered the
 852            <a href="https://jvns.ca/blog/2021/12/04/how-to-use-dig/">post by Julia Evans
 853            on using <code>dig</code></a>. Pretty helpful!</p>
 854          <p>She also wrote a <a href="https://dns-lookup.jvns.ca/">simple DNS lookup tool</a>.</p>
 855        </div>
 856        <div class="tags">
 857          <span class="tag">DNS</span>
 858          <span class="tag">TIL</span>
 859          <span class="tag">Julia Evans</span>
 860        </div>
 861      </div>
 862
 863      <!-- === Release Card === -->
 864
 865      <div class="release-card">
 866        <span class="release-project">tektoncd/triggers</span>
 867        <span class="release-version">v0.30.0</span>
 868        <span class="release-date"> · March 5, 2026</span>
 869      </div>
 870
 871      <!-- === GitHub batch === -->
 872
 873      <div class="stream-card stream-card-compact">
 874        <div class="card-date">
 875          <time datetime="2026-03-04">March 4, 2026</time> · GitHub
 876        </div>
 877
 878        <div class="github-entry">
 879          <span class="gh-icon pr"></span>
 880          <div class="gh-content">
 881            <div class="gh-title"><a href="#">nixos/navidrome: init module</a></div>
 882            <div class="gh-repo">NixOS/nixpkgs#298412</div>
 883          </div>
 884          <div class="gh-date">merged</div>
 885        </div>
 886
 887        <div class="github-entry">
 888          <span class="gh-icon pr"></span>
 889          <div class="gh-content">
 890            <div class="gh-title"><a href="#">pruner: add configurable retention policies</a></div>
 891            <div class="gh-repo">tektoncd/pruner#87</div>
 892          </div>
 893          <div class="gh-date">merged</div>
 894        </div>
 895      </div>
 896
 897      <!-- === Longer note with code === -->
 898
 899      <div class="stream-card" id="00112">
 900        <a class="card-anchor" href="#00112">§</a>
 901        <div class="card-date">
 902          <time datetime="2026-03-01T11:00:00Z">March 1, 2026</time>
 903        </div>
 904        <div class="card-title">Bun ❤️ SQLite</div>
 905        <div class="card-body">
 906          <p>Today I learned that Bun has a
 907            <a href="https://bun.com/docs/runtime/sqlite">SQLite driver built in</a>. That's pretty cool:</p>
 908
 909          <pre><code>import { Database } from 'bun:sqlite'
 910
 911const db = new Database('mydb.sqlite', { create: true })
 912
 913const query = db.query(`
 914  SELECT * FROM users WHERE name = ?
 915`)
 916
 917console.log(query.all('vincent'))</code></pre>
 918        </div>
 919        <div class="tags">
 920          <span class="tag">TIL</span>
 921          <span class="tag">Bun</span>
 922          <span class="tag">JavaScript</span>
 923        </div>
 924      </div>
 925
 926      <div class="year-separator">Earlier</div>
 927
 928      <a class="page-entry" href="#">
 929        <span class="marker new">+</span>
 930        <span class="page-title">A Digital Garden</span>
 931        <span class="page-date">2024</span>
 932        <span class="arrow"></span>
 933      </a>
 934
 935      <a class="page-entry" href="#">
 936        <span class="marker new">+</span>
 937        <span class="page-title">Random thoughts after 2 years</span>
 938        <span class="page-date">2022</span>
 939        <span class="arrow"></span>
 940      </a>
 941
 942    </div>
 943
 944    <!-- ============================================================
 945         SECTION 8: PROSE EXAMPLE (full article feel)
 946         ============================================================ -->
 947
 948    <hr>
 949
 950    <h2 id="prose"><a class="anchor" href="#prose">§</a>Prose Example</h2>
 951
 952    <p class="dropcap">This section demonstrates how a full article or journal entry would look with this design system. The warm background, serif body text, and generous line-height create a reading experience closer to a book than a typical blog. Every decision — from the font choice to the margin width — serves readability.</p>
 953
 954    <p>As I said in "Random thoughts after 2 years," I've been inspired by <a href="https://joelhooks.com/digital-garden">Joel's digital garden</a> article. This space is inspired by <a href="https://www.la-grange.net/">a</a> <a href="https://larlet.fr/david/">lot</a> <a href="https://tomcritchlow.com/">of</a> <a href="https://joelhooks.com/">other</a> spaces, but adapted to my vision.</p>
 955
 956    <p>I really like the way Joel speaks about it:<span class="sidenote-number"></span>
 957      <span class="sidenote">Joel Hooks, <em>My blog is a digital garden, not a blog</em>. This framing changed how I think about publishing online.</span>
 958    </p>
 959
 960    <blockquote>
 961      <p>The phrase "digital garden" is a metaphor for thinking about writing and creating that focuses less on the resulting "content", and more on the process, care, and craft it takes to get there.</p>
 962    </blockquote>
 963
 964    <p>I think I've always struggled with the blog approach. I sometimes want to publish things that are not related to time. That can be true no matter when you read it. The opposite is true as well — some things are deeply temporal, like a <span class="smallcaps">TIL</span> or a stream entry.</p>
 965
 966    <p>That's why the <em>flux</em> works alongside the garden: the garden holds evergreen pages; the flux captures the flow of time.<span class="sidenote-number"></span>
 967      <span class="sidenote">Gwern makes a similar distinction with "tags" vs "essays." Some content is timeless reference, some is a snapshot of thinking at a moment.</span>
 968    </p>
 969
 970    <h3>Technical Detail</h3>
 971
 972    <p>The flux tool itself is simple Go. The core type is an <code>Entry</code> struct with a <code>Kind</code> discriminator. Each source implements a <code>Fetch</code> method:</p>
 973
 974    <pre><code><span class="cmt">// Source is the interface all flux providers implement.</span>
 975<span class="k">type</span> <span class="fn-def">Source</span> <span class="k">interface</span> {
 976    <span class="fn-def">Name</span>() <span class="typ">string</span>
 977    <span class="fn-def">Fetch</span>(ctx <span class="typ">context.Context</span>, since <span class="typ">time.Time</span>) ([]<span class="typ">Entry</span>, <span class="typ">error</span>)
 978}</code></pre>
 979
 980    <p>The aggregator merges entries, sorts by date, deduplicates by <code>ID</code>, and feeds the result to renderers (JSON Feed, Atom, HTML template). No framework. No JavaScript. Just static files deployed via <code>rsync</code>.</p>
 981
 982    <div class="callout">
 983      <div class="callout-title">Why no framework?</div>
 984      <p>As Kenneth Reitz puts it: "If Fly.io disappears tomorrow, I can have this running on a Raspberry Pi in 10 minutes. Portability is a form of independence." Same principle here. The output is pure HTML+CSS that any web server can host. The Go binary is the only moving part.</p>
 985    </div>
 986
 987    <!-- ============================================================
 988         FOOTER
 989         ============================================================ -->
 990
 991    <footer>
 992      <p>Vincent Demeester · <a href="https://vincent.demeester.fr">vincent.demeester.fr</a></p>
 993      <p class="mt-sm">Design sandbox — combining ideas from
 994        <a href="https://beathagenlocher.com/stream/">Beat Hagenlocher</a>,
 995        <a href="https://gwern.net/design">Gwern</a>,
 996        <a href="https://danielmiessler.com">Daniel Miessler</a>,
 997        <a href="https://christiantietze.de">Christian Tietze</a>,
 998        <a href="https://craftinginterpreters.com">Crafting Interpreters</a>,
 999        <a href="https://edwardtufte.github.io/tufte-css/">Tufte CSS</a>,
1000        <a href="https://larlet.fr/david/">David Larlet</a>,
1001        <a href="https://adactio.com">Adactio</a>,
1002        <a href="https://practicaltypography.com">Butterick</a>.
1003      </p>
1004    </footer>
1005
1006  </article>
1007
1008  <script src="flux.js"></script>
1009  <!-- removed inline script, see flux.js -->
1010  <!-- DEAD
1011      const toggle = document.getElementById('theme-toggle')
1012      const STORAGE_KEY = 'flux-theme'
1013
1014      // --- Helpers ---
1015      function getPreferredTheme() {
1016        const stored = localStorage.getItem(STORAGE_KEY)
1017        if (stored) return stored
1018        return window.matchMedia('(prefers-color-scheme: dark)').matches
1019          ? 'dark' : 'light'
1020      }
1021
1022      function hexToRgba(hex, alpha) {
1023        hex = hex.replace('#', '')
1024        if (hex.length === 3) hex = hex.split('').map(c => c + c).join('')
1025        const r = parseInt(hex.substring(0, 2), 16)
1026        const g = parseInt(hex.substring(2, 4), 16)
1027        const b = parseInt(hex.substring(4, 6), 16)
1028        return `rgba(${r}, ${g}, ${b}, ${alpha})`
1029      }
1030
1031      // --- Theme ---
1032      function applyTheme(theme) {
1033        document.documentElement.setAttribute('data-theme', theme)
1034        toggle.textContent = theme === 'dark' ? '☀️' : '🌙'
1035        localStorage.setItem(STORAGE_KEY, theme)
1036
1037        // Update swatch previews to show correct variant
1038        document.querySelectorAll('.accent-swatch').forEach(s => {
1039          s.style.background = theme === 'dark' ? s.dataset.dark : s.dataset.light
1040        })
1041
1042        // Re-apply accent for the new theme
1043        const l = localStorage.getItem('flux-accent-light')
1044        const d = localStorage.getItem('flux-accent-dark')
1045        if (l && d) applyAccent(l, d)
1046      }
1047
1048      // --- Accent ---
1049      function applyAccent(light, dark) {
1050        const root = document.documentElement
1051        const isDark = root.getAttribute('data-theme') === 'dark'
1052        const color = isDark ? dark : light
1053
1054        root.style.setProperty('--color-accent', color)
1055        root.style.setProperty('--color-accent-soft', hexToRgba(color, 0.08))
1056        root.style.setProperty('--callout-design-color', color)
1057        root.style.setProperty('--callout-design-bg', hexToRgba(color, 0.06))
1058
1059        localStorage.setItem('flux-accent-light', light)
1060        localStorage.setItem('flux-accent-dark', dark)
1061
1062        const label = document.getElementById('accent-label')
1063        if (label) label.textContent = `${light} / ${dark}`
1064
1065        document.querySelectorAll('.accent-swatch').forEach(s => {
1066          s.classList.toggle('active', s.dataset.light === light)
1067        })
1068      }
1069
1070      // --- Init theme ---
1071      applyTheme(getPreferredTheme())
1072
1073      // --- Init accent ---
1074      const storedLight = localStorage.getItem('flux-accent-light')
1075      const storedDark = localStorage.getItem('flux-accent-dark')
1076      if (storedLight && storedDark) {
1077        applyAccent(storedLight, storedDark)
1078      } else {
1079        document.querySelectorAll('.accent-swatch').forEach(s => {
1080          if (s.dataset.light === '#4a7c59') s.classList.add('active')
1081        })
1082      }
1083
1084      // --- Event listeners ---
1085      toggle.addEventListener('click', () => {
1086        const current = document.documentElement.getAttribute('data-theme')
1087        applyTheme(current === 'dark' ? 'light' : 'dark')
1088      })
1089
1090      document.querySelectorAll('.accent-swatch').forEach(swatch => {
1091        swatch.addEventListener('click', () => {
1092          applyAccent(swatch.dataset.light, swatch.dataset.dark)
1093        })
1094      })
1095
1096      window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
1097        if (!localStorage.getItem(STORAGE_KEY)) {
1098          applyTheme(e.matches ? 'dark' : 'light')
1099        }
1100      })
1101DEAD -->
1102</body>
1103</html>