feat: v1.13.0 — SVG Logo, Marquee, Player UX, Global Shortcuts fix

### Added
- SVG logo with theme-adaptive gradient in sidebar (full wordmark + P-icon for collapsed state)
- Player bar: song title/artist marquee scroll on overflow
- Player bar: live volume percentage tooltip on slider hover

### Changed
- Sidebar collapse button moved to right-edge hover tab
- Player bar: fixed 320px track info width, increased waveform margins
- Settings: Server tab opens by default
- Crossfade: experimental badge removed (stable)
- Help page: Lyrics/Keybindings/Font entries added, theme count corrected

### Fixed
- Global shortcuts double-fire (Rust-side ShortcutMap idempotency fix)
- W98 theme: comprehensive contrast fixes for navy hover backgrounds
- Help page: removed orphaned translation key under Playback

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Psychotoxical
2026-03-22 20:24:14 +01:00
parent d927ef2082
commit 5516d95b52
20 changed files with 781 additions and 97 deletions
+49
View File
@@ -0,0 +1,49 @@
import React, { useRef, useState, useEffect, useCallback } from 'react';
interface Props {
text: string;
className?: string;
style?: React.CSSProperties;
onClick?: () => void;
}
export default function MarqueeText({ text, className, style, onClick }: Props) {
const containerRef = useRef<HTMLDivElement>(null);
const textRef = useRef<HTMLSpanElement>(null);
const [scrollAmount, setScrollAmount] = useState(0);
const measure = useCallback(() => {
const container = containerRef.current;
const text = textRef.current;
if (!container || !text) return;
text.style.display = 'inline-block';
const textWidth = text.getBoundingClientRect().width;
text.style.display = '';
const overflow = textWidth - container.clientWidth;
setScrollAmount(overflow > 4 ? Math.ceil(overflow) : 0);
}, []);
useEffect(() => {
measure();
const ro = new ResizeObserver(measure);
if (containerRef.current) ro.observe(containerRef.current);
return () => ro.disconnect();
}, [text, measure]);
return (
<div
ref={containerRef}
className={`marquee-wrap${className ? ` ${className}` : ''}`}
style={style}
onClick={onClick}
>
<span
ref={textRef}
className={scrollAmount > 0 ? 'marquee-scroll' : ''}
style={scrollAmount > 0 ? { '--marquee-amount': `-${scrollAmount}px` } as React.CSSProperties : {}}
>
{text}
</span>
</div>
);
}