<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>florina sutanto</title>
    <link>https://florinasutanto.com/blog</link>
    <description>RSS feed for my blog posts</description>
    <atom:link href="https://florinasutanto.com/rss.xml" rel="self" type="application/rss+xml" />
    <item>
      <title><![CDATA[Setting up a Plex (and Jellyfin) media server]]></title>
      <link>https://florinasutanto.com/blog/2026/plex-jellyfin-setup</link>
      <guid isPermaLink="true">https://florinasutanto.com/blog/2026/plex-jellyfin-setup</guid>
      <description><![CDATA[I moved my media collection to a newly acquired PC server.]]></description>
      <content:encoded><![CDATA[<!--[--><p>Recently I acquired a PC—a 2015 Dell Optiplex 7020—from a friend who salvaged it from an e-waste pile and gave it to me for free!</p> <img src="/assets/blog/optiplex.webp" alt="Picture of my Dell Optiplex 7020" loading="lazy"/><!----> the floptiplex <p>I’ve been wanting to get into home servers for a while and was originally planning to get either a Raspberry Pi (which have been <a href="https://www.raspberrypi.com/news/a-new-3gb-raspberry-pi-4-for-83-75-and-more-memory-driven-price-increases/" rel="nofollow">absurdly expensive lately</a>) or a used PC off eBay, but this gave me the perfect opportunity to mess around with building my own server without having to take the financial hit. I’m mainly doing it to learn how to self-host projects and get better at computer networking (with an added benefit of freeing up my MacBook storage), plus my brain has somehow justified paying &lt;$10/month on a VPS = bad, whereas spending just as much on electric usage = okay, so here we are.</p> <p>After wiping the server and installing Ubuntu, I moved my media library from my MacBook to the Optiplex to set up a <a href="https://support.plex.tv/articles/200288286-what-is-plex/" rel="nofollow">Plex server</a> there. Plex isn’t a streaming service like Netflix and Hulu are (technically it does have mid streaming capabilities, but most people don’t use it for that)—rather, it’s a personal media server that allows you to organize and access your local (as in, downloaded) media files. That means going from something like this:</p> <img src="/assets/blog/media_local.webp" alt="Screenshot of my local media library" loading="lazy"/><!----> <p>To this:</p> <img src="/assets/blog/media_plex.webp" alt="Screenshot of my Plex media library" loading="lazy"/><!----> <p>I used to have a much larger media collection (that originated with ripping DVDs in middle school lol) but downgraded it over the years to allow more storage space on previous laptops. Which I massively regret! I love owning and having control over my media and NOT having to deal with the repeated devastation of having my favorite shows <a href="https://www.yahoo.com/entertainment/tv/articles/why-she-ra-princesses-power-055050823.html" rel="nofollow">be pulled off streaming platforms</a> at a corporation’s whim. Plus, the time and effort required to hunt stuff down and add them to my collection make me appreciate what I’m watching/listening to more, compared to lazily grazing on whatever’s on the worldwide top 10 that week.</p> <p>Anyway, some learnings from this process:</p> <h3>Avoid Buffering by Remuxing H.265 Files</h3> <p>I noticed that Plex had pretty major buffering issues when streaming H.265 video files. Reddit unearthed two solutions: transcode the files from H.265 to H.264, or remux them from <code>.mp4</code> to <code>.mkv</code>. I suspected that I didn’t have enough GPU power to do the former for like, 150 files at multi gigabytes each, so the latter it was. I used <a href="https://mkvtoolnix.download/" rel="nofollow">MKVToolNix</a> and it did the job perfectly; you could also use <code>ffmpeg</code>. I don’t know why this solves the issue exactly but it seems like the process <a href="https://www.reddit.com/r/PleX/comments/1kpbaoi/why_do_some_h265_encoding_files_stutter_when_i/" rel="nofollow">drops problematic metadata</a> from the files.</p> <h3>Tailscale + Jellyfin for Remote Streaming</h3> <p>Plex allows you to access your server from any device in your local network for free, and then makes you get a Plex Pass subscription if you want to access it remotely.</p> <p>I want remote access <em>and</em> don’t want to pay for a pass, so I connected my devices together via <a href="https://tailscale.com/" rel="nofollow">Tailscale</a>, which emulates the connection as if they’re all on the same local network. This was dead easy to set up—I just installed the app on the server and the devices I want connected, and signed in with my GitHub account.</p> <p>In theory, when Tailscale is active on both the server and the target device, I can run Plex from anywhere as if I was at home. But Plex <em>still</em> blocks me from remotely accessing the server even with Tailscale telling it that I’m on the same network. I don’t know if this is a skill issue on my end, but reading around on the internet makes me suspect that this might just be an artifact of enshittification and Plex has caught on to this workaround. Which I guess is fair, since they need money to run the service, and an annual remote pass is $20/year, but I just don’t see the point in paying an ongoing subscription to occasionally access my own files.</p> <p>Enter <a href="https://jellyfin.org/" rel="nofollow">Jellyfin</a>! Jellyfin is a free and open-source alternative to Plex that’s built to be entirely self-hosted, which means that you can do everything you can’t with Plex for free, like accessing your server remotely, downloading files for offline viewing, transcoding them for efficiency, etc. The UI is less polished and it seems like the client apps aren’t as good as Plex’s, but it’s free and works with Tailscale—when Tailscale is active on my laptop/phone/iPad I can stream from my Jellyfin server even when I’m on a different network.</p> <p>In retrospect, going with Jellyfin from the beginning would’ve saved me the headache, but I do like Plex’s UI better and it wasn’t hard to set both up. And besides, redundancy saves lives, or something. My plan is to stream with Plex when I’m at home (it seems to have a better TV client) and use Tailscale + Jellyfin when I’m away.</p> <h1>Is this overkill?</h1> <p>Yeah, pretty much. At least for the stage of life I’m in right now—I watch everything on my laptop in my room 90% of the time and can’t remember the last time I <em>really</em> needed my media library with me while I was away from home. I’m pretty much the only one accessing my collection anyway—everyone I know has multiple streaming subscriptions, and it’s not hard to find free streaming sites that host off-platform movies and shows.</p> <p>It’s just nice to have all of my favorite stuff in one place. Nice to see them more glammed up than how I have them in my local folders. And super fucking nice not be beholden to hostile corporations hell-bent on squeezing more money out of its customers while doling out worsening services. So yay to my media server!</p><!--]-->]]></content:encoded>
      <pubDate>Fri, 10 Apr 2026 12:00:00 GMT</pubDate>
      <category>hobby</category>
      <category>server</category>
    </item>
    <item>
      <title><![CDATA[Rubber ducking (Buffy ducking?)]]></title>
      <link>https://florinasutanto.com/blog/2026/rubber-ducking</link>
      <guid isPermaLink="true">https://florinasutanto.com/blog/2026/rubber-ducking</guid>
      <description><![CDATA[I have a Buffy tabletop figurine that I use for rubber duck debugging.]]></description>
      <content:encoded><![CDATA[<!--[--><p>I have a Buffy tabletop figurine that I use for <a href="https://en.wikipedia.org/wiki/Rubber_duck_debugging" rel="nofollow">rubber duck debugging</a>. I talk to her whenever I need to debug my thoughts, which applies anywhere from programming to when I need to hype myself up before giving a presentation. Talking out loud helps me untangle the jumble of ideas nesting in my brain—I often feel like I’m chasing after a swarming mass of thoughts in a pretty strenuous attempt to pin them down and dissect them into the right words. Not unlike SpongeBob trying to catch jellyfish with his little net.</p> <img src="/assets/blog/buffy_figurine.webp" alt="Buffy figurine" loading="lazy"/><!----> beep me, she said <p>She comes from an Unmatched game set that I bought at a <a href="https://www.hexnyc.com/" rel="nofollow">board game cafe</a> in New York, back when I spent a summer there in 2022. Nowadays it functions more as room decor than a game I actively play (Unmatched works better when there are multiple sets being played together), but it looks cool sitting on my Shelf of Things.</p> <img src="/assets/blog/buffy_game.webp" alt="Buffy game" loading="lazy"/><!----> angel, willow, and spike reigning over my yoga mat <p>I remember being on the fence about buying it—I came back three times before finally deciding to lay down 50 of my hard-earned dollars for it (I was making $15/hour on my internship…in NYC…), upon finding out that the set came with a Faith sidekick token. I wish it came with a Faith figurine instead, but maybe that’s a tall ask for a character that only appeared for 10% of the show (but had a long and storied impact on it and culture at large).</p> <img src="/assets/blog/buffy_tokens.webp" alt="Buffy tokens" loading="lazy"/><!----><!--]-->]]></content:encoded>
      <pubDate>Tue, 17 Mar 2026 12:00:00 GMT</pubDate>
      <category>personal</category>
    </item>
    <item>
      <title><![CDATA[Adding a comment section with HTML Comment Box]]></title>
      <link>https://florinasutanto.com/blog/2026/html-comment-box</link>
      <guid isPermaLink="true">https://florinasutanto.com/blog/2026/html-comment-box</guid>
      <description><![CDATA[With some SvelteKit modifications.]]></description>
      <content:encoded><![CDATA[<!--[--><p>My blog has a comment section now. Yay!</p> <p>I’m using <a href="https://www.htmlcommentbox.com/" rel="nofollow">HTML Comment Box</a>, which everybody on Neocities seems to use on their site. While it is framework agnostic (being a simple HTML widget), I haven’t seen it on any of the non-Neocities blogs I follow—those typically have a custom or more complicated set up for comments. I don’t really need a feature-rich comment section with analytics or user icons, nor do I want to set up a database for it, so this seemed like the best solution for my site (for now).</p> <p>HCB pretty much works right out of the box. After logging in with a Google account, click the <code>+ options</code> button to customize your widget. The most useful customization for me was ‘Show website field’, which links the comment author’s display name to their website.</p> <img src="/assets/blog/hcb1.webp" alt="Screenshot of HTML Comment Box's home page" loading="lazy"/><!----> <img src="/assets/blog/hcb2.webp" alt="Screenshot of HTML Comment Box's home page" loading="lazy"/><!----> <p>By default, the widget comes with an email field that allows visitors to be notified if anyone replies to their comment, but I’ve never liked the idea of submitting my personal email to random people’s blogs, so I decided to get rid of it on mine. Besides, if you have an HCB account and are logged in in your browser, it’ll automatically track every comment you receive or post on other HCB-powered comment sections.</p> <img src="/assets/blog/hcb3.webp" alt="Testing my comment section" loading="lazy"/><!----> <img src="/assets/blog/hcb4.webp" alt="Comments received in HCB's page" loading="lazy"/><!----> <p>The best part about it is that you have total control of the styling via CSS! You can read more about it <a href="https://www.htmlcommentbox.com/css-guide.html" rel="nofollow">here</a>, but essentially all I had to do was open Inspect Element and figure out which tags or classes to target.</p> <p>Here’s how other people have customized their widgets:</p> <a href="https://ribo.zone/"><img src="/assets/blog/hcb5.webp" alt="ribo.zone's comment section" loading="lazy"/><!----></a> from <a href="https://ribo.zone">ribo.zone</a> <a href="https://kayleerowena.com/"><img src="/assets/blog/hcb6.webp" alt="Kaylee Rowena's comment section" loading="lazy"/><!----></a> from <a href="https://kayleerowena.com/">Kaylee Rowena</a> <h1>Svelteifying the Embed Code</h1> <p>Immediately after logging in, HCB will give you code that you can copy and paste straight into your file. This is what it looks like by default.</p> <img src="/assets/blog/hcb7.webp" alt="Default HCB styling" loading="lazy"/><!----> kinda ugly... <p>If you’re working on a plain HTML file, you can stop here. All you need to do is style the elements accordingly.</p> <img src="/assets/blog/hcb8.webp" alt="Inspecting element for styling" loading="lazy"/><!----> look for the corresponding tags and classes here <p>If you’re using SvelteKit like I am, you need to place the script in an <code>onMount</code> hook so that it runs in the browser only after the component has been mounted. Otherwise, SvelteKit may try to render it in the server and cause a runtime error.</p> <p>Replace this:</p> <!----><pre tabindex="0"><code>&#x3C;script type="text/javascript" id="hcb">
 /*&#x3C;!--*/ if (!window.hcb_user) {
		hcb_user = {};
	}
	(function () {
		var s = document.createElement('script'),
			l = hcb_user.PAGE || ('' + window.location).replace(/'/g, '%27'),
			h = 'https://www.htmlcommentbox.com';
		s.setAttribute('type', 'text/javascript');
		s.setAttribute(
			'src',
			h +
				'/jread?page=' +
				encodeURIComponent(l).replace('+', '%2B') +
				'&#x26;mod=%241%24wq1rdBcg%247pXLI2Nftu0UD3pl.2vzg%2F' +
				'&#x26;opts=16798&#x26;num=10&#x26;ts=1772765904575'
		);
		if (typeof s != 'undefined') document.getElementsByTagName('head')[0].appendChild(s);
	})(); /*-->*/
&#x3C;/script></code></pre><!----> <p>With this:</p> <!----><pre tabindex="0"><code>&#x3C;script lang="ts">
  import { onMount } from 'svelte';

  onMount(() => {
    (window as any).hcb_user = {};

    const l = window.location.toString().replace(/'/g, '%27');
    const h = 'https://www.htmlcommentbox.com';
    const s = document.createElement('script');
    s.type = 'text/javascript';
    s.src =
      h +
      '/jread?page=' +
      encodeURIComponent(l).replace('+', '%2B') +
      '&#x26;mod=%241%24wq1rdBcg%247pXLI2Nftu0UD3pl.2vzg%2F' +
      '&#x26;opts=16798&#x26;num=10&#x26;ts=1772765904575';
    document.head.appendChild(s);
  });
&#x3C;/script></code></pre><!----> <p>Additionally, you can customize the labels by adding <a href="https://www.htmlcommentbox.com/advanced.html" rel="nofollow">this script</a> to the <code>hcb_user</code> variable:</p> <!----><pre tabindex="0"><code>onMount(() => {
  (window as any).hcb_user = {
    /* L10N */
    comments_header: 'Comments',
    name_label: 'Name',
    content_label: 'Leave a comment',
    // rest of script</code></pre><!----> <p>As the name suggests, HTML Comment Box supports formatting in HTML and not Markdown. Markdown is faster and easier to write in, but there’s something old-school about writing out HTML tags that I enjoy.</p><!--]-->]]></content:encoded>
      <pubDate>Thu, 05 Mar 2026 12:00:00 GMT</pubDate>
      <category>code</category>
      <category>tutorial</category>
      <category>site</category>
    </item>
    <item>
      <title><![CDATA[How to deploy a SvelteKit app to GitHub Pages]]></title>
      <link>https://florinasutanto.com/blog/2026/deploy-sveltekit-to-gh-pages</link>
      <guid isPermaLink="true">https://florinasutanto.com/blog/2026/deploy-sveltekit-to-gh-pages</guid>
      <description><![CDATA[What it says on the tin.]]></description>
      <content:encoded><![CDATA[<!--[--><p>This is the process I use to quickly set up a static SvelteKit app and publish it through GitHub Pages. This only works for simple, static sites—anything that requires code to run the server side (e.g. API calls that need to happen at runtime, anything with private keys) will not work with GH Pages.</p> <h1>1. Create a new project and set up the dependencies</h1> <!----><pre tabindex="0"><code>npx sv create YOUR_REPO_NAME</code></pre><!----> <p>My typical dependencies:</p> <ul><li>Which template would you like? <code>SvelteKit minimal</code></li> <li>Add type checking with TypeScript? <code>Yes, using Typescript syntax</code></li> <li>What would you like to add to your project? <code>prettier</code>, <code>eslint</code>, <code>tailwindcss</code>, <code>sveltekit-adapter</code>, <code>mdsvex</code></li> <li>Tailwindcss: Which plugins would you like to add? <code>typography</code></li> <li>Sveltekit-adapter: Which SvelteKit adapter would you like to use? <code>static</code></li> <li>Which package manager do you want to install dependencies with? <code>pnpm</code></li></ul> <h1>2. Make the following modifications to your codebase</h1> <ul><li>Install the gh-pages package</li></ul> <!----><pre tabindex="0"><code>pnpm i -D gh-pages</code></pre><!----> <ul><li>Replace the contents of <code>svelte.config.js</code> with the following:</li></ul> <!----><pre tabindex="0"><code>import { mdsvex } from 'mdsvex';
import adapter from '@sveltejs/adapter-static';

const dev = process.env.NODE_ENV === 'development';

/** @type {import('@sveltejs/kit').Config} */

export default {
 kit: {
  adapter: adapter({
   pages: 'build',
   assets: 'build',
   fallback: null
  }),
  paths: {
   base: dev ? '' : '/YOUR_REPO_NAME'
  }
 },
 preprocess: [mdsvex()],
 extensions: ['.svelte', '.svx']
};</code></pre><!----> <p>If you’re deploying to your root GitHub domain (e.g. username.github.io), remove <code>/YOUR_REPO_NAME</code> and leave it as an empty string (<code>''</code>).</p> <ul><li>In <code>src/routes</code>, create a <code>+layout.ts</code> file and add: <code>export const prerender = true;</code></li> <li>In <code>package.json</code>, under <code>scripts</code>, add: <code>"deploy": "pnpm run build &amp;&amp; gh-pages -d build -t",</code></li> <li>In the static folder, create an empty <code>.nojekyll</code> file. This is needed to bypass GitHub Pages’ default use of Jekyll as the static site generator. If you skip this step, your Pages site will only display the contents of README.md.</li></ul> <h1>3. Initialize git and push the repo (from the CLI)</h1> <!----><pre tabindex="0"><code>git init -b main
git add .
git commit -m "Initial commit"
gh repo create YOUR_REPO_NAME --public --source=. --remote=origin --push</code></pre><!----> <p>Or, if you’ve created the repo manually on GitHub’s site, run these commands after committing:</p> <!----><pre tabindex="0"><code>git remote add origin https://github.com/YOUR_USERNAME/YOUR_REPO_NAME.git
git branch -M main
git push -u origin main</code></pre><!----> <h1>4. Deploy to the gh-pages branch</h1> <!----><pre tabindex="0"><code>pnpm run deploy</code></pre><!----> <h1>5. Set up GitHub Pages</h1> <ul><li>Navigate to your repo on GitHub</li> <li>Go to Settings, then Pages on the left panel</li> <li>Make sure source is set to <code>Deploy from a branch</code> and branch is set to <code>gh-pages</code>, then save</li> <li>Once it’s done generating your site, you can find the link at the top of this page</li></ul> <h1>6. In the future</h1> <ul><li>To push files to the main branch:</li></ul> <!----><pre tabindex="0"><code>git add .
git commit -m "commit message"
git push origin main</code></pre><!----> <ul><li>To deploy to the gh-pages branch:</li></ul> <!----><pre tabindex="0"><code>pnpm run deploy</code></pre><!----> <p>If everything goes correctly, this is what you should see at <code>username.github.io/YOUR_REPO_NAME</code>:</p> <img src="/assets/blog/demo.webp" alt="Successful SvelteKit deployment to GitHub Pages" loading="lazy"/><!----> <p>That’s it!</p><!--]-->]]></content:encoded>
      <pubDate>Sun, 01 Mar 2026 12:00:00 GMT</pubDate>
      <category>code</category>
      <category>tutorial</category>
    </item>
    <item>
      <title><![CDATA[My RSS feed is up!]]></title>
      <link>https://florinasutanto.com/blog/2026/rss-feed</link>
      <guid isPermaLink="true">https://florinasutanto.com/blog/2026/rss-feed</guid>
      <description><![CDATA[RSS continues to be my favorite method of curating what I read on the internet.]]></description>
      <content:encoded><![CDATA[<!--[--><p>When I set up this blog I knew that I wanted to create an RSS feed for it. RSS continues to be my favorite method of curating what I read on the internet, stripped free of algorithms that control what I can and can’t see.</p> <p>These days, I primarily write and read blog posts on <a href="https://www.dreamwidth.org/" rel="nofollow">Dreamwidth</a>, a blogging platform that also acts as as feed reader, but adding external, non-Dreamwidth sites there is a little finnicky and I’d rather not <a href="https://www.dreamwidth.org/support/faqbrowse?faqid=27" rel="nofollow">generate accounts for other people’s feeds</a>. So in order for me to subscribe to blogs on standalone sites, I’ve set up NetNewsWire as my reader and synced it via iCloud—it’s free and open source, which I always appreciate. I’ve installed <a href="https://netnewswire.com/help/mac/6.0/en/getting-started.html" rel="nofollow">version 6</a> instead of the latest 7, since the latter requires me to upgrade my MacOS to Tahoe (v26) and I want to avoid the Liquid Glass updates as long as I can.</p> <p>Right now I’ve set up my RSS feed to display the entire content of each post. While I like how this shows up on the reader, I also anticipate wanting to attach interactive components (graphics, scrollytelling, weird layouts, etc.) to certain posts in the future, and I have no idea how this might look or whether it’ll just completely break the XML structure. I’ll have to cross that bridge later, but for now things are looking pretty neat!</p> <p>You can find my feed <a href="/rss.xml">here</a>.</p><!--]-->]]></content:encoded>
      <pubDate>Sun, 01 Mar 2026 12:00:00 GMT</pubDate>
      <category>code</category>
      <category>site</category>
    </item>
    <item>
      <title><![CDATA[Colophon]]></title>
      <link>https://florinasutanto.com/blog/2026/colophon</link>
      <guid isPermaLink="true">https://florinasutanto.com/blog/2026/colophon</guid>
      <description><![CDATA[How I built this site!]]></description>
      <content:encoded><![CDATA[<!--[--><p>Hello! Welcome to my site. This space is meant to be a catch-all for me to showcase my professional portfolio, practice web design and development, experiment with graphics, post personal projects, and write about things I find interesting.</p> <p>I imagine that everything here will constantly be in flux as my tastes and interests grow and change, but I’d like to document how I built this site as best as I can, both for my own sake and for whoever might find this useful.</p> <h1>Design</h1> <p>With personal projects, I normally do a bit of design testing on Figma (basic layout, color options, fonts, etc.), but truth be told, I find designing on the fly fun! They’re not avant garde by any means, but I like each page to look a little different from each other so more of my personality and choices can shine through. I am very inspired and influenced by the Indie Web and, following that ethos, want my web presence to reflect myself uniquely.</p> <p>The little doodles on the personal page were drawn using Affinity Designer (which is now <a href="https://www.canva.com/newsroom/news/affinity-free/" rel="nofollow">FREE</a>?!) as vectors on my iPad, then exported and edited on Figma. I find it faster to make final touches to SVGs on my laptop rather than my iPad.</p> <p>Animations were made using the <a href="https://gsap.com/" rel="nofollow">GSAP library</a>. I use <a href="https://shiki.style/" rel="nofollow">Shiki</a> as a syntax highlighter for code blocks.</p> <h1>Stack</h1> <p>This site is built (“hand coded”) with Svelte on the SvelteKit framework. Svelte allows me to easily write HTML, CSS, and JavaScript in self-contained component files, which it then compiles into small, optimized JavaScript bundles. SvelteKit is a web application framework that builds on top of these files by providing features like page and API routing, server-side rendering, static site generation, data loading, and so on. I got started with SvelteKit after poking around <a href="https://pudding.cool/" rel="nofollow">The Pudding</a>’s public repos for their stories and it’s been really easy to learn on the go! Now I use it for all web development projects at my job.</p> <p>I primarily use Tailwind for styling, though vanilla CSS comes in handy when there are edge cases that Tailwind can’t handle. I’m a big fan of Tailwind’s class-based styling system; it keeps everything in one place and makes it <em>so</em> easy to make things responsive. No need to worry about hunting down class names up and down a long CSS file. For reoccurring styles, I use design and styling tokens that are applied globally.</p> <p>All my writings are written in Markdown. Structured, repeatable texts like project descriptions live in <code>.yaml</code> files, since it’s easy to iterate through them. Since blog posts are longer and more varied, I’ll draft them in Obsidian and publish through a headless CMS called <a href="https://sveltiacms.app/" rel="nofollow">Sveltia CMS</a>, which I learned about from <a href="https://aman.bh/blog/2025/sveltia-cms-is-golden" rel="nofollow">this blog post</a>. With Sveltia, I don’t have to touch my repo whenever I want to post––simply adding <code>/admin/index.html</code> at the end of my domain will pull up the UI. Since it’s a Git-based CMS, publishing a post will automatically push the associated Markdown file (as well as any images) to my GitHub repo, which will in turn trigger an automatic deployment on Vercel. So easy!</p> <p>As a note, I do have to log into Sveltia with a GitHub Personal Access Token (PAT). I think it won’t allow me to log in with my GitHub account because I’m deploying my site on Vercel.</p> <h1>Deployment &amp; Hosting</h1> <p>The previous three iterations of my site were hosted on GitHub Pages; this is the first that’s hosted on Vercel. I’ll be honest, deploying a SvelteKit app to gh-pages was always a pain in the ass. I needed to route it in a specific way and remember to bypass Jekyll and add a CNAME file to the repo. I did it because I liked storing and hosting everything on one platform, and though it worked it was annoying to have to do this every time I rebuilt my site from scratch.</p> <p>Now that I have components that make server-side API calls, I can’t use GitHub Pages anymore since it can only deploy static sites. Luckily I’m used to using Vercel at work so it was an easy switch––just had to connect the repo, add my API keys to the environment variables, and that’s it! I’m not too worried about the bandwidth limits on the free tier right now, but as my site grows (especially with images) I might have to look into setting up a CDN.</p> <p>The only thing I pay for to get this site on the internet is my domain name, which costs $20/year via Squarespace. It’s been fine to use and I honestly barely touch it. I’ve learned that it’s much cheaper (~$11/year) to buy a domain off Porkbun, which is also highly rated, so when my plan ends this year I might make the switch (hopefully I’ll remember).</p> <blockquote><p><strong>Dev tip:</strong> To open localhost on your phone or tablet (for responsivity testing), run the command <code>pnpm dev --host</code>, copy the Network link, and open the link in a mobile browser. Make sure all devices are connected to the same network. It’s much easier and more reliable than using an emulator!</p></blockquote> <h1>Media</h1> <p>To compress images, I run the following ImageMagick command to resize them to a max width of 800-1200 pixels (so they look good in the image lightbox) and convert them to <code>.webp</code> files.</p> <p><strong>Loop through all images:</strong></p> <!----><pre tabindex="0"><code>for f in *.{jpg,png,jpeg,heic,JPG,PNG,JPEG,HEIC}(N); do magick "$f" -auto-orient -resize 800x -quality 90 "${f%.*}.webp"; done</code></pre><!----> <p><strong>By individual image:</strong></p> <!----><pre tabindex="0"><code>magick "image.jpg" -auto-orient -resize 800x -quality 100 "image.webp"</code></pre><!----> <p>I then apply Vercel’s built-in <a href="https://vercel.com/docs/image-optimization" rel="nofollow">Image Optimizer</a> to compress and cache the images at serve time, making them load faster on this site.</p> <p>I compress videos by running <a href="https://unix.stackexchange.com/a/693375" rel="nofollow">this ffmpeg command</a>: it resizes the video to a max width of 1200 pixels, encodes it with the H.265 codec, and then overwrites the original file so there’s only one copy at the end. This is hands down the best solution I’ve seen for getting 20+ MB videos down to less than 1 MB without compromising video quality.</p> <p><strong>Loop through all videos:</strong></p> <!----><pre tabindex="0"><code>for f in *.mp4; do
  ffmpeg -i "$f" -vf "scale='trunc(min(1200,iw)/2)*2:-2'" -c:v libx265 -vtag hvc1 -c:a copy "${f%.mp4}_temp.mp4"
  mv "${f%.mp4}_temp.mp4" "$f"
done</code></pre><!----> <p><strong>By individual video:</strong></p> <!----><pre tabindex="0"><code>ffmpeg -i "input.mp4" -vf "scale='trunc(min(1200,iw)/2)*2:-2'" -c:v libx265 -vtag hvc1 -c:a copy "input_temp.mp4" &#x26;&#x26; mv "input_temp.mp4" "input.mp4"</code></pre><!----> <p>I’m not sure if these are the best ways to deal with media optimization, to be honest. I want this process to be as frictionless as possible, and right now it involves a lot of manual manipulation to check if I’m striking the right balance between quality and size for each image. Compressing project covers is fine, since I’ll likely only have to it once in a while, but frequently dealing with blog images—especially ones with lots of text—might get tricky down the line. I’ll keep looking for better ways to deal with this issue.</p> <h1>APIs and Components</h1> <h2>LastFM + Discogs</h2> <p>The “Favorite Songs This Week” list in my scrapbook section displays my most listened to songs from the past 7 days in real time!</p> <p>To do this, I use <a href="https://www.last.fm/home" rel="nofollow">LastFM</a>’s API to pull the track name, artist, and URL. Unfortunately, their API doesn’t return any cover images or artwork, which I also want to display. To circumvent this, I first tried using <a href="https://musicbrainz.org/" rel="nofollow">MusicBrainz</a>’s API to query cover images based on the track and artist, but their rate limits made the requests way too slow––slow enough that I would scroll away because I got bored waiting for the covers to load.</p> <p>Luckily, I found out about <a href="https://www.discogs.com/" rel="nofollow">Discogs</a> through a Reddit thread, and their API works great for me. The only thing is that a lot of their listings have user-submitted, manually-taken photographs of vinyl covers as the cover artwork, instead of an official digital image for some reason. It’s not a big deal, but if any of the covers on my list look a little funny, that’s why. Personally, I think it’s odd but charming!</p> <h2>Hardcover</h2> <p><a href="https://hardcover.app/" rel="nofollow">Hardcover</a> is a great book-tracking alternative to Goodreads and StoryGraph, particularly because it’s the only one out of the three that offers an API.</p> <p>One of Hardcover’s developers has <a href="https://www.emgoto.com/hardcover-book-api/" rel="nofollow">a great tutorial on how to get started with the API</a>. The schema was kind of confusing to me at first (they say ‘contributor’ instead of ‘author’, which tripped me up), but once I got the hang of the GraphQL structure I was able to query books from three lists: my currently reading (which is just status id = 2), recently read (status id = 3), and favorites, which pulls from a list I created in Hardcover called site-favorites. The reason for the last one is that I didn’t necessarily want to display any book I rated 5 stars as my personal favorites, and I want to be able to manually control what goes in that list.</p> <p>While the API is still in development and could change at any time, I found their docs to be really helpful; they even have “try it yourself” sections that auto-load specific queries associated with your account, so you can just copy and paste it to your request.</p> <p>Aside from the API, I actually enjoy using Hardcover as my book-tracker platform. Their UI is more appealing to me than the other two, and I find it fitting that the founders <a href="https://hardcover.app/pages/about" rel="nofollow">specifically built it in response to Goodreads sunsetting their API</a>, which disrupted them from displaying their reading lists on their blog (which is what I’m doing!). They’re a community-driven, soon to be open-source project, and I wish them all the success! I also joined their Discord channel so I can keep an eye out for development updates.</p> <h2>Piano</h2> <p>The piano component at the bottom of my scrapbook section was lifted from <a href="https://svelte-piano.vercel.app/" rel="nofollow">Svelte Piano</a>.</p> <h1>AI Policy</h1> <p>I use Claude as a coding assistant, though I try not to use it gratuitously in an effort to learn and improve my programming skills. Aside from occasionally using Grammarly to check for spelling and grammar, I don’t use AI to generate any of the writing or art on my site. I am generally uninterested in using generative AI to produce any creative output.</p> <h1>Source Code</h1> <p>The full code for this site can be found <a href="https://github.com/retrospatial/florinasutanto" rel="nofollow">here</a>.</p><!--]-->]]></content:encoded>
      <pubDate>Sun, 25 Jan 2026 12:00:00 GMT</pubDate>
      <category>code</category>
      <category>site</category>
    </item>
  </channel>
</rss>