<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet href="/styles.xsl" type="text/xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://axoga.to/</id>
    <title>Chai's Blog</title>
    <updated>2026-04-10T00:00:00.000Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <author>
        <name>Chai Latier</name>
        <uri>https://axoga.to/</uri>
    </author>
    <link rel="alternate" href="https://axoga.to/blog"/>
    <link rel="self" href="https://axoga.to/blog.xml"/>
    <subtitle>The musings and adventures of a silly axogato ᓚᘏᗢ</subtitle>
    <logo>https://axoga.to/icon.png</logo>
    <icon>https://axoga.to/favicon.ico</icon>
    <rights>© 2023 – 2025. Chai Latier. All rights reserved.</rights>
    <entry>
        <title type="html"><![CDATA[You Are Not Clever for Posting XKCD 927]]></title>
        <id>https://axoga.to/blog/you-are-not-clever-for-posting-xkcd-927</id>
        <link href="https://axoga.to/blog/you-are-not-clever-for-posting-xkcd-927"/>
        <updated>2026-04-10T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[That's the one about standards]]></summary>
        <content type="html"><![CDATA[<p>A thought-terminating cliché is any phrase that you repeat to yourself to excuse your brain from the <em>laborious</em> act of critical thinking. Instead of weighing the nuances of the context and having a productive discussion, you dismiss it with a catchy phrase.</p>
<p>"It is what it is."</p>
<p>"Everything happens for a reason."</p>
<p>"Agree to disagree."</p>
<p><img src="https://imgs.xkcd.com/comics/standards_2x.png" alt="" /></p>
<p>Oops that's not a catchy phrase, it's the <a href="https://xkcd.com/927/">XKCD comic about standards by Randall Munroe</a>!</p>
<p>And yet... ==people use it just like a thought-terminating cliché==. When they see someone develop a new piece of software that already exists, they may consider different arguments:</p>
<ul>
<li>"You should be contributing to this existing project instead."</li>
<li>"You are reinventing the wheel."</li>
<li>"This will fragment the ecosystem further."</li>
</ul>
<p>But all those thoughts terminate the moment they decide to post XKCD 927. They aren't being clever, they shut off their brain to conserve energy.</p>
<h2>Memes are reductive</h2>
<p>What makes this comic so pervasive in the software development community is because there is ==a nugget of truth== here. Sometimes it <em>would</em> be better to focus on and improve an existing piece of software instead of recreating the same thing from scratch.</p>
<p>But to reduce the complex realities of any situation into a reshareable meme is absurd!</p>
<p>Take for example A/C chargers, which XKCD 927 mentions as its first example and in its bonus caption. A few years after this comic, USB-C was released and it has <em>completely taken over</em>. And not just for phones and other small devices, but it is also powering laptops and monitors, and going beyond just charging and challenging HDMI and DisplayPort too! With the right setup, you can connect and power a monitor with <em>a single USB-C cable from your desktop</em>.</p>
<p>But a naive<sup><a href="#fn1">[1]</a></sup> reading of the comic would tell you that USB-C shouldn't have been invented because it would have likely failed to become a unifying standard; this notion is of course <em>ridiculous</em>.</p>
<h2>Is the developer even <em>trying</em> to unify it?</h2>
<p>Hey, did you stop to ask yourself if the developer you're sending XKCD 927 to is even <em>trying</em> to create a unified standard? Because chances are ==they aren't==.</p>
<p>Developers, famously, have many different reasons (Citation needed.) for why they make stuff. Existing software may not be meeting their needs, the project may have rejected their contribution for design reasons, they may want more control and tighter integration than is possible.</p>
<p>Developers may even want to create stuff... <em>for fun</em>.</p>
<p>*gasp* How dare they. How dare they make stuff for silly reasons like <em>fun and learning</em> instead of using existing software and contributing to it.</p>
<p>Stop that. Stop telling people what they should or shouldn't do.</p>
<p>==Programming is a craft, and craftmanship is an art.== No single piece of software can be the silver bullet for everyone, and sometimes better software requires new software with a fundamentally different design.</p>
<p>And even if the developer <em>is</em> trying to create The Next Big Thing<img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="™️" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/2122.png" />, it might just succeed like USB-C.</p>
<hr />
<section>
<ol>
<li><p>Specifically naive because that is how the comic is often used. <a href="#fnref1">↩︎</a></p>
</li>
</ol>
</section>
]]></content>
        <category label="articles" term="articles"/>
        <category label="xkcd" term="xkcd"/>
        <published>2026-04-10T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[You Should Give Helium A Try]]></title>
        <id>https://axoga.to/blog/you-should-give-helium-a-try</id>
        <link href="https://axoga.to/blog/you-should-give-helium-a-try"/>
        <updated>2026-02-23T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A browser that respects your time and privacy]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>Tldr
<a href="https://helium.computer/">Direct link</a> to the Helium browser if you want to check it out right now. Just promise you'll come back and read my post ;)</p>
</blockquote>
<p>Browsers have become enshittified like everything else. Even Mozilla's Firefox is not immune to it with their existence being propped up by Google money, and <a href="https://www.pcmag.com/news/mozillas-new-ceo-its-time-to-evolve-firefox-into-an-ai-browser">their CEO glazing AI</a>. Ugh.</p>
<p>There's not that many other options, as most browsers have abandoned their in-house browser engines for Chromium and its manifest V3 designed to kill off ad blockers. Maybe you'll be happy with one of <a href="https://www.waterfox.com/">the</a> <a href="https://librewolf.net/">several</a> <a href="https://floorp.app/">Firefox</a> <a href="https://zen-browser.app/">forks</a>, but given Firefox's second-class status, you're bound to find compatability issues when web developers pretend Chrome is the only browser to exist.</p>
<p>What about the Ladybird Browser, a "truly independent web browser" using "a new browser engine built from scratch"? This sounds promising, but you can automatically write this one off for being vibe-coded and the creator supporting white replacement theory.</p>
<p>Now, <a href="https://servo.org/">Servo</a> is a pretty cool project, but they've got a long way to go thanks to how complicated browser engines have become. Their focus is specifically on embedded web rendering engines, rather than making a browser, but making a browser using it shouldn't be too hard. Keep an eye out for this one.</p>
<hr />
<p>With all that required reading out of the way, I want to share with you the browser I've switched to and love, <a href="https://helium.computer/">Helium</a>! It's a Chromium fork from the creators of <a href="https://cobalt.tools/">cobalt.tools</a>, and it does a <em>lot</em> to protect your privacy. It gets rid of Manifest V3, installs uBlock Origin by default, has anti-fingerprinting, makes zero web requests without your consent, and even anonymizes all internal requests to the Chrome Web Store.</p>
<p>But more than just having excellent privacy by default, it includes handy features like vertical tabs, side-by-side pages with split view, and !bangs (Lifted straight from DuckDuckGo :P) to skip the search engine and go directly to the website you want. For example, type <code>!minecraft axolotl</code> into the search bar and you'll be taken directly to the Minecraft wiki page for axolotls!</p>
<p>And just as important as having features is, so is excluding anti-features like built-in password managers or cloud-based history/data sync. The former is dangerously insecure, and the latter is freely giving your browsing data to data harvesting companies. I did come up with an interesting solution to sync my bookmarks another way, so I might write a blog post about that.</p>
<blockquote>
<p>[!danger] Hey! Listen!
If you're still using your browser's password manager, now would be a good time to switch to something secure and cross-platform, like <a href="https://bitwarden.com/">Bitwarden</a>.</p>
</blockquote>
<p>Anyhoo, it's genuinely refreshing to use such a deshittified and lightweight browser, so I want to let others know about it too! I hope it brings you just as much joy as it did to me to finally find a browser I can happily stick with :3</p>
]]></content>
        <category label="articles" term="articles"/>
        <category label="browsers" term="browsers"/>
        <published>2026-02-23T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Web Tools]]></title>
        <id>https://axoga.to/blog/web-tools</id>
        <link href="https://axoga.to/blog/web-tools"/>
        <updated>2026-01-01T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Last October I added many web tools to this website, but it wasn't linked anywhere until today!
]]></summary>
        <content type="html"><![CDATA[<p>Last October I added many <a href="/tools">web tools</a> to this website, but it wasn't linked anywhere other than through my Bluesky. I wasn't sure where I was going to link it at first, but the culmination of multiple factors prompted me to update the navbar to only top-level links. My <a href="/ask">ask box</a> is one of them, alongside this blog and the aforementioned tools, and I've been working on a digital collection of emojis and other digital files that would live at the top-level too. (Coming soon?)</p>
<p>Since I needed a new place to link the blog archive and stats page, I updated the blog start page to introduce the post categories, and appended those two links. It feels a little clunky, so I may change this later.</p>
<p>I hope you enjoy the suite of web tools! I put a lot of polish into them to make them the best they can be ^w^</p>
]]></content>
        <category label="updates" term="updates"/>
        <category label="tools" term="tools"/>
        <published>2026-01-01T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Apps I Use (2025)]]></title>
        <id>https://axoga.to/blog/apps-i-use-2025</id>
        <link href="https://axoga.to/blog/apps-i-use-2025"/>
        <updated>2025-12-31T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Last year I posted the list of apps I use, and I've been excited to post this year's update to that!
]]></summary>
        <content type="html"><![CDATA[<p>Last year I posted the list of <a href="/blog/apps-i-use-2024">apps I use</a>, and I've been excited to post this year's update to that!</p>
<ul>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="📨" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f4e8.png" /> <strong>Mail Client</strong>
<ul>
<li><s>Thunderbird (PC)</s></li>
<li><s>K-9 Mail (Android)</s></li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🆕" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f195.png" /> Proton Mail</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="📮" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f4ee.png" /> <strong>Mail Server</strong>
<ul>
<li><s>Gmail</s></li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🆕" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f195.png" /> Proton Mail</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🆕" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f195.png" /> SimpleLogin (Email Aliases)</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="📝" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f4dd.png" /> <strong>Notes</strong>
<ul>
<li>Obsidian</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="✅" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/2705.png" /> <strong>To-Do</strong>
<ul>
<li><a href="https://github.com/ransome1/sleek">sleek</a> (PC)</li>
<li><a href="https://c306.net/apps/#todotxtandroidCard">Todo.txt</a> (Android)</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="📷" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f4f7.png" /> <strong>Photo Shooting</strong>
<ul>
<li>Samsung Camera</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🖼" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f5bc.png" /> <strong>Photo Management</strong>
<ul>
<li>Samsung Gallery</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="📆" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f4c6.png" /> <strong>Calendar</strong>
<ul>
<li><s>Google Calendar</s></li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🆕" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f195.png" /> Proton Calendar</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="📁" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f4c1.png" /> <strong>Cloud File Storage</strong>
<ul>
<li>OneDrive</li>
<li><a href="https://syncthing.net/">SyncThing</a></li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="📖" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f4d6.png" /> <strong>RSS</strong>
<ul>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🆕" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f195.png" /> <a href="https://miniflux.app/">Miniflux</a></li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="👤" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f464.png" /> <strong>Contacts</strong>
<ul>
<li>Samsung Contacts</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🌐" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f310.png" /> <strong>Browser</strong>
<ul>
<li><s>Brave</s></li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🆕" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f195.png" /> <a href="https://helium.computer/">Helium</a></li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="💬" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f4ac.png" /> <strong>Chat</strong>
<ul>
<li>Discord</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🆕" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f195.png" /> <a href="https://www.rootapp.com/">Root</a></li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🔖" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f516.png" /> <strong>Bookmarks</strong>
<ul>
<li><a href="https://raindrop.io/">Raindrop.io</a></li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="📑" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f4d1.png" /> <strong>Read It Later</strong>
<ul>
<li><a href="https://raindrop.io/">Raindrop.io</a></li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="📜" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f4dc.png" /> <strong>Word Processing</strong>
<ul>
<li>LibreOffice</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="📈" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f4c8.png" /> <strong>Spreadsheets</strong>
<ul>
<li>LibreOffice</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="📊" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f4ca.png" /> <strong>Presentations</strong>
<ul>
<li>LibreOffice</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🛒" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f6d2.png" /> <strong>Shopping Lists</strong>
<ul>
<li>anywhere I can type</li>
<li><s>Whisk (Food)</s></li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🍴" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f374.png" /> <strong>Meal Planning</strong>
<ul>
<li><s>Whisk</s></li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="💰" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f4b0.png" /> <strong>Budgeting and Personal Finance</strong>
<ul>
<li><s><a href="https://hledger.org/">hledger</a></s></li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="📰" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f4f0.png" /> <strong>News</strong>
<ul>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🆕" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f195.png" /> <a href="https://miniflux.app/">Miniflux</a></li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🎵" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f3b5.png" /> <strong>Music</strong>
<ul>
<li>Spotify</li>
<li>Plex</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🎤" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f3a4.png" /> <strong>Podcasts</strong>
<ul>
<li>N/A</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🔐" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f510.png" /> <strong>Password Management</strong>
<ul>
<li>Bitwarden</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="⌨" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/2328.png" /> <strong>Code Editor</strong>
<ul>
<li>Visual Studio Code</li>
<li><s>Visual Studio (C#, Unity)</s></li>
<li>IntelliJ IDEA (Java)</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🖥" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f5a5.png" /> <strong>Terminal</strong>
<ul>
<li><s>Konsole</s></li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🆕" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f195.png" /> Ghostty</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🔀" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f500.png" /> <strong>Git Client</strong>
<ul>
<li><s>GitHub Desktop</s></li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🆕" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f195.png" /> <a href="https://gitfourchette.org/">GitFourchette</a></li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🗃" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f5c3.png" /> <strong>Git Server</strong>
<ul>
<li><s>GitHub</s></li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🆕" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f195.png" /> Forgejo</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🎨" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f3a8.png" /> <strong>Art</strong>
<ul>
<li>Clip Studio Paint</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🆕" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f195.png" /> Krita</li>
<li><a href="https://allusion-app.github.io/">Allusion</a> (Visual Library)</li>
<li><a href="https://www.pureref.com/">PureRef</a> (Reference Tool)</li>
</ul>
</li>
</ul>
]]></content>
        <category label="articles" term="articles"/>
        <category label="apps-i-use" term="apps-i-use"/>
        <published>2025-12-31T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[A Month of Daily Blogging]]></title>
        <id>https://axoga.to/blog/a-month-of-daily-blogging</id>
        <link href="https://axoga.to/blog/a-month-of-daily-blogging"/>
        <updated>2025-09-19T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[It's been a lot of fun writing and learning about all sorts of topics, and it's warmed my heart to see my writing be enjoyed and valued by friends and mutuals. But as I'm working for my dad again, it's becoming unsustainable to keep up the daily tempo.
]]></summary>
        <content type="html"><![CDATA[<p>I have been blogging every day for a whole month. It's been a lot of fun writing and learning about all sorts of topics, and it's warmed my heart to see my writing be enjoyed and valued by friends and mutuals. I've also found it useful for myself to answer a friend's question by linking to blog posts I've already written on the subject.</p>
<p>I still have plenty more I'd like to write about, but my life schedule has gotten busier with me working for my dad again, and I'm worried that the quality of my blog posts have dipped (or would soon) trying to keep up the daily tempo. Even if it didn't, it still leaves little time for me to explore bigger projects that take longer than one day to research and develop.</p>
<p>One of those projects is a <a href="/blog/developing-a-chat-app">chat app</a> spurred by the <a href="/blog/i-hate-discord">enshitification of Discord</a>. Early on I realized that it's going to take a while to make something decent, and that I'm not going to be able to write meaningful devlogs on a daily basis. I would need to lock-in on developing the app for a week, then compile my progress into a devlog.</p>
<p>That being said, I'm going to slow down on my blogging, but I'll still try to write at least once a week!</p>
]]></content>
        <category label="personal" term="personal"/>
        <category label="meta" term="meta"/>
        <published>2025-09-19T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Features I Want to See in Firefox]]></title>
        <id>https://axoga.to/blog/features-i-want-to-see-in-firefox</id>
        <link href="https://axoga.to/blog/features-i-want-to-see-in-firefox"/>
        <updated>2025-09-18T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Take back the web!]]></summary>
        <content type="html"><![CDATA[<p>Several weeks ago I switched off Brave to Firefox on both PC and mobile, as I believed it was a more conscientious choice. There are some thought strings here and there about better online privacy, distancing myself from AI slop features and crypto shit, and flipping the bird to Chrome, but with Mozilla being funded directly by Google it sometimes feels like I'm just picking the lesser of two evils.<sup><a href="#fn1">[1]</a></sup></p>
<p>Switching to Firefox, there are some nice new features and little ways it does things differently from Brave that I like, but there are some features I miss, like tab groups on mobile. Mozilla has a page for <a href="https://connect.mozilla.org/t5/ideas/idb-p/ideas">submitting ideas</a> and you can give kudos to the ones you like. I wanted to share the proposals I like, so if you like them too, you should let your voice be known!</p>
<h2><a href="https://connect.mozilla.org/t5/ideas/mobile-tab-grouping/idi-p/86">Mobile Tab Groups</a></h2>
<p>The biggest feature I miss from Brave mobile as I had just foreshadowed, is the ability to group tabs together into groups on Firefox mobile. Tab groups is a relatively recent feature on Firefox desktop, so it's only natural that mobile gets the same treatment soon!</p>
<p>It's a powerful organizational tool, and it makes it easier to focus on only a small section of tabs at a time. I used mine to keep web comics I was reading in one tab group, and I silo'd multi-tab research into their own tab groups. Occasionally I would have a tab group just for reading articles for later.</p>
<h2><a href="https://connect.mozilla.org/t5/ideas/support-jpeg-xl/idi-p/18433">JPEG XL Support</a></h2>
<p>I've talked about <a href="/blog/jpeg-xl-is-worth-it">how awesome JPEG XL is</a> before, as well as my scathing critique of Mozilla for snubbing the feature. It is currently the top 6th most voted idea, top 3rd if you exclude delivered ideas, so I'm curious how long are they going to ignore this popular idea. Please be sure to vote on it as it has big ramifications on the future of the open web!</p>
<h2><a href="https://connect.mozilla.org/t5/ideas/customizable-hotkeys/idi-p/4979">Customizable Hotkeys</a></h2>
<p>I am both surprised and a little flabbergasted this is not already a feature of Firefox? Come on Mozilla...</p>
<h2><a href="https://connect.mozilla.org/t5/ideas/save-all-settings-when-sync/idi-p/15290">Sync All Settings</a></h2>
<p>Firefox has a built-in sync feature, but some settings are not synced, primarily toolbar customizations. There is allegedly an <code>about:config</code> setting you can change to enable synching for that, but I've not been able to test it. Either way, it should be a default.</p>
<h2><a href="https://connect.mozilla.org/t5/ideas/workspaces-%C3%A0-la-opera/idi-p/66">Workspaces</a></h2>
<p>This is an interesting organizational feature that I've not been able to try for myself. The idea is that you can have workspaces with their own tabs and tab groups that you can contextually switch between. You might have one for your personal life, another for work, maybe another for research into some topic.</p>
<p>The Firefox fork <a href="https://floorp.app/">Floorp</a> has workspaces, but they work in a hacky way with Firefox sync, and in my short time testing it has lead to my tabs getting deleted :/</p>
<h2><a href="https://connect.mozilla.org/t5/ideas/bring-back-pwa-progressive-web-apps/idi-p/35">Progressive Web Apps</a></h2>
<p>I have not used PWAs much before, but I've been rethinking how I engage with websites, especially ones that I frequently open or always keep open, and it seems like PWAs could be a good idea for that. Someone <a href="https://github.com/filips123/PWAsForFirefox">has made an extension</a> that enables PWAs on Firefox desktop, so I'll be checking how that works for me :o</p>
<h2><a href="https://connect.mozilla.org/t5/ideas/work-with-the-developer-to-integrate-ublock-origin-directly-into/idi-p/10953">Integrate uBlock Origin</a></h2>
<p>I have no idea how feasible this is, so I was pretty amused seeing this be an actual idea request. Either way, it would be an admittedly very cool idea! Other browsers like Brave have ad blockers built-in, so why shouldn't Firefox too?</p>
<hr />
<section>
<ol>
<li><p>Or delaying the inevitable enshitification. <a href="#fnref1">↩︎</a></p>
</li>
</ol>
</section>
]]></content>
        <category label="articles" term="articles"/>
        <category label="firefox" term="firefox"/>
        <category label="browsers" term="browsers"/>
        <published>2025-09-18T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Human Data Structures]]></title>
        <id>https://axoga.to/blog/human-data-structures</id>
        <link href="https://axoga.to/blog/human-data-structures"/>
        <updated>2025-09-17T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Conventionally, data structures is a concept that came from computer science, but before computers humans also structured data in logical ways that made it easier for us to process information!
]]></summary>
        <content type="html"><![CDATA[<p>"Human data structures" is a bit of a funny anachronism. Conventionally, data structures is a concept that came from computer science, and it refers to how data is structured in memory, such as arrays, linked lists, hashmaps, and so on. But before computers, humans also structured data in logical ways that made it easier for us to process information!</p>
<p>I thought it would be interesting to analyze ways we used to traditionally store data and reason about it through the lens of computer science! Tangentially, I think this would also be useful within the context of plaintet and PKM (Personal Knowledge Management) systems for organizing our digital data in ways that align with our human nature.</p>
<h2>Lists</h2>
<p>This is, without a shadow of a doubt, the most powerful human data structure: the humble list. With a list you can do everything, and other human data structures are really just specializations and extensions of the list.</p>
<p>Lists naturally conform to plaintext, as each entry in a list pairs with each line in a text file. Items in a list are united by the purpose of the list, and entries are added and/or removed as a functional feature of the list. Examples include todo lists, shopping lists, wish lists, reading lists, idea lists, bucket lists, and so on. Because of the diversity of lists, they are the most useful notes in most people's PKM system.</p>
<p>In lists like todo lists and shopping lists, you first create the list, and then cross things off as you complete them. Other lists like wish lists and reading lists grow and shrink over time as you add and remove entries from them. Idea lists typically only grow as a permanent record, but sometimes lower quality or obsolete ideas are trimmed.</p>
<p>While most lists are just lines on a piece of paper, they can be further physicalized as separate sheets of paper in a stack, or sticky notes on a wall. Lists are also usually unordered, but sometimes the order is important, like in a music playlist.</p>
<h2>Schedules</h2>
<p>Schedules are a type of specialized list that are always ordered, and they always have a start time for each entry, and sometimes an end time too. You can string multiple schedules across several days, or even the entire week, and you can present them in a table-like graph where each cell is an hour-long block.</p>
<ul>
<li>7:00am Wake up</li>
<li>9:00am Go to work</li>
<li>1:00pm–1:30pm Lunch break</li>
<li>5:00pm Leave work</li>
<li>11:00pm Sleep</li>
</ul>
<p>Calendars are a variant of schedules that expand the time scope to months and years, but they still achieve the same thing.</p>
<h2>Tables</h2>
<p>Tables are really just lists of lists, because for each entry in a list, you attach additional data. While you might know tables like this:</p>
<table>
<thead>
<tr>
<th>Character</th>
<th>Gender</th>
<th>Species</th>
</tr>
</thead>
<tbody>
<tr>
<td>Chai</td>
<td>Female</td>
<td>Axogato</td>
</tr>
<tr>
<td>Amber</td>
<td>Nonbinary</td>
<td>Gemstone creature</td>
</tr>
<tr>
<td>Biscoff</td>
<td>Male</td>
<td>Cat</td>
</tr>
<tr>
<td>Mips</td>
<td>Male</td>
<td>Bunny fox</td>
</tr>
</tbody>
</table>
<p>This is topographically the same as the table!</p>
<ul>
<li>Chai
<ul>
<li>Female</li>
<li>Axogato</li>
</ul>
</li>
<li>Amber
<ul>
<li>Nonbinary</li>
<li>Gemstone creature</li>
</ul>
</li>
<li>Biscoff
<ul>
<li>Male</li>
<li>Cat</li>
</ul>
</li>
<li>Mips
<ul>
<li>Male</li>
<li>Bunny fox</li>
</ul>
</li>
</ul>
<p>One caveat is that this type of table can be <em>jagged</em>, where each sub-list can contain different number of entries. While this can be represented with empty cells in a table, this can sometimes lead to extra columns that are largely empty; you could even try to solve this by just putting a list in a cell.</p>
<p>Tables in either form are useful for things like relational data, rosters, logbooks, double-entry accounting, book indexes, bibliographies, and enhancing simple lists with more information. A wishlist could include prices and links to where you can buy it, and an idea list could include source links.</p>
<p>In my personal experience, I found this to be the biggest rabbit hole of useless PKM notes, where I felt that I needed to create a comprehensive table of extra information about stuff that I would never really use later.</p>
<h2>Folders</h2>
<p>Folders are like a specialized type of table that only has one row. You collect several different kinds of data that are related in some way, and you put them all together in a folder. This is such a useful concept that our computer filesystem are directly based on this. You can further combine several folders that are related in some higher level way and put them into a filing cabinet, and informational books are folder-like in their curation of varied information on a topic.</p>
<p>The idea of storing data together based on their relatedness is a much more useful concept when it comes to PKM systems. Data should be organized next to each other if they are related to each other in a useful manner, rather than be silo'd off into other folders based on some other arbitrary critera.</p>
<p>For example, if you have a project folder for a video you're editing, you shouldn't put your image assets into your computer's pictures folder just because they're images, they belong alongside the other project files.</p>
<h2>Conclusion</h2>
<p>There are many PKM systems out there, like the Johnny Decimal System, Zettelkasten, PARA, ACCESS, ACE, et cetera, and if you find one that works for you, that's great! But for the vast majority who want to manage their data and personal knowledge, lists and folders are going to be your most powerful tools in both digital and physical workspaces.</p>
]]></content>
        <category label="articles" term="articles"/>
        <category label="pkm" term="pkm"/>
        <published>2025-09-17T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Developing a Chat App]]></title>
        <id>https://axoga.to/blog/developing-a-chat-app</id>
        <link href="https://axoga.to/blog/developing-a-chat-app"/>
        <updated>2025-09-16T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Fine, I'll do it myself]]></summary>
        <content type="html"><![CDATA[<p>I don't think for one second that I could create the next Discord, but I've always wanted to make my own chat app and learn from the journey, and there's no better time than now, am I right? I also wanted to experiment with novel ideas, like using IndieLogin or ATProto for user accounts and federation.</p>
<p>In the past I might have created a separate simple HTML page and a Node.js/C# server, but I decided to use Astro for the whole stack so I can learn some new concepts that I can apply to my personal website.</p>
<p>Not knowing how to have JavaScript running persistently on Astro, I found <a href="https://logsnag.com/blog/streaming-data-to-the-browser-via-server-sent-events-sse-and-astro">this tutorial</a> that coincidentally showed how to specifically make a chat app using server-sent events. There was a bug with Astro that prevented their code from working, and I found GitHub issue and accompanying pull request draft from a couple months ago, but I couldn't figure out how to fix it myself. I ended up finding a trick to handle connection closures properly, which was the last piece of the puzzle.</p>
<p>It's a very simple architecture that I will be expanding on soon, but I wanted to deploy it right now and have my friends test it with me! Thank you Kett for showing me how to set up an SSH remote tunnel &lt;3</p>
<p>Here is our silly chat log :3 There are no names because I did not implement it, so have fun guessing who is who :P</p>
<pre><code>sorry for the crude ui I haven't done the css yet lol
meow :3
meoww
hi ketty!!
haaaiiii
😺
this is pretty simply behind the scenes, using SSE
but I dig it :3
the latency you feel is cause of my phone hotspot lol
&lt;marquee&gt;nom&lt;/marquee&gt;
awwwww
lol
*bites*
*blepping*
if i refresh does it preserve history
aaahh it seems to
whats sse
mhm! the server currently stores and sends the whole message history
SSE is server-sent events
distinct from websockets in someway o3o
reading the mdn entry on SSE i definitely read this page before lol
waow...
sending comparison screenshot in discord
especially the bit about the connection limit over non HTTP/2 connections
rawr
&lt;
Hewoooo :3
is squishy
hewwo :3
I'm reading that page rn and that's a really odd limitation? why would http/2 be special
hewwoooo
I will have all my messages like 'this' for now
'here is a tiny example :3'
it's all voices unattributed
pfft yeah I didn't get around to usernames or anything lol
i implemented a little chat thing over websockets years ago in godot
'i quite like this way of texting'
I'll have to consider if websockets is preferable for a chat app, I used SSE cause it was just what the tutorial I was following said to use
'is there anyway to send pictures or is it just text?'
it's just text, but I'd like to support images
i seeee
''
thank you guys for testing this silly thing out &lt;3
it's cool to see it working in prod
hehehehe
'you're welcome chai! I always like testing out new things :3'
</code></pre>
<p>I don't know what to call this chat app yet. I've come up with names like Harmony and Concord, (Very creative, I know.) and even Hackachat because this is may likely be more suited for technical users. There's probably some cool or silly name that would be perfect for this project, so if you've got any suggestions feel free to comment them below :3</p>
]]></content>
        <category label="articles" term="articles"/>
        <category label="chat apps" term="chat apps"/>
        <published>2025-09-16T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[I Hate Discord]]></title>
        <id>https://axoga.to/blog/i-hate-discord</id>
        <link href="https://axoga.to/blog/i-hate-discord"/>
        <updated>2025-09-15T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Please can a new chat app rise up]]></summary>
        <content type="html"><![CDATA[<p>I remember the early days of Discord when it was a fledgling chat app that blew Skype out of the water. Things were simple back then, there was no emojis or Nitro or profile decorations. You could trust that your messages on Discord were private.</p>
<p>As Discord grew, it added more exciting features, and I was always eager to read their latest blog posts. Back then, they went into the technical deets of how their backend system worked and scaled to millions of users. The creators of Discord really cared about their chat app for gamers.</p>
<p>But in recent years--and especially in recent months--I am seriously starting to hate Discord.</p>
<ul>
<li>Their Nitro advertising has become increasingly intrusive, and they are adding more ad systems like quests and orbs.</li>
<li>Their reporting system is highly abusable and violates one's own privacy, and their automated moderation systems are dogshit. I've received <em>two account strikes</em> for "spam" by using a server invite a friend sent me in DMs :| It gave me a captcha, I solved it, and I get locked out of my account. <em>What the fuck.</em></li>
<li>Their mobile app has started chewing an insane amount of battery on my phone, easily at least double my YouTube app in the same time frame.</li>
<li>And now Discord is sharing your private chats to law enforcement, allegedly without any warrants or emergency disclosure requests.</li>
</ul>
<p>And this is all before Discord has finalized their IPO (Initial Public Offering) for shareholders. (a.k.a. it's going to get <em>worse</em>)</p>
<p>I can't stand this shit anymore and I want a new chat app to rise up <em>now</em>. We need something new, something that would be resilient to this enshittification, like a federated chat app that we can self-host and be in full control of our data and user experience. <em>Please.</em></p>
<p>I know there are numerous open-source chat app projects out there, but many of them are in the very early stages or half-baked.<sup><a href="#fn1">[1]</a></sup> There are some genuinely cool projects out there that I want to support, like <a href="https://github.com/polyphony-chat">Polyphony</a>, <a href="https://spacebar.chat/">Spacebar</a>, and <a href="https://a.roomy.space/">Roomy.space</a>, but it can't come soon enough :(</p>
<hr />
<section>
<ol>
<li><p>Looking at you, Matrix. <a href="#fnref1">↩︎</a></p>
</li>
</ol>
</section>
]]></content>
        <category label="articles" term="articles"/>
        <category label="discord" term="discord"/>
        <category label="chat apps" term="chat apps"/>
        <category label="opinion" term="opinion"/>
        <published>2025-09-15T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Use Obsidian in Your Git Repo]]></title>
        <id>https://axoga.to/blog/use-obsidian-in-your-git-repo</id>
        <link href="https://axoga.to/blog/use-obsidian-in-your-git-repo"/>
        <updated>2025-09-14T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Colocate your design docs next to your code]]></summary>
        <content type="html"><![CDATA[<p>I've been talking with <a href="https://anniesden.dev/">Annie</a> about developing a video game together, and boy is she a firehouse of ideas! I told her to use <a href="https://obsidian.md/">Obsidian</a> to write them down, not just for herself, but for me as a shared design doc I can follow along. She comes back to me the next day saying she got Obsidian, and she created the vault directly <strong>inside the git repo</strong>!</p>
<p>In that moment my brain realizes how <em>brilliant</em> this is; not just using Obsidian's powerful featureset as a replacement for GitHub's wiki or other 3rd party services, but colocating the documentation right next to the code. When I used Obsidian, I just put <em>everything</em> into one giant vault; all my game dev projects, daily journals, arts and crafts, writing, ideas, etc.</p>
<p>Funny thing is, this isn't the first time I realized that keeping all project information in one place is useful.</p>
<p>Previously, I used Trello's kanban board for storing ideas for my games and projects, and later I transitioned to using a kanban plugin in Obsidian to minimize my contact surface with 3rd party apps. I then realized I can use GitHub's projects with its powerful UX to sort and label items and integrate directly with issues and pull requests!</p>
<p>In my short-sighted folly, I assumed GitHub wiki was the only choice, and I much prefered Obsidian with how powerful it was. I never thought to just, create the Obsidian vault inside the repo directly, in a <code>docs</code> folder at the root.</p>
<p>So thank you for that, Annie! And I hope this helps open the eyes of possibilities for others who read this, too ^^</p>
<h2>Addendum</h2>
<p>It would be <em>super cool</em> if Forgejo could have an integrated Obsidian vault viewer, or at least support internal wikilinks in markdown files. I'll have to see how feasible it is to extend or modify Forgejo to implement such a feature, but I think a more universal browser extension would be great.</p>
]]></content>
        <category label="articles" term="articles"/>
        <category label="git" term="git"/>
        <published>2025-09-14T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Lattes Aren't Just Coffee]]></title>
        <id>https://axoga.to/blog/lattes-arent-just-coffee</id>
        <link href="https://axoga.to/blog/lattes-arent-just-coffee"/>
        <updated>2025-09-13T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[*sips chai latte*]]></summary>
        <content type="html"><![CDATA[<p>It's a very common misconception that all lattes are coffees, in part because lattes are traditionally espresso-based. But the word "latte" is Italian for milk, so the defining characteristic of a latte is that it is primarily made of milk. As a result, there are many things you can make lattes out of that aren't coffee!</p>
<p>The most common non-coffee latte you can find--and my absolute <em>favorite</em>--is a hot cup of <strong>chai latte</strong>! I love it so much that I even chose Chai as my username<sup><a href="#fn1">[1]</a></sup> <img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="😅" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f605.png" /> It's based on a tea from India called <em>masala chai</em>; a black tea with a blend of spices such as cinnamon, cardamom, cloves, and ginger, and a splash of milk too.</p>
<p>I like masala chai on its own, but if you replace all the water with milk and optionally add some sweetener, you have a <em>killer</em> drink that even non-tea drinkers will love! You can find it at places like Dunkin, Starbucks, and any cafe worth their salt. Anytime I go to a new place and they have chai latte, I always go for it to discover just how they do theirs differently.</p>
<p>A less common latte you can find is the <strong>matcha latte</strong>. Matcha is a Japanese green tea made by grinding the entire tea leaf into a powder, so it has a bit of an earthy taste, but the milk and sweetener probably make it more tolerable :o</p>
<p>An even rarer latte I've had is the <strong>golden latte</strong>, which gets it's bright yellow color from turmeric. They're more often called golden milk, but it's the same thing really. It has a really interesting flavor I like, (More than matcha to be honest.) and it shares a bunch of the same spices as masala chai. Just make sure you don't spill it on yourself cause it will permanently stain your clothes!</p>
<p>Remember when I said chai latte is the most common non-coffee latte? Well, before writing this I didn't realize <strong>hot chocolate</strong> is actually a latte, and it is undeniably the most common! Afterall, who would want to drink hot cocoa without milk?</p>
<p>So as you can see, lattes are more than just coffees. You should give them a try next time you're in a cafe!</p>
<hr />
<section>
<ol>
<li><p>And as a bonus, "Chailotl" sounds similar to chai latte! <a href="#fnref1">↩︎</a></p>
</li>
</ol>
</section>
]]></content>
        <category label="articles" term="articles"/>
        <published>2025-09-13T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Left Behind in a Persistent World]]></title>
        <id>https://axoga.to/blog/left-behind-in-a-persistent-world</id>
        <link href="https://axoga.to/blog/left-behind-in-a-persistent-world"/>
        <updated>2025-09-12T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Hey guys I did some mining and building off camera]]></summary>
        <content type="html"><![CDATA[<p>Some video games have a persistent world where you, your friends, and sometimes strangers can hop on and off whenever you want, playing in the same shared world. Even when no one is offline, it's still there, waiting for someone to join.</p>
<p>You want to play with your friends at the same time, but sometimes they'll play without you. They might complete a dungeon raid without you, they might experience a new event for the first time without you, they might progress in the game without you.</p>
<p>Some people don't mind that, they can just catch up or try it again together.</p>
<p>But for others, it afflicts them with intense FOMO. It can make them feel forgotten, abandoned. It can demoralize them from even playing the game anymore.</p>
<p>When a game's persistent world is accessible 24/7, people can play anytime they want, and people don't all have the same schedule. People also have different interests that wax and wane. They might be too tired after work to bother playing games. So how do you solve that?</p>
<h2>MMORPGs Solved This Already</h2>
<p>Yeah there's no beating around the bush here lol</p>
<p>If you want to ensure you and your friends don't do important stuff without each other, <strong>schedule it</strong>! And if you schedule it at a consistent time each week, it makes it easier to integrate it into your schedule. You can discuss what would be the best time for everyone involved, though this becomes less effective the more players there are.</p>
<p>So the real question then is, why am I talking about it?</p>
<p>I had read a blog post by Kett titled <em><a href="https://racc.at/blog/?id=26">How Minecraft fails to captivate me, specifically</a></em>, where they go into the multiple facets that ultimately sour their experience of Minecraft. I believe that even if you solved four of their issues,<sup><a href="#fn1">[1]</a></sup> it's the bullet point about being left behind that can kill someone's motivation to keep playing.</p>
<p>You <em>could</em> schedule to only play on the Minecraft server at a certain time, but if you're hosting the server you can just... shut it down off schedule! If you've ever watched Minecraft civilization videos they do exactly that; one or two days a week where it's only up for a few hours.</p>
<p>This prevents players with way more open schedules from grinding dozens of hours when others are busy with work or school. It also makes it significantly more likely that you'll be playing at the same time as other people; it's a social game afterall!</p>
<p>I don't think this is the silver bullet that would make Kett like Minecraft, but I believe it's the most important factor, and something I will be taking into account when I start up my own Minecraft server.</p>
<hr />
<section>
<ol>
<li><p>Being made to host the server, never playing vanilla, using a huge broken modpack, and the players killing the ender dragon. <a href="#fnref1">↩︎</a></p>
</li>
</ol>
</section>
]]></content>
        <category label="articles" term="articles"/>
        <category label="games" term="games"/>
        <category label="minecraft" term="minecraft"/>
        <category label="multiplayer" term="multiplayer"/>
        <published>2025-09-12T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Social Backlog Paralysis]]></title>
        <id>https://axoga.to/blog/social-backlog-paralysis</id>
        <link href="https://axoga.to/blog/social-backlog-paralysis"/>
        <updated>2025-09-11T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Most backlogs are passive and no pressure; your Steam library, your reading list, maybe your YouTube watch later playlist. But I have a particular backlog that is *paralyzing*—my Discord DMs.
]]></summary>
        <content type="html"><![CDATA[<p>Most backlogs are passive and no pressure; your Steam library, your reading list, maybe your YouTube watch later playlist. But I have a particular backlog that is <em>paralyzing</em>--my Discord DMs.</p>
<p>When a friend sends me a DM, I make it my mission to read it and reply, no matter how long it takes me. I've had DMs with dozens of messages, from several people, dating back <em>months</em>, their icon perpetually camping in the top left corner of my screen. And the longer I wait, and the more messages they send me, the more paralyzed I feel to open it.</p>
<p><em>And I feel really bad about this</em> &gt;.&lt;</p>
<p>I don't want to inadvertently ghost someone for that long, making them worry why I'm not responding. Because I <em>want</em> to reply to all their messages, to show them that I <em>do</em> care about what they have to tell me. But it becomes this immense sense of awkwardness of having to explain why I was silent.</p>
<p>My friends understand when I explain it to them. Some of them are probably used to it now. One of them even said I didn't have to read all of their past messages. (I still did it anyways and replied to nearly all of them.)</p>
<p>I try not to let it control me like this, but like many of my backlogs I can't let it go easily. I just feel that I <em>need</em> to go through it all, sooner or later.</p>
<p>&lt;figcaption&gt;If you're wondering, yes I still have unread DMs and channels in my Discord server right now <img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="😅" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f605.png" />&lt;/figcaption&gt;</p>
]]></content>
        <category label="personal" term="personal"/>
        <published>2025-09-11T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[My First Contribution to IndieLogin]]></title>
        <id>https://axoga.to/blog/my-first-contribution-to-indielogin</id>
        <link href="https://axoga.to/blog/my-first-contribution-to-indielogin"/>
        <updated>2025-09-10T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[You can use Codeberg as an authentication provider now]]></summary>
        <content type="html"><![CDATA[<p><a href="https://indieauth.net/">IndieAuth</a> is a cool IndieWeb technology that lets you <strong>log in with your domain name as your identity</strong>! And <a href="https://indielogin.com/">IndieLogin</a> is a public IndieAuth provider you can use, but you can also self-host it. Currently, it supports <s>Twitter</s>, GitHub, email, and PGP keys.<sup><a href="#fn1">[1]</a></sup></p>
<p>Practically all developers have a GitHub account so it's convenient for them, but the <a href="https://giveupgithub.com/">enshittification of GitHub</a> has made it less desirable. My fellow developers have been increasingly turning towards <a href="https://codeberg.org/">Codeberg</a> and self-hosting <a href="https://forgejo.org/">Forgejo</a>, so I wanted to contribute to IndieLogin and add Codeberg as an authentication provider!</p>
<blockquote>
<p>Info
If you're excited about using this for your self-hosted Forgejo instance, I'm afraid that won't be possible, as IndieLogin would need to register an OAuth app on your instance.</p>
</blockquote>
<p>The project uses PHP (eugh), but since GitHub and Codeberg are very similar, I just had to copy and paste the relevant GitHub code and adapt it for Codeberg. I had some troubles getting the project to run locally as the only instructions in the README was basically "Run <code>composer start</code>", but I figured it out.</p>
<p>Just to be sure my local instance was working right, I created an OAuth app on GitHub and tried to authenticate myself that way, aaaand it's broken!! The README left out the part where I needed to enable a couple PHP extensions, and install MySQL, which my Debian laptop was refusing to because of a stupid lock file.</p>
<p>I was following along the Forgejo docs for <a href="https://forgejo.org/docs/v8.0/user/oauth2-provider/#using-codeberg-as-an-authentication-source">using Codeberg as an authentication source</a>, adding and changes some lines of code here and there so it's happy, and I found this <a href="https://codeberg.org/api/swagger">API page</a> so I could find the correct user endpoint for fetching their website URL and bio.</p>
<p>The completed pull request is <a href="https://github.com/aaronpk/indielogin.com/pull/139">right here</a>, so hopefully the developer accepts it soon! All in all, this took me about 4 hours to do, which I think is not bad :3</p>
<hr />
<section>
<ol>
<li><p>PGP keys were recently disabled for some reason. <a href="#fnref1">↩︎</a></p>
</li>
</ol>
</section>
]]></content>
        <category label="articles" term="articles"/>
        <category label="indieweb" term="indieweb"/>
        <category label="oauth" term="oauth"/>
        <published>2025-09-10T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[There Is Still Hope]]></title>
        <id>https://axoga.to/blog/there-is-still-hope</id>
        <link href="https://axoga.to/blog/there-is-still-hope"/>
        <updated>2025-09-09T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[We didn't find Misia the next morning, but we talked with the lady who spotted her and she agreed to use our animal trap to catch her.
]]></summary>
        <content type="html"><![CDATA[<p><em>An update from the <a href="/blog/i-miss-her">previous post</a>.</em></p>
<p>Misia wasn't found the next morning.</p>
<p>My mom found the trap triggered, but empty, with some of the food eaten. We concluded that someone's pet got caught, and I later noted that the rear door of the trap wasn't closed the way I did, so I know a human opened it.</p>
<p>My mom and I searched the area again this morning, calling out for Misia, but there was no sign of her. Cats that are displaced will hide in silence, they won't respond to your voice.</p>
<p>We went to talk with the lady that spotted her, and she gave us more information. She told us that she saw a brown and black furred cat run in front of her chasing a chipmunk, and she stopped in front of her, looking at her from 3 feet, then ran off to hide under someone's camper.</p>
<p>When I showed her a full picture of Misia she confirmed it was definitely her, and this gave me hope that she was still alive, having learned to hunt for food.</p>
<p>We had to leave the campsite to return back home (another grueling 14 hours of driving), so we gave the lady and her husband our animal trap, hoping they can try to catch Misia and ship her back home.</p>
<p>To my friends and to others who gave me their condolences, thank you so much. I hope she is found soon before the cold winter comes.</p>
]]></content>
        <category label="personal" term="personal"/>
        <published>2025-09-09T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[I Miss Her]]></title>
        <id>https://axoga.to/blog/i-miss-her</id>
        <link href="https://axoga.to/blog/i-miss-her"/>
        <updated>2025-09-08T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[One month ago we lost a cat on our cross-country RV trip. Her name was Misia, and she's a beautiful tortoiseshell female. Every day I pray that she is found safe and sound.
]]></summary>
        <content type="html"><![CDATA[<p>One month ago we lost a cat on our cross-country RV trip. Her name was Misia, and she's a beautiful tortoiseshell female, and part Maine Coon. She escaped at night through a hatch in our RV door that someone carelessly left open, but despite our best efforts to search for her, we failed.</p>
<p><img src="/images/blog/misia_1.jpg" alt="" /></p>
<p>We contacted a local animal shelter to give them her information, and we printed a missing poster for the campground. On our way back home, we stopped at the same campground to look for her again, but there was still no sign of her.</p>
<p>I cried a lot that first night, and I'm tearing up right now writing this post. I miss her so much. She was a joyous bundle of fur, always rubbing against my legs for attention, and sleeping with me at night. Every day we pray that she is found safe and sound.</p>
<p><img src="/images/blog/misia_2.jpg" alt="" /></p>
<p>But yesternight my mom recieved a call from the campground that one of the staff spotted her roaming near some dumpsters. They didn't get a photo, but they seemed confident it was her.</p>
<p>So this morning my mom and I woke up at 4 in the morning and drove for 14 hours all the way to the campground to find her. We set up an animal trap in the area she was spotted in hopes that we catch her overnight.</p>
<p>I hope that when I wake up tomorrow, she will be back in my arms, and I can share the good news.</p>
<p>I miss her so much.</p>
]]></content>
        <category label="personal" term="personal"/>
        <published>2025-09-08T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Blog Comments]]></title>
        <id>https://axoga.to/blog/blog-comments</id>
        <link href="https://axoga.to/blog/blog-comments"/>
        <updated>2025-09-07T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[This one is for you Lucia]]></summary>
        <content type="html"><![CDATA[<p>A few days ago I learned that my "Comment via Bluesky" button doesn't work the way I expected it to. I also had someone using my ask box as a way to send comments to my blog because they don't have a Bluesky!</p>
<p>So to rectify that, I've added a traditional comment system that anyone can use. It is based on my ask box code (detailed in <a href="/blog/how-i-built-my-ask-box">this post</a>), which means it has the same honeypot and IP rate limiter as the ask box. Comments do not appear immediately until after a site reboot, same as webmentions.</p>
<p>I also added dates to comments :)</p>
]]></content>
        <category label="updates" term="updates"/>
        <published>2025-09-07T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[You Should Know Git Exclude]]></title>
        <id>https://axoga.to/blog/you-should-know-git-exclude</id>
        <link href="https://axoga.to/blog/you-should-know-git-exclude"/>
        <updated>2025-09-06T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Ignore local test files without updating .gitignore]]></summary>
        <content type="html"><![CDATA[<p>I read an article today by Marijke titled <em><a href="https://marijkeluttekes.dev/blog/articles/2025/09/03/git-exclude-a-handy-feature-you-might-not-know-about/">Git exclude, a handy feature you might not know about</a></em>, and I was pleasantly surprised to learn about it! You can (and should!) read the original article, but I like compressing information down to its barest essentials.</p>
<p>If you've used git ignore before, git exclude functions similarly, but there are two key differences:</p>
<ol>
<li>You can only have <strong>one</strong> <code>exclude</code> file.</li>
<li>Git <strong>does not track</strong> the <code>exclude</code> file.</li>
</ol>
<p>The file is located in your <code>.git</code> directory, at <code>.git/info/exclude</code>, but if it doesn't exist you can create it. It is essentially a local <code>.gitignore</code>, letting you ignore temporary test files or personal scripts that shouldn't be committed to the repo, without updating the git ignore file.</p>
<p>For the past couple days I had a test page on my website that I didn't want to commit to the repo, but I didn't want to add to my <code>.gitignore</code> either, so it was just awkwardly left unstaged. But with git exclude I can have my cake and eat it too!</p>
]]></content>
        <category label="articles" term="articles"/>
        <category label="git" term="git"/>
        <published>2025-09-06T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Blog Stats]]></title>
        <id>https://axoga.to/blog/blog-stats</id>
        <link href="https://axoga.to/blog/blog-stats"/>
        <updated>2025-09-05T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[I created a stats page for my blog to compile the most interesting stats.
]]></summary>
        <content type="html"><![CDATA[<p>I created a <a href="/blog/stats">stats page</a> for my blog to compile the most interesting stats. I was motivated to do this because I didn't want to manually count how many blog posts I've written x3</p>
<p>So far there's only categories, tags, and series breakdowns, but in the near future I will expand on it with year breakdowns, and maybe even node graphs showing how my blog posts are interconnected by tags!</p>
<p>Tangentially, I created a new post category called <a href="/blog/personal">personal</a> and recategorized previous blog posts that were tagged with #personal.</p>
]]></content>
        <category label="updates" term="updates"/>
        <category label="meta" term="meta"/>
        <published>2025-09-05T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[25 Blog Posts Later...]]></title>
        <id>https://axoga.to/blog/25-blog-posts-later</id>
        <link href="https://axoga.to/blog/25-blog-posts-later"/>
        <updated>2025-09-04T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[That's an alot of blog posts]]></summary>
        <content type="html"><![CDATA[<p>When I started this blog, my <a href="/blog/why-i-want-to-blog">first post</a> reminisced on past blogs I started and abandoned, and at the time I hoped that this time would be different. The first year had 4 posts starting in September, which comes to a decent post per month, even if half the posts were just talking about the blog itself.</p>
<p>But in 2024 I really faltered, having written only one post the whole year. There were a lot of missed opportunities to talk about the Minecraft mods I had been developing, the Modfest events I participated in, and the fact that I was promoted to a staff position at Modfest to assist with running the event.</p>
<p>As the new year rolled around, I wanted to change that track record and get back to blogging, starting with my <em><a href="/blog/2024-retrospective">2024 Retrospective</a></em>. At the time I started going to college for a master's degree, which unbeknowst to me would hit me like a cement truck very soon. (Subtle forshadowing...) A month later Kett motivated me to write a blog post about anything, so I talked about how <a href="/blog/type-erasure-is-stupid">type erasure is stupid</a>.</p>
<p>And then the metaphorical cement truck hit me.</p>
<p>Close to half a year went by before I wrote another blog post, and I wasn't sure if I'd get to write more before the end of the year turned up. I felt down about a lot of things happening to me at the time, the on-going dumpster fire of a game dev market especially.</p>
<p>But then on August 19th, I wrote a new blog post titled <em><a href="/blog/youtube-is-better-with-rss">YouTube Is Better With RSS</a></em>. It took me two days to write, and I was really happy with sharing this newfound knowledge. The next day I wrote a simple blog post about the <a href="/blog/apps-i-use">apps I use</a>. And then I wrote another blog post, and then another blog post.</p>
<p>And before I knew it, I've been writing a blog post every day since then, with this post marking the 25th blog post. I've written 4 times as many blog posts this year as I have the first two years, isn't that crazy? And I'm just going to keep writing more blog posts every day until I run out of ideas.<sup><a href="#fn1">[1]</a></sup></p>
<p>A week ago I was checking my Nginx access logs to see how many people are regularly pinging my RSS feed. I found 4 visitors, and I only know who two of those are. It's not a lot, but yet it means a lot to me that there are people out there that I don't personally know, who want to read what I write. So thank you for coming around <img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="💜" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f49c.png" /></p>
<p>I hope the blog posts every day hasn't been too much for you guys :P</p>
<hr />
<section>
<ol>
<li><p>I've got 18 ideas in my ideas text file as of this writing. <a href="#fnref1">↩︎</a></p>
</li>
</ol>
</section>
]]></content>
        <category label="personal" term="personal"/>
        <category label="meta" term="meta"/>
        <published>2025-09-04T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Not So Peachy Loss]]></title>
        <id>https://axoga.to/blog/not-so-peachy-loss</id>
        <link href="https://axoga.to/blog/not-so-peachy-loss"/>
        <updated>2025-09-03T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Learning a hard lesson on how to store soft fruits]]></summary>
        <content type="html"><![CDATA[<p>Do you remember the peaches I picked from my visit to a <a href="/blog/i-went-to-a-fall-festival">fall festival</a>? Well, half of them were lost to mold ;w; Admittedly, it was my fault for leaving them all on the counter, stacked up in the bag I picked them with.</p>
<p>But I also didn't expect them to go bad so quickly, it's only been two days. I guess that's why grocery stores pick them before they fully ripen; they wouldn't survive being transported anyways. Something cool I learned is that the pits inside peaches are actually very easy to remove when they're fully ripe :o</p>
<p>Now that I learned what the tradeoff is for picking them fresh off the farm versus buying them from the grocery store, next time I'm definitely going to refridgerate them ^^;</p>
]]></content>
        <category label="personal" term="personal"/>
        <published>2025-09-03T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[A Decentralized Comment System]]></title>
        <id>https://axoga.to/blog/a-decentralized-comment-system</id>
        <link href="https://axoga.to/blog/a-decentralized-comment-system"/>
        <updated>2025-09-02T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Powered by webmentions!]]></summary>
        <content type="html"><![CDATA[<p>If you have a blog, you've probably thought about adding a comment section, and if you're using a blogging software that has it built-in, you just need to enable it. Otherwise, you either need to implement it yourself or use a third-party service like Disqus; the former isn't possible with a static site host, and the latter has privacy concerns.</p>
<p>But may I suggest something better? A decentralized comment system powered by <strong>webmentions</strong>, an open standard to notify any URL when you link to it from your site. What this means is that other people can comment on<sup><a href="#fn1">[1]</a></sup> your blog posts, using their own websites!</p>
<p>While you can manually implement this yourself on your blog, I'm gonna show you an easier way, and additionally support Bluesky as a comment source!</p>
<h2>Setting up IndieAuth</h2>
<p>Before we can work with webmentions, we need to first <a href="https://indielogin.com/setup">set up IndieAuth</a> on your website. It's pretty simple: you link to your GitHub profile from your home page using this HTML, and on your GitHub profile you need to link back to your URL.</p>
<pre><code>&lt;link href="https://github.com/chailotl" rel="me"&gt;
</code></pre>
<blockquote>
<p>Attention
The <code>rel="me"</code> attribute indicates that the URL you're pointing to is <em>you</em>, so you should <strong>never</strong> use it on links that you do not control.</p>
</blockquote>
<blockquote>
<p>Info
In recent events <a href="https://sfconservancy.org/GiveUpGitHub/">GitHub has been getting worse</a>, so if you want IndieLogin to support other logins, you can give your two cents on these issues: <a href="https://github.com/aaronpk/indielogin.com/issues/82">GitLab</a>, <a href="https://github.com/aaronpk/indielogin.com/issues/134">Bluesky</a>, <a href="https://github.com/aaronpk/indielogin.com/issues/97">Fediverse</a></p>
</blockquote>
<h2>Setting Up Webmentions</h2>
<p>Courtesy of Aaron Parecki, we have the <a href="https://webmention.io/">Webmention.io</a> service to collect and validate webmentions for us! To use it, you need to log in with your website, and then you put this HTML in your header. This will let other websites know where your webmentions endpoint is.</p>
<pre><code>&lt;link rel="webmention" href="https://webmention.io/axoga.to/webmention" /&gt;
</code></pre>
<p>And with a bit of client-side JavaScript we can fetch our webmentions and format them into a comment section! Adding support for likes and reposts will be left as an exercise for the reader <img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="😉" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f609.png" /></p>
<pre><code>const comments = document.getElementById("comments");
// This way will work in your dev env
const url = "https://axoga.to" + window.location.pathname;

fetch("https://webmention.io/api/mentions.jf2?target=" + url)
	.then(response =&gt; response.json())
	.then(json =&gt; json.children.filter(entry =&gt; entry["wm-property"] == "in-reply-to"))
	.then(replies =&gt; replies.forEach(reply =&gt; {
		let blockquote = document.createElement("blockquote");
		let a = document.createElement("a");
		let p = document.createElement("p");

		a.textContent = reply.author.name;
		a.setAttribute("href", reply.url);
		p.textContent = reply.content.text;

		blockquote.appendChild(a);
		blockquote.appendChild(p);
		comments.appendChild(blockquote);
	}));
</code></pre>
<h2>Setting up Brid.gy</h2>
<p>In a perfect world, everyone would have their own blogs and support webmentions. Unfortunately, that is not the case. Aside from convincing them to start a blog, (Which you should still do!) we have another way that leverages Bluesky to generate webmentions, using another service called <a href="https://brid.gy/">Brid.gy</a>!</p>
<p>To use it, make sure your URL is on your Bluesky profile before you log in with your handle and an <a href="https://bsky.app/settings/app-passwords">app password</a>; you can remove it afterwards. Once you've done that, it will now automatically search Bluesky for valid webmention targets!</p>
<p>To start receiving comments, you just need to post your blog post URL on Bluesky, and any comments your Bluesky post receives will be syndicated as webmentions to your blog post! How cool is that? This extends to likes and replies as well.</p>
<p>There is a lot more you can do with webmentions and Brid.gy, and set up all sorts of fancy integrations, but for just adding a comment section to your blog I think this is plenty sufficient ^^</p>
<hr />
<section>
<ol>
<li><p>Or like, or repost, or mention, or bookmark. <a href="#fnref1">↩︎</a></p>
</li>
</ol>
</section>
]]></content>
        <category label="articles" term="articles"/>
        <category label="technical" term="technical"/>
        <category label="indieweb" term="indieweb"/>
        <category label="webmentions" term="webmentions"/>
        <published>2025-09-02T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[I Went to a Fall Festival]]></title>
        <id>https://axoga.to/blog/i-went-to-a-fall-festival</id>
        <link href="https://axoga.to/blog/i-went-to-a-fall-festival"/>
        <updated>2025-09-01T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[And I picked half a peck of peaches]]></summary>
        <content type="html"><![CDATA[<p>When I set up <a href="https://miniflux.app/">Miniflux</a> as my RSS feed reader, I added a local independent news site so I can be more in touch with my community. I wanted to be aware of important initiatives like making my city more bike and pedestrian friendly, as well as any fun events I'd like to go to.</p>
<p>One of those events was a fall festival hosted by an orchard farm during the Labor Day long weekend, so today I drove an hour there with my brother and had a blast together! It's a lovely farm, with their own farmer's market and diner, and it had several activities for the fall festival.</p>
<p>The first thing we both did was jaunt over to the corn maze. We've never been to one before, so it was exciting as our very first time! We just followed the left-hand side the whole time, talking to each other about Magic the Gathering and indie horror games. I reminisced that if we were kids, with a bunch of other kids our age, we'd all probably be running through the maze trying to find the quickest way out &gt;w&lt;</p>
<p>Before we left the farm, we went through the maze a second time, following our same path, but this time I tracked my route to see what it would look like. I think my phone reduced its accuracy when it turned off its screen <img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="😅" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f605.png" /></p>
<p><img src="/images/blog/corn_maze_route.jpg" alt="" /></p>
<p>Afterwards, we visited the market. They had a lot of homemade whoopie pies and cookies, maple syrup and maple candies, apple cider and local sodas, and of course fresh produce. I was tempted by the maple cream and homemade rice crispies, but I was being conscious of my spending. I didn't take any photos other than these mushrooms I saw :3</p>
<p><img src="/images/blog/mushrooms.jpg" alt="" /></p>
<p>And then I got myself some yummy food!! It was a grilled fennel sausage with roasted peppers and onions, which I topped with mustard and relish, and a side of chips and apple cider. I was surprised by how big the hot dog was, and it was just as surpsingly soft and so homey and delicious. I want to learn how to make this myself at home!</p>
<p><img src="/images/blog/fennel_sausage.jpg" alt="" /></p>
<p>We then hitched a ride on a tractor, going all the around the farm while my brother talked to me about Pocket Frogs, an OG mobile game. It was nice sitting down and taking in the view, though the sun started hitting down on my back a lot.</p>
<p>Once we got off, we went to to go peach picking! They also had apples, strawberries, blueberries, and raspberries. My brother and I picked 4 nectarines, 5 white peaches, and 6 yellow peaches that we thought were ripe enough. (Fingers crossed!) The half a peck was pretty pricey at $30 though &gt;.&lt;</p>
<p>To cool ourselves off after cooking in the sun, we went to get some ice cream frappes. I got cotton candy flavor, and my brother went with maple walnut. We had a great time together at the festival--and we didn't do everything there was to do--so next year I wanna go with a few of my best friends :3</p>
]]></content>
        <category label="personal" term="personal"/>
        <published>2025-09-01T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Ask Box Feed]]></title>
        <id>https://axoga.to/blog/ask-box-feed</id>
        <link href="https://axoga.to/blog/ask-box-feed"/>
        <updated>2025-08-31T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[I added an RSS feed for my ask box, available in Atom and JSON formats.
]]></summary>
        <content type="html"><![CDATA[<p>I've added an RSS feed for my <a href="/ask">ask box</a>, available in <a href="/ask.xml">Atom</a> and <a href="/ask.json">JSON</a> formats. If you didn't know, my blog feed also comes in JSON if you append <code>.json</code> ;)</p>
]]></content>
        <category label="updates" term="updates"/>
        <category label="rss" term="rss"/>
        <published>2025-08-31T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Overnight Oats]]></title>
        <id>https://axoga.to/blog/overnight-oats</id>
        <link href="https://axoga.to/blog/overnight-oats"/>
        <updated>2025-08-30T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[The easiest and most versatile breakfast]]></summary>
        <content type="html"><![CDATA[<p>What I love about overnight oats is that they can be prepped the night before and be ready-to-eat in the morning; and if you made them in mason jars they can be taken with you to work! They are also so easy to make, requiring no cooking.<sup><a href="#fn1">[1]</a></sup></p>
<p><img src="./overnight-oats.jpg" alt="" /></p>
<h2>Ingredients</h2>
<ul>
<li>[ ] 1 cup (100g) rolled oats</li>
<li>[ ] 1 cup (240ml) milk</li>
<li>[ ] 1 tablespoon ground flaxseed</li>
<li>[ ] 3 tablespoons chopped pecans</li>
<li>[ ] 2 tablespoons craisins</li>
<li>[ ] 1 tablespoon maple syrup</li>
<li>[ ] Handful of fresh blueberries</li>
</ul>
<p>&lt;figcaption&gt;Exact quantities don't matter, you can eyeball these.&lt;/figcaption&gt;</p>
<h2>Steps</h2>
<ol>
<li>In a mason jar, combine the oats, flaxseed, pecans, and craisins</li>
<li>Close the jar and shake it</li>
<li>Combine the milk and maple syrup and pour into the jar</li>
<li>Top with blueberries and chill in the fridge overnight</li>
</ol>
<p><img src="/images/blog/overnight_oats.jpg" alt="" /></p>
<h2>Substitutions</h2>
<p>Nearly all of the ingredients in this recipe can be substituted or omitted to your preferences or whatever you have on hand. You can use various non-dairy milks like almond or soy milk, and, I haven't tried it for myself, but some people even suggest using fruit juices o.o</p>
<p>Flaxseed thickens the mixture and adds a significant nutritional boost of fiber, omega-3 fatty acids, and protein. You can use chia seeds or hemp seeds instead, though the latter won't thicken it as well. You can also just skip this.</p>
<p>Pecans can be swapped (or mixed!) with other chopped or slivered nuts, like almonds, walnuts, and peanuts, though I personally prefer pecans.</p>
<p>Craisins can be swapped with raisins, dried blueberries, or any other dried fruits that will plump up from the liquid.</p>
<p>Maple syrup can be substituted with honey, agave syrup, or date syrup. You can also use granulated or brown sugar if you dissolve it in the milk beforehand. I personally prefer dark maple syrup for its robust maple-y flavor.</p>
<p>And lastly, the fresh blueberries can be substituted with any fresh fruit, though I will suggest strawberries, raspberries, and blackberries as your go-to's.</p>
<hr />
<section>
<ol>
<li><p>Perfect for my friends who can't cook to save their life--you know who you are. <a href="#fnref1">↩︎</a></p>
</li>
</ol>
</section>
]]></content>
        <category label="recipes" term="recipes"/>
        <category label="breakfast" term="breakfast"/>
        <published>2025-08-30T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Unexpected RSS Feeds]]></title>
        <id>https://axoga.to/blog/unexpected-rss-feeds</id>
        <link href="https://axoga.to/blog/unexpected-rss-feeds"/>
        <updated>2025-08-29T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[And where to find them]]></summary>
        <content type="html"><![CDATA[<p>So you got a feed reader, and you want to know what you can subscribe to beyond blogs and news sites? (And if you don't have one, you should <a href="https://aboutfeeds.com/">consider it</a>!) There are a lot of sites that have RSS feeds, but not all of them indicate it on their page,<sup><a href="#fn1">[1]</a></sup> so let me share all that I found!</p>
<h3>YouTube</h3>
<p>I wrote a whole blog post about how <a href="/blog/youtube-is-better-with-rss">YouTube is better with RSS</a>, where you can not only subscribe to any YouTube channel or playlist, but also only a creator's videos or shorts or livestreams!</p>
<h3>Reddit</h3>
<p>You can add <code>.rss</code> to the end of the url of any feed and get an RSS feed! If you're using the top feed sorted by time, put <code>.rss</code> before the <code>?</code>.</p>
<pre><code>https://reddit.com/r/cats.rss
https://reddit.com/r/cats/top.rss?t=week
</code></pre>
<h3>Bluesky</h3>
<p>Similarly, you can add <code>/rss</code> to the end of anyone's Bluesky profile and get an RSS feed. Unfortunately, it does not include image embeds and only contains public posts. <a href="https://openrss.org/feeds/bluesky">Open RSS</a> seems to offer an improved version of Bluesky's RSS feeds, so they're worth a try.</p>
<pre><code>https://bsky.app/profile/lotsoflatte.bsky.social/rss
</code></pre>
<h3>Mastodon</h3>
<p>You can follow an account by adding <code>.rss</code> to the end of their profile URL, but keep in mind this only works on their home server! You can also do the same to a tag, but there doesn't appear to be any way to follow entire instances.</p>
<pre><code>https://woof.tech/@bagelcollie.rss
https://mastodon.social/tags/mastodon.rss
</code></pre>
<h3>Tumblr</h3>
<p>You can add <code>/rss</code> to the end of a Tumblr account to get an RSS feed, but this has to be their main page where the username in the URL is before "tumblr.com".<sup><a href="#fn2">[2]</a></sup></p>
<pre><code>https://important-animal-images.tumblr.com/rss
</code></pre>
<h3>Discourse</h3>
<p>Discourse is a forum software, and you can slap <code>.rss</code> to the end of any URL and get a feed, you get the drill by now :P It has a lot of different RSS feeds, so do check <a href="https://meta.discourse.org/t/finding-discourse-rss-feeds/264134">this thread</a> to see all it can do.</p>
<pre><code>https://meta.discourse.org/t/finding-discourse-rss-feeds/264134.rss
</code></pre>
<h3>GitHub</h3>
<p>You can get RSS feeds for commits, releases, and the public activity of specific users by appending <code>.atom</code> to any of those URLs.</p>
<pre><code>https://github.com/Chailotl.atom
https://github.com/Chailotl/web-to-epub/releases.atom
https://github.com/Chailotl/particular/commits/master.atom
</code></pre>
<h3>Podcasts</h3>
<p>Every podcast uses RSS feeds to deliver to your podcast app, including podcasts on Spotify! You would normally use a dedicated podcast app, but any feed reader would work.</p>
<h3>Wikipedia</h3>
<p>Wikipedia has a few interesting RSS feeds, and <a href="https://to-rss.xyz">to-rss.xyz</a> hosts a custom current events feed based on Wikipedia.</p>
<ul>
<li><a href="http://en.wikipedia.org/w/api.php?action=featuredfeed&amp;feed=featured">Featured Article</a></li>
<li><a href="https://en.wikipedia.org/w/api.php?action=featuredfeed&amp;feed=potd">Picture of the Day</a></li>
<li><a href="https://en.wikipedia.org/w/api.php?action=featuredfeed&amp;feed=onthisday">On this day...</a></li>
<li><a href="https://www.to-rss.xyz/wikipedia/current_events/">Current Events</a></li>
</ul>
<h3>Et Cetera</h3>
<p>If you want even more RSS feeds, here are a few more resources I found.</p>
<h4><a href="https://github.com/plenaryapp/awesome-rss-feeds">Awesome RSS Feeds</a></h4>
<p>A directory of curated lists of RSS feeds for a plethora of categories and news sources across countries.</p>
<h4><a href="https://indieblog.page/rss">indieblog.page</a></h4>
<p>Daily or weekly dose of random indie blog posts :o</p>
<hr />
<section>
<ol>
<li><p>Feed readers can automatically find these URLs if they are in the markup. <a href="#fnref1">↩︎</a></p>
</li>
<li><p>Unless they have a custom domain. <a href="#fnref2">↩︎</a></p>
</li>
</ol>
</section>
]]></content>
        <category label="articles" term="articles"/>
        <category label="rss" term="rss"/>
        <published>2025-08-29T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Turning the Web Into EPUBs]]></title>
        <id>https://axoga.to/blog/turning-the-web-into-epubs</id>
        <link href="https://axoga.to/blog/turning-the-web-into-epubs"/>
        <updated>2025-08-28T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[I made a browser extension to read the web offline]]></summary>
        <content type="html"><![CDATA[<p><a href="/blog/tags/web-to-epub">This mini-series</a> is coming to a close! It started with me wanting to read articles on my Kindle but only finding mediocre browser extensions to do the job, so it will now end with a brand new web to EPUB extension by yours truly :3</p>
<p><a href="/blog/making-an-epub-manually">Turning my blog posts into EPUBs</a> isn't that hard as I have access to the original markdown files. Turning any arbitrary web page into an EPUB? Now <em>that's</em> a toughie. Fortunately, Mozilla open-sourced their <a href="https://github.com/mozilla/readability">readability library</a> that they use for Firefox's reader view, so I just needed to make an extension that uses this to create and download EPUBs.</p>
<h2>Picking A Framework</h2>
<p>JavaScript frameworks are comfy to use, and I found three for making browser extensions; thankfully someone on <a href="https://www.reddit.com/r/chrome_extensions/comments/1k1c8gv/comparing_frameworks_for_extension_development/">Reddit</a> made this comparison chart. I wanted to develop the extension for both Chrome and Firefox, so CRXJS was out. Framework-agnosticism is something I value, but when I saw Plasmo has <em>paid plans</em> I crossed it off and went with <a href="https://wxt.dev/">WXT</a>.</p>
<p><img src="/images/blog/browser_extension_frameworks.jpg" alt="" /></p>
<p>WXT has a simple bootstrap project, and the project structure feels familiar having used Next.js and Astro before; I opted for using a <code>src/</code> directory as it was more like Astro. I didn't use much of WXT's features, but I appreciated their <a href="https://wxt.dev/guide/essentials/publishing.html">publishing page</a> telling you how and where to publish to the different storefronts.</p>
<h2>The Interesting Bits</h2>
<p>There are 3 main kinds of scripts that browser extensions can have; the <code>content.js</code> script that runs directly on the page, the <code>background.js</code> script that runs on a hidden background page (or in the case of Chrome, it's a web worker), and a <code>popup.js</code> script that runs in the popup when you click on the extension icon. These are all sandboxed into their own documents<sup><a href="#fn1">[1]</a></sup>, which means the popup and background scripts cannot interact with the web page.</p>
<p>Since the button to convert the page to an EPUB is in the popup, it needs to send a message to the content script to read the page through the readability library. Since there is only one popup script, but many content scripts, it also needs to send it to the right one.</p>
<pre><code>// popups.js
browser.tabs.query({
	active: true,
	currentWindow: true,
}).then(tabs =&gt; browser.tabs.sendMessage(tabs[0].id, {}));

// content.js
browser.runtime.onMessage.addListener(message =&gt; {
	// You need to clone the document as it will delete elements
	const documentClone = document.cloneNode(true);
	const article = new Readability(documentClone).parse();

	// Check if article exists
	if (article.content) {
		alert("No article found");
		return;
	}

	// ...
}
</code></pre>
<p>The same steps were taken from <a href="/blog/generating-epubs-for-my-blog">the previous post</a>, but using this helper function to extract the files embedded in the extension. As for images, you can't <code>fetch()</code> them in the content script dues to CORS; but you can in the popup or background scripts. The <a href="https://sharp.pixelplumbing.com/">sharp</a> library unfortunately doesn't work in the browser, so I'm extracting the images as is.<sup><a href="#fn2">[2]</a></sup></p>
<pre><code>async function getFile(path) {
	const url = browser.runtime.getURL(path);
	return (await fetch(url)).text();
}
</code></pre>
<p>Something I learned, despite already knowing this, is that using an actual HTML parser is so much easier than writing cursed regex <img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="😅" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f605.png" /> I'm also making use of it's ability to turn HTML into XHTML, as EPUBs require that.</p>
<pre><code>// Parse HTML string
const frag = document.createRange().createContextualFragment(article.content);

// ...

// Serialize to XHTML string
const content = new XMLSerializer().serializeToString(frag);
</code></pre>
<p>What's super cursed about downloading files from extensions is that you can't. At least, not normally. What you have to do is create an <code>&lt;a&gt;</code> element, turn the file into a data URL, add it to the link, and force your browser to click it. Thanks, I hate it .w.</p>
<pre><code>// Download zip file
const a = document.createElement('a');
document.body.appendChild(a);
a.style.display = 'none';
const url = window.URL.createObjectURL(zipData);
a.href = url;
a.download = title + '.epub';
a.click();
window.URL.revokeObjectURL(url);
</code></pre>
<h2>Post Mortem</h2>
<p>That's about all the interesting things I had to say about developing a cross-browser extension! I had to make a minor update after seeing the Chrome version not working, because of how it handles things slightly differently. If you want to read the source for yourself, <a href="https://codeberg.org/Chai/web-to-epub">here ya go</a>, and as for the downloads, here they are!</p>
<ul>
<li><a href="https://addons.mozilla.org/addon/web-to-epub">Firefox</a></li>
<li><a href="https://codeberg.org/Chai/web-to-epub/releases">Codeberg</a></li>
<li><a href="https://chrome.google.com/webstore/detail/igccdlajeoclenjabgnhpanejjmioijl">Chrome</a></li>
</ul>
<p>In the future I will continue to update the extension so that it provides better quality EPUBs, such as bespoke support for various websites in situations where the readability library extracts too much or too little.</p>
<hr />
<section>
<ol>
<li><p>Except for background scripts on Chrome, which have no document. <a href="#fnref1">↩︎</a></p>
</li>
<li><p>Maybe I can find other libraries that can resizes images in the browser? <a href="#fnref2">↩︎</a></p>
</li>
</ol>
</section>
]]></content>
        <category label="articles" term="articles"/>
        <category label="web-to-epub" term="web-to-epub"/>
        <category label="ebooks" term="ebooks"/>
        <category label="javascript" term="javascript"/>
        <published>2025-08-28T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[I have an 88x31 Button Now!]]></title>
        <id>https://axoga.to/blog/i-have-an-88x31-button-now</id>
        <link href="https://axoga.to/blog/i-have-an-88x31-button-now"/>
        <updated>2025-08-27T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Send me your buttons oomfies]]></summary>
        <content type="html"><![CDATA[<p>If you were on the internet around the early 2000's, you might remember <a href="https://88x31.nl/">these little guys</a>--88 by 31 pixel images used for all sorts of things, like linking to your friends or favorite sites, or proudly displaying your interests. It took me a while, but I finally have an 88x31 button of my own!</p>
<p>&lt;div class="flex flex-col items-center"&gt;
&lt;Stamp href='&lt;a href="https://axoga.to"&gt;&lt;img src="https://axoga.to/images/stamps/chai.png"&gt;&lt;/a&gt;' src="/images/stamps/chai.png" copy={true} /&gt;</p>
<p>&lt;figcaption class="w-fit"&gt;Click to copy my embed code!&lt;/figcaption&gt;
&lt;/div&gt;</p>
<p>The slick hover animation was inspired by what Kett did with <a href="https://racc.at/#stamps">their buttons</a>; in fact I was inspired by Kett in the first place to have <a href="/#stamps">my own button collection</a> ^_^" My collection right now is pretty modest, so if you want to exchange buttons I'd be happy to add yours to my front page! You can contact me through <a href="https://discord.gg/SuZb3aJUCx">my Discord server</a>, DM me on Bluesky, or comment below :3</p>
]]></content>
        <category label="articles" term="articles"/>
        <category label="indieweb" term="indieweb"/>
        <published>2025-08-27T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[JPEG XL Is Worth It]]></title>
        <id>https://axoga.to/blog/jpeg-xl-is-worth-it</id>
        <link href="https://axoga.to/blog/jpeg-xl-is-worth-it"/>
        <updated>2025-08-26T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[The superior image format Google wants to kill]]></summary>
        <content type="html"><![CDATA[<p>Today is going to be a short post about JPEG XL. For those who don't know, it's an image format that can do everything PNG, JPEG, and GIF can do, but better, and <em>smaller</em>. It can do lossless and lossy compression, it can do animations with transparency, it has progressive decoding. And Google wants to kill it.</p>
<p>I won't linger on the quantified benefits and technical details, as I want to share something else, but if you're interested here is a <a href="https://jpegxl.info/">community website</a> with excellent comparisons with other formats, a <a href="https://jpegxl.info/resources/jpeg-xl-test-page">JPEG XL test page</a>, and a video on the topic if that's more your jam.</p>
<p>&lt;figure class="rehype-figure"&gt;
&lt;iframe style="width: 100%; aspect-ratio: 16/9;" src="https://www.youtube.com/embed/FlWjf8asI4Y?si=qxGiAn5iatRMdj5w" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/figure&gt;</p>
<h2>How Is Google Killing It?</h2>
<p>If you checked out that JPEG XL test page, you probably saw this:</p>
<p><img src="/images/blog/image_format_comparison.png" alt="" /></p>
<p>Chrome removed JPEG XL support in February 2023 (and by extension, all Chromium-based browsers too), and Firefox being the Google-funded puppet they are, wanted to remain "neutral" on the topic. Safari does support the image format, so kudos to Apple for that.</p>
<p>So how do you use this image format if most of the big browsers don't support it? With browser extensions! Here is one for <a href="https://chromewebstore.google.com/detail/jpeg-xl-viewer/bkhdlfmkaenamnlbpdfplekldlnghchp">Chrome</a> and <a href="https://addons.mozilla.org/en-US/firefox/addon/jxl">Firefox</a>. In my short time testing them, they are not perfect. They don't support animations, and they sometimes bug out with <code>&lt;picture&gt;</code> elements like on the test page, but they're a free and easy way to add support to your browser.</p>
<p>If you want better JPEG XL support and you're willing to change browsers, here's a probably exhaustive list of the ones that do:</p>
<ul>
<li><em>Chromium-based</em>
<ul>
<li>Thorium</li>
<li>Chromite</li>
</ul>
</li>
<li><em>Firefox-based</em>
<ul>
<li>Waterfox</li>
<li>Floorp</li>
<li>Pale Moon</li>
<li>Basilisk</li>
</ul>
</li>
<li><em>Mac-OS only</em>
<ul>
<li>Safari</li>
<li>Orion</li>
</ul>
</li>
</ul>
<p>&lt;figcaption&gt;Allegedly Firefox Nightly supports it with a flag, but Kett said it didn't do anything.&lt;/figcaption&gt;</p>
<p>And if you have your own blog or website, try to use JPEG XLs! With a <code>&lt;picture&gt;</code> element you can have a fallback for browsers that don't support it. Here is a sample code for your use.</p>
<pre><code>&lt;picture&gt;
	&lt;source srcset="image.jxl" type="image/jxl"&gt;
	&lt;img src="image.png"&gt;
&lt;/picture&gt;
</code></pre>
]]></content>
        <category label="articles" term="articles"/>
        <category label="jpeg xl" term="jpeg xl"/>
        <published>2025-08-26T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Jailbreaking My Kindle]]></title>
        <id>https://axoga.to/blog/jailbreaking-my-kindle-4</id>
        <link href="https://axoga.to/blog/jailbreaking-my-kindle-4"/>
        <updated>2025-08-25T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Prying Amazon's cold dead hands off my device]]></summary>
        <content type="html"><![CDATA[<p>About a month ago a YouTube video titled <em><a href="https://www.youtube.com/watch?v=Qtk7ERwlIAk">It's Time to Jailbreak Your Kindle.</a></em> appeared on my feed, and I took interest by stashing it into my gigantic watch later playlist. With all the EPUB stuff I've been working on lately (including a web to EPUB extension in review!) I wanted to see what jailbreaking my Kindle would offer, particularly a Kindle as old as mine.</p>
<p>The go-to site is the <a href="https://kindlemodding.org/">Kindle Modding Wiki</a>, which will guide you through jailbreaking every model of Kindle, installing homebrew, and installing the KOReader document viewer. (And even remove ads!)</p>
<p>I was confident I could jailbreak and install all the homebrew I wanted in half an hour, but a lot of the <a href="https://kindlemodding.org/jailbreaking/post-jailbreak/">post jailbreak</a> instructions are centered around the new models, and I hit several bumps that took me down the forum rabit hole. So if you have a Kindle 4 like mine, these pointers will be helpful for you!</p>
<ol>
<li>
<p><strong>Install MKK</strong></p>
<p>Normally the jailbreak will automatically install the <strong>Mobileread Kindlet Kit</strong>, but not for old Kindles. You can find the download in <a href="https://www.mobileread.com/forums/showthread.php?t=225030">this thread</a> under "KUAL &amp; KUAL extensions". Copy the correct file to your root folder and update your Kindle.</p>
</li>
<li>
<p><strong>Do not install MRPI</strong></p>
<p>Old Kindles do not use MRPI to install extensions, so do not install MRPI. The <code>;log mrpi</code> command will not work as a result, so don't bother with it. Instead, you install extensions by putting their bin file in the root folder and updating your Kindle.</p>
</li>
<li>
<p><strong>Do not install <code>Update_KUALBooklet_*_install.bin</code></strong></p>
<p>This is only usuable if you use MRPI, and since we can't use MRPI, this is useless. Instead, you want to copy <code>KUAL-KDK-1.0.azw2</code> to your documents folder.</p>
</li>
<li>
<p><strong>Install updated certificates</strong></p>
<p>Awkwardly, the certificates for KUAL expired this year, so it will not run. Go to <a href="https://www.mobileread.com/forums/showpost.php?p=4506164&amp;postcount=1295">this post</a> and download the DevCerts zip file. Copy the correct file to your root folder and update your Kindle.</p>
</li>
</ol>
<p>This tip is for all Kindles, but if you want to install <a href="https://wiki.mobileread.com/wiki/Kindle_Screen_Saver_Hack_for_all_2.x,_3.x_%26_4.x_Kindles">custom screensavers</a> on your Kindle, you need to get rid of the special offers screensavers. Kindle devs will not offer any help how to do this as they do not want to get on the bad side of Amazon. But I'm not a Kindle dev, am I? <img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="😉" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f609.png" /></p>
<p>So to remove ads, download the <a href="https://scriptlets.notmarek.com/">Disable ADs</a> scriptlet and <a href="/downloads/disable_ads.zip">these two files</a>. Extract all files to this directory <code>K:/extensions/disable_ads/</code>, then run the extension from KUAL.</p>
<h2>Post Mortem</h2>
<p>Rather anti-climactically, I ended up not liking KOReader, so I'm just going to stick with the vanilla reader, even if it doesn't support EPUBs. But I <em>do</em> love having custom screensavers on my Kindle!</p>
<p>I also learned that with a jailbroken Kindle I can run Python scripts that can turn it into a <a href="https://github.com/scolby33/weather_kindle">weather forecast</a> display, among other cool things <img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="👀" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f440.png" /> Maybe I'll buy a cheap Kindle or two from eBay to play around with...</p>
]]></content>
        <category label="articles" term="articles"/>
        <category label="technical" term="technical"/>
        <category label="ebooks" term="ebooks"/>
        <published>2025-08-25T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[The Last Great Kindle]]></title>
        <id>https://axoga.to/blog/the-last-great-kindle</id>
        <link href="https://axoga.to/blog/the-last-great-kindle"/>
        <updated>2025-08-24T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[The best ereader no one will make again]]></summary>
        <content type="html"><![CDATA[<p>My first and only ereader is a fourth-generation Kindle my mom got me in 2012, 13 years ago. That's a very long time in the world of rapid technological advancement, so you'd think that in that time they have made better ereaders. Right? <em>Right??</em></p>
<p><em>Sigh.</em></p>
<p>Credit where credit is due, there <em>have</em> been some excellent new features, like higher DPI, Bluetooth, warm backlights, and USB-C. But those upsides are not worth the greatest sacrifice Amazon has made--the death of the page turn button.</p>
<p>Now, you may point out there are plenty of other ereader brands who still rock page turn buttons (and that's great and all), but my problem with them is <strong>they all suck.</strong> Like, what's with these ridiculous pill buttons?? Why does every ereader only use this one design?</p>
<p><img src="/images/blog/kindle_oasis.jpg" alt="" title="A Kindle Oasis" /></p>
<p>What the Kindle 4 had were these bevel-style page turn buttons. They sat comfortably under your thumb, and were easy to press. The next page button was notably larger than the previous page button. And the best part? The buttons were on both sides!</p>
<p><img src="/images/blog/kindle_1.jpg" alt="" title="The bevel-style page turn buttons of the Kindle 4" /></p>
<p>But it gets better, cause the Kindle 4 only had buttons for navigation due to it not having a touchscreen. These are a tactile joy to use over touchscreens; a home button, a keyboard button, a menu button, a back button, and a 5-way controller. There even used to be a Kindle with an entire keyboard, which I imagine would be excellent for those who take notes often.</p>
<p><img src="/images/blog/kindle_2.jpg" alt="" title="My Kindle 4" /></p>
<p>Recently, I came across the Nook GlowLight 4 Plus, which has what appear to be bevel page turn buttons!! So I headed on over to my local Barnes &amp; Nobles, ready to buy the ereader, and they had a display model out. I checked it out and was... sorely dissapointed. It's not a real bevel button :[</p>
<p>The hinges are between the button pairs, so the top buttons tilt up, and the bottom ones tilt down, meaning you can't press them near the hinge. This tactile feeling did not inspire joy for me. On top of that, the UX was a bit of a mess, and I really didn't love using the touchscreen to navigate around.</p>
<p>I would have liked to at least give it a fair try, but Barnes &amp; Nobles did not have a return policy on their ereaders, so I didn't want to put down $200 on a device I wasn't sure about.</p>
<p><img src="/images/blog/nook.jpg" alt="" title="A Nook GlowLight 4 Plus" /></p>
<p>Aside from the tragic loss of the page turn button, there's another trend amongst ereader makers that I find dubiously questionable: running Android. Sure, it's a far more flexible and hackable operating system than traditional ereader OS's, but it comes at a great cost to their battery lives. While Kindles and Nooks have battery lifes of up to a month, Android-powered ereaders <strong>only last a day.</strong></p>
<p>eReaders can last a long time off the grid, they can have a great tactile experience, and they can be read outside in the direct sun. Sacrificing the greatest advantages ereaders have to compete with smartphones is simply stupid in my opinion. And some of them don't even hide it.</p>
<p><img src="/images/blog/palma.jpg" alt="" title="A Boox Palma" /></p>
<p>I wish ereader makers would take note and release a modern take on the Kindle 4. Warm backlights, USB-C, Bluetooth and/or a headphone jack to listen to audiobooks, a super power-efficient OS, and <em>no touchscreens.</em></p>
<p>I guess a girl can only dream <img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🥀" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f940.png" /></p>
]]></content>
        <category label="articles" term="articles"/>
        <category label="ebooks" term="ebooks"/>
        <category label="opinion" term="opinion"/>
        <published>2025-08-24T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[How I Built My Ask Box]]></title>
        <id>https://axoga.to/blog/how-i-built-my-ask-box</id>
        <link href="https://axoga.to/blog/how-i-built-my-ask-box"/>
        <updated>2025-08-23T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[I like answering silly questions :3]]></summary>
        <content type="html"><![CDATA[<p>After seeing <a href="https://racc.at/ask/">Kett's ask box</a> I wanted to have my own <a href="/ask">ask box</a>, it seemed pretty fun to recieve and answer questions from friends and mutuals, and as a nerd who likes making stuff myself, this was no exception! Since my backend is <a href="https://astro.build/">Astro</a>, the implentation will be particular to how Astro does things, but the concepts are transferable.</p>
<p>Without further ado, let's start with the client side of things. It is just an HTML form that posts to the same URL when the button is pressed. <code>autocomplete="off"</code> prevents your browser from trying to automatically fill it with your information, and the question is the only <code>required</code> input.</p>
<pre><code>&lt;form method="POST" autocomplete="off"&gt;
	&lt;label&gt;
		Name
		&lt;input type="text" name="name" placeholder="anonymous" /&gt;
	&lt;/label&gt;
	&lt;label class="offscreen"&gt;
		Email
		&lt;input type="email" name="email" tabindex="-1" /&gt;
	&lt;/label&gt;
	&lt;label&gt;
		Question
		&lt;textarea name="question" rows="5" required /&gt;
	&lt;/label&gt;
	&lt;button&gt;Ask&lt;/button&gt;
&lt;/form&gt;
</code></pre>
<p>The email input is actually a honeypot! The <code>tabindex</code> is set to <code>-1</code> to prevent you from navigating to it by keyboard, and the <code>offscreen</code> class hides it off-screen. This is preferred to using <code>display: none;</code> or <code>visiblity: hidden;</code> as simple bots can test for this without needing a full browser to test if a position is on-screen.</p>
<pre><code>.offscreen {
	position: fixed;
	top: 100%;
	left: 100%;
}
</code></pre>
<p>In the frontmatter of the ask box page I have a separate route to handle POST requests, and it has a simple IP-based rate limiter using the <a href="https://www.npmjs.com/package/lambda-rate-limiter">lambda-rate-limiter</a> library. For this to work properly, I need to mark the page as dynamic.</p>
<pre><code>// It is stored as a singleton in the config file


export const prerender = false;

if (Astro.request.method === "POST") {
	try {
		await rateLimit(2, Astro.clientAddress);
	} catch (error) {
		return new Response("Too many requests", { status: 429 });
	}

	// ...
}
</code></pre>
<p>After passing the rate limiter, I fetch the form data, and if the name was empty I change it to anonymous.</p>
<pre><code>const data = await Astro.request.formData();
let name = data.get("name");
const question = data.get("question");

if (name === "") { name = "anonymous"; }
</code></pre>
<p>If the question was empty (because you bypassed the <code>required</code> attribute) or the email was filled, the honeypot activates! It logs your name and IP address, and ignores your question.</p>
<pre><code>if (question === "") {
	throw new Error(`Honeypot triggered:\n  ${name} from ${Astro.clientAddress}`);
}
if (data.get("email")) {
	throw new Error(`Honeypot triggered:\n  ${name} asked "${question}" from ${Astro.clientAddress}`);
}
</code></pre>
<p>Next, I read the <code>askbox.json</code> file, push the new question to the end of the list, and write it back to the disk.</p>
<pre><code>let askboxJson = fs.readFileSync("./src/data/askbox.json", "utf-8");
let askbox: any[] = JSON.parse(askboxJson);

askbox.push({
	name: name,
	question: question,
	answer: "",
	date: global.Date.now(),
	id: (askbox.at(-1).id ?? -1) + 1
});

askboxJson = JSON.stringify(askbox, null, "\t");
fs.writeFileSync("./src/data/askbox.json", askboxJson, "utf-8");
</code></pre>
<p>Because my website is pulled from a git repo, I can actually push the changes straight to the repo, letting me easily answer questions anywhere I can login to GitHub! I include "[skip ci]" in the commit message to prevent it from activating my deploy action.</p>
<pre><code>// Only push if in the production environment
if (import.meta.env.PROD) {
	exec('git add ./src/data/askbox.json &amp;&amp; git commit -m "[skip ci] Askbox" &amp;&amp; git push', { encoding: "utf-8" });
}
</code></pre>
<p>To alert me to new questions, I send a webhook to a private Discord channel, and then I redirect the client to a funny URL name :3</p>
<pre><code>fetch("https://discord.com/api/webhooks/xxxxxxxxxx", {
	method: "POST",
	headers: { "Content-type": "application/json" },
	body: JSON.stringify({
		content: `**${name}** asked you a question!\n\n&gt;&gt;&gt; ${question}`
	})
});

return Astro.redirect("/ask?ed");
</code></pre>
<p>Rendering the questions is a matter of loading the json file, filtering out unanswered questions, and mapping them to HTML. With Astro I use content collections to give myself a super easy API to query the json file.</p>
<pre><code>const askbox = (await getCollection("askbox"))
	.map(entry =&gt; entry.data)
	.filter(entry =&gt; entry.answer)
	.reverse();
</code></pre>
<p>Yep, that's really it!</p>
<p>If you're curious, the question rendering looks like this. Each entry in the askbox is mapped to an HTML fragment and filled with the entry's data. <code>&lt;Date /&gt;</code> is a custom JSX component I made that handles rendering dates the way I like. <code>entry.answer</code> is actually markdown, but you can assume it is HTML for simplicity.</p>
<pre><code>{askbox.map(entry =&gt; (
	&lt;hr&gt;
	&lt;div&gt;
		&lt;blockquote class="question"&gt;
			&lt;div class="name-and-date"&gt;
				&lt;span&gt;{entry.name}&lt;/span&gt;
				&lt;Date date={entry.date} /&gt;
			&lt;/div&gt;
			{entry.question.split("\n").map(line =&gt; (
				&lt;p&gt;{line}&lt;/p&gt;
			))}
		&lt;/blockquote&gt;
		&lt;div class="answer" set:html={entry.answer} /&gt;
	&lt;/div&gt;
))}
</code></pre>
]]></content>
        <category label="articles" term="articles"/>
        <category label="astro" term="astro"/>
        <category label="javascript" term="javascript"/>
        <published>2025-08-23T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Generating EPUBs for My Blog]]></title>
        <id>https://axoga.to/blog/generating-epubs-for-my-blog</id>
        <link href="https://axoga.to/blog/generating-epubs-for-my-blog"/>
        <updated>2025-08-22T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[You can read my blog posts on ur Kindle now]]></summary>
        <content type="html"><![CDATA[<p><a href="/blog/making-an-epub-manually">Making EPUBs by hand</a> is tedious work, so I automated the process for my whole blog! At the top right corner there is now a "Read Offline" button that downloads an EPUB that you can sideload on your ereaders. If you have a Kindle, you can use your Send-to-Kindle email and it will convert it to a format your Kindle can read.</p>
<h2>How It's Made</h2>
<p>Since EPUBs are just ZIP archives, I'm using <a href="https://stuk.github.io/jszip/">JSZip</a> to assemble them; here is a simplified example of that process. The template files are based on my work from my <a href="/blog/making-an-epub-manually">previous blog post</a>. It was quite easy working with this library, so I'm excited to apply this knowledge for future projects!</p>
<pre><code>

const zip = new JSZip();

// Read template files and replace $TOKENS
const toc_ncx = fs.readFileSync('src/assets/epub/toc.ncx')
	.toString()
	.replaceAll('$SLUG', entry.id)
	.replaceAll('$TITLE', post.title);

// Add files to zip, with mimetype as the first file,
zip.file('mimetype', 'application/epub+zip');
zip.file('OEBPS/toc.ncx', toc_ncx);

// Generate zip file
const zipData = await zip.generateAsync({
	type: "blob",
	streamFiles: true
});
</code></pre>
<p>I used the same code as my RSS feed to parse the markdown into XHTML. I run an extra processing step to surround <code>&lt;img&gt;</code> elements in a <code>&lt;div&gt;</code>, downscale the image in <a href="https://sharp.pixelplumbing.com/">sharp</a>, convert it to a JPG, add it to the zip, and point the <code>src</code> attribute to the new file location.</p>
<pre><code>// Downscale image and convert
const imageBuf = await sharp(`public/${src}`)
	.resize(1200, null, { withoutEnlargement: true })
	.jpeg()
	.toBuffer();

// Get filename and store it for later
const filename = src.match(/.+\/(.+)\..+/)[1];
images.push(filename);

// Add image to zip, and return modified &lt;img&gt;
zip.file(`OEBPS/images/${filename}.jpg`, imageBuf);
return `&lt;div&gt;&lt;img src="../images/${filename}.jpg" alt="" /&gt;&lt;/div&gt;`;
</code></pre>
<p>This is run inside of the callback of an async <code>replaceAll()</code> from the <a href="https://www.npmjs.com/package/str-async-replace">str-async-replace</a> package because sharp's <code>toBuffer()</code> is async. Here's the regex pattern it uses.</p>
<pre><code>/&lt;p&gt;&lt;img.+?src="(.+?)".+?\/&gt;&lt;\/p&gt;/g
</code></pre>
<p>And then I replace an <code>$IMAGES</code> token in the <code>content.opf</code> template with the results of this function. This is necessary because EPUBs require that all files are declared in the manifest.</p>
<pre><code>images.map(img =&gt; `&lt;item id="${img}" href="images/${img}.jpg" media-type="image/jpeg"/&gt;`).join('\r\n\t\t')
</code></pre>
<h2>Post Mortem</h2>
<p>This was super cool to make! I think what ereaders need is a first-class reading experience for online sources, like I am now offering for my blog. There are still some quirks with the system--emojis and certain images don't load--but I'll keep working on it over time as I notice any issues.</p>
<p>Happy readings! <img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="📖" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f4d6.png" /> <img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🎉" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f389.png" /></p>
]]></content>
        <category label="articles" term="articles"/>
        <category label="web-to-epub" term="web-to-epub"/>
        <category label="ebooks" term="ebooks"/>
        <category label="javascript" term="javascript"/>
        <published>2025-08-22T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Making an EPUB Manually]]></title>
        <id>https://axoga.to/blog/making-an-epub-manually</id>
        <link href="https://axoga.to/blog/making-an-epub-manually"/>
        <updated>2025-08-21T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[It's an ebook file thing]]></summary>
        <content type="html"><![CDATA[<p>I was on a cross-country RV trip recently, and to pass the time I was watching YouTube videos, playing <em>The Legend of Zelda: Echoes of Wisdom</em> on my Switch, and reading a lot of online articles. Reading on my phone isn't ideal, but it <em>is</em> the most convenient. I still have a 4th gen Kindle that my mom got me in 2012, so it got me thinking if I can read web articles on it.</p>
<p>I checked out some web-to-epub converters in extension and website formats, but a lot of them outputted bad typography and poorly sized images. There was one extension that did a decent enough job, but they only gave me 10 free credits to use :/</p>
<h2>Dissecting the EPUB</h2>
<p>I read somewhere that EPUBs are just a zip file with HTML, CSS, and XML, so if I could hack one together, I could figure out how to make my own converter! Using one of the EPUBs that I converted from an article, I changed the extension to ZIP and started poking around inside.</p>
<pre><code>|-- META-INF/
|   |-- container.xml
|-- OEBPS/
|   |-- images/
|   |   |-- image_001.png
|   |   |-- image_002.png
|   |-- text/
|   |   |-- chapter_001.xhtml
|   |   |-- chapter_002.xhtml
|   |   |-- chapter_003.xhtml
|   |-- styles/
|   |   |-- stylesheet.css
|   |-- content.opf
|   |-- toc.ncx
|-- mimetype
</code></pre>
<p>The <code>mimetype</code> file is the simplest, as it is just this text. It lets ereaders know this is an EPUB file.</p>
<pre><code>application/epub+zip
</code></pre>
<p>The next file an ereader checks is <code>META-INF/container.xml</code>, which points to the root of the EPUB.</p>
<pre><code>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container"&gt;
	&lt;rootfiles&gt;
		&lt;rootfile full-path="OEBPS/content.opf" media-type="application/oebps-package+xml"/&gt;
	&lt;/rootfiles&gt;
&lt;/container&gt;
</code></pre>
<p>And that file it's pointing to is <code>OEBPS/content.opf</code>, which is just another XML file. This file declares the ebook's metadata, and a manifest that points to all the files it will be using; chapters, stylesheets, images, and the table of contents (<code>toc.ncx</code>).</p>
<pre><code>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;package xmlns="http://www.idpf.org/2007/opf" unique-identifier="BookID" version="2.0"&gt;
	&lt;metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf"&gt;
		&lt;dc:title&gt;Title of the Book&lt;/dc:title&gt;
		&lt;dc:creator&gt;Author Name&lt;/dc:creator&gt;
		&lt;dc:date opf:event="publication"&gt;2025-08-21&lt;/dc:date&gt;
		&lt;dc:language&gt;en&lt;/dc:language&gt;
		&lt;dc:identifier id="BookID" opf:scheme="CustomID"&gt;unique-identifier&lt;/dc:identifier&gt;
	&lt;/metadata&gt;

	&lt;manifest&gt;
		&lt;item id="toc" href="toc.ncx" media-type="application/x-dtbncx+xml"/&gt;
		&lt;item id="styles" href="styles/stylesheet.css" media-type="text/css"/&gt;
		&lt;item id="chapter_001" href="text/chapter_001.xhtml" media-type="application/xhtml+xml"/&gt;
		...
		&lt;item id="image_001" href="images/image_001.png" media-type="image/png"/&gt;
		...
	&lt;/manifest&gt;

	&lt;spine toc="toc"&gt;
		&lt;itemref idref="chapter_001"/&gt;
		&lt;itemref idref="chapter_002"/&gt;
		&lt;itemref idref="chapter_003"/&gt;
	&lt;/spine&gt;
&lt;/package&gt;
</code></pre>
<p>The <code>toc.ncx</code> is yet another XML file, and it lets you construct nav points that point to not just chapters, but headings within those chapters too.</p>
<pre><code>&lt;?xml version="1.0" encoding="UTF-8" ?&gt;
&lt;!DOCTYPE ncx PUBLIC "-//NISO//DTD ncx 2005-1//EN" "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd"&gt;
&lt;ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1" xml:lang="en"&gt;
	&lt;head&gt;
		&lt;meta name="dtb:uid" content="unique-identifier"/&gt;
		&lt;meta name="dtb:depth" content="1"/&gt;
		&lt;meta name="dtb:totalPageCount" content="0"/&gt;
		&lt;meta name="dtb:maxPageNumber" content="0"/&gt;
	&lt;/head&gt;

	&lt;docTitle&gt;
		&lt;text&gt;Title of the Book&lt;/text&gt;
	&lt;/docTitle&gt;

	&lt;docAuthor&gt;
		&lt;text&gt;Author Name&lt;/text&gt;
	&lt;/docAuthor&gt;

	&lt;navMap&gt;
		&lt;navPoint class="chapter" id="navPoint-1" playOrder="1"&gt;
			&lt;navLabel&gt;
				&lt;text&gt;Chapter 1&lt;/text&gt;
			&lt;/navLabel&gt;
			&lt;content src="text/chapter_001.xhtml"/&gt;
		&lt;/navPoint&gt;
		...
	&lt;/navMap&gt;
&lt;/ncx&gt;
</code></pre>
<p>And finally, the chapter file itself. It's quite similar to normal HTML pages served on the web, however, it is <em>not</em> HTML. It is <strong>XHTML</strong>, which requires all elements to be closed (<code>&lt;br /&gt;</code>). Specific to EPUB 2.0, images need to be inside a <code>div</code>, for some reason. The rest of the files like the CSS are just as you would expect them.</p>
<pre><code>&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"&gt;
&lt;html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"&gt;
	&lt;head&gt;
		&lt;link rel="stylesheet" href="../styles/stylesheet.css" type="text/css" /&gt;
		&lt;title&gt;Title of the Book&lt;/title&gt;
	&lt;/head&gt;

	&lt;body&gt;
		&lt;h1&gt;Title of the Book&lt;/h1&gt;
		&lt;hr /&gt;
		&lt;p&gt;Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.&lt;/p&gt;
		&lt;div&gt;&lt;img src="../images/image_001.png" /&gt;&lt;/div&gt;
	&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<h2>Putting It Back Together</h2>
<p>Now that I've dissected the EPUB, I turned my <a href="/blog/why-i-want-to-blog">first blog post</a> into one and got it working in a browser ereader on my PC. Because Kindles do not support EPUB I needed to convert it to a MOBI, but the two converters I tried were throwing errors. Maybe I made a mistake somewhere?</p>
<p>I found this <a href="https://www.w3.org/publishing/epubcheck/">EPUB validator</a> and ran it on my ebook... <code>Mimetype file entry is missing or is not the first file in the archive.</code> ...okay, that's weird, cause it is <em>right there</em>. There were other issues that cropped up, including one with only one Google search result from 2006, but I got those fixed.</p>
<p>A quick search later, and the validator expects that <code>mimetype</code> is not only the first file in the zip archive, but <em>also</em> that it is uncompressed =_= A StackOverflow post suggested renaming it to <code>##mimetype</code> so that it is alphabetically sorted to the front, then rename it back to <code>mimetype</code> from inside the archive. Yeesh.</p>
<p>Anyways, once it passed the validator, it was able to be converted to a MOBI, and after a quick transfer to my Kindle, here is the screenshot! And here is a <a href="/downloads/why-i-want-to-blog.epub">download</a> to the eblog post :3</p>
<p><img src="/images/blog/kindle_screenshot.png" alt="" /></p>
<h2>Post Mortem</h2>
<p>Finding information on the file structure of EPUBs was a little difficult as most people suggest to just use an existing editor that handles it all for you. The validation stuff was annoying, but not too bad. All in all, now that I know how to do this, doing it again will be super easy--and I hope that documenting it here makes it easy for others to try it themselves!</p>
<p>There are two things I want to try next. There is that browser extension idea so I can easily and freely convert articles to EPUBs and read them on my Kindle. The other idea I got after using my blog post as a sample, is to offer EPUBs of my blog posts! I don't know how hard that would be, but it is something I'd like to learn.</p>
]]></content>
        <category label="articles" term="articles"/>
        <category label="web-to-epub" term="web-to-epub"/>
        <category label="technical" term="technical"/>
        <category label="ebooks" term="ebooks"/>
        <published>2025-08-21T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Apps I Use (2024)]]></title>
        <id>https://axoga.to/blog/apps-i-use-2024</id>
        <link href="https://axoga.to/blog/apps-i-use-2024"/>
        <updated>2025-08-20T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[I came across a post by Abhinav and the apps he uses, and saw there was a whole collection of people writing blog posts about their app defaults, so I wanted to join in the fun :3
]]></summary>
        <content type="html"><![CDATA[<p>I came across a post by <a href="https://notes.abhinavsarkar.net/2024/default-apps">Abhinav</a> and the apps he uses, and saw there was a whole <a href="https://defaults.rknight.me/">collection</a> of people writing blog posts about their app defaults, so I wanted to join in the fun :3</p>
<p>&lt;figcaption&gt;I'm closer to early than late, but I will be writing this from the context of the apps I used by the end of last year.&lt;/figcaption&gt;</p>
<ul>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="📨" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f4e8.png" /> <strong>Mail Client</strong>
<ul>
<li>Thunderbird (PC)</li>
<li>K-9 Mail (Android)</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="📮" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f4ee.png" /> <strong>Mail Server</strong>
<ul>
<li>Gmail</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="📝" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f4dd.png" /> <strong>Notes</strong>
<ul>
<li>Obsidian</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="✅" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/2705.png" /> <strong>To-Do</strong>
<ul>
<li><a href="https://github.com/ransome1/sleek">sleek</a> (PC)</li>
<li><a href="https://c306.net/apps/#todotxtandroidCard">Todo.txt</a> (Android)</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="📷" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f4f7.png" /> <strong>Photo Shooting</strong>
<ul>
<li>Samsung Camera</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🖼" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f5bc.png" /> <strong>Photo Management</strong>
<ul>
<li>Samsung Gallery</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="📆" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f4c6.png" /> <strong>Calendar</strong>
<ul>
<li>Google Calendar</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="📁" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f4c1.png" /> <strong>Cloud File Storage</strong>
<ul>
<li>OneDrive</li>
<li><a href="https://syncthing.net/">SyncThing</a></li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="📖" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f4d6.png" /> <strong>RSS</strong>
<ul>
<li>N/A</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="👤" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f464.png" /> <strong>Contacts</strong>
<ul>
<li>Samsung Contacts</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🌐" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f310.png" /> <strong>Browser</strong>
<ul>
<li>Brave</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="💬" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f4ac.png" /> <strong>Chat</strong>
<ul>
<li>Discord</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🔖" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f516.png" /> <strong>Bookmarks</strong>
<ul>
<li><a href="https://raindrop.io/">Raindrop.io</a></li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="📑" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f4d1.png" /> <strong>Read It Later</strong>
<ul>
<li><a href="https://raindrop.io/">Raindrop.io</a></li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="📜" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f4dc.png" /> <strong>Word Processing</strong>
<ul>
<li>LibreOffice</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="📈" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f4c8.png" /> <strong>Spreadsheets</strong>
<ul>
<li>LibreOffice</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="📊" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f4ca.png" /> <strong>Presentations</strong>
<ul>
<li>LibreOffice</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🛒" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f6d2.png" /> <strong>Shopping Lists</strong>
<ul>
<li>anywhere I can type</li>
<li>Whisk (Food)</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🍴" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f374.png" /> <strong>Meal Planning</strong>
<ul>
<li>Whisk</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="💰" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f4b0.png" /> <strong>Budgeting and Personal Finance</strong>
<ul>
<li><a href="https://hledger.org/">hledger</a></li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="📰" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f4f0.png" /> <strong>News</strong>
<ul>
<li>Google News</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🎵" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f3b5.png" /> <strong>Music</strong>
<ul>
<li>Spotify</li>
<li>Plex</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🎤" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f3a4.png" /> <strong>Podcasts</strong>
<ul>
<li>N/A</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🔐" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f510.png" /> <strong>Password Management</strong>
<ul>
<li>Bitwarden</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="⌨" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/2328.png" /> <strong>Code Editor</strong>
<ul>
<li>Visual Studio Code</li>
<li>Visual Studio (C#, Unity)</li>
<li>IntelliJ IDEA (Java)</li>
</ul>
</li>
<li><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🎨" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f3a8.png" /> <strong>Art</strong>
<ul>
<li>Clip Studio Paint</li>
<li><a href="https://allusion-app.github.io/">Allusion</a> (Visual Library)</li>
<li><a href="https://www.pureref.com/">PureRef</a> (Reference Tool)</li>
</ul>
</li>
</ul>
]]></content>
        <category label="articles" term="articles"/>
        <category label="apps-i-use" term="apps-i-use"/>
        <published>2025-08-20T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[YouTube Is Better With RSS]]></title>
        <id>https://axoga.to/blog/youtube-is-better-with-rss</id>
        <link href="https://axoga.to/blog/youtube-is-better-with-rss"/>
        <updated>2025-08-19T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Choose what videos and playlists you want to subscribe to]]></summary>
        <content type="html"><![CDATA[<p>Did you know that every YouTube channel has a <strong>secret RSS feed</strong>? Short for "Really Simple Syndication", it is an XML file that contains the latest entries published on a website, which you can subscribe to using a feed reader. Practically all blogs and news sites support RSS, even forums like Reddit too! The <a href="https://aboutfeeds.com/">About Feeds</a> website goes into more detail and shows you how to get one yourself if you're interested.</p>
<p>Being in control of your YouTube feed is nice, but most people probably don't care about that; the YouTube app is just more convenient. But there is an ace up the metaphorical RSS sleeve. <em>Three aces, in fact.</em></p>
<ol>
<li>
<p>A good feed reader lets you filter out entries using <strong>filter rules</strong>. YouTube RSS feeds don't have tags, but you can still filter by the video title and description.</p>
</li>
<li>
<p>Did you know every YouTube playlist has a <strong>secret RSS feed</strong> too? If you only care about a channel's specific series (and they are kind enough to make a playlist for it), you can subscribe to just those videos!</p>
</li>
<li>
<p>Saving the best for last, every YouTube channel <em>also</em> has <strong>3 secret playlists</strong>... One for just videos, one for just shorts, and one for livestreams. <em>Yes</em>, you can subscribe to <em>just</em> videos and not be flooded with shorts spam. This is freaking awesome.</p>
</li>
</ol>
<p>To access these feeds, you either need the channel ID or the playlist ID, which can be found in the YouTube URL. If instead of a channel ID you see a <code>@username</code>, search up "youtube channel id finder". Now just paste it at the end of the correct URL.</p>
<pre><code>https://www.youtube.com/feeds/videos.xml?channel_id=
</code></pre>
<pre><code>https://www.youtube.com/feeds/videos.xml?playlist_id=
</code></pre>
<p>To get the playlist ID for just videos or shorts or livestreams, change the first two letters of the channel ID (which will always be <code>UC</code>) to any of these other letters.</p>
<table>
<thead>
<tr>
<th>Videos</th>
<th>Shorts</th>
<th>Livestreams</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>UULF</code></td>
<td><code>UUSH</code></td>
<td><code>UULV</code></td>
</tr>
</tbody>
</table>
]]></content>
        <category label="articles" term="articles"/>
        <category label="youtube" term="youtube"/>
        <category label="rss" term="rss"/>
        <published>2025-08-19T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[You Should Have a Website, Actually]]></title>
        <id>https://axoga.to/blog/you-should-have-a-website-actually</id>
        <link href="https://axoga.to/blog/you-should-have-a-website-actually"/>
        <updated>2025-07-27T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Imagine if your Discord bio was like, bigger, and way cooler]]></summary>
        <content type="html"><![CDATA[<p>The internet is great for learning nerd shit and contributing to rampant consumerism, but what it <em>really</em> is good for is <strong>self-expression</strong>! <img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="✨" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/2728.png" /> For decades the internet has been a digital canvas for netizens to talk about anything, share their interests, or just make a really fun and interesting web page to explore.</p>
<p>And I've seen some super visually creative websites before; although I wish I had the talent to pull that off, I'm happy with how clean this website looks ^.^</p>
<p>So what can you do with a website? Lots of things! Here's a list of ideas to jumpstart your inspiration :3</p>
<ul>
<li>Talk about your hobbies and interests
<ul>
<li>Video games</li>
<li>Programming</li>
<li>Airsoft</li>
<li>Camping</li>
<li>Collections</li>
</ul>
</li>
<li>Share things you have created
<ul>
<li>Photographs</li>
<li>Writing</li>
<li>Drawings</li>
<li>Arts &amp; crafts</li>
<li>Screenshots of Minecraft builds</li>
</ul>
</li>
<li>Use it as your bio and put all your social media links there</li>
<li>Post your commission sheet information, like <a href="https://boorakun.carrd.co/#coms">Boorakun</a></li>
<li>Write a blog about something you like or are knowledgeable in</li>
<li>Create a visually interesting webpage</li>
<li>Write fiction in character, like the <a href="https://scp-wiki.wikidot.com/">SCP wiki</a> or <a href="https://cosmic.voyage/">Cosmic Voyage</a></li>
</ul>
<h2>Free Options</h2>
<p>It used to be the case that you needed either money or the technical knowledge to design and host a website, but there are plenty of free options nowadays, and some even come with great visual editors!</p>
<table>
<thead>
<tr>
<th>Site</th>
<th>Subdomain</th>
<th>Custom Domain<sup><a href="#fn1">[1]</a></sup></th>
<th>Visual Editor</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="https://carrd.co/">Carrd</a></td>
<td>*.carrd.co</td>
<td>$19/year (~$1.58/month)</td>
<td><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="✅" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/2705.png" /></td>
</tr>
<tr>
<td><a href="https://neocities.org/">Neocities</a></td>
<td>*.neocities.org</td>
<td>$5/month</td>
<td><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="❌" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/274c.png" /></td>
</tr>
<tr>
<td><a href="https://nekoweb.org/">Nekoweb</a></td>
<td>*.nekoweb.org</td>
<td>$3/month</td>
<td><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="❌" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/274c.png" /></td>
</tr>
<tr>
<td><a href="https://mmm.page/">mmm.page</a></td>
<td>*.mmm.page</td>
<td>$12/month</td>
<td><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="✅" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/2705.png" /></td>
</tr>
<tr>
<td><a href="https://straw.page/">Straw.Page</a></td>
<td>*.straw.page</td>
<td>$49/year (~$4.08/month)</td>
<td><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="✅" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/2705.png" /></td>
</tr>
<tr>
<td><a href="https://pages.github.com/">Github Pages</a></td>
<td>*.github.io</td>
<td>Free</td>
<td><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="❌" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/274c.png" /></td>
</tr>
<tr>
<td><a href="https://slidde.co/">slidde</a></td>
<td>*.slidde.co</td>
<td>$19/year (~$1.58/month)</td>
<td><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="✅" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/2705.png" /></td>
</tr>
<tr>
<td><a href="https://pagy.co/">Pagy</a></td>
<td>*.pagy.site</td>
<td>Free</td>
<td><img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="✅" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/2705.png" /></td>
</tr>
</tbody>
</table>
<h3>Carrd</h3>
<p>This is the most common site I have seen, often used for aggregating all your social media links, or for putting up your commission sheet information. I used to <a href="https://chailotl.carrd.co/">use it myself</a>, until I created my website.</p>
<h3>Neocities</h3>
<p>A spiritual successor to GeoCities; it gives you an in-browser HTML editor and resources to learn HTML, CSS, and JavaScript. Being able to learn that without having to worry about hosting or costs is great, and it'll open up your opportunities for other options down the line.</p>
<p>It's also a social network, where you can explore other sites and leave comments. You might even get inspired too! ^.^</p>
<h3>Nekoweb</h3>
<p>Nekoweb shares a lot of similarities with Neocities in its function and old school vibes. It also offers some pretty tech-y stuff like FTP and Git support, support for custom HTTP headers, and a Linux VM to build your site using any static site generator.</p>
<h3>mmm.page</h3>
<p>mmm.page has a more flexible visual editor than Carrd.co, and it has the fun vibes of Neocities. You can also browse other people's sites, but there's less of a focus on community than either Neocities or Nekoweb.</p>
<h3>Straw.Page</h3>
<p>This is similar to mmm.page, with a focus on the visual editor being very mobile-friendly, and a look that screams "graphic design is my passion". There is an explore button in the menu but it currently does not work.</p>
<h3>Github Pages</h3>
<p>This is meant for creating pages for your Github projects, but it lets you create one personal page. You are going to have to learn how to use GitHub and git to create and modify your website. I used to use it for my portfolio, but currently it hosts some of my web tools.</p>
<h3>slidde</h3>
<p>I found this when researching for more options, and it looks to be a clone of Carrd, sporting a similar name, editor, and even the same price.</p>
<h3>Pagy</h3>
<p>Another option I found, with custom domains surprisingly being a free feature; good on them. The editor appears to be more flexible than Carrd, but a little more organized than mmm.page.</p>
<blockquote>
<p>Note
There are more free options, but they feel too business-y for my tastes. If you really want to check them out, here are a few more: <a href="https://versoly.com/">Versoly</a>, <a href="https://www.umso.com/">Umso</a>, <a href="https://www.wix.com/">Wix</a></p>
</blockquote>
<h2>Getting Your Paws Dirty</h2>
<p>You wanna host websites the good 'ol way? Great! When you make it yourself, you have full control over everything, and unlike the static sites from before, you can do a lot of cool stuff from the server side. For example, my blog fetches posts from Bluesky that talk about my blog posts and renders them as comments below! I also created an <a href="https://axoga.to/ask">ask box</a> where you can submit questions to me.</p>
<p>This is not going to be a step-by-step guide on how to self-host, but I'll show you what you need so you can figure it out yourself. For example, you can learn <a href="https://www.w3schools.com/html/">HTML</a>, <a href="https://www.w3schools.com/css/">CSS</a>, and <a href="https://www.w3schools.com/js/">JavaScript</a> from W3Schools; I still regularly use them to brush up on my knowledge and learn new things.</p>
<p>The free options all offered subdomains that you can use, but when self-hosting you will need to get your own domain! With all the wacky modern TLDs available, you have a lot of affordable options to pick. I fully recommend buying them through <a href="https://porkbun.com/">Porkbun</a>, as you'll get the best rates and amazing service.</p>
<p>Next, you have to decide if you are going to pay for a VPS (Virtual Private Server) or self-host at home. You can get a VPS from <a href="https://www.digitalocean.com/pricing/droplets#basic-droplets">DigitalOcean</a> or <a href="https://www.linode.com/pricing/#compute-shared">Linode</a> for $4-6 a month. I have a $6 droplet from DigitalOcean, and it runs this website, my portfolio website, another website, a Discord bot, my automation system, a Maven repo, a BlueMap instance, and a Syncthing instance. That's a lot of stuff on just one small VPS!</p>
<p>If you decide to self-host at home instead, you can have a more powerful system for no monthly cost, but there are other things you'll need to do instead. Firstly, you need to port forward <code>80</code> and <code>443</code> so your web server is accessible from the internet. If you have no access to the router, then you're rather out of luck :(</p>
<p>And secondly, you should use <a href="https://www.cloudflare.com/application-services/products/dns/">Cloudflare DNS</a> as a reverse proxy to mask your home IP. It is completely free because it's their loss-leader service. You <em>can</em> do without this, but exposing your IP like that is a DDOS and dox risk. Technically, you should also use a static IP—which ISPs charge for—but in reality your IP will not change unless the router is restarted.</p>
<h2>So What's Next?</h2>
<p>What do you mean what's next? Go make a cool website!! And maybe share it with me by commenting below :3</p>
<hr />
<section>
<ol>
<li><p>The cheapest plan needed to use a custom domain. <a href="#fnref1">↩︎</a></p>
</li>
</ol>
</section>
]]></content>
        <category label="articles" term="articles"/>
        <category label="indieweb" term="indieweb"/>
        <category label="self-hosting" term="self-hosting"/>
        <published>2025-07-27T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Type Erasure Is Stupid]]></title>
        <id>https://axoga.to/blog/type-erasure-is-stupid</id>
        <link href="https://axoga.to/blog/type-erasure-is-stupid"/>
        <updated>2025-02-19T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[How a leaked implementation detail wrongfully became a feature]]></summary>
        <content type="html"><![CDATA[<p>This is going to be a bit of a technical rant about something most programmers won't have the misfortune of dealing with.</p>
<p>But goddamnit <em>it matters to me</em>.</p>
<p>If you're not using cringe languages with type erasure (Like Java.), let me clue you in on what it is first. It is a "necessary feature" where generic type information is simply <em>forgotten</em> by the compiler. What this means is that <code>List&lt;int&gt;</code> and <code>List&lt;double&gt;</code> both become <code>List</code>.</p>
<p>This might not seem like a biggie, but this has a big ramification on method signatures<sup><a href="#fn1">[1]</a></sup>, making this code uncompilable.</p>
<pre><code>// Before type erasure
public void method(List&lt;int&gt; list) { ... }

public void method(List&lt;double&gt; list) { ... }

// After type erasure
public void method(List list) { ... }

public void method(List list) { ... } // signature conflict
</code></pre>
<p>Now, you might say this example is contrived because you can just change the method name to resolve the signature conflict--and it might even add clarity--but here is a different example that doesn't involve method signatures.</p>
<pre><code>class JsonReader&lt;T&gt;
{
	public T fromJson(String json)
	{
		return new ObjectMapper().readValue(json, T.class);
	}
}
</code></pre>
<pre><code>var fruitReader = new JsonReader&lt;Fruit&gt;();

Fruit fruit = fruitReader.fromJson(json);
</code></pre>
<p>After type erasure, <code>T.class</code> becomes invalid because there is no type to retrieve the class from!</p>
<p>Is this an impossible problem? No, <em>and that's the infuriating part.</em></p>
<p>Because you can manually include the type information by passing it as a parameter.</p>
<pre><code>public T fromJson(String json, Class&lt;T&gt; clazz)
{
	return new ObjectMapper().readValue(json, clazz);
}
</code></pre>
<pre><code>Fruit fruit = fruitReader.fromJson(json, Fruit.class);
</code></pre>
<p>But I don't want to do this!! It's ugly!</p>
<p>More importantly, if I can solve it, <em>why can't the language devs do it?</em></p>
<h2>The Implementation Detail Is Leaking</h2>
<p>In the world of programming design, you first lay out what it is you want to accomplish, e.g. creating a <code>List</code> class. What a design doesn't dictate is <em>how</em> it is implemented; in fact, it shouldn't care <em>at all</em> how something is implemented. These technical concerns are for the developers, and they are called <strong>implementation details</strong>.</p>
<p>Taking our <code>List</code> class example, the two primary ways you can implement a <code>List</code> is either using arrays or linked lists. Regardless of how they're implemented, they should be functionally identical<sup><a href="#fn2">[2]</a></sup>, and most of the times you'll just ask for a <code>List</code> and not care if it is an <code>ArrayList</code> or a <code>LinkedList</code>.</p>
<p>The thing about implementation details, is that they are undocumented and can change on a whim, so it is risky to depend on their internal behavior. These should always be hidden from the end-user.</p>
<p>While exposing internal behavior is largely benign--as the fault lies upon any programmers who try to exploit it--<em>leaking</em> internal behaviors such that you must take it into account is a <em>huge</em> no-no. Think <code>unsafe</code> in Rust; functions that use <code>unsafe</code> code must not let it bubble beyond its braces.</p>
<p>So what does that make type erasure?</p>
<p><em><strong>A leaked implementation detail.</strong></em></p>
<p>It is a <a href="https://openjdk.org/projects/valhalla/design-notes/in-defense-of-erasure">two-decade old solution</a> to generics for Java that should have been shelved long ago for modern solutions. A <em>teenager</em> <a href="https://github.com/Auties00/reified">solved part of the problem</a> that Java devs claimed was impossible. C# is basically Microsoft Java (According to Kett :3), and it doesn't have type erasure; in fact it can pull off sick tricks like this.</p>
<pre><code>public class Factory&lt;T&gt; where T:new()
{
	public T create()
	{
		return new T();
	}
}
</code></pre>
<p>What is Java's excuse?</p>
<p><em>"b-but types don't exist in the machine code--"</em>
Don't care, they exist in dev space.</p>
<p>The whole point of types is to enforce cohesion. The compiler already has access to the type information to fill in the blanks, it just erroneously throws it away early.</p>
<p>I should not have to work around someone else's mediocre solution. Programming is about expressing what you want; it is the language's job to execute it.</p>
<blockquote>
<p>every day I come across the curse of type erasure my eyes bleed black viscous ichor</p>
<p>@ Chai</p>
</blockquote>
<hr />
<section>
<ol>
<li><p>A method signature is unique if either the method's name or its type parameters are different. <a href="#fnref1">↩︎</a></p>
</li>
<li><p>If you need to eek out every last bit of performance, you can choose one of the two to best suit your case. <a href="#fnref2">↩︎</a></p>
</li>
</ol>
</section>
]]></content>
        <category label="articles" term="articles"/>
        <category label="technical" term="technical"/>
        <category label="opinion" term="opinion"/>
        <published>2025-02-19T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[2024 Retrospective]]></title>
        <id>https://axoga.to/blog/2024-retrospective</id>
        <link href="https://axoga.to/blog/2024-retrospective"/>
        <updated>2025-01-07T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[So long 2024 and hello new year!]]></summary>
        <content type="html"><![CDATA[<p>Writing this now, I realized that this marks the first quarter of the 21st century, and I think that's pretty cool. Is this something else other people noticed as well? It didn't seem like to me that this was at the top of anyone's mind with all the doom and gloom of current world events, and I don't blame them, what a year huh? But this blog isn't the place for that! Instead, I want to share with you my best and worst moments of yesteryear.</p>
<p>The year of 2024 left me with mixed feelings as it contained some of the best moments in my life, and good contenders for some of the worst. If you kept in touch with me, or even met me last year, you know exactly what was my highlight of last year—<em>Minecraft modding</em>.</p>
<h2>The Good</h2>
<p>I started modding Minecraft 4 years ago, and my first project was <em>Siltbox</em>, a vanilla overhaul mod where I held strong design opinions which were rooted in Mojang's own design philosophy. I invested a lot of time and effort into this ambitious mod, and I developed a lot of interesting ideas. But like most projects my focus shifted away and I ended up shelving it for a long time.</p>
<p>Fast forward to early 2024, my friend Mudkip was developing a Minecraft modpack designed around Cobblemon with a splash of Create. He was having problems with motivation, so I half jokingly told him that if he reminds me to work on Siltbox, I'll remind him to work on his modpack. I would later come to really appreciate him taking me up on that deal.</p>
<p>Given how long ago it was Minecraft has seen several major updates, and at the time the latest version was 1.20, while Siltbox was based on the 1.16 version. I initially wanted to stick to the old version and fully develop the mod before I ported it over to minimize maintenance work, but something changed my mind when I saw what the modern modding ecosystem was like. For the first time in Minecraft's modding history, vanilla friendly mods gained mainstream popularity with heavy hitters like Create and Farmer's Delight. <em>I was elated!</em></p>
<p>The thing about Siltbox is that it was more than a content mod, it was functionally a modpack. Its scope was broader than any large mod, and it had bug fixes and quality of life features; it aimed to be a complete and cohesive experience. This was because at the time I didn't like most mods, and I especially hated most modpacks for being poorly designed and highly vanilla unfriendly. I was going to make all the mods for my modpack myself, and keeping it as a single monolithic mod was easiest for me to develop.</p>
<p>But there was now a large and growing offering of quality vanilla friendly mods that I am actually excited about playing with, mods which I would want to be part of the Siltbox experience. My friends had also urged me for years to split up Siltbox so they could pick the features they want, so I made the decision to take Siltbox's best features and develop them as separate mods for the latest version of Minecraft.</p>
<p>My first mod was <a href="https://modrinth.com/mod/chais-inventory-sorter">Chai's Inventory Sorter</a>, which was a direct port of the inventory sorter feature from Siltbox, polished up and served with a brand new configuration screen. This would become one of my most popular mods for a few months until the release of <a href="https://modrinth.com/mod/particular">Particular</a>, which would take off like a rocket and opened up a lot of exciting opportunities for me, one of which was becoming friends with Laynce, one of the few Minecraft content creators I watch and someone I look up to!</p>
<p>I would go on to release 15 mods and accumulate 930k downloads on <a href="https://modrinth.com/user/Chai">Modrinth</a> by the end of the year, and those mods would bring in a lot of new people and friends, growing my Discord server from a humble 30 members to over 10 times that! :0</p>
<p><img src="/images/blog/particular.webp" alt="" /></p>
<p>In June I participated in <a href="https://modfest.net/">ModFest</a>, a game jam for Minecraft mods. My submission was the <a href="https://modrinth.com/mod/wowozela">Wowozela</a>, which ended up being a popular mod on the showcase server and it won the 2nd funniest mod award! I had a lot of fun participating in that event and I invited a bunch of my friends to play on the showcase server with me. I did have some pet peeves with one of their base mods, so I made a bunch of contributions, and it seems one of the ModFest staff noticed my enthusiasm and invited me to join their team! I felt honored to be part of something so cool, and I was eager to improve the event as a team member.</p>
<p>October would welcome a second ModFest, where I casually flexed by developing and submitting 5 whole mods, three of which were in the top 5 of their respective best theme categories. My primary submission <a href="https://modrinth.com/mod/fbombs">FBombs</a> won 3 awards: 2nd best throwbacks-themed, 4th best booth, and 4th funniest. I also designed an 8-part quest line in collaboration with several other booths, and the reward was that you could activate a timed atomic bomb that would blow up my FBombs booth, and vaporize all guests within range in nuclear fire :3</p>
<p><img src="/images/blog/fbombs.png" alt="" /></p>
<p><img src="/images/blog/modfest_1.21.png" alt="" /></p>
<p>Probably the most exciting thing has to be something that hasn't been fully realized yet. When I was developing my inventory sorter mod, I wanted to use a config library to provide the config screen, and I ended up going with <a href="https://modrinth.com/mod/owo-lib">owo-lib</a>. I soon realized that it was pretty limited in its capabilities, so I put in some suggestions for the developer and even offered to submit code contributions, but the lead developer bizarrely turned them all down.</p>
<p>Undeterred, I created <a href="https://modrinth.com/mod/sushi-bar">Sushi Bar</a> as a library mod to extend owo-lib and Lavender with common sense features it was missing. This method would prove to be unsustainable though. Those mods gave me a lot of headaches because they were difficult to extend and introduced some baffling bugs.</p>
<p>After reaching a boiling point I threw in the towel and decided to turn Sushi Bar into its own fully fledged config screen mod! Having worked with Minecraft's GUI code before, I decided to skip that dumpster fire and roll my own UI component system to build the config screen on top of. As I shared my progress in the ModFest server though, it started garnering a lot of attention from regulars who were interested in both the novel UI component system I was developing, and the accompanying config screen. They commented on how clean the code looked, and how elegant the whole system was—it seems I was onto something really good here!</p>
<p>I had to temporarily shelve this for ModFest 1.21, so I wasn't able to release it last year, but I can tell it's going to make a big splash when it releases and seriously challenge the major config library mods.</p>
<p><img src="/images/blog/sushi_bar_config_screen.png" alt="" /></p>
<h3>Honorable Mention</h3>
<p>The runner up for the best thing I've done in 2024 was visiting Pax East with my friends again! It's a video &amp; tabletop gaming convention, and it's such a blast going the full 4 days every year. I'm excited to go again next year :3</p>
<p><img src="/images/blog/pax_east_2024.jpg" alt="" /></p>
<h2>The Bad</h2>
<p>Rollercoasters have their peaks and valleys, and 2024 was no exception. The biggest valley for me is the (still ongoing) "healthy" diet that my mom has been forcing on my family. Now, I'm no stranger to eating healthy, and you <em>can</em> do it in a way that it is delicious as heck, but whatever my mom found this time seems to claim that every single normal ingredient and proven cooking method to cook delicious meals is "bad". Here is a non-exhaustive list so you can be just as confused as I am.</p>
<ul>
<li>Pan frying</li>
<li>Salt</li>
<li>Spices</li>
<li>Sauces</li>
<li>Vegetable oils</li>
<li>Butter</li>
<li>Fat</li>
<li>Wheat</li>
<li>Gluten</li>
<li>Meat (unless from chicken or fish)</li>
<li>Cheese</li>
<li>Eggs</li>
<li>Milk</li>
<li>Ordinary potatoes</li>
</ul>
<p>"Ordinary potatoes" you wonder? Yes, normal potatoes of the russet, yukon gold, etc. kind aren't good enough for my mom. She went out of her way to find strange sweet potato varieties that just taste bad, especially after being boiled with no salt. And I normally love sweet potato fries! Speaking of fries, we got a french fry cutter which would be really cool, but my mom <strong>only</strong> bakes them with no salt, which means she isn't making fries but baked potatoes posing as fries :/</p>
<p>Now, I love a good fish dish, but my mom does absolutely everything to make it taste bland and watery—baked in the oven with zero butter or spices. <em>Please</em> for the love of god don't bake a fish unless you know what you're doing, instead pan fry it in some butter and drizzle with a bit of lemon juice to make yourself a delicious $5 meal. One time, my mom made a vegetable soup so repulsive that even she herself agreed it was bad. She's has been keeping this diet up for over 6 months now and I constantly crave eating normal food… <img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🥺" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f97a.png" /></p>
<h3>Dishonorable Mention</h3>
<p>Would you believe me if I said the second worst thing that happened to me this year was a trip to Aruba? Vacations are supposed to be relaxing and carefree, but that was far from the experience I had. Everything in Aruba is insanely expensive, so my family was scrounging by on cheap groceries and assembling hodgepodge meals in our kitchenless motel room, and every day I was dragged out into the blasting heat and left exhausted and sweaty each day. I didn't have fun most of the time, and I felt like I needed a vacation from that "vacation" :(</p>
<p>The one thing I really enjoyed about the trip was going scuba diving for the first time, it was so cool staying underwater for a long period of time and observing all the sea life around us. We got to see sea turtles gnawing away on some sea grass, a prickly sea urchin, and even a moray eel hiding under a rock!</p>
<h2>The Future</h2>
<p>New Year's resolutions are dumb and CGP Grey argues this excellently in his video about having <a href="https://www.youtube.com/watch?v=NVGuFdX5guE">themed years</a>. I wholeheartedly recommend you watch his video as it prefaces what I will be talking about in a moment.</p>
<p>My theme for this year is the <strong>Year of Structure</strong>.</p>
<p>For many months now, I felt like I haven't been in control of my life, and I've let a lot of things stagnate. I drew less last year than the year before, I wrote nothing in my journal, I only released a single blog post, and I made no progress on <a href="https://axoga.to/tags/cascadia">Cascadia</a>. It's honestly a miracle I was able to gather enough focus for even just Minecraft modding. High school, college, and work used to provide me with structure and schedules, but without them I've become aimless. I haven't learned how to manage my time on my own.</p>
<p>And I want to take back control.</p>
<p>With the theme of structure, it'll provide me with the mental framework to determine the kinds of decisions I should make to bring back structure to my life. This blog post is one of those decisions. Here's hoping it won't be the last one ^_^</p>
]]></content>
        <category label="personal" term="personal"/>
        <category label="new years" term="new years"/>
        <published>2025-01-07T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[The Making of Buildpiles]]></title>
        <id>https://axoga.to/blog/the-making-of-buildpiles</id>
        <link href="https://axoga.to/blog/the-making-of-buildpiles"/>
        <updated>2024-03-07T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Buildpiles are a new activity where you build together in Minecraft; now I need a way to show off our world builds]]></summary>
        <content type="html"><![CDATA[<p>I like the idea of Drawpiles; you collaboratively draw on a shared canvas with your friends and have a great time doodling together, sometimes letting your drawings interact with each other! But for some people when there is an apparent skill gap, Drawpiles presents a ripe opportunity to compare yourself, which can lead to you not feeling very good about yourself. I'm one of those people &gt;_&lt;</p>
<p>Though, even if you put a pin in that not everyone knows how to draw. I want to have activities in my <a href="https://discord.gg/SuZb3aJUCx">server</a> that most people can participate in. So October of last year I came up with an idea: everyone can play Minecraft, so why not collaboratively build in the same Minecraft world? That's the idea behind Buildpiles.</p>
<p>Admittedly, this blog post is way overdue as the first Buildpile was hosted October 14th 2023, nearly 5 months ago. Better late than never, am I right? Anyways, from now on you can check out all Buildpiles, current and future, right <a href="https://axoga.to/buildpile/">here</a>.</p>
<p><img src="./the-making-of-buildpiles.png" alt="" /></p>
<h2>The Technical Deets</h2>
<p>If you're still reading, you probably want to know how it works behind the scene. Hopefully you didn't want to see anything technically impressive as it ended up being pretty simple. Fortunately this means it would be just as easy for you to set up for yourself!</p>
<blockquote>
<p>Tldr
I used <a href="https://bluemap.bluecolored.de/">BlueMap</a> to generate a 3D model of the world that you can view in the browser.</p>
</blockquote>
<p>When I was first getting into this, I thought I would need to generate a 3D model out of the Minecraft world and find a way to embed that in the browser. I knew there was a common pipeline to import Minecraft worlds into Blender, so I started my search with embedding models in the browser.</p>
<p>I promptly found <a href="https://modelviewer.dev/">&lt;model-viewer&gt;</a> which made it seem very easy to do just that! But a caveat I immediately noticed was that it only supports <code>glTF</code> and <code>GLB</code> model formats, whereas I'm more familiar with <code>FBX</code> and <code>OBJ</code>. Hopefully that won't be a snag.</p>
<h3>A Dead End</h3>
<p>Next up on the list: model generation! The popular choice for Blender users is to use <a href="https://github.com/jmc2obj/j-mc-2-obj">jMc2Obj</a> which spits out <code>OBJ</code> models; not a challenge for Blender, but not a format I can natively use.</p>
<p><img src="/images/blog/buildpile_test_obj.png" alt="" /></p>
<p>This was decent at best. Transparency worked, but textures were blurry and the grass colors didn't match. I could probably fix the blurryness by disabling bilinear filtering, but the mismatched colors would be troublesome. Hopefully the conversion to <code>GLB</code> doesn't break anything.</p>
<p><img src="/images/blog/buildpile_test_glb.png" alt="" /></p>
<p>Oh- oh no. This looks dreadful. What is that awful sheen? What happened to the transparency? And why is the grass one-sided too? Even if I could fix the previous issues <em>and</em> these new ones, I really did not want to do this manually. This was just not going to work.</p>
<p>So I went looking for alternatives. I found <a href="https://bluemap.bluecolored.de/">BlueMap</a>, but on the surface it seemed to just be a Spigot/Fabric plugin for a Minecraft server. That would be cool for when I host my own Minecraft server (eventually), but I wanted something standalone, so I steered my search elsewhere.</p>
<h3>RTX Minecraft</h3>
<p>What I found next was <a href="https://chunky-dev.github.io/docs/">Chunky</a>, a fascinating piece of software that can render path traced images of Minecraft worlds. Path traced renders would look gorgeous, but depending on the resolution and hardware they can take quite a while to render. Might as well as give it a try.</p>
<p><img src="/images/blog/buildpile_test_chunky.png" alt="" /></p>
<p>This looks great! But it took a non-insignificant amount of time to render, and with this isometric view I would need to do this three more times to ensure full coverage of the chunk. And ideally at a higher resolution too.</p>
<p>Since I could not find any other workable solution in time, I ended up going with this and rendering four 1920×3840 resolution renders. Here is one of them at a chunky<sup><a href="#fn1">[1]</a></sup> 7.52 megabytes (!!)</p>
<p><img src="/images/blog/buildpile_1_north-west.png" alt="" /></p>
<h3>With Fresh Eyes</h3>
<p>A few busy months pass, and I'm thinking again about how to finish this project. With a fresh start I do some web searching and find <a href="https://overviewer.org/">Overviewer</a>. It calls itself a "high-reslution Minecraft world renderer", and it generates isometric images you can pan across. I'm pretty fond with how it looks, and it looks to be simpler to setup than making several minute long renders in Chunky.</p>
<p><img src="/images/blog/overviewer_1.png" alt="" /></p>
<p>There's just something about this that tickles my fancy. A sense of nostalgia and adventure to explore the whole world map. Here is the <a href="https://overviewer.org/wow/">link</a> to this map.</p>
<p><img src="/images/blog/overviewer_2.png" alt="" /></p>
<p>When reading the documentation on how this is set up, I checked out the most recent blog post titled "The End". I thought it was talking about Minecraft's End, but it was instead about how the project was officially over. The software of course still works and is actively used by some large servers, but it was still a little sad to see.</p>
<p>But within the blog post it recommended <a href="https://bluemap.bluecolored.de/">BlueMap</a> as an alternative! Overviewer seemed to support a standalone mode, so I figured if they were recommending BlueMap that it could have a similar mode too, and to my surprise it did!</p>
<p>Setting it up was really easy, requiring running just 3 commands to set up config files, render the map, then run a web server. My desktop's Java installation was being fussy with too old versions, but I soon had it up and running on my VPS.</p>
<h2>Post Mortem</h2>
<p>My friends and I had a lot of fun at the Buildpile and they've been nagging me to host another one soon. I was putting that off until I figured out a workflow and wrote this blog post, so stay tuned for more Buildpiles!</p>
<hr />
<section>
<ol>
<li><p>I guess that's why the software is called Chunky-- <img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="💥" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f4a5.png" /> <img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" decoding="async" src="/images/emoji/real_gun.png" /> <a href="#fnref1">↩︎</a></p>
</li>
</ol>
</section>
]]></content>
        <category label="articles" term="articles"/>
        <category label="minecraft" term="minecraft"/>
        <published>2024-03-07T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Cascadia 0.2]]></title>
        <id>https://axoga.to/blog/cascadia-0.2</id>
        <link href="https://axoga.to/blog/cascadia-0.2"/>
        <updated>2023-11-12T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Drawing lines and smelling flowers 🌹]]></summary>
        <content type="html"><![CDATA[<p>A new devlog with the first update for Cascadia! There may not seem to be a lot here, but there were some major under-the-hood changes that I needed to do to set up the groundwork for future updates as time goes on.</p>
<p>Download <a href="/downloads/cascadia-0.2.zip">Pre-Alpha 0.2</a> and smell the pretty flowers <img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="🌹" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f339.png" /></p>
<p><img src="./cascadia-0.2.png" alt="" /></p>
<h2>Changelog</h2>
<h3>Additions</h3>
<ul>
<li><strong>Red poppy</strong>, a new flower block with a different kind of block model</li>
<li><strong>Block selection box</strong>, a wireframe that appears around blocks you mouse over that are within reach</li>
</ul>
<h3>Changes</h3>
<ul>
<li>Player spawn at the surface instead of at world height limit</li>
<li>Block types and models are loaded from json</li>
</ul>
<h3>Fixes</h3>
<ul>
<li>Fixed game window permanently grabbing your mouse cursor
<ul>
<li>You can now press escape to release it</li>
</ul>
</li>
<li>Fixed being able to pick bedrock</li>
<li>Fixed jumping physics bug that allowed you to hang from the ceiling</li>
<li>Fixed being able to move faster diagonally</li>
<li>Fixed world border blocking raycasts</li>
</ul>
<hr />
<p>To get Cascadia out of prototyping and into pre-alpha, I had to make some game jam-esque sacrifices by hardcoding things into the program. The two I'm sharing today are both graphics-related: render passes and chunk rendering.</p>
<h2>What's a render pass?</h2>
<p>When you want to draw something to the screen, you need to give your GPU instructions and data to work with. Those instructions are called shaders and that data is given through a pipeline. Altogether this makes up a render pass!</p>
<p>Games can use multiple render passes to create the final image, like terrain, character meshes, water, post-processing effects, and UI.</p>
<p>Cascadia has one non-UI render pass which can only render terrain, which is a problem if I want to render something that isn't terrain.</p>
<p>Since this is will be a long-term effort, I'll start small and set up a line drawing render pass so I can give players a block selection box when they mouse over a block within reach.</p>
<h2>Drawing lines can't be hard, right?</h2>
<p>Drawing lines on screen is apparently a <a href="https://mattdesl.svbtle.com/drawing-lines-is-hard">hard problem</a>.</p>
<p>Shaders may support drawing line primitives, but they kind of suck as detailed in the linked blog post and were not going to work in my game. Guess I'll have to triangulate my own lines.</p>
<p>There are many ways to go about doing this, especially more so in 3D, so I'll skip to what I ended up going with: <strong>screen-space projected lines</strong>.</p>
<p>Let's break down what that means.</p>
<h2>Spaaaaace</h2>
<p>When working in graphics there's a concept of <strong>spaces</strong>; coordinate systems that are centered around a specific origin.</p>
<p>There are many kinds of spaces, object space (sometimes called "model space"), world space, view space (sometimes called "eye space"), clip space, and screen space.</p>
<p><strong>Object space</strong> is the coordinate system that the vertices of a model reside in, relative to the model's origin.</p>
<p>Of course, models don't permanently sit at <code>(0, 0, 0)</code>, they can be positioned within <strong>world space</strong>, and thus relative to the world's origin.</p>
<p><strong>View space</strong> is the coordinate system in front of the camera. In fact, in shader code you transform world space into view space—the world moves around the camera!</p>
<p>But view space on its own has no perspective, which is where <strong>clip space</strong> comes in. By doing some clever math, you can apply perspective to get clip space, which is ultimately what you submit to the GPU's rasterizer<sup><a href="#fn1">[1]</a></sup>.</p>
<p>So what's <strong>screen space</strong> then? Well it's the pixel coordinates of your screen! We want to <strong>project</strong> the 3D world space coordinates of our lines into screen space so that we can triangulate it there and get lines of constant thickness.</p>
<p>If we were to triangulate without projecting to screen space first, our lines would experience perspective and appear thinner the further away they are.</p>
<h2>Hmm, chunky</h2>
<p>Cascadia's chunk renderer is hardcoded to create cube meshes, but not all blocks are cubic, like the new red poppies. I <em>could</em> hardcode those in, but this is just unsustainable in the long-term.</p>
<p>So I created a data-driven system that loads block models through json files! Block models can have multiple sub-elements, each with their own shape, size, rotation, and textures.</p>
<p>It also supports parenting, which allows a model to inherit from a template to simplify the process and minimize redundant code. Pretty neat, huh?</p>
<h2>Postmortem</h2>
<p>The line renderer and block models are both important system for Cascadia. In their current state they are a bit rough, but so is the rest of the code :P In time they'll get polished.</p>
<p>So what's next for Cascadia? More polish! And hopefully some new content too. It's hard to pinpoint exactly what I'll work on next as I'm still learning how to do things, and it'll probably stay that way until I reach a more stable version where I can focus more on content, such as alpha and beta.</p>
<hr />
<section>
<ol>
<li><p>The rasterizer is what turns triangles into pixels on your screen. <a href="#fnref1">↩︎</a></p>
</li>
</ol>
</section>
]]></content>
        <category label="devlogs" term="devlogs"/>
        <category label="cascadia" term="cascadia"/>
        <category label="gamedev" term="gamedev"/>
        <category label="graphics" term="graphics"/>
        <published>2023-11-12T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Cascadia 0.1]]></title>
        <id>https://axoga.to/blog/cascadia-0.1</id>
        <link href="https://axoga.to/blog/cascadia-0.1"/>
        <updated>2023-11-03T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Learning Rust to develop my dream game!]]></summary>
        <content type="html"><![CDATA[<p>This first devlog concludes the test phase of Cascadia, my survival<sup><a href="#fn1">[1]</a></sup> sandbox game!</p>
<p>Download <a href="/downloads/cascadia-0.1.zip">Pre-Alpha 0.1</a> and give it a try <img style="display: inline-block; width: 1.375em; height: 1.375em; vertical-align: text-bottom;" draggable="false" alt="💜" decoding="async" src="https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/1f49c.png" /></p>
<p><img src="./cascadia-0.1.png" alt="" /></p>
<h2>Features</h2>
<ul>
<li>Placing and breaking blocks</li>
<li>10 blocks
<ul>
<li>Bedrock</li>
<li>Stone</li>
<li>Dirt</li>
<li>Grass</li>
<li>Logs</li>
<li>Leaves</li>
<li>Planks</li>
<li>Bricks</li>
<li>Sand</li>
<li>Glass</li>
</ul>
</li>
<li>Randomly generated 256×64×256 world
<ul>
<li>Cliffs</li>
<li>Trees</li>
<li>Sand traps</li>
<li>Caves</li>
</ul>
</li>
<li>Physics</li>
<li>Lighting</li>
</ul>
<hr />
<p>Cascadia's test phase of development spanned from September 21st to November 3rd, with a 3 week break between October 1st and October 23rd due to college. (If you're bad at math, that's 21 days!)</p>
<p>It saw a few architectural changes before settling on a bespoke game engine written in Rust, using <a href="https://crates.io/crates/winit">winit</a> for window creation and input handling and <a href="https://crates.io/crates/wgpu">wgpu</a> for graphics.</p>
<p>So let's take a trip down memory lane, shall we?</p>
<h2>Pre-wgpu</h2>
<p>Not knowing where to start, Kett recommended that I try <a href="https://www.raylib.com/">raylib</a>, a C library for game programming. I found a binding for Rust and made this simple test scene using raylib's built-in draw functions.</p>
<p><img src="/images/blog/cascadia/0.1/1.gif" alt="" /></p>
<p>I then figured out how to texture those cubes!</p>
<p><img src="/images/blog/cascadia/0.1/2.png" alt="" /></p>
<p>But I was having a lot of trouble trying to figure out what the Rust bindings for raylib were. Raylib has plenty of examples on their website, but they were only for C. So I gave up and switched to <a href="https://bevyengine.org/">Bevy</a>, a data-driven game engine built in Rust.</p>
<p><img src="/images/blog/cascadia/0.1/3.png" alt="" /></p>
<p>Bevy uses an Entity Component System (ECS) pattern where entities are blank slates that you attach components to and process with systems. It's a pretty novel idea to me, and it seems to be quite popular in Rust and gaining traction outside of it too.</p>
<p>But Bevy's ham-fisted approach to treating <em>everything</em> as an ECS—including UI—just rubbed me the wrong way. I needed to find something better.</p>
<p>Seeing that Bevy used <a href="https://wgpu.rs/">wgpu</a> for its graphics, a low-level cross-platform graphics API, I found a <a href="https://sotrh.github.io/learn-wgpu/#what-is-wgpu">wgpu tutorial</a> for creating a render pipeline from scratch. For the next 3 days I poured hours reading, learning, and writing low-level graphics.</p>
<p>I really hoped this would be worth the effort.</p>
<h2>wgpu</h2>
<p><img src="/images/blog/cascadia/0.1/4.png" alt="" /></p>
<p>I was quite exhilarated with what I had made, my very own render pipeline! From scratch! Without using a game engine!</p>
<p>But the future still felt uncertain to me as I didn't know how much steam I would have to continue working at such a low level, nor if I would be able to find enough resources and tutorials online to know what to do next.</p>
<p>And count my lucky stars, because I found a different <a href="https://whoisryosuke.com/blog/2022/render-pipelines-in-wgpu-and-rust/">tutorial</a> that cleaned up and expanded upon the previous wgpu tutorial! (Which left me with a monolithic file with nearly a thousand lines of code)</p>
<p>With a much more readable source code, I felt invigorated to continue toiling away and implemented raycasting, allowing me to place and break blocks for the first time!</p>
<p><img src="/videos/blog/cascadia/0.1/5.mp4" alt="" /></p>
<p>I was still rendering individually instanced cubes which wasn't going to work in the long-term, so I got to work on mesh generation. Here is the very first mesh I generated.</p>
<p><img src="/images/blog/cascadia/0.1/6.png" alt="" /></p>
<p>I then rendered square faces proper…</p>
<p><img src="/images/blog/cascadia/0.1/7.png" alt="" /></p>
<p>…culled hidden faces…</p>
<p><img src="/images/blog/cascadia/0.1/8.png" alt="" /></p>
<p>…and added the rest of the faces! This is a view from inside the mesh.</p>
<p><img src="/images/blog/cascadia/0.1/9.png" alt="" /></p>
<h2>Vertex data</h2>
<p>Soon after I integrated texture array support into the render pipeline and rendered grass!</p>
<p>I wanted to use texture arrays instead of a texture sheet because it would be easier to set up. Instead of calculating the correct UVs for a given texture, I just pass a texture index to the shader.</p>
<p>With this index-first approach, I could even make animated textures super easy to implement in the future.</p>
<p><img src="/images/blog/cascadia/0.1/10.png" alt="" /></p>
<p>And with my newfound knowledge of the render pipeline and the vertex buffer data, I added lighting data to the vertices to give blocks some contrast lighting.</p>
<p><img src="/images/blog/cascadia/0.1/11.png" alt="" /></p>
<p>Adding shadows was pretty easy too using the same vertex light data queried from a light array I added to the chunk.</p>
<p><img src="/images/blog/cascadia/0.1/12.png" alt="" /></p>
<h2>Chunks</h2>
<p>The next step was to have more than one chunk. This was going to have a few challenges, namely getting lighting to work properly across chunks, and to cull the faces of blocks between chunks, but for now I put those problems off for later.</p>
<p><img src="/images/blog/cascadia/0.1/13.png" alt="" /></p>
<p>With the ability to place different blocks now, I made the first dirt house in Cascadia.</p>
<p><img src="/images/blog/cascadia/0.1/14.png" alt="" /></p>
<p>I then added some new blocks that needed their own rendering code. Glass and leaves are both transparent, but leaves fully render to neighboring leaves for a neat layering effect. Logs were the first block to use multiple textures.</p>
<p><img src="/images/blog/cascadia/0.1/15.png" alt="" /></p>
<p><img src="/images/blog/cascadia/0.1/16.png" alt="" /></p>
<p>Here's a gratuity shot of 64,000 chunks being rendered at once :3</p>
<p><img src="/images/blog/cascadia/0.1/17.png" alt="" /></p>
<p>Although I don't have footage of it, this test version marked the inclusion of the first iteration of physics! (Which unbeknownst to me would haunt me for a week as I pulled my hair out trying to fix numerous collision bugs)</p>
<p><img src="/images/blog/cascadia/0.1/18.png" alt="" /></p>
<h2>Terrain generation</h2>
<p>Here's the first version with naturally generated trees! Yeah they're pretty silly looking.</p>
<p><img src="/images/blog/cascadia/0.1/19.png" alt="" /></p>
<p>Lighting has returned along with some new terrain generation!</p>
<p><img src="/images/blog/cascadia/0.1/20.png" alt="" /></p>
<p>I sprited a new font for Cascadia, and using a distance fog<sup><a href="#fn2">[2]</a></sup> tutorial meant for glsl shaders, I was able to translate it into wgsl, the shading language that wgpu uses.</p>
<p><img src="/images/blog/cascadia/0.1/21.png" alt="" /></p>
<p>Another terrain generation change to include more varied terrain such as cliffs.</p>
<p><img src="/images/blog/cascadia/0.1/22.png" alt="" /></p>
<p>And finally, I vanquished the last collision bug. There were few edge cases where you could phase through the edge of a block, and this video was me testing those.</p>
<p><img src="/videos/blog/cascadia/0.1/23.mp4" alt="" /></p>
<h2>Postmortem</h2>
<p>Whew, that was a lot of progress in a short span of time! And in that time I learned a lot, from programming in Rust (gotta love that borrow checker) to writing low-level graphics with wgpu.</p>
<p>I'm genuinely thrilled with what I have made so far, and I'm excited to continue working on Cascadia to make it my dream game!</p>
<hr />
<section>
<ol>
<li><p>I know there isn't any survival mechanics yet, but those will come soon! <a href="#fnref1">↩︎</a></p>
</li>
<li><p>The fog is coming the fog is coming the fog is coming <a href="#fnref2">↩︎</a></p>
</li>
</ol>
</section>
]]></content>
        <category label="devlogs" term="devlogs"/>
        <category label="cascadia" term="cascadia"/>
        <category label="gamedev" term="gamedev"/>
        <category label="rust" term="rust"/>
        <published>2023-11-03T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[How I Built My Blog]]></title>
        <id>https://axoga.to/blog/how-i-built-my-blog</id>
        <link href="https://axoga.to/blog/how-i-built-my-blog"/>
        <updated>2023-10-06T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Take a look behind the curtain of the tech stack powering my blog]]></summary>
        <content type="html"><![CDATA[<p>I've never been a fan of modern website architecture. Websites these days are so bloated with JavaScript and suffer bad performance on low-end devices. It's because of this that I'm a big supporter of minimal JS websites. And <em>don't</em> get me started on JS frameworks like <a href="https://i.stack.imgur.com/sGhaO.gif">jQuery</a>.</p>
<p>Every website I've built in the past was made exclusively with HTML, CSS--and on rare occasions--a bit of JavaScript as a treat. You'd be surprised how much animation you can do with just CSS selectors!</p>
<p>But if that was all I did then I wouldn't be talking about it much, would I? You could just press F12 to check out my website's page source. No, I did something <em>different</em> this time.</p>
<p>But before I can talk about my blog, I first have to talk about (...my sponsor! Rai-- *explodes*) how I built <a href="https://axoga.to">my website</a>.</p>
<h2>How I built my website</h2>
<p>When I opened up the text editor to start making my website, I had two choices: stick with what I'm familiar with and use plain HTML and CSS, or learn something new.</p>
<p>The world of web design is full of frameworks and libraries to do anything you need. Svelte, Vue, Angular, React, you name it. Years ago I tried Angular once, but I didn't like the way it forced me to structure my code. I ended up making the assumption that other frameworks were the same.</p>
<p>So when I was checking out a <a href="https://www.w3schools.com/react/react_intro.asp">React tutorial</a> on W3Schools, I was surprised to see how simple and agnostic it was. It wasn't a framework, but a view library, and it had one task: quickly and efficiently update the browser's DOM by reacting to changes.</p>
<p>Here's a short briefer of how it works. React keeps a lightweight <em>virtual DOM</em> which is a copy of the browser DOM. Any time there is a change to the virtual DOM, it can pinpoint what changed and reflect that in the browser DOM.</p>
<p>While this is designed for large websites like Facebook and <s>Twitter</s> X to keep track of everything without grinding to a halt, it includes two other paradigms that I personally find invaluable.</p>
<h3>React components</h3>
<p>The first is components. To be able to keep a lightweight shell for the virtual DOM, it needs to be broken up into discrete components. A page might have components for the profile, search bar, and feed, and the feed can contain post components, which could be further broken up into like and share button components.</p>
<pre><code>// Page
&lt;SearchBar /&gt;
&lt;Profile /&gt;
&lt;Feed /&gt;

// Feed component
&lt;div className="Feed"&gt;
	&lt;Post msg={ "Hello" } /&gt;
	&lt;Post msg={ "world" } /&gt;
	&lt;Post msg={ "!" } /&gt;
&lt;/div&gt;

// Post component
&lt;div className="Post"&gt;
	&lt;p&gt;{ props.msg }&lt;/p&gt;
	&lt;div&gt;
		&lt;LikeButton /&gt;
		&lt;ShareButton /&gt;
	&lt;/div&gt;
&lt;/div&gt;
</code></pre>
<p>Now, whenever you need to update a like counter in real time, it's easy for React to see what changed and efficiently update the browser DOM.</p>
<p>These components are modular, meaning I can reuse them in multiple places like functions in programming! They are also data-driven, which means I can pass in data for it to render.</p>
<p>For example, here is the JSON object for one of the game cards on my home page.</p>
<pre><code>{
	name: "Bunpacking",
	url: "https://chailotl.itch.io/bunpacking",
	desc: [
		"You're a postbunny making deliveries around the island.",
		"I made this with Kett in 72 hours for Ludum Dare 53. I did the programming and they did all the assets."
	],
	thumb: "https://img.itch.zone/aW1nLzEyMDQ5MTI3LnBuZw==/315x250%23c/oertQ5.png",
	date: "2023-05-01",
	links: [
		{
			name: "Ludum Dare entry",
			url: "https://ldjam.com/events/ludum-dare/53/bunpacking"
		},
		{
			name: "source code",
			url: "https://github.com/Chailotl/ludum-dare-53"
		}
	]
}
</code></pre>
<p>React takes this and puts it into a <code>ShowcaseCard</code> component I made and it renders out like this! Pretty slick, huh?</p>
<p><img src="/images/blog/bunpacking_card.png" alt="" /></p>
<h3>JavaScript XML</h3>
<p>If you've ever created HTML elements in JavaScript, you'll know it isn't pretty. And if you have to create a lot of nested tags, it's best to use something like <a href="https://www.w3schools.com/tags/tag_template.asp">template tags</a>.</p>
<pre><code>// Clunky!
const myElement = document.createElement("h1");
myElement.innerText = "Hello world!";
</code></pre>
<p>But all is not lost for there is something better. <em>Much</em> Better.</p>
<p>Introducing… JavaScript XML (JSX)!</p>
<pre><code>// So cool!
const myElement = &lt;h1&gt;Hello world!&lt;/h1&gt;;
</code></pre>
<p>That's right, <em>inline HTML</em>. You can even do inline JavaScript expressions too!</p>
<pre><code>const text = "Hello world!";
const myElement = &lt;h1&gt;{text}&lt;/h1&gt;;
</code></pre>
<p>While this is just pretty syntactic sugar that compiles down to plain JavaScript, it makes programming HTML fun and easy!</p>
<h3>Create React App</h3>
<p>If you're privy to JS, you'll know that these fancy React components and JSX won't "just work" in JS because it doesn't natively understand them. You'll need a transpiler to turn it into valid JS, just like you would with TypeScript. That's where <strong>Create React App</strong> (CRA) comes in.</p>
<p>To make jumpstarting a React app quick and easy, the team behind React created CRA. Not only does it handle the transpiling, it also doubles as your local development web server. Any changes you do in the code will be instantly reflected on the webpage, making prototyping a breeze.</p>
<p>There is more to what React can do, but the gist of it is that my website is built on React and its wonderful feature set.</p>
<h2>How I built my blog</h2>
<p>"So your blog is built with React? Case closed then--" Not so fast! While I could have used just React and called it a day, there were some features I was looking for in my blog, and some problems I needed to overcome.</p>
<ol>
<li>Client-side render load</li>
<li>Writing blog posts in markdown</li>
</ol>
<p>Let's go over the first point.</p>
<h3>The limitations of Create React App</h3>
<p>CRA is an awesome way to quickly get into making your first React application, but it has one glaring problem: <strong>everything is rendered client-side.</strong></p>
<p>When you open the React page, your browser is sent an empty HTML document, and the scripts which are included with it then build the page. <em>All in your browser.</em></p>
<p>What this means is that for a short amount of time you will see nothing on the page until React renders it. This is also an unnecessary load on your browser. It gets worse if you have JavaScript disabled: you'll see never see anything!</p>
<p>While there are some clever solutions to this, such as pre-rendering the HTML then hydrating<sup><a href="#fn1">[1]</a></sup> it, I wasn't going to be happy with this ultimately.<sup><a href="#fn2">[2]</a></sup></p>
<h3>Switching to Next.js</h3>
<p>There are two methods besides client-side rendering, and those are <strong>server-side rendering</strong> (SSR) and <strong>static site generation</strong> (SSG).</p>
<p>SSR as the name implies is having the server render the HTML each time a browser requests it, and SSG turns the interactive React app into static HTML files you can upload to any webhost.</p>
<p>I chose to go with the SSR route with Next.js, a framework built on React. Next.js has a lot of nice features, such as caching rendered pages, and automatically optimizing images, fonts, and scripts for even speedier page loads.</p>
<h3>Implementing markdown</h3>
<p>The main thing I wanted for my blog is to be able to write posts in markdown like so.</p>
<pre><code># A heading

Normal text, *italic text*, a [link](https://google.com), and some **bold text**!
</code></pre>
<p>Lucky for me, getting this to work seamlessly with Next.js was a chinch. I just had to install <code>@next/mdx</code>, configure it in <code>next.config.js</code>, and then I could import markdown files like a React component!<sup><a href="#fn3">[3]</a></sup></p>
<p>Because this is all rendered server-side, I can do stuff like grabbing all posts with a certain tag or category without making the client do a bunch of network requests for <em>each and every markdown file.</em></p>
<h3>Extending markdown with plugins</h3>
<p>The way the markdown importer works is by leveraging the <strong>unified</strong> ecosystem. It's a project that transforms content with abstract syntax trees (ASTs), where remark handles markdown and rehype handles HMTL.</p>
<p>In layman's terms, it can translate markdown into a "shared language", and then translate that into HTML.</p>
<p>You may think this is over-engineered, but doing it this way provides a powerful ability: using remark and rehype plugins to do additional transformations between markdown and HTML.</p>
<p>One of these plugins is <code>remark-gfm</code>, and it adds support for GitHub-flavor markdown. It lets me do <s>strikethroughs</s>, footnotes (Hi :3), tables, and task lists.</p>
<table>
<thead>
<tr>
<th>Name</th>
<th>Height</th>
<th>Weight</th>
<th>Color</th>
</tr>
</thead>
<tbody>
<tr>
<td>Chailotl</td>
<td>3 ft</td>
<td>35 lbs</td>
<td>lavender</td>
</tr>
</tbody>
</table>
<ul>
<li>[ ] chores</li>
<li>[x] playing games</li>
</ul>
<p>Here's a list of the remark and rehype plugins I am currently using.</p>
<ul>
<li><code>remark-breaks</code> adds <code>&lt;br&gt;</code> to preserve line breaks</li>
<li><code>remark-frontmatter</code> is something I will be talking about later!</li>
<li><code>remark-emoji</code> turns <code>:shortcodes:</code> into emojis</li>
<li><code>rehype-twemojify</code> turns emojis into Twemojis</li>
<li><code>rehype-highlight</code> adds CSS classes to code blocks so I can give them syntax highlighting</li>
<li><code>rehype-slug</code> adds id's to headers</li>
<li><code>rehype-autolink-headings</code> uses those id's to add links to headings</li>
</ul>
<h3>The magic of MDX</h3>
<p>When I was researching how to import markdown, I stumbled across something magical. It's called <a href="https://mdxjs.com/">MDX</a>, and it lets me use JSX in my markdown. Oh, that wavy text I used just now? That's a React component in action!</p>
<p>In theory I could write any component I want to do whatever I need and import it into my blog posts. Could be charts, interactive elements, YouTube embeds, you name it!</p>
<p>Maybe I'll write a future blog post sharing all the fun components I came up with.</p>
<h3>Metadata</h3>
<p>Two months ago Kett introduced me to an awesome writing app called <a href="https://obsidian.md/">Obsidian</a>. Its notes are stored in markdown, and it has a feature called front matter.</p>
<p>In essence, front matter is the note's metadata, and it can be queried in a variety of ways in Obsidian. For example you could have a folder of video game reviews, and the front matter could store information such as the game's name, description, date of review, your rating, etc.</p>
<p>You can add front matter to a note by writing YAML at the top and enclosing it with a fence.</p>
<pre><code>---
name: A Hat in Time
description: 'A Hat in Time is a cute-as-heck 3D platformer featuring a little girl who stitches hats for wicked powers! Freely explore giant worlds and recover Time Pieces to travel to new heights!'
genres: ['3D Platformer', 'Collectathon', 'Adventure']
myRating: 9
wouldRecommend: true
reviewDate: 2022-08-23
---
</code></pre>
<p>And I thought to myself, I could use this for my blog posts! So I did. Here's the first post's front matter as an example.</p>
<pre><code>---
title: Why I want to blog
description: As a kid I was enchanted with the idea of blogging so I created many blogs about different topics growing up. Today none of them exist, so what made me want to start another blog?
lead: ''
category: blog
tags: ['personal', 'meta']
publishDate: 2023-09-12
isPublished: true
---
</code></pre>
<ul>
<li><code>title</code> and <code>abstract</code> describe the post when reading from the main page</li>
<li><code>category</code> decides whether it's located under blogs or devlogs</li>
<li><code>tags</code> lets you search the post by tag</li>
<li><code>publishDate</code> lists when the post was published and determines the sorting order</li>
<li><code>isPublished</code> determines whether the post is visible on my blog</li>
</ul>
<p>Who knows, I could have unpublished blogs you could read if you just type the right url ;)</p>
<h3>Setting up an RSS feed</h3>
<p>What's a blog without an RSS feed? Well, it would still be a blog because I'm not sure if many people still use RSS feeds, but hey, it'll make me happy knowing my blog is complete.</p>
<p>This was actually something I struggled quite a lot with, and for one specific reason. Fetching all the blog posts and reading their front matter to build the feed was easy enough, but turning the markdown into HTML for the feed's content was a lot harder than it should have been. I tried what must've been a dozen different methods, only for all of them to fail one way or another.</p>
<p>The markdown-as-a-React-component had no built-in method to turn it into a string. React had a function to render HTML as a string, but it was not only broken server-side, but didn't even work when I fixed it because of asynchronous bullshit.</p>
<p>I tried a couple of HTML-to-string modules, but none of them worked because of the same async problem.</p>
<p>At one point I just gave up and shoved the raw markdown into the feed, but Kett was quick to notice how janky it looked.</p>
<p>So I resigned and went with the nuclear route of running an entire separate markdown-to-HTML library just to get it working for my feed.</p>
<p>While I could have done that in the first place and saved myself a lot of headache, the reason I wanted to use Next.js to import the markdown and turn it into a string was so that I used the same MDX import pipeline with the same remark/rehype plugin configs. Oh well.</p>
<h2>Postmortem</h2>
<p>So why go through all the effort of building my own blog stack instead of using a ready-to-use solution like Wordpress or Medium?</p>
<p>For me it was a learning opportunity on what goes into making a blog from scratch. I can then use that knowledge for future projects, especially for when I apply to web dev jobs!</p>
<p>In the end I am happy with how all these pieces fit together, but that doesn't mark the end of my blog's development! There's always more to do, from refining my blog's theme to creating more React components to use in my posts, so stay tuned!</p>
<hr />
<section>
<ol>
<li><p>Hydration is the process of using client-side JavaScript to add interactivity to server-rendered HTML. <a href="#fnref1">↩︎</a></p>
</li>
<li><p>CRA has other problems such as compatibility issues with some modules, and it's rather outdated. <a href="#fnref2">↩︎</a></p>
</li>
<li><p>I had to write a bit of wrapper code to make it work dynamically for my use case. <a href="#fnref3">↩︎</a></p>
</li>
</ol>
</section>
]]></content>
        <category label="articles" term="articles"/>
        <category label="technical" term="technical"/>
        <category label="meta" term="meta"/>
        <published>2023-10-06T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Why I Want To Blog]]></title>
        <id>https://axoga.to/blog/why-i-want-to-blog</id>
        <link href="https://axoga.to/blog/why-i-want-to-blog"/>
        <updated>2023-09-12T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[As a kid I was enchanted with the idea of blogging so I created many blogs about different topics growing up. Today none of them exist, so what made me want to start another blog?]]></summary>
        <content type="html"><![CDATA[<p>As a kid I was enchanted with the idea of blogging. The idea of making my mark on the internet one blog post at a time. I created many blogs growing up, each one about whatever topic was holding my attention at the time.</p>
<p>The very first blog I made was a video game review blog, and the first (and only) post was a review of <em>Super Mario World</em> on the SNES, during a time when the Wii was the latest Nintendo console.</p>
<p>My motivation stemmed from a magazine called <em>Game Informer</em>. Each month I would get a new issue in the mail, and I was excited to hear what was happening in the world of video games. I loved reading their articles and reviews, and I wanted to be a part of it.</p>
<p>Although my game review blog may not have been useful to anyone, I did it for myself and I was happy with it.</p>
<p>The next couple blogs I made were about other topics such as Minecraft, a now-defunct game studio I wanted to start, and Unity game development. But today none of those blogs exist anymore, not even leaving a digital trace on the internet.</p>
<p>So what makes this time different?</p>
<h2>Why I want to blog</h2>
<p>A blog can be many things, a personal journal, a travel photoblog, even a video game devlog. Despite their diverse differences, all of them share one key component: they are a window to the writer's soul.</p>
<p>When you read someone's writing, you are reading more than just words on a page, you catch a glimpse of their life experiences, expressed through their writing style and word choice, their emotions and reactions, and the topics they chose to write about or not.</p>
<p>But just as it is a window for others to peer into, it is a window for me to learn more about myself. By writing down my thoughts, it gives me the opportunity to critically think them over.</p>
<p>Thoughts in my head are fuzzy and ephemeral. Words are concrete and lasting.</p>
<p>Blogging is also an opportunity to learn in public, where you write about what you learned as if you were writing for someone else—because in a couple months you will be.</p>
<p>Have you ever come back to something you were writing, only to not remember the train and thoughts it was based off of? As programmers we comment our code not just for ourselves, but for others <em>and</em> our future selves who will not remember why we wrote it this way.</p>
<p>This is what learning in public is about. By writing down how we learned something as if we were writing for someone else, we can then quickly pick it back up later.</p>
<h2>What I want to blog</h2>
<p>So what do I want to blog about exactly? Well, whatever I feel like writing about, I suppose! I have thoughts about all kinds of topics I'd like to gush about, ideas for future game projects I want to share, and writing devlogs for the games I develop. No doubt there will be more things I'll share here when the mood strikes!</p>
<p>This will be my personal outlet to share what I think, what I learn, and what I experience. It will help guide me to improve my writing and articulate my thoughts clearer, even if nobody else is reading.</p>
<p>It will be my little slice of the internet.</p>
]]></content>
        <category label="personal" term="personal"/>
        <category label="meta" term="meta"/>
        <published>2023-09-12T00:00:00.000Z</published>
    </entry>
</feed>