mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 06:25:41 +00:00
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:
@@ -0,0 +1,19 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
export type FontId = 'inter' | 'outfit' | 'dm-sans' | 'nunito' | 'rubik' | 'space-grotesk' | 'figtree' | 'manrope' | 'plus-jakarta-sans' | 'lexend';
|
||||
|
||||
interface FontState {
|
||||
font: FontId;
|
||||
setFont: (font: FontId) => void;
|
||||
}
|
||||
|
||||
export const useFontStore = create<FontState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
font: 'inter',
|
||||
setFont: (font) => set({ font }),
|
||||
}),
|
||||
{ name: 'psysonic_font' }
|
||||
)
|
||||
);
|
||||
@@ -0,0 +1,80 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
export type KeyAction =
|
||||
| 'play-pause'
|
||||
| 'next'
|
||||
| 'prev'
|
||||
| 'volume-up'
|
||||
| 'volume-down'
|
||||
| 'seek-forward'
|
||||
| 'seek-backward'
|
||||
| 'toggle-queue'
|
||||
| 'fullscreen-player'
|
||||
| 'native-fullscreen';
|
||||
|
||||
// key = action, value = e.code string (e.g. 'Space', 'KeyN', 'F11') or null for unbound
|
||||
export type Bindings = Record<KeyAction, string | null>;
|
||||
|
||||
export const DEFAULT_BINDINGS: Bindings = {
|
||||
'play-pause': 'Space',
|
||||
'next': null,
|
||||
'prev': null,
|
||||
'volume-up': null,
|
||||
'volume-down': null,
|
||||
'seek-forward': null,
|
||||
'seek-backward': null,
|
||||
'toggle-queue': null,
|
||||
'fullscreen-player': null,
|
||||
'native-fullscreen': 'F11',
|
||||
};
|
||||
|
||||
interface KeybindingsState {
|
||||
bindings: Bindings;
|
||||
setBinding: (action: KeyAction, code: string | null) => void;
|
||||
resetToDefaults: () => void;
|
||||
}
|
||||
|
||||
export const useKeybindingsStore = create<KeybindingsState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
bindings: { ...DEFAULT_BINDINGS },
|
||||
setBinding: (action, code) =>
|
||||
set(s => ({ bindings: { ...s.bindings, [action]: code } })),
|
||||
resetToDefaults: () => set({ bindings: { ...DEFAULT_BINDINGS } }),
|
||||
}),
|
||||
{ name: 'psysonic_keybindings' }
|
||||
)
|
||||
);
|
||||
|
||||
/** Format an e.code value into a human-readable label. */
|
||||
export function formatKeyCode(code: string): string {
|
||||
if (code === 'Space') return 'Space';
|
||||
if (code === 'ArrowUp') return '↑';
|
||||
if (code === 'ArrowDown') return '↓';
|
||||
if (code === 'ArrowLeft') return '←';
|
||||
if (code === 'ArrowRight') return '→';
|
||||
if (code === 'Escape') return 'Esc';
|
||||
if (code === 'Enter') return 'Enter';
|
||||
if (code === 'Backspace') return '⌫';
|
||||
if (code === 'Tab') return 'Tab';
|
||||
if (code === 'Delete') return 'Del';
|
||||
if (code === 'Home') return 'Home';
|
||||
if (code === 'End') return 'End';
|
||||
if (code === 'PageUp') return 'PgUp';
|
||||
if (code === 'PageDown') return 'PgDn';
|
||||
if (/^F\d+$/.test(code)) return code;
|
||||
if (code.startsWith('Key')) return code.slice(3).toUpperCase();
|
||||
if (code.startsWith('Digit')) return code.slice(5);
|
||||
if (code === 'Minus') return '-';
|
||||
if (code === 'Equal') return '=';
|
||||
if (code === 'BracketLeft') return '[';
|
||||
if (code === 'BracketRight') return ']';
|
||||
if (code === 'Semicolon') return ';';
|
||||
if (code === 'Quote') return "'";
|
||||
if (code === 'Backslash') return '\\';
|
||||
if (code === 'Comma') return ',';
|
||||
if (code === 'Period') return '.';
|
||||
if (code === 'Slash') return '/';
|
||||
return code;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
type Theme = 'mocha' | 'macchiato' | 'frappe' | 'latte' | 'nord' | 'nord-snowstorm' | 'nord-frost' | 'nord-aurora' | 'psychowave' | 'classic-winamp' | 'poison' | 'nucleo' | 'gruvbox-dark-hard' | 'gruvbox-dark-medium' | 'gruvbox-dark-soft' | 'gruvbox-light-hard' | 'gruvbox-light-medium' | 'gruvbox-light-soft' | 'tokyo-night' | 'tokyo-night-storm' | 'tokyo-night-light';
|
||||
type Theme = 'mocha' | 'macchiato' | 'frappe' | 'latte' | 'nord' | 'nord-snowstorm' | 'nord-frost' | 'nord-aurora' | 'psychowave' | 'wnamp' | 'poison' | 'nucleo' | 'navy-jukebox' | 'cobalt-media' | 'onyx-cinema' | 'vintage-tube-radio' | 'neon-drift' | 'aero-glass' | 'luna-teal' | 'cupertino-light' | 'cupertino-dark' | 'gruvbox-dark-hard' | 'gruvbox-dark-medium' | 'gruvbox-dark-soft' | 'gruvbox-light-hard' | 'gruvbox-light-medium' | 'gruvbox-light-soft' | 'tokyo-night' | 'tokyo-night-storm' | 'tokyo-night-light';
|
||||
|
||||
interface ThemeState {
|
||||
theme: Theme;
|
||||
|
||||
Reference in New Issue
Block a user