diff --git a/apps/web/src/app/settings/settings.component.html b/apps/web/src/app/settings/settings.component.html
index a23a5308b..314e151e4 100644
--- a/apps/web/src/app/settings/settings.component.html
+++ b/apps/web/src/app/settings/settings.component.html
@@ -14,18 +14,20 @@
class="settings-layout"
novalidate
>
+
@if (!isDialog) {
-
+ {{ 'SETTINGS.TITLE' | translate }}
+
}
diff --git a/apps/web/src/app/settings/settings.component.scss b/apps/web/src/app/settings/settings.component.scss
index 5fcd5c475..4c6c0db57 100644
--- a/apps/web/src/app/settings/settings.component.scss
+++ b/apps/web/src/app/settings/settings.component.scss
@@ -235,50 +235,59 @@
}
}
+// Compact segmented control — replaces the previous 3 buttons each ~78px
+// tall. Per the UX audit, "Light/Dark/System" was reading as the most
+// important setting on the page because of its sheer footprint. Now the
+// row reads at the same weight as the other compact rows (Language,
+// Startup behavior, etc.), restoring the visual hierarchy.
.theme-switcher {
- width: min(100%, 540px);
- display: grid;
- grid-template-columns: repeat(3, minmax(0, 1fr));
- gap: 8px;
+ width: min(100%, 360px);
+ display: inline-flex;
+ padding: 3px;
+ gap: 2px;
+ background: color-mix(
+ in srgb,
+ var(--mat-sys-surface-container-low) 80%,
+ transparent
+ );
+ border: 1px solid
+ color-mix(in srgb, var(--mat-sys-outline-variant) 60%, transparent);
+ border-radius: 10px;
}
.theme-switcher__option {
+ flex: 1 1 0;
display: inline-flex;
align-items: center;
justify-content: center;
- gap: 8px;
- min-height: 54px;
- padding: 12px 14px;
- border: 1px solid
- color-mix(in srgb, var(--mat-sys-outline-variant) 70%, transparent);
- border-radius: 16px;
- background: color-mix(
- in srgb,
- var(--mat-sys-surface-container-low) 88%,
- transparent
- );
+ gap: 6px;
+ min-height: 30px;
+ padding: 4px 12px;
+ border: 0;
+ border-radius: 7px;
+ background: transparent;
color: var(--mat-sys-on-surface-variant);
font: inherit;
- font-size: 0.9rem;
- font-weight: 600;
- letter-spacing: -0.01em;
+ font-size: 0.82rem;
+ font-weight: 500;
+ letter-spacing: 0;
cursor: pointer;
transition:
- border-color 0.18s ease,
- background 0.18s ease,
- box-shadow 0.18s ease,
- color 0.18s ease;
+ background 0.16s ease,
+ color 0.16s ease,
+ box-shadow 0.16s ease;
mat-icon {
- width: 18px;
- height: 18px;
- font-size: 18px;
+ width: 16px;
+ height: 16px;
+ font-size: 16px;
+ line-height: 16px;
}
&:hover {
background: color-mix(
in srgb,
- var(--mat-sys-surface-container-high) 86%,
+ var(--mat-sys-surface-container-highest) 70%,
transparent
);
color: var(--mat-sys-on-surface);
@@ -286,31 +295,19 @@
&:focus-visible {
outline: none;
- box-shadow: 0 0 0 3px
- color-mix(in srgb, var(--app-selection-color) 18%, transparent);
+ box-shadow: 0 0 0 2px
+ color-mix(in srgb, var(--app-selection-color) 50%, transparent);
}
}
.theme-switcher__option--selected {
- border-color: var(--settings-group-active-ring);
- background: linear-gradient(
- 135deg,
- color-mix(
- in srgb,
- var(--app-selection-surface-strong) 72%,
- var(--mat-sys-surface-container-high)
- ),
- color-mix(
- in srgb,
- var(--app-selection-surface) 72%,
- var(--mat-sys-surface-container)
- )
- );
+ background: var(--app-widget-bg, var(--mat-sys-surface));
color: var(--app-selection-color);
+ font-weight: 600;
box-shadow:
+ 0 1px 2px rgba(0, 0, 0, 0.18),
inset 0 0 0 1px
- color-mix(in srgb, var(--app-selection-border) 54%, transparent),
- 0 10px 18px -22px var(--settings-group-active-glow);
+ color-mix(in srgb, var(--app-selection-border) 36%, transparent);
}
.epg-sources {
@@ -708,21 +705,31 @@
);
}
+// Sticky-footer Save bar — full content width so it never overlaps form
+// rows. The previous chip-style bar (border-radius:18px, margin-left:auto)
+// floated in the bottom-right corner and clipped fields underneath; the
+// UX audit called this out as the worst of three available patterns.
+// Now the bar acts as a proper footer: spans the column, separator line
+// on top, sits flush with the bottom edge while still sticking on scroll.
.action-bar {
position: sticky;
bottom: calc(
var(--settings-action-bar-offset) + var(--settings-safe-bottom)
);
- margin-left: auto;
+ margin-left: 0;
+ margin-right: 0;
z-index: 100;
display: flex;
justify-content: flex-end;
align-items: center;
gap: 12px;
flex-wrap: wrap;
- padding: 10px 14px;
- border: 1px solid var(--settings-action-bar-border);
- border-radius: 18px;
+ padding: 12px 18px;
+ border-top: 1px solid var(--settings-action-bar-border);
+ border-left: 0;
+ border-right: 0;
+ border-bottom: 0;
+ border-radius: 0;
background: var(--settings-action-bar-bg);
backdrop-filter: blur(12px);
box-shadow: var(--settings-action-bar-shadow);
@@ -735,16 +742,21 @@
box-shadow: none;
border-radius: 16px;
padding: 12px;
+ border-top: 1px solid var(--settings-action-bar-border);
justify-content: space-between;
}
.settings-save-button.mat-mdc-button-base {
- background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
- color: #fff;
+ // Use the unified blue primary (--app-selection-color) instead of the
+ // old hand-rolled #3b82f6 gradient that didn't match the rest of the
+ // app's CTAs after the color unification commit.
+ background: var(--app-selection-color);
+ color: var(--app-selection-on-color, #fff);
font-weight: 500;
padding: 0 20px;
border-radius: 10px;
- box-shadow: 0 4px 14px rgba(59, 130, 246, 0.28);
+ box-shadow: 0 4px 14px
+ color-mix(in srgb, var(--app-selection-color) 28%, transparent);
transition:
transform 0.2s ease,
box-shadow 0.2s ease,
@@ -756,7 +768,8 @@
&:hover:not([disabled]) {
transform: translateY(-1px);
- box-shadow: 0 6px 18px rgba(59, 130, 246, 0.38);
+ box-shadow: 0 6px 18px
+ color-mix(in srgb, var(--app-selection-color) 38%, transparent);
}
&:active:not([disabled]) {
diff --git a/apps/web/src/assets/i18n/ar.json b/apps/web/src/assets/i18n/ar.json
index 6fc137424..cd2980df2 100644
--- a/apps/web/src/assets/i18n/ar.json
+++ b/apps/web/src/assets/i18n/ar.json
@@ -375,9 +375,9 @@
"PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again."
},
"THEMES": {
- "DARK_THEME": "السمة الداكنة",
- "LIGHT_THEME": "السمة الفاتحة",
- "SYSTEM_THEME": "سمة النظام"
+ "DARK_THEME": "داكن",
+ "LIGHT_THEME": "فاتح",
+ "SYSTEM_THEME": "النظام"
},
"PLAYBACK_DIAGNOSTICS": {
"UNSUPPORTED_CONTAINER": {
diff --git a/apps/web/src/assets/i18n/ary.json b/apps/web/src/assets/i18n/ary.json
index b5a8c3cf0..35ae7c48a 100644
--- a/apps/web/src/assets/i18n/ary.json
+++ b/apps/web/src/assets/i18n/ary.json
@@ -375,9 +375,9 @@
"PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again."
},
"THEMES": {
- "DARK_THEME": "المظهر الداكن",
- "LIGHT_THEME": "المظهر الفاتح",
- "SYSTEM_THEME": "مظهر النظام"
+ "DARK_THEME": "داكن",
+ "LIGHT_THEME": "فاتح",
+ "SYSTEM_THEME": "النظام"
},
"PLAYBACK_DIAGNOSTICS": {
"UNSUPPORTED_CONTAINER": {
diff --git a/apps/web/src/assets/i18n/by.json b/apps/web/src/assets/i18n/by.json
index b06d6758a..5921f839a 100644
--- a/apps/web/src/assets/i18n/by.json
+++ b/apps/web/src/assets/i18n/by.json
@@ -375,9 +375,9 @@
"PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again."
},
"THEMES": {
- "DARK_THEME": "Цёмная тэма",
- "LIGHT_THEME": "Светлая тэма",
- "SYSTEM_THEME": "Сістэмная тэма"
+ "DARK_THEME": "Цёмная",
+ "LIGHT_THEME": "Светлая",
+ "SYSTEM_THEME": "Сістэмная"
},
"PLAYBACK_DIAGNOSTICS": {
"UNSUPPORTED_CONTAINER": {
diff --git a/apps/web/src/assets/i18n/el.json b/apps/web/src/assets/i18n/el.json
index 58afa845a..69ec3d7ec 100644
--- a/apps/web/src/assets/i18n/el.json
+++ b/apps/web/src/assets/i18n/el.json
@@ -375,9 +375,9 @@
"PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again."
},
"THEMES": {
- "DARK_THEME": "Σκούρο θέμα",
- "LIGHT_THEME": "Ανοιχτόχρωμο θέμα",
- "SYSTEM_THEME": "Θέμα συστήματος"
+ "DARK_THEME": "Σκούρο",
+ "LIGHT_THEME": "Ανοιχτό",
+ "SYSTEM_THEME": "Σύστημα"
},
"PLAYBACK_DIAGNOSTICS": {
"UNSUPPORTED_CONTAINER": {
diff --git a/apps/web/src/assets/i18n/en.json b/apps/web/src/assets/i18n/en.json
index 7f4b17947..f708e035c 100644
--- a/apps/web/src/assets/i18n/en.json
+++ b/apps/web/src/assets/i18n/en.json
@@ -375,9 +375,9 @@
"PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again."
},
"THEMES": {
- "DARK_THEME": "Dark theme",
- "LIGHT_THEME": "Light theme",
- "SYSTEM_THEME": "System theme"
+ "DARK_THEME": "Dark",
+ "LIGHT_THEME": "Light",
+ "SYSTEM_THEME": "System"
},
"PLAYBACK_DIAGNOSTICS": {
"UNSUPPORTED_CONTAINER": {
diff --git a/apps/web/src/assets/i18n/es.json b/apps/web/src/assets/i18n/es.json
index c43bd4d08..3de1b6ec4 100644
--- a/apps/web/src/assets/i18n/es.json
+++ b/apps/web/src/assets/i18n/es.json
@@ -375,9 +375,9 @@
"PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again."
},
"THEMES": {
- "DARK_THEME": "Tema oscuro",
- "LIGHT_THEME": "Tema claro",
- "SYSTEM_THEME": "Tema del sistema"
+ "DARK_THEME": "Oscuro",
+ "LIGHT_THEME": "Claro",
+ "SYSTEM_THEME": "Sistema"
},
"PLAYBACK_DIAGNOSTICS": {
"UNSUPPORTED_CONTAINER": {
diff --git a/apps/web/src/assets/i18n/fr.json b/apps/web/src/assets/i18n/fr.json
index 6b573bf0e..97b37ee9d 100644
--- a/apps/web/src/assets/i18n/fr.json
+++ b/apps/web/src/assets/i18n/fr.json
@@ -375,9 +375,9 @@
"PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again."
},
"THEMES": {
- "DARK_THEME": "Thème foncé",
- "LIGHT_THEME": "Thème clair",
- "SYSTEM_THEME": "Thème du système"
+ "DARK_THEME": "Sombre",
+ "LIGHT_THEME": "Clair",
+ "SYSTEM_THEME": "Système"
},
"PLAYBACK_DIAGNOSTICS": {
"UNSUPPORTED_CONTAINER": {
diff --git a/apps/web/src/assets/i18n/it.json b/apps/web/src/assets/i18n/it.json
index fbefedf2a..ed86645e7 100644
--- a/apps/web/src/assets/i18n/it.json
+++ b/apps/web/src/assets/i18n/it.json
@@ -375,9 +375,9 @@
"PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again."
},
"THEMES": {
- "DARK_THEME": "Tema scuro",
- "LIGHT_THEME": "Tema chiaro",
- "SYSTEM_THEME": "Tema di sistema"
+ "DARK_THEME": "Scuro",
+ "LIGHT_THEME": "Chiaro",
+ "SYSTEM_THEME": "Sistema"
},
"PLAYBACK_DIAGNOSTICS": {
"UNSUPPORTED_CONTAINER": {
diff --git a/apps/web/src/assets/i18n/ja.json b/apps/web/src/assets/i18n/ja.json
index 8159150ef..9ea348d08 100644
--- a/apps/web/src/assets/i18n/ja.json
+++ b/apps/web/src/assets/i18n/ja.json
@@ -375,9 +375,9 @@
"PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again."
},
"THEMES": {
- "DARK_THEME": "ダークテーマ",
- "LIGHT_THEME": "ライトテーマ",
- "SYSTEM_THEME": "システムテーマ"
+ "DARK_THEME": "ダーク",
+ "LIGHT_THEME": "ライト",
+ "SYSTEM_THEME": "システム"
},
"PLAYBACK_DIAGNOSTICS": {
"UNSUPPORTED_CONTAINER": {
diff --git a/apps/web/src/assets/i18n/ko.json b/apps/web/src/assets/i18n/ko.json
index 49fa7ac26..98212cdc1 100644
--- a/apps/web/src/assets/i18n/ko.json
+++ b/apps/web/src/assets/i18n/ko.json
@@ -375,9 +375,9 @@
"PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again."
},
"THEMES": {
- "DARK_THEME": "어두운 테마",
- "LIGHT_THEME": "밝은 테마",
- "SYSTEM_THEME": "시스템 테마"
+ "DARK_THEME": "어둡게",
+ "LIGHT_THEME": "밝게",
+ "SYSTEM_THEME": "시스템"
},
"PLAYBACK_DIAGNOSTICS": {
"UNSUPPORTED_CONTAINER": {
diff --git a/apps/web/src/assets/i18n/nl.json b/apps/web/src/assets/i18n/nl.json
index d5e31f633..4c7d2a961 100644
--- a/apps/web/src/assets/i18n/nl.json
+++ b/apps/web/src/assets/i18n/nl.json
@@ -375,9 +375,9 @@
"PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again."
},
"THEMES": {
- "DARK_THEME": "Donker thema",
- "LIGHT_THEME": "Licht thema",
- "SYSTEM_THEME": "Systeemthema"
+ "DARK_THEME": "Donker",
+ "LIGHT_THEME": "Licht",
+ "SYSTEM_THEME": "Systeem"
},
"PLAYBACK_DIAGNOSTICS": {
"UNSUPPORTED_CONTAINER": {
diff --git a/apps/web/src/assets/i18n/pl.json b/apps/web/src/assets/i18n/pl.json
index d9067313d..fe97670d9 100644
--- a/apps/web/src/assets/i18n/pl.json
+++ b/apps/web/src/assets/i18n/pl.json
@@ -375,9 +375,9 @@
"PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again."
},
"THEMES": {
- "DARK_THEME": "Ciemny motyw",
- "LIGHT_THEME": "Jasny motyw",
- "SYSTEM_THEME": "Motyw systemowy"
+ "DARK_THEME": "Ciemny",
+ "LIGHT_THEME": "Jasny",
+ "SYSTEM_THEME": "Systemowy"
},
"PLAYBACK_DIAGNOSTICS": {
"UNSUPPORTED_CONTAINER": {
diff --git a/apps/web/src/assets/i18n/pt.json b/apps/web/src/assets/i18n/pt.json
index 415663cfb..433e20503 100644
--- a/apps/web/src/assets/i18n/pt.json
+++ b/apps/web/src/assets/i18n/pt.json
@@ -375,9 +375,9 @@
"PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again."
},
"THEMES": {
- "DARK_THEME": "Tema escuro",
- "LIGHT_THEME": "Tema claro",
- "SYSTEM_THEME": "Tema do sistema"
+ "DARK_THEME": "Escuro",
+ "LIGHT_THEME": "Claro",
+ "SYSTEM_THEME": "Sistema"
},
"PLAYBACK_DIAGNOSTICS": {
"UNSUPPORTED_CONTAINER": {
diff --git a/apps/web/src/assets/i18n/ru.json b/apps/web/src/assets/i18n/ru.json
index dd7e840ef..d2bcaa0ff 100644
--- a/apps/web/src/assets/i18n/ru.json
+++ b/apps/web/src/assets/i18n/ru.json
@@ -375,9 +375,9 @@
"PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again."
},
"THEMES": {
- "DARK_THEME": "Тёмная тема",
- "LIGHT_THEME": "Светлая тема",
- "SYSTEM_THEME": "Системная тема"
+ "DARK_THEME": "Тёмная",
+ "LIGHT_THEME": "Светлая",
+ "SYSTEM_THEME": "Системная"
},
"PLAYBACK_DIAGNOSTICS": {
"UNSUPPORTED_CONTAINER": {
diff --git a/apps/web/src/assets/i18n/tr.json b/apps/web/src/assets/i18n/tr.json
index a7d4e824e..3e732d5d6 100644
--- a/apps/web/src/assets/i18n/tr.json
+++ b/apps/web/src/assets/i18n/tr.json
@@ -375,9 +375,9 @@
"PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again."
},
"THEMES": {
- "DARK_THEME": "Koyu tema",
- "LIGHT_THEME": "Açık tema",
- "SYSTEM_THEME": "Sistem teması"
+ "DARK_THEME": "Koyu",
+ "LIGHT_THEME": "Açık",
+ "SYSTEM_THEME": "Sistem"
},
"PLAYBACK_DIAGNOSTICS": {
"UNSUPPORTED_CONTAINER": {
diff --git a/apps/web/src/assets/i18n/zh.json b/apps/web/src/assets/i18n/zh.json
index b1f1798d3..5380c69ae 100644
--- a/apps/web/src/assets/i18n/zh.json
+++ b/apps/web/src/assets/i18n/zh.json
@@ -375,8 +375,8 @@
"PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again."
},
"THEMES": {
- "DARK_THEME": "暗黑模式",
- "LIGHT_THEME": "明亮模式",
+ "DARK_THEME": "深色",
+ "LIGHT_THEME": "浅色",
"SYSTEM_THEME": "跟随系统"
},
"PLAYBACK_DIAGNOSTICS": {
diff --git a/apps/web/src/assets/i18n/zhtw.json b/apps/web/src/assets/i18n/zhtw.json
index c0aa33b49..7a25f94d0 100644
--- a/apps/web/src/assets/i18n/zhtw.json
+++ b/apps/web/src/assets/i18n/zhtw.json
@@ -375,8 +375,8 @@
"PLAYLISTS_REMOVE_FAILED": "Could not delete playlists. Please try again."
},
"THEMES": {
- "DARK_THEME": "深色主題",
- "LIGHT_THEME": "亮色主題",
+ "DARK_THEME": "深色",
+ "LIGHT_THEME": "淺色",
"SYSTEM_THEME": "跟隨系統"
},
"PLAYBACK_DIAGNOSTICS": {
diff --git a/apps/web/src/styles.scss b/apps/web/src/styles.scss
index f1ec3dcfb..dc0e6ecb5 100644
--- a/apps/web/src/styles.scss
+++ b/apps/web/src/styles.scss
@@ -203,3 +203,20 @@ textarea,
.mat-mdc-snack-bar-container.settings-snackbar {
margin-bottom: calc(76px + env(safe-area-inset-bottom, 0px));
}
+
+// WCAG-friendly visually-hidden helper — content stays in the
+// accessibility tree and DOM query results but occupies no layout box.
+// Used where a visible heading would duplicate adjacent navigation
+// context (e.g. Settings page title that the left rail already
+// announces).
+.visually-hidden {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ white-space: nowrap;
+ border: 0;
+}
diff --git a/libs/workspace/shell/feature/src/lib/workspace-shell/components/workspace-shell-header/workspace-shell-header.component.html b/libs/workspace/shell/feature/src/lib/workspace-shell/components/workspace-shell-header/workspace-shell-header.component.html
index 01afaf1cd..ed8593c74 100644
--- a/libs/workspace/shell/feature/src/lib/workspace-shell/components/workspace-shell-header/workspace-shell-header.component.html
+++ b/libs/workspace/shell/feature/src/lib/workspace-shell/components/workspace-shell-header/workspace-shell-header.component.html
@@ -1,18 +1,24 @@
-