add projects dropdown
All checks were successful
Test and deploy staging / staging (push) Successful in 1m48s
All checks were successful
Test and deploy staging / staging (push) Successful in 1m48s
This commit is contained in:
38
src/lib/projects.ts
Normal file
38
src/lib/projects.ts
Normal 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;
|
||||
@@ -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,13 +173,73 @@
|
||||
</a>
|
||||
<nav class="ml-auto hidden items-center gap-6 text-base md:flex" aria-label="Main navigation">
|
||||
{#each navigation as item (item.href)}
|
||||
<a
|
||||
href={resolve(item.href)}
|
||||
aria-current={current(item.href) ? 'page' : undefined}
|
||||
class={current(item.href)
|
||||
? 'text-[var(--site-text)]'
|
||||
: 'text-[var(--site-muted)] hover:text-[var(--site-text)]'}>{item.label}</a
|
||||
>
|
||||
{#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}
|
||||
class={current(item.href)
|
||||
? '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"
|
||||
|
||||
@@ -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' },
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user