Design Prot Merge #1
@@ -0,0 +1,80 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type { SessionUser } from '$lib/types';
|
||||||
|
import Upload from '@lucide/svelte/icons/upload';
|
||||||
|
import Folder from '@lucide/svelte/icons/folder';
|
||||||
|
import LogOut from '@lucide/svelte/icons/log-out';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
user: SessionUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { user }: Props = $props();
|
||||||
|
|
||||||
|
let menuOpen = $state(false);
|
||||||
|
let menuRef: HTMLDivElement | undefined = $state();
|
||||||
|
|
||||||
|
function onDocumentClick(event: MouseEvent) {
|
||||||
|
if (menuRef && !menuRef.contains(event.target as Node)) menuOpen = false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<header
|
||||||
|
class="sticky top-0 z-40 flex h-14 items-center justify-between border-b border-line bg-bg/85 page-x backdrop-blur-md md:hidden"
|
||||||
|
>
|
||||||
|
<a href="/" class="text-lg font-extrabold tracking-wide">Musify</a>
|
||||||
|
|
||||||
|
<div class="relative" bind:this={menuRef}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={() => (menuOpen = !menuOpen)}
|
||||||
|
aria-label="Tu cuenta"
|
||||||
|
aria-expanded={menuOpen}
|
||||||
|
class="flex items-center rounded-full"
|
||||||
|
>
|
||||||
|
{#if user.picture}
|
||||||
|
<img src={user.picture} alt="" class="h-9 w-9 rounded-full object-cover" />
|
||||||
|
{:else}
|
||||||
|
<span
|
||||||
|
class="grid h-9 w-9 place-items-center rounded-full bg-surface-2 text-sm font-semibold uppercase"
|
||||||
|
>
|
||||||
|
{user.name.charAt(0)}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{#if menuOpen}
|
||||||
|
<div
|
||||||
|
class="animate-pop absolute top-full right-0 mt-2 w-56 overflow-hidden rounded-panel border border-line bg-elevated py-1.5 shadow-menu"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="/library"
|
||||||
|
onclick={() => (menuOpen = false)}
|
||||||
|
class="flex w-full items-center gap-3 px-3 py-2.5 text-base font-semibold text-fg-2 transition hover:bg-hover"
|
||||||
|
>
|
||||||
|
<Folder class="h-[18px] w-[18px]" strokeWidth={2} />
|
||||||
|
Canciones subidas
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="/upload"
|
||||||
|
onclick={() => (menuOpen = false)}
|
||||||
|
class="flex w-full items-center gap-3 px-3 py-2.5 text-base font-semibold text-fg-2 transition hover:bg-hover"
|
||||||
|
>
|
||||||
|
<Upload class="h-[18px] w-[18px]" strokeWidth={2} />
|
||||||
|
Subir música
|
||||||
|
</a>
|
||||||
|
<div class="my-1.5 border-t border-line"></div>
|
||||||
|
<form method="POST" action="/logout" data-sveltekit-reload>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="flex w-full items-center gap-3 px-3 py-2.5 text-base font-semibold text-fg-2 transition hover:bg-hover"
|
||||||
|
>
|
||||||
|
<LogOut class="h-[18px] w-[18px]" strokeWidth={2} />
|
||||||
|
Salir
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<svelte:window onclick={onDocumentClick} />
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { page } from '$app/state';
|
||||||
|
import Home from '@lucide/svelte/icons/house';
|
||||||
|
import Search from '@lucide/svelte/icons/search';
|
||||||
|
import Music from '@lucide/svelte/icons/music';
|
||||||
|
import Folder from '@lucide/svelte/icons/folder';
|
||||||
|
|
||||||
|
const links = [
|
||||||
|
{ href: '/', label: 'Inicio', icon: Home },
|
||||||
|
{ href: '/explore', label: 'Buscar', icon: Search },
|
||||||
|
{ href: '/playlists', label: 'Listas', icon: Music },
|
||||||
|
{ href: '/library', label: 'Subidas', icon: Folder }
|
||||||
|
];
|
||||||
|
|
||||||
|
function isActive(href: string) {
|
||||||
|
return href === '/' ? page.url.pathname === '/' : page.url.pathname.startsWith(href);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<nav
|
||||||
|
class="fixed inset-x-0 bottom-0 z-50 border-t border-line bg-bg/95 backdrop-blur-md md:hidden"
|
||||||
|
style="padding-bottom:var(--mf-safe-b)"
|
||||||
|
>
|
||||||
|
<ul class="grid h-[var(--mf-nav-h)] grid-cols-4">
|
||||||
|
{#each links as link (link.href)}
|
||||||
|
{@const active = isActive(link.href)}
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href={link.href}
|
||||||
|
aria-current={active ? 'page' : undefined}
|
||||||
|
class="flex h-full flex-col items-center justify-center gap-1 transition-colors {active
|
||||||
|
? 'text-fg'
|
||||||
|
: 'text-fg-3'}"
|
||||||
|
>
|
||||||
|
<link.icon class="h-[22px] w-[22px] {active ? 'text-accent-soft' : ''}" />
|
||||||
|
<span class="text-[11px] leading-none font-medium">{link.label}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
<aside
|
<aside
|
||||||
class="sticky top-0 flex h-screen flex-shrink-0 flex-col gap-7 border-r border-line px-[18px] py-[26px]"
|
class="sticky top-0 hidden h-screen shrink-0 flex-col gap-7 border-r border-line px-4.5 py-6.5 md:flex"
|
||||||
style="width:var(--mf-sidebar-w)"
|
style="width:var(--mf-sidebar-w)"
|
||||||
>
|
>
|
||||||
<a href="/" class="px-3 text-xl font-extrabold tracking-wide">Musify</a>
|
<a href="/" class="px-3 text-xl font-extrabold tracking-wide">Musify</a>
|
||||||
@@ -83,11 +83,11 @@
|
|||||||
<div class="relative flex flex-col gap-7" bind:this={navBox}>
|
<div class="relative flex flex-col gap-7" bind:this={navBox}>
|
||||||
<div
|
<div
|
||||||
class="pointer-events-none absolute inset-x-0 z-0 overflow-hidden rounded-control bg-surface-hover {animate
|
class="pointer-events-none absolute inset-x-0 z-0 overflow-hidden rounded-control bg-surface-hover {animate
|
||||||
? 'transition-[top,height] duration-200 ease-[cubic-bezier(0.4,0,0.2,1)]'
|
? 'transition-[top,height] duration-200 ease-in-out'
|
||||||
: ''}"
|
: ''}"
|
||||||
style="top:{pillTop}px; height:{pillHeight}px; opacity:{pillVisible ? 1 : 0}"
|
style="top:{pillTop}px; height:{pillHeight}px; opacity:{pillVisible ? 1 : 0}"
|
||||||
>
|
>
|
||||||
<span class="absolute inset-y-0 left-0 w-[3px] rounded-r-full bg-accent-soft"></span>
|
<span class="absolute inset-y-0 left-0 w-0.75 rounded-r-full bg-accent-soft"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav class="relative z-10 flex flex-col gap-1">
|
<nav class="relative z-10 flex flex-col gap-1">
|
||||||
@@ -115,7 +115,7 @@
|
|||||||
type="submit"
|
type="submit"
|
||||||
class="flex w-full items-center gap-3 px-3 py-2.5 text-base font-semibold text-fg-2 transition hover:bg-hover"
|
class="flex w-full items-center gap-3 px-3 py-2.5 text-base font-semibold text-fg-2 transition hover:bg-hover"
|
||||||
>
|
>
|
||||||
<LogOut class="h-[18px] w-[18px]" strokeWidth={2} />
|
<LogOut class="h-4.5 w-4.5" strokeWidth={2} />
|
||||||
Salir
|
Salir
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -130,7 +130,7 @@
|
|||||||
<img src={user.picture} alt="" class="h-9 w-9 rounded-full object-cover" />
|
<img src={user.picture} alt="" class="h-9 w-9 rounded-full object-cover" />
|
||||||
{:else}
|
{:else}
|
||||||
<span
|
<span
|
||||||
class="grid h-9 w-9 flex-shrink-0 place-items-center rounded-full bg-surface-2 text-sm font-semibold uppercase"
|
class="grid h-9 w-9 shrink-0 place-items-center rounded-full bg-surface-2 text-sm font-semibold uppercase"
|
||||||
>
|
>
|
||||||
{user.name.charAt(0)}
|
{user.name.charAt(0)}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -3,15 +3,15 @@
|
|||||||
|
|
||||||
export type ContextMenuState = TrackTarget & { x: number; y: number; openLeft: boolean };
|
export type ContextMenuState = TrackTarget & { x: number; y: number; openLeft: boolean };
|
||||||
|
|
||||||
const MENU_WIDTH = 208;
|
const MENU_WIDTH = 240;
|
||||||
const SUBMENU_WIDTH = 224;
|
const SUBMENU_WIDTH = 256;
|
||||||
|
|
||||||
export function contextMenuStateFor(event: MouseEvent, target: TrackTarget): ContextMenuState {
|
export function contextMenuStateFor(event: MouseEvent, target: TrackTarget): ContextMenuState {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
return {
|
return {
|
||||||
...target,
|
...target,
|
||||||
x: Math.min(event.clientX, window.innerWidth - MENU_WIDTH - 8),
|
x: Math.max(8, Math.min(event.clientX, window.innerWidth - MENU_WIDTH - 8)),
|
||||||
y: Math.min(event.clientY, window.innerHeight - 60),
|
y: Math.max(8, Math.min(event.clientY, window.innerHeight - 60)),
|
||||||
openLeft: event.clientX + MENU_WIDTH + SUBMENU_WIDTH + 16 > window.innerWidth
|
openLeft: event.clientX + MENU_WIDTH + SUBMENU_WIDTH + 16 > window.innerWidth
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -70,12 +70,12 @@
|
|||||||
<ChevronRight class="h-4 w-4 shrink-0 text-fg-3" />
|
<ChevronRight class="h-4 w-4 shrink-0 text-fg-3" />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="invisible absolute top-0 z-50 opacity-0 transition group-hover/addmenu:visible group-hover/addmenu:opacity-100 {menu.openLeft
|
class="invisible absolute top-0 z-50 opacity-0 transition group-hover/addmenu:visible group-hover/addmenu:opacity-100 max-sm:top-full max-sm:right-0 max-sm:left-0 max-sm:px-0 max-sm:pt-1.5 {menu.openLeft
|
||||||
? 'right-full pr-1.5'
|
? 'right-full pr-1.5'
|
||||||
: 'left-full pl-1.5'}"
|
: 'left-full pl-1.5'}"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="w-64 overflow-hidden rounded-panel border border-line bg-elevated p-2 shadow-menu"
|
class="w-64 overflow-hidden rounded-panel border border-line bg-elevated p-2 shadow-menu max-sm:w-full"
|
||||||
>
|
>
|
||||||
<div class="max-h-80 overflow-y-auto">
|
<div class="max-h-80 overflow-y-auto">
|
||||||
{#each playlists as playlist (playlist.id)}
|
{#each playlists as playlist (playlist.id)}
|
||||||
@@ -107,7 +107,7 @@
|
|||||||
trackIds={[]}
|
trackIds={[]}
|
||||||
hue={hueFor(playlist.id)}
|
hue={hueFor(playlist.id)}
|
||||||
size="small"
|
size="small"
|
||||||
class="h-8 w-8 flex-shrink-0 rounded-control"
|
class="h-8 w-8 shrink-0 rounded-control"
|
||||||
/>
|
/>
|
||||||
<span class="truncate text-base text-fg-2">{playlist.name}</span>
|
<span class="truncate text-base text-fg-2">{playlist.name}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -6,11 +6,22 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="animate-enter fixed right-0 bottom-0 z-50 h-[92px] border-t border-line bg-bg"
|
class="animate-enter fixed right-0 left-0 z-40 h-[var(--mf-player-h)] border-t border-line bg-bg md:left-[var(--mf-sidebar-w)] md:z-50"
|
||||||
style="left:var(--mf-sidebar-w)"
|
style="bottom:calc(var(--mf-nav-h) + var(--mf-safe-b))"
|
||||||
>
|
>
|
||||||
|
<div class="absolute inset-x-0 top-0 md:hidden">
|
||||||
|
<SeekBar compact />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex h-full items-center gap-3 page-x md:hidden">
|
||||||
|
<div class="min-w-0 flex-1">
|
||||||
|
<TrackInfo />
|
||||||
|
</div>
|
||||||
|
<TransportControls compact />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="grid h-full items-center gap-5 px-8"
|
class="hidden h-full items-center gap-5 px-8 md:grid"
|
||||||
style="grid-template-columns:minmax(200px,1fr) minmax(0,3fr) minmax(200px,1fr)"
|
style="grid-template-columns:minmax(200px,1fr) minmax(0,3fr) minmax(200px,1fr)"
|
||||||
>
|
>
|
||||||
<TrackInfo />
|
<TrackInfo />
|
||||||
|
|||||||
@@ -2,32 +2,52 @@
|
|||||||
import { player } from '$lib/player/player.svelte';
|
import { player } from '$lib/player/player.svelte';
|
||||||
import { fmtTime } from '$lib/format';
|
import { fmtTime } from '$lib/format';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
compact?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { compact = false }: Props = $props();
|
||||||
|
|
||||||
function seek(event: MouseEvent) {
|
function seek(event: MouseEvent) {
|
||||||
const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();
|
const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();
|
||||||
player.seekFraction((event.clientX - rect.left) / rect.width);
|
player.seekFraction((event.clientX - rect.left) / rect.width);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex w-full max-w-172 items-center gap-3">
|
{#if compact}
|
||||||
<span class="text-right text-xs text-muted tabular-nums">
|
|
||||||
{fmtTime(player.progress)}
|
|
||||||
</span>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={seek}
|
onclick={seek}
|
||||||
aria-label="Barra de progreso"
|
aria-label="Barra de progreso"
|
||||||
class="group relative h-1.5 flex-1 cursor-pointer rounded-full bg-track transition-[height] hover:h-2"
|
class="relative block h-1 w-full cursor-pointer bg-track"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="absolute inset-y-0 left-0 rounded-full bg-accent/60 group-hover:brightness-110"
|
class="absolute inset-y-0 left-0 bg-accent/70"
|
||||||
style="width:{player.progressPercent}%"
|
style="width:{player.progressPercent}%"
|
||||||
></div>
|
></div>
|
||||||
<span
|
|
||||||
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}%"
|
|
||||||
></span>
|
|
||||||
</button>
|
</button>
|
||||||
<span class="text-xs text-muted tabular-nums">
|
{:else}
|
||||||
{fmtTime(player.current.duration)}
|
<div class="flex w-full max-w-172 items-center gap-3">
|
||||||
</span>
|
<span class="text-right text-xs text-muted tabular-nums">
|
||||||
</div>
|
{fmtTime(player.progress)}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={seek}
|
||||||
|
aria-label="Barra de progreso"
|
||||||
|
class="group relative h-1.5 flex-1 cursor-pointer rounded-full bg-track transition-[height] hover:h-2"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="absolute inset-y-0 left-0 rounded-full bg-accent/60 group-hover:brightness-110"
|
||||||
|
style="width:{player.progressPercent}%"
|
||||||
|
></div>
|
||||||
|
<span
|
||||||
|
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}%"
|
||||||
|
></span>
|
||||||
|
</button>
|
||||||
|
<span class="text-xs text-muted tabular-nums">
|
||||||
|
{fmtTime(player.current.duration)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import ExplicitBadge from '$lib/components/ui/ExplicitBadge.svelte';
|
import ExplicitBadge from '$lib/components/ui/ExplicitBadge.svelte';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex min-w-0 items-center gap-3">
|
<div class="flex min-w-0 items-center gap-2.5 md:gap-3">
|
||||||
{#if player.current.id}
|
{#if player.current.id}
|
||||||
<Cover
|
<Cover
|
||||||
trackId={player.current.id}
|
trackId={player.current.id}
|
||||||
@@ -12,11 +12,11 @@
|
|||||||
hue={player.current.hue}
|
hue={player.current.hue}
|
||||||
size="small"
|
size="small"
|
||||||
alt={player.current.title}
|
alt={player.current.title}
|
||||||
class="h-13 w-13 shrink-0 rounded-lg shadow-[0_6px_16px_-6px_rgba(0,0,0,0.6)]"
|
class="h-11 w-11 shrink-0 rounded-lg shadow-[0_6px_16px_-6px_rgba(0,0,0,0.6)] md:h-13 md:w-13"
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<div
|
<div
|
||||||
class="h-13 w-13 shrink-0 rounded-lg shadow-[0_6px_16px_-6px_rgba(0,0,0,0.6)]"
|
class="h-11 w-11 shrink-0 rounded-lg shadow-[0_6px_16px_-6px_rgba(0,0,0,0.6)] md:h-13 md:w-13"
|
||||||
style="background:{player.artGradient}"
|
style="background:{player.artGradient}"
|
||||||
></div>
|
></div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,14 +1,21 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { player } from '$lib/player/player.svelte';
|
import { player } from '$lib/player/player.svelte';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
compact?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { compact = false }: Props = $props();
|
||||||
|
|
||||||
|
const sideClass = $derived(
|
||||||
|
compact
|
||||||
|
? 'grid h-10 w-10 place-items-center text-fg opacity-70 transition active:scale-90'
|
||||||
|
: 'flex text-fg opacity-70 transition hover:opacity-100 active:scale-90'
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex items-center gap-5">
|
<div class="flex shrink-0 items-center {compact ? 'gap-0.5' : 'gap-5'}">
|
||||||
<button
|
<button type="button" onclick={() => player.previous()} aria-label="Anterior" class={sideClass}>
|
||||||
type="button"
|
|
||||||
onclick={() => player.previous()}
|
|
||||||
aria-label="Anterior"
|
|
||||||
class="flex text-fg opacity-70 transition hover:opacity-100 active:scale-90"
|
|
||||||
>
|
|
||||||
<svg width="16" height="14" viewBox="0 0 16 14" fill="currentColor">
|
<svg width="16" height="14" viewBox="0 0 16 14" fill="currentColor">
|
||||||
<rect x="0" y="0" width="2.5" height="14" rx="1" />
|
<rect x="0" y="0" width="2.5" height="14" rx="1" />
|
||||||
<path d="M15 0 5 7l10 7z" />
|
<path d="M15 0 5 7l10 7z" />
|
||||||
@@ -19,7 +26,7 @@
|
|||||||
type="button"
|
type="button"
|
||||||
onclick={() => player.toggle()}
|
onclick={() => player.toggle()}
|
||||||
aria-label={player.playing ? 'Pausar' : 'Reproducir'}
|
aria-label={player.playing ? 'Pausar' : 'Reproducir'}
|
||||||
class="grid h-[36px] w-[36px] place-items-center rounded-full bg-fg text-bg shadow-lg transition duration-100 active:scale-95"
|
class="grid h-[36px] w-[36px] shrink-0 place-items-center rounded-full bg-fg text-bg shadow-lg transition duration-100 active:scale-95"
|
||||||
>
|
>
|
||||||
{#if player.playing}
|
{#if player.playing}
|
||||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
|
<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
|
||||||
@@ -33,12 +40,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button
|
<button type="button" onclick={() => player.next()} aria-label="Siguiente" class={sideClass}>
|
||||||
type="button"
|
|
||||||
onclick={() => player.next()}
|
|
||||||
aria-label="Siguiente"
|
|
||||||
class="flex text-fg opacity-70 transition hover:opacity-100 active:scale-90"
|
|
||||||
>
|
|
||||||
<svg width="16" height="14" viewBox="0 0 16 14" fill="currentColor">
|
<svg width="16" height="14" viewBox="0 0 16 14" fill="currentColor">
|
||||||
<path d="M1 0 11 7 1 14z" />
|
<path d="M1 0 11 7 1 14z" />
|
||||||
<rect x="13.5" y="0" width="2.5" height="14" rx="1" />
|
<rect x="13.5" y="0" width="2.5" height="14" rx="1" />
|
||||||
|
|||||||
@@ -35,10 +35,10 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const variants = {
|
const variants = {
|
||||||
primary: 'bg-accent-soft font-semibold text-on-accent active:scale-[0.97]',
|
primary: 'bg-accent-soft 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 text-fg hover:bg-surface-hover',
|
||||||
danger: 'bg-danger font-semibold text-on-accent hover:brightness-110'
|
danger: 'bg-danger text-on-accent hover:brightness-110'
|
||||||
};
|
};
|
||||||
|
|
||||||
const classes = $derived(
|
const classes = $derived(
|
||||||
|
|||||||
@@ -31,7 +31,9 @@
|
|||||||
title={label}
|
title={label}
|
||||||
class="grid place-items-center rounded-control text-muted transition hover:bg-hover hover:text-fg {sizes[
|
class="grid place-items-center rounded-control text-muted transition hover:bg-hover hover:text-fg {sizes[
|
||||||
size
|
size
|
||||||
]} {revealOnHover ? 'opacity-0 group-hover:opacity-100 focus-visible:opacity-100' : ''} {klass}"
|
]} {revealOnHover
|
||||||
|
? 'sm:opacity-0 sm:group-hover:opacity-100 sm:focus-visible:opacity-100'
|
||||||
|
: ''} {klass}"
|
||||||
>
|
>
|
||||||
{@render children()}
|
{@render children()}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -10,7 +10,8 @@
|
|||||||
let {
|
let {
|
||||||
name,
|
name,
|
||||||
fallbackUrl,
|
fallbackUrl,
|
||||||
class: klass = 'h-52 w-52 rounded-control border border-line bg-surface hover:border-accent/50'
|
class:
|
||||||
|
klass = 'h-40 w-40 sm:h-52 sm:w-52 rounded-control border border-line bg-surface hover:border-accent/50'
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
let preview = $state('');
|
let preview = $state('');
|
||||||
|
|||||||
@@ -4,17 +4,24 @@
|
|||||||
interface Props {
|
interface Props {
|
||||||
as?: 'ul' | 'div';
|
as?: 'ul' | 'div';
|
||||||
min?: string;
|
min?: string;
|
||||||
|
minMobile?: string;
|
||||||
class?: string;
|
class?: string;
|
||||||
children: Snippet;
|
children: Snippet;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { as = 'div', min = '180px', class: klass = '', children }: Props = $props();
|
let {
|
||||||
|
as = 'div',
|
||||||
|
min = '180px',
|
||||||
|
minMobile = '150px',
|
||||||
|
class: klass = '',
|
||||||
|
children
|
||||||
|
}: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:element
|
<svelte:element
|
||||||
this={as}
|
this={as}
|
||||||
class="grid gap-4 {klass}"
|
class="grid media-grid gap-3 sm:gap-4 {klass}"
|
||||||
style="grid-template-columns:repeat(auto-fill,minmax({min},1fr))"
|
style="--mf-grid-min:{min}; --mf-grid-min-mobile:{minMobile}"
|
||||||
>
|
>
|
||||||
{@render children()}
|
{@render children()}
|
||||||
</svelte:element>
|
</svelte:element>
|
||||||
|
|||||||
@@ -74,7 +74,7 @@
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
bind:this={panel}
|
bind:this={panel}
|
||||||
class="animate-pop w-full {maxWidth} rounded-panel p-6 shadow-2xl outline-none {panelClass}"
|
class="animate-pop max-h-[85dvh] w-full overflow-y-auto {maxWidth} rounded-panel p-5 shadow-2xl outline-none sm:p-6 {panelClass}"
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-modal="true"
|
aria-modal="true"
|
||||||
aria-label={title}
|
aria-label={title}
|
||||||
|
|||||||
@@ -9,6 +9,6 @@
|
|||||||
let { class: klass = '', children }: Props = $props();
|
let { class: klass = '', children }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="page-x pt-11 pb-10 {klass}">
|
<section class="page-x pt-7 pb-8 sm:pt-11 sm:pb-10 {klass}">
|
||||||
{@render children()}
|
{@render children()}
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -11,11 +11,11 @@
|
|||||||
let { title, subtitle, class: klass = '', actions }: Props = $props();
|
let { title, subtitle, class: klass = '', actions }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex items-end justify-between gap-4 {klass}">
|
<div class="flex flex-col gap-4 sm:flex-row sm:items-end sm:justify-between {klass}">
|
||||||
<div class="min-w-0">
|
<div class="min-w-0">
|
||||||
<h1 class="text-3xl font-bold tracking-tight text-fg sm:text-4xl">{title}</h1>
|
<h1 class="text-2xl font-bold tracking-tight text-fg sm:text-4xl">{title}</h1>
|
||||||
{#if subtitle}
|
{#if subtitle}
|
||||||
<p class="mt-2 text-fg-2">{subtitle}</p>
|
<p class="mt-2 text-sm text-fg-2 sm:text-base">{subtitle}</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if actions}
|
{#if actions}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
}}
|
}}
|
||||||
class="mt-4 w-full max-w-4xl space-y-4"
|
class="mt-4 w-full max-w-4xl space-y-4"
|
||||||
>
|
>
|
||||||
<div class="flex items-stretch gap-8">
|
<div class="flex flex-col items-stretch gap-5 sm:flex-row sm:gap-8">
|
||||||
<Field label="Portada" optional>
|
<Field label="Portada" optional>
|
||||||
<ImageDropzone name="cover" fallbackUrl={coverFallbackUrl} />
|
<ImageDropzone name="cover" fallbackUrl={coverFallbackUrl} />
|
||||||
</Field>
|
</Field>
|
||||||
|
|||||||
@@ -7,4 +7,4 @@
|
|||||||
let { title, class: klass = '' }: Props = $props();
|
let { title, class: klass = '' }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h2 class="mb-6 text-2xl font-bold text-fg {klass}">{title}</h2>
|
<h2 class="mb-4 text-xl font-bold text-fg sm:mb-6 sm:text-2xl {klass}">{title}</h2>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
let { padding = 'md', class: klass = '', children }: Props = $props();
|
let { padding = 'md', class: klass = '', children }: Props = $props();
|
||||||
|
|
||||||
const paddings = { sm: 'p-4', md: 'p-6', lg: 'p-12' };
|
const paddings = { sm: 'p-4', md: 'p-5 sm:p-6', lg: 'p-6 sm:p-12' };
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="rounded-panel border border-line bg-surface transition {paddings[padding]} {klass}">
|
<div class="rounded-panel border border-line bg-surface transition {paddings[padding]} {klass}">
|
||||||
|
|||||||
@@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
<div
|
<div
|
||||||
class="group grid items-center gap-8 rounded-control px-3 py-2.5 transition hover:bg-surface {klass}"
|
class="group grid track-grid items-center gap-3 rounded-control px-2 py-2.5 transition hover:bg-surface sm:gap-8 sm:px-3 {klass}"
|
||||||
style="grid-template-columns:{table.columns}"
|
style="--mf-track-cols:{table.columns}; --mf-track-cols-mobile:{table.columnsMobile}"
|
||||||
{oncontextmenu}
|
{oncontextmenu}
|
||||||
>
|
>
|
||||||
{@render children()}
|
{@render children()}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
export interface TrackTableContext {
|
export interface TrackTableContext {
|
||||||
readonly columns: string;
|
readonly columns: string;
|
||||||
|
readonly columnsMobile: string;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -12,24 +13,30 @@
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
columns: string;
|
columns: string;
|
||||||
|
columnsMobile?: string;
|
||||||
class?: string;
|
class?: string;
|
||||||
headers: Snippet;
|
headers: Snippet;
|
||||||
children: Snippet;
|
children: Snippet;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { columns, class: klass = '', headers, children }: Props = $props();
|
let { columns, columnsMobile, class: klass = '', headers, children }: Props = $props();
|
||||||
|
|
||||||
|
const mobile = $derived(columnsMobile ?? columns);
|
||||||
|
|
||||||
setContext<TrackTableContext>(trackTableKey, {
|
setContext<TrackTableContext>(trackTableKey, {
|
||||||
get columns() {
|
get columns() {
|
||||||
return columns;
|
return columns;
|
||||||
|
},
|
||||||
|
get columnsMobile() {
|
||||||
|
return mobile;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class={klass}>
|
<div class={klass}>
|
||||||
<div
|
<div
|
||||||
class="grid items-center gap-8 border-b border-line px-3 pb-2 text-sm font-medium tracking-wide text-muted uppercase"
|
class="grid track-grid items-center gap-3 border-b border-line px-2 pb-2 text-sm font-medium tracking-wide text-muted uppercase sm:gap-8 sm:px-3"
|
||||||
style="grid-template-columns:{columns}"
|
style="--mf-track-cols:{columns}; --mf-track-cols-mobile:{mobile}"
|
||||||
>
|
>
|
||||||
{@render headers()}
|
{@render headers()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
{hue}
|
{hue}
|
||||||
size="small"
|
size="small"
|
||||||
alt={title}
|
alt={title}
|
||||||
class="{coverSize} flex-shrink-0 rounded-control"
|
class="{coverSize} shrink-0 rounded-control"
|
||||||
>
|
>
|
||||||
{@render overlay?.()}
|
{@render overlay?.()}
|
||||||
</Cover>
|
</Cover>
|
||||||
|
|||||||
@@ -21,5 +21,15 @@
|
|||||||
--mf-scrim: #000000;
|
--mf-scrim: #000000;
|
||||||
--mf-on-art: #ffffff;
|
--mf-on-art: #ffffff;
|
||||||
|
|
||||||
--mf-sidebar-w: 264px;
|
--mf-sidebar-w: 288px;
|
||||||
|
--mf-nav-h: 60px;
|
||||||
|
--mf-player-h: 64px;
|
||||||
|
--mf-safe-b: env(safe-area-inset-bottom, 0px);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (width >= 48rem) {
|
||||||
|
:root {
|
||||||
|
--mf-nav-h: 0px;
|
||||||
|
--mf-player-h: 92px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,13 +48,17 @@
|
|||||||
</PageHeader>
|
</PageHeader>
|
||||||
|
|
||||||
{#if items.length > 0}
|
{#if items.length > 0}
|
||||||
<TrackTable columns="32px 1fr 100px 100px 64px 36px" class="mt-8">
|
<TrackTable
|
||||||
|
columns="32px 1fr 100px 100px 64px 36px"
|
||||||
|
columnsMobile="28px 1fr 52px 32px"
|
||||||
|
class="mt-6 sm:mt-8"
|
||||||
|
>
|
||||||
{#snippet headers()}
|
{#snippet headers()}
|
||||||
<span class="text-center">#</span>
|
<span class="text-center">#</span>
|
||||||
<span>Título</span>
|
<span>Título</span>
|
||||||
<span class="text-center">Subida</span>
|
<span class="hidden text-center sm:block">Subida</span>
|
||||||
<span class="text-center">Escuchas</span>
|
<span class="hidden text-center sm:block">Escuchas</span>
|
||||||
<span class="text-center">Duración</span>
|
<span class="hidden text-center sm:block">Duración</span>
|
||||||
<span></span>
|
<span></span>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
{#each items as track, i (track.id)}
|
{#each items as track, i (track.id)}
|
||||||
@@ -71,8 +75,8 @@
|
|||||||
hue={hueFor(track.id)}
|
hue={hueFor(track.id)}
|
||||||
onClick={() => togglePlay(i)}
|
onClick={() => togglePlay(i)}
|
||||||
/>
|
/>
|
||||||
<TrackMeta>{fmtDate(track.createdAt)}</TrackMeta>
|
<TrackMeta class="hidden sm:block">{fmtDate(track.createdAt)}</TrackMeta>
|
||||||
<TrackMeta>{Number(track.listensCount)}</TrackMeta>
|
<TrackMeta class="hidden sm:block">{Number(track.listensCount)}</TrackMeta>
|
||||||
<TrackMeta>{fmtTime(Number(track.duration))}</TrackMeta>
|
<TrackMeta>{fmtTime(Number(track.duration))}</TrackMeta>
|
||||||
<form
|
<form
|
||||||
method="POST"
|
method="POST"
|
||||||
|
|||||||
@@ -34,20 +34,24 @@
|
|||||||
Volver a tus listas
|
Volver a tus listas
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="mt-6 flex flex-col gap-6 sm:flex-row sm:items-end">
|
<div class="mt-5 flex flex-col gap-5 sm:mt-6 sm:flex-row sm:items-end sm:gap-6">
|
||||||
<PlaylistArt
|
<PlaylistArt
|
||||||
playlistId={id}
|
playlistId={id}
|
||||||
{trackIds}
|
{trackIds}
|
||||||
hue={hueFor(id)}
|
hue={hueFor(id)}
|
||||||
size="large"
|
size="large"
|
||||||
version={updatedAt}
|
version={updatedAt}
|
||||||
class="h-44 w-44 flex-shrink-0 rounded-art-lg shadow-art-lg"
|
class="h-36 w-36 shrink-0 rounded-art-lg shadow-art-lg sm:h-44 sm:w-44"
|
||||||
/>
|
/>
|
||||||
<div class="min-w-0 flex-1">
|
<div class="min-w-0 flex-1">
|
||||||
<p class="text-sm tracking-[0.14em] text-fg-3 uppercase">Lista</p>
|
<p class="text-sm tracking-[0.14em] text-fg-3 uppercase">Lista</p>
|
||||||
<h1 class="mt-1.5 text-5xl font-bold tracking-tight text-fg sm:text-7xl">{name}</h1>
|
<h1
|
||||||
|
class="mt-1.5 text-3xl font-bold tracking-tight break-words text-fg sm:text-5xl md:text-7xl"
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</h1>
|
||||||
{#if description}
|
{#if description}
|
||||||
<p class="mt-3 max-w-2xl text-fg-2">{description}</p>
|
<p class="mt-3 max-w-2xl text-sm text-fg-2 sm:text-base">{description}</p>
|
||||||
{/if}
|
{/if}
|
||||||
<p class="mt-3 text-sm text-muted">
|
<p class="mt-3 text-sm text-muted">
|
||||||
{trackIds.length}
|
{trackIds.length}
|
||||||
@@ -56,7 +60,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-7 flex items-center gap-2.5">
|
<div class="mt-6 flex flex-wrap items-center gap-2.5 sm:mt-7">
|
||||||
<Button size="sm" onclick={onPlayAll} disabled={trackIds.length === 0}>
|
<Button size="sm" onclick={onPlayAll} disabled={trackIds.length === 0}>
|
||||||
{#if playing}
|
{#if playing}
|
||||||
<Pause class="h-4 w-4" fill="currentColor" strokeWidth={1.5} />
|
<Pause class="h-4 w-4" fill="currentColor" strokeWidth={1.5} />
|
||||||
|
|||||||
@@ -32,12 +32,16 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<TrackTable columns="32px 1fr 140px 64px 36px" class="mt-8">
|
<TrackTable
|
||||||
|
columns="32px 1fr 140px 64px 36px"
|
||||||
|
columnsMobile="28px 1fr 52px 32px"
|
||||||
|
class="mt-6 sm:mt-8"
|
||||||
|
>
|
||||||
{#snippet headers()}
|
{#snippet headers()}
|
||||||
<span class="text-center">#</span>
|
<span class="text-center">#</span>
|
||||||
<span>Título</span>
|
<span>Título</span>
|
||||||
<span class="text-center">Añadida</span>
|
<span class="hidden text-center sm:block">Añadida</span>
|
||||||
<span class="text-center">Duración</span>
|
<span class="hidden text-center sm:block">Duración</span>
|
||||||
<span></span>
|
<span></span>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
{#each tracks as track, i (track.id)}
|
{#each tracks as track, i (track.id)}
|
||||||
@@ -67,7 +71,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</TrackTitleCell>
|
</TrackTitleCell>
|
||||||
<TrackMeta>{fmtDate(track.createdAt)}</TrackMeta>
|
<TrackMeta class="hidden sm:block">{fmtDate(track.createdAt)}</TrackMeta>
|
||||||
<TrackMeta>{fmtTime(Number(track.duration))}</TrackMeta>
|
<TrackMeta>{fmtTime(Number(track.duration))}</TrackMeta>
|
||||||
<form
|
<form
|
||||||
method="POST"
|
method="POST"
|
||||||
|
|||||||
@@ -82,12 +82,12 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="mt-12">
|
<div class="mt-10 sm:mt-12">
|
||||||
<SectionHeading title="Sugerencias" />
|
<SectionHeading title="Sugerencias" />
|
||||||
<TrackTable columns="1fr 64px 36px">
|
<TrackTable columns="1fr 64px 36px" columnsMobile="1fr 52px 32px">
|
||||||
{#snippet headers()}
|
{#snippet headers()}
|
||||||
<span>Título</span>
|
<span>Título</span>
|
||||||
<span class="text-center">Duración</span>
|
<span class="hidden text-center sm:block">Duración</span>
|
||||||
<span></span>
|
<span></span>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
{#each suggestions as item, i (idFor(item))}
|
{#each suggestions as item, i (idFor(item))}
|
||||||
|
|||||||
@@ -112,7 +112,7 @@
|
|||||||
subtitle="Añade una canción con su portada y título. Nosotros la procesamos para streaming."
|
subtitle="Añade una canción con su portada y título. Nosotros la procesamos para streaming."
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ol class="mt-10 grid gap-4 sm:grid-cols-3">
|
<ol class="mt-8 grid gap-3 sm:mt-10 sm:grid-cols-3 sm:gap-4">
|
||||||
{#each steps as step, i (step.key)}
|
{#each steps as step, i (step.key)}
|
||||||
<li>
|
<li>
|
||||||
<Surface class={stepClass(stepStates[i])}>
|
<Surface class={stepClass(stepStates[i])}>
|
||||||
@@ -146,9 +146,11 @@
|
|||||||
«{publishedTitle}» se está procesando. Aparecerá lista para reproducir en tu biblioteca en
|
«{publishedTitle}» se está procesando. Aparecerá lista para reproducir en tu biblioteca en
|
||||||
unos momentos.
|
unos momentos.
|
||||||
</p>
|
</p>
|
||||||
<div class="mt-8 flex items-center justify-center gap-4">
|
<div class="mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row sm:gap-4">
|
||||||
<Button href="/library" size="lg">Ir a la biblioteca</Button>
|
<Button href="/library" size="lg" class="w-full sm:w-auto">Ir a la biblioteca</Button>
|
||||||
<Button variant="secondary" size="lg" onclick={reset}>Subir otra</Button>
|
<Button variant="secondary" size="lg" onclick={reset} class="w-full sm:w-auto">
|
||||||
|
Subir otra
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Surface>
|
</Surface>
|
||||||
{:else}
|
{:else}
|
||||||
@@ -182,7 +184,7 @@
|
|||||||
}}
|
}}
|
||||||
ondragleave={() => (dragging = false)}
|
ondragleave={() => (dragging = false)}
|
||||||
ondrop={onDrop}
|
ondrop={onDrop}
|
||||||
class="relative flex min-h-72 flex-col items-center justify-center rounded-panel border border-dashed bg-surface p-10 text-center transition {dropClass}"
|
class="relative flex min-h-56 flex-col items-center justify-center rounded-panel border border-dashed bg-surface p-6 text-center transition sm:min-h-72 sm:p-10 {dropClass}"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
bind:this={audioInput}
|
bind:this={audioInput}
|
||||||
@@ -193,28 +195,32 @@
|
|||||||
class="absolute inset-0 cursor-pointer opacity-0"
|
class="absolute inset-0 cursor-pointer opacity-0"
|
||||||
aria-label="Seleccionar archivo de audio"
|
aria-label="Seleccionar archivo de audio"
|
||||||
/>
|
/>
|
||||||
<span class="grid h-20 w-20 place-items-center rounded-full bg-accent-soft text-on-accent">
|
<span
|
||||||
|
class="grid h-16 w-16 place-items-center rounded-full bg-accent-soft text-on-accent sm:h-20 sm:w-20"
|
||||||
|
>
|
||||||
{#if audioName === ''}
|
{#if audioName === ''}
|
||||||
<Headphones class="h-9 w-9" />
|
<Headphones class="h-8 w-8 sm:h-9 sm:w-9" />
|
||||||
{:else}
|
{:else}
|
||||||
<Music class="h-9 w-9" />
|
<Music class="h-8 w-8 sm:h-9 sm:w-9" />
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
{#if audioName === ''}
|
{#if audioName === ''}
|
||||||
<p class="mt-5 text-lg font-medium text-fg">
|
<p class="mt-5 text-base font-medium text-fg sm:text-lg">
|
||||||
Arrastra tu audio aquí o haz clic para elegir
|
Arrastra tu audio aquí o haz clic para elegir
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-2 text-sm text-muted">MP3, FLAC, WAV…</p>
|
<p class="mt-2 text-sm text-muted">MP3, FLAC, WAV…</p>
|
||||||
{:else}
|
{:else}
|
||||||
<p class="mt-5 max-w-full truncate text-lg font-medium text-fg">{audioName}</p>
|
<p class="mt-5 max-w-full truncate text-base font-medium text-fg sm:text-lg">
|
||||||
|
{audioName}
|
||||||
|
</p>
|
||||||
<p class="mt-2 text-sm text-muted">{formatSize(audioSize)} · Listo para subir</p>
|
<p class="mt-2 text-sm text-muted">{formatSize(audioSize)} · Listo para subir</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid items-start gap-8 sm:grid-cols-[auto_1fr]">
|
<div class="grid items-start gap-6 sm:grid-cols-[auto_1fr] sm:gap-8">
|
||||||
<Field label="Portada">
|
<Field label="Portada">
|
||||||
<label
|
<label
|
||||||
class="relative grid h-44 w-44 cursor-pointer place-items-center overflow-hidden rounded-control border border-line bg-surface transition hover:border-accent/50"
|
class="relative grid h-36 w-36 cursor-pointer place-items-center overflow-hidden rounded-control border border-line bg-surface transition hover:border-accent/50 sm:h-44 sm:w-44"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
@@ -274,9 +280,13 @@
|
|||||||
<Alert tone="danger">{errorMsg}</Alert>
|
<Alert tone="danger">{errorMsg}</Alert>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="flex items-center justify-end gap-4">
|
<div class="flex flex-col-reverse gap-3 sm:flex-row sm:items-center sm:justify-end sm:gap-4">
|
||||||
<Button href="/explore" variant="secondary" size="lg">Cancelar</Button>
|
<Button href="/explore" variant="secondary" size="lg" class="w-full sm:w-auto">
|
||||||
<Button type="submit" size="lg" disabled={!canPublish}>Publicar</Button>
|
Cancelar
|
||||||
|
</Button>
|
||||||
|
<Button type="submit" size="lg" disabled={!canPublish} class="w-full sm:w-auto">
|
||||||
|
Publicar
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@
|
|||||||
import favicon from '$lib/assets/favicon.svg';
|
import favicon from '$lib/assets/favicon.svg';
|
||||||
import PlayerBar from '$lib/components/player/PlayerBar.svelte';
|
import PlayerBar from '$lib/components/player/PlayerBar.svelte';
|
||||||
import Sidebar from '$lib/components/Sidebar.svelte';
|
import Sidebar from '$lib/components/Sidebar.svelte';
|
||||||
|
import MobileHeader from '$lib/components/MobileHeader.svelte';
|
||||||
|
import MobileNav from '$lib/components/MobileNav.svelte';
|
||||||
import { player } from '$lib/player/player.svelte';
|
import { player } from '$lib/player/player.svelte';
|
||||||
import { ACCENT_LIGHTNESS, ACCENT_CHROMA, ACCENT_HUE } from '$lib/theme/color';
|
import { ACCENT_LIGHTNESS, ACCENT_CHROMA, ACCENT_HUE } from '$lib/theme/color';
|
||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
@@ -66,7 +68,13 @@
|
|||||||
<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-23' : 'pb-0'}">
|
<MobileHeader user={data.user} />
|
||||||
|
<main
|
||||||
|
class="flex-1 transition-[padding]"
|
||||||
|
style="padding-bottom:calc(var(--mf-nav-h) + var(--mf-safe-b) + {hasTrack
|
||||||
|
? 'var(--mf-player-h)'
|
||||||
|
: '0px'})"
|
||||||
|
>
|
||||||
{#key page.url.pathname}
|
{#key page.url.pathname}
|
||||||
<div class="animate-fade">{@render children()}</div>
|
<div class="animate-fade">{@render children()}</div>
|
||||||
{/key}
|
{/key}
|
||||||
@@ -74,6 +82,7 @@
|
|||||||
{#if hasTrack}
|
{#if hasTrack}
|
||||||
<PlayerBar />
|
<PlayerBar />
|
||||||
{/if}
|
{/if}
|
||||||
|
<MobileNav />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -168,23 +168,23 @@
|
|||||||
<meta name="description" content="Tu música, sin límites." />
|
<meta name="description" content="Tu música, sin límites." />
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<section class="relative overflow-hidden page-x pt-11 pb-16">
|
<section class="relative overflow-hidden page-x pt-8 pb-10 sm:pt-11 sm: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(--color-accent), color-mix(in oklch, var(--color-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 sm:gap-2.5">
|
||||||
<p class="tracking-[0.14em] text-fg-2 uppercase">{data.greeting}</p>
|
<p class="text-sm tracking-[0.14em] text-fg-2 uppercase sm:text-base">{data.greeting}</p>
|
||||||
<h1 class="max-w-160 text-5xl leading-[1.05] font-extrabold text-fg">
|
<h1 class="max-w-160 text-3xl leading-[1.05] font-extrabold text-fg sm:text-5xl">
|
||||||
Tu música. Sin límites.
|
Tu música. Sin límites.
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{#if recentlyPlayed.length > 0}
|
{#if recentlyPlayed.length > 0}
|
||||||
<section class="page-x pt-6 pb-3">
|
<section class="page-x pt-5 pb-3 sm:pt-6">
|
||||||
<SectionHeading title="Escuchado recientemente" />
|
<SectionHeading title="Escuchado recientemente" />
|
||||||
<MediaGrid min="300px">
|
<MediaGrid min="300px" minMobile="220px">
|
||||||
{#each recentlyPlayed as track, i (track.id)}
|
{#each recentlyPlayed as track, i (track.id)}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -225,7 +225,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if playlists.length > 0}
|
{#if playlists.length > 0}
|
||||||
<section class="page-x pt-6 pb-3">
|
<section class="page-x pt-5 pb-3 sm:pt-6">
|
||||||
<SectionHeading title="Mis listas" />
|
<SectionHeading title="Mis listas" />
|
||||||
<MediaGrid>
|
<MediaGrid>
|
||||||
{#each playlists as playlist, i (playlist.id)}
|
{#each playlists as playlist, i (playlist.id)}
|
||||||
@@ -242,7 +242,7 @@
|
|||||||
</section>
|
</section>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<section class="page-x pt-6 pb-10">
|
<section class="page-x pt-5 pb-10 sm:pt-6">
|
||||||
<SectionHeading title="Novedades" />
|
<SectionHeading title="Novedades" />
|
||||||
{#if latest.length > 0}
|
{#if latest.length > 0}
|
||||||
<MediaGrid as="ul">
|
<MediaGrid as="ul">
|
||||||
|
|||||||
@@ -35,7 +35,27 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@utility page-x {
|
@utility page-x {
|
||||||
padding-inline: --spacing(8);
|
padding-inline: --spacing(4);
|
||||||
|
|
||||||
|
@media (width >= 40rem) {
|
||||||
|
padding-inline: --spacing(8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility media-grid {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(var(--mf-grid-min-mobile), 1fr));
|
||||||
|
|
||||||
|
@media (width >= 40rem) {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(var(--mf-grid-min), 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility track-grid {
|
||||||
|
grid-template-columns: var(--mf-track-cols-mobile);
|
||||||
|
|
||||||
|
@media (width >= 40rem) {
|
||||||
|
grid-template-columns: var(--mf-track-cols);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@utility scrollbar-stable {
|
@utility scrollbar-stable {
|
||||||
@@ -156,6 +176,8 @@ html {
|
|||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
scrollbar-color: rgba(255, 255, 255, 0.18) transparent;
|
scrollbar-color: rgba(255, 255, 255, 0.18) transparent;
|
||||||
background-color: var(--mf-bg);
|
background-color: var(--mf-bg);
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
html.home {
|
html.home {
|
||||||
|
|||||||
Reference in New Issue
Block a user