Add mobile styles, social icons and README badges

This commit is contained in:
SirBlob committed 2026-10-26 16:08:26 -04:00
1 parent be028d6934
commit a0fd2fa2a1
23 files changed
+225 -174

No files matched your search

+55 -142
View File
@@ -1,152 +1,65 @@
# Sir Blob's Desktop <div align="center">
<h1>Sir Blob's Desktop</h1>
<p>My portfolio website, built as a web version of my Arch Linux desktop.</p>
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. <img src="https://img.shields.io/badge/Arch_Linux-1793D1?style=for-the-badge&logo=arch-linux&logoColor=white" alt="Arch Linux" />
<img src="https://img.shields.io/badge/Hyprland-00A86B?style=for-the-badge&logo=hyprland&logoColor=white" alt="Hyprland" />
<img src="https://img.shields.io/badge/SvelteKit-FF3E00?style=for-the-badge&logo=svelte&logoColor=white" alt="SvelteKit" />
<img src="https://img.shields.io/badge/TypeScript-3178C6?style=for-the-badge&logo=typescript&logoColor=white" alt="TypeScript" />
<img src="https://img.shields.io/badge/Tailwind_CSS-06B6D4?style=for-the-badge&logo=tailwindcss&logoColor=white" alt="Tailwind CSS" />
<img src="https://img.shields.io/badge/Three.js-000000?style=for-the-badge&logo=threedotjs&logoColor=white" alt="Three.js" />
<img src="https://img.shields.io/badge/Bun-000000?style=for-the-badge&logo=bun&logoColor=white" alt="Bun" />
</div>
---
The site recreates my [blomarchy](https://git.sirblob.co/SirBlob/blomarchy) setup in the browser: a Hyprland-style tiling desktop, the Quickshell bar, and kitty terminals you can type into.
## Features
- **Tiling windows** - every page opens as a window that tiles like Hyprland's dwindle layout, with gradient borders.
- **Quickshell bar** - app launcher, workspaces, clock, live Discord status, battery and a theme picker.
- **Kitty terminals** - a real command line with history and tab completion. Try `fastfetch`, `whoami` or `cowsay`.
- **Desktop icons** - grouped into pages, terminal scripts and socials (GitHub, LinkedIn, Devpost, Discord).
- **13 themes** - Flats, Retro 82, Tokyo Night, Catppuccin, Arch Linux, Gruvbox, Nord, Kanagawa, Wintry, Rose, Cerberus, Catppuccin Latte and Rose Pine Dawn, each with its own wallpaper.
- **Portfolio** - about, portfolio, projects (open source, packages, hackathons, research), blog with RSS, 3D models and links with QR codes.
- **Mobile friendly** - on small screens windows go fullscreen with tabs to switch between them.
## Keybinds
| Keys | Action |
| --- | --- |
| Alt + Enter | Open a terminal |
| Alt + Space | App launcher |
| Alt + Q | Close window |
| Alt + F | Fullscreen page |
| Alt + J / K | Next / previous window |
| Alt + 1-9 | Switch workspace |
| Alt + T | Next theme |
Alt stands in for Super, since browsers can't use it.
## Terminal commands
| Command | Does |
| --- | --- |
| `help` | list every command |
| `whoami` | who I am |
| `fastfetch` | system info |
| `skills` | skills by category |
| `experience` | work experience |
| `projects [category]` | list my projects |
| `contact` | where to find me |
| `ls` / `cd <page>` | browse and open pages |
| `theme [name]` | list or switch themes |
| `cowsay <text>` | moo |
## Running ## 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.
```bash ```bash
bun install bun install
bun run dev bun run dev
bun run build
bun run start bun run start
bun run check
``` ```
## How the desktop works Site content lives in `content/`.
- 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.
```json
{
"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:
```json
{ "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.
```markdown
---
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`.
+22 -4
View File
@@ -9,6 +9,9 @@
{ "number": 6, "name": "portfolio", "path": "/portfolio" }, { "number": 6, "name": "portfolio", "path": "/portfolio" },
{ "number": 7, "name": "models", "path": "/models" } { "number": 7, "name": "models", "path": "/models" }
], ],
"iconGroups": [
{
"name": "pages",
"icons": [ "icons": [
{ "label": "about", "icon": "ph:user-circle-duotone", "open": { "page": "/about" } }, { "label": "about", "icon": "ph:user-circle-duotone", "open": { "page": "/about" } },
{ "label": "portfolio", "icon": "ph:briefcase-duotone", "open": { "page": "/portfolio" } }, { "label": "portfolio", "icon": "ph:briefcase-duotone", "open": { "page": "/portfolio" } },
@@ -16,13 +19,28 @@
{ "label": "hackathons", "icon": "ph:trophy-duotone", "open": { "page": "/projects/hackathons" } }, { "label": "hackathons", "icon": "ph:trophy-duotone", "open": { "page": "/projects/hackathons" } },
{ "label": "blog", "icon": "ph:notebook-duotone", "open": { "page": "/blog" } }, { "label": "blog", "icon": "ph:notebook-duotone", "open": { "page": "/blog" } },
{ "label": "models", "icon": "ph:cube-duotone", "open": { "page": "/models" } }, { "label": "models", "icon": "ph:cube-duotone", "open": { "page": "/models" } },
{ "label": "links", "icon": "ph:qr-code-duotone", "open": { "page": "/links" } }, { "label": "links", "icon": "ph:qr-code-duotone", "open": { "page": "/links" } }
]
},
{
"name": "scripts",
"icons": [
{ "label": "kitty", "icon": "ph:terminal-window-duotone", "open": { "terminal": "" } }, { "label": "kitty", "icon": "ph:terminal-window-duotone", "open": { "terminal": "" } },
{ "label": "whoami.sh", "icon": "ph:identification-card-duotone", "open": { "terminal": "whoami" } },
{ "label": "fastfetch.sh", "icon": "ph:cpu-duotone", "open": { "terminal": "fastfetch" } }, { "label": "fastfetch.sh", "icon": "ph:cpu-duotone", "open": { "terminal": "fastfetch" } },
{ "label": "whoami.sh", "icon": "ph:identification-card-duotone", "open": { "terminal": "whoami" } },
{ "label": "skills.sh", "icon": "ph:lightning-duotone", "open": { "terminal": "skills" } }, { "label": "skills.sh", "icon": "ph:lightning-duotone", "open": { "terminal": "skills" } },
{ "label": "contact.sh", "icon": "ph:envelope-simple-duotone", "open": { "terminal": "contact" } }, { "label": "contact.sh", "icon": "ph:envelope-simple-duotone", "open": { "terminal": "contact" } }
{ "label": "github", "icon": "ph:github-logo-duotone", "open": { "url": "https://github.com/SirBlobby" } } ]
},
{
"name": "socials",
"icons": [
{ "label": "github", "icon": "ph:github-logo-duotone", "open": { "url": "https://github.com/SirBlobby" } },
{ "label": "linkedin", "icon": "ph:linkedin-logo-duotone", "open": { "url": "https://www.linkedin.com/in/gmanjunatha/" } },
{ "label": "devpost", "icon": "simple-icons:devpost", "open": { "url": "https://devpost.com/Sir_Blob_" } },
{ "label": "discord", "icon": "ph:discord-logo-duotone", "open": { "url": "https://discord.com/users/sir_blob_" } }
]
}
], ],
"startupCommand": "fastfetch" "startupCommand": "fastfetch"
} }
+2 -1
View File
@@ -1,7 +1,8 @@
[ [
{ {
"role": "Undergraduate Research Assistant, CREATE Lab", "role": "Undergraduate Research Assistant, CREATE Lab",
"organization": "George Mason University, College of Engineering and Computing", "organization": "College of Engineering and Computing",
"school": "George Mason University",
"period": "May 2026 – Present", "period": "May 2026 – Present",
"highlights": [ "highlights": [
"Built Astroswarm, a visual coding game that turns swarm control into a playable simulator, generating trial data at scale on which algorithms best defend a target.", "Built Astroswarm, a visual coding game that turns swarm control into a playable simulator, generating trial data at scale on which algorithms best defend a target.",
+2 -2
View File
@@ -15,7 +15,7 @@
"image": "/hacks/learningbuddy.png", "image": "/hacks/learningbuddy.png",
"tags": ["C++", "ESP32-S3", "WebSocket", "MongoDB", "IoT"], "tags": ["C++", "ESP32-S3", "WebSocket", "MongoDB", "IoT"],
"featured": true, "featured": true,
"event": { "name": "HackFax x PatriotHacks 2026", "university": "George Mason University", "location": "Fairfax, VA", "year": "2026" }, "event": { "name": "HackFax x PatriotHacks 2026", "year": "2026" },
"awards": [{ "track": "Best use of MongoDB", "place": "Winner" }], "awards": [{ "track": "Best use of MongoDB", "place": "Winner" }],
"links": { "code": "https://github.com/SirBlobby/LearningBuddy", "devpost": "https://devpost.com/software/studybuddy-bc0i3j" } "links": { "code": "https://github.com/SirBlobby/LearningBuddy", "devpost": "https://devpost.com/software/studybuddy-bc0i3j" }
}, },
@@ -25,7 +25,7 @@
"image": "/hacks/carbin.png", "image": "/hacks/carbin.png",
"tags": ["AI", "Azure", "CloudConvert", "Python", "React", "TypeScript", "API"], "tags": ["AI", "Azure", "CloudConvert", "Python", "React", "TypeScript", "API"],
"featured": true, "featured": true,
"event": { "name": "PatriotHacks 2024", "university": "George Mason University", "location": "Fairfax, VA", "year": "2024" }, "event": { "name": "PatriotHacks 2024", "year": "2024" },
"awards": [ "awards": [
{ "track": "Save the World", "place": "2nd Place" }, { "track": "Save the World", "place": "2nd Place" },
{ "track": "Microsoft X Cloudforce", "place": "2nd Place" } { "track": "Microsoft X Cloudforce", "place": "2nd Place" }
+1 -1
View File
@@ -1,7 +1,7 @@
[ [
{ {
"name": "Astroswarm", "name": "Astroswarm",
"organization": "CREATE Lab, George Mason University", "organization": "CREATE Lab",
"period": "May 2026 – Present", "period": "May 2026 – Present",
"description": "A visual coding game that turns swarm control into a playable simulator, generating trial data at scale on which algorithms best defend a target. Part of a research program on controlling robot fleets with one operator.", "description": "A visual coding game that turns swarm control into a playable simulator, generating trial data at scale on which algorithms best defend a target. Part of a research program on controlling robot fleets with one operator.",
"tags": ["Python", "ROS", "Swarm Robotics"], "tags": ["Python", "ROS", "Swarm Robotics"],
+1 -1
View File
@@ -2,7 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<meta name="text-scale" content="scale" /> <meta name="text-scale" content="scale" />
<script> <script>
try { try {
+3 -8
View File
@@ -16,7 +16,6 @@
}: { pageOpen: boolean; pageTitle: string; pageIcon: string; pageKey: string; children: Snippet } = }: { pageOpen: boolean; pageTitle: string; pageIcon: string; pageKey: string; children: Snippet } =
$props(); $props();
const gapInPixels = 10;
const narrowWidth = 768; const narrowWidth = 768;
let areaWidth = $state(1280); let areaWidth = $state(1280);
@@ -44,7 +43,7 @@
function styleFor(id: string): string { function styleFor(id: string): string {
const rect = layout.get(id); const rect = layout.get(id);
return rect ? tileStyle(rect, gapInPixels) : 'display: none'; return rect ? tileStyle(rect, isNarrow ? 4 : 10) : 'display: none';
} }
</script> </script>
@@ -151,12 +150,8 @@
} }
@media (max-width: 767px) { @media (max-width: 767px) {
.icons-layer :global(.desktop-icons) { .icons-layer {
grid-auto-flow: row; overflow-y: auto;
grid-template-columns: repeat(auto-fill, 112px);
grid-template-rows: none;
grid-auto-rows: 104px;
justify-content: center;
} }
} }
</style> </style>
+21 -7
View File
@@ -2,10 +2,12 @@
import Icon from '@iconify/svelte'; import Icon from '@iconify/svelte';
import { desktop } from '#lib/content'; import { desktop } from '#lib/content';
import { runDesktopAction } from '#lib/desktop/actions'; import { runDesktopAction } from '#lib/desktop/actions';
const icons = desktop.iconGroups.flatMap((group) => group.icons);
</script> </script>
<nav class="desktop-icons" aria-label="Desktop"> <nav class="desktop-icons" aria-label="Desktop">
{#each desktop.icons as item (item.label)} {#each icons as item (item.label)}
<button class="desktop-icon" title={item.label} onclick={() => runDesktopAction(item.open)}> <button class="desktop-icon" title={item.label} onclick={() => runDesktopAction(item.open)}>
<span class="icon-box"> <span class="icon-box">
<Icon icon={item.icon} width="36" /> <Icon icon={item.icon} width="36" />
@@ -19,9 +21,9 @@
.desktop-icons { .desktop-icons {
display: grid; display: grid;
grid-auto-flow: column; grid-auto-flow: column;
grid-template-rows: repeat(auto-fill, 104px); grid-template-rows: repeat(auto-fill, 100px);
grid-auto-columns: 112px; grid-auto-columns: 116px;
gap: 6px; gap: 4px;
height: 100%; height: 100%;
padding: 4px; padding: 4px;
align-content: start; align-content: start;
@@ -32,7 +34,7 @@
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
gap: 7px; gap: 7px;
padding: 6px 4px; padding: 6px 2px;
background: none; background: none;
border: none; border: none;
color: var(--foreground); color: var(--foreground);
@@ -69,11 +71,12 @@
.icon-label { .icon-label {
max-width: 100%; max-width: 100%;
padding: 1px 6px; padding: 1px 6px;
overflow: hidden;
background: color-mix(in srgb, var(--background) 88%, transparent); background: color-mix(in srgb, var(--background) 88%, transparent);
font-size: 13px; font-size: 13px;
line-height: 1.35; line-height: 1.35;
text-align: center; white-space: nowrap;
word-break: break-word; text-overflow: ellipsis;
} }
.desktop-icon:hover .icon-label, .desktop-icon:hover .icon-label,
@@ -81,4 +84,15 @@
background: var(--accent); background: var(--accent);
color: var(--background); color: var(--background);
} }
@media (max-width: 767px) {
.desktop-icons {
grid-auto-flow: row;
grid-template-columns: repeat(auto-fill, 104px);
grid-template-rows: none;
grid-auto-rows: 100px;
justify-content: center;
height: auto;
}
}
</style> </style>
+16 -1
View File
@@ -19,7 +19,8 @@
open: { page: page.path } open: { page: page.path }
})); }));
const iconEntries: LauncherEntry[] = desktop.icons const iconEntries: LauncherEntry[] = desktop.iconGroups
.flatMap((group) => group.icons)
.filter((item) => !('page' in item.open)) .filter((item) => !('page' in item.open))
.map((item) => ({ .map((item) => ({
label: item.label, label: item.label,
@@ -172,4 +173,18 @@
.empty { .empty {
padding: 0.5rem; padding: 0.5rem;
} }
@media (max-width: 640px) {
.launcher {
top: calc(var(--bar-height) + 8px);
}
.results {
max-height: 60dvh;
}
.result {
padding: 0.6rem 0.5rem;
}
}
</style> </style>
+12
View File
@@ -144,5 +144,17 @@
flex: 1; flex: 1;
min-height: 0; min-height: 0;
overflow: auto; overflow: auto;
overscroll-behavior: contain;
}
@media (max-width: 640px) {
.title-bar {
height: 36px;
}
.close-button {
width: 34px;
height: 34px;
}
} }
</style> </style>
@@ -146,4 +146,11 @@
padding: 0.3rem 0.6rem; padding: 0.3rem 0.6rem;
font-size: 0.75rem; font-size: 0.75rem;
} }
@container (max-width: 600px) {
.canvas-area {
height: 280px;
touch-action: none;
}
}
</style> </style>
+6 -1
View File
@@ -78,7 +78,12 @@
@media (max-width: 640px) { @media (max-width: 640px) {
.bar { .bar {
font-size: 12px; padding: 0 4px;
font-size: 13px;
}
.bar :global(.tray-button) {
width: 36px;
} }
} }
</style> </style>
+13 -1
View File
@@ -20,13 +20,15 @@
<span class="clock full">{now ? formatClock(now) : ''}</span> <span class="clock full">{now ? formatClock(now) : ''}</span>
<span class="clock short">{now ? formatClock(now).split(' ').slice(1).join(' ') : ''}</span> <span class="clock short">{now ? formatClock(now).split(' ').slice(1).join(' ') : ''}</span>
<span class="clock time-only">{now ? formatClock(now).split(' ').at(-1) : ''}</span>
<style> <style>
.clock { .clock {
white-space: nowrap; white-space: nowrap;
} }
.short { .short,
.time-only {
display: none; display: none;
} }
@@ -39,4 +41,14 @@
display: inline; display: inline;
} }
} }
@media (max-width: 420px) {
.short {
display: none;
}
.time-only {
display: inline;
}
}
</style> </style>
+10
View File
@@ -55,4 +55,14 @@
padding: 0.4rem; padding: 0.4rem;
background: var(--background); background: var(--background);
} }
@media (max-width: 640px) {
.popup {
position: fixed;
top: calc(var(--bar-height) + 6px);
right: 8px;
left: 8px;
width: auto;
}
}
</style> </style>
+1 -1
View File
@@ -68,7 +68,7 @@
} }
.workspace { .workspace {
width: 20px; width: 22px;
} }
} }
-1
View File
@@ -16,7 +16,6 @@
['Name', profile.displayName], ['Name', profile.displayName],
['Title', profile.title], ['Title', profile.title],
['Uptime', `${uptimeDays} days`], ['Uptime', `${uptimeDays} days`],
['School', profile.education.school],
['Languages', (profile.skills.Languages ?? []).slice(0, 5).join(', ')] ['Languages', (profile.skills.Languages ?? []).slice(0, 5).join(', ')]
]); ]);
</script> </script>
@@ -142,6 +142,7 @@
.terminal { .terminal {
height: 100%; height: 100%;
overflow-y: auto; overflow-y: auto;
overscroll-behavior: contain;
padding: 0.75rem 1.1rem 1.5rem; padding: 0.75rem 1.1rem 1.5rem;
line-height: 1.5; line-height: 1.5;
cursor: text; cursor: text;
@@ -185,4 +186,15 @@
font: inherit; font: inherit;
caret-color: var(--foreground); caret-color: var(--foreground);
} }
@media (max-width: 640px) {
.terminal {
padding: 0.6rem 0.7rem 1.25rem;
font-size: 14px;
}
input {
font-size: 16px;
}
}
</style> </style>
+14
View File
@@ -45,4 +45,18 @@
p { p {
margin: 0.5rem 0 0; margin: 0.5rem 0 0;
} }
@container (max-width: 520px) {
.profile-intro {
gap: 0.9rem;
}
img {
width: 110px;
}
h1 {
font-size: 1.3rem;
}
}
</style> </style>
+7 -1
View File
@@ -34,6 +34,7 @@ export interface LabeledLink {
export interface Experience { export interface Experience {
role: string; role: string;
organization: string; organization: string;
school?: string;
employmentType?: string; employmentType?: string;
period: string; period: string;
highlights: string[]; highlights: string[];
@@ -128,6 +129,11 @@ export interface Model3D {
export type DesktopAction = { page: string } | { terminal: string } | { url: string }; export type DesktopAction = { page: string } | { terminal: string } | { url: string };
export interface DesktopIconGroup {
name: string;
icons: DesktopIcon[];
}
export interface DesktopIcon { export interface DesktopIcon {
label: string; label: string;
icon: string; icon: string;
@@ -143,7 +149,7 @@ export interface Workspace {
export interface DesktopContent { export interface DesktopContent {
defaultTheme: string; defaultTheme: string;
workspaces: Workspace[]; workspaces: Workspace[];
icons: DesktopIcon[]; iconGroups: DesktopIconGroup[];
startupCommand: string; startupCommand: string;
} }
-1
View File
@@ -23,7 +23,6 @@ function whoamiOutput(): OutputBlock[] {
line(span('─'.repeat(header.length), 'muted')), line(span('─'.repeat(header.length), 'muted')),
field('alias', profile.displayName), field('alias', profile.displayName),
field('role', profile.title), field('role', profile.title),
field('school', `${profile.education.school}, ${profile.education.degree}`),
...(currentRole ? [field('now', span(currentRole.role, 'orange'), span(' @ ', 'muted'), currentRole.organization)] : []), ...(currentRole ? [field('now', span(currentRole.role, 'orange'), span(' @ ', 'muted'), currentRole.organization)] : []),
...(interests.length ? [field('interests', ...joinWithDots(interests.map((interest) => span(interest, 'magenta'))))] : []), ...(interests.length ? [field('interests', ...joinWithDots(interests.map((interest) => span(interest, 'magenta'))))] : []),
blankLine, blankLine,
+1
View File
@@ -55,6 +55,7 @@
flex-direction: column; flex-direction: column;
height: 100vh; height: 100vh;
height: 100dvh; height: 100dvh;
padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
background-color: var(--dark-background); background-color: var(--dark-background);
background-image: var(--wallpaper), radial-gradient(circle at 70% 30%, var(--lighter-background), var(--dark-background)); background-image: var(--wallpaper), radial-gradient(circle at 70% 30%, var(--lighter-background), var(--dark-background));
background-size: cover; background-size: cover;
+18
View File
@@ -34,6 +34,8 @@ body {
font-weight: 600; font-weight: 600;
font-size: 16px; font-size: 16px;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-webkit-text-size-adjust: 100%;
text-size-adjust: 100%;
} }
a { a {
@@ -94,3 +96,19 @@ button {
.muted { .muted {
color: var(--muted); color: var(--muted);
} }
@media (max-width: 640px) {
:root {
--gaps-out: 6px;
--border-size: 2px;
--bar-height: 40px;
}
.page {
padding: 0.6rem 0.85rem 1.5rem;
}
.card-grid {
grid-template-columns: 1fr;
}
}
+1 -1
View File
@@ -48,7 +48,7 @@
<article class="entry"> <article class="entry">
<h3>{job.role}</h3> <h3>{job.role}</h3>
<div class="organization"> <div class="organization">
{job.organization}{#if job.employmentType}<span class="muted"> · {job.employmentType}</span>{/if} {#if job.school}{job.school}, {/if}{job.organization}{#if job.employmentType}<span class="muted"> · {job.employmentType}</span>{/if}
</div> </div>
<div class="muted">{job.period}</div> <div class="muted">{job.period}</div>
{#each job.awards ?? [] as award (award)} {#each job.awards ?? [] as award (award)}