leanrada.com notes
フィード

Flow field visualisation on my homepage
leanrada.com notes
I created a new visualisation for my homepage rotation.Here’s a demo:It’s just a basic flow field simulation with particles tracing their paths along the space. I used the p5.js framework. It reacts to mouse movements!I did a few optimisations to make it smoother, but in the end I had to lower the framerate and tried to make the low frame rate look intentional. Optimisation was important especially for mobile devices.Some of the optimisations include:Using sprites / textures instead of the vector / shape drawing APIs. This was the biggest speedup. This meant I had to predraw the particle (a small dot) on a transparent texture, and use that texture at runtime instead. Sharing the same sprites across particles. This was also big one. I think this is called ‘instancing’. Having more particles per particle. The actual sprite is a big transparent square that contains many dots. This ‘dot spray’ sprite is stamped for each actual simulated particle. This means we can reduce the amount of simu
3日前

Naturalest sort
leanrada.com notes
For RSS readers: This article contains interactive content available on the original post on leanrada.com.You may have heard of the legendary naturalSort in JavaScript./** Natural Sort algorithm for Javascript - Version 0.7 - Released under MIT license* Author: Jim Palmer (based on chunking idea from Dave Koelle)*/function naturalSort (a, b) { var re = /(^-?[0-9]+(\.?[0-9]*)[df]?e?[0-9]?$|^0x[0-9a-f]+$|[0-9]+)/gi, sre = /(^[ ]*|[ ]*$)/g, dre = /(^([\w ]+,?[\w ]+)?[\w ]+,?[\w ]+\d+:\d+(:\d+)?[\w ]?|^\d{1,4}[\/\-]\d{1,4}[\/\-]\d{1,4}|^\w+, \w+ \d+, \d{4})/, hre = /^0x[0-9a-f]+$/i, ore = /^0/, i = function(s) { return naturalSort.insensitive && (''+s).toLowerCase() || ''+s }, // convert all to strings strip whitespace x = i(a).replace(sre, '') || '', y = i(b).replace(sre, '') || '', // chunk/tokenize xN = x.replace(re, '\0$1\0').replace(/\0$/,'').replace(/^\0/,'').split('\0'), yN = y.replace(re, '\0$1\0').replace(/\0$/,'').replace(/^\0/,'').split('\0'), // numeric, hex or date detection x
9日前

Japanese typing in QMK firmware
leanrada.com notes
For RSS readers: This article contains interactive content available on the original post on leanrada.com.Implemented a Japanese input method in my keyboard firmware (based on QMK).It’s used to write in katakana and hiragana (syllable-based writing systems for Japanese) using Roman / Latin letters on my keyboard.How kana worksHiragana and katakana (collectively kana) are syllabaries, writing systems where a consonant-vowel syllable is written as a single character.A common way to type kana on a regular keyboard is romaji, using Roman letters to produce kana. For example, sushi = すsuしshi.Kana are usually organised as a table with vowels as columns and consonants as rows: A I U E O K かka きki くku けke こko S さsa しsi/shi すsu せse そso T たta ちti/chi つtu/tsu てte とto ⋮ ⋮ ⋮ ⋮ ⋮ ⋮ This tabular structure inspired the core idea behind my implementation.The mapThe core idea is a 2D lookup table. Since flash memory (program space) is severely limited on keyboard microcontrollers, I wanted to represent
23日前

Cool URLs don’t change with snapshot testing
leanrada.com notes
I wrote automated tests to keep track of my site’s URLs and prevent links from breaking.When I reorganised my site, I changed a lot of URLs and broke some inbound links. Now that more and more sites have started linking to my site, I don’t want to inadvertently break links again.Cool URIs don't changeTim Berners-Lee (1998)This post is NOT about how to implement ‘cool URLs’. Too many ways to do that depending on the site’s setup and the site’s philosophy even. Rather, this post is about ensuring cool URLs — a contract to guarantee that published URLs continue to work. It doesn’t matter how your specific site implementation fulfil that contract.There already exists a contract: a social contract when you publish a webpage. Other people can link to your URL on their sites, and you can link to other people’s content on your site in return. And we expect these inbound and outbound links to work, otherwise the World Wide Web would be pretty boring to browse.A stronger contract is to treat URL
2ヶ月前

Implementing a Baybayin abugida input method on keyboard hardware
leanrada.com notes
For RSS readers: This article contains interactive content available on the original post on leanrada.com.I made an input method to write Baybayin, a syllable-based writing system, using Roman letters on a hardware keyboard. Banner photo lifted from a Reddit post showing Baybayin keycaps. This post is about a real Baybayin keyboard though.The writing systemBaybayin is an ancient writing system in which a consonant-vowel syllable is written as one letter — it is an abugida. By default, letters have an inherent vowel which is /a/. To indicate a different vowel, a dot is added above or below the letter.ᜃ (ka): The base letter on its own is the consonant plus the vowel ‘a’. ᜃᜒ (ki): A dot above makes it end with ‘i’ / ‘e’. ᜃᜓ (ku): A dot below makes it end with ‘u’ / ‘o’. ᜃ᜕ (k): A slash cancels the vowel (it’s called a virama).An example of a full word is ᜆtaᜆ᜕tᜎᜓlo(tatlo), which means the number three.HTML tip: I’m using the <ruby> element to annotate Baybayin pronunciation in this post,
3ヶ月前

Editing my website directly in the browser
leanrada.com notes
This website is editable!If you add ?edit to any URL on this site, it activates edit mode. In this mode, you can edit text and apply basic formatting to any element.Of course, you won’t be able to save changes without having write access to my files, but I can. Using the Web File System API, it can update my local copy of the website’s source code.In short, I can edit my site via my site. In fact, this article was written this way!It’s still rough in some cases, but the solution is usable.But why?As usual, I made this mainly for fun. But having a WYSIWYG (what-you-see-is-what-you-get) editor for my posts has been a long time goal ever since I rewrote my site in vanilla HTML.After I moved away from a static site generator, the need for an extra transformational build step disappeared, allowing the raw HTML source to be directly served in the client. This unlocked a path to source editing in the client! As well as other basic things for free.The HTML file can now act as the editing platf
7ヶ月前

Making my GitHub heatmap widget
leanrada.com notes
For RSS readers: This article contains interactive content available on the original post on leanrada.com.This post is about how I made the GitHub heatmap widget on my site.Here’s the raw, live WebComponent <gh-contribs> by the way:Interactive content: Visit the post to interact with this content.Alternative name: Gh ContribsScraping the dataFirst, I had to scrape the heatmap data. I don’t know if there’s a proper API, but I found an endpoint that renders an HTML partial of what I wanted.As far as I know, the GitHub website today works using partial HTMLs to update its UI without reloading the whole page. I think this endpoint populates the contribution graph section of the profile page.The endpoint that returns a user’s contribution graph is https://github.com/users/{username}/contributions. I presume the user has to have contribution stats public. This undocumented API could also break at any time. 😬The response HTML for my github.com/users/Kalabasa/contributionsLoading this endpoin...
8ヶ月前

Vibecoding Lima Gang, web-based map visualization
leanrada.com notes
I made Lima Gang, a map-based visualization, over a weekend using ‘vibecoding’.Lima what?lima means five in several languages particularly in island nations in Southeast Asia and the Pacific. These are part of the Austronesian language family that spans several seas from Madagascar to Indonesia, to Taiwan, to Hawaii.Extent of Austronesian peoples. They must’ve been good with boats. Source: WikimediaThe Lima Gang is a meme based on the fact that the word lima seemed to have survived intact, echoed in various tongues, and now somehow uniting people across vast oceans.This is more than a meme, Unicode even gave recognition to the Lima Gang by including its gang sign as an official emoji: 🖐🏽Jokes aside, I’m posting this to share a vibecoding experience.VibecodingThis small one-off app that I knew almost exactly how to make is a perfect case for some vibecoding practice. My initial thoughts were, it was going to be mostly boilerplate HTML code, some standard map UI code, and some standard...
9ヶ月前

My own RSS reader
leanrada.com notes
I started making my own RSS reader webapp (a what reader?). It can be accessed at /tools/rss/. Currently it’s hardcoded to just load my own site feed leanrada.com/rss.xml and that’s totally fine.I made this primarily to test how my own posts get rendered in ‘reader mode’.Reader modeYou see, I like to incorporate interactive elements in my posts. However, custom elements and JS are scrubbed by RSS reader apps, ultimately resulting in a clean but confusing and broken experience. Even custom CSS which I use to format some graphs and illustrations are lost.The same could be said for the Reading mode feature offered by most full-fledged web browsers today, if not their add-ons. It’s a feature that strips ‘distracting’ elements away. My own RSS reader actually uses the same library used by Mozilla Firefox’s Reader View.My barebones RSS reader showing a reader mode transformation of posts.But many of my posts are not meant to be just for reading. I want my posts to be interactive and playable
10ヶ月前

Simple live reload for developing static sites
leanrada.com notes
When developing my website, I’m using a simple client-side script to automatically reload the page whenever I make a change to the source files.Since it’s not coupled to any particular backend, I could continue using python3 -m http.server -d ./site/ or whatever local web server I wanted and it would still work. I could clone this repo on a new machine and get going with only preinstalled programs: a text editor, a browser, and a Python (or whatever) HTTP server. And live reload should* just work.Here’s the code (39 lines):let watching = new Set();watch(location.href);new PerformanceObserver((list) => { for (const entry of list.getEntries()) { watch(entry.name); }}).observe({ type: "resource", buffered: true });function watch(urlString) { if (!urlString) return; const url = new URL(urlString); if (url.origin !== location.origin) return; if (watching.has(url.pathname)) return; watching.add(url.pathname); console.log("watching", url.pathname); let lastModified, etag; async function check
1年前

Language evolves, so we must too. On generative art
leanrada.com notes
A year ago, I stood by the statement that AI Art is not Generative Art, arguing that the term ‘generative art’ is an art movement in itself that is separate from AI art.Safe to say, it has been a losing battle.Even the converse, “generative art is not necessarily AI art”, would be met by confusion from most people. ‘Generative’ has been equated to AI.Fidenza, a generative artwork system, by Tyler HobbsExhibitionr/generative - Has the word 'generative' been hijacked by AI?I think it's time to move on to a different label. Words are forcommunication, and if a word results in failure of communication, weshould either agree on a common definition (we can’t) or just use betterword.So, what are the other words for this art?If you haven’t read the mentioned prequel post, you may not understand what “art” I’m taliking about. There’s one example above, but here’s more.Fortunately, there are already well-established terms to describe themedium:Procedural art Algorithmic art Creative codingIt’s j
1年前

Minimal CSS-only blurry image placeholders
leanrada.com notes
Here’s a CSS technique that produces blurry image placeholders (LQIPs) without cluttering up your markup — Only a single custom property needed!<img src="…" style="--lqip:192900">The custom property above gives you this image: Try changing the property’s value (WARNING: FLASHING) { const card = document.currentScript.parentElement; const input = card.querySelector("input"); const code = card.querySelector("code"); const preview = card.querySelector("div"); let currentValueStr = "192900"; let targetCode = null input.addEventListener("input", event => { if (!targetCode) { targetCode = Array.from(code.querySelectorAll("span")).filter(el => el.textContent.includes(currentValueStr)).slice(-1)[0] ?? code; } const lqip = Number(event.currentTarget.value); // use this page's lqip to avoid breakage if I ever update the scheme preview.style.setProperty("--my-lqip", lqip); targetCode.innerHTML = targetCode.innerHTML.replace(currentValueStr, lqip); currentValueStr = String(lqip); }); } Granted, it
1年前

Inline rendering with document.currentScript
leanrada.com notes
For quick and dirty rendering of simple dynamic content, you may not need the complexity of a templating language like Handlebars or a PHP backend.Let’s use the example phrase, “Come on, it’s <currentYear>”. It should result in “Come on, it’s 2025$(new Date().getFullYear())” when rendered today.You can write this directly in HTML—without IDs, classes, or querySelectors in your JS! Thanks to the document.currentScript property, we can refer to the currently running <script> element directly and go from there.So the dynamic phrase “Come on, it’s 2025$(new Date().getFullYear())” would now be written as:Come on, it’s<script> document.currentScript.replaceWith(new Date().getFullYear())</script>The script simply replaces itself with its computed value on the spot.The code’s a bit wordy though, but we can alias it to a constant like $ via $=(...n)=>document.currentScript.replaceWith(...n). Then we’d have something reminiscent of template literals in JS.Come on, it’s <script>$(new Date().getFu
1年前

Rewriting my site in vanilla web
leanrada.com notes
I rewrote this website in vanilla HTML/CSS/JS. Here’s the story.But why?Over the years, I’ve used a bunch of libraries & frameworks to build this website, before finally making my own static site generator that I called compose-html. As the name suggests, it composes HTML files together, very much like Astro.compose-html’s READMEI like systems that have a small set of tight abstractions that are easy to understand. I’m not a fan of rigid, non-atomic concepts like “pages” and “layouts”, “themes”, “frontmatters” — I mean, these are just ‘components’ and ‘data’! I dislike those that dictate your project directory structure and coding style.If your documentation has a ‘Project structure’ section, I’m out!So I built my own simple site builder and that was nice BUT it didn’t end up making life easier. The real world is messy, and HTML more so. Simply composing pieces of HTML together isn’t that straightforward and the abstraction leaked. My compose-html framework eventually turned into a 2k
1年前

CSS sprite sheet animations
leanrada.com notes
For RSS readers: This article contains interactive content available on the original post on leanrada.com.Check out this demo first (Click it!):Yes, it’s the Twitter heart button. This heart animation was done using an old technique called sprite sheets🡵.Interactive content: Visit the website to play with interactive content!Alternative text: spinning cloverInteractive content: Visit the website to play with interactive content!Alternative text: spinning cloverInteractive content: Visit the website to play with interactive content!Alternative text: spinning cloverOn the web sprite sheets are used mainly to reduce the amount of HTTP requests by bundling multiple images together into a single image file. Displaying a sub-image involves clipping the sheet in the appropriate coordinates.Sprite sheet / texture atlas of Minecraft blocksThe bandwidth benefit has been largely mitigated by HTTP/2 now, but sprite sheets have another purpose: animations! Displaying animations is one of the prima...
1年前

Centering a div in a div in 2020
leanrada.com notes
tl;dr: use place-content: center on a grid container..container { display: grid; place-content: center;}Here’s how that looks like:That’s it. Two CSS rules.Yes, four years late according to caniuse. But this is apparently still not well-known today.Based on recent developments🡵, looks like we just need a few more years before we can finally get rid of the extra display rule so we can have the one CSS rule to center them all.
1年前

I made an app to fix my motion sickness
leanrada.com notes
Last May, Apple announced a new feature called Vehicle Motion Cues for their iOS devices. It’s an overlay that can help reduce motion sickness while riding a vehicle.I have really bad motion sickness, and riding cars, buses, and trains makes me nauseous. This feature would have been a nice relief for me, but as it stands, I use Android.Instead of buying a Malus fruit device, I took the matter into my own programmer hands. I created an alternative app for Android.To be sure, I checked the patents. Apple does have one regarding a certain motion sickness solution, but it’s specifically for head-mounted displays, not handheld devices. I figured it’s because there is prior art for handheld devices, such as KineStop for Android by Urbandroid🡵.My app is called EasyQueasy. What it does is display onscreen vestibular signals that try to help prevent motion sickness. This functions as an overlay that is displayed on top of whatever you’re doing, browsing the web or watching videos.The app is op...
2年前

Stop using ease-out in your UIs!
leanrada.com notes
For RSS readers: This article contains interactive content available on the original post on leanrada.com.Before anything, let me present you with a set of controls with no context.Press me Press me Stop using ease-out, or ease-in-out, or whatever-out, in every UI animation!There is a lot of propaganda on the internet against ease-in, saying that it's “unnatural”, “unusual”, or that it's “of the devil”. Some say that it's both “sluggish” and “abrupt”. Many pointing to ease-out as the safe, smooth, satisfying messiah of animation (including its safer kin, ease-in-out). There are even published ‘best practices’ which can be summed up to “just use ease-out”🡵. This post is here to set things straight — in a nonlinear fashion.So, why not ease-out? And what to use instead?Reason #1. It’s overusedLet’s get the weakest point out of the way. Ease out is boring because it’s everywhere. Because it’s part of the browser default ease function (which is a combination of a fast ease-in with a slow e...
2年前

Creating a halftone effect with CSS
leanrada.com notes
For RSS readers: This article contains interactive content available on the original post on leanrada.com.Here’s a quick halftone effect (i.e. a retro printed look) using CSS with only one div at the minimum.First of all, here’s a live demo:Interactive content: Visit the website to play with interactive content!Alternative text: CSS halftone demoToggle the filter class using the checkbox above.To further illustrate the halftone effect, the following demo can vary the size of the dots and the degree to which they ‘bleed’:Interactive content: Visit the website to play with interactive content!Alternative text: CSS halftone demo Bleed There are several ways to do this in CSS. The above is a bit more advanced with 2-3 extra divs. I’ll try to show a simple method, first.Halftone basicsTo keep it simple, let’s start with a black-and-white image. It should be easy to layer in additional colors with the same principle.Interactive content: Visit the website to play with interactive content!Alte
2年前

AI art is not generative art
leanrada.com notes
AI art is not generative art (clickbait title). While the technical definition says that one is a subset of the other, I think it’s useful to distinguish between these two categories.Why I’m writing this in the first place — Starting 2022, the term “generative art” had been progressively becoming synonymous with art produced by AI text-to-image systems. As a consumer and producer of (traditional) generative art, it was becoming a bit annoying to browse generative art content on the internet. Whether through tags like #generativeart or communities like r/generative, spaces are being flooded with AI-generated images which I and many others are not interested in. End rant.In 2024, things are a bit different. AI art is now commonly referred to as ‘AI art’. I shouldn’t have procrastinated writing this post for so long.There are also cases where generative artists are pressured to relabel their art, so as to not be mistaken for being AI (and avoid things associated with it). It’s an unfortun
2年前