remove emojis with real icons
All checks were successful
Test and deploy staging / staging (push) Successful in 1m46s

This commit is contained in:
2026-09-23 16:12:41 +00:00
parent f1cd0ef849
commit 4b63e9ca11
12 changed files with 137 additions and 50 deletions

View File

@@ -1,4 +1,5 @@
<script lang="ts">
import Music2 from 'lucide-svelte/icons/music-2';
let { src, class: className = '' }: { src?: string; class?: string } = $props();
let failed = $state(false);
$effect(() => {
@@ -18,6 +19,6 @@
class="flex h-full items-center justify-center text-2xl text-gray-500"
aria-hidden="true"
>
♫
<Music2 size={28} />
</div>{/if}
</div>

View File

@@ -6,9 +6,6 @@ import postgres from 'postgres';
const client = postgres('postgresql://postgres:postgres@localhost:5432/db');
const db = drizzle(client, { schema });
await db.insert(topics).values([
{ name: 'Math', emoji: '👍' },
{ name: 'Programming', emoji: '👍' }
]);
await db.insert(topics).values([{ name: 'Math' }, { name: 'Programming' }]);
process.exit(0);

View File

@@ -6,6 +6,7 @@
import { page } from '$app/state';
import { getSeo } from '$lib/seo';
import CopyButton from '$lib/components/CopyButton.svelte';
import ExternalLink from 'lucide-svelte/icons/external-link';
import SiOsu from '~icons/simple-icons/osu';
import SiSteam from '~icons/simple-icons/steam';
import SiTwitch from '~icons/simple-icons/twitch';
@@ -257,7 +258,8 @@
<a
href="https://git.stanrunge.dev/stan/personal-website"
rel="external"
class="hover:text-[var(--site-text)]">source ↗</a
class="inline-flex items-center gap-1 hover:text-[var(--site-text)]"
>source <ExternalLink size={14} aria-hidden="true" /></a
>
</div>
<nav class="flex flex-wrap items-center justify-center gap-1" aria-label="Social links">

View File

@@ -2,6 +2,9 @@
import { resolve } from '$app/paths';
import portrait from '$lib/assets/stan.jpg?url';
import ProgressBar from '$lib/components/ProgressBar.svelte';
import ArrowRight from 'lucide-svelte/icons/arrow-right';
import ExternalLink from 'lucide-svelte/icons/external-link';
import Mail from 'lucide-svelte/icons/mail';
import type { PageProps } from './$types';
let { data }: PageProps = $props();
@@ -21,7 +24,7 @@
},
{
name: 'animaldex',
subtitle: 'add neighborhood pets to the world map 😼',
subtitle: 'add neighborhood pets to the world map',
href: '/projects/animaldex',
external: false
},
@@ -68,15 +71,26 @@
<a
href="https://git.stanrunge.dev/stan"
rel="external"
class="hover:text-[var(--site-text)]">git ↗</a
class="inline-flex items-center gap-1 hover:text-[var(--site-text)]"
>git <ExternalLink size={14} aria-hidden="true" /></a
>
<a href="https://x.com/stanrunge" rel="external" class="hover:text-[var(--site-text)]"
>twitter ↗</a
<a
href="https://x.com/stanrunge"
rel="external"
class="inline-flex items-center gap-1 hover:text-[var(--site-text)]"
>twitter <ExternalLink size={14} aria-hidden="true" /></a
>
<a href="https://esports.vash.software" rel="external" class="hover:text-[var(--site-text)]"
>vash esports ↗</a
<a
href="https://esports.vash.software"
rel="external"
class="inline-flex items-center gap-1 hover:text-[var(--site-text)]"
>vash esports <ExternalLink size={14} aria-hidden="true" /></a
>
<a
href="mailto:stan@stanrunge.dev"
class="inline-flex items-center gap-1 hover:text-[var(--site-text)]"
>email <Mail size={14} aria-hidden="true" /></a
>
<a href="mailto:stan@stanrunge.dev" class="hover:text-[var(--site-text)]">email ↗</a>
</nav>
</div>
<img
@@ -93,7 +107,11 @@
<section aria-labelledby="projects-heading" class="border-t border-[var(--site-border)] pt-8">
<h2 id="projects-heading" class="mb-4 text-2xl font-semibold tracking-tight">
<a href={resolve('/projects')} class="hover:text-[var(--site-muted)]">projects ↗</a>
<a
href={resolve('/projects')}
class="inline-flex items-center gap-1 hover:text-[var(--site-muted)]"
>projects <ArrowRight size={20} aria-hidden="true" /></a
>
</h2>
<ul>
{#each projects as project (project.name)}
@@ -108,7 +126,11 @@
<span class="block">{project.name}</span>
<span class="mt-1 block text-base text-[var(--site-muted)]">{project.subtitle}</span
>
</span><span aria-hidden="true" class="shrink-0 text-[var(--site-muted)]">↗</span>
</span><ExternalLink
size={18}
aria-hidden="true"
class="shrink-0 text-[var(--site-muted)]"
/>
</a>
{:else}
<a
@@ -119,7 +141,11 @@
<span class="block">{project.name}</span>
<span class="mt-1 block text-base text-[var(--site-muted)]">{project.subtitle}</span
>
</span><span aria-hidden="true" class="shrink-0 text-[var(--site-muted)]">↗</span>
</span><ArrowRight
size={18}
aria-hidden="true"
class="shrink-0 text-[var(--site-muted)]"
/>
</a>
{/if}
</li>
@@ -129,7 +155,11 @@
<section aria-labelledby="lists-heading" class="pt-14 sm:pt-20">
<h2 id="lists-heading" class="mb-4 text-2xl font-semibold tracking-tight">
<a href={resolve('/lists')} class="hover:text-[var(--site-muted)]">lists ↗</a>
<a
href={resolve('/lists')}
class="inline-flex items-center gap-1 hover:text-[var(--site-muted)]"
>lists <ArrowRight size={20} aria-hidden="true" /></a
>
</h2>
<ul>
{#each data.lists as list (list.id)}
@@ -140,12 +170,13 @@
>
<span class="flex flex-wrap items-center justify-between gap-x-6 gap-y-1">
<span>{list.name}</span>
<span class="text-base text-[var(--site-muted)]">
<span class="inline-flex items-center gap-1 text-base text-[var(--site-muted)]">
{#if list.isProgress && data.user}
{list.total ? Math.round((list.checked / list.total) * 100) : 0}% · {list.checked}/{list.total}
{:else}
{list.total} {list.total === 1 ? 'item' : 'items'}
{/if} ↗
{/if}
<ArrowRight size={16} aria-hidden="true" />
</span>
</span>
{#if list.isProgress}<span class="mt-3 block"

View File

@@ -1,6 +1,8 @@
<script lang="ts">
import { enhance } from '$app/forms';
import { resolve } from '$app/paths';
import ArrowLeft from 'lucide-svelte/icons/arrow-left';
import ArrowRight from 'lucide-svelte/icons/arrow-right';
let { data, form } = $props();
@@ -24,7 +26,8 @@
<div class="mx-auto max-w-5xl px-4 py-10 sm:px-6 sm:py-16">
<a
href={resolve('/blog')}
class="text-base text-[var(--site-muted)] hover:text-[var(--site-text)]">← blog</a
class="inline-flex items-center gap-1 text-base text-[var(--site-muted)] hover:text-[var(--site-text)]"
><ArrowLeft size={16} aria-hidden="true" /> blog</a
>
<div class="mt-5 mb-8 flex flex-wrap items-end justify-between gap-4">
<div>
@@ -34,7 +37,8 @@
{#if data.blogArticle.status === 'published'}
<a
href={resolve('/blog/[slug]', { slug: data.blogArticle.slug })}
class="text-base underline underline-offset-4">view article ↗</a
class="inline-flex items-center gap-1 text-base underline underline-offset-4"
>view article <ArrowRight size={16} aria-hidden="true" /></a
>
{/if}
</div>

View File

@@ -1,5 +1,7 @@
<script lang="ts">
import { resolve } from '$app/paths';
import ArrowLeft from 'lucide-svelte/icons/arrow-left';
import Check from 'lucide-svelte/icons/check';
import { enhance } from '$app/forms';
import ListPictureDropzone from '$lib/components/ListPictureDropzone.svelte';
import ProgressBar from '$lib/components/ProgressBar.svelte';
@@ -48,7 +50,8 @@
<section class="mx-auto max-w-4xl space-y-6 px-4 py-10 sm:px-6 sm:py-14">
<a
href={resolve('/lists')}
class="text-base text-[var(--site-muted)] hover:text-[var(--site-text)]">← lists</a
class="inline-flex items-center gap-1 text-base text-[var(--site-muted)] hover:text-[var(--site-text)]"
><ArrowLeft size={16} aria-hidden="true" /> lists</a
>
<div class="flex flex-wrap items-end justify-between gap-4">
<div>
@@ -173,7 +176,7 @@
class="flex h-8 w-8 items-center justify-center rounded-full border border-[var(--site-border)] text-base"
aria-label={`${completed.has(item.id) ? 'Uncheck' : 'Check'} ${item.name}`}
>
{completed.has(item.id) ? '✓' : ''}
{#if completed.has(item.id)}<Check size={16} aria-hidden="true" />{/if}
</button>
</form>
{/if}

View File

@@ -1,5 +1,7 @@
<script lang="ts">
import { resolve } from '$app/paths';
import ArrowRight from 'lucide-svelte/icons/arrow-right';
import ExternalLink from 'lucide-svelte/icons/external-link';
const projects = [
{ name: 'catchemall', href: '/projects/catch-em-all', external: false },
@@ -30,18 +32,22 @@
rel="external"
class="flex items-center justify-between gap-4 py-5 hover:text-[var(--site-muted)]"
>
<span>{project.name}</span><span aria-hidden="true" class="text-[var(--site-muted)]"
>↗</span
>
<span>{project.name}</span><ExternalLink
size={18}
aria-hidden="true"
class="text-[var(--site-muted)]"
/>
</a>
{:else}
<a
href={resolve(project.href)}
class="flex items-center justify-between gap-4 py-5 hover:text-[var(--site-muted)]"
>
<span>{project.name}</span><span aria-hidden="true" class="text-[var(--site-muted)]"
>↗</span
>
<span>{project.name}</span><ArrowRight
size={18}
aria-hidden="true"
class="text-[var(--site-muted)]"
/>
</a>
{/if}
</li>

View File

@@ -2,6 +2,9 @@
import { onMount, onDestroy, tick } from 'svelte';
import { resolve } from '$app/paths';
import { page } from '$app/state';
import ArrowRight from 'lucide-svelte/icons/arrow-right';
import PawPrint from 'lucide-svelte/icons/paw-print';
import X from 'lucide-svelte/icons/x';
import type { Map as MaptilerMap, Marker as MaptilerMarker } from '@maptiler/sdk';
interface TrailPoint {
@@ -223,7 +226,11 @@
el.style.backgroundSize = 'cover';
el.style.backgroundPosition = 'center';
} else {
el.textContent = '🐾';
el.innerHTML =
'<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="4" r="2"/><circle cx="18" cy="8" r="2"/><circle cx="20" cy="16" r="2"/><path d="M9 10a5 5 0 0 1 5 5v3.5a3.5 3.5 0 0 1-6.84 1.045Q6.52 17.48 4.46 16.84A3.5 3.5 0 0 1 5.5 10Z"/></svg>';
el.style.display = 'flex';
el.style.alignItems = 'center';
el.style.justifyContent = 'center';
}
el.onclick = () => selectAnimal(feature);
@@ -409,7 +416,8 @@
<button
onclick={() => (selected = null)}
aria-label="Close animal details"
class="absolute top-3 right-3 rounded bg-black px-3 py-1 text-white">✕</button
class="absolute top-3 right-3 flex h-8 w-8 items-center justify-center rounded bg-black text-white"
><X size={18} aria-hidden="true" /></button
>
</div>
{/if}
@@ -450,9 +458,9 @@
</div>
<a
href={resolve(`/projects/animaldex/register?animalId=${selected.animalId}`)}
class="mt-5 inline-block text-base underline underline-offset-4"
class="mt-5 inline-flex items-center gap-1 text-base underline underline-offset-4"
>
i saw this animal too ↗
i saw this animal too <ArrowRight size={16} aria-hidden="true" />
</a>
</div>
</div>
@@ -478,7 +486,7 @@
class="h-12 w-12 rounded object-cover"
/>{:else}<span
class="flex h-12 w-12 items-center justify-center rounded bg-[var(--site-surface)]"
>🐾</span
><PawPrint size={24} aria-hidden="true" /></span
>{/if}
<span class="min-w-0">
<span class="block font-medium">{animal.animalName || animal.species}</span>

View File

@@ -2,6 +2,12 @@
import type { PageData } from './$types';
import { invalidateAll } from '$app/navigation';
import { resolve } from '$app/paths';
import ArrowLeft from 'lucide-svelte/icons/arrow-left';
import Bot from 'lucide-svelte/icons/bot';
import Check from 'lucide-svelte/icons/check';
import MapPin from 'lucide-svelte/icons/map-pin';
import PawPrint from 'lucide-svelte/icons/paw-print';
import X from 'lucide-svelte/icons/x';
let { data }: { data: PageData } = $props();
@@ -40,8 +46,10 @@
</div>
{:else}
<section class="mx-auto max-w-4xl px-4 py-10 sm:px-6 sm:py-14">
<a href={resolve('/projects/animaldex')} class="text-base text-[var(--site-muted)]"
>← animaldex</a
<a
href={resolve('/projects/animaldex')}
class="inline-flex items-center gap-1 text-base text-[var(--site-muted)]"
><ArrowLeft size={16} aria-hidden="true" /> animaldex</a
>
<div class="mt-5 mb-8 flex flex-wrap items-end justify-between gap-4">
<h1 class="text-4xl font-semibold tracking-tight">animaldex admin</h1>
@@ -65,7 +73,9 @@
{#if item.photoUrl}
<img src={item.photoUrl} alt="" class="h-full w-full object-cover" />
{:else}
<div class="flex h-full items-center justify-center text-2xl">🐾</div>
<div class="flex h-full items-center justify-center">
<PawPrint size={28} aria-hidden="true" />
</div>
{/if}
</div>
<div class="min-w-0 flex-1">
@@ -81,8 +91,8 @@
</span>
</div>
{#if item.aiBreedSuggestion}
<p class="mt-1 text-base text-[var(--site-muted)]">
🤖 AI: {item.aiBreedSuggestion} ({Math.round(
<p class="mt-1 flex items-center gap-1 text-base text-[var(--site-muted)]">
<Bot size={16} aria-hidden="true" /> AI: {item.aiBreedSuggestion} ({Math.round(
(item.aiBreedConfidence ?? 0) * 100
)}%)
</p>
@@ -97,8 +107,9 @@
})}
{#if item.reporterName}· by {item.reporterName}{/if}
</p>
<p class="mt-0.5 text-base text-[var(--site-muted)]">
📍 {item.sightingLat.toFixed(5)}, {item.sightingLng.toFixed(5)}
<p class="mt-0.5 flex items-center gap-1 text-base text-[var(--site-muted)]">
<MapPin size={16} aria-hidden="true" />
{item.sightingLat.toFixed(5)}, {item.sightingLng.toFixed(5)}
</p>
</div>
</div>
@@ -106,16 +117,22 @@
<button
onclick={() => moderate(item.animalId, 'accept')}
disabled={actionLoading !== null || status === 'accepted'}
class="flex-1 rounded border border-[var(--site-border)] py-2 text-base disabled:opacity-50"
class="inline-flex flex-1 items-center justify-center gap-1 rounded border border-[var(--site-border)] py-2 text-base disabled:opacity-50"
>
{actionLoading === `animals-${item.animalId}-accept` ? '…' : '✓ Accept'}
{#if actionLoading === `animals-${item.animalId}-accept`}…{:else}<Check
size={16}
aria-hidden="true"
/> Accept{/if}
</button>
<button
onclick={() => moderate(item.animalId, 'deny')}
disabled={actionLoading !== null || status === 'denied'}
class="flex-1 rounded border border-[var(--site-border)] py-2 text-base disabled:opacity-50"
class="inline-flex flex-1 items-center justify-center gap-1 rounded border border-[var(--site-border)] py-2 text-base disabled:opacity-50"
>
{actionLoading === `animals-${item.animalId}-deny` ? '…' : '✕ Deny'}
{#if actionLoading === `animals-${item.animalId}-deny`}…{:else}<X
size={16}
aria-hidden="true"
/> Deny{/if}
</button>
</div>
</div>

View File

@@ -4,6 +4,8 @@
import { onDestroy, onMount } from 'svelte';
import { page } from '$app/stores';
import { goto } from '$app/navigation';
import ArrowLeft from 'lucide-svelte/icons/arrow-left';
import X from 'lucide-svelte/icons/x';
const animalId = $derived($page.url.searchParams.get('animalId'));
const isNewSighting = $derived(!!animalId);
@@ -240,7 +242,8 @@
<section class="mx-auto max-w-3xl px-4 py-10 sm:px-6 sm:py-14">
<a
href={resolve('/projects/animaldex')}
class="text-base text-[var(--site-muted)] hover:text-[var(--site-text)]">← animaldex</a
class="inline-flex items-center gap-1 text-base text-[var(--site-muted)] hover:text-[var(--site-text)]"
><ArrowLeft size={16} aria-hidden="true" /> animaldex</a
>
<h1 class="mt-5 text-4xl font-semibold tracking-tight sm:text-5xl">
{isNewSighting ? 'add a sighting' : 'add an animal'}
@@ -278,7 +281,7 @@
onclick={() => removePhoto(i)}
aria-label={`Remove photo ${i + 1}`}
class="absolute top-1 right-1 flex h-5 w-5 items-center justify-center rounded-full bg-black/60 text-xs text-white"
>✕</button
><X size={12} aria-hidden="true" /></button
>
</div>
{/each}

View File

@@ -1,5 +1,7 @@
<script lang="ts">
import { onMount } from 'svelte';
import Check from 'lucide-svelte/icons/check';
import ExternalLink from 'lucide-svelte/icons/external-link';
import { horses, race, type Horse } from '$lib/derby/data';
import { scenarios } from '$lib/derby/practice';
import { races } from '$lib/derby/races';
@@ -497,7 +499,9 @@
<ol class="steps">
{#each steps as st, i (st.title)}
<li class:done={st.done}>
<span class="step-n">{st.done ? '✓' : i + 1}</span>
<span class="step-n"
>{#if st.done}<Check size={16} aria-hidden="true" />{:else}{i + 1}{/if}</span
>
<div>
<a href={st.href}>{st.title}</a>
<p>{st.detail}</p>
@@ -969,7 +973,9 @@
</ul>
<div class="source-links">
{#each race.sources as s (s.href)}
<a href={s.href} target="_blank" rel="noreferrer">{s.label} ↗</a>
<a href={s.href} target="_blank" rel="noreferrer"
>{s.label} <ExternalLink size={14} aria-hidden="true" /></a
>
{/each}
</div>
</div>
@@ -1915,6 +1921,9 @@
margin-top: 16px;
}
.source-links a {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 0.76rem;
border: 1px solid #07100c;
border-radius: 99px;

View File

@@ -1,5 +1,6 @@
<script lang="ts">
import { untrack } from 'svelte';
import ExternalLink from 'lucide-svelte/icons/external-link';
import { races, type Runner } from '$lib/derby/races';
import { betValue, fairOdds, formatTime, placings } from '$lib/derby/model';
import {
@@ -708,7 +709,9 @@
</ul>
<div class="source-links">
{#each race.sources as s (s.href)}
<a href={s.href} target="_blank" rel="noreferrer">{s.label} ↗</a>
<a href={s.href} target="_blank" rel="noreferrer"
>{s.label} <ExternalLink size={14} aria-hidden="true" /></a
>
{/each}
</div>
</div>
@@ -1518,6 +1521,9 @@
margin-top: 16px;
}
.source-links a {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 0.76rem;
border: 1px solid #07100c;
border-radius: 99px;