498 lines
18 KiB
Svelte
498 lines
18 KiB
Svelte
<script lang="ts">
|
|
import { page } from '$app/state';
|
|
import { queue, joinQueue, leaveQueue } from '$lib/stores/queue.svelte';
|
|
|
|
const user = $derived(page.data?.user);
|
|
const activeMatch = $derived(page.data?.activeMatch);
|
|
const unreadCount = $derived((page.data as any)?.unreadNotificationCount ?? 0);
|
|
|
|
let { open = $bindable(false) } = $props();
|
|
let notifOpen = $state(false);
|
|
let notifications = $state<any[]>([]);
|
|
let loadingNotifs = $state(false);
|
|
|
|
function isActive(href: string) {
|
|
if (href === '/') return page.url.pathname === '/';
|
|
return page.url.pathname.startsWith(href);
|
|
}
|
|
|
|
const isAdmin = $derived(user?.role === 'admin');
|
|
|
|
// Close sidebar on navigation (mobile)
|
|
$effect(() => {
|
|
page.url.pathname;
|
|
open = false;
|
|
notifOpen = false;
|
|
});
|
|
|
|
async function toggleNotifications() {
|
|
notifOpen = !notifOpen;
|
|
if (notifOpen) {
|
|
await loadNotifications();
|
|
}
|
|
}
|
|
|
|
async function loadNotifications() {
|
|
loadingNotifs = true;
|
|
try {
|
|
const res = await fetch('/api/notifications');
|
|
if (res.ok) notifications = await res.json();
|
|
} finally {
|
|
loadingNotifs = false;
|
|
}
|
|
}
|
|
|
|
async function handleAcceptInvite(referenceId: string) {
|
|
const res = await fetch(`/api/invites/${referenceId}/accept`, { method: 'POST' });
|
|
if (res.ok) {
|
|
const match = await res.json();
|
|
notifications = notifications.map((n) =>
|
|
n.referenceId === referenceId
|
|
? { ...n, actionedAt: new Date().toISOString(), read: true }
|
|
: n
|
|
);
|
|
window.location.href = `/matches/${match.id}`;
|
|
}
|
|
}
|
|
|
|
async function handleDeclineInvite(referenceId: string) {
|
|
const res = await fetch(`/api/invites/${referenceId}/decline`, { method: 'POST' });
|
|
if (res.ok) {
|
|
notifications = notifications.map((n) =>
|
|
n.referenceId === referenceId
|
|
? { ...n, actionedAt: new Date().toISOString(), read: true }
|
|
: n
|
|
);
|
|
}
|
|
}
|
|
|
|
async function markAllRead() {
|
|
await fetch('/api/notifications/read', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ all: true })
|
|
});
|
|
notifications = notifications.map((n) => ({ ...n, read: true }));
|
|
}
|
|
|
|
function timeAgo(date: string | Date) {
|
|
const d = new Date(date);
|
|
const diff = Date.now() - d.getTime();
|
|
const mins = Math.floor(diff / 60000);
|
|
if (mins < 1) return 'just now';
|
|
if (mins < 60) return `${mins}m ago`;
|
|
const hours = Math.floor(mins / 60);
|
|
if (hours < 24) return `${hours}h ago`;
|
|
const days = Math.floor(hours / 24);
|
|
return `${days}d ago`;
|
|
}
|
|
</script>
|
|
|
|
{#snippet navIcon(name: string)}
|
|
{#if name === 'dashboard'}
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-4 w-4"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
><rect x="3" y="3" width="7" height="7" /><rect x="14" y="3" width="7" height="7" /><rect
|
|
x="14"
|
|
y="14"
|
|
width="7"
|
|
height="7"
|
|
/><rect x="3" y="14" width="7" height="7" /></svg
|
|
>
|
|
{:else if name === 'matches'}
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-4 w-4"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
><path d="M14.5 17.5 3 6V3h3l11.5 11.5" /><path d="m13 19 3.5-3.5" /><path
|
|
d="m16.5 22 5-5"
|
|
/><path d="M10 5.5 6 2H3v3l4 4" /></svg
|
|
>
|
|
{:else if name === 'mappools'}
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-4 w-4"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
><circle cx="8" cy="18" r="3" /><path d="M11 18V8l9-1v10" /><circle
|
|
cx="20"
|
|
cy="17"
|
|
r="3"
|
|
/></svg
|
|
>
|
|
{:else if name === 'teams'}
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-4 w-4"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" /><circle cx="9" cy="7" r="4" /><path
|
|
d="M23 21v-2a4 4 0 0 0-3-3.87"
|
|
/><path d="M16 3.13a4 4 0 0 1 0 7.75" /></svg
|
|
>
|
|
{:else if name === 'leaderboard'}
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-4 w-4"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
><line x1="18" y1="20" x2="18" y2="10" /><line x1="12" y1="20" x2="12" y2="4" /><line
|
|
x1="6"
|
|
y1="20"
|
|
x2="6"
|
|
y2="14"
|
|
/></svg
|
|
>
|
|
{:else if name === 'settings'}
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-4 w-4"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
><path
|
|
d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"
|
|
/><circle cx="12" cy="12" r="3" /></svg
|
|
>
|
|
{:else if name === 'admin'}
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-4 w-4"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2" /></svg
|
|
>
|
|
{/if}
|
|
{/snippet}
|
|
|
|
<!-- Backdrop (mobile only) -->
|
|
{#if open}
|
|
<button
|
|
class="fixed inset-0 z-40 bg-black/60 backdrop-blur-sm lg:hidden"
|
|
onclick={() => (open = false)}
|
|
aria-label="Close menu"
|
|
></button>
|
|
{/if}
|
|
|
|
<aside
|
|
class="fixed top-0 left-0 z-50 flex h-full w-56 flex-col border-r border-border bg-surface-800 transition-transform duration-200 {open
|
|
? 'translate-x-0'
|
|
: '-translate-x-full'} lg:z-40 lg:translate-x-0"
|
|
>
|
|
<!-- Logo + Notifications -->
|
|
<div class="flex items-center justify-between px-5 py-5">
|
|
<a href="/" class="flex items-center gap-2.5">
|
|
<img src="/logo.png" alt="Vash Esports" class="h-8 w-8" />
|
|
<span class="font-700 text-base font-bold tracking-tight text-text-primary">Vash Esports</span
|
|
>
|
|
</a>
|
|
{#if user}
|
|
<div class="relative">
|
|
<button
|
|
onclick={toggleNotifications}
|
|
class="relative rounded-md p-1.5 text-text-secondary transition-colors hover:bg-surface-700 hover:text-text-primary"
|
|
aria-label="Notifications"
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-4 w-4"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
><path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9" /><path
|
|
d="M10.3 21a1.94 1.94 0 0 0 3.4 0"
|
|
/></svg
|
|
>
|
|
{#if unreadCount > 0}
|
|
<span
|
|
class="font-700 absolute -top-0.5 -right-0.5 flex h-4 min-w-4 items-center justify-center rounded-full bg-accent px-1 text-[10px] text-surface-900"
|
|
>{unreadCount > 99 ? '99+' : unreadCount}</span
|
|
>
|
|
{/if}
|
|
</button>
|
|
|
|
{#if notifOpen}
|
|
<!-- Backdrop -->
|
|
<button
|
|
class="fixed inset-0 z-50"
|
|
onclick={() => (notifOpen = false)}
|
|
aria-label="Close notifications"
|
|
></button>
|
|
|
|
<!-- Dropdown -->
|
|
<div
|
|
class="absolute top-full left-0 z-50 mt-2 w-72 rounded-lg border border-border bg-surface-800 shadow-xl"
|
|
>
|
|
<div class="flex items-center justify-between border-b border-border px-3 py-2">
|
|
<span class="font-600 text-xs text-text-secondary">Notifications</span>
|
|
{#if notifications.some((n) => !n.read)}
|
|
<button onclick={markAllRead} class="text-[10px] text-accent hover:underline"
|
|
>Mark all read</button
|
|
>
|
|
{/if}
|
|
</div>
|
|
<div class="max-h-80 overflow-y-auto">
|
|
{#if loadingNotifs}
|
|
<div class="px-3 py-6 text-center text-xs text-text-secondary">Loading...</div>
|
|
{:else if notifications.length === 0}
|
|
<div class="px-3 py-6 text-center text-xs text-text-secondary">
|
|
No notifications
|
|
</div>
|
|
{:else}
|
|
{#each notifications as n}
|
|
<div class="border-b border-border/50 px-3 py-2.5 {n.read ? 'opacity-60' : ''}">
|
|
<div class="flex items-start gap-2">
|
|
{#if !n.read}
|
|
<span class="mt-1.5 h-1.5 w-1.5 shrink-0 rounded-full bg-accent"></span>
|
|
{/if}
|
|
<div class="min-w-0 flex-1">
|
|
<p class="font-600 text-xs text-text-primary">{n.title}</p>
|
|
{#if n.message}
|
|
<p class="mt-0.5 text-[11px] text-text-secondary">{n.message}</p>
|
|
{/if}
|
|
<p class="mt-0.5 text-[10px] text-text-secondary/60">
|
|
{timeAgo(n.createdAt)}
|
|
</p>
|
|
|
|
{#if n.type === 'match_invite' && !n.actionedAt && n.referenceId}
|
|
<div class="mt-1.5 flex gap-1.5">
|
|
<button
|
|
onclick={() => handleAcceptInvite(n.referenceId)}
|
|
class="font-600 rounded bg-green-500/20 px-2 py-0.5 text-[10px] text-green-400 transition-colors hover:bg-green-500/30"
|
|
>Accept</button
|
|
>
|
|
<button
|
|
onclick={() => handleDeclineInvite(n.referenceId)}
|
|
class="font-600 rounded bg-red-500/20 px-2 py-0.5 text-[10px] text-red-400 transition-colors hover:bg-red-500/30"
|
|
>Decline</button
|
|
>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/each}
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<!-- Queue Button -->
|
|
<div class="px-3 pb-4">
|
|
{#if activeMatch && page.url.pathname !== `/matches/${activeMatch.id}`}
|
|
<a
|
|
href="/matches/{activeMatch.id}"
|
|
class="font-600 flex w-full items-center justify-center gap-2 rounded-lg border border-yellow-500/30 bg-yellow-500/10 px-3 py-2.5 text-sm text-yellow-300 transition-colors hover:bg-yellow-500/20"
|
|
>
|
|
<div class="relative h-2 w-2">
|
|
<div class="absolute inset-0 animate-ping rounded-full bg-yellow-400 opacity-75"></div>
|
|
<div class="h-2 w-2 rounded-full bg-yellow-400"></div>
|
|
</div>
|
|
Go to Match
|
|
</a>
|
|
{:else if queue.status?.inQueue}
|
|
<div class="flex flex-col gap-1.5">
|
|
<div
|
|
class="font-600 flex items-center justify-center gap-2 rounded-lg border border-yellow-500/30 bg-yellow-500/10 px-3 py-2.5 text-sm text-yellow-300"
|
|
>
|
|
<span class="h-1.5 w-1.5 animate-pulse rounded-full bg-yellow-400"></span>
|
|
In Queue ({queue.status.queueSize})
|
|
</div>
|
|
<button
|
|
onclick={leaveQueue}
|
|
disabled={queue.loading}
|
|
class="flex w-full items-center justify-center rounded-md px-3 py-1.5 text-xs text-red-400 transition-colors hover:bg-red-500/10 disabled:opacity-50"
|
|
>
|
|
Leave Queue
|
|
</button>
|
|
</div>
|
|
{:else if !activeMatch}
|
|
<button
|
|
onclick={joinQueue}
|
|
disabled={queue.loading}
|
|
class="font-600 flex w-full items-center justify-center gap-2 rounded-lg bg-accent px-3 py-2.5 text-sm text-surface-900 transition-colors hover:bg-accent-hover disabled:opacity-50"
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-4 w-4"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
><path d="M14.5 17.5 3 6V3h3l11.5 11.5" /><path d="m13 19 3.5-3.5" /><path
|
|
d="m16.5 22 5-5"
|
|
/><path d="M10 5.5 6 2H3v3l4 4" /></svg
|
|
>
|
|
{queue.loading ? 'Joining...' : 'Find Match'}
|
|
</button>
|
|
{/if}
|
|
</div>
|
|
|
|
<!-- Nav -->
|
|
<nav class="mt-0 flex flex-1 flex-col gap-0.5 px-3">
|
|
{#each [{ href: '/', label: 'Dashboard', icon: 'dashboard' }, { href: '/matches', label: 'Matches', icon: 'matches' }, { href: '/mappools', label: 'Mappools', icon: 'mappools' }, { href: '/teams', label: 'Teams', icon: 'teams' }, { href: '/leaderboard', label: 'Leaderboard', icon: 'leaderboard' }] as item (item.href)}
|
|
<a
|
|
href={item.href}
|
|
class="font-500 flex items-center gap-3 rounded-md px-3 py-2 text-sm transition-colors {isActive(
|
|
item.href
|
|
)
|
|
? 'bg-accent-dim text-accent'
|
|
: 'text-text-secondary hover:bg-surface-700 hover:text-text-primary'}"
|
|
>
|
|
{@render navIcon(item.icon)}
|
|
{item.label}
|
|
</a>
|
|
{/each}
|
|
|
|
{#if isAdmin}
|
|
<div class="mt-4 mb-1 px-3">
|
|
<p class="font-600 text-[10px] tracking-widest text-text-secondary uppercase">Staff</p>
|
|
</div>
|
|
<a
|
|
href="/admin"
|
|
class="font-500 flex items-center gap-3 rounded-md px-3 py-2 text-sm transition-colors {isActive(
|
|
'/admin'
|
|
)
|
|
? 'bg-red-500/10 text-red-400'
|
|
: 'text-text-secondary hover:bg-surface-700 hover:text-text-primary'}"
|
|
>
|
|
{@render navIcon('admin')}
|
|
Admin
|
|
</a>
|
|
{/if}
|
|
</nav>
|
|
|
|
<!-- Bottom -->
|
|
<div class="border-t border-border p-3">
|
|
<!-- Social links -->
|
|
<div class="mb-1 flex items-center gap-1 px-2 py-2">
|
|
<a
|
|
href="https://github.com/VashSoftware/esports"
|
|
target="_blank"
|
|
rel="noopener"
|
|
aria-label="GitHub"
|
|
class="rounded-md p-1.5 text-text-secondary/50 transition-colors hover:bg-surface-700 hover:text-text-primary"
|
|
>
|
|
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="currentColor"
|
|
><path
|
|
d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"
|
|
/></svg
|
|
>
|
|
</a>
|
|
<a
|
|
href="https://x.com/vashesports"
|
|
target="_blank"
|
|
rel="noopener"
|
|
aria-label="X / Twitter"
|
|
class="rounded-md p-1.5 text-text-secondary/50 transition-colors hover:bg-surface-700 hover:text-text-primary"
|
|
>
|
|
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="currentColor"
|
|
><path
|
|
d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.744l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z"
|
|
/></svg
|
|
>
|
|
</a>
|
|
<a
|
|
href="https://www.youtube.com/@vash-esports"
|
|
target="_blank"
|
|
rel="noopener"
|
|
aria-label="YouTube"
|
|
class="rounded-md p-1.5 text-text-secondary/50 transition-colors hover:bg-surface-700 hover:text-text-primary"
|
|
>
|
|
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="currentColor"
|
|
><path
|
|
d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"
|
|
/></svg
|
|
>
|
|
</a>
|
|
<a
|
|
href="https://www.twitch.tv/vashesports"
|
|
target="_blank"
|
|
rel="noopener"
|
|
aria-label="Twitch"
|
|
class="rounded-md p-1.5 text-text-secondary/50 transition-colors hover:bg-surface-700 hover:text-text-primary"
|
|
>
|
|
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="currentColor"
|
|
><path
|
|
d="M11.571 4.714h1.715v5.143H11.57zm4.715 0H18v5.143h-1.714zM6 0 1.714 4.286v15.428h5.143V24l4.286-4.286h3.428L22.286 12V0zm14.571 11.143-3.428 3.428h-3.429l-3 3v-3H6.857V1.714h13.714z"
|
|
/></svg
|
|
>
|
|
</a>
|
|
<a
|
|
href="https://discord.gg/n3mZgWk"
|
|
target="_blank"
|
|
rel="noopener"
|
|
aria-label="Discord"
|
|
class="rounded-md p-1.5 text-text-secondary/50 transition-colors hover:bg-surface-700 hover:text-text-primary"
|
|
>
|
|
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="currentColor"
|
|
><path
|
|
d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057.1 18.108.12 18.156.15 18.197a19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"
|
|
/></svg
|
|
>
|
|
</a>
|
|
</div>
|
|
|
|
<a
|
|
href="/settings"
|
|
class="font-500 flex items-center gap-3 rounded-md px-3 py-2 text-sm transition-colors {isActive(
|
|
'/settings'
|
|
)
|
|
? 'bg-accent-dim text-accent'
|
|
: 'text-text-secondary hover:bg-surface-700 hover:text-text-primary'}"
|
|
>
|
|
{@render navIcon('settings')}
|
|
Settings
|
|
</a>
|
|
<div class="mt-2 flex gap-3 px-3 pb-1">
|
|
<a
|
|
href="/terms"
|
|
class="text-xs text-text-secondary/50 transition-colors hover:text-text-secondary">Terms</a
|
|
>
|
|
<a
|
|
href="/privacy"
|
|
class="text-xs text-text-secondary/50 transition-colors hover:text-text-secondary"
|
|
>Privacy</a
|
|
>
|
|
</div>
|
|
</div>
|
|
</aside>
|