Design Prot Merge #1
@@ -20,15 +20,31 @@
|
||||
> sobre el "chrome" de la app o sobre arte/gradiente saturado, se extrajeron
|
||||
> `lib/components/ui/CollectionHeader.svelte` (portada + título + meta + acciones, usado
|
||||
> en `AlbumHeader`, `PlaylistHeader`, `liked/+page.svelte`, `mixes/[id]/+page.svelte`,
|
||||
> `albums/external/.../+page.svelte`) y `lib/components/AccountMenu.svelte` (estado de
|
||||
> apertura + panel del menú de cuenta, usado en `TopBar`/`MobileHeader`), y se corrigió
|
||||
> `--color-hover` en `layout.css`, que estaba hardcodeado en vez de apuntar a
|
||||
> `--mf-surface-2` (rompía el modo claro para cualquier `hover:bg-hover`). Pendiente de
|
||||
> quien retome esto: verificación visual en el navegador con una sesión autenticada real
|
||||
> (esta sesión no tenía credenciales del IdP de Zitadel ni acceso para activar
|
||||
> `allowAnonymousListening`), y `lib/components/ui/Rail.svelte`, que el grep de
|
||||
> huérfanos de §5 marca sin importadores — no se tocó porque no forma parte de este
|
||||
> barrido, pero conviene confirmarlo a mano y borrarlo si procede.
|
||||
> `albums/external/.../+page.svelte` y `u/[id]/+page.svelte`) y
|
||||
> `lib/components/ui/AccountMenu.svelte` (estado de apertura + panel del menú de cuenta,
|
||||
> usado en `TopBar`/`MobileHeader`), y se corrigió `--color-hover` en `layout.css`, que
|
||||
> estaba hardcodeado en vez de apuntar a `--mf-surface-2` (rompía el modo claro para
|
||||
> cualquier `hover:bg-hover`).
|
||||
>
|
||||
> **Repaso posterior a la verificación visual** (ya con sesión autenticada): se detectó
|
||||
> que el fix de §2 anterior para la tarjeta de género de `explore/+page.svelte` estaba
|
||||
> mal — usaba `var(--mf-ink)` como extremo oscuro del degradado, pero `--mf-ink` es el
|
||||
> token de "texto sobre botón de acento" y se invierte a blanco en modo claro, rompiendo
|
||||
> la tarjeta (fondo blanco en vez de degradado oscuro). Se creó un token nuevo,
|
||||
> `--mf-tile-shade` (deliberadamente **sin** variante de modo claro, igual que
|
||||
> `--mf-cover-grad`/`--mf-liked-grad`, porque es "arte" decorativo, no "chrome"), y se usa
|
||||
> ahí en su lugar. También se cambió la línea de meta de `CollectionHeader` de
|
||||
> `text-muted` a `text-fg-2` (mejor contraste en modo claro; `text-muted` rondaba ~3:1,
|
||||
> por debajo de WCAG AA para texto normal) y se hizo `actions` opcional para poder migrar
|
||||
> páginas sin acciones condicionales (como `u/[id]/+page.svelte`) sin renderizar un
|
||||
> `<div>` vacío.
|
||||
>
|
||||
> Pendiente de quien retome esto: `lib/components/ui/Rail.svelte` no tiene importadores
|
||||
> (confirmado con el mismo método de §5, no porque §5 lo listara — ese hallazgo es nuevo)
|
||||
> — no se tocó porque no forma parte de este barrido, pero conviene confirmarlo a mano y
|
||||
> borrarlo si procede. También queda pendiente repetir la verificación visual (§6) sobre
|
||||
> los cambios de este repaso: tarjetas de género de Explorar, cabeceras de colección
|
||||
> (contraste de la línea de meta) y `u/[id]/+page.svelte`.
|
||||
|
||||
## 0. Objetivo
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import Folder from '@lucide/svelte/icons/folder';
|
||||
import LogIn from '@lucide/svelte/icons/log-in';
|
||||
import MenuItem from './ui/MenuItem.svelte';
|
||||
import AccountMenu from './AccountMenu.svelte';
|
||||
import AccountMenu from './ui/AccountMenu.svelte';
|
||||
|
||||
interface Props {
|
||||
user: SessionUser | null;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import X from '@lucide/svelte/icons/x';
|
||||
import ChevronLeft from '@lucide/svelte/icons/chevron-left';
|
||||
import ChevronRight from '@lucide/svelte/icons/chevron-right';
|
||||
import AccountMenu from './AccountMenu.svelte';
|
||||
import AccountMenu from './ui/AccountMenu.svelte';
|
||||
|
||||
interface Props {
|
||||
user: SessionUser | null;
|
||||
@@ -107,11 +107,12 @@
|
||||
{accountUrl}
|
||||
panelClass="absolute top-full right-0 z-20 mt-2 w-52 overflow-hidden"
|
||||
>
|
||||
{#snippet trigger({ toggle })}
|
||||
{#snippet trigger({ toggle, open })}
|
||||
<button
|
||||
type="button"
|
||||
onclick={toggle}
|
||||
aria-label="Tu cuenta"
|
||||
aria-expanded={open}
|
||||
class="block h-8.25 w-8.25 shrink-0 overflow-hidden rounded-full border border-line-strong transition active:scale-95"
|
||||
>
|
||||
{#if user.picture}
|
||||
|
||||
+2
-2
@@ -6,8 +6,8 @@
|
||||
import Settings from '@lucide/svelte/icons/settings';
|
||||
import Sun from '@lucide/svelte/icons/sun';
|
||||
import Moon from '@lucide/svelte/icons/moon';
|
||||
import MenuItem from './ui/MenuItem.svelte';
|
||||
import GlassMenu from './ui/GlassMenu.svelte';
|
||||
import MenuItem from './MenuItem.svelte';
|
||||
import GlassMenu from './GlassMenu.svelte';
|
||||
import { themeMode } from '$lib/theme/mode.svelte';
|
||||
|
||||
interface Props {
|
||||
@@ -8,7 +8,7 @@
|
||||
meta?: string;
|
||||
align?: 'center' | 'end';
|
||||
cover: Snippet;
|
||||
actions: Snippet;
|
||||
actions?: Snippet;
|
||||
}
|
||||
|
||||
let { eyebrow, title, description, meta, align = 'end', cover, actions }: Props = $props();
|
||||
@@ -33,11 +33,13 @@
|
||||
<p class="mt-3 max-w-2xl text-sm text-fg-2 sm:text-base">{description}</p>
|
||||
{/if}
|
||||
{#if meta}
|
||||
<p class="mt-3 text-sm text-muted">{meta}</p>
|
||||
<p class="mt-3 text-sm text-fg-2">{meta}</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex flex-wrap items-center gap-2.5 sm:mt-7">
|
||||
{#if actions}
|
||||
<div class="mt-6 flex flex-wrap items-center gap-2.5 sm:mt-7">
|
||||
{@render actions()}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
--mf-logo-glow: 0 0 20px oklch(0.74 0.145 145 / 0.3);
|
||||
--mf-cover-grad: linear-gradient(150deg, oklch(0.5 0.13 145), oklch(0.16 0.035 145));
|
||||
--mf-liked-grad: linear-gradient(150deg, oklch(0.4 0.09 330), oklch(0.18 0.04 10));
|
||||
--mf-tile-shade: rgba(9, 9, 12, 0.95);
|
||||
|
||||
--mf-accent: oklch(0.74 0.145 145);
|
||||
--mf-accent-title: oklch(0.87 0.07 145);
|
||||
|
||||
@@ -477,7 +477,7 @@
|
||||
href={buildHref(genre.query)}
|
||||
class="animate-enter group relative block h-28 overflow-hidden rounded-2xl p-4.25 transition-[filter] hover:brightness-125"
|
||||
style="animation-delay:{Math.min(i, 10) *
|
||||
40}ms; background:linear-gradient(140deg, oklch(0.24 0.07 {genre.hue}), var(--mf-ink))"
|
||||
40}ms; background:linear-gradient(140deg, oklch(0.24 0.07 {genre.hue}), var(--mf-tile-shade))"
|
||||
>
|
||||
<div class="font-display text-base font-semibold tracking-[-0.02em] text-fg">
|
||||
{genre.label}
|
||||
|
||||
@@ -8,11 +8,13 @@
|
||||
import EmptyState from '$lib/components/ui/EmptyState.svelte';
|
||||
import Button from '$lib/components/ui/Button.svelte';
|
||||
import ArtistAvatar from '$lib/components/ui/ArtistAvatar.svelte';
|
||||
import CollectionHeader from '$lib/components/ui/CollectionHeader.svelte';
|
||||
|
||||
let { data, form } = $props();
|
||||
|
||||
const profile = $derived(data.profile);
|
||||
const playlists = $derived(data.playlists);
|
||||
const canFollow = $derived(!data.isOwnProfile && !data.isAnonymous);
|
||||
|
||||
let following = $state(untrack(() => data.isFollowing));
|
||||
let submitting = $state(false);
|
||||
@@ -28,25 +30,7 @@
|
||||
<meta name="description" content="Perfil de {profile.name} en Musify." />
|
||||
</svelte:head>
|
||||
|
||||
<Page>
|
||||
<div class="flex flex-col gap-5 sm:flex-row sm:items-center sm:gap-6">
|
||||
<ArtistAvatar name={profile.name} imageUrl={profile.profilePictureUrl} size={144} />
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="text-xs font-semibold tracking-[0.16em] text-fg-2 uppercase">Perfil</p>
|
||||
<h1
|
||||
class="mt-3 font-display text-3xl font-semibold tracking-[-0.035em] break-words text-fg sm:text-4xl"
|
||||
>
|
||||
{profile.name}
|
||||
</h1>
|
||||
<p class="mt-3.25 text-sm text-fg-2">
|
||||
{profile.followersCount}
|
||||
{profile.followersCount === 1 ? 'seguidor' : 'seguidores'} · {profile.followingCount} siguiendo
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if !data.isOwnProfile && !data.isAnonymous}
|
||||
<div class="mt-6 flex items-center gap-2.5 sm:mt-7">
|
||||
{#snippet followAction()}
|
||||
<form
|
||||
method="POST"
|
||||
action={following ? '?/unfollow' : '?/follow'}
|
||||
@@ -67,8 +51,22 @@
|
||||
{following ? 'Dejar de seguir' : 'Seguir'}
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
||||
<Page>
|
||||
<CollectionHeader
|
||||
eyebrow="Perfil"
|
||||
title={profile.name}
|
||||
meta="{profile.followersCount} {profile.followersCount === 1
|
||||
? 'seguidor'
|
||||
: 'seguidores'} · {profile.followingCount} siguiendo"
|
||||
align="center"
|
||||
actions={canFollow ? followAction : undefined}
|
||||
>
|
||||
{#snippet cover()}
|
||||
<ArtistAvatar name={profile.name} imageUrl={profile.profilePictureUrl} size={144} />
|
||||
{/snippet}
|
||||
</CollectionHeader>
|
||||
|
||||
<div class="mt-9">
|
||||
<h2 class="font-display text-lg font-semibold tracking-[-0.02em] text-fg">
|
||||
|
||||
Reference in New Issue
Block a user