diff --git a/README.md b/README.md
index 4e83027..31e3edf 100644
--- a/README.md
+++ b/README.md
@@ -1,33 +1,6 @@
-# sv
+# atmo.watch
-Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
-
-## Creating a project
-
-If you're seeing this, you've probably already done this step. Congrats!
-
-```sh
-# create a new project
-npx sv create my-app
-```
-
-To recreate this project with the same configuration:
-
-```sh
-# recreate this project
-pnpm dlx sv@0.17.0 create --template minimal --types ts --add prettier eslint vitest="usages:unit,component" playwright tailwindcss="plugins:typography,forms" --install pnpm 06-atmo-watch
-```
-
-## Developing
-
-Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
-
-```sh
-npm run dev
-
-# or start the server and open the app in a new browser tab
-npm run dev -- --open
-```
+review movies and tv shows with your atmosphere account, using popfeed.social's lexicons.
## API Worker
diff --git a/src/lib/components/AccountMenuItem.svelte b/src/lib/components/AccountMenuItem.svelte
index bee6805..574c678 100644
--- a/src/lib/components/AccountMenuItem.svelte
+++ b/src/lib/components/AccountMenuItem.svelte
@@ -3,9 +3,20 @@
import type { Pathname } from '$app/types';
import { loginDialog } from '$lib/login.svelte';
import { UserRound } from '@lucide/svelte';
+ import Avatar from './Avatar.svelte';
import MenuItem from './MenuItem.svelte';
- let { did, onLoginOpen }: { did: string | null; onLoginOpen?: () => void } = $props();
+ let {
+ did,
+ avatarUrl,
+ variant = 'sidebar',
+ onLoginOpen
+ }: {
+ did: string | null;
+ avatarUrl?: string;
+ variant?: 'sidebar' | 'bottom';
+ onLoginOpen?: () => void;
+ } = $props();
let profileHref: Pathname | undefined = $derived(
did ? (`/profile/${did}` as Pathname) : undefined
);
@@ -25,7 +36,16 @@
tooltip={did ? 'Profile' : 'Log in'}
{active}
onclick={openLogin}
- class="md:mt-auto"
+ {variant}
+ class={variant === 'sidebar' ? (did ? 'p-1.5 md:mt-auto md:p-1.5' : 'md:mt-auto') : undefined}
>
-
+ {#if did}
+
+ {:else}
+
+ {/if}
diff --git a/src/lib/components/AppMenu.svelte b/src/lib/components/AppMenu.svelte
index dede2d8..cd3cc6e 100644
--- a/src/lib/components/AppMenu.svelte
+++ b/src/lib/components/AppMenu.svelte
@@ -4,69 +4,48 @@
import MenuItem from './MenuItem.svelte';
import SearchMenuItem from './SearchMenuItem.svelte';
- let { did }: { did: string | null } = $props();
- let menuOpen = $state(false);
-
- $effect(() => {
- if (page.url.pathname) menuOpen = false;
- });
+ let { did, avatarUrl }: { did: string | null; avatarUrl?: string } = $props();
+{#snippet homeIcon()}
+
+{/snippet}
+
-{#if menuOpen}
-
-{/if}
-
-
+
+
+
+
+
diff --git a/src/lib/components/BackdropGallery.svelte b/src/lib/components/BackdropGallery.svelte
new file mode 100644
index 0000000..ea3cbd7
--- /dev/null
+++ b/src/lib/components/BackdropGallery.svelte
@@ -0,0 +1,147 @@
+
+
+
+
+
+ {#each images as image, index (image.source === 'tmdb' ? image.path : image.url)}
+
+ {/each}
+
+
+
+
+
+
+ Backdrops for {title}
+
+ Backdrop {selectedIndex + 1} of {images.length}. Use the left and right arrow keys to
+ browse, or press Escape to close.
+
+
+ {#if selectedImage}
+
+ {/if}
+
+ {#if images.length > 1}
+
+
+
+
+
+ {selectedIndex + 1} / {images.length}
+
+ {/if}
+
+
+
+ Close backdrops
+
+
+
+
diff --git a/src/lib/components/ItemPage.svelte b/src/lib/components/ItemPage.svelte
index 09c29d7..8497edd 100644
--- a/src/lib/components/ItemPage.svelte
+++ b/src/lib/components/ItemPage.svelte
@@ -2,6 +2,7 @@
import { resolve } from '$app/paths';
import { page } from '$app/state';
import Avatar from './Avatar.svelte';
+ import BackdropGallery from './BackdropGallery.svelte';
import Container from './Container.svelte';
import ExternalRatings from './ExternalRatings.svelte';
import ItemsGrid from './ItemsGrid.svelte';
@@ -62,6 +63,7 @@
);
let showAllReviews = $state(false);
let showAllRecommendations = $state(false);
+ let showAllCast = $state(false);
let writtenReviews = $derived(data.reviews.filter((review) => review.text.trim()));
let displayedReviews = $derived(showAllReviews ? writtenReviews : writtenReviews.slice(0, 3));
let mobileRecommendations = $derived(
@@ -70,11 +72,15 @@
let desktopRecommendations = $derived(
showAllRecommendations ? data.recommendations : data.recommendations.slice(0, 10)
);
+ let castShowMoreClass = $derived(
+ data.cast.length > 10 ? '' : data.cast.length > 8 ? 'lg:hidden' : 'sm:hidden'
+ );
$effect(() => {
if (data.item.tmdbId > 0 && data.item.creativeWorkType) {
showAllReviews = false;
showAllRecommendations = false;
+ showAllCast = false;
}
});
@@ -114,19 +120,13 @@
{#if data.item.backdrop}
-
-
})
-
-
+
{/if}
+
@@ -325,15 +325,23 @@
{/if}
{#if data.cast.length > 0}
-
+
Cast
- {#each data.cast as castMember (castMember.id)}
+ {#each data.cast as castMember, index (castMember.id)}
{/each}
+
+ {#if !showAllCast && data.cast.length > 6}
+
+ {/if}
+
+ {/if}
+
+ {#if data.backdrops.length > 0}
+
{/if}
diff --git a/src/lib/components/MediaCollection.svelte b/src/lib/components/MediaCollection.svelte
new file mode 100644
index 0000000..139bd36
--- /dev/null
+++ b/src/lib/components/MediaCollection.svelte
@@ -0,0 +1,52 @@
+
+
+
+ {title}
+
+
+
+
+
+
+
+
+ {#if !expanded && items.length > 6}
+
+ {/if}
+
diff --git a/src/lib/components/MenuItem.svelte b/src/lib/components/MenuItem.svelte
index f1d1fe5..e7ac9c6 100644
--- a/src/lib/components/MenuItem.svelte
+++ b/src/lib/components/MenuItem.svelte
@@ -10,6 +10,7 @@
tooltip = label,
active = false,
onclick,
+ variant = 'sidebar',
class: className,
children
}: {
@@ -18,13 +19,17 @@
tooltip?: string;
active?: boolean;
onclick?: () => void;
+ variant?: 'sidebar' | 'bottom';
class?: string;
children: Snippet;
} = $props();
let styles = $derived(
cn(
- 'group relative flex h-10 w-full items-center gap-3 rounded-lg p-2.5 text-sm font-semibold transition-colors md:size-10',
+ 'group relative flex transition-colors',
+ variant === 'bottom'
+ ? 'h-12 min-w-0 flex-col items-center justify-center gap-1 rounded-xl px-2 py-1 text-[0.65rem] font-medium'
+ : 'h-10 w-full items-center gap-3 rounded-lg p-2.5 text-sm font-semibold md:size-10',
active
? 'bg-accent-500/10 text-accent-300'
: 'text-base-300 hover:bg-white/10 hover:text-white',
@@ -35,12 +40,14 @@
{#snippet content()}
{@render children()}
- {label}
-
- {tooltip}
-
+ {label}
+ {#if variant === 'sidebar'}
+
+ {tooltip}
+
+ {/if}
{/snippet}
{#if href}
diff --git a/src/lib/components/SearchMenuItem.svelte b/src/lib/components/SearchMenuItem.svelte
index fccbbfa..f7f2d2e 100644
--- a/src/lib/components/SearchMenuItem.svelte
+++ b/src/lib/components/SearchMenuItem.svelte
@@ -7,7 +7,11 @@
import MenuItem from './MenuItem.svelte';
import SearchCommand from './SearchCommand.svelte';
- let { did, onOpen }: { did: string | null; onOpen?: () => void } = $props();
+ let {
+ did,
+ variant = 'sidebar',
+ onOpen
+ }: { did: string | null; variant?: 'sidebar' | 'bottom'; onOpen?: () => void } = $props();
let open = $state(false);
$effect(() => {
@@ -31,7 +35,7 @@
-
diff --git a/src/routes/profile/[actor]/+layout.server.ts b/src/routes/profile/[actor]/+layout.server.ts
index 528a5c2..19b725c 100644
--- a/src/routes/profile/[actor]/+layout.server.ts
+++ b/src/routes/profile/[actor]/+layout.server.ts
@@ -39,12 +39,23 @@ export const load: LayoutServerLoad = async ({ params }) => {
const did = profileEntry.did;
const avatar =
bskyProfile?.value && 'avatar' in bskyProfile.value ? bskyProfile.value.avatar : undefined;
+ const popfeedBanner = popfeedProfile?.value?.banner;
+ const bskyBanner = bskyProfile?.value?.banner;
+ const banner = popfeedBanner ?? bskyBanner;
+ const bannerUrl = banner
+ ? getAtprotoCdnImageUrl({ did, blob: banner, preset: 'banner' })
+ : undefined;
+
return {
profile: {
did,
handle: profileEntry.handle ?? did,
displayName: popfeedProfile?.value?.displayName ?? bskyProfile?.value?.displayName,
- avatarUrl: avatar ? getAtprotoCdnImageUrl({ did, blob: avatar, preset: 'avatar' }) : undefined
+ description: popfeedProfile?.value?.description ?? bskyProfile?.value?.description,
+ avatarUrl: avatar
+ ? getAtprotoCdnImageUrl({ did, blob: avatar, preset: 'avatar' })
+ : undefined,
+ bannerUrl
}
};
};
diff --git a/src/routes/profile/[actor]/+page.svelte b/src/routes/profile/[actor]/+page.svelte
index 53e2575..d993fd6 100644
--- a/src/routes/profile/[actor]/+page.svelte
+++ b/src/routes/profile/[actor]/+page.svelte
@@ -1,6 +1,6 @@