Initial release with i18n and theming

This commit is contained in:
Psychotoxical
2026-03-09 19:02:57 +01:00
commit 65459e53f1
110 changed files with 29536 additions and 0 deletions
+87
View File
@@ -0,0 +1,87 @@
import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Play, ListPlus } from 'lucide-react';
import { getRandomAlbums, SubsonicAlbum, buildCoverArtUrl, getAlbum } from '../api/subsonic';
import { usePlayerStore } from '../store/playerStore';
export default function Hero() {
const [album, setAlbum] = useState<SubsonicAlbum | null>(null);
const navigate = useNavigate();
useEffect(() => {
let cancelled = false;
getRandomAlbums(1).then(albums => {
if (!cancelled && albums[0]) setAlbum(albums[0]);
}).catch(() => {});
return () => { cancelled = true; };
}, []);
if (!album) return <div className="hero-placeholder" />;
const coverUrl = album.coverArt ? buildCoverArtUrl(album.coverArt, 800) : '';
return (
<div
className="hero"
role="banner"
aria-label="Album des Augenblicks"
onClick={() => navigate(`/album/${album.id}`)}
style={{ cursor: 'pointer' }}
>
{coverUrl && (
<div
className="hero-bg"
style={{ backgroundImage: `url(${coverUrl})` }}
aria-hidden="true"
/>
)}
<div className="hero-overlay" aria-hidden="true" />
<div className="hero-content animate-fade-in">
{coverUrl && (
<img className="hero-cover" src={coverUrl} alt={`${album.name} Cover`} />
)}
<div className="hero-text">
<span className="hero-eyebrow">Album des Augenblicks</span>
<h2 className="hero-title">{album.name}</h2>
<p className="hero-artist">{album.artist}</p>
<div className="hero-meta">
{album.year && <span className="badge">{album.year}</span>}
{album.genre && <span className="badge">{album.genre}</span>}
{album.songCount && <span className="badge">{album.songCount} Tracks</span>}
</div>
<div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap' }}>
<button
className="hero-play-btn"
id="hero-play-btn"
onClick={e => { e.stopPropagation(); navigate(`/album/${album.id}`); }}
aria-label={`Album ${album.name} abspielen`}
>
<Play size={18} fill="currentColor" />
Album abspielen
</button>
<button
className="btn btn-surface"
onClick={async (e) => {
e.stopPropagation();
try {
const albumData = await getAlbum(album.id);
const tracks = albumData.songs.map(s => ({
id: s.id, title: s.title, artist: s.artist, album: s.album,
albumId: s.albumId, duration: s.duration, coverArt: s.coverArt, track: s.track,
year: s.year, bitRate: s.bitRate, suffix: s.suffix, userRating: s.userRating,
}));
usePlayerStore.getState().enqueue(tracks);
} catch (err) { }
}}
style={{ padding: '0 1.5rem', fontWeight: 600, fontSize: '0.95rem' }}
data-tooltip="Ganzes Album zur Warteschlange hinzufügen"
>
<ListPlus size={18} />
Einreihen
</button>
</div>
</div>
</div>
</div>
);
}