feat: refactor play area into sidebar layout with buttplug, recordings, and leaderboard sub-pages
- Add /play sidebar layout (mobile nav + desktop sidebar) with SexyBackground - Move buttplug device control to /play/buttplug with Empty component and scan button - Move recordings from /me/recordings to /play/recordings - Move leaderboard to /play/leaderboard; redirect /leaderboard → /play/leaderboard - Redirect /me/recordings → /play/recordings and /play → /play/buttplug - Remove recordings entry from /me sidebar nav - Rename "SexyPlay" → "Play", swap bluetooth icon for rocket, remove subtitle - Add play.nav i18n keys (play, recordings, leaderboard, back_to_site, back_mobile) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
108
packages/frontend/src/routes/play/+layout.svelte
Normal file
108
packages/frontend/src/routes/play/+layout.svelte
Normal file
@@ -0,0 +1,108 @@
|
||||
<script lang="ts">
|
||||
import { page } from "$app/state";
|
||||
import { _ } from "svelte-i18n";
|
||||
import { Avatar, AvatarImage, AvatarFallback } from "$lib/components/ui/avatar";
|
||||
import { getUserInitials } from "$lib/utils";
|
||||
import { getAssetUrl } from "$lib/api";
|
||||
import SexyBackground from "$lib/components/background/background.svelte";
|
||||
|
||||
const { children, data } = $props();
|
||||
|
||||
const navLinks = $derived([
|
||||
{ name: $_("play.nav.play"), href: "/play/buttplug", icon: "icon-[ri--rocket-line]", exact: false },
|
||||
{ name: $_("play.nav.recordings"), href: "/play/recordings", icon: "icon-[ri--play-list-2-line]", exact: false },
|
||||
{ name: $_("play.nav.leaderboard"), href: "/play/leaderboard", icon: "icon-[ri--trophy-line]", exact: false },
|
||||
]);
|
||||
|
||||
function isActive(link: { href: string; exact: boolean }) {
|
||||
if (link.exact) return page.url.pathname === link.href;
|
||||
return page.url.pathname.startsWith(link.href);
|
||||
}
|
||||
|
||||
const user = $derived(data.authStatus.user!);
|
||||
const avatarUrl = $derived(
|
||||
user.avatar ? (getAssetUrl(user.avatar, "thumbnail") ?? undefined) : undefined,
|
||||
);
|
||||
const displayName = $derived(user.artist_name ?? user.email);
|
||||
</script>
|
||||
|
||||
<div class="min-h-screen bg-gradient-to-br from-background via-primary/5 to-accent/5 relative">
|
||||
<SexyBackground />
|
||||
|
||||
<div class="container mx-auto px-4 relative z-10">
|
||||
<!-- Mobile top nav -->
|
||||
<div class="lg:hidden border-b border-border/40">
|
||||
<div class="flex items-center gap-1 overflow-x-auto py-2 scrollbar-none">
|
||||
<a
|
||||
href="/"
|
||||
class="shrink-0 flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors px-2"
|
||||
>
|
||||
<span class="icon-[ri--arrow-left-line] h-4 w-4"></span>
|
||||
<span class="hidden sm:inline">{$_("play.nav.back_mobile")}</span>
|
||||
</a>
|
||||
{#each navLinks as link (link.href)}
|
||||
<a
|
||||
href={link.href}
|
||||
class={`shrink-0 flex items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-sm font-medium transition-colors ${
|
||||
isActive(link)
|
||||
? "bg-primary/10 text-primary"
|
||||
: "text-muted-foreground hover:text-foreground hover:bg-muted/50"
|
||||
}`}
|
||||
>
|
||||
<span class={`${link.icon} h-4 w-4 shrink-0`}></span>
|
||||
<span class="hidden sm:inline">{link.name}</span>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Desktop layout -->
|
||||
<div class="flex min-h-screen">
|
||||
<!-- Sidebar (desktop only) -->
|
||||
<aside class="hidden lg:flex w-56 shrink-0 flex-col border-r border-border/40">
|
||||
<div class="px-4 py-5 border-b border-border/40">
|
||||
<a
|
||||
href="/"
|
||||
class="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
<span class="icon-[ri--arrow-left-line] h-3.5 w-3.5"></span>
|
||||
{$_("play.nav.back_to_site")}
|
||||
</a>
|
||||
<div class="mt-3 flex items-center gap-3">
|
||||
<Avatar class="h-9 w-9 shrink-0">
|
||||
<AvatarImage src={avatarUrl} alt={displayName} />
|
||||
<AvatarFallback class="text-xs">
|
||||
{getUserInitials(displayName)}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div class="min-w-0">
|
||||
<p class="text-sm font-semibold text-foreground truncate">{displayName}</p>
|
||||
<p class="text-xs text-primary">{$_("play.title")}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="flex-1 p-3 space-y-1">
|
||||
{#each navLinks as link (link.href)}
|
||||
<a
|
||||
href={link.href}
|
||||
class={`flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors ${
|
||||
isActive(link)
|
||||
? "bg-primary/10 text-primary"
|
||||
: "text-muted-foreground hover:text-foreground hover:bg-muted/50"
|
||||
}`}
|
||||
>
|
||||
<span class={`${link.icon} h-4 w-4`}></span>
|
||||
{link.name}
|
||||
</a>
|
||||
{/each}
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<!-- Main content -->
|
||||
<main class="flex-1 min-w-0">
|
||||
{@render children()}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user