refactor(web-player): centralise styles and drop dead code
Styles: adds the scrim and on-art tokens so the black overlays and the white marks drawn over artwork stop being raw tailwind colours, and routes every component through the semantic layer instead of reaching into the --mf-* palette. Renames --mf-accent-60, which was actually an 80% mix, to --mf-accent-soft, and removes the unused --mf-radius and the --color-neutral-950 override. Dead code: the root +layout.server.ts already redirects everything except /login, so the anonymous branches in the home and explore loads and the needsAuth empty state could never run. Deletes the unused accentSoft helper, and stops exporting helpers nobody imports. fmtTime and fmtDate move out of theme/color.ts into $lib/format.ts; they are not colour concerns. The accent animation now cancels its pending frame on teardown.
This commit is contained in:
@@ -69,7 +69,7 @@
|
|||||||
? 'text-fg'
|
? 'text-fg'
|
||||||
: 'text-fg-3 hover:bg-hover hover:text-fg'}"
|
: 'text-fg-3 hover:bg-hover hover:text-fg'}"
|
||||||
>
|
>
|
||||||
<link.icon class="h-6 w-6 shrink-0" style={active ? 'color:var(--mf-accent-60)' : ''} />
|
<link.icon class="h-6 w-6 shrink-0 {active ? 'text-accent-soft' : ''}" />
|
||||||
{link.label}
|
{link.label}
|
||||||
</a>
|
</a>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
@@ -87,10 +87,7 @@
|
|||||||
: ''}"
|
: ''}"
|
||||||
style="top:{pillTop}px; height:{pillHeight}px; opacity:{pillVisible ? 1 : 0}"
|
style="top:{pillTop}px; height:{pillHeight}px; opacity:{pillVisible ? 1 : 0}"
|
||||||
>
|
>
|
||||||
<span
|
<span class="absolute inset-y-0 left-0 w-[3px] rounded-r-full bg-accent-soft"></span>
|
||||||
class="absolute inset-y-0 left-0 w-[3px] rounded-r-full"
|
|
||||||
style="background:var(--mf-accent-60)"
|
|
||||||
></span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav class="relative z-10 flex flex-col gap-1">
|
<nav class="relative z-10 flex flex-col gap-1">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { player } from '$lib/player/player.svelte';
|
import { player } from '$lib/player/player.svelte';
|
||||||
import { fmtTime } from '$lib/theme/color';
|
import { fmtTime } from '$lib/format';
|
||||||
|
|
||||||
function seek(event: MouseEvent) {
|
function seek(event: MouseEvent) {
|
||||||
const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();
|
const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();
|
||||||
@@ -23,11 +23,11 @@
|
|||||||
style="width:{player.progressPercent}%"
|
style="width:{player.progressPercent}%"
|
||||||
></div>
|
></div>
|
||||||
<span
|
<span
|
||||||
class="pointer-events-none absolute top-1/2 h-3 w-3 -translate-x-1/2 -translate-y-1/2 rounded-full bg-white opacity-0 shadow-[0_1px_4px_rgba(0,0,0,0.5)] transition-opacity duration-75 group-hover:opacity-100"
|
class="pointer-events-none absolute top-1/2 h-3 w-3 -translate-x-1/2 -translate-y-1/2 rounded-full bg-on-art opacity-0 shadow-[0_1px_4px_rgba(0,0,0,0.5)] transition-opacity duration-75 group-hover:opacity-100"
|
||||||
style="left:{player.progressPercent}%"
|
style="left:{player.progressPercent}%"
|
||||||
></span>
|
></span>
|
||||||
</button>
|
</button>
|
||||||
<span class="text-xs text-(--mf-text-4) tabular-nums">
|
<span class="text-xs text-muted tabular-nums">
|
||||||
{fmtTime(player.current.duration)}
|
{fmtTime(player.current.duration)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
oninput={handleVolumeChange}
|
oninput={handleVolumeChange}
|
||||||
aria-label="Volumen"
|
aria-label="Volumen"
|
||||||
class="mf-volume"
|
class="mf-volume"
|
||||||
style="background:linear-gradient(to right, var(--mf-accent-60) {player.volume}%, var(--mf-track) {player.volume}%)"
|
style="background:linear-gradient(to right, var(--color-accent-soft) {player.volume}%, var(--color-track) {player.volume}%)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -37,14 +37,14 @@
|
|||||||
width: 12px;
|
width: 12px;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: var(--mf-text);
|
background: var(--color-fg);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.mf-volume::-moz-range-thumb {
|
.mf-volume::-moz-range-thumb {
|
||||||
width: 12px;
|
width: 12px;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: var(--mf-text);
|
background: var(--color-fg);
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
primary: 'bg-accent-soft font-semibold text-on-accent active:scale-[0.97]',
|
primary: 'bg-accent-soft font-semibold text-on-accent active:scale-[0.97]',
|
||||||
secondary: 'border border-line text-fg hover:bg-hover',
|
secondary: 'border border-line text-fg hover:bg-hover',
|
||||||
subtle: 'bg-surface-2 font-semibold text-fg hover:bg-surface-hover',
|
subtle: 'bg-surface-2 font-semibold text-fg hover:bg-surface-hover',
|
||||||
danger: 'bg-danger font-semibold text-white hover:brightness-110'
|
danger: 'bg-danger font-semibold text-on-accent hover:brightness-110'
|
||||||
};
|
};
|
||||||
|
|
||||||
const classes = $derived(
|
const classes = $derived(
|
||||||
|
|||||||
@@ -68,7 +68,7 @@
|
|||||||
|
|
||||||
{#if open}
|
{#if open}
|
||||||
<div
|
<div
|
||||||
class="animate-fade fixed inset-0 z-[60] grid place-items-center bg-black/70 p-4 backdrop-blur-sm"
|
class="animate-fade fixed inset-0 z-[60] grid place-items-center bg-scrim/70 p-4 backdrop-blur-sm"
|
||||||
role="presentation"
|
role="presentation"
|
||||||
onclick={onBackdropClick}
|
onclick={onBackdropClick}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -13,10 +13,10 @@
|
|||||||
];
|
];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="absolute inset-0 flex items-end justify-center gap-[2.5px] bg-black/45 pb-2">
|
<div class="absolute inset-0 flex items-end justify-center gap-[2.5px] bg-scrim/45 pb-2">
|
||||||
{#each bars as bar, i (i)}
|
{#each bars as bar, i (i)}
|
||||||
<span
|
<span
|
||||||
class="w-[3px] origin-bottom rounded-full bg-white"
|
class="w-[3px] origin-bottom rounded-full bg-on-art"
|
||||||
style="height:14px; animation:equalize {bar.dur}s ease-in-out {bar.delay}s infinite; animation-play-state:{paused
|
style="height:14px; animation:equalize {bar.dur}s ease-in-out {bar.delay}s infinite; animation-play-state:{paused
|
||||||
? 'paused'
|
? 'paused'
|
||||||
: 'running'}"
|
: 'running'}"
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
export function fmtTime(seconds: number): string {
|
||||||
|
const total = Math.max(0, Math.floor(seconds || 0));
|
||||||
|
const minutes = Math.floor(total / 60);
|
||||||
|
const rest = total % 60;
|
||||||
|
return `${minutes}:${rest < 10 ? '0' : ''}${rest}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dateFormatter = new Intl.DateTimeFormat('es', { dateStyle: 'medium' });
|
||||||
|
|
||||||
|
export function fmtDate(value: string | number | Date): string {
|
||||||
|
return dateFormatter.format(new Date(value));
|
||||||
|
}
|
||||||
@@ -40,7 +40,7 @@ export interface ApiTrackLike {
|
|||||||
isExplicit?: boolean;
|
isExplicit?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isYouTubeTrack(track: ApiTrackLike): boolean {
|
function isYouTubeTrack(track: ApiTrackLike): boolean {
|
||||||
return track.source === 'YouTube';
|
return track.source === 'YouTube';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { createApiClient } from '$lib/server/api';
|
import type { createApiClient } from '$lib/server/api';
|
||||||
import type { YouTubeSong } from '$lib/types';
|
import type { YouTubeSong } from '$lib/types';
|
||||||
|
|
||||||
export const YOUTUBE_FILLER_QUERIES = [
|
const YOUTUBE_FILLER_QUERIES = [
|
||||||
'pop',
|
'pop',
|
||||||
'rock',
|
'rock',
|
||||||
'reggaeton',
|
'reggaeton',
|
||||||
@@ -24,7 +24,7 @@ export interface YoutubeFiller {
|
|||||||
continuationToken: string;
|
continuationToken: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function pickRandomFillerQuery(): string {
|
function pickRandomFillerQuery(): string {
|
||||||
return YOUTUBE_FILLER_QUERIES[Math.floor(Math.random() * YOUTUBE_FILLER_QUERIES.length)];
|
return YOUTUBE_FILLER_QUERIES[Math.floor(Math.random() * YOUTUBE_FILLER_QUERIES.length)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,23 +15,6 @@ export function accentForHue(hue: number): string {
|
|||||||
return `oklch(${ACCENT_LIGHTNESS}% ${ACCENT_CHROMA} ${hue})`;
|
return `oklch(${ACCENT_LIGHTNESS}% ${ACCENT_CHROMA} ${hue})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function accentSoft(hue: number, pct: number): string {
|
|
||||||
return `color-mix(in srgb, ${accentForHue(hue)} ${pct}%, transparent)`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function gradientForHue(hue: number): string {
|
export function gradientForHue(hue: number): string {
|
||||||
return `linear-gradient(135deg, oklch(70% 0.15 ${hue}), oklch(36% 0.13 ${hue + 28}))`;
|
return `linear-gradient(135deg, oklch(70% 0.15 ${hue}), oklch(36% 0.13 ${hue + 28}))`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fmtTime(seconds: number): string {
|
|
||||||
const total = Math.max(0, Math.floor(seconds || 0));
|
|
||||||
const minutes = Math.floor(total / 60);
|
|
||||||
const rest = total % 60;
|
|
||||||
return `${minutes}:${rest < 10 ? '0' : ''}${rest}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const dateFormatter = new Intl.DateTimeFormat('es', { dateStyle: 'medium' });
|
|
||||||
|
|
||||||
export function fmtDate(value: string | number | Date): string {
|
|
||||||
return dateFormatter.format(new Date(value));
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -16,7 +16,10 @@
|
|||||||
--mf-track: rgba(255, 255, 255, 0.12);
|
--mf-track: rgba(255, 255, 255, 0.12);
|
||||||
|
|
||||||
--mf-accent: oklch(60% 0.17 145);
|
--mf-accent: oklch(60% 0.17 145);
|
||||||
--mf-accent-60: color-mix(in oklch, var(--mf-accent) 80%, transparent);
|
--mf-accent-soft: color-mix(in oklch, var(--mf-accent) 80%, transparent);
|
||||||
--mf-radius: 12px;
|
|
||||||
|
--mf-scrim: #000000;
|
||||||
|
--mf-on-art: #ffffff;
|
||||||
|
|
||||||
--mf-sidebar-w: 264px;
|
--mf-sidebar-w: 264px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export function targetCoverSrc(target: TrackTarget): string | undefined {
|
|||||||
return target.kind === 'youtube' ? target.song.thumbnailUrl : undefined;
|
return target.kind === 'youtube' ? target.song.thumbnailUrl : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function queueIdForTarget(target: TrackTarget): string | number {
|
function queueIdForTarget(target: TrackTarget): string | number {
|
||||||
return target.kind === 'youtube' ? target.song.videoId : queueIdForTrack(target.track);
|
return target.kind === 'youtube' ? target.song.videoId : queueIdForTrack(target.track);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { PageServerLoad, Actions } from './$types';
|
import type { PageServerLoad, Actions } from './$types';
|
||||||
import { createApiClient, unwrapOrError } from '$lib/server/api';
|
import { createApiClient, requireUser, unwrapOrError } from '$lib/server/api';
|
||||||
import { fetchYoutubeFiller } from '$lib/server/youtube';
|
import { fetchYoutubeFiller } from '$lib/server/youtube';
|
||||||
import { addTrackAction, addYouTubeToPlaylistAction } from '$lib/server/playlistActions';
|
import { addTrackAction, addYouTubeToPlaylistAction } from '$lib/server/playlistActions';
|
||||||
import { EXPLORE_PAGE_SIZE as PAGE_SIZE, YOUTUBE_FILLER_LIMIT } from '$lib/config';
|
import { EXPLORE_PAGE_SIZE as PAGE_SIZE, YOUTUBE_FILLER_LIMIT } from '$lib/config';
|
||||||
@@ -8,23 +8,21 @@ export const load: PageServerLoad = async ({ url, locals, fetch }) => {
|
|||||||
const query = url.searchParams.get('q')?.trim() ?? '';
|
const query = url.searchParams.get('q')?.trim() ?? '';
|
||||||
const page = Math.max(1, Number(url.searchParams.get('page')) || 1);
|
const page = Math.max(1, Number(url.searchParams.get('page')) || 1);
|
||||||
|
|
||||||
|
const user = requireUser(locals, url);
|
||||||
const api = createApiClient({ fetch, accessToken: locals.accessToken ?? undefined });
|
const api = createApiClient({ fetch, accessToken: locals.accessToken ?? undefined });
|
||||||
|
|
||||||
const tracksPromise = api.GET('/tracks', {
|
const tracksPromise = api.GET('/tracks', {
|
||||||
params: { query: { name: query || undefined, pageNumber: page, pageSize: PAGE_SIZE } }
|
params: { query: { name: query || undefined, pageNumber: page, pageSize: PAGE_SIZE } }
|
||||||
});
|
});
|
||||||
|
|
||||||
const searchPromise =
|
const searchPromise = query
|
||||||
query && locals.accessToken
|
? api.GET('/youtube/search', { params: { query: { query } } })
|
||||||
? api.GET('/youtube/search', { params: { query: { query } } })
|
|
||||||
: Promise.resolve(null);
|
|
||||||
|
|
||||||
const playlistsPromise = locals.user
|
|
||||||
? api.GET('/playlists/users/{userId}', {
|
|
||||||
params: { path: { userId: locals.user.sub }, query: { pageNumber: 1, pageSize: 50 } }
|
|
||||||
})
|
|
||||||
: Promise.resolve(null);
|
: Promise.resolve(null);
|
||||||
|
|
||||||
|
const playlistsPromise = api.GET('/playlists/users/{userId}', {
|
||||||
|
params: { path: { userId: user.sub }, query: { pageNumber: 1, pageSize: 50 } }
|
||||||
|
});
|
||||||
|
|
||||||
const [tracksRes, searchRes, playlistsRes] = await Promise.all([
|
const [tracksRes, searchRes, playlistsRes] = await Promise.all([
|
||||||
tracksPromise,
|
tracksPromise,
|
||||||
searchPromise,
|
searchPromise,
|
||||||
@@ -41,8 +39,7 @@ export const load: PageServerLoad = async ({ url, locals, fetch }) => {
|
|||||||
youtubeFiller: query
|
youtubeFiller: query
|
||||||
? Promise.resolve(null)
|
? Promise.resolve(null)
|
||||||
: fetchYoutubeFiller(api, locals.accessToken, YOUTUBE_FILLER_LIMIT),
|
: fetchYoutubeFiller(api, locals.accessToken, YOUTUBE_FILLER_LIMIT),
|
||||||
playlists: playlistsRes?.data?.items ?? [],
|
playlists: playlistsRes?.data?.items ?? []
|
||||||
needsAuth: !locals.user
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -84,9 +84,7 @@
|
|||||||
|
|
||||||
const hasMore = $derived(localHasNext || ytContinuation !== '');
|
const hasMore = $derived(localHasNext || ytContinuation !== '');
|
||||||
|
|
||||||
const nothingFound = $derived(
|
const nothingFound = $derived(!!data.query && items.length === 0 && !data.ytError);
|
||||||
!!data.query && items.length === 0 && !data.needsAuth && !data.ytError
|
|
||||||
);
|
|
||||||
|
|
||||||
function buildHref(query: string) {
|
function buildHref(query: string) {
|
||||||
return query ? `/explore?q=${encodeURIComponent(query)}` : '/explore';
|
return query ? `/explore?q=${encodeURIComponent(query)}` : '/explore';
|
||||||
@@ -212,12 +210,7 @@
|
|||||||
<InfiniteScroll onLoadMore={loadMore} {hasMore} loading={loadingMore} />
|
<InfiniteScroll onLoadMore={loadMore} {hasMore} loading={loadingMore} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if data.needsAuth}
|
{#if data.ytError}
|
||||||
<EmptyState
|
|
||||||
icon={Music}
|
|
||||||
description="Inicia sesión para ver también resultados de YouTube Music."
|
|
||||||
/>
|
|
||||||
{:else if data.ytError}
|
|
||||||
<EmptyState
|
<EmptyState
|
||||||
icon={Music}
|
icon={Music}
|
||||||
description="YouTube Music no está disponible ahora mismo. Inténtalo de nuevo."
|
description="YouTube Music no está disponible ahora mismo. Inténtalo de nuevo."
|
||||||
|
|||||||
@@ -4,7 +4,8 @@
|
|||||||
import X from '@lucide/svelte/icons/x';
|
import X from '@lucide/svelte/icons/x';
|
||||||
import Music from '@lucide/svelte/icons/music';
|
import Music from '@lucide/svelte/icons/music';
|
||||||
import { player, toQueueItems } from '$lib/player/player.svelte';
|
import { player, toQueueItems } from '$lib/player/player.svelte';
|
||||||
import { hueFor, fmtTime, fmtDate } from '$lib/theme/color';
|
import { hueFor } from '$lib/theme/color';
|
||||||
|
import { fmtTime, fmtDate } from '$lib/format';
|
||||||
import Page from '$lib/components/ui/Page.svelte';
|
import Page from '$lib/components/ui/Page.svelte';
|
||||||
import PageHeader from '$lib/components/ui/PageHeader.svelte';
|
import PageHeader from '$lib/components/ui/PageHeader.svelte';
|
||||||
import EmptyState from '$lib/components/ui/EmptyState.svelte';
|
import EmptyState from '$lib/components/ui/EmptyState.svelte';
|
||||||
|
|||||||
@@ -9,7 +9,8 @@
|
|||||||
youTubeThumbnailUrl,
|
youTubeThumbnailUrl,
|
||||||
type ApiTrackLike
|
type ApiTrackLike
|
||||||
} from '$lib/player/player.svelte';
|
} from '$lib/player/player.svelte';
|
||||||
import { hueFor, fmtTime, fmtDate } from '$lib/theme/color';
|
import { hueFor } from '$lib/theme/color';
|
||||||
|
import { fmtTime, fmtDate } from '$lib/format';
|
||||||
import Badge from '$lib/components/ui/Badge.svelte';
|
import Badge from '$lib/components/ui/Badge.svelte';
|
||||||
import IconButton from '$lib/components/ui/IconButton.svelte';
|
import IconButton from '$lib/components/ui/IconButton.svelte';
|
||||||
import TrackTable from '$lib/components/ui/TrackTable.svelte';
|
import TrackTable from '$lib/components/ui/TrackTable.svelte';
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
type ApiTrackLike,
|
type ApiTrackLike,
|
||||||
type QueueItem
|
type QueueItem
|
||||||
} from '$lib/player/player.svelte';
|
} from '$lib/player/player.svelte';
|
||||||
import { hueFor, fmtTime } from '$lib/theme/color';
|
import { hueFor } from '$lib/theme/color';
|
||||||
|
import { fmtTime } from '$lib/format';
|
||||||
import type { YouTubeSong } from '$lib/types';
|
import type { YouTubeSong } from '$lib/types';
|
||||||
import SectionHeading from '$lib/components/ui/SectionHeading.svelte';
|
import SectionHeading from '$lib/components/ui/SectionHeading.svelte';
|
||||||
import IconButton from '$lib/components/ui/IconButton.svelte';
|
import IconButton from '$lib/components/ui/IconButton.svelte';
|
||||||
|
|||||||
@@ -52,18 +52,19 @@
|
|||||||
if (t < 1) rafId = requestAnimationFrame(tick);
|
if (t < 1) rafId = requestAnimationFrame(tick);
|
||||||
};
|
};
|
||||||
rafId = requestAnimationFrame(tick);
|
rafId = requestAnimationFrame(tick);
|
||||||
|
return () => cancelAnimationFrame(rafId);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head><link rel="icon" href={favicon} /></svelte:head>
|
<svelte:head><link rel="icon" href={favicon} /></svelte:head>
|
||||||
|
|
||||||
{#if isAuthPage}
|
{#if isAuthPage || !data.user}
|
||||||
<div class="min-h-screen bg-bg text-fg antialiased">
|
<div class="min-h-screen bg-bg text-fg antialiased">
|
||||||
{@render children()}
|
{@render children()}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex min-h-screen bg-bg text-fg antialiased">
|
<div class="flex min-h-screen bg-bg text-fg antialiased">
|
||||||
<Sidebar user={data.user!} />
|
<Sidebar user={data.user} />
|
||||||
<div class="flex min-w-0 flex-1 flex-col">
|
<div class="flex min-w-0 flex-1 flex-col">
|
||||||
<main class="flex-1 transition-[padding] {hasTrack ? 'pb-[92px]' : 'pb-0'}">
|
<main class="flex-1 transition-[padding] {hasTrack ? 'pb-[92px]' : 'pb-0'}">
|
||||||
{#key page.url.pathname}
|
{#key page.url.pathname}
|
||||||
|
|||||||
@@ -1,24 +1,23 @@
|
|||||||
import type { PageServerLoad, Actions } from './$types';
|
import type { PageServerLoad, Actions } from './$types';
|
||||||
import { createApiClient, playlistCoverTrackIds } from '$lib/server/api';
|
import { createApiClient, playlistCoverTrackIds, requireUser } from '$lib/server/api';
|
||||||
import { fetchYoutubeFiller, shuffle } from '$lib/server/youtube';
|
import { fetchYoutubeFiller, shuffle } from '$lib/server/youtube';
|
||||||
import { addTrackAction, addYouTubeToPlaylistAction } from '$lib/server/playlistActions';
|
import { addTrackAction, addYouTubeToPlaylistAction } from '$lib/server/playlistActions';
|
||||||
import { HOME_LATEST_PAGE_SIZE, YOUTUBE_FILLER_LIMIT } from '$lib/config';
|
import { HOME_LATEST_PAGE_SIZE, YOUTUBE_FILLER_LIMIT } from '$lib/config';
|
||||||
import { pickGreeting } from '$lib/server/greeting';
|
import { pickGreeting } from '$lib/server/greeting';
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ locals, fetch }) => {
|
export const load: PageServerLoad = async ({ locals, url, fetch }) => {
|
||||||
|
const user = requireUser(locals, url);
|
||||||
const api = createApiClient({ fetch, accessToken: locals.accessToken ?? undefined });
|
const api = createApiClient({ fetch, accessToken: locals.accessToken ?? undefined });
|
||||||
|
|
||||||
const latestPromise = api.GET('/tracks', {
|
const latestPromise = api.GET('/tracks', {
|
||||||
params: { query: { pageNumber: 1, pageSize: HOME_LATEST_PAGE_SIZE } }
|
params: { query: { pageNumber: 1, pageSize: HOME_LATEST_PAGE_SIZE } }
|
||||||
});
|
});
|
||||||
const playlistsPromise = locals.user
|
const playlistsPromise = api.GET('/playlists/users/{userId}', {
|
||||||
? api.GET('/playlists/users/{userId}', {
|
params: { path: { userId: user.sub }, query: { pageNumber: 1, pageSize: 12 } }
|
||||||
params: { path: { userId: locals.user.sub }, query: { pageNumber: 1, pageSize: 12 } }
|
});
|
||||||
})
|
const recentlyPlayedPromise = api.GET('/users/{id}/listening-history', {
|
||||||
: Promise.resolve({ data: undefined });
|
params: { path: { id: user.sub } }
|
||||||
const recentlyPlayedPromise = locals.user
|
});
|
||||||
? api.GET('/users/{id}/listening-history', { params: { path: { id: locals.user.sub } } })
|
|
||||||
: Promise.resolve({ data: undefined });
|
|
||||||
|
|
||||||
const [latest, playlists, recentlyPlayed] = await Promise.all([
|
const [latest, playlists, recentlyPlayed] = await Promise.all([
|
||||||
latestPromise,
|
latestPromise,
|
||||||
|
|||||||
@@ -171,7 +171,7 @@
|
|||||||
<section class="relative overflow-hidden page-x pt-11 pb-16">
|
<section class="relative overflow-hidden page-x pt-11 pb-16">
|
||||||
<div
|
<div
|
||||||
class="pointer-events-none absolute inset-0 blur-[70px] saturate-150"
|
class="pointer-events-none absolute inset-0 blur-[70px] saturate-150"
|
||||||
style="background:linear-gradient(135deg, var(--mf-accent), color-mix(in oklch, var(--mf-accent), black 55%));animation:breathe 9s ease-in-out infinite"
|
style="background:linear-gradient(135deg, var(--color-accent), color-mix(in oklch, var(--color-accent), black 55%));animation:breathe 9s ease-in-out infinite"
|
||||||
></div>
|
></div>
|
||||||
<div class="animate-enter relative flex flex-col gap-2.5">
|
<div class="animate-enter relative flex flex-col gap-2.5">
|
||||||
<p class="tracking-[0.14em] text-fg-2 uppercase">{data.greeting}</p>
|
<p class="tracking-[0.14em] text-fg-2 uppercase">{data.greeting}</p>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
--font-sans: 'Inter Variable', ui-sans-serif, system-ui, sans-serif;
|
--font-sans: 'Inter Variable', ui-sans-serif, system-ui, sans-serif;
|
||||||
--color-neutral-950: #08080a;
|
|
||||||
|
|
||||||
--color-bg: var(--mf-bg);
|
--color-bg: var(--mf-bg);
|
||||||
--color-elevated: var(--mf-elevated);
|
--color-elevated: var(--mf-elevated);
|
||||||
@@ -18,8 +17,10 @@
|
|||||||
--color-fg-3: var(--mf-text-3);
|
--color-fg-3: var(--mf-text-3);
|
||||||
--color-muted: var(--mf-text-4);
|
--color-muted: var(--mf-text-4);
|
||||||
--color-accent: var(--mf-accent);
|
--color-accent: var(--mf-accent);
|
||||||
--color-accent-soft: var(--mf-accent-60);
|
--color-accent-soft: var(--mf-accent-soft);
|
||||||
--color-on-accent: #ffffff;
|
--color-on-accent: var(--mf-on-art);
|
||||||
|
--color-scrim: var(--mf-scrim);
|
||||||
|
--color-on-art: var(--mf-on-art);
|
||||||
--color-danger: oklch(63.7% 0.237 25.331);
|
--color-danger: oklch(63.7% 0.237 25.331);
|
||||||
--color-danger-fg: oklch(88.5% 0.062 18.334);
|
--color-danger-fg: oklch(88.5% 0.062 18.334);
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<div class="grid min-h-screen place-items-center bg-bg px-6 text-fg">
|
<div class="grid min-h-screen place-items-center bg-bg px-6 text-fg">
|
||||||
<div class="flex w-full max-w-[440px] flex-col items-center gap-9 text-center">
|
<div class="flex w-full max-w-110 flex-col items-center gap-9 text-center">
|
||||||
<div class="text-3xl font-extrabold tracking-tight">Musify</div>
|
<div class="text-3xl font-extrabold tracking-tight">Musify</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
Reference in New Issue
Block a user