Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 9 additions & 10 deletions web/src/components/DeletePlaylistDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,19 +111,19 @@ export function DeletePlaylistDialog({
aria-modal="true"
aria-labelledby={headingId}
tabIndex={-1}
className="fixed inset-x-0 top-1/2 z-50 mx-auto -translate-y-1/2 w-full max-w-md px-4"
className="fixed inset-x-0 top-1/2 z-50 mx-auto w-full max-w-md -translate-y-1/2 px-4"
>
<div className="rounded-2xl border border-[var(--line)] bg-[var(--header-bg)] p-6 shadow-[0_20px_60px_rgba(0,0,0,0.2)] backdrop-blur-lg">
<h2 id={headingId} className="text-xl font-bold text-[var(--sea-ink)]">
<div className="panel panel-pad">
<h2 id={headingId} className="text-xl font-bold text-(--sea-ink)">
Delete playlist
</h2>
<p className="mt-3 text-sm text-[var(--sea-ink-soft)]">
<span className="font-semibold text-[var(--sea-ink)]">{playlistName}</span> will be
removed permanently. The tracks themselves are not deleted; they stay in your library.
<p className="mt-3 text-sm text-(--sea-ink-soft)">
<span className="font-semibold text-(--sea-ink)">{playlistName}</span> will be removed
permanently. The tracks themselves are not deleted; they stay in your library.
</p>

{error && (
<p role="alert" className="mt-4 text-sm font-medium text-red-600 dark:text-red-400">
<p role="alert" className="error-card mt-4 text-sm font-medium">
{error}
</p>
)}
Expand All @@ -134,16 +134,15 @@ export function DeletePlaylistDialog({
type="button"
onClick={onClose}
disabled={submitting}
className="rounded-xl border border-[var(--line)] bg-[var(--chip-bg)] px-4 py-2 text-sm font-semibold text-[var(--sea-ink)] transition hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-60"
className="button button-ghost"
>
Cancel
</button>
<button
type="button"
onClick={onConfirm}
disabled={submitting}
className="rounded-xl px-4 py-2 text-sm font-semibold text-white transition hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-60"
style={{ backgroundColor: '#dc2626' }}
className="button button-danger"
>
{submitting ? 'Deleting…' : 'Delete playlist'}
</button>
Expand Down
10 changes: 6 additions & 4 deletions web/src/components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export default function Footer() {
const year = new Date().getFullYear()

return (
<footer className="mt-20 border-t border-[var(--line)] px-4 pb-14 pt-10 text-[var(--sea-ink-soft)]">
<footer className="site-footer mt-20 px-4 pb-14 pt-10 text-(--sea-ink-soft)">
<div className="page-wrap flex flex-col items-center justify-between gap-4 text-center sm:flex-row sm:text-left">
<div className="flex items-center gap-2">
<span style={{ color: 'var(--accent-600)' }}>
Expand All @@ -16,21 +16,23 @@ export default function Footer() {
href="https://github.com/InstaZDLL"
target="_blank"
rel="noreferrer"
className="font-semibold text-[var(--sea-ink)] no-underline hover:underline"
className="font-semibold text-(--sea-ink) no-underline hover:underline"
>
InstaZDLL
</a>
. Open source under AGPL-3.0.
</p>
</div>
<p className="island-kicker m-0">Your music, your server, every device.</p>
<p className="m-0 text-xs font-semibold tracking-[0.08em] uppercase">
Your files stay home
</p>
</div>
<div className="mt-4 flex justify-center gap-4">
<a
href="https://github.com/InstaZDLL/WaveFlow"
target="_blank"
rel="noreferrer"
className="rounded-xl p-2 text-[var(--sea-ink-soft)] transition hover:bg-[var(--link-bg-hover)] hover:text-[var(--sea-ink)]"
className="rounded-xl p-2 text-(--sea-ink-soft) transition hover:bg-(--link-bg-hover) hover:text-(--sea-ink)"
>
<span className="sr-only">WaveFlow on GitHub</span>
<svg viewBox="0 0 16 16" aria-hidden="true" width="32" height="32">
Expand Down
19 changes: 8 additions & 11 deletions web/src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ export default function Header() {
}

return (
<header className="sticky top-0 z-50 border-b border-[var(--line)] bg-[var(--header-bg)] px-4 backdrop-blur-lg">
<nav className="page-wrap flex flex-wrap items-center gap-x-3 gap-y-2 py-3 sm:py-4">
<header className="sticky top-0 z-50 border-b border-(--line) bg-(--header-bg) px-4 backdrop-blur-xl">
<nav className="page-wrap flex flex-wrap items-center gap-x-3 gap-y-2 py-3">
<h2 className="m-0 flex-shrink-0 text-base font-semibold tracking-tight">
<Link
to="/"
className="inline-flex items-center gap-2 rounded-full border border-[var(--chip-line)] bg-[var(--chip-bg)] px-3 py-1.5 text-sm text-[var(--sea-ink)] no-underline shadow-[0_8px_24px_rgba(30,90,72,0.08)] sm:px-4 sm:py-2"
className="inline-flex items-center gap-2 rounded-xl border border-(--chip-line) bg-(--chip-bg) px-3 py-2 text-sm text-(--sea-ink) no-underline shadow-[0_1px_0_var(--inset-glint)_inset]"
>
<span style={{ color: 'var(--accent-600)' }}>
<WaveflowLogo size={18} label={null} />
Expand All @@ -48,7 +48,7 @@ export default function Header() {
</Link>
</h2>

<div className="order-3 flex w-full flex-wrap items-center gap-x-4 gap-y-1 pb-1 text-sm font-semibold sm:order-none sm:w-auto sm:flex-nowrap sm:pb-0">
<div className="order-3 flex w-full flex-wrap items-center gap-x-1 gap-y-1 pb-1 text-sm font-semibold sm:order-none sm:w-auto sm:flex-nowrap sm:pb-0">
<Link to="/" className="nav-link" activeProps={{ className: 'nav-link is-active' }}>
Home
</Link>
Expand Down Expand Up @@ -80,7 +80,7 @@ export default function Header() {
href="https://github.com/InstaZDLL/WaveFlow"
target="_blank"
rel="noreferrer"
className="hidden rounded-xl p-2 text-[var(--sea-ink-soft)] transition hover:bg-[var(--link-bg-hover)] hover:text-[var(--sea-ink)] sm:block"
className="hidden rounded-xl p-2 text-(--sea-ink-soft) transition hover:bg-(--link-bg-hover) hover:text-(--sea-ink) sm:block"
>
<span className="sr-only">WaveFlow on GitHub</span>
<svg viewBox="0 0 16 16" aria-hidden="true" width="24" height="24">
Expand All @@ -101,15 +101,15 @@ export default function Header() {
{isPending ? null : session?.user ? (
<>
<span
className="hidden text-sm text-[var(--sea-ink-soft)] sm:inline"
className="hidden text-sm text-(--sea-ink-soft) sm:inline"
title={session.user.email}
>
{session.user.name || session.user.email}
</span>
<button
type="button"
onClick={onSignOut}
className="rounded-xl border border-[var(--line)] bg-[var(--chip-bg)] px-3 py-1.5 text-sm font-semibold text-[var(--sea-ink)] transition hover:opacity-90"
className="button button-ghost min-h-0 px-3 py-2"
>
Sign out
</button>
Expand All @@ -123,10 +123,7 @@ export default function Header() {
>
Sign in
</Link>
<Link
to="/sign-up"
className="rounded-xl bg-[var(--sea-ink)] px-3 py-1.5 text-sm font-semibold text-white transition hover:opacity-90"
>
<Link to="/sign-up" className="button button-primary min-h-0 px-3 py-2">
Sign up
</Link>
</>
Expand Down
20 changes: 8 additions & 12 deletions web/src/components/NowPlayingOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export function NowPlayingOverlay({ open, onClose }: NowPlayingOverlayProps) {
type="button"
onClick={onClose}
aria-label="Close now playing"
className="flex h-10 w-10 items-center justify-center rounded-full text-[var(--sea-ink)] transition hover:bg-[var(--link-bg-hover)]"
className="flex h-10 w-10 items-center justify-center rounded-full text-(--sea-ink) transition hover:bg-(--link-bg-hover)"
>
<svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true">
<path
Expand All @@ -111,18 +111,14 @@ export function NowPlayingOverlay({ open, onClose }: NowPlayingOverlayProps) {
<WaveflowLogo size={120} label={null} />
</div>
<div className="flex flex-col gap-1">
<h2 className="text-2xl font-bold text-[var(--sea-ink)] sm:text-3xl">
{current.title}
</h2>
{current.artist && (
<p className="text-base text-[var(--sea-ink-soft)]">{current.artist}</p>
)}
<h2 className="text-2xl font-bold text-(--sea-ink) sm:text-3xl">{current.title}</h2>
{current.artist && <p className="text-base text-(--sea-ink-soft)">{current.artist}</p>}
</div>
</div>

<div className="flex w-full max-w-xl flex-col gap-4">
<div className="flex items-center gap-3">
<span className="w-12 text-right text-xs tabular-nums text-[var(--sea-ink-soft)]">
<span className="w-12 text-right text-xs tabular-nums text-(--sea-ink-soft)">
{formatTime(seekScrub ?? player.position)}
</span>
<input
Expand Down Expand Up @@ -157,7 +153,7 @@ export function NowPlayingOverlay({ open, onClose }: NowPlayingOverlayProps) {
className="min-w-0 flex-1"
style={{ accentColor: 'var(--accent-600)' }}
/>
<span className="w-12 text-left text-xs tabular-nums text-[var(--sea-ink-soft)]">
<span className="w-12 text-left text-xs tabular-nums text-(--sea-ink-soft)">
{formatTime(durationSec)}
</span>
</div>
Expand All @@ -168,7 +164,7 @@ export function NowPlayingOverlay({ open, onClose }: NowPlayingOverlayProps) {
onClick={player.previous}
aria-label="Previous track"
disabled={player.isLoading}
className="flex h-12 w-12 items-center justify-center rounded-full text-[var(--sea-ink)] transition hover:bg-[var(--link-bg-hover)] disabled:opacity-40"
className="flex h-12 w-12 items-center justify-center rounded-full text-(--sea-ink) transition hover:bg-(--link-bg-hover) disabled:opacity-40"
>
<svg viewBox="0 0 24 24" width="26" height="26" aria-hidden="true">
<path d="M6 6h2v12H6zM9.5 12l8.5-6v12z" fill="currentColor" />
Expand Down Expand Up @@ -198,7 +194,7 @@ export function NowPlayingOverlay({ open, onClose }: NowPlayingOverlayProps) {
onClick={player.next}
aria-label="Next track"
disabled={player.isLoading || player.queue.length === 0}
className="flex h-12 w-12 items-center justify-center rounded-full text-[var(--sea-ink)] transition hover:bg-[var(--link-bg-hover)] disabled:opacity-40"
className="flex h-12 w-12 items-center justify-center rounded-full text-(--sea-ink) transition hover:bg-(--link-bg-hover) disabled:opacity-40"
>
<svg viewBox="0 0 24 24" width="26" height="26" aria-hidden="true">
<path d="M16 6h2v12h-2zM6 6l8.5 6L6 18z" fill="currentColor" />
Expand All @@ -212,7 +208,7 @@ export function NowPlayingOverlay({ open, onClose }: NowPlayingOverlayProps) {
width="18"
height="18"
aria-hidden="true"
className="text-[var(--sea-ink-soft)]"
className="text-(--sea-ink-soft)"
>
<path
fill="currentColor"
Expand Down
10 changes: 5 additions & 5 deletions web/src/components/OAuthButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,14 @@ export function OAuthButtons({ enabled, callbackURL }: Props) {
type="button"
onClick={() => onClick(provider.id)}
disabled={pending !== null}
className="flex items-center justify-center gap-2 rounded-xl border border-[var(--line)] bg-white/80 px-4 py-2.5 text-sm font-semibold text-[var(--sea-ink)] transition hover:bg-white disabled:cursor-not-allowed disabled:opacity-60 dark:bg-black/30 dark:hover:bg-black/40"
className="button button-ghost w-full"
>
{provider.icon}
<span>{pending === provider.id ? 'Redirecting…' : provider.label}</span>
</button>
))}
{error && (
<p role="alert" className="text-sm font-medium text-red-600 dark:text-red-400">
<p role="alert" className="error-card text-sm font-medium">
{error}
</p>
)}
Expand All @@ -135,10 +135,10 @@ export function OAuthButtons({ enabled, callbackURL }: Props) {
*/
export function OAuthDivider() {
return (
<div className="my-2 flex items-center gap-3 text-xs uppercase tracking-wider text-[var(--sea-ink-soft)]">
<span className="h-px flex-1 bg-[var(--line)]" />
<div className="my-2 flex items-center gap-3 text-xs uppercase tracking-wider text-(--sea-ink-soft)">
<span className="h-px flex-1 bg-(--line)" />
<span>or</span>
<span className="h-px flex-1 bg-[var(--line)]" />
<span className="h-px flex-1 bg-(--line)" />
</div>
)
}
21 changes: 8 additions & 13 deletions web/src/components/PlayableTrackList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,47 +111,42 @@ export function PlayableTrackList({
}

if (tracks.length === 0) {
return <p className="text-base text-[var(--sea-ink-soft)]">{emptyMessage}</p>
return <div className="status-card">{emptyMessage}</div>
}

return (
<>
{error && (
<p
role="alert"
className="mb-4 rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700 dark:border-red-900 dark:bg-red-950/40 dark:text-red-300"
>
<p role="alert" className="error-card mb-4 text-sm">
{error}
</p>
)}
<ul aria-label={label} className="divide-y divide-[var(--line)]">
<ul aria-label={label} className="media-list">
{tracks.map((track) => {
const isCurrent = player.current?.trackId === track.id
const isPending = pendingTrackId === track.id
return (
<li key={track.id} className="flex items-center gap-3 py-2">
<li key={track.id} className="media-row">
<button
type="button"
onClick={() => play(track)}
disabled={isPending}
aria-label={`Play ${track.title}`}
className="flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-full border border-[var(--line)] bg-[var(--chip-bg)] transition hover:opacity-90 disabled:opacity-50"
className="flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-xl border border-(--line) bg-(--chip-bg) text-xs font-bold transition hover:bg-(--link-bg-hover) disabled:opacity-50"
>
{isPending ? '…' : '▶'}
</button>
<div className="min-w-0 flex-1">
<p
className={`truncate text-sm ${
isCurrent ? 'font-semibold text-[var(--sea-ink)]' : 'text-[var(--sea-ink)]'
}`}
className={`truncate text-sm ${isCurrent ? 'font-bold text-(--sea-ink)' : 'font-medium text-(--sea-ink)'}`}
>
{track.title}
</p>
{track.codec && (
<p className="truncate text-xs text-[var(--sea-ink-soft)]">{track.codec}</p>
<p className="truncate text-xs text-(--sea-ink-soft)">{track.codec}</p>
)}
</div>
<span className="text-xs tabular-nums text-[var(--sea-ink-soft)]">
<span className="text-xs tabular-nums text-(--sea-ink-soft)">
{formatTime(track.duration_ms / 1000)}
</span>
</li>
Expand Down
Loading
Loading