<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Tasty Notes]]></title><description><![CDATA[Obsidian digital garden]]></description><link>http://github.com/dylang/node-rss</link><image><url>site-lib/media/favicon.png</url><title>Tasty Notes</title><link></link></image><generator>Webpage HTML Export plugin for Obsidian</generator><lastBuildDate>Sat, 08 Aug 2026 01:11:42 GMT</lastBuildDate><atom:link href="site-lib/rss.xml" rel="self" type="application/rss+xml"/><pubDate>Sat, 08 Aug 2026 01:11:37 GMT</pubDate><ttl>60</ttl><dc:creator></dc:creator><item><title><![CDATA[Welcome]]></title><description><![CDATA[Hello strange; welcome to my personal website.This website is presented as a disorganised archive of notes and write ups for programming and art projects I find interesting enough to share.All work presented here is my own not the product of AI.If you would like to follow me on social media I can be found at:
<a data-tooltip-position="top" aria-label="https://tastybugs.club/@TastyTentacles" rel="noopener nofollow" class="external-link is-unresolved" href="https://tastybugs.club/@TastyTentacles" target="_self">@tastytentacles</a> on Mastodon
<br><a data-tooltip-position="top" aria-label="https://bsky.app/profile/tastybugs.bsky.social" rel="noopener nofollow" class="external-link is-unresolved" href="https://bsky.app/profile/tastybugs.bsky.social" target="_self">@tastybugs</a> on BlueSky.
]]></description><link>tasty_notes/welcome.html</link><guid isPermaLink="false">tasty_notes/Welcome.md</guid><pubDate>Sat, 08 Aug 2026 01:11:27 GMT</pubDate></item><item><title><![CDATA[Signature Cards]]></title><description><![CDATA[This is the third rebuild of this card system. The first version was written in pure JavaScript, The second version I build in Godot but while the resulting WebAssembly binary was graphically more complected it came at the expense of 39.5 MB of binary and 12.6 MB of pck. Significantly to large for a toy, not even factoring the assets.This version is built entirely using <a data-tooltip-position="top" aria-label="https://threejs.org/" rel="noopener nofollow" class="external-link is-unresolved" href="https://threejs.org/" target="_self">Three.js</a> and comes out to a cool 382.6 KB 😚👌
This is also the first time I have used Three.js for a project, and I am infatuated.The basic implementation is the same as the previous two versions. Three.js uses a similar node system to most conventional game engines so the set up is almost identical to the last version. I will be using some images of version 2 when appropriate.&lt;div style="width: 100%"&gt; &lt;iframe src="https://tastybugs.ink/hidden/card_engine/?source=https://tastybugs.ink/hidden/card_data/me_card/data.json" style="width: 40em; height: 40em;"&gt;&lt;/iframe&gt;
&lt;/div&gt;
This version also handles multiple instances cleanly in addition to being a lot lighter.I am embedding an iframe pointing to the card page and a data.json file containing the cards information.{ "name": "Aleq de Villiers", "edge_color": "#cfc884", "type_color": "#a5e339", "tag_color": "#e6d329", "metal": 0.9, "rough": 1.0, "type": "DEV", "description": "I grew up reading about fantastic technologies and lived long enough to see them become complicated reality.", "tags": [ "She/They", "South African", "Linux", "Rust", "C/++", "C#", "Java/TypeScript", "Godot", "Unity", "Unreal" ], "layers": [ "https://tastybugs.ink/hidden/card_data/me_card/me_tile.png", "https://tastybugs.ink/hidden/card_data/me_card/land_0_tile.png", "https://tastybugs.ink/hidden/card_data/me_card/land_1_tile.png", "https://tastybugs.ink/hidden/card_data/me_card/land_2_tile.png", "https://tastybugs.ink/hidden/card_data/me_card/land_3_tile.png" ]
}
The data.json being loaded in is straightforward. Image layers are loaded in by url and some basic attributes are exposed for chrome edging.<br><img alt="sig_card_a.png" src="tasty_notes/web_dev/file_blob/sig_card_a.png" target="_self" style="width: 654px; max-width: 100%;">Structurally the cards are still constructed the same way. We start with multiple layers, and then apply a stencil buffer to clip some layers; hiding the edges with the remaining layers.One major difference with this version is that I am drawing the card mask and content when the page loads; to save bandwidth.// --- Draw card frame ---
const frame_canvas = document.createElement('canvas');
const frame_ctx = frame_canvas.getContext('2d'); frame_canvas.width = 1024;
frame_canvas.height = 1024;
frame_ctx.fillStyle = "black";
frame_ctx.fillRect(0, 0, 1024, 1024); frame_ctx.strokeStyle = "white";
frame_ctx.lineWidth = 6;
frame_ctx.beginPath();
frame_ctx.roundRect(frame_left, frame_top, frame_width, frame_hight, [16])
frame_ctx.stroke(); const frame_texture = new THREE.CanvasTexture(frame_canvas);
In this sections I am drawing the frame mask.Thee.js makes setting up a stencil buffer as straightforward as Godot. When I create my renderer I enable the stencil buffer. There is only one buffer but I am creating a variable to track it anyway.const renderer = new THREE.WebGLRenderer({ stencil: true, antialias: true
}); ... let stencil_ref = 1;
Then I tell my portal material to write to the stencil buffer and pass in my reference.const portal_material = new THREE.MeshStandardMaterial();
...
portal_material.stencilRef = stencil_ref;
portal_material.stencilFunc = THREE.AlwaysStencilFunc;
portal_material.stencilZPass = THREE.ReplaceStencilOp;
Finally when setting up my layer materials I tell them to mask against the buffer I just wrote to; with our reference.const layer_material = new THREE.MeshStandardMaterial();
...
layer_material.stencilWrite = true;
layer_material.stencilRef = stencil_ref;
layer_material.stencilFunc = THREE.EqualStencilFunc;
And that is it. I construct the card using multiple quads based on the data passed in at load and add it to the scene.// --- Spawn card object ---
const plane = new THREE.PlaneGeometry(2, 2);
const card_root = new THREE.Object3D(); const text_mesh = new THREE.Mesh(plane, text_material);
card_root.add(text_mesh); const edge_quad = new THREE.Mesh(plane, edge_material);
card_root.add(edge_quad); const portal_quad = new THREE.Mesh(plane, portal_material);
portal_quad.position.set(0, 0, -.001);
card_root.add(portal_quad); for (let i in card_data["layers"]) { const url = card_data["layers"][i]; const layer_material = new THREE.MeshStandardMaterial(); layer_material.opacity = 0; layer_material.transparent = true; layer_material.stencilWrite = true; layer_material.stencilRef = stencil_ref; layer_material.stencilFunc = THREE.EqualStencilFunc; loader.loadAsync(url).then((texture) =&gt; { layer_material.map = texture; layer_material.opacity = 1; layer_material.needsUpdate = true; }) const layer_quad = new THREE.Mesh(plane, layer_material); const layer_scale = 1 + .1 * i layer_quad.position.set(0, 0, -.1 -.1 * i); layer_quad.scale.set(layer_scale, layer_scale, layer_scale); card_root.add(layer_quad);
} scene.add(card_root);
If you want to create your own cards to host on your own website, you might have guessed, that is straightforward.First of all you are going to need file hosting. Layers are loaded remotely, you can host your own images and data files as a subdirectory of your website and then point to it in a similar manner as above.&lt;iframe src="https://tastybugs.ink/hidden/card_engine/?source=[your data file url]" style="width: 512px; height: 512px;"&gt;&lt;/iframe&gt;
You can also download and host the files yourself. There are only 4 files you need in addition to your asset files.Place these files in their own subdirectory of your website or file hosting service and point to it in the same way as before.&lt;iframe src="[Your end-point]?source=[your data file url]" style="width: 512px; height: 512px;"&gt;&lt;/iframe&gt;
The noise texture can be replaced to create different texture effects.Layers should be layed out with the focal object in the centre third of the image and need to be 1 by 1 images to fit the card mesh, any scale will work it's up to you. I used 1024px by 1024px images. You can use as many layers as you want; there is no hard limit.]]></description><link>tasty_notes/web_dev/signature-cards.html</link><guid isPermaLink="false">tasty_notes/web_dev/Signature Cards.md</guid><pubDate>Sat, 08 Aug 2026 01:10:09 GMT</pubDate><enclosure url="tasty_notes/web_dev/file_blob/sig_card_a.png" length="0" type="image/png"/><content:encoded>&lt;figure&gt;&lt;img src=&quot;tasty_notes/web_dev/file_blob/sig_card_a.png&quot;&gt;&lt;/figure&gt;</content:encoded></item><item><title><![CDATA[Liquid UI in Godot using 2D displacement]]></title><description><![CDATA[This article is based on a now deleted thread I posted in 2025.In 2025 Apple released&nbsp;<a data-tooltip-position="top" aria-label="https://www.apple.com/uk/os/ios/" rel="noopener nofollow" class="external-link is-unresolved" href="https://www.apple.com/uk/os/ios/" target="_self">iOS 26</a>&nbsp;and their liquid glass design language. Many people compared this design language to&nbsp;<a data-tooltip-position="top" aria-label="https://aesthetics.fandom.com/wiki/Frutiger_Aero" rel="noopener nofollow" class="external-link is-unresolved" href="https://aesthetics.fandom.com/wiki/Frutiger_Aero" target="_self">Frutiger Aero</a>&nbsp;because of its heavy use of real time liquid distortion effects. In this article I will show you how I created a similar real time distortion effect in Godot; and explain the theory used to create this effect. So you better understand how to apply real time distortion in any rendering system.The basic idea behind any kind of real time distortion is that you need to separate the elements into 3 groups. Foreground: Everything in front of the distorting element.
Lens: The element creating the distortion effect.
Background: Everything behind the distorting element.In this example I am using Godot so we can use screen texture as our background layer; because it will contain all the elements bellow the current element. Our current element is therefor the lens. We can ignore foreground in this case because Godot will also be handling our compositing.If we where assembling this effect in a more standard 3D derived pipeline we would have to set up multiple render passes to correctly produce the effect.Once we have our background and lens elements we need to render our background onto our lens so that it appears transparent. We start by grabbing the lenses position and size as it overlaps with the background. we can then use this information to sample the colour of our background at any given position inside our lens element.<br><img alt="liquid_frog_diagram.png" src="tasty_notes/godot/file_blob/liquid_frog_diagram.png" target="_self">In the diagram on above I am representing an arbitrary position inside the lens element as the blue point. In order to create the effect that light is bending as it passes through our imaginary lens we need a distortion vector for each point in the lens element. Here we are going to use a normal map because this element does not dynamically change shape. I am representing the normal map in red. If we apply the normal maps vector when sampling our background image we arrive at our final product, the colour of our background at any given point of our element, offset by a distortion map. Here I am representing that with purple, this is the final value written to the lens element texture.As a result, this element is not actually transparent. The transparency of the element is an illusion created by the context the element is presented in and the apparent distortion.<br><img alt="liquid_frog_graph_view.png" src="tasty_notes/godot/file_blob/liquid_frog_graph_view.png" target="_self">
The project I am implementing this in is a simple 2D pet sim. I was inspired by games like Pikmin which use water droplet elements in their UI design to capture the scale the game takes place in. I am trying to create a similar effect in this project, both to capture the scale of the pets and because they are frog. Moisture feels appropriate.<br><img alt="liquid_frog_normal_texture.png" src="tasty_notes/godot/file_blob/liquid_frog_normal_texture.png" target="_self"><br><img alt="liquid_frog_mask_texture.png" src="tasty_notes/godot/file_blob/liquid_frog_mask_texture.png" target="_self">The Normal map on the top is what I originally produced for this effect. I made it using Inkscape with the rest of the graphics. Unfortunately Godot does not support the standards required to import this graphic as a vector image.Instead of exporting the normal map as the only rasterized graphic in the project. I decided to use a mask texture instead. This mask was originally intended to create an edge highlight but we can also use it to apply our fragments distance from the centre of the element in place of our normal map look up. Which will work the same way as our normal map because our lens element is convex. Concave lenses need a correct normal map to fake distortion appropriately and a deliberate normal vector will generally produce a better effect, and be cheaper, then something derived in real time.<br><img alt="liquid_frog_final.png" src="tasty_notes/godot/file_blob/liquid_frog_final.png" target="_self">When I combine this shader with my speech bubble system we get this result. The distortion effect can be adjusted with a sacler to make it less or more dramatic and we can see how the distortion correctly handles both UI and world elements. It also works on GPU particle 2D systems, although it's ability to layer lenses is limited.If I was going to implement this again. I would use the full normal texture and add more detail to it. While the smooth blob like distortions are appealing against complex enough background, I would like to see animated ripples in the distortion for it to feel fully liquid.]]></description><link>tasty_notes/godot/liquid-ui-in-godot-using-2d-displacement.html</link><guid isPermaLink="false">tasty_notes/godot/Liquid UI in Godot using 2D displacement.md</guid><pubDate>Thu, 06 Aug 2026 17:36:06 GMT</pubDate><enclosure url="tasty_notes/godot/file_blob/liquid_frog_diagram.png" length="0" type="image/png"/><content:encoded>&lt;figure&gt;&lt;img src=&quot;tasty_notes/godot/file_blob/liquid_frog_diagram.png&quot;&gt;&lt;/figure&gt;</content:encoded></item><item><title><![CDATA[sig_card_a]]></title><description><![CDATA[<img src="tasty_notes/web_dev/file_blob/sig_card_a.png" target="_self">]]></description><link>tasty_notes/web_dev/file_blob/sig_card_a.html</link><guid isPermaLink="false">tasty_notes/web_dev/file_blob/sig_card_a.png</guid><pubDate>Thu, 06 Aug 2026 16:00:29 GMT</pubDate><enclosure url="tasty_notes/web_dev/file_blob/sig_card_a.png" length="0" type="image/png"/><content:encoded>&lt;figure&gt;&lt;img src=&quot;tasty_notes/web_dev/file_blob/sig_card_a.png&quot;&gt;&lt;/figure&gt;</content:encoded></item><item><title><![CDATA[liquid_frog_diagram]]></title><description><![CDATA[<img src="tasty_notes/godot/file_blob/liquid_frog_diagram.png" target="_self">]]></description><link>tasty_notes/godot/file_blob/liquid_frog_diagram.html</link><guid isPermaLink="false">tasty_notes/godot/file_blob/liquid_frog_diagram.png</guid><pubDate>Thu, 06 Aug 2026 14:34:03 GMT</pubDate><enclosure url="tasty_notes/godot/file_blob/liquid_frog_diagram.png" length="0" type="image/png"/><content:encoded>&lt;figure&gt;&lt;img src=&quot;tasty_notes/godot/file_blob/liquid_frog_diagram.png&quot;&gt;&lt;/figure&gt;</content:encoded></item><item><title><![CDATA[liquid_frog_final]]></title><description><![CDATA[<img src="tasty_notes/godot/file_blob/liquid_frog_final.png" target="_self">]]></description><link>tasty_notes/godot/file_blob/liquid_frog_final.html</link><guid isPermaLink="false">tasty_notes/godot/file_blob/liquid_frog_final.png</guid><pubDate>Thu, 06 Aug 2026 14:31:34 GMT</pubDate><enclosure url="tasty_notes/godot/file_blob/liquid_frog_final.png" length="0" type="image/png"/><content:encoded>&lt;figure&gt;&lt;img src=&quot;tasty_notes/godot/file_blob/liquid_frog_final.png&quot;&gt;&lt;/figure&gt;</content:encoded></item><item><title><![CDATA[liquid_frog_icon]]></title><description><![CDATA[<img src="tasty_notes/godot/file_blob/liquid_frog_icon.png" target="_self">]]></description><link>tasty_notes/godot/file_blob/liquid_frog_icon.html</link><guid isPermaLink="false">tasty_notes/godot/file_blob/liquid_frog_icon.png</guid><pubDate>Thu, 06 Aug 2026 14:30:29 GMT</pubDate><enclosure url="tasty_notes/godot/file_blob/liquid_frog_icon.png" length="0" type="image/png"/><content:encoded>&lt;figure&gt;&lt;img src=&quot;tasty_notes/godot/file_blob/liquid_frog_icon.png&quot;&gt;&lt;/figure&gt;</content:encoded></item><item><title><![CDATA[liquid_frog_mask_texture]]></title><description><![CDATA[<img src="tasty_notes/godot/file_blob/liquid_frog_mask_texture.png" target="_self">]]></description><link>tasty_notes/godot/file_blob/liquid_frog_mask_texture.html</link><guid isPermaLink="false">tasty_notes/godot/file_blob/liquid_frog_mask_texture.png</guid><pubDate>Thu, 06 Aug 2026 14:25:16 GMT</pubDate><enclosure url="tasty_notes/godot/file_blob/liquid_frog_mask_texture.png" length="0" type="image/png"/><content:encoded>&lt;figure&gt;&lt;img src=&quot;tasty_notes/godot/file_blob/liquid_frog_mask_texture.png&quot;&gt;&lt;/figure&gt;</content:encoded></item><item><title><![CDATA[liquid_frog_normal_texture]]></title><description><![CDATA[<img src="tasty_notes/godot/file_blob/liquid_frog_normal_texture.png" target="_self">]]></description><link>tasty_notes/godot/file_blob/liquid_frog_normal_texture.html</link><guid isPermaLink="false">tasty_notes/godot/file_blob/liquid_frog_normal_texture.png</guid><pubDate>Thu, 06 Aug 2026 14:25:06 GMT</pubDate><enclosure url="tasty_notes/godot/file_blob/liquid_frog_normal_texture.png" length="0" type="image/png"/><content:encoded>&lt;figure&gt;&lt;img src=&quot;tasty_notes/godot/file_blob/liquid_frog_normal_texture.png&quot;&gt;&lt;/figure&gt;</content:encoded></item><item><title><![CDATA[liquid_frog_graph_view]]></title><description><![CDATA[<img src="tasty_notes/godot/file_blob/liquid_frog_graph_view.png" target="_self">]]></description><link>tasty_notes/godot/file_blob/liquid_frog_graph_view.html</link><guid isPermaLink="false">tasty_notes/godot/file_blob/liquid_frog_graph_view.png</guid><pubDate>Thu, 06 Aug 2026 14:24:26 GMT</pubDate><enclosure url="tasty_notes/godot/file_blob/liquid_frog_graph_view.png" length="0" type="image/png"/><content:encoded>&lt;figure&gt;&lt;img src=&quot;tasty_notes/godot/file_blob/liquid_frog_graph_view.png&quot;&gt;&lt;/figure&gt;</content:encoded></item></channel></rss>