Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 21 additions & 26 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -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"]
}
25 changes: 16 additions & 9 deletions src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -59,7 +56,6 @@
}
}

/* ── Animation utilities ───────────────────────────────────── */
.animate-fade-in-up {
animation: fadeInUp 0.6s var(--ease-out-expo) both;
}
Expand All @@ -68,7 +64,6 @@
animation: fadeInUp 0.35s var(--ease-out-expo) both;
}

/* Animation delay utilities */
.animate-delay-0 {
animation-delay: 0ms;
}
Expand Down Expand Up @@ -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);
Expand All @@ -113,7 +106,6 @@
}
}

/* ── Live status pulse ─────────────────────────────────────── */
@keyframes livePulse {
0%,
100% {
Expand All @@ -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,
Expand Down
5 changes: 1 addition & 4 deletions src/app.d.ts
Original file line number Diff line number Diff line change
@@ -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<Database>;
safeGetSession: () => Promise<{ session: Session | null; user: User | null }>;
Expand All @@ -13,8 +12,6 @@ declare global {
interface PageData {
session: Session | null;
}
// interface PageState {}
// interface Platform {}
}
}

Expand Down
9 changes: 4 additions & 5 deletions src/hooks.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
Expand Down
1 change: 0 additions & 1 deletion src/lib/dashboard/GettingStarted.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand Down
19 changes: 7 additions & 12 deletions src/lib/friends/components/List.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -26,7 +27,6 @@

let { friends, deletingFriends, onDeleteFriend, onReorderFriends }: Props = $props();

// Sorting
type SortKey = 'time' | 'username' | 'display_name';
type SortDir = 'asc' | 'desc';

Expand Down Expand Up @@ -89,7 +89,6 @@
let touchStartY = 0;
let longPressTimer: ReturnType<typeof setTimeout> | 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;
Expand Down Expand Up @@ -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(() => {
Expand Down Expand Up @@ -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(() => {
Expand Down Expand Up @@ -402,7 +399,6 @@
<div class="space-y-3" role="list" bind:this={containerRef} ondragleave={handleDragLeave}>
{#if friends && friends.length > 0}
{#each friends as friend, index (friend.id)}
<!-- Sole direct child required for animate:flip; gap + item nested inside -->
<div animate:flip={{ duration: 240, easing: cubicOut }}>
{#if draggedIndex !== -1 && dropGapIndex === index && !isNeutralGap(dropGapIndex, draggedIndex)}
<div
Expand Down Expand Up @@ -434,10 +430,14 @@
ondragend={handleDragEnd}
>
<div class="flex w-full items-center gap-3">
<!-- LEFT: identity cluster (avatar + name) -->
<div class="flex w-28 flex-shrink-0 items-center gap-2.5 sm:w-36">
<div class="avatar flex-shrink-0">
<Avatar name={friend.id} size={40} variant="beam" />
<Avatar
name={friend.id}
size={40}
variant={avatarSettings.variant}
colors={avatarSettings.colors}
/>
</div>
<div class="min-w-0">
<h3 class="truncate text-sm leading-tight font-semibold">
Expand All @@ -449,10 +449,8 @@
</div>
</div>

<!-- Separator -->
<div class="bg-base-content/10 h-8 w-px flex-shrink-0" aria-hidden="true"></div>

<!-- RIGHT: status + time (dominant) -->
<div class="min-w-0 flex-1">
{#key friend.status}
{#if friend.status}
Expand All @@ -479,15 +477,13 @@
{/key}
</div>

<!-- Delete spinner (only visible while deleting) -->
{#if deletingFriends.has(friend.id)}
<span
class="loading loading-spinner loading-xs text-base-content/40 flex-shrink-0"
></span>
{/if}
</div>

<!-- Expand panel: full status + full timestamp -->
<div
class="grid transition-[grid-template-rows] duration-200 ease-out"
style="grid-template-rows: {expandedFriendId === friend.id ? '1fr' : '0fr'}"
Expand Down Expand Up @@ -543,7 +539,6 @@
</div>
</div>
</div>
<!-- end animate:flip wrapper -->
{/each}

{#if draggedIndex !== -1 && dropGapIndex === friends.length && !isNeutralGap(dropGapIndex, draggedIndex)}
Expand Down
19 changes: 15 additions & 4 deletions src/lib/friends/components/Requests.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -271,7 +272,7 @@
<form onsubmit={handleFriendRequest} class="mb-2">
<div class="join w-full">
<div class="w-full">
<label class="input validator join-item w-full">
<div class="input validator join-item w-full">
<svg class="h-[1em] opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g
stroke-linejoin="round"
Expand Down Expand Up @@ -299,7 +300,7 @@
autocapitalize="none"
spellcheck="false"
/>
</label>
</div>
</div>
<button
class="btn btn-neutral join-item"
Expand Down Expand Up @@ -340,7 +341,12 @@
<div class="flex w-full items-center justify-between gap-3">
<div class="flex min-w-0 flex-1 items-center gap-3">
<div class="avatar flex-shrink-0">
<Avatar name={request.requester_id} size={40} variant="beam" />
<Avatar
name={request.requester_id}
size={40}
variant={avatarSettings.variant}
colors={avatarSettings.colors}
/>
</div>
<div class="flex min-w-0 flex-col">
<span class="truncate"
Expand Down Expand Up @@ -422,7 +428,12 @@
<div class="flex w-full items-center justify-between gap-3">
<div class="flex flex-1 items-center gap-3">
<div class="avatar">
<Avatar name={request.target_id} size={40} variant="beam" />
<Avatar
name={request.target_id}
size={40}
variant={avatarSettings.variant}
colors={avatarSettings.colors}
/>
</div>
<div class="flex flex-col">
<span>{getDisplayName(request.target_display_name, request.target_username)}</span
Expand Down
12 changes: 5 additions & 7 deletions src/lib/status/components/Section.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,19 @@
let successTimer: ReturnType<typeof setTimeout> | null = null;

let statusCharacterCount = $derived(statusInputText?.length ?? 0);
// Show counter only when within 14 chars of the limit - enough notice, no noise otherwise
let showCounter = $derived(statusCharacterCount >= MAX_STATUS_LENGTH - 14);
let showCharCountNearLimit = $derived(statusCharacterCount >= MAX_STATUS_LENGTH - 14);
let charsRemaining = $derived(MAX_STATUS_LENGTH - statusCharacterCount);

// Track currentStatus changes to flash a success checkmark on the submit button
let prevStatus: string | undefined;
let lastKnownStatus: string | undefined;
$effect(() => {
if (prevStatus !== undefined && currentStatus !== prevStatus) {
if (lastKnownStatus !== undefined && currentStatus !== lastKnownStatus) {
if (successTimer) clearTimeout(successTimer);
showSuccess = true;
successTimer = setTimeout(() => {
showSuccess = false;
}, 700);
}
prevStatus = currentStatus;
lastKnownStatus = currentStatus;
});

const handleQuickStatusChange = (statusText: string, statusId: string) => {
Expand Down Expand Up @@ -124,7 +122,7 @@
maxlength={MAX_STATUS_LENGTH}
required
/>
{#if showCounter}
{#if showCharCountNearLimit}
<span
transition:fly={{ x: 4, duration: 140, easing: cubicOut }}
class="pointer-events-none absolute top-1/2 right-3 -translate-y-1/2 text-xs tabular-nums select-none {charsRemaining <
Expand Down
Loading
Loading