mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 23:05:46 +00:00
feat(fullscreen): performance fixes + appearance settings
Adds no_compositing_mode Tauri command; frontend adds html.no-compositing on Linux to replace GPU-only CSS effects (backdrop-filter, filter, mask-image) with software-friendly equivalents. Settings → Appearance → Fullscreen Player: toggle for artist portrait visibility + 0–80% dimming slider. Fix: long words in lyric lines now wrap correctly.
This commit is contained in:
@@ -1489,6 +1489,16 @@ fn is_tiling_wm() -> bool {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tauri command: returns true when WEBKIT_DISABLE_COMPOSITING_MODE=1 is set.
|
||||||
|
/// The frontend uses this to apply a CSS class that swaps out GPU-only effects
|
||||||
|
/// (backdrop-filter, CSS filter, mask-image) for software-friendly equivalents.
|
||||||
|
#[tauri::command]
|
||||||
|
fn no_compositing_mode() -> bool {
|
||||||
|
std::env::var("WEBKIT_DISABLE_COMPOSITING_MODE")
|
||||||
|
.map(|v| v == "1")
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
/// Tauri command: lets the frontend know whether we're running under a tiling
|
/// Tauri command: lets the frontend know whether we're running under a tiling
|
||||||
/// WM so it can decide whether to render the custom TitleBar component.
|
/// WM so it can decide whether to render the custom TitleBar component.
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -1683,6 +1693,7 @@ pub fn run() {
|
|||||||
greet,
|
greet,
|
||||||
exit_app,
|
exit_app,
|
||||||
set_window_decorations,
|
set_window_decorations,
|
||||||
|
no_compositing_mode,
|
||||||
is_tiling_wm_cmd,
|
is_tiling_wm_cmd,
|
||||||
register_global_shortcut,
|
register_global_shortcut,
|
||||||
unregister_global_shortcut,
|
unregister_global_shortcut,
|
||||||
|
|||||||
@@ -88,6 +88,13 @@ function AppShell() {
|
|||||||
invoke<boolean>('is_tiling_wm_cmd').then(setIsTilingWm).catch(() => {});
|
invoke<boolean>('is_tiling_wm_cmd').then(setIsTilingWm).catch(() => {});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!IS_LINUX) return;
|
||||||
|
invoke<boolean>('no_compositing_mode').then(noComp => {
|
||||||
|
if (noComp) document.documentElement.classList.add('no-compositing');
|
||||||
|
}).catch(() => {});
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const win = getCurrentWindow();
|
const win = getCurrentWindow();
|
||||||
// Check initial state (e.g. app launched maximised / already fullscreen).
|
// Check initial state (e.g. app launched maximised / already fullscreen).
|
||||||
|
|||||||
@@ -355,7 +355,9 @@ export default function FullscreenPlayer({ onClose }: FullscreenPlayerProps) {
|
|||||||
}, [currentTrack?.artistId]);
|
}, [currentTrack?.artistId]);
|
||||||
|
|
||||||
const portraitUrl = artistBgUrl || resolvedCoverUrl;
|
const portraitUrl = artistBgUrl || resolvedCoverUrl;
|
||||||
const showFullscreenLyrics = useAuthStore(s => s.showFullscreenLyrics);
|
const showFullscreenLyrics = useAuthStore(s => s.showFullscreenLyrics);
|
||||||
|
const showFsArtistPortrait = useAuthStore(s => s.showFsArtistPortrait);
|
||||||
|
const fsPortraitDim = useAuthStore(s => s.fsPortraitDim);
|
||||||
|
|
||||||
// Pre-fetch next track's 300px cover into the IndexedDB cache.
|
// Pre-fetch next track's 300px cover into the IndexedDB cache.
|
||||||
// Selector returns only the coverArt id, so it only re-runs on actual changes.
|
// Selector returns only the coverArt id, so it only re-runs on actual changes.
|
||||||
@@ -419,7 +421,10 @@ export default function FullscreenPlayer({ onClose }: FullscreenPlayerProps) {
|
|||||||
aria-label={t('player.fullscreen')}
|
aria-label={t('player.fullscreen')}
|
||||||
data-idle={isIdle}
|
data-idle={isIdle}
|
||||||
onMouseMove={handleMouseMove}
|
onMouseMove={handleMouseMove}
|
||||||
style={dynamicAccent ? { '--dynamic-fs-accent': dynamicAccent } as React.CSSProperties : undefined}
|
style={{
|
||||||
|
...(dynamicAccent ? { '--dynamic-fs-accent': dynamicAccent } : {}),
|
||||||
|
'--fs-portrait-dim': String(fsPortraitDim / 100),
|
||||||
|
} as React.CSSProperties}
|
||||||
>
|
>
|
||||||
|
|
||||||
{/* Layer 0 — animated dark mesh gradient (real divs = will-change possible) */}
|
{/* Layer 0 — animated dark mesh gradient (real divs = will-change possible) */}
|
||||||
@@ -429,7 +434,7 @@ export default function FullscreenPlayer({ onClose }: FullscreenPlayerProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Layer 1 — artist portrait, right half, object-fit: contain */}
|
{/* Layer 1 — artist portrait, right half, object-fit: contain */}
|
||||||
<FsPortrait url={portraitUrl} />
|
{showFsArtistPortrait && <FsPortrait url={portraitUrl} />}
|
||||||
|
|
||||||
{/* Layer 2 — horizontal scrim: dark left → transparent right */}
|
{/* Layer 2 — horizontal scrim: dark left → transparent right */}
|
||||||
<div className="fs-scrim" aria-hidden="true" />
|
<div className="fs-scrim" aria-hidden="true" />
|
||||||
|
|||||||
@@ -614,6 +614,10 @@ export const deTranslation = {
|
|||||||
infiniteQueue: 'Endlose Warteschlange',
|
infiniteQueue: 'Endlose Warteschlange',
|
||||||
infiniteQueueDesc: 'Automatisch Zufallstitel anhängen wenn die Warteschlange leer wird',
|
infiniteQueueDesc: 'Automatisch Zufallstitel anhängen wenn die Warteschlange leer wird',
|
||||||
experimental: 'Experimentell',
|
experimental: 'Experimentell',
|
||||||
|
fsPlayerSection: 'Vollbild-Player',
|
||||||
|
fsShowArtistPortrait: 'Künstlerfoto anzeigen',
|
||||||
|
fsShowArtistPortraitDesc: 'Künstlerfoto (oder Albumcover) auf der rechten Seite des Vollbild-Players anzeigen.',
|
||||||
|
fsPortraitDim: 'Abdunkelung des Fotos',
|
||||||
seekbarStyle: 'Seekbar-Stil',
|
seekbarStyle: 'Seekbar-Stil',
|
||||||
seekbarStyleDesc: 'Aussehen der Wiedergabe-Seekbar auswählen',
|
seekbarStyleDesc: 'Aussehen der Wiedergabe-Seekbar auswählen',
|
||||||
seekbarWaveform: 'Wellenform',
|
seekbarWaveform: 'Wellenform',
|
||||||
|
|||||||
@@ -616,6 +616,10 @@ export const enTranslation = {
|
|||||||
infiniteQueue: 'Infinite Queue',
|
infiniteQueue: 'Infinite Queue',
|
||||||
infiniteQueueDesc: 'Automatically append random tracks when the queue runs out',
|
infiniteQueueDesc: 'Automatically append random tracks when the queue runs out',
|
||||||
experimental: 'Experimental',
|
experimental: 'Experimental',
|
||||||
|
fsPlayerSection: 'Fullscreen Player',
|
||||||
|
fsShowArtistPortrait: 'Show artist photo',
|
||||||
|
fsShowArtistPortraitDesc: 'Display the artist photo (or album art) on the right side of the fullscreen player.',
|
||||||
|
fsPortraitDim: 'Photo dimming',
|
||||||
seekbarStyle: 'Seekbar Style',
|
seekbarStyle: 'Seekbar Style',
|
||||||
seekbarStyleDesc: 'Choose the look of the player seek bar',
|
seekbarStyleDesc: 'Choose the look of the player seek bar',
|
||||||
seekbarWaveform: 'Waveform',
|
seekbarWaveform: 'Waveform',
|
||||||
|
|||||||
@@ -612,6 +612,10 @@ export const frTranslation = {
|
|||||||
infiniteQueue: 'File infinie',
|
infiniteQueue: 'File infinie',
|
||||||
infiniteQueueDesc: 'Ajouter automatiquement des morceaux aléatoires quand la file est épuisée',
|
infiniteQueueDesc: 'Ajouter automatiquement des morceaux aléatoires quand la file est épuisée',
|
||||||
experimental: 'Expérimental',
|
experimental: 'Expérimental',
|
||||||
|
fsPlayerSection: 'Lecteur plein écran',
|
||||||
|
fsShowArtistPortrait: "Afficher la photo de l'artiste",
|
||||||
|
fsShowArtistPortraitDesc: "Afficher la photo de l'artiste (ou la pochette) sur le côté droit du lecteur plein écran.",
|
||||||
|
fsPortraitDim: "Assombrissement de la photo",
|
||||||
seekbarStyle: 'Style de la barre de lecture',
|
seekbarStyle: 'Style de la barre de lecture',
|
||||||
seekbarStyleDesc: 'Choisir l\'apparence de la barre de progression',
|
seekbarStyleDesc: 'Choisir l\'apparence de la barre de progression',
|
||||||
seekbarWaveform: 'Forme d\'onde',
|
seekbarWaveform: 'Forme d\'onde',
|
||||||
|
|||||||
@@ -611,6 +611,10 @@ export const nbTranslation = {
|
|||||||
preloadEarly: 'Tidlig (etter 5 s avspilling)',
|
preloadEarly: 'Tidlig (etter 5 s avspilling)',
|
||||||
preloadCustom: 'Egendefinert',
|
preloadCustom: 'Egendefinert',
|
||||||
preloadCustomSeconds: 'Sekunder før slutt: {{n}}',
|
preloadCustomSeconds: 'Sekunder før slutt: {{n}}',
|
||||||
|
fsPlayerSection: 'Fullskjermspiller',
|
||||||
|
fsShowArtistPortrait: 'Vis artistbilde',
|
||||||
|
fsShowArtistPortraitDesc: 'Vis artistbilde (eller albumomslag) på høyre side av fullskjermspilleren.',
|
||||||
|
fsPortraitDim: 'Mørklegging av bilde',
|
||||||
seekbarStyle: 'Søkefelt-stil',
|
seekbarStyle: 'Søkefelt-stil',
|
||||||
seekbarStyleDesc: 'Velg utseendet på avspillingssøkefeltet',
|
seekbarStyleDesc: 'Velg utseendet på avspillingssøkefeltet',
|
||||||
seekbarWaveform: 'Bølgeform',
|
seekbarWaveform: 'Bølgeform',
|
||||||
|
|||||||
@@ -612,6 +612,10 @@ export const nlTranslation = {
|
|||||||
infiniteQueue: 'Oneindige wachtrij',
|
infiniteQueue: 'Oneindige wachtrij',
|
||||||
infiniteQueueDesc: 'Automatisch willekeurige nummers toevoegen als de wachtrij leeg raakt',
|
infiniteQueueDesc: 'Automatisch willekeurige nummers toevoegen als de wachtrij leeg raakt',
|
||||||
experimental: 'Experimenteel',
|
experimental: 'Experimenteel',
|
||||||
|
fsPlayerSection: 'Volledig scherm speler',
|
||||||
|
fsShowArtistPortrait: 'Artiestfoto tonen',
|
||||||
|
fsShowArtistPortraitDesc: 'Artiestfoto (of albumhoes) weergeven aan de rechterkant van de volledigschermspeler.',
|
||||||
|
fsPortraitDim: 'Verduistering foto',
|
||||||
seekbarStyle: 'Zoekbalkstijl',
|
seekbarStyle: 'Zoekbalkstijl',
|
||||||
seekbarStyleDesc: 'Kies het uiterlijk van de afspeelbalk',
|
seekbarStyleDesc: 'Kies het uiterlijk van de afspeelbalk',
|
||||||
seekbarWaveform: 'Golfvorm',
|
seekbarWaveform: 'Golfvorm',
|
||||||
|
|||||||
@@ -640,6 +640,10 @@ export const ruTranslation = {
|
|||||||
infiniteQueue: 'Бесконечная очередь',
|
infiniteQueue: 'Бесконечная очередь',
|
||||||
infiniteQueueDesc: 'Подмешивать случайные треки, когда очередь закончится',
|
infiniteQueueDesc: 'Подмешивать случайные треки, когда очередь закончится',
|
||||||
experimental: 'Экспериментально',
|
experimental: 'Экспериментально',
|
||||||
|
fsPlayerSection: 'Полноэкранный плеер',
|
||||||
|
fsShowArtistPortrait: 'Отображать фото артиста',
|
||||||
|
fsShowArtistPortraitDesc: 'Показывать фото артиста (или обложку альбома) в правой части полноэкранного плеера.',
|
||||||
|
fsPortraitDim: 'Затемнение фото',
|
||||||
seekbarStyle: 'Стиль прогресс-бара',
|
seekbarStyle: 'Стиль прогресс-бара',
|
||||||
seekbarStyleDesc: 'Выбор внешнего вида полосы воспроизведения',
|
seekbarStyleDesc: 'Выбор внешнего вида полосы воспроизведения',
|
||||||
seekbarWaveform: 'Форма волны',
|
seekbarWaveform: 'Форма волны',
|
||||||
|
|||||||
@@ -608,6 +608,10 @@ export const zhTranslation = {
|
|||||||
infiniteQueue: '无限队列',
|
infiniteQueue: '无限队列',
|
||||||
infiniteQueueDesc: '队列播完时自动追加随机曲目',
|
infiniteQueueDesc: '队列播完时自动追加随机曲目',
|
||||||
experimental: '实验性',
|
experimental: '实验性',
|
||||||
|
fsPlayerSection: '全屏播放器',
|
||||||
|
fsShowArtistPortrait: '显示艺术家照片',
|
||||||
|
fsShowArtistPortraitDesc: '在全屏播放器右侧显示艺术家照片(或专辑封面)。',
|
||||||
|
fsPortraitDim: '照片暗化',
|
||||||
seekbarStyle: '进度条样式',
|
seekbarStyle: '进度条样式',
|
||||||
seekbarStyleDesc: '选择播放进度条的外观',
|
seekbarStyleDesc: '选择播放进度条的外观',
|
||||||
seekbarWaveform: '波形',
|
seekbarWaveform: '波形',
|
||||||
|
|||||||
+37
-1
@@ -5,7 +5,7 @@ import { useNavigate, useLocation } from 'react-router-dom';
|
|||||||
import {
|
import {
|
||||||
Wifi, WifiOff, Globe, Music2, Sliders, LogOut, CheckCircle2, FolderOpen,
|
Wifi, WifiOff, Globe, Music2, Sliders, LogOut, CheckCircle2, FolderOpen,
|
||||||
Palette, Server, Plus, Trash2, Eye, EyeOff, Info, ExternalLink, Shuffle, X, Play, Type, Keyboard, ChevronDown,
|
Palette, Server, Plus, Trash2, Eye, EyeOff, Info, ExternalLink, Shuffle, X, Play, Type, Keyboard, ChevronDown,
|
||||||
GripVertical, PanelLeft, RotateCcw, LayoutGrid, AppWindow, HardDrive, Upload, Download, Waves, Star, Clock, ZoomIn, Sparkles, AlertTriangle
|
GripVertical, PanelLeft, RotateCcw, LayoutGrid, AppWindow, HardDrive, Upload, Download, Waves, Star, Clock, ZoomIn, Sparkles, AlertTriangle, Maximize2
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import i18n from '../i18n';
|
import i18n from '../i18n';
|
||||||
import { exportBackup, importBackup } from '../utils/backup';
|
import { exportBackup, importBackup } from '../utils/backup';
|
||||||
@@ -1450,6 +1450,42 @@ export default function Settings() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section className="settings-section">
|
||||||
|
<div className="settings-section-header">
|
||||||
|
<Maximize2 size={18} />
|
||||||
|
<h2>{t('settings.fsPlayerSection')}</h2>
|
||||||
|
</div>
|
||||||
|
<div className="settings-card">
|
||||||
|
<div className="settings-toggle-row">
|
||||||
|
<div>
|
||||||
|
<div style={{ fontWeight: 500 }}>{t('settings.fsShowArtistPortrait')}</div>
|
||||||
|
<div style={{ fontSize: 12, color: 'var(--text-muted)' }}>{t('settings.fsShowArtistPortraitDesc')}</div>
|
||||||
|
</div>
|
||||||
|
<label className="toggle-switch" aria-label={t('settings.fsShowArtistPortrait')}>
|
||||||
|
<input type="checkbox" checked={auth.showFsArtistPortrait} onChange={e => auth.setShowFsArtistPortrait(e.target.checked)} />
|
||||||
|
<span className="toggle-track" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{auth.showFsArtistPortrait && (
|
||||||
|
<div style={{ marginTop: '1rem', paddingTop: '1rem', borderTop: '1px solid var(--border)' }}>
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '0.5rem' }}>
|
||||||
|
<span style={{ fontSize: 13, color: 'var(--text-secondary)' }}>{t('settings.fsPortraitDim')}</span>
|
||||||
|
<span style={{ fontSize: 13, fontWeight: 600, color: 'var(--accent)', minWidth: 36, textAlign: 'right' }}>{auth.fsPortraitDim}%</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min={0}
|
||||||
|
max={80}
|
||||||
|
step={1}
|
||||||
|
value={auth.fsPortraitDim}
|
||||||
|
onChange={e => auth.setFsPortraitDim(parseInt(e.target.value, 10))}
|
||||||
|
className="ui-scale-slider"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section className="settings-section">
|
<section className="settings-section">
|
||||||
<div className="settings-section-header">
|
<div className="settings-section-header">
|
||||||
<Sliders size={18} />
|
<Sliders size={18} />
|
||||||
|
|||||||
@@ -67,6 +67,9 @@ interface AuthState {
|
|||||||
enableNeteaselyrics: boolean;
|
enableNeteaselyrics: boolean;
|
||||||
lyricsSources: LyricsSourceConfig[];
|
lyricsSources: LyricsSourceConfig[];
|
||||||
showFullscreenLyrics: boolean;
|
showFullscreenLyrics: boolean;
|
||||||
|
showFsArtistPortrait: boolean;
|
||||||
|
/** Portrait dimming 0–100 (percent), applied as CSS rgba alpha */
|
||||||
|
fsPortraitDim: number;
|
||||||
showChangelogOnUpdate: boolean;
|
showChangelogOnUpdate: boolean;
|
||||||
lastSeenChangelogVersion: string;
|
lastSeenChangelogVersion: string;
|
||||||
|
|
||||||
@@ -188,6 +191,8 @@ interface AuthState {
|
|||||||
setEnableNeteaselyrics: (v: boolean) => void;
|
setEnableNeteaselyrics: (v: boolean) => void;
|
||||||
setLyricsSources: (sources: LyricsSourceConfig[]) => void;
|
setLyricsSources: (sources: LyricsSourceConfig[]) => void;
|
||||||
setShowFullscreenLyrics: (v: boolean) => void;
|
setShowFullscreenLyrics: (v: boolean) => void;
|
||||||
|
setShowFsArtistPortrait: (v: boolean) => void;
|
||||||
|
setFsPortraitDim: (v: number) => void;
|
||||||
setShowChangelogOnUpdate: (v: boolean) => void;
|
setShowChangelogOnUpdate: (v: boolean) => void;
|
||||||
setLastSeenChangelogVersion: (v: string) => void;
|
setLastSeenChangelogVersion: (v: string) => void;
|
||||||
setSeekbarStyle: (v: SeekbarStyle) => void;
|
setSeekbarStyle: (v: SeekbarStyle) => void;
|
||||||
@@ -278,6 +283,8 @@ export const useAuthStore = create<AuthState>()(
|
|||||||
enableNeteaselyrics: false,
|
enableNeteaselyrics: false,
|
||||||
lyricsSources: DEFAULT_LYRICS_SOURCES,
|
lyricsSources: DEFAULT_LYRICS_SOURCES,
|
||||||
showFullscreenLyrics: true,
|
showFullscreenLyrics: true,
|
||||||
|
showFsArtistPortrait: true,
|
||||||
|
fsPortraitDim: 28,
|
||||||
showChangelogOnUpdate: true,
|
showChangelogOnUpdate: true,
|
||||||
lastSeenChangelogVersion: '',
|
lastSeenChangelogVersion: '',
|
||||||
seekbarStyle: 'waveform',
|
seekbarStyle: 'waveform',
|
||||||
@@ -396,6 +403,8 @@ export const useAuthStore = create<AuthState>()(
|
|||||||
setEnableNeteaselyrics: (v: boolean) => set({ enableNeteaselyrics: v }),
|
setEnableNeteaselyrics: (v: boolean) => set({ enableNeteaselyrics: v }),
|
||||||
setLyricsSources: (sources) => set({ lyricsSources: sources }),
|
setLyricsSources: (sources) => set({ lyricsSources: sources }),
|
||||||
setShowFullscreenLyrics: (v: boolean) => set({ showFullscreenLyrics: v }),
|
setShowFullscreenLyrics: (v: boolean) => set({ showFullscreenLyrics: v }),
|
||||||
|
setShowFsArtistPortrait: (v: boolean) => set({ showFsArtistPortrait: v }),
|
||||||
|
setFsPortraitDim: (v: number) => set({ fsPortraitDim: v }),
|
||||||
setShowChangelogOnUpdate: (v) => set({ showChangelogOnUpdate: v }),
|
setShowChangelogOnUpdate: (v) => set({ showChangelogOnUpdate: v }),
|
||||||
setLastSeenChangelogVersion: (v) => set({ lastSeenChangelogVersion: v }),
|
setLastSeenChangelogVersion: (v) => set({ lastSeenChangelogVersion: v }),
|
||||||
|
|
||||||
|
|||||||
@@ -3119,6 +3119,17 @@
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Subtle darkening overlay — plain div paint, no GPU cost.
|
||||||
|
Alpha driven by --fs-portrait-dim (set via inline style on .fs-player). */
|
||||||
|
.fs-portrait-wrap::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 1;
|
||||||
|
pointer-events: none;
|
||||||
|
background: rgba(0, 0, 0, var(--fs-portrait-dim, 0.28));
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Horizontal scrim — dark left edge for text legibility ── */
|
/* ── Horizontal scrim — dark left edge for text legibility ── */
|
||||||
.fs-scrim {
|
.fs-scrim {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -3440,6 +3451,8 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
word-break: break-word;
|
||||||
font-size: 2vh;
|
font-size: 2vh;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
color: rgba(255, 255, 255, 0.22);
|
color: rgba(255, 255, 255, 0.22);
|
||||||
@@ -3469,6 +3482,59 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ── no-compositing overrides ─────────────────────────────────────────────────
|
||||||
|
Applied only when WEBKIT_DISABLE_COMPOSITING_MODE=1 is set (Arch Linux etc).
|
||||||
|
Replaces GPU-only effects (backdrop-filter, CSS filter, mask-image) with
|
||||||
|
software-friendly equivalents so the fullscreen player stays responsive. */
|
||||||
|
|
||||||
|
/* fs-player: remove will-change — without GPU compositor it only wastes RAM */
|
||||||
|
html.no-compositing .fs-player {
|
||||||
|
will-change: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Close button: replace frosted-glass blur with flat semi-opaque background */
|
||||||
|
html.no-compositing .fs-close {
|
||||||
|
backdrop-filter: none;
|
||||||
|
background: rgba(0, 0, 0, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Portrait: remove CSS filter (drop-shadow) and will-change */
|
||||||
|
html.no-compositing .fs-portrait {
|
||||||
|
filter: none;
|
||||||
|
will-change: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Portrait wrap: replace mask-image fade with a pseudo-element overlay gradient.
|
||||||
|
pointer-events: none on wrap means the overlay never blocks clicks. */
|
||||||
|
html.no-compositing .fs-portrait-wrap {
|
||||||
|
-webkit-mask-image: none;
|
||||||
|
mask-image: none;
|
||||||
|
}
|
||||||
|
html.no-compositing .fs-portrait-wrap::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 1;
|
||||||
|
pointer-events: none;
|
||||||
|
background: linear-gradient(
|
||||||
|
to right,
|
||||||
|
#06060e 0%,
|
||||||
|
rgba(6, 6, 14, 0.4) 6%,
|
||||||
|
transparent 16%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Lyrics overlay: just remove the fade — no replacement needed */
|
||||||
|
html.no-compositing .fs-lyrics-overlay {
|
||||||
|
-webkit-mask-image: none;
|
||||||
|
mask-image: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Lyrics rail: remove will-change */
|
||||||
|
html.no-compositing .fs-lyrics-rail {
|
||||||
|
will-change: auto;
|
||||||
|
}
|
||||||
|
|
||||||
/* Chat */
|
/* Chat */
|
||||||
.chat-popup {
|
.chat-popup {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
Reference in New Issue
Block a user