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 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.