add projects dropdown
All checks were successful
Test and deploy staging / staging (push) Successful in 1m48s

This commit is contained in:
2026-09-23 16:44:21 +00:00
parent 4b63e9ca11
commit 3691a85fdf
3 changed files with 131 additions and 39 deletions

38
src/lib/projects.ts Normal file
View File

@@ -0,0 +1,38 @@
export const projects = [
{
name: 'vash esports',
subtitle: 'the best way to play osu!',
href: 'https://esports.vash.software',
external: true
},
{
name: 'catchemall',
subtitle: 'catch 1025 pokemon across 53 games',
href: '/projects/catch-em-all',
external: false
},
{
name: 'animaldex',
subtitle: 'add neighborhood pets to the world map',
href: '/projects/animaldex',
external: false
},
{
name: 'osu! map finder',
subtitle: 'find osu! maps for Spotify songs/albums/playlists',
href: '/projects/spotify-beatmap-finder',
external: false
},
{
name: 'derby lens',
subtitle: 'estimate odds for horse betting',
href: '/projects/derby',
external: false
},
{
name: 'money tracker',
subtitle: 'a manual ledger for accounts, bills and utilities',
href: '/projects/money-tracker',
external: false
}
] as const;

View File

@@ -6,6 +6,8 @@
import { page } from '$app/state';
import { getSeo } from '$lib/seo';
import CopyButton from '$lib/components/CopyButton.svelte';
import { projects } from '$lib/projects';
import ChevronDown from 'lucide-svelte/icons/chevron-down';
import ExternalLink from 'lucide-svelte/icons/external-link';
import SiOsu from '~icons/simple-icons/osu';
import SiSteam from '~icons/simple-icons/steam';
@@ -171,6 +173,65 @@
</a>
<nav class="ml-auto hidden items-center gap-6 text-base md:flex" aria-label="Main navigation">
{#each navigation as item (item.href)}
{#if item.href === '/projects'}
<div class="group relative">
<a
href={resolve(item.href)}
aria-current={current(item.href) ? 'page' : undefined}
aria-haspopup="true"
class="inline-flex items-center gap-1 {current(item.href)
? 'text-[var(--site-text)]'
: 'text-[var(--site-muted)] hover:text-[var(--site-text)]'}"
>{item.label}
<ChevronDown
size={14}
aria-hidden="true"
class="transition-transform group-focus-within:rotate-180 group-hover:rotate-180"
/></a
>
<div
class="invisible absolute top-full left-1/2 z-50 -translate-x-1/2 pt-3 opacity-0 transition-opacity group-focus-within:visible group-focus-within:opacity-100 group-hover:visible group-hover:opacity-100"
>
<ul
class="w-72 rounded-md border border-[var(--site-border)] bg-[var(--site-bg)] py-2 shadow-lg"
>
{#each projects as project (project.name)}
<li>
{#if project.external}
<a
href={project.href}
rel="external"
class="flex items-center justify-between gap-3 px-4 py-2 hover:bg-[var(--site-surface)] focus-visible:bg-[var(--site-surface)]"
>
<span class="min-w-0">
<span class="block text-[var(--site-text)]">{project.name}</span>
<span class="block truncate text-sm text-[var(--site-muted)]"
>{project.subtitle}</span
>
</span><ExternalLink
size={14}
aria-hidden="true"
class="shrink-0 text-[var(--site-muted)]"
/>
</a>
{:else}
<a
href={resolve(project.href)}
aria-current={current(project.href) ? 'page' : undefined}
class="block px-4 py-2 hover:bg-[var(--site-surface)] focus-visible:bg-[var(--site-surface)]"
>
<span class="block text-[var(--site-text)]">{project.name}</span>
<span class="block truncate text-sm text-[var(--site-muted)]"
>{project.subtitle}</span
>
</a>
{/if}
</li>
{/each}
</ul>
</div>
</div>
{:else}
<a
href={resolve(item.href)}
aria-current={current(item.href) ? 'page' : undefined}
@@ -178,6 +239,7 @@
? 'text-[var(--site-text)]'
: 'text-[var(--site-muted)] hover:text-[var(--site-text)]'}>{item.label}</a
>
{/if}
{/each}
</nav>
<div class="ml-auto hidden items-center gap-5 text-base md:ml-4 md:flex">
@@ -222,6 +284,29 @@
<a href={resolve(item.href)} onclick={() => (menuOpen = false)} class="w-full py-2"
>{item.label}</a
>
{#if item.href === '/projects'}
<ul class="mb-2 flex w-full flex-col text-[var(--site-muted)]">
{#each projects as project (project.name)}
<li>
{#if project.external}
<a
href={project.href}
rel="external"
onclick={() => (menuOpen = false)}
class="inline-flex items-center gap-1 py-1.5 hover:text-[var(--site-text)]"
>{project.name} <ExternalLink size={12} aria-hidden="true" /></a
>
{:else}
<a
href={resolve(project.href)}
onclick={() => (menuOpen = false)}
class="block py-1.5 hover:text-[var(--site-text)]">{project.name}</a
>
{/if}
</li>
{/each}
</ul>
{/if}
{/each}
<div
class="mt-3 flex w-full items-center justify-center gap-5 border-t border-[var(--site-border)] pt-4"

View File

@@ -2,6 +2,7 @@
import { resolve } from '$app/paths';
import portrait from '$lib/assets/stan.jpg?url';
import ProgressBar from '$lib/components/ProgressBar.svelte';
import { projects } from '$lib/projects';
import ArrowRight from 'lucide-svelte/icons/arrow-right';
import ExternalLink from 'lucide-svelte/icons/external-link';
import Mail from 'lucide-svelte/icons/mail';
@@ -9,38 +10,6 @@
let { data }: PageProps = $props();
const projects = [
{
name: 'vash esports',
subtitle: 'the best way to play osu!',
href: 'https://esports.vash.software',
external: true
},
{
name: 'catchemall',
subtitle: 'catch 1025 pokemon across 53 games',
href: '/projects/catch-em-all',
external: false
},
{
name: 'animaldex',
subtitle: 'add neighborhood pets to the world map',
href: '/projects/animaldex',
external: false
},
{
name: 'osu! map finder',
subtitle: 'find osu! maps for Spotify songs/albums/playlists',
href: '/projects/spotify-beatmap-finder',
external: false
},
{
name: 'derby lens',
subtitle: 'estimate odds for horse betting',
href: '/projects/derby',
external: false
}
] as const;
const experience = [
{ when: '2026–now', role: 'associate software engineer', place: 'Cargill' },
{