atmo.rsvp
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

add embeds

Florian 55ae4e78 3774598b

+347 -66
+70
src/routes/(app)/+layout.svelte
··· 1 + <script lang="ts"> 2 + import { AtprotoLoginModal, atProtoLoginModalState } from '@foxui/social'; 3 + import { login, signup } from '$lib/atproto'; 4 + import { user } from '$lib/atproto/auth.svelte'; 5 + import { Head, Navbar, Button, Avatar } from '@foxui/core'; 6 + import { resolve } from '$app/paths'; 7 + import { page } from '$app/state'; 8 + 9 + let { children } = $props(); 10 + </script> 11 + 12 + <Navbar class="right-2 left-2 mx-auto max-w-3xl rounded-full pl-6 pr-3 top-2"> 13 + <div class="flex items-center gap-6"> 14 + <a href={resolve("/")} class="text-base-500 hover:text-base-900 dark:text-base-400 dark:hover:text-base-50 text-sm font-medium transition-colors">events</a> 15 + <a href="/calendar" class="text-base-500 hover:text-base-900 dark:text-base-400 dark:hover:text-base-50 text-sm font-medium transition-colors">calendar</a> 16 + </div> 17 + <div class="flex items-center gap-4"> 18 + <a href="/search" class="text-base-500 hover:text-base-900 dark:text-base-400 dark:hover:text-base-50 transition-colors"> 19 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="size-5"> 20 + <path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" /> 21 + </svg> 22 + </a> 23 + {#if user.isLoggedIn} 24 + <Button href="/create" class="hidden sm:inline-flex">Create Event</Button> 25 + <Button href="/create" size="icon" class="sm:hidden"> 26 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="size-5"> 27 + <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" /> 28 + </svg> 29 + </Button> 30 + <a href="/p/{user.profile?.handle || user.did}" class="shrink-0"> 31 + <Avatar 32 + src={user.profile?.avatar} 33 + alt="" 34 + fallback={(user.profile?.handle || user.did || '?').charAt(0).toUpperCase()} 35 + class="size-10 rounded-full object-cover" 36 + /> 37 + </a> 38 + {:else} 39 + <Button onclick={() => atProtoLoginModalState.show()} variant="ghost" class="hidden sm:inline-flex">Create Event</Button> 40 + <Button onclick={() => atProtoLoginModalState.show()} variant="ghost" size="icon" class="sm:hidden"> 41 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="size-5"> 42 + <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" /> 43 + </svg> 44 + </Button> 45 + <Button onclick={() => atProtoLoginModalState.show()}>Login</Button> 46 + {/if} 47 + </div> 48 + </Navbar> 49 + 50 + <div class="pt-14"> 51 + {@render children()} 52 + </div> 53 + 54 + <AtprotoLoginModal 55 + login={async (handle) => { 56 + await login(handle); 57 + return true; 58 + }} 59 + signup={async () => { 60 + signup(); 61 + return true; 62 + }} 63 + /> 64 + 65 + <Head 66 + title="atmo.rsvp" 67 + emojiFavicon="🗓️" 68 + description="discover and attend events" 69 + image={page.data?.ogImage ?? '/og.png'} 70 + />
+1 -66
src/routes/+layout.svelte
··· 1 1 <script lang="ts"> 2 2 import '../app.css'; 3 - import { AtprotoLoginModal, atProtoLoginModalState } from '@foxui/social'; 4 - import { login, signup } from '$lib/atproto'; 5 - import { user } from '$lib/atproto/auth.svelte'; 6 - import { Head, Navbar, Button, Avatar } from '@foxui/core'; 7 - import { resolve } from '$app/paths'; 8 - import { page } from '$app/state'; 9 - 10 3 let { children } = $props(); 11 4 </script> 12 5 13 - <Navbar class="right-2 left-2 mx-auto max-w-3xl rounded-full pl-6 pr-3 top-2"> 14 - <div class="flex items-center gap-6"> 15 - <a href={resolve("/")} class="text-base-500 hover:text-base-900 dark:text-base-400 dark:hover:text-base-50 text-sm font-medium transition-colors">events</a> 16 - <a href="/calendar" class="text-base-500 hover:text-base-900 dark:text-base-400 dark:hover:text-base-50 text-sm font-medium transition-colors">calendar</a> 17 - </div> 18 - <div class="flex items-center gap-4"> 19 - <a href="/search" class="text-base-500 hover:text-base-900 dark:text-base-400 dark:hover:text-base-50 transition-colors"> 20 - <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="size-5"> 21 - <path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" /> 22 - </svg> 23 - </a> 24 - {#if user.isLoggedIn} 25 - <Button href="/create" class="hidden sm:inline-flex">Create Event</Button> 26 - <Button href="/create" size="icon" class="sm:hidden"> 27 - <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="size-5"> 28 - <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" /> 29 - </svg> 30 - </Button> 31 - <a href="/p/{user.profile?.handle || user.did}" class="shrink-0"> 32 - <Avatar 33 - src={user.profile?.avatar} 34 - alt="" 35 - fallback={(user.profile?.handle || user.did || '?').charAt(0).toUpperCase()} 36 - class="size-10 rounded-full object-cover" 37 - /> 38 - </a> 39 - {:else} 40 - <Button onclick={() => atProtoLoginModalState.show()} variant="ghost" class="hidden sm:inline-flex">Create Event</Button> 41 - <Button onclick={() => atProtoLoginModalState.show()} variant="ghost" size="icon" class="sm:hidden"> 42 - <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="size-5"> 43 - <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" /> 44 - </svg> 45 - </Button> 46 - <Button onclick={() => atProtoLoginModalState.show()}>Login</Button> 47 - {/if} 48 - </div> 49 - </Navbar> 50 - 51 - <div class="pt-14"> 52 - {@render children()} 53 - </div> 54 - 55 - <AtprotoLoginModal 56 - login={async (handle) => { 57 - await login(handle); 58 - return true; 59 - }} 60 - signup={async () => { 61 - signup(); 62 - return true; 63 - }} 64 - /> 65 - 66 - <Head 67 - title="atmo.rsvp" 68 - emojiFavicon="🗓️" 69 - description="discover and attend events" 70 - image={page.data?.ogImage ?? '/og.png'} 71 - /> 6 + {@render children()}
src/routes/+page.server.ts src/routes/(app)/+page.server.ts
src/routes/+page.svelte src/routes/(app)/+page.svelte
src/routes/api/geocoding/+server.ts src/routes/(app)/api/geocoding/+server.ts
src/routes/calendar/+page.server.ts src/routes/(app)/calendar/+page.server.ts
src/routes/calendar/+page.svelte src/routes/(app)/calendar/+page.svelte
src/routes/create/+page.server.ts src/routes/(app)/create/+page.server.ts
src/routes/create/+page.svelte src/routes/(app)/create/+page.svelte
+5
src/routes/embed/+layout.svelte
··· 1 + <script lang="ts"> 2 + let { children } = $props(); 3 + </script> 4 + 5 + {@render children()}
+69
src/routes/embed/p/[actor]/e/[rkey]/+page.server.ts
··· 1 + import { error } from '@sveltejs/kit'; 2 + import { getActor } from '$lib/actor'; 3 + import { 4 + flattenEventRecord, 5 + getEventRecordFromContrail, 6 + getHostProfile, 7 + getRsvpStatus, 8 + getViewerRsvpFromContrail 9 + } from '$lib/contrail'; 10 + import type { ActorIdentifier } from '@atcute/lexicons'; 11 + 12 + export async function load({ params, url }) { 13 + const { rkey } = params; 14 + const did = await getActor(params.actor); 15 + 16 + if (!did || !rkey) { 17 + throw error(404, 'Event not found'); 18 + } 19 + 20 + try { 21 + const eventRecord = await getEventRecordFromContrail({ 22 + did, 23 + rkey, 24 + profiles: true 25 + }); 26 + 27 + const eventData = eventRecord ? flattenEventRecord(eventRecord) : null; 28 + 29 + if (!eventData) { 30 + throw error(404, 'Event not found'); 31 + } 32 + 33 + const viewerDid = url.searchParams.get('did'); 34 + 35 + const viewerRsvpRecord = viewerDid 36 + ? await getViewerRsvpFromContrail({ 37 + eventUri: eventRecord!.uri, 38 + actor: viewerDid as ActorIdentifier 39 + }) 40 + : null; 41 + 42 + const hostProfile = getHostProfile(did, eventRecord!.profiles) ?? null; 43 + 44 + // Compute thumbnail URL server-side 45 + let thumbnailUrl: string | null = null; 46 + if (eventData.media && eventData.media.length > 0) { 47 + const thumb = eventData.media.find((m: any) => m.role === 'thumbnail'); 48 + if (thumb?.content?.ref?.$link) { 49 + thumbnailUrl = `https://cdn.bsky.app/img/feed_thumbnail/plain/${did}/${thumb.content.ref.$link}@webp`; 50 + } 51 + } 52 + 53 + return { 54 + eventData, 55 + actorDid: did, 56 + rkey, 57 + eventUri: eventRecord!.uri, 58 + eventCid: eventData.cid ?? null, 59 + hostProfile, 60 + thumbnailUrl, 61 + viewerDid, 62 + viewerRsvpStatus: getRsvpStatus(viewerRsvpRecord?.record?.status), 63 + viewerRsvpRkey: viewerRsvpRecord?.rkey ?? null 64 + }; 65 + } catch (e) { 66 + if (e && typeof e === 'object' && 'status' in e) throw e; 67 + throw error(404, 'Event not found'); 68 + } 69 + }
+202
src/routes/embed/p/[actor]/e/[rkey]/+page.svelte
··· 1 + <script lang="ts"> 2 + import { onMount, untrack } from 'svelte'; 3 + import { resolve } from '$app/paths'; 4 + import Avatar from 'svelte-boring-avatars'; 5 + 6 + interface AtmoEmbedSDK { 7 + getParams(): { base: string; accent: string; dark: boolean; did: string | null }; 8 + createRecord(opts: { collection: string; rkey?: string; record: Record<string, unknown> }): Promise<{ uri: string }>; 9 + deleteRecord(opts: { collection: string; rkey: string }): Promise<void>; 10 + } 11 + 12 + declare global { 13 + interface Window { 14 + AtmoEmbed?: AtmoEmbedSDK; 15 + } 16 + } 17 + 18 + let { data } = $props(); 19 + 20 + let rsvpStatus: 'going' | 'interested' | 'notgoing' | null = $state(untrack(() => data.viewerRsvpStatus)); 21 + let rsvpRkey: string | null = $state(untrack(() => data.viewerRsvpRkey)); 22 + let submitting = $state(false); 23 + 24 + let eventUrl = $derived(`https://atmo.rsvp/p/${data.actorDid}/e/${data.rkey}`); 25 + 26 + let startDate = $derived(new Date(data.eventData.startsAt)); 27 + let endDate = $derived(data.eventData.endsAt ? new Date(data.eventData.endsAt) : null); 28 + 29 + function formatDate(date: Date): string { 30 + const options: Intl.DateTimeFormatOptions = { month: 'short', day: 'numeric' }; 31 + if (date.getFullYear() !== new Date().getFullYear()) { 32 + options.year = 'numeric'; 33 + } 34 + return date.toLocaleDateString('en-US', options); 35 + } 36 + 37 + function formatTime(date: Date): string { 38 + return date.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' }); 39 + } 40 + 41 + function getLocationString(): string | null { 42 + const locations = data.eventData.locations; 43 + if (!locations || locations.length === 0) return null; 44 + const loc = locations.find((v) => v.$type === 'community.lexicon.location.address') as 45 + | { name?: string; street?: string; locality?: string; region?: string } 46 + | undefined; 47 + if (!loc) return null; 48 + if (loc.name) return loc.name; 49 + const parts = [loc.locality, loc.region].filter(Boolean); 50 + return parts.length > 0 ? parts.join(', ') : null; 51 + } 52 + 53 + let location = $derived(getLocationString()); 54 + 55 + let isSameDay = $derived( 56 + endDate && 57 + startDate.getFullYear() === endDate.getFullYear() && 58 + startDate.getMonth() === endDate.getMonth() && 59 + startDate.getDate() === endDate.getDate() 60 + ); 61 + 62 + onMount(() => { 63 + if (window.AtmoEmbed) { 64 + const { base, accent, dark } = window.AtmoEmbed.getParams(); 65 + const html = document.documentElement; 66 + if (base) html.classList.add(base); 67 + if (accent) html.classList.add(accent); 68 + if (dark) html.classList.add('dark'); 69 + } 70 + }); 71 + 72 + async function submitRsvp(status: 'going' | 'interested') { 73 + if (!window.AtmoEmbed || !data.viewerDid) return; 74 + submitting = true; 75 + try { 76 + const result = await window.AtmoEmbed.createRecord({ 77 + collection: 'community.lexicon.calendar.rsvp', 78 + record: { 79 + $type: 'community.lexicon.calendar.rsvp', 80 + createdWith: 'https://atmo.rsvp', 81 + status: `community.lexicon.calendar.rsvp#${status}`, 82 + subject: { 83 + uri: data.eventUri, 84 + ...(data.eventCid ? { cid: data.eventCid } : {}) 85 + }, 86 + createdAt: new Date().toISOString() 87 + } 88 + }); 89 + rsvpStatus = status; 90 + if (result?.uri) { 91 + const parts = result.uri.split('/'); 92 + rsvpRkey = parts[parts.length - 1]; 93 + } 94 + } catch (e) { 95 + console.error('RSVP failed:', e); 96 + } finally { 97 + submitting = false; 98 + } 99 + } 100 + 101 + async function cancelRsvp() { 102 + if (!window.AtmoEmbed || !rsvpRkey) return; 103 + submitting = true; 104 + try { 105 + await window.AtmoEmbed.deleteRecord({ 106 + collection: 'community.lexicon.calendar.rsvp', 107 + rkey: rsvpRkey 108 + }); 109 + rsvpStatus = null; 110 + rsvpRkey = null; 111 + } catch (e) { 112 + console.error('Cancel RSVP failed:', e); 113 + } finally { 114 + submitting = false; 115 + } 116 + } 117 + </script> 118 + 119 + <svelte:head> 120 + <script src="https://atmo.social/embed-sdk.js"></script> 121 + </svelte:head> 122 + 123 + <div class="bg-base-50 dark:bg-base-900 text-base-900 dark:text-base-50 flex h-full items-center gap-3 overflow-hidden p-3"> 124 + <!-- Thumbnail --> 125 + <a href={eventUrl} target="_blank" rel="noopener noreferrer" class="size-24 shrink-0 overflow-hidden rounded-xl"> 126 + {#if data.thumbnailUrl} 127 + <img 128 + src={data.thumbnailUrl} 129 + alt={data.eventData.name} 130 + class="size-full object-cover" 131 + /> 132 + {:else} 133 + <div class="size-full [&>svg]:size-full"> 134 + <Avatar 135 + size={96} 136 + name={data.rkey} 137 + variant="marble" 138 + colors={['#92A1C6', '#146A7C', '#F0AB3D', '#C271B4', '#C20D90']} 139 + square 140 + /> 141 + </div> 142 + {/if} 143 + </a> 144 + 145 + <!-- Details + RSVP --> 146 + <div class="flex min-w-0 flex-1 flex-col justify-center gap-1"> 147 + <a href={eventUrl} target="_blank" rel="noopener noreferrer" class="min-w-0"> 148 + <h2 class="line-clamp-2 text-sm leading-tight font-semibold">{data.eventData.name}</h2> 149 + <p class="text-base-500 dark:text-base-400 mt-0.5 truncate text-xs"> 150 + {formatDate(startDate)}, {formatTime(startDate)}{#if endDate && isSameDay} - {formatTime(endDate)}{/if} 151 + </p> 152 + {#if location} 153 + <p class="text-base-500 dark:text-base-400 truncate text-xs">{location}</p> 154 + {/if} 155 + {#if data.hostProfile} 156 + <p class="text-base-400 dark:text-base-500 truncate text-xs"> 157 + by {data.hostProfile.displayName || data.hostProfile.handle || data.actorDid} 158 + </p> 159 + {/if} 160 + </a> 161 + 162 + <!-- RSVP --> 163 + <div class="mt-1"> 164 + {#if !data.viewerDid} 165 + <p class="text-base-400 dark:text-base-500 text-xs">Log in to RSVP</p> 166 + {:else if rsvpStatus === 'going'} 167 + <div class="flex items-center justify-between rounded-lg border border-green-200 bg-green-50 px-2.5 py-1.5 dark:border-green-900/50 dark:bg-green-900/20"> 168 + <div class="flex items-center gap-1.5"> 169 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-3.5 text-green-600 dark:text-green-400"> 170 + <path fill-rule="evenodd" d="M16.704 4.153a.75.75 0 0 1 .143 1.052l-8 10.5a.75.75 0 0 1-1.127.075l-4.5-4.5a.75.75 0 0 1 1.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 0 1 1.05-.143Z" clip-rule="evenodd" /> 171 + </svg> 172 + <span class="text-xs font-semibold text-green-700 dark:text-green-300">You're Going!</span> 173 + </div> 174 + <button onclick={cancelRsvp} disabled={submitting} class="cursor-pointer text-xs text-green-600/60 transition-colors hover:text-green-700 dark:text-green-400/60 dark:hover:text-green-300">Remove</button> 175 + </div> 176 + {:else if rsvpStatus === 'interested'} 177 + <div class="flex items-center justify-between rounded-lg border border-amber-200 bg-amber-50 px-2.5 py-1.5 dark:border-amber-900/50 dark:bg-amber-900/20"> 178 + <div class="flex items-center gap-1.5"> 179 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-3.5 text-amber-600 dark:text-amber-400"> 180 + <path fill-rule="evenodd" d="M10.868 2.884c-.321-.772-1.415-.772-1.736 0l-1.83 4.401-4.753.381c-.833.067-1.171 1.107-.536 1.651l3.62 3.102-1.106 4.637c-.194.813.691 1.456 1.405 1.02L10 15.591l4.069 2.485c.713.436 1.598-.207 1.404-1.02l-1.106-4.637 3.62-3.102c.635-.544.297-1.584-.536-1.65l-4.752-.382-1.831-4.401Z" clip-rule="evenodd" /> 181 + </svg> 182 + <span class="text-xs font-semibold text-amber-700 dark:text-amber-300">You're Interested</span> 183 + </div> 184 + <button onclick={cancelRsvp} disabled={submitting} class="cursor-pointer text-xs text-amber-600/60 transition-colors hover:text-amber-700 dark:text-amber-400/60 dark:hover:text-amber-300">Remove</button> 185 + </div> 186 + {:else} 187 + <div class="flex gap-2"> 188 + <button 189 + onclick={() => submitRsvp('going')} 190 + disabled={submitting} 191 + class="bg-accent-600 hover:bg-accent-700 cursor-pointer rounded-lg px-3 py-1 text-xs font-medium text-white transition-colors disabled:opacity-50" 192 + >Going</button> 193 + <button 194 + onclick={() => submitRsvp('interested')} 195 + disabled={submitting} 196 + class="bg-base-200 dark:bg-base-800 hover:bg-base-300 dark:hover:bg-base-700 text-base-700 dark:text-base-300 cursor-pointer rounded-lg px-3 py-1 text-xs font-medium transition-colors disabled:opacity-50" 197 + >Interested</button> 198 + </div> 199 + {/if} 200 + </div> 201 + </div> 202 + </div>
src/routes/events/+page.server.ts src/routes/(app)/events/+page.server.ts
src/routes/events/+page.svelte src/routes/(app)/events/+page.svelte
src/routes/login/+page.server.ts src/routes/(app)/login/+page.server.ts
src/routes/login/+page.svelte src/routes/(app)/login/+page.svelte
src/routes/p/+page.server.ts src/routes/(app)/p/+page.server.ts
src/routes/p/[actor]/+page.server.ts src/routes/(app)/p/[actor]/+page.server.ts
src/routes/p/[actor]/+page.svelte src/routes/(app)/p/[actor]/+page.svelte
src/routes/p/[actor]/calendar.ics/+server.ts src/routes/(app)/p/[actor]/calendar.ics/+server.ts
src/routes/p/[actor]/e/[rkey]/+page.server.ts src/routes/(app)/p/[actor]/e/[rkey]/+page.server.ts
src/routes/p/[actor]/e/[rkey]/+page.svelte src/routes/(app)/p/[actor]/e/[rkey]/+page.svelte
src/routes/p/[actor]/e/[rkey]/EventAttendees.svelte src/routes/(app)/p/[actor]/e/[rkey]/EventAttendees.svelte
src/routes/p/[actor]/e/[rkey]/data.json/+server.ts src/routes/(app)/p/[actor]/e/[rkey]/data.json/+server.ts
src/routes/p/[actor]/e/[rkey]/edit/+page.server.ts src/routes/(app)/p/[actor]/e/[rkey]/edit/+page.server.ts
src/routes/p/[actor]/e/[rkey]/edit/+page.svelte src/routes/(app)/p/[actor]/e/[rkey]/edit/+page.svelte
src/routes/p/[actor]/e/[rkey]/og.png/+server.ts src/routes/(app)/p/[actor]/e/[rkey]/og.png/+server.ts
src/routes/p/[actor]/e/[rkey]/og.png/EventOgImage.svelte src/routes/(app)/p/[actor]/e/[rkey]/og.png/EventOgImage.svelte
src/routes/p/[actor]/e/[rkey]/og.png/avatars/index.ts src/routes/(app)/p/[actor]/e/[rkey]/og.png/avatars/index.ts
src/routes/p/[actor]/e/[rkey]/og.png/avatars/lib/components/Avatar.svelte src/routes/(app)/p/[actor]/e/[rkey]/og.png/avatars/lib/components/Avatar.svelte
src/routes/p/[actor]/e/[rkey]/og.png/avatars/lib/components/AvatarBauhaus.svelte src/routes/(app)/p/[actor]/e/[rkey]/og.png/avatars/lib/components/AvatarBauhaus.svelte
src/routes/p/[actor]/e/[rkey]/og.png/avatars/lib/components/AvatarBeam.svelte src/routes/(app)/p/[actor]/e/[rkey]/og.png/avatars/lib/components/AvatarBeam.svelte
src/routes/p/[actor]/e/[rkey]/og.png/avatars/lib/components/AvatarMarble.svelte src/routes/(app)/p/[actor]/e/[rkey]/og.png/avatars/lib/components/AvatarMarble.svelte
src/routes/p/[actor]/e/[rkey]/og.png/avatars/lib/components/AvatarPixel.svelte src/routes/(app)/p/[actor]/e/[rkey]/og.png/avatars/lib/components/AvatarPixel.svelte
src/routes/p/[actor]/e/[rkey]/og.png/avatars/lib/components/AvatarRing.svelte src/routes/(app)/p/[actor]/e/[rkey]/og.png/avatars/lib/components/AvatarRing.svelte
src/routes/p/[actor]/e/[rkey]/og.png/avatars/lib/components/AvatarSunset.svelte src/routes/(app)/p/[actor]/e/[rkey]/og.png/avatars/lib/components/AvatarSunset.svelte
src/routes/p/[actor]/e/[rkey]/og.png/avatars/lib/components/CONSTANTS.ts src/routes/(app)/p/[actor]/e/[rkey]/og.png/avatars/lib/components/CONSTANTS.ts
src/routes/p/[actor]/e/[rkey]/og.png/avatars/lib/utils.ts src/routes/(app)/p/[actor]/e/[rkey]/og.png/avatars/lib/utils.ts
src/routes/p/[actor]/e/calendar/+server.ts src/routes/(app)/p/[actor]/e/calendar/+server.ts
src/routes/p/[actor]/hosting/+page.server.ts src/routes/(app)/p/[actor]/hosting/+page.server.ts
src/routes/p/[actor]/hosting/+page.svelte src/routes/(app)/p/[actor]/hosting/+page.svelte
src/routes/p/[actor]/notify-updates/+server.ts src/routes/(app)/p/[actor]/notify-updates/+server.ts
src/routes/p/[actor]/past-events/+page.server.ts src/routes/(app)/p/[actor]/past-events/+page.server.ts
src/routes/p/[actor]/past-events/+page.svelte src/routes/(app)/p/[actor]/past-events/+page.svelte
src/routes/p/atmosphereconf.org/+page.server.ts src/routes/(app)/p/atmosphereconf.org/+page.server.ts
src/routes/p/atmosphereconf.org/+page.svelte src/routes/(app)/p/atmosphereconf.org/+page.svelte
src/routes/p/atmosphereconf.org/Countdown.svelte src/routes/(app)/p/atmosphereconf.org/Countdown.svelte
src/routes/p/atmosphereconf.org/DaySchedule.svelte src/routes/(app)/p/atmosphereconf.org/DaySchedule.svelte
src/routes/p/atmosphereconf.org/ScheduleEventCell.svelte src/routes/(app)/p/atmosphereconf.org/ScheduleEventCell.svelte
src/routes/p/atmosphereconf.org/schedule-utils.ts src/routes/(app)/p/atmosphereconf.org/schedule-utils.ts
src/routes/search/+page.server.ts src/routes/(app)/search/+page.server.ts
src/routes/search/+page.svelte src/routes/(app)/search/+page.svelte