leanrada.com notes

フィード

記事のアイキャッチ画像
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...
6日前
記事のアイキャッチ画像
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...
2ヶ月前
記事のアイキャッチ画像
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
3ヶ月前
記事のアイキャッチ画像
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
4ヶ月前
記事のアイキャッチ画像
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
4ヶ月前
記事のアイキャッチ画像
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
4ヶ月前
記事のアイキャッチ画像
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
5ヶ月前
記事のアイキャッチ画像
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
5ヶ月前
記事のアイキャッチ画像
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...
9ヶ月前
記事のアイキャッチ画像
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.
10ヶ月前
記事のアイキャッチ画像
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...
1年前
記事のアイキャッチ画像
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...
1年前
記事のアイキャッチ画像
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
1年前
記事のアイキャッチ画像
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
1年前
記事のアイキャッチ画像
htmz story
leanrada.com notes
This post is not the usual programming post. It’s been an interesting week, I guess.I just finished my mini side-project htmz🡵, a snippet / library / microframework / whatever for HTML whose main feature was that it only weighed a total of 181 bytes. It almost fits inside a single ‘tweet’ (wait, the limit is now 280 not 140?).Here is the entire framework in its final form:<iframe hidden name=htmz onload="setTimeout(()=>document.querySelector(contentWindow.location.hash||null)?.replaceWith(...contentDocument.body.childNodes))"></iframe>See the project documentation🡵 for more info on what it does.I posted it on Hacker News🡵, went to sleep because it was 3am at that point. Then the next morning it was at the top of HN!I didn’t expect this at all. But naturally I rushed to the comments section which quickly grew too numerous for me to read. They were generally positive, and acknowledged the project’s hackyness and elegance (these adjectives usually mean opposite things). It was pretty c...
1年前
記事のアイキャッチ画像
Pure CSS single-page app routing
leanrada.com notes
You’re probably a busy person, so here’s the CSS:section:not(:target) { display: none;}Demo: Open in a new tab My AppOpen in a new tabExplanationThe :target🡵 CSS selector selects the element that is targeted by the URL fragment.Combined with :not, we can hide sections that are not referenced by the URL fragment.Just as JS routers use the fragment to hide/show sections in the DOM, this “CSS router” uses the same fragment to hide/show sections in the DOM.Experiment: Default sectionNotice that the example above doesn’t start with the Home section. The content is blank initially. This is because on initial page load we don’t have a URL fragment to begin with.We need to make an exception for the Home section.Let’s start by not hiding the #home section by default. Only hide #home if there’s a specific :target section.- section:not(:target) {+ section:not(#home, :target),+ :root:has(:target) #home { display: none; }Demo v2: Open in a new tab My AppOpen in a new tabExperiment: Nested routesOn...
1年前
記事のアイキャッチ画像
getDuolingoStreak()
leanrada.com notes
How to fetch your Duolingo streak using an unconfirmed API on duolingo.com:function getDuolingoStreak(username) { const res = await fetch( `https://www.duolingo.com/2017-06-30/users?username=${username}&fields=streak,streakData%7BcurrentStreak,previousStreak%7D%7D` ); const data = await res.json(); const userData = data.users[0]; // I didn't know which of these fields matter, so I just get the max of them. const streak = Math.max( userData?.streak ?? 0, userData?.streakData?.currentStreak?.length ?? 0, userData?.streakData?.previousStreak?.length ?? 0 ); return streak;}That’s my current max streak.I can then render this data into a card like that. I put one of these cards in the /misc/ section.Let’s look at the API itself. www.duolingo.com/2017-06-30 seems to be the API prefix, which is a bit weird. What is 2017-06-30? What happened on that date?🡵 Maybe the Duolingo team used a date-based versioning🡵 at the time?In any case, big thanks to the Duolingo team for keeping this apparently...
2年前
記事のアイキャッチ画像
Writing a single-file website with Rust
leanrada.com notes
At the company I work for, there is something called a “professional education leave”, which lets me take a day off to learn something new or attend conferences or whatever professional education means. I took the opportunity to learn the Rust🡵 programming language.I didn’t want to follow tutorials, so I thought of a project to learn with, which ended up being “make a single-file website web server”. It was inspired by the article my website is one binary by j3s🡵.Rust first impressionsfn main() { println!("Hello World!");}My first impression was that it is a mature programming language that is crazy about compile-time stuff. For example, the above println! call is a macro, which expands to something more verbose… Apparently, with macros you can also make full-blown DSL🡵s that are directly embedded in Rust code.The rustc compiler’s error messages were very good.I think I haven’t really dived deep enough to discover the standout features that I keep hearing about like memory managemen...
2年前
記事のアイキャッチ画像
2023 in review
leanrada.com notes
What happened in 2023? Stuff happened.2023 website redesign 🎨Feb 2023, I redesigned my site, with a goal of making it more personal instead of just being a mere portfolio.It came with a blog section, which made me start writing a blog.Started a blog 🖊“I’m starting a blog!I’ve done some blogging in the past (for gamedev), and I already do write-ups for my projects, so I think it’d be good to officially keep a blog!I have some ideas to populate the first few posts, then we’ll see how it goes from there.Watch this space!”First blog post on the new site (now unpublished), 24 Feb 2023Well, it went well! In 2023 I had:The hottest posts were:🔥 Sort, sweep, and prune: Collision detection algorithms🔥 Dynamic patrol behaviour in stealth games with Markov chainsIt is a nice new hobby and something I shall continue. 🙌Side projects 💻This year I launched two webapps, portabl.ink and GuhitKudlit!Portablink was just a fun experiment without much utility. I don’t have analytics on it so I don’t k...
2年前
記事のアイキャッチ画像
My personalised 55% split keyboard
leanrada.com notes
For RSS readers: This article contains interactive content available on the original post on leanrada.com.I’ve been using my Lily58 split keyboard🡵 for more than a year now. It’s a compact 58-key split keyboard with column-staggered keys, 55% the size of a standard 104-key keyboard.Thanks to the open-source firmware/framework QMK🡵, I customised it and programmed it to my satisfaction. I figured now’s a good time to post about my layout so far.The keyboard in questionQuick rundownHardware: Split keyboard with 58 keys in total. A 32×128 pixel OLED screen on each half. Both halves are connected by wire, and the whole thing is wired USB-C to the computer.Software: It runs the QMK firmware, which I used to implement 7-8 layers and several custom functions. I customised the heck out of my keyboard’s firmware.The basicsInteractive content: Visit the website to play with interactive content!Alternative text: Lily58 keyboard layout diagram Left hand: 〔Esc 〕〔 ⌃[ 〕〔 ⌃] 〕〔⌃⇧Tab〕〔⌃Tab〕〔 〕 〔 ? 〕〔 ...
2年前