<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>sam le</title>
    <link>https://sam.dev/</link>
    <description>sam le is a creative coder and high school student. He writes about software engineering, web development, and side projects.</description>
    <generator>Hugo</generator>
    <language>en</language>
    <copyright>Copyright © 2025–2026 sam le. All rights reserved.</copyright>
    <lastBuildDate>Tue, 27 Oct 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://sam.dev/index.xml" rel="self" type="application/rss+xml"/>
    <item><title>A Spotify visualiser for less than $10</title><link>https://sam.dev/blog/spotify-cyd/</link><pubDate>Tue, 27 Oct 2026 00:00:00 +0000</pubDate><dc:creator>sam le</dc:creator><guid>https://sam.dev/blog/spotify-cyd/</guid><description>How I turned a need into a nightmare working with the CYD.</description><content:encoded><![CDATA[My desk used to have a problem. Between the keyboard, the monitor, and the empty space beside my mouse, something was missing. I didn't want another generic plastic gadget glowing with obnoxious RGB LEDs. I didn't want to fall into the consumerism trap: I&nbsp;wanted something that I could be able to build AND customise myself.<p>That itch is what led me down the topic of microcontrollers. What started as a casual weekend need turned into a growing interest -&nbsp; building a desktop dashboard that breathes life into my workspace, without the need to manually check myself.</p><p>Every good desk gadget needs a dual personality. When I'm locked into deep focus and the room is quiet, my workspace shouldn't really scream for attention. So the idle state is a&nbsp;digital clock which stares back at me, flanked by localised weather icons and a subtle date readout.</p><p><br></p><h2 id="fighting-the-pixel-wars" aria-labelledby="fighting-the-pixel-wars-text"><span id="fighting-the-pixel-wars-text" class="h-text">Fighting the pixel wars</span><a class="anchor" href="#fighting-the-pixel-wars" aria-label="Link to this section"></a></h2><p>Building for a 320x240 screen is equal parts liberating and agonizing. Every single pixel counts.</p><p>Early on, my album art was behaving like a stubborn puzzle piece, rendering only half of the cover or clipping awkwardly off the edges because the container size didn't match the scaled-down JPEG output. Worse yet, when long track titles kicked in, they collided straight into the progress bar, creating a cluttered mess.</p><p>Fixing it meant dialing in the math, (since I actually have no clue if there's a UI visualiser for ESP32s):</p><ul><li><p>Centering the album art precisely within a 150x150 boundary so 300x300 JPEGs decode cleanly.</p></li><li><p>Creating a dedicated vertical hierarchy (<code>TITLE_Y</code>, <code>ARTIST_Y</code>, <code>ALBUM_Y</code>) with protective spacing buffers.</p></li><li><p>Padding the bottom layout so the elapsed time numbers and the progress bar never step on each other's toes.</p></li></ul><h2 id="section" aria-labelledby="section-text"><span id="section-text" class="h-text"><br></span><a class="anchor" href="#section" aria-label="Link to this section"></a></h2><figure class="fig"><figcaption>First version of the visualiser!</figcaption></figure><p><br></p><h2 id="debugging" aria-labelledby="debugging-text"><span id="debugging-text" class="h-text">Debugging</span><a class="anchor" href="#debugging" aria-label="Link to this section"></a></h2><p>The biggest heartbreak of working with hardware isn't wiring mistakes or bad code; it's the random bugs that happen to appear in the middle of the night.</p><p>In my early versions, the display would run flawlessly for a few hours, and then completely lock up. I’d walk back to my desk to find a frozen screen, forcing me to yank the USB-C cable and replug it.</p><p>The culprit? Heap fragmentation. Every time a track changed, my code was dynamically calling <code>malloc()</code> and <code>free()</code> to handle the incoming 40KB JPEG image buffer. Over hours of continuous API polling, the ESP32's heap memory became Swiss cheese until it choked and crashed.</p><p>So I killed dynamic allocation entirely for the buffer. By pre-allocating a global <code>jpgBuffer</code> right at startup, the memory footprint stays dead-steady. The board doesn't have to scramble for breathing room anymore, making it stable enough to run indefinitely. I'm still on the lookout for many more unprecedented problems.</p><p><br></p><h2 id="aesthetics" aria-labelledby="aesthetics-text"><span id="aesthetics-text" class="h-text">Aesthetics</span><a class="anchor" href="#aesthetics" aria-label="Link to this section"></a></h2>Afterwards, the e<figure class="fig"><img src="/images/postmedia/hardware/spotify-esp32/beforecase.jpg" alt="Before 3D printing the case." loading="lazy" decoding="async"><figcaption>Before 3D printing the case.</figcaption></figure><p><br></p><br><p><br></p><h2 id="final-thoughts-why-we-build" aria-labelledby="final-thoughts-why-we-build-text"><span id="final-thoughts-why-we-build-text" class="h-text">Final Thoughts: Why We Build</span><a class="anchor" href="#final-thoughts-why-we-build" aria-label="Link to this section"></a></h2><p>At the end of the day, plugging a raw microcontroller into a USB port and watching it come alive with your own layout, your own fonts, and your own API keys is an unmatched feeling! In case you want to spin up your own desktop companion, the source code and everything else is linked in my <a href="https://github.com/itssmnle/spotifyesp32" rel="noopener noreferrer">GitHub</a>. (I update it regularly for bugs, and feel free to suggest changes).</p><p>If you've got an ESP32 CYD gathering dust in a drawer somewhere, pull it out, flash the code, and give your desk the companion it deserves.</p><p><br></p>]]></content:encoded></item>
<item><title>Raspberry Pis are interesting</title><link>https://sam.dev/blog/hardware/raspberry-pi-and-pihole/</link><pubDate>Sat, 10 Oct 2026 00:00:00 +0000</pubDate><dc:creator>sam le</dc:creator><guid>https://sam.dev/blog/hardware/raspberry-pi-and-pihole/</guid><description>Diving into Raspberry Pi OS, headless Linux administration, and setting up network-wide ad blocking.</description><content:encoded><![CDATA[<h2 id="first-steps" aria-labelledby="first-steps-text">
              <span id="first-steps-text" class="h-text">Stepping into Raspberry Pi OS</span>
              <a class="anchor" href="#first-steps" aria-label="Link to this section"></a>
            </h2>
            <p>Hardware tinkering has always been a fascination of mine, but software-wise, diving deeper into Linux and managing a headless single-board computer is a whole different kind of rewarding. Recently, I got my hands on a Raspberry Pi 3B and set out to understand the ins and outs of this magnificent hardware.</p>
            <p>Moving away from a graphical desktop environment to operating entirely through CLI taught me a lot. Breaking things and having to reflash the SD card became part of the tuition...</p>
            <figure class="fig">
              <img src="/images/postmedia/hardware/pihole-setup.jpg" alt="Downloading Pi-Hole directly on the machine">
              <figcaption>Downloading Pi-Hole directly on the machine.</figcaption>
            </figure>
            <h2 id="setting-up-pihole" aria-labelledby="setting-up-pihole-text">
              <span id="setting-up-pihole-text" class="h-text">Building a Pi-hole</span>
              <a class="anchor" href="#setting-up-pihole" aria-label="Link to this section"></a>
            </h2>
            <p>With the Pi up and running, I wanted a practical project - which was when I decided to install <strong>Pi-hole</strong>, acting as a DNS sinkhole for network-wide ad blocking.</p>
            <p>The installation process was remarkably straightforward, but configuring my router to hand out the Pi’s IP as the primary DNS server required lots of work. Firstly, I didn't know my router gateway credentials, so that took an entire week to figure out (including calling support from my ISP, which was not pleasant). So once setting up DNS to route to the Pi and binding my it to a static IP was complete, as well as configuring SSH for a headless setup, it was all good to go!</br>In all honesty, the thing shouldnt've taken so long, but figuring it out myself was the rewarding part of it, I guess.</p>
            <figure class="fig">
              <img src="/images/postmedia/hardware/pihole-hub.png" alt="Pi-hole dashboard showing blocked ads and queries">
              <figcaption>Here's the pi-hole dashboard, after 2 days of activity!</figcaption>
            </figure>]]></content:encoded></item>
<item><title>Introducing Contexto</title><link>https://sam.dev/blog/games/introducing-contexto/</link><pubDate>Fri, 11 Sep 2026 00:00:00 +0000</pubDate><dc:creator>sam le</dc:creator><guid>https://sam.dev/blog/games/introducing-contexto/</guid><description>A semantic word guessing game inspired by the enthusiasm of friends at school, built as a browser extension.</description><content:encoded><![CDATA[<h2 id="school-inspiration" aria-labelledby="school-inspiration-text">
              <span id="school-inspiration-text" class="h-text">A game everyone kept talking about</span>
              <a class="anchor" href="#school-inspiration" aria-label="Link to this section"></a>
            </h2>
            <p>Contexto first caught my attention during tutor time and in class, where my friends kept coming back to it. They really enjoyed trying to find the hidden word, and seeing how much fun a simple guessing game could create made me want to build my own version. I called it <strong>Contexto</strong>, after the game that started the conversations.</p>

            <figure class="fig">
              <img src="/images/postmedia/contexto/contexto.png" alt="Contexto word guessing game branding and interface preview" loading="eager" fetchpriority="high" decoding="async" width="440" height="280">
              <figcaption>Contexto, a small word game built around the fun of guessing together.</figcaption>
            </figure>

            <h2 id="meaning-not-spelling" aria-labelledby="meaning-not-spelling-text">
              <span id="meaning-not-spelling-text" class="h-text">Guess by meaning, not spelling</span>
              <a class="anchor" href="#meaning-not-spelling" aria-label="Link to this section"></a>
            </h2>
            <p>Instead of telling you whether a guess is spelled correctly or shares letters with the answer, Contexto ranks words by how close their meaning is to the secret word. A low rank means you are getting warmer, while a high one sends you back to explore a different idea. That makes each guess feel like a clue, and the next word is often a small leap of intuition.</p>

            <h2 id="daily-and-infinite" aria-labelledby="daily-and-infinite-text">
              <span id="daily-and-infinite-text" class="h-text">A daily puzzle, or another round</span>
              <a class="anchor" href="#daily-and-infinite" aria-label="Link to this section"></a>
            </h2>
            <p>I wanted the extension to work for a quick daily game and for those times when one round turns into several. You can play the current puzzle, choose another game, or jump into a random one. Guesses are ranked by closeness, and a history panel keeps completed games and personal stats in view. Hints and a give-up option make it easier to keep moving when a word has you stuck.</p>

            <h2 id="building-the-extension" aria-labelledby="building-the-extension-text">
              <span id="building-the-extension-text" class="h-text">Keeping the game close at hand</span>
              <a class="anchor" href="#building-the-extension" aria-label="Link to this section"></a>
            </h2>
            <p>Contexto is a Chrome extension with a compact popup and an option to open a larger view. The game uses the Contexto service to check guesses, while browser storage keeps the current game, settings, and history between visits. That combination lets the game stay lightweight without losing the progress that makes a longer guessing session satisfying.</p>]]></content:encoded></item>
<item><title>Color theory..?</title><link>https://sam.dev/blog/games/introducing-spectrum/</link><pubDate>Fri, 04 Sep 2026 00:00:00 +0000</pubDate><dc:creator>sam le</dc:creator><guid>https://sam.dev/blog/games/introducing-spectrum/</guid><description>A minimalist color memory game built as a browser extension to test chromatic recall and visual precision.</description><content:encoded><![CDATA[<h2 id="color-theory" aria-labelledby="color-theory-text">
              <span id="color-theory-text" class="h-text">A simple idea in color theory</span>
              <a class="anchor" href="#color-theory" aria-label="Link to this section"></a>
            </h2>
            <p>I've always been intrigued by color theory and how a small set of values can describe a shade with surprising precision. While scrolling through reels, I came across a similar color-memory idea and wondered how simply it could work as a game. It helped me develop <a href="https://chromewebstore.google.com/detail/spectrum/jkjlbhbmmedngjceejobaibbdkjogfgb">spectrum</a>, an extension that asks players to remember colors and recreate them from memory.</p>

            <figure class="fig">
              <img src="/images/postmedia/spectrum/home.png" alt="Spectrum extension start screen" loading="eager" fetchpriority="high" decoding="async" width="640" height="360">
              <figcaption>The start screen.</figcaption>
            </figure>

            <h2 id="gameplay-mechanics" aria-labelledby="gameplay-mechanics-text">
              <span id="gameplay-mechanics-text" class="h-text">How the mechanics work</span>
              <a class="anchor" href="#gameplay-mechanics" aria-label="Link to this section"></a>
            </h2>
            <p>The gameplay is quite straightforward. Players are presented with five random colors in sequence. Each color flashes on screen for five seconds with a countdown. Once the timer expires, players adjust sliders for hue, saturation, and lightness to recreate the shade they just saw - easily said than done though!</p>

            <figure class="fig">
              <img src="/images/postmedia/spectrum/memorise.png" alt="Memorizing phase with active countdown timer" loading="lazy" decoding="async" width="640" height="360">
              <figcaption>Memorizing phase with the running ms countdown.</figcaption>
            </figure>

            <h2 id="scoring-and-precision" aria-labelledby="scoring-and-precision-text">
              <span id="scoring-and-precision-text" class="h-text">Scoring chromatic accuracy</span>
              <a class="anchor" href="#scoring-and-precision" aria-label="Link to this section"></a>
            </h2>
            <p>Scoring is calculated using an algorithm based on HSL color space geometry. Hue is weighted most heavily since our brains perceive angular color shifts drastically, while saturation and lightness are graded using smooth power curves. Scores range from zero to ten per round, leading to a maximum possible score of fifty points, in which you get a message depending on your accuracy. (Out of all the people I know, not many score over 45).</p>


            <figure class="fig">
              <img src="/images/postmedia/spectrum/narrow-eyes.gif" alt="Squinting" loading="lazy" decoding="async" width="400" height="360">
              <figcaption>The squinting is always not on purpose - it always makes me laugh seeing others do.</figcaption>
            </figure>

            <h2 id="tech-stack" aria-labelledby="tech-stack-text">
              <span id="tech-stack-text" class="h-text">Under the hood with Plasmo and React</span>
              <a class="anchor" href="#tech-stack" aria-label="Link to this section"></a>
            </h2>
            <p>It's built using modern web tooling, using React, TypeScript, and Tailwind CSS. Persistence is handled via Chrome storage with automatic session recovery.</p>]]></content:encoded></item>
<item><title>KojoNote, formerly StudyForge</title><link>https://sam.dev/blog/software/introducing-kojonote/</link><pubDate>Sat, 15 Aug 2026 00:00:00 +0000</pubDate><dc:creator>sam le</dc:creator><guid>https://sam.dev/blog/software/introducing-kojonote/</guid><description>A unified revision platform designed to make learning more accessible, engaging, and effective for students.</description><content:encoded><![CDATA[<h2 id=one-place aria-labelledby=one-place-text>
            <span id=one-place-text class=h-text>A single place for revision</span>
            <a class=anchor href=#one-place aria-label="Link to this section"></a>
          </h2>
          <p><strong>KojoNote</strong> (which originally started as <a href="/blog/software/introducing-studyforge/">StudyForge</a>) is a platform designed to make revision more accessible, engaging, and effective for KS3 students. It is particularly beneficial for students who require additional support in subjects such as Science, while encouraging independent learning beyond the classroom. The core aim is to provide students with a single platform that brings together everything they need for effective revision—including a dashboard, revision notes, student notes, flashcards, and interactive practice modules.</p>
          <figure class="fig">
            <img src="/images/postmedia/kojonote/kojonote-banner.png" alt="Our current brand identity!">
            <figcaption>Our current brand identity!</figcaption>
          </figure>
          <h2 id=curriculum aria-labelledby=curriculum-text>
            <span id=curriculum-text class=h-text>Grounded in the KS3 curriculum</span>
            <a class=anchor href=#curriculum aria-label="Link to this section"></a>
          </h2>
          <p>To ensure the content aligns closely with the school's curriculum, the KS3 curriculum has been integrated directly into the system. Mathematics as well as the three core Sciences (Biology, Chemistry, and Physics) have already been implemented, with content specifically tailored to our curriculum requirements. As the platform develops, there is significant potential to extend its scope to support iGCSE and, eventually, A-Level students.</p>

          <h2 id=one-learning-space aria-labelledby=one-learning-space-text>
            <span id=one-learning-space-text class=h-text>Bringing learning together</span>
            <a class=anchor href=#one-learning-space aria-label="Link to this section"></a>
          </h2>
          <p>Feedback on the concept from staff members has been overwhelmingly positive. One of the platform's key strengths is its ability to combine teacher-assigned work and independent revision within a single environment. Rather than students relying on multiple systems for homework, notes, and revision materials, KojoNote aims to provide a unified experience that simplifies both learning and organisation.</p>

          <p>The platform draws inspiration from services such as <a href="https://www.khanacademy.org" target="_blank" rel="noopener noreferrer">Khan Academy</a>, <a href="https://www.savemyexams.com" target="_blank" rel="noopener noreferrer">Save My Exams</a>, and <a href="https://www.carousel-learning.com" target="_blank" rel="noopener noreferrer">Carousel Learning</a>, while seeking to offer a more personalised experience. Although external resources such as <a href="https://www.physicsandmathstutor.com" target="_blank" rel="noopener noreferrer">PMT</a> and <a href="https://www.sparxmaths.uk" target="_blank" rel="noopener noreferrer">Sparx</a> continue to play an important role, KojoNote acts as a central hub that directs students to the most relevant materials and resources. Furthermore, content can be developed collaboratively by both students and teachers, allowing the platform to become increasingly tailored to the needs of the community over time.</p>

          <h2 id=building-together aria-labelledby=building-together-text>
            <span id=building-together-text class=h-text>Building together</span>
            <a class=anchor href=#building-together aria-label="Link to this section"></a>
          </h2>
          <p>Building this isn't a solo journey—I'm lucky enough to have my wonderful friend who is helping me with illustrations, and I hope to build and expand an ambitious team around the project as it grows!</p>

          <h2 id=lasting-notes aria-labelledby=lasting-notes-text>
            <span id=lasting-notes-text class=h-text>Keeping notes accessible</span>
            <a class=anchor href=#lasting-notes aria-label="Link to this section"></a>
          </h2>
          <p>Another major challenge in school environments relates to Microsoft Teams and Class Notebooks—when student Teams are archived or deleted at the end of an academic year, notes stored within the associated OneNote Class Notebooks can become difficult or impossible to access. KojoNote addresses this challenge through a permanent 'notes' system (currently a work-in-progress), enabling students to retain access to their notes throughout their time at school regardless of changes to Teams, classes, or year groups. I hope to get a tenant to simplify login via Azure AD integration!</p>

          <h2 id=join-kojonote aria-labelledby=join-kojonote-text>
            <span id=join-kojonote-text class=h-text>Get involved</span>
            <a class=anchor href=#join-kojonote aria-label="Link to this section"></a>
          </h2>
          <p>If anyone is interested in collaborating or coming on board, feel free to <a href="https://kojonote.web.app/join" target="_blank" rel="noopener noreferrer">join us here</a>!</p>]]></content:encoded></item>
<item><title>Why I Built CompassBite</title><link>https://sam.dev/blog/software/why-i-built-compassbite/</link><pubDate>Fri, 24 Jul 2026 00:00:00 +0000</pubDate><dc:creator>sam le</dc:creator><guid>https://sam.dev/blog/software/why-i-built-compassbite/</guid><description>A food and beverage specialized compass to find your nearest restaurant, coffee shop, or bar using OpenStreetMap.</description><content:encoded><![CDATA[<p>Every weekend, my family goes out for breakfast, and every weekend the same problem shows up before we even leave the house. My parents struggle to find a local restaurant that suits everyone, so we open review apps, scroll through noisy listings, see the same sponsored places, and often end up going somewhere we already know. Being the kid in the family, and I got tired of watching that weekly debate, so I decided to build an app called <strong>CompassBite</strong>.</p>
            <figure class="fig">
              <img src="/images/postmedia/compassbite/clueless-spongebob.gif" alt="The general frustration of looking for a place to eat. Seriously couldn't we settle for cereal?" loading="eager" fetchpriority="high" decoding="async" width="640" height="360">
              <figcaption>The tiring chore of looking around for a restaurant... seriously?</figcaption>
            </figure>
            <h2 id=the-idea aria-labelledby=the-idea-text>
              <span id=the-idea-text class=h-text>The idea</span>
              <a class=anchor href=#the-idea aria-label="Link to this section">
              </a>
            </h2>
            <p>The idea is fairly simple. CompassBite finds your current location, looks for nearby food and drink places <a href="https://www.openstreetmap.org/" target="_blank" rel="noopener noreferrer">using <strong>OpenStreetMap</strong></a>, and shows restaurants, cafes, and bars on a map. I currently have a basic version running <a href="https://expo.dev" target="_blank" rel="noopener noreferrer">through a sandbox</a>. Currently data caching, filters for opening hours and location type is available. The app is still in development though.</p>
            <p>Building it has been harder than I expected. I thought getting the location and showing nearby restaurants would be the easy part, but I had to learn way too much just for this one project, one of which was understanding how to work with Overpass queries. <code>MapLibre</code> brought its own problems too, especially frequent rendering bugs I <i><strong>kept</strong></i> on encountering. You cannot believe the amount of times I threw my phone over the couch in frustration.</p>
            <p>General programming forums did not help much because my problems were very specific to OSM, so I found help elsewhere:</p>
            <ul>
              <li>The <a href="https://community.openstreetmap.org" target="_blank" rel="noopener noreferrer">OpenStreetMap Forum</a> became my primary place to refer to about all things OpenStreetMap.</li>
              <li>A thread on <a href="https://forum.openstreetmap.fr" target="_blank" rel="noopener noreferrer">forum.openstreetmap.fr</a> helped me understand server timeouts and alternative Overpass instances.</li>
              <li><a href="https://gis.stackexchange.com" target="_blank" rel="noopener noreferrer">GIS Stack Exchange</a> helped with geospatial logic.</li>
              <li>For <code>MapLibre</code> issues, fortunately <a href="https://slack.openstreetmap.us" target="_blank" rel="noopener noreferrer">OSM's US Slack</a> helped with it.</li>
            </ul>
            <p>I <i>*also*</i> got a lot of help from my lovely buddy Codex. Whenever I was stuck on a build error or a bug, it helped me fix the problems, but eventually stumped at the nicher problems that required looking into the forums manually. They taught me how OSM worked, and Codex helped me build the app that talks to it.</p>
            <figure class="fig">
              <img src="/images/postmedia/compassbite/claude-limit.gif" alt="Typing out every line manually when my Codex limits run out:" loading="eager" fetchpriority="high" decoding="async" width="640" height="360">
              <figcaption>This but Codex. (you get the gist)</figcaption>
            </figure>
            <p>CompassBite is still a work in progress - which I hope one day my parents can use it to find breakfast without the weekly struggle! But when I open my local build on a weekend, it can show me a local cafe nearby that we never noticed, and that makes the hard parts so worth it. It was a great learning opportunity that also gave me a deeper understanding of how geospatial data is processed and used to power location-based features.</br></br>I also look forward to sharing more updates as the app develops, and hopefully open source the software for all Android users! (or publish it to the Play Store).</br></br></br>A little bit more on OSM, if you'd like to read more: </p>
            <h2 id=osm-overview aria-labelledby=osm-overview-text>
              <span id=osm-overview-text class=h-text>A general overview of OSM</span>
              <a class=anchor href=#osm-overview aria-label="Link to this section">
              </a>
            </h2>
            <p>OpenStreetMap stores "the planet" (the full global dataset) in a PostgreSQL relational database. It relies heavily on <code><a href="https://postgis.net" target="_blank" rel="noopener noreferrer">PostGIS</a></code>, an extension that adds support for geographic objects, allowing SQL queries to calculate distances, map areas, and run spatial coordinates. Initially I thought it (OSM) would just fetch from Google's existing infrastructure, but oh boy was I wrong.</p>
            <p> To get data smoothly in and out of the database, the backend uses tools like <code>osm2pgsql</code> to convert standard map data into <code>PostGIS</code> format, and Osmium, a fast C++ <i>"Swiss Army knife"</i> framework for processing raw mapping logs and tracking file updates.</p>
            <p>To turn raw coordinates into a visual map, OSM uses a rendering toolkit called <code><a href="https://mapnik.org" target="_blank" rel="noopener noreferrer">Mapnik</a></code>. It converts geographic data points into images, which tile servers then chop into small square pieces so web browsers and apps can load local areas instantly.</p>
            <p>Map searches are powered by <code><a href="https://nominatim.org" target="_blank" rel="noopener noreferrer">Nominatim</a></code>, which translates text addresses into map coordinates. For developers looking to extract specific information from the map, the <code><a href="https://openstreetmap.org" target="_blank" rel="noopener noreferrer">Overpass API</a></code> acts as a fast data filtering engine that pulls custom location datasets without taxing the main database.</p>]]></content:encoded></item>
<item><title>My Football Mount Rushmore</title><link>https://sam.dev/blog/misc/football-mount-rushmore/</link><pubDate>Mon, 22 Dec 2025 00:00:00 +0000</pubDate><dc:creator>sam le</dc:creator><guid>https://sam.dev/blog/misc/football-mount-rushmore/</guid><description>Five players who&#39;ve given me the most joy, frustration, and sheer disbelief over the years as a Barcelona fan.</description><content:encoded><![CDATA[<p>Every football fan has that list: the one you defend in group chats, the one that starts friendly debates at the pub, the one that makes your friends roll their eyes because you’ve brought it up <em>again</em>. Well, here’s mine: five players who’ve given me the most joy, frustration, and most importantly - sheer disbelief over the years. And yes, I’m a Barcelona fan, so you already know some of these picks come with a little extra bias.</p>

            <h2 id="lionel-messi" aria-labelledby="lionel-messi-text"><span id="lionel-messi-text" class="h-text">Lionel Messi</span><a class="anchor" href="#lionel-messi" aria-label="Link to this section"></a></h2>
            <p>Where do you even start? There’s nothing left to say about Messi that hasn’t been said, and yet somehow every time he touches the ball it feels like a new sentence. What I love most isn’t the goals (though, yes, <em>those</em> goals). It’s the way he sees the game three seconds before anyone else: the pass nobody else spotted; the dribble that shouldn’t work against three defenders and yet does. Watching Messi is like watching someone play chess while everyone else plays checkers, and I feel lucky to have watched it live rather than just in highlight reels. As an <a href="https://www.fcbarcelona.com" rel="noopener noreferrer">FC Barcelona</a> fan, I’ll never stop being grateful that the best years of his career, including the 2008-12 years under Pep that made Barca undefeatable.</p>

            <h2 id="robert-lewandowski" aria-labelledby="robert-lewandowski-text"><span id="robert-lewandowski-text" class="h-text">Robert Lewandowski</span><a class="anchor" href="#robert-lewandowski" aria-label="Link to this section"></a></h2>
            <p>If Messi is poetry, Lewandowski is engineering, and I mean that as the highest compliment. There’s something deeply satisfying about a number nine who does exactly what he’s supposed to do, only better than anyone. The positioning, the movement, the finishing with either foot, the headers. Five goals in nine minutes is the kind of thing you tell your grandkids about. He’s proof that relentless consistency and hard work can be just as awe-inspiring as raw genius. And seeing him bring that ruthlessness to Barca after everything he achieved in Germany has been a proper treat: we finally got the number 9 we’d been missing.</p>

            <figure class="fig"><img src="/images/postmedia/sports/lewandowski-barca.jpg" alt="Lewandowski at Barcelona" loading="lazy" decoding="async"><figcaption>Still carrying the team as a veteran - respect.</figcaption></figure>
            <h2 id="antoine-griezmann" aria-labelledby="antoine-griezmann-text"><span id="antoine-griezmann-text" class="h-text">
              Antoine Griezmann</span><a class="anchor" href="#antoine-griezmann" aria-label="Link to this section"></a></h2>
            <p>Griezmann might be the most <em>intelligent</em> player on this list. He’s never been the fastest or the flashiest, but watch him off the ball and you’ll see a player who reads the game like a novel he’s already finished. He drops deep, links play, defends when he has to, and pops up with big goals when it matters most. The 2018 World Cup was his masterpiece. He’s the kind of player who makes everyone around him better, and honestly, every team needs a Griezmann.</p><p><br></p><figure class="fig"><img src="/images/postmedia/sports/griezmann-barca.jpg" alt="Griezmann at Barcelona" loading="lazy" decoding="async"><figcaption>A generational striker, just not his best at Barca.</figcaption></figure><p><br></p>

            <h2 id="david-luiz-chaos-but-make-it-art" aria-labelledby="david-luiz-chaos-but-make-it-art-text"><span id="david-luiz-chaos-but-make-it-art-text" class="h-text">David Luiz: Chaos, But Make It Art</span><a class="anchor" href="#david-luiz-chaos-but-make-it-art" aria-label="Link to this section"></a></h2>
            <p>Okay, hear me out: David Luiz is not on this list because he’s the most reliable defender in history. He’s here because football is <em>entertainment</em>, and nobody understood that better than David Luiz. The marauding runs from centre-back, the 60-yard diagonals, the free-kicks, the tackles: either genius or disaster, with absolutely no middle ground. He played every match like it was a cup final and he’d bet his house on it. And let’s not forget: he was genuinely brilliant in that 2012 Champions League run and the 2014 World Cup until… well, <em>you know</em>. Football needs characters, and David Luiz is a <em>character</em>.</p>

            <h2 id="neymar-the-showman" aria-labelledby="neymar-the-showman-text"><span id="neymar-the-showman-text" class="h-text">Neymar: The Showman</span><a class="anchor" href="#neymar-the-showman" aria-label="Link to this section"></a></h2>
            <p>Neymar is the player who makes you gasp and groan within the same ten seconds, sometimes with the same touch. The pure audacity of his game (the rainbow flicks, the elasticos, the dribbles that seem designed to humiliate) is exactly why I watch football. Yes, he goes down easily; yes, the drama is exhausting. But when he’s on song, he’s unplayable, and the Santos and Barcelona versions of Neymar were some of the most electric football I’ve ever seen. That MSN front three is still the most fun I’ve ever had watching football - the best version of football I think anyone can achieve - being selfless. He played like the game was supposed to be <em>fun</em>, and to be honest, more players should.</p>

            <h2 id="the-common-thread" aria-labelledby="the-common-thread-text"><span id="the-common-thread-text" class="h-text">The common thread</span><a class="anchor" href="#the-common-thread" aria-label="Link to this section"></a></h2>
            <p>Looking at this list, I notice something: none of these players are <strong>boring</strong>. Five completely different ways to be brilliant at football, and that’s exactly why I love this sport. There’s no single right way to play it.</p>
            <p>And yes, being a Barca fan definitely shaped this list. Three of these five gave me some of my best memories in blue and red, and the other two are exactly the kind of players you’d want in a Barça shirt anyway.</p>
            <p>Who’s on your list?</p>]]></content:encoded></item>
<item><title>StudyForge: a new initiative I&#39;m planning on</title><link>https://sam.dev/blog/software/introducing-studyforge/</link><pubDate>Fri, 14 Nov 2025 00:00:00 +0000</pubDate><dc:creator>sam le</dc:creator><guid>https://sam.dev/blog/software/introducing-studyforge/</guid><description>A lightweight, minimal web tool focused on scope, speed, and getting core revision materials done.</description><content:encoded><![CDATA[<h2 id="minimal-focus" aria-labelledby="minimal-focus-text">
              <span id="minimal-focus-text" class="h-text">A minimal approach to revision</span>
              <a class="anchor" href="#minimal-focus" aria-label="Link to this section"></a>
            </h2>
            <p><strong>StudyForge</strong> is a fresh side project I’ve been hacking away at recently, in order to solve an abundant need in the academic space! The philosophy behind it is simple: to get core revision tasks done as quickly as possible.</p>

            <h2 id="the-scope" aria-labelledby="the-scope-text">
              <span id="the-scope-text" class="h-text">Focusing on the scope</span>
              <a class="anchor" href="#the-scope" aria-label="Link to this section"></a>
            </h2>
            <p>Too many study tools suffer from feature creep before they even solve the primary problem. With StudyForge, I wanted to strip away complex dashboards and unnecessary settings to focus purely on creating a reliable place for essential notes and quick flashcard practice, similar to <a href="https://www.savemyexams.co.uk/">SaveMyExams</a>.</p>
            <p>The interface is purposely bare-bones for now, while I try to define the scope of the project.</p>
            <figure class="fig">
              <img src="/images/postmedia/kojonote/studyforge-logo.png" alt="StudyForge's logo" width="600" height="200">
              <figcaption class="figure-caption">Our logo - I thought it was simple enough without overthinking it.</br>(NOTE - I forgot to take pictures of the old UI. whoops!)</figcaption>
            </figure>
            <h2 id="what-comes-next" aria-labelledby="what-comes-next-text">
              <span id="what-comes-next-text" class="h-text">Building the foundation</span>
              <a class="anchor" href="#what-comes-next" aria-label="Link to this section"></a>
            </h2>
            <p>Since this is a brand new project, the primary goal right now is testing out core UX patterns for organizing revision materials. I’m experimenting with simple ways to render markdown notes and build fast, responsive quiz modules. Built on <i>Vite+React</i> though, so I have a lot of flexibility in how I approach the development.</p>
            <p>It’s an early experiment, but getting the core functionality right first will give a solid foundation to iterate on as new ideas take shape down the line!</p>]]></content:encoded></item>
<item><title>Welcome To My Blog</title><link>https://sam.dev/blog/welcome-to-my-blog/</link><pubDate>Wed, 12 Nov 2025 00:00:00 +0000</pubDate><dc:creator>sam le</dc:creator><guid>https://sam.dev/blog/welcome-to-my-blog/</guid><description>A personal corner of the web for thoughts, projects, and creative endeavors.</description><content:encoded><![CDATA[<p>Welcome to my blog! I built this space to document what I am working on, share thoughts on software, and reflect on projects along the way.</p>
            <p>I’ve always loved building things: from tinkering with early code projects to reverse engineering things and (hopefully), creating something useful for others. Having my own website feels like having a proper digital workbench where I can publish write-ups, tutorial notes, and side-project deep dives without relying on algorithms or big social platforms.</p>
            <p>Expect posts about web development, embedded systems, open-source tools, and occasional experiments. Thanks for stopping by!</p>]]></content:encoded></item>
  </channel>
</rss>
