Sir Blob's Desktop

A portfolio website that recreates my Arch Linux desktop (Hyprland, Quickshell bar, kitty terminals) in the browser. Built with SvelteKit 3, Svelte 5 runes and Tailwind CSS 4.

Running

start builds the site with Bun (so it does not depend on the system Node version) and serves it with Bun on PORT (default 3000), reading .env for the Discord variables.

bun install
bun run dev
bun run build
bun run start
bun run check

How the desktop works

  • The top bar mirrors the blob-shell Quickshell bar: chai menu (app launcher), workspaces 1-9, clock, Discord status and theme picker.
  • Every page (/about, /projects/hackathons, ...) opens as a tiled window, so old resume links still work.
  • Terminals open next to it and tile with the Hyprland dwindle layout.
  • Desktop icons open a page, a terminal running a command, or an external link.
  • On small screens the focused window goes fullscreen and open windows are listed as tabs.

Keybinds

Keys Action
Alt + Enter Open a kitty terminal
Alt + Space App launcher
Alt + Q Close focused window
Alt + F Fullscreen the page window
Alt + J / K Focus next / previous window
Alt + 1-9 Switch workspace
Alt + T Next theme

Alt stands in for Super, which browsers do not receive.

Editing content

Everything you would normally change lives in content/. No code changes needed.

File Contents
content/profile.json name (shown only on the portfolio page), bio, avatar, education, socials (also used by the links page), skills, interests
content/experience.json experience with highlights, tech, awards and links
content/leadership.json leadership roles
content/about.json hobbies, games, shows, movies
content/projects/opensource.json open source projects
content/projects/packages.json published packages
content/projects/hackathons.json hackathon projects, events and awards
content/projects/research.json research roles and projects
content/models.json 3D models (files go in static/models/)
content/desktop.json default theme, workspaces, desktop icons, startup command
content/blog.json blog name, tagline, posts per page
content/blog/*.md blog posts

Projects

Every project in every category uses the same shape. Only name, description, tags and links are required.

{
	"name": "HushMap",
	"description": "What it does.",
	"image": "/hacks/hushmap.png",
	"tags": ["Svelte", "FastAPI"],
	"featured": true,
	"event": { "name": "Bitcamp 2026", "university": "University of Maryland", "location": "College Park, MD", "year": "2026" },
	"awards": [{ "track": "Best UI/UX Hack", "place": "Winner" }],
	"demoMayBeOffline": false,
	"version": "2.2.0",
	"links": { "demo": "https://...", "code": "https://...", "devpost": "https://...", "package": "https://...", "article": "https://..." }
}

Featured projects are listed first.

Desktop icons

Each icon in content/desktop.json has a label, an Iconify icon and one open action:

{ "label": "about", "icon": "mdi:account", "open": { "page": "/about" } }
{ "label": "skills.sh", "icon": "mdi:script-text", "open": { "terminal": "skills" } }
{ "label": "github", "icon": "mdi:github", "open": { "url": "https://github.com/SirBlobby" } }

{ "terminal": "" } opens an empty terminal.

Blog posts

Add a markdown file to content/blog/. The file name is the URL slug.

---
title: My Post Title
author: Sir Blob
date: 2026-01-01
tags: meta, notes
excerpt: One line summary for the list and the RSS feed.
---

Post content in markdown.

The RSS feed is at /blog/rss.xml.

Themes

Themes live in src/lib/theme/themes.ts and use the same keys as a blomarchy colors.toml, so a theme can be copied over from the dotfiles. Each theme can set a wallpaper (from static/wallpapers/) and an activeBorder gradient. The default theme is set in content/desktop.json.

Shipped themes: Flats (default), Retro 82, Tokyo Night, Catppuccin, Arch Linux, Gruvbox, Nord, Kanagawa, Wintry, Rose, Cerberus, Catppuccin Latte, Rose Pine Dawn.

Terminal commands

help, whoami, fastfetch, skills, contact, email, experience, projects [category], ls, cd <page>, theme [name], echo, cowsay, date, clear, exit. Page names (about, hackathons, ...) and social names (github, linkedin, ...) also work as commands. Tab completes commands, pages after cd, themes after theme and categories after projects; when several match it lists them like bash. Up/Down walk history, Ctrl+L clears.

Commands live in src/lib/terminal/commands/.

Code layout

Path Contents
src/lib/content/ typed loader for content/
src/lib/blog/ markdown renderer, frontmatter, highlighting, dates
src/lib/theme/ theme palettes, stylesheet generation, current theme state
src/lib/desktop/ window manager, dwindle tiling, keybinds, desktop actions
src/lib/terminal/ terminal output model and commands
src/lib/site/pages.ts page list used by the terminal and launcher
src/lib/components/shell/ bar, workspaces, clock, theme menu, Discord status
src/lib/server/discord.ts Discord bot and presence lookup
src/lib/components/desktop/ desktop, windows, icons, launcher
src/lib/components/terminal/ kitty terminal
src/lib/components/ui/ shared page pieces
src/routes/ pages

Discord status

The bar shows live Discord presence (status, avatar, guild tag, activities). A discord.js bot starts with the server (src/hooks.server.ts) and the bar polls /api/discord every 30 seconds. Copy .env.example to .env and fill in:

Variable Value
DISCORD_TOKEN bot token, with the Server Members and Presence intents enabled
DISCORD_USER_ID your user id
DISCORD_GUILD_ID a server both you and the bot are in

Variables are declared in src/env.ts. Without them the widget shows your profile name as offline.

Analytics

Umami is loaded in src/app.html.

Languages
Svelte 45.5%
TypeScript 33%
Python 21%
CSS 0.3%