<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Home on Sean Wong</title><link>https://seanwong.xyz/</link><description>Recent content in Home on Sean Wong</description><generator>Hugo</generator><language>en</language><lastBuildDate>Sun, 28 Jun 2026 09:00:00 -0700</lastBuildDate><atom:link href="https://seanwong.xyz/index.xml" rel="self" type="application/rss+xml"/><item><title>ResearchBridge</title><link>https://seanwong.xyz/projects/research-bridge/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://seanwong.xyz/projects/research-bridge/</guid><description>&lt;p&gt;ResearchBridge matches pre-med students to research, volunteer, and clinical
opportunities by meaning rather than keywords. It embeds a student&amp;rsquo;s background
and each opportunity, retrieves the closest matches, and writes a short
explanation of why each one fits. When nothing is a good match, it says so
instead of forcing a weak one.&lt;/p&gt;
&lt;p&gt;The stack is a FastAPI backend with PostgreSQL and pgvector for vector search,
embeddings for the matching, and an ingestion pipeline that pulls and normalizes
opportunity listings. Most of the real work turned out to be in that data layer,
where sources are messy and extractions fail quietly.&lt;/p&gt;</description></item><item><title>StrideBound</title><link>https://seanwong.xyz/projects/stridebound/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://seanwong.xyz/projects/stridebound/</guid><description>&lt;p&gt;StrideBound turns GPS-tracked runs into a creature-collection RPG. Before a run, users choose a loadout of up to three items from their backpack. After the run, the app awards points and pet XP, then rolls a monster encounter influenced by distance traveled and which items were equipped - a loop designed to make item choice matter and give the next run a reason to happen.&lt;/p&gt;
&lt;p&gt;The core tracking challenge was GPS noise. Mobile location updates arrive continuously, including low-accuracy readings that can accumulate as phantom distance when the user is standing still. Two things gate each location update before it touches the distance calculation: an accuracy threshold that drops noisy readings, and a CoreMotion pedometer check that skips processing entirely when steps haven&amp;rsquo;t advanced. The previous coordinate also resets on pause/resume, so the app never draws a straight-line jump between pre-pause and post-resume positions as counted distance. CoreLocation updates flow through an &lt;code&gt;AsyncStream&amp;lt;CLLocation&amp;gt;&lt;/code&gt; wrapper in &lt;code&gt;LocationService&lt;/code&gt;, which lets the session coordinator handle them with Swift concurrency rather than delegate callbacks scattered across the codebase.&lt;/p&gt;</description></item><item><title>CupQuest</title><link>https://seanwong.xyz/projects/cupquest/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://seanwong.xyz/projects/cupquest/</guid><description>&lt;p&gt;Built the social and messaging layer for a coffee shop discovery app - friend requests, pending states, mutual acceptance, and an activity feed that surfaces friend reviews first on any shop page. The underlying app lets users find nearby cafes via Yelp and Google Maps data and post drink or venue reviews. My ownership covered those social features end to end, on a four-person team.&lt;/p&gt;
&lt;p&gt;The friendship schema required more thought than a simple friends array. An early version could add the same user multiple times and had duplicated add/remove logic. The deeper issue was the race condition: two users sending each other friend requests simultaneously would create two separate pending documents with no way to resolve them to a mutual state. The fix was modeling friendships as directional relationship documents with a status field, then querying both directions before returning a user&amp;rsquo;s current friend status - so the system always resolves to one canonical state regardless of request order.&lt;/p&gt;</description></item><item><title>The Glucose Diaries</title><link>https://seanwong.xyz/projects/glucose-diaries/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://seanwong.xyz/projects/glucose-diaries/</guid><description>&lt;p&gt;A static, client-side visualizer for 9,515 continuous glucose readings I collected over six weeks wearing a Dexcom Stelo. It&amp;rsquo;s a React and TypeScript single-page app served from GitLab Pages — no backend, no database. The dataset ships as a static asset and every interaction (zoom, pan, annotation lookup) happens in the browser, which keeps the site fast and free to host.&lt;/p&gt;
&lt;p&gt;The detail I cared most about was shareable deep links. The chart encodes its entire view state in the URL as query parameters — &lt;code&gt;start&lt;/code&gt; and &lt;code&gt;end&lt;/code&gt; as epoch-millisecond bounds, plus an optional &lt;code&gt;note&lt;/code&gt; string for the annotation label. That means any specific moment in the data can be linked to directly: the &lt;a href="https://sean_wong.gitlab.io/glucose-diaries/?start=1780876800000&amp;amp;end=1780885800000&amp;amp;note=Basketball&amp;#43;%E2%80%94&amp;#43;high-intensity&amp;#43;burst&amp;#43;raises&amp;#43;glucose"&gt;basketball adrenaline spike&lt;/a&gt;, a &lt;a href="https://sean_wong.gitlab.io/glucose-diaries/?start=1778482800000&amp;amp;end=1778828399000&amp;amp;note=Fasting&amp;#43;%E2%80%94&amp;#43;low&amp;#43;glucose&amp;#43;%26&amp;#43;reduced&amp;#43;variability"&gt;fasting stretch&lt;/a&gt;, an &lt;a href="https://sean_wong.gitlab.io/glucose-diaries/?start=1778907600000&amp;amp;end=1778940000000&amp;amp;note=Overnight&amp;#43;stability&amp;#43;%E2%80%94&amp;#43;fasting&amp;#43;baseline"&gt;overnight baseline window&lt;/a&gt;. The accompanying write-up uses this heavily — each section drops the reader into the exact time window it&amp;rsquo;s describing, so the prose and the chart stay in sync without screenshots that go stale.&lt;/p&gt;</description></item><item><title>Questions &amp; Answers Microservices API</title><link>https://seanwong.xyz/projects/questions-and-answers-microservices-api/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://seanwong.xyz/projects/questions-and-answers-microservices-api/</guid><description>&lt;p&gt;Built a Questions and Answers microservice for an e-commerce product page, designed to hold up under realistic catalog traffic. The service handles question listing, answer listing with nested photo URLs, posting, helpful-vote tracking, and content reporting through a RESTful Express API backed by PostgreSQL.&lt;/p&gt;
&lt;p&gt;The initial &lt;code&gt;/qa/questions&lt;/code&gt; route worked against small datasets but was returning ~1,888 ms on the full production-sized data. Before adding any caching, &lt;code&gt;EXPLAIN ANALYZE&lt;/code&gt; pointed at the hot query paths, and adding indexes on those columns brought response time down to ~211 ms - roughly a 9x improvement without touching any application code. That step alone mattered more than anything that came after. Layering Redis on top using a cache-aside strategy pushed the service through a 10,000-client Loader.io test (one minute) with a mean response time of ~62 ms and a 100% success rate. The sequencing is worth noting: Redis was compressing an already-fast query, not rescuing a slow one - a meaningfully different situation for reasoning about where the ceiling actually is.&lt;/p&gt;</description></item><item><title>Compilation Video Generator</title><link>https://seanwong.xyz/projects/compilation-video-generator/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://seanwong.xyz/projects/compilation-video-generator/</guid><description>&lt;p&gt;Python CLI that automates the full pipeline from Twitch clip discovery to a finished YouTube-ready compilation video. Selenium and BeautifulSoup handle the scraping layer — navigating Twitch&amp;rsquo;s clip pages and extracting clip URLs for a chosen game category. yt-dlp downloads the source media. MoviePy assembles the clips into a final output with configurable intro and outro segments, creator tag overlays, and export settings tuned for YouTube upload.&lt;/p&gt;
&lt;p&gt;The pipeline architecture maps cleanly to a general ETL pattern: discover, acquire, transform, export. Each stage is loosely coupled enough that the scraping layer could be swapped for a different source without touching the assembly logic, and the assembly logic could produce different output formats without touching acquisition. The creator attribution overlay falls out of this design as a transform-stage default rather than an option — compilation videos frequently strip the original clip source, and baking attribution into the assembly step makes crediting streamers the path of least resistance instead of an extra step.&lt;/p&gt;</description></item><item><title>Grid Epidemiology Demo</title><link>https://seanwong.xyz/projects/grid-epidemiology-demo/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://seanwong.xyz/projects/grid-epidemiology-demo/</guid><description>&lt;p&gt;Built for a computational modeling course in evolution and ecology, this browser-based simulation models local disease transmission on a 2D grid. One patient zero infects susceptible neighbors in a four-neighbor spread model, wave by wave, while the grid visualization and a time-series chart update in real time. Susceptible, infected, and vaccinated states are tracked per cell; vaccination is pre-assigned randomly before the outbreak begins.&lt;/p&gt;
&lt;p&gt;The controls — grid size, vaccination rate, frame delay, pause, restart, and single-step advance — are designed for experimentation rather than passive observation. The single-step mode is particularly useful for tracing exactly how a specific outbreak propagates through a sparsely or densely vaccinated grid.&lt;/p&gt;</description></item><item><title>DVD Screen Saver</title><link>https://seanwong.xyz/projects/dvd-screen-saver/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://seanwong.xyz/projects/dvd-screen-saver/</guid><description>&lt;p&gt;Canvas-based recreation of the classic bouncing DVD logo animation, extended with aim-and-launch controls, configurable trail effects, and real-time parameter tuning via dat.GUI. The animation runs on an HTML5 canvas element with frame-by-frame collision detection against the viewport edges; each wall bounce randomizes the logo color.&lt;/p&gt;
&lt;p&gt;The animation runs on a &lt;code&gt;requestAnimationFrame&lt;/code&gt; loop, which ties motion to the browser&amp;rsquo;s actual frame timing rather than a fixed &lt;code&gt;setInterval&lt;/code&gt;. The practical consequence is that velocity is applied as a per-frame delta against the elapsed time, so the logo moves at the same real-world speed whether the display refreshes at 60Hz or 144Hz instead of running faster on a faster monitor. The trail effect comes from this same loop: rather than clearing the canvas each frame, it paints a low-opacity fill over the previous frame, so older positions fade out gradually instead of vanishing — and the trail-persistence slider is just the opacity of that fill.&lt;/p&gt;</description></item><item><title>Atelier E-Commerce Front-End</title><link>https://seanwong.xyz/projects/atelier-ecommerce-front-end/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://seanwong.xyz/projects/atelier-ecommerce-front-end/</guid><description>&lt;p&gt;Full product detail page for a retail storefront, built as a team of four. My ownership area was Related Products and Outfit List: a carousel of product cards with ratings, sale-price handling, and a comparison modal, plus an outfit list that stores user-saved items across page sessions. The rest of the page - style selection, image gallery, Q&amp;amp;A, and reviews - belonged to my teammates.&lt;/p&gt;
&lt;p&gt;The trickiest part was async rendering order. Related Product cards depended on several data sources that arrived at different times: the related product ID list, per-product metadata, style images, and review metadata. Cards were rendering before the IDs were available, showing stale or empty states. The fix involved restructuring the request flow with &lt;code&gt;async&lt;/code&gt;/&lt;code&gt;await&lt;/code&gt;, consolidating the scattered fetch helpers into a single &lt;code&gt;getHandler&lt;/code&gt; utility, and using &lt;code&gt;useEffect&lt;/code&gt; dependency arrays to re-fetch card data whenever upstream IDs changed. Once that sequencing was right, the cards stayed consistent.&lt;/p&gt;</description></item><item><title>Flyclicker</title><link>https://seanwong.xyz/projects/flyclicker/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://seanwong.xyz/projects/flyclicker/</guid><description>&lt;p&gt;Flyclicker was built to solve a specific problem in a neuroscience lab at the Kavli Institute at UC San Diego. A locomotion-triggered heat stress protocol required an operator to watch a screen and manually interact with the apparatus each time a Drosophila fly moved. Flyclicker replaced that loop — a Python process watches a user-defined screen region, detects pixel-level change against a configurable threshold, and fires the configured click or keystroke automatically.&lt;/p&gt;</description></item><item><title>Plate Math</title><link>https://seanwong.xyz/projects/plate-math/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://seanwong.xyz/projects/plate-math/</guid><description>&lt;p&gt;Plate Math solves the everyday friction of loading a barbell: you have a target weight, you need to know which plates go on each side. The calculator handles both imperial and metric, accounts for the bar weight, and confirms the full loaded total. It also enforces sleeve capacity — if a combination is physically impossible to fit on the bar, it won&amp;rsquo;t suggest it.&lt;/p&gt;
&lt;p&gt;The core of the calculator is a constrained variant of the coin-change problem: find the plate combination that hits a per-side target. The textbook greedy approach — always take the largest plate that fits — works for a complete plate set but breaks against a real gym inventory, where you might have four 45s and no 25s. The solver works from a configurable inventory of available plates per side rather than assuming infinite supply, and falls back to the closest achievable weight when an exact match isn&amp;rsquo;t possible, since &amp;ldquo;you can&amp;rsquo;t make exactly 137.5 here&amp;rdquo; is a more useful answer than no answer.&lt;/p&gt;</description></item><item><title>Cancer Genomics</title><link>https://seanwong.xyz/projects/cancer-genomics/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://seanwong.xyz/projects/cancer-genomics/</guid><description>&lt;p&gt;Built for a bioinformatics course, this interactive atlas maps mutation frequency across major cancer types on top of the NCI Genomic Data Commons API. It queries live GDC data in the browser for five key oncogenes and tumor suppressors — TP53, KRAS, EGFR, PIK3CA, and PTEN — across TCGA cohorts, so the data reflects the actual GDC dataset rather than a static snapshot.&lt;/p&gt;
&lt;p&gt;Two comparison modes address a real limitation of raw mutation counts: large cohorts will always show more mutations just by having more patients. Toggling to the normalized rate view divides mutated cases by cohort size, letting you compare TP53 prevalence in a 500-patient cohort against a 50-patient cohort on equal footing. A pinned second-gene comparison adds another layer — you can hold a cohort fixed and see how two genes&amp;rsquo; mutation patterns differ within the same project.&lt;/p&gt;</description></item><item><title>Titanic Survival Case Study</title><link>https://seanwong.xyz/projects/titanic-survival-case-study/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://seanwong.xyz/projects/titanic-survival-case-study/</guid><description>&lt;p&gt;Starting from the historical claim &amp;ldquo;women and children first,&amp;rdquo; this project cleans the Titanic passenger manifest, computes survival rates by sex, class, and age group, and runs chi-square tests against a null model to determine whether the observed outcomes deviate from chance — or whether the cultural narrative holds up statistically.&lt;/p&gt;
&lt;p&gt;The most deliberate design decision was making the analysis interactive: adjusting the child-age threshold (is an 18-year-old a child?) or filtering by passenger class immediately reruns the survival rates, group composition, expected death counts, and p-values in the browser. All of that — CSV parsing, row normalization, chi-square computation, and chart rendering — runs client-side with no server dependency. Porting the chi-square test out of a notebook and into vanilla JavaScript meant reimplementing the expected-frequency and test-statistic math by hand rather than calling into a stats library, which forced a clearer understanding of what the test actually computes.&lt;/p&gt;</description></item><item><title>A Lossless Music Library That Lives on My Own Server</title><link>https://seanwong.xyz/notes/self-hosting-on-a-spare-laptop-part-15/</link><pubDate>Sun, 28 Jun 2026 09:00:00 -0700</pubDate><guid>https://seanwong.xyz/notes/self-hosting-on-a-spare-laptop-part-15/</guid><description>&lt;h2 id="the-service-that-was-overdue"&gt;The service that was overdue&lt;/h2&gt;
&lt;p&gt;The laptop hums in the corner, the data is backed up, and I&amp;rsquo;d stopped reaching for cloud apps out of habit. So the server kept doing what it keeps doing and earned one more service, and this one had been waiting longer than any of the others. Music is the hobby I&amp;rsquo;ve put the most hours into, and somehow it was the last media I hadn&amp;rsquo;t brought home. Time to fix that.&lt;/p&gt;</description></item><item><title>The Glucose Diaries: What My CGM Revealed About My Biology</title><link>https://seanwong.xyz/notes/the-glucose-diaries/</link><pubDate>Sat, 20 Jun 2026 00:00:00 +0000</pubDate><guid>https://seanwong.xyz/notes/the-glucose-diaries/</guid><description>&lt;p&gt;I&amp;rsquo;m not diabetic. I don&amp;rsquo;t have prediabetes. I wore a continuous glucose monitor for six weeks because I was curious — and because I have enough of a biology background to suspect the data would be interesting.&lt;/p&gt;
&lt;p&gt;It was.&lt;/p&gt;
&lt;p&gt;Nine thousand, five hundred and fifteen readings later, I&amp;rsquo;ve got a dataset that tells stories I never expected to see about my own body. The most surprising one? My glucose &lt;em&gt;rises&lt;/em&gt; during weightlifting and &lt;em&gt;falls&lt;/em&gt; during a run, and those two facts trace back to completely different physiological mechanisms. My liver was doing things I&amp;rsquo;d read about in textbooks and never thought I&amp;rsquo;d actually observe in myself.&lt;/p&gt;</description></item><item><title>Friction Can Be a Feature</title><link>https://seanwong.xyz/notes/friction-can-be-a-feature/</link><pubDate>Mon, 15 Jun 2026 09:00:00 -0700</pubDate><guid>https://seanwong.xyz/notes/friction-can-be-a-feature/</guid><description>&lt;h2 id="the-case-for-slowing-things-down-on-purpose"&gt;The case for slowing things down on purpose&lt;/h2&gt;
&lt;p&gt;Most digital design treats friction as a bug. I&amp;rsquo;ve come to think some of it is a feature. A pause before an action, a tool that makes you wait, a step you can&amp;rsquo;t skip — these are usually filed under &amp;ldquo;bad UX.&amp;rdquo; But the same delay that annoys you out of a compulsive tap is the delay that hands you back a choice. The thesis here is simple: make important work easier, and make compulsive consumption slower.&lt;/p&gt;</description></item><item><title>Automating Music Discovery With an LLM and yt-dlp</title><link>https://seanwong.xyz/notes/automating-music-discovery-with-an-llm/</link><pubDate>Sat, 06 Jun 2026 09:14:00 -0700</pubDate><guid>https://seanwong.xyz/notes/automating-music-discovery-with-an-llm/</guid><description>&lt;h2 id="i-wanted-my-favorite-hobby-to-come-with-me"&gt;I wanted my favorite hobby to come with me&lt;/h2&gt;
&lt;p&gt;Music has been the one hobby that never left. It&amp;rsquo;s the thing I put real hours into, the soundtrack to everything else — and the part I love most is the hunt: turning up a track I&amp;rsquo;ve never heard and knowing, a few seconds in, that it&amp;rsquo;s a keeper.&lt;/p&gt;
&lt;p&gt;I wanted that hunt to come with me — to happen on a walk, in the kitchen, on the train. The finding is the fun part; I just wanted it to travel.&lt;/p&gt;</description></item><item><title>Self-Hosting on a Spare Laptop: A Home Server Series</title><link>https://seanwong.xyz/notes/self-hosting-on-a-spare-laptop/</link><pubDate>Sat, 30 May 2026 00:00:00 -0700</pubDate><guid>https://seanwong.xyz/notes/self-hosting-on-a-spare-laptop/</guid><description>&lt;h2 id="why-this-series-exists"&gt;Why this series exists&lt;/h2&gt;
&lt;p&gt;This series is my build log for turning an old laptop into a quiet, LAN-only home server. The technical goal is simple: run useful local services without buying new hardware. The personal goal matters more: build a calmer digital environment where my media, documents, photos, and videos live in tools I intentionally choose.&lt;/p&gt;
&lt;p&gt;I started with digital minimalism, then worked forward in small steps: Debian, SSH, storage folders, Jellyfin, Audiobookshelf, Immich, Paperless-ngx, qBittorrent-nox, yt-dlp, Homepage, Pi-hole, and Caddy. From there the project grew into the parts that keep a server trustworthy: backups, remote access, monitoring, a from-scratch checkpoint on what I&amp;rsquo;d do differently, and a lossless music stack.&lt;/p&gt;</description></item><item><title>Minimal Linux on a ThinkPad X260 with Debian and dwm</title><link>https://seanwong.xyz/notes/pimp-my-laptop/</link><pubDate>Sun, 24 May 2026 00:00:00 -0700</pubDate><guid>https://seanwong.xyz/notes/pimp-my-laptop/</guid><description>&lt;p&gt;I wanted a laptop that felt like mine again.&lt;/p&gt;
&lt;p&gt;Not a laptop buried under vendor defaults. Not a machine full of apps I never opened. Not a desktop that made every simple action feel like it had to pass through three layers of settings menus first. I wanted something plain, fast, keyboard-driven, and easy to understand.&lt;/p&gt;
&lt;p&gt;So I took a Lenovo ThinkPad X260 and decided to pimp it out the way I wanted: Debian Minimal, suckless tools, a black/red ThinkPad-inspired theme, a custom wallpaper, a tiny status bar, and dotfiles I could actually read.&lt;/p&gt;</description></item><item><title>Local-First Media: Self-Hosting Without Infinite Feeds</title><link>https://seanwong.xyz/notes/local-first-media-diet/</link><pubDate>Mon, 11 May 2026 18:47:08 -0800</pubDate><guid>https://seanwong.xyz/notes/local-first-media-diet/</guid><description>&lt;h2 id="why-im-building-a-local-first-media-diet"&gt;Why I’m Building a Local-First Media Diet&lt;/h2&gt;
&lt;p&gt;When most people hear “self-hosting media,” they imagine a technical hobby. A personal Netflix server. A way to store movies. A fun excuse to tinker with hardware. That is part of it. But for me, the real goal is much more important. I am not building a home server so I can own more media. I am building it so I can choose what reaches my attention by default.&lt;/p&gt;</description></item><item><title>Using Pi-hole and Caddy for Local HTTPS URLs</title><link>https://seanwong.xyz/notes/self-hosting-on-a-spare-laptop-part-10/</link><pubDate>Sat, 02 May 2026 20:26:45 -0700</pubDate><guid>https://seanwong.xyz/notes/self-hosting-on-a-spare-laptop-part-10/</guid><description>&lt;p&gt;So far in this series, most of my services have been running fine on the LAN, but I’ve still been opening them the ugly way:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;http://192.168.1.50:8096&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;http://192.168.1.50:13378&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;http://192.168.1.50:2283&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It works, but it gets annoying fast. I don’t want to memorize ports, and typing IP addresses into the browser every time makes the setup feel more hacked together than it really is.&lt;/p&gt;
&lt;p&gt;In this post, I clean that up with two pieces:&lt;/p&gt;</description></item><item><title>Setting Up Homepage as a Home Server Dashboard</title><link>https://seanwong.xyz/notes/self-hosting-on-a-spare-laptop-part-9/</link><pubDate>Sat, 25 Apr 2026 20:57:45 -0700</pubDate><guid>https://seanwong.xyz/notes/self-hosting-on-a-spare-laptop-part-9/</guid><description>&lt;h2 id="hosting-homepage-with-docker"&gt;Hosting Homepage with Docker&lt;/h2&gt;
&lt;p&gt;In the earlier posts in this series, I add a few services (Jellyfin, Audiobookshelf, etc.). At some point, typing IPs and ports starts to feel like juggling. This post is where I add Homepage — a clean dashboard that lives at one LAN URL and links out to everything else.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What this solves&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Gives the home server one dashboard instead of scattered bookmarks.&lt;/li&gt;
&lt;li&gt;Links directly to each LAN service.&lt;/li&gt;
&lt;li&gt;Shows basic server resource info at a glance.&lt;/li&gt;
&lt;li&gt;Makes the setup feel like one system instead of separate experiments.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h2 id="1-create-folders"&gt;1. Create folders&lt;/h2&gt;
&lt;p&gt;I keep service configs under &lt;code&gt;/srv/appdata/&lt;/code&gt;, so I make a spot for Homepage.&lt;/p&gt;</description></item><item><title>Automating YouTube Downloads with yt-dlp and systemd</title><link>https://seanwong.xyz/notes/self-hosting-on-a-spare-laptop-part-8/</link><pubDate>Sat, 18 Apr 2026 20:03:26 -0700</pubDate><guid>https://seanwong.xyz/notes/self-hosting-on-a-spare-laptop-part-8/</guid><description>&lt;h2 id="getting-past-the-youtube-infinite-scroll"&gt;Getting Past the YouTube Infinite Scroll&lt;/h2&gt;
&lt;p&gt;In the previous posts, I set up my little server to host media locally. This one is about automating YouTube downloads with &lt;strong&gt;yt-dlp&lt;/strong&gt;, optimized so it doesn’t waste time re-checking a million old uploads, and runs on a &lt;strong&gt;systemd timer&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This is not meant to mirror all of YouTube. The point is narrower: I want a small personal queue from channels I already care about, downloaded on a schedule, watchable later through my local media setup. It removes the “open YouTube and get distracted” step.&lt;/p&gt;</description></item><item><title>Installing qBittorrent-nox as a Headless Download Manager</title><link>https://seanwong.xyz/notes/self-hosting-on-a-spare-laptop-part-7/</link><pubDate>Sat, 11 Apr 2026 21:24:32 -0700</pubDate><guid>https://seanwong.xyz/notes/self-hosting-on-a-spare-laptop-part-7/</guid><description>&lt;h2 id="installing-qbittorrent-nox"&gt;Installing qBittorrent-nox&lt;/h2&gt;
&lt;p&gt;After Immich, I wanted a simple way to grab a few &lt;strong&gt;legit torrents&lt;/strong&gt; (Linux ISOs, open datasets, etc.) from the same little server — without running a full desktop UI. &lt;strong&gt;qBittorrent-nox&lt;/strong&gt; is the headless version of qBittorrent, and you manage it from a web browser.&lt;/p&gt;
&lt;p&gt;Same rules as the other services in this series: &lt;strong&gt;LAN-only&lt;/strong&gt;. Local IP, no domain, no reverse proxy.&lt;/p&gt;
&lt;p&gt;I’m going to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;install &lt;code&gt;qbittorrent-nox&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;run it as a dedicated user&lt;/li&gt;
&lt;li&gt;keep the Web UI reachable only from my LAN&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What this solves&lt;/strong&gt;&lt;/p&gt;</description></item><item><title>Setting Up Paperless-ngx for Document Search</title><link>https://seanwong.xyz/notes/self-hosting-on-a-spare-laptop-part-6/</link><pubDate>Sat, 04 Apr 2026 20:35:57 -0700</pubDate><guid>https://seanwong.xyz/notes/self-hosting-on-a-spare-laptop-part-6/</guid><description>&lt;h2 id="installing-paperless-ngx"&gt;Installing Paperless-ngx&lt;/h2&gt;
&lt;p&gt;This server started as a way to keep entertainment off my phone and out of the cloud. But once it was stable, it kind of turned into a tiny home “office” server too.&lt;/p&gt;
&lt;p&gt;So I added Paperless-ngx for documents: ingest, OCR, and search. It’s the same idea as Immich, but for PDFs instead of photos — and it stays LAN-only.&lt;/p&gt;
&lt;p&gt;For this install, I kept it simple and used the official installation script, which sets up a Docker Compose deployment for you.&lt;/p&gt;</description></item><item><title>Self-Hosting Immich for Private Photo Backups</title><link>https://seanwong.xyz/notes/self-hosting-on-a-spare-laptop-part-5/</link><pubDate>Sat, 28 Mar 2026 21:18:09 -0700</pubDate><guid>https://seanwong.xyz/notes/self-hosting-on-a-spare-laptop-part-5/</guid><description>&lt;h2 id="hosting-immich"&gt;Hosting Immich&lt;/h2&gt;
&lt;p&gt;After Jellyfin (video) and Audiobookshelf (audio), I wanted something like Google Photos, but not Google Photos — automatic phone backups, a clean timeline, and the fun stuff like face grouping and smart search. That’s where Immich fits: a self-hosted photo/video manager built around browsing, searching, and backing up your library on your own server.&lt;/p&gt;
&lt;p&gt;The key decision I made for this install: store all uploaded photos/videos on a separate hard drive mounted at: &lt;code&gt;/srv/storage/pictures&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Setting Up Audiobookshelf for Audiobooks and Podcasts</title><link>https://seanwong.xyz/notes/self-hosting-on-a-spare-laptop-part-4/</link><pubDate>Sat, 21 Mar 2026 20:25:41 -0700</pubDate><guid>https://seanwong.xyz/notes/self-hosting-on-a-spare-laptop-part-4/</guid><description>&lt;h2 id="installing-audiobookshelf"&gt;Installing Audiobookshelf&lt;/h2&gt;
&lt;p&gt;In &lt;a href="https://seanwong.xyz/notes/self-hosting-on-a-spare-laptop-part-3/"&gt;Part 3&lt;/a&gt;, I get Jellyfin running for video. Now I add &lt;strong&gt;Audiobookshelf&lt;/strong&gt; so I can stream audiobooks (and podcasts) from the same little server. This post is just the install + first boot. I keep it simple and LAN-only.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What this solves&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Keeps audiobooks and podcasts in one self-hosted library.&lt;/li&gt;
&lt;li&gt;Avoids recommendation-heavy podcast apps for saved shows.&lt;/li&gt;
&lt;li&gt;Stores audio files under the same &lt;code&gt;/srv/media/&lt;/code&gt; structure.&lt;/li&gt;
&lt;li&gt;Adds offline listening through the mobile app workflow.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h2 id="1-create-the-folders-under-srvmedia"&gt;1. Create the folders under &lt;code&gt;/srv/media/&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;I keep everything in &lt;code&gt;/srv/media/&lt;/code&gt;, so I make a clean spot for audiobooks + Audiobookshelf’s data.&lt;/p&gt;</description></item><item><title>Setting Up Jellyfin on a Spare Laptop Home Server</title><link>https://seanwong.xyz/notes/self-hosting-on-a-spare-laptop-part-3/</link><pubDate>Sat, 14 Mar 2026 21:07:54 -0700</pubDate><guid>https://seanwong.xyz/notes/self-hosting-on-a-spare-laptop-part-3/</guid><description>&lt;h2 id="hosting-jellyfin"&gt;Hosting Jellyfin&lt;/h2&gt;
&lt;p&gt;In &lt;a href="https://seanwong.xyz/notes/self-hosting-on-a-spare-laptop-part-1/"&gt;part 1&lt;/a&gt; I get the laptop running as a basic server.
In &lt;a href="https://seanwong.xyz/notes/self-hosting-on-a-spare-laptop-part-2/"&gt;part 2&lt;/a&gt; I set up &lt;code&gt;/srv/media/&lt;/code&gt;, permissions, SSH keys, and some power tweaks.&lt;/p&gt;
&lt;p&gt;This post is where I host my first service: &lt;strong&gt;Jellyfin&lt;/strong&gt;. I plan on accessing it using only the local IP address (no domain name, no reverse proxy).&lt;/p&gt;
&lt;p&gt;I wanted Jellyfin to be the first real app because it gives fast feedback. If it works, I can point a browser at the server, add a library, and immediately see whether my folder layout and permissions from part 2 were a good idea. If it does not work, the failure is usually visible: the service is down, the port is blocked, or Jellyfin cannot read the media folder.&lt;/p&gt;</description></item><item><title>How to Set Up a Spare Laptop as a Debian Home Server</title><link>https://seanwong.xyz/notes/self-hosting-on-a-spare-laptop-part-2/</link><pubDate>Sun, 08 Mar 2026 20:48:12 -0700</pubDate><guid>https://seanwong.xyz/notes/self-hosting-on-a-spare-laptop-part-2/</guid><description>&lt;h2 id="old-laptop--home-server-base-setup"&gt;Old laptop → home server (base setup)&lt;/h2&gt;
&lt;p&gt;I’m repurposing an old laptop as a local-only server. The goal is simple: a stable box on my LAN that can run Jellyfin, audiobookshelf, Immich, and a yt-dlp workflow later.&lt;/p&gt;
&lt;p&gt;This is the boring foundation post. No dashboards yet, no shiny app screenshots, just the parts that make every later service easier to run: a clean Debian install, SSH access, a predictable folder layout, shared permissions, and laptop power settings that do not fight the idea of a server.&lt;/p&gt;</description></item><item><title>Building a Home Server for Digital Minimalism</title><link>https://seanwong.xyz/notes/self-hosting-on-a-spare-laptop-part-1/</link><pubDate>Sun, 01 Mar 2026 20:11:37 -0800</pubDate><guid>https://seanwong.xyz/notes/self-hosting-on-a-spare-laptop-part-1/</guid><description>&lt;h2 id="new-year-new-resolution-less-screen-time"&gt;New year, new resolution: less screen time&lt;/h2&gt;
&lt;p&gt;This year I chose something simple—&lt;strong&gt;decreasing my screen time&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;A lot of the time I spend in front of a screen isn’t intentional. I’ll boot up the computer or pick up the phone to do one thing, and then get pulled into infinite scrolling feeds, recommendations, and algorithms. Willpower helps, but I’d rather do something more reliable:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Design a digital environment that isn’t constantly trying to steal my attention.&lt;/p&gt;</description></item><item><title>Plate Math: Barbell Plate Calculator for Lifters</title><link>https://seanwong.xyz/notes/plate-math/</link><pubDate>Thu, 19 Feb 2026 17:32:19 -0800</pubDate><guid>https://seanwong.xyz/notes/plate-math/</guid><description>&lt;h2 id="plate-math-a-simple-way-to-load-a-barbell-faster"&gt;Plate Math: A Simple Way to Load a Barbell Faster&lt;/h2&gt;
&lt;p&gt;If you lift regularly, you’ve probably done this: stand next to a bar, do the plate math in your head, feel confident… and still end up re-racking because the total is off.&lt;/p&gt;
&lt;p&gt;I built &lt;strong&gt;Plate Math&lt;/strong&gt; to remove that friction.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Plate Math&lt;/strong&gt; is a small React app: type a target weight and it tells you exactly what to put on the bar—fast. You get:&lt;/p&gt;</description></item><item><title>about me</title><link>https://seanwong.xyz/about/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://seanwong.xyz/about/</guid><description>&lt;p&gt;I’m a Los Angeles-based software engineer building across Python, JavaScript, and Swift.&lt;/p&gt;
&lt;p&gt;My path into tech wasn’t linear. I studied Human Biology at UC San Diego, spent two years in medical school, and eventually found my way back to the kind of work that made me feel most alive: building.&lt;/p&gt;
&lt;p&gt;I first discovered that while creating FlyClicker, a Python tool that automated research workflows and monitored CPU and memory usage. It was one of the first times I saw how code could transform frustration into leverage and ideas into something real. That stuck with me.&lt;/p&gt;</description></item><item><title>background</title><link>https://seanwong.xyz/resume/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://seanwong.xyz/resume/</guid><description/></item><item><title>now</title><link>https://seanwong.xyz/now/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://seanwong.xyz/now/</guid><description>&lt;p&gt;a snapshot of what has my attention right now:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;looking for a full-stack / genai engineering role&lt;/li&gt;
&lt;li&gt;building genai + rag projects in the open: repos, demos, architecture notes&lt;/li&gt;
&lt;li&gt;training toward a &lt;a href="https://en.wikipedia.org/wiki/Front_lever#/media/File:John_Gill_-_Front_Lever_-_1962.JPG"&gt;front lever&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;as a lefty, (re)learning to write with my right hand&lt;/li&gt;
&lt;li&gt;spinning drum &amp;amp; bass&lt;/li&gt;
&lt;li&gt;lost in &lt;a href="https://www.brandonsanderson.com/what-is-the-cosmere/"&gt;brandon sanderson&amp;rsquo;s cosmere&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;making the occasional bit of &lt;a href="https://seanwong.xyz/gallery/"&gt;art&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;last updated june 2026.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>sketchbook</title><link>https://seanwong.xyz/gallery/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://seanwong.xyz/gallery/</guid><description>&lt;div class="sketch-gallery"&gt;&lt;a href="https://seanwong.xyz/gallery/images/a_bathing_duck_hu_5ea9136f7afb4d1c.webp" style="--ar: 0.7750;"&gt;
 &lt;img src="https://seanwong.xyz/gallery/images/a_bathing_duck_hu_fdb3d3bb673f4800.webp" alt="a bathing duck" width="279" height="360" loading="lazy" decoding="async"&gt;
 &lt;/a&gt;&lt;a href="https://seanwong.xyz/gallery/images/abstract_shape_hu_3aaac5aae007f36b.webp" style="--ar: 1.2889;"&gt;
 &lt;img src="https://seanwong.xyz/gallery/images/abstract_shape_hu_76e29dc1043acb60.webp" alt="abstract shape" width="464" height="360" loading="lazy" decoding="async"&gt;
 &lt;/a&gt;&lt;a href="https://seanwong.xyz/gallery/images/aconite_hu_715f3b81657946d9.webp" style="--ar: 1.2667;"&gt;
 &lt;img src="https://seanwong.xyz/gallery/images/aconite_hu_b93b126dc64325ee.webp" alt="aconite" width="456" height="360" loading="lazy" decoding="async"&gt;
 &lt;/a&gt;&lt;a href="https://seanwong.xyz/gallery/images/camel_hu_546d5f07fcb50b7c.webp" style="--ar: 1.2889;"&gt;
 &lt;img src="https://seanwong.xyz/gallery/images/camel_hu_100c6c36806bb330.webp" alt="camel" width="464" height="360" loading="lazy" decoding="async"&gt;
 &lt;/a&gt;&lt;a href="https://seanwong.xyz/gallery/images/dapper_pig_hu_dd44aa786b51bf23.webp" style="--ar: 0.7750;"&gt;
 &lt;img src="https://seanwong.xyz/gallery/images/dapper_pig_hu_d26014a83489da97.webp" alt="dapper pig" width="279" height="360" loading="lazy" decoding="async"&gt;
 &lt;/a&gt;&lt;a href="https://seanwong.xyz/gallery/images/dense_forest_hu_48b9092c90cc089f.webp" style="--ar: 1.3333;"&gt;
 &lt;img src="https://seanwong.xyz/gallery/images/dense_forest_hu_2662c6802f90b3bf.webp" alt="dense forest" width="480" height="360" loading="lazy" decoding="async"&gt;
 &lt;/a&gt;&lt;a href="https://seanwong.xyz/gallery/images/exotic_fruits_hu_f5a2a1c76feb3e6.webp" style="--ar: 1.2889;"&gt;
 &lt;img src="https://seanwong.xyz/gallery/images/exotic_fruits_hu_157db41b970084b9.webp" alt="exotic fruits" width="464" height="360" loading="lazy" decoding="async"&gt;
 &lt;/a&gt;&lt;a href="https://seanwong.xyz/gallery/images/father_day_card_hu_990bd9d5ee8e7952.webp" style="--ar: 1.2889;"&gt;
 &lt;img src="https://seanwong.xyz/gallery/images/father_day_card_hu_edddda3633ce6f46.webp" alt="father day card" width="464" height="360" loading="lazy" decoding="async"&gt;
 &lt;/a&gt;&lt;a href="https://seanwong.xyz/gallery/images/fish_hu_288134e491d774ca.webp" style="--ar: 0.7750;"&gt;
 &lt;img src="https://seanwong.xyz/gallery/images/fish_hu_1f264bf93a94db6c.webp" alt="fish" width="279" height="360" loading="lazy" decoding="async"&gt;
 &lt;/a&gt;&lt;a href="https://seanwong.xyz/gallery/images/fruitimorphs_hu_eb0036133073680f.webp" style="--ar: 0.7750;"&gt;
 &lt;img src="https://seanwong.xyz/gallery/images/fruitimorphs_hu_db4b2683fed9b661.webp" alt="fruitimorphs" width="279" height="360" loading="lazy" decoding="async"&gt;
 &lt;/a&gt;&lt;a href="https://seanwong.xyz/gallery/images/gastropod_hu_c607398e44b9eca8.webp" style="--ar: 0.7750;"&gt;
 &lt;img src="https://seanwong.xyz/gallery/images/gastropod_hu_bbea03c468934e92.webp" alt="gastropod" width="279" height="360" loading="lazy" decoding="async"&gt;
 &lt;/a&gt;&lt;a href="https://seanwong.xyz/gallery/images/generic_mouse_and_friends_hu_b3fb4ff389bc598c.webp" style="--ar: 1.2889;"&gt;
 &lt;img src="https://seanwong.xyz/gallery/images/generic_mouse_and_friends_hu_29eda668f2b74cc5.webp" alt="generic mouse and friends" width="464" height="360" loading="lazy" decoding="async"&gt;
 &lt;/a&gt;&lt;a href="https://seanwong.xyz/gallery/images/happy_holidays_hu_e2a4fcd82a349ff9.webp" style="--ar: 1.2889;"&gt;
 &lt;img src="https://seanwong.xyz/gallery/images/happy_holidays_hu_4cd87925ba4395f3.webp" alt="happy holidays" width="464" height="360" loading="lazy" decoding="async"&gt;
 &lt;/a&gt;&lt;a href="https://seanwong.xyz/gallery/images/lemonade_hu_d761d080fe1d0c3f.webp" style="--ar: 0.7750;"&gt;
 &lt;img src="https://seanwong.xyz/gallery/images/lemonade_hu_4dd725a4c52e9068.webp" alt="lemonade" width="279" height="360" loading="lazy" decoding="async"&gt;
 &lt;/a&gt;&lt;a href="https://seanwong.xyz/gallery/images/locating_fish_hu_dd71db0b1c8c6914.webp" style="--ar: 0.7750;"&gt;
 &lt;img src="https://seanwong.xyz/gallery/images/locating_fish_hu_d5c6ac167b084f2b.webp" alt="locating fish" width="279" height="360" loading="lazy" decoding="async"&gt;
 &lt;/a&gt;&lt;a href="https://seanwong.xyz/gallery/images/my_house_hu_37fd16fa94446541.webp" style="--ar: 0.7750;"&gt;
 &lt;img src="https://seanwong.xyz/gallery/images/my_house_hu_f2d67fdb335a3e86.webp" alt="my house" width="279" height="360" loading="lazy" decoding="async"&gt;
 &lt;/a&gt;&lt;a href="https://seanwong.xyz/gallery/images/mythical_forest_friends_hu_87db29f2e849259b.webp" style="--ar: 0.7750;"&gt;
 &lt;img src="https://seanwong.xyz/gallery/images/mythical_forest_friends_hu_4e363377dcbc6efb.webp" alt="mythical forest friends" width="279" height="360" loading="lazy" decoding="async"&gt;
 &lt;/a&gt;&lt;a href="https://seanwong.xyz/gallery/images/mythical_forest_friends2_hu_f4c78346954b6227.webp" style="--ar: 1.2889;"&gt;
 &lt;img src="https://seanwong.xyz/gallery/images/mythical_forest_friends2_hu_f62e3a385bbe9e49.webp" alt="mythical forest friends2" width="464" height="360" loading="lazy" decoding="async"&gt;
 &lt;/a&gt;&lt;a href="https://seanwong.xyz/gallery/images/patricius_star_hu_a8d0498796d24f44.webp" style="--ar: 0.7750;"&gt;
 &lt;img src="https://seanwong.xyz/gallery/images/patricius_star_hu_a54826ac83490f7f.webp" alt="patricius star" width="279" height="360" loading="lazy" decoding="async"&gt;
 &lt;/a&gt;&lt;a href="https://seanwong.xyz/gallery/images/perched_hawk_hu_599dbb75020998c9.webp" style="--ar: 0.7750;"&gt;
 &lt;img src="https://seanwong.xyz/gallery/images/perched_hawk_hu_2dc208dbe8cdd98d.webp" alt="perched hawk" width="279" height="360" loading="lazy" decoding="async"&gt;
 &lt;/a&gt;&lt;a href="https://seanwong.xyz/gallery/images/petunia_hu_c5e7deeb0cf241f7.webp" style="--ar: 1.1833;"&gt;
 &lt;img src="https://seanwong.xyz/gallery/images/petunia_hu_2b326d51cd0ce557.webp" alt="petunia" width="426" height="360" loading="lazy" decoding="async"&gt;
 &lt;/a&gt;&lt;a href="https://seanwong.xyz/gallery/images/pig_and_bear_hu_98cafc3d7d7fee9a.webp" style="--ar: 0.7750;"&gt;
 &lt;img src="https://seanwong.xyz/gallery/images/pig_and_bear_hu_9394dcf4b06e458a.webp" alt="pig and bear" width="279" height="360" loading="lazy" decoding="async"&gt;
 &lt;/a&gt;&lt;a href="https://seanwong.xyz/gallery/images/pond_hu_ece686a53b4ae53e.webp" style="--ar: 1.3370;"&gt;
 &lt;img src="https://seanwong.xyz/gallery/images/pond_hu_f5ce004e354f34f1.webp" alt="pond" width="480" height="359" loading="lazy" decoding="async"&gt;
 &lt;/a&gt;&lt;a href="https://seanwong.xyz/gallery/images/senor_crab_hu_5e2125abaa9d9864.webp" style="--ar: 0.7750;"&gt;
 &lt;img src="https://seanwong.xyz/gallery/images/senor_crab_hu_171298fccf6992e8.webp" alt="senor crab" width="279" height="360" loading="lazy" decoding="async"&gt;
 &lt;/a&gt;&lt;a href="https://seanwong.xyz/gallery/images/shells_on_beach_hu_b6d8bce228eeb07e.webp" style="--ar: 0.7750;"&gt;
 &lt;img src="https://seanwong.xyz/gallery/images/shells_on_beach_hu_eef9b90e46e0df03.webp" alt="shells on beach" width="279" height="360" loading="lazy" decoding="async"&gt;
 &lt;/a&gt;&lt;a href="https://seanwong.xyz/gallery/images/thieving_bear_hu_f3002009fe06d0d4.webp" style="--ar: 1.2889;"&gt;
 &lt;img src="https://seanwong.xyz/gallery/images/thieving_bear_hu_30cc0ce599d8a940.webp" alt="thieving bear" width="464" height="360" loading="lazy" decoding="async"&gt;
 &lt;/a&gt;&lt;a href="https://seanwong.xyz/gallery/images/treefrog_hu_56345e20dd8b0f3f.webp" style="--ar: 0.7750;"&gt;
 &lt;img src="https://seanwong.xyz/gallery/images/treefrog_hu_123a836230ba4c9e.webp" alt="treefrog" width="279" height="360" loading="lazy" decoding="async"&gt;
 &lt;/a&gt;&lt;/div&gt;</description></item></channel></rss>