feat: v1.9.0 — new themes, keybindings, font picker, home play behavior

Themes:
- Add Neon Drift (midnight blue / electric cyan synthwave)
- Add Cupertino Light + Cupertino Dark (macOS Ventura-inspired, frosted glass)
- Add Betriebssysteme group (Cupertino, Aero Glass, Luna Teal)
- Rename all trademarked theme IDs to original names (WnAmp, Navy Jukebox,
  Cobalt Media, Onyx Cinema, Aero Glass, Luna Teal)
- Reorder ThemePicker: Psysonic Themes first, then Mediaplayer, Betriebssysteme

Keybindings:
- New keybindingsStore with 10 bindable actions (play/pause, next, prev,
  volume, seek ±10s, queue, fullscreen, native fullscreen)
- Settings UI for rebinding; defaults: Space=play-pause, F11=native-fullscreen

Font picker:
- New fontStore; 10 UI fonts selectable in Settings → Appearance
- Applied via data-font attribute on <html>

Home page:
- AlbumCard: Details button → Play button via playAlbum() utility
- Hero: Play Album starts playback with 700ms fade-out instead of navigating
- playAlbum.ts: fade, store-only volume restore, playTrack handoff

Now Playing:
- 3-column hero layout; EQ bars truly centred (flex:1 on both outer columns)
- Background brightness 0.25→0.55, overlay 0.55→0.38 (art now visible)
- Better text contrast for track times, card links, section titles

Linux audio:
- Set PIPEWIRE_LATENCY + PULSE_LATENCY_MSEC before stream creation
  to reduce ALSA snd_pcm_recover underrun frequency on PipeWire

Remove butterchurn visualizer (VisualizerCanvas, butterchurn.d.ts)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Psychotoxical
2026-03-21 14:22:02 +01:00
parent 0b1ed8cc5a
commit 57b70e6154
30 changed files with 2760 additions and 702 deletions
+96 -16
View File
@@ -56,6 +56,12 @@ const enTranslation = {
nobody: 'Nobody is currently listening.',
minutesAgo: '{{n}}m ago',
nothingPlaying: 'Nothing playing yet. Start a track!',
aboutArtist: 'About the Artist',
fromAlbum: 'From this Album',
viewAlbum: 'View Album',
goToArtist: 'Go to Artist',
readMore: 'Read more',
showLess: 'Show less',
},
contextMenu: {
playNow: 'Play Now',
@@ -261,6 +267,7 @@ const enTranslation = {
languageDe: 'German',
languageFr: 'French',
languageNl: 'Dutch',
font: 'Font',
theme: 'Theme',
appearance: 'Appearance',
servers: 'Servers',
@@ -335,8 +342,23 @@ const enTranslation = {
tabPlayback: 'Playback',
tabLibrary: 'Library',
tabAppearance: 'Appearance',
tabShortcuts: 'Shortcuts',
tabServer: 'Server',
tabAbout: 'About',
shortcutsReset: 'Reset to defaults',
shortcutListening: 'Press a key…',
shortcutUnbound: '—',
shortcutClear: 'Clear',
shortcutPlayPause: 'Play / Pause',
shortcutNext: 'Next track',
shortcutPrev: 'Previous track',
shortcutVolumeUp: 'Volume up',
shortcutVolumeDown: 'Volume down',
shortcutSeekForward: 'Seek forward 10s',
shortcutSeekBackward: 'Seek backward 10s',
shortcutToggleQueue: 'Toggle queue',
shortcutFullscreenPlayer: 'Fullscreen player',
shortcutNativeFullscreen: 'Native fullscreen',
playbackTitle: 'Playback',
replayGain: 'Replay Gain',
replayGainDesc: 'Normalize track volume using ReplayGain metadata',
@@ -393,8 +415,6 @@ const enTranslation = {
a17: 'A scrobble is submitted after you\'ve listened to 50% of a track.',
q22: 'What is the waveform in the player bar?',
a22: 'The waveform seekbar replaces the classic progress slider. Click anywhere on it to jump to that position, or drag to scrub. The played portion glows with a blue-to-mauve gradient, the buffered range appears slightly brighter, and the unplayed portion is faded.',
q23: 'How do I use the MilkDrop visualizer?',
a23: 'Open the fullscreen player (click the album art in the player bar), then click the waveform icon in the top-right corner. The visualizer starts with a random MilkDrop preset. Use the shuffle button next to it to cycle through hundreds of presets.',
q24: 'Can I shuffle the queue?',
a24: 'Yes. Open the queue panel and click the shuffle icon in the queue header. The currently playing track stays at position 1 — all remaining tracks are randomly reordered.',
q25: 'Do Last.fm and Wikipedia links on artist pages open in a browser?',
@@ -440,6 +460,8 @@ const enTranslation = {
emptyQueue: 'The queue is empty.',
trackSingular: 'track',
trackPlural: 'tracks',
showRemaining: 'Show remaining time',
showTotal: 'Show total time',
},
statistics: {
title: 'Statistics',
@@ -483,8 +505,6 @@ const enTranslation = {
openFullscreen: 'Open Fullscreen Player',
fullscreen: 'Fullscreen Player',
closeFullscreen: 'Close Fullscreen',
visualizer: 'Visualizer',
nextPreset: 'Next preset',
closeTooltip: 'Close (Esc)',
noTitle: 'No Title',
stop: 'Stop',
@@ -557,6 +577,12 @@ const deTranslation = {
nobody: 'Gerade hört niemand Musik.',
minutesAgo: 'vor {{n}}m',
nothingPlaying: 'Noch nichts am Laufen. Leg einen Track auf!',
aboutArtist: 'Über den Künstler',
fromAlbum: 'Aus diesem Album',
viewAlbum: 'Album öffnen',
goToArtist: 'Zum Künstler',
readMore: 'Mehr lesen',
showLess: 'Weniger anzeigen',
},
contextMenu: {
playNow: 'Direkt abspielen',
@@ -762,6 +788,7 @@ const deTranslation = {
languageDe: 'Deutsch',
languageFr: 'Französisch',
languageNl: 'Niederländisch',
font: 'Schriftart',
theme: 'Design',
appearance: 'Darstellung',
servers: 'Server',
@@ -836,7 +863,22 @@ const deTranslation = {
tabPlayback: 'Wiedergabe',
tabLibrary: 'Bibliothek',
tabAppearance: 'Darstellung',
tabShortcuts: 'Tastenkürzel',
tabServer: 'Server',
shortcutsReset: 'Auf Standard zurücksetzen',
shortcutListening: 'Taste drücken…',
shortcutUnbound: '—',
shortcutClear: 'Löschen',
shortcutPlayPause: 'Wiedergabe / Pause',
shortcutNext: 'Nächster Titel',
shortcutPrev: 'Vorheriger Titel',
shortcutVolumeUp: 'Lautstärke erhöhen',
shortcutVolumeDown: 'Lautstärke verringern',
shortcutSeekForward: '10s vorspulen',
shortcutSeekBackward: '10s zurückspulen',
shortcutToggleQueue: 'Warteschlange ein-/ausblenden',
shortcutFullscreenPlayer: 'Vollbild-Player',
shortcutNativeFullscreen: 'Nativer Vollbildmodus',
tabAbout: 'Info',
playbackTitle: 'Wiedergabe',
replayGain: 'Replay Gain',
@@ -894,8 +936,6 @@ const deTranslation = {
a17: 'Ein Scrobble wird übermittelt, wenn 50 % eines Tracks gehört wurden.',
q22: 'Was ist die Waveform in der Playerleiste?',
a22: 'Die Waveform-Leiste ersetzt den klassischen Fortschrittsbalken. Klick auf eine beliebige Stelle zum Springen, ziehen zum Scrubben. Der gespielte Teil leuchtet in einem Blau-Mauve-Farbverlauf, der gepufferte Bereich erscheint etwas heller, der ungespielter Teil ist abgeblendet.',
q23: 'Wie benutze ich den MilkDrop-Visualizer?',
a23: 'Vollbild-Player öffnen (Klick auf das Album-Cover in der Playerleiste), dann oben rechts auf das Waveform-Icon klicken. Der Visualizer startet mit einem zufälligen MilkDrop-Preset. Mit dem Shuffle-Button daneben durch Hunderte von Presets wechseln.',
q24: 'Kann ich die Warteschlange mischen?',
a24: 'Ja. Die Warteschlange öffnen und auf das Shuffle-Icon im Header klicken. Der aktuell laufende Track bleibt an Position 1 — alle restlichen Tracks werden zufällig neu geordnet.',
q25: 'Öffnen Last.fm- und Wikipedia-Links auf Künstlerseiten im Browser?',
@@ -941,6 +981,8 @@ const deTranslation = {
emptyQueue: 'Die Warteschlange ist leer.',
trackSingular: 'Titel',
trackPlural: 'Titel',
showRemaining: 'Restzeit anzeigen',
showTotal: 'Gesamtzeit anzeigen',
},
statistics: {
title: 'Statistiken',
@@ -984,8 +1026,6 @@ const deTranslation = {
openFullscreen: 'Vollbild-Player öffnen',
fullscreen: 'Vollbild-Player',
closeFullscreen: 'Vollbild schließen',
visualizer: 'Visualizer',
nextPreset: 'Nächstes Preset',
closeTooltip: 'Schließen (Esc)',
noTitle: 'Kein Titel',
stop: 'Stop',
@@ -1058,6 +1098,12 @@ const frTranslation = {
nobody: 'Personne n\'écoute en ce moment.',
minutesAgo: 'il y a {{n}} min',
nothingPlaying: 'Rien en cours. Lancez un morceau !',
aboutArtist: "À propos de l'artiste",
fromAlbum: 'De cet album',
viewAlbum: "Voir l'album",
goToArtist: "Aller à l'artiste",
readMore: 'Lire la suite',
showLess: 'Réduire',
},
contextMenu: {
playNow: 'Lire maintenant',
@@ -1263,6 +1309,7 @@ const frTranslation = {
languageDe: 'Allemand',
languageFr: 'Français',
languageNl: 'Néerlandais',
font: 'Police',
theme: 'Thème',
appearance: 'Apparence',
servers: 'Serveurs',
@@ -1337,7 +1384,22 @@ const frTranslation = {
tabPlayback: 'Lecture',
tabLibrary: 'Bibliothèque',
tabAppearance: 'Apparence',
tabShortcuts: 'Raccourcis',
tabServer: 'Serveur',
shortcutsReset: 'Réinitialiser',
shortcutListening: 'Appuyez sur une touche…',
shortcutUnbound: '—',
shortcutClear: 'Effacer',
shortcutPlayPause: 'Lecture / Pause',
shortcutNext: 'Piste suivante',
shortcutPrev: 'Piste précédente',
shortcutVolumeUp: 'Volume +',
shortcutVolumeDown: 'Volume -',
shortcutSeekForward: 'Avancer de 10s',
shortcutSeekBackward: 'Reculer de 10s',
shortcutToggleQueue: 'Afficher/masquer la file',
shortcutFullscreenPlayer: 'Lecteur plein écran',
shortcutNativeFullscreen: 'Plein écran natif',
tabAbout: 'À propos',
playbackTitle: 'Lecture',
replayGain: 'Replay Gain',
@@ -1395,8 +1457,6 @@ const frTranslation = {
a17: 'Un scrobble est soumis après avoir écouté 50% d\'une piste.',
q22: 'Qu\'est-ce que la forme d\'onde dans la barre du lecteur ?',
a22: 'La barre de forme d\'onde remplace le curseur de progression classique. Cliquez n\'importe où pour sauter à cette position, ou faites glisser pour naviguer.',
q23: 'Comment utiliser le visualiseur MilkDrop ?',
a23: 'Ouvrez le lecteur plein écran (cliquez sur la pochette), puis cliquez sur l\'icône de forme d\'onde en haut à droite. Utilisez le bouton aléatoire pour parcourir les préréglages.',
q24: 'Puis-je mélanger la file d\'attente ?',
a24: 'Oui. Ouvrez le panneau de file et cliquez sur l\'icône de mélange. La piste en cours reste en position 1 — les autres sont réorganisées aléatoirement.',
q25: 'Les liens Last.fm et Wikipédia s\'ouvrent-ils dans un navigateur ?',
@@ -1442,6 +1502,8 @@ const frTranslation = {
emptyQueue: 'La file d\'attente est vide.',
trackSingular: 'piste',
trackPlural: 'pistes',
showRemaining: 'Afficher le temps restant',
showTotal: 'Afficher la durée totale',
},
statistics: {
title: 'Statistiques',
@@ -1485,8 +1547,6 @@ const frTranslation = {
openFullscreen: 'Ouvrir le lecteur plein écran',
fullscreen: 'Lecteur plein écran',
closeFullscreen: 'Fermer le plein écran',
visualizer: 'Visualiseur',
nextPreset: 'Préréglage suivant',
closeTooltip: 'Fermer (Échap)',
noTitle: 'Sans titre',
stop: 'Arrêter',
@@ -1559,6 +1619,12 @@ const nlTranslation = {
nobody: 'Er luistert momenteel niemand.',
minutesAgo: '{{n}} min geleden',
nothingPlaying: 'Nog niets bezig. Start een nummer!',
aboutArtist: 'Over de artiest',
fromAlbum: 'Van dit album',
viewAlbum: 'Album openen',
goToArtist: 'Naar artiest',
readMore: 'Meer lezen',
showLess: 'Minder tonen',
},
contextMenu: {
playNow: 'Nu afspelen',
@@ -1764,6 +1830,7 @@ const nlTranslation = {
languageDe: 'Duits',
languageFr: 'Frans',
languageNl: 'Nederlands',
font: 'Lettertype',
theme: 'Thema',
appearance: 'Weergave',
servers: 'Servers',
@@ -1838,7 +1905,22 @@ const nlTranslation = {
tabPlayback: 'Afspelen',
tabLibrary: 'Bibliotheek',
tabAppearance: 'Weergave',
tabShortcuts: 'Sneltoetsen',
tabServer: 'Server',
shortcutsReset: 'Standaard herstellen',
shortcutListening: 'Druk op een toets…',
shortcutUnbound: '—',
shortcutClear: 'Wissen',
shortcutPlayPause: 'Afspelen / Pauzeren',
shortcutNext: 'Volgend nummer',
shortcutPrev: 'Vorig nummer',
shortcutVolumeUp: 'Volume omhoog',
shortcutVolumeDown: 'Volume omlaag',
shortcutSeekForward: '10s vooruitspoelen',
shortcutSeekBackward: '10s terugspoelen',
shortcutToggleQueue: 'Wachtrij tonen/verbergen',
shortcutFullscreenPlayer: 'Volledigschermspeler',
shortcutNativeFullscreen: 'Systeemvolledig scherm',
tabAbout: 'Over',
playbackTitle: 'Afspelen',
replayGain: 'Replay Gain',
@@ -1896,8 +1978,6 @@ const nlTranslation = {
a17: 'Een scrobble wordt verstuurd nadat je 50% van een nummer hebt geluisterd.',
q22: 'Wat is de golfvorm in de spelerbalk?',
a22: 'De golfvormzoekbalk vervangt de klassieke voortgangsschuifregelaar. Klik ergens om naar die positie te springen, of sleep om door het nummer te navigeren.',
q23: 'Hoe gebruik ik de MilkDrop-visualizer?',
a23: 'Open de volledig-schermspeler (klik op de albumhoes) en klik dan op het golfvormpictogram rechtsboven. Gebruik de willekeurigknop om door honderden presets te bladeren.',
q24: 'Kan ik de wachtrij shufflen?',
a24: 'Ja. Open het wachtrijpaneel en klik op het shufflepictogram. Het huidige nummer blijft op positie 1 — alle overige nummers worden willekeurig geherordend.',
q25: 'Openen Last.fm- en Wikipedia-links in een browser?',
@@ -1943,6 +2023,8 @@ const nlTranslation = {
emptyQueue: 'De wachtrij is leeg.',
trackSingular: 'nummer',
trackPlural: 'nummers',
showRemaining: 'Resterende tijd tonen',
showTotal: 'Totale tijd tonen',
},
statistics: {
title: 'Statistieken',
@@ -1986,8 +2068,6 @@ const nlTranslation = {
openFullscreen: 'Volledig-schermspeler openen',
fullscreen: 'Volledig-schermspeler',
closeFullscreen: 'Volledig scherm sluiten',
visualizer: 'Visualizer',
nextPreset: 'Volgende preset',
closeTooltip: 'Sluiten (Escape)',
noTitle: 'Geen titel',
stop: 'Stoppen',