<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.3.4">Jekyll</generator><link href="https://mrprecioustech.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://mrprecioustech.github.io/" rel="alternate" type="text/html" /><updated>2026-09-22T18:52:05+00:00</updated><id>https://mrprecioustech.github.io/feed.xml</id><title type="html">MrPreciousTech</title><subtitle>MrPreciousTech is a personal tech blog by Mr Precious — practical tutorials, deep dives and field notes on JavaScript, Python, web development and the tools developers love.</subtitle><author><name>Mr Precious</name></author><entry><title type="html">Hello, World: Why I’m Writing on the Internet in 2026</title><link href="https://mrprecioustech.github.io/2026/09/18/hello-world/" rel="alternate" type="text/html" title="Hello, World: Why I’m Writing on the Internet in 2026" /><published>2026-09-18T09:00:00+00:00</published><updated>2026-09-18T09:00:00+00:00</updated><id>https://mrprecioustech.github.io/2026/09/18/hello-world</id><content type="html" xml:base="https://mrprecioustech.github.io/2026/09/18/hello-world/"><![CDATA[<p>Somehow, “blogging is dead” has been the consensus for about fifteen years now — and yet here I am, hitting publish on a brand-new personal tech blog in 2026. Let me explain myself.</p>

<h2 id="the-short-version">The short version</h2>

<p>I learn things, I forget things, and then I re-learn the same things six months later. This blog is my attempt to break that cycle. Every post here is a note to future-me, published in public just in case it helps present-you too.</p>

<blockquote class="callout">
  <p>The best way to learn something deeply is to explain it to someone else. The second best way is to write it down where your future self can find it.</p>
</blockquote>

<h2 id="what-youll-find-here">What you’ll find here</h2>

<p>This isn’t a niche blog with a content strategy and a posting calendar. It’s a personal one, so expect whatever I’m genuinely interested in at the time. In practice, that means:</p>

<ul>
  <li><strong>Practical tutorials</strong> — things I actually built, with working code you can copy.</li>
  <li><strong>Deep dives</strong> — topics like closures, CSS layout, or Git internals, explained the way I wish someone had explained them to me.</li>
  <li><strong>Field notes</strong> — tools, setups and workflows I’m trying out, with honest verdicts.</li>
  <li><strong>Opinionated rants (occasionally)</strong> — I promise to keep these short and well-argued.</li>
</ul>

<h2 id="what-you-wont-find-here">What you won’t find here</h2>

<ul>
  <li>AI-generated filler. Every word here was typed by a human (me), fueled by coffee.</li>
  <li>“10x developer” hustle content. I write code, I make mistakes, I fix them. That’s the whole brand.</li>
  <li>Paywalls, popups, or newsletter guilt-trips. There’s an <a href="/feed.xml">RSS feed</a> and that’s it.</li>
</ul>

<h2 id="why-a-blog-and-not-just-tweets-or-videos">Why a blog and not just tweets or videos?</h2>

<p>Nothing against short-form content, but some ideas need more than 280 characters and a thread emoji. A blog post:</p>

<ol>
  <li><strong>Is searchable</strong> — Google can find it years later. Good luck finding a tweet from 2023.</li>
  <li><strong>Is permanent</strong> — I own this site. No algorithm decides who sees it.</li>
  <li><strong>Forces clarity</strong> — writing 1,000 words about something exposes every gap in my understanding.</li>
</ol>

<p>That last point is the real reason I’m here. Writing is thinking, and publishing is accountability.</p>

<h2 id="the-plan">The plan</h2>

<p>New posts every couple of weeks. If you want to follow along, grab the <a href="/feed.xml">RSS feed</a>, star the <a href="https://github.com/mrprecioustech/mrprecioustech.github.io">repo on GitHub</a>, or just bookmark the site like it’s 2009.</p>

<p>And if you’re thinking of starting your own blog: do it. It costs nothing — I built this entire site with Jekyll and GitHub Pages for free, and I wrote a <a href="/2026/09/10/jekyll-github-pages-blog/">whole tutorial about it</a>.</p>

<p>See you in the next post. ✍</p>]]></content><author><name>Mr Precious</name></author><category term="blogging" /><category term="meta" /><summary type="html"><![CDATA[Everyone says blogging is dead. I disagree — here's why I started this blog, what I'll write about, and why you might want to start one too.]]></summary></entry><entry><title type="html">How I Built This Blog: Jekyll + GitHub Pages for Free</title><link href="https://mrprecioustech.github.io/2026/09/10/jekyll-github-pages-blog/" rel="alternate" type="text/html" title="How I Built This Blog: Jekyll + GitHub Pages for Free" /><published>2026-09-10T09:00:00+00:00</published><updated>2026-09-10T09:00:00+00:00</updated><id>https://mrprecioustech.github.io/2026/09/10/jekyll-github-pages-blog</id><content type="html" xml:base="https://mrprecioustech.github.io/2026/09/10/jekyll-github-pages-blog/"><![CDATA[<p>This blog you’re reading right now costs me exactly <strong>$0 per month</strong> to run. No hosting bill, no database, no CMS subscription. It’s a static site built with <a href="https://jekyllrb.com">Jekyll</a> and hosted on <a href="https://pages.github.com">GitHub Pages</a>. Here’s the full recipe.</p>

<h2 id="why-jekyll">Why Jekyll?</h2>

<p>Jekyll is a static site generator: you write posts in Markdown, and it turns them into plain HTML files. For a personal blog, that means:</p>

<ul>
  <li><strong>Free hosting</strong> — GitHub Pages serves static sites for free, straight from your repo.</li>
  <li><strong>Blazing fast</strong> — there’s no database or server-side rendering. Just files.</li>
  <li><strong>Version controlled</strong> — every post is a commit. Full history, branches, pull requests for your own blog.</li>
  <li><strong>Markdown-native</strong> — if you can write a README, you can write a blog post.</li>
</ul>

<h2 id="step-1-create-the-repository">Step 1: Create the repository</h2>

<p>GitHub gives every user one special repository: <code class="language-plaintext highlighter-rouge">&lt;username&gt;.github.io</code>. Push a site there and it goes live at <code class="language-plaintext highlighter-rouge">https://&lt;username&gt;.github.io</code>. Create it, then clone it locally:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/&lt;username&gt;/&lt;username&gt;.github.io.git
<span class="nb">cd</span> &lt;username&gt;.github.io
</code></pre></div></div>

<h2 id="step-2-set-up-jekyll">Step 2: Set up Jekyll</h2>

<p>You’ll need Ruby. On most systems:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># macOS (with Homebrew)</span>
brew <span class="nb">install </span>ruby

<span class="c"># Ubuntu / Debian</span>
<span class="nb">sudo </span>apt <span class="nb">install </span>ruby-full build-essential

<span class="c"># Then install Jekyll</span>
gem <span class="nb">install </span>jekyll bundler
</code></pre></div></div>

<p>Create a <code class="language-plaintext highlighter-rouge">Gemfile</code> in your repo root so builds are reproducible:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">source</span> <span class="s2">"https://rubygems.org"</span>

<span class="n">gem</span> <span class="s2">"jekyll"</span><span class="p">,</span> <span class="s2">"~&gt; 4.3.0"</span>
<span class="n">gem</span> <span class="s2">"jekyll-feed"</span><span class="p">,</span> <span class="s2">"~&gt; 0.17"</span>      <span class="c1"># RSS feed</span>
<span class="n">gem</span> <span class="s2">"jekyll-seo-tag"</span><span class="p">,</span> <span class="s2">"~&gt; 2.8"</span>    <span class="c1"># meta tags</span>
<span class="n">gem</span> <span class="s2">"jekyll-sitemap"</span><span class="p">,</span> <span class="s2">"~&gt; 1.4"</span>    <span class="c1"># sitemap.xml</span>
<span class="n">gem</span> <span class="s2">"jekyll-paginate"</span><span class="p">,</span> <span class="s2">"~&gt; 1.1"</span>   <span class="c1"># homepage pagination</span>
<span class="n">gem</span> <span class="s2">"webrick"</span><span class="p">,</span> <span class="s2">"~&gt; 1.8"</span>           <span class="c1"># `jekyll serve` on Ruby 3+</span>
</code></pre></div></div>

<h2 id="step-3-add-the-config">Step 3: Add the config</h2>

<p>Jekyll is configured with a single YAML file, <code class="language-plaintext highlighter-rouge">_config.yml</code>:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">title</span><span class="pi">:</span> <span class="s">My Awesome Blog</span>
<span class="na">description</span><span class="pi">:</span> <span class="s">Thoughts on code and other things.</span>
<span class="na">url</span><span class="pi">:</span> <span class="s">https://&lt;username&gt;.github.io</span>

<span class="na">permalink</span><span class="pi">:</span> <span class="s">pretty</span>
<span class="na">markdown</span><span class="pi">:</span> <span class="s">kramdown</span>

<span class="na">plugins</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">jekyll-feed</span>
  <span class="pi">-</span> <span class="s">jekyll-seo-tag</span>
  <span class="pi">-</span> <span class="s">jekyll-sitemap</span>
  <span class="pi">-</span> <span class="s">jekyll-paginate</span>

<span class="na">paginate</span><span class="pi">:</span> <span class="m">5</span>
<span class="na">paginate_path</span><span class="pi">:</span> <span class="s">/page:num/</span>
</code></pre></div></div>

<h2 id="step-4-write-your-first-post">Step 4: Write your first post</h2>

<p>Posts live in <code class="language-plaintext highlighter-rouge">_posts/</code> and follow a strict naming convention: <code class="language-plaintext highlighter-rouge">YEAR-MONTH-DAY-title.md</code>. Each one starts with <strong>front matter</strong> — a YAML block that Jekyll reads for metadata:</p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">---</span>
<span class="na">layout</span><span class="pi">:</span> <span class="s">post</span>
<span class="na">title</span><span class="pi">:</span> <span class="s2">"</span><span class="s">My</span><span class="nv"> </span><span class="s">First</span><span class="nv"> </span><span class="s">Post"</span>
<span class="na">date</span><span class="pi">:</span> <span class="s">2026-09-10 09:00:00 +0000</span>
<span class="na">tags</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">hello</span><span class="pi">,</span> <span class="nv">blogging</span><span class="pi">]</span>
<span class="nn">---</span>

Hello, world! This is my first post.

<span class="gu">## A section heading</span>

Some text with <span class="sb">`inline code`</span> and a <span class="p">[</span><span class="nv">link</span><span class="p">](</span><span class="sx">https://example.com</span><span class="p">)</span>.
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">{{ site.title }}</code> style double-curly syntax you see in Jekyll themes is <strong>Liquid</strong>, the templating language. Layouts in <code class="language-plaintext highlighter-rouge">_layouts/</code> and reusable snippets in <code class="language-plaintext highlighter-rouge">_includes/</code> let you build pages from components:</p>

<div class="language-liquid highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">{%</span><span class="w"> </span><span class="nt">for</span><span class="w"> </span><span class="nv">post</span><span class="w"> </span><span class="nt">in</span><span class="w"> </span><span class="nv">site</span><span class="p">.</span><span class="nv">posts</span><span class="w"> </span><span class="na">limit</span><span class="o">:</span><span class="w"> </span><span class="mi">5</span><span class="w"> </span><span class="cp">%}</span>
  &lt;a href="<span class="cp">{{</span><span class="w"> </span><span class="nv">post</span><span class="p">.</span><span class="nv">url</span><span class="w"> </span><span class="cp">}}</span>"&gt;<span class="cp">{{</span><span class="w"> </span><span class="nv">post</span><span class="p">.</span><span class="nv">title</span><span class="w"> </span><span class="cp">}}</span>&lt;/a&gt;
<span class="cp">{%</span><span class="w"> </span><span class="nt">endfor</span><span class="w"> </span><span class="cp">%}</span>
</code></pre></div></div>

<blockquote class="callout">
  <p>Inside your own posts, wrap any Liquid-looking code in raw tags (<code class="language-plaintext highlighter-rouge">{&amp;#37; raw &amp;#37;}</code> … <code class="language-plaintext highlighter-rouge">{&amp;#37; endraw &amp;#37;}</code>), otherwise Jekyll will try to render it. Ask me how I know.</p>
</blockquote>

<h2 id="step-5-preview-locally">Step 5: Preview locally</h2>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bundle <span class="nb">install
</span>bundle <span class="nb">exec </span>jekyll serve
</code></pre></div></div>

<p>Open <code class="language-plaintext highlighter-rouge">http://localhost:4000</code> and there’s your blog, with live rebuilds on every save.</p>

<h2 id="step-6-deploy-with-github-actions">Step 6: Deploy with GitHub Actions</h2>

<p>The modern way to publish is a workflow file at <code class="language-plaintext highlighter-rouge">.github/workflows/jekyll.yml</code> that builds your site and deploys it to Pages on every push to <code class="language-plaintext highlighter-rouge">main</code>:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">Deploy Jekyll site to GitHub Pages</span>

<span class="na">on</span><span class="pi">:</span>
  <span class="na">push</span><span class="pi">:</span>
    <span class="na">branches</span><span class="pi">:</span> <span class="pi">[</span><span class="nv">main</span><span class="pi">]</span>

<span class="na">permissions</span><span class="pi">:</span>
  <span class="na">contents</span><span class="pi">:</span> <span class="s">read</span>
  <span class="na">pages</span><span class="pi">:</span> <span class="s">write</span>
  <span class="na">id-token</span><span class="pi">:</span> <span class="s">write</span>

<span class="na">jobs</span><span class="pi">:</span>
  <span class="na">build</span><span class="pi">:</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v4</span>
      <span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">ruby/setup-ruby@v1</span>
        <span class="na">with</span><span class="pi">:</span>
          <span class="na">ruby-version</span><span class="pi">:</span> <span class="s2">"</span><span class="s">3.3"</span>
          <span class="na">bundler-cache</span><span class="pi">:</span> <span class="kc">true</span>
      <span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/configure-pages@v5</span>
      <span class="pi">-</span> <span class="na">run</span><span class="pi">:</span> <span class="s">bundle exec jekyll build</span>
        <span class="na">env</span><span class="pi">:</span>
          <span class="na">JEKYLL_ENV</span><span class="pi">:</span> <span class="s">production</span>
      <span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/upload-pages-artifact@v3</span>

  <span class="na">deploy</span><span class="pi">:</span>
    <span class="na">needs</span><span class="pi">:</span> <span class="s">build</span>
    <span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
    <span class="na">environment</span><span class="pi">:</span>
      <span class="na">name</span><span class="pi">:</span> <span class="s">github-pages</span>
      <span class="na">url</span><span class="pi">:</span> <span class="s">${{ steps.deployment.outputs.page_url }}</span>
    <span class="na">steps</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="na">id</span><span class="pi">:</span> <span class="s">deployment</span>
        <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/deploy-pages@v4</span>
</code></pre></div></div>

<p>Then go to your repo’s <strong>Settings → Pages</strong> and set <strong>Source</strong> to <strong>GitHub Actions</strong>. Push to <code class="language-plaintext highlighter-rouge">main</code>, wait a minute, and your blog is live. Every future post is just <code class="language-plaintext highlighter-rouge">git push</code> away.</p>

<h2 id="what-i-added-on-top">What I added on top</h2>

<p>The basics above get you a working blog in an afternoon. This site adds a few extras, all in the repo if you want to steal ideas:</p>

<table>
  <thead>
    <tr>
      <th>Feature</th>
      <th>How</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Custom theme, dark mode</td>
      <td>Hand-written CSS + a tiny JS toggle</td>
    </tr>
    <tr>
      <td>Client-side search</td>
      <td>A generated <code class="language-plaintext highlighter-rouge">search.json</code> + ~60 lines of vanilla JS</td>
    </tr>
    <tr>
      <td>Reading progress + TOC</td>
      <td>~40 lines of vanilla JS, no libraries</td>
    </tr>
    <tr>
      <td>Comments (optional)</td>
      <td><a href="https://giscus.app">Giscus</a>, backed by GitHub Discussions</td>
    </tr>
    <tr>
      <td>SEO + social cards</td>
      <td><code class="language-plaintext highlighter-rouge">jekyll-seo-tag</code> does the heavy lifting</td>
    </tr>
  </tbody>
</table>

<h2 id="final-thoughts">Final thoughts</h2>

<p>Could I have used Next.js, Astro, or Hugo? Sure — they’re all great. But for a personal blog, Jekyll hits a sweet spot: dead simple, free forever, and boring in the best possible way. The less time I spend on infrastructure, the more time I spend writing.</p>

<p>Now stop reading about blogging platforms and go write something. The best blog setup is the one that’s already published.</p>]]></content><author><name>Mr Precious</name></author><category term="jekyll" /><category term="webdev" /><category term="tutorial" /><summary type="html"><![CDATA[No database, no server bills, no CMS lock-in. Here's exactly how I built and deployed this blog with Jekyll and GitHub Pages — and how you can too.]]></summary></entry><entry><title type="html">JavaScript Closures, Finally Explained</title><link href="https://mrprecioustech.github.io/2026/08/28/javascript-closures/" rel="alternate" type="text/html" title="JavaScript Closures, Finally Explained" /><published>2026-08-28T09:00:00+00:00</published><updated>2026-08-28T09:00:00+00:00</updated><id>https://mrprecioustech.github.io/2026/08/28/javascript-closures</id><content type="html" xml:base="https://mrprecioustech.github.io/2026/08/28/javascript-closures/"><![CDATA[<p>Every JavaScript developer hits the “closures” chapter, nods along pretending to understand, and then gets humbled by it in an interview two years later. Let’s fix that properly — with examples you’ll actually use, not just <code class="language-plaintext highlighter-rouge">for (var i...)</code> trivia.</p>

<h2 id="the-one-sentence-definition">The one-sentence definition</h2>

<p><strong>A closure is a function that remembers the variables from the scope where it was created — even after that scope has finished executing.</strong></p>

<p>That’s it. Everything else is commentary. Let’s prove it:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">makeCounter</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">let</span> <span class="nx">count</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="c1">// ← this variable "should" disappear when makeCounter returns</span>

  <span class="k">return</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
    <span class="nx">count</span> <span class="o">+=</span> <span class="mi">1</span><span class="p">;</span>
    <span class="k">return</span> <span class="nx">count</span><span class="p">;</span>
  <span class="p">};</span>
<span class="p">}</span>

<span class="kd">const</span> <span class="nx">counter</span> <span class="o">=</span> <span class="nf">makeCounter</span><span class="p">();</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nf">counter</span><span class="p">());</span> <span class="c1">// 1</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nf">counter</span><span class="p">());</span> <span class="c1">// 2</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nf">counter</span><span class="p">());</span> <span class="c1">// 3</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">makeCounter()</code> finished running on line 1 of the usage. But <code class="language-plaintext highlighter-rouge">count</code> is still alive, privately held by the returned function. That private, persistent memory <strong>is</strong> the closure.</p>

<h2 id="why-should-you-care">Why should you care?</h2>

<p>Because closures are everywhere in real code:</p>

<h3 id="1-private-state-without-classes">1. Private state (without classes)</h3>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">createBankAccount</span><span class="p">(</span><span class="nx">initialBalance</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">let</span> <span class="nx">balance</span> <span class="o">=</span> <span class="nx">initialBalance</span><span class="p">;</span>

  <span class="k">return</span> <span class="p">{</span>
    <span class="nf">deposit</span><span class="p">(</span><span class="nx">amount</span><span class="p">)</span> <span class="p">{</span> <span class="nx">balance</span> <span class="o">+=</span> <span class="nx">amount</span><span class="p">;</span> <span class="k">return</span> <span class="nx">balance</span><span class="p">;</span> <span class="p">},</span>
    <span class="nf">withdraw</span><span class="p">(</span><span class="nx">amount</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">if </span><span class="p">(</span><span class="nx">amount</span> <span class="o">&gt;</span> <span class="nx">balance</span><span class="p">)</span> <span class="k">throw</span> <span class="k">new</span> <span class="nc">Error</span><span class="p">(</span><span class="dl">"</span><span class="s2">Insufficient funds</span><span class="dl">"</span><span class="p">);</span>
      <span class="nx">balance</span> <span class="o">-=</span> <span class="nx">amount</span><span class="p">;</span>
      <span class="k">return</span> <span class="nx">balance</span><span class="p">;</span>
    <span class="p">},</span>
    <span class="nf">getBalance</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">balance</span><span class="p">;</span> <span class="p">},</span>
  <span class="p">};</span>
<span class="p">}</span>

<span class="kd">const</span> <span class="nx">account</span> <span class="o">=</span> <span class="nf">createBankAccount</span><span class="p">(</span><span class="mi">100</span><span class="p">);</span>
<span class="nx">account</span><span class="p">.</span><span class="nf">deposit</span><span class="p">(</span><span class="mi">50</span><span class="p">);</span>      <span class="c1">// 150</span>
<span class="nx">account</span><span class="p">.</span><span class="nx">balance</span><span class="p">;</span>          <span class="c1">// undefined — truly private!</span>
<span class="nx">account</span><span class="p">.</span><span class="nf">getBalance</span><span class="p">();</span>     <span class="c1">// 150</span>
</code></pre></div></div>

<p>No <code class="language-plaintext highlighter-rouge">#private</code> syntax, no TypeScript, no classes — just a closure doing what closures do.</p>

<h3 id="2-function-factories">2. Function factories</h3>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">greetWith</span><span class="p">(</span><span class="nx">greeting</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="nf">function </span><span class="p">(</span><span class="nx">name</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">return</span> <span class="s2">`</span><span class="p">${</span><span class="nx">greeting</span><span class="p">}</span><span class="s2">, </span><span class="p">${</span><span class="nx">name</span><span class="p">}</span><span class="s2">!`</span><span class="p">;</span>
  <span class="p">};</span>
<span class="p">}</span>

<span class="kd">const</span> <span class="nx">sayHello</span> <span class="o">=</span> <span class="nf">greetWith</span><span class="p">(</span><span class="dl">"</span><span class="s2">Hello</span><span class="dl">"</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">sayHi</span> <span class="o">=</span> <span class="nf">greetWith</span><span class="p">(</span><span class="dl">"</span><span class="s2">Hi</span><span class="dl">"</span><span class="p">);</span>

<span class="nf">sayHello</span><span class="p">(</span><span class="dl">"</span><span class="s2">Ada</span><span class="dl">"</span><span class="p">);</span> <span class="c1">// "Hello, Ada!"</span>
<span class="nf">sayHi</span><span class="p">(</span><span class="dl">"</span><span class="s2">Grace</span><span class="dl">"</span><span class="p">);</span>  <span class="c1">// "Hi, Grace!"</span>
</code></pre></div></div>

<p>Each returned function carries its own <code class="language-plaintext highlighter-rouge">greeting</code>. This pattern shows up constantly in middleware, event handlers, and configuration.</p>

<h3 id="3-debouncing-a-real-world-classic">3. Debouncing (a real-world classic)</h3>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">debounce</span><span class="p">(</span><span class="nx">fn</span><span class="p">,</span> <span class="nx">delay</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">let</span> <span class="nx">timerId</span><span class="p">;</span> <span class="c1">// ← shared across every call of the returned function</span>

  <span class="k">return</span> <span class="nf">function </span><span class="p">(...</span><span class="nx">args</span><span class="p">)</span> <span class="p">{</span>
    <span class="nf">clearTimeout</span><span class="p">(</span><span class="nx">timerId</span><span class="p">);</span>
    <span class="nx">timerId</span> <span class="o">=</span> <span class="nf">setTimeout</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="nx">fn</span><span class="p">.</span><span class="nf">apply</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="nx">args</span><span class="p">),</span> <span class="nx">delay</span><span class="p">);</span>
  <span class="p">};</span>
<span class="p">}</span>

<span class="kd">const</span> <span class="nx">search</span> <span class="o">=</span> <span class="nf">debounce</span><span class="p">((</span><span class="nx">query</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nf">fetchResults</span><span class="p">(</span><span class="nx">query</span><span class="p">),</span> <span class="mi">300</span><span class="p">);</span>
<span class="nx">input</span><span class="p">.</span><span class="nf">addEventListener</span><span class="p">(</span><span class="dl">"</span><span class="s2">input</span><span class="dl">"</span><span class="p">,</span> <span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nf">search</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">value</span><span class="p">));</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">timerId</code> persists between keystrokes because of the closure. Without it, you’d need a global variable or a class — messier either way.</p>

<h2 id="the-classic-gotcha-and-why-let-fixed-it">The classic gotcha (and why <code class="language-plaintext highlighter-rouge">let</code> fixed it)</h2>

<p>You’ve probably seen this interview question:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for </span><span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="mi">3</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
  <span class="nf">setTimeout</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">i</span><span class="p">),</span> <span class="mi">100</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// logs: 3, 3, 3  😱</span>
</code></pre></div></div>

<p>All three callbacks close over the <strong>same</strong> <code class="language-plaintext highlighter-rouge">i</code>, which is <code class="language-plaintext highlighter-rouge">3</code> by the time they run. The old-school fix was an IIFE; the modern fix is <code class="language-plaintext highlighter-rouge">let</code>, which creates a fresh binding per iteration:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for </span><span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;</span> <span class="mi">3</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
  <span class="nf">setTimeout</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">i</span><span class="p">),</span> <span class="mi">100</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// logs: 0, 1, 2  ✅</span>
</code></pre></div></div>

<p>Each iteration gets its own <code class="language-plaintext highlighter-rouge">i</code>, so each closure captures its own value. Mystery solved.</p>

<h2 id="the-mental-model">The mental model</h2>

<p>When you define a function in JavaScript, it doesn’t just capture code — it captures a <strong>backpack</strong> containing references to all the variables in scope at that moment. Wherever the function goes (returned, passed as a callback, stored in an object), the backpack goes with it.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">outer</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">backpack</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">packed 🎒</span><span class="dl">"</span><span class="p">;</span>
  <span class="k">return</span> <span class="kd">function</span> <span class="nf">inner</span><span class="p">()</span> <span class="p">{</span>
    <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">backpack</span><span class="p">);</span> <span class="c1">// inner carries `backpack` wherever it goes</span>
  <span class="p">};</span>
<span class="p">}</span>

<span class="kd">const</span> <span class="nx">fn</span> <span class="o">=</span> <span class="nf">outer</span><span class="p">();</span>
<span class="nf">fn</span><span class="p">();</span> <span class="c1">// "packed 🎒"</span>
</code></pre></div></div>

<blockquote class="callout">
  <p>Closures don’t copy values — they hold <strong>references</strong> to variables. If the variable changes later, the closure sees the new value. This is the source of both their power and 90% of closure bugs.</p>
</blockquote>

<h2 id="quick-self-test">Quick self-test</h2>

<p>What does this log, and why?</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nf">makeAdders</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">adders</span> <span class="o">=</span> <span class="p">[];</span>
  <span class="k">for </span><span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="nx">i</span> <span class="o">&lt;=</span> <span class="mi">3</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
    <span class="nx">adders</span><span class="p">.</span><span class="nf">push</span><span class="p">(()</span> <span class="o">=&gt;</span> <span class="nx">i</span> <span class="o">*</span> <span class="mi">10</span><span class="p">);</span>
  <span class="p">}</span>
  <span class="k">return</span> <span class="nx">adders</span><span class="p">;</span>
<span class="p">}</span>

<span class="kd">const</span> <span class="p">[</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">,</span> <span class="nx">c</span><span class="p">]</span> <span class="o">=</span> <span class="nf">makeAdders</span><span class="p">();</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nf">a</span><span class="p">(),</span> <span class="nf">b</span><span class="p">(),</span> <span class="nf">c</span><span class="p">());</span>
</code></pre></div></div>

<details>
<summary>Click to reveal the answer</summary>

`40 40 40`. All three arrow functions close over the same `var i`, which equals `4` after the loop ends. Change `var` to `let` and you get `10 20 30`.

</details>

<h2 id="wrapping-up">Wrapping up</h2>

<ul>
  <li>A <strong>closure</strong> = a function + the variables it remembers from where it was defined.</li>
  <li>Use them for <strong>private state</strong>, <strong>function factories</strong>, and <strong>callbacks that need memory</strong> (debounce, event handlers, memoization).</li>
  <li>Remember they capture <strong>references, not copies</strong> — <code class="language-plaintext highlighter-rouge">let</code> vs <code class="language-plaintext highlighter-rouge">var</code> in loops is the classic trap.</li>
</ul>

<p>Once you see closures, you can’t unsee them: React hooks, Express middleware, module patterns — it’s closures all the way down. And now you actually understand them. You’re welcome, future interview-you.</p>]]></content><author><name>Mr Precious</name></author><category term="javascript" /><category term="webdev" /><summary type="html"><![CDATA[Closures confuse every JavaScript developer exactly once. This is the explanation I wish I'd had — with practical examples you'll actually use.]]></summary></entry><entry><title type="html">Automate the Boring Stuff: 5 Python Scripts I Actually Use</title><link href="https://mrprecioustech.github.io/2026/08/15/python-automation/" rel="alternate" type="text/html" title="Automate the Boring Stuff: 5 Python Scripts I Actually Use" /><published>2026-08-15T09:00:00+00:00</published><updated>2026-08-15T09:00:00+00:00</updated><id>https://mrprecioustech.github.io/2026/08/15/python-automation</id><content type="html" xml:base="https://mrprecioustech.github.io/2026/08/15/python-automation/"><![CDATA[<p>I have a rule: <strong>if I’ve done something twice, I automate it the third time.</strong> Not because I’m lazy — okay, partly because I’m lazy — but because computers are better at boring repetitive work than I am, and I’d rather spend that time building things.</p>

<p>Here are five small Python scripts I genuinely use. No frameworks, no dependencies beyond the standard library (except one). Copy them, tweak them, make them yours.</p>

<h2 id="1-the-downloads-folder-rescue">1. The Downloads folder rescue</h2>

<p>Everyone’s <code class="language-plaintext highlighter-rouge">Downloads</code> folder is a disaster zone. This script sorts it by file extension in seconds:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="n">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>

<span class="n">DOWNLOADS</span> <span class="o">=</span> <span class="n">Path</span><span class="p">.</span><span class="nf">home</span><span class="p">()</span> <span class="o">/</span> <span class="sh">"</span><span class="s">Downloads</span><span class="sh">"</span>

<span class="n">CATEGORIES</span> <span class="o">=</span> <span class="p">{</span>
    <span class="sh">"</span><span class="s">Images</span><span class="sh">"</span><span class="p">:</span> <span class="p">{</span><span class="sh">"</span><span class="s">.jpg</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">.jpeg</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">.png</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">.gif</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">.svg</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">.webp</span><span class="sh">"</span><span class="p">},</span>
    <span class="sh">"</span><span class="s">Documents</span><span class="sh">"</span><span class="p">:</span> <span class="p">{</span><span class="sh">"</span><span class="s">.pdf</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">.docx</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">.txt</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">.md</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">.xlsx</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">.csv</span><span class="sh">"</span><span class="p">},</span>
    <span class="sh">"</span><span class="s">Videos</span><span class="sh">"</span><span class="p">:</span> <span class="p">{</span><span class="sh">"</span><span class="s">.mp4</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">.mkv</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">.mov</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">.avi</span><span class="sh">"</span><span class="p">},</span>
    <span class="sh">"</span><span class="s">Audio</span><span class="sh">"</span><span class="p">:</span> <span class="p">{</span><span class="sh">"</span><span class="s">.mp3</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">.wav</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">.flac</span><span class="sh">"</span><span class="p">},</span>
    <span class="sh">"</span><span class="s">Archives</span><span class="sh">"</span><span class="p">:</span> <span class="p">{</span><span class="sh">"</span><span class="s">.zip</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">.tar</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">.gz</span><span class="sh">"</span><span class="p">},</span>
    <span class="sh">"</span><span class="s">Installers</span><span class="sh">"</span><span class="p">:</span> <span class="p">{</span><span class="sh">"</span><span class="s">.exe</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">.msi</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">.dmg</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">.deb</span><span class="sh">"</span><span class="p">},</span>
<span class="p">}</span>

<span class="k">def</span> <span class="nf">organize</span><span class="p">():</span>
    <span class="n">moved</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="k">for</span> <span class="nb">file</span> <span class="ow">in</span> <span class="n">DOWNLOADS</span><span class="p">.</span><span class="nf">iterdir</span><span class="p">():</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="nb">file</span><span class="p">.</span><span class="nf">is_file</span><span class="p">():</span>
            <span class="k">continue</span>
        <span class="k">for</span> <span class="n">folder</span><span class="p">,</span> <span class="n">extensions</span> <span class="ow">in</span> <span class="n">CATEGORIES</span><span class="p">.</span><span class="nf">items</span><span class="p">():</span>
            <span class="k">if</span> <span class="nb">file</span><span class="p">.</span><span class="n">suffix</span><span class="p">.</span><span class="nf">lower</span><span class="p">()</span> <span class="ow">in</span> <span class="n">extensions</span><span class="p">:</span>
                <span class="n">target</span> <span class="o">=</span> <span class="n">DOWNLOADS</span> <span class="o">/</span> <span class="n">folder</span> <span class="o">/</span> <span class="nb">file</span><span class="p">.</span><span class="n">name</span>
                <span class="n">target</span><span class="p">.</span><span class="n">parent</span><span class="p">.</span><span class="nf">mkdir</span><span class="p">(</span><span class="n">exist_ok</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
                <span class="nb">file</span><span class="p">.</span><span class="nf">rename</span><span class="p">(</span><span class="n">target</span><span class="p">)</span>
                <span class="n">moved</span> <span class="o">+=</span> <span class="mi">1</span>
                <span class="k">break</span>
    <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Organized </span><span class="si">{</span><span class="n">moved</span><span class="si">}</span><span class="s"> files. You</span><span class="sh">'</span><span class="s">re welcome.</span><span class="sh">"</span><span class="p">)</span>

<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="sh">"</span><span class="s">__main__</span><span class="sh">"</span><span class="p">:</span>
    <span class="nf">organize</span><span class="p">()</span>
</code></pre></div></div>

<p>Run it monthly. Thank me later.</p>

<h2 id="2-bulk-file-renamer">2. Bulk file renamer</h2>

<p>Downloaded 200 photos named <code class="language-plaintext highlighter-rouge">IMG_20260815_143822.jpg</code>? Rename them all with a pattern:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="n">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>

<span class="k">def</span> <span class="nf">bulk_rename</span><span class="p">(</span><span class="n">folder</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">pattern</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">dry_run</span><span class="p">:</span> <span class="nb">bool</span> <span class="o">=</span> <span class="bp">True</span><span class="p">):</span>
    <span class="sh">"""</span><span class="s">Rename files to pattern-001.ext, pattern-002.ext, ...</span><span class="sh">"""</span>
    <span class="n">files</span> <span class="o">=</span> <span class="nf">sorted</span><span class="p">(</span><span class="nc">Path</span><span class="p">(</span><span class="n">folder</span><span class="p">).</span><span class="nf">glob</span><span class="p">(</span><span class="sh">"</span><span class="s">*</span><span class="sh">"</span><span class="p">))</span>
    <span class="n">files</span> <span class="o">=</span> <span class="p">[</span><span class="n">f</span> <span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">files</span> <span class="k">if</span> <span class="n">f</span><span class="p">.</span><span class="nf">is_file</span><span class="p">()]</span>

    <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="nb">file</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="n">files</span><span class="p">,</span> <span class="n">start</span><span class="o">=</span><span class="mi">1</span><span class="p">):</span>
        <span class="n">new_name</span> <span class="o">=</span> <span class="sa">f</span><span class="sh">"</span><span class="si">{</span><span class="n">pattern</span><span class="si">}</span><span class="s">-</span><span class="si">{</span><span class="n">i</span><span class="si">:</span><span class="mi">03</span><span class="n">d</span><span class="si">}{</span><span class="nb">file</span><span class="p">.</span><span class="n">suffix</span><span class="si">}</span><span class="sh">"</span>
        <span class="k">if</span> <span class="n">dry_run</span><span class="p">:</span>
            <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="si">{</span><span class="nb">file</span><span class="p">.</span><span class="n">name</span><span class="si">}</span><span class="s">  →  </span><span class="si">{</span><span class="n">new_name</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="nb">file</span><span class="p">.</span><span class="nf">rename</span><span class="p">(</span><span class="nb">file</span><span class="p">.</span><span class="n">parent</span> <span class="o">/</span> <span class="n">new_name</span><span class="p">)</span>

<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="sh">"</span><span class="s">__main__</span><span class="sh">"</span><span class="p">:</span>
    <span class="c1"># Always dry-run first!
</span>    <span class="nf">bulk_rename</span><span class="p">(</span><span class="sh">"</span><span class="s">./photos</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">vacation</span><span class="sh">"</span><span class="p">,</span> <span class="n">dry_run</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
</code></pre></div></div>

<blockquote class="callout">
  <p>Always do a dry run before bulk-renaming anything. There is no undo button for <code class="language-plaintext highlighter-rouge">rename()</code>. The <code class="language-plaintext highlighter-rouge">dry_run</code> flag exists because I learned this the hard way.</p>
</blockquote>

<h2 id="3-log-file-detective">3. Log file detective</h2>

<p>Need to find every error in a 500MB log file? Don’t open it in an editor — scan it:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">re</span>
<span class="kn">from</span> <span class="n">collections</span> <span class="kn">import</span> <span class="n">Counter</span>
<span class="kn">from</span> <span class="n">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>

<span class="n">ERROR_PATTERN</span> <span class="o">=</span> <span class="n">re</span><span class="p">.</span><span class="nf">compile</span><span class="p">(</span><span class="sa">r</span><span class="sh">"</span><span class="s">ERROR|CRITICAL|Traceback</span><span class="sh">"</span><span class="p">,</span> <span class="n">re</span><span class="p">.</span><span class="n">IGNORECASE</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">scan_logs</span><span class="p">(</span><span class="n">path</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">context_lines</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">2</span><span class="p">):</span>
    <span class="n">lines</span> <span class="o">=</span> <span class="nc">Path</span><span class="p">(</span><span class="n">path</span><span class="p">).</span><span class="nf">read_text</span><span class="p">(</span><span class="n">errors</span><span class="o">=</span><span class="sh">"</span><span class="s">replace</span><span class="sh">"</span><span class="p">).</span><span class="nf">splitlines</span><span class="p">()</span>
    <span class="n">error_lines</span> <span class="o">=</span> <span class="p">[</span><span class="n">i</span> <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">line</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="n">lines</span><span class="p">)</span> <span class="k">if</span> <span class="n">ERROR_PATTERN</span><span class="p">.</span><span class="nf">search</span><span class="p">(</span><span class="n">line</span><span class="p">)]</span>

    <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Found </span><span class="si">{</span><span class="nf">len</span><span class="p">(</span><span class="n">error_lines</span><span class="p">)</span><span class="si">}</span><span class="s"> error lines in </span><span class="si">{</span><span class="nf">len</span><span class="p">(</span><span class="n">lines</span><span class="p">)</span><span class="si">:</span><span class="p">,</span><span class="si">}</span><span class="s"> total lines</span><span class="se">\n</span><span class="sh">"</span><span class="p">)</span>

    <span class="c1"># Show the most common error messages
</span>    <span class="n">messages</span> <span class="o">=</span> <span class="nc">Counter</span><span class="p">(</span>
        <span class="n">lines</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="nf">strip</span><span class="p">()[:</span><span class="mi">100</span><span class="p">]</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">error_lines</span> <span class="k">if</span> <span class="sh">"</span><span class="s">Traceback</span><span class="sh">"</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">lines</span><span class="p">[</span><span class="n">i</span><span class="p">]</span>
    <span class="p">)</span>
    <span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">Top errors:</span><span class="sh">"</span><span class="p">)</span>
    <span class="k">for</span> <span class="n">message</span><span class="p">,</span> <span class="n">count</span> <span class="ow">in</span> <span class="n">messages</span><span class="p">.</span><span class="nf">most_common</span><span class="p">(</span><span class="mi">5</span><span class="p">):</span>
        <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">  </span><span class="si">{</span><span class="n">count</span><span class="si">:</span><span class="mi">4</span><span class="n">d</span><span class="si">}</span><span class="s">x  </span><span class="si">{</span><span class="n">message</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>

<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="sh">"</span><span class="s">__main__</span><span class="sh">"</span><span class="p">:</span>
    <span class="nf">scan_logs</span><span class="p">(</span><span class="sh">"</span><span class="s">app.log</span><span class="sh">"</span><span class="p">)</span>
</code></pre></div></div>

<p>This turns “the app is broken, good luck” into “here are the 3 errors causing 95% of the failures” in about a second.</p>

<h2 id="4-website-uptime-checker">4. Website uptime checker</h2>

<p>A tiny monitor that pings your sites and tells you which ones are down:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">urllib.request</span>
<span class="kn">from</span> <span class="n">datetime</span> <span class="kn">import</span> <span class="n">datetime</span>

<span class="n">SITES</span> <span class="o">=</span> <span class="p">[</span>
    <span class="sh">"</span><span class="s">https://mrprecioustech.github.io</span><span class="sh">"</span><span class="p">,</span>
    <span class="sh">"</span><span class="s">https://github.com</span><span class="sh">"</span><span class="p">,</span>
<span class="p">]</span>

<span class="k">def</span> <span class="nf">check_sites</span><span class="p">():</span>
    <span class="k">for</span> <span class="n">site</span> <span class="ow">in</span> <span class="n">SITES</span><span class="p">:</span>
        <span class="k">try</span><span class="p">:</span>
            <span class="k">with</span> <span class="n">urllib</span><span class="p">.</span><span class="n">request</span><span class="p">.</span><span class="nf">urlopen</span><span class="p">(</span><span class="n">site</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span> <span class="k">as</span> <span class="n">response</span><span class="p">:</span>
                <span class="n">status</span> <span class="o">=</span> <span class="n">response</span><span class="p">.</span><span class="n">status</span>
                <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">✅ </span><span class="si">{</span><span class="n">site</span><span class="si">}</span><span class="s"> — HTTP </span><span class="si">{</span><span class="n">status</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
        <span class="k">except</span> <span class="nb">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
            <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">❌ </span><span class="si">{</span><span class="n">site</span><span class="si">}</span><span class="s"> — DOWN (</span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s">)</span><span class="sh">"</span><span class="p">)</span>

    <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="se">\n</span><span class="s">Checked at </span><span class="si">{</span><span class="n">datetime</span><span class="p">.</span><span class="nf">now</span><span class="p">()</span><span class="si">:</span><span class="o">%</span><span class="n">Y</span><span class="o">-%</span><span class="n">m</span><span class="o">-%</span><span class="n">d</span> <span class="o">%</span><span class="n">H</span><span class="si">:</span><span class="o">%</span><span class="n">M</span><span class="si">:</span><span class="o">%</span><span class="n">S</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>

<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="sh">"</span><span class="s">__main__</span><span class="sh">"</span><span class="p">:</span>
    <span class="nf">check_sites</span><span class="p">()</span>
</code></pre></div></div>

<p>Pair it with cron (<code class="language-plaintext highlighter-rouge">*/15 * * * *</code>) and you’ve got a free uptime monitor. No SaaS subscription required.</p>

<h2 id="5-markdown-blog-post-scaffolder">5. Markdown blog post scaffolder</h2>

<p>Since I write this blog in Markdown, I made a script that creates a new Jekyll post with the front matter pre-filled:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">re</span>
<span class="kn">from</span> <span class="n">datetime</span> <span class="kn">import</span> <span class="n">date</span>
<span class="kn">from</span> <span class="n">pathlib</span> <span class="kn">import</span> <span class="n">Path</span>

<span class="n">TEMPLATE</span> <span class="o">=</span> <span class="sh">"""</span><span class="s">---
layout: post
title: </span><span class="sh">"</span><span class="s">{title}</span><span class="sh">"</span><span class="s">
date: {today} 09:00:00 +0000
description: </span><span class="sh">""</span><span class="s">
tags: []
accent: indigo
---

Write something great.
</span><span class="sh">"""</span>

<span class="k">def</span> <span class="nf">new_post</span><span class="p">(</span><span class="n">title</span><span class="p">:</span> <span class="nb">str</span><span class="p">):</span>
    <span class="n">slug</span> <span class="o">=</span> <span class="n">re</span><span class="p">.</span><span class="nf">sub</span><span class="p">(</span><span class="sa">r</span><span class="sh">"</span><span class="s">[^a-z0-9]+</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">-</span><span class="sh">"</span><span class="p">,</span> <span class="n">title</span><span class="p">.</span><span class="nf">lower</span><span class="p">()).</span><span class="nf">strip</span><span class="p">(</span><span class="sh">"</span><span class="s">-</span><span class="sh">"</span><span class="p">)</span>
    <span class="n">today</span> <span class="o">=</span> <span class="n">date</span><span class="p">.</span><span class="nf">today</span><span class="p">().</span><span class="nf">isoformat</span><span class="p">()</span>
    <span class="n">filename</span> <span class="o">=</span> <span class="sa">f</span><span class="sh">"</span><span class="s">_posts/</span><span class="si">{</span><span class="n">today</span><span class="si">}</span><span class="s">-</span><span class="si">{</span><span class="n">slug</span><span class="si">}</span><span class="s">.md</span><span class="sh">"</span>

    <span class="nc">Path</span><span class="p">(</span><span class="n">filename</span><span class="p">).</span><span class="nf">write_text</span><span class="p">(</span><span class="n">TEMPLATE</span><span class="p">.</span><span class="nf">format</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="n">title</span><span class="p">,</span> <span class="n">today</span><span class="o">=</span><span class="n">today</span><span class="p">))</span>
    <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Created </span><span class="si">{</span><span class="n">filename</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>

<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="sh">"</span><span class="s">__main__</span><span class="sh">"</span><span class="p">:</span>
    <span class="kn">import</span> <span class="n">sys</span>
    <span class="nf">new_post</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="k">if</span> <span class="nf">len</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">1</span> <span class="k">else</span> <span class="sh">"</span><span class="s">Untitled Post</span><span class="sh">"</span><span class="p">)</span>
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>python new_post.py <span class="s2">"My Brilliant Idea"</span>
<span class="c"># Created _posts/2026-08-15-my-brilliant-idea.md</span>
</code></pre></div></div>

<h2 id="the-automation-mindset">The automation mindset</h2>

<p>The scripts aren’t the point — the habit is. Every time you catch yourself thinking “ugh, this again,” that’s automation knocking. Start small:</p>

<ol>
  <li><strong>Notice</strong> the repetition (the “ugh, this again” moment).</li>
  <li><strong>Do it manually once more</strong>, but pay attention to the exact steps.</li>
  <li><strong>Script the steps</strong> — ugly first drafts are fine.</li>
  <li><strong>Save it somewhere</strong> you’ll find it (a <code class="language-plaintext highlighter-rouge">scripts/</code> folder, a gist, this blog).</li>
</ol>

<p>The best automation isn’t the cleverest one. It’s the one you actually use.</p>]]></content><author><name>Mr Precious</name></author><category term="python" /><category term="automation" /><summary type="html"><![CDATA[If you've done it twice, script it. Five small, practical Python automations — file cleanup, bulk renaming, log scanning and more — with complete code.]]></summary></entry><entry><title type="html">Dark Mode Done Right with CSS Custom Properties</title><link href="https://mrprecioustech.github.io/2026/07/30/css-dark-mode/" rel="alternate" type="text/html" title="Dark Mode Done Right with CSS Custom Properties" /><published>2026-07-30T09:00:00+00:00</published><updated>2026-07-30T09:00:00+00:00</updated><id>https://mrprecioustech.github.io/2026/07/30/css-dark-mode</id><content type="html" xml:base="https://mrprecioustech.github.io/2026/07/30/css-dark-mode/"><![CDATA[<p>Every site needs dark mode now. The good news: with CSS custom properties, it’s genuinely easy — no CSS-in-JS, no duplicate stylesheets, no build step. This exact technique powers the theme toggle on this blog. Here’s the full recipe.</p>

<h2 id="the-core-idea">The core idea</h2>

<p>Define every color <strong>once</strong> as a CSS variable, then redefine the variables under a <code class="language-plaintext highlighter-rouge">[data-theme="dark"]</code> selector. Everything else in your CSS references variables, never raw colors:</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">:root</span> <span class="p">{</span>
  <span class="py">--bg</span><span class="p">:</span> <span class="nx">#ffffff</span><span class="p">;</span>
  <span class="py">--text</span><span class="p">:</span> <span class="nx">#101828</span><span class="p">;</span>
  <span class="py">--muted</span><span class="p">:</span> <span class="nx">#5b6474</span><span class="p">;</span>
  <span class="py">--border</span><span class="p">:</span> <span class="nx">#e4e7ee</span><span class="p">;</span>
  <span class="py">--accent</span><span class="p">:</span> <span class="nx">#4f46e5</span><span class="p">;</span>
<span class="p">}</span>

<span class="o">[</span><span class="nt">data-theme</span><span class="o">=</span><span class="s1">"dark"</span><span class="o">]</span> <span class="p">{</span>
  <span class="py">--bg</span><span class="p">:</span> <span class="nx">#0b0f19</span><span class="p">;</span>
  <span class="py">--text</span><span class="p">:</span> <span class="nx">#e7ebf3</span><span class="p">;</span>
  <span class="py">--muted</span><span class="p">:</span> <span class="nx">#9aa4b8</span><span class="p">;</span>
  <span class="py">--border</span><span class="p">:</span> <span class="nx">#202941</span><span class="p">;</span>
  <span class="py">--accent</span><span class="p">:</span> <span class="nx">#818cf8</span><span class="p">;</span>
<span class="p">}</span>

<span class="nt">body</span> <span class="p">{</span>
  <span class="nl">background</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="l">--bg</span><span class="p">);</span>
  <span class="nl">color</span><span class="p">:</span> <span class="nf">var</span><span class="p">(</span><span class="l">--text</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Toggle the <code class="language-plaintext highlighter-rouge">data-theme</code> attribute on <code class="language-plaintext highlighter-rouge">&lt;html&gt;</code> and the entire site re-themes instantly. That’s 90% of the work.</p>

<h2 id="dont-hardcode-colors--anywhere">Don’t hardcode colors — anywhere</h2>

<p>The one discipline this requires: <strong>every color in your CSS must be a variable.</strong> The moment you write <code class="language-plaintext highlighter-rouge">color: #333</code> in some component, you’ve created a spot that won’t theme. A few tips:</p>

<ul>
  <li>Do an audit pass: search your CSS for <code class="language-plaintext highlighter-rouge">#</code> and <code class="language-plaintext highlighter-rouge">rgb(</code> outside the token definitions.</li>
  <li>Give semantic names (<code class="language-plaintext highlighter-rouge">--text</code>, <code class="language-plaintext highlighter-rouge">--muted</code>, <code class="language-plaintext highlighter-rouge">--border</code>) not color names (<code class="language-plaintext highlighter-rouge">--gray-500</code>).</li>
  <li>Remember the sneaky ones: <code class="language-plaintext highlighter-rouge">box-shadow</code>, <code class="language-plaintext highlighter-rouge">border-color</code>, SVG <code class="language-plaintext highlighter-rouge">fill</code>/<code class="language-plaintext highlighter-rouge">stroke</code>, <code class="language-plaintext highlighter-rouge">::selection</code>, and form controls.</li>
</ul>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">/* Bad: invisible in dark mode */</span>
<span class="nc">.card</span> <span class="p">{</span> <span class="nl">border</span><span class="p">:</span> <span class="m">1px</span> <span class="nb">solid</span> <span class="nx">#e4e7ee</span><span class="p">;</span> <span class="p">}</span>

<span class="c">/* Good: themes automatically */</span>
<span class="nc">.card</span> <span class="p">{</span> <span class="nl">border</span><span class="p">:</span> <span class="m">1px</span> <span class="nb">solid</span> <span class="nf">var</span><span class="p">(</span><span class="l">--border</span><span class="p">);</span> <span class="p">}</span>
</code></pre></div></div>

<h2 id="respect-the-system-preference">Respect the system preference</h2>

<p>Don’t default everyone to light mode. CSS can detect the OS setting:</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">/* Optional: default to dark for dark-mode OS users, before JS runs */</span>
<span class="k">@media</span> <span class="p">(</span><span class="n">prefers-color-scheme</span><span class="p">:</span> <span class="n">dark</span><span class="p">)</span> <span class="p">{</span>
  <span class="nd">:root:not</span><span class="o">([</span><span class="nt">data-theme</span><span class="o">])</span> <span class="p">{</span>
    <span class="py">--bg</span><span class="p">:</span> <span class="nx">#0b0f19</span><span class="p">;</span>
    <span class="py">--text</span><span class="p">:</span> <span class="nx">#e7ebf3</span><span class="p">;</span>
    <span class="c">/* ... same dark tokens ... */</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>But duplicating tokens is ugly. Better: handle it in JavaScript (next section) and use the media query only as a signal, not a styling mechanism.</p>

<h2 id="kill-the-flash-fouc">Kill the flash (FOUC)</h2>

<p>Here’s the classic dark-mode bug: the page paints light, then JavaScript loads and flips it to dark. That flash is jarring. The fix is a tiny <strong>blocking script in <code class="language-plaintext highlighter-rouge">&lt;head&gt;</code></strong> that sets the theme before first paint:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;script&gt;</span>
  <span class="p">(</span><span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
    <span class="k">try</span> <span class="p">{</span>
      <span class="kd">var</span> <span class="nx">saved</span> <span class="o">=</span> <span class="nx">localStorage</span><span class="p">.</span><span class="nf">getItem</span><span class="p">(</span><span class="dl">"</span><span class="s2">my-theme</span><span class="dl">"</span><span class="p">);</span>
      <span class="kd">var</span> <span class="nx">theme</span> <span class="o">=</span> <span class="nx">saved</span> <span class="o">||</span> <span class="p">(</span><span class="nb">window</span><span class="p">.</span><span class="nf">matchMedia</span><span class="p">(</span>
        <span class="dl">"</span><span class="s2">(prefers-color-scheme: dark)</span><span class="dl">"</span><span class="p">).</span><span class="nx">matches</span> <span class="p">?</span> <span class="dl">"</span><span class="s2">dark</span><span class="dl">"</span> <span class="p">:</span> <span class="dl">"</span><span class="s2">light</span><span class="dl">"</span><span class="p">);</span>
      <span class="nb">document</span><span class="p">.</span><span class="nx">documentElement</span><span class="p">.</span><span class="nf">setAttribute</span><span class="p">(</span><span class="dl">"</span><span class="s2">data-theme</span><span class="dl">"</span><span class="p">,</span> <span class="nx">theme</span><span class="p">);</span>
    <span class="p">}</span> <span class="k">catch </span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span>
      <span class="nb">document</span><span class="p">.</span><span class="nx">documentElement</span><span class="p">.</span><span class="nf">setAttribute</span><span class="p">(</span><span class="dl">"</span><span class="s2">data-theme</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">light</span><span class="dl">"</span><span class="p">);</span>
    <span class="p">}</span>
  <span class="p">})();</span>
<span class="nt">&lt;/script&gt;</span>
</code></pre></div></div>

<p>Yes, it’s render-blocking — that’s the entire point. It’s 10 lines; the cost is negligible and the flash is gone.</p>

<h2 id="the-toggle-button">The toggle button</h2>

<p>With the loader script in place, the toggle itself is trivial:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">KEY</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">my-theme</span><span class="dl">"</span><span class="p">;</span>

<span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">theme-toggle</span><span class="dl">"</span><span class="p">).</span><span class="nf">addEventListener</span><span class="p">(</span><span class="dl">"</span><span class="s2">click</span><span class="dl">"</span><span class="p">,</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">var</span> <span class="nx">current</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">documentElement</span><span class="p">.</span><span class="nf">getAttribute</span><span class="p">(</span><span class="dl">"</span><span class="s2">data-theme</span><span class="dl">"</span><span class="p">);</span>
  <span class="kd">var</span> <span class="nx">next</span> <span class="o">=</span> <span class="nx">current</span> <span class="o">===</span> <span class="dl">"</span><span class="s2">dark</span><span class="dl">"</span> <span class="p">?</span> <span class="dl">"</span><span class="s2">light</span><span class="dl">"</span> <span class="p">:</span> <span class="dl">"</span><span class="s2">dark</span><span class="dl">"</span><span class="p">;</span>
  <span class="nb">document</span><span class="p">.</span><span class="nx">documentElement</span><span class="p">.</span><span class="nf">setAttribute</span><span class="p">(</span><span class="dl">"</span><span class="s2">data-theme</span><span class="dl">"</span><span class="p">,</span> <span class="nx">next</span><span class="p">);</span>
  <span class="k">try</span> <span class="p">{</span>
    <span class="nx">localStorage</span><span class="p">.</span><span class="nf">setItem</span><span class="p">(</span><span class="nx">KEY</span><span class="p">,</span> <span class="nx">next</span><span class="p">);</span>
  <span class="p">}</span> <span class="k">catch </span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* private browsing mode */</span> <span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>

<p>Priority order, which the loader script implements:</p>

<ol>
  <li><strong>Saved preference</strong> in <code class="language-plaintext highlighter-rouge">localStorage</code> (explicit user choice wins).</li>
  <li><strong>OS preference</strong> via <code class="language-plaintext highlighter-rouge">prefers-color-scheme</code>.</li>
  <li><strong>Light</strong> as the fallback.</li>
</ol>

<h2 id="show-the-right-icon">Show the right icon</h2>

<p>Your toggle should show a sun in dark mode and a moon in light mode (i.e., show what you’ll switch <em>to</em>). Pure CSS, no JS needed:</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">.theme-toggle</span> <span class="nc">.icon-moon</span> <span class="p">{</span> <span class="nl">display</span><span class="p">:</span> <span class="nb">none</span><span class="p">;</span> <span class="p">}</span>

<span class="o">[</span><span class="nt">data-theme</span><span class="o">=</span><span class="s1">"dark"</span><span class="o">]</span> <span class="nc">.theme-toggle</span> <span class="nc">.icon-sun</span> <span class="p">{</span> <span class="nl">display</span><span class="p">:</span> <span class="nb">none</span><span class="p">;</span> <span class="p">}</span>
<span class="o">[</span><span class="nt">data-theme</span><span class="o">=</span><span class="s1">"dark"</span><span class="o">]</span> <span class="nc">.theme-toggle</span> <span class="nc">.icon-moon</span> <span class="p">{</span> <span class="nl">display</span><span class="p">:</span> <span class="nb">block</span><span class="p">;</span> <span class="p">}</span>
</code></pre></div></div>

<p>Both SVGs live in the button; CSS shows the right one.</p>

<h2 id="polish-smooth-transitions">Polish: smooth transitions</h2>

<p>An instant flip can feel abrupt. A short transition on the main surfaces smooths it out:</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">body</span> <span class="p">{</span>
  <span class="nl">transition</span><span class="p">:</span> <span class="n">background-color</span> <span class="m">0.25s</span> <span class="nb">ease</span><span class="p">,</span> <span class="nb">color</span> <span class="m">0.25s</span> <span class="nb">ease</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Keep it subtle (under 300ms) and only on large surfaces — transitioning every element’s colors can cause lag on big pages. And respect users who prefer reduced motion:</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">@media</span> <span class="p">(</span><span class="n">prefers-reduced-motion</span><span class="p">:</span> <span class="n">reduce</span><span class="p">)</span> <span class="p">{</span>
  <span class="nt">body</span> <span class="p">{</span> <span class="nl">transition</span><span class="p">:</span> <span class="nb">none</span><span class="p">;</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="checklist">Checklist</h2>

<p>Before you ship dark mode, verify:</p>

<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Every color is a variable (search for <code class="language-plaintext highlighter-rouge">#</code> and <code class="language-plaintext highlighter-rouge">rgb(</code>)</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Blocking loader script in <code class="language-plaintext highlighter-rouge">&lt;head&gt;</code> — no flash on reload</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Saved preference beats OS preference beats default</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Toggle icon reflects the <em>target</em> theme</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><code class="language-plaintext highlighter-rouge">theme-color</code> meta tags for the mobile browser chrome</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Code blocks, tables, images and shadows all checked in both themes</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Reduced-motion users don’t get the transition</li>
</ul>

<p>Dark mode done right is invisible: users just feel like the site respects them. Now go theme something.</p>]]></content><author><name>Mr Precious</name></author><category term="css" /><category term="webdev" /><category term="tutorial" /><summary type="html"><![CDATA[A complete recipe for theming: CSS variables, a no-flash loader script, system-preference detection and a persistent toggle — in under 60 lines.]]></summary></entry><entry><title type="html">My Developer Toolbox in 2026</title><link href="https://mrprecioustech.github.io/2026/07/12/dev-toolbox-2026/" rel="alternate" type="text/html" title="My Developer Toolbox in 2026" /><published>2026-07-12T09:00:00+00:00</published><updated>2026-07-12T09:00:00+00:00</updated><id>https://mrprecioustech.github.io/2026/07/12/dev-toolbox-2026</id><content type="html" xml:base="https://mrprecioustech.github.io/2026/07/12/dev-toolbox-2026/"><![CDATA[<p>Developers love talking about their setups, and I’m no exception. Here’s an honest tour of the tools I use daily in 2026 — not an affiliate-link shopping list, just what survived contact with real work.</p>

<h2 id="the-daily-drivers">The daily drivers</h2>

<h3 id="editor-vs-code-with-neovim-keybindings">Editor: VS Code (with Neovim keybindings)</h3>

<p>I’ve done the pure-Neovim era, the Emacs curiosity phase, and the “maybe I should try Zed” weekend. I keep coming back to VS Code with the Vim extension: 90% of Vim’s speed with 0% of the config maintenance.</p>

<p>Non-negotiable extensions: Vim emulation, ESLint, Prettier, GitLens, and an error-lens style inline diagnostics plugin. Everything else is negotiable.</p>

<h3 id="terminal-whatevers-fast--tmux">Terminal: whatever’s fast + tmux</h3>

<p>The terminal emulator matters less than people think — I use the platform default with a Nerd Font. What matters is <strong>tmux</strong>: persistent sessions, split panes, and the ability to detach from a long-running job and walk away.</p>

<p>My entire tmux config is maybe 15 lines. The best tool config is the one you can recreate from memory.</p>

<h3 id="shell-zsh--a-short-prompt">Shell: zsh + a short prompt</h3>

<p>Oh My Zsh was slowing my shell startup to nearly a second, so I stripped it back to the essentials: syntax highlighting, autosuggestions, and <code class="language-plaintext highlighter-rouge">git</code> aliases I actually type. Prompt shows: directory, git branch, dirty state. Nothing else. Fast shells make you feel fast.</p>

<h2 id="cli-apps-worth-installing">CLI apps worth installing</h2>

<p>These small tools earn their place every single day:</p>

<table>
  <thead>
    <tr>
      <th>Tool</th>
      <th>Replaces</th>
      <th>Why it’s better</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ripgrep</code> (<code class="language-plaintext highlighter-rouge">rg</code>)</td>
      <td><code class="language-plaintext highlighter-rouge">grep</code></td>
      <td>Respects <code class="language-plaintext highlighter-rouge">.gitignore</code>, stupidly fast</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">fd</code></td>
      <td><code class="language-plaintext highlighter-rouge">find</code></td>
      <td>Sane syntax, colorized output</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">bat</code></td>
      <td><code class="language-plaintext highlighter-rouge">cat</code></td>
      <td>Syntax highlighting + line numbers + paging</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">eza</code></td>
      <td><code class="language-plaintext highlighter-rouge">ls</code></td>
      <td>Git status baked into listings</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">fzf</code></td>
      <td><code class="language-plaintext highlighter-rouge">Ctrl+R</code></td>
      <td>Fuzzy-find anything: files, history, processes</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">zoxide</code></td>
      <td><code class="language-plaintext highlighter-rouge">cd</code></td>
      <td>Learns your directories, <code class="language-plaintext highlighter-rouge">z blog</code> just works</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">jq</code></td>
      <td>eyeballs</td>
      <td>Query JSON like it’s a database</td>
    </tr>
  </tbody>
</table>

<p>If you install just one: make it <code class="language-plaintext highlighter-rouge">fzf</code>. Pipe anything into it and your terminal becomes searchable.</p>

<h2 id="the-supporting-cast">The supporting cast</h2>

<ul>
  <li><strong>Git + GitHub</strong> — obvious, but the skill isn’t knowing git, it’s writing good commit messages and small PRs.</li>
  <li><strong>Docker</strong> — for “works on my machine” insurance. If a project has more than two dependencies, it gets a container.</li>
  <li><strong>Python</strong> — my automation duct tape. Half the scripts on this blog started as “I’ll just quickly…” (see: <a href="/2026/08/15/python-automation/">5 Python scripts I actually use</a>).</li>
  <li><strong>Obsidian</strong> — notes in plain Markdown files I own. No lock-in, works offline, syncs over git.</li>
  <li><strong>Bitwarden</strong> — a password manager isn’t optional anymore. Unique 20-character passwords everywhere, one master password in my head.</li>
</ul>

<h2 id="what-i-deliberately-dont-use">What I deliberately don’t use</h2>

<ul>
  <li><strong>A second monitor’s worth of dashboards.</strong> If a metric doesn’t change what I do today, I don’t need to see it today.</li>
  <li><strong>AI autocomplete I can’t audit.</strong> I use AI assistants for boilerplate and explanations — but every line gets read before it gets committed. Trust, but verify, then verify again.</li>
  <li><strong>Eleven note-taking apps.</strong> One notes folder. One todo list. Boring and effective.</li>
</ul>

<h2 id="the-meta-lesson">The meta-lesson</h2>

<blockquote class="callout">
  <p>Tools don’t make you productive — <strong>removing friction</strong> makes you productive. Every tool on this list survived because it removed more friction than it added.</p>
</blockquote>

<p>My advice for building your own toolbox:</p>

<ol>
  <li><strong>Start boring.</strong> Defaults are fine until they hurt. Only optimize real pain.</li>
  <li><strong>Learn one tool deeply</strong> before collecting ten shallowly. A black belt in your editor beats white belts in five.</li>
  <li><strong>Revisit yearly.</strong> I do this audit every July. Tools (and needs) change — your setup should too.</li>
  <li><strong>Back up your dotfiles.</strong> Future-you, setting up a new machine at midnight before a deadline, says thanks.</li>
</ol>

<p>What’s in your toolbox? If there’s something life-changing I’m missing, I’d genuinely love to hear about it — find me on <a href="https://github.com/mrprecioustech">GitHub</a>.</p>]]></content><author><name>Mr Precious</name></author><category term="tools" /><category term="productivity" /><summary type="html"><![CDATA[The editors, terminals, CLI apps and services I reach for every day — what made the cut, what got replaced, and what I'd recommend to anyone.]]></summary></entry></feed>