<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
    <title>Matt&apos;s Blog</title>
    <link>https://mattsmith.id.au/blog.html</link>
    <description>This blog is focused on software development and draws on things I&apos;m learning or thinking about at work or while working on hobby projects.</description>
    <atom:link href="https://mattsmith.id.au/blog.xml" rel="self" type="application/rss+xml" />
    
        <item>
            <title>My Minecraft Mod: Kiosk Block</title>
            <link>https://mattsmith.id.au/blog/2026/09-20-minecraft-mod-kiosk-block.html</link>
            <description><![CDATA[ <h1>My Minecraft Mod: Kiosk Block</h1><p>Previously I wrote about <a href="https://mattsmith.id.au/blog/2026/09-15-getting-started-minecraft-modding.html">Getting Started with Minecraft Modding</a>; this is a continuation of that topic.</p><p>In learning to mod Minecraft, an early idea I had was to have a set of special worker villagers that gather resources for me and bring them to a central warehouse. Rather than just have them deposit into random chests, I wanted to add a sense of collaboration between the villagers. Still, I needed some central point that the workers would use for exchanging resources. The kiosk block is my way to do it. Workers deposit items that they want to store in the kiosk. They also communicate a list of resources they need such as pickaxes. A kiosk worker is in charge of the warehouse and fetches the required items as needed while storing the things dropped off at the kiosk.</p><p>The kiosk is basically a custom chest: it behaves exactly like a chest but has a custom texture and an ID within the game so the workers treat it as the kiosk and not just any other chest (while it's just a chest, it's semantically different).</p><p><img src="https://mattsmith.id.au/images/2026/2026-09-20_15.43.00.png" alt="The Kiosk Block" /></p><h2>Background</h2><p>Blocks in Minecraft are all descendants of the <code>BlockBehavior</code> class. The <code>KioskBlock</code> extends the <code>BaseEntityBlock</code> which adds the ability for the block to store state per instance via BlockEntity. The basic block classes set up shared properties for all blocks of the given type.</p><figure>
<a href="https://mattsmith.id.au/images/2026/mermaid-diagram-2026-10-05-204333.png"><img alt="BlockBehavior Class Hierarchy" src="https://mattsmith.id.au/images/2026/mermaid-diagram-2026-10-05-204333-680.png" /></a>
<figcaption>BlockBehavior Class Hierarchy (click to expand)</figcaption>
</figure><p>Block entities are state stored on blocks per position / instance. In the case of the kiosk (and chests), the items stored in the kiosk must be managed by a descendant of <code>BlockEntity</code>. The <code>BaseContainerBlockEntity</code> is what chests are descended from so <code>KioskBlockEntity</code> extends that in order to get the chest-like menus and behaviour.</p><figure>
<a href="https://mattsmith.id.au/images/2026/mermaid-diagram-2026-10-05-204246.png"><img alt="BlockEntity Class Hierarchy" src="https://mattsmith.id.au/images/2026/mermaid-diagram-2026-10-05-204246-680.png" /></a>
<figcaption>BlockEntity Class Hierarchy (click to expand)</figcaption>
</figure><h2>The Mod Base Class</h2><p>The base class of the mod should be set up by the template or mod generator. I think I renamed mine from ExampleMod. It needs to have an <code>id()</code> method that is used to create the custom block for the mod.</p><p>You should see it implements <code>ModInitializer</code>, sets a <code>MOD_ID</code> string and declares the <code>id()</code> function.</p><p>The <code>fabric.mod.json</code> declares the <code>entrypoints</code> object that may need renaming to match your main mod class.</p><h2>The Kiosk Block</h2><p>The Fabric Documentation explains how to create a custom block. The first article is <a href="https://docs.fabricmc.net/develop/blocks/first-block">Creating Your First Block</a>.</p><p>The basic steps are:</p><ul><li>Create a class to keep the mod item ids</li><li>Create a class that registers the mod block</li><li>Call the block initializers from the main mod class <code>onInitialize()</code> method.</li><li>Set BlockBehaviour.Properties with strength, sound type, etc.</li><li>Add the block to the creative tab</li><li>Add a translation</li><li>Customise the model and texture</li><li>Add a state definition</li><li>Add block drops</li><li>Set harvesting tool</li><li>Set mining levels</li></ul><p>The tutorial doesn't go into much depth on the properties since it's a big topic. Here's what I set:</p><pre><code class="language-java"><span style="color:#0086b3">BlockBehaviour</span><span style="color:#0086b3">.</span><span style="color:#0086b3">Properties</span><span style="color:#323232">.</span><span style="color:#323232">of</span><span style="color:#323232">(</span><span style="color:#323232">)</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">.</span><span style="color:#323232">strength</span><span style="color:#323232">(</span><span style="color:#0086b3">5.0</span><span style="color:#a71d5d;font-weight:bold">f</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">6.0</span><span style="color:#a71d5d;font-weight:bold">f</span><span style="color:#323232">)</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">.</span><span style="color:#323232">noOcclusion</span><span style="color:#323232">(</span><span style="color:#323232">)</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">.</span><span style="color:#323232">setId</span><span style="color:#323232">(</span><span style="color:#0086b3">ModBlockItemIds</span><span style="color:#0086b3">.</span><span style="color:#0086b3">KIOSK</span><span style="color:#323232">.</span><span style="color:#323232">block</span><span style="color:#323232">(</span><span style="color:#323232">)</span><span style="color:#323232">)</span></code></pre><p>The strength makes it so I don't accidentally one-shot break the kiosk. The <code>noOcclusion</code> thing is to prevent certain sides of the kiosk from becoming transparent since I made it a weird shape. The mod crashed without the setId() call.</p><p>For the model and texture I used <a href="https://blockbench.net/">Blockbench</a>. There are tutorials on YouTube etc that will teach you to use it. When done, click &quot;File -&gt; Export -&gt; Export Block/Item Model&quot; to get the model.json file. Also, for the texture, right click on the texture file and do a &quot;Save As&quot; to get a PNG file.</p><p>I didn't add a harvesting tool or set mining levels.</p><p>The Fabric documentation has pages going into more details about the Block Models and Block States. For the Kiosk block, I didn't need anything special so it's all default. I did put a few things into the Block Entity though.</p><h2>Block Entity</h2><p>The <a href="https://docs.fabricmc.net/develop/blocks/block-entities">Fabric Tutorial for Block Entities</a> explains how to extend <code>BlockEntity</code>. For the kiosk, I extended <code>BaseContainerBlockEntity</code> because the kiosk is basically a chest. I didn't use <code>RandomizableContainerBlockEntity</code> which is what chests actually extend because I don't need the random loot - kiosks don't randomly spawn with items in them.</p><p>The documentation includes an example of making the block entity count clicks. The kiosk block has different functionality.</p><p>First I set the chest size to 27 and implemented the mandatory overrides:</p><pre><code class="language-java"><span style="color:#a71d5d;font-weight:bold">public</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">class</span><span style="color:#323232"> </span><span style="color:#0086b3">KioskBlockEntity</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">extends</span><span style="color:#323232"> </span><span style="color:#0086b3">BaseContainerBlockEntity</span><span style="color:#323232"> </span><span style="color:#323232">{</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#a71d5d;font-weight:bold">private</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">static</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">final</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">int</span><span style="color:#323232"> </span><span style="color:#323232">SIZE</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">=</span><span style="color:#323232"> </span><span style="color:#0086b3">27</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#a71d5d;font-weight:bold">private</span><span style="color:#323232"> </span><span style="color:#0086b3">NonNullList</span><span style="color:#323232">&lt;</span><span style="color:#0086b3">ItemStack</span><span style="color:#323232">&gt;</span><span style="color:#323232"> items </span><span style="color:#a71d5d;font-weight:bold">=</span><span style="color:#323232"> </span><span style="color:#0086b3">NonNullList</span><span style="color:#323232">.</span><span style="color:#323232">withSize</span><span style="color:#323232">(</span><span style="color:#0086b3">SIZE</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">ItemStack</span><span style="color:#0086b3">.</span><span style="color:#0086b3">EMPTY</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">@</span><span style="color:#323232">Override</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#a71d5d;font-weight:bold">public</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">int</span><span style="color:#323232"> </span><span style="color:#795da3;font-weight:bold">getContainerSize</span><span style="color:#323232">(</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#323232">{</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#a71d5d;font-weight:bold">return</span><span style="color:#323232"> </span><span style="color:#0086b3">SIZE</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">}</span><span style="color:#323232">
</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">@</span><span style="color:#323232">Override</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#a71d5d;font-weight:bold">protected</span><span style="color:#323232"> </span><span style="color:#0086b3">NonNullList</span><span style="color:#323232">&lt;</span><span style="color:#0086b3">ItemStack</span><span style="color:#323232">&gt;</span><span style="color:#323232"> </span><span style="color:#795da3;font-weight:bold">getItems</span><span style="color:#323232">(</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#323232">{</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#a71d5d;font-weight:bold">return</span><span style="color:#323232"> </span><span style="color:#323232">this</span><span style="color:#323232">.</span><span style="color:#323232">items</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">}</span><span style="color:#323232">
</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">@</span><span style="color:#323232">Override</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#a71d5d;font-weight:bold">protected</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">void</span><span style="color:#323232"> </span><span style="color:#795da3;font-weight:bold">setItems</span><span style="color:#323232">(</span><span style="color:#0086b3">NonNullList</span><span style="color:#323232">&lt;</span><span style="color:#0086b3">ItemStack</span><span style="color:#323232">&gt;</span><span style="color:#323232"> </span><span style="color:#323232">items</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#323232">{</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#323232">this</span><span style="color:#323232">.</span><span style="color:#323232">items </span><span style="color:#a71d5d;font-weight:bold">=</span><span style="color:#323232"> items</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">}</span><span style="color:#323232">
</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">@</span><span style="color:#323232">Override</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#a71d5d;font-weight:bold">protected</span><span style="color:#323232"> </span><span style="color:#0086b3">AbstractContainerMenu</span><span style="color:#323232"> </span><span style="color:#795da3;font-weight:bold">createMenu</span><span style="color:#323232">(</span><span style="color:#a71d5d;font-weight:bold">int</span><span style="color:#323232"> </span><span style="color:#323232">containerId</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">Inventory</span><span style="color:#323232"> </span><span style="color:#323232">inventory</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#323232">{</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#969896;font-style:italic">//</span><span style="color:#969896;font-style:italic"> threeRows is hardcoded to SIZE slots; changing SIZE requires a different ChestMenu factory or a custom menu.
</span><span style="color:#323232">        </span><span style="color:#a71d5d;font-weight:bold">return</span><span style="color:#323232"> </span><span style="color:#0086b3">ChestMenu</span><span style="color:#323232">.</span><span style="color:#323232">threeRows</span><span style="color:#323232">(</span><span style="color:#323232">containerId</span><span style="color:#323232">,</span><span style="color:#323232"> inventory</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#323232">this</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">}</span><span style="color:#323232">
</span><span style="color:#323232">}</span></code></pre><p>There's also a name translation for the chest menu:</p><pre><code class="language-java"><span style="color:#323232">    </span><span style="color:#a71d5d;font-weight:bold">private</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">static</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">final</span><span style="color:#323232"> </span><span style="color:#0086b3">Component</span><span style="color:#323232"> </span><span style="color:#0086b3">DEFAULT_NAME</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">=</span><span style="color:#323232"> </span><span style="color:#0086b3">Component</span><span style="color:#323232">.</span><span style="color:#323232">translatable</span><span style="color:#323232">(</span><span style="color:#183691">&quot;</span><span style="color:#183691">container.supplychain.kiosk</span><span style="color:#183691">&quot;</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">@</span><span style="color:#323232">Override</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#a71d5d;font-weight:bold">protected</span><span style="color:#323232"> </span><span style="color:#0086b3">Component</span><span style="color:#323232"> </span><span style="color:#323232">getDefaultName</span><span style="color:#323232">(</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#323232">{</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#a71d5d;font-weight:bold">return</span><span style="color:#323232"> </span><span style="color:#0086b3">DEFAULT_NAME</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">}</span></code></pre><p>And in <code>src/main/resources/assets/supplychain/lang/en_us.json</code>, I added the translation entry under the existing block name translation (that is added when creating the block):</p><pre><code class="language-json"><span style="color:#323232">{</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#183691;font-weight:bold">&quot;</span><span style="color:#183691;font-weight:bold">block.supplychain.kiosk</span><span style="color:#183691;font-weight:bold">&quot;</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#323232">&quot;</span><span style="color:#323232">Kiosk</span><span style="color:#323232">&quot;</span><span style="color:#323232">,</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#183691;font-weight:bold">&quot;</span><span style="color:#183691;font-weight:bold">container.supplychain.kiosk</span><span style="color:#183691;font-weight:bold">&quot;</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#323232">&quot;</span><span style="color:#323232">Kiosk</span><span style="color:#323232">&quot;</span><span style="color:#323232">
</span><span style="color:#323232">}</span></code></pre><p>The kiosk also manages the &quot;warehouse&quot; stock. When a special kiosk worker adds and removes items from the kiosk, it updates the stock ledger as well. The stock levels are used by other workers to trigger when to get more resources or craft more items.</p><p>It's pretty standard java:</p><pre><code class="language-java"><span style="color:#323232">    </span><span style="color:#a71d5d;font-weight:bold">private</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">final</span><span style="color:#323232"> </span><span style="color:#0086b3">Map</span><span style="color:#323232">&lt;</span><span style="color:#0086b3">Item</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">Integer</span><span style="color:#323232">&gt;</span><span style="color:#323232"> stock </span><span style="color:#a71d5d;font-weight:bold">=</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">new</span><span style="color:#323232"> </span><span style="color:#0086b3">HashMap</span><span style="color:#323232">&lt;</span><span style="color:#323232">&gt;</span><span style="color:#323232">(</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#a71d5d;font-weight:bold">public</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">int</span><span style="color:#323232"> </span><span style="color:#323232">getStockCount</span><span style="color:#323232">(</span><span style="color:#0086b3">Item</span><span style="color:#323232"> item</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#323232">{</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#a71d5d;font-weight:bold">return</span><span style="color:#323232"> </span><span style="color:#323232">this</span><span style="color:#323232">.</span><span style="color:#323232">stock</span><span style="color:#323232">.</span><span style="color:#323232">getOrDefault</span><span style="color:#323232">(</span><span style="color:#323232">item</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">0</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">}</span><span style="color:#323232">
</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#a71d5d;font-weight:bold">public</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">void</span><span style="color:#323232"> </span><span style="color:#323232">incrementStock</span><span style="color:#323232">(</span><span style="color:#0086b3">Item</span><span style="color:#323232"> item</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">int</span><span style="color:#323232"> amount</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#323232">{</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#a71d5d;font-weight:bold">if</span><span style="color:#323232"> </span><span style="color:#323232">(</span><span style="color:#323232">amount </span><span style="color:#a71d5d;font-weight:bold">&lt;=</span><span style="color:#323232"> </span><span style="color:#0086b3">0</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">return</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#323232">this</span><span style="color:#323232">.</span><span style="color:#323232">stock</span><span style="color:#323232">.</span><span style="color:#323232">merge</span><span style="color:#323232">(</span><span style="color:#323232">item</span><span style="color:#323232">,</span><span style="color:#323232"> amount</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">Integer</span><span style="color:#323232">::</span><span style="color:#323232">sum</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#323232">setChanged</span><span style="color:#323232">(</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">}</span><span style="color:#323232">
</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#a71d5d;font-weight:bold">public</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">void</span><span style="color:#323232"> </span><span style="color:#323232">decrementStock</span><span style="color:#323232">(</span><span style="color:#0086b3">Item</span><span style="color:#323232"> item</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">int</span><span style="color:#323232"> amount</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#323232">{</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#a71d5d;font-weight:bold">if</span><span style="color:#323232"> </span><span style="color:#323232">(</span><span style="color:#323232">amount </span><span style="color:#a71d5d;font-weight:bold">&lt;=</span><span style="color:#323232"> </span><span style="color:#0086b3">0</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">return</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#a71d5d;font-weight:bold">int</span><span style="color:#323232"> current </span><span style="color:#a71d5d;font-weight:bold">=</span><span style="color:#323232"> </span><span style="color:#323232">this</span><span style="color:#323232">.</span><span style="color:#323232">stock</span><span style="color:#323232">.</span><span style="color:#323232">getOrDefault</span><span style="color:#323232">(</span><span style="color:#323232">item</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">0</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#a71d5d;font-weight:bold">int</span><span style="color:#323232"> next </span><span style="color:#a71d5d;font-weight:bold">=</span><span style="color:#323232"> </span><span style="color:#0086b3">Math</span><span style="color:#323232">.</span><span style="color:#323232">max</span><span style="color:#323232">(</span><span style="color:#0086b3">0</span><span style="color:#323232">,</span><span style="color:#323232"> current </span><span style="color:#a71d5d;font-weight:bold">-</span><span style="color:#323232"> amount</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#a71d5d;font-weight:bold">if</span><span style="color:#323232"> </span><span style="color:#323232">(</span><span style="color:#323232">next </span><span style="color:#a71d5d;font-weight:bold">==</span><span style="color:#323232"> </span><span style="color:#0086b3">0</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#323232">{</span><span style="color:#323232">
</span><span style="color:#323232">            </span><span style="color:#323232">this</span><span style="color:#323232">.</span><span style="color:#323232">stock</span><span style="color:#323232">.</span><span style="color:#323232">remove</span><span style="color:#323232">(</span><span style="color:#323232">item</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#323232">}</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">else</span><span style="color:#323232"> </span><span style="color:#323232">{</span><span style="color:#323232">
</span><span style="color:#323232">            </span><span style="color:#323232">this</span><span style="color:#323232">.</span><span style="color:#323232">stock</span><span style="color:#323232">.</span><span style="color:#323232">put</span><span style="color:#323232">(</span><span style="color:#323232">item</span><span style="color:#323232">,</span><span style="color:#323232"> next</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#323232">}</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#323232">setChanged</span><span style="color:#323232">(</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">}</span><span style="color:#323232">
</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#a71d5d;font-weight:bold">public</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">void</span><span style="color:#323232"> </span><span style="color:#323232">resetStock</span><span style="color:#323232">(</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#323232">{</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#323232">this</span><span style="color:#323232">.</span><span style="color:#323232">stock</span><span style="color:#323232">.</span><span style="color:#323232">clear</span><span style="color:#323232">(</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#323232">setChanged</span><span style="color:#323232">(</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">}</span><span style="color:#323232">
</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#a71d5d;font-weight:bold">public</span><span style="color:#323232"> </span><span style="color:#0086b3">Map</span><span style="color:#323232">&lt;</span><span style="color:#0086b3">Item</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">Integer</span><span style="color:#323232">&gt;</span><span style="color:#323232"> </span><span style="color:#323232">getStockSnapshot</span><span style="color:#323232">(</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#323232">{</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#a71d5d;font-weight:bold">return</span><span style="color:#323232"> </span><span style="color:#0086b3">Collections</span><span style="color:#323232">.</span><span style="color:#323232">unmodifiableMap</span><span style="color:#323232">(</span><span style="color:#323232">this</span><span style="color:#323232">.</span><span style="color:#323232">stock</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">}</span></code></pre><p>While the focus of the tutorial is the <code>BlockEntity</code> class, there is some interaction with the <code>BaseEntityBlock</code> subclass when it comes to block interaction. I implemented something similar to the tutorial, partly for logging the stock but it was required for doing the chest interaction:</p><pre><code class="language-java"><span style="color:#323232">    </span><span style="color:#969896;font-style:italic">//</span><span style="color:#969896;font-style:italic"> In class KioskBlock (not KioskBlockEntity):
</span><span style="color:#323232">    </span><span style="color:#323232">@</span><span style="color:#323232">Override</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#a71d5d;font-weight:bold">protected</span><span style="color:#323232"> </span><span style="color:#0086b3">InteractionResult</span><span style="color:#323232"> </span><span style="color:#323232">useWithoutItem</span><span style="color:#323232">(</span><span style="color:#0086b3">BlockState</span><span style="color:#323232"> state</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">Level</span><span style="color:#323232"> level</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">BlockPos</span><span style="color:#323232"> pos</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">Player</span><span style="color:#323232"> player</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">BlockHitResult</span><span style="color:#323232"> hitResult</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#323232">{</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#a71d5d;font-weight:bold">if</span><span style="color:#323232"> </span><span style="color:#323232">(</span><span style="color:#a71d5d;font-weight:bold">!</span><span style="color:#323232">level</span><span style="color:#323232">.</span><span style="color:#323232">isClientSide</span><span style="color:#323232">(</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">&amp;&amp;</span><span style="color:#323232"> level</span><span style="color:#323232">.</span><span style="color:#323232">getBlockEntity</span><span style="color:#323232">(</span><span style="color:#323232">pos</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">instanceof</span><span style="color:#323232"> </span><span style="color:#0086b3">KioskBlockEntity</span><span style="color:#323232"> kioskBlockEntity</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#323232">{</span><span style="color:#323232">
</span><span style="color:#323232">            </span><span style="color:#0086b3">SupplyChain</span><span style="color:#0086b3">.</span><span style="color:#0086b3">LOGGER</span><span style="color:#323232">.</span><span style="color:#323232">info</span><span style="color:#323232">(</span><span style="color:#183691">&quot;</span><span style="color:#183691">[Kiosk@{}] stock: {}</span><span style="color:#183691">&quot;</span><span style="color:#323232">,</span><span style="color:#323232"> pos</span><span style="color:#323232">,</span><span style="color:#323232"> kioskBlockEntity</span><span style="color:#323232">.</span><span style="color:#323232">getStockSnapshot</span><span style="color:#323232">(</span><span style="color:#323232">)</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">            player</span><span style="color:#323232">.</span><span style="color:#323232">openMenu</span><span style="color:#323232">(</span><span style="color:#323232">kioskBlockEntity</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#323232">}</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#a71d5d;font-weight:bold">return</span><span style="color:#323232"> </span><span style="color:#0086b3">InteractionResult</span><span style="color:#0086b3">.</span><span style="color:#0086b3">SUCCESS</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">}</span></code></pre><p>There are a couple of tutorials about customising chest-like blocks on the Fabric website: <a href="https://docs.fabricmc.net/develop/blocks/block-containers">Block Containers</a> and <a href="https://docs.fabricmc.net/develop/blocks/container-menus">Container Menus</a>.</p><p>There are a few other convenience things in the class that I won't mention yet.</p><p>In the &quot;Saving and Loading Data&quot; side of things, I save the stock:</p><pre><code class="language-java"><span style="color:#323232">    </span><span style="color:#a71d5d;font-weight:bold">private</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">static</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">final</span><span style="color:#323232"> </span><span style="color:#0086b3">Codec</span><span style="color:#323232">&lt;</span><span style="color:#0086b3">Map</span><span style="color:#323232">&lt;</span><span style="color:#0086b3">Item</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">Integer</span><span style="color:#323232">&gt;</span><span style="color:#323232">&gt;</span><span style="color:#323232"> </span><span style="color:#0086b3">STOCK_CODEC</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">=</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#0086b3">Codec</span><span style="color:#323232">.</span><span style="color:#323232">unboundedMap</span><span style="color:#323232">(</span><span style="color:#0086b3">BuiltInRegistries</span><span style="color:#0086b3">.</span><span style="color:#0086b3">ITEM</span><span style="color:#323232">.</span><span style="color:#323232">byNameCodec</span><span style="color:#323232">(</span><span style="color:#323232">)</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">Codec</span><span style="color:#0086b3">.</span><span style="color:#0086b3">INT</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">@</span><span style="color:#323232">Override</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#a71d5d;font-weight:bold">protected</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">void</span><span style="color:#323232"> </span><span style="color:#323232">loadAdditional</span><span style="color:#323232">(</span><span style="color:#0086b3">ValueInput</span><span style="color:#323232"> input</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#323232">{</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#323232">super</span><span style="color:#323232">.</span><span style="color:#323232">loadAdditional</span><span style="color:#323232">(</span><span style="color:#323232">input</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#323232">this</span><span style="color:#323232">.</span><span style="color:#323232">items </span><span style="color:#a71d5d;font-weight:bold">=</span><span style="color:#323232"> </span><span style="color:#0086b3">NonNullList</span><span style="color:#323232">.</span><span style="color:#323232">withSize</span><span style="color:#323232">(</span><span style="color:#323232">this</span><span style="color:#323232">.</span><span style="color:#323232">getContainerSize</span><span style="color:#323232">(</span><span style="color:#323232">)</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">ItemStack</span><span style="color:#0086b3">.</span><span style="color:#0086b3">EMPTY</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#0086b3">ContainerHelper</span><span style="color:#323232">.</span><span style="color:#323232">loadAllItems</span><span style="color:#323232">(</span><span style="color:#323232">input</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#323232">this</span><span style="color:#323232">.</span><span style="color:#323232">items</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#323232">this</span><span style="color:#323232">.</span><span style="color:#323232">stock</span><span style="color:#323232">.</span><span style="color:#323232">clear</span><span style="color:#323232">(</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">        input</span><span style="color:#323232">.</span><span style="color:#323232">read</span><span style="color:#323232">(</span><span style="color:#183691">&quot;</span><span style="color:#183691">Stock</span><span style="color:#183691">&quot;</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">STOCK_CODEC</span><span style="color:#323232">)</span><span style="color:#323232">.</span><span style="color:#323232">ifPresent</span><span style="color:#323232">(</span><span style="color:#323232">map</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">-&gt;</span><span style="color:#323232"> </span><span style="color:#323232">this</span><span style="color:#323232">.</span><span style="color:#323232">stock</span><span style="color:#323232">.</span><span style="color:#323232">putAll</span><span style="color:#323232">(</span><span style="color:#323232">map</span><span style="color:#323232">)</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">}</span><span style="color:#323232">
</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">@</span><span style="color:#323232">Override</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#a71d5d;font-weight:bold">protected</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">void</span><span style="color:#323232"> </span><span style="color:#323232">saveAdditional</span><span style="color:#323232">(</span><span style="color:#0086b3">ValueOutput</span><span style="color:#323232"> output</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#323232">{</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#323232">super</span><span style="color:#323232">.</span><span style="color:#323232">saveAdditional</span><span style="color:#323232">(</span><span style="color:#323232">output</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#0086b3">ContainerHelper</span><span style="color:#323232">.</span><span style="color:#323232">saveAllItems</span><span style="color:#323232">(</span><span style="color:#323232">output</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#323232">this</span><span style="color:#323232">.</span><span style="color:#323232">items</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">        output</span><span style="color:#323232">.</span><span style="color:#323232">store</span><span style="color:#323232">(</span><span style="color:#183691">&quot;</span><span style="color:#183691">Stock</span><span style="color:#183691">&quot;</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">STOCK_CODEC</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#323232">this</span><span style="color:#323232">.</span><span style="color:#323232">stock</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">}</span></code></pre><p>The Codec is generally the way the game serialises and deserialises data. There are standard simple codecs like <code>Codec.INT</code> which will handle an integer number. The <code>BuiltInRegistries.ITEM.byNameCodec()</code> function returns the codec that will convert an &quot;Item&quot; to a string that is unique e.g. &quot;minecraft:oak_log&quot; and vice versa. It is a function instead of a static lookup because some items are registered dynamically e.g. the kiosk block itself. The <code>unboundedMap</code> combines the other two codecs to read and write a map that has the item as a key and integer as the value.</p><p>For data synching, the chest base class does the work so I didn't need to implement anything. For the stock, I did add calls to <code>setChanged()</code> but since the kiosk doesn't send the stock to the client, there was no need for <code>getUpdatePacket()</code>.</p><p>Likewise, I didn't add any tickers.</p><p>So that wraps it for the Kiosk block. This allowed my workers (not yet explained in this series) to exchange items and store things in a warehouse.</p> ]]></description>
            <pubDate>Sun, 20 Sep 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="true">https://mattsmith.id.au/blog/2026/09-20-minecraft-mod-kiosk-block.html</guid>
        </item>
    
        <item>
            <title>Getting Started with Minecraft Modding</title>
            <link>https://mattsmith.id.au/blog/2026/09-15-getting-started-minecraft-modding.html</link>
            <description><![CDATA[ <h1>Getting Started with Minecraft Modding</h1><p>From time to time, I get inspired to go back to Minecraft and maybe start a &quot;forever world&quot; with lore and an expansive network of bases linked by roads or Nether portals or railways etc... However, I find the game to be a bit lonely - the NPCs are pretty limited. But I'm also not a social gamer, I just think it's fun when the gameplay has some interesting characters around to add some life. It would be cool to have some custom villagers living at my bases that do a bit more and maybe have a bit of personality.</p><p>I'm not much of Java programmer so I'd never seriously considered any Minecraft modding. However, we live in an age where learning a language and code base is greatly accelerated if you have access to an LLM so it became possible for me to upskill in this area.</p><h2>Choosing a Framework</h2><p>The two major players in modding seem to be <a href="https://forums.minecraftforge.net/">Forge</a> and <a href="https://fabricmc.net/">Fabric</a>. Forge is older and more complete. Fabric is light-weight and updates faster when new versions come out.</p><p>I chose the Fabric framework because I generally distrust frameworks that try to do too much for you. While they are more convenient to get started with, I find that once you are mature and experienced, they become a limitation and you find yourself having to mod the framework itself to get outside of the box it puts you in.</p><p>Having said that, I didn't actually try Forge so I'd be open to giving it a go some time to see what it's really like.</p><p>The downside of Fabric is that it's bare-bones - you have to figure out things and build infrastructure for yourself ... which actually suites me perfectly.</p><h2>Installing Fabric</h2><p>I run Linux for developing but I game on Windows. This is a bit inconvenient but I worked around it ok. The actual Fabric mod loader was pretty easy to install, just <a href="https://docs.fabricmc.net/players/installing-fabric/windows">follow the instructions</a>. Also I installed the Fabric API which is the base mod that provides all the hooks that your mod will use.</p><p>The dev environment was a little trickier. The first step was getting the right Java version. For Minecraft 26.1, I used Java version 25 (I can't remember how I landed on that version). I run Debian so the apt command was <code>sudo apt install openjdk-25-jdk</code>.</p><p>After this, I cloned the <a href="https://github.com/FabricMC/fabric-example-mod/">Fabric Example Mod</a> , deleted the <code>.git</code> dir and reinitialised it as a new repo using <code>git init</code>. I probably should have used the template generator as per these <a href="https://docs.fabricmc.net/develop/getting-started/creating-a-project">Creating a Project</a> instructions but I didn't see that until later on.</p><p>I didn't bother too much with the IDE stuff. I use VSCode but just stick to the file tree and text editing. I prefer to build and run other tools on the command line. Give the IDE stuff a look though - it will probably make it easier. Instructions are on that <a href="https://docs.fabricmc.net/develop/getting-started/creating-a-project">Creating a Project</a>
page that you should have just read. As I previously mentioned, I've made life hard for myself by developing on a headless Linux server and running desktop on Windows. It's probably just better to commit to one or the other instead of trying to play in both worlds.</p><p>The dev environment uses Gradle as the build system. It conveniently installs some dependencies as well. The <a href="https://fabricmc.net/develop/">Develop</a> instructions have an example <code>gradle.properties</code> file which gets you started.</p><p>Before I could get too far, I wanted to have the Minecraft sources to look at as well as the Fabric API sources. The Fabric API is just a git clone: <a href="https://github.com/FabricMC/fabric-api">fabric-api</a>. There is a Gradle command that actually decompiles Minecraft and produces the sources: <code>./gradlew genSources</code>. This outputs two jar files deep in a subdirectory of the project. The idea is that the IDE should pick these up and know how to search them when you want to find sources. But because I'm difficult, I didn't want to set up my IDE for that. Instead: <code>fd 26.2-sources.jar .gradle</code> is the magic command to locate them. I then created a <code>minecraft-sources</code> dir and unpacked the jar files using the <code>unzip</code> command. Don't put that in your git repo btw, I don't think Microsoft would be cool with you distributing their code.</p><h2>Build, Run, Debug and Learn Using an Agent</h2><p>I was then able to build my first mod using <code>./gradlew build</code>. I can't remember exactly what I did first, I think I worked through some of <a href="https://docs.fabricmc.net/develop/items/first-item">Creating Your First Item</a>. The build command creates a <code>.jar</code> file in <code>./build/libs</code>. I scripted a <code>scp</code> command to copy that onto my Windows PC at <code>&quot;$env:APPDATA\.minecraft\mods\&quot;</code> and fired up Minecraft to try it out. Note that it's handy to have VSCode open to <code>&quot;$env:APPDATA\.minecraft\logs\latest.log&quot;</code> so you can watch it crash.</p><p>At the start of this article, I mentioned using LLMs, this is the point at which I started leaning on one pretty heavily to figure out what to do. To start with, it's handy to drop crash dumps into the agent to diagnose what they mean and figure out what to fix. I also found the agent very good for explaining the Minecraft code base and the Fabric API so I could figure out what to do. Personally I wanted to learn this as much as I could so I would query the agent to locate files that dealt with interesting areas and give me code walkthroughs. I then wrote my best attempt at the mod and used the agent to fix my mistakes. I find this an effective way to learn.</p><p>In the next few posts, I'll talk about what my mod does and how it works: <a href="https://mattsmith.id.au/blog/2026/09-20-minecraft-mod-kiosk-block.html">My Minecraft Mod: Kiosk Block</a>.</p> ]]></description>
            <pubDate>Tue, 15 Sep 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="true">https://mattsmith.id.au/blog/2026/09-15-getting-started-minecraft-modding.html</guid>
        </item>
    
        <item>
            <title>Thoughts About AI Specifically LLMs</title>
            <link>https://mattsmith.id.au/blog/2026/09-15-thoughts-about-ai.html</link>
            <description><![CDATA[ <h1>Thoughts About AI, Specifically LLMs</h1><p>It's hard to be a software developer at the moment without engaging with LLMs. Many workplaces are introducing the technology to their workflows in various ways as senior leadership is sold on promises of 10x productivity and developers are threatened with obsoletion if they don't embrace token maxing.</p><p>Meanwhile the big AI companies are rolling out data centres into any spare patch of earth close enough to a power station and water source. Energy consumption of data centres is <a href="https://www.energyintel.com.au/case-studies/data-centres-energy-australia">significant and growing</a> having an impact on nearby communities as well as contributing to overall energy demand at a time where it's crucial to be tackling climate change. Water consumption is also an issue and simply <a href="https://www.datacenterknowledge.com/cooling/why-data-centers-rarely-reuse-cooling-water">recycling water isn't on the cards</a>. The problem of AI scraper bots is a significant <a href="https://www.digitalapplied.com/blog/ai-crawler-bot-traffic-statistics-2026-data-reference">burden on Internet infrastructure</a>.</p><p>Then there are the ethical issues. Training of LLMs requires &quot;large&quot; volumes of &quot;language&quot; to form the &quot;model&quot;. It's not just written language though - all forms of media are up for grabs: spoken language, music and visual art are all made into models. LLM companies have violated copyrights of countless creators to train their models. While the legal arguments can go around and around, no one can deny that the overwhelming majority of authors never anticipated that their work could become part of an LLM and so never could have had feelings or made decisions about that possibility. The LLM companies have not given creators a chance to make that decision, they have denied them agency.</p><p>On the generation side of things, LLMs are being used to replace creators. There is a strong economic incentive to use an LLM for creative work instead of employing a creator. LLM outputs have become known as &quot;AI slop&quot;: derivative, surface quality works that fulfil the commercial goal of drawing someone's attention. While commercial creation is not the ambition of the majority of creators, it has been the thing that pays the bills and allowed people to develop skills for making true art that enriches our culture.</p><p>So basically LLMs suck. Right? So why am I using them, not just at work, but in my home projects too? Part of the problem for me personally is that I'm implicated through my profession. I use LLMs at work, all day, every day, and if I don't like it, my options are to change careers. Maybe I could find a company to work at that hasn't adopted AI yet, but that would be temporary. AI is here and it's not going away. A year ago I was resisting. A year ago I believed the bubble would burst and it would all come crashing down. We would then return to the better version of the world that existed before LLMs. I feel like I've read a &quot;AI is over&quot; / &quot;the bubble has burst&quot; article every day for the last year and yet every day I show up at work and Claude Code is still there, happy to do my bidding and my industry continues to be more and more reliant on it.</p><p>I like writing code. I like doing things with computers. I always have. In a way, I've always wanted LLMs. As a kid I dreamed of building my own R2D2 and C3PO - It's why I wanted to be a software developer and engineer. I got a thrill when K9 saved day on Doctor Who. I groaned with the crew of the Enterprise as Data attempted to learn how to tell a joke. Now I have something like that technology. It's awesome! It's truly like a miracle. I just wish that it hadn't come at such a cost.</p><p>I'm in the position where I can benefit greatly from AI for personal skill development. I could choose to boycott it and forego the benefits it offers but that sacrifice will not change anything except to make me angry at other people who don't make that same trade. My chosen path is to use AI as responsibly as I can and accept that modern life involves hypocrisy and guilt. Like the cartoon: <a href="https://thenib.com/mister-gotcha/">We should improve society somewhat</a>.</p><p>So while I maintain a political stance against what is going on with AI big tech right now, I have chosen to use AI for my own benefit where it makes sense and doesn't violate too many of my ideals. A guiding principal for me is that if I really care about the quality of something, then I will not generate it with an LLM - especially writing. (The converse is that when I'm consuming, if I can detect LLM generated content, then I form the opinion that the publisher is insulting me.) But I make full use of LLMs for tasks where it has proven to be truly useful.</p><p>For software development, LLMs are truly exceptional. Having access to an LLM agent is like having the <a href="https://stackexchange.com/leagues/1/week/stackoverflow/2026-09-06?sort=totalreputation">highest ranked contributors to Stack Overflow</a> sitting next to you. I use an LLM agent to guide me through unfamiliar code bases, generate boilerplate code and proof-of-concept solutions, generate tests, self-review code and debug stack dumps. It does all of these things within minutes if not seconds where it would usually take me hours.</p><p>Where I avoid using AI is for generating whole solutions at once. I find that if it does too much unsupervised, while it might eventually converge on working code, that code will be unreadable bloated garbage full of layers of contradictions. The token maxers will tell you the solution to that problem is bigger contexts and more agents (ie. spend more money) but I have found a cheaper solution that works great and is far more enjoyable for me: I call it using my own brain. I don't think I'll ever reach the point where I find joy in watching a machine do the part of my job that I most enjoy which is designing the solutions. I feel for people (including in my own profession) where that has already been taken away from them.</p><p>For home use, I have a subscription to <a href="https://www.minimax.io/about">MiniMax</a> which is a cheap price-capped Chinese model with ok benchmarks (similar to GPT-5.6 Luna or Deepseek V4 Pro). It's smart enough for my needs at the right price. I would much prefer to run a local model but that is beyond my means at the moment.</p><p>I hope this has been helpful to others trying to find their way through this time and I hope that we can give each other grace for the positions we arrive at when it comes to AI.</p> ]]></description>
            <pubDate>Tue, 15 Sep 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="true">https://mattsmith.id.au/blog/2026/09-15-thoughts-about-ai.html</guid>
        </item>
    
        <item>
            <title>Glare Layer of Pokemon cards implemented in CSS and javascript</title>
            <link>https://mattsmith.id.au/blog/2026/05-24-pokemon-cards-4.html</link>
            <description><![CDATA[ <h1>Glare Layer of Pokemon cards implemented in CSS and javascript</h1><p>In the <a href="https://mattsmith.id.au/blog/2026/05-23-pokemon-cards-3.html">previous post</a> I explained how to
add the mask layer to the pokemon card. Now we will look at the glare layer.</p><p>The glare layer is pretty subtle but adds a kind-of spotlight effect making the
card seem to reflect light as it moves. This is accomplished using a <code>div</code> that
sits in front of all the layers and blends in a <code>radial-gradiant</code> background
that moves with the mouse.</p><p>Here's the HTML. The new element has class <code>card__glare</code>:</p><pre><code class="language-html"><span style="color:#323232">&lt;</span><span style="color:#63a35c">div</span><span style="color:#323232"> </span><span style="color:#795da3">class</span><span style="color:#323232">=</span><span style="color:#183691">&quot;</span><span style="color:#183691">card</span><span style="color:#183691">&quot;</span><span style="color:#323232"> </span><span style="color:#795da3">id</span><span style="color:#323232">=</span><span style="color:#183691">&quot;</span><span style="color:#183691">holocard</span><span style="color:#183691">&quot;</span><span style="color:#323232">&gt;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">&lt;</span><span style="color:#63a35c">div</span><span style="color:#323232"> </span><span style="color:#795da3">class</span><span style="color:#323232">=</span><span style="color:#183691">&quot;</span><span style="color:#183691">card__rotator</span><span style="color:#183691">&quot;</span><span style="color:#323232">&gt;</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#323232">&lt;</span><span style="color:#63a35c">div</span><span style="color:#323232"> </span><span style="color:#795da3">class</span><span style="color:#323232">=</span><span style="color:#183691">&quot;</span><span style="color:#183691">card__front</span><span style="color:#183691">&quot;</span><span style="color:#323232">&gt;</span><span style="color:#323232">
</span><span style="color:#323232">            </span><span style="color:#323232">&lt;</span><span style="color:#63a35c">img</span><span style="color:#323232"> </span><span style="color:#795da3">src</span><span style="color:#323232">=</span><span style="color:#183691">&quot;</span><span style="color:#183691">/images/matt-pokemon.png</span><span style="color:#183691">&quot;</span><span style="color:#323232"> </span><span style="color:#795da3">width</span><span style="color:#323232">=</span><span style="color:#183691">&quot;</span><span style="color:#183691">330</span><span style="color:#183691">&quot;</span><span style="color:#323232"> </span><span style="color:#795da3">height</span><span style="color:#323232">=</span><span style="color:#183691">&quot;</span><span style="color:#183691">460</span><span style="color:#183691">&quot;</span><span style="color:#323232"> /&gt;</span><span style="color:#323232">
</span><span style="color:#323232">            </span><span style="color:#323232">&lt;</span><span style="color:#63a35c">div</span><span style="color:#323232"> </span><span style="color:#795da3">class</span><span style="color:#323232">=</span><span style="color:#183691">&quot;</span><span style="color:#183691">card__shine</span><span style="color:#183691">&quot;</span><span style="color:#323232">&gt;</span><span style="color:#323232">&lt;/</span><span style="color:#63a35c">div</span><span style="color:#323232">&gt;</span><span style="color:#323232">
</span><span style="color:#323232">            </span><span style="color:#323232">&lt;</span><span style="color:#63a35c">div</span><span style="color:#323232"> </span><span style="color:#795da3">class</span><span style="color:#323232">=</span><span style="color:#183691">&quot;</span><span style="color:#183691">card__glare</span><span style="color:#183691">&quot;</span><span style="color:#323232">&gt;</span><span style="color:#323232">&lt;/</span><span style="color:#63a35c">div</span><span style="color:#323232">&gt;</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#323232">&lt;/</span><span style="color:#63a35c">div</span><span style="color:#323232">&gt;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">&lt;/</span><span style="color:#63a35c">div</span><span style="color:#323232">&gt;</span><span style="color:#323232">
</span><span style="color:#323232">&lt;/</span><span style="color:#63a35c">div</span><span style="color:#323232">&gt;</span></code></pre><p>In the CSS, the div is styled with <code>background-image: radial-gradiant</code>:</p><pre><code class="language-css"><span style="color:#323232">.</span><span style="color:#795da3">card_glare</span><span style="color:#323232"> </span><span style="color:#323232">{</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#0086b3">background-image</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#323232">radial-gradient</span><span style="color:#323232">(</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#a71d5d;font-weight:bold">circle</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">at</span><span style="color:#323232"> var(--pointer-x</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#323232">var</span><span style="color:#323232">(</span><span style="color:#0086b3">--</span><span style="color:#0086b3">pointer-y</span><span style="color:#323232">)</span><span style="color:#323232">,
</span><span style="color:#323232">        </span><span style="color:#323232">rgb</span><span style="color:#323232">(</span><span style="color:#0086b3">255</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">255</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">255</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#0086b3">0</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232">,
</span><span style="color:#323232">        </span><span style="color:#323232">rgba</span><span style="color:#323232">(</span><span style="color:#0086b3">134</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">138</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">141</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">0.33</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#0086b3">45</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232">,
</span><span style="color:#323232">        </span><span style="color:#323232">rgba</span><span style="color:#323232">(</span><span style="color:#0086b3">51</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">51</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">51</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">0.9</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#0086b3">130</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">);
</span><span style="color:#323232">    </span><span style="color:#0086b3">opacity</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#0086b3">0</span><span style="color:#0086b3">.5</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#0086b3">mix-blend-mode</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#323232">hard-light</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#0086b3">filter</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#323232">brightness</span><span style="color:#323232">(</span><span style="color:#0086b3">.9</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#323232">contrast</span><span style="color:#323232">(</span><span style="color:#0086b3">1.75</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">}</span></code></pre><p>Note the use of <code>--pointer-x/y</code> variables which are set by javascript to
percentage values from the top left corner. The <code>opacity</code> is static in this
case. Other cards vary the opacity so that the glare layer becomes invisible
towards the center of the card and is stronger when the mouse moves to the
edges. That gives a metallic effect where as the effect for this card is more of
plastic.</p><p>Here's what it looks like on its own (you might need to hover the mouse to make
it show up)</p><style></style><div class="card_demo6" id="holocard_demo6">
    <div class="card__rotator_demo6">
        <div class="card__front_demo6">
            <!--<img src="/images/matt-pokemon.png" width="330" height="460" />
            <div class="card__shine_demo6"></div> -->
            <div class="card__glare_demo6"></div>
        </div>
    </div>
</div><script></script><p>And here it is on the card. This is very close to the finished card. There are
some more subtle details in the actual pokemon-cards-css files but we've covered
the basics here and hopefully you learned as much as I did!</p><style></style><div class="card_demo7" id="holocard_demo7">
    <div class="card__rotator_demo7">
        <div class="card__front_demo7">
            <img src="https://mattsmith.id.au/images/matt-pokemon.png" width="330" height="460" />
            <div class="card__shine_demo7"></div>
            <div class="card__glare_demo7"></div>
        </div>
    </div>
</div><script></script> ]]></description>
            <pubDate>Sun, 24 May 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="true">https://mattsmith.id.au/blog/2026/05-24-pokemon-cards-4.html</guid>
        </item>
    
        <item>
            <title>Mask Layer of Pokemon cards implemented in CSS and javascript</title>
            <link>https://mattsmith.id.au/blog/2026/05-23-pokemon-cards-3.html</link>
            <description><![CDATA[ <h1>Mask Layer of Pokemon cards implemented in CSS and javascript</h1><p>In the <a href="https://mattsmith.id.au/blog/2026/05-22-pokemon-cards-2.html">previous post</a> I explained how to
add holographic shine to the pokemon card. Now we will look at the mask layer.</p><p>This was a fun area to learn about. For background, read <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Masking/Introduction">Introduction to CSS
masking</a>
at MDN.</p><p>The TL;DR is, make an image where the bright white parts are what you want to
let through and the transparent parts are what you want to block. For the
holographic image, that means the parts you want to see look holographic should
be white and the parts where you don't want holographic stuff should be
transparent.</p><p>Here is the mask image I made in GIMP (transparent areas will show as grey):</p><style></style><div id="mask-image">
<img src="https://mattsmith.id.au/images/matt-pokemon-mask.png" alt="Mask Image" width=330 />
</div><p>It is applied to the shine layers using CSS:</p><pre><code class="language-css"><span style="color:#323232">.</span><span style="color:#795da3">card__shine</span><span style="color:#323232">,
</span><span style="color:#323232">.</span><span style="color:#795da3">card__shine</span><span style="color:#323232">::</span><span style="color:#323232">after</span><span style="color:#323232"> </span><span style="color:#323232">{</span><span style="color:#323232">
</span><span style="color:#323232">  </span><span style="color:#0086b3">-webkit-</span><span style="color:#0086b3">mask-image</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#323232">url</span><span style="color:#323232">(</span><span style="color:#183691">/</span><span style="color:#183691">i</span><span style="color:#183691">m</span><span style="color:#183691">a</span><span style="color:#183691">g</span><span style="color:#183691">e</span><span style="color:#183691">s</span><span style="color:#183691">/</span><span style="color:#183691">m</span><span style="color:#183691">a</span><span style="color:#183691">t</span><span style="color:#183691">t</span><span style="color:#183691">-</span><span style="color:#183691">p</span><span style="color:#183691">o</span><span style="color:#183691">k</span><span style="color:#183691">e</span><span style="color:#183691">m</span><span style="color:#183691">o</span><span style="color:#183691">n</span><span style="color:#183691">-</span><span style="color:#183691">m</span><span style="color:#183691">a</span><span style="color:#183691">s</span><span style="color:#183691">k</span><span style="color:#183691">.</span><span style="color:#183691">p</span><span style="color:#183691">n</span><span style="color:#183691">g</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">  </span><span style="color:#0086b3">mask-image</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#323232">url</span><span style="color:#323232">(</span><span style="color:#183691">/</span><span style="color:#183691">i</span><span style="color:#183691">m</span><span style="color:#183691">a</span><span style="color:#183691">g</span><span style="color:#183691">e</span><span style="color:#183691">s</span><span style="color:#183691">/</span><span style="color:#183691">m</span><span style="color:#183691">a</span><span style="color:#183691">t</span><span style="color:#183691">t</span><span style="color:#183691">-</span><span style="color:#183691">p</span><span style="color:#183691">o</span><span style="color:#183691">k</span><span style="color:#183691">e</span><span style="color:#183691">m</span><span style="color:#183691">o</span><span style="color:#183691">n</span><span style="color:#183691">-</span><span style="color:#183691">m</span><span style="color:#183691">a</span><span style="color:#183691">s</span><span style="color:#183691">k</span><span style="color:#183691">.</span><span style="color:#183691">p</span><span style="color:#183691">n</span><span style="color:#183691">g</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">  </span><span style="color:#0086b3">-webkit-</span><span style="color:#0086b3">mask-size</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#323232">cover</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">  </span><span style="color:#0086b3">mask-size</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#323232">cover</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">  </span><span style="color:#0086b3">-webkit-</span><span style="color:#0086b3">mask-position</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#323232">center</span><span style="color:#323232"> </span><span style="color:#323232">center</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">  </span><span style="color:#0086b3">mask-position</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#323232">center</span><span style="color:#323232"> </span><span style="color:#323232">center</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">}</span></code></pre><style></style><div class="card_demo4" id="holocard_demo4">
    <div class="card__rotator_demo4">
        <div class="card__front_demo4">
            <!-- <img src="" width="330" height="460" />  -->
            <div class="card__shine_demo4"></div>
        </div>
    </div>
</div><script></script><p>Now is a good time to blend this over the top of the actual card image. This is
not world class art work sorry. I used a Snapchat filter to make myself look
animated and then used Inkscape to add a border and some text.</p><p>Uncomment the previously commented image url:</p><pre><code class="language-html"><span style="color:#323232">&lt;</span><span style="color:#63a35c">div</span><span style="color:#323232"> </span><span style="color:#795da3">class</span><span style="color:#323232">=</span><span style="color:#183691">&quot;</span><span style="color:#183691">card_demo4</span><span style="color:#183691">&quot;</span><span style="color:#323232"> </span><span style="color:#795da3">id</span><span style="color:#323232">=</span><span style="color:#183691">&quot;</span><span style="color:#183691">holocard_demo4</span><span style="color:#183691">&quot;</span><span style="color:#323232">&gt;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">&lt;</span><span style="color:#63a35c">div</span><span style="color:#323232"> </span><span style="color:#795da3">class</span><span style="color:#323232">=</span><span style="color:#183691">&quot;</span><span style="color:#183691">card__rotator_demo4</span><span style="color:#183691">&quot;</span><span style="color:#323232">&gt;</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#323232">&lt;</span><span style="color:#63a35c">div</span><span style="color:#323232"> </span><span style="color:#795da3">class</span><span style="color:#323232">=</span><span style="color:#183691">&quot;</span><span style="color:#183691">card__front_demo4</span><span style="color:#183691">&quot;</span><span style="color:#323232">&gt;</span><span style="color:#323232">
</span><span style="color:#323232">            </span><span style="color:#323232">&lt;</span><span style="color:#63a35c">img</span><span style="color:#323232"> </span><span style="color:#795da3">src</span><span style="color:#323232">=</span><span style="color:#183691">&quot;</span><span style="color:#183691">/images/matt-pokemon.png</span><span style="color:#183691">&quot;</span><span style="color:#323232"> </span><span style="color:#795da3">width</span><span style="color:#323232">=</span><span style="color:#183691">&quot;</span><span style="color:#183691">330</span><span style="color:#183691">&quot;</span><span style="color:#323232"> </span><span style="color:#795da3">height</span><span style="color:#323232">=</span><span style="color:#183691">&quot;</span><span style="color:#183691">460</span><span style="color:#183691">&quot;</span><span style="color:#323232"> /&gt;</span><span style="color:#323232">
</span><span style="color:#323232">            </span><span style="color:#323232">&lt;</span><span style="color:#63a35c">div</span><span style="color:#323232"> </span><span style="color:#795da3">class</span><span style="color:#323232">=</span><span style="color:#183691">&quot;</span><span style="color:#183691">card__shine_demo4</span><span style="color:#183691">&quot;</span><span style="color:#323232">&gt;</span><span style="color:#323232">&lt;/</span><span style="color:#63a35c">div</span><span style="color:#323232">&gt;</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#323232">&lt;/</span><span style="color:#63a35c">div</span><span style="color:#323232">&gt;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">&lt;/</span><span style="color:#63a35c">div</span><span style="color:#323232">&gt;</span><span style="color:#323232">
</span><span style="color:#323232">&lt;/</span><span style="color:#63a35c">div</span><span style="color:#323232">&gt;</span><span style="color:#323232">
</span></code></pre><p>There's also a bit of css work still to do because the shine layer is positioned
below the image instead of behind it. We need to do a bit of z-index and
position work in the CSS. The <code>display: grid</code> and <code>grid-area: 1/1</code> make the divs
stack. The <code>transform</code> and <code>z-index</code> ensure the shine layer is in front. The
<code>mix-blend-mode: color-dodge</code> property makes the shine layer blend with the
image nicely.</p><pre><code class="language-css"><span style="color:#323232">.</span><span style="color:#795da3">card__rotator_demo5</span><span style="color:#323232"> </span><span style="color:#63a35c">*</span><span style="color:#323232"> </span><span style="color:#323232">{</span><span style="color:#323232">
</span><span style="color:#323232">  </span><span style="color:#0086b3">width</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#0086b3">100</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">  </span><span style="color:#0086b3">display</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#323232">grid</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">  </span><span style="color:#0086b3">grid-area</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#0086b3">1</span><span style="color:#323232">/</span><span style="color:#0086b3">1</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">}</span><span style="color:#323232">
</span><span style="color:#323232">
</span><span style="color:#323232">.</span><span style="color:#795da3">card__shine_demo5</span><span style="color:#323232">,
</span><span style="color:#323232">.</span><span style="color:#795da3">card__shine_demo5</span><span style="color:#323232">::</span><span style="color:#323232">after</span><span style="color:#323232"> </span><span style="color:#323232">{</span><span style="color:#323232">
</span><span style="color:#323232">  </span><span style="color:#0086b3">transform</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#323232">translateZ</span><span style="color:#323232">(</span><span style="color:#0086b3">1</span><span style="color:#a71d5d;font-weight:bold">px</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">  </span><span style="color:#0086b3">z-index</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#0086b3">3</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">  </span><span style="color:#0086b3">mix-blend-mode</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#323232">color-dodge</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">  ...</span><span style="color:#323232">other</span><span style="color:#323232"> </span><span style="color:#323232">stuff</span><span style="color:#323232">...
</span><span style="color:#323232">}</span></code></pre><style></style><div class="card_demo5" id="holocard_demo5">
    <div class="card__rotator_demo5">
        <div class="card__front_demo5">
            <img src="https://mattsmith.id.au/images/matt-pokemon.png" width="330" height="460" />
            <div class="card__shine_demo5"></div>
        </div>
    </div>
</div><script></script><p>We're almost done but there's one more layer to go to add a subtle reflection to
make the surface appear glossy. <a href="https://mattsmith.id.au/blog/2026/05-24-pokemon-cards-4.html">Glare Layer - Pokemon cards
CSS</a></p> ]]></description>
            <pubDate>Sat, 23 May 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="true">https://mattsmith.id.au/blog/2026/05-23-pokemon-cards-3.html</guid>
        </item>
    
        <item>
            <title>Shine Layers of Pokemon cards implemented in CSS and javascript</title>
            <link>https://mattsmith.id.au/blog/2026/05-22-pokemon-cards-2.html</link>
            <description><![CDATA[ <h1>Shine Layers of Pokemon cards implemented in CSS and javascript</h1><p>In the <a href="https://mattsmith.id.au/blog/2026/05-21-pokemon-cards.html">previous post</a> I explained how to
create a basic 3D card in JS/CSS. This post is about the &quot;shine&quot; layers used to
create the holographic effect.</p><p>There is one main element in the HTML with class <code>.card__shine</code> and then a
pseudo-element is created: <code>::after</code>. Some cards also have a <code>::before</code> layer
too.</p><p>The HTML is similar to the previous post but now there's the <code>&lt;div
class=&quot;card__shine&quot;&gt;</code> element:</p><pre><code class="language-html"><span style="color:#323232">&lt;</span><span style="color:#63a35c">div</span><span style="color:#323232"> </span><span style="color:#795da3">class</span><span style="color:#323232">=</span><span style="color:#183691">&quot;</span><span style="color:#183691">card</span><span style="color:#183691">&quot;</span><span style="color:#323232"> </span><span style="color:#795da3">id</span><span style="color:#323232">=</span><span style="color:#183691">&quot;</span><span style="color:#183691">holocard</span><span style="color:#183691">&quot;</span><span style="color:#323232">&gt;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">&lt;</span><span style="color:#63a35c">div</span><span style="color:#323232"> </span><span style="color:#795da3">class</span><span style="color:#323232">=</span><span style="color:#183691">&quot;</span><span style="color:#183691">card__rotator</span><span style="color:#183691">&quot;</span><span style="color:#323232">&gt;</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#323232">&lt;</span><span style="color:#63a35c">div</span><span style="color:#323232"> </span><span style="color:#795da3">class</span><span style="color:#323232">=</span><span style="color:#183691">&quot;</span><span style="color:#183691">card__front</span><span style="color:#183691">&quot;</span><span style="color:#323232">&gt;</span><span style="color:#323232">
</span><span style="color:#323232">            </span><span style="color:#969896;font-style:italic">&lt;!--</span><span style="color:#969896;font-style:italic"> &lt;img src=&quot;&quot; width=&quot;330&quot; height=&quot;460&quot; /&gt;  </span><span style="color:#969896;font-style:italic">--&gt;</span><span style="color:#323232">
</span><span style="color:#323232">            </span><span style="color:#323232">&lt;</span><span style="color:#63a35c">div</span><span style="color:#323232"> </span><span style="color:#795da3">class</span><span style="color:#323232">=</span><span style="color:#183691">&quot;</span><span style="color:#183691">card__shine</span><span style="color:#183691">&quot;</span><span style="color:#323232">&gt;</span><span style="color:#323232">&lt;/</span><span style="color:#63a35c">div</span><span style="color:#323232">&gt;</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#323232">&lt;/</span><span style="color:#63a35c">div</span><span style="color:#323232">&gt;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">&lt;/</span><span style="color:#63a35c">div</span><span style="color:#323232">&gt;</span><span style="color:#323232">
</span><span style="color:#323232">&lt;/</span><span style="color:#63a35c">div</span><span style="color:#323232">&gt;</span><span style="color:#323232">
</span></code></pre><p>Let's look at each CSS layer one at a time. The cards all differ in technique so
each one needs to be looked at individually but the one I used for this website
has two identical layers that slide in opposite directions as the mouse moves.</p><p>The basic background image is some texture <code>grain.webp</code>:
<img src="https://mattsmith.id.au/pokemon-cards-css/img/grain.webp" alt="grain" /></p><p>Then a rainbow layer is blended onto it:</p><pre><code class="language-css"><span style="color:#323232">.</span><span style="color:#795da3">card__shine</span><span style="color:#323232"> </span><span style="color:#323232">{</span><span style="color:#323232">
</span><span style="color:#323232">  </span><span style="color:#0086b3">background-image</span><span style="color:#323232">:</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">url</span><span style="color:#323232">(</span><span style="color:#183691">&quot;</span><span style="color:#183691">/pokemon-cards-css/img/grain.webp</span><span style="color:#183691">&quot;</span><span style="color:#323232">)</span><span style="color:#323232">,
</span><span style="color:#323232">    </span><span style="color:#323232">repeating-linear-gradient</span><span style="color:#323232">(</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#0086b3">0</span><span style="color:#a71d5d;font-weight:bold">deg</span><span style="color:#323232">,</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#323232">rgb</span><span style="color:#323232">(</span><span style="color:#0086b3">255</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">122</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">117</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#0086b3">5</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232">,</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#323232">rgb</span><span style="color:#323232">(</span><span style="color:#0086b3">255</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">237</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">97</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#0086b3">10</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232">,</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#323232">rgb</span><span style="color:#323232">(</span><span style="color:#0086b3">168</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">255</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">97</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#0086b3">15</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232">,</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#323232">rgb</span><span style="color:#323232">(</span><span style="color:#0086b3">133</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">255</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">247</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#0086b3">20</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232">,</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#323232">rgb</span><span style="color:#323232">(</span><span style="color:#0086b3">122</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">149</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">255</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#0086b3">25</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232">,</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#323232">rgb</span><span style="color:#323232">(</span><span style="color:#0086b3">216</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">117</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">255</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#0086b3">30</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232">,</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#323232">rgb</span><span style="color:#323232">(</span><span style="color:#0086b3">255</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">122</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">117</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#0086b3">35</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">
</span><span style="color:#323232">  </span><span style="color:#0086b3">background-blend-mode</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#323232">screen</span><span style="color:#323232">, </span><span style="color:#323232">hue</span><span style="color:#323232">, </span><span style="color:#323232">hard-light</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">  </span><span style="color:#0086b3">background-size</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#0086b3">500</span><span style="color:#a71d5d;font-weight:bold">px</span><span style="color:#323232"> </span><span style="color:#0086b3">100</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232">, </span><span style="color:#0086b3">200</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232"> </span><span style="color:#0086b3">700</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">  </span><span style="color:#0086b3">background-position</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#323232">center</span><span style="color:#323232">, </span><span style="color:#0086b3">0</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232"> </span><span style="color:#323232">var</span><span style="color:#323232">(</span><span style="color:#0086b3">--</span><span style="color:#0086b3">background-y</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">
</span><span style="color:#323232">  </span><span style="color:#0086b3">filter</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#323232">brightness</span><span style="color:#323232">(</span><span style="color:#0086b3">.8</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#323232">contrast</span><span style="color:#323232">(</span><span style="color:#0086b3">2.95</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#323232">saturate</span><span style="color:#323232">(</span><span style="color:#0086b3">.65</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">}</span><span style="color:#323232">
</span></code></pre><p>Note that there is a <code>--background-y</code> variable now. This is controlled by
javascript and makes the rainbow slide around with the mouse pointer. We'll set
<code>--background-x</code> while we're here because that's used soon. Actually <code>--pointer-x</code>
and <code>--pointer-y</code> will be used later as well. They are the same as the background
variables but I think Simon wanted to keep them semantically different in the
code.</p><pre><code class="language-javascript"><span style="color:#323232">    </span><span style="color:#969896;font-style:italic">//</span><span style="color:#969896;font-style:italic"> These lines are added to the interact function that we previously defined
</span><span style="color:#323232">    </span><span style="color:#323232">card</span><span style="color:#323232">.</span><span style="color:#323232">style</span><span style="color:#323232">.</span><span style="color:#323232">setProperty</span><span style="color:#323232">(</span><span style="color:#183691">'</span><span style="color:#183691">--pointer-x</span><span style="color:#183691">'</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#183691">`</span><span style="color:#323232">${</span><span style="color:#323232">pointerXPc</span><span style="color:#323232">}</span><span style="color:#183691">%</span><span style="color:#183691">`</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">card</span><span style="color:#323232">.</span><span style="color:#323232">style</span><span style="color:#323232">.</span><span style="color:#323232">setProperty</span><span style="color:#323232">(</span><span style="color:#183691">'</span><span style="color:#183691">--pointer-y</span><span style="color:#183691">'</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#183691">`</span><span style="color:#323232">${</span><span style="color:#323232">pointerYPc</span><span style="color:#323232">}</span><span style="color:#183691">%</span><span style="color:#183691">`</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">card</span><span style="color:#323232">.</span><span style="color:#323232">style</span><span style="color:#323232">.</span><span style="color:#323232">setProperty</span><span style="color:#323232">(</span><span style="color:#183691">'</span><span style="color:#183691">--background-x</span><span style="color:#183691">'</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#183691">`</span><span style="color:#323232">${</span><span style="color:#323232">pointerXPc</span><span style="color:#323232">}</span><span style="color:#183691">%</span><span style="color:#183691">`</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">card</span><span style="color:#323232">.</span><span style="color:#323232">style</span><span style="color:#323232">.</span><span style="color:#323232">setProperty</span><span style="color:#323232">(</span><span style="color:#183691">'</span><span style="color:#183691">--background-y</span><span style="color:#183691">'</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#183691">`</span><span style="color:#323232">${</span><span style="color:#323232">pointerYPc</span><span style="color:#323232">}</span><span style="color:#183691">%</span><span style="color:#183691">`</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span></code></pre><style></style><div class="card_demo1" id="holocard_demo1">
    <div class="card__rotator_demo1">
        <div class="card__front_demo1">
            <!-- <img src="" width="330" height="460" />  -->
            <div class="card__shine_demo1"></div>
        </div>
    </div>
</div><script></script><p>Next we blend in another layer that also moves with the mouse pointer and creates
diagonal bands of light:</p><pre><code class="language-css"><span style="color:#323232">.</span><span style="color:#795da3">card__shine</span><span style="color:#323232"> </span><span style="color:#323232">{</span><span style="color:#323232">
</span><span style="color:#323232">  </span><span style="color:#0086b3">background-image</span><span style="color:#323232">:</span><span style="color:#323232">
</span><span style="color:#323232">    ...stuff </span><span style="color:#323232">from</span><span style="color:#323232"> before...
</span><span style="color:#323232">    </span><span style="color:#323232">repeating-linear-gradient</span><span style="color:#323232">(</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#0086b3">133</span><span style="color:#a71d5d;font-weight:bold">deg</span><span style="color:#323232">,</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#323232">rgb</span><span style="color:#323232">(</span><span style="color:#0086b3">14</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">21</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">46</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#0086b3">0</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232">,</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#323232">rgb</span><span style="color:#323232">(</span><span style="color:#0086b3">143</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">163</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">163</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#0086b3">3.8</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232">,</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#323232">rgb</span><span style="color:#323232">(</span><span style="color:#0086b3">143</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">193</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">193</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#0086b3">4.5</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232">,</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#323232">rgb</span><span style="color:#323232">(</span><span style="color:#0086b3">143</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">163</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">163</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#0086b3">5.2</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232">,</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#323232">rgb</span><span style="color:#323232">(</span><span style="color:#0086b3">14</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">21</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">46</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#0086b3">10</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232">,</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#323232">rgb</span><span style="color:#323232">(</span><span style="color:#0086b3">14</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">21</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">46</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#0086b3">12</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">
</span><span style="color:#323232">  </span><span style="color:#0086b3">background-blend-mode</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#323232">screen</span><span style="color:#323232">, </span><span style="color:#323232">hue</span><span style="color:#323232">, </span><span style="color:#323232">hard-light</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">  </span><span style="color:#0086b3">background-size</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#0086b3">500</span><span style="color:#a71d5d;font-weight:bold">px</span><span style="color:#323232"> </span><span style="color:#0086b3">100</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232">, </span><span style="color:#0086b3">200</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232"> </span><span style="color:#0086b3">700</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232">, </span><span style="color:#0086b3">300</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232"> </span><span style="color:#0086b3">100</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">  </span><span style="color:#0086b3">background-position</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#323232">center</span><span style="color:#323232">, </span><span style="color:#0086b3">0</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232"> </span><span style="color:#323232">var</span><span style="color:#323232">(</span><span style="color:#0086b3">--</span><span style="color:#0086b3">background-y</span><span style="color:#323232">)</span><span style="color:#323232">, </span><span style="color:#323232">var</span><span style="color:#323232">(</span><span style="color:#0086b3">--</span><span style="color:#0086b3">background-x</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#323232">var</span><span style="color:#323232">(</span><span style="color:#0086b3">--</span><span style="color:#0086b3">background-y</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">
</span><span style="color:#323232">  </span><span style="color:#0086b3">filter</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#323232">brightness</span><span style="color:#323232">(</span><span style="color:#0086b3">.8</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#323232">contrast</span><span style="color:#323232">(</span><span style="color:#0086b3">2.95</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#323232">saturate</span><span style="color:#323232">(</span><span style="color:#0086b3">.65</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">}</span><span style="color:#323232">
</span></code></pre><style></style><div class="card_demo2" id="holocard_demo2">
    <div class="card__rotator_demo2">
        <div class="card__front_demo2">
            <!-- <img src="" width="330" height="460" />  -->
            <div class="card__shine_demo2"></div>
        </div>
    </div>
</div><script></script><p>Yet another layer is blended in, a radial-gradiant that provides a bit of glare
but I couldn't tell the difference. Later on we'll add more glare but I think
this was supposed to be like the light reflecting off the foil at the back of
the card.</p><pre><code class="language-css"><span style="color:#323232">  </span><span style="color:#323232">.</span><span style="color:#795da3">card__shine</span><span style="color:#323232"> </span><span style="color:#323232">{</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#0086b3">background-image</span><span style="color:#323232">:</span><span style="color:#323232">
</span><span style="color:#323232">    ... previous stuff ...
</span><span style="color:#323232">    </span><span style="color:#323232">radial-gradient</span><span style="color:#323232">(</span><span style="color:#323232">
</span><span style="color:#323232">      </span><span style="color:#323232">farthest-corner</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">circle</span><span style="color:#323232">
</span><span style="color:#323232">      </span><span style="color:#a71d5d;font-weight:bold">at</span><span style="color:#323232"> var(--pointer-x</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#323232">var</span><span style="color:#323232">(</span><span style="color:#0086b3">--</span><span style="color:#0086b3">pointer-y</span><span style="color:#323232">)</span><span style="color:#323232">,
</span><span style="color:#323232">      </span><span style="color:#323232">rgba</span><span style="color:#323232">(</span><span style="color:#0086b3">0</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">0</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">0</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">0.1</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#0086b3">12</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232">,
</span><span style="color:#323232">      </span><span style="color:#323232">rgba</span><span style="color:#323232">(</span><span style="color:#0086b3">0</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">0</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">0</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">0.15</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#0086b3">20</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232">,
</span><span style="color:#323232">      </span><span style="color:#323232">rgba</span><span style="color:#323232">(</span><span style="color:#0086b3">0</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">0</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">0</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">0.25</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#0086b3">120</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232">);
</span><span style="color:#323232">  </span><span style="color:#323232">}</span><span style="color:#323232">
</span><span style="color:#323232">
</span><span style="color:#323232">  </span><span style="color:#63a35c">background-size</span><span style="color:#323232">: 500px 100%, 200% 700%, 300% 100%, 200% 100%;
</span><span style="color:#323232">  </span><span style="color:#63a35c">background-position</span><span style="color:#323232">: center,
</span><span style="color:#323232">    0% </span><span style="color:#63a35c">var</span><span style="color:#323232">(--</span><span style="color:#63a35c">background-y</span><span style="color:#323232">),
</span><span style="color:#323232">    </span><span style="color:#63a35c">var</span><span style="color:#323232">(--</span><span style="color:#63a35c">background-x</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#63a35c">var</span><span style="color:#323232">(--</span><span style="color:#63a35c">background-y</span><span style="color:#323232">),
</span><span style="color:#323232">    </span><span style="color:#63a35c">var</span><span style="color:#323232">(--</span><span style="color:#63a35c">background-x</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#63a35c">var</span><span style="color:#323232">(--</span><span style="color:#63a35c">background-y</span><span style="color:#323232">);
</span></code></pre><p>Now we get to the pseudo element <code>::after</code>. It shares the same css as the
<code>.card__shine</code> layer but overrides the background-position and background-size
properties so that it moves in the opposite direction. The filter and blend
modes are different too so that they interact - sometimes cancelling and
sometimes boosting the glow. I don't know why, but it's important to set
<code>content: &quot;&quot;;</code> and <code>display: grid;</code>, otherwise the <code>::after</code> layer doesn't show.</p><pre><code class="language-css"><span style="color:#323232">.</span><span style="color:#795da3">card__shine</span><span style="color:#323232">::</span><span style="color:#323232">after</span><span style="color:#323232"> </span><span style="color:#323232">{</span><span style="color:#323232">
</span><span style="color:#323232">  </span><span style="color:#0086b3">content</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#183691">&quot;</span><span style="color:#183691">&quot;</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">  </span><span style="color:#0086b3">display</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#323232">grid</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">
</span><span style="color:#323232">  </span><span style="color:#0086b3">background-position</span><span style="color:#323232">:</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">center</span><span style="color:#323232">,
</span><span style="color:#323232">    </span><span style="color:#0086b3">0</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232"> </span><span style="color:#323232">var</span><span style="color:#323232">(</span><span style="color:#0086b3">--</span><span style="color:#0086b3">background-y</span><span style="color:#323232">)</span><span style="color:#323232">,
</span><span style="color:#323232">    </span><span style="color:#323232">calc</span><span style="color:#323232">(</span><span style="color:#323232"> </span><span style="color:#323232">var</span><span style="color:#323232">(</span><span style="color:#0086b3">--</span><span style="color:#0086b3">background-x</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">*</span><span style="color:#323232"> </span><span style="color:#0086b3">-1</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#323232">calc</span><span style="color:#323232">(</span><span style="color:#323232"> </span><span style="color:#323232">var</span><span style="color:#323232">(</span><span style="color:#0086b3">--</span><span style="color:#0086b3">background-y</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">*</span><span style="color:#323232"> </span><span style="color:#0086b3">-1</span><span style="color:#323232">)</span><span style="color:#323232">,
</span><span style="color:#323232">    </span><span style="color:#323232">var</span><span style="color:#323232">(</span><span style="color:#0086b3">--</span><span style="color:#0086b3">background-x</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#323232">var</span><span style="color:#323232">(</span><span style="color:#0086b3">--</span><span style="color:#0086b3">background-y</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">  </span><span style="color:#0086b3">background-size</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#0086b3">500</span><span style="color:#a71d5d;font-weight:bold">px</span><span style="color:#323232"> </span><span style="color:#0086b3">100</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232">, </span><span style="color:#0086b3">200</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232"> </span><span style="color:#0086b3">400</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232">, </span><span style="color:#0086b3">195</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232"> </span><span style="color:#0086b3">100</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232">, </span><span style="color:#0086b3">200</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232"> </span><span style="color:#0086b3">100</span><span style="color:#a71d5d;font-weight:bold">%</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">
</span><span style="color:#323232">  </span><span style="color:#0086b3">filter</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#323232">brightness</span><span style="color:#323232">(</span><span style="color:#0086b3">1</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#323232">contrast</span><span style="color:#323232">(</span><span style="color:#0086b3">2.5</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#323232">saturate</span><span style="color:#323232">(</span><span style="color:#0086b3">1.75</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">  </span><span style="color:#0086b3">mix-blend-mode</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#323232">soft-light</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">}</span></code></pre><p>If it looks blank, then hover the mouse over it. My browser shows an empty box
until the interact function is called. Maybe it's a z-index issue.</p><style></style><div class="card_demo3" id="holocard_demo3">
    <div class="card__rotator_demo3">
        <div class="card__front_demo3">
            <!-- <img src="" width="330" height="460" />  -->
            <div class="card__shine_demo3"></div>
        </div>
    </div>
</div><script></script><p>Next: <a href="https://mattsmith.id.au/blog/2026/05-23-pokemon-cards-3.html">Mask Layer of Pokemon cards implemented in CSS and javascript</a></p> ]]></description>
            <pubDate>Fri, 22 May 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="true">https://mattsmith.id.au/blog/2026/05-22-pokemon-cards-2.html</guid>
        </item>
    
        <item>
            <title>Pokemon cards implemented in CSS and javascript</title>
            <link>https://mattsmith.id.au/blog/2026/05-21-pokemon-cards.html</link>
            <description><![CDATA[ <h1>Pokemon cards implemented in CSS and javascript</h1><p>A friend suggested that I create a pokemon card of myself for my website. When
we were googling Pokemon cards and CSS, the top hit was Simon Goellner's
<a href="https://poke-holo.simey.me/">Pokemon Cards Gallery</a> which led us to the
<a href="https://github.com/simeydotme/pokemon-cards-css">pokemon-cards-css GitHub
project</a>. I was pretty excited
by this and immediately wanted to understand how it worked. Simon has posted a
comprehensive <a href="https://deepwiki.com/simeydotme/pokemon-cards-css">Wiki site that explains the architecture of the
effect</a> which helped me get a
grasp of things but I also learned a lot by looking at the card in the Firefox
inspector and trying to reverse engineer the effect myself.</p><p>Here are some things to look at and steps I followed to integrate this into my
website without using Svelte since this website is very static.</p><p>I won't go into everything because there are so many details. The more I look at
this effect, the more impressed I am at how much attention to detail there is!
So this explanation will just cover the big ideas that I learnt.</p><h2>Part 1: Movement</h2><p>I'm going to divide this up into multiple posts because it takes awhile to
explain. Each concept is a bit of work so in the interests of getting other
stuff done, I'll make this a series.</p><p>First of all, to see the holographic effect, the card needs to move and catch
the light. Simon has published a stand-alone component for this but I went for
something even simpler. Also, the <a href="https://deepwiki.com/simeydotme/pokemon-cards-css/3.4-interactive-effects-system">architecture with all the details is
explained on the
wiki</a>.
Given a basic card HTML div, you can make it rotate in response to the mouse with
some pretty basic JS:</p><pre><code class="language-html"><span style="color:#323232">&lt;</span><span style="color:#63a35c">div</span><span style="color:#323232"> </span><span style="color:#795da3">class</span><span style="color:#323232">=</span><span style="color:#183691">&quot;</span><span style="color:#183691">card</span><span style="color:#183691">&quot;</span><span style="color:#323232"> </span><span style="color:#795da3">id</span><span style="color:#323232">=</span><span style="color:#183691">&quot;</span><span style="color:#183691">holocard</span><span style="color:#183691">&quot;</span><span style="color:#323232">&gt;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">&lt;</span><span style="color:#63a35c">div</span><span style="color:#323232"> </span><span style="color:#795da3">class</span><span style="color:#323232">=</span><span style="color:#183691">&quot;</span><span style="color:#183691">card__rotator</span><span style="color:#183691">&quot;</span><span style="color:#323232">&gt;</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#323232">&lt;</span><span style="color:#63a35c">div</span><span style="color:#323232"> </span><span style="color:#795da3">class</span><span style="color:#323232">=</span><span style="color:#183691">&quot;</span><span style="color:#183691">card__front</span><span style="color:#183691">&quot;</span><span style="color:#323232">&gt;</span><span style="color:#323232">
</span><span style="color:#323232">        </span><span style="color:#323232">&lt;/</span><span style="color:#63a35c">div</span><span style="color:#323232">&gt;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">&lt;/</span><span style="color:#63a35c">div</span><span style="color:#323232">&gt;</span><span style="color:#323232">
</span><span style="color:#323232">&lt;/</span><span style="color:#63a35c">div</span><span style="color:#323232">&gt;</span></code></pre><p>Here are the styles that rotate the card__rotator element (and everything inside
it). The rotation is controlled using <code>--rotate-x</code> and <code>--rotate-y</code> which are
set by the javascript.</p><pre><code class="language-css"><span style="color:#323232">.</span><span style="color:#795da3">card</span><span style="color:#323232"> </span><span style="color:#323232">{</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#0086b3">border</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#323232">gray</span><span style="color:#323232"> </span><span style="color:#323232">solid</span><span style="color:#323232"> </span><span style="color:#0086b3">1</span><span style="color:#a71d5d;font-weight:bold">px</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#0086b3">width</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#0086b3">330</span><span style="color:#a71d5d;font-weight:bold">px</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#0086b3">height</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#0086b3">460</span><span style="color:#a71d5d;font-weight:bold">px</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#0086b3">perspective</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#0086b3">600</span><span style="color:#a71d5d;font-weight:bold">px</span><span style="color:#323232">;</span><span style="color:#323232">  </span><span style="color:#969896;font-style:italic">/*</span><span style="color:#969896;font-style:italic"> has to be on the parent element </span><span style="color:#969896;font-style:italic">*/</span><span style="color:#323232">
</span><span style="color:#323232">}</span><span style="color:#323232">
</span><span style="color:#323232">
</span><span style="color:#323232">.</span><span style="color:#795da3">card__rotator</span><span style="color:#323232"> </span><span style="color:#323232">{</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#969896;font-style:italic">/*</span><span style="color:#969896;font-style:italic"> The rotate-x/y variables changed in the javascript make the div move </span><span style="color:#969896;font-style:italic">*/</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#0086b3">transform-style</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#323232">preserve-3d</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#0086b3">transform</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#323232">rotateY</span><span style="color:#323232">(</span><span style="color:#323232">var(--rotate-x</span><span style="color:#323232">)</span><span style="color:#323232">) </span><span style="color:#323232">rotate</span><span style="color:#323232">X(</span><span style="color:#323232">var</span><span style="color:#323232">(</span><span style="color:#0086b3">--</span><span style="color:#0086b3">rotate-y</span><span style="color:#323232">));
</span><span style="color:#323232">}</span><span style="color:#323232">
</span><span style="color:#323232">
</span><span style="color:#323232">.</span><span style="color:#795da3">card__front</span><span style="color:#323232"> </span><span style="color:#323232">{</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#0086b3">width</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#0086b3">330</span><span style="color:#a71d5d;font-weight:bold">px</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#0086b3">height</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#0086b3">460</span><span style="color:#a71d5d;font-weight:bold">px</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#0086b3">background-color</span><span style="color:#323232">:</span><span style="color:#323232"> </span><span style="color:#323232">grey</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">}</span></code></pre><p>Here is the most basic javascript to react to mouse pointer events:</p><pre><code class="language-javascript"><span style="color:#a71d5d;font-weight:bold">const</span><span style="color:#323232"> </span><span style="color:#323232">card</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">=</span><span style="color:#323232"> </span><span style="color:#0086b3">document</span><span style="color:#323232">.</span><span style="color:#0086b3">getElementById</span><span style="color:#323232">(</span><span style="color:#183691">'</span><span style="color:#183691">holocard</span><span style="color:#183691">'</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">
</span><span style="color:#969896;font-style:italic">/*</span><span style="color:#969896;font-style:italic"> A simple function to keep a value inside expected bounds </span><span style="color:#969896;font-style:italic">*/</span><span style="color:#323232">
</span><span style="color:#a71d5d;font-weight:bold">function</span><span style="color:#323232"> </span><span style="color:#795da3;font-weight:bold">clamp</span><span style="color:#323232">(</span><span style="color:#323232">v</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#323232">min</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#323232">max</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#323232">{</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#a71d5d;font-weight:bold">return</span><span style="color:#323232"> </span><span style="color:#0086b3">Math</span><span style="color:#323232">.</span><span style="color:#323232">min</span><span style="color:#323232">(</span><span style="color:#0086b3">Math</span><span style="color:#323232">.</span><span style="color:#323232">max</span><span style="color:#323232">(</span><span style="color:#323232">v</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#323232">min</span><span style="color:#323232">)</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#323232">max</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">}</span><span style="color:#323232">
</span><span style="color:#323232">
</span><span style="color:#969896;font-style:italic">/*</span><span style="color:#969896;font-style:italic"> Interact is called by mouse events </span><span style="color:#969896;font-style:italic">*/</span><span style="color:#323232">
</span><span style="color:#a71d5d;font-weight:bold">function</span><span style="color:#323232"> </span><span style="color:#795da3;font-weight:bold">interact</span><span style="color:#323232">(</span><span style="color:#323232">e</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#323232">{</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#969896;font-style:italic">/*</span><span style="color:#969896;font-style:italic"> Calculate the cursor position as x/y coordinates normalised to 100% values </span><span style="color:#969896;font-style:italic">*/</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#a71d5d;font-weight:bold">const</span><span style="color:#323232"> </span><span style="color:#323232">rect</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">=</span><span style="color:#323232"> </span><span style="color:#323232">card</span><span style="color:#323232">.</span><span style="color:#323232">getBoundingClientRect</span><span style="color:#323232">(</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#a71d5d;font-weight:bold">const</span><span style="color:#323232"> </span><span style="color:#323232">x</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">=</span><span style="color:#323232"> </span><span style="color:#323232">e</span><span style="color:#323232">.</span><span style="color:#323232">clientX</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">-</span><span style="color:#323232"> </span><span style="color:#323232">rect</span><span style="color:#323232">.</span><span style="color:#323232">left</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#a71d5d;font-weight:bold">const</span><span style="color:#323232"> </span><span style="color:#323232">y</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">=</span><span style="color:#323232"> </span><span style="color:#323232">e</span><span style="color:#323232">.</span><span style="color:#323232">clientY</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">-</span><span style="color:#323232"> </span><span style="color:#323232">rect</span><span style="color:#323232">.</span><span style="color:#323232">top</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#a71d5d;font-weight:bold">const</span><span style="color:#323232"> </span><span style="color:#323232">pointerXPc</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">=</span><span style="color:#323232"> </span><span style="color:#323232">clamp</span><span style="color:#323232">(</span><span style="color:#323232">(</span><span style="color:#323232">x</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">/</span><span style="color:#323232"> </span><span style="color:#323232">rect</span><span style="color:#323232">.</span><span style="color:#323232">width</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">*</span><span style="color:#323232"> </span><span style="color:#0086b3">100</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">0</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">100</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#a71d5d;font-weight:bold">const</span><span style="color:#323232"> </span><span style="color:#323232">pointerYPc</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">=</span><span style="color:#323232"> </span><span style="color:#323232">clamp</span><span style="color:#323232">(</span><span style="color:#323232">(</span><span style="color:#323232">y</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">/</span><span style="color:#323232"> </span><span style="color:#323232">rect</span><span style="color:#323232">.</span><span style="color:#323232">height</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">*</span><span style="color:#323232"> </span><span style="color:#0086b3">100</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">0</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#0086b3">100</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#969896;font-style:italic">/*</span><span style="color:#969896;font-style:italic"> rotation is set to a scaled value based on the x/y position </span><span style="color:#969896;font-style:italic">*/</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">card</span><span style="color:#323232">.</span><span style="color:#323232">style</span><span style="color:#323232">.</span><span style="color:#323232">setProperty</span><span style="color:#323232">(</span><span style="color:#183691">'</span><span style="color:#183691">--rotate-x</span><span style="color:#183691">'</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#183691">`</span><span style="color:#323232">${</span><span style="color:#323232">(</span><span style="color:#323232">pointerXPc</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">-</span><span style="color:#323232"> </span><span style="color:#0086b3">50</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">*</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">-</span><span style="color:#0086b3">20</span><span style="color:#a71d5d;font-weight:bold">/</span><span style="color:#0086b3">50</span><span style="color:#323232">}</span><span style="color:#183691">deg</span><span style="color:#183691">`</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">card</span><span style="color:#323232">.</span><span style="color:#323232">style</span><span style="color:#323232">.</span><span style="color:#323232">setProperty</span><span style="color:#323232">(</span><span style="color:#183691">'</span><span style="color:#183691">--rotate-y</span><span style="color:#183691">'</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#183691">`</span><span style="color:#323232">${</span><span style="color:#323232">(</span><span style="color:#323232">pointerYPc</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">-</span><span style="color:#323232"> </span><span style="color:#0086b3">50</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#a71d5d;font-weight:bold">*</span><span style="color:#323232"> </span><span style="color:#0086b3">20</span><span style="color:#a71d5d;font-weight:bold">/</span><span style="color:#0086b3">50</span><span style="color:#323232">}</span><span style="color:#183691">deg</span><span style="color:#183691">`</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">}</span><span style="color:#323232">
</span><span style="color:#323232">
</span><span style="color:#a71d5d;font-weight:bold">function</span><span style="color:#323232"> </span><span style="color:#795da3;font-weight:bold">interactEnd</span><span style="color:#323232">(</span><span style="color:#323232">e</span><span style="color:#323232">)</span><span style="color:#323232"> </span><span style="color:#323232">{</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">card</span><span style="color:#323232">.</span><span style="color:#323232">style</span><span style="color:#323232">.</span><span style="color:#323232">setProperty</span><span style="color:#323232">(</span><span style="color:#183691">'</span><span style="color:#183691">--rotate-x</span><span style="color:#183691">'</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#183691">'</span><span style="color:#183691">0deg</span><span style="color:#183691">'</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">    </span><span style="color:#323232">card</span><span style="color:#323232">.</span><span style="color:#323232">style</span><span style="color:#323232">.</span><span style="color:#323232">setProperty</span><span style="color:#323232">(</span><span style="color:#183691">'</span><span style="color:#183691">--rotate-y</span><span style="color:#183691">'</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#183691">'</span><span style="color:#183691">0deg</span><span style="color:#183691">'</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">}</span><span style="color:#323232">
</span><span style="color:#323232">
</span><span style="color:#323232">card</span><span style="color:#323232">.</span><span style="color:#323232">addEventListener</span><span style="color:#323232">(</span><span style="color:#183691">'</span><span style="color:#183691">pointermove</span><span style="color:#183691">'</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#323232">interact</span><span style="color:#323232">)</span><span style="color:#323232">;</span><span style="color:#323232">
</span><span style="color:#323232">card</span><span style="color:#323232">.</span><span style="color:#323232">addEventListener</span><span style="color:#323232">(</span><span style="color:#183691">'</span><span style="color:#183691">pointerleave</span><span style="color:#183691">'</span><span style="color:#323232">,</span><span style="color:#323232"> </span><span style="color:#323232">interactEnd</span><span style="color:#323232">)</span><span style="color:#323232">;</span></code></pre><p>And the result is a card you can hover the mouse over.</p><style></style><div class="card_demo1" id="holocard-demo-1">
    <div class="card__rotator_demo1">
        <div class="card__front_demo1">
        </div>
    </div>
</div><script></script><p>Next: <a href="https://mattsmith.id.au/blog/2026/05-22-pokemon-cards-2.html">Add the Shine Layer</a></p> ]]></description>
            <pubDate>Thu, 21 May 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="true">https://mattsmith.id.au/blog/2026/05-21-pokemon-cards.html</guid>
        </item>
    
        <item>
            <title>Feature branches vs feature flags</title>
            <link>https://mattsmith.id.au/blog/2026/05-17-feature-branch-vs-flag.html</link>
            <description><![CDATA[ <h1>Feature Branches vs Feature Flags</h1><p>Many organisations use some combination of feature branches and feature flags.
But how do you decide what combination will work for your organisation?</p><p>In researching for this article, I came across this blog post at Rollgate which
pretty much covers everything that was on my mind. <a href="https://rollgate.io/blog/feature-flags-vs-feature-branches">Feature Flags vs Feature
Branches: When to Use
Each</a>.</p><p>So I will do a bit of a TL;DR of that article and conclude with a few of my own
observations. Here are the main points:</p><p>It's not either/or, feature flags and feature branches solve two different but
related problems: Feature branches allow control over code integration.
Feature flags allow control over feature release.</p><p>Feature branches provide code isolation and allow checks before the feature goes
into main. But they can be a burden if allowed to live for too long. I certainly
relate to the &quot;all-or-nothing release&quot; comment. I've seen it too many times: the
feature languishes in a branch, unable to be released until every idea that any
stakeholder had about the feature has been implemented and second guessed and in
all that time, not a single customer has seen it and even internal stakeholders
have only glimpsed it in sprint demos but not been able to interact with it.</p><p>Feature flags give the flexibility to release anytime but introduce more
complexity, expand the testing exponentially and can go stale.</p><p>The largest tech companies in the world use feature flags and feature branches:
Google, Netflix, Meta and Spotify.</p><p>The rest of the article goes into more details on the nuts and bolts of
implementing a feature flag service, git branching strategy and ways to prevent
pitfalls such as stale feature flags.</p><p>Here are a few thoughts I had based on my experiences in this area:</p><p>In embedded software, feature flags have some added complexities. Since the
software is versioned and needs to be rigorously tested, the cost of feature
flags is greater. Typically embedded firmware updates come with a migration
scheme that must upgrade the configuration. The migrations are now more complex
because the different combinations of feature flags must be considered as well.</p><p>In embedded software, there are limited tools for feature flags. The solution is
more than likely to be a in-house rolled solution involving hidden configuration
flags. This also means there won't be dashboards and other tools to track which
flags are running at any time. Explicit logging will be needed to ensure that
support engineers can interrogate which feature flags were enabled at the time
of a bug. If there are automated tools for collecting device state in the
context of a failure, ensure feature flag states are included.</p><p>The complexity added to CI by feature flags can't be understated. It's not just
about testing the feature itself but if that feature interacts with another
feature, then the other feature must now be tested twice or some decision needs
to be made about leaving some tests out and how to balance the risk.</p><p>Vertical slicing of features is essential for keeping feature branches short
lived. A feature flag actually enables this. If the stakeholders are in an
all-or-nothing mindset about feature delivery, then the small iterations of the
feature can be merged to main safely behind a feature flag, avoiding a long
lived feature branch.</p><h2>Further Reading</h2><p><a href="https://www.cloudbees.com/blog/feature-flags-vs-feature-branches">Don’t Branch that Code! Feature Flags vs. Feature Branches</a></p><p><a href="https://www.harness.io/blog/feature-flags-vs-feature-branches">Feature Flags vs. Feature Branches</a></p><p><a href="https://www.statsig.com/perspectives/feature-flags-vs-feature-branches">Feature flags vs feature branches in continuous delivery</a></p> ]]></description>
            <pubDate>Sun, 17 May 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="true">https://mattsmith.id.au/blog/2026/05-17-feature-branch-vs-flag.html</guid>
        </item>
    
        <item>
            <title>Coupling between packages and end-to-end testing</title>
            <link>https://mattsmith.id.au/blog/2026/05-13-coupling-between-packages.html</link>
            <description><![CDATA[ <h1>Coupling between packages and end-to-end testing</h1><p>Many organisations keep their code in multiple repositories which can create
challenges when trying to develop using continuous integration.</p><p>Let me explain. imagine you have an embedded Linux system with some proprietary
packages running on it: A web UI front end, a REST API that the Web UI talks to,
various CLI tools and daemons that interact with the hardware.</p><p><img src="https://mattsmith.id.au/images/2026/coupled-packages.excalidraw-resized.png" alt="Coupled Packages" /></p><p>You develop each of these packages in their own repository and they have
self-contained CI tests which use mocks for any interactions with other parts of
the system.</p><p>The day comes that you realise the manual end-to-end build and test steps you're
doing are not scalable. You need to set up a CI system that will build a system
image and run end-to-end tests on PRs before they are merged.</p><p>But there's a big problem now because of coupled changes across repositories.
You want to update the Web UI but it depends on a change to the REST API which
is in a different repository. How do you test that? You have to make the CI
build two PRs from different repos and combine them. How can you automate it?</p><p>Before I talk about some answers, a quick note about coupling. Coupling is where
a piece of software depends on some other piece of software behaving a certain
way. It is actually how all software works. But when most developers talk about
coupling as a problem, they are referring to coupling that breaks application
programming interface (API) contracts. APIs are how software systems can be
coupled safely. Most of the time when I'm talking about coupling here, I'm
meaning the general sense of two packages that interact, not necessarily
breaking APIs.</p><p>Coupling of packages is not really unique to embedded Linux systems, it will
happen in any end-to-end testing in CI when applications of multiple repos need
to depend on each other. To solve the issue, you have three main approaches:</p><ol><li><p>Migration through multiple commits: Make multiple commits using backwards
compatibility. You add support for the new work but ensure it can still also
work the old way. Then you upgrade the dependent system to make use of the
new way, then you remove the support for the old way.</p></li><li><p>Orchestration and composition: An orchestration layer is introduced to your
CI that can be made aware of dependencies between commits and configure the
build to compose the PRs across multiple repos.</p></li><li><p>Use a monorepo: Anything that is causing coupling issues should just be put
into one big repo. For an embedded Linux system, this would mean having your
Yocto layer and proprietary applications all in the one repo with only a few
independent and very stable packages staying in their own repos.</p></li></ol><p>The downside of migration through multiple commits is that the commits all have
to go through CI and get reviewed. It adds a lot of overhead to a potentially
small change and depending on how slow your test and review stages are, it could
be prohibitively expensive.</p><p>One theoretical benefit of migration through multiple commits is it creates a
disincentive to couple changes together. Developers are challenged to avoid
changing APIs where possible. In practice, this is impossible. Some repositories
are naturally coupled - even if they are very different code bases e.g. you
might have a C back-end and Javascript front-end in different repositories that
are often dependent because some change in the back-end needs to also be
reflected in the front-end.</p><p>Orchestration and composition is supported to some extent in CI tools. There is
usually some facility to trigger other workflows and pass payloads between them
but it always seems pretty fragile to me. The configurations are complex and
seem to be add-ons rather than designed in from the start. In my experience it
is not a reliable approach. If you're in a position where you are choosing which
CI system to adopt, then put composability on a must-have features list.</p><p>Monorepos are the more traditional systems I've seen. They are the simplest
case. There are a few disadvantages though:</p><ul><li>They become massive! The monorepo grows and grows and it's difficult to trim
them back. Cloning them can take a very long time and use a lot of bandwidth
if you're using cloud services.</li><li>They enable code that breaks API boundaries. Once the barriers to coupled
packages are removed, developers will start writing code that violates API
design principles. Not on purpose but because of time pressures or by mistake.
It is up to the senior engineers to now police the system with more vigilance
or come up with some automated checks.</li></ul><p>So what's the answer? I think it's a combination of the three options. A rule
I've heard before is that &quot;code that changes together should stay together&quot;. The
idea is usually for architecture but it can apply to repository organisation as
well. The default should be to isolate packages in their own repositories and
build out a pretty flexible API early on so that ongoing changes are minimised.
If the nature of the package is that it has to change in sync with another
package more often than not, then consider merging the two repositories. Once
you have packages sharing a repository, introduce review checklists and
automation that alert or prevent developers from breaking APIs.</p> ]]></description>
            <pubDate>Wed, 13 May 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="true">https://mattsmith.id.au/blog/2026/05-13-coupling-between-packages.html</guid>
        </item>
    
        <item>
            <title>Release Branches for Embedded Software</title>
            <link>https://mattsmith.id.au/blog/2026/05-09-release-branches.html</link>
            <description><![CDATA[ <h1>Release Branches for Embedded Software</h1><p>I've been thinking a lot about release branches. Many software companies are not
really doing releases anymore with continuous deployment / delivery and SaaS
offerings but in the embedded world, firmware is developed and packaged as
versioned files that must be rigorously tested with the aim that they will be
deployed and perhaps never upgraded or only infrequently upgraded when critical
security updates are required.</p><p>This means that many companies don't have to worry about release branches. For us
embedded developers, the way release branches are managed can have a big impact
on the ability to support customers with hotfixes and patch releases as well as
timely delivery of feature releases.</p><p>The three most talked about git &quot;flow&quot; methods that I can find are GitFlow,
GitHub-Flow and GitLab-Flow. Of these, only gitflow and Gitlab-flow are suitable
for embedded development. I'll briefly explain github-flow anyway.</p><h2>What are we trying to achieve?</h2><p>It's worth taking some time to understand why git branching is important.
Collaboration is fundamental to software development and git / scm is at the
heart of the collaboration when it comes to code.</p><p>Some features these flows try to optimise for:</p><ul><li><p>Minimise the time code spends in branches. When code is in branches, it
quickly drifts from the main branch as other code is merged in. The longer it
stays in a branch, the harder it is to reconcile at merge time.</p></li><li><p>Some form of tagged release. Whether using a release branch or not, all git
flows have some point at which the code is released. This is important when it
comes to support. The developers supporting the software must know which range
of commits are in each release so they know which code a customer is running
by knowing the release version.</p></li><li><p>Isolation of releases. While code is being prepared for release, most
organisations are also developing the next round of features. The release
should happen in parallel and be isolated from changes that result from the
features under development.</p></li></ul><h2>Different flow styles</h2><p>GitFlow is the original &quot;flow&quot; method that started the conversation. In 2010,
software developer Vincent Driessen blogged about how he uses git branches and
in particular how this helps manage releases. He called it
<a href="https://nvie.com/posts/a-successful-git-branching-model/">GitFlow</a>.</p><p><img src="https://mattsmith.id.au/images/2026/mermaid-diagram-2026-05-11-220036-resized.png" alt="GitFlow Diagram" /></p><p>By his own amendment in 2020, GitFlow is not suited to more modern practices
such as continuous delivery since it adds overheads such as release processes
and hotfixes.</p><p>Some other things to note about GitFlow:</p><ul><li><p>It mentions decentralised development where developers pull from each others
repositories to share code. I have rarely worked that way. In practice most
companies use a central server and if there is sharing, it is through
branches. This often also results in rewriting of history to hide WIP commits,
a practice that git didn't support very well in 2010 but is commonplace now.</p></li><li><p>It assumes you are only supporting the most recent release. The main branch is
considered the stable release. If you need to make a hotfix, you branch from
head of the main branch which is the most recent release. There are no long
lived release branches that let you amend an older release. In embedded
software, there are often customers who can't upgrade to the latest release,
whether it be due to hardware limitations or integrations with other systems,
those customers often need a patch release targeted at their older software.</p></li><li><p>It promotes use of <code>--no-ff</code> commits. This can be a contentious topic amongst
developers. Many developers advocate for linear git commit history where
merges are performed using rebase and fast-forward (<code>--ff-only</code>). This might
also be a product of 2010s thinking where rebasing was a more dangerous
operation.</p></li></ul><p><a href="https://docs.github.com/en/get-started/using-github/github-flow">GitHub-Flow</a>
is maybe the more common flow style in use now. It is suited to continuous
delivery. It is simply feature branch -&gt; PR -&gt; main. The main branch is just
tagged as a release whenever development has reached some point that the
developers are happy with or when a particularly featureful PR has landed.</p><p><img src="https://mattsmith.id.au/images/2026/mermaid-diagram-2026-05-11-215231-resized.png" alt="GitHub-Flow Diagram" /></p><p>For embedded development, GitHub-Flow lacks some important features:</p><ul><li><p>Embedded software releases require rigorous testing that often takes days if
not weeks. It is not practical to perform that kind of validation for every PR
(unless you have really good automation).</p></li><li><p>There is no way to make hotfixes or patch releases. For reasons discussed
above, many customers of embedded software require patch releases.</p></li></ul><p><a href="https://docs.gitlab.co.jp/ee/topics/gitlab_flow.html">GitLab-Flow</a> bridges the
gap between GitFlow and GitHub-Flow. It is very similar to GitHub-Flow in its
most basic form but documents a release branching strategy for versioned
software.</p><p>In GitLab-Flow, the main branch and feature branches are used with PRs just like
in GitHub-Flow. When it comes time for release, a release branch is created from
main. As the release branch is tested, any problems found are fixed in main and
then cherry-picked to the release branch. Once released, the release branch is
tagged. If hotfixes or patch releases are needed, the required fixes are
cherry-picked from main to the release branch and tested. Once stable, the
release branch is tagged again.</p><p><img src="https://mattsmith.id.au/images/2026/mermaid-diagram-2026-05-11-215042-resized.png" alt="GitLab-Flow Diagram" /></p><p>Some features of GitLab-Flow development with release branches:</p><ul><li><p>There are fewer branches. The main branch is used more and some continuous
integration styles are possible. Developers do feature branches and PRs just
as they do for continuously delivered applications.</p></li><li><p>Supports patch releases on multiple released versions in a sane way. It would
be a pain if a customer wanted to skip a patch release e.g. they want 3.1.3
but not the previous fix 3.1.2 but that would be very rare and still possible
by branching the release branch.</p></li><li><p>There is a lot of cherry-picking.</p></li></ul><h2>What is a code freeze?</h2><p>A long time ago when it was hard to make branches in repositories, organisations
would have a code freeze before release. This is where development on new
features stops, the main branch is locked and only bugfixes for the release are
allowed. The whole team focuses on the release. While this practice is outdated
for most software development, it is still used to some extent for embedded
projects. A simple reason is where regression testing an embedded product is
labour intensive and all developers are required for testing and bugfixing. it
might not be worth going through the process of a release branch when there is
no other development happening during the release phase.</p><h2>Should release branches be closed?</h2><p>GitFlow promotes closing release branches once the release is merged. A stable
branch contains the most recently released code at its head. This simplifies
matters and makes automating some release processes easier. GitLab-Flow promotes
keeping release branches open until the release is no longer supported. For
embedded development, it is better to have the option of supporting the older
releases so keeping release branches around should be planned for and a strict
naming convention should be adhered to. Tooling can be developed to aid managing
the branches.</p><h2>Cherry-picks vs merging</h2><p>GitFlow promotes merging the release branch to the development branch. In that
scenario, fixes during release testing go onto the release branch directly and
future regressions are prevented by merging the release branch back to develop
when done.</p><p>GitLab-Flow promotes fixing all bugs in the main branch and cherry-picking from
the main branch to the release branch so that the main branch never misses out
on the bugfix. Cherry-picks are more risky than branch merges because they rely
on knowing which commits have the desired fixes. Sometimes developers make
mistakes and don't label things properly or they sneak a bugfix into a different
commit or even accidentally fix a bug in one commit but they think it was
resolved by another. There can be dependencies between commits that require
conflict resolution. Often the original developer must be consulted to resolve
those issues. Releases can be hectic which puts pressure on developers and can
lead to mistakes.</p><p>Despite that, in some cases, cherry-picks are the better trade-off, especially
when a linear git history is prioritised. For example, merges involving
submodules and multiple repositories can be just as error prone.</p><h2>Multiple Repositories and Submodules</h2><p>Many organisations maintain multiple repositories and submodules to share code
between different products. This poses a few problems when it comes to releases.</p><p>The first question is whether to lock releases in the repositories all together.
This would mean that release branches are created in all the repositories and
the references are to the heads of those release branches. The advantage of this
is the multiple repositories don't all need their own release life-cycles. If
this approach is used, then take care to make the naming scheme product
specific, otherwise different products releasing using the same shared
repository will have name clashes and confusion will reign.</p><p>If the repository releases are decoupled, then they should be in a state where
they are either very stable or they are treated like a kind-of in house vendor
library. Releases of these repositories will need to be coordinated in response
to the needs of the various products that reference them. The disadvantage of
this approach is that if a bugfix is needed in the repository, then the product
release is delayed until the internal release of the repository can be made (it
might be very quick if it supports continuous delivery).</p><p>Branching of repositories that reference other repositories is complicated. For
example, when the upper repository is branched, it still refers to the main
branch of the submodule. The submodule must also be branched for bug fixes and
the reference in the upper repository updated. This second step is often
forgotten so it is recommended to create automated processes to ensure it is
done.</p><p>If you choose to avoid cherry-picks and instead decide to merge the release
branch back into main, then submodules also cause issues. Imagine you have made
several fixes in the submodule and updated the upper repository several times.
Now you want to merge back to main. If you are using <code>--no-ff</code>, it's not so bad.
The submodule should be merged first and the when merging the upper repository,
git will find the merge commit and update the reference. When using rebase and
fast-forward, it's not so easy. As soon as you rebase the submodule, you change
history. This breaks the upper module references to the submodule. You need to
find the upper repository reference updates and reconcile them with the new
history. In most cases it's easier to rewrite the upper repository references
updates so they all but the last one is removed. This rewriting of history is
error prone but some developers believe it is worth it to preserve a linear main
branch.</p><h2>A little plug for Mermaid</h2><p><a href="https://mermaid.ai/open-source/">Mermaid</a> is a diagramming tool that supports
documentation as code. The diagrams are mostly human readable code that can be
committed to git alongside README.md files. It happens to support Git diagrams
which is really cool and how I made the diagrams for this blog post.</p><p>GitFlow mermaid:</p><pre><code class="language-mermaid">gitGraph
    commit
    branch develop
    checkout develop
    commit
    branch feature
    checkout feature
    commit
    commit
    checkout develop
    merge feature
    branch release
    checkout release
    commit
    commit
    checkout main
    merge release tag: &quot;3.2.0&quot;
    branch hotfix
    checkout hotfix
    commit
    checkout main
    merge hotfix tag: &quot;3.2.1&quot;
    checkout develop
    commit</code></pre><p>GitHub-Flow with release tag mermaid:</p><pre><code class="language-mermaid">gitGraph
    commit
    branch feature
    checkout feature
    commit
    checkout main
    commit
    checkout feature
    commit
    checkout main
    merge feature
    commit tag: &quot;3.2.1&quot;</code></pre><p>GitLab-Flow with release branch mermaid:</p><pre><code class="language-mermaid">gitGraph
    commit
    branch feature
    checkout feature
    commit
    checkout main
    commit
    checkout feature
    commit
    checkout main
    merge feature
    commit
    branch release
    checkout release
    commit
    commit tag: &quot;3.2.1&quot;
    checkout main
    commit</code></pre><h2>Further Reading</h2><p><a href="https://medium.com/@smita.s.kothari/mastering-github-release-branches-versioning-a-complete-guide-3ea42ebd0d0f">Mastering GitHub Release Branches &amp; Versioning: A Complete Guide</a></p><p><a href="https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow">Comparing Git workflows: What you should know: GitFlow</a></p> ]]></description>
            <pubDate>Sat, 09 May 2026 00:00:00 +0000</pubDate>
            <guid isPermaLink="true">https://mattsmith.id.au/blog/2026/05-09-release-branches.html</guid>
        </item>
    
</channel>
</rss>
