ui changes and cs prices
All checks were successful
Test and deploy staging / staging (push) Successful in 11m52s

This commit is contained in:
2026-09-25 00:44:06 +00:00
parent c757d0882a
commit abac077541
200 changed files with 6236 additions and 2311 deletions

View File

@@ -6,9 +6,14 @@
import { page } from '$app/state';
import { getSeo } from '$lib/seo';
import CopyButton from '$lib/components/CopyButton.svelte';
import ProjectIcon from '$lib/components/ProjectIcon.svelte';
import { projects } from '$lib/projects';
import { Button, buttonVariants } from '$lib/components/ui/button/index.js';
import * as Sheet from '$lib/components/ui/sheet/index.js';
import ArrowRight from 'lucide-svelte/icons/arrow-right';
import ChevronDown from 'lucide-svelte/icons/chevron-down';
import ExternalLink from 'lucide-svelte/icons/external-link';
import Menu from 'lucide-svelte/icons/menu';
import SiOsu from '~icons/simple-icons/osu';
import SiSteam from '~icons/simple-icons/steam';
import SiTwitch from '~icons/simple-icons/twitch';
@@ -26,6 +31,7 @@
let { data, children } = $props();
const seo = $derived(getSeo(page.url.pathname, page.data, data.environment));
let menuOpen = $state(false);
let projectsMenuOpen = $state(false);
type ThemePreference = 'dark' | 'system' | 'light';
let theme = $state<ThemePreference>('dark');
const navigation = [
@@ -76,6 +82,7 @@
: preference;
document.documentElement.dataset.themePreference = preference;
document.documentElement.dataset.theme = resolvedTheme;
document.documentElement.classList.toggle('dark', resolvedTheme === 'dark');
document
.querySelector('meta[name="theme-color"]')
?.setAttribute('content', resolvedTheme === 'light' ? '#ffffff' : '#000000');
@@ -94,6 +101,16 @@
function current(href: string) {
return page.url.pathname === href || (href !== '/' && page.url.pathname.startsWith(href + '/'));
}
function handleProjectsPointerEnter(event: PointerEvent) {
if (event.pointerType === 'touch') return;
projectsMenuOpen = true;
}
function handleProjectsPointerLeave(event: PointerEvent) {
if (event.pointerType === 'touch') return;
projectsMenuOpen = false;
}
</script>
{#snippet themeIcon()}
@@ -153,183 +170,177 @@
class="sr-only focus:not-sr-only focus:absolute focus:z-50 focus:bg-[var(--site-bg)] focus:px-4 focus:py-2"
>skip to content</a
>
<header class="border-b border-[var(--site-border)]">
<div class="mx-auto flex max-w-6xl items-center gap-5 px-4 py-4 sm:px-6">
<header class="border-b border-border bg-background/95 backdrop-blur">
<div class="mx-auto flex max-w-6xl items-center gap-3 px-4 py-3 sm:px-6">
<a
href={resolve('/')}
class="flex shrink-0 items-center gap-3 text-base font-semibold tracking-tight"
onclick={() => (menuOpen = false)}
class="flex shrink-0 items-center gap-3 font-semibold tracking-tight"
aria-label="Stan Runge home"
>
<span class="h-7 w-7 overflow-hidden rounded-full bg-black">
<img
<span class="h-7 w-7 overflow-hidden rounded-full bg-black"
><img
src="{assets}/favicon-32x32.png"
alt=""
width="32"
height="32"
class="h-full w-full scale-[1.7]"
/>
</span>
/></span
>
<span>stan runge</span>
</a>
<nav class="ml-auto hidden items-center gap-6 text-base md:flex" aria-label="Main navigation">
<nav class="ml-auto hidden items-center gap-1 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}
<div
class="relative"
role="group"
aria-label="Projects menu"
onpointerenter={handleProjectsPointerEnter}
onpointerleave={handleProjectsPointerLeave}
>
<Button
href={resolve('/projects')}
variant={current(item.href) ? 'secondary' : 'ghost'}
size="sm"
class="cursor-pointer gap-1"
aria-label="Browse projects"
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
aria-expanded={projectsMenuOpen}
onclick={() => (projectsMenuOpen = false)}
onkeydown={(event) => {
if (event.key === 'Escape') projectsMenuOpen = false;
}}>{item.label}<ChevronDown size={15} /></Button
>
<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"
{#if projectsMenuOpen}
<div
class="absolute top-full left-0 z-50 max-h-[70vh] w-72 overflow-y-auto rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md"
aria-label="Projects"
>
<a
href={resolve('/projects')}
class="flex items-center rounded-sm px-2 py-1.5 text-sm font-medium outline-none hover:bg-accent hover:text-accent-foreground focus-visible:bg-accent"
>all projects <ArrowRight size={14} class="ml-auto" /></a
>
<div class="my-1 h-px bg-border"></div>
{#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>
<a
href={project.external ? project.href : resolve(project.href)}
rel={project.external ? 'external' : undefined}
class="flex items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent hover:text-accent-foreground focus-visible:bg-accent"
>
<span
class="flex size-8 shrink-0 items-center justify-center rounded border border-border bg-[var(--site-surface)] text-[var(--site-muted)]"
>
<ProjectIcon name={project.name} class="size-4" />
</span>
<span class="min-w-0"
><span class="block font-medium">{project.name}</span><span
class="block truncate text-xs text-muted-foreground"
>{project.subtitle}</span
></span
>
{#if project.external}<ExternalLink size={14} class="ml-auto shrink-0" />{/if}
</a>
{/each}
</ul>
</div>
</div>
{/if}
</div>
{:else}
<a
<Button
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
variant={current(item.href) ? 'secondary' : 'ghost'}
size="sm">{item.label}</Button
>
{/if}
{/each}
</nav>
<div class="ml-auto hidden items-center gap-5 text-base md:ml-4 md:flex">
<button
type="button"
<div class="hidden items-center gap-1 md:flex">
<Button
variant="ghost"
size="icon"
onclick={toggleTheme}
aria-label={`Theme: ${theme}. Switch to ${theme === 'dark' ? 'system' : theme === 'system' ? 'light' : 'dark'} mode`}
title={`Theme: ${theme}`}
class="flex h-10 w-10 items-center justify-center text-[var(--site-muted)] hover:text-[var(--site-text)]"
>{@render themeIcon()}</button
aria-label={`Theme: ${theme}. Switch mode`}
title={`Theme: ${theme}`}>{@render themeIcon()}</Button
>
{#if data.user}
<a href={resolve('/account')} class="max-w-28 truncate hover:underline"
>{data.user.name}</a
<Button href={resolve('/account')} variant="outline" size="sm" class="max-w-36 truncate"
>{data.user.name}</Button
>
{:else}
<a
<Button
href={resolve(
`/login?returnTo=${encodeURIComponent(page.url.pathname + page.url.search)}`
)}
class="hover:underline">sign in</a
variant="outline"
size="sm">sign in</Button
>
{/if}
</div>
<button
type="button"
class="ml-auto text-base text-[var(--site-muted)] md:hidden"
aria-label={menuOpen ? 'Close menu' : 'Open menu'}
aria-expanded={menuOpen}
aria-controls="mobile-menu"
onclick={() => (menuOpen = !menuOpen)}>{menuOpen ? 'close' : 'menu'}</button
>
</div>
{#if menuOpen}
<nav
id="mobile-menu"
class="border-t border-[var(--site-border)] px-4 py-4 md:hidden"
aria-label="Mobile navigation"
>
<div class="mx-auto flex max-w-6xl flex-col items-center gap-1 text-center text-base">
{#each navigation as item (item.href)}
<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"
<Sheet.Root bind:open={menuOpen}>
<Sheet.Trigger
class={buttonVariants({ variant: 'ghost', size: 'icon' }) + ' ml-auto md:hidden'}
aria-label="Open navigation"><Menu size={20} /></Sheet.Trigger
>
<Sheet.Content side="right" class="overflow-y-auto p-6">
<Sheet.Header
><Sheet.Title>stan runge</Sheet.Title><Sheet.Description>Navigation</Sheet.Description
></Sheet.Header
>
<button
type="button"
<nav class="flex flex-col gap-1" aria-label="Mobile navigation">
{#each navigation as item (item.href)}
<Button
href={resolve(item.href)}
variant={current(item.href) ? 'secondary' : 'ghost'}
class="justify-start"
onclick={() => (menuOpen = false)}>{item.label}</Button
>
{#if item.href === '/projects'}
<div class="ml-3 flex flex-col border-l border-border pl-3">
{#each projects as project (project.name)}
<Button
href={project.external ? project.href : resolve(project.href)}
variant="ghost"
size="sm"
class="justify-start"
onclick={() => (menuOpen = false)}>{project.name}</Button
>
{/each}
</div>
{/if}
{/each}
</nav>
<Sheet.Footer
class="mt-auto flex-row items-center justify-between border-t border-border pt-4"
>
<Button
variant="ghost"
size="icon"
onclick={toggleTheme}
aria-label={`Theme: ${theme}. Switch to ${theme === 'dark' ? 'system' : theme === 'system' ? 'light' : 'dark'} mode`}
title={`Theme: ${theme}`}
class="flex h-10 w-10 items-center justify-center text-[var(--site-muted)]"
>{@render themeIcon()}</button
aria-label={`Theme: ${theme}. Switch mode`}>{@render themeIcon()}</Button
>
{#if data.user}
<a href={resolve('/account')} onclick={() => (menuOpen = false)}>account</a>
<form method="POST" action="/logout"><button>sign out</button></form>
<Button
href={resolve('/account')}
variant="outline"
onclick={() => (menuOpen = false)}>account</Button
>
<form method="POST" action="/logout">
<Button type="submit" variant="outline">sign out</Button>
</form>
{:else}
<a href={resolve('/login')} onclick={() => (menuOpen = false)}>sign in</a>
<a href={resolve('/register')} onclick={() => (menuOpen = false)}>register</a>
<Button href={resolve('/login')} variant="outline" onclick={() => (menuOpen = false)}
>sign in</Button
>
<Button
href={resolve('/register')}
variant="outline"
onclick={() => (menuOpen = false)}>register</Button
>
{/if}
</div>
</div>
</nav>
{/if}
</Sheet.Footer>
</Sheet.Content>
</Sheet.Root>
</div>
</header>
<main id="main" class="min-w-0 flex-1">{@render children()}</main>
<footer class="mt-16 border-t border-[var(--site-border)]">
@@ -349,13 +360,13 @@
</div>
<nav class="flex flex-wrap items-center justify-center gap-1" aria-label="Social links">
{#each socialLinks as { icon: Icon, label, href } (label)}
<a
<Button
{href}
variant="ghost"
size="icon-lg"
rel="external"
aria-label={label}
title={label}
class="flex h-10 w-10 items-center justify-center text-xl hover:text-[var(--site-text)]"
><Icon /></a
title={label}><Icon class="size-6" /></Button
>
{/each}
{#each copyLinks as item (item.label)}<CopyButton {...item} />{/each}