feat(themes): warn about animated themes on high-CPU setups (#1020)

* feat(themes): warn about animated themes on high-CPU setups

Show a warning icon + tooltip on animated themes (those defining
@keyframes) in the store and in Your Themes, on Linux setups where
animation is costly — the Nvidia WebKit quirk is active or compositing
is forced off. The Nvidia detection already runs once at startup; its
result is recorded (theme_animation.rs) and read via a new
theme_animation_risk command — no GPU re-probe. Display-only; never
shown off Linux. The animated flag comes from the registry (store) or
the theme CSS (Your Themes).

* docs(themes): note the animated-theme warning PR in the Theme Store entry
This commit is contained in:
Psychotoxical
2026-06-07 20:38:36 +02:00
committed by GitHub
parent daa6fbbfd7
commit 88f7a7bc90
20 changed files with 144 additions and 7 deletions
+1 -1
View File
@@ -56,7 +56,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Themes — community Theme Store ### Themes — community Theme Store
**By [@Psychotoxical](https://github.com/Psychotoxical), PR [#1009](https://github.com/Psychotoxical/psysonic/pull/1009), [#1011](https://github.com/Psychotoxical/psysonic/pull/1011), [#1012](https://github.com/Psychotoxical/psysonic/pull/1012), [#1013](https://github.com/Psychotoxical/psysonic/pull/1013), [#1014](https://github.com/Psychotoxical/psysonic/pull/1014), [#1015](https://github.com/Psychotoxical/psysonic/pull/1015), [#1016](https://github.com/Psychotoxical/psysonic/pull/1016), [#1018](https://github.com/Psychotoxical/psysonic/pull/1018)** **By [@Psychotoxical](https://github.com/Psychotoxical), PR [#1009](https://github.com/Psychotoxical/psysonic/pull/1009), [#1011](https://github.com/Psychotoxical/psysonic/pull/1011), [#1012](https://github.com/Psychotoxical/psysonic/pull/1012), [#1013](https://github.com/Psychotoxical/psysonic/pull/1013), [#1014](https://github.com/Psychotoxical/psysonic/pull/1014), [#1015](https://github.com/Psychotoxical/psysonic/pull/1015), [#1016](https://github.com/Psychotoxical/psysonic/pull/1016), [#1018](https://github.com/Psychotoxical/psysonic/pull/1018), [#1020](https://github.com/Psychotoxical/psysonic/pull/1020)**
* New **Settings → Themes** tab: pick a theme, set the day/night scheduler, and browse a built-in **Theme Store** to install, update and uninstall community themes — with search, a dark/light filter, and full-size thumbnail previews. * New **Settings → Themes** tab: pick a theme, set the day/night scheduler, and browse a built-in **Theme Store** to install, update and uninstall community themes — with search, a dark/light filter, and full-size thumbnail previews.
* The app now bundles six core themes (Catppuccin Mocha & Latte, Kanagawa Wave, Stark HUD, and the colour-blind-safe Vision Dark / Vision Navy); every other palette installs on demand from the [psysonic-themes](https://github.com/Psysonic/psysonic-themes) repo. Installed themes are saved locally and apply instantly at startup, even offline. * The app now bundles six core themes (Catppuccin Mocha & Latte, Kanagawa Wave, Stark HUD, and the colour-blind-safe Vision Dark / Vision Navy); every other palette installs on demand from the [psysonic-themes](https://github.com/Psysonic/psysonic-themes) repo. Installed themes are saved locally and apply instantly at startup, even offline.
+2
View File
@@ -6,6 +6,7 @@ mod cover_cache;
mod library_analysis_backfill; mod library_analysis_backfill;
mod lib_commands; mod lib_commands;
mod theme_import; mod theme_import;
pub mod theme_animation;
pub use psysonic_integration::discord; pub use psysonic_integration::discord;
@@ -653,6 +654,7 @@ pub fn run() {
performance_cpu_snapshot, performance_cpu_snapshot,
set_subsonic_wire_user_agent, set_subsonic_wire_user_agent,
no_compositing_mode, no_compositing_mode,
theme_animation_risk,
linux_xdg_session_type, linux_xdg_session_type,
is_tiling_wm_cmd, is_tiling_wm_cmd,
open_mini_player, open_mini_player,
@@ -23,6 +23,7 @@ pub(crate) use perf::performance_cpu_snapshot;
pub(crate) use platform::{ pub(crate) use platform::{
linux_wayland_gpu_font_tuning_active, linux_wayland_text_render_settings_available, linux_wayland_gpu_font_tuning_active, linux_wayland_text_render_settings_available,
set_linux_wayland_text_render_profile, set_linux_webkit_smooth_scrolling, set_window_decorations, set_linux_wayland_text_render_profile, set_linux_webkit_smooth_scrolling, set_window_decorations,
theme_animation_risk,
}; };
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
pub(crate) use platform::{ pub(crate) use platform::{
@@ -137,6 +137,29 @@ pub(crate) fn linux_webkit_apply_wayland_gpu_font_tuning(win: &tauri::WebviewWin
} }
/// Toggle native window decorations at runtime (Linux custom title bar opt-out). /// Toggle native window decorations at runtime (Linux custom title bar opt-out).
/// Tauri command: true when theme animations may be costly on this setup —
/// Linux with the Nvidia WebKit quirk active (recorded once at startup) or
/// compositing forced off. The frontend warns on animated themes when true.
/// Always false off Linux.
#[tauri::command]
pub(crate) fn theme_animation_risk() -> bool {
#[cfg(target_os = "linux")]
{
// Compositing forced off → GPU-accelerated effects/animation are costly.
if std::env::var("WEBKIT_DISABLE_COMPOSITING_MODE")
.map(|v| v == "1")
.unwrap_or(false)
{
return true;
}
crate::theme_animation::nvidia_quirk_active()
}
#[cfg(not(target_os = "linux"))]
{
false
}
}
#[tauri::command] #[tauri::command]
pub(crate) fn set_window_decorations(enabled: bool, app_handle: tauri::AppHandle) { pub(crate) fn set_window_decorations(enabled: bool, app_handle: tauri::AppHandle) {
if let Some(win) = app_handle.get_webview_window("main") { if let Some(win) = app_handle.get_webview_window("main") {
+6 -1
View File
@@ -17,11 +17,16 @@ fn apply_linux_webkit_nvidia_quirk() {
// may still be `XDG_SESSION_TYPE=wayland`. The quirk maps that to `__NV_DISABLE_EXPLICIT_SYNC`, // may still be `XDG_SESSION_TYPE=wayland`. The quirk maps that to `__NV_DISABLE_EXPLICIT_SYNC`,
// which mismatches a real X11 EGL stack and can leave the webview gray — mirror the native-X11 // which mismatches a real X11 EGL stack and can leave the webview gray — mirror the native-X11
// branch (`WEBKIT_DISABLE_DMABUF_RENDERER` only) whenever GDK is pinned to x11 first in the list. // branch (`WEBKIT_DISABLE_DMABUF_RENDERER` only) whenever GDK is pinned to x11 first in the list.
// Detect once and record it for the UI's animated-theme CPU-load warning,
// so the theme_animation_risk command never has to re-probe the GPU.
let kind = needs_workaround();
psysonic_lib::theme_animation::set_nvidia_quirk_active(!matches!(kind, WorkaroundKind::None));
let forced_x11_gdk = std::env::var("GDK_BACKEND").ok().is_some_and(|s| { let forced_x11_gdk = std::env::var("GDK_BACKEND").ok().is_some_and(|s| {
matches!(s.split(',').next().map(str::trim), Some("x11")) matches!(s.split(',').next().map(str::trim), Some("x11"))
}); });
if forced_x11_gdk { if forced_x11_gdk {
match needs_workaround() { match kind {
WorkaroundKind::None => {} WorkaroundKind::None => {}
WorkaroundKind::DisableWebkitDmabufRenderer | WorkaroundKind::DisableNvExplicitSync => { WorkaroundKind::DisableWebkitDmabufRenderer | WorkaroundKind::DisableNvExplicitSync => {
set_webkit_disable_dmabuf_renderer(); set_webkit_disable_dmabuf_renderer();
+22
View File
@@ -0,0 +1,22 @@
//! Startup-recorded display hint for the theme system.
//!
//! The Nvidia/WebKit GPU quirk is detected once at process start (in `main()`,
//! before the webview/GTK init). We record whether it was needed so the UI can
//! warn that animated themes may raise CPU load on this setup — without
//! re-probing the GPU later. Read via the `theme_animation_risk` command
//! (`lib_commands::app_api::platform`).
use std::sync::OnceLock;
static NVIDIA_QUIRK_ACTIVE: OnceLock<bool> = OnceLock::new();
/// Record the startup Nvidia-WebKit-quirk detection. Called once from `main()`.
pub fn set_nvidia_quirk_active(active: bool) {
let _ = NVIDIA_QUIRK_ACTIVE.set(active);
}
/// Whether the Nvidia WebKit quirk was needed at startup. False when unrecorded
/// (non-Linux, or GPU acceleration opted in via `PSYSONIC_WEBKIT_GPU_ACCEL`).
pub(crate) fn nvidia_quirk_active() -> bool {
NVIDIA_QUIRK_ACTIVE.get().copied().unwrap_or(false)
}
+17 -3
View File
@@ -1,8 +1,9 @@
import { Check, X } from 'lucide-react'; import { AlertTriangle, Check, X } from 'lucide-react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useThemeStore } from '../../store/themeStore'; import { useThemeStore } from '../../store/themeStore';
import { useInstalledThemesStore } from '../../store/installedThemesStore'; import { useInstalledThemesStore } from '../../store/installedThemesStore';
import { uninstallTheme } from '../../utils/themes/uninstallTheme'; import { uninstallTheme } from '../../utils/themes/uninstallTheme';
import { useThemeAnimationRisk } from '../../hooks/useThemeAnimationRisk';
import { FIXED_THEMES } from './fixedThemes'; import { FIXED_THEMES } from './fixedThemes';
/** Pull a 3-band swatch (bg / card / accent) out of an installed theme's CSS. */ /** Pull a 3-band swatch (bg / card / accent) out of an installed theme's CSS. */
@@ -26,6 +27,7 @@ interface Card {
accent: string; accent: string;
fixed: boolean; fixed: boolean;
accessibility: boolean; accessibility: boolean;
animated: boolean;
} }
/** /**
@@ -39,12 +41,13 @@ export function InstalledThemes() {
const active = useThemeStore(s => s.theme); const active = useThemeStore(s => s.theme);
const setTheme = useThemeStore(s => s.setTheme); const setTheme = useThemeStore(s => s.setTheme);
const installed = useInstalledThemesStore(s => s.themes); const installed = useInstalledThemesStore(s => s.themes);
const animRisk = useThemeAnimationRisk();
const cards: Card[] = [ const cards: Card[] = [
...FIXED_THEMES.map(f => ({ id: f.id, label: f.label, bg: f.bg, card: f.card, accent: f.accent, fixed: true, accessibility: !!f.accessibility })), ...FIXED_THEMES.map(f => ({ id: f.id, label: f.label, bg: f.bg, card: f.card, accent: f.accent, fixed: true, accessibility: !!f.accessibility, animated: false })),
...installed.map(it => { ...installed.map(it => {
const s = swatch(it.css); const s = swatch(it.css);
return { id: it.id, label: it.name, bg: s.bg, card: s.card, accent: s.accent, fixed: false, accessibility: (it.tags || []).includes('accessibility') }; return { id: it.id, label: it.name, bg: s.bg, card: s.card, accent: s.accent, fixed: false, accessibility: (it.tags || []).includes('accessibility'), animated: /@(?:-[a-z]+-)?keyframes\b/i.test(it.css) };
}), }),
]; ];
@@ -101,6 +104,17 @@ export function InstalledThemes() {
CVD-safe CVD-safe
</span> </span>
)} )}
{animRisk && c.animated && (
<span
role="img"
aria-label={t('settings.themeAnimationWarning')}
data-tooltip={t('settings.themeAnimationWarning')}
data-tooltip-pos="top"
style={{ display: 'inline-flex', color: 'var(--warning)', marginTop: 2 }}
>
<AlertTriangle size={12} />
</span>
)}
</button> </button>
{!c.fixed && ( {!c.fixed && (
<button <button
+14 -1
View File
@@ -1,8 +1,9 @@
import { useEffect, useMemo, useRef, useState } from 'react'; import { useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Check, ChevronLeft, ChevronRight, Download, RefreshCw, Trash2, WifiOff } from 'lucide-react'; import { AlertTriangle, Check, ChevronLeft, ChevronRight, Download, RefreshCw, Trash2, WifiOff } from 'lucide-react';
import { open as openUrl } from '@tauri-apps/plugin-shell'; import { open as openUrl } from '@tauri-apps/plugin-shell';
import CoverLightbox from '../CoverLightbox'; import CoverLightbox from '../CoverLightbox';
import { useThemeAnimationRisk } from '../../hooks/useThemeAnimationRisk';
import { useThemeStore } from '../../store/themeStore'; import { useThemeStore } from '../../store/themeStore';
import { useInstalledThemesStore, type InstalledTheme } from '../../store/installedThemesStore'; import { useInstalledThemesStore, type InstalledTheme } from '../../store/installedThemesStore';
import { import {
@@ -33,6 +34,7 @@ export function ThemeStoreSection() {
const setTheme = useThemeStore(s => s.setTheme); const setTheme = useThemeStore(s => s.setTheme);
const installed = useInstalledThemesStore(s => s.themes); const installed = useInstalledThemesStore(s => s.themes);
const install = useInstalledThemesStore(s => s.install); const install = useInstalledThemesStore(s => s.install);
const animRisk = useThemeAnimationRisk();
const [themes, setThemes] = useState<RegistryTheme[] | null>(null); const [themes, setThemes] = useState<RegistryTheme[] | null>(null);
const [generatedAt, setGeneratedAt] = useState(''); const [generatedAt, setGeneratedAt] = useState('');
@@ -293,6 +295,17 @@ export function ThemeStoreSection() {
<Check size={12} /> {t('settings.themeStoreActive')} <Check size={12} /> {t('settings.themeStoreActive')}
</span> </span>
)} )}
{animRisk && th.animated && (
<span
role="img"
aria-label={t('settings.themeAnimationWarning')}
data-tooltip={t('settings.themeAnimationWarning')}
data-tooltip-pos="top"
style={{ display: 'inline-flex', color: 'var(--warning)' }}
>
<AlertTriangle size={14} />
</span>
)}
</div> </div>
<div style={{ fontSize: 12, color: 'var(--text-muted)' }}> <div style={{ fontSize: 12, color: 'var(--text-muted)' }}>
{t('settings.themeStoreByAuthor', { author: th.author })} {t('settings.themeStoreByAuthor', { author: th.author })}
+1 -1
View File
@@ -357,7 +357,7 @@ const CONTRIBUTOR_ENTRIES = [
'Performance Probe: Monitor/Toggles redesign, live CPU/RSS/thread metrics, overlay pins and sparklines, macOS snapshots (PR #890)', 'Performance Probe: Monitor/Toggles redesign, live CPU/RSS/thread metrics, overlay pins and sparklines, macOS snapshots (PR #890)',
'Performance Probe: opt-in thread-group CPU poll toggle and includeThreadGroups IPC fix (PR #891)', 'Performance Probe: opt-in thread-group CPU poll toggle and includeThreadGroups IPC fix (PR #891)',
'Queue: switchable display mode — Queue (upcoming only) vs Playlist (full list), with header toggle and settings entry (PR #922)', 'Queue: switchable display mode — Queue (upcoming only) vs Playlist (full list), with header toggle and settings entry (PR #922)',
'Community Theme Store: semantic-token refactor, dedicated Themes tab with day/night scheduler, install/update/uninstall, local .zip import, and free-form community themes (safety floor + state-reactive styling); 80+ palettes moved to an on-demand CDN repo (PR #1009, #1011, #1012, #1013, #1014, #1015, #1016, #1018)', 'Community Theme Store: semantic-token refactor, dedicated Themes tab with day/night scheduler, install/update/uninstall, local .zip import, and free-form community themes (safety floor + state-reactive styling); 80+ palettes moved to an on-demand CDN repo (PR #1009, #1011, #1012, #1013, #1014, #1015, #1016, #1018, #1020)',
], ],
}, },
{ {
+46
View File
@@ -0,0 +1,46 @@
import { useEffect, useState } from 'react';
import { invoke } from '@tauri-apps/api/core';
import { IS_LINUX } from '../utils/platform';
/**
* Whether animated themes may raise CPU load on this setup Linux with the
* Nvidia WebKit quirk active (recorded at startup) or compositing forced off.
* The store / theme cards show a warning on animated themes when this is true.
*
* Fetched once and cached for the process (read-once, per the Tauri-boundary
* rule). Always false off Linux, so the `invoke` is skipped there.
*/
let cached: boolean | null = null;
export function useThemeAnimationRisk(): boolean {
const [risk, setRisk] = useState(cached ?? false);
useEffect(() => {
if (cached !== null) {
setRisk(cached);
return;
}
if (!IS_LINUX) {
cached = false;
return;
}
let alive = true;
const p = invoke<boolean>('theme_animation_risk');
// Guard the mocked-in-tests case where invoke isn't a real promise.
if (p && typeof (p as Promise<boolean>).then === 'function') {
(p as Promise<boolean>)
.then((v) => {
cached = !!v;
if (alive) setRisk(cached);
})
.catch(() => {
cached = false;
});
}
return () => {
alive = false;
};
}, []);
return risk;
}
+1
View File
@@ -324,6 +324,7 @@ export const settings = {
tabThemes: 'Themes', tabThemes: 'Themes',
themesYourThemesTitle: 'Deine Themes', themesYourThemesTitle: 'Deine Themes',
themesCvdTooltip: 'Farbfehlsichtigkeits-sicher Deuteranopie, Protanopie, Tritanopie', themesCvdTooltip: 'Farbfehlsichtigkeits-sicher Deuteranopie, Protanopie, Tritanopie',
themeAnimationWarning: 'Dieses Theme nutzt Animationen, die auf deinem Setup (Nvidia/Linux oder Compositing aus) die CPU-Last erhöhen können.',
themeStoreTitle: 'Theme-Store', themeStoreTitle: 'Theme-Store',
themeStoreSubmitText: 'Eigenes Theme gebaut? Teil es mit der Community — mehr Infos im Themes-Repository.', themeStoreSubmitText: 'Eigenes Theme gebaut? Teil es mit der Community — mehr Infos im Themes-Repository.',
themeStoreSubmitLink: 'Themes-Repository öffnen', themeStoreSubmitLink: 'Themes-Repository öffnen',
+1
View File
@@ -391,6 +391,7 @@ export const settings = {
tabThemes: 'Themes', tabThemes: 'Themes',
themesYourThemesTitle: 'Your Themes', themesYourThemesTitle: 'Your Themes',
themesCvdTooltip: 'Colour-blind safe — deuteranopia, protanopia, tritanopia', themesCvdTooltip: 'Colour-blind safe — deuteranopia, protanopia, tritanopia',
themeAnimationWarning: 'This theme uses animations that may raise CPU usage on your setup (Nvidia/Linux or compositing off).',
themeStoreTitle: 'Theme Store', themeStoreTitle: 'Theme Store',
themeStoreSubmitText: 'Made your own theme? Share it with the community — more info in the themes repository.', themeStoreSubmitText: 'Made your own theme? Share it with the community — more info in the themes repository.',
themeStoreSubmitLink: 'Open the themes repository', themeStoreSubmitLink: 'Open the themes repository',
+1
View File
@@ -322,6 +322,7 @@ export const settings = {
tabThemes: 'Temas', tabThemes: 'Temas',
themesYourThemesTitle: 'Tus temas', themesYourThemesTitle: 'Tus temas',
themesCvdTooltip: 'Apto para daltonismo: deuteranopía, protanopía, tritanopía', themesCvdTooltip: 'Apto para daltonismo: deuteranopía, protanopía, tritanopía',
themeAnimationWarning: 'Este tema usa animaciones que pueden aumentar el uso de CPU en tu sistema (Nvidia/Linux o compositing desactivado).',
themeStoreTitle: 'Tienda de temas', themeStoreTitle: 'Tienda de temas',
themeStoreSubmitText: '¿Has creado tu propio tema? Compártelo con la comunidad — más información en el repositorio de temas.', themeStoreSubmitText: '¿Has creado tu propio tema? Compártelo con la comunidad — más información en el repositorio de temas.',
themeStoreSubmitLink: 'Abrir el repositorio de temas', themeStoreSubmitLink: 'Abrir el repositorio de temas',
+1
View File
@@ -320,6 +320,7 @@ export const settings = {
tabThemes: 'Thèmes', tabThemes: 'Thèmes',
themesYourThemesTitle: 'Vos thèmes', themesYourThemesTitle: 'Vos thèmes',
themesCvdTooltip: 'Adapté au daltonisme — deutéranopie, protanopie, tritanopie', themesCvdTooltip: 'Adapté au daltonisme — deutéranopie, protanopie, tritanopie',
themeAnimationWarning: 'Ce thème utilise des animations qui peuvent augmenter lutilisation du processeur sur votre configuration (Nvidia/Linux ou compositing désactivé).',
themeStoreTitle: 'Boutique de thèmes', themeStoreTitle: 'Boutique de thèmes',
themeStoreSubmitText: "Vous avez créé votre propre thème ? Partagez-le avec la communauté — plus d'infos dans le dépôt de thèmes.", themeStoreSubmitText: "Vous avez créé votre propre thème ? Partagez-le avec la communauté — plus d'infos dans le dépôt de thèmes.",
themeStoreSubmitLink: 'Ouvrir le dépôt de thèmes', themeStoreSubmitLink: 'Ouvrir le dépôt de thèmes',
+1
View File
@@ -323,6 +323,7 @@ export const settings = {
tabThemes: 'Temaer', tabThemes: 'Temaer',
themesYourThemesTitle: 'Dine temaer', themesYourThemesTitle: 'Dine temaer',
themesCvdTooltip: 'Fargeblind-sikker deuteranopi, protanopi, tritanopi', themesCvdTooltip: 'Fargeblind-sikker deuteranopi, protanopi, tritanopi',
themeAnimationWarning: 'Dette temaet bruker animasjoner som kan øke CPU-bruken på oppsettet ditt (Nvidia/Linux eller compositing av).',
themeStoreTitle: 'Temabutikk', themeStoreTitle: 'Temabutikk',
themeStoreSubmitText: 'Laget ditt eget tema? Del det med fellesskapet — mer info i tema-repoet.', themeStoreSubmitText: 'Laget ditt eget tema? Del det med fellesskapet — mer info i tema-repoet.',
themeStoreSubmitLink: 'Åpne tema-repoet', themeStoreSubmitLink: 'Åpne tema-repoet',
+1
View File
@@ -320,6 +320,7 @@ export const settings = {
tabThemes: "Thema's", tabThemes: "Thema's",
themesYourThemesTitle: "Jouw thema's", themesYourThemesTitle: "Jouw thema's",
themesCvdTooltip: 'Kleurenblind-veilig deuteranopie, protanopie, tritanopie', themesCvdTooltip: 'Kleurenblind-veilig deuteranopie, protanopie, tritanopie',
themeAnimationWarning: 'Dit thema gebruikt animaties die het CPU-gebruik op jouw systeem kunnen verhogen (Nvidia/Linux of compositing uit).',
themeStoreTitle: 'Themawinkel', themeStoreTitle: 'Themawinkel',
themeStoreSubmitText: 'Eigen thema gemaakt? Deel het met de community — meer info in de themarepository.', themeStoreSubmitText: 'Eigen thema gemaakt? Deel het met de community — meer info in de themarepository.',
themeStoreSubmitLink: 'Themarepository openen', themeStoreSubmitLink: 'Themarepository openen',
+1
View File
@@ -326,6 +326,7 @@ export const settings = {
tabThemes: 'Teme', tabThemes: 'Teme',
themesYourThemesTitle: 'Temele tale', themesYourThemesTitle: 'Temele tale',
themesCvdTooltip: 'Sigur pentru daltonism deuteranopie, protanopie, tritanopie', themesCvdTooltip: 'Sigur pentru daltonism deuteranopie, protanopie, tritanopie',
themeAnimationWarning: 'Această temă folosește animații care pot crește utilizarea procesorului pe configurația ta (Nvidia/Linux sau compositing dezactivat).',
themeStoreTitle: 'Magazin de teme', themeStoreTitle: 'Magazin de teme',
themeStoreSubmitText: 'Ți-ai creat propria temă? Împărtășește-o cu comunitatea — mai multe informații în depozitul de teme.', themeStoreSubmitText: 'Ți-ai creat propria temă? Împărtășește-o cu comunitatea — mai multe informații în depozitul de teme.',
themeStoreSubmitLink: 'Deschide depozitul de teme', themeStoreSubmitLink: 'Deschide depozitul de teme',
+1
View File
@@ -402,6 +402,7 @@ export const settings = {
tabThemes: 'Темы', tabThemes: 'Темы',
themesYourThemesTitle: 'Ваши темы', themesYourThemesTitle: 'Ваши темы',
themesCvdTooltip: 'Безопасно при дальтонизме — дейтеранопия, протанопия, тританопия', themesCvdTooltip: 'Безопасно при дальтонизме — дейтеранопия, протанопия, тританопия',
themeAnimationWarning: 'Эта тема использует анимации, которые могут повысить нагрузку на процессор в вашей конфигурации (Nvidia/Linux или композитинг выключен).',
themeStoreTitle: 'Магазин тем', themeStoreTitle: 'Магазин тем',
themeStoreSubmitText: 'Создали свою тему? Поделитесь с сообществом — подробности в репозитории тем.', themeStoreSubmitText: 'Создали свою тему? Поделитесь с сообществом — подробности в репозитории тем.',
themeStoreSubmitLink: 'Открыть репозиторий тем', themeStoreSubmitLink: 'Открыть репозиторий тем',
+1
View File
@@ -319,6 +319,7 @@ export const settings = {
tabThemes: '主题', tabThemes: '主题',
themesYourThemesTitle: '你的主题', themesYourThemesTitle: '你的主题',
themesCvdTooltip: '色觉障碍友好 — 绿色盲、红色盲、蓝色盲', themesCvdTooltip: '色觉障碍友好 — 绿色盲、红色盲、蓝色盲',
themeAnimationWarning: '此主题使用动画,在你的环境(Nvidia/Linux 或关闭合成)下可能会增加 CPU 占用。',
themeStoreTitle: '主题商店', themeStoreTitle: '主题商店',
themeStoreSubmitText: '做了自己的主题?与社区分享——更多信息见主题仓库。', themeStoreSubmitText: '做了自己的主题?与社区分享——更多信息见主题仓库。',
themeStoreSubmitLink: '打开主题仓库', themeStoreSubmitLink: '打开主题仓库',
+2
View File
@@ -22,6 +22,8 @@ export interface RegistryTheme {
description: string; description: string;
mode: 'dark' | 'light'; mode: 'dark' | 'light';
tags?: string[]; tags?: string[];
/** True when the theme defines @keyframes (used for the CPU-load warning). */
animated?: boolean;
/** Repo-relative path to the theme's CSS. */ /** Repo-relative path to the theme's CSS. */
css: string; css: string;
/** Repo-relative path to the thumbnail. */ /** Repo-relative path to the thumbnail. */