remove emojis with real icons
All checks were successful
Test and deploy staging / staging (push) Successful in 1m46s
All checks were successful
Test and deploy staging / staging (push) Successful in 1m46s
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import Music2 from 'lucide-svelte/icons/music-2';
|
||||||
let { src, class: className = '' }: { src?: string; class?: string } = $props();
|
let { src, class: className = '' }: { src?: string; class?: string } = $props();
|
||||||
let failed = $state(false);
|
let failed = $state(false);
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
@@ -18,6 +19,6 @@
|
|||||||
class="flex h-full items-center justify-center text-2xl text-gray-500"
|
class="flex h-full items-center justify-center text-2xl text-gray-500"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
♫
|
<Music2 size={28} />
|
||||||
</div>{/if}
|
</div>{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,9 +6,6 @@ import postgres from 'postgres';
|
|||||||
const client = postgres('postgresql://postgres:postgres@localhost:5432/db');
|
const client = postgres('postgresql://postgres:postgres@localhost:5432/db');
|
||||||
const db = drizzle(client, { schema });
|
const db = drizzle(client, { schema });
|
||||||
|
|
||||||
await db.insert(topics).values([
|
await db.insert(topics).values([{ name: 'Math' }, { name: 'Programming' }]);
|
||||||
{ name: 'Math', emoji: '👍' },
|
|
||||||
{ name: 'Programming', emoji: '👍' }
|
|
||||||
]);
|
|
||||||
|
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import { getSeo } from '$lib/seo';
|
import { getSeo } from '$lib/seo';
|
||||||
import CopyButton from '$lib/components/CopyButton.svelte';
|
import CopyButton from '$lib/components/CopyButton.svelte';
|
||||||
|
import ExternalLink from 'lucide-svelte/icons/external-link';
|
||||||
import SiOsu from '~icons/simple-icons/osu';
|
import SiOsu from '~icons/simple-icons/osu';
|
||||||
import SiSteam from '~icons/simple-icons/steam';
|
import SiSteam from '~icons/simple-icons/steam';
|
||||||
import SiTwitch from '~icons/simple-icons/twitch';
|
import SiTwitch from '~icons/simple-icons/twitch';
|
||||||
@@ -257,7 +258,8 @@
|
|||||||
<a
|
<a
|
||||||
href="https://git.stanrunge.dev/stan/personal-website"
|
href="https://git.stanrunge.dev/stan/personal-website"
|
||||||
rel="external"
|
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>
|
</div>
|
||||||
<nav class="flex flex-wrap items-center justify-center gap-1" aria-label="Social links">
|
<nav class="flex flex-wrap items-center justify-center gap-1" aria-label="Social links">
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
import { resolve } from '$app/paths';
|
import { resolve } from '$app/paths';
|
||||||
import portrait from '$lib/assets/stan.jpg?url';
|
import portrait from '$lib/assets/stan.jpg?url';
|
||||||
import ProgressBar from '$lib/components/ProgressBar.svelte';
|
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';
|
import type { PageProps } from './$types';
|
||||||
|
|
||||||
let { data }: PageProps = $props();
|
let { data }: PageProps = $props();
|
||||||
@@ -21,7 +24,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'animaldex',
|
name: 'animaldex',
|
||||||
subtitle: 'add neighborhood pets to the world map 😼',
|
subtitle: 'add neighborhood pets to the world map',
|
||||||
href: '/projects/animaldex',
|
href: '/projects/animaldex',
|
||||||
external: false
|
external: false
|
||||||
},
|
},
|
||||||
@@ -68,15 +71,26 @@
|
|||||||
<a
|
<a
|
||||||
href="https://git.stanrunge.dev/stan"
|
href="https://git.stanrunge.dev/stan"
|
||||||
rel="external"
|
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)]"
|
<a
|
||||||
>twitter ↗</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)]"
|
<a
|
||||||
>vash esports ↗</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>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
<img
|
<img
|
||||||
@@ -93,7 +107,11 @@
|
|||||||
|
|
||||||
<section aria-labelledby="projects-heading" class="border-t border-[var(--site-border)] pt-8">
|
<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">
|
<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>
|
</h2>
|
||||||
<ul>
|
<ul>
|
||||||
{#each projects as project (project.name)}
|
{#each projects as project (project.name)}
|
||||||
@@ -108,7 +126,11 @@
|
|||||||
<span class="block">{project.name}</span>
|
<span class="block">{project.name}</span>
|
||||||
<span class="mt-1 block text-base text-[var(--site-muted)]">{project.subtitle}</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>
|
</a>
|
||||||
{:else}
|
{:else}
|
||||||
<a
|
<a
|
||||||
@@ -119,7 +141,11 @@
|
|||||||
<span class="block">{project.name}</span>
|
<span class="block">{project.name}</span>
|
||||||
<span class="mt-1 block text-base text-[var(--site-muted)]">{project.subtitle}</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>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
</li>
|
</li>
|
||||||
@@ -129,7 +155,11 @@
|
|||||||
|
|
||||||
<section aria-labelledby="lists-heading" class="pt-14 sm:pt-20">
|
<section aria-labelledby="lists-heading" class="pt-14 sm:pt-20">
|
||||||
<h2 id="lists-heading" class="mb-4 text-2xl font-semibold tracking-tight">
|
<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>
|
</h2>
|
||||||
<ul>
|
<ul>
|
||||||
{#each data.lists as list (list.id)}
|
{#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 class="flex flex-wrap items-center justify-between gap-x-6 gap-y-1">
|
||||||
<span>{list.name}</span>
|
<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}
|
{#if list.isProgress && data.user}
|
||||||
{list.total ? Math.round((list.checked / list.total) * 100) : 0}% · {list.checked}/{list.total}
|
{list.total ? Math.round((list.checked / list.total) * 100) : 0}% · {list.checked}/{list.total}
|
||||||
{:else}
|
{:else}
|
||||||
{list.total} {list.total === 1 ? 'item' : 'items'}
|
{list.total} {list.total === 1 ? 'item' : 'items'}
|
||||||
{/if} ↗
|
{/if}
|
||||||
|
<ArrowRight size={16} aria-hidden="true" />
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
{#if list.isProgress}<span class="mt-3 block"
|
{#if list.isProgress}<span class="mt-3 block"
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { enhance } from '$app/forms';
|
import { enhance } from '$app/forms';
|
||||||
import { resolve } from '$app/paths';
|
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();
|
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">
|
<div class="mx-auto max-w-5xl px-4 py-10 sm:px-6 sm:py-16">
|
||||||
<a
|
<a
|
||||||
href={resolve('/blog')}
|
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 class="mt-5 mb-8 flex flex-wrap items-end justify-between gap-4">
|
||||||
<div>
|
<div>
|
||||||
@@ -34,7 +37,8 @@
|
|||||||
{#if data.blogArticle.status === 'published'}
|
{#if data.blogArticle.status === 'published'}
|
||||||
<a
|
<a
|
||||||
href={resolve('/blog/[slug]', { slug: data.blogArticle.slug })}
|
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}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { resolve } from '$app/paths';
|
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 { enhance } from '$app/forms';
|
||||||
import ListPictureDropzone from '$lib/components/ListPictureDropzone.svelte';
|
import ListPictureDropzone from '$lib/components/ListPictureDropzone.svelte';
|
||||||
import ProgressBar from '$lib/components/ProgressBar.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">
|
<section class="mx-auto max-w-4xl space-y-6 px-4 py-10 sm:px-6 sm:py-14">
|
||||||
<a
|
<a
|
||||||
href={resolve('/lists')}
|
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 class="flex flex-wrap items-end justify-between gap-4">
|
||||||
<div>
|
<div>
|
||||||
@@ -173,7 +176,7 @@
|
|||||||
class="flex h-8 w-8 items-center justify-center rounded-full border border-[var(--site-border)] text-base"
|
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}`}
|
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>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { resolve } from '$app/paths';
|
import { resolve } from '$app/paths';
|
||||||
|
import ArrowRight from 'lucide-svelte/icons/arrow-right';
|
||||||
|
import ExternalLink from 'lucide-svelte/icons/external-link';
|
||||||
|
|
||||||
const projects = [
|
const projects = [
|
||||||
{ name: 'catchemall', href: '/projects/catch-em-all', external: false },
|
{ name: 'catchemall', href: '/projects/catch-em-all', external: false },
|
||||||
@@ -30,18 +32,22 @@
|
|||||||
rel="external"
|
rel="external"
|
||||||
class="flex items-center justify-between gap-4 py-5 hover:text-[var(--site-muted)]"
|
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>{project.name}</span><ExternalLink
|
||||||
>↗</span
|
size={18}
|
||||||
>
|
aria-hidden="true"
|
||||||
|
class="text-[var(--site-muted)]"
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
{:else}
|
{:else}
|
||||||
<a
|
<a
|
||||||
href={resolve(project.href)}
|
href={resolve(project.href)}
|
||||||
class="flex items-center justify-between gap-4 py-5 hover:text-[var(--site-muted)]"
|
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>{project.name}</span><ArrowRight
|
||||||
>↗</span
|
size={18}
|
||||||
>
|
aria-hidden="true"
|
||||||
|
class="text-[var(--site-muted)]"
|
||||||
|
/>
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
import { onMount, onDestroy, tick } from 'svelte';
|
import { onMount, onDestroy, tick } from 'svelte';
|
||||||
import { resolve } from '$app/paths';
|
import { resolve } from '$app/paths';
|
||||||
import { page } from '$app/state';
|
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';
|
import type { Map as MaptilerMap, Marker as MaptilerMarker } from '@maptiler/sdk';
|
||||||
|
|
||||||
interface TrailPoint {
|
interface TrailPoint {
|
||||||
@@ -223,7 +226,11 @@
|
|||||||
el.style.backgroundSize = 'cover';
|
el.style.backgroundSize = 'cover';
|
||||||
el.style.backgroundPosition = 'center';
|
el.style.backgroundPosition = 'center';
|
||||||
} else {
|
} 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);
|
el.onclick = () => selectAnimal(feature);
|
||||||
@@ -409,7 +416,8 @@
|
|||||||
<button
|
<button
|
||||||
onclick={() => (selected = null)}
|
onclick={() => (selected = null)}
|
||||||
aria-label="Close animal details"
|
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>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -450,9 +458,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<a
|
<a
|
||||||
href={resolve(`/projects/animaldex/register?animalId=${selected.animalId}`)}
|
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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -478,7 +486,7 @@
|
|||||||
class="h-12 w-12 rounded object-cover"
|
class="h-12 w-12 rounded object-cover"
|
||||||
/>{:else}<span
|
/>{:else}<span
|
||||||
class="flex h-12 w-12 items-center justify-center rounded bg-[var(--site-surface)]"
|
class="flex h-12 w-12 items-center justify-center rounded bg-[var(--site-surface)]"
|
||||||
>🐾</span
|
><PawPrint size={24} aria-hidden="true" /></span
|
||||||
>{/if}
|
>{/if}
|
||||||
<span class="min-w-0">
|
<span class="min-w-0">
|
||||||
<span class="block font-medium">{animal.animalName || animal.species}</span>
|
<span class="block font-medium">{animal.animalName || animal.species}</span>
|
||||||
|
|||||||
@@ -2,6 +2,12 @@
|
|||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import { invalidateAll } from '$app/navigation';
|
import { invalidateAll } from '$app/navigation';
|
||||||
import { resolve } from '$app/paths';
|
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();
|
let { data }: { data: PageData } = $props();
|
||||||
|
|
||||||
@@ -40,8 +46,10 @@
|
|||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<section class="mx-auto max-w-4xl px-4 py-10 sm:px-6 sm:py-14">
|
<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)]"
|
<a
|
||||||
>← animaldex</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">
|
<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>
|
<h1 class="text-4xl font-semibold tracking-tight">animaldex admin</h1>
|
||||||
@@ -65,7 +73,9 @@
|
|||||||
{#if item.photoUrl}
|
{#if item.photoUrl}
|
||||||
<img src={item.photoUrl} alt="" class="h-full w-full object-cover" />
|
<img src={item.photoUrl} alt="" class="h-full w-full object-cover" />
|
||||||
{:else}
|
{: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}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="min-w-0 flex-1">
|
<div class="min-w-0 flex-1">
|
||||||
@@ -81,8 +91,8 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{#if item.aiBreedSuggestion}
|
{#if item.aiBreedSuggestion}
|
||||||
<p class="mt-1 text-base text-[var(--site-muted)]">
|
<p class="mt-1 flex items-center gap-1 text-base text-[var(--site-muted)]">
|
||||||
🤖 AI: {item.aiBreedSuggestion} ({Math.round(
|
<Bot size={16} aria-hidden="true" /> AI: {item.aiBreedSuggestion} ({Math.round(
|
||||||
(item.aiBreedConfidence ?? 0) * 100
|
(item.aiBreedConfidence ?? 0) * 100
|
||||||
)}%)
|
)}%)
|
||||||
</p>
|
</p>
|
||||||
@@ -97,8 +107,9 @@
|
|||||||
})}
|
})}
|
||||||
{#if item.reporterName}· by {item.reporterName}{/if}
|
{#if item.reporterName}· by {item.reporterName}{/if}
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-0.5 text-base text-[var(--site-muted)]">
|
<p class="mt-0.5 flex items-center gap-1 text-base text-[var(--site-muted)]">
|
||||||
📍 {item.sightingLat.toFixed(5)}, {item.sightingLng.toFixed(5)}
|
<MapPin size={16} aria-hidden="true" />
|
||||||
|
{item.sightingLat.toFixed(5)}, {item.sightingLng.toFixed(5)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -106,16 +117,22 @@
|
|||||||
<button
|
<button
|
||||||
onclick={() => moderate(item.animalId, 'accept')}
|
onclick={() => moderate(item.animalId, 'accept')}
|
||||||
disabled={actionLoading !== null || status === 'accepted'}
|
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>
|
||||||
<button
|
<button
|
||||||
onclick={() => moderate(item.animalId, 'deny')}
|
onclick={() => moderate(item.animalId, 'deny')}
|
||||||
disabled={actionLoading !== null || status === 'denied'}
|
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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { goto } from '$app/navigation';
|
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 animalId = $derived($page.url.searchParams.get('animalId'));
|
||||||
const isNewSighting = $derived(!!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">
|
<section class="mx-auto max-w-3xl px-4 py-10 sm:px-6 sm:py-14">
|
||||||
<a
|
<a
|
||||||
href={resolve('/projects/animaldex')}
|
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">
|
<h1 class="mt-5 text-4xl font-semibold tracking-tight sm:text-5xl">
|
||||||
{isNewSighting ? 'add a sighting' : 'add an animal'}
|
{isNewSighting ? 'add a sighting' : 'add an animal'}
|
||||||
@@ -278,7 +281,7 @@
|
|||||||
onclick={() => removePhoto(i)}
|
onclick={() => removePhoto(i)}
|
||||||
aria-label={`Remove photo ${i + 1}`}
|
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"
|
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>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
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 { horses, race, type Horse } from '$lib/derby/data';
|
||||||
import { scenarios } from '$lib/derby/practice';
|
import { scenarios } from '$lib/derby/practice';
|
||||||
import { races } from '$lib/derby/races';
|
import { races } from '$lib/derby/races';
|
||||||
@@ -497,7 +499,9 @@
|
|||||||
<ol class="steps">
|
<ol class="steps">
|
||||||
{#each steps as st, i (st.title)}
|
{#each steps as st, i (st.title)}
|
||||||
<li class:done={st.done}>
|
<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>
|
<div>
|
||||||
<a href={st.href}>{st.title}</a>
|
<a href={st.href}>{st.title}</a>
|
||||||
<p>{st.detail}</p>
|
<p>{st.detail}</p>
|
||||||
@@ -969,7 +973,9 @@
|
|||||||
</ul>
|
</ul>
|
||||||
<div class="source-links">
|
<div class="source-links">
|
||||||
{#each race.sources as s (s.href)}
|
{#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}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1915,6 +1921,9 @@
|
|||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
.source-links a {
|
.source-links a {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
font-size: 0.76rem;
|
font-size: 0.76rem;
|
||||||
border: 1px solid #07100c;
|
border: 1px solid #07100c;
|
||||||
border-radius: 99px;
|
border-radius: 99px;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { untrack } from 'svelte';
|
import { untrack } from 'svelte';
|
||||||
|
import ExternalLink from 'lucide-svelte/icons/external-link';
|
||||||
import { races, type Runner } from '$lib/derby/races';
|
import { races, type Runner } from '$lib/derby/races';
|
||||||
import { betValue, fairOdds, formatTime, placings } from '$lib/derby/model';
|
import { betValue, fairOdds, formatTime, placings } from '$lib/derby/model';
|
||||||
import {
|
import {
|
||||||
@@ -708,7 +709,9 @@
|
|||||||
</ul>
|
</ul>
|
||||||
<div class="source-links">
|
<div class="source-links">
|
||||||
{#each race.sources as s (s.href)}
|
{#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}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1518,6 +1521,9 @@
|
|||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
.source-links a {
|
.source-links a {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
font-size: 0.76rem;
|
font-size: 0.76rem;
|
||||||
border: 1px solid #07100c;
|
border: 1px solid #07100c;
|
||||||
border-radius: 99px;
|
border-radius: 99px;
|
||||||
|
|||||||
Reference in New Issue
Block a user