feat(tv): add corner spotlight backdrop option (#1628)

New TV-only Appearance toggle that pins the spotlight artwork to the
top-right corner (68% width, 72% height) with the left and bottom edges
feathered into the scaffold background, so the logo, metadata, and
shelves sit on a calm surface instead of the image. Default is off —
the full-bleed spotlight stays exactly as it is.

The artwork keeps its full-screen request size: the corner box only
crops the layout, so transcode URLs and image caches are unchanged and
toggling the setting stays instant.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Albert
2026-07-20 10:06:25 +02:00
committed by GitHub
co-authored by Claude Fable 5
parent 0707d4d9b1
commit f8cb550be7
9 changed files with 140 additions and 73 deletions
+2
View File
@@ -108,6 +108,8 @@
"libraryDensity": "Mediathekdichte",
"compact": "Kompakt",
"comfortable": "Großzügig",
"tvCornerSpotlightBackdrop": "Backdrop in der Ecke",
"tvCornerSpotlightBackdropDescription": "Zeigt das Spotlight-Artwork oben rechts statt bildschirmfüllend",
"viewMode": "Ansichtsmodus",
"gridView": "Raster",
"listView": "Liste",
+2
View File
@@ -108,6 +108,8 @@
"libraryDensity": "Library Density",
"compact": "Compact",
"comfortable": "Comfortable",
"tvCornerSpotlightBackdrop": "Corner Spotlight Backdrop",
"tvCornerSpotlightBackdropDescription": "Show spotlight artwork in the top-right corner instead of filling the screen",
"viewMode": "View Mode",
"gridView": "Grid",
"listView": "List",
+1 -1
View File
@@ -4,7 +4,7 @@
/// To regenerate, run: `dart run slang`
///
/// Locales: 16
/// Strings: 22851 (1428 per locale)
/// Strings: 22855 (1428 per locale)
// coverage:ignore-file
// ignore_for_file: type=lint, unused_import
+8 -4
View File
@@ -239,6 +239,8 @@ class _TranslationsSettingsDe extends TranslationsSettingsEn {
@override String get libraryDensity => 'Mediathekdichte';
@override String get compact => 'Kompakt';
@override String get comfortable => 'Großzügig';
@override String get tvCornerSpotlightBackdrop => 'Backdrop in der Ecke';
@override String get tvCornerSpotlightBackdropDescription => 'Zeigt das Spotlight-Artwork oben rechts statt bildschirmfüllend';
@override String get viewMode => 'Ansichtsmodus';
@override String get gridView => 'Raster';
@override String get listView => 'Liste';
@@ -2221,6 +2223,8 @@ extension on TranslationsDe {
'settings.libraryDensity' => 'Mediathekdichte',
'settings.compact' => 'Kompakt',
'settings.comfortable' => 'Großzügig',
'settings.tvCornerSpotlightBackdrop' => 'Backdrop in der Ecke',
'settings.tvCornerSpotlightBackdropDescription' => 'Zeigt das Spotlight-Artwork oben rechts statt bildschirmfüllend',
'settings.viewMode' => 'Ansichtsmodus',
'settings.gridView' => 'Raster',
'settings.listView' => 'Liste',
@@ -2633,10 +2637,10 @@ extension on TranslationsDe {
'messages.autoRemovedWatchedDownloads' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('de'))(n, one: 'Automatisch entfernt: ${n} angesehener Download', other: 'Automatisch entfernt: ${n} angesehene Downloads', ),
'messages.removedFromContinueWatching' => 'Aus Weiterschauen\' entfernt',
'messages.errorLoading' => ({required Object error}) => 'Fehler: ${error}',
'messages.streamInterrupted' => 'Der Stream wurde unterbrochen. Drücke auf Wiedergabe oder spule, um es erneut zu versuchen.',
'messages.liveStreamInterrupted' => 'Der Livestream wurde unterbrochen. Drücke auf Wiedergabe, um es erneut zu versuchen.',
_ => null,
} ?? switch (path) {
'messages.streamInterrupted' => 'Der Stream wurde unterbrochen. Drücke auf Wiedergabe oder spule, um es erneut zu versuchen.',
'messages.liveStreamInterrupted' => 'Der Livestream wurde unterbrochen. Drücke auf Wiedergabe, um es erneut zu versuchen.',
'messages.fileInfoNotAvailable' => 'Dateiinfo nicht verfügbar',
'messages.errorLoadingFileInfo' => ({required Object error}) => 'Fehler beim Laden der Dateiinfo: ${error}',
'messages.errorLoadingSeries' => 'Fehler beim Laden der Serie',
@@ -3147,10 +3151,10 @@ extension on TranslationsDe {
'watchTogether.guestSwitchUnavailable' => 'Wechsel fehlgeschlagen — Server nicht für Synchronisierung verfügbar',
'watchTogether.guestSwitchFailed' => 'Wechsel fehlgeschlagen — Inhalt auf diesem Server nicht gefunden',
'downloads.title' => 'Downloads',
'downloads.manage' => 'Verwalten',
'downloads.tvShows' => 'Serien',
_ => null,
} ?? switch (path) {
'downloads.manage' => 'Verwalten',
'downloads.tvShows' => 'Serien',
'downloads.movies' => 'Filme',
'downloads.music' => 'Musik',
'downloads.tracksQueued' => ({required Object count}) => '${count} Titel zum Download in Warteschlange',
+12 -4
View File
@@ -437,6 +437,12 @@ class TranslationsSettingsEn {
/// en: 'Comfortable'
String get comfortable => 'Comfortable';
/// en: 'Corner Spotlight Backdrop'
String get tvCornerSpotlightBackdrop => 'Corner Spotlight Backdrop';
/// en: 'Show spotlight artwork in the top-right corner instead of filling the screen'
String get tvCornerSpotlightBackdropDescription => 'Show spotlight artwork in the top-right corner instead of filling the screen';
/// en: 'View Mode'
String get viewMode => 'View Mode';
@@ -5078,6 +5084,8 @@ extension on Translations {
'settings.libraryDensity' => 'Library Density',
'settings.compact' => 'Compact',
'settings.comfortable' => 'Comfortable',
'settings.tvCornerSpotlightBackdrop' => 'Corner Spotlight Backdrop',
'settings.tvCornerSpotlightBackdropDescription' => 'Show spotlight artwork in the top-right corner instead of filling the screen',
'settings.viewMode' => 'View Mode',
'settings.gridView' => 'Grid',
'settings.listView' => 'List',
@@ -5490,10 +5498,10 @@ extension on Translations {
'messages.autoRemovedWatchedDownloads' => ({required num n}) => (_root.$meta.cardinalResolver ?? PluralResolvers.cardinal('en'))(n, one: 'Auto-removed ${n} watched download', other: 'Auto-removed ${n} watched downloads', ),
'messages.removedFromContinueWatching' => 'Removed from Continue Watching',
'messages.errorLoading' => ({required Object error}) => 'Error: ${error}',
'messages.streamInterrupted' => 'The stream was interrupted. Press play or seek to retry.',
'messages.liveStreamInterrupted' => 'The live stream was interrupted. Press play to retry.',
_ => null,
} ?? switch (path) {
'messages.streamInterrupted' => 'The stream was interrupted. Press play or seek to retry.',
'messages.liveStreamInterrupted' => 'The live stream was interrupted. Press play to retry.',
'messages.fileInfoNotAvailable' => 'File information not available',
'messages.errorLoadingFileInfo' => ({required Object error}) => 'Error loading file info: ${error}',
'messages.errorLoadingSeries' => 'Error loading series',
@@ -6004,10 +6012,10 @@ extension on Translations {
'watchTogether.guestSwitchUnavailable' => 'Couldn\'t switch — server unavailable for sync',
'watchTogether.guestSwitchFailed' => 'Couldn\'t switch — content not found on this server',
'downloads.title' => 'Downloads',
'downloads.manage' => 'Manage',
'downloads.tvShows' => 'TV Shows',
_ => null,
} ?? switch (path) {
'downloads.manage' => 'Manage',
'downloads.tvShows' => 'TV Shows',
'downloads.movies' => 'Movies',
'downloads.music' => 'Music',
'downloads.tracksQueued' => ({required Object count}) => '${count} tracks queued for download',