From 49eb41d1bae2b29e119fe74184031bfdbbe158a8 Mon Sep 17 00:00:00 2001 From: novatorem <16753077+novatorem@users.noreply.github.com> Date: Sat, 21 Mar 2026 10:06:50 -0400 Subject: [PATCH] Improve avatar system and add customization --- .vscode/settings.json | 47 +++--- src/app.css | 25 ++-- src/app.d.ts | 5 +- src/hooks.server.ts | 9 +- src/lib/dashboard/GettingStarted.svelte | 1 - src/lib/friends/components/List.svelte | 19 +-- src/lib/friends/components/Requests.svelte | 19 ++- src/lib/status/components/Section.svelte | 12 +- src/lib/stores/avatar.svelte.ts | 49 ++++++ src/lib/ui/AvatarSettings.svelte | 128 ++++++++++++++++ src/lib/ui/DebugPanel.svelte | 2 +- src/lib/ui/Navigation.svelte | 2 - src/lib/ui/ThemeSelect.svelte | 4 +- src/routes/+layout.ts | 1 - src/routes/+page.svelte | 5 - src/routes/auth/+page.svelte | 6 +- src/routes/auth/reset-password/+page.svelte | 4 +- src/routes/dashboard/+layout.server.ts | 2 - src/routes/dashboard/friends/+page.svelte | 1 - src/routes/dashboard/settings/+page.svelte | 157 ++++++++++++++++---- tsconfig.json | 5 - 21 files changed, 377 insertions(+), 126 deletions(-) create mode 100644 src/lib/stores/avatar.svelte.ts create mode 100644 src/lib/ui/AvatarSettings.svelte diff --git a/.vscode/settings.json b/.vscode/settings.json index 5e41859..84e56f7 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,32 +1,27 @@ { - // Editor settings - "editor.formatOnSave": true, - "editor.insertSpaces": false, - "editor.tabSize": 2, - "editor.detectIndentation": false, - "editor.codeActionsOnSave": { - "source.fixAll": "explicit", - "source.organizeImports": "explicit" - }, + "editor.formatOnSave": true, + "editor.insertSpaces": false, + "editor.tabSize": 2, + "editor.detectIndentation": false, + "editor.codeActionsOnSave": { + "source.fixAll": "explicit", + "source.organizeImports": "explicit" + }, - // TypeScript settings - "typescript.updateImportsOnFileMove.enabled": "always", - "typescript.suggest.autoImports": true, - "typescript.preferences.importModuleSpecifier": "shortest", + "typescript.updateImportsOnFileMove.enabled": "always", + "typescript.suggest.autoImports": true, + "typescript.preferences.importModuleSpecifier": "shortest", - // Svelte settings - "[svelte]": { - "editor.defaultFormatter": "svelte.svelte-vscode" - }, - "svelte.plugin.svelte.format.enable": true, - "svelte.plugin.svelte.defaultScriptLanguage": "ts", + "[svelte]": { + "editor.defaultFormatter": "svelte.svelte-vscode" + }, + "svelte.plugin.svelte.format.enable": true, + "svelte.plugin.svelte.defaultScriptLanguage": "ts", - // File handling - "files.eol": "\n", - "files.insertFinalNewline": true, - "files.trimTrailingWhitespace": true, + "files.eol": "\n", + "files.insertFinalNewline": true, + "files.trimTrailingWhitespace": true, - // ESLint settings - "eslint.validate": ["javascript", "typescript", "svelte"], - "cSpell.words": ["PGRST", "rezonate", "supabase"] + "eslint.validate": ["javascript", "typescript", "svelte"], + "cSpell.words": ["PGRST", "rezonate", "supabase"] } diff --git a/src/app.css b/src/app.css index a35ec50..418ea71 100644 --- a/src/app.css +++ b/src/app.css @@ -3,18 +3,15 @@ themes: all; } -/* ── Typography ────────────────────────────────────────────── */ @theme { --font-sans: 'Figtree', ui-sans-serif, system-ui, sans-serif; } -/* ── Easing tokens ─────────────────────────────────────────── */ :root { --ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1); --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1); } -/* ── Keyframes ─────────────────────────────────────────────── */ @keyframes fadeInUp { from { opacity: 0; @@ -59,7 +56,6 @@ } } -/* ── Animation utilities ───────────────────────────────────── */ .animate-fade-in-up { animation: fadeInUp 0.6s var(--ease-out-expo) both; } @@ -68,7 +64,6 @@ animation: fadeInUp 0.35s var(--ease-out-expo) both; } -/* Animation delay utilities */ .animate-delay-0 { animation-delay: 0ms; } @@ -97,13 +92,11 @@ animation: modalIn 0.28s var(--ease-out-quart) both; } -/* ── Button press micro-interaction ───────────────────────── */ .btn:not(:disabled):active { transform: scale(0.96); transition: transform 0.08s var(--ease-out-quart) !important; } -/* ── Drop placement confirmation ──────────────────────────── */ @keyframes justPlaced { 0% { outline: 2px solid color-mix(in oklch, var(--color-primary) 55%, transparent); @@ -113,7 +106,6 @@ } } -/* ── Live status pulse ─────────────────────────────────────── */ @keyframes livePulse { 0%, 100% { @@ -128,7 +120,22 @@ animation: livePulse 1.8s ease-in-out infinite; } -/* ── Global reduced-motion catch-all ───────────────────────── */ +@layer components { + .settings-field-hint { + overflow-wrap: anywhere; + @apply text-base-content/60 block w-full max-w-full text-sm leading-relaxed break-words hyphens-auto whitespace-normal sm:text-base; + } + + .settings-field-hint-error { + overflow-wrap: anywhere; + @apply text-error block w-full max-w-full text-sm leading-relaxed break-words hyphens-auto whitespace-normal sm:text-base; + } + + .input-trailing-icon-btn { + @apply text-base-content/50 hover:text-base-content absolute top-1/2 right-1 flex min-h-[44px] min-w-[44px] -translate-y-1/2 items-center justify-center transition-colors; + } +} + @media (prefers-reduced-motion: reduce) { *, ::before, diff --git a/src/app.d.ts b/src/app.d.ts index 0d32ed6..7d172d2 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -1,9 +1,8 @@ import type { Session, SupabaseClient, User } from '@supabase/supabase-js'; -import type { Database } from './database.types.ts'; // import generated types +import type { Database } from './database.types.ts'; declare global { namespace App { - // interface Error {} interface Locals { supabase: SupabaseClient; safeGetSession: () => Promise<{ session: Session | null; user: User | null }>; @@ -13,8 +12,6 @@ declare global { interface PageData { session: Session | null; } - // interface PageState {} - // interface Platform {} } } diff --git a/src/hooks.server.ts b/src/hooks.server.ts index c22c2c1..71752f8 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -13,13 +13,12 @@ import https from 'node:https'; const isDev = process.env.NODE_ENV === 'development'; const httpsAgent = isDev ? new https.Agent({ rejectUnauthorized: false }) : undefined; +type FetchInitWithAgent = RequestInit & { agent?: import('node:https').Agent }; + const customFetch = (input: URL | RequestInfo, init?: RequestInit) => { if (isDev) { - return fetch(input, { - ...init, - // @ts-expect-error - Agent is not in standard RequestInit but works with Node.js fetch - agent: httpsAgent - }); + const initWithAgent: FetchInitWithAgent = { ...init, agent: httpsAgent }; + return fetch(input, initWithAgent); } return fetch(input, init); }; diff --git a/src/lib/dashboard/GettingStarted.svelte b/src/lib/dashboard/GettingStarted.svelte index a7fe480..9e5fe04 100644 --- a/src/lib/dashboard/GettingStarted.svelte +++ b/src/lib/dashboard/GettingStarted.svelte @@ -16,7 +16,6 @@ const allDone = $derived(hasStatus && hasFriends); - // Auto-dismiss once both tasks are complete (after a short delay so user sees the completion) $effect(() => { if (allDone && !dismissed) { const timer = setTimeout(() => { diff --git a/src/lib/friends/components/List.svelte b/src/lib/friends/components/List.svelte index 0be6128..8edc8a0 100644 --- a/src/lib/friends/components/List.svelte +++ b/src/lib/friends/components/List.svelte @@ -5,6 +5,7 @@ import { getDisplayName } from '$lib/ui/notifications'; import RelativeTime from '$lib/ui/RelativeTime.svelte'; import Avatar from 'svelte-boring-avatars'; + import { avatarSettings } from '$lib/stores/avatar.svelte'; import { flip } from 'svelte/animate'; import { cubicOut } from 'svelte/easing'; import { fly, scale } from 'svelte/transition'; @@ -26,7 +27,6 @@ let { friends, deletingFriends, onDeleteFriend, onReorderFriends }: Props = $props(); - // Sorting type SortKey = 'time' | 'username' | 'display_name'; type SortDir = 'asc' | 'desc'; @@ -89,7 +89,6 @@ let touchStartY = 0; let longPressTimer: ReturnType | null = null; - // Cached item rects - populated at drag start to avoid repeated DOM queries during move let cachedItemRects: { top: number; bottom: number; height: number }[] = []; let touchMoveRafId: number | null = null; let pendingTouchY: number | null = null; @@ -148,7 +147,6 @@ friends = next; onReorderFriends?.(next); sortKey = null; - // Flash the placed item with a warm confirmation ring if (lastDroppedTimer) clearTimeout(lastDroppedTimer); lastDroppedId = moved.id; lastDroppedTimer = setTimeout(() => { @@ -225,7 +223,6 @@ if (draggedIndex === -1) return; event.preventDefault(); - // Buffer the latest Y and coalesce updates to once per animation frame pendingTouchY = touch.clientY; if (touchMoveRafId === null) { touchMoveRafId = requestAnimationFrame(() => { @@ -402,7 +399,6 @@
{#if friends && friends.length > 0} {#each friends as friend, index (friend.id)} -
{#if draggedIndex !== -1 && dropGapIndex === index && !isNeutralGap(dropGapIndex, draggedIndex)}
-
- +

@@ -449,10 +449,8 @@

- -
{#key friend.status} {#if friend.status} @@ -479,7 +477,6 @@ {/key}
- {#if deletingFriends.has(friend.id)} -
- {/each} {#if draggedIndex !== -1 && dropGapIndex === friends.length && !isNeutralGap(dropGapIndex, draggedIndex)} diff --git a/src/lib/friends/components/Requests.svelte b/src/lib/friends/components/Requests.svelte index ce2a9ce..54242fa 100644 --- a/src/lib/friends/components/Requests.svelte +++ b/src/lib/friends/components/Requests.svelte @@ -3,6 +3,7 @@ import { getDisplayName, handleDatabaseError, NotificationManager } from '$lib/ui/notifications'; import type { SupabaseClient, User } from '@supabase/supabase-js'; import Avatar from 'svelte-boring-avatars'; + import { avatarSettings } from '$lib/stores/avatar.svelte'; import { cubicOut } from 'svelte/easing'; import { fly } from 'svelte/transition'; @@ -271,7 +272,7 @@
-
+ {/each} +
+
+ +
+
+ Colors +
+
+ {#each avatarSettings.colors as color, index (index)} + + {/each} +
+ +
+
+
+ diff --git a/src/lib/ui/DebugPanel.svelte b/src/lib/ui/DebugPanel.svelte index 1b66771..9148528 100644 --- a/src/lib/ui/DebugPanel.svelte +++ b/src/lib/ui/DebugPanel.svelte @@ -176,7 +176,7 @@ {#if isOpen} -
+
diff --git a/src/lib/ui/Navigation.svelte b/src/lib/ui/Navigation.svelte index d57f677..ef7c517 100644 --- a/src/lib/ui/Navigation.svelte +++ b/src/lib/ui/Navigation.svelte @@ -39,7 +39,6 @@ >