<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Inxeoz Docs</title>
    <link>/</link>
    <description>Recent content on Inxeoz Docs</description>
    <generator>Hugo</generator>
    <language>en</language>
    <lastBuildDate>Thu, 20 Aug 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>EPUB Reader</title>
      <link>/posts/tech/epub-reader/</link>
      <pubDate>Thu, 20 Aug 2026 00:00:00 +0000</pubDate>
      <guid>/posts/tech/epub-reader/</guid>
      <description>&lt;p&gt;A small reader that lives entirely in this page. Pick an &lt;code&gt;.epub&lt;/code&gt;, and it opens like a real book: turn pages with the buttons, the arrow keys, or a swipe on touch screens, jump around with the table of contents, and adjust the text size. Everything runs in your browser — the file is never uploaded anywhere.&lt;/p&gt;&#xA;&lt;div id=&#34;epub-app&#34; class=&#34;epub-app&#34;&gt;&#xA;&lt;style&gt;&#xA;/* EPUB reader — styled with the site&#39;s own design tokens */&#xA;.epub-app { margin: 1.5rem 0; }&#xA;.epub-drop {&#xA;    display: block;&#xA;    padding: 2.75rem 1rem;&#xA;    text-align: center;&#xA;    border: 2px dashed var(--border-color);&#xA;    border-radius: 8px;&#xA;    color: var(--secondary-text);&#xA;    cursor: pointer;&#xA;    transition: border-color .15s ease, color .15s ease;&#xA;}&#xA;.epub-drop:hover, .epub-drop.dragover { border-color: var(--link-color); color: var(--link-color); }&#xA;.epub-drop .epub-drop-title { display: block; font-weight: var(--font-weight-semibold); font-size: 1.1rem; }&#xA;.epub-drop .epub-drop-sub { display: block; margin-top: .35rem; font-size: .85rem; }&#xA;.epub-drop input[type=&#34;file&#34;] {&#xA;    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;&#xA;    overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;&#xA;}&#xA;.epub-toolbar {&#xA;    display: flex; flex-wrap: wrap; align-items: center; gap: .35rem;&#xA;    padding: .4rem .55rem;&#xA;    background: var(--surface-color);&#xA;    border: 1px solid var(--border-color);&#xA;    border-radius: 6px 6px 0 0;&#xA;}&#xA;.epub-toolbar .epub-book-title {&#xA;    margin-right: auto; font-weight: var(--font-weight-semibold); font-size: .9rem;&#xA;    max-width: 45%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;&#xA;}&#xA;.epub-btn {&#xA;    display: inline-flex; align-items: center; justify-content: center;&#xA;    min-width: 2rem; height: 2rem; padding: 0 .55rem;&#xA;    background: none; border: 1px solid var(--border-color); border-radius: 4px;&#xA;    color: var(--text-color); font: inherit; font-size: .8rem; cursor: pointer;&#xA;    transition: color .15s ease, border-color .15s ease;&#xA;}&#xA;.epub-btn:hover { color: var(--link-color); border-color: var(--link-color); }&#xA;.epub-btn:focus-visible { outline: 3px solid var(--link-color); outline-offset: 2px; }&#xA;.epub-page-info { font: var(--font-weight-normal) .78rem var(--font-mono); color: var(--secondary-text); padding: 0 .25rem; }&#xA;.epub-toc {&#xA;    position: absolute; z-index: 20; top: 0; left: 0; width: min(20rem, 100%);&#xA;    max-height: 55vh; overflow-y: auto;&#xA;    background: var(--surface-color);&#xA;    border: 1px solid var(--border-color); border-radius: 0 0 6px 6px;&#xA;    box-shadow: 0 6px 16px -4px rgba(0, 0, 0, .25);&#xA;}&#xA;.epub-toc ul { list-style: none; margin: .35rem 0; padding-left: .9rem; }&#xA;.epub-toc &gt; ul { padding-left: .4rem; }&#xA;.epub-toc a {&#xA;    display: block; padding: .3rem .45rem; border-radius: 4px;&#xA;    color: var(--link-color); text-decoration: none; font-size: .88rem;&#xA;}&#xA;.epub-toc a:hover { background: var(--code-bg); }&#xA;.epub-toc .epub-toc-label { padding: .3rem .45rem; color: var(--text-color); font-weight: var(--font-weight-semibold); font-size: .88rem; }&#xA;.epub-toc-wrap { position: relative; }&#xA;.epub-viewport {&#xA;    position: relative;&#xA;    height: clamp(340px, 68vh, 780px);&#xA;    background: var(--card-bg);&#xA;    border: 1px solid var(--border-color); border-top: none;&#xA;    border-radius: 0 0 6px 6px;&#xA;    overflow: hidden;&#xA;}&#xA;.epub-viewport iframe { width: 100%; height: 100%; border: 0; }&#xA;.epub-status { margin: .5rem 0 0; font-size: .85rem; color: var(--secondary-text); min-height: 1.1em; }&#xA;.epub-status:empty { min-height: 0; margin: 0; }&#xA;[hidden] { display: none !important; }&#xA;&lt;/style&gt;&#xA;&lt;div id=&#34;epub-library&#34;&gt;&#xA;  &lt;label class=&#34;epub-drop&#34; id=&#34;epub-drop&#34; for=&#34;epub-file&#34;&gt;&#xA;    &lt;span class=&#34;epub-drop-title&#34;&gt;Drop an EPUB here&lt;/span&gt;&#xA;    &lt;span class=&#34;epub-drop-sub&#34;&gt;…or click to choose a file. It stays on your device — nothing is uploaded.&lt;/span&gt;&#xA;    &lt;input type=&#34;file&#34; id=&#34;epub-file&#34; accept=&#34;.epub,application/epub+zip&#34;&gt;&#xA;  &lt;/label&gt;&#xA;&lt;/div&gt;&#xA;&lt;div id=&#34;epub-reader&#34; hidden&gt;&#xA;  &lt;div class=&#34;epub-toolbar&#34;&gt;&#xA;    &lt;span class=&#34;epub-book-title&#34; id=&#34;epub-book-title&#34;&gt;Book&lt;/span&gt;&#xA;    &lt;button type=&#34;button&#34; class=&#34;epub-btn&#34; id=&#34;epub-prev&#34; aria-label=&#34;Previous page&#34;&gt;← Prev&lt;/button&gt;&#xA;    &lt;button type=&#34;button&#34; class=&#34;epub-btn&#34; id=&#34;epub-next&#34; aria-label=&#34;Next page&#34;&gt;Next →&lt;/button&gt;&#xA;    &lt;span class=&#34;epub-page-info&#34; id=&#34;epub-page-info&#34;&gt;&lt;/span&gt;&#xA;    &lt;button type=&#34;button&#34; class=&#34;epub-btn&#34; id=&#34;epub-toc-btn&#34;&gt;Contents&lt;/button&gt;&#xA;    &lt;button type=&#34;button&#34; class=&#34;epub-btn&#34; id=&#34;epub-zoom-out&#34; aria-label=&#34;Decrease text size&#34;&gt;A−&lt;/button&gt;&#xA;    &lt;button type=&#34;button&#34; class=&#34;epub-btn&#34; id=&#34;epub-zoom-in&#34; aria-label=&#34;Increase text size&#34;&gt;A+&lt;/button&gt;&#xA;    &lt;button type=&#34;button&#34; class=&#34;epub-btn&#34; id=&#34;epub-close&#34;&gt;Choose another&lt;/button&gt;&#xA;  &lt;/div&gt;&#xA;  &lt;div class=&#34;epub-toc-wrap&#34;&gt;&#xA;    &lt;nav class=&#34;epub-toc&#34; id=&#34;epub-toc&#34; hidden aria-label=&#34;Table of contents&#34;&gt;&lt;/nav&gt;&#xA;  &lt;/div&gt;&#xA;  &lt;div class=&#34;epub-viewport&#34; id=&#34;epub-viewport&#34; aria-label=&#34;Book pages&#34;&gt;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;p class=&#34;epub-status&#34; id=&#34;epub-status&#34; role=&#34;status&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>Hugo Is More Extensible Than You Think</title>
      <link>/posts/tech/hugo-hidden-features/</link>
      <pubDate>Sun, 09 Aug 2026 00:00:00 +0000</pubDate>
      <guid>/posts/tech/hugo-hidden-features/</guid>
      <description>&lt;p&gt;&lt;img src=&#34;/posts/tech/hugo-hidden-features/vendor-hugo_hu_74f9505d7f973f78.webp&#34; alt=&#34;Hidden-Hugo-Feature&#34;  loading=&#34;lazy&#34; width=&#34;1200&#34; height=&#34;670&#34; data-testid=&#34;content-image&#34; /&gt;&lt;/p&gt;&#xA;&lt;p&gt;Hugo markets itself as &amp;ldquo;a Fast and Flexible Static Site Generator&amp;rdquo;, but its documentation barely hints at how deep the tool actually goes. Most people touch four commands (&lt;code&gt;hugo&lt;/code&gt;, &lt;code&gt;hugo server&lt;/code&gt;, &lt;code&gt;hugo new&lt;/code&gt;, &lt;code&gt;hugo mod&lt;/code&gt;) and call it a day.&lt;/p&gt;&#xA;&lt;p&gt;This article is a tour of the parts of Hugo that stay hidden — the output format system that gives this very blog a raw-markdown twin for every post, the CLI subcommands most users never discover, the template engine that is secretly a real programming language, and the module system that turns themes into versioned dependencies.&lt;/p&gt;</description>
    </item>
    <item>
      <title>The Telescoping Blueprint: A Simple Way to Derive Every Sum-of-Powers Formula</title>
      <link>/posts/tech/math/sum-of-series/</link>
      <pubDate>Fri, 07 Aug 2026 00:00:00 +0000</pubDate>
      <guid>/posts/tech/math/sum-of-series/</guid>
      <description>&lt;h3 id=&#34;introduction-the-engine-behind-every-sum-formula&#34;&gt;Introduction: The Engine Behind Every Sum Formula&lt;/h3&gt;&#xA;&lt;p&gt;If you have ever memorized formulas like &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1.355em;vertical-align:-0.345em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;&lt;span class=&#34;mopen nulldelimiter&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mfrac&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:1.01em;&#34;&gt;&lt;span style=&#34;top:-2.655em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:3em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size6 size3 mtight&#34;&gt;&lt;span class=&#34;mord mtight&#34;&gt;&lt;span class=&#34;mord mtight&#34;&gt;2&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;top:-3.23em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:3em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;frac-line&#34; style=&#34;border-bottom-width:0.04em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;top:-3.485em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:3em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size6 size3 mtight&#34;&gt;&lt;span class=&#34;mord mtight&#34;&gt;&lt;span class=&#34;mord mathnormal mtight&#34;&gt;n&lt;/span&gt;&lt;span class=&#34;mopen mtight&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord mathnormal mtight&#34;&gt;n&lt;/span&gt;&lt;span class=&#34;mbin mtight&#34;&gt;+&lt;/span&gt;&lt;span class=&#34;mord mtight&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;mclose mtight&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.345em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;mclose nulldelimiter&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt; or &lt;span class=&#34;katex&#34;&gt;&lt;span class=&#34;katex-html&#34; aria-hidden=&#34;true&#34;&gt;&lt;span class=&#34;base&#34;&gt;&lt;span class=&#34;strut&#34; style=&#34;height:1.355em;vertical-align:-0.345em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mord&#34;&gt;&lt;span class=&#34;mopen nulldelimiter&#34;&gt;&lt;/span&gt;&lt;span class=&#34;mfrac&#34;&gt;&lt;span class=&#34;vlist-t vlist-t2&#34;&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:1.01em;&#34;&gt;&lt;span style=&#34;top:-2.655em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:3em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size6 size3 mtight&#34;&gt;&lt;span class=&#34;mord mtight&#34;&gt;&lt;span class=&#34;mord mtight&#34;&gt;6&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;top:-3.23em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:3em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;frac-line&#34; style=&#34;border-bottom-width:0.04em;&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;top:-3.485em;&#34;&gt;&lt;span class=&#34;pstrut&#34; style=&#34;height:3em;&#34;&gt;&lt;/span&gt;&lt;span class=&#34;sizing reset-size6 size3 mtight&#34;&gt;&lt;span class=&#34;mord mtight&#34;&gt;&lt;span class=&#34;mord mathnormal mtight&#34;&gt;n&lt;/span&gt;&lt;span class=&#34;mopen mtight&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord mathnormal mtight&#34;&gt;n&lt;/span&gt;&lt;span class=&#34;mbin mtight&#34;&gt;+&lt;/span&gt;&lt;span class=&#34;mord mtight&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;mclose mtight&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;mopen mtight&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mord mtight&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;mord mathnormal mtight&#34;&gt;n&lt;/span&gt;&lt;span class=&#34;mbin mtight&#34;&gt;+&lt;/span&gt;&lt;span class=&#34;mord mtight&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;mclose mtight&#34;&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-s&#34;&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;vlist-r&#34;&gt;&lt;span class=&#34;vlist&#34; style=&#34;height:0.345em;&#34;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;mclose nulldelimiter&#34;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;, you might have wondered: &lt;em&gt;&amp;ldquo;Where do these actually come from?&amp;rdquo;&lt;/em&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>The Ultimate Guide to Bitwise Operations: 20 Green Flags and 20 Red Flags</title>
      <link>/posts/tech/bitwise-green-red-flags/</link>
      <pubDate>Fri, 07 Aug 2026 00:00:00 +0000</pubDate>
      <guid>/posts/tech/bitwise-green-red-flags/</guid>
      <description>&lt;p&gt;Bitwise operations are the hidden superpower of low-level programming. They directly manipulate the binary bits of integers, offering unparalleled speed, memory efficiency, and elegant solutions to complex combinatorial problems.&lt;/p&gt;&#xA;&lt;p&gt;However, they are also the sharpest knife in the drawer—incredibly useful when handled correctly, but devastating when misused.&lt;/p&gt;&#xA;&lt;p&gt;This comprehensive guide provides a hard-nosed decision framework: &lt;strong&gt;20 Green Flags&lt;/strong&gt; (situations where bitwise is the &lt;em&gt;perfect&lt;/em&gt; tool) and &lt;strong&gt;20 Red Flags&lt;/strong&gt; (situations where bitwise will destroy your code&amp;rsquo;s readability, portability, or correctness).&lt;/p&gt;</description>
    </item>
    <item>
      <title>Cloudflare Tunnel Failing on Your Wi-Fi? Use WARP to Fix It</title>
      <link>/posts/tech/cloudflare-tunnel-warp-fix/</link>
      <pubDate>Sun, 26 Jul 2026 00:00:00 +0000</pubDate>
      <guid>/posts/tech/cloudflare-tunnel-warp-fix/</guid>
      <description>&lt;p&gt;Cloudflare Tunnel creates a secure, outbound-only connection from your device to Cloudflare&amp;rsquo;s global network. No port forwarding, no public IP, no router changes needed.&lt;/p&gt;&#xA;&lt;p&gt;But many users hit this error:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block-wrapper&#34; data-testid=&#34;code-block&#34;&gt;&#xA;  &lt;div class=&#34;code-toolbar&#34; data-testid=&#34;code-toolbar&#34;&gt;&#xA;    &lt;button class=&#34;wrap-code-btn&#34; aria-label=&#34;Toggle line wrap&#34; title=&#34;Toggle line wrap&#34; data-testid=&#34;wrap-code-btn&#34;&gt;&#xA;      &lt;svg width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34;&gt;&lt;line x1=&#34;3&#34; y1=&#34;6&#34; x2=&#34;21&#34; y2=&#34;6&#34;&gt;&lt;/line&gt;&lt;path d=&#34;M3 12h15a3 3 0 1 1 0 6h-4&#34;&gt;&lt;/path&gt;&lt;polyline points=&#34;16 16 14 18 16 20&#34;&gt;&lt;/polyline&gt;&lt;line x1=&#34;3&#34; y1=&#34;18&#34; x2=&#34;10&#34; y2=&#34;18&#34;&gt;&lt;/line&gt;&lt;/svg&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;button class=&#34;copy-code-btn&#34; aria-label=&#34;Copy code&#34; data-testid=&#34;copy-code-btn&#34;&gt;&#xA;      &lt;svg width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34;&gt;&lt;rect x=&#34;9&#34; y=&#34;9&#34; width=&#34;13&#34; height=&#34;13&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;&lt;path d=&#34;M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;&#xA;    &lt;/button&gt;&#xA;  &lt;/div&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;dial tcp 198.41.128.100:7844: i/o timeout&#xA;failed to dial a quic connection&#xA;connection timeout&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;This happens because many networks block port 7844. Here&amp;rsquo;s the fix.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Export Only Changed Files From Git While Preserving Folder Structure</title>
      <link>/posts/tech/git-export-changed-files/</link>
      <pubDate>Sun, 26 Jul 2026 00:00:00 +0000</pubDate>
      <guid>/posts/tech/git-export-changed-files/</guid>
      <description>&lt;p&gt;Sometimes you need to export only the files changed in Git instead of sharing the entire project. This is useful for deployments, patch updates, code reviews, or sharing incremental changes.&lt;/p&gt;&#xA;&lt;p&gt;This guide explains how to export only affected files while keeping the original folder structure intact.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-this-method-does&#34;&gt;What This Method Does&lt;/h2&gt;&#xA;&lt;p&gt;This approach exports:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Modified files&lt;/li&gt;&#xA;&lt;li&gt;Newly added files&lt;/li&gt;&#xA;&lt;li&gt;Renamed files&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;While preserving the original folder hierarchy.&lt;/p&gt;&#xA;&lt;p&gt;It does NOT export:&lt;/p&gt;</description>
    </item>
    <item>
      <title>Fixing Frappe DocTypes After a Restore Without Source Code</title>
      <link>/posts/tech/frappe-doctype-restore-guide/</link>
      <pubDate>Sun, 26 Jul 2026 00:00:00 +0000</pubDate>
      <guid>/posts/tech/frappe-doctype-restore-guide/</guid>
      <description>&lt;p&gt;You restored a Frappe database backup and now every custom DocType returns &lt;code&gt;Module Not Found&lt;/code&gt; or &lt;code&gt;The resource you are looking for is not available&lt;/code&gt;. The database has all the data, but Frappe can&amp;rsquo;t find the controller files.&lt;/p&gt;&#xA;&lt;p&gt;This happens because Frappe DocTypes can exist in two states, and the restore process doesn&amp;rsquo;t always match what Frappe expects. Here&amp;rsquo;s how to diagnose and fix it.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-root-cause&#34;&gt;The Root Cause&lt;/h2&gt;&#xA;&lt;p&gt;Frappe DocTypes have a &lt;code&gt;custom&lt;/code&gt; flag:&lt;/p&gt;</description>
    </item>
    <item>
      <title>Getting MotionPro VPN Working on Arch Linux (Every Fix I Found)</title>
      <link>/posts/tech/motionpro-vpn-arch-linux/</link>
      <pubDate>Sun, 26 Jul 2026 00:00:00 +0000</pubDate>
      <guid>/posts/tech/motionpro-vpn-arch-linux/</guid>
      <description>&lt;p&gt;MotionPro is the VPN client for Array Networks&amp;rsquo; AG SSL VPN appliances. Official builds only target Windows, macOS, Ubuntu, RedHat, and CentOS — there&amp;rsquo;s no native Arch package from the vendor. This guide walks through getting it fully working on Arch, based on a real troubleshooting session that hit every common failure point.&lt;/p&gt;&#xA;&lt;h2 id=&#34;overview&#34;&gt;Overview&lt;/h2&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Install MotionPro (via AUR preferred, or the Ubuntu &lt;code&gt;.sh&lt;/code&gt; installer as fallback)&lt;/li&gt;&#xA;&lt;li&gt;Fix the daemon (&lt;code&gt;vpnd&lt;/code&gt;) so it actually starts and runs with the right privileges&lt;/li&gt;&#xA;&lt;li&gt;Fix the GUI&amp;rsquo;s display backend (Qt platform plugin) if it won&amp;rsquo;t launch&lt;/li&gt;&#xA;&lt;li&gt;Configure the connection profile correctly&lt;/li&gt;&#xA;&lt;li&gt;Resolve tunnel/network conflicts with other VPN or DNS-tunneling software&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;1-install-motionpro&#34;&gt;1. Install MotionPro&lt;/h2&gt;&#xA;&lt;h3 id=&#34;preferred-aur&#34;&gt;Preferred: AUR&lt;/h3&gt;&#xA;&lt;div class=&#34;code-block-wrapper&#34; data-lang=&#34;bash&#34; data-testid=&#34;code-block&#34;&gt;&#xA;  &lt;div class=&#34;code-toolbar&#34; data-testid=&#34;code-toolbar&#34;&gt;&#xA;    &lt;span class=&#34;lang-code-btn&#34; aria-label=&#34;Code language: bash&#34; data-testid=&#34;lang-code-btn&#34;&gt;bash&lt;/span&gt;&#xA;    &lt;button class=&#34;wrap-code-btn&#34; aria-label=&#34;Toggle line wrap&#34; title=&#34;Toggle line wrap&#34; data-testid=&#34;wrap-code-btn&#34;&gt;&#xA;      &lt;svg width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34;&gt;&lt;line x1=&#34;3&#34; y1=&#34;6&#34; x2=&#34;21&#34; y2=&#34;6&#34;&gt;&lt;/line&gt;&lt;path d=&#34;M3 12h15a3 3 0 1 1 0 6h-4&#34;&gt;&lt;/path&gt;&lt;polyline points=&#34;16 16 14 18 16 20&#34;&gt;&lt;/polyline&gt;&lt;line x1=&#34;3&#34; y1=&#34;18&#34; x2=&#34;10&#34; y2=&#34;18&#34;&gt;&lt;/line&gt;&lt;/svg&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;button class=&#34;copy-code-btn&#34; aria-label=&#34;Copy code&#34; data-testid=&#34;copy-code-btn&#34;&gt;&#xA;      &lt;svg width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34;&gt;&lt;rect x=&#34;9&#34; y=&#34;9&#34; width=&#34;13&#34; height=&#34;13&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;&lt;path d=&#34;M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;&#xA;    &lt;/button&gt;&#xA;  &lt;/div&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;yay -S motionpro motionpro-gui&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;This installs proper Arch-native binaries and file locations under &lt;code&gt;/opt/MotionPro/&lt;/code&gt; and &lt;code&gt;/usr/bin/MotionPro&lt;/code&gt;.&lt;/p&gt;</description>
    </item>
    <item>
      <title>How to Fix and Automate Waydroid Internet Connectivity on Linux</title>
      <link>/posts/tech/waydroid-networking-fix/</link>
      <pubDate>Sun, 26 Jul 2026 00:00:00 +0000</pubDate>
      <guid>/posts/tech/waydroid-networking-fix/</guid>
      <description>&lt;p&gt;Waydroid runs Android inside an LXC container on Linux. It&amp;rsquo;s fast, it&amp;rsquo;s clean, and then you open a browser and nothing loads.&lt;/p&gt;&#xA;&lt;p&gt;You get &lt;code&gt;connect: Network is unreachable&lt;/code&gt; or &lt;code&gt;ping: unknown host&lt;/code&gt;. The Android system boots fine, apps install fine, but the network is dead.&lt;/p&gt;&#xA;&lt;p&gt;This happens because Waydroid&amp;rsquo;s virtual bridge (&lt;code&gt;waydroid0&lt;/code&gt;) needs proper IP forwarding, firewall exceptions, and DNS configuration to actually reach the outside world. Here&amp;rsquo;s how to fix it manually, then automate it so you never think about it again.&lt;/p&gt;</description>
    </item>
    <item>
      <title>How to Run Cloudflare WARP and a Corporate VPN Simultaneously on Linux</title>
      <link>/posts/tech/split-vpn-linux-warp-motionpro/</link>
      <pubDate>Sun, 26 Jul 2026 00:00:00 +0000</pubDate>
      <guid>/posts/tech/split-vpn-linux-warp-motionpro/</guid>
      <description>&lt;p&gt;Modern Linux systems often need to run multiple VPNs at the same time. A common setup: Cloudflare WARP for secure internet access and an enterprise VPN (Array Networks / MotionPro) for internal company servers, where only specific internal IPs should go through the enterprise VPN.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-were-solving&#34;&gt;What We&amp;rsquo;re Solving&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Traffic&lt;/th&gt;&#xA;          &lt;th&gt;Route&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Internet&lt;/td&gt;&#xA;          &lt;td&gt;Cloudflare WARP&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Internal server (10.0.50.10)&lt;/td&gt;&#xA;          &lt;td&gt;MotionPro VPN&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Everything else&lt;/td&gt;&#xA;          &lt;td&gt;Unchanged&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;h2 id=&#34;environment-overview&#34;&gt;Environment Overview&lt;/h2&gt;&#xA;&lt;h3 id=&#34;vpns&#34;&gt;VPNs&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Enterprise VPN&lt;/strong&gt;: Array Networks / Ivanti MotionPro&#xA;&lt;ul&gt;&#xA;&lt;li&gt;CLI: &lt;code&gt;vpn_cmdline&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;Tunnel interface: &lt;code&gt;tun0&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;External VPN&lt;/strong&gt;: Cloudflare WARP&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Interface: &lt;code&gt;CloudflareWARP&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;Uses policy routing&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;target&#34;&gt;Target&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Internal SSH server: &lt;code&gt;10.0.50.10&lt;/code&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;part-1-connecting-to-motionpro-on-linux&#34;&gt;Part 1: Connecting to MotionPro on Linux&lt;/h2&gt;&#xA;&lt;h3 id=&#34;installation-arch-linux&#34;&gt;Installation (Arch Linux)&lt;/h3&gt;&#xA;&lt;div class=&#34;code-block-wrapper&#34; data-lang=&#34;bash&#34; data-testid=&#34;code-block&#34;&gt;&#xA;  &lt;div class=&#34;code-toolbar&#34; data-testid=&#34;code-toolbar&#34;&gt;&#xA;    &lt;span class=&#34;lang-code-btn&#34; aria-label=&#34;Code language: bash&#34; data-testid=&#34;lang-code-btn&#34;&gt;bash&lt;/span&gt;&#xA;    &lt;button class=&#34;wrap-code-btn&#34; aria-label=&#34;Toggle line wrap&#34; title=&#34;Toggle line wrap&#34; data-testid=&#34;wrap-code-btn&#34;&gt;&#xA;      &lt;svg width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34;&gt;&lt;line x1=&#34;3&#34; y1=&#34;6&#34; x2=&#34;21&#34; y2=&#34;6&#34;&gt;&lt;/line&gt;&lt;path d=&#34;M3 12h15a3 3 0 1 1 0 6h-4&#34;&gt;&lt;/path&gt;&lt;polyline points=&#34;16 16 14 18 16 20&#34;&gt;&lt;/polyline&gt;&lt;line x1=&#34;3&#34; y1=&#34;18&#34; x2=&#34;10&#34; y2=&#34;18&#34;&gt;&lt;/line&gt;&lt;/svg&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;button class=&#34;copy-code-btn&#34; aria-label=&#34;Copy code&#34; data-testid=&#34;copy-code-btn&#34;&gt;&#xA;      &lt;svg width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34;&gt;&lt;rect x=&#34;9&#34; y=&#34;9&#34; width=&#34;13&#34; height=&#34;13&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;&lt;path d=&#34;M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;&#xA;    &lt;/button&gt;&#xA;  &lt;/div&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;yay -S motionpro&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;Verify:&lt;/p&gt;</description>
    </item>
    <item>
      <title>How to Set Up *.test Domains Locally Without Breaking Your DNS</title>
      <link>/posts/tech/wildcard-dns-test-setup/</link>
      <pubDate>Sun, 26 Jul 2026 00:00:00 +0000</pubDate>
      <guid>/posts/tech/wildcard-dns-test-setup/</guid>
      <description>&lt;p&gt;Local development environments often need custom domains like &lt;code&gt;myapp.test&lt;/code&gt;, &lt;code&gt;api.test&lt;/code&gt;, or &lt;code&gt;backend.internal.test&lt;/code&gt;. But wildcard resolution (&lt;code&gt;*.test → 127.0.0.1&lt;/code&gt;) is tricky.&lt;/p&gt;&#xA;&lt;p&gt;Most tutorials break DNS by:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Overwriting &lt;code&gt;/etc/resolv.conf&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;Disabling systemd-resolved&lt;/li&gt;&#xA;&lt;li&gt;Hijacking port 53&lt;/li&gt;&#xA;&lt;li&gt;Interfering with libvirt DNS&lt;/li&gt;&#xA;&lt;li&gt;Using &lt;code&gt;.local&lt;/code&gt; (reserved for mDNS)&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;This article walks through the correct, safe, reversible approach using dnsmasq on a separate port with systemd-resolved split DNS routing.&lt;/p&gt;&#xA;&lt;h2 id=&#34;goal&#34;&gt;Goal&lt;/h2&gt;&#xA;&lt;p&gt;We want &lt;code&gt;*.test → 127.0.0.1&lt;/code&gt; without breaking system DNS, VPN DNS, libvirt/QEMU DNS, or systemd-resolved.&lt;/p&gt;</description>
    </item>
    <item>
      <title>How to Set Up CI/CD for Frappe When Your Server Has No Internet</title>
      <link>/posts/tech/frappe-cicd-airgapped/</link>
      <pubDate>Sun, 26 Jul 2026 00:00:00 +0000</pubDate>
      <guid>/posts/tech/frappe-cicd-airgapped/</guid>
      <description>&lt;p&gt;Frappe Bench applications require schema migrations, Docker images, and database backups. When the production server has no internet access, you need a CI/CD pipeline that builds locally, ships artifacts offline, and supports one-click rollback.&lt;/p&gt;&#xA;&lt;p&gt;This guide covers a Jenkins-based pipeline that does exactly that.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-problem&#34;&gt;The Problem&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Frappe apps require schema migrations&lt;/li&gt;&#xA;&lt;li&gt;Docker images must be deployed without pulling from the internet&lt;/li&gt;&#xA;&lt;li&gt;Rollback must restore both code and database&lt;/li&gt;&#xA;&lt;li&gt;Deployment must be repeatable, auditable, and safe&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;architecture&#34;&gt;Architecture&lt;/h2&gt;&#xA;&lt;div class=&#34;code-block-wrapper&#34; data-testid=&#34;code-block&#34;&gt;&#xA;  &lt;div class=&#34;code-toolbar&#34; data-testid=&#34;code-toolbar&#34;&gt;&#xA;    &lt;button class=&#34;wrap-code-btn&#34; aria-label=&#34;Toggle line wrap&#34; title=&#34;Toggle line wrap&#34; data-testid=&#34;wrap-code-btn&#34;&gt;&#xA;      &lt;svg width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34;&gt;&lt;line x1=&#34;3&#34; y1=&#34;6&#34; x2=&#34;21&#34; y2=&#34;6&#34;&gt;&lt;/line&gt;&lt;path d=&#34;M3 12h15a3 3 0 1 1 0 6h-4&#34;&gt;&lt;/path&gt;&lt;polyline points=&#34;16 16 14 18 16 20&#34;&gt;&lt;/polyline&gt;&lt;line x1=&#34;3&#34; y1=&#34;18&#34; x2=&#34;10&#34; y2=&#34;18&#34;&gt;&lt;/line&gt;&lt;/svg&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;button class=&#34;copy-code-btn&#34; aria-label=&#34;Copy code&#34; data-testid=&#34;copy-code-btn&#34;&gt;&#xA;      &lt;svg width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34;&gt;&lt;rect x=&#34;9&#34; y=&#34;9&#34; width=&#34;13&#34; height=&#34;13&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;&lt;path d=&#34;M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;&#xA;    &lt;/button&gt;&#xA;  &lt;/div&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Developer → Git Push&#xA;        ↓&#xA;Jenkins (Internet Allowed)&#xA;        ↓&#xA;Build Docker Image&#xA;        ↓&#xA;docker save → image.tar&#xA;        ↓&#xA;scp → Offline Server&#xA;        ↓&#xA;docker load + docker compose up&#xA;        ↓&#xA;bench migrate&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;Rollback:&lt;/p&gt;</description>
    </item>
    <item>
      <title>How to Version-Control Everything You Built in Frappe&#39;s UI</title>
      <link>/posts/tech/frappe-export-fixtures-guide/</link>
      <pubDate>Sun, 26 Jul 2026 00:00:00 +0000</pubDate>
      <guid>/posts/tech/frappe-export-fixtures-guide/</guid>
      <description>&lt;p&gt;In Frappe projects, most things are created via the UI: DocTypes, Custom Fields, Client Scripts, Server Scripts, Workflows, Roles, Dashboards, and Master Data. At some point you need to export all of this so it can be version-controlled, installed on another site, or migrated to production.&lt;/p&gt;&#xA;&lt;p&gt;The missing piece most people forget is converting UI DocTypes into real app DocTypes using &lt;code&gt;custom = 0&lt;/code&gt;. Once you do that, the rest of the system works exactly as designed.&lt;/p&gt;</description>
    </item>
    <item>
      <title>My Company Only Has an Android VPN App — Here&#39;s How I Made It Work on Linux</title>
      <link>/posts/tech/android-vpn-gateway-linux/</link>
      <pubDate>Sun, 26 Jul 2026 00:00:00 +0000</pubDate>
      <guid>/posts/tech/android-vpn-gateway-linux/</guid>
      <description>&lt;p&gt;Many organizations only provide VPN access through an Android application like MotionPro. If you develop on Linux, you&amp;rsquo;re stuck — the laptop can&amp;rsquo;t directly access internal company resources.&lt;/p&gt;&#xA;&lt;p&gt;Instead of fighting the VPN, use the Android phone as a secure SSH jump host. The phone provides VPN connectivity while your Linux laptop keeps its native development environment.&lt;/p&gt;&#xA;&lt;h2 id=&#34;architecture&#34;&gt;Architecture&lt;/h2&gt;&#xA;&lt;div class=&#34;code-block-wrapper&#34; data-testid=&#34;code-block&#34;&gt;&#xA;  &lt;div class=&#34;code-toolbar&#34; data-testid=&#34;code-toolbar&#34;&gt;&#xA;    &lt;button class=&#34;wrap-code-btn&#34; aria-label=&#34;Toggle line wrap&#34; title=&#34;Toggle line wrap&#34; data-testid=&#34;wrap-code-btn&#34;&gt;&#xA;      &lt;svg width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34;&gt;&lt;line x1=&#34;3&#34; y1=&#34;6&#34; x2=&#34;21&#34; y2=&#34;6&#34;&gt;&lt;/line&gt;&lt;path d=&#34;M3 12h15a3 3 0 1 1 0 6h-4&#34;&gt;&lt;/path&gt;&lt;polyline points=&#34;16 16 14 18 16 20&#34;&gt;&lt;/polyline&gt;&lt;line x1=&#34;3&#34; y1=&#34;18&#34; x2=&#34;10&#34; y2=&#34;18&#34;&gt;&lt;/line&gt;&lt;/svg&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;button class=&#34;copy-code-btn&#34; aria-label=&#34;Copy code&#34; data-testid=&#34;copy-code-btn&#34;&gt;&#xA;      &lt;svg width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34;&gt;&lt;rect x=&#34;9&#34; y=&#34;9&#34; width=&#34;13&#34; height=&#34;13&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;&lt;path d=&#34;M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;&#xA;    &lt;/button&gt;&#xA;  &lt;/div&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;                     Company Network&#xA;                    (10.0.0.0/8)&#xA;                           │&#xA;                    MotionPro VPN&#xA;                           │&#xA;          ┌──────────────────────────┐&#xA;          │ Android Phone            │&#xA;          │                          │&#xA;          │ MotionPro                │&#xA;          │ Termux                   │&#xA;          │ OpenSSH Server           │&#xA;          │ USB Tethering (RNDIS)    │&#xA;          └────────────┬─────────────┘&#xA;                       │&#xA;               USB Network (RNDIS)&#xA;             192.168.42.0/24&#xA;                       │&#xA;          ┌────────────▼─────────────┐&#xA;          │ Linux Laptop             │&#xA;          │                          │&#xA;          │ SSH                      │&#xA;          │ rsync                    │&#xA;          │ Git                      │&#xA;          │ tmux                     │&#xA;          └──────────────────────────┘&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;/div&gt;&#xA;&lt;h2 id=&#34;why-this-works&#34;&gt;Why This Works&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;No rooting required&lt;/li&gt;&#xA;&lt;li&gt;No VPN installation on Linux&lt;/li&gt;&#xA;&lt;li&gt;No company configuration changes&lt;/li&gt;&#xA;&lt;li&gt;Native Linux development&lt;/li&gt;&#xA;&lt;li&gt;Works with SSH, rsync, Git, tmux, SCP, SFTP, and VS Code Remote SSH&lt;/li&gt;&#xA;&lt;li&gt;Portable&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;step-1-install-openssh-in-termux&#34;&gt;Step 1: Install OpenSSH in Termux&lt;/h2&gt;&#xA;&lt;p&gt;On your Android phone:&lt;/p&gt;</description>
    </item>
    <item>
      <title>NGINX in Front of Traefik: Enterprise Frappe Deployments on RHEL</title>
      <link>/posts/tech/multi-reverse-proxy-nginx-traefik/</link>
      <pubDate>Sun, 26 Jul 2026 00:00:00 +0000</pubDate>
      <guid>/posts/tech/multi-reverse-proxy-nginx-traefik/</guid>
      <description>&lt;p&gt;In restricted enterprise environments (VPN-only, SELinux enforcing, firewalld enabled), applications are often exposed on non-standard ports like &lt;code&gt;:8100&lt;/code&gt;. This works but leads to poor user experience, security concerns, and scalability limitations.&lt;/p&gt;&#xA;&lt;p&gt;This article documents a multi reverse proxy architecture that exposes applications using hostnames only, without leaking internal ports, while keeping all security controls enabled.&lt;/p&gt;&#xA;&lt;h2 id=&#34;target-architecture&#34;&gt;Target Architecture&lt;/h2&gt;&#xA;&lt;div class=&#34;code-block-wrapper&#34; data-testid=&#34;code-block&#34;&gt;&#xA;  &lt;div class=&#34;code-toolbar&#34; data-testid=&#34;code-toolbar&#34;&gt;&#xA;    &lt;button class=&#34;wrap-code-btn&#34; aria-label=&#34;Toggle line wrap&#34; title=&#34;Toggle line wrap&#34; data-testid=&#34;wrap-code-btn&#34;&gt;&#xA;      &lt;svg width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34;&gt;&lt;line x1=&#34;3&#34; y1=&#34;6&#34; x2=&#34;21&#34; y2=&#34;6&#34;&gt;&lt;/line&gt;&lt;path d=&#34;M3 12h15a3 3 0 1 1 0 6h-4&#34;&gt;&lt;/path&gt;&lt;polyline points=&#34;16 16 14 18 16 20&#34;&gt;&lt;/polyline&gt;&lt;line x1=&#34;3&#34; y1=&#34;18&#34; x2=&#34;10&#34; y2=&#34;18&#34;&gt;&lt;/line&gt;&lt;/svg&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;button class=&#34;copy-code-btn&#34; aria-label=&#34;Copy code&#34; data-testid=&#34;copy-code-btn&#34;&gt;&#xA;      &lt;svg width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34;&gt;&lt;rect x=&#34;9&#34; y=&#34;9&#34; width=&#34;13&#34; height=&#34;13&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;&lt;path d=&#34;M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;&#xA;    &lt;/button&gt;&#xA;  &lt;/div&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Client (VPN)&#xA;   |&#xA;   | http://site-a.example.com&#xA;   |&#xA;NGINX (host :80)&#xA;   |&#xA;   | proxy_pass http://127.0.0.1:8100&#xA;   v&#xA;Traefik (Docker reverse proxy)&#xA;   |&#xA;   v&#xA;Frappe (site routing by Host header)&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;/div&gt;&#xA;&lt;h2 id=&#34;components-and-roles&#34;&gt;Components and Roles&lt;/h2&gt;&#xA;&lt;p&gt;&lt;strong&gt;NGINX&lt;/strong&gt; — Front door. Listens on port 80, routes by hostname.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Strict Vertical Curvy Corners Navigation — TOC Design</title>
      <link>/posts/tech/toc-strict-vertical-curvy/</link>
      <pubDate>Sun, 26 Jul 2026 00:00:00 +0000</pubDate>
      <guid>/posts/tech/toc-strict-vertical-curvy/</guid>
      <description>&lt;p&gt;This post demonstrates a strict vertical Table of Contents design with smooth cubic Bézier S-curves connecting heading levels. The vertical lines stay perfectly straight (│) while diagonals transition smoothly (╲ or ╱) using cubic Bézier curves with vertically-aligned control points for perfect tangent continuity.&lt;/p&gt;&#xA;&lt;h2 id=&#34;features-demonstrated&#34;&gt;Features Demonstrated&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Strict vertical lines&lt;/strong&gt; (│) at each TOC item position&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Smooth S-curve transitions&lt;/strong&gt; between heading levels using cubic Bézier curves&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Vertically-aligned control points&lt;/strong&gt; for perfect tangent continuity&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;IntersectionObserver-based ScrollSpy&lt;/strong&gt; with rootMargin for precise activation&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Smooth SVG path animation&lt;/strong&gt; with &lt;code&gt;transition: d 0.35s cubic-bezier(0.4, 0, 0.2, 1)&lt;/code&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Drop shadow glow&lt;/strong&gt; on active path segment&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Sidebar auto-scroll&lt;/strong&gt; to keep active item visible&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;!DOCTYPE html&gt;&#xA;&lt;html lang=&#34;en&#34;&gt;&#xA;&lt;head&gt;&#xA;  &lt;meta charset=&#34;UTF-8&#34; /&gt;&#xA;  &lt;meta name=&#34;viewport&#34; content=&#34;width=device-width, initial-scale=1.0&#34; /&gt;&#xA;  &lt;title&gt;Strict Vertical Curvy Corners Navigation&lt;/title&gt;&#xA;  &lt;style&gt;&#xA;    * {&#xA;      box-sizing: border-box;&#xA;      margin: 0;&#xA;      padding: 0;&#xA;    }&#xA;&#xA;    html {&#xA;      scroll-behavior: smooth;&#xA;    }&#xA;&#xA;    body {&#xA;      background-color: #0b0f17;&#xA;      color: #94a3b8;&#xA;      font-family: -apple-system, BlinkMacSystemFont, &#34;Segoe UI&#34;, Roboto, sans-serif;&#xA;      line-height: 1.6;&#xA;    }&#xA;&#xA;    .layout-container {&#xA;      max-width: 1100px;&#xA;      margin: 0 auto;&#xA;      padding: 3rem 2rem;&#xA;      display: grid;&#xA;      grid-template-columns: 1fr 280px;&#xA;      gap: 4rem;&#xA;      border: 2px solid #1e293b;&#xA;      border-radius: 12px;&#xA;      background: #0e141f;&#xA;    }&#xA;&#xA;    .article-content {&#xA;      color: #cbd5e1;&#xA;    }&#xA;&#xA;    .article-content h1 {&#xA;      font-size: 2.25rem;&#xA;      color: #f8fafc;&#xA;      margin-bottom: 1.5rem;&#xA;    }&#xA;&#xA;    .article-content section {&#xA;      margin-bottom: 3.5rem;&#xA;      padding-top: 1rem;&#xA;    }&#xA;&#xA;    .article-content h2 {&#xA;      font-size: 1.5rem;&#xA;      color: #f1f5f9;&#xA;      margin-bottom: 0.75rem;&#xA;    }&#xA;&#xA;    .article-content h3 {&#xA;      font-size: 1.15rem;&#xA;      color: #e2e8f0;&#xA;      margin-bottom: 0.5rem;&#xA;      margin-top: 1.25rem;&#xA;    }&#xA;&#xA;    .article-content p {&#xA;      margin-bottom: 1rem;&#xA;      color: #94a3b8;&#xA;    }&#xA;&#xA;    .toc-sidebar {&#xA;      position: sticky;&#xA;      top: 2rem;&#xA;      align-self: start;&#xA;      max-height: calc(100vh - 4rem);&#xA;      overflow-y: auto;&#xA;      padding-right: 0.5rem;&#xA;    }&#xA;&#xA;    .toc-sidebar::-webkit-scrollbar {&#xA;      width: 4px;&#xA;    }&#xA;    .toc-sidebar::-webkit-scrollbar-thumb {&#xA;      background: #1e293b;&#xA;      border-radius: 2px;&#xA;    }&#xA;&#xA;    .toc-title {&#xA;      font-size: 0.85rem;&#xA;      font-weight: 700;&#xA;      letter-spacing: 0.05em;&#xA;      text-transform: uppercase;&#xA;      color: #f1f5f9;&#xA;      margin-bottom: 1.25rem;&#xA;    }&#xA;&#xA;    .toc-container {&#xA;      position: relative;&#xA;    }&#xA;&#xA;    .toc-svg {&#xA;      position: absolute;&#xA;      left: 0;&#xA;      top: 0;&#xA;      width: 40px;&#xA;      height: 100%;&#xA;      pointer-events: none;&#xA;      overflow: visible;&#xA;    }&#xA;&#xA;    .bg-path {&#xA;      stroke: #1e293b;&#xA;      stroke-width: 2.5px;&#xA;      fill: none;&#xA;      stroke-linecap: round;&#xA;      stroke-linejoin: round;&#xA;    }&#xA;&#xA;    .active-path {&#xA;      stroke: #10b981;&#xA;      stroke-width: 2.5px;&#xA;      fill: none;&#xA;      stroke-linecap: round;&#xA;      stroke-linejoin: round;&#xA;      filter: drop-shadow(0px 0px 8px rgba(16, 185, 129, 0.6));&#xA;      transition: d 0.35s cubic-bezier(0.4, 0, 0.2, 1);&#xA;    }&#xA;&#xA;    .toc-list {&#xA;      list-style: none;&#xA;      padding-left: 28px;&#xA;    }&#xA;&#xA;    .toc-item {&#xA;      display: flex;&#xA;      align-items: center;&#xA;      min-height: 38px;&#xA;    }&#xA;&#xA;    .toc-item.nested {&#xA;      padding-left: 1.25rem;&#xA;    }&#xA;&#xA;    .toc-link {&#xA;      font-size: 0.88rem;&#xA;      color: #64748b;&#xA;      text-decoration: none;&#xA;      transition: color 0.2s ease, transform 0.2s ease;&#xA;      display: block;&#xA;      padding: 0.2rem 0;&#xA;    }&#xA;&#xA;    .toc-link:hover {&#xA;      color: #e2e8f0;&#xA;    }&#xA;&#xA;    .toc-item.active .toc-link {&#xA;      color: #10b981;&#xA;      font-weight: 600;&#xA;      transform: translateX(2px);&#xA;    }&#xA;  &lt;/style&gt;&#xA;&lt;/head&gt;&#xA;&lt;body&gt;&#xA;&#xA;  &lt;div class=&#34;layout-container&#34;&gt;&#xA;    &lt;main class=&#34;article-content&#34;&gt;&#xA;      &lt;h1&gt;Documentation Guide&lt;/h1&gt;&#xA;&#xA;      &lt;section id=&#34;introduction&#34;&gt;&#xA;        &lt;h2&gt;Introduction&lt;/h2&gt;&#xA;        &lt;p&gt;Welcome to the component documentation. This guide will walk you through setup, configuration, and advanced usage examples.&lt;/p&gt;</description>
    </item>
    <item>
      <title>TOC Design Sample Article</title>
      <link>/posts/tech/toc-design-sample/</link>
      <pubDate>Sun, 26 Jul 2026 00:00:00 +0000</pubDate>
      <guid>/posts/tech/toc-design-sample/</guid>
      <description>&lt;p&gt;This is a sample article to test the Table of Contents design with headings from H1 through H4.&lt;/p&gt;&#xA;&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;&#xA;&lt;p&gt;This article demonstrates the Table of Contents (TOC) design with various heading levels. The TOC should display headings from H2 through H4 by default.&lt;/p&gt;&#xA;&lt;h3 id=&#34;what-this-tests&#34;&gt;What This Tests&lt;/h3&gt;&#xA;&lt;p&gt;This sample covers:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;H2 heading rendering&lt;/li&gt;&#xA;&lt;li&gt;H3 heading nesting&lt;/li&gt;&#xA;&lt;li&gt;H4 heading depth&lt;/li&gt;&#xA;&lt;li&gt;TOC navigation behavior&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;heading-level-2---first-section&#34;&gt;Heading Level 2 - First Section&lt;/h2&gt;&#xA;&lt;p&gt;This is the first H2 section. It contains some introductory content.&lt;/p&gt;</description>
    </item>
    <item>
      <title>How to Set Up Multiple Frappe Benches on One Machine</title>
      <link>/posts/tech/frappe/how-to-set-multi-bench/</link>
      <pubDate>Tue, 23 Jun 2026 00:00:00 +0000</pubDate>
      <guid>/posts/tech/frappe/how-to-set-multi-bench/</guid>
      <description>&lt;p&gt;Run two (or more) Frappe benches side-by-side — each with its own web server, Redis instances, and site — without port conflicts.&lt;/p&gt;&#xA;&lt;h2 id=&#34;why&#34;&gt;Why&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Isolate development environments per project or per app.&lt;/li&gt;&#xA;&lt;li&gt;Run a stable bench alongside an experimental one.&lt;/li&gt;&#xA;&lt;li&gt;Test different Frappe versions or app branches concurrently.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;overview&#34;&gt;Overview&lt;/h2&gt;&#xA;&lt;p&gt;Each bench needs its own set of ports for:&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Service&lt;/th&gt;&#xA;          &lt;th&gt;Default port&lt;/th&gt;&#xA;          &lt;th&gt;Notes&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Web server&lt;/td&gt;&#xA;          &lt;td&gt;8000&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;webserver_port&lt;/code&gt; in &lt;code&gt;common_site_config.json&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Redis cache&lt;/td&gt;&#xA;          &lt;td&gt;13000&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;redis_cache.conf&lt;/code&gt; → &lt;code&gt;port&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Redis queue&lt;/td&gt;&#xA;          &lt;td&gt;11000&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;redis_queue.conf&lt;/code&gt; → &lt;code&gt;port&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Redis socketio&lt;/td&gt;&#xA;          &lt;td&gt;12000&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;redis_socketio.conf&lt;/code&gt; → &lt;code&gt;port&lt;/code&gt; (create it)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;SocketIO&lt;/td&gt;&#xA;          &lt;td&gt;9000&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;socketio_port&lt;/code&gt; in &lt;code&gt;common_site_config.json&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;Second bench increments each by 1 (or any offset): 8001, 13001, 11001, 12001, 9002.&lt;/p&gt;</description>
    </item>
    <item>
      <title>How to Use FizzBee for Formal Verification of Distributed Systems</title>
      <link>/posts/tech/how-to-use-fizzbee/</link>
      <pubDate>Tue, 16 Jun 2026 00:00:00 +0000</pubDate>
      <guid>/posts/tech/how-to-use-fizzbee/</guid>
      <description>&lt;p&gt;&lt;strong&gt;FizzBee&lt;/strong&gt; is a formal specification language and model checker for designing and verifying distributed systems. It uses a Python-like syntax (Starlark) — far more approachable than TLA+ or Alloy while offering the same rigorous state-space exploration.&lt;/p&gt;&#xA;&lt;p&gt;You write a model of your system, specify invariants (safety) and liveness properties, and FizzBee exhaustively checks every possible state transition to find bugs &lt;em&gt;before&lt;/em&gt; you write production code.&lt;/p&gt;&#xA;&lt;h2 id=&#34;quick-start&#34;&gt;Quick Start&lt;/h2&gt;&#xA;&lt;h3 id=&#34;online-playground&#34;&gt;Online Playground&lt;/h3&gt;&#xA;&lt;p&gt;No installation needed: &lt;a href=&#34;https://fizzbee.io/play&#34; class=&#34;external-link&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;https://fizzbee.io/play&lt;/a&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>How LLMs Actually Work — From Data to Intelligence</title>
      <link>/posts/tech/llm-internals-guide/</link>
      <pubDate>Sat, 13 Jun 2026 00:00:00 +0000</pubDate>
      <guid>/posts/tech/llm-internals-guide/</guid>
      <description>&lt;style&gt;&#xA;  :root{&#xA;    --paper:#f5f0e5;&#xA;    --paper-2:#ece4d4;&#xA;    --ink:#211b16;&#xA;    --ink-soft:#5b5147;&#xA;    --terra:#c0492b;&#xA;    --teal:#176b63;&#xA;    --gold:#cf952a;&#xA;    --panel:#fffaf2;&#xA;    --panel-2:#f0e6d5;&#xA;    --panel-line:#d8c7ad;&#xA;    --glow:#f0b35a;&#xA;    --canvas-bg:#ede4d3;&#xA;  }&#xA;  [data-theme=&#34;dark&#34;]{&#xA;    --paper:#1a1714;&#xA;    --paper-2:#231e1a;&#xA;    --ink:#e8ddd0;&#xA;    --ink-soft:#a89a89;&#xA;    --panel:#0e0b09;&#xA;    --panel-2:#1a1511;&#xA;    --panel-line:#2c241e;&#xA;    --glow:#e8a84a;&#xA;    --canvas-bg:#12100d;&#xA;  }&#xA;  *{box-sizing:border-box;}&#xA;  html{scroll-behavior:smooth;}&#xA;  .post-content #progress{position:fixed;top:0;left:0;height:4px;width:0;background:linear-gradient(90deg,var(--terra),var(--gold));z-index:200;transition:width .1s linear;}&#xA;  .post-content nav#dots{position:fixed;right:18px;top:50%;transform:translateY(-50%);z-index:150;display:flex;flex-direction:column;gap:11px;}&#xA;  .post-content nav#dots a{width:11px;height:11px;border-radius:50%;border:1.5px solid var(--ink-soft);background:transparent;transition:all .25s;display:block;}&#xA;  .post-content nav#dots a.active{background:var(--terra);border-color:var(--terra);transform:scale(1.35);}&#xA;  .post-content nav#dots a:hover{border-color:var(--terra);}&#xA;  @media(max-width:880px){.post-content nav#dots{display:none;}}&#xA;  .post-content header.hero{min-height:min(96vh,700px);display:flex;flex-direction:column;justify-content:center;max-width:920px;margin:0 auto;position:relative;overflow:hidden;background:var(--paper);font-family:&#34;Newsreader&#34;,Georgia,serif;-webkit-font-smoothing:antialiased;background-image:radial-gradient(circle at 12% -10%, rgba(192,73,43,.06), transparent 40%),radial-gradient(circle at 100% 8%, rgba(23,107,99,.06), transparent 38%);border:12px double var(--ink);padding:clamp(32px,6vh,70px);}&#xA;  [data-theme=&#34;dark&#34;] .post-content header.hero{background-image:radial-gradient(circle at 12% -10%, rgba(192,73,43,.12), transparent 40%),radial-gradient(circle at 100% 8%, rgba(23,107,99,.12), transparent 38%);border-color:var(--ink);}&#xA;  .post-content header.hero::before{content:&#39;&#39;;position:absolute;inset:-1px;border:1px solid var(--ink-soft);pointer-events:none;}&#xA;  [data-theme=&#34;dark&#34;] .post-content header.hero::before{border-color:var(--ink-soft);}&#xA;  .post-content header.hero::after{content:&#39;&#39;;position:absolute;inset:0;background-image:url(&#34;data:image/svg+xml,%3Csvg viewBox=&#39;0 0 256 256&#39; xmlns=&#39;http://www.w3.org/2000/svg&#39;%3E%3Cfilter id=&#39;n&#39;%3E%3CfeTurbulence type=&#39;fractalNoise&#39; baseFrequency=&#39;0.85&#39; numOctaves=&#39;4&#39; stitchTiles=&#39;stitch&#39;/%3E%3C/filter%3E%3Crect width=&#39;100%25&#39; height=&#39;100%25&#39; filter=&#39;url(%23n)&#39; opacity=&#39;0.5&#39;/%3E%3C/svg%3E&#34;);opacity:0.04;mix-blend-mode:overlay;pointer-events:none;}&#xA;  [data-theme=&#34;dark&#34;] .post-content header.hero::after{opacity:0.025;}&#xA;  .post-content .eyebrow{font-family:&#34;JetBrains Mono&#34;,monospace;font-size:13px;letter-spacing:.22em;text-transform:uppercase;color:var(--terra);margin-bottom:22px;}&#xA;  .post-content h1{font-family:&#34;Fraunces&#34;,serif;font-weight:900;font-size:clamp(46px,9vw,104px);line-height:.95;letter-spacing:-.02em;margin:0 0 8px;color:var(--ink);}&#xA;  .post-content h1 .light{font-weight:400;font-style:italic;color:var(--teal);}&#xA;  .post-content .subtitle{font-size:clamp(20px,2.6vw,27px);color:var(--ink-soft);max-width:620px;margin-top:24px;font-style:italic;}&#xA;  .post-content .byline{font-family:&#34;JetBrains Mono&#34;,monospace;font-size:13px;color:var(--ink-soft);margin-top:38px;line-height:1.8;}&#xA;  .post-content .byline b{color:var(--ink);font-weight:600;}&#xA;&#xA;  .post-content .stage{background:linear-gradient(180deg,var(--panel),var(--panel-2));color:var(--ink);border-radius:16px;padding:32px 30px;margin:40px 0;box-shadow:0 24px 60px -30px rgba(94,64,31,.32);font-family:&#34;Newsreader&#34;,serif;border:1px solid var(--panel-line);max-width:100vw;}&#xA;  .post-content .stage.is-active{animation:stagePulse .42s ease-out;}&#xA;  [data-theme=&#34;dark&#34;] .post-content .stage{background:linear-gradient(180deg,var(--panel-2),var(--panel));color:#efe7da;box-shadow:0 24px 60px -28px rgba(0,0,0,.8);}&#xA;  .post-content .stage .stage-label{font-family:&#34;JetBrains Mono&#34;,monospace;font-size:11px;letter-spacing:.2em;text-transform:uppercase;color:var(--glow);margin-bottom:8px;}&#xA;  .post-content .stage h4{font-family:&#34;Fraunces&#34;,serif;font-weight:600;font-size:24px;margin:0 0 16px;color:var(--ink);}&#xA;  [data-theme=&#34;dark&#34;] .post-content .stage h4{color:#fff;}&#xA;  .post-content .stage p{font-size:16px;line-height:1.62;color:var(--ink-soft);margin:14px 0 18px;}&#xA;  [data-theme=&#34;dark&#34;] .post-content .stage p{color:#d9cfbf;}&#xA;  .post-content .stage .mono{font-family:&#34;JetBrains Mono&#34;,monospace;font-size:13px;}&#xA;  .post-content .stage canvas{border:1px solid var(--panel-line);box-shadow:inset 0 0 0 1px rgba(0,0,0,.04),0 18px 42px -34px #000;}&#xA;&#xA;  .post-content .btn{font-family:&#34;JetBrains Mono&#34;,monospace;font-size:13px;cursor:pointer;border:none;background:var(--terra);color:#fff;padding:10px 17px;border-radius:8px;letter-spacing:.04em;transition:transform .12s,filter .12s,box-shadow .12s;box-shadow:0 10px 22px -18px var(--terra);}&#xA;  .post-content .btn:hover{filter:brightness(1.12);transform:translateY(-1px);}&#xA;  .post-content .btn:focus-visible{outline:2px solid var(--glow);outline-offset:3px;}&#xA;  .post-content .btn.just-clicked{animation:buttonPop .24s ease-out;}&#xA;  .post-content .btn.ghost{background:#fffaf2;border:1px solid var(--panel-line);color:var(--ink-soft);box-shadow:none;}&#xA;  [data-theme=&#34;dark&#34;] .post-content .btn.ghost{background:#15110f;border-color:#4b3e35;color:#d9cfbf;}&#xA;  .post-content .btn.ghost.on{background:var(--teal);border-color:var(--teal);color:#fff;}&#xA;  .post-content .btn.small{padding:7px 12px;font-size:12px;}&#xA;  .post-content .controls{display:flex;flex-wrap:wrap;gap:10px;align-items:center;margin:18px 0;}&#xA;  .post-content table.calc{width:100%;border-collapse:collapse;font-family:&#34;JetBrains Mono&#34;,monospace;font-size:13px;margin-top:10px;}&#xA;  .post-content table.calc td,.post-content table.calc th{padding:7px 8px;text-align:left;border-bottom:1px solid var(--panel-line);}&#xA;  .post-content table.calc th{color:var(--glow);font-weight:600;font-size:11px;letter-spacing:.08em;text-transform:uppercase;}&#xA;&#xA;  .post-content .sent{line-height:2.6;margin:10px 0;}&#xA;  .post-content .aw{position:relative;display:inline-block;padding:5px 9px;border-radius:7px;margin:3px;font-family:&#34;JetBrains Mono&#34;,monospace;font-size:15px;transition:background .35s,color .35s,transform .18s,box-shadow .18s;color:var(--ink);}&#xA;  [data-theme=&#34;dark&#34;] .post-content .aw{color:#f2eadf;}&#xA;  .post-content .aw:hover{transform:translateY(-1px);box-shadow:0 0 0 1px rgba(240,179,90,.35);}&#xA;  .post-content input[type=range]{width:100%;accent-color:var(--terra);height:24px;}&#xA;  .post-content .lbl{font-family:&#34;JetBrains Mono&#34;,monospace;font-size:12px;color:var(--ink-soft);display:flex;justify-content:space-between;}&#xA;  [data-theme=&#34;dark&#34;] .post-content .lbl{color:#cfc3b2;}&#xA;  .post-content .arch{display:flex;gap:18px;justify-content:center;flex-wrap:wrap;margin-top:12px;}&#xA;  .post-content .stackcol{flex:1;min-width:210px;}&#xA;  .post-content .stackcol h5{font-family:&#34;JetBrains Mono&#34;,monospace;font-size:12px;letter-spacing:.15em;text-transform:uppercase;color:var(--glow);text-align:center;margin:0 0 10px;}&#xA;  .post-content .block{background:#fffaf2;border:1px solid var(--panel-line);border-radius:9px;padding:11px 12px;margin:8px 0;font-size:13px;cursor:pointer;transition:all .2s;color:var(--ink-soft);}&#xA;  .post-content .block:hover{border-color:var(--terra);background:#f7edde;color:var(--ink);}&#xA;  .post-content .block b{color:var(--ink);font-family:&#34;Fraunces&#34;,serif;font-weight:600;font-size:15px;display:block;}&#xA;  [data-theme=&#34;dark&#34;] .post-content .block{background:var(--panel-2);color:#cfc3b2;}&#xA;  [data-theme=&#34;dark&#34;] .post-content .block b{color:#fff;}&#xA;  .post-content .explainbox{min-height:54px;margin-top:16px;padding:15px 16px;background:#fffaf2;border-radius:9px;font-size:14px;color:var(--ink-soft);border:1px dashed var(--panel-line);line-height:1.55;}&#xA;  [data-theme=&#34;dark&#34;] .post-content .explainbox{background:var(--canvas-bg);color:#d8cebd;border-color:#51443a;}&#xA;  .post-content .chart{display:flex;align-items:flex-end;gap:20px;height:230px;margin:24px 0 8px;padding:0 6px;}&#xA;  .post-content .chbar{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:flex-end;height:100%;}&#xA;  .post-content .chbar .col{width:100%;border-radius:8px 8px 0 0;background:var(--panel-2);transition:height 1.1s cubic-bezier(.2,.8,.2,1);height:0;position:relative;}&#xA;  .post-content .chbar.us .col{background:linear-gradient(180deg,var(--terra),var(--gold));}&#xA;  .post-content .chbar .val{font-family:&#34;JetBrains Mono&#34;,monospace;font-size:13px;margin-bottom:6px;color:var(--ink);}&#xA;  .post-content .chbar .name{font-family:&#34;JetBrains Mono&#34;,monospace;font-size:11px;margin-top:8px;color:var(--ink-soft);text-align:center;line-height:1.3;}&#xA;  .post-content .legend{font-family:&#34;JetBrains Mono&#34;,monospace;font-size:12px;color:var(--ink-soft);margin-top:12px;line-height:1.5;}&#xA;  [data-theme=&#34;dark&#34;] .post-content .chbar .val{color:#e7ddcd;}&#xA;  [data-theme=&#34;dark&#34;] .post-content .chbar .name{color:#9c8e7d;}&#xA;  [data-theme=&#34;dark&#34;] .post-content .legend{color:#c0b2a1;}&#xA;  .post-content .barwrap{background:var(--panel-2);border-radius:7px;overflow:hidden;}&#xA;  [data-theme=&#34;dark&#34;] .post-content .barwrap{background:#3a2f28;}&#xA;  [data-theme=&#34;dark&#34;] .post-content .block:hover{background:#3a2f28;color:#e7ddcd;}&#xA;&#xA;  .post-content .callout{background:#fff7e9;border-left:4px solid var(--gold);border-radius:0 12px 12px 0;padding:16px 18px;margin:20px 0;font-size:15px;color:var(--ink);line-height:1.5;}&#xA;  .post-content .callout .q{font-family:&#34;JetBrains Mono&#34;,monospace;font-size:12px;color:var(--gold);font-weight:600;letter-spacing:.1em;text-transform:uppercase;}&#xA;  .post-content .callout .a{color:var(--ink-soft);font-style:italic;}&#xA;  .post-content .callout .a b{color:var(--teal);font-style:normal;}&#xA;  [data-theme=&#34;dark&#34;] .post-content .callout{background:var(--panel-2);color:#e7ddcd;}&#xA;  [data-theme=&#34;dark&#34;] .post-content .callout .a{color:#cfc3b2;}&#xA;&#xA;  .post-content .bridge{display:grid;grid-template-columns:auto 1fr;gap:12px;align-items:start;background:linear-gradient(135deg,rgba(23,107,99,.12),rgba(207,149,42,.08));border:1px solid rgba(207,149,42,.28);border-radius:14px;padding:16px 18px;margin:30px 0;color:var(--ink);}&#xA;  .post-content .bridge .arrow{font-family:&#34;JetBrains Mono&#34;,monospace;color:var(--terra);font-weight:700;font-size:18px;line-height:1.4;}&#xA;  .post-content .bridge b{color:var(--teal);}&#xA;  .post-content .bridge p{margin:0;color:var(--ink-soft);line-height:1.55;}&#xA;  .post-content .depth{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;margin:26px 0;}&#xA;  .post-content .depth-card{background:var(--paper-2);border:1px solid rgba(91,81,71,.22);border-radius:10px;padding:14px 15px;}&#xA;  .post-content .depth-card h5{font-family:&#34;JetBrains Mono&#34;,monospace;font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--terra);margin:0 0 8px;}&#xA;  .post-content .depth-card p{margin:0;color:var(--ink-soft);font-size:14px;line-height:1.5;}&#xA;  .post-content .qa-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:24px 0 34px;}&#xA;  .post-content .qa-grid details{background:var(--paper-2);border:1px solid rgba(91,81,71,.24);border-radius:10px;padding:12px 14px;color:var(--ink-soft);}&#xA;  .post-content .qa-grid summary{cursor:pointer;font-family:&#34;JetBrains Mono&#34;,monospace;font-size:12px;line-height:1.45;color:var(--ink);font-weight:600;}&#xA;  .post-content .qa-grid details[open]{border-color:rgba(192,73,43,.45);box-shadow:0 12px 30px -24px rgba(33,20,10,.55);}&#xA;  .post-content .qa-grid p{font-size:14px;line-height:1.55;margin:10px 0 0;}&#xA;  .post-content .formula-note{font-family:&#34;JetBrains Mono&#34;,monospace;font-size:12px;color:var(--ink-soft);background:rgba(207,149,42,.1);border:1px dashed rgba(207,149,42,.4);border-radius:9px;padding:10px 12px;margin:12px 0 26px;}&#xA;&#xA;  .post-content .roadmap{max-width:920px;margin:34px auto 44px;display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;}&#xA;  .post-content .roadmap a{display:block;text-decoration:none;background:var(--paper-2);border:1px solid rgba(91,81,71,.22);border-radius:10px;padding:13px 14px;color:var(--ink-soft);transition:transform .14s,border-color .14s,background .14s;}&#xA;  .post-content .roadmap a:hover{transform:translateY(-2px);border-color:var(--terra);background:rgba(192,73,43,.08);}&#xA;  .post-content .roadmap b{display:block;font-family:&#34;JetBrains Mono&#34;,monospace;font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--terra);margin-bottom:5px;}&#xA;  .post-content .roadmap span{font-size:14px;line-height:1.35;}&#xA;  @media(max-width:760px){.post-content .depth,.post-content .qa-grid,.post-content .roadmap{grid-template-columns:1fr;}.post-content header.hero{min-height:auto;margin:16px 0;border-width:8px;padding:28px 20px;}.post-content .stage{padding:22px 16px;border-radius:12px;overflow-x:auto;}.post-content .bridge{grid-template-columns:1fr;}.post-content .chart{gap:10px;}.post-content .calc{overflow-x:auto;display:block;width:100%;}}&#xA;  @media(max-width:480px){.post-content .stage{padding:18px 12px;margin:28px 0;}.post-content .stage h4{font-size:20px;}.post-content .controls{gap:6px;}.post-content .btn{padding:8px 12px;font-size:12px;}.post-content .btn.small{padding:5px 8px;font-size:11px;}.post-content .headbtns{gap:4px;flex-wrap:nowrap;overflow-x:auto;-webkit-overflow-scrolling:touch;}.post-content .chbar .name{font-size:9px;overflow-wrap:break-word;}}&#xA;  @media(max-width:380px){.post-content .cell{width:32px;height:32px;font-size:10px;}.post-content .clockrow{gap:4px;}}&#xA;  [data-theme=&#34;dark&#34;] .post-content .bridge{background:linear-gradient(135deg,rgba(23,107,99,.18),rgba(207,149,42,.08));border-color:rgba(207,149,42,.22);}&#xA;  [data-theme=&#34;dark&#34;] .post-content .depth-card,[data-theme=&#34;dark&#34;] .post-content .qa-grid details,[data-theme=&#34;dark&#34;] .post-content .roadmap a{background:#211b16;border-color:#3a2f28;}&#xA;  [data-theme=&#34;dark&#34;] .post-content .qa-grid summary{color:#e8ddd0;}&#xA;&#xA;  .post-content .merge-row{display:flex;flex-wrap:wrap;gap:6px;margin:16px 0;font-family:&#34;JetBrains Mono&#34;,monospace;font-size:16px;}&#xA;  .post-content .merge-tok{background:#fffaf2;border:1px solid var(--panel-line);border-radius:7px;padding:6px 11px;color:var(--ink);transition:all .3s;animation:tokenIn .22s ease-out both;}&#xA;  .post-content .merge-tok.merging{border-color:var(--terra);background:#f7dfc9;color:#7f2d18;transform:scale(1.08);box-shadow:0 0 0 3px rgba(192,73,43,.18);}&#xA;  .post-content .merge-tok.merged{background:var(--teal);color:#fff;border-color:var(--teal);animation:mergePop .38s ease-out both;}&#xA;  [data-theme=&#34;dark&#34;] .post-content .merge-tok{background:#201a16;border-color:#4a3c32;color:#efe7da;}&#xA;  [data-theme=&#34;dark&#34;] .post-content .merge-tok.merging{background:#3a241c;color:var(--glow);}&#xA;&#xA;&#xA;  @keyframes stagePulse{0%{box-shadow:0 0 0 0 rgba(240,179,90,.28),0 24px 60px -28px rgba(33,20,10,.6);}100%{box-shadow:0 0 0 16px rgba(240,179,90,0),0 24px 60px -28px rgba(33,20,10,.6);}}&#xA;  @keyframes buttonPop{50%{transform:translateY(-1px) scale(1.035);}}&#xA;  @keyframes tokenIn{from{opacity:0;transform:translateY(5px) scale(.96);}to{opacity:1;transform:translateY(0) scale(1);}}&#xA;  @keyframes mergePop{0%{transform:scale(.92);}55%{transform:scale(1.12);}100%{transform:scale(1);}}&#xA;  .post-content .headbtns{display:flex;gap:6px;flex-wrap:wrap;}&#xA;  .post-content .bar{height:14px;border-radius:7px;background:linear-gradient(90deg,var(--teal),var(--glow));transition:width .5s cubic-bezier(.2,.8,.2,1);}&#xA;  .post-content .clockrow{display:flex;gap:6px;flex-wrap:wrap;margin:10px 0;}&#xA;  .post-content .cell{width:44px;height:44px;border-radius:8px;background:var(--panel-2);border:1px solid var(--panel-line);display:flex;align-items:center;justify-content:center;font-family:&#34;JetBrains Mono&#34;,monospace;font-size:13px;color:var(--ink-soft);transition:all .3s;}&#xA;  .post-content .cell.done{background:var(--teal);color:#fff;border-color:var(--teal);}&#xA;  .post-content .cell.active{background:var(--terra);color:#fff;border-color:var(--terra);transform:scale(1.08);}&#xA;  .post-content .token{font-family:&#34;JetBrains Mono&#34;,monospace;font-size:12px;display:inline-block;padding:3px 8px;background:rgba(23,107,99,.08);border:1px solid rgba(23,107,99,.18);border-radius:6px;color:var(--ink-soft);}&#xA;  [data-theme=&#34;dark&#34;] .post-content .token{background:rgba(23,107,99,.15);border-color:rgba(23,107,99,.25);color:#cfc3b2;}&#xA;  .post-content textarea{width:100%;min-height:120px;background:var(--canvas-bg);border:1px solid var(--panel-line);color:var(--ink);padding:12px;border-radius:10px;font-family:&#39;JetBrains Mono&#39;,monospace;font-size:13px;line-height:1.6;resize:vertical;}&#xA;  [data-theme=&#34;dark&#34;] .post-content textarea{background:var(--panel);color:#e7ddcd;}&#xA;  @media(prefers-reduced-motion:reduce){.post-content *{animation-duration:.001ms!important;transition-duration:.001ms!important;scroll-behavior:auto!important;}}&#xA;  .post-content .jorgan-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;margin:18px 0;}&#xA;  .post-content .jorgan-card{background:var(--paper-2);border:1px solid var(--panel-line);border-radius:10px;padding:15px;display:flex;flex-direction:column;gap:6px;}&#xA;  .post-content .jorgan-card h5{font-family:&#34;JetBrains Mono&#34;,monospace;font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--terra);margin:0;}&#xA;  .post-content .jorgan-card p{margin:0;font-size:14px;line-height:1.5;color:var(--ink-soft);font-family:&#34;Newsreader&#34;,serif;}&#xA;  .post-content .jorgan-card p b{color:var(--teal);font-weight:600;}&#xA;  .post-content .jorgan-card canvas{align-self:flex-start;border-radius:6px;border:1px solid var(--panel-line);background:var(--canvas-bg);}&#xA;  @media(max-width:600px){.post-content .jorgan-grid{grid-template-columns:1fr;}}&#xA;  [data-theme=&#34;dark&#34;] .post-content .jorgan-card{background:#211b16;border-color:#3a2f28;}&#xA;  .post-content .jorgan-section{font-family:&#34;JetBrains Mono&#34;,monospace;font-size:12px;letter-spacing:.16em;text-transform:uppercase;color:var(--glow);margin:0 0 4px;}&#xA;  .post-content .jorgan-wave-box{background:var(--panel-2);border:1px solid var(--panel-line);border-radius:10px;padding:16px;margin:16px 0;display:flex;flex-wrap:wrap;gap:16px;align-items:center;}&#xA;  .post-content .jorgan-wave-box canvas{border-radius:6px;background:var(--canvas-bg);border:1px solid var(--panel-line);}&#xA;  .post-content .jorgan-wave-box p{margin:0;flex:1;min-width:200px;font-size:14px;line-height:1.55;color:var(--ink-soft);}&#xA;  [data-theme=&#34;dark&#34;] .post-content .jorgan-wave-box{background:#211b16;border-color:#3a2f28;}&#xA;  .post-content .katex-display,.post-content .katex-block{overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;max-width:100%;}&#xA;&lt;/style&gt;&#xA;&lt;div id=&#34;progress&#34; data-testid=&#34;progress-bar&#34;&gt;&lt;/div&gt;&#xA;&lt;nav id=&#34;dots&#34; data-testid=&#34;dot-nav&#34;&gt;&lt;/nav&gt;&#xA;&lt;header class=&#34;hero&#34;&gt;&#xA;  &lt;div class=&#34;eyebrow&#34;&gt;A complete guide &amp;middot; From raw text to intelligence&lt;/div&gt;&#xA;  &lt;h1&gt;How LLMs&lt;br&gt;Actually &lt;span class=&#34;light&#34;&gt;Work&lt;/span&gt;&lt;/h1&gt;&#xA;  &lt;div style=&#34;font-family:&#39;JetBrains Mono&#39;,monospace;font-size:13px;letter-spacing:.14em;text-transform:uppercase;color:var(--teal);margin-top:2px;&#34;&gt;From Data to Intelligence&lt;/div&gt;&#xA;  &lt;div style=&#34;width:80px;height:2px;background:var(--terra);margin:16px 0 22px;&#34;&gt;&lt;/div&gt;&#xA;  &lt;div class=&#34;subtitle&#34;&gt;We start with a pile of internet text and build, step by step, a system that learns hidden patterns between words — then we look inside to see what it learned and how to make it better. Every mechanism explained visually, with things you can click and watch.&lt;/div&gt;&#xA;  &lt;div class=&#34;byline&#34;&gt;&#xA;    This is not about one paper or one model. It&#39;s about the full pipeline — &lt;b&gt;tokenization&lt;/b&gt;, &lt;b&gt;embeddings&lt;/b&gt;, &lt;b&gt;attention&lt;/b&gt;, &lt;b&gt;training&lt;/b&gt;, &lt;b&gt;inference&lt;/b&gt;, and &lt;b&gt;interpretability&lt;/b&gt; — that every modern LLM uses.&#xA;  &lt;/div&gt;&#xA;&lt;/header&gt;&#xA;&lt;div class=&#34;roadmap&#34; aria-label=&#34;Article roadmap&#34;&gt;&#xA;  &lt;a href=&#34;#s0&#34;&gt;&lt;b&gt;00 Key Terms&lt;/b&gt;&lt;span&gt;concepts visually explained&lt;/span&gt;&lt;/a&gt;&#xA;  &lt;a href=&#34;#s1&#34;&gt;&lt;b&gt;01 Foundation&lt;/b&gt;&lt;span&gt;neurons, weights, matrices&lt;/span&gt;&lt;/a&gt;&#xA;  &lt;a href=&#34;#s2&#34;&gt;&lt;b&gt;02 Data&lt;/b&gt;&lt;span&gt;tokenization and BPE&lt;/span&gt;&lt;/a&gt;&#xA;  &lt;a href=&#34;#s3&#34;&gt;&lt;b&gt;03 Meaning&lt;/b&gt;&lt;span&gt;embeddings as learned vectors&lt;/span&gt;&lt;/a&gt;&#xA;  &lt;a href=&#34;#s4&#34;&gt;&lt;b&gt;04 Order&lt;/b&gt;&lt;span&gt;positions and sequence structure&lt;/span&gt;&lt;/a&gt;&#xA;  &lt;a href=&#34;#s5&#34;&gt;&lt;b&gt;05 Context&lt;/b&gt;&lt;span&gt;attention, heads, masks&lt;/span&gt;&lt;/a&gt;&#xA;  &lt;a href=&#34;#s6&#34;&gt;&lt;b&gt;06 Memory&lt;/b&gt;&lt;span&gt;MLPs and residual stream&lt;/span&gt;&lt;/a&gt;&#xA;  &lt;a href=&#34;#s7&#34;&gt;&lt;b&gt;07 Learning&lt;/b&gt;&lt;span&gt;loss, gradients, scale&lt;/span&gt;&lt;/a&gt;&#xA;  &lt;a href=&#34;#s8&#34;&gt;&lt;b&gt;08 Use&lt;/b&gt;&lt;span&gt;fine-tuning and generation&lt;/span&gt;&lt;/a&gt;&#xA;  &lt;a href=&#34;#s9&#34;&gt;&lt;b&gt;09 Inspect&lt;/b&gt;&lt;span&gt;interpretability and circuits&lt;/span&gt;&lt;/a&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;&lt;span id=&#34;s0&#34; class=&#34;s&#34; data-testid=&#34;marker-section-0&#34;&gt;&lt;/span&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>Swapping Large or Stalled Files with mitmproxy</title>
      <link>/posts/tech/mitmproxy-swap-files/</link>
      <pubDate>Mon, 01 Jun 2026 00:00:00 +0000</pubDate>
      <guid>/posts/tech/mitmproxy-swap-files/</guid>
      <description>&lt;p&gt;You&amp;rsquo;re on a slow network, the JS bundle is 16 MB, and your page won&amp;rsquo;t finish loading. Or a CDN returns a 504 every third request. You already have the file locally — you just need the browser to use &lt;em&gt;that&lt;/em&gt; instead of fetching it again.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;mitmproxy&lt;/strong&gt; can intercept the specific request and swap in a local file transparently. The server never knows, the browser never knows, and the page loads instantly.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Classic 2D Games — Play in Browser</title>
      <link>/posts/tech/classic-2d-games/</link>
      <pubDate>Sun, 31 May 2026 00:00:00 +0000</pubDate>
      <guid>/posts/tech/classic-2d-games/</guid>
      <description>&lt;p&gt;5 classic arcade games, implemented from scratch in HTML Canvas + JavaScript. No frameworks, no dependencies — just you, the keyboard, and a few hundred lines of game loop.&lt;/p&gt;&#xA;&lt;style&gt;&#xA;.game-wrap{margin:2rem 0}&#xA;.game-wrap details{background:var(--surface-color);border:2px solid var(--border-color);border-radius:8px;margin:1rem 0;overflow:hidden}&#xA;.game-wrap details summary{padding:1rem 1.25rem;cursor:pointer;font-weight:700;font-size:1.1rem;user-select:none;background:var(--surface-color);border-bottom:2px solid transparent}&#xA;.game-wrap details[open] summary{border-bottom-color:var(--border-color);margin-bottom:0}&#xA;.game-body{padding:1.25rem;text-align:center}&#xA;.game-body canvas{display:block;margin:0 auto;background:#111;border-radius:4px;max-width:100%}&#xA;.game-controls{display:flex;gap:.5rem;justify-content:center;margin-top:.75rem;flex-wrap:wrap}&#xA;.game-controls button{padding:.4rem 1rem;font-family:var(--font-mono);font-size:.85rem;cursor:pointer;background:var(--link-color);color:#fff;border:none;border-radius:4px;transition:filter .15s}&#xA;.game-controls button:hover{filter:brightness(1.15)}&#xA;.game-controls button.secondary{background:var(--secondary-text)}&#xA;.game-score{margin-top:.5rem;font-family:var(--font-mono);font-size:1rem;color:var(--secondary-text)}&#xA;.game-hint{font-size:.78rem;color:var(--secondary-text);margin-top:.5rem;opacity:.7}&#xA;@media(max-width:600px){.game-body{padding:.75rem}.game-body canvas{width:100%!important;height:auto!important}}&#xA;&lt;/style&gt;&#xA;&lt;div class=&#34;game-wrap&#34;&gt;&#xA;&lt;h2 id=&#34;select-a-game&#34;&gt;Select a game&lt;/h2&gt;&#xA;&lt;details id=&#34;g-snake&#34;&gt;&#xA;&lt;summary&gt;🐍 Snake&lt;/summary&gt;&#xA;&lt;div class=&#34;game-body&#34;&gt;&#xA;&lt;canvas id=&#34;snakeCanvas&#34; width=&#34;400&#34; height=&#34;400&#34;&gt;&lt;/canvas&gt;&#xA;&lt;div class=&#34;game-controls&#34;&gt;&#xA;&lt;button onclick=&#34;startSnake()&#34;&gt;Start / Restart&lt;/button&gt;&#xA;&lt;/div&gt;&#xA;&lt;div class=&#34;game-score&#34;&gt;Score: &lt;span id=&#34;snakeScore&#34;&gt;0&lt;/span&gt;&lt;/div&gt;&#xA;&lt;div class=&#34;game-hint&#34;&gt;Arrow keys to move &amp;middot; P to pause&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;/details&gt;&#xA;&lt;details id=&#34;g-tetris&#34;&gt;&#xA;&lt;summary&gt;🧱 Tetris&lt;/summary&gt;&#xA;&lt;div class=&#34;game-body&#34;&gt;&#xA;&lt;canvas id=&#34;tetrisCanvas&#34; width=&#34;300&#34; height=&#34;600&#34;&gt;&lt;/canvas&gt;&#xA;&lt;div class=&#34;game-controls&#34;&gt;&#xA;&lt;button onclick=&#34;startTetris()&#34;&gt;Start / Restart&lt;/button&gt;&#xA;&lt;/div&gt;&#xA;&lt;div class=&#34;game-score&#34;&gt;Score: &lt;span id=&#34;tetrisScore&#34;&gt;0&lt;/span&gt; &amp;middot; Lines: &lt;span id=&#34;tetrisLines&#34;&gt;0&lt;/span&gt;&lt;/div&gt;&#xA;&lt;div class=&#34;game-hint&#34;&gt;← → move &amp;middot; ↑ rotate &amp;middot; ↓ soft drop &amp;middot; Space hard drop &amp;middot; P pause&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;/details&gt;&#xA;&lt;details id=&#34;g-pong&#34;&gt;&#xA;&lt;summary&gt;🏓 Pong&lt;/summary&gt;&#xA;&lt;div class=&#34;game-body&#34;&gt;&#xA;&lt;canvas id=&#34;pongCanvas&#34; width=&#34;600&#34; height=&#34;400&#34;&gt;&lt;/canvas&gt;&#xA;&lt;div class=&#34;game-controls&#34;&gt;&#xA;&lt;button onclick=&#34;startPong()&#34;&gt;Start / Restart&lt;/button&gt;&#xA;&lt;/div&gt;&#xA;&lt;div class=&#34;game-score&#34;&gt;&lt;span id=&#34;pongScore&#34;&gt;0 - 0&lt;/span&gt;&lt;/div&gt;&#xA;&lt;div class=&#34;game-hint&#34;&gt;W/S to move left paddle &amp;middot; ↑/↓ for right (2-player) &amp;middot; Space to serve&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;/details&gt;&#xA;&lt;details id=&#34;g-breakout&#34;&gt;&#xA;&lt;summary&gt;🧱 Breakout&lt;/summary&gt;&#xA;&lt;div class=&#34;game-body&#34;&gt;&#xA;&lt;canvas id=&#34;breakoutCanvas&#34; width=&#34;480&#34; height=&#34;360&#34;&gt;&lt;/canvas&gt;&#xA;&lt;div class=&#34;game-controls&#34;&gt;&#xA;&lt;button onclick=&#34;startBreakout()&#34;&gt;Start / Restart&lt;/button&gt;&#xA;&lt;/div&gt;&#xA;&lt;div class=&#34;game-score&#34;&gt;Score: &lt;span id=&#34;breakoutScore&#34;&gt;0&lt;/span&gt;&lt;/div&gt;&#xA;&lt;div class=&#34;game-hint&#34;&gt;← → to move paddle &amp;middot; mouse also works&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;/details&gt;&#xA;&lt;details id=&#34;g-minesweeper&#34;&gt;&#xA;&lt;summary&gt;💣 Minesweeper&lt;/summary&gt;&#xA;&lt;div class=&#34;game-body&#34;&gt;&#xA;&lt;canvas id=&#34;mineCanvas&#34; width=&#34;360&#34; height=&#34;360&#34;&gt;&lt;/canvas&gt;&#xA;&lt;div class=&#34;game-controls&#34;&gt;&#xA;&lt;button onclick=&#34;startMinesweeper()&#34;&gt;New Game&lt;/button&gt;&#xA;&lt;/div&gt;&#xA;&lt;div class=&#34;game-score&#34;&gt;&lt;span id=&#34;mineStatus&#34;&gt;Click a cell to start&lt;/span&gt;&lt;/div&gt;&#xA;&lt;div class=&#34;game-hint&#34;&gt;Left-click to reveal &amp;middot; Right-click to flag&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;/details&gt;&#xA;&lt;/div&gt;&#xA;&lt;script&gt;&#xA;/* ───────── Snake ───────── */&#xA;let snakeGame = null;&#xA;&#xA;function startSnake() {&#xA;  if (snakeGame) { snakeGame.stop(); snakeGame = null; }&#xA;  snakeGame = new SnakeGame();&#xA;}&#xA;&#xA;class SnakeGame {&#xA;  constructor() {&#xA;    this.canvas = document.getElementById(&#39;snakeCanvas&#39;);&#xA;    this.ctx = this.canvas.getContext(&#39;2d&#39;);&#xA;    this.size = 20; this.grid = 20;&#xA;    this.reset();&#xA;    this.bindKeys();&#xA;    this.loop();&#xA;  }&#xA;  reset() {&#xA;    this.snake = [{x:10,y:10}];&#xA;    this.dir = {x:1,y:0}; this.nextDir = {x:1,y:0};&#xA;    this.food = this.spawnFood();&#xA;    this.score = 0; this.running = true; this.paused = false;&#xA;    this.draw();&#xA;  }&#xA;  spawnFood() {&#xA;    let p;&#xA;    do { p = {x:Math.floor(Math.random()*this.grid), y:Math.floor(Math.random()*this.grid)}; }&#xA;    while (this.snake.some(s =&gt; s.x===p.x &amp;&amp; s.y===p.y));&#xA;    return p;&#xA;  }&#xA;  bindKeys() {&#xA;    this._key = e =&gt; {&#xA;      if (e.key===&#39;p&#39;||e.key===&#39;P&#39;) { this.paused = !this.paused; this.draw(); return; }&#xA;      if (this.paused) return;&#xA;      const map = {&#39;ArrowUp&#39;:{x:0,y:-1},&#39;ArrowDown&#39;:{x:0,y:1},&#39;ArrowLeft&#39;:{x:-1,y:0},&#39;ArrowRight&#39;:{x:1,y:0}};&#xA;      const d = map[e.key]; if (!d) return;&#xA;      if (d.x !== -this.dir.x || d.y !== -this.dir.y) this.nextDir = d;&#xA;      e.preventDefault();&#xA;    };&#xA;    document.addEventListener(&#39;keydown&#39;, this._key);&#xA;  }&#xA;  update() {&#xA;    if (!this.running || this.paused) return;&#xA;    this.dir = this.nextDir;&#xA;    const head = {x:this.snake[0].x+this.dir.x, y:this.snake[0].y+this.dir.y};&#xA;    if (head.x&lt;0||head.x&gt;=this.grid||head.y&lt;0||head.y&gt;=this.grid || this.snake.some(s=&gt;s.x===head.x&amp;&amp;s.y===head.y)) {&#xA;      this.running = false; this.draw(); return;&#xA;    }&#xA;    this.snake.unshift(head);&#xA;    if (head.x===this.food.x &amp;&amp; head.y===this.food.y) {&#xA;      this.score += 10; document.getElementById(&#39;snakeScore&#39;).textContent = this.score;&#xA;      this.food = this.spawnFood();&#xA;    } else this.snake.pop();&#xA;    this.draw();&#xA;  }&#xA;  draw() {&#xA;    const ctx = this.ctx, s = this.canvas.width/this.grid;&#xA;    ctx.fillStyle = &#39;#111&#39;; ctx.fillRect(0,0,this.canvas.width,this.canvas.height);&#xA;    this.snake.forEach((p,i) =&gt; { ctx.fillStyle = i===0?&#39;#39ff14&#39;:&#39;#1a8a0a&#39;; ctx.fillRect(p.x*s,p.y*s,s-1,s-1); });&#xA;    ctx.fillStyle = &#39;#ff3333&#39;; ctx.fillRect(this.food.x*s,this.food.y*s,s-1,s-1);&#xA;    if (!this.running) { ctx.fillStyle = &#39;rgba(0,0,0,.6)&#39;; ctx.fillRect(0,0,this.canvas.width,this.canvas.height);&#xA;      ctx.fillStyle = &#39;#fff&#39;; ctx.font = &#39;24px monospace&#39;; ctx.textAlign = &#39;center&#39;;&#xA;      ctx.fillText(&#39;Game Over&#39;, this.canvas.width/2, this.canvas.height/2); }&#xA;    else if (this.paused) { ctx.fillStyle = &#39;rgba(0,0,0,.5)&#39;; ctx.fillRect(0,0,this.canvas.width,this.canvas.height);&#xA;      ctx.fillStyle = &#39;#fff&#39;; ctx.font = &#39;24px monospace&#39;; ctx.textAlign = &#39;center&#39;;&#xA;      ctx.fillText(&#39;Paused&#39;, this.canvas.width/2, this.canvas.height/2); }&#xA;  }&#xA;  loop() { this.update(); this._timer = setTimeout(()=&gt;this.loop(), 120); }&#xA;  stop() { clearTimeout(this._timer); document.removeEventListener(&#39;keydown&#39;, this._key); }&#xA;}&#xA;&#xA;/* ───────── Tetris ───────── */&#xA;let tetrisGame = null;&#xA;&#xA;function startTetris() {&#xA;  if (tetrisGame) { tetrisGame.stop(); tetrisGame = null; }&#xA;  tetrisGame = new TetrisGame();&#xA;}&#xA;&#xA;class TetrisGame {&#xA;  constructor() {&#xA;    this.canvas = document.getElementById(&#39;tetrisCanvas&#39;);&#xA;    this.ctx = this.canvas.getContext(&#39;2d&#39;);&#xA;    this.cols = 10; this.rows = 20; this.bs = this.canvas.width/this.cols;&#xA;    this.pieces = [&#xA;      {blocks:[[1,1],[1,1]],color:&#39;#ffd700&#39;}, // O&#xA;      {blocks:[[0,1,0],[1,1,1]],color:&#39;#9b59b6&#39;}, // T&#xA;      {blocks:[[1,0,0],[1,1,1]],color:&#39;#3498db&#39;}, // L&#xA;      {blocks:[[0,0,1],[1,1,1]],color:&#39;#e67e22&#39;}, // J&#xA;      {blocks:[[1,1,0],[0,1,1]],color:&#39;#2ecc71&#39;}, // S&#xA;      {blocks:[[0,1,1],[1,1,0]],color:&#39;#e74c3c&#39;}, // Z&#xA;      {blocks:[[1,1,1,1]],color:&#39;#1abc9c&#39;} // I&#xA;    ];&#xA;    this.reset(); this.bindKeys(); this.loop();&#xA;  }&#xA;  reset() {&#xA;    this.board = Array.from({length:this.rows},()=&gt;Array(this.cols).fill(0));&#xA;    this.score = 0; this.lines = 0; this.running = true; this.paused = false; this.dropInt = 500;&#xA;    this.spawnPiece();&#xA;  }&#xA;  spawnPiece() {&#xA;    const p = this.pieces[Math.floor(Math.random()*this.pieces.length)];&#xA;    this.piece = {blocks:p.blocks, color:p.color, x:Math.floor((this.cols-p.blocks[0].length)/2), y:0};&#xA;    this.lockDelay = 0;&#xA;    if (this.collides(0,0)) { this.running = false; this.draw(); }&#xA;  }&#xA;  collides(dx,dy) {&#xA;    for (let r=0;r&lt;this.piece.blocks.length;r++) for (let c=0;c&lt;this.piece.blocks[r].length;c++) {&#xA;      if (!this.piece.blocks[r][c]) continue;&#xA;      const nx = this.piece.x+c+dx, ny = this.piece.y+r+dy;&#xA;      if (nx&lt;0||nx&gt;=this.cols||ny&gt;=this.rows) return true;&#xA;      if (ny&gt;=0 &amp;&amp; this.board[ny][nx]) return true;&#xA;    }&#xA;    return false;&#xA;  }&#xA;  lock() {&#xA;    for (let r=0;r&lt;this.piece.blocks.length;r++) for (let c=0;c&lt;this.piece.blocks[r].length;c++) {&#xA;      if (!this.piece.blocks[r][c]) continue;&#xA;      const ny = this.piece.y+r;&#xA;      if (ny&gt;=0) this.board[ny][this.piece.x+c] = this.piece.color;&#xA;    }&#xA;    this.clearLines(); this.spawnPiece();&#xA;  }&#xA;  clearLines() {&#xA;    let cleared = 0;&#xA;    for (let r=this.rows-1;r&gt;=0;r--) {&#xA;      if (this.board[r].every(c=&gt;c)) {&#xA;        this.board.splice(r,1); this.board.unshift(Array(this.cols).fill(0));&#xA;        cleared++; r++;&#xA;      }&#xA;    }&#xA;    if (cleared) {&#xA;      this.lines += cleared; this.score += [0,100,300,500,800][cleared];&#xA;      document.getElementById(&#39;tetrisScore&#39;).textContent = this.score;&#xA;      document.getElementById(&#39;tetrisLines&#39;).textContent = this.lines;&#xA;      this.dropInt = Math.max(100,500-this.lines*15);&#xA;    }&#xA;  }&#xA;  move(dx) { if (!this.collides(dx,0)) this.piece.x += dx; }&#xA;  rotate() {&#xA;    const old = this.piece.blocks;&#xA;    const rot = old[0].map((_,i)=&gt;old.map(r=&gt;r[i]).reverse());&#xA;    this.piece.blocks = rot;&#xA;    if (this.collides(0,0)) this.piece.blocks = old;&#xA;  }&#xA;  hardDrop() { while (!this.collides(0,1)) this.piece.y++; this.lock(); }&#xA;  bindKeys() {&#xA;    this._key = e =&gt; {&#xA;      if (e.key===&#39;p&#39;||e.key===&#39;P&#39;) { this.paused=!this.paused; this.draw(); return; }&#xA;      if (!this.running||this.paused) return;&#xA;      if (e.key===&#39;ArrowLeft&#39;) { this.move(-1); this.draw(); }&#xA;      if (e.key===&#39;ArrowRight&#39;) { this.move(1); this.draw(); }&#xA;      if (e.key===&#39;ArrowUp&#39;) { this.rotate(); this.draw(); }&#xA;      if (e.key===&#39;ArrowDown&#39;) { if (!this.collides(0,1)) { this.piece.y++; this.draw(); } }&#xA;      if (e.key===&#39; &#39;) { e.preventDefault(); this.hardDrop(); this.draw(); }&#xA;    };&#xA;    document.addEventListener(&#39;keydown&#39;, this._key);&#xA;  }&#xA;  draw() {&#xA;    const ctx = this.ctx, bs = this.bs;&#xA;    ctx.fillStyle=&#39;#111&#39;; ctx.fillRect(0,0,this.canvas.width,this.canvas.height);&#xA;    for (let r=0;r&lt;this.rows;r++) for (let c=0;c&lt;this.cols;c++) {&#xA;      if (this.board[r][c]) { ctx.fillStyle=this.board[r][c]; ctx.fillRect(c*bs,r*bs,bs-1,bs-1); }&#xA;    }&#xA;    if (this.piece &amp;&amp; this.running) {&#xA;      for (let r=0;r&lt;this.piece.blocks.length;r++) for (let c=0;c&lt;this.piece.blocks[r].length;c++) {&#xA;        if (!this.piece.blocks[r][c]) continue;&#xA;        const ny = this.piece.y+r;&#xA;        if (ny&gt;=0) { ctx.fillStyle=this.piece.color; ctx.fillRect((this.piece.x+c)*bs,ny*bs,bs-1,bs-1); }&#xA;      }&#xA;    }&#xA;    ctx.strokeStyle=&#39;#333&#39;; ctx.lineWidth=1; ctx.strokeRect(0,0,this.canvas.width,this.canvas.height);&#xA;    if (!this.running) { ctx.fillStyle=&#39;rgba(0,0,0,.6)&#39;; ctx.fillRect(0,0,this.canvas.width,this.canvas.height);&#xA;      ctx.fillStyle=&#39;#fff&#39;; ctx.font=&#39;24px monospace&#39;; ctx.textAlign=&#39;center&#39;; ctx.fillText(&#39;Game Over&#39;,this.canvas.width/2,this.canvas.height/2); }&#xA;    else if (this.paused) { ctx.fillStyle=&#39;rgba(0,0,0,.5)&#39;; ctx.fillRect(0,0,this.canvas.width,this.canvas.height);&#xA;      ctx.fillStyle=&#39;#fff&#39;; ctx.font=&#39;24px monospace&#39;; ctx.textAlign=&#39;center&#39;; ctx.fillText(&#39;Paused&#39;,this.canvas.width/2,this.canvas.height/2); }&#xA;  }&#xA;  loop() { this._timer = setTimeout(()=&gt;{ if(this.running&amp;&amp;!this.paused) { if(!this.collides(0,1))this.piece.y++; else this.lock(); this.draw(); } this.loop(); }, this.dropInt); }&#xA;  stop() { clearTimeout(this._timer); document.removeEventListener(&#39;keydown&#39;, this._key); }&#xA;}&#xA;&#xA;/* ───────── Pong ───────── */&#xA;let pongGame = null;&#xA;&#xA;function startPong() {&#xA;  if (pongGame) { pongGame.stop(); pongGame = null; }&#xA;  pongGame = new PongGame();&#xA;}&#xA;&#xA;class PongGame {&#xA;  constructor() {&#xA;    this.canvas = document.getElementById(&#39;pongCanvas&#39;);&#xA;    this.ctx = this.canvas.getContext(&#39;2d&#39;);&#xA;    this.W=this.canvas.width; this.H=this.canvas.height;&#xA;    this.reset(); this.bindKeys(); this.last=performance.now(); this.loop();&#xA;  }&#xA;  reset() {&#xA;    this.lScore=0; this.rScore=0;&#xA;    this.ball={x:this.W/2,y:this.H/2,dx:4*(Math.random()&gt;.5?1:-1),dy:3*(Math.random()&gt;.5?1:-1),r:6};&#xA;    this.paddleH=60; this.paddleW=8; this.lPaddle=this.H/2; this.rPaddle=this.H/2;&#xA;    this.keys={}; this.running=true; this.serving=true;&#xA;  }&#xA;  bindKeys() {&#xA;    this._down = e =&gt; { this.keys[e.key]=true; if(e.key===&#39; &#39;&amp;&amp;this.serving){this.serving=false;this.ball.dx=4*(Math.random()&gt;.5?1:-1);this.ball.dy=3*(Math.random()&gt;.5?1:-1);}};&#xA;    this._up = e =&gt; { this.keys[e.key]=false; };&#xA;    document.addEventListener(&#39;keydown&#39;,this._down); document.addEventListener(&#39;keyup&#39;,this._up);&#xA;  }&#xA;  update(dt) {&#xA;    if (!this.running) return;&#xA;    if (this.keys[&#39;w&#39;]||this.keys[&#39;W&#39;]) this.lPaddle = Math.max(0,this.lPaddle-400*dt);&#xA;    if (this.keys[&#39;s&#39;]||this.keys[&#39;S&#39;]) this.lPaddle = Math.min(this.H-this.paddleH,this.lPaddle+400*dt);&#xA;    if (this.keys[&#39;ArrowUp&#39;]) this.rPaddle = Math.max(0,this.rPaddle-400*dt);&#xA;    if (this.keys[&#39;ArrowDown&#39;]) this.rPaddle = Math.min(this.H-this.paddleH,this.rPaddle+400*dt);&#xA;    if (this.serving) return;&#xA;    this.ball.x += this.ball.dx; this.ball.y += this.ball.dy;&#xA;    if (this.ball.y-this.ball.r&lt;0||this.ball.y+this.ball.r&gt;this.H) this.ball.dy = -this.ball.dy;&#xA;    if (this.ball.x-this.ball.r&lt;this.paddleW &amp;&amp; this.ball.y&gt;this.lPaddle &amp;&amp; this.ball.y&lt;this.lPaddle+this.paddleH &amp;&amp; this.ball.dx&lt;0)&#xA;      { this.ball.dx = -this.ball.dx*1.05; this.ball.x = this.paddleW+this.ball.r; }&#xA;    if (this.ball.x+this.ball.r&gt;this.W-this.paddleW &amp;&amp; this.ball.y&gt;this.rPaddle &amp;&amp; this.ball.y&lt;this.rPaddle+this.paddleH &amp;&amp; this.ball.dx&gt;0)&#xA;      { this.ball.dx = -this.ball.dx*1.05; this.ball.x = this.W-this.paddleW-this.ball.r; }&#xA;    if (this.ball.x&lt;0) { this.rScore++; this.serving=true; this.ball={x:this.W/2,y:this.H/2,dx:0,dy:0,r:6}; }&#xA;    if (this.ball.x&gt;this.W) { this.lScore++; this.serving=true; this.ball={x:this.W/2,y:this.H/2,dx:0,dy:0,r:6}; }&#xA;    document.getElementById(&#39;pongScore&#39;).textContent = this.lScore+&#39; - &#39;+this.rScore;&#xA;  }&#xA;  draw() {&#xA;    const ctx=this.ctx;&#xA;    ctx.fillStyle=&#39;#111&#39;; ctx.fillRect(0,0,this.W,this.H);&#xA;    ctx.fillStyle=&#39;#fff&#39;; ctx.fillRect(0,this.lPaddle,this.paddleW,this.paddleH);&#xA;    ctx.fillRect(this.W-this.paddleW,this.rPaddle,this.paddleW,this.paddleH);&#xA;    ctx.beginPath(); ctx.arc(this.ball.x,this.ball.y,this.ball.r,0,Math.PI*2); ctx.fillStyle=&#39;#fff&#39;; ctx.fill();&#xA;    ctx.strokeStyle=&#39;#333&#39;; ctx.setLineDash([8,8]); ctx.beginPath(); ctx.moveTo(this.W/2,0); ctx.lineTo(this.W/2,this.H); ctx.stroke(); ctx.setLineDash([]);&#xA;    if (this.serving) { ctx.fillStyle=&#39;#fff&#39;; ctx.font=&#39;16px monospace&#39;; ctx.textAlign=&#39;center&#39;; ctx.fillText(&#39;Press Space to serve&#39;,this.W/2,30); }&#xA;  }&#xA;  loop() { const now=performance.now(); this.update((now-this.last)/1000); this.last=now; this.draw(); this._timer=requestAnimationFrame(()=&gt;this.loop()); }&#xA;  stop() { cancelAnimationFrame(this._timer); document.removeEventListener(&#39;keydown&#39;,this._down); document.removeEventListener(&#39;keyup&#39;,this._up); }&#xA;}&#xA;&#xA;/* ───────── Breakout ───────── */&#xA;let breakoutGame = null;&#xA;&#xA;function startBreakout() {&#xA;  if (breakoutGame) { breakoutGame.stop(); breakoutGame = null; }&#xA;  breakoutGame = new BreakoutGame();&#xA;}&#xA;&#xA;class BreakoutGame {&#xA;  constructor() {&#xA;    this.canvas = document.getElementById(&#39;breakoutCanvas&#39;);&#xA;    this.ctx = this.canvas.getContext(&#39;2d&#39;);&#xA;    this.W=this.canvas.width; this.H=this.canvas.height;&#xA;    this.reset(); this.bindKeys(); this.bindMouse(); this.last=performance.now(); this.loop();&#xA;  }&#xA;  reset() {&#xA;    this.paddleW=80; this.paddleH=12; this.paddleX=(this.W-this.paddleW)/2;&#xA;    this.ball={x:this.W/2,y:this.H-30,r:6,dx:4,dy:-4};&#xA;    this.bricks=[]; this.score=0; this.running=true;&#xA;    const colors=[&#39;#ff6b6b&#39;,&#39;#ffd93d&#39;,&#39;#6bcb77&#39;,&#39;#4d96ff&#39;,&#39;#9b59b6&#39;];&#xA;    for (let r=0;r&lt;5;r++) for (let c=0;c&lt;8;c++)&#xA;      this.bricks.push({x:c*60+4,y:r*22+4,w:56,h:18,color:colors[r],alive:true});&#xA;  }&#xA;  bindKeys() {&#xA;    this._key = e =&gt; { if (e.key===&#39;ArrowLeft&#39;) this.moveLeft=true; if (e.key===&#39;ArrowRight&#39;) this.moveRight=true; if (e.key===&#39; &#39;) { e.preventDefault(); this.launch(); } };&#xA;    this._keyUp = e =&gt; { if (e.key===&#39;ArrowLeft&#39;) this.moveLeft=false; if (e.key===&#39;ArrowRight&#39;) this.moveRight=false; };&#xA;    document.addEventListener(&#39;keydown&#39;,this._key); document.addEventListener(&#39;keyup&#39;,this._keyUp);&#xA;  }&#xA;  bindMouse() {&#xA;    this._mouse = e =&gt; { const r=this.canvas.getBoundingClientRect(); this.mouseX=e.clientX-r.left; };&#xA;    document.addEventListener(&#39;mousemove&#39;,this._mouse);&#xA;  }&#xA;  launch() { if (!this.running) this.reset(); }&#xA;  update(dt) {&#xA;    if (!this.running) return;&#xA;    const speed=400;&#xA;    if (this.moveLeft) this.paddleX=Math.max(0,this.paddleX-speed*dt);&#xA;    if (this.moveRight) this.paddleX=Math.min(this.W-this.paddleW,this.paddleX+speed*dt);&#xA;    if (this.mouseX!=null) this.paddleX=Math.max(0,Math.min(this.W-this.paddleW,this.mouseX-this.paddleW/2));&#xA;    this.ball.x+=this.ball.dx; this.ball.y+=this.ball.dy;&#xA;    if (this.ball.x-this.ball.r&lt;0||this.ball.x+this.ball.r&gt;this.W) this.ball.dx=-this.ball.dx;&#xA;    if (this.ball.y-this.ball.r&lt;0) this.ball.dy=-this.ball.dy;&#xA;    if (this.ball.y+this.ball.r&gt;this.H) { this.running=false; this.ball.dx=0; this.ball.dy=0; return; }&#xA;    if (this.ball.y+this.ball.r&gt;this.H-this.paddleH-5 &amp;&amp; this.ball.x&gt;this.paddleX &amp;&amp; this.ball.x&lt;this.paddleX+this.paddleW) {&#xA;      const hit=(this.ball.x-this.paddleX)/this.paddleW;&#xA;      const angle=hit*Math.PI-Math.PI/2;&#xA;      const spd=Math.sqrt(this.ball.dx*this.ball.dx+this.ball.dy*this.ball.dy);&#xA;      this.ball.dx=Math.cos(angle)*spd; this.ball.dy=-Math.abs(Math.sin(angle)*spd);&#xA;      this.ball.y=this.H-this.paddleH-5-this.ball.r;&#xA;    }&#xA;    for (const b of this.bricks) {&#xA;      if (!b.alive) continue;&#xA;      if (this.ball.x&gt; b.x &amp;&amp; this.ball.x&lt;b.x+b.w &amp;&amp; this.ball.y&gt; b.y &amp;&amp; this.ball.y&lt;b.y+b.h) {&#xA;        b.alive=false; this.ball.dy=-this.ball.dy; this.score+=10;&#xA;        document.getElementById(&#39;breakoutScore&#39;).textContent=this.score;&#xA;      }&#xA;    }&#xA;  }&#xA;  draw() {&#xA;    const ctx=this.ctx;&#xA;    ctx.fillStyle=&#39;#111&#39;; ctx.fillRect(0,0,this.W,this.H);&#xA;    for (const b of this.bricks) if(b.alive){ctx.fillStyle=b.color;ctx.fillRect(b.x,b.y,b.w,b.h);}&#xA;    ctx.fillStyle=&#39;#fff&#39;; ctx.fillRect(this.paddleX,this.H-this.paddleH-5,this.paddleW,this.paddleH);&#xA;    ctx.beginPath(); ctx.arc(this.ball.x,this.ball.y,this.ball.r,0,Math.PI*2); ctx.fillStyle=&#39;#fff&#39;; ctx.fill();&#xA;    if (!this.running) { ctx.fillStyle=&#39;rgba(0,0,0,.5)&#39;; ctx.fillRect(0,0,this.W,this.H);&#xA;      ctx.fillStyle=&#39;#fff&#39;; ctx.font=&#39;24px monospace&#39;; ctx.textAlign=&#39;center&#39;; ctx.fillText(&#39;Game Over&#39;,this.W/2,this.H/2);&#xA;      ctx.font=&#39;14px monospace&#39;; ctx.fillText(&#39;Press Start again&#39;,this.W/2,this.H/2+30); }&#xA;  }&#xA;  loop() { const now=performance.now(); this.update((now-this.last)/1000); this.last=now; this.draw(); this._timer=requestAnimationFrame(()=&gt;this.loop()); }&#xA;  stop() { cancelAnimationFrame(this._timer); document.removeEventListener(&#39;keydown&#39;,this._key); document.removeEventListener(&#39;keyup&#39;,this._keyUp); document.removeEventListener(&#39;mousemove&#39;,this._mouse); }&#xA;}&#xA;&#xA;/* ───────── Minesweeper ───────── */&#xA;let mineGame = null;&#xA;&#xA;function startMinesweeper() {&#xA;  if (mineGame) { mineGame.stop(); mineGame = null; }&#xA;  mineGame = new MineGame();&#xA;}&#xA;&#xA;class MineGame {&#xA;  constructor() {&#xA;    this.canvas=document.getElementById(&#39;mineCanvas&#39;);&#xA;    this.ctx=this.canvas.getContext(&#39;2d&#39;);&#xA;    this.rows=9; this.cols=9; this.mines=10; this.bs=this.canvas.width/this.cols;&#xA;    this.reset(); this.bindMouse();&#xA;  }&#xA;  reset() {&#xA;    this.board=Array.from({length:this.rows},()=&gt;Array(this.cols).fill(0));&#xA;    this.revealed=Array.from({length:this.rows},()=&gt;Array(this.cols).fill(false));&#xA;    this.flagged=Array.from({length:this.rows},()=&gt;Array(this.cols).fill(false));&#xA;    this.gameOver=false; this.won=false; this.first=true;&#xA;    document.getElementById(&#39;mineStatus&#39;).textContent=&#39;Click a cell to start&#39;;&#xA;    this.draw();&#xA;  }&#xA;  plant(startR,startC) {&#xA;    let placed=0;&#xA;    while(placed&lt;this.mines) {&#xA;      const r=Math.floor(Math.random()*this.rows), c=Math.floor(Math.random()*this.cols);&#xA;      if (this.board[r][c]===-1||(Math.abs(r-startR)&lt;=1&amp;&amp;Math.abs(c-startC)&lt;=1)) continue;&#xA;      this.board[r][c]=-1; placed++;&#xA;    }&#xA;    for(let r=0;r&lt;this.rows;r++) for(let c=0;c&lt;this.cols;c++)&#xA;      if(this.board[r][c]!==-1) this.board[r][c]=this.countAdj(r,c);&#xA;  }&#xA;  countAdj(r,c) { let n=0; for(let dr=-1;dr&lt;=1;dr++) for(let dc=-1;dc&lt;=1;dc++) { const nr=r+dr,nc=c+dc; if(nr&gt;=0&amp;&amp;nr&lt;this.rows&amp;&amp;nc&gt;=0&amp;&amp;nc&lt;this.cols&amp;&amp;this.board[nr][nc]===-1) n++; } return n; }&#xA;  reveal(r,c) {&#xA;    if(r&lt;0||r&gt;=this.rows||c&lt;0||c&gt;=this.cols||this.revealed[r][c]||this.flagged[r][c]) return;&#xA;    this.revealed[r][c]=true;&#xA;    if(this.board[r][c]===-1) { this.gameOver=true; this.draw(); document.getElementById(&#39;mineStatus&#39;).textContent=&#39;💥 Game Over! Click New Game&#39;; return; }&#xA;    if(this.board[r][c]===0) for(let dr=-1;dr&lt;=1;dr++) for(let dc=-1;dc&lt;=1;dc++) this.reveal(r+dr,c+dc);&#xA;    this.checkWin();&#xA;  }&#xA;  checkWin() {&#xA;    let total=0,rev=0;&#xA;    for(let r=0;r&lt;this.rows;r++) for(let c=0;c&lt;this.cols;c++) { total++; if(this.revealed[r][c]||this.flagged[r][c]) rev++; }&#xA;    this.won=(total-rev)===this.mines;&#xA;    if(this.won) document.getElementById(&#39;mineStatus&#39;).textContent=&#39;🎉 You Win!&#39;;&#xA;    else document.getElementById(&#39;mineStatus&#39;).textContent=&#39;Keep going...&#39;;&#xA;  }&#xA;  bindMouse() {&#xA;    this._click = e =&gt; {&#xA;      const r=this.canvas.getBoundingClientRect();&#xA;      const x=Math.floor((e.clientX-r.left)/this.bs), y=Math.floor((e.clientY-r.top)/this.bs);&#xA;      if(x&lt;0||x&gt;=this.cols||y&lt;0||y&gt;=this.rows||this.gameOver||this.won) return;&#xA;      if(e.button===2) { this.flagged[y][x]=!this.flagged[y][x]; this.draw(); e.preventDefault(); return; }&#xA;      if(this.first) { this.plant(y,x); this.first=false; }&#xA;      this.reveal(y,x); this.draw();&#xA;    };&#xA;    this._cm = e =&gt; e.preventDefault();&#xA;    this.canvas.addEventListener(&#39;mousedown&#39;,this._click);&#xA;    this.canvas.addEventListener(&#39;contextmenu&#39;,this._cm);&#xA;  }&#xA;  draw() {&#xA;    const ctx=this.ctx, bs=this.bs;&#xA;    for(let r=0;r&lt;this.rows;r++) for(let c=0;c&lt;this.cols;c++) {&#xA;      ctx.fillStyle=this.revealed[r][c]?&#39;#2a2a2a&#39;:&#39;#333&#39;; ctx.fillRect(c*bs,r*bs,bs-1,bs-1);&#xA;      if(this.flagged[r][c]) { ctx.fillStyle=&#39;#ff4444&#39;; ctx.font=&#39;16px monospace&#39;; ctx.textAlign=&#39;center&#39;; ctx.textBaseline=&#39;middle&#39;; ctx.fillText(&#39;🚩&#39;,c*bs+bs/2,r*bs+bs/2); }&#xA;      else if(this.revealed[r][c]) {&#xA;        if(this.board[r][c]===-1) { ctx.fillStyle=&#39;#ff0000&#39;; ctx.font=&#39;16px monospace&#39;; ctx.textAlign=&#39;center&#39;; ctx.textBaseline=&#39;middle&#39;; ctx.fillText(&#39;💣&#39;,c*bs+bs/2,r*bs+bs/2); }&#xA;        else if(this.board[r][c]&gt;0) { ctx.fillStyle=[&#39;#fff&#39;,&#39;#4d96ff&#39;,&#39;#6bcb77&#39;,&#39;#ff6b6b&#39;,&#39;#9b59b6&#39;,&#39;#ffd93d&#39;,&#39;#ff9ff3&#39;,&#39;#54a0ff&#39;,&#39;#5f27cd&#39;][this.board[r][c]]; ctx.font=&#39;bold 14px monospace&#39;; ctx.textAlign=&#39;center&#39;; ctx.textBaseline=&#39;middle&#39;; ctx.fillText(this.board[r][c],c*bs+bs/2,r*bs+bs/2); }&#xA;      }&#xA;    }&#xA;    if(this.gameOver) for(let r=0;r&lt;this.rows;r++) for(let c=0;c&lt;this.cols;c++) if(this.board[r][c]===-1&amp;&amp;!this.revealed[r][c]) { ctx.fillStyle=&#39;#cc4444&#39;; ctx.font=&#39;14px monospace&#39;; ctx.textAlign=&#39;center&#39;; ctx.textBaseline=&#39;middle&#39;; ctx.fillText(&#39;💣&#39;,c*bs+bs/2,r*bs+bs/2); }&#xA;  }&#xA;  stop() { this.canvas.removeEventListener(&#39;mousedown&#39;,this._click); this.canvas.removeEventListener(&#39;contextmenu&#39;,this._cm); }&#xA;}&#xA;&lt;/script&gt;</description>
    </item>
    <item>
      <title>Lightning Fast Android &amp; Gradle Builds</title>
      <link>/posts/tech/android-gradle-fast-build/</link>
      <pubDate>Sun, 31 May 2026 00:00:00 +0000</pubDate>
      <guid>/posts/tech/android-gradle-fast-build/</guid>
      <description>&lt;p&gt;Waiting 3–5 minutes for every Gradle build is a productivity killer. Here&amp;rsquo;s every optimisation I apply to cut build times down to seconds — not minutes.&lt;/p&gt;&#xA;&lt;p&gt;On my machine: &lt;strong&gt;20GB RAM + 12th Gen Intel Core i3&lt;/strong&gt; (4 P-cores, no E-cores). These settings are tuned to squeeze every drop of performance out of that hardware.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-one-shot-config&#34;&gt;The One-Shot Config&lt;/h2&gt;&#xA;&lt;p&gt;Drop this into your project-level &lt;code&gt;gradle.properties&lt;/code&gt;. Tuned for 20GB RAM + 12th Gen Intel.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Spaceboy Theme: Configuration Reference</title>
      <link>/posts/tech/spaceboy-config-guide/</link>
      <pubDate>Fri, 29 May 2026 00:00:00 +0000</pubDate>
      <guid>/posts/tech/spaceboy-config-guide/</guid>
      <description>&lt;p&gt;Every parameter can be set in two places. &lt;strong&gt;&lt;code&gt;hugo.toml [params]&lt;/code&gt;&lt;/strong&gt; sets the site-wide default. &lt;strong&gt;Front matter&lt;/strong&gt; on a single post overrides it. Front matter always wins.&lt;/p&gt;&#xA;&lt;h2 id=&#34;typography&#34;&gt;Typography&lt;/h2&gt;&#xA;&lt;h3 id=&#34;font-family&#34;&gt;Font Family&lt;/h3&gt;&#xA;&lt;p&gt;The &lt;code&gt;font&lt;/code&gt; parameter replaces &lt;code&gt;--font-body&lt;/code&gt; for the entire page — headings, nav, and body text.&lt;/p&gt;&#xA;&lt;p&gt;By default (no &lt;code&gt;font&lt;/code&gt; parameter), &lt;code&gt;--font-body&lt;/code&gt; uses &lt;strong&gt;Atkinson Hyperlegible&lt;/strong&gt; (Google Fonts), a highly-legible sans-serif designed for low-vision readers.&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Value&lt;/th&gt;&#xA;          &lt;th&gt;Renders as&lt;/th&gt;&#xA;          &lt;th&gt;Best for&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;em&gt;(none)&lt;/em&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Atkinson Hyperlegible &lt;em&gt;(default)&lt;/em&gt;&lt;/td&gt;&#xA;          &lt;td&gt;General reading, accessibility&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;mono&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;JetBrains Mono&lt;/td&gt;&#xA;          &lt;td&gt;Tech posts, code-heavy writing&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;serif&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Georgia → Charter → Palatino&lt;/td&gt;&#xA;          &lt;td&gt;Stories, novels, essays, research&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;sans&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;system-ui → Segoe UI → Roboto&lt;/td&gt;&#xA;          &lt;td&gt;Short-form, recipes, reviews&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;any CSS string&lt;/td&gt;&#xA;          &lt;td&gt;Custom font stack&lt;/td&gt;&#xA;          &lt;td&gt;Total control&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;div class=&#34;code-block-wrapper&#34; data-lang=&#34;yaml&#34; data-testid=&#34;code-block&#34;&gt;&#xA;  &lt;div class=&#34;code-toolbar&#34; data-testid=&#34;code-toolbar&#34;&gt;&#xA;    &lt;span class=&#34;lang-code-btn&#34; aria-label=&#34;Code language: yaml&#34; data-testid=&#34;lang-code-btn&#34;&gt;yaml&lt;/span&gt;&#xA;    &lt;button class=&#34;wrap-code-btn&#34; aria-label=&#34;Toggle line wrap&#34; title=&#34;Toggle line wrap&#34; data-testid=&#34;wrap-code-btn&#34;&gt;&#xA;      &lt;svg width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34;&gt;&lt;line x1=&#34;3&#34; y1=&#34;6&#34; x2=&#34;21&#34; y2=&#34;6&#34;&gt;&lt;/line&gt;&lt;path d=&#34;M3 12h15a3 3 0 1 1 0 6h-4&#34;&gt;&lt;/path&gt;&lt;polyline points=&#34;16 16 14 18 16 20&#34;&gt;&lt;/polyline&gt;&lt;line x1=&#34;3&#34; y1=&#34;18&#34; x2=&#34;10&#34; y2=&#34;18&#34;&gt;&lt;/line&gt;&lt;/svg&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;button class=&#34;copy-code-btn&#34; aria-label=&#34;Copy code&#34; data-testid=&#34;copy-code-btn&#34;&gt;&#xA;      &lt;svg width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34;&gt;&lt;rect x=&#34;9&#34; y=&#34;9&#34; width=&#34;13&#34; height=&#34;13&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;&lt;path d=&#34;M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;&#xA;    &lt;/button&gt;&#xA;  &lt;/div&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;font&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;serif&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;div class=&#34;callout callout-note&#34; role=&#34;note&#34;&gt;&#xA;  &lt;div class=&#34;callout-title&#34;&gt;Note&lt;/div&gt;&#xA;  &lt;div class=&#34;callout-body&#34;&gt;&lt;code&gt;font: serif&lt;/code&gt; also sets &lt;code&gt;--letter-spacing-normal: 0.01em&lt;/code&gt; and &lt;code&gt;--line-height-body: 1.9&lt;/code&gt; automatically — both improve serif body readability without any extra config.&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&#xA;&lt;h3 id=&#34;drop-cap&#34;&gt;Drop Cap&lt;/h3&gt;&#xA;&lt;p&gt;Enlarges the first letter of the first paragraph using CSS &lt;code&gt;::first-letter&lt;/code&gt;. No markup changes needed.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Docker to Setup (D2S) - Frappe Multi-Site Bench</title>
      <link>/posts/tech/d2s-frappe-multisite/</link>
      <pubDate>Mon, 25 May 2026 00:00:00 +0000</pubDate>
      <guid>/posts/tech/d2s-frappe-multisite/</guid>
      <description>&lt;p&gt;Step-by-step guide to create and run multiple isolated Frappe sites using &lt;strong&gt;Traefik reverse proxy&lt;/strong&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Based on &lt;a href=&#34;https://github.com/frappe/frappe_docker&#34; class=&#34;external-link&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;frappe_docker&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;architecture&#34;&gt;Architecture&lt;/h2&gt;&#xA;&lt;h3 id=&#34;what-we-built&#34;&gt;What We Built&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Complete isolation&lt;/strong&gt;: Each site has separate MariaDB, Redis, and application containers&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Auto-discovery&lt;/strong&gt;: Traefik routes based on &lt;code&gt;Host&lt;/code&gt; header — no manual port mapping&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Scalable&lt;/strong&gt;: Add unlimited sites without changing existing configuration&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Single entrypoint&lt;/strong&gt;: All sites share one HTTP port (&lt;code&gt;:8100&lt;/code&gt;)&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&#xA;&#xA;&#xA;  &#xA;  &lt;figure class=&#34;mermaid-diagram&#34; data-testid=&#34;mermaid-diagram&#34;&gt;&lt;svg id=&#34;mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79&#34; width=&#34;100%&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34; xmlns:xlink=&#34;http://www.w3.org/1999/xlink&#34; class=&#34;flowchart&#34; style=&#34;max-width: 1176.52px; background-color: transparent;&#34; viewBox=&#34;0 -0.000003814697265625 1176.515625 425.6409606933594&#34; role=&#34;graphics-document document&#34; aria-roledescription=&#34;flowchart-v2&#34;&gt;&lt;style&gt;:root{--chart-line-1:#c0492b;--chart-line-2:#176b63;--chart-line-3:#cf952a;}[data-theme=&#34;dark&#34;]{--chart-line-1:#ff7b5a;--chart-line-2:#5ab8b0;--chart-line-3:#ffcc5a;}&lt;/style&gt;&lt;style&gt;#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79{font-family:&#34;trebuchet ms&#34;,verdana,arial,sans-serif;font-size:16px;fill:var(--text-color);}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .error-icon{fill:#552222;}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .error-text{fill:#552222;stroke:#552222;}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .edge-thickness-normal{stroke-width:1px;}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .edge-thickness-thick{stroke-width:3.5px;}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .edge-pattern-solid{stroke-dasharray:0;}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .edge-thickness-invisible{stroke-width:0;fill:none;}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .edge-pattern-dashed{stroke-dasharray:3;}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .edge-pattern-dotted{stroke-dasharray:2;}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .marker{fill:var(--link-color);stroke:var(--link-color);}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .marker.cross{stroke:var(--link-color);}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 svg{font-family:&#34;trebuchet ms&#34;,verdana,arial,sans-serif;font-size:16px;}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 p{margin:0;}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .label{font-family:&#34;trebuchet ms&#34;,verdana,arial,sans-serif;color:var(--text-color);}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .cluster-label text{fill:var(--text-color);}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .cluster-label span{color:var(--text-color);}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .cluster-label span p{background-color:transparent;}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .label text,#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 span{fill:var(--text-color);color:var(--text-color);}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .node rect,#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .node circle,#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .node ellipse,#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .node polygon,#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .node path{fill:var(--surface-color);stroke:var(--text-color);stroke-width:1px;}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .rough-node .label text,#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .node .label text,#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .image-shape .label,#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .icon-shape .label{text-anchor:middle;}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .rough-node .label,#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .node .label,#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .image-shape .label,#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .icon-shape .label{text-align:center;}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .node.clickable{cursor:pointer;}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .root .anchor path{fill:var(--link-color)!important;stroke-width:0;stroke:var(--link-color);}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .arrowheadPath{fill:var(--text-color);}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .edgePath .path{stroke:var(--link-color);stroke-width:1px;}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .flowchart-link{stroke:var(--link-color);fill:none;}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .edgeLabel{background-color:var(--surface-color);text-align:center;}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .edgeLabel p{background-color:var(--surface-color);}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .edgeLabel rect{opacity:0.5;background-color:var(--surface-color);fill:var(--surface-color);}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .labelBkg{background-color:rgba(191, 222, 205, 0.5);}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .cluster rect{fill:var(--code-bg);stroke:var(--text-color);stroke-width:1px;}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .cluster text{fill:var(--text-color);}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .cluster span{color:var(--text-color);}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:&#34;trebuchet ms&#34;,verdana,arial,sans-serif;font-size:12px;background:var(--code-bg);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:var(--text-color);}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 rect.text{fill:none;stroke-width:0;}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .icon-shape,#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .image-shape{background-color:var(--surface-color);text-align:center;}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .icon-shape p,#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .image-shape p{background-color:var(--surface-color);padding:2px;}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .icon-shape .label rect,#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .image-shape .label rect{opacity:0.5;background-color:var(--surface-color);fill:var(--surface-color);}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 .node .neo-node{stroke:var(--text-color);}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 [data-look=&#34;neo&#34;].node rect,#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 [data-look=&#34;neo&#34;].cluster rect,#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 [data-look=&#34;neo&#34;].node polygon{stroke:var(--text-color);filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 [data-look=&#34;neo&#34;].node path{stroke:var(--text-color);stroke-width:1px;}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 [data-look=&#34;neo&#34;].node .outer-path{filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 [data-look=&#34;neo&#34;].node .neo-line path{stroke:var(--text-color);filter:none;}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 [data-look=&#34;neo&#34;].node circle{stroke:var(--text-color);filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 [data-look=&#34;neo&#34;].node circle .state-start{fill:#000000;}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 [data-look=&#34;neo&#34;].icon-shape .icon{fill:var(--text-color);filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 [data-look=&#34;neo&#34;].icon-shape .icon-neo path{stroke:var(--text-color);filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79 :root{--mermaid-font-family:&#34;trebuchet ms&#34;,verdana,arial,sans-serif;}&lt;/style&gt;&lt;g&gt;&lt;marker id=&#34;mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79_flowchart-v2-pointEnd&#34; class=&#34;marker flowchart-v2&#34; viewBox=&#34;0 0 10 10&#34; refX=&#34;5&#34; refY=&#34;5&#34; markerUnits=&#34;userSpaceOnUse&#34; markerWidth=&#34;8&#34; markerHeight=&#34;8&#34; orient=&#34;auto&#34;&gt;&lt;path d=&#34;M 0 0 L 10 5 L 0 10 z&#34; class=&#34;arrowMarkerPath&#34; style=&#34;stroke-width: 1; stroke-dasharray: 1, 0;&#34;/&gt;&lt;/marker&gt;&lt;marker id=&#34;mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79_flowchart-v2-pointStart&#34; class=&#34;marker flowchart-v2&#34; viewBox=&#34;0 0 10 10&#34; refX=&#34;4.5&#34; refY=&#34;5&#34; markerUnits=&#34;userSpaceOnUse&#34; markerWidth=&#34;8&#34; markerHeight=&#34;8&#34; orient=&#34;auto&#34;&gt;&lt;path d=&#34;M 0 5 L 10 10 L 10 0 z&#34; class=&#34;arrowMarkerPath&#34; style=&#34;stroke-width: 1; stroke-dasharray: 1, 0;&#34;/&gt;&lt;/marker&gt;&lt;marker id=&#34;mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79_flowchart-v2-pointEnd-margin&#34; class=&#34;marker flowchart-v2&#34; viewBox=&#34;0 0 11.5 14&#34; refX=&#34;11.5&#34; refY=&#34;7&#34; markerUnits=&#34;userSpaceOnUse&#34; markerWidth=&#34;10.5&#34; markerHeight=&#34;14&#34; orient=&#34;auto&#34;&gt;&lt;path d=&#34;M 0 0 L 11.5 7 L 0 14 z&#34; class=&#34;arrowMarkerPath&#34; style=&#34;stroke-width: 0; stroke-dasharray: 1, 0;&#34;/&gt;&lt;/marker&gt;&lt;marker id=&#34;mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79_flowchart-v2-pointStart-margin&#34; class=&#34;marker flowchart-v2&#34; viewBox=&#34;0 0 11.5 14&#34; refX=&#34;1&#34; refY=&#34;7&#34; markerUnits=&#34;userSpaceOnUse&#34; markerWidth=&#34;11.5&#34; markerHeight=&#34;14&#34; orient=&#34;auto&#34;&gt;&lt;polygon points=&#34;0,7 11.5,14 11.5,0&#34; class=&#34;arrowMarkerPath&#34; style=&#34;stroke-width: 0; stroke-dasharray: 1, 0;&#34;/&gt;&lt;/marker&gt;&lt;marker id=&#34;mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79_flowchart-v2-circleEnd&#34; class=&#34;marker flowchart-v2&#34; viewBox=&#34;0 0 10 10&#34; refX=&#34;11&#34; refY=&#34;5&#34; markerUnits=&#34;userSpaceOnUse&#34; markerWidth=&#34;11&#34; markerHeight=&#34;11&#34; orient=&#34;auto&#34;&gt;&lt;circle cx=&#34;5&#34; cy=&#34;5&#34; r=&#34;5&#34; class=&#34;arrowMarkerPath&#34; style=&#34;stroke-width: 1; stroke-dasharray: 1, 0;&#34;/&gt;&lt;/marker&gt;&lt;marker id=&#34;mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79_flowchart-v2-circleStart&#34; class=&#34;marker flowchart-v2&#34; viewBox=&#34;0 0 10 10&#34; refX=&#34;-1&#34; refY=&#34;5&#34; markerUnits=&#34;userSpaceOnUse&#34; markerWidth=&#34;11&#34; markerHeight=&#34;11&#34; orient=&#34;auto&#34;&gt;&lt;circle cx=&#34;5&#34; cy=&#34;5&#34; r=&#34;5&#34; class=&#34;arrowMarkerPath&#34; style=&#34;stroke-width: 1; stroke-dasharray: 1, 0;&#34;/&gt;&lt;/marker&gt;&lt;marker id=&#34;mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79_flowchart-v2-circleEnd-margin&#34; class=&#34;marker flowchart-v2&#34; viewBox=&#34;0 0 10 10&#34; refY=&#34;5&#34; refX=&#34;12.25&#34; markerUnits=&#34;userSpaceOnUse&#34; markerWidth=&#34;14&#34; markerHeight=&#34;14&#34; orient=&#34;auto&#34;&gt;&lt;circle cx=&#34;5&#34; cy=&#34;5&#34; r=&#34;5&#34; class=&#34;arrowMarkerPath&#34; style=&#34;stroke-width: 0; stroke-dasharray: 1, 0;&#34;/&gt;&lt;/marker&gt;&lt;marker id=&#34;mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79_flowchart-v2-circleStart-margin&#34; class=&#34;marker flowchart-v2&#34; viewBox=&#34;0 0 10 10&#34; refX=&#34;-2&#34; refY=&#34;5&#34; markerUnits=&#34;userSpaceOnUse&#34; markerWidth=&#34;14&#34; markerHeight=&#34;14&#34; orient=&#34;auto&#34;&gt;&lt;circle cx=&#34;5&#34; cy=&#34;5&#34; r=&#34;5&#34; class=&#34;arrowMarkerPath&#34; style=&#34;stroke-width: 0; stroke-dasharray: 1, 0;&#34;/&gt;&lt;/marker&gt;&lt;marker id=&#34;mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79_flowchart-v2-crossEnd&#34; class=&#34;marker cross flowchart-v2&#34; viewBox=&#34;0 0 11 11&#34; refX=&#34;12&#34; refY=&#34;5.2&#34; markerUnits=&#34;userSpaceOnUse&#34; markerWidth=&#34;11&#34; markerHeight=&#34;11&#34; orient=&#34;auto&#34;&gt;&lt;path d=&#34;M 1,1 l 9,9 M 10,1 l -9,9&#34; class=&#34;arrowMarkerPath&#34; style=&#34;stroke-width: 2; stroke-dasharray: 1, 0;&#34;/&gt;&lt;/marker&gt;&lt;marker id=&#34;mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79_flowchart-v2-crossStart&#34; class=&#34;marker cross flowchart-v2&#34; viewBox=&#34;0 0 11 11&#34; refX=&#34;-1&#34; refY=&#34;5.2&#34; markerUnits=&#34;userSpaceOnUse&#34; markerWidth=&#34;11&#34; markerHeight=&#34;11&#34; orient=&#34;auto&#34;&gt;&lt;path d=&#34;M 1,1 l 9,9 M 10,1 l -9,9&#34; class=&#34;arrowMarkerPath&#34; style=&#34;stroke-width: 2; stroke-dasharray: 1, 0;&#34;/&gt;&lt;/marker&gt;&lt;marker id=&#34;mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79_flowchart-v2-crossEnd-margin&#34; class=&#34;marker cross flowchart-v2&#34; viewBox=&#34;0 0 15 15&#34; refX=&#34;17.7&#34; refY=&#34;7.5&#34; markerUnits=&#34;userSpaceOnUse&#34; markerWidth=&#34;12&#34; markerHeight=&#34;12&#34; orient=&#34;auto&#34;&gt;&lt;path d=&#34;M 1,1 L 14,14 M 1,14 L 14,1&#34; class=&#34;arrowMarkerPath&#34; style=&#34;stroke-width: 2.5;&#34;/&gt;&lt;/marker&gt;&lt;marker id=&#34;mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79_flowchart-v2-crossStart-margin&#34; class=&#34;marker cross flowchart-v2&#34; viewBox=&#34;0 0 15 15&#34; refX=&#34;-3.5&#34; refY=&#34;7.5&#34; markerUnits=&#34;userSpaceOnUse&#34; markerWidth=&#34;12&#34; markerHeight=&#34;12&#34; orient=&#34;auto&#34;&gt;&lt;path d=&#34;M 1,1 L 14,14 M 1,14 L 14,1&#34; class=&#34;arrowMarkerPath&#34; style=&#34;stroke-width: 2.5; stroke-dasharray: 1, 0;&#34;/&gt;&lt;/marker&gt;&lt;g class=&#34;root&#34;&gt;&lt;g class=&#34;clusters&#34;/&gt;&lt;g class=&#34;edgePaths&#34;&gt;&lt;path d=&#34;M108.906,206.661L125.078,204.248C141.25,201.836,173.594,197.011,205.276,196.567C236.959,196.122,267.98,200.057,283.49,202.025L299.001,203.992&#34; id=&#34;mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79-L_C_T_0&#34; class=&#34;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&#34; style=&#34;;&#34; data-edge=&#34;true&#34; data-et=&#34;edge&#34; data-id=&#34;L_C_T_0&#34; data-points=&#34;W3sieCI6MTA4LjkwNjI1LCJ5IjoyMDYuNjYwNzkzNzUyOTQwMDd9LHsieCI6MjA1LjkzNzUsInkiOjE5Mi4xODY4MDI4NjQwNzQ3fSx7IngiOjMwMi45Njg3NSwieSI6MjA0LjQ5NTQ2NTQxNjQ0MTkzfV0=&#34; data-look=&#34;classic&#34; marker-end=&#34;url(#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79_flowchart-v2-pointEnd)&#34;/&gt;&lt;path d=&#34;M108.906,221.713L125.078,224.125C141.25,226.537,173.594,231.362,205.276,231.807C236.959,232.252,267.98,228.317,283.49,226.349L299.001,224.382&#34; id=&#34;mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79-L_C_T_2&#34; class=&#34;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&#34; style=&#34;;&#34; data-edge=&#34;true&#34; data-et=&#34;edge&#34; data-id=&#34;L_C_T_2&#34; data-points=&#34;W3sieCI6MTA4LjkwNjI1LCJ5IjoyMjEuNzEyODExOTc1MjA5MzV9LHsieCI6MjA1LjkzNzUsInkiOjIzNi4xODY4MDI4NjQwNzQ3fSx7IngiOjMwMi45Njg3NSwieSI6MjIzLjg3ODE0MDMxMTcwNzQ4fV0=&#34; data-look=&#34;classic&#34; marker-end=&#34;url(#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79_flowchart-v2-pointEnd)&#34;/&gt;&lt;path d=&#34;M415.207,187.187L434.655,172.535C454.104,157.883,493.001,128.58,524.472,113.928C555.943,99.277,579.987,99.277,592.009,99.277L604.031,99.277&#34; id=&#34;mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79-L_T_P1F_0&#34; class=&#34;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&#34; style=&#34;;&#34; data-edge=&#34;true&#34; data-et=&#34;edge&#34; data-id=&#34;L_T_P1F_0&#34; data-points=&#34;W3sieCI6NDE1LjIwNjg0NjQ1NTk0OTMsInkiOjE4Ny4xODY4MDI4NjQwNzQ3fSx7IngiOjUzMS44OTg0Mzc1LCJ5Ijo5OS4yNzY1NjQ1OTgwODM1fSx7IngiOjYwOC4wMzEyNSwieSI6OTkuMjc2NTY0NTk4MDgzNX1d&#34; data-look=&#34;classic&#34; marker-end=&#34;url(#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79_flowchart-v2-pointEnd)&#34;/&gt;&lt;path d=&#34;M415.207,241.187L434.655,255.839C454.104,270.49,493.001,299.794,524.472,314.445C555.943,329.097,579.987,329.097,592.009,329.097L604.031,329.097&#34; id=&#34;mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79-L_T_P2F_0&#34; class=&#34;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&#34; style=&#34;;&#34; data-edge=&#34;true&#34; data-et=&#34;edge&#34; data-id=&#34;L_T_P2F_0&#34; data-points=&#34;W3sieCI6NDE1LjIwNjg0NjQ1NTk0OTMsInkiOjI0MS4xODY4MDI4NjQwNzQ3fSx7IngiOjUzMS44OTg0Mzc1LCJ5IjozMjkuMDk3MDQxMTMwMDY1OX0seyJ4Ijo2MDguMDMxMjUsInkiOjMyOS4wOTcwNDExMzAwNjU5fV0=&#34; data-look=&#34;classic&#34; marker-end=&#34;url(#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79_flowchart-v2-pointEnd)&#34;/&gt;&lt;path d=&#34;M800.547,99.277L804.714,99.277C808.88,99.277,817.214,99.277,824.88,99.277C832.547,99.277,839.547,99.277,843.047,99.277L846.547,99.277&#34; id=&#34;mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79-L_P1F_P1B_0&#34; class=&#34;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&#34; style=&#34;;&#34; data-edge=&#34;true&#34; data-et=&#34;edge&#34; data-id=&#34;L_P1F_P1B_0&#34; data-points=&#34;W3sieCI6ODAwLjU0Njg3NSwieSI6OTkuMjc2NTY0NTk4MDgzNX0seyJ4Ijo4MjUuNTQ2ODc1LCJ5Ijo5OS4yNzY1NjQ1OTgwODM1fSx7IngiOjg1MC41NDY4NzUsInkiOjk5LjI3NjU2NDU5ODA4MzV9XQ==&#34; data-look=&#34;classic&#34; marker-end=&#34;url(#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79_flowchart-v2-pointEnd)&#34;/&gt;&lt;path d=&#34;M800.547,329.097L804.714,329.097C808.88,329.097,817.214,329.097,824.88,329.097C832.547,329.097,839.547,329.097,843.047,329.097L846.547,329.097&#34; id=&#34;mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79-L_P2F_P2B_0&#34; class=&#34;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&#34; style=&#34;;&#34; data-edge=&#34;true&#34; data-et=&#34;edge&#34; data-id=&#34;L_P2F_P2B_0&#34; data-points=&#34;W3sieCI6ODAwLjU0Njg3NSwieSI6MzI5LjA5NzA0MTEzMDA2NTl9LHsieCI6ODI1LjU0Njg3NSwieSI6MzI5LjA5NzA0MTEzMDA2NTl9LHsieCI6ODUwLjU0Njg3NSwieSI6MzI5LjA5NzA0MTEzMDA2NTl9XQ==&#34; data-look=&#34;classic&#34; marker-end=&#34;url(#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79_flowchart-v2-pointEnd)&#34;/&gt;&lt;path d=&#34;M1002.478,72.277L1013.112,67.201C1023.746,62.125,1045.014,51.973,1059.147,46.897C1073.281,41.821,1080.281,41.821,1083.781,41.821L1087.281,41.821&#34; id=&#34;mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79-L_P1B_P1D_0&#34; class=&#34;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&#34; style=&#34;;&#34; data-edge=&#34;true&#34; data-et=&#34;edge&#34; data-id=&#34;L_P1B_P1D_0&#34; data-points=&#34;W3sieCI6MTAwMi40Nzg0NTc2NTgxOTYxLCJ5Ijo3Mi4yNzY1NjQ1OTgwODM1fSx7IngiOjEwNjYuMjgxMjUsInkiOjQxLjgyMTQ0NTQ2NTA4Nzg5fSx7IngiOjEwOTEuMjgxMjUsInkiOjQxLjgyMTQ0NTQ2NTA4Nzg5fV0=&#34; data-look=&#34;classic&#34; marker-end=&#34;url(#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79_flowchart-v2-pointEnd)&#34;/&gt;&lt;path d=&#34;M1002.478,302.097L1013.112,297.021C1023.746,291.945,1045.014,281.794,1059.147,276.718C1073.281,271.642,1080.281,271.642,1083.781,271.642L1087.281,271.642&#34; id=&#34;mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79-L_P2B_P2D_0&#34; class=&#34;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&#34; style=&#34;;&#34; data-edge=&#34;true&#34; data-et=&#34;edge&#34; data-id=&#34;L_P2B_P2D_0&#34; data-points=&#34;W3sieCI6MTAwMi40Nzg0NTc2NTgxOTYxLCJ5IjozMDIuMDk3MDQxMTMwMDY1OX0seyJ4IjoxMDY2LjI4MTI1LCJ5IjoyNzEuNjQxOTIxOTk3MDcwM30seyJ4IjoxMDkxLjI4MTI1LCJ5IjoyNzEuNjQxOTIxOTk3MDcwM31d&#34; data-look=&#34;classic&#34; marker-end=&#34;url(#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79_flowchart-v2-pointEnd)&#34;/&gt;&lt;path d=&#34;M1002.478,126.277L1013.112,131.352C1023.746,136.428,1045.014,146.58,1060.925,151.656C1076.836,156.732,1087.391,156.732,1092.668,156.732L1097.945,156.732&#34; id=&#34;mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79-L_P1B_P1R_0&#34; class=&#34;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&#34; style=&#34;;&#34; data-edge=&#34;true&#34; data-et=&#34;edge&#34; data-id=&#34;L_P1B_P1R_0&#34; data-points=&#34;W3sieCI6MTAwMi40Nzg0NTc2NTgxOTYxLCJ5IjoxMjYuMjc2NTY0NTk4MDgzNX0seyJ4IjoxMDY2LjI4MTI1LCJ5IjoxNTYuNzMxNjgzNzMxMDc5MX0seyJ4IjoxMTAxLjk0NTMxMjUsInkiOjE1Ni43MzE2ODM3MzEwNzkxfV0=&#34; data-look=&#34;classic&#34; marker-end=&#34;url(#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79_flowchart-v2-pointEnd)&#34;/&gt;&lt;path d=&#34;M1002.478,356.097L1013.112,361.173C1023.746,366.249,1045.014,376.4,1060.925,381.476C1076.836,386.552,1087.391,386.552,1092.668,386.552L1097.945,386.552&#34; id=&#34;mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79-L_P2B_P2R_0&#34; class=&#34;edge-thickness-normal edge-pattern-solid edge-thickness-normal edge-pattern-solid flowchart-link&#34; style=&#34;;&#34; data-edge=&#34;true&#34; data-et=&#34;edge&#34; data-id=&#34;L_P2B_P2R_0&#34; data-points=&#34;W3sieCI6MTAwMi40Nzg0NTc2NTgxOTYxLCJ5IjozNTYuMDk3MDQxMTMwMDY1OX0seyJ4IjoxMDY2LjI4MTI1LCJ5IjozODYuNTUyMTYwMjYzMDYxNX0seyJ4IjoxMTAxLjk0NTMxMjUsInkiOjM4Ni41NTIxNjAyNjMwNjE1fV0=&#34; data-look=&#34;classic&#34; marker-end=&#34;url(#mmd-7d0469a3e4438bded6d14ef558a7c46569557fa1bc515b6f1ec252d035074f79_flowchart-v2-pointEnd)&#34;/&gt;&lt;/g&gt;&lt;g class=&#34;edgeLabels&#34;&gt;&lt;g class=&#34;edgeLabel&#34; transform=&#34;translate(205.79111, 192.20864)&#34;&gt;&lt;g class=&#34;label&#34; data-id=&#34;L_C_T_0&#34; transform=&#34;translate(-72.03125, -12)&#34;&gt;&lt;foreignObject width=&#34;144.0625&#34; height=&#34;24&#34;&gt;&lt;div xmlns=&#34;http://www.w3.org/1999/xhtml&#34; class=&#34;labelBkg&#34; style=&#34;display: table-cell; white-space: nowrap; line-height: 1.5; max-width: 200px; text-align: center;&#34;&gt;&lt;span class=&#34;edgeLabel&#34;&gt;&lt;p&gt;Host: s1.inxeoz.com&lt;/p&gt;</description>
    </item>
    <item>
      <title>Attention Is All You Need — Explained Simply</title>
      <link>/posts/tech/attention-is-all-you-need/</link>
      <pubDate>Thu, 21 May 2026 00:00:00 +0000</pubDate>
      <guid>/posts/tech/attention-is-all-you-need/</guid>
      <description>&lt;style&gt;&#xA;  :root{&#xA;    --paper:#f5f0e5;&#xA;    --paper-2:#ece4d4;&#xA;    --ink:#211b16;&#xA;    --ink-soft:#5b5147;&#xA;    --terra:#c0492b;&#xA;    --teal:#176b63;&#xA;    --gold:#cf952a;&#xA;    --panel:#171311;&#xA;    --panel-2:#231c18;&#xA;    --panel-line:#3a2f28;&#xA;    --glow:#f0b35a;&#xA;  }&#xA;  [data-theme=&#34;dark&#34;]{&#xA;    --paper:#1a1714;&#xA;    --paper-2:#231e1a;&#xA;    --ink:#e8ddd0;&#xA;    --ink-soft:#a89a89;&#xA;    --panel:#0e0b09;&#xA;    --panel-2:#1a1511;&#xA;    --panel-line:#2c241e;&#xA;    --glow:#e8a84a;&#xA;  }&#xA;  *{box-sizing:border-box;}&#xA;  html{scroll-behavior:smooth;}&#xA;  .post-content #progress{position:fixed;top:0;left:0;height:4px;width:0;background:linear-gradient(90deg,var(--terra),var(--gold));z-index:200;transition:width .1s linear;}&#xA;  .post-content nav#dots{position:fixed;right:18px;top:50%;transform:translateY(-50%);z-index:150;display:flex;flex-direction:column;gap:11px;}&#xA;  .post-content nav#dots a{width:11px;height:11px;border-radius:50%;border:1.5px solid var(--ink-soft);background:transparent;transition:all .25s;display:block;}&#xA;  .post-content nav#dots a.active{background:var(--terra);border-color:var(--terra);transform:scale(1.35);}&#xA;  .post-content nav#dots a:hover{border-color:var(--terra);}&#xA;  @media(max-width:880px){.post-content nav#dots{display:none;}}&#xA;  .post-content header.hero{min-height:96vh;display:flex;flex-direction:column;justify-content:center;max-width:920px;margin:0 auto;position:relative;overflow:hidden;background:var(--paper);font-family:&#34;Newsreader&#34;,Georgia,serif;-webkit-font-smoothing:antialiased;background-image:radial-gradient(circle at 12% -10%, rgba(192,73,43,.06), transparent 40%),radial-gradient(circle at 100% 8%, rgba(23,107,99,.06), transparent 38%);border:12px double var(--ink);padding:clamp(32px,6vh,70px);}&#xA;  [data-theme=&#34;dark&#34;] .post-content header.hero{background-image:radial-gradient(circle at 12% -10%, rgba(192,73,43,.12), transparent 40%),radial-gradient(circle at 100% 8%, rgba(23,107,99,.12), transparent 38%);border-color:var(--ink);}&#xA;  .post-content header.hero::before{content:&#39;&#39;;position:absolute;inset:-1px;border:1px solid var(--ink-soft);pointer-events:none;}&#xA;  [data-theme=&#34;dark&#34;] .post-content header.hero::before{border-color:var(--ink-soft);}&#xA;  .post-content header.hero::after{content:&#39;&#39;;position:absolute;inset:0;background-image:url(&#34;data:image/svg+xml,%3Csvg viewBox=&#39;0 0 256 256&#39; xmlns=&#39;http://www.w3.org/2000/svg&#39;%3E%3Cfilter id=&#39;n&#39;%3E%3CfeTurbulence type=&#39;fractalNoise&#39; baseFrequency=&#39;0.85&#39; numOctaves=&#39;4&#39; stitchTiles=&#39;stitch&#39;/%3E%3C/filter%3E%3Crect width=&#39;100%25&#39; height=&#39;100%25&#39; filter=&#39;url(%23n)&#39; opacity=&#39;0.5&#39;/%3E%3C/svg%3E&#34;);opacity:0.04;mix-blend-mode:overlay;pointer-events:none;}&#xA;  [data-theme=&#34;dark&#34;] .post-content header.hero::after{opacity:0.025;}&#xA;  .post-content .eyebrow{font-family:&#34;JetBrains Mono&#34;,monospace;font-size:13px;letter-spacing:.22em;text-transform:uppercase;color:var(--terra);margin-bottom:22px;}&#xA;  .post-content h1{font-family:&#34;Fraunces&#34;,serif;font-weight:900;font-size:clamp(46px,9vw,104px);line-height:.95;letter-spacing:-.02em;margin:0 0 8px;color:var(--ink);}&#xA;  .post-content h1 .light{font-weight:400;font-style:italic;color:var(--teal);}&#xA;  .post-content .subtitle{font-size:clamp(20px,2.6vw,27px);color:var(--ink-soft);max-width:620px;margin-top:24px;font-style:italic;}&#xA;  .post-content .byline{font-family:&#34;JetBrains Mono&#34;,monospace;font-size:13px;color:var(--ink-soft);margin-top:38px;line-height:1.8;}&#xA;  .post-content .byline b{color:var(--ink);font-weight:600;}&#xA;&#xA;  .post-content .stage{background:var(--panel);color:#efe7da;border-radius:16px;padding:30px 28px;margin:34px 0;box-shadow:0 24px 60px -28px rgba(33,20,10,.6);font-family:&#34;Newsreader&#34;,serif;border:1px solid var(--panel-line);}&#xA;  [data-theme=&#34;dark&#34;] .post-content .stage{box-shadow:0 24px 60px -28px rgba(0,0,0,.8);}&#xA;  .post-content .stage .stage-label{font-family:&#34;JetBrains Mono&#34;,monospace;font-size:11px;letter-spacing:.2em;text-transform:uppercase;color:var(--glow);margin-bottom:6px;}&#xA;  .post-content .stage h4{font-family:&#34;Fraunces&#34;,serif;font-weight:600;font-size:22px;margin:0 0 14px;color:#fff;}&#xA;  .post-content .stage p{font-size:16px;line-height:1.55;color:#cfc3b2;margin:12px 0;}&#xA;  .post-content .stage .mono{font-family:&#34;JetBrains Mono&#34;,monospace;font-size:13px;}&#xA;  .post-content .btn{font-family:&#34;JetBrains Mono&#34;,monospace;font-size:13px;cursor:pointer;border:none;background:var(--terra);color:#fff;padding:9px 16px;border-radius:8px;letter-spacing:.04em;transition:transform .12s,filter .12s;}&#xA;  .post-content .btn:hover{filter:brightness(1.1);transform:translateY(-1px);}&#xA;  .post-content .btn.ghost{background:transparent;border:1px solid var(--panel-line);color:#cfc3b2;}&#xA;  .post-content .btn.ghost.on{background:var(--teal);border-color:var(--teal);color:#fff;}&#xA;  .post-content .btn.small{padding:6px 12px;font-size:12px;}&#xA;  .post-content .controls{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin:14px 0;}&#xA;  .post-content table.calc{width:100%;border-collapse:collapse;font-family:&#34;JetBrains Mono&#34;,monospace;font-size:13px;margin-top:10px;}&#xA;  .post-content table.calc td,.post-content table.calc th{padding:7px 8px;text-align:left;border-bottom:1px solid var(--panel-line);}&#xA;  .post-content table.calc th{color:var(--glow);font-weight:600;font-size:11px;letter-spacing:.08em;text-transform:uppercase;}&#xA;  .post-content .bar{height:14px;border-radius:7px;background:linear-gradient(90deg,var(--teal),var(--glow));transition:width .5s cubic-bezier(.2,.8,.2,1);}&#xA;  .post-content .barwrap{background:#2c241e;border-radius:7px;overflow:hidden;width:120px;display:inline-block;vertical-align:middle;}&#xA;  .post-content .clockrow{display:flex;gap:6px;flex-wrap:wrap;margin:10px 0;}&#xA;  .post-content .cell{width:44px;height:44px;border-radius:8px;background:var(--panel-2);border:1px solid var(--panel-line);display:flex;align-items:center;justify-content:center;font-family:&#34;JetBrains Mono&#34;,monospace;font-size:13px;color:#9c8e7d;transition:all .3s;}&#xA;  .post-content .cell.done{background:var(--teal);color:#fff;border-color:var(--teal);}&#xA;  .post-content .cell.active{background:var(--terra);color:#fff;border-color:var(--terra);transform:scale(1.08);}&#xA;  .post-content .sent{line-height:2.6;margin:10px 0;}&#xA;  .post-content .aw{position:relative;display:inline-block;padding:4px 8px;border-radius:7px;margin:2px;font-family:&#34;JetBrains Mono&#34;,monospace;font-size:15px;transition:background .35s,color .35s;color:#e7ddcd;}&#xA;  .post-content input[type=range]{width:100%;accent-color:var(--terra);height:24px;}&#xA;  .post-content .lbl{font-family:&#34;JetBrains Mono&#34;,monospace;font-size:12px;color:#cfc3b2;display:flex;justify-content:space-between;}&#xA;  .post-content .headbtns{display:flex;gap:6px;flex-wrap:wrap;}&#xA;  .post-content .arch{display:flex;gap:18px;justify-content:center;flex-wrap:wrap;margin-top:12px;}&#xA;  .post-content .stackcol{flex:1;min-width:210px;}&#xA;  .post-content .stackcol h5{font-family:&#34;JetBrains Mono&#34;,monospace;font-size:12px;letter-spacing:.15em;text-transform:uppercase;color:var(--glow);text-align:center;margin:0 0 10px;}&#xA;  .post-content .block{background:var(--panel-2);border:1px solid var(--panel-line);border-radius:9px;padding:11px 12px;margin:8px 0;font-size:13px;cursor:pointer;transition:all .2s;color:#cfc3b2;}&#xA;  .post-content .block:hover{border-color:var(--glow);background:#2e251f;color:#fff;}&#xA;  .post-content .block b{color:#fff;font-family:&#34;Fraunces&#34;,serif;font-weight:600;font-size:15px;display:block;}&#xA;  .post-content .explainbox{min-height:54px;margin-top:14px;padding:13px 15px;background:#0e0b09;border-radius:9px;font-size:14px;color:#cfc3b2;border:1px dashed var(--panel-line);}&#xA;  .post-content .chart{display:flex;align-items:flex-end;gap:20px;height:230px;margin:24px 0 8px;padding:0 6px;}&#xA;  .post-content .chbar{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:flex-end;height:100%;}&#xA;  .post-content .chbar .col{width:100%;border-radius:8px 8px 0 0;background:var(--panel-2);transition:height 1.1s cubic-bezier(.2,.8,.2,1);height:0;position:relative;}&#xA;  .post-content .chbar.us .col{background:linear-gradient(180deg,var(--terra),var(--gold));}&#xA;  .post-content .chbar .val{font-family:&#34;JetBrains Mono&#34;,monospace;font-size:13px;margin-bottom:6px;color:#e7ddcd;}&#xA;  .post-content .chbar .name{font-family:&#34;JetBrains Mono&#34;,monospace;font-size:11px;margin-top:8px;color:#9c8e7d;text-align:center;line-height:1.3;}&#xA;  .post-content .legend{font-family:&#34;JetBrains Mono&#34;,monospace;font-size:12px;color:#9c8e7d;margin-top:8px;}&#xA;  .post-content textarea{width:100%;height:150px;background:#0e0b09;border:1px solid var(--panel-line);color:#e7ddcd;padding:12px;border-radius:10px;font-family:&#39;JetBrains Mono&#39;,monospace;font-size:13px;line-height:1.6;resize:vertical;}&#xA;  [data-theme=&#34;dark&#34;] .post-content .barwrap{background:#3a2f28;}&#xA;  [data-theme=&#34;dark&#34;] .post-content .block:hover{background:#3a2f28;color:#e7ddcd;}&#xA;  [data-theme=&#34;dark&#34;] .post-content textarea,[data-theme=&#34;dark&#34;] .post-content .explainbox{background:#000;}&#xA;  [data-theme=&#34;dark&#34;] .post-content .chbar .name{color:#7a6a59;}&#xA;&lt;/style&gt;&#xA;&lt;div id=&#34;progress&#34;&gt;&lt;/div&gt;&#xA;&lt;nav id=&#34;dots&#34;&gt;&lt;/nav&gt;&#xA;&lt;header class=&#34;hero&#34;&gt;&#xA;  &lt;div class=&#34;eyebrow&#34;&gt;The paper that built modern AI &amp;middot; Vaswani et al., 2017&lt;/div&gt;&#xA;  &lt;h1&gt;Attention Is&lt;br&gt;All You &lt;span class=&#34;light&#34;&gt;Need&lt;/span&gt;&lt;/h1&gt;&#xA;  &lt;div style=&#34;width:80px;height:2px;background:var(--terra);margin:16px 0 22px;&#34;&gt;&lt;/div&gt;&#xA;  &lt;div class=&#34;subtitle&#34;&gt;The famous research paper behind ChatGPT, translation apps, and almost every AI you use today — rewritten so anyone can follow it, with things you can actually play with.&lt;/div&gt;&#xA;  &lt;div class=&#34;byline&#34;&gt;&#xA;    Original authors: &lt;b&gt;Vaswani, Shazeer, Parmar, Uszkoreit, Jones, Gomez, Kaiser, Polosukhin&lt;/b&gt;&lt;br&gt;&#xA;    Google Brain &amp;amp; Google Research &amp;middot; Published at NeurIPS 2017&lt;br&gt;&#xA;    This page: a plain-language, interactive walkthrough.&#xA;  &lt;/div&gt;&#xA;&lt;/header&gt;&#xA;&lt;p&gt;&lt;span id=&#34;s1&#34; class=&#34;s&#34;&gt;&lt;/span&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>data-testid — From Zero to Hero</title>
      <link>/posts/tech/data-testid-zero-to-hero/</link>
      <pubDate>Thu, 21 May 2026 00:00:00 +0000</pubDate>
      <guid>/posts/tech/data-testid-zero-to-hero/</guid>
      <description>&lt;p&gt;A complete guide to mandatory &lt;code&gt;data-testid&lt;/code&gt; enforcement, duplicate detection, auto-generation, and E2E workflow — with every command you need.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;1-what-is-data-testid-and-why-do-you-need-it&#34;&gt;1. What is &lt;code&gt;data-testid&lt;/code&gt; and why do you need it?&lt;/h2&gt;&#xA;&lt;p&gt;&lt;code&gt;data-testid&lt;/code&gt; is an HTML attribute that decouples your tests from CSS classes, DOM structure, and text content. Instead of brittle selectors like &lt;code&gt;.btn-primary &amp;gt; span:first-child&lt;/code&gt;, you write:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block-wrapper&#34; data-lang=&#34;js&#34; data-testid=&#34;code-block&#34;&gt;&#xA;  &lt;div class=&#34;code-toolbar&#34; data-testid=&#34;code-toolbar&#34;&gt;&#xA;    &lt;span class=&#34;lang-code-btn&#34; aria-label=&#34;Code language: js&#34; data-testid=&#34;lang-code-btn&#34;&gt;js&lt;/span&gt;&#xA;    &lt;button class=&#34;wrap-code-btn&#34; aria-label=&#34;Toggle line wrap&#34; title=&#34;Toggle line wrap&#34; data-testid=&#34;wrap-code-btn&#34;&gt;&#xA;      &lt;svg width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34;&gt;&lt;line x1=&#34;3&#34; y1=&#34;6&#34; x2=&#34;21&#34; y2=&#34;6&#34;&gt;&lt;/line&gt;&lt;path d=&#34;M3 12h15a3 3 0 1 1 0 6h-4&#34;&gt;&lt;/path&gt;&lt;polyline points=&#34;16 16 14 18 16 20&#34;&gt;&lt;/polyline&gt;&lt;line x1=&#34;3&#34; y1=&#34;18&#34; x2=&#34;10&#34; y2=&#34;18&#34;&gt;&lt;/line&gt;&lt;/svg&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;button class=&#34;copy-code-btn&#34; aria-label=&#34;Copy code&#34; data-testid=&#34;copy-code-btn&#34;&gt;&#xA;      &lt;svg width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34;&gt;&lt;rect x=&#34;9&#34; y=&#34;9&#34; width=&#34;13&#34; height=&#34;13&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;&lt;path d=&#34;M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;&#xA;    &lt;/button&gt;&#xA;  &lt;/div&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-js&#34; data-lang=&#34;js&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;page&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;locator&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;[data-testid=&amp;#34;btn:submit-request&amp;#34;]&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;).&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;click&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;Your tests survive refactors. Your CSS changes don&amp;rsquo;t break your CI.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Frappe Docker Setup (Custom Image &#43; App)</title>
      <link>/posts/tech/frappe-docker-setup/</link>
      <pubDate>Thu, 21 May 2026 00:00:00 +0000</pubDate>
      <guid>/posts/tech/frappe-docker-setup/</guid>
      <description>&lt;p&gt;Based on &lt;a href=&#34;https://github.com/frappe/frappe_docker&#34; class=&#34;external-link&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;frappe_docker&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;This guide walks through deploying a custom Frappe app using Docker — from cloning frappe_docker and building a custom image with your app baked in, to spinning up containers and creating the first site. It also covers offline/air-gapped deployment for servers without internet access.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;1-clone-repository&#34;&gt;1. Clone Repository&lt;/h2&gt;&#xA;&lt;div class=&#34;code-block-wrapper&#34; data-lang=&#34;bash&#34; data-testid=&#34;code-block&#34;&gt;&#xA;  &lt;div class=&#34;code-toolbar&#34; data-testid=&#34;code-toolbar&#34;&gt;&#xA;    &lt;span class=&#34;lang-code-btn&#34; aria-label=&#34;Code language: bash&#34; data-testid=&#34;lang-code-btn&#34;&gt;bash&lt;/span&gt;&#xA;    &lt;button class=&#34;wrap-code-btn&#34; aria-label=&#34;Toggle line wrap&#34; title=&#34;Toggle line wrap&#34; data-testid=&#34;wrap-code-btn&#34;&gt;&#xA;      &lt;svg width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34;&gt;&lt;line x1=&#34;3&#34; y1=&#34;6&#34; x2=&#34;21&#34; y2=&#34;6&#34;&gt;&lt;/line&gt;&lt;path d=&#34;M3 12h15a3 3 0 1 1 0 6h-4&#34;&gt;&lt;/path&gt;&lt;polyline points=&#34;16 16 14 18 16 20&#34;&gt;&lt;/polyline&gt;&lt;line x1=&#34;3&#34; y1=&#34;18&#34; x2=&#34;10&#34; y2=&#34;18&#34;&gt;&lt;/line&gt;&lt;/svg&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;button class=&#34;copy-code-btn&#34; aria-label=&#34;Copy code&#34; data-testid=&#34;copy-code-btn&#34;&gt;&#xA;      &lt;svg width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34;&gt;&lt;rect x=&#34;9&#34; y=&#34;9&#34; width=&#34;13&#34; height=&#34;13&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;&lt;path d=&#34;M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;&#xA;    &lt;/button&gt;&#xA;  &lt;/div&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;mkdir frappe-wiki&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; frappe-wiki&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git clone --depth &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; https://github.com/frappe/frappe_docker .&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;2-define-apps-appsjson&#34;&gt;2. Define Apps (&lt;code&gt;apps.json&lt;/code&gt;)&lt;/h2&gt;&#xA;&lt;div class=&#34;code-block-wrapper&#34; data-lang=&#34;json&#34; data-testid=&#34;code-block&#34;&gt;&#xA;  &lt;div class=&#34;code-toolbar&#34; data-testid=&#34;code-toolbar&#34;&gt;&#xA;    &lt;span class=&#34;lang-code-btn&#34; aria-label=&#34;Code language: json&#34; data-testid=&#34;lang-code-btn&#34;&gt;json&lt;/span&gt;&#xA;    &lt;button class=&#34;wrap-code-btn&#34; aria-label=&#34;Toggle line wrap&#34; title=&#34;Toggle line wrap&#34; data-testid=&#34;wrap-code-btn&#34;&gt;&#xA;      &lt;svg width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34;&gt;&lt;line x1=&#34;3&#34; y1=&#34;6&#34; x2=&#34;21&#34; y2=&#34;6&#34;&gt;&lt;/line&gt;&lt;path d=&#34;M3 12h15a3 3 0 1 1 0 6h-4&#34;&gt;&lt;/path&gt;&lt;polyline points=&#34;16 16 14 18 16 20&#34;&gt;&lt;/polyline&gt;&lt;line x1=&#34;3&#34; y1=&#34;18&#34; x2=&#34;10&#34; y2=&#34;18&#34;&gt;&lt;/line&gt;&lt;/svg&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;button class=&#34;copy-code-btn&#34; aria-label=&#34;Copy code&#34; data-testid=&#34;copy-code-btn&#34;&gt;&#xA;      &lt;svg width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34;&gt;&lt;rect x=&#34;9&#34; y=&#34;9&#34; width=&#34;13&#34; height=&#34;13&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;&lt;path d=&#34;M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;&#xA;    &lt;/button&gt;&#xA;  &lt;/div&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;url&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;https://github.com/frappe/wiki&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;branch&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;develop&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;3-build-custom-image&#34;&gt;3. Build Custom Image&lt;/h2&gt;&#xA;&lt;h3 id=&#34;recommended-buildkit&#34;&gt;Recommended (BuildKit)&lt;/h3&gt;&#xA;&lt;div class=&#34;code-block-wrapper&#34; data-lang=&#34;bash&#34; data-testid=&#34;code-block&#34;&gt;&#xA;  &lt;div class=&#34;code-toolbar&#34; data-testid=&#34;code-toolbar&#34;&gt;&#xA;    &lt;span class=&#34;lang-code-btn&#34; aria-label=&#34;Code language: bash&#34; data-testid=&#34;lang-code-btn&#34;&gt;bash&lt;/span&gt;&#xA;    &lt;button class=&#34;wrap-code-btn&#34; aria-label=&#34;Toggle line wrap&#34; title=&#34;Toggle line wrap&#34; data-testid=&#34;wrap-code-btn&#34;&gt;&#xA;      &lt;svg width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34;&gt;&lt;line x1=&#34;3&#34; y1=&#34;6&#34; x2=&#34;21&#34; y2=&#34;6&#34;&gt;&lt;/line&gt;&lt;path d=&#34;M3 12h15a3 3 0 1 1 0 6h-4&#34;&gt;&lt;/path&gt;&lt;polyline points=&#34;16 16 14 18 16 20&#34;&gt;&lt;/polyline&gt;&lt;line x1=&#34;3&#34; y1=&#34;18&#34; x2=&#34;10&#34; y2=&#34;18&#34;&gt;&lt;/line&gt;&lt;/svg&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;button class=&#34;copy-code-btn&#34; aria-label=&#34;Copy code&#34; data-testid=&#34;copy-code-btn&#34;&gt;&#xA;      &lt;svg width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34;&gt;&lt;rect x=&#34;9&#34; y=&#34;9&#34; width=&#34;13&#34; height=&#34;13&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;&lt;path d=&#34;M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;&#xA;    &lt;/button&gt;&#xA;  &lt;/div&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;docker build &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;  --build-arg&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;FRAPPE_PATH&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;https://github.com/frappe/frappe &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;  --build-arg&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;FRAPPE_BRANCH&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;version-16 &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;  --secret&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;apps_json,src&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;apps.json &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;  --tag&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;custom:16 &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;  --file&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;images/layered/Containerfile .&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;h3 id=&#34;for-older-docker-versions&#34;&gt;For Older Docker Versions&lt;/h3&gt;&#xA;&lt;div class=&#34;code-block-wrapper&#34; data-lang=&#34;bash&#34; data-testid=&#34;code-block&#34;&gt;&#xA;  &lt;div class=&#34;code-toolbar&#34; data-testid=&#34;code-toolbar&#34;&gt;&#xA;    &lt;span class=&#34;lang-code-btn&#34; aria-label=&#34;Code language: bash&#34; data-testid=&#34;lang-code-btn&#34;&gt;bash&lt;/span&gt;&#xA;    &lt;button class=&#34;wrap-code-btn&#34; aria-label=&#34;Toggle line wrap&#34; title=&#34;Toggle line wrap&#34; data-testid=&#34;wrap-code-btn&#34;&gt;&#xA;      &lt;svg width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34;&gt;&lt;line x1=&#34;3&#34; y1=&#34;6&#34; x2=&#34;21&#34; y2=&#34;6&#34;&gt;&lt;/line&gt;&lt;path d=&#34;M3 12h15a3 3 0 1 1 0 6h-4&#34;&gt;&lt;/path&gt;&lt;polyline points=&#34;16 16 14 18 16 20&#34;&gt;&lt;/polyline&gt;&lt;line x1=&#34;3&#34; y1=&#34;18&#34; x2=&#34;10&#34; y2=&#34;18&#34;&gt;&lt;/line&gt;&lt;/svg&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;button class=&#34;copy-code-btn&#34; aria-label=&#34;Copy code&#34; data-testid=&#34;copy-code-btn&#34;&gt;&#xA;      &lt;svg width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34;&gt;&lt;rect x=&#34;9&#34; y=&#34;9&#34; width=&#34;13&#34; height=&#34;13&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;&lt;path d=&#34;M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;&#xA;    &lt;/button&gt;&#xA;  &lt;/div&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;export&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;APPS_JSON_BASE64&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;base64 -w &lt;span class=&#34;m&#34;&gt;0&lt;/span&gt; apps.json&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;docker build &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;  --build-arg&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;FRAPPE_PATH&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;https://github.com/frappe/frappe &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;  --build-arg&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;FRAPPE_BRANCH&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;version-16 &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;  --build-arg&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;APPS_JSON_BASE64&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$APPS_JSON_BASE64&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;  --tag&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;custom:16 &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;  --file&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;images/layered/Containerfile .&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;4-environment-configuration-customenv&#34;&gt;4. Environment Configuration (&lt;code&gt;custom.env&lt;/code&gt;)&lt;/h2&gt;&#xA;&lt;div class=&#34;code-block-wrapper&#34; data-lang=&#34;bash&#34; data-testid=&#34;code-block&#34;&gt;&#xA;  &lt;div class=&#34;code-toolbar&#34; data-testid=&#34;code-toolbar&#34;&gt;&#xA;    &lt;span class=&#34;lang-code-btn&#34; aria-label=&#34;Code language: bash&#34; data-testid=&#34;lang-code-btn&#34;&gt;bash&lt;/span&gt;&#xA;    &lt;button class=&#34;wrap-code-btn&#34; aria-label=&#34;Toggle line wrap&#34; title=&#34;Toggle line wrap&#34; data-testid=&#34;wrap-code-btn&#34;&gt;&#xA;      &lt;svg width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34;&gt;&lt;line x1=&#34;3&#34; y1=&#34;6&#34; x2=&#34;21&#34; y2=&#34;6&#34;&gt;&lt;/line&gt;&lt;path d=&#34;M3 12h15a3 3 0 1 1 0 6h-4&#34;&gt;&lt;/path&gt;&lt;polyline points=&#34;16 16 14 18 16 20&#34;&gt;&lt;/polyline&gt;&lt;line x1=&#34;3&#34; y1=&#34;18&#34; x2=&#34;10&#34; y2=&#34;18&#34;&gt;&lt;/line&gt;&lt;/svg&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;button class=&#34;copy-code-btn&#34; aria-label=&#34;Copy code&#34; data-testid=&#34;copy-code-btn&#34;&gt;&#xA;      &lt;svg width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34;&gt;&lt;rect x=&#34;9&#34; y=&#34;9&#34; width=&#34;13&#34; height=&#34;13&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;&lt;path d=&#34;M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;&#xA;    &lt;/button&gt;&#xA;  &lt;/div&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;ERPNEXT_VERSION&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;v16.13.0&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;DB_PASSWORD&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;123&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;FRAPPE_SITE_NAME_HEADER&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;wiki.localhost&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;HTTP_PUBLISH_PORT&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;8072&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;CUSTOM_IMAGE&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;custom&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;CUSTOM_TAG&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;16&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;PULL_POLICY&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;missing&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;5-generate-compose-file&#34;&gt;5. Generate Compose File&lt;/h2&gt;&#xA;&lt;div class=&#34;code-block-wrapper&#34; data-lang=&#34;bash&#34; data-testid=&#34;code-block&#34;&gt;&#xA;  &lt;div class=&#34;code-toolbar&#34; data-testid=&#34;code-toolbar&#34;&gt;&#xA;    &lt;span class=&#34;lang-code-btn&#34; aria-label=&#34;Code language: bash&#34; data-testid=&#34;lang-code-btn&#34;&gt;bash&lt;/span&gt;&#xA;    &lt;button class=&#34;wrap-code-btn&#34; aria-label=&#34;Toggle line wrap&#34; title=&#34;Toggle line wrap&#34; data-testid=&#34;wrap-code-btn&#34;&gt;&#xA;      &lt;svg width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34;&gt;&lt;line x1=&#34;3&#34; y1=&#34;6&#34; x2=&#34;21&#34; y2=&#34;6&#34;&gt;&lt;/line&gt;&lt;path d=&#34;M3 12h15a3 3 0 1 1 0 6h-4&#34;&gt;&lt;/path&gt;&lt;polyline points=&#34;16 16 14 18 16 20&#34;&gt;&lt;/polyline&gt;&lt;line x1=&#34;3&#34; y1=&#34;18&#34; x2=&#34;10&#34; y2=&#34;18&#34;&gt;&lt;/line&gt;&lt;/svg&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;button class=&#34;copy-code-btn&#34; aria-label=&#34;Copy code&#34; data-testid=&#34;copy-code-btn&#34;&gt;&#xA;      &lt;svg width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34;&gt;&lt;rect x=&#34;9&#34; y=&#34;9&#34; width=&#34;13&#34; height=&#34;13&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;&lt;path d=&#34;M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;&#xA;    &lt;/button&gt;&#xA;  &lt;/div&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;docker compose --env-file custom.env &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;  -f compose.yaml &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;  -f overrides/compose.mariadb.yaml &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;  -f overrides/compose.redis.yaml &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;  -f overrides/compose.noproxy.yaml &lt;span class=&#34;se&#34;&gt;\&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;  config &amp;gt; compose.custom.yaml&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;hr&gt;&#xA;&lt;h3 id=&#34;offline--air-gapped-setup&#34;&gt;Offline / Air-Gapped Setup&lt;/h3&gt;&#xA;&lt;p&gt;Steps 1–5 can be done locally (if server doesn&amp;rsquo;t have internet connection).&lt;/p&gt;</description>
    </item>
    <item>
      <title>Neural Network Concepts &amp; Architecture</title>
      <link>/posts/tech/neural-network-concepts/</link>
      <pubDate>Tue, 21 Apr 2026 00:00:00 +0000</pubDate>
      <guid>/posts/tech/neural-network-concepts/</guid>
      <description>&lt;p&gt;This document explains the logical structure and operational principles of a feedforward neural network with backpropagation.&lt;/p&gt;&#xA;&lt;h2 id=&#34;network-topology&#34;&gt;Network Topology&lt;/h2&gt;&#xA;&lt;p&gt;A neural network is organized into layers of interconnected processing units called &lt;strong&gt;Neurons&lt;/strong&gt;. Data flows from the input layer through hidden layers to the output layer.&lt;/p&gt;&#xA;&#xA;&#xA;&#xA;&#xA;  &#xA;  &lt;figure class=&#34;mermaid-diagram&#34; data-testid=&#34;mermaid-diagram&#34;&gt;&lt;svg id=&#34;mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c&#34; width=&#34;100%&#34; xmlns=&#34;http://www.w3.org/2000/svg&#34; xmlns:xlink=&#34;http://www.w3.org/1999/xlink&#34; class=&#34;flowchart&#34; style=&#34;max-width: 487.141px; background-color: transparent;&#34; viewBox=&#34;0 0 487.140625 431.59375&#34; role=&#34;graphics-document document&#34; aria-roledescription=&#34;flowchart-v2&#34;&gt;&lt;style&gt;:root{--chart-line-1:#c0492b;--chart-line-2:#176b63;--chart-line-3:#cf952a;}[data-theme=&#34;dark&#34;]{--chart-line-1:#ff7b5a;--chart-line-2:#5ab8b0;--chart-line-3:#ffcc5a;}&lt;/style&gt;&lt;style&gt;[data-theme=&#34;dark&#34;]{--sb-c-dfd:#1f6b1f;--sb-cl-dfd:#f0f0f0;--sb-c-bbf:#1f1f6b;--sb-cl-bbf:#f0f0f0;--sb-c-f9f:#6b1f6b;--sb-cl-f9f:#f0f0f0;}&lt;/style&gt;&lt;style&gt;#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c{font-family:&#34;trebuchet ms&#34;,verdana,arial,sans-serif;font-size:16px;fill:var(--text-color);}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .error-icon{fill:#552222;}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .error-text{fill:#552222;stroke:#552222;}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .edge-thickness-normal{stroke-width:1px;}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .edge-thickness-thick{stroke-width:3.5px;}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .edge-pattern-solid{stroke-dasharray:0;}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .edge-thickness-invisible{stroke-width:0;fill:none;}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .edge-pattern-dashed{stroke-dasharray:3;}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .edge-pattern-dotted{stroke-dasharray:2;}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .marker{fill:var(--link-color);stroke:var(--link-color);}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .marker.cross{stroke:var(--link-color);}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c svg{font-family:&#34;trebuchet ms&#34;,verdana,arial,sans-serif;font-size:16px;}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c p{margin:0;}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .label{font-family:&#34;trebuchet ms&#34;,verdana,arial,sans-serif;color:var(--text-color);}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .cluster-label text{fill:var(--text-color);}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .cluster-label span{color:var(--text-color);}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .cluster-label span p{background-color:transparent;}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .label text,#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c span{fill:var(--text-color);color:var(--text-color);}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .node rect,#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .node circle,#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .node ellipse,#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .node polygon,#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .node path{fill:var(--surface-color);stroke:var(--text-color);stroke-width:1px;}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .rough-node .label text,#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .node .label text,#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .image-shape .label,#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .icon-shape .label{text-anchor:middle;}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .rough-node .label,#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .node .label,#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .image-shape .label,#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .icon-shape .label{text-align:center;}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .node.clickable{cursor:pointer;}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .root .anchor path{fill:var(--link-color)!important;stroke-width:0;stroke:var(--link-color);}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .arrowheadPath{fill:var(--text-color);}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .edgePath .path{stroke:var(--link-color);stroke-width:1px;}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .flowchart-link{stroke:var(--link-color);fill:none;}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .edgeLabel{background-color:var(--surface-color);text-align:center;}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .edgeLabel p{background-color:var(--surface-color);}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .edgeLabel rect{opacity:0.5;background-color:var(--surface-color);fill:var(--surface-color);}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .labelBkg{background-color:rgba(191, 222, 205, 0.5);}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .cluster rect{fill:var(--code-bg);stroke:var(--text-color);stroke-width:1px;}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .cluster text{fill:var(--text-color);}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .cluster span{color:var(--text-color);}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:&#34;trebuchet ms&#34;,verdana,arial,sans-serif;font-size:12px;background:var(--code-bg);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .flowchartTitleText{text-anchor:middle;font-size:18px;fill:var(--text-color);}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c rect.text{fill:none;stroke-width:0;}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .icon-shape,#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .image-shape{background-color:var(--surface-color);text-align:center;}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .icon-shape p,#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .image-shape p{background-color:var(--surface-color);padding:2px;}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .icon-shape .label rect,#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .image-shape .label rect{opacity:0.5;background-color:var(--surface-color);fill:var(--surface-color);}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c .node .neo-node{stroke:var(--text-color);}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c [data-look=&#34;neo&#34;].node rect,#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c [data-look=&#34;neo&#34;].cluster rect,#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c [data-look=&#34;neo&#34;].node polygon{stroke:var(--text-color);filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c [data-look=&#34;neo&#34;].node path{stroke:var(--text-color);stroke-width:1px;}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c [data-look=&#34;neo&#34;].node .outer-path{filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c [data-look=&#34;neo&#34;].node .neo-line path{stroke:var(--text-color);filter:none;}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c [data-look=&#34;neo&#34;].node circle{stroke:var(--text-color);filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c [data-look=&#34;neo&#34;].node circle .state-start{fill:#000000;}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c [data-look=&#34;neo&#34;].icon-shape .icon{fill:var(--text-color);filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c [data-look=&#34;neo&#34;].icon-shape .icon-neo path{stroke:var(--text-color);filter:drop-shadow(1px 2px 2px rgba(185, 185, 185, 1));}#mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c :root{--mermaid-font-family:&#34;trebuchet ms&#34;,verdana,arial,sans-serif;}&lt;/style&gt;&lt;g&gt;&lt;marker id=&#34;mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c_flowchart-v2-pointEnd&#34; class=&#34;marker flowchart-v2&#34; viewBox=&#34;0 0 10 10&#34; refX=&#34;5&#34; refY=&#34;5&#34; markerUnits=&#34;userSpaceOnUse&#34; markerWidth=&#34;8&#34; markerHeight=&#34;8&#34; orient=&#34;auto&#34;&gt;&lt;path d=&#34;M 0 0 L 10 5 L 0 10 z&#34; class=&#34;arrowMarkerPath&#34; style=&#34;stroke-width: 1; stroke-dasharray: 1, 0;&#34;/&gt;&lt;/marker&gt;&lt;marker id=&#34;mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c_flowchart-v2-pointStart&#34; class=&#34;marker flowchart-v2&#34; viewBox=&#34;0 0 10 10&#34; refX=&#34;4.5&#34; refY=&#34;5&#34; markerUnits=&#34;userSpaceOnUse&#34; markerWidth=&#34;8&#34; markerHeight=&#34;8&#34; orient=&#34;auto&#34;&gt;&lt;path d=&#34;M 0 5 L 10 10 L 10 0 z&#34; class=&#34;arrowMarkerPath&#34; style=&#34;stroke-width: 1; stroke-dasharray: 1, 0;&#34;/&gt;&lt;/marker&gt;&lt;marker id=&#34;mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c_flowchart-v2-pointEnd-margin&#34; class=&#34;marker flowchart-v2&#34; viewBox=&#34;0 0 11.5 14&#34; refX=&#34;11.5&#34; refY=&#34;7&#34; markerUnits=&#34;userSpaceOnUse&#34; markerWidth=&#34;10.5&#34; markerHeight=&#34;14&#34; orient=&#34;auto&#34;&gt;&lt;path d=&#34;M 0 0 L 11.5 7 L 0 14 z&#34; class=&#34;arrowMarkerPath&#34; style=&#34;stroke-width: 0; stroke-dasharray: 1, 0;&#34;/&gt;&lt;/marker&gt;&lt;marker id=&#34;mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c_flowchart-v2-pointStart-margin&#34; class=&#34;marker flowchart-v2&#34; viewBox=&#34;0 0 11.5 14&#34; refX=&#34;1&#34; refY=&#34;7&#34; markerUnits=&#34;userSpaceOnUse&#34; markerWidth=&#34;11.5&#34; markerHeight=&#34;14&#34; orient=&#34;auto&#34;&gt;&lt;polygon points=&#34;0,7 11.5,14 11.5,0&#34; class=&#34;arrowMarkerPath&#34; style=&#34;stroke-width: 0; stroke-dasharray: 1, 0;&#34;/&gt;&lt;/marker&gt;&lt;marker id=&#34;mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c_flowchart-v2-circleEnd&#34; class=&#34;marker flowchart-v2&#34; viewBox=&#34;0 0 10 10&#34; refX=&#34;11&#34; refY=&#34;5&#34; markerUnits=&#34;userSpaceOnUse&#34; markerWidth=&#34;11&#34; markerHeight=&#34;11&#34; orient=&#34;auto&#34;&gt;&lt;circle cx=&#34;5&#34; cy=&#34;5&#34; r=&#34;5&#34; class=&#34;arrowMarkerPath&#34; style=&#34;stroke-width: 1; stroke-dasharray: 1, 0;&#34;/&gt;&lt;/marker&gt;&lt;marker id=&#34;mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c_flowchart-v2-circleStart&#34; class=&#34;marker flowchart-v2&#34; viewBox=&#34;0 0 10 10&#34; refX=&#34;-1&#34; refY=&#34;5&#34; markerUnits=&#34;userSpaceOnUse&#34; markerWidth=&#34;11&#34; markerHeight=&#34;11&#34; orient=&#34;auto&#34;&gt;&lt;circle cx=&#34;5&#34; cy=&#34;5&#34; r=&#34;5&#34; class=&#34;arrowMarkerPath&#34; style=&#34;stroke-width: 1; stroke-dasharray: 1, 0;&#34;/&gt;&lt;/marker&gt;&lt;marker id=&#34;mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c_flowchart-v2-circleEnd-margin&#34; class=&#34;marker flowchart-v2&#34; viewBox=&#34;0 0 10 10&#34; refY=&#34;5&#34; refX=&#34;12.25&#34; markerUnits=&#34;userSpaceOnUse&#34; markerWidth=&#34;14&#34; markerHeight=&#34;14&#34; orient=&#34;auto&#34;&gt;&lt;circle cx=&#34;5&#34; cy=&#34;5&#34; r=&#34;5&#34; class=&#34;arrowMarkerPath&#34; style=&#34;stroke-width: 0; stroke-dasharray: 1, 0;&#34;/&gt;&lt;/marker&gt;&lt;marker id=&#34;mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c_flowchart-v2-circleStart-margin&#34; class=&#34;marker flowchart-v2&#34; viewBox=&#34;0 0 10 10&#34; refX=&#34;-2&#34; refY=&#34;5&#34; markerUnits=&#34;userSpaceOnUse&#34; markerWidth=&#34;14&#34; markerHeight=&#34;14&#34; orient=&#34;auto&#34;&gt;&lt;circle cx=&#34;5&#34; cy=&#34;5&#34; r=&#34;5&#34; class=&#34;arrowMarkerPath&#34; style=&#34;stroke-width: 0; stroke-dasharray: 1, 0;&#34;/&gt;&lt;/marker&gt;&lt;marker id=&#34;mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c_flowchart-v2-crossEnd&#34; class=&#34;marker cross flowchart-v2&#34; viewBox=&#34;0 0 11 11&#34; refX=&#34;12&#34; refY=&#34;5.2&#34; markerUnits=&#34;userSpaceOnUse&#34; markerWidth=&#34;11&#34; markerHeight=&#34;11&#34; orient=&#34;auto&#34;&gt;&lt;path d=&#34;M 1,1 l 9,9 M 10,1 l -9,9&#34; class=&#34;arrowMarkerPath&#34; style=&#34;stroke-width: 2; stroke-dasharray: 1, 0;&#34;/&gt;&lt;/marker&gt;&lt;marker id=&#34;mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c_flowchart-v2-crossStart&#34; class=&#34;marker cross flowchart-v2&#34; viewBox=&#34;0 0 11 11&#34; refX=&#34;-1&#34; refY=&#34;5.2&#34; markerUnits=&#34;userSpaceOnUse&#34; markerWidth=&#34;11&#34; markerHeight=&#34;11&#34; orient=&#34;auto&#34;&gt;&lt;path d=&#34;M 1,1 l 9,9 M 10,1 l -9,9&#34; class=&#34;arrowMarkerPath&#34; style=&#34;stroke-width: 2; stroke-dasharray: 1, 0;&#34;/&gt;&lt;/marker&gt;&lt;marker id=&#34;mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c_flowchart-v2-crossEnd-margin&#34; class=&#34;marker cross flowchart-v2&#34; viewBox=&#34;0 0 15 15&#34; refX=&#34;17.7&#34; refY=&#34;7.5&#34; markerUnits=&#34;userSpaceOnUse&#34; markerWidth=&#34;12&#34; markerHeight=&#34;12&#34; orient=&#34;auto&#34;&gt;&lt;path d=&#34;M 1,1 L 14,14 M 1,14 L 14,1&#34; class=&#34;arrowMarkerPath&#34; style=&#34;stroke-width: 2.5;&#34;/&gt;&lt;/marker&gt;&lt;marker id=&#34;mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c_flowchart-v2-crossStart-margin&#34; class=&#34;marker cross flowchart-v2&#34; viewBox=&#34;0 0 15 15&#34; refX=&#34;-3.5&#34; refY=&#34;7.5&#34; markerUnits=&#34;userSpaceOnUse&#34; markerWidth=&#34;12&#34; markerHeight=&#34;12&#34; orient=&#34;auto&#34;&gt;&lt;path d=&#34;M 1,1 L 14,14 M 1,14 L 14,1&#34; class=&#34;arrowMarkerPath&#34; style=&#34;stroke-width: 2.5; stroke-dasharray: 1, 0;&#34;/&gt;&lt;/marker&gt;&lt;g class=&#34;root&#34;&gt;&lt;g class=&#34;clusters&#34;&gt;&lt;g class=&#34;cluster&#34; id=&#34;mmd-45134c61379a295fa6e1dd92395ef8d3eef3d99311e3ee30997648c3f06bb86c-OL&#34; data-look=&#34;classic&#34;&gt;&lt;rect style=&#34;fill:var(--sb-c-dfd, #dfd) !important;stroke:var(--link-color) !important;stroke-width:2px !important&#34; x=&#34;352.765625&#34; y=&#34;34.52734375&#34; width=&#34;126.375&#34; height=&#34;333.65234375&#34;/&gt;&lt;g class=&#34;cluster-label&#34; transform=&#34;translate(369.703125, 34.52734375)&#34;&gt;&lt;foreignObject width=&#34;92.5&#34; height=&#34;24&#34;&gt;&lt;div xmlns=&#34;http://www.w3.org/1999/xhtml&#34; style=&#34;display: table-cell; white-space: nowrap; line-height: 1.5;&#34;&gt;&lt;span class=&#34;nodeLabel&#34; style=&#34;color:var(--sb-cl-dfd, #1f2937) !important&#34;&gt;&lt;p&gt;Output Layer&lt;/p&gt;</description>
    </item>
    <item>
      <title>Rust Patterns: Don’t Peel the Onion</title>
      <link>/posts/tech/rust-onion-analogy/</link>
      <pubDate>Tue, 31 Mar 2026 00:00:00 +0000</pubDate>
      <guid>/posts/tech/rust-onion-analogy/</guid>
      <description>&lt;h3&gt;&lt;/h3&gt;&#xA;&lt;p&gt;&lt;img src=&#34;/posts/tech/rust-onion-analogy/rust-onion-analogy_hu_ab0957b5f241ca5e.webp&#34; alt=&#34;Rust ownership as onion layers diagram&#34;  loading=&#34;lazy&#34; width=&#34;1200&#34; height=&#34;655&#34; data-testid=&#34;content-image&#34; /&gt;&lt;/p&gt;&#xA;&lt;p&gt;In Rust, working with references is a lot like handling an onion.  Understanding when to &amp;ldquo;peel&amp;rdquo; the onion and when to keep it whole is the  secret to mastering pattern matching and mutation.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h4 id=&#34;the-onion-analogy&#34;&gt;The Onion Analogy&lt;/h4&gt;&#xA;&lt;p&gt;Think of a mutable reference (&amp;amp;mut i32) as an onion:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;The Outer Layer: This is the reference (the &amp;ldquo;address&amp;rdquo; or &amp;ldquo;handle&amp;rdquo;).&lt;/li&gt;&#xA;&lt;li&gt;The Core: This is the actual data (the number itself).&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;hr&gt;&#xA;&lt;h4 id=&#34;peeling-the-onion-mut-v&#34;&gt;Peeling the Onion (&amp;amp;mut v)&lt;/h4&gt;&#xA;&lt;p&gt;When you use a pattern like &lt;code&gt;for &amp;amp;mut v in slice.iter_mut()&lt;/code&gt;, you are explicitly telling Rust to &lt;code&gt;peel off&lt;/code&gt; the reference layer.&lt;/p&gt;</description>
    </item>
    <item>
      <title>How to create partitions using cfdisk</title>
      <link>/posts/tech/how-to-use-cfdisk/</link>
      <pubDate>Mon, 23 Mar 2026 00:00:00 +0000</pubDate>
      <guid>/posts/tech/how-to-use-cfdisk/</guid>
      <description>&lt;p&gt;In this guide, we will create two partitions:&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;code&gt;Root Partition&lt;/code&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;code&gt;EFI Partition&lt;/code&gt;&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;p&gt;A &lt;em&gt;&lt;strong&gt;Swap Partition&lt;/strong&gt;&lt;/em&gt; is optional and not required for a normal setup, so we will skip it.&lt;/p&gt;&#xA;&lt;div class=&#34;code-block-wrapper&#34; data-testid=&#34;code-block&#34;&gt;&#xA;  &lt;div class=&#34;code-toolbar&#34; data-testid=&#34;code-toolbar&#34;&gt;&#xA;    &lt;button class=&#34;wrap-code-btn&#34; aria-label=&#34;Toggle line wrap&#34; title=&#34;Toggle line wrap&#34; data-testid=&#34;wrap-code-btn&#34;&gt;&#xA;      &lt;svg width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34;&gt;&lt;line x1=&#34;3&#34; y1=&#34;6&#34; x2=&#34;21&#34; y2=&#34;6&#34;&gt;&lt;/line&gt;&lt;path d=&#34;M3 12h15a3 3 0 1 1 0 6h-4&#34;&gt;&lt;/path&gt;&lt;polyline points=&#34;16 16 14 18 16 20&#34;&gt;&lt;/polyline&gt;&lt;line x1=&#34;3&#34; y1=&#34;18&#34; x2=&#34;10&#34; y2=&#34;18&#34;&gt;&lt;/line&gt;&lt;/svg&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;button class=&#34;copy-code-btn&#34; aria-label=&#34;Copy code&#34; data-testid=&#34;copy-code-btn&#34;&gt;&#xA;      &lt;svg width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34;&gt;&lt;rect x=&#34;9&#34; y=&#34;9&#34; width=&#34;13&#34; height=&#34;13&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;&lt;path d=&#34;M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;&#xA;    &lt;/button&gt;&#xA;  &lt;/div&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;EFI Partition: 1GB (Type: EFI System)&#xA;&#xA;Root Partition: 15GB (Type: Linux Filesystem)&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;/div&gt;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;Use the &lt;code&gt;lsblk&lt;/code&gt; command to view details about available disks:&lt;/p&gt;</description>
    </item>
    <item>
      <title>Install Frappe Framework</title>
      <link>/posts/tech/install-frappe-framework/</link>
      <pubDate>Mon, 23 Mar 2026 00:00:00 +0000</pubDate>
      <guid>/posts/tech/install-frappe-framework/</guid>
      <description>&lt;p&gt;&lt;a href=&#34;https://docs.frappe.io/framework/user/en/basics#what-is-frappe-framework&#34; class=&#34;external-link&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;What is Frappe Framework?&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Frappe is a full stack, batteries-included, web framework written in Python and Javascript.&lt;/p&gt;&#xA;&lt;p&gt;It is the framework which powers &lt;a href=&#34;https://erpnext.com/&#34; class=&#34;external-link&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;ERPNext&lt;/a&gt;. It is pretty generic and can be used to build database driven apps.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://docs.frappe.io/framework/user/en/basics&#34; class=&#34;external-link&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;https://docs.frappe.io/framework/user/en/basics&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;h4 id=&#34;so-whats-in-it&#34;&gt;So Whats in it&lt;/h4&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Doctypes ( similar to form )&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Workflow (enable flow of form from role to role i.e user to authority)&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;Client Script (Manages ui Interaction in frappe frontend)&lt;/p&gt;</description>
    </item>
    <item>
      <title>Rust In HTML/JS</title>
      <link>/posts/tech/rust-wasm-js/</link>
      <pubDate>Sun, 22 Mar 2026 00:00:00 +0000</pubDate>
      <guid>/posts/tech/rust-wasm-js/</guid>
      <description>&lt;p&gt;&lt;img src=&#34;/posts/tech/rust-wasm-js/wasm_hu_5ec735f47848b01d.webp&#34; alt=&#34;WebAssembly Rust to JavaScript diagram&#34;  loading=&#34;lazy&#34; width=&#34;1200&#34; height=&#34;593&#34; data-testid=&#34;content-image&#34; /&gt;&lt;/p&gt;&#xA;&lt;p&gt;WebAssembly (WASM) allows developers to run high-performance Rust code directly in web applications. This guide covers the steps to compile Rust code to WASM and integrate it with JavaScript/HTML.&lt;/p&gt;&#xA;&lt;h3 id=&#34;1-setting-up-the-rust-project&#34;&gt;1. Setting Up the Rust Project&lt;/h3&gt;&#xA;&lt;h3 id=&#34;install-rust--wasm-pack&#34;&gt;Install Rust &amp;amp; wasm-pack&lt;/h3&gt;&#xA;&lt;p&gt;Ensure Rust and &lt;code&gt;wasm-pack&lt;/code&gt; are installed:&lt;/p&gt;&#xA;&lt;div class=&#34;code-block-wrapper&#34; data-lang=&#34;bash&#34; data-testid=&#34;code-block&#34;&gt;&#xA;  &lt;div class=&#34;code-toolbar&#34; data-testid=&#34;code-toolbar&#34;&gt;&#xA;    &lt;span class=&#34;lang-code-btn&#34; aria-label=&#34;Code language: bash&#34; data-testid=&#34;lang-code-btn&#34;&gt;bash&lt;/span&gt;&#xA;    &lt;button class=&#34;wrap-code-btn&#34; aria-label=&#34;Toggle line wrap&#34; title=&#34;Toggle line wrap&#34; data-testid=&#34;wrap-code-btn&#34;&gt;&#xA;      &lt;svg width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34;&gt;&lt;line x1=&#34;3&#34; y1=&#34;6&#34; x2=&#34;21&#34; y2=&#34;6&#34;&gt;&lt;/line&gt;&lt;path d=&#34;M3 12h15a3 3 0 1 1 0 6h-4&#34;&gt;&lt;/path&gt;&lt;polyline points=&#34;16 16 14 18 16 20&#34;&gt;&lt;/polyline&gt;&lt;line x1=&#34;3&#34; y1=&#34;18&#34; x2=&#34;10&#34; y2=&#34;18&#34;&gt;&lt;/line&gt;&lt;/svg&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;button class=&#34;copy-code-btn&#34; aria-label=&#34;Copy code&#34; data-testid=&#34;copy-code-btn&#34;&gt;&#xA;      &lt;svg width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34;&gt;&lt;rect x=&#34;9&#34; y=&#34;9&#34; width=&#34;13&#34; height=&#34;13&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;&lt;path d=&#34;M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;&#xA;    &lt;/button&gt;&#xA;  &lt;/div&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;rustup update&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cargo install wasm-pack&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;h3 id=&#34;create-a-new-rust-library&#34;&gt;Create a New Rust Library&lt;/h3&gt;&#xA;&lt;div class=&#34;code-block-wrapper&#34; data-lang=&#34;bash&#34; data-testid=&#34;code-block&#34;&gt;&#xA;  &lt;div class=&#34;code-toolbar&#34; data-testid=&#34;code-toolbar&#34;&gt;&#xA;    &lt;span class=&#34;lang-code-btn&#34; aria-label=&#34;Code language: bash&#34; data-testid=&#34;lang-code-btn&#34;&gt;bash&lt;/span&gt;&#xA;    &lt;button class=&#34;wrap-code-btn&#34; aria-label=&#34;Toggle line wrap&#34; title=&#34;Toggle line wrap&#34; data-testid=&#34;wrap-code-btn&#34;&gt;&#xA;      &lt;svg width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34;&gt;&lt;line x1=&#34;3&#34; y1=&#34;6&#34; x2=&#34;21&#34; y2=&#34;6&#34;&gt;&lt;/line&gt;&lt;path d=&#34;M3 12h15a3 3 0 1 1 0 6h-4&#34;&gt;&lt;/path&gt;&lt;polyline points=&#34;16 16 14 18 16 20&#34;&gt;&lt;/polyline&gt;&lt;line x1=&#34;3&#34; y1=&#34;18&#34; x2=&#34;10&#34; y2=&#34;18&#34;&gt;&lt;/line&gt;&lt;/svg&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;button class=&#34;copy-code-btn&#34; aria-label=&#34;Copy code&#34; data-testid=&#34;copy-code-btn&#34;&gt;&#xA;      &lt;svg width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34;&gt;&lt;rect x=&#34;9&#34; y=&#34;9&#34; width=&#34;13&#34; height=&#34;13&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;&lt;path d=&#34;M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;&#xA;    &lt;/button&gt;&#xA;  &lt;/div&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cargo new --lib my_wasm_project&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; my_wasm_project&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;p&gt;Update &lt;code&gt;Cargo.toml&lt;/code&gt;:&lt;/p&gt;</description>
    </item>
    <item>
      <title>SVG Icons In Font Using Fantasticon</title>
      <link>/posts/tech/svg-icon-font/</link>
      <pubDate>Sun, 22 Mar 2026 00:00:00 +0000</pubDate>
      <guid>/posts/tech/svg-icon-font/</guid>
      <description>&lt;p&gt;&lt;img src=&#34;/posts/tech/svg-icon-font/svg-font_hu_720c1e9944cf0a5d.webp&#34; alt=&#34;SVG to icon font workflow&#34;  loading=&#34;lazy&#34; width=&#34;1200&#34; height=&#34;654&#34; data-testid=&#34;content-image&#34; /&gt;&lt;/p&gt;&#xA;&lt;h3 id=&#34;-why-use-an-icon-font&#34;&gt;✨ Why Use an Icon Font?&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Single HTTP request&lt;/strong&gt; for all icons&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Unicode character-based&lt;/strong&gt; referencing (like &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;b&lt;/code&gt;, &lt;code&gt;c&lt;/code&gt;)&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Easier styling&lt;/strong&gt; via CSS&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Cleaner markup&lt;/strong&gt; than embedding raw SVGs&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;step-by-step-create-a-custom-icon-font-with-fantasticon&#34;&gt;Step-by-Step: Create a Custom Icon Font with Fantasticon&lt;/h3&gt;&#xA;&lt;h3 id=&#34;1-install-fantasticon&#34;&gt;1. Install Fantasticon&lt;/h3&gt;&#xA;&lt;p&gt;On Arch Linux (or any system with &lt;code&gt;npm&lt;/code&gt;):&lt;/p&gt;&#xA;&lt;div class=&#34;code-block-wrapper&#34; data-lang=&#34;bash&#34; data-testid=&#34;code-block&#34;&gt;&#xA;  &lt;div class=&#34;code-toolbar&#34; data-testid=&#34;code-toolbar&#34;&gt;&#xA;    &lt;span class=&#34;lang-code-btn&#34; aria-label=&#34;Code language: bash&#34; data-testid=&#34;lang-code-btn&#34;&gt;bash&lt;/span&gt;&#xA;    &lt;button class=&#34;wrap-code-btn&#34; aria-label=&#34;Toggle line wrap&#34; title=&#34;Toggle line wrap&#34; data-testid=&#34;wrap-code-btn&#34;&gt;&#xA;      &lt;svg width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34;&gt;&lt;line x1=&#34;3&#34; y1=&#34;6&#34; x2=&#34;21&#34; y2=&#34;6&#34;&gt;&lt;/line&gt;&lt;path d=&#34;M3 12h15a3 3 0 1 1 0 6h-4&#34;&gt;&lt;/path&gt;&lt;polyline points=&#34;16 16 14 18 16 20&#34;&gt;&lt;/polyline&gt;&lt;line x1=&#34;3&#34; y1=&#34;18&#34; x2=&#34;10&#34; y2=&#34;18&#34;&gt;&lt;/line&gt;&lt;/svg&gt;&#xA;    &lt;/button&gt;&#xA;    &lt;button class=&#34;copy-code-btn&#34; aria-label=&#34;Copy code&#34; data-testid=&#34;copy-code-btn&#34;&gt;&#xA;      &lt;svg width=&#34;16&#34; height=&#34;16&#34; viewBox=&#34;0 0 24 24&#34; fill=&#34;none&#34; stroke=&#34;currentColor&#34; stroke-width=&#34;2&#34;&gt;&lt;rect x=&#34;9&#34; y=&#34;9&#34; width=&#34;13&#34; height=&#34;13&#34; rx=&#34;2&#34; ry=&#34;2&#34;&gt;&lt;/rect&gt;&lt;path d=&#34;M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1&#34;&gt;&lt;/path&gt;&lt;/svg&gt;&#xA;    &lt;/button&gt;&#xA;  &lt;/div&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo pacman -S nodejs npm&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;sudo npm install -g fantasticon&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;h3 id=&#34;2-prepare-your-svg-icons&#34;&gt;2. Prepare Your SVG Icons&lt;/h3&gt;&#xA;&lt;p&gt;Create a folder like this:&lt;/p&gt;</description>
    </item>
    <item>
      <title>Installing Archlinux using ISO</title>
      <link>/posts/tech/installing-archlinux/</link>
      <pubDate>Mon, 16 Mar 2026 00:00:00 +0000</pubDate>
      <guid>/posts/tech/installing-archlinux/</guid>
      <description>&lt;p&gt;&lt;img src=&#34;/posts/tech/installing-archlinux/archlinux_hu_c0d070bfb323c530.webp&#34; alt=&#34;Archlinux &#34;  loading=&#34;lazy&#34; width=&#34;1200&#34; height=&#34;593&#34; data-testid=&#34;content-image&#34; /&gt;&lt;/p&gt;&#xA;&lt;p&gt;Arch Linux is a lightweight and highly customizable Linux distribution. This guide covers the full installation process and details how to modify an existing Arch installation using the Arch Live ISO.&lt;/p&gt;&#xA;&lt;h2 id=&#34;part-1-installing-arch-linux&#34;&gt;Part 1: Installing Arch Linux&lt;/h2&gt;&#xA;&lt;h3 id=&#34;step-1-boot-into-the-arch-linux-iso&#34;&gt;Step 1: Boot into the Arch Linux ISO&lt;/h3&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Download the latest Arch Linux ISO from the official website.&lt;/p&gt;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://archlinux.org/download/&#34; class=&#34;external-link&#34; target=&#34;_blank&#34; rel=&#34;noopener noreferrer&#34;&gt;https://archlinux.org/download/&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Create a bootable USB using tools like dd , Rufus, &lt;code&gt;ventoy &lt;/code&gt;(recommended), balenaEtcher:&lt;/p&gt;</description>
    </item>
    <item>
      <title></title>
      <link>/posts/tech/matrix/nth-fibo-num-by-matrix/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/posts/tech/matrix/nth-fibo-num-by-matrix/</guid>
      <description>&lt;h1 id=&#34;unlocking-the-fibonacci-sequence-the-power-of-matrix-exponentiation&#34;&gt;Unlocking the Fibonacci Sequence: The Power of Matrix Exponentiation&lt;/h1&gt;&#xA;&lt;p&gt;Most programmers first encounter the Fibonacci sequence through a simple recursive function. But as &lt;code&gt;n&lt;/code&gt; grows, that elegant recursion turns into an exponential nightmare. Dynamic programming (memoization or tabulation) brings it down to &lt;strong&gt;O(n)&lt;/strong&gt;, which is great—until &lt;code&gt;n&lt;/code&gt; is a million, or a billion.&lt;/p&gt;&#xA;&lt;p&gt;What if I told you we can compute the 1000th Fibonacci number in the &lt;strong&gt;time it takes to do about 10 multiplications&lt;/strong&gt;? That is the magic of &lt;strong&gt;Matrix Exponentiation&lt;/strong&gt;.&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
