diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-all-hands-on-deck-class-1-a-2021-s5e1-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-all-hands-on-deck-class-1-a-2021-s5e1-backdrop.jpg new file mode 100644 index 00000000..f5b2f94c Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-all-hands-on-deck-class-1-a-2021-s5e1-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-all-hands-on-deck-class-1-a-2021-s5e1-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-all-hands-on-deck-class-1-a-2021-s5e1-poster.jpg new file mode 100644 index 00000000..cf571b93 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-all-hands-on-deck-class-1-a-2021-s5e1-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-announcement-2022-s1e7-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-announcement-2022-s1e7-backdrop.jpg new file mode 100644 index 00000000..a874a0c0 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-announcement-2022-s1e7-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-announcement-2022-s1e7-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-announcement-2022-s1e7-poster.jpg new file mode 100644 index 00000000..df5dc498 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-announcement-2022-s1e7-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-episode-1-2025-s1e1-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-episode-1-2025-s1e1-backdrop.jpg new file mode 100644 index 00000000..f2519b04 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-episode-1-2025-s1e1-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-episode-1-2025-s1e1-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-episode-1-2025-s1e1-poster.jpg new file mode 100644 index 00000000..77febd79 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-episode-1-2025-s1e1-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-episode-2-2016-s1e2-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-episode-2-2016-s1e2-backdrop.jpg new file mode 100644 index 00000000..3f8bdeec Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-episode-2-2016-s1e2-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-episode-2-2016-s1e2-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-episode-2-2016-s1e2-poster.jpg new file mode 100644 index 00000000..d29448bb Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-episode-2-2016-s1e2-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-erickerhead-2026-s9e6-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-erickerhead-2026-s9e6-backdrop.jpg new file mode 100644 index 00000000..1c4c0ffc Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-erickerhead-2026-s9e6-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-erickerhead-2026-s9e6-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-erickerhead-2026-s9e6-poster.jpg new file mode 100644 index 00000000..b95c4890 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-erickerhead-2026-s9e6-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-girls-interrupted-2007-s1e7-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-girls-interrupted-2007-s1e7-backdrop.jpg new file mode 100644 index 00000000..ffff979b Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-girls-interrupted-2007-s1e7-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-girls-interrupted-2007-s1e7-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-girls-interrupted-2007-s1e7-poster.jpg new file mode 100644 index 00000000..38428e76 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-girls-interrupted-2007-s1e7-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-i-heart-ny-2002-s4e18-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-i-heart-ny-2002-s4e18-backdrop.jpg new file mode 100644 index 00000000..57beacc1 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-i-heart-ny-2002-s4e18-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-i-heart-ny-2002-s4e18-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-i-heart-ny-2002-s4e18-poster.jpg new file mode 100644 index 00000000..e546cc6d Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-i-heart-ny-2002-s4e18-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-new-day-2021-s1e2-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-new-day-2021-s1e2-backdrop.jpg new file mode 100644 index 00000000..1f747323 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-new-day-2021-s1e2-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-new-day-2021-s1e2-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-new-day-2021-s1e2-poster.jpg new file mode 100644 index 00000000..2f23d1fa Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-new-day-2021-s1e2-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-no-honeymoon-2020-s1e8-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-no-honeymoon-2020-s1e8-backdrop.jpg new file mode 100644 index 00000000..9d95ba77 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-no-honeymoon-2020-s1e8-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-no-honeymoon-2020-s1e8-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-no-honeymoon-2020-s1e8-poster.jpg new file mode 100644 index 00000000..49c61208 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-no-honeymoon-2020-s1e8-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-notapusy-2005-s3e4-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-notapusy-2005-s3e4-backdrop.jpg new file mode 100644 index 00000000..ecb7e8cd Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-notapusy-2005-s3e4-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-notapusy-2005-s3e4-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-notapusy-2005-s3e4-poster.jpg new file mode 100644 index 00000000..02b0dd43 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-notapusy-2005-s3e4-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-one-of-us-2024-s1e7-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-one-of-us-2024-s1e7-backdrop.jpg new file mode 100644 index 00000000..66bb4d00 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-one-of-us-2024-s1e7-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-one-of-us-2024-s1e7-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-one-of-us-2024-s1e7-poster.jpg new file mode 100644 index 00000000..00a07ca7 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-one-of-us-2024-s1e7-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-remembrance-day-2026-s2e1-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-remembrance-day-2026-s2e1-backdrop.jpg new file mode 100644 index 00000000..23a242da Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-remembrance-day-2026-s2e1-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-remembrance-day-2026-s2e1-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-remembrance-day-2026-s2e1-poster.jpg new file mode 100644 index 00000000..930943b0 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-remembrance-day-2026-s2e1-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-sex-and-the-country-2001-s4e9-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-sex-and-the-country-2001-s4e9-backdrop.jpg new file mode 100644 index 00000000..57beacc1 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-sex-and-the-country-2001-s4e9-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-sex-and-the-country-2001-s4e9-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-sex-and-the-country-2001-s4e9-poster.jpg new file mode 100644 index 00000000..e546cc6d Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-sex-and-the-country-2001-s4e9-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-south-american-biker-gangs-2026-s29e1-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-south-american-biker-gangs-2026-s29e1-backdrop.jpg new file mode 100644 index 00000000..cd0aec39 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-south-american-biker-gangs-2026-s29e1-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-south-american-biker-gangs-2026-s29e1-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-south-american-biker-gangs-2026-s29e1-poster.jpg new file mode 100644 index 00000000..d8568d54 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-south-american-biker-gangs-2026-s29e1-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-the-breakup-part-2-2001-s1e17-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-the-breakup-part-2-2001-s1e17-backdrop.jpg new file mode 100644 index 00000000..b42ba953 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-the-breakup-part-2-2001-s1e17-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-the-breakup-part-2-2001-s1e17-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-the-breakup-part-2-2001-s1e17-poster.jpg new file mode 100644 index 00000000..23b668cb Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-the-breakup-part-2-2001-s1e17-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-the-man-who-would-be-vogue-2018-s2e1-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-the-man-who-would-be-vogue-2018-s2e1-backdrop.jpg new file mode 100644 index 00000000..84944d39 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-the-man-who-would-be-vogue-2018-s2e1-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-the-man-who-would-be-vogue-2018-s2e1-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-the-man-who-would-be-vogue-2018-s2e1-poster.jpg new file mode 100644 index 00000000..4086bf17 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-the-man-who-would-be-vogue-2018-s2e1-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-the-uncomfortability-2010-s1e2-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-the-uncomfortability-2010-s1e2-backdrop.jpg new file mode 100644 index 00000000..bc22e11a Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-the-uncomfortability-2010-s1e2-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-the-uncomfortability-2010-s1e2-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-the-uncomfortability-2010-s1e2-poster.jpg new file mode 100644 index 00000000..ae305043 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-the-uncomfortability-2010-s1e2-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-three-monsters-1992-s1e6-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-three-monsters-1992-s1e6-backdrop.jpg new file mode 100644 index 00000000..ba65ca2d Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-three-monsters-1992-s1e6-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-three-monsters-1992-s1e6-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-three-monsters-1992-s1e6-poster.jpg new file mode 100644 index 00000000..f5843766 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-three-monsters-1992-s1e6-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-tomorrow-2024-s3e1-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-tomorrow-2024-s3e1-backdrop.jpg new file mode 100644 index 00000000..9c9cca57 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-tomorrow-2024-s3e1-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-tomorrow-2024-s3e1-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-tomorrow-2024-s3e1-poster.jpg new file mode 100644 index 00000000..bbf0ab28 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-tomorrow-2024-s3e1-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-what-more-do-i-need-2008-s5e7-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-what-more-do-i-need-2008-s5e7-backdrop.jpg new file mode 100644 index 00000000..40ec3001 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-what-more-do-i-need-2008-s5e7-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/episode-what-more-do-i-need-2008-s5e7-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/episode-what-more-do-i-need-2008-s5e7-poster.jpg new file mode 100644 index 00000000..13f0f7b3 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/episode-what-more-do-i-need-2008-s5e7-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-101-dalmatians-ii-patch-s-london-adventure-2003-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-101-dalmatians-ii-patch-s-london-adventure-2003-backdrop.jpg new file mode 100644 index 00000000..f0d6f752 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-101-dalmatians-ii-patch-s-london-adventure-2003-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-101-dalmatians-ii-patch-s-london-adventure-2003-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-101-dalmatians-ii-patch-s-london-adventure-2003-poster.jpg new file mode 100644 index 00000000..b9b72894 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-101-dalmatians-ii-patch-s-london-adventure-2003-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-12-years-a-slave-2013-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-12-years-a-slave-2013-backdrop.jpg new file mode 100644 index 00000000..d93357e5 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-12-years-a-slave-2013-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-12-years-a-slave-2013-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-12-years-a-slave-2013-poster.jpg new file mode 100644 index 00000000..ff1f55e6 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-12-years-a-slave-2013-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-a-bronx-tale-1993-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-a-bronx-tale-1993-backdrop.jpg new file mode 100644 index 00000000..15c11ff7 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-a-bronx-tale-1993-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-a-bronx-tale-1993-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-a-bronx-tale-1993-poster.jpg new file mode 100644 index 00000000..f7444271 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-a-bronx-tale-1993-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-a-silent-voice-the-movie-2016-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-a-silent-voice-the-movie-2016-backdrop.jpg new file mode 100644 index 00000000..b386b136 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-a-silent-voice-the-movie-2016-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-a-silent-voice-the-movie-2016-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-a-silent-voice-the-movie-2016-poster.jpg new file mode 100644 index 00000000..580b22f4 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-a-silent-voice-the-movie-2016-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-absolution-2024-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-absolution-2024-backdrop.jpg new file mode 100644 index 00000000..eb28037c Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-absolution-2024-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-absolution-2024-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-absolution-2024-poster.jpg new file mode 100644 index 00000000..3ce49112 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-absolution-2024-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-across-the-hall-2009-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-across-the-hall-2009-backdrop.jpg new file mode 100644 index 00000000..6c1ee8ac Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-across-the-hall-2009-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-across-the-hall-2009-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-across-the-hall-2009-poster.jpg new file mode 100644 index 00000000..52362b4e Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-across-the-hall-2009-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-addicted-2014-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-addicted-2014-backdrop.jpg new file mode 100644 index 00000000..5c016976 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-addicted-2014-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-addicted-2014-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-addicted-2014-poster.jpg new file mode 100644 index 00000000..e9a03c14 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-addicted-2014-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-adulterers-2016-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-adulterers-2016-backdrop.jpg new file mode 100644 index 00000000..331391ed Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-adulterers-2016-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-adulterers-2016-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-adulterers-2016-poster.jpg new file mode 100644 index 00000000..8b65745b Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-adulterers-2016-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-all-the-money-in-the-world-2017-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-all-the-money-in-the-world-2017-backdrop.jpg new file mode 100644 index 00000000..ac4b828a Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-all-the-money-in-the-world-2017-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-all-the-money-in-the-world-2017-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-all-the-money-in-the-world-2017-poster.jpg new file mode 100644 index 00000000..8beb2f7f Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-all-the-money-in-the-world-2017-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-alone-with-her-2006-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-alone-with-her-2006-backdrop.jpg new file mode 100644 index 00000000..ddf8f1dc Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-alone-with-her-2006-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-alone-with-her-2006-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-alone-with-her-2006-poster.jpg new file mode 100644 index 00000000..7ce6fa96 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-alone-with-her-2006-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-american-gangster-2007-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-american-gangster-2007-backdrop.jpg new file mode 100644 index 00000000..cb5c5de7 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-american-gangster-2007-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-american-gangster-2007-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-american-gangster-2007-poster.jpg new file mode 100644 index 00000000..0f41d299 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-american-gangster-2007-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-annie-hall-1977-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-annie-hall-1977-backdrop.jpg new file mode 100644 index 00000000..16d0b27d Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-annie-hall-1977-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-annie-hall-1977-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-annie-hall-1977-poster.jpg new file mode 100644 index 00000000..9023a691 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-annie-hall-1977-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-ant-man-and-the-wasp-quantumania-2023-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-ant-man-and-the-wasp-quantumania-2023-backdrop.jpg new file mode 100644 index 00000000..2a99846d Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-ant-man-and-the-wasp-quantumania-2023-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-ant-man-and-the-wasp-quantumania-2023-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-ant-man-and-the-wasp-quantumania-2023-poster.jpg new file mode 100644 index 00000000..030addf0 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-ant-man-and-the-wasp-quantumania-2023-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-apocalypse-now-1979-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-apocalypse-now-1979-backdrop.jpg new file mode 100644 index 00000000..93292da3 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-apocalypse-now-1979-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-apocalypse-now-1979-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-apocalypse-now-1979-poster.jpg new file mode 100644 index 00000000..09cd6aa8 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-apocalypse-now-1979-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-appleseed-alpha-2014-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-appleseed-alpha-2014-backdrop.jpg new file mode 100644 index 00000000..82579b86 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-appleseed-alpha-2014-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-appleseed-alpha-2014-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-appleseed-alpha-2014-poster.jpg new file mode 100644 index 00000000..62bd0afa Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-appleseed-alpha-2014-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-aquaman-and-the-lost-kingdom-2023-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-aquaman-and-the-lost-kingdom-2023-backdrop.jpg new file mode 100644 index 00000000..6693cb6d Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-aquaman-and-the-lost-kingdom-2023-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-aquaman-and-the-lost-kingdom-2023-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-aquaman-and-the-lost-kingdom-2023-poster.jpg new file mode 100644 index 00000000..4550bdaa Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-aquaman-and-the-lost-kingdom-2023-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-arctic-dogs-2019-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-arctic-dogs-2019-backdrop.jpg new file mode 100644 index 00000000..1794c99d Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-arctic-dogs-2019-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-arctic-dogs-2019-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-arctic-dogs-2019-poster.jpg new file mode 100644 index 00000000..07552443 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-arctic-dogs-2019-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-arrival-2016-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-arrival-2016-backdrop.jpg new file mode 100644 index 00000000..605724e7 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-arrival-2016-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-arrival-2016-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-arrival-2016-poster.jpg new file mode 100644 index 00000000..fe224a15 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-arrival-2016-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-avatar-aang-the-last-airbender-2026-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-avatar-aang-the-last-airbender-2026-backdrop.jpg new file mode 100644 index 00000000..d3b12fe0 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-avatar-aang-the-last-airbender-2026-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-avatar-aang-the-last-airbender-2026-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-avatar-aang-the-last-airbender-2026-poster.jpg new file mode 100644 index 00000000..f57f1052 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-avatar-aang-the-last-airbender-2026-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-avengers-age-of-ultron-2015-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-avengers-age-of-ultron-2015-backdrop.jpg new file mode 100644 index 00000000..f739d620 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-avengers-age-of-ultron-2015-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-avengers-age-of-ultron-2015-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-avengers-age-of-ultron-2015-poster.jpg new file mode 100644 index 00000000..300840ff Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-avengers-age-of-ultron-2015-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-avengers-confidential-black-widow-punisher-2014-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-avengers-confidential-black-widow-punisher-2014-backdrop.jpg new file mode 100644 index 00000000..8163931a Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-avengers-confidential-black-widow-punisher-2014-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-avengers-confidential-black-widow-punisher-2014-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-avengers-confidential-black-widow-punisher-2014-poster.jpg new file mode 100644 index 00000000..b096fff2 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-avengers-confidential-black-widow-punisher-2014-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-avengers-infinity-war-2018-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-avengers-infinity-war-2018-backdrop.jpg new file mode 100644 index 00000000..90dc4672 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-avengers-infinity-war-2018-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-avengers-infinity-war-2018-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-avengers-infinity-war-2018-poster.jpg new file mode 100644 index 00000000..57df9298 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-avengers-infinity-war-2018-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-awake-2007-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-awake-2007-backdrop.jpg new file mode 100644 index 00000000..6ddd7f59 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-awake-2007-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-awake-2007-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-awake-2007-poster.jpg new file mode 100644 index 00000000..32595897 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-awake-2007-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-batman-ninja-2018-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-batman-ninja-2018-backdrop.jpg new file mode 100644 index 00000000..9b538d9f Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-batman-ninja-2018-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-batman-ninja-2018-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-batman-ninja-2018-poster.jpg new file mode 100644 index 00000000..7f59127b Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-batman-ninja-2018-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-batman-the-dark-knight-returns-part-1-2012-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-batman-the-dark-knight-returns-part-1-2012-backdrop.jpg new file mode 100644 index 00000000..9d385801 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-batman-the-dark-knight-returns-part-1-2012-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-batman-the-dark-knight-returns-part-1-2012-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-batman-the-dark-knight-returns-part-1-2012-poster.jpg new file mode 100644 index 00000000..ce5623fe Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-batman-the-dark-knight-returns-part-1-2012-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-batman-v-superman-dawn-of-justice-2016-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-batman-v-superman-dawn-of-justice-2016-backdrop.jpg new file mode 100644 index 00000000..b4fa63b9 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-batman-v-superman-dawn-of-justice-2016-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-batman-v-superman-dawn-of-justice-2016-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-batman-v-superman-dawn-of-justice-2016-poster.jpg new file mode 100644 index 00000000..f8115170 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-batman-v-superman-dawn-of-justice-2016-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-black-adam-2022-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-black-adam-2022-backdrop.jpg new file mode 100644 index 00000000..3232fb22 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-black-adam-2022-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-black-adam-2022-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-black-adam-2022-poster.jpg new file mode 100644 index 00000000..dde008b5 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-black-adam-2022-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-black-bear-2020-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-black-bear-2020-backdrop.jpg new file mode 100644 index 00000000..4b0b262f Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-black-bear-2020-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-black-bear-2020-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-black-bear-2020-poster.jpg new file mode 100644 index 00000000..61c8860f Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-black-bear-2020-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-black-panther-2018-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-black-panther-2018-backdrop.jpg new file mode 100644 index 00000000..f0b8de8a Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-black-panther-2018-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-black-panther-2018-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-black-panther-2018-poster.jpg new file mode 100644 index 00000000..6fdaa3d2 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-black-panther-2018-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-black-panther-wakanda-forever-2022-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-black-panther-wakanda-forever-2022-backdrop.jpg new file mode 100644 index 00000000..ac7ad532 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-black-panther-wakanda-forever-2022-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-black-panther-wakanda-forever-2022-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-black-panther-wakanda-forever-2022-poster.jpg new file mode 100644 index 00000000..d0219a75 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-black-panther-wakanda-forever-2022-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-blame-2017-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-blame-2017-backdrop.jpg new file mode 100644 index 00000000..f39cf5ce Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-blame-2017-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-blame-2017-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-blame-2017-poster.jpg new file mode 100644 index 00000000..8d50255f Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-blame-2017-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-bleach-the-movie-fade-to-black-2008-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-bleach-the-movie-fade-to-black-2008-backdrop.jpg new file mode 100644 index 00000000..f426d0ce Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-bleach-the-movie-fade-to-black-2008-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-bleach-the-movie-fade-to-black-2008-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-bleach-the-movie-fade-to-black-2008-poster.jpg new file mode 100644 index 00000000..d3619fbd Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-bleach-the-movie-fade-to-black-2008-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-bohemian-rhapsody-2018-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-bohemian-rhapsody-2018-backdrop.jpg new file mode 100644 index 00000000..b6b5a7cd Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-bohemian-rhapsody-2018-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-bohemian-rhapsody-2018-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-bohemian-rhapsody-2018-poster.jpg new file mode 100644 index 00000000..20d2f9ee Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-bohemian-rhapsody-2018-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-braveheart-1995-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-braveheart-1995-backdrop.jpg new file mode 100644 index 00000000..c1c84c59 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-braveheart-1995-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-braveheart-1995-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-braveheart-1995-poster.jpg new file mode 100644 index 00000000..32fd8d4d Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-braveheart-1995-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-butterfly-on-a-wheel-2007-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-butterfly-on-a-wheel-2007-backdrop.jpg new file mode 100644 index 00000000..e49fe1d5 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-butterfly-on-a-wheel-2007-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-butterfly-on-a-wheel-2007-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-butterfly-on-a-wheel-2007-poster.jpg new file mode 100644 index 00000000..b54cc5c7 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-butterfly-on-a-wheel-2007-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-by-any-means-2026-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-by-any-means-2026-backdrop.jpg new file mode 100644 index 00000000..e536359f Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-by-any-means-2026-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-by-any-means-2026-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-by-any-means-2026-poster.jpg new file mode 100644 index 00000000..92fd601f Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-by-any-means-2026-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-captain-america-civil-war-2016-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-captain-america-civil-war-2016-backdrop.jpg new file mode 100644 index 00000000..5700c4e7 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-captain-america-civil-war-2016-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-captain-america-civil-war-2016-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-captain-america-civil-war-2016-poster.jpg new file mode 100644 index 00000000..426142ef Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-captain-america-civil-war-2016-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-captain-america-the-first-avenger-2011-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-captain-america-the-first-avenger-2011-backdrop.jpg new file mode 100644 index 00000000..411ceebe Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-captain-america-the-first-avenger-2011-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-captain-america-the-first-avenger-2011-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-captain-america-the-first-avenger-2011-poster.jpg new file mode 100644 index 00000000..bba96574 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-captain-america-the-first-avenger-2011-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-captain-america-the-winter-soldier-2014-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-captain-america-the-winter-soldier-2014-backdrop.jpg new file mode 100644 index 00000000..5661fcff Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-captain-america-the-winter-soldier-2014-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-captain-america-the-winter-soldier-2014-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-captain-america-the-winter-soldier-2014-poster.jpg new file mode 100644 index 00000000..c2dc5612 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-captain-america-the-winter-soldier-2014-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-careful-what-you-wish-for-2015-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-careful-what-you-wish-for-2015-backdrop.jpg new file mode 100644 index 00000000..988e20d5 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-careful-what-you-wish-for-2015-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-careful-what-you-wish-for-2015-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-careful-what-you-wish-for-2015-poster.jpg new file mode 100644 index 00000000..b58ce600 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-careful-what-you-wish-for-2015-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-castle-in-the-sky-1986-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-castle-in-the-sky-1986-backdrop.jpg new file mode 100644 index 00000000..f785c549 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-castle-in-the-sky-1986-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-castle-in-the-sky-1986-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-castle-in-the-sky-1986-poster.jpg new file mode 100644 index 00000000..a6a99fee Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-castle-in-the-sky-1986-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-chicago-2002-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-chicago-2002-backdrop.jpg new file mode 100644 index 00000000..6b3d02a3 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-chicago-2002-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-chicago-2002-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-chicago-2002-poster.jpg new file mode 100644 index 00000000..1def9414 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-chicago-2002-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-citizen-kane-1941-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-citizen-kane-1941-backdrop.jpg new file mode 100644 index 00000000..d41a10ef Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-citizen-kane-1941-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-citizen-kane-1941-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-citizen-kane-1941-poster.jpg new file mode 100644 index 00000000..f26414a7 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-citizen-kane-1941-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-coherence-2014-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-coherence-2014-backdrop.jpg new file mode 100644 index 00000000..37da64f9 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-coherence-2014-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-coherence-2014-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-coherence-2014-poster.jpg new file mode 100644 index 00000000..f3601b9b Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-coherence-2014-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-conclave-2024-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-conclave-2024-backdrop.jpg new file mode 100644 index 00000000..db0a7fcd Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-conclave-2024-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-conclave-2024-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-conclave-2024-poster.jpg new file mode 100644 index 00000000..c6aecefa Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-conclave-2024-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-crime-of-passion-1956-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-crime-of-passion-1956-backdrop.jpg new file mode 100644 index 00000000..6c70a452 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-crime-of-passion-1956-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-crime-of-passion-1956-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-crime-of-passion-1956-poster.jpg new file mode 100644 index 00000000..4acfa871 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-crime-of-passion-1956-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-deep-water-2022-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-deep-water-2022-backdrop.jpg new file mode 100644 index 00000000..bec98992 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-deep-water-2022-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-deep-water-2022-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-deep-water-2022-poster.jpg new file mode 100644 index 00000000..018d4f4b Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-deep-water-2022-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-derailed-2005-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-derailed-2005-backdrop.jpg new file mode 100644 index 00000000..e958449b Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-derailed-2005-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-derailed-2005-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-derailed-2005-poster.jpg new file mode 100644 index 00000000..d93f3124 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-derailed-2005-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-die-my-love-2025-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-die-my-love-2025-backdrop.jpg new file mode 100644 index 00000000..754e713f Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-die-my-love-2025-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-die-my-love-2025-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-die-my-love-2025-poster.jpg new file mode 100644 index 00000000..f6eca818 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-die-my-love-2025-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-dream-house-2011-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-dream-house-2011-backdrop.jpg new file mode 100644 index 00000000..29ecc103 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-dream-house-2011-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-dream-house-2011-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-dream-house-2011-poster.jpg new file mode 100644 index 00000000..d3444637 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-dream-house-2011-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-dream-lover-1994-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-dream-lover-1994-backdrop.jpg new file mode 100644 index 00000000..7f5a31ac Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-dream-lover-1994-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-dream-lover-1994-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-dream-lover-1994-poster.jpg new file mode 100644 index 00000000..3b563cf9 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-dream-lover-1994-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-drop-2025-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-drop-2025-backdrop.jpg new file mode 100644 index 00000000..ee642530 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-drop-2025-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-drop-2025-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-drop-2025-poster.jpg new file mode 100644 index 00000000..4cf7d66b Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-drop-2025-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-eastern-promises-2007-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-eastern-promises-2007-backdrop.jpg new file mode 100644 index 00000000..9096a0fa Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-eastern-promises-2007-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-eastern-promises-2007-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-eastern-promises-2007-poster.jpg new file mode 100644 index 00000000..01a26f3b Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-eastern-promises-2007-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-easy-a-2010-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-easy-a-2010-backdrop.jpg new file mode 100644 index 00000000..c27fa57b Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-easy-a-2010-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-easy-a-2010-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-easy-a-2010-poster.jpg new file mode 100644 index 00000000..45e13a32 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-easy-a-2010-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-el-camino-a-breaking-bad-movie-2019-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-el-camino-a-breaking-bad-movie-2019-backdrop.jpg new file mode 100644 index 00000000..8edc02a6 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-el-camino-a-breaking-bad-movie-2019-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-el-camino-a-breaking-bad-movie-2019-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-el-camino-a-breaking-bad-movie-2019-poster.jpg new file mode 100644 index 00000000..7a62df45 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-el-camino-a-breaking-bad-movie-2019-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-emancipation-2022-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-emancipation-2022-backdrop.jpg new file mode 100644 index 00000000..ac0777c7 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-emancipation-2022-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-emancipation-2022-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-emancipation-2022-poster.jpg new file mode 100644 index 00000000..c1bee971 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-emancipation-2022-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-faces-1968-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-faces-1968-backdrop.jpg new file mode 100644 index 00000000..60273dd9 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-faces-1968-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-faces-1968-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-faces-1968-poster.jpg new file mode 100644 index 00000000..43f996bd Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-faces-1968-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-fatale-2020-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-fatale-2020-backdrop.jpg new file mode 100644 index 00000000..b13c33c6 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-fatale-2020-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-fatale-2020-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-fatale-2020-poster.jpg new file mode 100644 index 00000000..dda9d622 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-fatale-2020-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-fighting-with-my-family-2019-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-fighting-with-my-family-2019-backdrop.jpg new file mode 100644 index 00000000..46835996 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-fighting-with-my-family-2019-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-fighting-with-my-family-2019-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-fighting-with-my-family-2019-poster.jpg new file mode 100644 index 00000000..169c466d Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-fighting-with-my-family-2019-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-force-of-nature-the-dry-2-2024-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-force-of-nature-the-dry-2-2024-backdrop.jpg new file mode 100644 index 00000000..3735ee63 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-force-of-nature-the-dry-2-2024-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-force-of-nature-the-dry-2-2024-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-force-of-nature-the-dry-2-2024-poster.jpg new file mode 100644 index 00000000..93ba0d71 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-force-of-nature-the-dry-2-2024-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-free-state-of-jones-2016-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-free-state-of-jones-2016-backdrop.jpg new file mode 100644 index 00000000..a7e5ddff Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-free-state-of-jones-2016-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-free-state-of-jones-2016-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-free-state-of-jones-2016-poster.jpg new file mode 100644 index 00000000..e69af7c0 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-free-state-of-jones-2016-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-from-up-on-poppy-hill-2011-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-from-up-on-poppy-hill-2011-backdrop.jpg new file mode 100644 index 00000000..32a28bd7 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-from-up-on-poppy-hill-2011-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-from-up-on-poppy-hill-2011-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-from-up-on-poppy-hill-2011-poster.jpg new file mode 100644 index 00000000..264076e2 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-from-up-on-poppy-hill-2011-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-ghost-in-the-shell-1995-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-ghost-in-the-shell-1995-backdrop.jpg new file mode 100644 index 00000000..716b8a87 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-ghost-in-the-shell-1995-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-ghost-in-the-shell-1995-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-ghost-in-the-shell-1995-poster.jpg new file mode 100644 index 00000000..58d221ba Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-ghost-in-the-shell-1995-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-ghost-in-the-shell-2-innocence-2004-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-ghost-in-the-shell-2-innocence-2004-backdrop.jpg new file mode 100644 index 00000000..4a9931ed Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-ghost-in-the-shell-2-innocence-2004-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-ghost-in-the-shell-2-innocence-2004-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-ghost-in-the-shell-2-innocence-2004-poster.jpg new file mode 100644 index 00000000..ae262147 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-ghost-in-the-shell-2-innocence-2004-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-gosford-park-2001-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-gosford-park-2001-backdrop.jpg new file mode 100644 index 00000000..42c4cea6 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-gosford-park-2001-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-gosford-park-2001-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-gosford-park-2001-poster.jpg new file mode 100644 index 00000000..6a6aa651 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-gosford-park-2001-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-grave-of-the-fireflies-1988-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-grave-of-the-fireflies-1988-backdrop.jpg new file mode 100644 index 00000000..0e1add06 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-grave-of-the-fireflies-1988-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-grave-of-the-fireflies-1988-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-grave-of-the-fireflies-1988-poster.jpg new file mode 100644 index 00000000..1b71996a Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-grave-of-the-fireflies-1988-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-howl-s-moving-castle-2004-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-howl-s-moving-castle-2004-backdrop.jpg new file mode 100644 index 00000000..0aeef2ba Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-howl-s-moving-castle-2004-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-howl-s-moving-castle-2004-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-howl-s-moving-castle-2004-poster.jpg new file mode 100644 index 00000000..042a6c80 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-howl-s-moving-castle-2004-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-in-the-heights-2021-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-in-the-heights-2021-backdrop.jpg new file mode 100644 index 00000000..7cd4ee24 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-in-the-heights-2021-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-in-the-heights-2021-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-in-the-heights-2021-poster.jpg new file mode 100644 index 00000000..e05bfdb0 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-in-the-heights-2021-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-jennifer-s-body-2009-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-jennifer-s-body-2009-backdrop.jpg new file mode 100644 index 00000000..032771a0 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-jennifer-s-body-2009-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-jennifer-s-body-2009-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-jennifer-s-body-2009-poster.jpg new file mode 100644 index 00000000..41cc1eb5 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-jennifer-s-body-2009-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-kiki-s-delivery-service-1989-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-kiki-s-delivery-service-1989-backdrop.jpg new file mode 100644 index 00000000..2a108f0c Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-kiki-s-delivery-service-1989-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-kiki-s-delivery-service-1989-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-kiki-s-delivery-service-1989-poster.jpg new file mode 100644 index 00000000..80a59b1e Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-kiki-s-delivery-service-1989-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-lemony-snicket-s-a-series-of-unfortunate-events-2004-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-lemony-snicket-s-a-series-of-unfortunate-events-2004-backdrop.jpg new file mode 100644 index 00000000..38203b77 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-lemony-snicket-s-a-series-of-unfortunate-events-2004-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-lemony-snicket-s-a-series-of-unfortunate-events-2004-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-lemony-snicket-s-a-series-of-unfortunate-events-2004-poster.jpg new file mode 100644 index 00000000..4c78fee9 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-lemony-snicket-s-a-series-of-unfortunate-events-2004-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-locke-2014-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-locke-2014-backdrop.jpg new file mode 100644 index 00000000..a24e64ab Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-locke-2014-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-locke-2014-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-locke-2014-poster.jpg new file mode 100644 index 00000000..94ebe97d Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-locke-2014-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-look-who-s-back-2015-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-look-who-s-back-2015-backdrop.jpg new file mode 100644 index 00000000..a064686f Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-look-who-s-back-2015-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-look-who-s-back-2015-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-look-who-s-back-2015-poster.jpg new file mode 100644 index 00000000..423043cf Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-look-who-s-back-2015-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-memories-1995-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-memories-1995-backdrop.jpg new file mode 100644 index 00000000..2fcaeaad Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-memories-1995-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-memories-1995-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-memories-1995-poster.jpg new file mode 100644 index 00000000..f3725b42 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-memories-1995-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-mercy-2026-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-mercy-2026-backdrop.jpg new file mode 100644 index 00000000..45591cc8 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-mercy-2026-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-mercy-2026-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-mercy-2026-poster.jpg new file mode 100644 index 00000000..cfc0d812 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-mercy-2026-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-night-is-short-walk-on-girl-2017-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-night-is-short-walk-on-girl-2017-backdrop.jpg new file mode 100644 index 00000000..da38be48 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-night-is-short-walk-on-girl-2017-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-night-is-short-walk-on-girl-2017-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-night-is-short-walk-on-girl-2017-poster.jpg new file mode 100644 index 00000000..c6fa6a26 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-night-is-short-walk-on-girl-2017-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-normal-2026-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-normal-2026-backdrop.jpg new file mode 100644 index 00000000..51711864 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-normal-2026-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-normal-2026-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-normal-2026-poster.jpg new file mode 100644 index 00000000..f4c89db7 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-normal-2026-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-outlaw-king-2018-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-outlaw-king-2018-backdrop.jpg new file mode 100644 index 00000000..54ac5a2a Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-outlaw-king-2018-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-outlaw-king-2018-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-outlaw-king-2018-poster.jpg new file mode 100644 index 00000000..d6760468 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-outlaw-king-2018-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-panic-room-2002-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-panic-room-2002-backdrop.jpg new file mode 100644 index 00000000..ec877751 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-panic-room-2002-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-panic-room-2002-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-panic-room-2002-poster.jpg new file mode 100644 index 00000000..0b2a0d30 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-panic-room-2002-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-peaky-blinders-the-immortal-man-2026-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-peaky-blinders-the-immortal-man-2026-backdrop.jpg new file mode 100644 index 00000000..e9d7155a Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-peaky-blinders-the-immortal-man-2026-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-peaky-blinders-the-immortal-man-2026-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-peaky-blinders-the-immortal-man-2026-poster.jpg new file mode 100644 index 00000000..a4814472 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-peaky-blinders-the-immortal-man-2026-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-ponyo-2008-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-ponyo-2008-backdrop.jpg new file mode 100644 index 00000000..8aa4ecbd Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-ponyo-2008-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-ponyo-2008-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-ponyo-2008-poster.jpg new file mode 100644 index 00000000..f548e831 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-ponyo-2008-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-porco-rosso-1992-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-porco-rosso-1992-backdrop.jpg new file mode 100644 index 00000000..63d8112e Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-porco-rosso-1992-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-porco-rosso-1992-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-porco-rosso-1992-poster.jpg new file mode 100644 index 00000000..4a6a2bd1 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-porco-rosso-1992-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-princess-mononoke-1997-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-princess-mononoke-1997-backdrop.jpg new file mode 100644 index 00000000..9486d2cf Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-princess-mononoke-1997-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-princess-mononoke-1997-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-princess-mononoke-1997-poster.jpg new file mode 100644 index 00000000..21dd9359 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-princess-mononoke-1997-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-prisoners-2013-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-prisoners-2013-backdrop.jpg new file mode 100644 index 00000000..724008c5 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-prisoners-2013-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-prisoners-2013-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-prisoners-2013-poster.jpg new file mode 100644 index 00000000..411b149b Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-prisoners-2013-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-racing-stripes-2005-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-racing-stripes-2005-backdrop.jpg new file mode 100644 index 00000000..1d332cec Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-racing-stripes-2005-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-racing-stripes-2005-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-racing-stripes-2005-poster.jpg new file mode 100644 index 00000000..cc2ef2ce Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-racing-stripes-2005-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-reincarnated-2012-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-reincarnated-2012-backdrop.jpg new file mode 100644 index 00000000..326e8ebc Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-reincarnated-2012-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-reincarnated-2012-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-reincarnated-2012-poster.jpg new file mode 100644 index 00000000..e9ab449c Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-reincarnated-2012-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-requiem-for-a-dream-2000-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-requiem-for-a-dream-2000-backdrop.jpg new file mode 100644 index 00000000..21842ef2 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-requiem-for-a-dream-2000-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-requiem-for-a-dream-2000-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-requiem-for-a-dream-2000-poster.jpg new file mode 100644 index 00000000..6f89abc3 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-requiem-for-a-dream-2000-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-rogue-one-a-star-wars-story-2016-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-rogue-one-a-star-wars-story-2016-backdrop.jpg new file mode 100644 index 00000000..ff0c14c0 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-rogue-one-a-star-wars-story-2016-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-rogue-one-a-star-wars-story-2016-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-rogue-one-a-star-wars-story-2016-poster.jpg new file mode 100644 index 00000000..c79e70a1 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-rogue-one-a-star-wars-story-2016-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-secondhand-lions-2003-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-secondhand-lions-2003-backdrop.jpg new file mode 100644 index 00000000..7ea43937 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-secondhand-lions-2003-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-secondhand-lions-2003-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-secondhand-lions-2003-poster.jpg new file mode 100644 index 00000000..33408fa0 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-secondhand-lions-2003-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-self-reliance-2024-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-self-reliance-2024-backdrop.jpg new file mode 100644 index 00000000..3cc0d123 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-self-reliance-2024-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-self-reliance-2024-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-self-reliance-2024-poster.jpg new file mode 100644 index 00000000..0f6017ed Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-self-reliance-2024-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-serpico-1973-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-serpico-1973-backdrop.jpg new file mode 100644 index 00000000..c9649485 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-serpico-1973-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-serpico-1973-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-serpico-1973-poster.jpg new file mode 100644 index 00000000..8ddd6e52 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-serpico-1973-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-shaft-2000-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-shaft-2000-backdrop.jpg new file mode 100644 index 00000000..13f14f93 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-shaft-2000-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-shaft-2000-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-shaft-2000-poster.jpg new file mode 100644 index 00000000..6b2b79ce Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-shaft-2000-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-shipwrecked-nightmare-at-sea-2026-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-shipwrecked-nightmare-at-sea-2026-backdrop.jpg new file mode 100644 index 00000000..6e98e242 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-shipwrecked-nightmare-at-sea-2026-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-shipwrecked-nightmare-at-sea-2026-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-shipwrecked-nightmare-at-sea-2026-poster.jpg new file mode 100644 index 00000000..f200a28d Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-shipwrecked-nightmare-at-sea-2026-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-sinbad-legend-of-the-seven-seas-2003-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-sinbad-legend-of-the-seven-seas-2003-backdrop.jpg new file mode 100644 index 00000000..d04685fe Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-sinbad-legend-of-the-seven-seas-2003-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-sinbad-legend-of-the-seven-seas-2003-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-sinbad-legend-of-the-seven-seas-2003-poster.jpg new file mode 100644 index 00000000..428bb38b Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-sinbad-legend-of-the-seven-seas-2003-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-sleeping-with-other-people-2015-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-sleeping-with-other-people-2015-backdrop.jpg new file mode 100644 index 00000000..df5bb046 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-sleeping-with-other-people-2015-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-sleeping-with-other-people-2015-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-sleeping-with-other-people-2015-poster.jpg new file mode 100644 index 00000000..a5ffff1f Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-sleeping-with-other-people-2015-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-snowpiercer-2013-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-snowpiercer-2013-backdrop.jpg new file mode 100644 index 00000000..c1281a9c Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-snowpiercer-2013-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-snowpiercer-2013-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-snowpiercer-2013-poster.jpg new file mode 100644 index 00000000..be1873fd Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-snowpiercer-2013-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-spider-man-brand-new-day-2026-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-spider-man-brand-new-day-2026-backdrop.jpg new file mode 100644 index 00000000..2fdaadb4 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-spider-man-brand-new-day-2026-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-spider-man-brand-new-day-2026-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-spider-man-brand-new-day-2026-poster.jpg new file mode 100644 index 00000000..51fa155f Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-spider-man-brand-new-day-2026-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-spider-man-far-from-home-2019-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-spider-man-far-from-home-2019-backdrop.jpg new file mode 100644 index 00000000..d09f655a Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-spider-man-far-from-home-2019-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-spider-man-far-from-home-2019-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-spider-man-far-from-home-2019-poster.jpg new file mode 100644 index 00000000..28521997 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-spider-man-far-from-home-2019-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-spider-man-homecoming-2017-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-spider-man-homecoming-2017-backdrop.jpg new file mode 100644 index 00000000..6088fadf Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-spider-man-homecoming-2017-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-spider-man-homecoming-2017-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-spider-man-homecoming-2017-poster.jpg new file mode 100644 index 00000000..97fc9c05 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-spider-man-homecoming-2017-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-spider-man-no-way-home-2021-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-spider-man-no-way-home-2021-backdrop.jpg new file mode 100644 index 00000000..a76afec9 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-spider-man-no-way-home-2021-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-spider-man-no-way-home-2021-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-spider-man-no-way-home-2021-poster.jpg new file mode 100644 index 00000000..bc92a4cd Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-spider-man-no-way-home-2021-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-spirited-away-2001-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-spirited-away-2001-backdrop.jpg new file mode 100644 index 00000000..af91c123 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-spirited-away-2001-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-spirited-away-2001-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-spirited-away-2001-poster.jpg new file mode 100644 index 00000000..5a79da62 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-spirited-away-2001-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-steins-gate-the-movie-load-region-of-deja-vu-2013-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-steins-gate-the-movie-load-region-of-deja-vu-2013-backdrop.jpg new file mode 100644 index 00000000..f6675060 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-steins-gate-the-movie-load-region-of-deja-vu-2013-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-steins-gate-the-movie-load-region-of-deja-vu-2013-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-steins-gate-the-movie-load-region-of-deja-vu-2013-poster.jpg new file mode 100644 index 00000000..e85ddb90 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-steins-gate-the-movie-load-region-of-deja-vu-2013-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-taxi-driver-1976-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-taxi-driver-1976-backdrop.jpg new file mode 100644 index 00000000..7946ea40 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-taxi-driver-1976-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-taxi-driver-1976-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-taxi-driver-1976-poster.jpg new file mode 100644 index 00000000..c410b981 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-taxi-driver-1976-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-alto-knights-2025-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-alto-knights-2025-backdrop.jpg new file mode 100644 index 00000000..ea619c14 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-alto-knights-2025-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-alto-knights-2025-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-alto-knights-2025-poster.jpg new file mode 100644 index 00000000..bb009cbf Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-alto-knights-2025-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-amazing-spider-man-2-2014-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-amazing-spider-man-2-2014-backdrop.jpg new file mode 100644 index 00000000..7c5878b7 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-amazing-spider-man-2-2014-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-amazing-spider-man-2-2014-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-amazing-spider-man-2-2014-poster.jpg new file mode 100644 index 00000000..4de2711f Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-amazing-spider-man-2-2014-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-amazing-spider-man-2012-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-amazing-spider-man-2012-backdrop.jpg new file mode 100644 index 00000000..75ae9b64 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-amazing-spider-man-2012-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-amazing-spider-man-2012-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-amazing-spider-man-2012-poster.jpg new file mode 100644 index 00000000..c1aec876 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-amazing-spider-man-2012-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-animatrix-2003-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-animatrix-2003-backdrop.jpg new file mode 100644 index 00000000..7d67ccb0 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-animatrix-2003-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-animatrix-2003-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-animatrix-2003-poster.jpg new file mode 100644 index 00000000..65553d47 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-animatrix-2003-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-avengers-2012-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-avengers-2012-backdrop.jpg new file mode 100644 index 00000000..83636f25 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-avengers-2012-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-avengers-2012-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-avengers-2012-poster.jpg new file mode 100644 index 00000000..be87067c Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-avengers-2012-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-boy-and-the-heron-2023-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-boy-and-the-heron-2023-backdrop.jpg new file mode 100644 index 00000000..a19dc77b Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-boy-and-the-heron-2023-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-boy-and-the-heron-2023-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-boy-and-the-heron-2023-poster.jpg new file mode 100644 index 00000000..094df64e Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-boy-and-the-heron-2023-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-breakfast-club-1985-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-breakfast-club-1985-backdrop.jpg new file mode 100644 index 00000000..9f979cd8 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-breakfast-club-1985-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-breakfast-club-1985-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-breakfast-club-1985-poster.jpg new file mode 100644 index 00000000..d1823120 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-breakfast-club-1985-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-dry-2021-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-dry-2021-backdrop.jpg new file mode 100644 index 00000000..2f016498 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-dry-2021-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-dry-2021-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-dry-2021-poster.jpg new file mode 100644 index 00000000..a0379f2f Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-dry-2021-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-girl-who-leapt-through-time-2006-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-girl-who-leapt-through-time-2006-backdrop.jpg new file mode 100644 index 00000000..6e920467 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-girl-who-leapt-through-time-2006-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-girl-who-leapt-through-time-2006-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-girl-who-leapt-through-time-2006-poster.jpg new file mode 100644 index 00000000..ded6cd2a Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-girl-who-leapt-through-time-2006-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-godfather-1972-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-godfather-1972-backdrop.jpg new file mode 100644 index 00000000..8fbb8f2a Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-godfather-1972-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-godfather-1972-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-godfather-1972-poster.jpg new file mode 100644 index 00000000..94111d9b Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-godfather-1972-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-godfather-part-ii-1974-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-godfather-part-ii-1974-backdrop.jpg new file mode 100644 index 00000000..6dbff534 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-godfather-part-ii-1974-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-godfather-part-ii-1974-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-godfather-part-ii-1974-poster.jpg new file mode 100644 index 00000000..1328c99e Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-godfather-part-ii-1974-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-godfather-part-iii-1990-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-godfather-part-iii-1990-backdrop.jpg new file mode 100644 index 00000000..d2080487 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-godfather-part-iii-1990-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-godfather-part-iii-1990-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-godfather-part-iii-1990-poster.jpg new file mode 100644 index 00000000..abbe5e99 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-godfather-part-iii-1990-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-great-gatsby-2013-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-great-gatsby-2013-backdrop.jpg new file mode 100644 index 00000000..7a4f8a32 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-great-gatsby-2013-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-great-gatsby-2013-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-great-gatsby-2013-poster.jpg new file mode 100644 index 00000000..e949fb3e Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-great-gatsby-2013-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-hours-2002-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-hours-2002-backdrop.jpg new file mode 100644 index 00000000..507a78c4 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-hours-2002-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-hours-2002-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-hours-2002-poster.jpg new file mode 100644 index 00000000..f87e919f Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-hours-2002-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-killing-of-a-sacred-deer-2017-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-killing-of-a-sacred-deer-2017-backdrop.jpg new file mode 100644 index 00000000..dd755ee8 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-killing-of-a-sacred-deer-2017-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-killing-of-a-sacred-deer-2017-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-killing-of-a-sacred-deer-2017-poster.jpg new file mode 100644 index 00000000..83464d4c Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-killing-of-a-sacred-deer-2017-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-last-of-the-mohicans-1992-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-last-of-the-mohicans-1992-backdrop.jpg new file mode 100644 index 00000000..3256d38d Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-last-of-the-mohicans-1992-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-last-of-the-mohicans-1992-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-last-of-the-mohicans-1992-poster.jpg new file mode 100644 index 00000000..950e9ba1 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-last-of-the-mohicans-1992-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-last-samurai-2003-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-last-samurai-2003-backdrop.jpg new file mode 100644 index 00000000..509af2d6 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-last-samurai-2003-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-last-samurai-2003-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-last-samurai-2003-poster.jpg new file mode 100644 index 00000000..0d58440c Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-last-samurai-2003-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-lighthouse-2019-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-lighthouse-2019-backdrop.jpg new file mode 100644 index 00000000..862c064a Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-lighthouse-2019-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-lighthouse-2019-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-lighthouse-2019-poster.jpg new file mode 100644 index 00000000..15f26533 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-lighthouse-2019-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-matrix-1999-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-matrix-1999-backdrop.jpg new file mode 100644 index 00000000..c3bf8f52 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-matrix-1999-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-matrix-1999-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-matrix-1999-poster.jpg new file mode 100644 index 00000000..c292dce5 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-matrix-1999-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-matrix-reloaded-2003-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-matrix-reloaded-2003-backdrop.jpg new file mode 100644 index 00000000..dcbf7b96 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-matrix-reloaded-2003-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-matrix-reloaded-2003-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-matrix-reloaded-2003-poster.jpg new file mode 100644 index 00000000..ab11f50f Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-matrix-reloaded-2003-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-matrix-resurrections-2021-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-matrix-resurrections-2021-backdrop.jpg new file mode 100644 index 00000000..99d85a40 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-matrix-resurrections-2021-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-matrix-resurrections-2021-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-matrix-resurrections-2021-poster.jpg new file mode 100644 index 00000000..4477ef1e Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-matrix-resurrections-2021-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-matrix-revolutions-2003-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-matrix-revolutions-2003-backdrop.jpg new file mode 100644 index 00000000..2cc02692 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-matrix-revolutions-2003-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-matrix-revolutions-2003-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-matrix-revolutions-2003-poster.jpg new file mode 100644 index 00000000..7236cc4e Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-matrix-revolutions-2003-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-patriot-2000-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-patriot-2000-backdrop.jpg new file mode 100644 index 00000000..ce7ce498 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-patriot-2000-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-patriot-2000-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-patriot-2000-poster.jpg new file mode 100644 index 00000000..c8582133 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-patriot-2000-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-secret-life-of-pets-2-2019-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-secret-life-of-pets-2-2019-backdrop.jpg new file mode 100644 index 00000000..698f4ad2 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-secret-life-of-pets-2-2019-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-secret-life-of-pets-2-2019-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-secret-life-of-pets-2-2019-poster.jpg new file mode 100644 index 00000000..fe50c2f8 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-secret-life-of-pets-2-2019-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-substance-2024-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-substance-2024-backdrop.jpg new file mode 100644 index 00000000..47a8b372 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-substance-2024-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-substance-2024-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-substance-2024-poster.jpg new file mode 100644 index 00000000..72accfed Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-substance-2024-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-tale-of-the-princess-kaguya-2013-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-tale-of-the-princess-kaguya-2013-backdrop.jpg new file mode 100644 index 00000000..b9157b91 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-tale-of-the-princess-kaguya-2013-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-tale-of-the-princess-kaguya-2013-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-tale-of-the-princess-kaguya-2013-poster.jpg new file mode 100644 index 00000000..882621cb Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-tale-of-the-princess-kaguya-2013-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-uprising-2026-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-uprising-2026-backdrop.jpg new file mode 100644 index 00000000..638eab3d Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-uprising-2026-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-uprising-2026-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-uprising-2026-poster.jpg new file mode 100644 index 00000000..21976395 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-uprising-2026-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-wild-robot-2024-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-wild-robot-2024-backdrop.jpg new file mode 100644 index 00000000..bb48460b Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-wild-robot-2024-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-wild-robot-2024-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-wild-robot-2024-poster.jpg new file mode 100644 index 00000000..0894754d Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-wild-robot-2024-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-wind-rises-2013-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-wind-rises-2013-backdrop.jpg new file mode 100644 index 00000000..2af40e28 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-wind-rises-2013-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-wind-rises-2013-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-wind-rises-2013-poster.jpg new file mode 100644 index 00000000..b7688c77 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-wind-rises-2013-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-witcher-nightmare-of-the-wolf-2021-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-witcher-nightmare-of-the-wolf-2021-backdrop.jpg new file mode 100644 index 00000000..cb5bfb28 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-witcher-nightmare-of-the-wolf-2021-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-witcher-nightmare-of-the-wolf-2021-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-witcher-nightmare-of-the-wolf-2021-poster.jpg new file mode 100644 index 00000000..6db33db1 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-witcher-nightmare-of-the-wolf-2021-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-witcher-sirens-of-the-deep-2025-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-witcher-sirens-of-the-deep-2025-backdrop.jpg new file mode 100644 index 00000000..aad8ea2c Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-witcher-sirens-of-the-deep-2025-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-the-witcher-sirens-of-the-deep-2025-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-the-witcher-sirens-of-the-deep-2025-poster.jpg new file mode 100644 index 00000000..8a54bc7f Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-the-witcher-sirens-of-the-deep-2025-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-thelma-louise-1991-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-thelma-louise-1991-backdrop.jpg new file mode 100644 index 00000000..a35327a9 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-thelma-louise-1991-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-thelma-louise-1991-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-thelma-louise-1991-poster.jpg new file mode 100644 index 00000000..5a4ff0ab Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-thelma-louise-1991-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-triangle-of-sadness-2022-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-triangle-of-sadness-2022-backdrop.jpg new file mode 100644 index 00000000..7c9b445d Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-triangle-of-sadness-2022-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-triangle-of-sadness-2022-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-triangle-of-sadness-2022-poster.jpg new file mode 100644 index 00000000..aee092c6 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-triangle-of-sadness-2022-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-tsotsi-2005-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-tsotsi-2005-backdrop.jpg new file mode 100644 index 00000000..e119de53 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-tsotsi-2005-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-tsotsi-2005-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-tsotsi-2005-poster.jpg new file mode 100644 index 00000000..362ddd82 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-tsotsi-2005-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-unknown-number-the-high-school-catfish-2025-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-unknown-number-the-high-school-catfish-2025-backdrop.jpg new file mode 100644 index 00000000..f456c01d Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-unknown-number-the-high-school-catfish-2025-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-unknown-number-the-high-school-catfish-2025-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-unknown-number-the-high-school-catfish-2025-poster.jpg new file mode 100644 index 00000000..e32685f3 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-unknown-number-the-high-school-catfish-2025-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-vice-2018-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-vice-2018-backdrop.jpg new file mode 100644 index 00000000..2918db3a Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-vice-2018-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-vice-2018-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-vice-2018-poster.jpg new file mode 100644 index 00000000..76855126 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-vice-2018-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-vicky-cristina-barcelona-2008-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-vicky-cristina-barcelona-2008-backdrop.jpg new file mode 100644 index 00000000..20eaa159 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-vicky-cristina-barcelona-2008-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-vicky-cristina-barcelona-2008-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-vicky-cristina-barcelona-2008-poster.jpg new file mode 100644 index 00000000..1351cad1 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-vicky-cristina-barcelona-2008-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-warriors-of-the-wind-1984-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-warriors-of-the-wind-1984-backdrop.jpg new file mode 100644 index 00000000..1fb22b96 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-warriors-of-the-wind-1984-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-warriors-of-the-wind-1984-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-warriors-of-the-wind-1984-poster.jpg new file mode 100644 index 00000000..918e424f Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-warriors-of-the-wind-1984-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-we-bury-the-dead-2026-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-we-bury-the-dead-2026-backdrop.jpg new file mode 100644 index 00000000..4a24720d Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-we-bury-the-dead-2026-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-we-bury-the-dead-2026-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-we-bury-the-dead-2026-poster.jpg new file mode 100644 index 00000000..a0d65904 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-we-bury-the-dead-2026-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-white-hot-the-rise-fall-of-abercrombie-fitch-2022-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-white-hot-the-rise-fall-of-abercrombie-fitch-2022-backdrop.jpg new file mode 100644 index 00000000..2f186030 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-white-hot-the-rise-fall-of-abercrombie-fitch-2022-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/movie-white-hot-the-rise-fall-of-abercrombie-fitch-2022-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/movie-white-hot-the-rise-fall-of-abercrombie-fitch-2022-poster.jpg new file mode 100644 index 00000000..3a123e80 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/movie-white-hot-the-rise-fall-of-abercrombie-fitch-2022-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-12-monkeys-2015-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-12-monkeys-2015-backdrop.jpg new file mode 100644 index 00000000..3d2825f5 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-12-monkeys-2015-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-12-monkeys-2015-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-12-monkeys-2015-poster.jpg new file mode 100644 index 00000000..96e3596e Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-12-monkeys-2015-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-56-days-2026-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-56-days-2026-backdrop.jpg new file mode 100644 index 00000000..dcda667a Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-56-days-2026-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-56-days-2026-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-56-days-2026-poster.jpg new file mode 100644 index 00000000..64ca587b Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-56-days-2026-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-90-day-fiance-before-the-90-days-2017-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-90-day-fiance-before-the-90-days-2017-backdrop.jpg new file mode 100644 index 00000000..24f66f2d Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-90-day-fiance-before-the-90-days-2017-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-90-day-fiance-before-the-90-days-2017-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-90-day-fiance-before-the-90-days-2017-poster.jpg new file mode 100644 index 00000000..3362da59 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-90-day-fiance-before-the-90-days-2017-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-90-day-fiance-the-other-way-2019-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-90-day-fiance-the-other-way-2019-backdrop.jpg new file mode 100644 index 00000000..b18891fd Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-90-day-fiance-the-other-way-2019-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-90-day-fiance-the-other-way-2019-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-90-day-fiance-the-other-way-2019-poster.jpg new file mode 100644 index 00000000..b4d256eb Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-90-day-fiance-the-other-way-2019-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-a-knight-of-the-seven-kingdoms-2026-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-a-knight-of-the-seven-kingdoms-2026-backdrop.jpg new file mode 100644 index 00000000..ce12fc71 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-a-knight-of-the-seven-kingdoms-2026-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-a-knight-of-the-seven-kingdoms-2026-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-a-knight-of-the-seven-kingdoms-2026-poster.jpg new file mode 100644 index 00000000..79f1351c Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-a-knight-of-the-seven-kingdoms-2026-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-absolutely-fabulous-1992-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-absolutely-fabulous-1992-backdrop.jpg new file mode 100644 index 00000000..2df9722d Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-absolutely-fabulous-1992-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-absolutely-fabulous-1992-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-absolutely-fabulous-1992-poster.jpg new file mode 100644 index 00000000..afd371a9 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-absolutely-fabulous-1992-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-accel-world-2012-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-accel-world-2012-backdrop.jpg new file mode 100644 index 00000000..1f5f6bdc Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-accel-world-2012-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-accel-world-2012-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-accel-world-2012-poster.jpg new file mode 100644 index 00000000..207b1dd1 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-accel-world-2012-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-accused-2023-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-accused-2023-backdrop.jpg new file mode 100644 index 00000000..d41c6397 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-accused-2023-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-accused-2023-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-accused-2023-poster.jpg new file mode 100644 index 00000000..74af010a Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-accused-2023-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-adults-2025-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-adults-2025-backdrop.jpg new file mode 100644 index 00000000..9cc0ffe2 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-adults-2025-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-adults-2025-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-adults-2025-poster.jpg new file mode 100644 index 00000000..53a7ba33 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-adults-2025-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-after-life-2019-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-after-life-2019-backdrop.jpg new file mode 100644 index 00000000..eaf9a4fa Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-after-life-2019-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-after-life-2019-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-after-life-2019-poster.jpg new file mode 100644 index 00000000..4fae4e33 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-after-life-2019-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-agatha-christie-s-marple-2004-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-agatha-christie-s-marple-2004-backdrop.jpg new file mode 100644 index 00000000..0c52f2e6 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-agatha-christie-s-marple-2004-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-agatha-christie-s-marple-2004-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-agatha-christie-s-marple-2004-poster.jpg new file mode 100644 index 00000000..ea7dda99 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-agatha-christie-s-marple-2004-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-ahsoka-2023-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-ahsoka-2023-backdrop.jpg new file mode 100644 index 00000000..7343fb32 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-ahsoka-2023-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-ahsoka-2023-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-ahsoka-2023-poster.jpg new file mode 100644 index 00000000..a29d6cb4 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-ahsoka-2023-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-all-american-homecoming-2022-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-all-american-homecoming-2022-backdrop.jpg new file mode 100644 index 00000000..09fbb9dc Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-all-american-homecoming-2022-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-all-american-homecoming-2022-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-all-american-homecoming-2022-poster.jpg new file mode 100644 index 00000000..09ccfc33 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-all-american-homecoming-2022-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-all-creatures-great-and-small-1978-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-all-creatures-great-and-small-1978-backdrop.jpg new file mode 100644 index 00000000..909ae1ba Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-all-creatures-great-and-small-1978-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-all-creatures-great-and-small-1978-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-all-creatures-great-and-small-1978-poster.jpg new file mode 100644 index 00000000..5b1666e6 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-all-creatures-great-and-small-1978-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-all-creatures-great-small-2020-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-all-creatures-great-small-2020-backdrop.jpg new file mode 100644 index 00000000..d2bcc88d Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-all-creatures-great-small-2020-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-all-creatures-great-small-2020-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-all-creatures-great-small-2020-poster.jpg new file mode 100644 index 00000000..b506f990 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-all-creatures-great-small-2020-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-american-crime-story-2016-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-american-crime-story-2016-backdrop.jpg new file mode 100644 index 00000000..84944d39 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-american-crime-story-2016-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-american-crime-story-2016-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-american-crime-story-2016-poster.jpg new file mode 100644 index 00000000..4086bf17 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-american-crime-story-2016-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-american-dad-2005-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-american-dad-2005-backdrop.jpg new file mode 100644 index 00000000..75a1948b Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-american-dad-2005-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-american-dad-2005-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-american-dad-2005-poster.jpg new file mode 100644 index 00000000..1f9e239a Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-american-dad-2005-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-american-gods-2017-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-american-gods-2017-backdrop.jpg new file mode 100644 index 00000000..4817c690 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-american-gods-2017-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-american-gods-2017-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-american-gods-2017-poster.jpg new file mode 100644 index 00000000..c02c46da Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-american-gods-2017-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-american-housewife-2016-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-american-housewife-2016-backdrop.jpg new file mode 100644 index 00000000..bf758ae0 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-american-housewife-2016-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-american-housewife-2016-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-american-housewife-2016-poster.jpg new file mode 100644 index 00000000..5f92bf05 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-american-housewife-2016-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-american-primeval-2025-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-american-primeval-2025-backdrop.jpg new file mode 100644 index 00000000..c3bfe5e1 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-american-primeval-2025-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-american-primeval-2025-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-american-primeval-2025-poster.jpg new file mode 100644 index 00000000..7f10e6aa Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-american-primeval-2025-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-and-just-like-that-2021-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-and-just-like-that-2021-backdrop.jpg new file mode 100644 index 00000000..59c981e5 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-and-just-like-that-2021-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-and-just-like-that-2021-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-and-just-like-that-2021-poster.jpg new file mode 100644 index 00000000..9904c74b Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-and-just-like-that-2021-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-andor-2022-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-andor-2022-backdrop.jpg new file mode 100644 index 00000000..a874a0c0 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-andor-2022-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-andor-2022-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-andor-2022-poster.jpg new file mode 100644 index 00000000..df5dc498 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-andor-2022-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-andromeda-2000-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-andromeda-2000-backdrop.jpg new file mode 100644 index 00000000..199249d7 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-andromeda-2000-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-andromeda-2000-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-andromeda-2000-poster.jpg new file mode 100644 index 00000000..67b766b0 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-andromeda-2000-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-anne-with-an-e-2017-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-anne-with-an-e-2017-backdrop.jpg new file mode 100644 index 00000000..15e325c9 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-anne-with-an-e-2017-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-anne-with-an-e-2017-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-anne-with-an-e-2017-poster.jpg new file mode 100644 index 00000000..55d443b4 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-anne-with-an-e-2017-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-aqua-teen-hunger-force-2000-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-aqua-teen-hunger-force-2000-backdrop.jpg new file mode 100644 index 00000000..d0e37959 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-aqua-teen-hunger-force-2000-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-aqua-teen-hunger-force-2000-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-aqua-teen-hunger-force-2000-poster.jpg new file mode 100644 index 00000000..e0c97321 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-aqua-teen-hunger-force-2000-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-arifureta-from-commonplace-to-world-s-strongest-2019-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-arifureta-from-commonplace-to-world-s-strongest-2019-backdrop.jpg new file mode 100644 index 00000000..9300e20f Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-arifureta-from-commonplace-to-world-s-strongest-2019-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-arifureta-from-commonplace-to-world-s-strongest-2019-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-arifureta-from-commonplace-to-world-s-strongest-2019-poster.jpg new file mode 100644 index 00000000..6a8f9a24 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-arifureta-from-commonplace-to-world-s-strongest-2019-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-arrested-development-2003-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-arrested-development-2003-backdrop.jpg new file mode 100644 index 00000000..ecb7e8cd Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-arrested-development-2003-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-arrested-development-2003-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-arrested-development-2003-poster.jpg new file mode 100644 index 00000000..02b0dd43 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-arrested-development-2003-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-arrow-2012-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-arrow-2012-backdrop.jpg new file mode 100644 index 00000000..b32f5aca Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-arrow-2012-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-arrow-2012-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-arrow-2012-poster.jpg new file mode 100644 index 00000000..adee3bb7 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-arrow-2012-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-atlantis-2013-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-atlantis-2013-backdrop.jpg new file mode 100644 index 00000000..39ecc0d6 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-atlantis-2013-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-atlantis-2013-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-atlantis-2013-poster.jpg new file mode 100644 index 00000000..5cd5f098 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-atlantis-2013-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-avatar-seven-havens-2026-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-avatar-seven-havens-2026-backdrop.jpg new file mode 100644 index 00000000..dacfd59e Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-avatar-seven-havens-2026-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-avatar-seven-havens-2026-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-avatar-seven-havens-2026-poster.jpg new file mode 100644 index 00000000..61f459c6 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-avatar-seven-havens-2026-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-avatar-the-last-airbender-2005-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-avatar-the-last-airbender-2005-backdrop.jpg new file mode 100644 index 00000000..f60120da Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-avatar-the-last-airbender-2005-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-avatar-the-last-airbender-2005-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-avatar-the-last-airbender-2005-poster.jpg new file mode 100644 index 00000000..6f99263a Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-avatar-the-last-airbender-2005-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-avatar-the-last-airbender-2024-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-avatar-the-last-airbender-2024-backdrop.jpg new file mode 100644 index 00000000..ad740218 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-avatar-the-last-airbender-2024-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-avatar-the-last-airbender-2024-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-avatar-the-last-airbender-2024-poster.jpg new file mode 100644 index 00000000..9d46c2ff Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-avatar-the-last-airbender-2024-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-awkward-2011-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-awkward-2011-backdrop.jpg new file mode 100644 index 00000000..1ca5e56c Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-awkward-2011-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-awkward-2011-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-awkward-2011-poster.jpg new file mode 100644 index 00000000..8da6e8e2 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-awkward-2011-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-b-b-vol-liefde-2021-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-b-b-vol-liefde-2021-backdrop.jpg new file mode 100644 index 00000000..57ece15e Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-b-b-vol-liefde-2021-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-b-b-vol-liefde-2021-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-b-b-vol-liefde-2021-poster.jpg new file mode 100644 index 00000000..a12ac33e Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-b-b-vol-liefde-2021-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-back-to-life-2019-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-back-to-life-2019-backdrop.jpg new file mode 100644 index 00000000..7ffef681 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-back-to-life-2019-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-back-to-life-2019-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-back-to-life-2019-poster.jpg new file mode 100644 index 00000000..b00730a8 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-back-to-life-2019-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-ballers-2015-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-ballers-2015-backdrop.jpg new file mode 100644 index 00000000..c4685e84 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-ballers-2015-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-ballers-2015-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-ballers-2015-poster.jpg new file mode 100644 index 00000000..f1c18f69 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-ballers-2015-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-baretta-1975-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-baretta-1975-backdrop.jpg new file mode 100644 index 00000000..4d61dfbe Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-baretta-1975-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-baretta-1975-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-baretta-1975-poster.jpg new file mode 100644 index 00000000..ceef0c9d Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-baretta-1975-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-based-on-a-true-story-2023-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-based-on-a-true-story-2023-backdrop.jpg new file mode 100644 index 00000000..b59c0e58 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-based-on-a-true-story-2023-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-based-on-a-true-story-2023-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-based-on-a-true-story-2023-poster.jpg new file mode 100644 index 00000000..386d3d90 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-based-on-a-true-story-2023-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-batman-the-brave-and-the-bold-2008-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-batman-the-brave-and-the-bold-2008-backdrop.jpg new file mode 100644 index 00000000..69e567cb Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-batman-the-brave-and-the-bold-2008-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-batman-the-brave-and-the-bold-2008-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-batman-the-brave-and-the-bold-2008-poster.jpg new file mode 100644 index 00000000..a864f50c Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-batman-the-brave-and-the-bold-2008-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-batwoman-2019-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-batwoman-2019-backdrop.jpg new file mode 100644 index 00000000..d04a1a20 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-batwoman-2019-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-batwoman-2019-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-batwoman-2019-poster.jpg new file mode 100644 index 00000000..7ca86592 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-batwoman-2019-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-beat-bobby-flay-2013-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-beat-bobby-flay-2013-backdrop.jpg new file mode 100644 index 00000000..e6c36ddd Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-beat-bobby-flay-2013-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-beat-bobby-flay-2013-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-beat-bobby-flay-2013-poster.jpg new file mode 100644 index 00000000..fe11122d Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-beat-bobby-flay-2013-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-benson-1979-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-benson-1979-backdrop.jpg new file mode 100644 index 00000000..32bd63a9 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-benson-1979-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-benson-1979-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-benson-1979-poster.jpg new file mode 100644 index 00000000..fe264c76 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-benson-1979-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-big-boys-2022-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-big-boys-2022-backdrop.jpg new file mode 100644 index 00000000..c7c91e2d Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-big-boys-2022-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-big-boys-2022-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-big-boys-2022-poster.jpg new file mode 100644 index 00000000..93e1e86a Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-big-boys-2022-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-big-city-greens-2018-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-big-city-greens-2018-backdrop.jpg new file mode 100644 index 00000000..381774e7 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-big-city-greens-2018-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-big-city-greens-2018-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-big-city-greens-2018-poster.jpg new file mode 100644 index 00000000..ef024946 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-big-city-greens-2018-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-big-mouth-2017-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-big-mouth-2017-backdrop.jpg new file mode 100644 index 00000000..760e0262 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-big-mouth-2017-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-big-mouth-2017-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-big-mouth-2017-poster.jpg new file mode 100644 index 00000000..4f535fec Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-big-mouth-2017-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-black-ish-2014-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-black-ish-2014-backdrop.jpg new file mode 100644 index 00000000..967fb621 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-black-ish-2014-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-black-ish-2014-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-black-ish-2014-poster.jpg new file mode 100644 index 00000000..97c885ad Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-black-ish-2014-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-black-lightning-2018-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-black-lightning-2018-backdrop.jpg new file mode 100644 index 00000000..3a0854c9 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-black-lightning-2018-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-black-lightning-2018-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-black-lightning-2018-poster.jpg new file mode 100644 index 00000000..733f5cbc Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-black-lightning-2018-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-black-rabbit-2025-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-black-rabbit-2025-backdrop.jpg new file mode 100644 index 00000000..48df9d46 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-black-rabbit-2025-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-black-rabbit-2025-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-black-rabbit-2025-poster.jpg new file mode 100644 index 00000000..3ff9aeed Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-black-rabbit-2025-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-bleach-2004-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-bleach-2004-backdrop.jpg new file mode 100644 index 00000000..ef8eaaa3 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-bleach-2004-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-bleach-2004-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-bleach-2004-poster.jpg new file mode 100644 index 00000000..b233e033 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-bleach-2004-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-blue-planet-ii-2017-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-blue-planet-ii-2017-backdrop.jpg new file mode 100644 index 00000000..8175e568 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-blue-planet-ii-2017-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-blue-planet-ii-2017-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-blue-planet-ii-2017-poster.jpg new file mode 100644 index 00000000..5e193ab2 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-blue-planet-ii-2017-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-brassic-2019-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-brassic-2019-backdrop.jpg new file mode 100644 index 00000000..86d3d806 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-brassic-2019-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-brassic-2019-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-brassic-2019-poster.jpg new file mode 100644 index 00000000..73b77df2 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-brassic-2019-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-breaking-bad-2008-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-breaking-bad-2008-backdrop.jpg new file mode 100644 index 00000000..e4ac4ae3 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-breaking-bad-2008-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-breaking-bad-2008-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-breaking-bad-2008-poster.jpg new file mode 100644 index 00000000..c4f7217c Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-breaking-bad-2008-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-breeders-2020-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-breeders-2020-backdrop.jpg new file mode 100644 index 00000000..9d95ba77 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-breeders-2020-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-breeders-2020-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-breeders-2020-poster.jpg new file mode 100644 index 00000000..49c61208 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-breeders-2020-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-californication-2007-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-californication-2007-backdrop.jpg new file mode 100644 index 00000000..ffff979b Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-californication-2007-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-californication-2007-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-californication-2007-poster.jpg new file mode 100644 index 00000000..38428e76 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-californication-2007-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-call-the-midwife-2012-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-call-the-midwife-2012-backdrop.jpg new file mode 100644 index 00000000..1ee28b8d Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-call-the-midwife-2012-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-call-the-midwife-2012-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-call-the-midwife-2012-poster.jpg new file mode 100644 index 00000000..466a1a7b Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-call-the-midwife-2012-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-chainsaw-man-2022-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-chainsaw-man-2022-backdrop.jpg new file mode 100644 index 00000000..f1fbee3e Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-chainsaw-man-2022-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-chainsaw-man-2022-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-chainsaw-man-2022-poster.jpg new file mode 100644 index 00000000..a3779772 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-chainsaw-man-2022-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-chernobyl-2019-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-chernobyl-2019-backdrop.jpg new file mode 100644 index 00000000..818a6665 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-chernobyl-2019-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-chernobyl-2019-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-chernobyl-2019-poster.jpg new file mode 100644 index 00000000..7664f4b0 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-chernobyl-2019-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-community-2009-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-community-2009-backdrop.jpg new file mode 100644 index 00000000..8deb8d5c Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-community-2009-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-community-2009-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-community-2009-poster.jpg new file mode 100644 index 00000000..e75072ae Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-community-2009-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-cyberpunk-edgerunners-2022-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-cyberpunk-edgerunners-2022-backdrop.jpg new file mode 100644 index 00000000..2920cf5a Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-cyberpunk-edgerunners-2022-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-cyberpunk-edgerunners-2022-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-cyberpunk-edgerunners-2022-poster.jpg new file mode 100644 index 00000000..8d338532 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-cyberpunk-edgerunners-2022-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-dan-da-dan-2024-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-dan-da-dan-2024-backdrop.jpg new file mode 100644 index 00000000..0cbc4b07 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-dan-da-dan-2024-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-dan-da-dan-2024-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-dan-da-dan-2024-poster.jpg new file mode 100644 index 00000000..f42cec16 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-dan-da-dan-2024-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-dead-end-job-2026-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-dead-end-job-2026-backdrop.jpg new file mode 100644 index 00000000..3525c2fc Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-dead-end-job-2026-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-dead-end-job-2026-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-dead-end-job-2026-poster.jpg new file mode 100644 index 00000000..f5546d88 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-dead-end-job-2026-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-derry-girls-2018-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-derry-girls-2018-backdrop.jpg new file mode 100644 index 00000000..76fae98c Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-derry-girls-2018-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-derry-girls-2018-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-derry-girls-2018-poster.jpg new file mode 100644 index 00000000..56a8cd79 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-derry-girls-2018-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-desperate-housewives-2004-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-desperate-housewives-2004-backdrop.jpg new file mode 100644 index 00000000..40ec3001 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-desperate-housewives-2004-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-desperate-housewives-2004-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-desperate-housewives-2004-poster.jpg new file mode 100644 index 00000000..0c4c38bc Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-desperate-housewives-2004-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-dopesick-2021-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-dopesick-2021-backdrop.jpg new file mode 100644 index 00000000..8d9f2727 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-dopesick-2021-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-dopesick-2021-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-dopesick-2021-poster.jpg new file mode 100644 index 00000000..ac0b5900 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-dopesick-2021-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-dr-stone-2019-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-dr-stone-2019-backdrop.jpg new file mode 100644 index 00000000..5ac396b6 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-dr-stone-2019-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-dr-stone-2019-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-dr-stone-2019-poster.jpg new file mode 100644 index 00000000..0783ed8c Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-dr-stone-2019-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-dune-prophecy-2024-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-dune-prophecy-2024-backdrop.jpg new file mode 100644 index 00000000..5b940b9f Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-dune-prophecy-2024-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-dune-prophecy-2024-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-dune-prophecy-2024-poster.jpg new file mode 100644 index 00000000..eef2a4f3 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-dune-prophecy-2024-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-east-of-eden-2026-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-east-of-eden-2026-backdrop.jpg new file mode 100644 index 00000000..89dfafbf Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-east-of-eden-2026-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-east-of-eden-2026-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-east-of-eden-2026-poster.jpg new file mode 100644 index 00000000..6f06f2c9 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-east-of-eden-2026-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-etoile-2025-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-etoile-2025-backdrop.jpg new file mode 100644 index 00000000..a8e9576c Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-etoile-2025-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-etoile-2025-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-etoile-2025-poster.jpg new file mode 100644 index 00000000..f078bc89 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-etoile-2025-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-fallout-2024-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-fallout-2024-backdrop.jpg new file mode 100644 index 00000000..c9d6a190 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-fallout-2024-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-fallout-2024-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-fallout-2024-poster.jpg new file mode 100644 index 00000000..5e87a07d Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-fallout-2024-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-fawlty-towers-1975-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-fawlty-towers-1975-backdrop.jpg new file mode 100644 index 00000000..2c9c258f Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-fawlty-towers-1975-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-fawlty-towers-1975-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-fawlty-towers-1975-poster.jpg new file mode 100644 index 00000000..53b406da Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-fawlty-towers-1975-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-firefly-2002-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-firefly-2002-backdrop.jpg new file mode 100644 index 00000000..fa3b7604 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-firefly-2002-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-firefly-2002-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-firefly-2002-poster.jpg new file mode 100644 index 00000000..9ad599b3 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-firefly-2002-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-fleabag-2016-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-fleabag-2016-backdrop.jpg new file mode 100644 index 00000000..e5a83462 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-fleabag-2016-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-fleabag-2016-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-fleabag-2016-poster.jpg new file mode 100644 index 00000000..bd3ccea5 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-fleabag-2016-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-frieren-beyond-journey-s-end-2023-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-frieren-beyond-journey-s-end-2023-backdrop.jpg new file mode 100644 index 00000000..a42a2c94 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-frieren-beyond-journey-s-end-2023-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-frieren-beyond-journey-s-end-2023-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-frieren-beyond-journey-s-end-2023-poster.jpg new file mode 100644 index 00000000..cb94104e Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-frieren-beyond-journey-s-end-2023-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-fullmetal-alchemist-brotherhood-2009-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-fullmetal-alchemist-brotherhood-2009-backdrop.jpg new file mode 100644 index 00000000..91ce4909 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-fullmetal-alchemist-brotherhood-2009-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-fullmetal-alchemist-brotherhood-2009-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-fullmetal-alchemist-brotherhood-2009-poster.jpg new file mode 100644 index 00000000..935e4944 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-fullmetal-alchemist-brotherhood-2009-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-gilmore-girls-2000-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-gilmore-girls-2000-backdrop.jpg new file mode 100644 index 00000000..b42ba953 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-gilmore-girls-2000-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-gilmore-girls-2000-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-gilmore-girls-2000-poster.jpg new file mode 100644 index 00000000..23b668cb Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-gilmore-girls-2000-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-ginny-georgia-2021-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-ginny-georgia-2021-backdrop.jpg new file mode 100644 index 00000000..1271d577 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-ginny-georgia-2021-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-ginny-georgia-2021-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-ginny-georgia-2021-poster.jpg new file mode 100644 index 00000000..1fca7bf6 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-ginny-georgia-2021-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-gravity-falls-2012-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-gravity-falls-2012-backdrop.jpg new file mode 100644 index 00000000..b9ef40fa Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-gravity-falls-2012-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-gravity-falls-2012-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-gravity-falls-2012-poster.jpg new file mode 100644 index 00000000..41c051e0 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-gravity-falls-2012-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-hellcats-2010-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-hellcats-2010-backdrop.jpg new file mode 100644 index 00000000..38e856b0 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-hellcats-2010-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-hellcats-2010-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-hellcats-2010-poster.jpg new file mode 100644 index 00000000..2427108b Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-hellcats-2010-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-high-potential-2024-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-high-potential-2024-backdrop.jpg new file mode 100644 index 00000000..66bb4d00 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-high-potential-2024-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-high-potential-2024-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-high-potential-2024-poster.jpg new file mode 100644 index 00000000..00a07ca7 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-high-potential-2024-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-how-to-change-your-mind-2022-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-how-to-change-your-mind-2022-backdrop.jpg new file mode 100644 index 00000000..9e4171e3 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-how-to-change-your-mind-2022-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-how-to-change-your-mind-2022-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-how-to-change-your-mind-2022-poster.jpg new file mode 100644 index 00000000..e80c7999 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-how-to-change-your-mind-2022-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-how-to-get-to-heaven-from-belfast-2026-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-how-to-get-to-heaven-from-belfast-2026-backdrop.jpg new file mode 100644 index 00000000..a605895b Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-how-to-get-to-heaven-from-belfast-2026-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-how-to-get-to-heaven-from-belfast-2026-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-how-to-get-to-heaven-from-belfast-2026-poster.jpg new file mode 100644 index 00000000..eb5fc487 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-how-to-get-to-heaven-from-belfast-2026-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-interrogation-2020-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-interrogation-2020-backdrop.jpg new file mode 100644 index 00000000..c347e016 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-interrogation-2020-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-interrogation-2020-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-interrogation-2020-poster.jpg new file mode 100644 index 00000000..f3a21b66 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-interrogation-2020-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-invincible-2021-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-invincible-2021-backdrop.jpg new file mode 100644 index 00000000..e8120387 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-invincible-2021-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-invincible-2021-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-invincible-2021-poster.jpg new file mode 100644 index 00000000..c278e37e Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-invincible-2021-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-it-s-always-sunny-in-philadelphia-2005-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-it-s-always-sunny-in-philadelphia-2005-backdrop.jpg new file mode 100644 index 00000000..cbdc6525 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-it-s-always-sunny-in-philadelphia-2005-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-it-s-always-sunny-in-philadelphia-2005-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-it-s-always-sunny-in-philadelphia-2005-poster.jpg new file mode 100644 index 00000000..652923ee Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-it-s-always-sunny-in-philadelphia-2005-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-jujutsu-kaisen-2020-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-jujutsu-kaisen-2020-backdrop.jpg new file mode 100644 index 00000000..79e7d6bd Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-jujutsu-kaisen-2020-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-jujutsu-kaisen-2020-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-jujutsu-kaisen-2020-poster.jpg new file mode 100644 index 00000000..45f5ff22 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-jujutsu-kaisen-2020-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-kaiju-no-8-2024-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-kaiju-no-8-2024-backdrop.jpg new file mode 100644 index 00000000..480b2a8e Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-kaiju-no-8-2024-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-kaiju-no-8-2024-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-kaiju-no-8-2024-poster.jpg new file mode 100644 index 00000000..062496fa Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-kaiju-no-8-2024-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-landman-2024-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-landman-2024-backdrop.jpg new file mode 100644 index 00000000..009fbfc2 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-landman-2024-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-landman-2024-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-landman-2024-poster.jpg new file mode 100644 index 00000000..3d348798 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-landman-2024-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-last-samurai-standing-2025-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-last-samurai-standing-2025-backdrop.jpg new file mode 100644 index 00000000..c85a210b Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-last-samurai-standing-2025-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-last-samurai-standing-2025-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-last-samurai-standing-2025-poster.jpg new file mode 100644 index 00000000..a9789909 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-last-samurai-standing-2025-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-lord-of-the-flies-2026-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-lord-of-the-flies-2026-backdrop.jpg new file mode 100644 index 00000000..1249a8f3 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-lord-of-the-flies-2026-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-lord-of-the-flies-2026-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-lord-of-the-flies-2026-poster.jpg new file mode 100644 index 00000000..b1229ed8 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-lord-of-the-flies-2026-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-love-death-robots-2019-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-love-death-robots-2019-backdrop.jpg new file mode 100644 index 00000000..3694bfc4 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-love-death-robots-2019-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-love-death-robots-2019-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-love-death-robots-2019-poster.jpg new file mode 100644 index 00000000..08449aa9 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-love-death-robots-2019-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-love-is-blind-2020-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-love-is-blind-2020-backdrop.jpg new file mode 100644 index 00000000..5cea5b52 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-love-is-blind-2020-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-love-is-blind-2020-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-love-is-blind-2020-poster.jpg new file mode 100644 index 00000000..62ccf357 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-love-is-blind-2020-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-maniac-2018-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-maniac-2018-backdrop.jpg new file mode 100644 index 00000000..9d46de1d Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-maniac-2018-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-maniac-2018-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-maniac-2018-poster.jpg new file mode 100644 index 00000000..a1985217 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-maniac-2018-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-maximum-pleasure-guaranteed-2026-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-maximum-pleasure-guaranteed-2026-backdrop.jpg new file mode 100644 index 00000000..20748a70 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-maximum-pleasure-guaranteed-2026-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-maximum-pleasure-guaranteed-2026-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-maximum-pleasure-guaranteed-2026-poster.jpg new file mode 100644 index 00000000..abe1b2e2 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-maximum-pleasure-guaranteed-2026-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-minx-2022-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-minx-2022-backdrop.jpg new file mode 100644 index 00000000..bd459fde Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-minx-2022-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-minx-2022-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-minx-2022-poster.jpg new file mode 100644 index 00000000..a8797fe5 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-minx-2022-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-mussolini-son-of-the-century-2024-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-mussolini-son-of-the-century-2024-backdrop.jpg new file mode 100644 index 00000000..21674163 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-mussolini-son-of-the-century-2024-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-mussolini-son-of-the-century-2024-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-mussolini-son-of-the-century-2024-poster.jpg new file mode 100644 index 00000000..925d5c49 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-mussolini-son-of-the-century-2024-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-my-hero-academia-2016-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-my-hero-academia-2016-backdrop.jpg new file mode 100644 index 00000000..f5b2f94c Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-my-hero-academia-2016-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-my-hero-academia-2016-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-my-hero-academia-2016-poster.jpg new file mode 100644 index 00000000..cf571b93 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-my-hero-academia-2016-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-neon-genesis-evangelion-1995-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-neon-genesis-evangelion-1995-backdrop.jpg new file mode 100644 index 00000000..d80e17c1 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-neon-genesis-evangelion-1995-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-neon-genesis-evangelion-1995-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-neon-genesis-evangelion-1995-poster.jpg new file mode 100644 index 00000000..f86cd698 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-neon-genesis-evangelion-1995-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-norsemen-2016-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-norsemen-2016-backdrop.jpg new file mode 100644 index 00000000..58805e67 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-norsemen-2016-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-norsemen-2016-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-norsemen-2016-poster.jpg new file mode 100644 index 00000000..e7206b88 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-norsemen-2016-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-offspring-2010-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-offspring-2010-backdrop.jpg new file mode 100644 index 00000000..bc22e11a Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-offspring-2010-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-offspring-2010-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-offspring-2010-poster.jpg new file mode 100644 index 00000000..ae305043 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-offspring-2010-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-one-piece-2023-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-one-piece-2023-backdrop.jpg new file mode 100644 index 00000000..3fa4a282 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-one-piece-2023-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-one-piece-2023-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-one-piece-2023-poster.jpg new file mode 100644 index 00000000..286e6260 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-one-piece-2023-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-only-murders-in-the-building-2021-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-only-murders-in-the-building-2021-backdrop.jpg new file mode 100644 index 00000000..0b768d54 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-only-murders-in-the-building-2021-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-only-murders-in-the-building-2021-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-only-murders-in-the-building-2021-poster.jpg new file mode 100644 index 00000000..a6b147a4 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-only-murders-in-the-building-2021-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-outlander-blood-of-my-blood-2025-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-outlander-blood-of-my-blood-2025-backdrop.jpg new file mode 100644 index 00000000..23a242da Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-outlander-blood-of-my-blood-2025-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-outlander-blood-of-my-blood-2025-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-outlander-blood-of-my-blood-2025-poster.jpg new file mode 100644 index 00000000..930943b0 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-outlander-blood-of-my-blood-2025-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-over-the-garden-wall-2014-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-over-the-garden-wall-2014-backdrop.jpg new file mode 100644 index 00000000..43ac4428 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-over-the-garden-wall-2014-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-over-the-garden-wall-2014-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-over-the-garden-wall-2014-poster.jpg new file mode 100644 index 00000000..34b1ed84 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-over-the-garden-wall-2014-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-peaky-blinders-2013-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-peaky-blinders-2013-backdrop.jpg new file mode 100644 index 00000000..da587ec3 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-peaky-blinders-2013-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-peaky-blinders-2013-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-peaky-blinders-2013-poster.jpg new file mode 100644 index 00000000..25ceea88 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-peaky-blinders-2013-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-pluribus-2025-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-pluribus-2025-backdrop.jpg new file mode 100644 index 00000000..e64c0268 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-pluribus-2025-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-pluribus-2025-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-pluribus-2025-poster.jpg new file mode 100644 index 00000000..1ea4711b Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-pluribus-2025-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-record-of-ragnarok-2021-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-record-of-ragnarok-2021-backdrop.jpg new file mode 100644 index 00000000..d68a4060 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-record-of-ragnarok-2021-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-record-of-ragnarok-2021-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-record-of-ragnarok-2021-poster.jpg new file mode 100644 index 00000000..97d481d6 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-record-of-ragnarok-2021-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-rick-and-morty-2013-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-rick-and-morty-2013-backdrop.jpg new file mode 100644 index 00000000..1c4c0ffc Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-rick-and-morty-2013-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-rick-and-morty-2013-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-rick-and-morty-2013-poster.jpg new file mode 100644 index 00000000..b95c4890 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-rick-and-morty-2013-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-ripley-2024-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-ripley-2024-backdrop.jpg new file mode 100644 index 00000000..2e7399c1 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-ripley-2024-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-ripley-2024-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-ripley-2024-poster.jpg new file mode 100644 index 00000000..de0b32dd Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-ripley-2024-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-rivals-2024-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-rivals-2024-backdrop.jpg new file mode 100644 index 00000000..fe7025e4 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-rivals-2024-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-rivals-2024-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-rivals-2024-poster.jpg new file mode 100644 index 00000000..296ac67a Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-rivals-2024-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-rooster-2026-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-rooster-2026-backdrop.jpg new file mode 100644 index 00000000..f0f7a19e Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-rooster-2026-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-rooster-2026-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-rooster-2026-poster.jpg new file mode 100644 index 00000000..b698f8a6 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-rooster-2026-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-sakamoto-days-2025-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-sakamoto-days-2025-backdrop.jpg new file mode 100644 index 00000000..3186e89a Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-sakamoto-days-2025-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-sakamoto-days-2025-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-sakamoto-days-2025-poster.jpg new file mode 100644 index 00000000..ed9358b3 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-sakamoto-days-2025-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-say-nothing-2024-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-say-nothing-2024-backdrop.jpg new file mode 100644 index 00000000..445862dc Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-say-nothing-2024-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-say-nothing-2024-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-say-nothing-2024-poster.jpg new file mode 100644 index 00000000..cf50fc09 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-say-nothing-2024-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-secret-level-2024-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-secret-level-2024-backdrop.jpg new file mode 100644 index 00000000..54d2a13e Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-secret-level-2024-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-secret-level-2024-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-secret-level-2024-poster.jpg new file mode 100644 index 00000000..1b6e67f6 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-secret-level-2024-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-sentenced-to-be-a-hero-2025-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-sentenced-to-be-a-hero-2025-backdrop.jpg new file mode 100644 index 00000000..8d081a15 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-sentenced-to-be-a-hero-2025-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-sentenced-to-be-a-hero-2025-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-sentenced-to-be-a-hero-2025-poster.jpg new file mode 100644 index 00000000..2fb1c815 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-sentenced-to-be-a-hero-2025-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-severance-2022-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-severance-2022-backdrop.jpg new file mode 100644 index 00000000..a24c03bd Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-severance-2022-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-severance-2022-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-severance-2022-poster.jpg new file mode 100644 index 00000000..5a29d6f9 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-severance-2022-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-sex-and-the-city-1998-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-sex-and-the-city-1998-backdrop.jpg new file mode 100644 index 00000000..57beacc1 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-sex-and-the-city-1998-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-sex-and-the-city-1998-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-sex-and-the-city-1998-poster.jpg new file mode 100644 index 00000000..e546cc6d Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-sex-and-the-city-1998-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-silo-2023-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-silo-2023-backdrop.jpg new file mode 100644 index 00000000..bcc1c4b7 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-silo-2023-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-silo-2023-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-silo-2023-poster.jpg new file mode 100644 index 00000000..b493bc7b Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-silo-2023-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-six-feet-under-2001-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-six-feet-under-2001-backdrop.jpg new file mode 100644 index 00000000..4d2c02ad Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-six-feet-under-2001-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-six-feet-under-2001-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-six-feet-under-2001-poster.jpg new file mode 100644 index 00000000..559d6bd1 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-six-feet-under-2001-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-skymed-2022-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-skymed-2022-backdrop.jpg new file mode 100644 index 00000000..c7ec326b Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-skymed-2022-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-skymed-2022-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-skymed-2022-poster.jpg new file mode 100644 index 00000000..2540dad7 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-skymed-2022-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-so-you-think-you-can-dance-2008-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-so-you-think-you-can-dance-2008-backdrop.jpg new file mode 100644 index 00000000..feb777b2 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-so-you-think-you-can-dance-2008-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-so-you-think-you-can-dance-2008-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-so-you-think-you-can-dance-2008-poster.jpg new file mode 100644 index 00000000..a2e91496 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-so-you-think-you-can-dance-2008-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-south-park-1997-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-south-park-1997-backdrop.jpg new file mode 100644 index 00000000..cd0aec39 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-south-park-1997-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-south-park-1997-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-south-park-1997-poster.jpg new file mode 100644 index 00000000..d8568d54 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-south-park-1997-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-spy-x-family-2022-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-spy-x-family-2022-backdrop.jpg new file mode 100644 index 00000000..8764e6eb Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-spy-x-family-2022-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-spy-x-family-2022-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-spy-x-family-2022-poster.jpg new file mode 100644 index 00000000..fc326930 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-spy-x-family-2022-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-100-2014-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-100-2014-backdrop.jpg new file mode 100644 index 00000000..8d41cb22 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-100-2014-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-100-2014-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-100-2014-poster.jpg new file mode 100644 index 00000000..4d254cb0 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-100-2014-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-abbott-and-costello-show-1952-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-abbott-and-costello-show-1952-backdrop.jpg new file mode 100644 index 00000000..53e1d593 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-abbott-and-costello-show-1952-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-abbott-and-costello-show-1952-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-abbott-and-costello-show-1952-poster.jpg new file mode 100644 index 00000000..7d6768ea Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-abbott-and-costello-show-1952-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-afterparty-2022-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-afterparty-2022-backdrop.jpg new file mode 100644 index 00000000..72ca19db Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-afterparty-2022-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-afterparty-2022-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-afterparty-2022-poster.jpg new file mode 100644 index 00000000..9beacde2 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-afterparty-2022-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-baby-sitters-club-2020-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-baby-sitters-club-2020-backdrop.jpg new file mode 100644 index 00000000..d72d7c7e Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-baby-sitters-club-2020-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-baby-sitters-club-2020-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-baby-sitters-club-2020-poster.jpg new file mode 100644 index 00000000..7e4e7be2 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-baby-sitters-club-2020-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-bear-2022-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-bear-2022-backdrop.jpg new file mode 100644 index 00000000..9c9cca57 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-bear-2022-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-bear-2022-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-bear-2022-poster.jpg new file mode 100644 index 00000000..bbf0ab28 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-bear-2022-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-beast-in-me-2025-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-beast-in-me-2025-backdrop.jpg new file mode 100644 index 00000000..e7727a79 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-beast-in-me-2025-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-beast-in-me-2025-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-beast-in-me-2025-poster.jpg new file mode 100644 index 00000000..c78c5cf0 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-beast-in-me-2025-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-bold-type-2017-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-bold-type-2017-backdrop.jpg new file mode 100644 index 00000000..dd84deab Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-bold-type-2017-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-bold-type-2017-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-bold-type-2017-poster.jpg new file mode 100644 index 00000000..f663eb8b Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-bold-type-2017-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-boys-2019-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-boys-2019-backdrop.jpg new file mode 100644 index 00000000..a834d9fb Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-boys-2019-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-boys-2019-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-boys-2019-poster.jpg new file mode 100644 index 00000000..f67ff16f Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-boys-2019-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-capture-2019-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-capture-2019-backdrop.jpg new file mode 100644 index 00000000..42256fc1 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-capture-2019-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-capture-2019-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-capture-2019-poster.jpg new file mode 100644 index 00000000..a4039011 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-capture-2019-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-diplomat-2023-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-diplomat-2023-backdrop.jpg new file mode 100644 index 00000000..b25ab5f3 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-diplomat-2023-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-diplomat-2023-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-diplomat-2023-poster.jpg new file mode 100644 index 00000000..2eb48b62 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-diplomat-2023-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-durrells-2016-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-durrells-2016-backdrop.jpg new file mode 100644 index 00000000..3f8bdeec Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-durrells-2016-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-durrells-2016-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-durrells-2016-poster.jpg new file mode 100644 index 00000000..d29448bb Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-durrells-2016-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-final-problem-2026-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-final-problem-2026-backdrop.jpg new file mode 100644 index 00000000..61a310d8 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-final-problem-2026-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-final-problem-2026-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-final-problem-2026-poster.jpg new file mode 100644 index 00000000..f0332826 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-final-problem-2026-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-four-seasons-2025-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-four-seasons-2025-backdrop.jpg new file mode 100644 index 00000000..a358664c Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-four-seasons-2025-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-four-seasons-2025-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-four-seasons-2025-poster.jpg new file mode 100644 index 00000000..fd252088 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-four-seasons-2025-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-get-down-2016-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-get-down-2016-backdrop.jpg new file mode 100644 index 00000000..1de4a3ca Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-get-down-2016-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-get-down-2016-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-get-down-2016-poster.jpg new file mode 100644 index 00000000..7bb38abd Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-get-down-2016-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-hunting-wives-2025-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-hunting-wives-2025-backdrop.jpg new file mode 100644 index 00000000..d1758fef Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-hunting-wives-2025-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-hunting-wives-2025-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-hunting-wives-2025-poster.jpg new file mode 100644 index 00000000..034273eb Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-hunting-wives-2025-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-last-of-us-2023-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-last-of-us-2023-backdrop.jpg new file mode 100644 index 00000000..fa28e38d Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-last-of-us-2023-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-last-of-us-2023-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-last-of-us-2023-poster.jpg new file mode 100644 index 00000000..7da462da Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-last-of-us-2023-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-legend-of-korra-2012-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-legend-of-korra-2012-backdrop.jpg new file mode 100644 index 00000000..ab6086bd Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-legend-of-korra-2012-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-legend-of-korra-2012-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-legend-of-korra-2012-poster.jpg new file mode 100644 index 00000000..a7086d5e Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-legend-of-korra-2012-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-lord-of-the-rings-the-rings-of-power-2022-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-lord-of-the-rings-the-rings-of-power-2022-backdrop.jpg new file mode 100644 index 00000000..0b88fd4b Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-lord-of-the-rings-the-rings-of-power-2022-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-lord-of-the-rings-the-rings-of-power-2022-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-lord-of-the-rings-the-rings-of-power-2022-poster.jpg new file mode 100644 index 00000000..dc387fcd Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-lord-of-the-rings-the-rings-of-power-2022-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-lost-flowers-of-alice-hart-2023-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-lost-flowers-of-alice-hart-2023-backdrop.jpg new file mode 100644 index 00000000..c5d17d97 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-lost-flowers-of-alice-hart-2023-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-lost-flowers-of-alice-hart-2023-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-lost-flowers-of-alice-hart-2023-poster.jpg new file mode 100644 index 00000000..71407f36 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-lost-flowers-of-alice-hart-2023-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-night-manager-2016-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-night-manager-2016-backdrop.jpg new file mode 100644 index 00000000..327aa449 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-night-manager-2016-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-night-manager-2016-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-night-manager-2016-poster.jpg new file mode 100644 index 00000000..7ab2dbf5 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-night-manager-2016-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-pitt-2025-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-pitt-2025-backdrop.jpg new file mode 100644 index 00000000..b593444a Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-pitt-2025-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-pitt-2025-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-pitt-2025-poster.jpg new file mode 100644 index 00000000..ad1ec8ff Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-pitt-2025-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-tatami-galaxy-2010-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-tatami-galaxy-2010-backdrop.jpg new file mode 100644 index 00000000..a267b4cb Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-tatami-galaxy-2010-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-tatami-galaxy-2010-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-tatami-galaxy-2010-poster.jpg new file mode 100644 index 00000000..5324dc75 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-tatami-galaxy-2010-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-walk-in-2022-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-walk-in-2022-backdrop.jpg new file mode 100644 index 00000000..e37f8d5c Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-walk-in-2022-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-walk-in-2022-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-walk-in-2022-poster.jpg new file mode 100644 index 00000000..715d0c80 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-walk-in-2022-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-white-lotus-2021-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-white-lotus-2021-backdrop.jpg new file mode 100644 index 00000000..1f747323 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-white-lotus-2021-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-white-lotus-2021-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-white-lotus-2021-poster.jpg new file mode 100644 index 00000000..2f23d1fa Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-white-lotus-2021-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-witcher-2019-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-witcher-2019-backdrop.jpg new file mode 100644 index 00000000..acca834e Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-witcher-2019-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-the-witcher-2019-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-the-witcher-2019-poster.jpg new file mode 100644 index 00000000..f5eb8f59 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-the-witcher-2019-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-undercover-2019-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-undercover-2019-backdrop.jpg new file mode 100644 index 00000000..ad972c17 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-undercover-2019-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-undercover-2019-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-undercover-2019-poster.jpg new file mode 100644 index 00000000..8611eab4 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-undercover-2019-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-upload-2020-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-upload-2020-backdrop.jpg new file mode 100644 index 00000000..f350a49c Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-upload-2020-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-upload-2020-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-upload-2020-poster.jpg new file mode 100644 index 00000000..547d3b7a Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-upload-2020-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-vinland-saga-2019-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-vinland-saga-2019-backdrop.jpg new file mode 100644 index 00000000..9ae58ec4 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-vinland-saga-2019-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-vinland-saga-2019-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-vinland-saga-2019-poster.jpg new file mode 100644 index 00000000..7d0ba17a Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-vinland-saga-2019-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-widow-s-bay-2026-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-widow-s-bay-2026-backdrop.jpg new file mode 100644 index 00000000..03b1c335 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-widow-s-bay-2026-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-widow-s-bay-2026-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-widow-s-bay-2026-poster.jpg new file mode 100644 index 00000000..8c02a0d7 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-widow-s-bay-2026-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-yellowjackets-2021-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-yellowjackets-2021-backdrop.jpg new file mode 100644 index 00000000..960c2d23 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-yellowjackets-2021-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-yellowjackets-2021-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-yellowjackets-2021-poster.jpg new file mode 100644 index 00000000..c7e16a57 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-yellowjackets-2021-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-yu-yu-hakusho-1992-backdrop.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-yu-yu-hakusho-1992-backdrop.jpg new file mode 100644 index 00000000..ba65ca2d Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-yu-yu-hakusho-1992-backdrop.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/assets/tv-yu-yu-hakusho-1992-poster.jpg b/app/src/debug/assets/wolfskin-discover/assets/tv-yu-yu-hakusho-1992-poster.jpg new file mode 100644 index 00000000..f5843766 Binary files /dev/null and b/app/src/debug/assets/wolfskin-discover/assets/tv-yu-yu-hakusho-1992-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-discover/fixture.json b/app/src/debug/assets/wolfskin-discover/fixture.json new file mode 100644 index 00000000..450f0ece --- /dev/null +++ b/app/src/debug/assets/wolfskin-discover/fixture.json @@ -0,0 +1 @@ +{"schemaVersion": 1, "generatedAt": "2026-11-04T20:42:12.030108+00:00", "profiles": [{"key": "justin", "name": "Justin", "initial": "J", "color": "#62c4ff", "destinations": {"Home": {"kind": "home"}, "Movies": {"kind": "library", "collection": "movies"}, "Shows": {"kind": "library", "collection": "shows"}, "Anime": {"kind": "library", "collection": "anime"}, "Discover": {"kind": "discover", "collection": "discover"}}, "movies": [{"id": "movie-the-girl-who-leapt-through-time-2006", "title": "The Girl Who Leapt Through Time", "year": 2006, "type": "movie", "runtimeSeconds": 5886, "seasonCount": null, "communityRating": 7.772, "criticRating": 87, "genres": ["Fantasy", "Animation", "Drama"], "overview": "When 17-year-old Makoto Konno gains the ability to 'leap' backwards through time, she immediately sets about improving her grades and preventing personal mishaps. However, she soon realises that changing the past\u2026", "poster": "assets/movie-the-girl-who-leapt-through-time-2006-poster.jpg", "backdrop": "assets/movie-the-girl-who-leapt-through-time-2006-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T17:39:11.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 14069, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 87, "rtCriticsLabel": "Fresh", "rtAudience": 90, "rtAudienceLabel": "Upright", "imdb": 7.6, "imdbVotes": 76917, "tmdb": 7.775, "tmdbVotes": 2345}}, {"id": "movie-locke-2014", "title": "Locke", "year": 2014, "type": "movie", "runtimeSeconds": 5090, "seasonCount": null, "communityRating": 6.891, "criticRating": 91, "genres": ["Drama", "Thriller"], "overview": "Ivan Locke has worked hard to craft a good life for himself. Tonight, that life will collapse around him. On the eve of the biggest challenge of his career, Ivan receives a phone call that sets in motion a series of\u2026", "poster": "assets/movie-locke-2014-poster.jpg", "backdrop": "assets/movie-locke-2014-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T17:38:48.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 210479, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 90, "rtCriticsLabel": "Certified Fresh", "rtAudience": 72, "rtAudienceLabel": "Upright", "imdb": 7.1, "imdbVotes": 171265, "tmdb": 6.889, "tmdbVotes": 3088}}, {"id": "movie-look-who-s-back-2015", "title": "Look Who's Back", "year": 2015, "type": "movie", "runtimeSeconds": 6936, "seasonCount": null, "communityRating": 6.793, "criticRating": null, "genres": ["Comedy"], "overview": "When Adolf Hitler reawakens at the site of his former bunker in present-day Berlin, he is mistaken for a comedian and quickly becomes a media phenomenon.", "poster": "assets/movie-look-who-s-back-2015-poster.jpg", "backdrop": "assets/movie-look-who-s-back-2015-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:27:48.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 318917, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": 81, "rtAudienceLabel": "Upright", "imdb": 7, "imdbVotes": 54672, "tmdb": 6.796, "tmdbVotes": 1990}}, {"id": "movie-memories-1995", "title": "Memories", "year": 1995, "type": "movie", "runtimeSeconds": 6805, "seasonCount": null, "communityRating": 7.465, "criticRating": null, "genres": ["Fantasy", "Animation", "Science Fiction"], "overview": "Three back-to-back anime films by three different directors make up this sci-fi trilogy three years in the making.", "poster": "assets/movie-memories-1995-poster.jpg", "backdrop": "assets/movie-memories-1995-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-30T21:36:03.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 42994, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": 82, "rtAudienceLabel": "Upright", "imdb": 7.5, "imdbVotes": 26638, "tmdb": 7.459, "tmdbVotes": 622}}, {"id": "movie-reincarnated-2012", "title": "Reincarnated", "year": 2012, "type": "movie", "runtimeSeconds": 5759, "seasonCount": null, "communityRating": 6.145, "criticRating": 28, "genres": ["Music", "Documentary"], "overview": "Evolution as an artist is often times what separates legends from the more mundane. After being heavily influenced by his experience in Jamaica \u2013 and his subsequent name change from Snoop Dogg to Snoop Lion \u2013 the LBC\u2026", "poster": "assets/movie-reincarnated-2012-poster.jpg", "backdrop": "assets/movie-reincarnated-2012-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:27:26.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 123431, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 28, "rtCriticsLabel": "Rotten", "rtAudience": 41, "rtAudienceLabel": "Spilled", "imdb": 6.2, "imdbVotes": 2563, "tmdb": 6.1, "tmdbVotes": 69}}, {"id": "movie-shipwrecked-nightmare-at-sea-2026", "title": "Shipwrecked: Nightmare at Sea", "year": 2026, "type": "movie", "runtimeSeconds": 5385, "seasonCount": null, "communityRating": 7.181, "criticRating": null, "genres": ["Documentary"], "overview": "Never-before-seen footage and survivor accounts trace the 2012 shipwreck of a luxury cruise and the disaster that ensued, in this immersive documentary.", "poster": "assets/movie-shipwrecked-nightmare-at-sea-2026-poster.jpg", "backdrop": "assets/movie-shipwrecked-nightmare-at-sea-2026-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-20T20:11:43.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 1715492, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": 6.5, "imdbVotes": 5080, "tmdb": 7.172, "tmdbVotes": 87}}, {"id": "movie-steins-gate-the-movie-load-region-of-deja-vu-2013", "title": "Steins;Gate: The Movie - Load Region of D\u00e9j\u00e0 Vu", "year": 2013, "type": "movie", "runtimeSeconds": 5403, "seasonCount": null, "communityRating": 7.8, "criticRating": null, "genres": ["Animation", "Mystery", "Sci-Fi"], "overview": "After the events of the anime, Rintarou begins to feel the repercussions of extensive time travel, and eventually completely fades from reality. Kurisu, being the only companion to remember him, now must find a way\u2026", "poster": "assets/movie-steins-gate-the-movie-load-region-of-deja-vu-2013-poster.jpg", "backdrop": "assets/movie-steins-gate-the-movie-load-region-of-deja-vu-2013-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-22T14:55:54.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}], "shows": [{"id": "tv-andor-2022", "title": "Andor", "year": 2022, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 8.293, "criticRating": null, "genres": ["Sci-Fi & Fantasy", "Action & Adventure", "Drama"], "overview": "In an era filled with danger, deception and intrigue, Cassian Andor will discover the difference he can make in the struggle against the tyrannical Galactic Empire. He embarks on a path that is destined to turn him\u2026", "poster": "assets/tv-andor-2022-poster.jpg", "backdrop": "assets/tv-andor-2022-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0432996Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 83867, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 96, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.321, "tmdbVotes": 2312}}, {"id": "tv-the-boys-2019", "title": "The Boys", "year": 2019, "type": "tv", "runtimeSeconds": null, "seasonCount": 5, "communityRating": 8.443, "criticRating": null, "genres": ["Sci-Fi & Fantasy", "Action & Adventure"], "overview": "A group of vigilantes known informally as \u201cThe Boys\u201d set out to take down corrupt superheroes with no more than blue-collar grit and a willingness to fight dirty.", "poster": "assets/tv-the-boys-2019-poster.jpg", "backdrop": "assets/tv-the-boys-2019-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0410131Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 76479, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 92, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.4, "tmdbVotes": 13628}}, {"id": "tv-californication-2007", "title": "Californication", "year": 2007, "type": "tv", "runtimeSeconds": 1680, "seasonCount": 7, "communityRating": 7.632, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "A self-loathing, alcoholic writer attempts to repair his damaged relationships with his daughter and her mother while combating sex addiction, a budding drug problem, and the seeming inability to avoid making bad\u2026", "poster": "assets/tv-californication-2007-poster.jpg", "backdrop": "assets/tv-californication-2007-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0428037Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 1215, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 57, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.62, "tmdbVotes": 1406}}, {"id": "tv-firefly-2002", "title": "Firefly", "year": 2002, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 8.347, "criticRating": null, "genres": ["Drama", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "In the year 2517, the universe is a vast frontier where lawlessness reigns and adventure awaits! Join Captain Malcolm Reynolds and his ragtag crew aboard the spaceship Serenity as they navigate the fringes of\u2026", "poster": "assets/tv-firefly-2002-poster.jpg", "backdrop": "assets/tv-firefly-2002-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-20T11:40:08.4202369Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 1437, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 77, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.351, "tmdbVotes": 2550}}, {"id": "tv-fleabag-2016", "title": "Fleabag", "year": 2016, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 8.278, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "A portrait into the mind of a dry-witted, sexual, angry, porn-watching, grief-riddled woman, trying to make sense of the world. As she hurls herself headlong at modern living, Fleabag is thrown roughly up against the\u2026", "poster": "assets/tv-fleabag-2016-poster.jpg", "backdrop": "assets/tv-fleabag-2016-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T17:42:25.4848214Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 67070, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.3, "tmdbVotes": 1993}}, {"id": "tv-gravity-falls-2012", "title": "Gravity Falls", "year": 2012, "type": "tv", "runtimeSeconds": 1320, "seasonCount": 2, "communityRating": 8.619, "criticRating": null, "genres": ["Action & Adventure", "Animation", "Comedy"], "overview": "Twin brother and sister Dipper and Mabel Pines are in for an unexpected adventure when they spend the summer helping their great uncle Stan run a tourist trap in the mysterious town of Gravity Falls, Oregon.", "poster": "assets/tv-gravity-falls-2012-poster.jpg", "backdrop": "assets/tv-gravity-falls-2012-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0435827Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 40075, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.624, "tmdbVotes": 3647}}, {"id": "tv-invincible-2021", "title": "INVINCIBLE", "year": 2021, "type": "tv", "runtimeSeconds": null, "seasonCount": 4, "communityRating": 8.638, "criticRating": null, "genres": ["Animation", "Drama", "Sci-Fi & Fantasy"], "overview": "Mark Grayson is a normal teenager except for the fact that his father is the most powerful superhero on the planet. Shortly after his seventeenth birthday, Mark begins to develop powers of his own and enters into his\u2026", "poster": "assets/tv-invincible-2021-poster.jpg", "backdrop": "assets/tv-invincible-2021-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.040159Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 95557, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 99, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.6, "tmdbVotes": 6155}}, {"id": "tv-the-legend-of-korra-2012", "title": "The Legend of Korra", "year": 2012, "type": "tv", "runtimeSeconds": 1380, "seasonCount": 4, "communityRating": 8.2, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "Avatar Korra, a headstrong, rebellious, feisty young woman who continually challenges and breaks with tradition, is on her quest to become a fully realized Avatar. In this story, the Avatar struggles to find balance\u2026", "poster": "assets/tv-the-legend-of-korra-2012-poster.jpg", "backdrop": "assets/tv-the-legend-of-korra-2012-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-20T00:19:23.2494945Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 33880, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 88, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.221, "tmdbVotes": 2482}}, {"id": "tv-love-death-robots-2019", "title": "Love, Death & Robots", "year": 2019, "type": "tv", "runtimeSeconds": null, "seasonCount": 4, "communityRating": 8.205, "criticRating": null, "genres": ["Animation", "Sci-Fi & Fantasy"], "overview": "Terrifying creatures, wicked surprises and dark comedy converge in this NSFW anthology of animated stories presented by Tim Miller and David Fincher.", "poster": "assets/tv-love-death-robots-2019-poster.jpg", "backdrop": "assets/tv-love-death-robots-2019-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-20T11:31:20.8818757Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 86831, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 89, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.203, "tmdbVotes": 4238}}, {"id": "tv-norsemen-2016", "title": "Norsemen", "year": 2016, "type": "tv", "runtimeSeconds": 1800, "seasonCount": 3, "communityRating": 7.177, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "Norsemen is an epic and humorous drama series set in the Viking Age. The residents of an 8th-century Viking village experience political rivalry, social change and innovations that upend their culture and way of life.", "poster": "assets/tv-norsemen-2016-poster.jpg", "backdrop": "assets/tv-norsemen-2016-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.034182Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 68126, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.2, "tmdbVotes": 233}}, {"id": "tv-rick-and-morty-2013", "title": "Rick and Morty", "year": 2013, "type": "tv", "runtimeSeconds": null, "seasonCount": 9, "communityRating": 8.679, "criticRating": null, "genres": ["Animation", "Comedy", "Sci-Fi & Fantasy"], "overview": "Follows a sociopathic genius scientist who drags his inherently timid grandson on adventures across the universe.", "poster": "assets/tv-rick-and-morty-2013-poster.jpg", "backdrop": "assets/tv-rick-and-morty-2013-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0419581Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 60625, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 91, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.7, "tmdbVotes": 11548}}, {"id": "tv-secret-level-2024", "title": "Secret Level", "year": 2024, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.411, "criticRating": null, "genres": ["Animation", "Sci-Fi & Fantasy", "Action & Adventure"], "overview": "Adult animated series of original short stories which are set within the worlds of beloved video games. Each episode serves as a gateway to a new adventure, unlocking exciting worlds from beloved gaming classics and\u2026", "poster": "assets/tv-secret-level-2024-poster.jpg", "backdrop": "assets/tv-secret-level-2024-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0336417Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 261579, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 65, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.392, "tmdbVotes": 612}}, {"id": "tv-silo-2023", "title": "Silo", "year": 2023, "type": "tv", "runtimeSeconds": null, "seasonCount": 3, "communityRating": 8.178, "criticRating": null, "genres": ["Sci-Fi & Fantasy", "Drama"], "overview": "In a ruined and toxic future, thousands live in a giant silo deep underground. After its sheriff breaks a cardinal rule and residents die mysteriously, engineer Juliette starts to uncover shocking secrets and the\u2026", "poster": "assets/tv-silo-2023-poster.jpg", "backdrop": "assets/tv-silo-2023-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0346526Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 125988, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 92, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.232, "tmdbVotes": 2881}}, {"id": "tv-south-park-1997", "title": "South Park", "year": 1997, "type": "tv", "runtimeSeconds": null, "seasonCount": 29, "communityRating": 8.336, "criticRating": null, "genres": ["Animation", "Comedy"], "overview": "Follow the misadventures of four irreverent grade-schoolers in the quiet, dysfunctional town of South Park, Colorado.", "poster": "assets/tv-south-park-1997-poster.jpg", "backdrop": "assets/tv-south-park-1997-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0456124Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 2190, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 80, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.332, "tmdbVotes": 5250}}], "anime": [{"id": "tv-bleach-2004", "title": "Bleach", "year": 2004, "type": "tv", "runtimeSeconds": 1440, "seasonCount": 17, "communityRating": 8.363, "criticRating": null, "genres": ["Action & Adventure", "Animation", "Sci-Fi & Fantasy"], "overview": "For as long as he can remember, Ichigo Kurosaki has been able to see ghosts. But when he meets Rukia, a Soul Reaper who battles evil spirits known as Hollows, he finds his life is changed forever. Now, with a\u2026", "poster": "assets/tv-bleach-2004-poster.jpg", "backdrop": "assets/tv-bleach-2004-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0359827Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 30984, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.4, "tmdbVotes": 2331}}, {"id": "tv-chainsaw-man-2022", "title": "Chainsaw Man", "year": 2022, "type": "tv", "runtimeSeconds": 1440, "seasonCount": 1, "communityRating": 8.515, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "Denji has a simple dream\u2014to live a happy and peaceful life, spending time with a girl he likes. This is a far cry from reality, however, as Denji is forced by the yakuza into killing devils in order to pay off his\u2026", "poster": "assets/tv-chainsaw-man-2022-poster.jpg", "backdrop": "assets/tv-chainsaw-man-2022-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-20T22:28:13.2095501Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 114410, "artwork": {"posterKind": "center_crop", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 97, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.508, "tmdbVotes": 2364}}, {"id": "tv-cyberpunk-edgerunners-2022", "title": "Cyberpunk: Edgerunners", "year": 2022, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 8.466, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "In a dystopia riddled with corruption and cybernetic implants, a talented but reckless street kid strives to become a mercenary outlaw \u2014 an edgerunner.", "poster": "assets/tv-cyberpunk-edgerunners-2022-poster.jpg", "backdrop": "assets/tv-cyberpunk-edgerunners-2022-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-20T22:28:13.2093107Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 105248, "artwork": {"posterKind": "center_crop", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 89, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.507, "tmdbVotes": 2132}}, {"id": "tv-dan-da-dan-2024", "title": "Dan Da Dan", "year": 2024, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 8.538, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Comedy"], "overview": "In a bet to prove whether ghosts or aliens exist, two high schoolers face terrifying paranormal threats, gain superpowers and maybe even fall in love?!", "poster": "assets/tv-dan-da-dan-2024-poster.jpg", "backdrop": "assets/tv-dan-da-dan-2024-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-22T10:31:16.2652252Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 240411, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.517, "tmdbVotes": 986}}, {"id": "tv-dr-stone-2019", "title": "Dr. STONE", "year": 2019, "type": "tv", "runtimeSeconds": 1440, "seasonCount": 4, "communityRating": 8.541, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Comedy"], "overview": "One fateful day, all of humanity was petrified by a blinding flash of light. After several millennia, high schooler Taiju awakens and finds himself lost in a world of statues. However, he\u2019s not alone! His\u2026", "poster": "assets/tv-dr-stone-2019-poster.jpg", "backdrop": "assets/tv-dr-stone-2019-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-20T22:28:13.2091579Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 86031, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.53, "tmdbVotes": 1814}}, {"id": "tv-frieren-beyond-journey-s-end-2023", "title": "Frieren: Beyond Journey's End", "year": 2023, "type": "tv", "runtimeSeconds": 1500, "seasonCount": 2, "communityRating": 8.803, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Drama"], "overview": "After the party of heroes defeated the Demon King, they restored peace to the land and returned to lives of solitude. Generations pass, and the elven mage Frieren comes face to face with humanity\u2019s mortality. She\u2026", "poster": "assets/tv-frieren-beyond-journey-s-end-2023-poster.jpg", "backdrop": "assets/tv-frieren-beyond-journey-s-end-2023-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-20T22:28:13.2094435Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 209867, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.826, "tmdbVotes": 1016}}, {"id": "tv-fullmetal-alchemist-brotherhood-2009", "title": "Fullmetal Alchemist: Brotherhood", "year": 2009, "type": "tv", "runtimeSeconds": 1500, "seasonCount": 1, "communityRating": 8.7, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "Disregard for alchemy\u2019s laws ripped half of Edward Elric\u2019s limbs from his body and left his brother Alphonse\u2019s soul clinging to a suit of armor. To restore what was lost, the brothers seek the Philosopher\u2019s Stone.\u2026", "poster": "assets/tv-fullmetal-alchemist-brotherhood-2009-poster.jpg", "backdrop": "assets/tv-fullmetal-alchemist-brotherhood-2009-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-20T10:27:12.3573939Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 31911, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.7, "tmdbVotes": 2633}}, {"id": "tv-jujutsu-kaisen-2020", "title": "JUJUTSU KAISEN", "year": 2020, "type": "tv", "runtimeSeconds": 1440, "seasonCount": 3, "communityRating": 8.583, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "Yuji Itadori is a boy with tremendous physical strength, though he lives a completely ordinary high school life. One day, to save a classmate who has been attacked by curses, he eats the finger of Ryomen Sukuna,\u2026", "poster": "assets/tv-jujutsu-kaisen-2020-poster.jpg", "backdrop": "assets/tv-jujutsu-kaisen-2020-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-20T22:28:13.207573Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 95479, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.574, "tmdbVotes": 4735}}, {"id": "tv-kaiju-no-8-2024", "title": "Kaiju No. 8", "year": 2024, "type": "tv", "runtimeSeconds": 1440, "seasonCount": 2, "communityRating": 8.436, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "In a world plagued by creatures known as Kaiju, Kafka Hibino aspired to enlist in The Defense Force. He makes a promise to enlist with his childhood friend, Mina Ashiro. Soon, life takes them in separate ways. While\u2026", "poster": "assets/tv-kaiju-no-8-2024-poster.jpg", "backdrop": "assets/tv-kaiju-no-8-2024-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-22T10:31:16.2646566Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 207468, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.4, "tmdbVotes": 836}}, {"id": "tv-my-hero-academia-2016", "title": "My Hero Academia", "year": 2016, "type": "tv", "runtimeSeconds": 1440, "seasonCount": 8, "communityRating": 8.607, "criticRating": null, "genres": ["Action & Adventure", "Animation", "Sci-Fi & Fantasy"], "overview": "After he saves a bully from a Villain, a normal student is granted a superpower that allows him to attend a high school training academy for Heroes.", "poster": "assets/tv-my-hero-academia-2016-poster.jpg", "backdrop": "assets/tv-my-hero-academia-2016-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-20T22:28:13.2089315Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 65930, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.6, "tmdbVotes": 5426}}, {"id": "tv-neon-genesis-evangelion-1995", "title": "Neon Genesis Evangelion", "year": 1995, "type": "tv", "runtimeSeconds": 1500, "seasonCount": 1, "communityRating": 8.568, "criticRating": null, "genres": ["Sci-Fi & Fantasy", "Animation", "Drama"], "overview": "At the turn of the century, the Angels returned to Earth, seeking to wipe out humanity in an apocalyptic fury. Devastated, mankind's last remnants moved underground to wait for the day when the Angels would come back\u2026", "poster": "assets/tv-neon-genesis-evangelion-1995-poster.jpg", "backdrop": "assets/tv-neon-genesis-evangelion-1995-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0340629Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 890, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.56, "tmdbVotes": 2205}}, {"id": "tv-record-of-ragnarok-2021", "title": "Record of Ragnarok", "year": 2021, "type": "tv", "runtimeSeconds": null, "seasonCount": 3, "communityRating": 8.402, "criticRating": null, "genres": ["Action & Adventure", "Animation", "Sci-Fi & Fantasy"], "overview": "Before eradicating humankind from the world, the gods give them one last chance to prove themselves worthy of survival. Let the Ragnarok battles begin.", "poster": "assets/tv-record-of-ragnarok-2021-poster.jpg", "backdrop": "assets/tv-record-of-ragnarok-2021-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-20T22:28:13.2085766Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 114868, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.403, "tmdbVotes": 1929}}, {"id": "tv-sakamoto-days-2025", "title": "SAKAMOTO DAYS", "year": 2025, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 8.071, "criticRating": null, "genres": ["Animation", "Comedy", "Action & Adventure"], "overview": "Once the greatest hitman of all, Taro Sakamoto retired in the name of love. But when his past catches up, he must fight to protect his beloved family.", "poster": "assets/tv-sakamoto-days-2025-poster.jpg", "backdrop": "assets/tv-sakamoto-days-2025-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-22T10:31:16.266066Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 207332, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.057, "tmdbVotes": 375}}, {"id": "tv-sentenced-to-be-a-hero-2025", "title": "Sentenced to Be a Hero", "year": 2025, "type": "tv", "runtimeSeconds": 1440, "seasonCount": 1, "communityRating": 8.338, "criticRating": null, "genres": ["Animation", "Sci-Fi & Fantasy", "Action & Adventure"], "overview": "In a world where heroism is a punishment, Xylo Forbartz, a condemned goddess killer, battles endless hordes of monstrous abominations as part of Penal Hero Unit 9004. Death is no escape, only a cycle of resurrection\u2026", "poster": "assets/tv-sentenced-to-be-a-hero-2025-poster.jpg", "backdrop": "assets/tv-sentenced-to-be-a-hero-2025-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-22T10:31:16.2666791Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 249907, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.244, "tmdbVotes": 129}}, {"id": "tv-spy-x-family-2022", "title": "SPY x FAMILY", "year": 2022, "type": "tv", "runtimeSeconds": 1440, "seasonCount": 3, "communityRating": 8.506, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Comedy"], "overview": "A spy, an assassin and a telepath come together to pose as a family, each for their own reasons, while hiding their true identities from each other.", "poster": "assets/tv-spy-x-family-2022-poster.jpg", "backdrop": "assets/tv-spy-x-family-2022-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-22T10:31:16.2670364Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 120089, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.5, "tmdbVotes": 2382}}, {"id": "tv-the-tatami-galaxy-2010", "title": "The Tatami Galaxy", "year": 2010, "type": "tv", "runtimeSeconds": 1500, "seasonCount": 2, "communityRating": 8.271, "criticRating": null, "genres": ["Animation", "Comedy", "Drama"], "overview": "When a college dropout stops for a late night bite at a mysterious ramen stand, he crosses paths with a self-proclaimed deity of matrimony. This bizarre meeting sends the young man hurtling through a horrifying\u2026", "poster": "assets/tv-the-tatami-galaxy-2010-poster.jpg", "backdrop": "assets/tv-the-tatami-galaxy-2010-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-18T05:52:51.0023256Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 36243, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.3, "tmdbVotes": 152}}, {"id": "tv-vinland-saga-2019", "title": "Vinland Saga", "year": 2019, "type": "tv", "runtimeSeconds": 1500, "seasonCount": 2, "communityRating": 8.527, "criticRating": null, "genres": ["Animation", "War & Politics", "Action & Adventure"], "overview": "For a thousand years, the Vikings have made quite a name and reputation for themselves as the strongest families with a thirst for violence. Thorfinn, the son of one of the Vikings' greatest warriors, spends his\u2026", "poster": "assets/tv-vinland-saga-2019-poster.jpg", "backdrop": "assets/tv-vinland-saga-2019-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-20T22:28:13.207389Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 88803, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.509, "tmdbVotes": 1064}}, {"id": "tv-yu-yu-hakusho-1992", "title": "Yu Yu Hakusho", "year": 1992, "type": "tv", "runtimeSeconds": 1440, "seasonCount": 4, "communityRating": 8.5, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "After dying to save a boy, delinquent tough guy Yusuke Urameshi is granted another chance at life by redeeming himself as a \"Spirit Detective.\"", "poster": "assets/tv-yu-yu-hakusho-1992-poster.jpg", "backdrop": "assets/tv-yu-yu-hakusho-1992-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-19T21:46:01.5733049Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 30669, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.475, "tmdbVotes": 519}}], "resume": [], "nextUp": [{"id": "episode-three-monsters-1992-s1e6", "title": "Three Monsters", "year": 1992, "type": "episode", "runtimeSeconds": 1421, "seasonCount": null, "communityRating": 7.4, "criticRating": null, "genres": ["Animation", "Action", "Adventure"], "overview": "Yusuke receives his first real case as Spirit Detective. Three demon thieves have muscled their way into King Yama's sacred vault, stealing the three Artifacts of Darkness. Yusuke must recover the artifacts and bring\u2026", "poster": "assets/episode-three-monsters-1992-s1e6-poster.jpg", "backdrop": "assets/episode-three-monsters-1992-s1e6-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-19T21:59:21.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "Yu Yu Hakusho", "seasonNumber": 1, "episodeNumber": 6, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}, {"id": "episode-all-hands-on-deck-class-1-a-2021-s5e1", "title": "All Hands on Deck! Class 1-A", "year": 2021, "type": "episode", "runtimeSeconds": 1433, "seasonCount": null, "communityRating": 6.938, "criticRating": null, "genres": ["Animation", "Action", "Adventure"], "overview": "There's an emergency drill at U.A. High School, and Class 1-A is requested to report for duty!", "poster": "assets/episode-all-hands-on-deck-class-1-a-2021-s5e1-poster.jpg", "backdrop": "assets/episode-all-hands-on-deck-class-1-a-2021-s5e1-backdrop.jpg", "played": false, "playCount": 2, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-22T04:04:50.0000000Z", "lastPlayedAt": "2026-10-19T17:47:09.3028025Z", "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "My Hero Academia", "seasonNumber": 5, "episodeNumber": 1, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}, {"id": "episode-erickerhead-2026-s9e6", "title": "Erickerhead", "year": 2026, "type": "episode", "runtimeSeconds": 1357, "seasonCount": null, "communityRating": 8.196, "criticRating": null, "genres": [], "overview": "Rick's stomach is mad, broh. But it's all-you-can-eat-and-drink here, broh!", "poster": "assets/episode-erickerhead-2026-s9e6-poster.jpg", "backdrop": "assets/episode-erickerhead-2026-s9e6-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-28T10:23:23.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "Rick and Morty", "seasonNumber": 9, "episodeNumber": 6, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}, {"id": "episode-girls-interrupted-2007-s1e7", "title": "Girls, Interrupted", "year": 2007, "type": "episode", "runtimeSeconds": 1642, "seasonCount": null, "communityRating": 7.4, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "Hank is disturbed by Becca's negative reaction towards the concept of happy endings, even more so when he finds out that it is partially his fault.\nMarcy comes up with the idea of a threesome with Charlie and Dani,\u2026", "poster": "assets/episode-girls-interrupted-2007-s1e7-poster.jpg", "backdrop": "assets/episode-girls-interrupted-2007-s1e7-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-05T02:46:18.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "Californication", "seasonNumber": 1, "episodeNumber": 7, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}, {"id": "episode-announcement-2022-s1e7", "title": "Announcement", "year": 2022, "type": "episode", "runtimeSeconds": 3012, "seasonCount": null, "communityRating": 7.8, "criticRating": null, "genres": ["Action", "Adventure", "Drama"], "overview": "The repercussions of the Aldhani heist reach all the way to Coruscant, while Cassian returns home.", "poster": "assets/episode-announcement-2022-s1e7-poster.jpg", "backdrop": "assets/episode-announcement-2022-s1e7-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T12:56:24.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "Andor", "seasonNumber": 1, "episodeNumber": 7, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}, {"id": "episode-south-american-biker-gangs-2026-s29e1", "title": "South American Biker Gangs", "year": 2026, "type": "episode", "runtimeSeconds": 1335, "seasonCount": null, "communityRating": 6.4, "criticRating": null, "genres": ["Animation", "Comedy"], "overview": "When e-bikes arrive in South America, the boys take over the streets and the parents of South America can hardly recognize South America.", "poster": "assets/episode-south-american-biker-gangs-2026-s29e1-poster.jpg", "backdrop": "assets/episode-south-american-biker-gangs-2026-s29e1-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-01T06:40:46.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "South Park", "seasonNumber": 29, "episodeNumber": 1, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}], "recentlyAdded": [{"id": "movie-steins-gate-the-movie-load-region-of-deja-vu-2013", "title": "Steins;Gate: The Movie - Load Region of D\u00e9j\u00e0 Vu", "year": 2013, "type": "movie", "runtimeSeconds": 5403, "seasonCount": null, "communityRating": 7.8, "criticRating": null, "genres": ["Animation", "Mystery", "Sci-Fi"], "overview": "After the events of the anime, Rintarou begins to feel the repercussions of extensive time travel, and eventually completely fades from reality. Kurisu, being the only companion to remember him, now must find a way\u2026", "poster": "assets/movie-steins-gate-the-movie-load-region-of-deja-vu-2013-poster.jpg", "backdrop": "assets/movie-steins-gate-the-movie-load-region-of-deja-vu-2013-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-22T14:55:54.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}, {"id": "tv-yu-yu-hakusho-1992", "title": "Yu Yu Hakusho", "year": 1992, "type": "tv", "runtimeSeconds": 1440, "seasonCount": 4, "communityRating": 8.5, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "After dying to save a boy, delinquent tough guy Yusuke Urameshi is granted another chance at life by redeeming himself as a \"Spirit Detective.\"", "poster": "assets/tv-yu-yu-hakusho-1992-poster.jpg", "backdrop": "assets/tv-yu-yu-hakusho-1992-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-19T21:46:01.5733049Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 30669, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.475, "tmdbVotes": 519}}, {"id": "movie-memories-1995", "title": "Memories", "year": 1995, "type": "movie", "runtimeSeconds": 6805, "seasonCount": null, "communityRating": 7.465, "criticRating": null, "genres": ["Fantasy", "Animation", "Science Fiction"], "overview": "Three back-to-back anime films by three different directors make up this sci-fi trilogy three years in the making.", "poster": "assets/movie-memories-1995-poster.jpg", "backdrop": "assets/movie-memories-1995-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-30T21:36:03.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 42994, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": 82, "rtAudienceLabel": "Upright", "imdb": 7.5, "imdbVotes": 26638, "tmdb": 7.459, "tmdbVotes": 622}}, {"id": "tv-firefly-2002", "title": "Firefly", "year": 2002, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 8.347, "criticRating": null, "genres": ["Drama", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "In the year 2517, the universe is a vast frontier where lawlessness reigns and adventure awaits! Join Captain Malcolm Reynolds and his ragtag crew aboard the spaceship Serenity as they navigate the fringes of\u2026", "poster": "assets/tv-firefly-2002-poster.jpg", "backdrop": "assets/tv-firefly-2002-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-20T11:40:08.4202369Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 1437, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 77, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.351, "tmdbVotes": 2550}}, {"id": "tv-love-death-robots-2019", "title": "Love, Death & Robots", "year": 2019, "type": "tv", "runtimeSeconds": null, "seasonCount": 4, "communityRating": 8.205, "criticRating": null, "genres": ["Animation", "Sci-Fi & Fantasy"], "overview": "Terrifying creatures, wicked surprises and dark comedy converge in this NSFW anthology of animated stories presented by Tim Miller and David Fincher.", "poster": "assets/tv-love-death-robots-2019-poster.jpg", "backdrop": "assets/tv-love-death-robots-2019-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-20T11:31:20.8818757Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 86831, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 89, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.203, "tmdbVotes": 4238}}, {"id": "tv-the-legend-of-korra-2012", "title": "The Legend of Korra", "year": 2012, "type": "tv", "runtimeSeconds": 1380, "seasonCount": 4, "communityRating": 8.2, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "Avatar Korra, a headstrong, rebellious, feisty young woman who continually challenges and breaks with tradition, is on her quest to become a fully realized Avatar. In this story, the Avatar struggles to find balance\u2026", "poster": "assets/tv-the-legend-of-korra-2012-poster.jpg", "backdrop": "assets/tv-the-legend-of-korra-2012-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-20T00:19:23.2494945Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 33880, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 88, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.221, "tmdbVotes": 2482}}, {"id": "tv-spy-x-family-2022", "title": "SPY x FAMILY", "year": 2022, "type": "tv", "runtimeSeconds": 1440, "seasonCount": 3, "communityRating": 8.506, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Comedy"], "overview": "A spy, an assassin and a telepath come together to pose as a family, each for their own reasons, while hiding their true identities from each other.", "poster": "assets/tv-spy-x-family-2022-poster.jpg", "backdrop": "assets/tv-spy-x-family-2022-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-22T10:31:16.2670364Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 120089, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.5, "tmdbVotes": 2382}}, {"id": "tv-sentenced-to-be-a-hero-2025", "title": "Sentenced to Be a Hero", "year": 2025, "type": "tv", "runtimeSeconds": 1440, "seasonCount": 1, "communityRating": 8.338, "criticRating": null, "genres": ["Animation", "Sci-Fi & Fantasy", "Action & Adventure"], "overview": "In a world where heroism is a punishment, Xylo Forbartz, a condemned goddess killer, battles endless hordes of monstrous abominations as part of Penal Hero Unit 9004. Death is no escape, only a cycle of resurrection\u2026", "poster": "assets/tv-sentenced-to-be-a-hero-2025-poster.jpg", "backdrop": "assets/tv-sentenced-to-be-a-hero-2025-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-22T10:31:16.2666791Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 249907, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.244, "tmdbVotes": 129}}, {"id": "tv-sakamoto-days-2025", "title": "SAKAMOTO DAYS", "year": 2025, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 8.071, "criticRating": null, "genres": ["Animation", "Comedy", "Action & Adventure"], "overview": "Once the greatest hitman of all, Taro Sakamoto retired in the name of love. But when his past catches up, he must fight to protect his beloved family.", "poster": "assets/tv-sakamoto-days-2025-poster.jpg", "backdrop": "assets/tv-sakamoto-days-2025-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-22T10:31:16.266066Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 207332, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.057, "tmdbVotes": 375}}, {"id": "tv-dan-da-dan-2024", "title": "Dan Da Dan", "year": 2024, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 8.538, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Comedy"], "overview": "In a bet to prove whether ghosts or aliens exist, two high schoolers face terrifying paranormal threats, gain superpowers and maybe even fall in love?!", "poster": "assets/tv-dan-da-dan-2024-poster.jpg", "backdrop": "assets/tv-dan-da-dan-2024-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-22T10:31:16.2652252Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 240411, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.517, "tmdbVotes": 986}}, {"id": "tv-kaiju-no-8-2024", "title": "Kaiju No. 8", "year": 2024, "type": "tv", "runtimeSeconds": 1440, "seasonCount": 2, "communityRating": 8.436, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "In a world plagued by creatures known as Kaiju, Kafka Hibino aspired to enlist in The Defense Force. He makes a promise to enlist with his childhood friend, Mina Ashiro. Soon, life takes them in separate ways. While\u2026", "poster": "assets/tv-kaiju-no-8-2024-poster.jpg", "backdrop": "assets/tv-kaiju-no-8-2024-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-22T10:31:16.2646566Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 207468, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.4, "tmdbVotes": 836}}, {"id": "tv-chainsaw-man-2022", "title": "Chainsaw Man", "year": 2022, "type": "tv", "runtimeSeconds": 1440, "seasonCount": 1, "communityRating": 8.515, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "Denji has a simple dream\u2014to live a happy and peaceful life, spending time with a girl he likes. This is a far cry from reality, however, as Denji is forced by the yakuza into killing devils in order to pay off his\u2026", "poster": "assets/tv-chainsaw-man-2022-poster.jpg", "backdrop": "assets/tv-chainsaw-man-2022-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-20T22:28:13.2095501Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 114410, "artwork": {"posterKind": "center_crop", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 97, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.508, "tmdbVotes": 2364}}], "discover": [{"id": "movie-the-amazing-spider-man-2012", "title": "The Amazing Spider-Man", "year": 2012, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action", "Adventure", "Science Fiction"], "overview": "A teenage Peter Parker grapples with both high school and amazing super-human crises as his alter-ego Spider-Man.", "poster": "assets/movie-the-amazing-spider-man-2012-poster.jpg", "backdrop": "assets/movie-the-amazing-spider-man-2012-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 1930, "ratings": {"rtCritics": 71, "rtCriticsLabel": "Certified Fresh", "rtAudience": 77, "rtAudienceLabel": "Upright", "imdb": 7, "imdbVotes": 791938, "tmdb": 6.768, "tmdbVotes": 19312}}, {"id": "movie-the-amazing-spider-man-2-2014", "title": "The Amazing Spider-Man 2", "year": 2014, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action", "Adventure", "Science Fiction"], "overview": "For Peter Parker, life is busy. Between taking out the bad guys as Spider-Man and spending time with the person he loves, Gwen Stacy, high school graduation cannot come quickly enough. Peter has not forgotten about\u2026", "poster": "assets/movie-the-amazing-spider-man-2-2014-poster.jpg", "backdrop": "assets/movie-the-amazing-spider-man-2-2014-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 102382, "ratings": {"rtCritics": 51, "rtCriticsLabel": "Rotten", "rtAudience": 64, "rtAudienceLabel": "Upright", "imdb": 6.6, "imdbVotes": 611981, "tmdb": 6.567, "tmdbVotes": 14919}}, {"id": "movie-the-animatrix-2003", "title": "The Animatrix", "year": 2003, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Animation", "Science Fiction"], "overview": "Straight from the creators of the groundbreaking Matrix trilogy, this collection of short animated films from the world's leading anime directors fuses computer graphics and Japanese anime to provide the background\u2026", "poster": "assets/movie-the-animatrix-2003-poster.jpg", "backdrop": "assets/movie-the-animatrix-2003-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 55931, "ratings": {"rtCritics": 86, "rtCriticsLabel": "Fresh", "rtAudience": 78, "rtAudienceLabel": "Upright", "imdb": 7.3, "imdbVotes": 91929, "tmdb": 7.221, "tmdbVotes": 1872}}, {"id": "movie-ant-man-and-the-wasp-quantumania-2023", "title": "Ant-Man and the Wasp: Quantumania", "year": 2023, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action", "Adventure", "Science Fiction"], "overview": "Super-Hero partners Scott Lang and Hope van Dyne, along with with Hope's parents Janet van Dyne and Hank Pym, and Scott's daughter Cassie Lang, find themselves exploring the Quantum Realm, interacting with strange\u2026", "poster": "assets/movie-ant-man-and-the-wasp-quantumania-2023-poster.jpg", "backdrop": "assets/movie-ant-man-and-the-wasp-quantumania-2023-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 640146, "ratings": {"rtCritics": 46, "rtCriticsLabel": "Rotten", "rtAudience": 81, "rtAudienceLabel": "Upright", "imdb": 6, "imdbVotes": 281738, "tmdb": 6.197, "tmdbVotes": 6236}}, {"id": "movie-appleseed-alpha-2014", "title": "Appleseed Alpha", "year": 2014, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Animation", "Science Fiction", "Action"], "overview": "The Third World War (2099\u20132126) devastated part of the Earth. Former nations are reforming while new powers emerge, and groups of humans struggle to survive in ruined cities. The story follows Deunan Knute and her\u2026", "poster": "assets/movie-appleseed-alpha-2014-poster.jpg", "backdrop": "assets/movie-appleseed-alpha-2014-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "center_crop", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 269650, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": 59, "rtAudienceLabel": "Spilled", "imdb": 6.5, "imdbVotes": 9689, "tmdb": 6.808, "tmdbVotes": 436}}, {"id": "movie-aquaman-and-the-lost-kingdom-2023", "title": "Aquaman and the Lost Kingdom", "year": 2023, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action", "Adventure", "Fantasy"], "overview": "Black Manta seeks revenge on Aquaman for his father's death. Wielding the Black Trident's power, he becomes a formidable foe. To defend Atlantis, Arthur (Aquaman) forges an alliance with his imprisoned brother. They\u2026", "poster": "assets/movie-aquaman-and-the-lost-kingdom-2023-poster.jpg", "backdrop": "assets/movie-aquaman-and-the-lost-kingdom-2023-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 572802, "ratings": {"rtCritics": 33, "rtCriticsLabel": "Rotten", "rtAudience": 79, "rtAudienceLabel": "Upright", "imdb": 5.6, "imdbVotes": 122422, "tmdb": 6.48, "tmdbVotes": 3748}}, {"id": "movie-the-avengers-2012", "title": "The Avengers", "year": 2012, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Science Fiction", "Action", "Adventure"], "overview": "When an unexpected enemy emerges and threatens global safety and security, Nick Fury, director of the international peacekeeping agency known as S.H.I.E.L.D., finds himself in need of a team to pull the world back\u2026", "poster": "assets/movie-the-avengers-2012-poster.jpg", "backdrop": "assets/movie-the-avengers-2012-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 24428, "ratings": {"rtCritics": 91, "rtCriticsLabel": "Certified Fresh", "rtAudience": 91, "rtAudienceLabel": "Upright", "imdb": 8, "imdbVotes": 1590693, "tmdb": 8.084, "tmdbVotes": 40262}}, {"id": "movie-avengers-confidential-black-widow-punisher-2014", "title": "Avengers Confidential: Black Widow & Punisher", "year": 2014, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Animation", "Science Fiction", "Action"], "overview": "When the Punisher takes out a black-market weapons dealer, he stumbles upon a far-reaching terrorist plot devised by a group known as Leviathan.", "poster": "assets/movie-avengers-confidential-black-widow-punisher-2014-poster.jpg", "backdrop": "assets/movie-avengers-confidential-black-widow-punisher-2014-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 257346, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": 55, "rtAudienceLabel": "Spilled", "imdb": 5.7, "imdbVotes": 5166, "tmdb": 6.368, "tmdbVotes": 303}}, {"id": "movie-avengers-age-of-ultron-2015", "title": "Avengers: Age of Ultron", "year": 2015, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action", "Adventure", "Science Fiction"], "overview": "When Tony Stark tries to jumpstart a dormant peacekeeping program, things go awry and Earth\u2019s Mightiest Heroes are put to the ultimate test as the fate of the planet hangs in the balance. As the villainous Ultron\u2026", "poster": "assets/movie-avengers-age-of-ultron-2015-poster.jpg", "backdrop": "assets/movie-avengers-age-of-ultron-2015-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 99861, "ratings": {"rtCritics": 75, "rtCriticsLabel": "Certified Fresh", "rtAudience": 82, "rtAudienceLabel": "Upright", "imdb": 7.3, "imdbVotes": 1025876, "tmdb": 7.278, "tmdbVotes": 25289}}, {"id": "movie-avengers-infinity-war-2018", "title": "Avengers: Infinity War", "year": 2018, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Adventure", "Action", "Science Fiction"], "overview": "As the Avengers and their allies have continued to protect the world from threats too large for any one hero to handle, a new danger has emerged from the cosmic shadows: Thanos. A despot of intergalactic infamy, his\u2026", "poster": "assets/movie-avengers-infinity-war-2018-poster.jpg", "backdrop": "assets/movie-avengers-infinity-war-2018-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 299536, "ratings": {"rtCritics": 85, "rtCriticsLabel": "Certified Fresh", "rtAudience": 92, "rtAudienceLabel": "Upright", "imdb": 8.4, "imdbVotes": 1421954, "tmdb": 8.246, "tmdbVotes": 33331}}, {"id": "movie-batman-ninja-2018", "title": "Batman Ninja", "year": 2018, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Animation", "Action", "Science Fiction"], "overview": "Batman, along with many of his allies and adversaries, finds himself transported to feudal Japan by Gorilla Grodd's time displacement machine.", "poster": "assets/movie-batman-ninja-2018-poster.jpg", "backdrop": "assets/movie-batman-ninja-2018-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 485942, "ratings": {"rtCritics": 84, "rtCriticsLabel": "Fresh", "rtAudience": 60, "rtAudienceLabel": "Upright", "imdb": 5.6, "imdbVotes": 24579, "tmdb": 5.9, "tmdbVotes": 1106}}, {"id": "movie-batman-v-superman-dawn-of-justice-2016", "title": "Batman v Superman: Dawn of Justice", "year": 2016, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action", "Adventure", "Fantasy"], "overview": "Fearing the actions of a god-like Super Hero left unchecked, Gotham City\u2019s own formidable, forceful vigilante takes on Metropolis\u2019s most revered, modern-day savior, while the world wrestles with what sort of hero it\u2026", "poster": "assets/movie-batman-v-superman-dawn-of-justice-2016-poster.jpg", "backdrop": "assets/movie-batman-v-superman-dawn-of-justice-2016-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 209112, "ratings": {"rtCritics": 28, "rtCriticsLabel": "Rotten", "rtAudience": 63, "rtAudienceLabel": "Upright", "imdb": 6.4, "imdbVotes": 801382, "tmdb": 6.014, "tmdbVotes": 19527}}, {"id": "movie-batman-the-dark-knight-returns-part-1-2012", "title": "Batman: The Dark Knight Returns, Part 1", "year": 2012, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Science Fiction", "Action", "Animation"], "overview": "Batman has not been seen for ten years. A new breed of criminal ravages Gotham City, forcing 55-year-old Bruce Wayne back into the cape and cowl. But, does he still have what it takes to fight crime in a new era?", "poster": "assets/movie-batman-the-dark-knight-returns-part-1-2012-poster.jpg", "backdrop": "assets/movie-batman-the-dark-knight-returns-part-1-2012-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 123025, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": 93, "rtAudienceLabel": "Upright", "imdb": 7.9, "imdbVotes": 68943, "tmdb": 7.742, "tmdbVotes": 1766}}, {"id": "movie-black-adam-2022", "title": "Black Adam", "year": 2022, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action", "Adventure", "Science Fiction"], "overview": "Nearly 5,000 years after he was bestowed with the almighty powers of the Egyptian gods\u2014and imprisoned just as quickly\u2014Black Adam is freed from his earthly tomb, ready to unleash his unique form of justice on the\u2026", "poster": "assets/movie-black-adam-2022-poster.jpg", "backdrop": "assets/movie-black-adam-2022-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 436270, "ratings": {"rtCritics": 39, "rtCriticsLabel": "Rotten", "rtAudience": 87, "rtAudienceLabel": "Upright", "imdb": 6.1, "imdbVotes": 309624, "tmdb": 6.747, "tmdbVotes": 7370}}, {"id": "movie-black-panther-2018", "title": "Black Panther", "year": 2018, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action", "Adventure", "Science Fiction"], "overview": "King T'Challa returns home to the reclusive, technologically advanced African nation of Wakanda to serve as his country's new leader. However, T'Challa soon finds that he is challenged for the throne by factions\u2026", "poster": "assets/movie-black-panther-2018-poster.jpg", "backdrop": "assets/movie-black-panther-2018-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 284054, "ratings": {"rtCritics": 96, "rtCriticsLabel": "Certified Fresh", "rtAudience": null, "rtAudienceLabel": "Spilled", "imdb": 7.3, "imdbVotes": 931120, "tmdb": 7.36, "tmdbVotes": 24190}}, {"id": "movie-black-panther-wakanda-forever-2022", "title": "Black Panther: Wakanda Forever", "year": 2022, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action", "Adventure", "Science Fiction"], "overview": "Queen Ramonda, Shuri, M\u2019Baku, Okoye and the Dora Milaje fight to protect their nation from intervening world powers in the wake of King T\u2019Challa\u2019s death. As the Wakandans strive to embrace their next chapter, the\u2026", "poster": "assets/movie-black-panther-wakanda-forever-2022-poster.jpg", "backdrop": "assets/movie-black-panther-wakanda-forever-2022-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 505642, "ratings": {"rtCritics": 84, "rtCriticsLabel": "Certified Fresh", "rtAudience": 93, "rtAudienceLabel": "Upright", "imdb": 6.6, "imdbVotes": 405088, "tmdb": 6.984, "tmdbVotes": 7920}}, {"id": "movie-blame-2017", "title": "BLAME!", "year": 2017, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action", "Drama", "Science Fiction"], "overview": "In the distant technological future, civilization has reached its ultimate Net-based form. An \"infection\" in the past caused the automated systems to spiral out of order, resulting in a multi-leveled city structure\u2026", "poster": "assets/movie-blame-2017-poster.jpg", "backdrop": "assets/movie-blame-2017-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 409421, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": 61, "rtAudienceLabel": "Upright", "imdb": 6.6, "imdbVotes": 10258, "tmdb": 6.523, "tmdbVotes": 370}}, {"id": "movie-bleach-the-movie-fade-to-black-2008", "title": "Bleach the Movie: Fade to Black", "year": 2008, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action", "Animation", "Fantasy"], "overview": "A strange disturbance in the World of the Living sends Ichigo and Kon to the Soul Society, where the Seireitei has been devastated by an explosion of reishi and Rukia is missing. Stranger still, none of the Soul\u2026", "poster": "assets/movie-bleach-the-movie-fade-to-black-2008-poster.jpg", "backdrop": "assets/movie-bleach-the-movie-fade-to-black-2008-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 17165, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": 67, "rtAudienceLabel": "Upright", "imdb": 7.1, "imdbVotes": 3693, "tmdb": 7.1, "tmdbVotes": 206}}, {"id": "tv-90-day-fiance-before-the-90-days-2017", "title": "90 Day Fianc\u00e9: Before the 90 Days", "year": 2017, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Reality"], "overview": "Follow couples who have an existing relationship online, but haven't met in person. We'll experience their journey as they travel to the other's foreign country for the first time in an attempt to establish an\u2026", "poster": "assets/tv-90-day-fiance-before-the-90-days-2017-poster.jpg", "backdrop": "assets/tv-90-day-fiance-before-the-90-days-2017-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 73319, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.5, "tmdbVotes": 45}}, {"id": "tv-90-day-fiance-the-other-way-2019", "title": "90 Day Fianc\u00e9: The Other Way", "year": 2019, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Reality"], "overview": "Americans move to a foreign country all in the name of love. Will uprooting their lives in the United States pay off, or will they be packing up and moving back to America brokenhearted?", "poster": "assets/tv-90-day-fiance-the-other-way-2019-poster.jpg", "backdrop": "assets/tv-90-day-fiance-the-other-way-2019-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 90046, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.8, "tmdbVotes": 34}}, {"id": "tv-the-100-2014", "title": "The 100", "year": 2014, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Sci-Fi & Fantasy", "Action & Adventure", "Drama"], "overview": "100 years in the future, when the Earth has been abandoned due to radioactivity, the last surviving humans live on an ark orbiting the planet \u2014 but the ark won't last forever. So the repressive regime picks 100\u2026", "poster": "assets/tv-the-100-2014-poster.jpg", "backdrop": "assets/tv-the-100-2014-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 48866, "ratings": {"rtCritics": 93, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.878, "tmdbVotes": 8855}}, {"id": "tv-the-abbott-and-costello-show-1952", "title": "The Abbott and Costello Show", "year": 1952, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Comedy"], "overview": "Bud and Lou are unemployed actors living in Mr. Fields\u2019 boarding house. Lou\u2019s girlfriend Hillary lives across the hall. Many situations arise leading to slapstick and puns.", "poster": "assets/tv-the-abbott-and-costello-show-1952-poster.jpg", "backdrop": "assets/tv-the-abbott-and-costello-show-1952-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 11821, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.2, "tmdbVotes": 22}}, {"id": "tv-accel-world-2012", "title": "Accel World", "year": 2012, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Drama"], "overview": "A short, overweight student uses the Neuro Link, an evolution of the Nerve Gear, to escape himself from the reality. But his skills in the virtual world brings him to the attention of Kuroyukihime, a popular student\u2026", "poster": "assets/tv-accel-world-2012-poster.jpg", "backdrop": "assets/tv-accel-world-2012-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "center_crop", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 43167, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.429, "tmdbVotes": 163}}, {"id": "tv-ahsoka-2023", "title": "Ahsoka", "year": 2023, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Sci-Fi & Fantasy", "Action & Adventure", "Drama"], "overview": "Former Jedi Knight Ahsoka Tano investigates an emerging threat to a vulnerable galaxy.", "poster": "assets/tv-ahsoka-2023-poster.jpg", "backdrop": "assets/tv-ahsoka-2023-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 114461, "ratings": {"rtCritics": 85, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.4, "tmdbVotes": 1351}}, {"id": "tv-american-dad-2005", "title": "American Dad!", "year": 2005, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Animation", "Comedy"], "overview": "The series focuses on an eccentric motley crew that is the Smith family and their three housemates: Father, husband, and breadwinner Stan Smith; his better half housewife, Francine Smith; their college-aged daughter,\u2026", "poster": "assets/tv-american-dad-2005-poster.jpg", "backdrop": "assets/tv-american-dad-2005-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 1433, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.022, "tmdbVotes": 2481}}, {"id": "tv-american-gods-2017", "title": "American Gods", "year": 2017, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Mystery", "Sci-Fi & Fantasy"], "overview": "An ex-con becomes the traveling partner of a conman who turns out to be one of the older gods trying to recruit troops to battle the upstart deities. Based on Neil Gaiman's fantasy novel.", "poster": "assets/tv-american-gods-2017-poster.jpg", "backdrop": "assets/tv-american-gods-2017-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 46639, "ratings": {"rtCritics": 75, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.1, "tmdbVotes": 1852}}, {"id": "tv-andromeda-2000", "title": "Andromeda", "year": 2000, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action & Adventure", "Drama", "Sci-Fi & Fantasy"], "overview": "Captain Dylan Hunt and his crew quest to restore a government that once presided over an extended peace and prosperity.", "poster": "assets/tv-andromeda-2000-poster.jpg", "backdrop": "assets/tv-andromeda-2000-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 2985, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.068, "tmdbVotes": 315}}, {"id": "tv-aqua-teen-hunger-force-2000", "title": "Aqua Teen Hunger Force", "year": 2000, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Animation", "Comedy"], "overview": "The surreal adventures of three anthropomorphic fast food items: Master Shake, Frylock and Meatwad, and their human nextdoor neighbor, Carl Brutananadilewski.", "poster": "assets/tv-aqua-teen-hunger-force-2000-poster.jpg", "backdrop": "assets/tv-aqua-teen-hunger-force-2000-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 251, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.5, "tmdbVotes": 272}}, {"id": "tv-arifureta-from-commonplace-to-world-s-strongest-2019", "title": "Arifureta: From Commonplace to World's Strongest", "year": 2019, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "Hajime Nagumo and his high school class are suddenly summoned to a fantastical land as heroes. But while most of his classmates have powerful stats and abilities, Hajime does not. Underappreciated and unprepared, he\u2026", "poster": "assets/tv-arifureta-from-commonplace-to-world-s-strongest-2019-poster.jpg", "backdrop": "assets/tv-arifureta-from-commonplace-to-world-s-strongest-2019-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 86034, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.382, "tmdbVotes": 662}}, {"id": "tv-arrow-2012", "title": "Arrow", "year": 2012, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Crime", "Drama", "Action & Adventure"], "overview": "Spoiled billionaire playboy Oliver Queen is missing and presumed dead when his yacht is lost at sea. He returns five years later a changed man, determined to clean up the city as a hooded vigilante armed with a bow.", "poster": "assets/tv-arrow-2012-poster.jpg", "backdrop": "assets/tv-arrow-2012-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 1412, "ratings": {"rtCritics": 86, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.834, "tmdbVotes": 6537}}, {"id": "tv-avatar-the-last-airbender-2024", "title": "Avatar: The Last Airbender", "year": 2024, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action & Adventure", "Sci-Fi & Fantasy", "Drama"], "overview": "A young boy known as the Avatar must master the four elemental powers to save a world at war \u2014 and fight a ruthless enemy bent on stopping him.", "poster": "assets/tv-avatar-the-last-airbender-2024-poster.jpg", "backdrop": "assets/tv-avatar-the-last-airbender-2024-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 82452, "ratings": {"rtCritics": 66, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.8, "tmdbVotes": 1406}}, {"id": "tv-avatar-seven-havens-2026", "title": "Avatar: Seven Havens", "year": 2026, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "In a world shattered by a devastating cataclysm, a young Earthbender discovers she's the new Avatar after Korra. But in this dangerous era, that title marks her as humanity's destroyer, not its savior. Hunted by both\u2026", "poster": "assets/tv-avatar-seven-havens-2026-poster.jpg", "backdrop": "assets/tv-avatar-seven-havens-2026-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 284833, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.9, "tmdbVotes": 183}}, {"id": "tv-batman-the-brave-and-the-bold-2008", "title": "Batman: The Brave and the Bold", "year": 2008, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "The Caped Crusader is teamed up with Blue Beetle, Green Arrow, Aquaman and countless others in his quest to uphold justice.", "poster": "assets/tv-batman-the-brave-and-the-bold-2008-poster.jpg", "backdrop": "assets/tv-batman-the-brave-and-the-bold-2008-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 15804, "ratings": {"rtCritics": 96, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.6, "tmdbVotes": 295}}, {"id": "tv-batwoman-2019", "title": "Batwoman", "year": 2019, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Sci-Fi & Fantasy", "Action & Adventure"], "overview": "Kate Kane, armed with a passion for social justice and a flair for speaking her mind, soars onto the streets of Gotham as Batwoman, an out lesbian and highly trained street fighter primed to snuff out the failing\u2026", "poster": "assets/tv-batwoman-2019-poster.jpg", "backdrop": "assets/tv-batwoman-2019-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 89247, "ratings": {"rtCritics": 83, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.8, "tmdbVotes": 1619}}, {"id": "tv-big-mouth-2017", "title": "Big Mouth", "year": 2017, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Comedy", "Animation"], "overview": "Teenage friends find their lives upended by the wonders and horrors of puberty in this edgy comedy from real-life pals Nick Kroll and Andrew Goldberg.", "poster": "assets/tv-big-mouth-2017-poster.jpg", "backdrop": "assets/tv-big-mouth-2017-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 74204, "ratings": {"rtCritics": 95, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.591, "tmdbVotes": 1274}}, {"id": "tv-black-lightning-2018", "title": "Black Lightning", "year": 2018, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action & Adventure", "Sci-Fi & Fantasy", "Drama"], "overview": "Jefferson Pierce is a man wrestling with a secret. As the father of two daughters and principal of a charter high school that also serves as a safe haven for young people in a New Orleans neighborhood overrun by gang\u2026", "poster": "assets/tv-black-lightning-2018-poster.jpg", "backdrop": "assets/tv-black-lightning-2018-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 71663, "ratings": {"rtCritics": 92, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.907, "tmdbVotes": 991}}], "discoverySource": {"state": "materialized_snapshot", "rankKnown": false, "selection": "First 18 alphabetically indexed movie stubs and first 18 series stubs, not ranked feed order", "observedAt": "2026-11-04T20:42:12.030108+00:00"}}, {"key": "mindel", "name": "Mindel", "initial": "M", "color": "#ff9d72", "destinations": {"Home": {"kind": "home"}, "Movies": {"kind": "library", "collection": "movies"}, "Shows": {"kind": "library", "collection": "shows"}, "Discover": {"kind": "discover", "collection": "discover"}}, "movies": [{"id": "movie-chicago-2002", "title": "Chicago", "year": 2002, "type": "movie", "runtimeSeconds": 6808, "seasonCount": null, "communityRating": 7.131, "criticRating": 87, "genres": ["Comedy", "Crime", "Drama"], "overview": "Murderesses Velma Kelly and Roxie Hart find themselves on death row together and fight for the fame that will keep them from the gallows in 1920s Chicago.", "poster": "assets/movie-chicago-2002-poster.jpg", "backdrop": "assets/movie-chicago-2002-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:24:53.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 1574, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 87, "rtCriticsLabel": "Certified Fresh", "rtAudience": 83, "rtAudienceLabel": "Upright", "imdb": 7.2, "imdbVotes": 259272, "tmdb": 7.135, "tmdbVotes": 3025}}, {"id": "movie-coherence-2014", "title": "Coherence", "year": 2014, "type": "movie", "runtimeSeconds": 5286, "seasonCount": null, "communityRating": 7.21, "criticRating": 89, "genres": ["Thriller", "Science Fiction"], "overview": "Four couples gather for dinner the night a mysterious and powerful comet passes overhead.", "poster": "assets/movie-coherence-2014-poster.jpg", "backdrop": "assets/movie-coherence-2014-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2017-08-12T23:49:00.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 220289, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 89, "rtCriticsLabel": "Certified Fresh", "rtAudience": 81, "rtAudienceLabel": "Upright", "imdb": 7.1, "imdbVotes": 180354, "tmdb": 7.208, "tmdbVotes": 3702}}, {"id": "movie-the-killing-of-a-sacred-deer-2017", "title": "The Killing of a Sacred Deer", "year": 2017, "type": "movie", "runtimeSeconds": 7262, "seasonCount": null, "communityRating": 7.003, "criticRating": 79, "genres": ["Drama", "Thriller", "Mystery"], "overview": "Dr. Steven Murphy is a renowned cardiovascular surgeon who presides over a spotless household with his wife and two children. Lurking at the margins of his idyllic suburban existence is Martin, a fatherless teen who\u2026", "poster": "assets/movie-the-killing-of-a-sacred-deer-2017-poster.jpg", "backdrop": "assets/movie-the-killing-of-a-sacred-deer-2017-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-22T21:49:26.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 399057, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 79, "rtCriticsLabel": "Certified Fresh", "rtAudience": 63, "rtAudienceLabel": "Upright", "imdb": 7, "imdbVotes": 219488, "tmdb": 7.001, "tmdbVotes": 4640}}, {"id": "movie-night-is-short-walk-on-girl-2017", "title": "Night Is Short, Walk On Girl", "year": 2017, "type": "movie", "runtimeSeconds": 5548, "seasonCount": null, "communityRating": 7.421, "criticRating": 90, "genres": ["Comedy", "Romance", "Animation"], "overview": "As a group of university students go out for a night on the town, a sophomore known only as \"The Girl with Black Hair\" experiences a series of surreal encounters with the local nightlife \u2013 all the while unaware of\u2026", "poster": "assets/movie-night-is-short-walk-on-girl-2017-poster.jpg", "backdrop": "assets/movie-night-is-short-walk-on-girl-2017-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T17:39:14.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 430214, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 90, "rtCriticsLabel": "Fresh", "rtAudience": 84, "rtAudienceLabel": "Upright", "imdb": 7.5, "imdbVotes": 7640, "tmdb": 7.4, "tmdbVotes": 267}}, {"id": "movie-thelma-louise-1991", "title": "Thelma & Louise", "year": 1991, "type": "movie", "runtimeSeconds": 7786, "seasonCount": null, "communityRating": 7.596, "criticRating": 87, "genres": ["Drama", "Crime", "Adventure"], "overview": "Taking a break from their dreary lives, close friends Thelma and Louise embark on a short weekend trip that ends in unforeseen incriminating circumstances. As fugitives, both women rediscover the strength of their\u2026", "poster": "assets/movie-thelma-louise-1991-poster.jpg", "backdrop": "assets/movie-thelma-louise-1991-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:24:40.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 1541, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 87, "rtCriticsLabel": "Certified Fresh", "rtAudience": 82, "rtAudienceLabel": "Upright", "imdb": 7.6, "imdbVotes": 197947, "tmdb": 7.594, "tmdbVotes": 4063}}, {"id": "movie-vicky-cristina-barcelona-2008", "title": "Vicky Cristina Barcelona", "year": 2008, "type": "movie", "runtimeSeconds": 5804, "seasonCount": null, "communityRating": 6.798, "criticRating": 80, "genres": ["Drama", "Romance"], "overview": "Two girlfriends on a summer holiday in Spain become enamored with the same painter, unaware that his ex-wife, with whom he has a tempestuous relationship, is about to re-enter the picture.", "poster": "assets/movie-vicky-cristina-barcelona-2008-poster.jpg", "backdrop": "assets/movie-vicky-cristina-barcelona-2008-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:25:34.0000000Z", "lastPlayedAt": "2026-08-11T20:12:57.3064089Z", "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 5038, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 80, "rtCriticsLabel": "Certified Fresh", "rtAudience": 74, "rtAudienceLabel": "Upright", "imdb": 7.1, "imdbVotes": 283286, "tmdb": 6.797, "tmdbVotes": 4164}}], "shows": [{"id": "tv-all-creatures-great-small-2020", "title": "All Creatures Great & Small", "year": 2020, "type": "tv", "runtimeSeconds": 3000, "seasonCount": 7, "communityRating": 8.2, "criticRating": null, "genres": ["Drama", "Comedy"], "overview": "The heartwarming and humorous adventures of a young country vet in the Yorkshire Dales in the 1930s. A remake of the 1978 series.", "poster": "assets/tv-all-creatures-great-small-2020-poster.jpg", "backdrop": "assets/tv-all-creatures-great-small-2020-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-11-04T20:13:03.4266156Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 108255, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 99, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.2, "tmdbVotes": 168}}, {"id": "tv-back-to-life-2019", "title": "Back to Life", "year": 2019, "type": "tv", "runtimeSeconds": 1500, "seasonCount": 2, "communityRating": 7.367, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "When Miri Matteson returns home after eighteen years, can she integrate back into her old life? With a terrible event from her past hanging over her, it won\u2019t be easy.", "poster": "assets/tv-back-to-life-2019-poster.jpg", "backdrop": "assets/tv-back-to-life-2019-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0688758Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 88510, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.4, "tmdbVotes": 65}}, {"id": "tv-big-boys-2022", "title": "Big Boys", "year": 2022, "type": "tv", "runtimeSeconds": 1440, "seasonCount": 3, "communityRating": 7.6, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "Two boys from very different ends of the 'spectrum of masculinity' become best friends at Brent University freshers' week; in their first year of uni, they explore, experiment and try to discover themselves, helping\u2026", "poster": "assets/tv-big-boys-2022-poster.jpg", "backdrop": "assets/tv-big-boys-2022-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-11-04T20:25:58.2156837Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 202851, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.6, "tmdbVotes": 49}}, {"id": "tv-call-the-midwife-2012", "title": "Call the Midwife", "year": 2012, "type": "tv", "runtimeSeconds": 3120, "seasonCount": 15, "communityRating": 7.5, "criticRating": null, "genres": ["Drama"], "overview": "Drama following the lives of a group of midwives working in the poverty-stricken East End of London during the 1950s, based on the best-selling memoirs of Jennifer Worth.", "poster": "assets/tv-call-the-midwife-2012-poster.jpg", "backdrop": "assets/tv-call-the-midwife-2012-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0377216Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 39793, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.577, "tmdbVotes": 267}}, {"id": "tv-derry-girls-2018", "title": "Derry Girls", "year": 2018, "type": "tv", "runtimeSeconds": 1380, "seasonCount": 3, "communityRating": 8.144, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "Amidst the political conflict of Northern Ireland in the 1990s, five secondary school students square off with the universal challenges of being a teenager.", "poster": "assets/tv-derry-girls-2018-poster.jpg", "backdrop": "assets/tv-derry-girls-2018-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-02T20:01:55.8955537Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 76148, "artwork": {"posterKind": "center_crop", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 99, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.138, "tmdbVotes": 454}}, {"id": "tv-desperate-housewives-2004", "title": "Desperate Housewives", "year": 2004, "type": "tv", "runtimeSeconds": 2520, "seasonCount": 8, "communityRating": 7.896, "criticRating": null, "genres": ["Mystery", "Drama", "Comedy"], "overview": "Looking down on her friends and family isn't a way of life for Mary Alice Young... it's a way of death. One day, in her perfect house, in the loveliest of suburbs, Mary Alice ended it all. Now she's taking us into\u2026", "poster": "assets/tv-desperate-housewives-2004-poster.jpg", "backdrop": "assets/tv-desperate-housewives-2004-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-29T20:56:40.3505596Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 693, "artwork": {"posterKind": "poster", "backdropKind": "center_crop"}, "ratings": {"rtCritics": 72, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.896, "tmdbVotes": 2171}}, {"id": "tv-the-diplomat-2023", "title": "The Diplomat", "year": 2023, "type": "tv", "runtimeSeconds": null, "seasonCount": 4, "communityRating": 7.569, "criticRating": null, "genres": ["Drama", "War & Politics"], "overview": "Amid an international crisis, a US diplomat contends with her high-profile job as ambassador to the UK and her strained marriage to a political star.", "poster": "assets/tv-the-diplomat-2023-poster.jpg", "backdrop": "assets/tv-the-diplomat-2023-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0640348Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 203857, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 93, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.652, "tmdbVotes": 521}}, {"id": "tv-the-durrells-2016", "title": "The Durrells", "year": 2016, "type": "tv", "runtimeSeconds": 2760, "seasonCount": 4, "communityRating": 7.402, "criticRating": null, "genres": ["Drama", "Comedy", "Family"], "overview": "In 1935, financially strapped widow Louisa Durrell, whose life has fallen apart, decides to move from England, with her four children (three sons, one daughter), to the island of Corfu, Greece. Once there, the family\u2026", "poster": "assets/tv-the-durrells-2016-poster.jpg", "backdrop": "assets/tv-the-durrells-2016-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0619436Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 66134, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 94, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.4, "tmdbVotes": 127}}, {"id": "tv-etoile-2025", "title": "\u00c9toile", "year": 2025, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.197, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "In an ambitious gambit to save their storied institutions, two world-renowned ballet companies in New York City and Paris swap their most talented stars.", "poster": "assets/tv-etoile-2025-poster.jpg", "backdrop": "assets/tv-etoile-2025-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0601383Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 225383, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 86, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.3, "tmdbVotes": 40}}, {"id": "tv-gilmore-girls-2000", "title": "Gilmore Girls", "year": 2000, "type": "tv", "runtimeSeconds": 2640, "seasonCount": 7, "communityRating": 7.893, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "Set in the charming town of Stars Hollow, Connecticut, the series follows the captivating lives of Lorelai and Rory Gilmore, a mother/daughter pair who have a relationship most people only dream of.", "poster": "assets/tv-gilmore-girls-2000-poster.jpg", "backdrop": "assets/tv-gilmore-girls-2000-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0631035Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 4586, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.9, "tmdbVotes": 1339}}, {"id": "tv-ginny-georgia-2021", "title": "Ginny & Georgia", "year": 2021, "type": "tv", "runtimeSeconds": null, "seasonCount": 3, "communityRating": 8.026, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "Free-spirited Georgia and her two kids, Ginny and Austin, move north in search of a fresh start but find that the road to new beginnings can be bumpy.", "poster": "assets/tv-ginny-georgia-2021-poster.jpg", "backdrop": "assets/tv-ginny-georgia-2021-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0694828Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 117581, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 68, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8, "tmdbVotes": 1840}}, {"id": "tv-hellcats-2010", "title": "Hellcats", "year": 2010, "type": "tv", "runtimeSeconds": 2460, "seasonCount": 1, "communityRating": 7.5, "criticRating": null, "genres": ["Drama"], "overview": "Pre-law student Marti Perkins' world is flipped upside down when she loses her scholarship, and realizes the only way she can stay in school is by reigniting her dormant teen gymnastic skills to win a place on Lancer\u2026", "poster": "assets/tv-hellcats-2010-poster.jpg", "backdrop": "assets/tv-hellcats-2010-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-22T20:39:02.7719318Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 32504, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 65, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.5, "tmdbVotes": 116}}, {"id": "tv-high-potential-2024", "title": "High Potential", "year": 2024, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 8.201, "criticRating": null, "genres": ["Drama", "Crime", "Mystery"], "overview": "Morgan is a single mom with an exceptional mind, whose unconventional knack for solving crimes leads to an unusual and unstoppable partnership with a by-the-book seasoned detective.", "poster": "assets/tv-high-potential-2024-poster.jpg", "backdrop": "assets/tv-high-potential-2024-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0686091Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 226637, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 98, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.3, "tmdbVotes": 707}}, {"id": "tv-how-to-get-to-heaven-from-belfast-2026", "title": "How to Get to Heaven from Belfast", "year": 2026, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.027, "criticRating": null, "genres": ["Mystery", "Comedy", "Drama"], "overview": "Three lifelong pals embark on a chaotic quest to solve the mystery of their old friend's suspicious death and keep their own dark secret under wraps.", "poster": "assets/tv-how-to-get-to-heaven-from-belfast-2026-poster.jpg", "backdrop": "assets/tv-how-to-get-to-heaven-from-belfast-2026-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0683192Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 233079, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 95, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.063, "tmdbVotes": 95}}, {"id": "tv-the-hunting-wives-2025", "title": "The Hunting Wives", "year": 2025, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 6.932, "criticRating": null, "genres": ["Drama", "Mystery"], "overview": "Sophie trades New England for East Texas and falls into a wealthy socialite's magnetic orbit \u2014 where a clique of housewives hide deadly secrets.", "poster": "assets/tv-the-hunting-wives-2025-poster.jpg", "backdrop": "assets/tv-the-hunting-wives-2025-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-20T19:42:46.3227892Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 244447, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 81, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.964, "tmdbVotes": 112}}, {"id": "tv-the-lost-flowers-of-alice-hart-2023", "title": "The Lost Flowers of Alice Hart", "year": 2023, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.634, "criticRating": null, "genres": ["Drama", "Mystery"], "overview": "After losing her parents in a mysterious fire, nine-year-old Alice Hart is raised by her grandmother June on a flower farm where she learns there are secrets within secrets. But years on, an unearthed betrayal sees\u2026", "poster": "assets/tv-the-lost-flowers-of-alice-hart-2023-poster.jpg", "backdrop": "assets/tv-the-lost-flowers-of-alice-hart-2023-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0607236Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 125816, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 82, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.64, "tmdbVotes": 161}}, {"id": "tv-love-is-blind-2020", "title": "Love Is Blind", "year": 2020, "type": "tv", "runtimeSeconds": null, "seasonCount": 11, "communityRating": 6.261, "criticRating": null, "genres": ["Reality"], "overview": "Nick and Vanessa Lachey host this social experiment where single men and women look for love and get engaged, all before meeting in person.", "poster": "assets/tv-love-is-blind-2020-poster.jpg", "backdrop": "assets/tv-love-is-blind-2020-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-22T17:11:08.2447226Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 99353, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 66, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.233, "tmdbVotes": 118}}, {"id": "tv-maximum-pleasure-guaranteed-2026", "title": "Maximum Pleasure Guaranteed", "year": 2026, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 6.676, "criticRating": null, "genres": ["Comedy"], "overview": "A newly divorced mom falls down a dangerous rabbit hole of blackmail, murder, and youth soccer.", "poster": "assets/tv-maximum-pleasure-guaranteed-2026-poster.jpg", "backdrop": "assets/tv-maximum-pleasure-guaranteed-2026-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-07T21:04:02.8277793Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 285404, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 93, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.799, "tmdbVotes": 92}}, {"id": "tv-minx-2022", "title": "Minx", "year": 2022, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 6.833, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "An earnest young feminist joins forces with a low-rent publisher to make the world's first erotic magazine for women.", "poster": "assets/tv-minx-2022-poster.jpg", "backdrop": "assets/tv-minx-2022-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0653613Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 118303, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 93, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.9, "tmdbVotes": 123}}, {"id": "tv-offspring-2010", "title": "Offspring", "year": 2010, "type": "tv", "runtimeSeconds": 3600, "seasonCount": 7, "communityRating": 6.9, "criticRating": null, "genres": ["Drama"], "overview": "An exuberant drama set in Melbourne's Fitzroy, centering on Nina Proudman and her struggle to deal with her fabulously messy family, her hunt for a decent love life and her tendency to overthink and fly off into fantasy.", "poster": "assets/tv-offspring-2010-poster.jpg", "backdrop": "assets/tv-offspring-2010-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0664581Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 33155, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.9, "tmdbVotes": 27}}, {"id": "tv-only-murders-in-the-building-2021", "title": "Only Murders in the Building", "year": 2021, "type": "tv", "runtimeSeconds": null, "seasonCount": 5, "communityRating": 8.398, "criticRating": null, "genres": ["Comedy", "Mystery", "Crime"], "overview": "Three strangers share an obsession with true crime and suddenly find themselves wrapped up in one.", "poster": "assets/tv-only-murders-in-the-building-2021-poster.jpg", "backdrop": "assets/tv-only-murders-in-the-building-2021-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-11-04T20:25:58.2163301Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 107113, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 95, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.398, "tmdbVotes": 2333}}, {"id": "tv-outlander-blood-of-my-blood-2025", "title": "Outlander: Blood of My Blood", "year": 2025, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 8.542, "criticRating": null, "genres": ["Drama"], "overview": "From the battlefields of World War I to the rugged Highlands of 18th century Scotland, two fated couples must defy the forces that seek to tear them apart, unfolding in surprising and unforeseen ways in this romantic\u2026", "poster": "assets/tv-outlander-blood-of-my-blood-2025-poster.jpg", "backdrop": "assets/tv-outlander-blood-of-my-blood-2025-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0691373Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 207484, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 94, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.738, "tmdbVotes": 124}}, {"id": "tv-rivals-2024", "title": "Rivals", "year": 2024, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 7.44, "criticRating": null, "genres": ["Drama"], "overview": "It is 1986 and Britain is booming. \"Rivals\" dives headfirst into the cutthroat world of TV where hair-dos are big and ambitions are even bigger. Deals are brokered in boardrooms, as well as bedrooms. Nobody can be\u2026", "poster": "assets/tv-rivals-2024-poster.jpg", "backdrop": "assets/tv-rivals-2024-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0645815Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 208921, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 96, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.4, "tmdbVotes": 93}}, {"id": "tv-rooster-2026", "title": "Rooster", "year": 2026, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.431, "criticRating": null, "genres": ["Comedy"], "overview": "On a college campus, an author navigates a complicated relationship with his daughter.", "poster": "assets/tv-rooster-2026-poster.jpg", "backdrop": "assets/tv-rooster-2026-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.063389Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 254953, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 88, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.423, "tmdbVotes": 144}}, {"id": "tv-sex-and-the-city-1998", "title": "Sex and the City", "year": 1998, "type": "tv", "runtimeSeconds": null, "seasonCount": 6, "communityRating": 7.365, "criticRating": null, "genres": ["Drama", "Comedy"], "overview": "Based on the bestselling book by Candace Bushnell, Sex and the City tells the story of four best friends, all single and in their late thirties, as they pursue their careers and talk about their sex lives, all while\u2026", "poster": "assets/tv-sex-and-the-city-1998-poster.jpg", "backdrop": "assets/tv-sex-and-the-city-1998-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0679324Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 105, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 70, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.351, "tmdbVotes": 2220}}, {"id": "tv-skymed-2022", "title": "SkyMed", "year": 2022, "type": "tv", "runtimeSeconds": 2640, "seasonCount": 4, "communityRating": 6.665, "criticRating": null, "genres": ["Drama"], "overview": "Life, death and drama at 20,000 feet. The series weaves together intense character journeys and high-stakes medical rescues, as we follow the triumphs, heartbreaks and tribulations of budding nurses and pilots flying\u2026", "poster": "assets/tv-skymed-2022-poster.jpg", "backdrop": "assets/tv-skymed-2022-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-22T19:44:29.2431354Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 133908, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.699, "tmdbVotes": 98}}, {"id": "tv-so-you-think-you-can-dance-2008", "title": "So You Think You Can Dance", "year": 2008, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 9.8, "criticRating": null, "genres": ["Family", "Reality"], "overview": "So You Think You Can Dance is a joint Belgian-Dutch dance competition show broadcast on RTL 5 and vtm television stations. The first season was broadcast only in the Netherlands, whereas starting with season 2, the\u2026", "poster": "assets/tv-so-you-think-you-can-dance-2008-poster.jpg", "backdrop": "assets/tv-so-you-think-you-can-dance-2008-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-23T11:11:35.4124418Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 40231, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 9.8, "tmdbVotes": 4}}, {"id": "tv-the-white-lotus-2021", "title": "The White Lotus", "year": 2021, "type": "tv", "runtimeSeconds": null, "seasonCount": 3, "communityRating": 7.597, "criticRating": null, "genres": ["Comedy", "Drama", "Mystery"], "overview": "Follow the exploits of various guests and employees at an exclusive tropical resort over the span of a week as with each passing day, a darker complexity emerges in these picture-perfect travelers, the hotel\u2019s\u2026", "poster": "assets/tv-the-white-lotus-2021-poster.jpg", "backdrop": "assets/tv-the-white-lotus-2021-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-20T02:45:52.8742674Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 111803, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 90, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.599, "tmdbVotes": 1604}}, {"id": "tv-yellowjackets-2021", "title": "Yellowjackets", "year": 2021, "type": "tv", "runtimeSeconds": null, "seasonCount": 3, "communityRating": 7.431, "criticRating": null, "genres": ["Drama", "Mystery"], "overview": "This equal parts survival epic, psychological horror story and coming-of-age drama tells the saga of a team of wildly talented high school girls soccer players who become the (un)lucky survivors of a plane crash deep\u2026", "poster": "assets/tv-yellowjackets-2021-poster.jpg", "backdrop": "assets/tv-yellowjackets-2021-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-11-04T20:31:47.4592756Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 117488, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 92, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.431, "tmdbVotes": 1070}}], "anime": [], "resume": [{"id": "episode-sex-and-the-country-2001-s4e9", "title": "Sex and the Country", "year": 2001, "type": "episode", "runtimeSeconds": 1751, "seasonCount": null, "communityRating": 5.9, "criticRating": null, "genres": ["Comedy", "Drama", "Romance"], "overview": "Carrie is dragged to Aidan's country home in Suffern, New York. Charlotte visits the MacDougal \"compound\" in Connecticut. Miranda yells at Steve when he tells her he has testicular cancer but has no clue about his\u2026", "poster": "assets/episode-sex-and-the-country-2001-s4e9-poster.jpg", "backdrop": "assets/episode-sex-and-the-country-2001-s4e9-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 96.08, "progress": 0.0549, "addedAt": "2026-07-17T10:53:15.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "Sex and the City", "seasonNumber": 4, "episodeNumber": 9, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}], "nextUp": [{"id": "episode-what-more-do-i-need-2008-s5e7", "title": "What More Do I Need?", "year": 2008, "type": "episode", "runtimeSeconds": 2521, "seasonCount": null, "communityRating": 8, "criticRating": null, "genres": ["Comedy", "Drama", "Mystery"], "overview": "Lynette and Tom uncover the unsettling truth about son Porter. Gabrielle suspects Carlos' best client, Virginia, has ulterior motives. Bree's moment of weakness could bring her great embarrassment and shame.\u2026", "poster": "assets/episode-what-more-do-i-need-2008-s5e7-poster.jpg", "backdrop": "assets/episode-what-more-do-i-need-2008-s5e7-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-29T14:37:28.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "Desperate Housewives", "seasonNumber": 5, "episodeNumber": 7, "artwork": {"posterKind": "center_crop", "backdropKind": "backdrop"}}, {"id": "episode-new-day-2021-s1e2", "title": "New Day", "year": 2021, "type": "episode", "runtimeSeconds": 3445, "seasonCount": null, "communityRating": 7.143, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "A cautiously optimistic Belinda walks a fine line with Tanya, while Armond brainstorms ways to deal with Shane. After her husband belittles her work, Rachel approaches Nicole for advice. Paula and Olivia lose track\u2026", "poster": "assets/episode-new-day-2021-s1e2-poster.jpg", "backdrop": "assets/episode-new-day-2021-s1e2-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-19T22:18:57.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "The White Lotus", "seasonNumber": 1, "episodeNumber": 2, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}, {"id": "episode-the-uncomfortability-2010-s1e2", "title": "The Uncomfortability", "year": 2010, "type": "episode", "runtimeSeconds": 2687, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "Nina's insecurities are revealed when a patient poses an awkward request. Meanwhile Geraldine decides to try online dating, and Jimmy is an unexpected saviour to Cherie and Darcy.", "poster": "assets/episode-the-uncomfortability-2010-s1e2-poster.jpg", "backdrop": "assets/episode-the-uncomfortability-2010-s1e2-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T11:47:42.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "Offspring", "seasonNumber": 1, "episodeNumber": 2, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}, {"id": "episode-episode-2-2016-s1e2", "title": "Episode 2", "year": 2016, "type": "episode", "runtimeSeconds": 2766, "seasonCount": null, "communityRating": 8.3, "criticRating": null, "genres": ["Biography", "Comedy", "Drama"], "overview": "Larry falls dangerously ill, and it falls to Theo and his neighbour Sven to put their rudimentary medical skills to the task of saving his life. The family's money worries continue, but Spiros brings them good news\u2026", "poster": "assets/episode-episode-2-2016-s1e2-poster.jpg", "backdrop": "assets/episode-episode-2-2016-s1e2-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-31T07:29:17.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "The Durrells", "seasonNumber": 1, "episodeNumber": 2, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}, {"id": "episode-remembrance-day-2026-s2e1", "title": "Remembrance Day", "year": 2026, "type": "episode", "runtimeSeconds": 3822, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Fantasy", "Romance"], "overview": "The fiery crosses have been lit across the Highlands; lovers must part, as the call to arms brings friends and enemies home and the Jacobite Rebellion begins in earnest.", "poster": "assets/episode-remembrance-day-2026-s2e1-poster.jpg", "backdrop": "assets/episode-remembrance-day-2026-s2e1-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-19T03:50:07.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "Outlander: Blood of My Blood", "seasonNumber": 2, "episodeNumber": 1, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}, {"id": "episode-one-of-us-2024-s1e7", "title": "One of Us", "year": 2024, "type": "episode", "runtimeSeconds": 2555, "seasonCount": null, "communityRating": 7.7, "criticRating": null, "genres": [], "overview": "Morgan agrees to show Ava around the LAPD, but things quickly take a dangerous turn when the precinct is held hostage by the friends of a recently convicted man. Can Morgan and the detectives outsmart their captors\u2026", "poster": "assets/episode-one-of-us-2024-s1e7-poster.jpg", "backdrop": "assets/episode-one-of-us-2024-s1e7-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T13:57:22.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "High Potential", "seasonNumber": 1, "episodeNumber": 7, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}, {"id": "episode-the-breakup-part-2-2001-s1e17", "title": "The Breakup, Part 2", "year": 2001, "type": "episode", "runtimeSeconds": 2669, "seasonCount": null, "communityRating": 7.4, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "After recent events, Rory keeps busy, refusing to give in to self-pity. Meanwhile, Stars Hollow's residents rally around her, much to her dismay.", "poster": "assets/episode-the-breakup-part-2-2001-s1e17-poster.jpg", "backdrop": "assets/episode-the-breakup-part-2-2001-s1e17-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T11:28:05.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "Gilmore Girls", "seasonNumber": 1, "episodeNumber": 17, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}, {"id": "episode-i-heart-ny-2002-s4e18", "title": "I Heart NY", "year": 2002, "type": "episode", "runtimeSeconds": 1905, "seasonCount": null, "communityRating": 6.667, "criticRating": null, "genres": ["Comedy", "Drama", "Romance"], "overview": "Carrie is shocked to find out that Big is moving to Napa, California and plans to give him a New York send-off. Charlotte begins dating another recent divorc\u00e9. Miranda prepares for the new baby as her delivery date\u2026", "poster": "assets/episode-i-heart-ny-2002-s4e18-poster.jpg", "backdrop": "assets/episode-i-heart-ny-2002-s4e18-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T12:35:14.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "Sex and the City", "seasonNumber": 4, "episodeNumber": 18, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}], "recentlyAdded": [{"id": "tv-yellowjackets-2021", "title": "Yellowjackets", "year": 2021, "type": "tv", "runtimeSeconds": null, "seasonCount": 3, "communityRating": 7.431, "criticRating": null, "genres": ["Drama", "Mystery"], "overview": "This equal parts survival epic, psychological horror story and coming-of-age drama tells the saga of a team of wildly talented high school girls soccer players who become the (un)lucky survivors of a plane crash deep\u2026", "poster": "assets/tv-yellowjackets-2021-poster.jpg", "backdrop": "assets/tv-yellowjackets-2021-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-11-04T20:31:47.4592756Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 117488, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 92, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.431, "tmdbVotes": 1070}}, {"id": "tv-only-murders-in-the-building-2021", "title": "Only Murders in the Building", "year": 2021, "type": "tv", "runtimeSeconds": null, "seasonCount": 5, "communityRating": 8.398, "criticRating": null, "genres": ["Comedy", "Mystery", "Crime"], "overview": "Three strangers share an obsession with true crime and suddenly find themselves wrapped up in one.", "poster": "assets/tv-only-murders-in-the-building-2021-poster.jpg", "backdrop": "assets/tv-only-murders-in-the-building-2021-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-11-04T20:25:58.2163301Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 107113, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 95, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.398, "tmdbVotes": 2333}}, {"id": "tv-big-boys-2022", "title": "Big Boys", "year": 2022, "type": "tv", "runtimeSeconds": 1440, "seasonCount": 3, "communityRating": 7.6, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "Two boys from very different ends of the 'spectrum of masculinity' become best friends at Brent University freshers' week; in their first year of uni, they explore, experiment and try to discover themselves, helping\u2026", "poster": "assets/tv-big-boys-2022-poster.jpg", "backdrop": "assets/tv-big-boys-2022-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-11-04T20:25:58.2156837Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 202851, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.6, "tmdbVotes": 49}}, {"id": "tv-all-creatures-great-small-2020", "title": "All Creatures Great & Small", "year": 2020, "type": "tv", "runtimeSeconds": 3000, "seasonCount": 7, "communityRating": 8.2, "criticRating": null, "genres": ["Drama", "Comedy"], "overview": "The heartwarming and humorous adventures of a young country vet in the Yorkshire Dales in the 1930s. A remake of the 1978 series.", "poster": "assets/tv-all-creatures-great-small-2020-poster.jpg", "backdrop": "assets/tv-all-creatures-great-small-2020-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-11-04T20:13:03.4266156Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 108255, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 99, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.2, "tmdbVotes": 168}}, {"id": "movie-the-killing-of-a-sacred-deer-2017", "title": "The Killing of a Sacred Deer", "year": 2017, "type": "movie", "runtimeSeconds": 7262, "seasonCount": null, "communityRating": 7.003, "criticRating": 79, "genres": ["Drama", "Thriller", "Mystery"], "overview": "Dr. Steven Murphy is a renowned cardiovascular surgeon who presides over a spotless household with his wife and two children. Lurking at the margins of his idyllic suburban existence is Martin, a fatherless teen who\u2026", "poster": "assets/movie-the-killing-of-a-sacred-deer-2017-poster.jpg", "backdrop": "assets/movie-the-killing-of-a-sacred-deer-2017-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-22T21:49:26.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 399057, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 79, "rtCriticsLabel": "Certified Fresh", "rtAudience": 63, "rtAudienceLabel": "Upright", "imdb": 7, "imdbVotes": 219488, "tmdb": 7.001, "tmdbVotes": 4640}}, {"id": "tv-derry-girls-2018", "title": "Derry Girls", "year": 2018, "type": "tv", "runtimeSeconds": 1380, "seasonCount": 3, "communityRating": 8.144, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "Amidst the political conflict of Northern Ireland in the 1990s, five secondary school students square off with the universal challenges of being a teenager.", "poster": "assets/tv-derry-girls-2018-poster.jpg", "backdrop": "assets/tv-derry-girls-2018-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-02T20:01:55.8955537Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 76148, "artwork": {"posterKind": "center_crop", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 99, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.138, "tmdbVotes": 454}}, {"id": "tv-so-you-think-you-can-dance-2008", "title": "So You Think You Can Dance", "year": 2008, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 9.8, "criticRating": null, "genres": ["Family", "Reality"], "overview": "So You Think You Can Dance is a joint Belgian-Dutch dance competition show broadcast on RTL 5 and vtm television stations. The first season was broadcast only in the Netherlands, whereas starting with season 2, the\u2026", "poster": "assets/tv-so-you-think-you-can-dance-2008-poster.jpg", "backdrop": "assets/tv-so-you-think-you-can-dance-2008-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-23T11:11:35.4124418Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 40231, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 9.8, "tmdbVotes": 4}}, {"id": "tv-hellcats-2010", "title": "Hellcats", "year": 2010, "type": "tv", "runtimeSeconds": 2460, "seasonCount": 1, "communityRating": 7.5, "criticRating": null, "genres": ["Drama"], "overview": "Pre-law student Marti Perkins' world is flipped upside down when she loses her scholarship, and realizes the only way she can stay in school is by reigniting her dormant teen gymnastic skills to win a place on Lancer\u2026", "poster": "assets/tv-hellcats-2010-poster.jpg", "backdrop": "assets/tv-hellcats-2010-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-22T20:39:02.7719318Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 32504, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 65, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.5, "tmdbVotes": 116}}, {"id": "tv-skymed-2022", "title": "SkyMed", "year": 2022, "type": "tv", "runtimeSeconds": 2640, "seasonCount": 4, "communityRating": 6.665, "criticRating": null, "genres": ["Drama"], "overview": "Life, death and drama at 20,000 feet. The series weaves together intense character journeys and high-stakes medical rescues, as we follow the triumphs, heartbreaks and tribulations of budding nurses and pilots flying\u2026", "poster": "assets/tv-skymed-2022-poster.jpg", "backdrop": "assets/tv-skymed-2022-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-22T19:44:29.2431354Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 133908, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.699, "tmdbVotes": 98}}, {"id": "tv-love-is-blind-2020", "title": "Love Is Blind", "year": 2020, "type": "tv", "runtimeSeconds": null, "seasonCount": 11, "communityRating": 6.261, "criticRating": null, "genres": ["Reality"], "overview": "Nick and Vanessa Lachey host this social experiment where single men and women look for love and get engaged, all before meeting in person.", "poster": "assets/tv-love-is-blind-2020-poster.jpg", "backdrop": "assets/tv-love-is-blind-2020-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-22T17:11:08.2447226Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 99353, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 66, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.233, "tmdbVotes": 118}}, {"id": "tv-the-hunting-wives-2025", "title": "The Hunting Wives", "year": 2025, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 6.932, "criticRating": null, "genres": ["Drama", "Mystery"], "overview": "Sophie trades New England for East Texas and falls into a wealthy socialite's magnetic orbit \u2014 where a clique of housewives hide deadly secrets.", "poster": "assets/tv-the-hunting-wives-2025-poster.jpg", "backdrop": "assets/tv-the-hunting-wives-2025-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-20T19:42:46.3227892Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 244447, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 81, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.964, "tmdbVotes": 112}}, {"id": "tv-the-white-lotus-2021", "title": "The White Lotus", "year": 2021, "type": "tv", "runtimeSeconds": null, "seasonCount": 3, "communityRating": 7.597, "criticRating": null, "genres": ["Comedy", "Drama", "Mystery"], "overview": "Follow the exploits of various guests and employees at an exclusive tropical resort over the span of a week as with each passing day, a darker complexity emerges in these picture-perfect travelers, the hotel\u2019s\u2026", "poster": "assets/tv-the-white-lotus-2021-poster.jpg", "backdrop": "assets/tv-the-white-lotus-2021-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-20T02:45:52.8742674Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 111803, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 90, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.599, "tmdbVotes": 1604}}], "discover": [{"id": "movie-across-the-hall-2009", "title": "Across the Hall", "year": 2009, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Thriller", "Mystery"], "overview": "Convinced that his fiancee is cheating on him, a man follows her to a hotel and calls his best friend to help him avoid a calamity.", "poster": "assets/movie-across-the-hall-2009-poster.jpg", "backdrop": "assets/movie-across-the-hall-2009-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 21469, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": 39, "rtAudienceLabel": "Spilled", "imdb": 5.8, "imdbVotes": 5130, "tmdb": 6, "tmdbVotes": 129}}, {"id": "movie-addicted-2014", "title": "Addicted", "year": 2014, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Thriller"], "overview": "A gallerist risks her family and flourishing career when she enters into an affair with a talented painter and slowly loses control of her life.", "poster": "assets/movie-addicted-2014-poster.jpg", "backdrop": "assets/movie-addicted-2014-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 235271, "ratings": {"rtCritics": 7, "rtCriticsLabel": "Rotten", "rtAudience": 51, "rtAudienceLabel": "Spilled", "imdb": 5, "imdbVotes": 13131, "tmdb": 5.928, "tmdbVotes": 466}}, {"id": "movie-adulterers-2016", "title": "Adulterers", "year": 2016, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Thriller", "Drama"], "overview": "On a steamy New Orleans afternoon, Sam Deuprey decides to surprise his beautiful wife, Ashley, for their anniversary and comes home early. But Ashley is not alone in the bedroom. She and her lover, Damien are about\u2026", "poster": "assets/movie-adulterers-2016-poster.jpg", "backdrop": "assets/movie-adulterers-2016-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 376004, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": 37, "rtAudienceLabel": "Spilled", "imdb": 5.1, "imdbVotes": 2144, "tmdb": 6.2, "tmdbVotes": 116}}, {"id": "movie-alone-with-her-2006", "title": "Alone With Her", "year": 2006, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Crime", "Drama", "Thriller"], "overview": "The harrowing story of a disturbed young man's attempts to win the affections of an unsuspecting young woman. When Doug first sees Amy, he instantly falls for her and begins to watch her every move, going so far as\u2026", "poster": "assets/movie-alone-with-her-2006-poster.jpg", "backdrop": "assets/movie-alone-with-her-2006-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 18206, "ratings": {"rtCritics": 70, "rtCriticsLabel": "Fresh", "rtAudience": 46, "rtAudienceLabel": "Spilled", "imdb": 6, "imdbVotes": 4359, "tmdb": 5.9, "tmdbVotes": 94}}, {"id": "movie-awake-2007", "title": "Awake", "year": 2007, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Thriller", "Crime", "Mystery"], "overview": "While undergoing heart surgery, a man experiences a phenomenon called \u2018anesthetic awareness\u2019, which leaves him awake but paralyzed throughout the operation. As various obstacles present themselves, his wife must make\u2026", "poster": "assets/movie-awake-2007-poster.jpg", "backdrop": "assets/movie-awake-2007-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 13483, "ratings": {"rtCritics": 23, "rtCriticsLabel": "Rotten", "rtAudience": 55, "rtAudienceLabel": "Spilled", "imdb": 6.5, "imdbVotes": 89389, "tmdb": 6.586, "tmdbVotes": 1649}}, {"id": "movie-black-bear-2020", "title": "Black Bear", "year": 2020, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Thriller"], "overview": "At a remote lake house in the Adirondack Mountains, a couple entertains an out-of-town guest looking for inspiration in her filmmaking. The group quickly falls into a calculated game of desire, manipulation, and\u2026", "poster": "assets/movie-black-bear-2020-poster.jpg", "backdrop": "assets/movie-black-bear-2020-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 624788, "ratings": {"rtCritics": 89, "rtCriticsLabel": "Certified Fresh", "rtAudience": 64, "rtAudienceLabel": "Upright", "imdb": 6.5, "imdbVotes": 18408, "tmdb": 6.337, "tmdbVotes": 315}}, {"id": "movie-butterfly-on-a-wheel-2007", "title": "Butterfly on a Wheel", "year": 2007, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Crime", "Drama", "Thriller"], "overview": "A sociopathic kidnapper methodically pushes a desperate pair of parents to their absolute breaking point.", "poster": "assets/movie-butterfly-on-a-wheel-2007-poster.jpg", "backdrop": "assets/movie-butterfly-on-a-wheel-2007-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 13250, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": 61, "rtAudienceLabel": "Upright", "imdb": 6.6, "imdbVotes": 35409, "tmdb": 6.457, "tmdbVotes": 595}}, {"id": "movie-careful-what-you-wish-for-2015", "title": "Careful What You Wish For", "year": 2015, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Thriller"], "overview": "A guy gets more than he bargained for after entering into an affair with the wife of an investment banker. Soon, a suspicious death and substantial life insurance policy embroil him in a scandal.", "poster": "assets/movie-careful-what-you-wish-for-2015-poster.jpg", "backdrop": "assets/movie-careful-what-you-wish-for-2015-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 317744, "ratings": {"rtCritics": 17, "rtCriticsLabel": "Rotten", "rtAudience": 61, "rtAudienceLabel": "Upright", "imdb": 5.6, "imdbVotes": 9452, "tmdb": 6.129, "tmdbVotes": 446}}, {"id": "movie-crime-of-passion-1956", "title": "Crime of Passion", "year": 1956, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Crime", "Thriller"], "overview": "Kathy leaves the newspaper business to marry homicide detective Bill, but is frustrated by his lack of ambition and the banality of life in the suburbs. Her drive to advance Bill's career soon takes her down a\u2026", "poster": "assets/movie-crime-of-passion-1956-poster.jpg", "backdrop": "assets/movie-crime-of-passion-1956-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "center_crop", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 1920, "ratings": {"rtCritics": 80, "rtCriticsLabel": "Fresh", "rtAudience": 34, "rtAudienceLabel": "Spilled", "imdb": 6.4, "imdbVotes": 3750, "tmdb": 6.1, "tmdbVotes": 57}}, {"id": "movie-deep-water-2022", "title": "Deep Water", "year": 2022, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Mystery", "Thriller"], "overview": "A well-to-do husband who allows his wife to have affairs in order to avoid a divorce becomes a prime suspect in the disappearance of her lovers.", "poster": "assets/movie-deep-water-2022-poster.jpg", "backdrop": "assets/movie-deep-water-2022-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 619979, "ratings": {"rtCritics": 35, "rtCriticsLabel": "Rotten", "rtAudience": 25, "rtAudienceLabel": "Spilled", "imdb": 5.5, "imdbVotes": 74098, "tmdb": 5.834, "tmdbVotes": 1755}}, {"id": "movie-derailed-2005", "title": "Derailed", "year": 2005, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Thriller"], "overview": "When two married business executives having an affair are blackmailed by a violent criminal, they are forced to turn the tables on him to save their families.", "poster": "assets/movie-derailed-2005-poster.jpg", "backdrop": "assets/movie-derailed-2005-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 8999, "ratings": {"rtCritics": 20, "rtCriticsLabel": "Rotten", "rtAudience": 58, "rtAudienceLabel": "Spilled", "imdb": 6.6, "imdbVotes": 79563, "tmdb": 6.405, "tmdbVotes": 1193}}, {"id": "movie-die-my-love-2025", "title": "Die My Love", "year": 2025, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama"], "overview": "After inheriting a remote Montana house, Jackson moves there from New York with his partner Grace, and the couple soon welcome a child. As Jackson becomes increasingly absent and rural isolation sets in, Grace\u2026", "poster": "assets/movie-die-my-love-2025-poster.jpg", "backdrop": "assets/movie-die-my-love-2025-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 1033148, "ratings": {"rtCritics": 74, "rtCriticsLabel": "Certified Fresh", "rtAudience": 45, "rtAudienceLabel": "Spilled", "imdb": 6, "imdbVotes": 50664, "tmdb": 6.1, "tmdbVotes": 683}}, {"id": "movie-dream-house-2011", "title": "Dream House", "year": 2011, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Mystery", "Drama", "Thriller"], "overview": "Publisher Will Atenton quits a lucrative job in New York to relocate his wife, Libby, and their daughters to a quaint town in New England. However, as they settle into their home the Atentons discover that a woman\u2026", "poster": "assets/movie-dream-house-2011-poster.jpg", "backdrop": "assets/movie-dream-house-2011-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 69668, "ratings": {"rtCritics": 7, "rtCriticsLabel": "Rotten", "rtAudience": 36, "rtAudienceLabel": "Spilled", "imdb": 6, "imdbVotes": 71386, "tmdb": 6.212, "tmdbVotes": 1480}}, {"id": "movie-dream-lover-1994", "title": "Dream Lover", "year": 1994, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Mystery", "Thriller"], "overview": "Not long after they cross paths at an art gallery, architect Ray Reardon and hypnotically sensual Lena are married with children. But as strange incidents occur, Ray begins to realize he may not really know the woman\u2026", "poster": "assets/movie-dream-lover-1994-poster.jpg", "backdrop": "assets/movie-dream-lover-1994-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 18190, "ratings": {"rtCritics": 57, "rtCriticsLabel": "Rotten", "rtAudience": 38, "rtAudienceLabel": "Spilled", "imdb": 6.2, "imdbVotes": 7865, "tmdb": 6.844, "tmdbVotes": 167}}, {"id": "movie-drop-2025", "title": "Drop", "year": 2025, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Mystery", "Thriller"], "overview": "Violet, a widowed mother on her first date in years, arrives at an upscale restaurant where she is relieved that her date, Henry, is more charming and handsome than she expected. But their chemistry begins to curdle\u2026", "poster": "assets/movie-drop-2025-poster.jpg", "backdrop": "assets/movie-drop-2025-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 1249213, "ratings": {"rtCritics": 83, "rtCriticsLabel": "Certified Fresh", "rtAudience": 79, "rtAudienceLabel": "Upright", "imdb": 6.1, "imdbVotes": 61018, "tmdb": 6.275, "tmdbVotes": 1023}}, {"id": "movie-eastern-promises-2007", "title": "Eastern Promises", "year": 2007, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Thriller", "Crime", "Mystery"], "overview": "A Russian teenager living in London dies during childbirth but leaves clues in her diary that could tie her child to a rape involving a violent Russian mob family.", "poster": "assets/movie-eastern-promises-2007-poster.jpg", "backdrop": "assets/movie-eastern-promises-2007-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 2252, "ratings": {"rtCritics": 89, "rtCriticsLabel": "Certified Fresh", "rtAudience": 83, "rtAudienceLabel": "Upright", "imdb": 7.6, "imdbVotes": 281015, "tmdb": 7.349, "tmdbVotes": 3900}}, {"id": "movie-faces-1968", "title": "Faces", "year": 1968, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama"], "overview": "Middle-aged suburban husband Richard abruptly tells his wife, Maria, that he wants a divorce. As Richard takes up with a younger woman, Maria enjoys a night on the town with her friends and meets a younger man. As\u2026", "poster": "assets/movie-faces-1968-poster.jpg", "backdrop": "assets/movie-faces-1968-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "center_crop", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 753, "ratings": {"rtCritics": 83, "rtCriticsLabel": "Fresh", "rtAudience": 86, "rtAudienceLabel": "Upright", "imdb": 7.4, "imdbVotes": 12950, "tmdb": 7.093, "tmdbVotes": 227}}, {"id": "movie-fatale-2020", "title": "Fatale", "year": 2020, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Thriller"], "overview": "After a wild one-night stand, successful sports agent Derrick Tyler watches his perfect life slowly disappear when he discovers the sexy and mysterious woman he risked everything for is determined police detective\u2026", "poster": "assets/movie-fatale-2020-poster.jpg", "backdrop": "assets/movie-fatale-2020-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 549514, "ratings": {"rtCritics": 46, "rtCriticsLabel": "Rotten", "rtAudience": 87, "rtAudienceLabel": "Upright", "imdb": 5.5, "imdbVotes": 16392, "tmdb": 6, "tmdbVotes": 401}}, {"id": "tv-56-days-2026", "title": "56 Days", "year": 2026, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Crime", "Mystery"], "overview": "When an unidentified body is found in a luxury apartment linked to Oliver Kennedy and his girlfriend Ciara Wyse, Detectives Lee Reardon and Karl Connolly reconstruct the couple's deadly romance across the past 56 days.", "poster": "assets/tv-56-days-2026-poster.jpg", "backdrop": "assets/tv-56-days-2026-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 241372, "ratings": {"rtCritics": 60, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.388, "tmdbVotes": 245}}, {"id": "tv-90-day-fiance-before-the-90-days-2017", "title": "90 Day Fianc\u00e9: Before the 90 Days", "year": 2017, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Reality"], "overview": "Follow couples who have an existing relationship online, but haven't met in person. We'll experience their journey as they travel to the other's foreign country for the first time in an attempt to establish an\u2026", "poster": "assets/tv-90-day-fiance-before-the-90-days-2017-poster.jpg", "backdrop": "assets/tv-90-day-fiance-before-the-90-days-2017-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 73319, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.5, "tmdbVotes": 45}}, {"id": "tv-90-day-fiance-the-other-way-2019", "title": "90 Day Fianc\u00e9: The Other Way", "year": 2019, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Reality"], "overview": "Americans move to a foreign country all in the name of love. Will uprooting their lives in the United States pay off, or will they be packing up and moving back to America brokenhearted?", "poster": "assets/tv-90-day-fiance-the-other-way-2019-poster.jpg", "backdrop": "assets/tv-90-day-fiance-the-other-way-2019-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 90046, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.8, "tmdbVotes": 34}}, {"id": "tv-absolutely-fabulous-1992", "title": "Absolutely Fabulous", "year": 1992, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Comedy"], "overview": "Set in the world of fashion and PR, immature fun-loving mother Edina Monsoon and her best friend Patsy drive Eddie's sensible daughter, Saffron, up the wall with their constant drug abuse and outrageous selfishness.\u2026", "poster": "assets/tv-absolutely-fabulous-1992-poster.jpg", "backdrop": "assets/tv-absolutely-fabulous-1992-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 4493, "ratings": {"rtCritics": 96, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.6, "tmdbVotes": 198}}, {"id": "tv-the-afterparty-2022", "title": "The Afterparty", "year": 2022, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Mystery", "Comedy", "Crime"], "overview": "Nothing is as it seems in this hilarious murder mystery where each suspect's story is told through a different film genre.", "poster": "assets/tv-the-afterparty-2022-poster.jpg", "backdrop": "assets/tv-the-afterparty-2022-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 106454, "ratings": {"rtCritics": 92, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.071, "tmdbVotes": 245}}, {"id": "tv-agatha-christie-s-marple-2004", "title": "Agatha Christie's Marple", "year": 2004, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Crime", "Mystery"], "overview": "The adventures of Miss Jane Marple, an elderly spinster living in the quiet little village of St Mary Mead. During her many visits to friends and relatives in other villages, Miss Marple often stumbles upon\u2026", "poster": "assets/tv-agatha-christie-s-marple-2004-poster.jpg", "backdrop": "assets/tv-agatha-christie-s-marple-2004-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 6127, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.9, "tmdbVotes": 152}}, {"id": "tv-all-american-homecoming-2022", "title": "All American: Homecoming", "year": 2022, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama"], "overview": "Follow the journeys of Simone Hicks, a tennis hopeful from Beverly Hills, and elite baseball player Damon Sims from Chicago, as they navigate life at the prestigious HBCU Bringston University.", "poster": "assets/tv-all-american-homecoming-2022-poster.jpg", "backdrop": "assets/tv-all-american-homecoming-2022-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 153723, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.3, "tmdbVotes": 70}}, {"id": "tv-and-just-like-that-2021", "title": "And Just Like That\u2026", "year": 2021, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "This new chapter of \u201cSex and the City\u201d follows Carrie, Miranda and Charlotte as they navigate the journey from the complicated reality of life and friendship in their 30s to the even more complicated reality of life\u2026", "poster": "assets/tv-and-just-like-that-2021-poster.jpg", "backdrop": "assets/tv-and-just-like-that-2021-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 116450, "ratings": {"rtCritics": 52, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.211, "tmdbVotes": 556}}, {"id": "tv-anne-with-an-e-2017", "title": "Anne with an E", "year": 2017, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Family"], "overview": "A coming-of-age story about an outsider who, against all odds and numerous challenges, fights for love and acceptance and for her place in the world. The series centers on a young orphaned girl in the late 1890s,\u2026", "poster": "assets/tv-anne-with-an-e-2017-poster.jpg", "backdrop": "assets/tv-anne-with-an-e-2017-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 70785, "ratings": {"rtCritics": 63, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.7, "tmdbVotes": 5019}}, {"id": "tv-awkward-2011", "title": "Awkward.", "year": 2011, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "An irreverent look at the conflict, chaos and humor that defines teenage life through the eyes of 15-year-old Jenna Hamilton whose life begins to change when a simple accident becomes an epic misunderstanding and is\u2026", "poster": "assets/tv-awkward-2011-poster.jpg", "backdrop": "assets/tv-awkward-2011-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 39859, "ratings": {"rtCritics": 78, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.4, "tmdbVotes": 427}}, {"id": "tv-the-baby-sitters-club-2020", "title": "The Baby-Sitters Club", "year": 2020, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Kids", "Drama"], "overview": "In this contemporary take on the beloved book series, five best friends launch a baby-sitting business that's big on fun and adventure.", "poster": "assets/tv-the-baby-sitters-club-2020-poster.jpg", "backdrop": "assets/tv-the-baby-sitters-club-2020-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 100412, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.9, "tmdbVotes": 506}}, {"id": "tv-ballers-2015", "title": "Ballers", "year": 2015, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Comedy"], "overview": "Looking at the lives of former and current football players, the show follows former superstar Spencer Strasmore as he gets his life on track in retirement while mentoring other current and former players through the\u2026", "poster": "assets/tv-ballers-2015-poster.jpg", "backdrop": "assets/tv-ballers-2015-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 62704, "ratings": {"rtCritics": 72, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.809, "tmdbVotes": 820}}, {"id": "tv-baretta-1975", "title": "Baretta", "year": 1975, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Crime", "Drama", "Mystery"], "overview": "Baretta is an American detective television series which ran on ABC from 1975 to 1978. The show was a milder version of a successful 1973\u201374 ABC series, Toma, starring Tony Musante as chameleon-like, real-life New\u2026", "poster": "assets/tv-baretta-1975-poster.jpg", "backdrop": "assets/tv-baretta-1975-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 3216, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.5, "tmdbVotes": 43}}, {"id": "tv-based-on-a-true-story-2023", "title": "Based on a True Story", "year": 2023, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "The lives of a realtor, a plumber and a former tennis star unexpectedly collide, exposing America\u2019s obsession with true crime, murder and the slow-close toilet seat.", "poster": "assets/tv-based-on-a-true-story-2023-poster.jpg", "backdrop": "assets/tv-based-on-a-true-story-2023-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 197373, "ratings": {"rtCritics": 79, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7, "tmdbVotes": 160}}, {"id": "tv-the-beast-in-me-2025", "title": "The Beast in Me", "year": 2025, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Mystery"], "overview": "A famous author is pulled into a twisted mind game with her rich, powerful new neighbor \u2014 who might be a murderer.", "poster": "assets/tv-the-beast-in-me-2025-poster.jpg", "backdrop": "assets/tv-the-beast-in-me-2025-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 250504, "ratings": {"rtCritics": 85, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.072, "tmdbVotes": 334}}, {"id": "tv-big-city-greens-2018", "title": "Big City Greens", "year": 2018, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Family", "Animation", "Comedy"], "overview": "The offbeat adventures of 10-year-old Cricket Green, a mischievous and optimistic country boy who moves to the big city with his wildly out of place family \u2013 older sister Tilly, father Bill and Gramma Alice.", "poster": "assets/tv-big-city-greens-2018-poster.jpg", "backdrop": "assets/tv-big-city-greens-2018-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 80587, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.555, "tmdbVotes": 82}}, {"id": "tv-the-bold-type-2017", "title": "The Bold Type", "year": 2017, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Comedy"], "overview": "A glimpse into the outrageous lives of Jane, Kat and Sutton, who are working at the nation's top women's magazine, Scarlet, while navigating their careers, identities and individual voices.", "poster": "assets/tv-the-bold-type-2017-poster.jpg", "backdrop": "assets/tv-the-bold-type-2017-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 69630, "ratings": {"rtCritics": 89, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.7, "tmdbVotes": 204}}, {"id": "tv-brassic-2019", "title": "Brassic", "year": 2019, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Comedy", "Drama", "Crime"], "overview": "A group of working-class friends finding unconventional ways to win at life in suburban northern England. These lads have dealt, scammed, bribed and conned their way through adolescence, but now, their dealing and\u2026", "poster": "assets/tv-brassic-2019-poster.jpg", "backdrop": "assets/tv-brassic-2019-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 92621, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.336, "tmdbVotes": 263}}], "discoverySource": {"state": "materialized_snapshot", "rankKnown": false, "selection": "First 18 alphabetically indexed movie stubs and first 18 series stubs, not ranked feed order", "observedAt": "2026-11-04T20:42:12.030108+00:00"}}, {"key": "shared", "name": "Shared", "initial": "S", "color": "#9be38f", "destinations": {"Home": {"kind": "home"}, "Movies": {"kind": "library", "collection": "movies"}, "Shows": {"kind": "library", "collection": "shows"}, "Discover": {"kind": "discover", "collection": "discover"}}, "movies": [{"id": "movie-american-gangster-2007", "title": "American Gangster", "year": 2007, "type": "movie", "runtimeSeconds": 10546, "seasonCount": null, "communityRating": 7.595, "criticRating": 81, "genres": ["Drama", "Crime"], "overview": "Loosely based on the criminal career of Frank Lucas, a gangster from La Grange, North Carolina, who smuggled heroin into the United States on American service planes returning from the Vietnam War, before being\u2026", "poster": "assets/movie-american-gangster-2007-poster.jpg", "backdrop": "assets/movie-american-gangster-2007-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-08-23T10:22:36.0000000Z", "lastPlayedAt": "2026-08-30T21:05:39.7952451Z", "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 4982, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 81, "rtCriticsLabel": "Certified Fresh", "rtAudience": 90, "rtAudienceLabel": "Upright", "imdb": 7.8, "imdbVotes": 495656, "tmdb": 7.599, "tmdbVotes": 6350}}, {"id": "movie-annie-hall-1977", "title": "Annie Hall", "year": 1977, "type": "movie", "runtimeSeconds": 5604, "seasonCount": null, "communityRating": 7.712, "criticRating": 97, "genres": ["Comedy", "Drama", "Romance"], "overview": "New York comedian Alvy Singer falls in love with the ditsy Annie Hall.", "poster": "assets/movie-annie-hall-1977-poster.jpg", "backdrop": "assets/movie-annie-hall-1977-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:24:23.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 703, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 97, "rtCriticsLabel": "Certified Fresh", "rtAudience": 92, "rtAudienceLabel": "Upright", "imdb": 7.9, "imdbVotes": 293349, "tmdb": 7.71, "tmdbVotes": 4391}}, {"id": "movie-apocalypse-now-1979", "title": "Apocalypse Now", "year": 1979, "type": "movie", "runtimeSeconds": 8835, "seasonCount": null, "communityRating": 8.261, "criticRating": 91, "genres": ["Drama", "War"], "overview": "At the height of the Vietnam war, Captain Benjamin Willard is sent on a dangerous mission that, officially, \"does not exist, nor will it ever exist.\" His goal is to locate - and eliminate - a mysterious Green Beret\u2026", "poster": "assets/movie-apocalypse-now-1979-poster.jpg", "backdrop": "assets/movie-apocalypse-now-1979-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:22:25.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 28, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 91, "rtCriticsLabel": "Certified Fresh", "rtAudience": 94, "rtAudienceLabel": "Upright", "imdb": 8.4, "imdbVotes": 781424, "tmdb": 8.3, "tmdbVotes": 9526}}, {"id": "movie-arrival-2016", "title": "Arrival", "year": 2016, "type": "movie", "runtimeSeconds": 6967, "seasonCount": null, "communityRating": 7.636, "criticRating": 94, "genres": ["Drama", "Science Fiction", "Mystery"], "overview": "Taking place after alien crafts land around the world, an expert linguist is recruited by the military to determine whether they come in peace or are a threat.", "poster": "assets/movie-arrival-2016-poster.jpg", "backdrop": "assets/movie-arrival-2016-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-14T19:54:12.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 329865, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 94, "rtCriticsLabel": "Certified Fresh", "rtAudience": 83, "rtAudienceLabel": "Upright", "imdb": 7.9, "imdbVotes": 883621, "tmdb": 7.638, "tmdbVotes": 20125}}, {"id": "movie-avatar-aang-the-last-airbender-2026", "title": "Avatar Aang: The Last Airbender", "year": 2026, "type": "movie", "runtimeSeconds": 5930, "seasonCount": null, "communityRating": 9.147, "criticRating": 90, "genres": ["Animation", "Adventure", "Fantasy"], "overview": "Avatar Aang, the world's last Airbender, learns of an ancient power that could save his culture from extinction. With the help of his friends, he embarks on a global quest to find it before it falls into the wrong\u2026", "poster": "assets/movie-avatar-aang-the-last-airbender-2026-poster.jpg", "backdrop": "assets/movie-avatar-aang-the-last-airbender-2026-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-25T09:53:09.0000000Z", "lastPlayedAt": "2026-08-02T19:22:33.6155383Z", "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 980431, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 90, "rtCriticsLabel": "Certified Fresh", "rtAudience": 99, "rtAudienceLabel": "Upright", "imdb": 7.7, "imdbVotes": 30792, "tmdb": 9, "tmdbVotes": 1417}}, {"id": "movie-bohemian-rhapsody-2018", "title": "Bohemian Rhapsody", "year": 2018, "type": "movie", "runtimeSeconds": 8077, "seasonCount": null, "communityRating": 7.95, "criticRating": 60, "genres": ["Music", "Drama"], "overview": "Singer Freddie Mercury, guitarist Brian May, drummer Roger Taylor and bass guitarist John Deacon take the music world by storm when they form the rock 'n' roll band Queen in 1970. Hit songs become instant classics.\u2026", "poster": "assets/movie-bohemian-rhapsody-2018-poster.jpg", "backdrop": "assets/movie-bohemian-rhapsody-2018-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:28:20.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 424694, "artwork": {"posterKind": "center_crop", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 60, "rtCriticsLabel": "Fresh", "rtAudience": 85, "rtAudienceLabel": "Upright", "imdb": 7.9, "imdbVotes": 655894, "tmdb": 7.947, "tmdbVotes": 18196}}, {"id": "movie-the-boy-and-the-heron-2023", "title": "The Boy and the Heron", "year": 2023, "type": "movie", "runtimeSeconds": 7437, "seasonCount": null, "communityRating": 7.4, "criticRating": 96, "genres": ["Animation", "Fantasy", "Drama"], "overview": "While the Second World War rages, the teenage Mahito, haunted by his mother's tragic death, is relocated from Tokyo to the serene rural home of his new stepmother Natsuko, a woman who bears a striking resemblance to\u2026", "poster": "assets/movie-the-boy-and-the-heron-2023-poster.jpg", "backdrop": "assets/movie-the-boy-and-the-heron-2023-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:25:15.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 508883, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 96, "rtCriticsLabel": "Certified Fresh", "rtAudience": 88, "rtAudienceLabel": "Upright", "imdb": 7.3, "imdbVotes": 152169, "tmdb": 7.387, "tmdbVotes": 2893}}, {"id": "movie-braveheart-1995", "title": "Braveheart", "year": 1995, "type": "movie", "runtimeSeconds": 10665, "seasonCount": null, "communityRating": 7.945, "criticRating": 74, "genres": ["Drama", "History", "War"], "overview": "Enraged at the slaughter of Murron, his new bride and childhood love, Scottish warrior William Wallace slays a platoon of the local English lord's soldiers. This leads the village to revolt and, eventually, the\u2026", "poster": "assets/movie-braveheart-1995-poster.jpg", "backdrop": "assets/movie-braveheart-1995-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-10T21:24:28.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 197, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 74, "rtCriticsLabel": "Certified Fresh", "rtAudience": 85, "rtAudienceLabel": "Upright", "imdb": 8.3, "imdbVotes": 1174990, "tmdb": 7.947, "tmdbVotes": 11584}}, {"id": "movie-the-breakfast-club-1985", "title": "The Breakfast Club", "year": 1985, "type": "movie", "runtimeSeconds": 5833, "seasonCount": null, "communityRating": 7.7, "criticRating": 87, "genres": ["Comedy", "Drama"], "overview": "Five high school students from different walks of life endure a Saturday detention under a power-hungry principal. The disparate group includes rebel John, princess Claire, outcast Allison, brainy Brian and Andrew,\u2026", "poster": "assets/movie-the-breakfast-club-1985-poster.jpg", "backdrop": "assets/movie-the-breakfast-club-1985-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:25:03.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 2108, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 87, "rtCriticsLabel": "Certified Fresh", "rtAudience": 92, "rtAudienceLabel": "Upright", "imdb": 7.8, "imdbVotes": 478743, "tmdb": 7.713, "tmdbVotes": 8815}}, {"id": "movie-castle-in-the-sky-1986", "title": "Castle in the Sky", "year": 1986, "type": "movie", "runtimeSeconds": 7474, "seasonCount": null, "communityRating": 8, "criticRating": 97, "genres": ["Adventure", "Fantasy", "Animation"], "overview": "A young boy and a girl with a magic crystal must race against pirates and foreign agents in a search for a legendary floating castle.", "poster": "assets/movie-castle-in-the-sky-1986-poster.jpg", "backdrop": "assets/movie-castle-in-the-sky-1986-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:23:09.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 10515, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 96, "rtCriticsLabel": "Fresh", "rtAudience": 91, "rtAudienceLabel": "Upright", "imdb": 8, "imdbVotes": 201545, "tmdb": 7.972, "tmdbVotes": 4868}}, {"id": "movie-citizen-kane-1941", "title": "Citizen Kane", "year": 1941, "type": "movie", "runtimeSeconds": 7173, "seasonCount": null, "communityRating": 7.966, "criticRating": 99, "genres": ["Mystery", "Drama"], "overview": "Newspaper magnate Charles Foster Kane is taken from his mother as a boy and made the ward of a rich industrialist. As a result, every well-meaning, tyrannical or self-destructive move he makes for the rest of his\u2026", "poster": "assets/movie-citizen-kane-1941-poster.jpg", "backdrop": "assets/movie-citizen-kane-1941-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:22:16.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 15, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 99, "rtCriticsLabel": "Certified Fresh", "rtAudience": 98, "rtAudienceLabel": "Upright", "imdb": 8.2, "imdbVotes": 498321, "tmdb": 7.962, "tmdbVotes": 6324}}, {"id": "movie-conclave-2024", "title": "Conclave", "year": 2024, "type": "movie", "runtimeSeconds": 7230, "seasonCount": null, "communityRating": 7.19, "criticRating": 93, "genres": ["Drama", "Thriller"], "overview": "After the unexpected death of the Pope, Cardinal Lawrence is tasked with managing the covert and ancient ritual of electing a new one. Sequestered in the Vatican with the Catholic Church\u2019s most powerful leaders until\u2026", "poster": "assets/movie-conclave-2024-poster.jpg", "backdrop": "assets/movie-conclave-2024-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:30:41.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 974576, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 93, "rtCriticsLabel": "Certified Fresh", "rtAudience": 86, "rtAudienceLabel": "Upright", "imdb": 7.4, "imdbVotes": 290498, "tmdb": 7.19, "tmdbVotes": 3744}}, {"id": "movie-the-dry-2021", "title": "The Dry", "year": 2021, "type": "movie", "runtimeSeconds": 7037, "seasonCount": null, "communityRating": 6.779, "criticRating": null, "genres": ["Crime", "Thriller", "Mystery"], "overview": "Aaron Falk returns to his drought-stricken hometown to attend a tragic funeral. But his return opens a decades-old wound - the unsolved death of a teenage girl.", "poster": "assets/movie-the-dry-2021-poster.jpg", "backdrop": "assets/movie-the-dry-2021-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:29:20.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 567797, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 90, "rtCriticsLabel": "Certified Fresh", "rtAudience": 89, "rtAudienceLabel": "Upright", "imdb": 6.8, "imdbVotes": 41935, "tmdb": 6.8, "tmdbVotes": 892}}, {"id": "movie-easy-a-2010", "title": "Easy A", "year": 2010, "type": "movie", "runtimeSeconds": 5545, "seasonCount": null, "communityRating": 6.8, "criticRating": 85, "genres": ["Comedy"], "overview": "Olive, an average high school student, sees her below-the-radar existence turn around overnight once she decides to use the school's gossip grapevine to advance her social standing. Now her classmates are turning\u2026", "poster": "assets/movie-easy-a-2010-poster.jpg", "backdrop": "assets/movie-easy-a-2010-backdrop.jpg", "played": true, "playCount": 1, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-17T19:59:04.0000000Z", "lastPlayedAt": "2026-10-17T20:02:51.1083337Z", "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 37735, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 85, "rtCriticsLabel": "Certified Fresh", "rtAudience": 77, "rtAudienceLabel": "Upright", "imdb": 7, "imdbVotes": 448780, "tmdb": 6.8, "tmdbVotes": 7619}}, {"id": "movie-el-camino-a-breaking-bad-movie-2019", "title": "El Camino: A Breaking Bad Movie", "year": 2019, "type": "movie", "runtimeSeconds": 7362, "seasonCount": null, "communityRating": 7.055, "criticRating": 92, "genres": ["Crime", "Drama", "Thriller"], "overview": "In the wake of his dramatic escape from captivity, Jesse Pinkman must come to terms with his past in order to forge some kind of future.", "poster": "assets/movie-el-camino-a-breaking-bad-movie-2019-poster.jpg", "backdrop": "assets/movie-el-camino-a-breaking-bad-movie-2019-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-21T23:31:11.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 559969, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 92, "rtCriticsLabel": "Certified Fresh", "rtAudience": 81, "rtAudienceLabel": "Upright", "imdb": 7.3, "imdbVotes": 354488, "tmdb": 7.056, "tmdbVotes": 5950}}, {"id": "movie-emancipation-2022", "title": "Emancipation", "year": 2022, "type": "movie", "runtimeSeconds": 7949, "seasonCount": null, "communityRating": 7.7, "criticRating": 45, "genres": ["Drama", "War", "History"], "overview": "Inspired by the gripping true story of a man who would do anything for his family\u2014and for freedom. When Peter, an enslaved man, risks his life to escape and return to his family, he embarks on a perilous journey of\u2026", "poster": "assets/movie-emancipation-2022-poster.jpg", "backdrop": "assets/movie-emancipation-2022-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-10T20:37:34.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 715931, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 45, "rtCriticsLabel": "Rotten", "rtAudience": 58, "rtAudienceLabel": "Spilled", "imdb": 6.3, "imdbVotes": 32079, "tmdb": 7.744, "tmdbVotes": 1319}}, {"id": "movie-fighting-with-my-family-2019", "title": "Fighting with My Family", "year": 2019, "type": "movie", "runtimeSeconds": 6509, "seasonCount": null, "communityRating": 6.84, "criticRating": 93, "genres": ["Comedy"], "overview": "Born into a tight-knit wrestling family, Paige and her brother Zak are ecstatic when they get the once-in-a-lifetime opportunity to try out for the WWE. But when only Paige earns a spot in the competitive training\u2026", "poster": "assets/movie-fighting-with-my-family-2019-poster.jpg", "backdrop": "assets/movie-fighting-with-my-family-2019-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-22T22:10:59.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 445629, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 93, "rtCriticsLabel": "Certified Fresh", "rtAudience": 86, "rtAudienceLabel": "Upright", "imdb": 7, "imdbVotes": 94576, "tmdb": 6.837, "tmdbVotes": 1952}}, {"id": "movie-force-of-nature-the-dry-2-2024", "title": "Force of Nature: The Dry 2", "year": 2024, "type": "movie", "runtimeSeconds": 6753, "seasonCount": null, "communityRating": 6.3, "criticRating": 66, "genres": ["Mystery", "Thriller"], "overview": "When five women take part in a corporate hiking retreat and only four come out on the other side, Federal Agents Aaron Falk and Carmen Cooper head deep into the Victorian mountain ranges to investigate in the hopes\u2026", "poster": "assets/movie-force-of-nature-the-dry-2-2024-poster.jpg", "backdrop": "assets/movie-force-of-nature-the-dry-2-2024-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:29:49.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 832262, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 66, "rtCriticsLabel": "Fresh", "rtAudience": 43, "rtAudienceLabel": "Spilled", "imdb": 5.9, "imdbVotes": 10343, "tmdb": 6.269, "tmdbVotes": 305}}, {"id": "movie-free-state-of-jones-2016", "title": "Free State of Jones", "year": 2016, "type": "movie", "runtimeSeconds": 8376, "seasonCount": null, "communityRating": 6.79, "criticRating": 48, "genres": ["Action", "Drama", "History"], "overview": "In 1863, Mississippi farmer Newt Knight serves as a medic for the Confederate Army. Opposed to slavery, Knight would rather help the wounded than fight the Union. After his nephew dies in battle, Newt returns home to\u2026", "poster": "assets/movie-free-state-of-jones-2016-poster.jpg", "backdrop": "assets/movie-free-state-of-jones-2016-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-11T17:56:53.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 316152, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 48, "rtCriticsLabel": "Rotten", "rtAudience": 64, "rtAudienceLabel": "Upright", "imdb": 6.9, "imdbVotes": 68931, "tmdb": 6.788, "tmdbVotes": 1730}}, {"id": "movie-from-up-on-poppy-hill-2011", "title": "From Up on Poppy Hill", "year": 2011, "type": "movie", "runtimeSeconds": 5482, "seasonCount": null, "communityRating": 7.5, "criticRating": 87, "genres": ["Animation", "Drama"], "overview": "Yokohama, 1963. Japan is picking itself up from the devastation of World War II and preparing to host the 1964 Olympics\u2014and the mood is one of both optimism and conflict as the young generation struggles to throw off\u2026", "poster": "assets/movie-from-up-on-poppy-hill-2011-poster.jpg", "backdrop": "assets/movie-from-up-on-poppy-hill-2011-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-07T14:41:03.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 83389, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 87, "rtCriticsLabel": "Certified Fresh", "rtAudience": 80, "rtAudienceLabel": "Upright", "imdb": 7.4, "imdbVotes": 60769, "tmdb": 7.509, "tmdbVotes": 2026}}, {"id": "movie-ghost-in-the-shell-1995", "title": "Ghost in the Shell", "year": 1995, "type": "movie", "runtimeSeconds": 4959, "seasonCount": null, "communityRating": 7.929, "criticRating": 95, "genres": ["Action", "Animation", "Science Fiction"], "overview": "In the year 2029, the barriers of our world have been broken down by the net and by cybernetics, but this brings new vulnerability to humans in the form of brain-hacking. When a highly-wanted hacker known as 'The\u2026", "poster": "assets/movie-ghost-in-the-shell-1995-poster.jpg", "backdrop": "assets/movie-ghost-in-the-shell-1995-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:22:57.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 9323, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 97, "rtCriticsLabel": "Certified Fresh", "rtAudience": 90, "rtAudienceLabel": "Upright", "imdb": 7.9, "imdbVotes": 178061, "tmdb": 7.928, "tmdbVotes": 4130}}, {"id": "movie-ghost-in-the-shell-2-innocence-2004", "title": "Ghost in the Shell 2: Innocence", "year": 2004, "type": "movie", "runtimeSeconds": 5950, "seasonCount": null, "communityRating": 7.352, "criticRating": 65, "genres": ["Animation", "Drama", "Science Fiction"], "overview": "Cyborg detective Batou is assigned to investigate a series of murders committed by gynoids\u2014doll-like cyborgs, which all malfunctioned, killed, then self-destructed afterwards. The brains of the gynoids initialize in\u2026", "poster": "assets/movie-ghost-in-the-shell-2-innocence-2004-poster.jpg", "backdrop": "assets/movie-ghost-in-the-shell-2-innocence-2004-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:23:38.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 12140, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 65, "rtCriticsLabel": "Fresh", "rtAudience": 80, "rtAudienceLabel": "Upright", "imdb": 7.4, "imdbVotes": 43280, "tmdb": 7.356, "tmdbVotes": 1049}}, {"id": "movie-the-godfather-1972", "title": "The Godfather", "year": 1972, "type": "movie", "runtimeSeconds": 10603, "seasonCount": null, "communityRating": 8.687, "criticRating": 97, "genres": ["Drama", "Crime"], "overview": "Spanning the years 1945 to 1955, a chronicle of the fictional Italian-American Corleone crime family. When organized crime family patriarch, Vito Corleone barely survives an attempt on his life, his youngest son,\u2026", "poster": "assets/movie-the-godfather-1972-poster.jpg", "backdrop": "assets/movie-the-godfather-1972-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:24:12.0000000Z", "lastPlayedAt": "2026-09-18T19:24:44.9855867Z", "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 238, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 97, "rtCriticsLabel": "Certified Fresh", "rtAudience": 98, "rtAudienceLabel": "Upright", "imdb": 9.2, "imdbVotes": 2262024, "tmdb": 8.688, "tmdbVotes": 23907}}, {"id": "movie-the-godfather-part-ii-1974", "title": "The Godfather Part II", "year": 1974, "type": "movie", "runtimeSeconds": 12031, "seasonCount": null, "communityRating": 8.573, "criticRating": 96, "genres": ["Drama", "Crime"], "overview": "In the continuing saga of the Corleone crime family, a young Vito Corleone grows up in Sicily and in 1910s New York. In the 1950s, Michael Corleone attempts to expand the family business into Las Vegas, Hollywood and\u2026", "poster": "assets/movie-the-godfather-part-ii-1974-poster.jpg", "backdrop": "assets/movie-the-godfather-part-ii-1974-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:24:18.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 240, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 96, "rtCriticsLabel": "Certified Fresh", "rtAudience": 97, "rtAudienceLabel": "Upright", "imdb": 9, "imdbVotes": 1521308, "tmdb": 8.571, "tmdbVotes": 14559}}, {"id": "movie-the-godfather-part-iii-1990", "title": "The Godfather Part III", "year": 1990, "type": "movie", "runtimeSeconds": 9476, "seasonCount": null, "communityRating": 7.416, "criticRating": 67, "genres": ["Crime", "Drama", "Thriller"], "overview": "In the midst of trying to legitimize his business dealings in 1979 New York and Italy, aging mafia don, Michael Corleone seeks forgiveness for his sins while taking a young protege under his wing.", "poster": "assets/movie-the-godfather-part-iii-1990-poster.jpg", "backdrop": "assets/movie-the-godfather-part-iii-1990-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:26:29.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 242, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 68, "rtCriticsLabel": "Fresh", "rtAudience": 68, "rtAudienceLabel": "Upright", "imdb": 7.5, "imdbVotes": 462448, "tmdb": 7.413, "tmdbVotes": 7159}}, {"id": "movie-gosford-park-2001", "title": "Gosford Park", "year": 2001, "type": "movie", "runtimeSeconds": 8247, "seasonCount": null, "communityRating": 6.815, "criticRating": 87, "genres": ["Drama", "Mystery", "Thriller"], "overview": "In 1930s England, a group of pretentious rich and famous gather together for a weekend of relaxation at a hunting resort. But when a murder occurs, each one of these interesting characters becomes a suspect.", "poster": "assets/movie-gosford-park-2001-poster.jpg", "backdrop": "assets/movie-gosford-park-2001-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:25:49.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 5279, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 87, "rtCriticsLabel": "Certified Fresh", "rtAudience": 78, "rtAudienceLabel": "Upright", "imdb": 7.2, "imdbVotes": 103883, "tmdb": 6.822, "tmdbVotes": 1317}}, {"id": "movie-grave-of-the-fireflies-1988", "title": "Grave of the Fireflies", "year": 1988, "type": "movie", "runtimeSeconds": 5313, "seasonCount": null, "communityRating": 8.45, "criticRating": 100, "genres": ["Animation", "Drama", "War"], "overview": "In the final months of World War II, 14-year-old Seita and his sister Setsuko are orphaned when their mother is killed during an air raid in Kobe, Japan. After a falling out with their aunt, they move into an\u2026", "poster": "assets/movie-grave-of-the-fireflies-1988-poster.jpg", "backdrop": "assets/movie-grave-of-the-fireflies-1988-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:24:14.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 12477, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Certified Fresh", "rtAudience": 95, "rtAudienceLabel": "Upright", "imdb": 8.5, "imdbVotes": 399802, "tmdb": 8.45, "tmdbVotes": 6938}}, {"id": "movie-the-great-gatsby-2013", "title": "The Great Gatsby", "year": 2013, "type": "movie", "runtimeSeconds": 8525, "seasonCount": null, "communityRating": 7.346, "criticRating": 49, "genres": ["Drama", "Romance"], "overview": "Nick Carraway arrives in New York in 1922, an era of loose morals, glittering jazz and bootleg kings. Chasing his own American Dream, Nick encounters the mysterious millionaire Gatsby and his bewitching cousin Daisy.\u2026", "poster": "assets/movie-the-great-gatsby-2013-poster.jpg", "backdrop": "assets/movie-the-great-gatsby-2013-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:26:50.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 64682, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 48, "rtCriticsLabel": "Rotten", "rtAudience": 67, "rtAudienceLabel": "Upright", "imdb": 7.2, "imdbVotes": 651882, "tmdb": 7.346, "tmdbVotes": 13290}}, {"id": "movie-the-hours-2002", "title": "The Hours", "year": 2002, "type": "movie", "runtimeSeconds": 6887, "seasonCount": null, "communityRating": 7.25, "criticRating": 79, "genres": ["Drama"], "overview": "The story of three women searching for more potent, meaningful lives. Each is alive at a different time and place, all are linked by their yearnings and their fears. Their stories intertwine, and finally come\u2026", "poster": "assets/movie-the-hours-2002-poster.jpg", "backdrop": "assets/movie-the-hours-2002-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:23:27.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 590, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 79, "rtCriticsLabel": "Certified Fresh", "rtAudience": 84, "rtAudienceLabel": "Upright", "imdb": 7.5, "imdbVotes": 148367, "tmdb": 7.249, "tmdbVotes": 1931}}, {"id": "movie-howl-s-moving-castle-2004", "title": "Howl's Moving Castle", "year": 2004, "type": "movie", "runtimeSeconds": 7149, "seasonCount": null, "communityRating": 8.4, "criticRating": 88, "genres": ["Fantasy", "Animation", "Adventure"], "overview": "Sophie, a young milliner, is turned into an elderly woman by a witch who enters her shop and curses her. She encounters a wizard named Howl and gets caught up in his resistance to fighting for the king.", "poster": "assets/movie-howl-s-moving-castle-2004-poster.jpg", "backdrop": "assets/movie-howl-s-moving-castle-2004-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:22:47.0000000Z", "lastPlayedAt": "2026-05-21T18:46:08.0000000Z", "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 4935, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 87, "rtCriticsLabel": "Certified Fresh", "rtAudience": 93, "rtAudienceLabel": "Upright", "imdb": 8.2, "imdbVotes": 524520, "tmdb": 8.391, "tmdbVotes": 11642}}, {"id": "movie-in-the-heights-2021", "title": "In the Heights", "year": 2021, "type": "movie", "runtimeSeconds": 8579, "seasonCount": null, "communityRating": 7.111, "criticRating": null, "genres": ["Drama", "Romance"], "overview": "The story of Usnavi, a bodega owner who has mixed feelings about closing his store and retiring to the Dominican Republic or staying in Washington Heights.", "poster": "assets/movie-in-the-heights-2021-poster.jpg", "backdrop": "assets/movie-in-the-heights-2021-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:29:11.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 467909, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 94, "rtCriticsLabel": "Certified Fresh", "rtAudience": 94, "rtAudienceLabel": "Upright", "imdb": 7.2, "imdbVotes": 56513, "tmdb": 7.104, "tmdbVotes": 826}}, {"id": "movie-jennifer-s-body-2009", "title": "Jennifer's Body", "year": 2009, "type": "movie", "runtimeSeconds": 6450, "seasonCount": null, "communityRating": 6.012, "criticRating": 47, "genres": ["Horror", "Comedy"], "overview": "Jennifer, a gorgeous, seductive cheerleader takes evil to a whole new level after she's possessed by a sinister demon. Now it's up to her best friend to stop Jennifer's reign of terror before it's too late.", "poster": "assets/movie-jennifer-s-body-2009-poster.jpg", "backdrop": "assets/movie-jennifer-s-body-2009-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-30T03:37:04.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 19994, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 47, "rtCriticsLabel": "Rotten", "rtAudience": 36, "rtAudienceLabel": "Spilled", "imdb": 5.6, "imdbVotes": 179710, "tmdb": 6.013, "tmdbVotes": 4481}}, {"id": "movie-kiki-s-delivery-service-1989", "title": "Kiki's Delivery Service", "year": 1989, "type": "movie", "runtimeSeconds": 6183, "seasonCount": null, "communityRating": 7.839, "criticRating": null, "genres": ["Animation", "Family", "Fantasy"], "overview": "A young witch, on her mandatory year of independent life, finds fitting into a new community difficult while she supports herself by running an air courier service.", "poster": "assets/movie-kiki-s-delivery-service-1989-poster.jpg", "backdrop": "assets/movie-kiki-s-delivery-service-1989-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:24:20.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 16859, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 98, "rtCriticsLabel": "Certified Fresh", "rtAudience": 89, "rtAudienceLabel": "Upright", "imdb": 7.8, "imdbVotes": 192477, "tmdb": 7.839, "tmdbVotes": 4811}}, {"id": "movie-the-last-of-the-mohicans-1992", "title": "The Last of the Mohicans", "year": 1992, "type": "movie", "runtimeSeconds": 6874, "seasonCount": null, "communityRating": 7.388, "criticRating": 88, "genres": ["History", "War", "Drama"], "overview": "In war-torn colonial America, in the midst of a bloody battle between British, the French and Native American allies, the aristocratic daughter of a British Colonel and her party are captured by a group of Huron\u2026", "poster": "assets/movie-the-last-of-the-mohicans-1992-poster.jpg", "backdrop": "assets/movie-the-last-of-the-mohicans-1992-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-10T20:59:22.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 9361, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 88, "rtCriticsLabel": "Certified Fresh", "rtAudience": 88, "rtAudienceLabel": "Upright", "imdb": 7.6, "imdbVotes": 210791, "tmdb": 7.39, "tmdbVotes": 3593}}, {"id": "movie-the-last-samurai-2003", "title": "The Last Samurai", "year": 2003, "type": "movie", "runtimeSeconds": 9251, "seasonCount": null, "communityRating": 7.61, "criticRating": 65, "genres": ["Drama", "Action", "War"], "overview": "Nathan Algren is an American hired to instruct the Japanese army in the ways of modern warfare, which finds him learning to respect the samurai and the honorable principles that rule them. Pressed to destroy the\u2026", "poster": "assets/movie-the-last-samurai-2003-poster.jpg", "backdrop": "assets/movie-the-last-samurai-2003-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-26T05:25:10.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 616, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 66, "rtCriticsLabel": "Fresh", "rtAudience": 83, "rtAudienceLabel": "Upright", "imdb": 7.8, "imdbVotes": 512982, "tmdb": 7.614, "tmdbVotes": 7887}}, {"id": "movie-lemony-snicket-s-a-series-of-unfortunate-events-2004", "title": "Lemony Snicket's A Series of Unfortunate Events", "year": 2004, "type": "movie", "runtimeSeconds": 6479, "seasonCount": null, "communityRating": 6.8, "criticRating": 72, "genres": ["Adventure", "Comedy", "Family"], "overview": "Three wealthy children's parents are killed in a fire. When they are sent to a distant relative, they find out that he is plotting to kill them and seize their fortune.", "poster": "assets/movie-lemony-snicket-s-a-series-of-unfortunate-events-2004-poster.jpg", "backdrop": "assets/movie-lemony-snicket-s-a-series-of-unfortunate-events-2004-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:26:09.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 11774, "artwork": {"posterKind": "center_crop", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 72, "rtCriticsLabel": "Fresh", "rtAudience": 63, "rtAudienceLabel": "Upright", "imdb": 6.8, "imdbVotes": 233636, "tmdb": 6.82, "tmdbVotes": 5447}}, {"id": "movie-the-lighthouse-2019", "title": "The Lighthouse", "year": 2019, "type": "movie", "runtimeSeconds": 6549, "seasonCount": null, "communityRating": 7.463, "criticRating": 90, "genres": ["Drama", "Fantasy", "Thriller"], "overview": "Two lighthouse keepers try to maintain their sanity while living on a remote and mysterious New England island in the 1890s.", "poster": "assets/movie-the-lighthouse-2019-poster.jpg", "backdrop": "assets/movie-the-lighthouse-2019-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:29:01.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 503919, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 90, "rtCriticsLabel": "Certified Fresh", "rtAudience": 72, "rtAudienceLabel": "Upright", "imdb": 7.3, "imdbVotes": 312344, "tmdb": 7.46, "tmdbVotes": 5895}}, {"id": "movie-locke-2014", "title": "Locke", "year": 2014, "type": "movie", "runtimeSeconds": 5090, "seasonCount": null, "communityRating": 6.888, "criticRating": 91, "genres": ["Drama", "Thriller"], "overview": "Ivan Locke has worked hard to craft a good life for himself. Tonight, that life will collapse around him. On the eve of the biggest challenge of his career, Ivan receives a phone call that sets in motion a series of\u2026", "poster": "assets/movie-locke-2014-poster.jpg", "backdrop": "assets/movie-locke-2014-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T17:38:48.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 210479, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 90, "rtCriticsLabel": "Certified Fresh", "rtAudience": 72, "rtAudienceLabel": "Upright", "imdb": 7.1, "imdbVotes": 171265, "tmdb": 6.889, "tmdbVotes": 3088}}, {"id": "movie-the-matrix-1999", "title": "The Matrix", "year": 1999, "type": "movie", "runtimeSeconds": 8179, "seasonCount": null, "communityRating": 8.259, "criticRating": 83, "genres": ["Action", "Science Fiction"], "overview": "Set in the 22nd century, The Matrix tells the story of a computer hacker who joins a group of underground insurgents fighting the vast and powerful computers who now rule the earth.", "poster": "assets/movie-the-matrix-1999-poster.jpg", "backdrop": "assets/movie-the-matrix-1999-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:18:12.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 603, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 83, "rtCriticsLabel": "Certified Fresh", "rtAudience": 85, "rtAudienceLabel": "Upright", "imdb": 8.7, "imdbVotes": 2289808, "tmdb": 8.262, "tmdbVotes": 29153}}, {"id": "movie-the-matrix-reloaded-2003", "title": "The Matrix Reloaded", "year": 2003, "type": "movie", "runtimeSeconds": 8298, "seasonCount": null, "communityRating": 7.085, "criticRating": 74, "genres": ["Adventure", "Action", "Thriller"], "overview": "The Resistance builds in numbers as humans are freed from the Matrix and brought to the city of Zion. Neo discovers his superpowers, including the ability to see the code inside the Matrix. With machine sentinels\u2026", "poster": "assets/movie-the-matrix-reloaded-2003-poster.jpg", "backdrop": "assets/movie-the-matrix-reloaded-2003-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:24:03.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 604, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 73, "rtCriticsLabel": "Certified Fresh", "rtAudience": 72, "rtAudienceLabel": "Upright", "imdb": 7.2, "imdbVotes": 678013, "tmdb": 7.087, "tmdbVotes": 12504}}, {"id": "movie-the-matrix-resurrections-2021", "title": "The Matrix Resurrections", "year": 2021, "type": "movie", "runtimeSeconds": 8875, "seasonCount": null, "communityRating": 6.303, "criticRating": 63, "genres": ["Science Fiction", "Action", "Adventure"], "overview": "Plagued by strange memories, Neo's life takes an unexpected turn when he finds himself back inside the Matrix.", "poster": "assets/movie-the-matrix-resurrections-2021-poster.jpg", "backdrop": "assets/movie-the-matrix-resurrections-2021-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:31:22.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 624860, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 63, "rtCriticsLabel": "Fresh", "rtAudience": 63, "rtAudienceLabel": "Upright", "imdb": 5.6, "imdbVotes": 307780, "tmdb": 6.294, "tmdbVotes": 6842}}, {"id": "movie-the-matrix-revolutions-2003", "title": "The Matrix Revolutions", "year": 2003, "type": "movie", "runtimeSeconds": 7756, "seasonCount": null, "communityRating": 6.758, "criticRating": 33, "genres": ["Adventure", "Action", "Thriller"], "overview": "The human city of Zion defends itself against the massive invasion of the machines as Neo fights to end the war at another front while also opposing the rogue Agent Smith.", "poster": "assets/movie-the-matrix-revolutions-2003-poster.jpg", "backdrop": "assets/movie-the-matrix-revolutions-2003-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:25:47.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 605, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 34, "rtCriticsLabel": "Rotten", "rtAudience": 60, "rtAudienceLabel": "Upright", "imdb": 6.8, "imdbVotes": 581553, "tmdb": 6.762, "tmdbVotes": 11292}}, {"id": "movie-mercy-2026", "title": "Mercy", "year": 2026, "type": "movie", "runtimeSeconds": 5968, "seasonCount": null, "communityRating": 7.077, "criticRating": null, "genres": ["Science Fiction", "Thriller", "Crime"], "overview": "In the near future, a detective stands on trial accused of murdering his wife. He has ninety minutes to prove his innocence to the advanced AI Judge he once championed, before it determines his fate.", "poster": "assets/movie-mercy-2026-poster.jpg", "backdrop": "assets/movie-mercy-2026-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:31:40.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 1236153, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 24, "rtCriticsLabel": "Rotten", "rtAudience": 81, "rtAudienceLabel": "Upright", "imdb": 6.2, "imdbVotes": 83878, "tmdb": 7.054, "tmdbVotes": 1812}}, {"id": "movie-normal-2026", "title": "Normal", "year": 2026, "type": "movie", "runtimeSeconds": 5514, "seasonCount": null, "communityRating": 7.267, "criticRating": 77, "genres": ["Action", "Crime", "Thriller"], "overview": "An unassuming substitute sheriff with a troubled past responds to a bank robbery in a small, sleepy town and unknowingly uncovers something far more explosive.", "poster": "assets/movie-normal-2026-poster.jpg", "backdrop": "assets/movie-normal-2026-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-08-21T08:33:18.0000000Z", "lastPlayedAt": "2026-09-24T23:07:52.1161581Z", "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 1242332, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 78, "rtCriticsLabel": "Certified Fresh", "rtAudience": 75, "rtAudienceLabel": "Upright", "imdb": 6.3, "imdbVotes": 37501, "tmdb": 7.241, "tmdbVotes": 786}}, {"id": "movie-outlaw-king-2018", "title": "Outlaw King", "year": 2018, "type": "movie", "runtimeSeconds": 7297, "seasonCount": null, "communityRating": 6.829, "criticRating": 63, "genres": ["Action", "History", "War"], "overview": "Forced into exile by the English after being crowned King of Scotland, legendary warrior Robert the Bruce fights to reclaim the throne.", "poster": "assets/movie-outlaw-king-2018-poster.jpg", "backdrop": "assets/movie-outlaw-king-2018-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:28:32.0000000Z", "lastPlayedAt": "2026-07-26T22:35:35.0000000Z", "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 454227, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 63, "rtCriticsLabel": "Fresh", "rtAudience": 69, "rtAudienceLabel": "Upright", "imdb": 6.9, "imdbVotes": 89876, "tmdb": 6.844, "tmdbVotes": 1780}}, {"id": "movie-panic-room-2002", "title": "Panic Room", "year": 2002, "type": "movie", "runtimeSeconds": 6717, "seasonCount": null, "communityRating": 6.794, "criticRating": 76, "genres": ["Crime", "Drama", "Thriller"], "overview": "Trapped in their New York brownstone's panic room, a hidden chamber built as a sanctuary in the event of break-ins, newly divorced Meg Altman and her young daughter Sarah play a deadly game of cat-and-mouse with\u2026", "poster": "assets/movie-panic-room-2002-poster.jpg", "backdrop": "assets/movie-panic-room-2002-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-19T20:50:01.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 4547, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 76, "rtCriticsLabel": "Certified Fresh", "rtAudience": 64, "rtAudienceLabel": "Upright", "imdb": 6.8, "imdbVotes": 317468, "tmdb": 6.796, "tmdbVotes": 5653}}, {"id": "movie-the-patriot-2000", "title": "The Patriot", "year": 2000, "type": "movie", "runtimeSeconds": 9887, "seasonCount": null, "communityRating": 7.214, "criticRating": 61, "genres": ["Drama", "History", "War"], "overview": "After proving himself on the field of battle in the French and Indian War, Benjamin Martin wants nothing more to do with such things, preferring the simple life of a farmer. But when his son Gabriel enlists in the\u2026", "poster": "assets/movie-the-patriot-2000-poster.jpg", "backdrop": "assets/movie-the-patriot-2000-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-10T20:50:00.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 2024, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 61, "rtCriticsLabel": "Fresh", "rtAudience": 81, "rtAudienceLabel": "Upright", "imdb": 7.2, "imdbVotes": 317639, "tmdb": 7.215, "tmdbVotes": 4535}}, {"id": "movie-peaky-blinders-the-immortal-man-2026", "title": "Peaky Blinders: The Immortal Man", "year": 2026, "type": "movie", "runtimeSeconds": 6844, "seasonCount": null, "communityRating": 7.187, "criticRating": 90, "genres": ["Crime", "Drama"], "overview": "After his estranged son gets embroiled in a Nazi plot, self-exiled gangster Tommy Shelby must return to Birmingham to save his family \u2014 and his nation.", "poster": "assets/movie-peaky-blinders-the-immortal-man-2026-poster.jpg", "backdrop": "assets/movie-peaky-blinders-the-immortal-man-2026-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:30:01.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 875828, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 90, "rtCriticsLabel": "Certified Fresh", "rtAudience": 87, "rtAudienceLabel": "Upright", "imdb": 6.5, "imdbVotes": 92130, "tmdb": 7.189, "tmdbVotes": 1304}}, {"id": "movie-ponyo-2008", "title": "Ponyo", "year": 2008, "type": "movie", "runtimeSeconds": 6530, "seasonCount": null, "communityRating": 7.763, "criticRating": 91, "genres": ["Animation", "Fantasy", "Family"], "overview": "When Sosuke, a young boy who lives on a clifftop overlooking the sea, rescues a stranded goldfish named Ponyo, he discovers more than he bargained for. Ponyo is a curious, energetic young creature who yearns to be\u2026", "poster": "assets/movie-ponyo-2008-poster.jpg", "backdrop": "assets/movie-ponyo-2008-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:23:53.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 12429, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 91, "rtCriticsLabel": "Certified Fresh", "rtAudience": 84, "rtAudienceLabel": "Upright", "imdb": 7.6, "imdbVotes": 190294, "tmdb": 7.759, "tmdbVotes": 5050}}, {"id": "movie-porco-rosso-1992", "title": "Porco Rosso", "year": 1992, "type": "movie", "runtimeSeconds": 5600, "seasonCount": null, "communityRating": 7.794, "criticRating": 96, "genres": ["Animation", "Adventure", "Fantasy"], "overview": "In Italy in the 1930s, sky pirates in biplanes terrorize wealthy cruise ships as they sail the Adriatic Sea. The only pilot brave enough to stop the scourge is the mysterious Porco Rosso, a former World War I flying\u2026", "poster": "assets/movie-porco-rosso-1992-poster.jpg", "backdrop": "assets/movie-porco-rosso-1992-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:23:22.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 11621, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 96, "rtCriticsLabel": "Fresh", "rtAudience": 87, "rtAudienceLabel": "Upright", "imdb": 7.7, "imdbVotes": 119396, "tmdb": 7.789, "tmdbVotes": 3879}}, {"id": "movie-princess-mononoke-1997", "title": "Princess Mononoke", "year": 1997, "type": "movie", "runtimeSeconds": 8003, "seasonCount": null, "communityRating": 8.321, "criticRating": 93, "genres": ["Adventure", "Fantasy", "Animation"], "overview": "Ashitaka, a prince of the disappearing Emishi people, is cursed by a demonized boar god and must journey to the west to find a cure. Along the way, he encounters San, a young human woman fighting to protect the\u2026", "poster": "assets/movie-princess-mononoke-1997-poster.jpg", "backdrop": "assets/movie-princess-mononoke-1997-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:22:28.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 128, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 94, "rtCriticsLabel": "Certified Fresh", "rtAudience": 99, "rtAudienceLabel": "Upright", "imdb": 8.3, "imdbVotes": 493735, "tmdb": 8.321, "tmdbVotes": 9438}}, {"id": "movie-prisoners-2013", "title": "Prisoners", "year": 2013, "type": "movie", "runtimeSeconds": 9192, "seasonCount": null, "communityRating": 8.106, "criticRating": 81, "genres": ["Drama", "Thriller", "Crime"], "overview": "After his six-year-old daughter is abducted, a desperate father takes justice into his own hands when he loses faith in the detective leading the investigation, setting both men on a collision course as the search\u2026", "poster": "assets/movie-prisoners-2013-poster.jpg", "backdrop": "assets/movie-prisoners-2013-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:27:33.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 146233, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 82, "rtCriticsLabel": "Certified Fresh", "rtAudience": 87, "rtAudienceLabel": "Upright", "imdb": 8.2, "imdbVotes": 963062, "tmdb": 8.105, "tmdbVotes": 13722}}, {"id": "movie-racing-stripes-2005", "title": "Racing Stripes", "year": 2005, "type": "movie", "runtimeSeconds": 6117, "seasonCount": null, "communityRating": 5.945, "criticRating": 35, "genres": ["Comedy", "Family"], "overview": "Shattered illusions are hard to repair -- especially for a good-hearted zebra named Stripes who's spent his life on a Kentucky farm amidst the sorely mistaken notion that he's a debonair thoroughbred. Once he faces\u2026", "poster": "assets/movie-racing-stripes-2005-poster.jpg", "backdrop": "assets/movie-racing-stripes-2005-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:26:02.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 6439, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 35, "rtCriticsLabel": "Rotten", "rtAudience": 42, "rtAudienceLabel": "Spilled", "imdb": 5.3, "imdbVotes": 19569, "tmdb": 5.962, "tmdbVotes": 705}}, {"id": "movie-requiem-for-a-dream-2000", "title": "Requiem for a Dream", "year": 2000, "type": "movie", "runtimeSeconds": 6086, "seasonCount": null, "communityRating": 8.005, "criticRating": 80, "genres": ["Crime", "Drama"], "overview": "The drug-induced utopias of four Coney Island residents are shattered when their addictions run deep.", "poster": "assets/movie-requiem-for-a-dream-2000-poster.jpg", "backdrop": "assets/movie-requiem-for-a-dream-2000-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:24:15.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 641, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 81, "rtCriticsLabel": "Certified Fresh", "rtAudience": 93, "rtAudienceLabel": "Upright", "imdb": 8.3, "imdbVotes": 989090, "tmdb": 8, "tmdbVotes": 11454}}, {"id": "movie-rogue-one-a-star-wars-story-2016", "title": "Rogue One: A Star Wars Story", "year": 2016, "type": "movie", "runtimeSeconds": 8038, "seasonCount": null, "communityRating": 7.51, "criticRating": 84, "genres": ["Action", "Adventure", "Science Fiction"], "overview": "A rogue band of resistance fighters unite for a mission to steal the Death Star plans and bring a new hope to the galaxy.", "poster": "assets/movie-rogue-one-a-star-wars-story-2016-poster.jpg", "backdrop": "assets/movie-rogue-one-a-star-wars-story-2016-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:28:00.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 330459, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 84, "rtCriticsLabel": "Certified Fresh", "rtAudience": 87, "rtAudienceLabel": "Upright", "imdb": 7.8, "imdbVotes": 768365, "tmdb": 7.512, "tmdbVotes": 16937}}, {"id": "movie-secondhand-lions-2003", "title": "Secondhand Lions", "year": 2003, "type": "movie", "runtimeSeconds": 6547, "seasonCount": null, "communityRating": 7.237, "criticRating": 60, "genres": ["Family", "Comedy", "Drama"], "overview": "The comedic adventures of an introverted boy left on the doorstep of a pair of reluctant, eccentric great-uncles, whose exotic remembrances stir the boy's spirit and re-ignite the men's lives.", "poster": "assets/movie-secondhand-lions-2003-poster.jpg", "backdrop": "assets/movie-secondhand-lions-2003-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-12T07:16:13.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 13156, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 59, "rtCriticsLabel": "Rotten", "rtAudience": 84, "rtAudienceLabel": "Upright", "imdb": 7.5, "imdbVotes": 66911, "tmdb": 7.242, "tmdbVotes": 872}}, {"id": "movie-the-secret-life-of-pets-2-2019", "title": "The Secret Life of Pets 2", "year": 2019, "type": "movie", "runtimeSeconds": 5164, "seasonCount": null, "communityRating": 6.95, "criticRating": 60, "genres": ["Animation", "Comedy", "Family"], "overview": "Max the terrier must cope with some major life changes when his owner gets married and has a baby. When the family takes a trip to the countryside, nervous Max has numerous run-ins with canine-intolerant cows,\u2026", "poster": "assets/movie-the-secret-life-of-pets-2-2019-poster.jpg", "backdrop": "assets/movie-the-secret-life-of-pets-2-2019-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-05T07:00:53.0000000Z", "lastPlayedAt": "2026-09-17T18:07:00.7024231Z", "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 412117, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 60, "rtCriticsLabel": "Fresh", "rtAudience": 90, "rtAudienceLabel": "Upright", "imdb": 6.4, "imdbVotes": 80464, "tmdb": 6.948, "tmdbVotes": 3450}}, {"id": "movie-self-reliance-2024", "title": "Self Reliance", "year": 2024, "type": "movie", "runtimeSeconds": 5461, "seasonCount": null, "communityRating": 5.888, "criticRating": 73, "genres": ["Comedy", "Thriller"], "overview": "When a man is offered a million dollars to play a game in which hunters try to kill him, he thinks he has found the perfect loophole: they can only attack when he\u2019s alone. His only problem is that none of his friends\u2026", "poster": "assets/movie-self-reliance-2024-poster.jpg", "backdrop": "assets/movie-self-reliance-2024-backdrop.jpg", "played": false, "playCount": 1, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:31:00.0000000Z", "lastPlayedAt": "2026-10-02T19:59:16.9286868Z", "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 1072876, "artwork": {"posterKind": "center_crop", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 73, "rtCriticsLabel": "Certified Fresh", "rtAudience": 56, "rtAudienceLabel": "Spilled", "imdb": 6, "imdbVotes": 23771, "tmdb": 5.866, "tmdbVotes": 346}}, {"id": "movie-serpico-1973", "title": "Serpico", "year": 1973, "type": "movie", "runtimeSeconds": 7805, "seasonCount": null, "communityRating": 7.5, "criticRating": 93, "genres": ["Crime", "Drama", "History"], "overview": "New York cop Frank Serpico blows the whistle on the rampant corruption in the force only to have his comrades turn against him.", "poster": "assets/movie-serpico-1973-poster.jpg", "backdrop": "assets/movie-serpico-1973-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-10T21:45:02.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 9040, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 93, "rtCriticsLabel": "Certified Fresh", "rtAudience": 88, "rtAudienceLabel": "Upright", "imdb": 7.7, "imdbVotes": 149750, "tmdb": 7.525, "tmdbVotes": 2323}}, {"id": "movie-shaft-2000", "title": "Shaft", "year": 2000, "type": "movie", "runtimeSeconds": 5960, "seasonCount": null, "communityRating": 6.026, "criticRating": 67, "genres": ["Action", "Adventure", "Crime"], "overview": "New York police detective John Shaft arrests Walter Wade Jr. for a racially motivated slaying. But the only eyewitness disappears, and Wade jumps bail for Switzerland. Two years later Wade returns to face trial,\u2026", "poster": "assets/movie-shaft-2000-poster.jpg", "backdrop": "assets/movie-shaft-2000-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-10T21:30:40.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 479, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 68, "rtCriticsLabel": "Fresh", "rtAudience": 40, "rtAudienceLabel": "Spilled", "imdb": 6, "imdbVotes": 86257, "tmdb": 6.024, "tmdbVotes": 1531}}, {"id": "movie-a-silent-voice-the-movie-2016", "title": "A Silent Voice: The Movie", "year": 2016, "type": "movie", "runtimeSeconds": 7804, "seasonCount": null, "communityRating": 8.398, "criticRating": null, "genres": ["Animation", "Drama", "Romance"], "overview": "Shouya Ishida starts bullying the new girl in class, Shouko Nishimiya, because she is deaf. But as the teasing continues, the rest of the class starts to turn on Shouya for his lack of compassion. When they leave\u2026", "poster": "assets/movie-a-silent-voice-the-movie-2016-poster.jpg", "backdrop": "assets/movie-a-silent-voice-the-movie-2016-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T17:39:46.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 378064, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 95, "rtCriticsLabel": "Fresh", "rtAudience": 94, "rtAudienceLabel": "Upright", "imdb": 8.2, "imdbVotes": 151926, "tmdb": 8.396, "tmdbVotes": 4764}}, {"id": "movie-sinbad-legend-of-the-seven-seas-2003", "title": "Sinbad: Legend of the Seven Seas", "year": 2003, "type": "movie", "runtimeSeconds": 5143, "seasonCount": null, "communityRating": 7.067, "criticRating": 44, "genres": ["Action", "Adventure", "Animation"], "overview": "The sailor of legend is framed by the goddess Eris for the theft of the Book of Peace, and must travel to her realm at the end of the world to retrieve it and save the life of his childhood friend Prince Proteus.", "poster": "assets/movie-sinbad-legend-of-the-seven-seas-2003-poster.jpg", "backdrop": "assets/movie-sinbad-legend-of-the-seven-seas-2003-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:26:25.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 14411, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 46, "rtCriticsLabel": "Rotten", "rtAudience": 57, "rtAudienceLabel": "Spilled", "imdb": 6.8, "imdbVotes": 65298, "tmdb": 7.071, "tmdbVotes": 2323}}, {"id": "movie-sleeping-with-other-people-2015", "title": "Sleeping with Other People", "year": 2015, "type": "movie", "runtimeSeconds": 6076, "seasonCount": null, "communityRating": 6.274, "criticRating": 64, "genres": ["Comedy", "Romance"], "overview": "Can two serial cheaters get a second chance at love? After a one-night stand in college, New Yorkers Lainey and Jake meet by chance twelve years later and discover they each have the same problem: because of their\u2026", "poster": "assets/movie-sleeping-with-other-people-2015-poster.jpg", "backdrop": "assets/movie-sleeping-with-other-people-2015-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:27:42.0000000Z", "lastPlayedAt": "2026-07-17T17:50:44.0000000Z", "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 288036, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 64, "rtCriticsLabel": "Fresh", "rtAudience": 56, "rtAudienceLabel": "Spilled", "imdb": 6.5, "imdbVotes": 52275, "tmdb": 6.275, "tmdbVotes": 1020}}, {"id": "movie-snowpiercer-2013", "title": "Snowpiercer", "year": 2013, "type": "movie", "runtimeSeconds": 7573, "seasonCount": null, "communityRating": 6.909, "criticRating": 94, "genres": ["Action", "Science Fiction", "Drama"], "overview": "In a future where a failed global-warming experiment kills off most life on the planet, a class system evolves aboard the Snowpiercer; a train that travels around the globe via a perpetual-motion engine.", "poster": "assets/movie-snowpiercer-2013-poster.jpg", "backdrop": "assets/movie-snowpiercer-2013-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:27:07.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 110415, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 94, "rtCriticsLabel": "Certified Fresh", "rtAudience": 72, "rtAudienceLabel": "Upright", "imdb": 7.1, "imdbVotes": 426976, "tmdb": 6.9, "tmdbVotes": 10807}}, {"id": "movie-spider-man-brand-new-day-2026", "title": "Spider-Man: Brand New Day", "year": 2026, "type": "movie", "runtimeSeconds": 8701, "seasonCount": null, "communityRating": 8.374, "criticRating": 90, "genres": ["Science Fiction", "Action", "Adventure"], "overview": "Fighting crime full-time as Spider-Man in a world that doesn't remember him\u2014and the pressure of seeing his old friends move on without him\u2014sparks a change in Peter Parker he may not have the power to control. But\u2026", "poster": "assets/movie-spider-man-brand-new-day-2026-poster.jpg", "backdrop": "assets/movie-spider-man-brand-new-day-2026-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-10T14:14:27.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 969681, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 90, "rtCriticsLabel": "Certified Fresh", "rtAudience": 97, "rtAudienceLabel": "Upright", "imdb": 7.9, "imdbVotes": 366886, "tmdb": 8.5, "tmdbVotes": 6537}}, {"id": "movie-spider-man-far-from-home-2019", "title": "Spider-Man: Far From Home", "year": 2019, "type": "movie", "runtimeSeconds": 7762, "seasonCount": null, "communityRating": 7.389, "criticRating": 91, "genres": ["Action", "Adventure", "Science Fiction"], "overview": "Peter Parker and his friends go on a summer trip to Europe. However, they will hardly be able to rest - Peter will have to agree to help Nick Fury uncover the mystery of creatures that cause natural disasters and\u2026", "poster": "assets/movie-spider-man-far-from-home-2019-poster.jpg", "backdrop": "assets/movie-spider-man-far-from-home-2019-backdrop.jpg", "played": true, "playCount": 1, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-10T14:25:47.0000000Z", "lastPlayedAt": "2026-10-10T17:50:35.6193119Z", "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 429617, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 91, "rtCriticsLabel": "Certified Fresh", "rtAudience": 95, "rtAudienceLabel": "Upright", "imdb": 7.3, "imdbVotes": 682887, "tmdb": 7.387, "tmdbVotes": 17847}}, {"id": "movie-spider-man-homecoming-2017", "title": "Spider-Man: Homecoming", "year": 2017, "type": "movie", "runtimeSeconds": 8008, "seasonCount": null, "communityRating": 7.333, "criticRating": 92, "genres": ["Action", "Adventure", "Science Fiction"], "overview": "Following the events of Captain America: Civil War, Peter Parker, with the help of his mentor Tony Stark, tries to balance his life as an ordinary high school student in Queens, New York City, with fighting crime as\u2026", "poster": "assets/movie-spider-man-homecoming-2017-poster.jpg", "backdrop": "assets/movie-spider-man-homecoming-2017-backdrop.jpg", "played": true, "playCount": 1, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-10T14:33:13.0000000Z", "lastPlayedAt": "2026-10-10T20:31:23.4444213Z", "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 315635, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 92, "rtCriticsLabel": "Certified Fresh", "rtAudience": 87, "rtAudienceLabel": "Upright", "imdb": 7.4, "imdbVotes": 840974, "tmdb": 7.332, "tmdbVotes": 24183}}, {"id": "movie-spider-man-no-way-home-2021", "title": "Spider-Man: No Way Home", "year": 2021, "type": "movie", "runtimeSeconds": 8890, "seasonCount": null, "communityRating": 7.947, "criticRating": 93, "genres": ["Action", "Adventure", "Science Fiction"], "overview": "Peter Parker is unmasked and no longer able to separate his normal life from the high-stakes of being a super-hero. When he asks for help from Doctor Strange the stakes become even more dangerous, forcing him to\u2026", "poster": "assets/movie-spider-man-no-way-home-2021-poster.jpg", "backdrop": "assets/movie-spider-man-no-way-home-2021-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-10T14:21:05.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 634649, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 93, "rtCriticsLabel": "Certified Fresh", "rtAudience": 97, "rtAudienceLabel": "Upright", "imdb": 8.1, "imdbVotes": 1082660, "tmdb": 7.947, "tmdbVotes": 23505}}, {"id": "movie-spirited-away-2001", "title": "Spirited Away", "year": 2001, "type": "movie", "runtimeSeconds": 7472, "seasonCount": null, "communityRating": 8.535, "criticRating": 96, "genres": ["Animation", "Family", "Fantasy"], "overview": "A young girl, Chihiro, becomes trapped in a strange new world of spirits. When her parents undergo a mysterious transformation, she must call upon the courage she never knew she had to free her family.", "poster": "assets/movie-spirited-away-2001-poster.jpg", "backdrop": "assets/movie-spirited-away-2001-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:22:42.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 129, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 96, "rtCriticsLabel": "Certified Fresh", "rtAudience": 99, "rtAudienceLabel": "Upright", "imdb": 8.6, "imdbVotes": 987658, "tmdb": 8.536, "tmdbVotes": 19176}}, {"id": "movie-the-substance-2024", "title": "The Substance", "year": 2024, "type": "movie", "runtimeSeconds": 8531, "seasonCount": null, "communityRating": 7.135, "criticRating": 89, "genres": ["Horror", "Science Fiction"], "overview": "A fading celebrity decides to use a black market drug, a cell-replicating substance that temporarily creates a younger, better version of herself.", "poster": "assets/movie-the-substance-2024-poster.jpg", "backdrop": "assets/movie-the-substance-2024-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:30:22.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 933260, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 89, "rtCriticsLabel": "Certified Fresh", "rtAudience": 76, "rtAudienceLabel": "Upright", "imdb": 7.2, "imdbVotes": 466542, "tmdb": 7.13, "tmdbVotes": 6746}}, {"id": "movie-the-tale-of-the-princess-kaguya-2013", "title": "The Tale of The Princess Kaguya", "year": 2013, "type": "movie", "runtimeSeconds": 8218, "seasonCount": null, "communityRating": 8.1, "criticRating": 100, "genres": ["Animation", "Drama", "Fantasy"], "overview": "Found inside a shining stalk of bamboo by an old bamboo cutter and his wife, a tiny girl grows rapidly into an exquisite young lady. The mysterious young princess enthrals all who encounter her. But, ultimately, she\u2026", "poster": "assets/movie-the-tale-of-the-princess-kaguya-2013-poster.jpg", "backdrop": "assets/movie-the-tale-of-the-princess-kaguya-2013-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-07T13:40:15.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 149871, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Certified Fresh", "rtAudience": 90, "rtAudienceLabel": "Upright", "imdb": 8, "imdbVotes": 63574, "tmdb": 8.083, "tmdbVotes": 2079}}, {"id": "movie-taxi-driver-1976", "title": "Taxi Driver", "year": 1976, "type": "movie", "runtimeSeconds": 6831, "seasonCount": null, "communityRating": 8.121, "criticRating": 89, "genres": ["Crime", "Drama"], "overview": "Suffering from insomnia, disturbed loner Travis Bickle takes a job as a New York City cabbie, haunting the streets nightly, growing increasingly detached from reality as he dreams of cleaning up the filthy city.", "poster": "assets/movie-taxi-driver-1976-poster.jpg", "backdrop": "assets/movie-taxi-driver-1976-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:23:04.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 103, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 89, "rtCriticsLabel": "Certified Fresh", "rtAudience": 93, "rtAudienceLabel": "Upright", "imdb": 8.2, "imdbVotes": 1046700, "tmdb": 8.116, "tmdbVotes": 13973}}, {"id": "movie-triangle-of-sadness-2022", "title": "Triangle of Sadness", "year": 2022, "type": "movie", "runtimeSeconds": 8833, "seasonCount": null, "communityRating": 7.005, "criticRating": 71, "genres": ["Comedy", "Drama"], "overview": "A celebrity model couple are invited on a luxury cruise for the uber-rich, helmed by an unhinged, alcoholic captain. What first appears Instagrammable ends catastrophically, leaving the survivors stranded on a desert\u2026", "poster": "assets/movie-triangle-of-sadness-2022-poster.jpg", "backdrop": "assets/movie-triangle-of-sadness-2022-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:29:00.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 497828, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 72, "rtCriticsLabel": "Fresh", "rtAudience": 80, "rtAudienceLabel": "Upright", "imdb": 7.2, "imdbVotes": 221069, "tmdb": 7, "tmdbVotes": 3104}}, {"id": "movie-tsotsi-2005", "title": "Tsotsi", "year": 2005, "type": "movie", "runtimeSeconds": 5663, "seasonCount": null, "communityRating": 6.937, "criticRating": 82, "genres": ["Crime", "Drama"], "overview": "A young South African boy from the Johannesburg ghetto named Tsotsi, meaning Gangster, leaves home as a child to get away from his helpless parents. Now a teenage thug, Tsotsi finds a baby in the back seat of a car\u2026", "poster": "assets/movie-tsotsi-2005-poster.jpg", "backdrop": "assets/movie-tsotsi-2005-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:24:58.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 868, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 82, "rtCriticsLabel": "Certified Fresh", "rtAudience": 86, "rtAudienceLabel": "Upright", "imdb": 7.2, "imdbVotes": 32526, "tmdb": 6.941, "tmdbVotes": 372}}, {"id": "movie-unknown-number-the-high-school-catfish-2025", "title": "Unknown Number: The High School Catfish", "year": 2025, "type": "movie", "runtimeSeconds": 5691, "seasonCount": null, "communityRating": 6.746, "criticRating": 85, "genres": ["Documentary"], "overview": "Vulgar, taunting texts blow up the phones of a teen and her boyfriend. Who's sending them \u2014 and why? This twisty documentary reveals the shocking answer.", "poster": "assets/movie-unknown-number-the-high-school-catfish-2025-poster.jpg", "backdrop": "assets/movie-unknown-number-the-high-school-catfish-2025-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:32:10.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 1517772, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 85, "rtCriticsLabel": "Fresh", "rtAudience": 69, "rtAudienceLabel": "Upright", "imdb": 6.7, "imdbVotes": 23039, "tmdb": 6.737, "tmdbVotes": 257}}, {"id": "movie-the-uprising-2026", "title": "The Uprising", "year": 2026, "type": "movie", "runtimeSeconds": 7684, "seasonCount": null, "communityRating": 7.846, "criticRating": 55, "genres": ["Action", "Drama", "History"], "overview": "As war and plague devastate 14th-century England, a humble peasant ignites a rebellion, uniting an army of commoners to defy the King\u2019s might in a desperate fight for justice and survival.", "poster": "assets/movie-the-uprising-2026-poster.jpg", "backdrop": "assets/movie-the-uprising-2026-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-11-02T16:37:03.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 977942, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 57, "rtCriticsLabel": "Rotten", "rtAudience": 65, "rtAudienceLabel": "Upright", "imdb": 6.2, "imdbVotes": 6622, "tmdb": 7.8, "tmdbVotes": 404}}, {"id": "movie-vice-2018", "title": "Vice", "year": 2018, "type": "movie", "runtimeSeconds": 7948, "seasonCount": null, "communityRating": 7.013, "criticRating": 64, "genres": ["Drama", "Comedy", "History"], "overview": "George W. Bush picks Dick Cheney, the CEO of Halliburton Co., to be his Republican running mate in the 2000 presidential election. No stranger to politics, Cheney's impressive r\u00e9sum\u00e9 includes stints as White House\u2026", "poster": "assets/movie-vice-2018-poster.jpg", "backdrop": "assets/movie-vice-2018-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:28:30.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 429197, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 64, "rtCriticsLabel": "Fresh", "rtAudience": 61, "rtAudienceLabel": "Upright", "imdb": 7.2, "imdbVotes": 177299, "tmdb": 7.014, "tmdbVotes": 3723}}, {"id": "movie-warriors-of-the-wind-1984", "title": "Warriors of the Wind", "year": 1984, "type": "movie", "runtimeSeconds": 7022, "seasonCount": null, "communityRating": 7.941, "criticRating": 87, "genres": ["Adventure", "Animation", "Fantasy"], "overview": "After a global war, the seaside kingdom known as the Valley of the Wind remains one of the last strongholds on Earth untouched by a poisonous jungle and the powerful insects that guard it. Led by the courageous\u2026", "poster": "assets/movie-warriors-of-the-wind-1984-poster.jpg", "backdrop": "assets/movie-warriors-of-the-wind-1984-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:22:20.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 81, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 87, "rtCriticsLabel": "Fresh", "rtAudience": 91, "rtAudienceLabel": "Upright", "imdb": 8, "imdbVotes": 200154, "tmdb": 7.94, "tmdbVotes": 4260}}, {"id": "movie-we-bury-the-dead-2026", "title": "We Bury the Dead", "year": 2026, "type": "movie", "runtimeSeconds": 5693, "seasonCount": null, "communityRating": 6.153, "criticRating": 87, "genres": ["Horror", "Thriller"], "overview": "After a catastrophic military disaster reanimates the dead, Ava searches for her missing husband amidst an increasingly hostile world.", "poster": "assets/movie-we-bury-the-dead-2026-poster.jpg", "backdrop": "assets/movie-we-bury-the-dead-2026-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-08-26T05:36:00.4272328Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 1198984, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 87, "rtCriticsLabel": "Certified Fresh", "rtAudience": 45, "rtAudienceLabel": "Spilled", "imdb": 5.6, "imdbVotes": 20693, "tmdb": 6.137, "tmdbVotes": 433}}, {"id": "movie-white-hot-the-rise-fall-of-abercrombie-fitch-2022", "title": "White Hot: The Rise & Fall of Abercrombie & Fitch", "year": 2022, "type": "movie", "runtimeSeconds": 5187, "seasonCount": null, "communityRating": 5.663, "criticRating": 66, "genres": ["Documentary"], "overview": "All the cool kids were wearing it. This documentary explores A&F's pop culture reign in the late '90s and early 2000s and how it thrived on exclusion.", "poster": "assets/movie-white-hot-the-rise-fall-of-abercrombie-fitch-2022-poster.jpg", "backdrop": "assets/movie-white-hot-the-rise-fall-of-abercrombie-fitch-2022-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:30:34.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 953371, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 66, "rtCriticsLabel": "Fresh", "rtAudience": 29, "rtAudienceLabel": "Spilled", "imdb": 5.8, "imdbVotes": 5655, "tmdb": 5.7, "tmdbVotes": 102}}, {"id": "movie-the-wild-robot-2024", "title": "The Wild Robot", "year": 2024, "type": "movie", "runtimeSeconds": 6101, "seasonCount": null, "communityRating": 8.308, "criticRating": 97, "genres": ["Family", "Animation", "Science Fiction"], "overview": "After a shipwreck, an intelligent robot called Roz is stranded on an uninhabited island. To survive the harsh environment, Roz bonds with the island's animals and cares for an orphaned baby goose.", "poster": "assets/movie-the-wild-robot-2024-poster.jpg", "backdrop": "assets/movie-the-wild-robot-2024-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:31:49.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 1184918, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 97, "rtCriticsLabel": "Certified Fresh", "rtAudience": 98, "rtAudienceLabel": "Upright", "imdb": 8.1, "imdbVotes": 241134, "tmdb": 8.309, "tmdbVotes": 6819}}, {"id": "movie-the-wind-rises-2013", "title": "The Wind Rises", "year": 2013, "type": "movie", "runtimeSeconds": 8031, "seasonCount": null, "communityRating": 7.784, "criticRating": 88, "genres": ["Animation", "Drama", "History"], "overview": "A lifelong love of flight inspires Japanese aviation engineer Jiro Horikoshi, whose storied career includes the creation of the A-6M World War II fighter plane.", "poster": "assets/movie-the-wind-rises-2013-poster.jpg", "backdrop": "assets/movie-the-wind-rises-2013-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:24:38.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 149870, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 89, "rtCriticsLabel": "Certified Fresh", "rtAudience": 85, "rtAudienceLabel": "Upright", "imdb": 7.8, "imdbVotes": 115756, "tmdb": 7.783, "tmdbVotes": 3536}}, {"id": "movie-the-witcher-nightmare-of-the-wolf-2021", "title": "The Witcher: Nightmare of the Wolf", "year": 2021, "type": "movie", "runtimeSeconds": 5015, "seasonCount": null, "communityRating": 7.422, "criticRating": 100, "genres": ["Animation", "Action", "Fantasy"], "overview": "Escaping from poverty to become a witcher, Vesemir slays monsters for coin and glory, but when a new menace rises, he must face the demons of his past.", "poster": "assets/movie-the-witcher-nightmare-of-the-wolf-2021-poster.jpg", "backdrop": "assets/movie-the-witcher-nightmare-of-the-wolf-2021-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-26T04:42:03.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 666243, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": 83, "rtAudienceLabel": "Upright", "imdb": 7.2, "imdbVotes": 55759, "tmdb": 7.407, "tmdbVotes": 1159}}, {"id": "movie-the-witcher-sirens-of-the-deep-2025", "title": "The Witcher: Sirens of the Deep", "year": 2025, "type": "movie", "runtimeSeconds": 5517, "seasonCount": null, "communityRating": 6.8, "criticRating": 60, "genres": ["Animation", "Fantasy", "Action"], "overview": "When human sailors are attacked by mysterious creatures of the deep, only one person can stop the war between land and sea: the Witcher, Geralt of Rivia.", "poster": "assets/movie-the-witcher-sirens-of-the-deep-2025-poster.jpg", "backdrop": "assets/movie-the-witcher-sirens-of-the-deep-2025-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-26T05:03:26.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 1203329, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 60, "rtCriticsLabel": "Fresh", "rtAudience": 37, "rtAudienceLabel": "Spilled", "imdb": 6.1, "imdbVotes": 11799, "tmdb": 6.741, "tmdbVotes": 303}}], "shows": [{"id": "tv-adults-2025", "title": "Adults", "year": 2025, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 6.495, "criticRating": null, "genres": ["Comedy"], "overview": "A group of twenty-somethings in New York trying to be good people, despite being neither \"good\" nor \"people\" yet.", "poster": "assets/tv-adults-2025-poster.jpg", "backdrop": "assets/tv-adults-2025-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-23T21:01:41.1814765Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 260458, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 88, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.5, "tmdbVotes": 102}}, {"id": "tv-american-crime-story-2016", "title": "American Crime Story", "year": 2016, "type": "tv", "runtimeSeconds": null, "seasonCount": 3, "communityRating": 7.573, "criticRating": null, "genres": ["Crime", "Drama"], "overview": "An anthology series centered around some of history's most famous criminal investigations.", "poster": "assets/tv-american-crime-story-2016-poster.jpg", "backdrop": "assets/tv-american-crime-story-2016-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0490488Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 64513, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 84, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.585, "tmdbVotes": 1094}}, {"id": "tv-american-primeval-2025", "title": "American Primeval", "year": 2025, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.691, "criticRating": null, "genres": ["Western", "Drama", "Action & Adventure"], "overview": "A mother and son fleeing from their past form a found family while confronting a harsh landscape of freedom and cruelty in the American West.", "poster": "assets/tv-american-primeval-2025-poster.jpg", "backdrop": "assets/tv-american-primeval-2025-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0573221Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 215866, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 71, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.741, "tmdbVotes": 538}}, {"id": "tv-arrested-development-2003", "title": "Arrested Development", "year": 2003, "type": "tv", "runtimeSeconds": null, "seasonCount": 5, "communityRating": 7.925, "criticRating": null, "genres": ["Comedy"], "overview": "The story of a wealthy family that lost everything, and the one son who had no choice but to keep them all together.", "poster": "assets/tv-arrested-development-2003-poster.jpg", "backdrop": "assets/tv-arrested-development-2003-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0513048Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 4589, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 75, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.906, "tmdbVotes": 1793}}, {"id": "tv-avatar-the-last-airbender-2005", "title": "Avatar: The Last Airbender", "year": 2005, "type": "tv", "runtimeSeconds": 1440, "seasonCount": 3, "communityRating": 8.78, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "In a war-torn world of elemental magic, a young boy reawakens to undertake a dangerous mystic quest to fulfill his destiny as the Avatar, and bring peace to the world.", "poster": "assets/tv-avatar-the-last-airbender-2005-poster.jpg", "backdrop": "assets/tv-avatar-the-last-airbender-2005-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0504536Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 246, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.787, "tmdbVotes": 5215}}, {"id": "tv-b-b-vol-liefde-2021", "title": "B&B Vol Liefde", "year": 2021, "type": "tv", "runtimeSeconds": 3720, "seasonCount": 1, "communityRating": 7.2, "criticRating": null, "genres": ["Reality"], "overview": "B&B Vol Liefde is a heartwarming adventure, full of romance, excitement, and unforgettable moments. Adventurous Dutch B&B owners abroad throw their doors wide open and let their hearts speak, determined to find that\u2026", "poster": "assets/tv-b-b-vol-liefde-2021-poster.jpg", "backdrop": "assets/tv-b-b-vol-liefde-2021-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-31T22:30:11.129657Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 124428, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.2, "tmdbVotes": 10}}, {"id": "tv-the-bear-2022", "title": "The Bear", "year": 2022, "type": "tv", "runtimeSeconds": null, "seasonCount": 5, "communityRating": 8.139, "criticRating": null, "genres": ["Drama", "Comedy"], "overview": "Carmy, a young fine-dining chef, comes home to Chicago to run his family sandwich shop. As he fights to transform the shop and himself, he works alongside a rough-around-the-edges crew that ultimately reveal\u2026", "poster": "assets/tv-the-bear-2022-poster.jpg", "backdrop": "assets/tv-the-bear-2022-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0467371Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 136315, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 93, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.158, "tmdbVotes": 2025}}, {"id": "tv-blue-planet-ii-2017", "title": "Blue Planet II", "year": 2017, "type": "tv", "runtimeSeconds": 3600, "seasonCount": 1, "communityRating": 8.64, "criticRating": null, "genres": ["Documentary", "Family"], "overview": "There is nowhere more powerful and unforgiving yet more beautiful and compelling than the ocean. Join us and explore the greatest yet least known parts of our planet.", "poster": "assets/tv-blue-planet-ii-2017-poster.jpg", "backdrop": "assets/tv-blue-planet-ii-2017-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0634849Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 74313, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 97, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.6, "tmdbVotes": 380}}, {"id": "tv-breeders-2020", "title": "Breeders", "year": 2020, "type": "tv", "runtimeSeconds": 1500, "seasonCount": 4, "communityRating": 6.8, "criticRating": null, "genres": ["Comedy"], "overview": "Exposing the parental-paradox that it is possible, in the very same moment, to love your child to the horizon of the universe, while being apoplectically angry enough to want to send them there.", "poster": "assets/tv-breeders-2020-poster.jpg", "backdrop": "assets/tv-breeders-2020-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-28T18:54:21.8885435Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 93290, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 92, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.8, "tmdbVotes": 104}}, {"id": "tv-chernobyl-2019", "title": "Chernobyl", "year": 2019, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 8.718, "criticRating": null, "genres": ["Drama"], "overview": "The true story of one of the worst man-made catastrophes in history: the catastrophic nuclear accident at Chernobyl. A tale of the brave men and women who sacrificed to save Europe from unimaginable disaster.", "poster": "assets/tv-chernobyl-2019-poster.jpg", "backdrop": "assets/tv-chernobyl-2019-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0469903Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 87108, "artwork": {"posterKind": "poster", "backdropKind": "center_crop"}, "ratings": {"rtCritics": 95, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.731, "tmdbVotes": 8496}}, {"id": "tv-community-2009", "title": "Community", "year": 2009, "type": "tv", "runtimeSeconds": null, "seasonCount": 6, "communityRating": 7.997, "criticRating": null, "genres": ["Comedy"], "overview": "Follow the lives of a group of students at what is possibly the world\u2019s worst community college in the fictional locale of Greendale, Colorado.", "poster": "assets/tv-community-2009-poster.jpg", "backdrop": "assets/tv-community-2009-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0531416Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 18347, "artwork": {"posterKind": "center_crop", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 88, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.994, "tmdbVotes": 2226}}, {"id": "tv-dead-end-job-2026", "title": "Dead-End Job", "year": 2026, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.7, "criticRating": null, "genres": ["Mystery"], "overview": "Drowning in debt, a young man takes lucrative part-time jobs from a mysterious agency \u2014 only to find his hustle for more money turning nightmarish.", "poster": "assets/tv-dead-end-job-2026-poster.jpg", "backdrop": "assets/tv-dead-end-job-2026-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-11-04T18:58:01.8352857Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 289517, "artwork": {"posterKind": "center_crop", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.7, "tmdbVotes": 38}}, {"id": "tv-dopesick-2021", "title": "Dopesick", "year": 2021, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 8.015, "criticRating": null, "genres": ["Crime", "Drama"], "overview": "The story of how one company triggered the worst drug epidemic in American history. Look into the epicenter of America's struggle with opioid addiction, from a distressed Virginia mining community, to the hallways of\u2026", "poster": "assets/tv-dopesick-2021-poster.jpg", "backdrop": "assets/tv-dopesick-2021-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0580168Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 110695, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 89, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.035, "tmdbVotes": 565}}, {"id": "tv-dune-prophecy-2024", "title": "Dune: Prophecy", "year": 2024, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.213, "criticRating": null, "genres": ["Sci-Fi & Fantasy", "Drama", "Action & Adventure"], "overview": "Ten thousand years before the ascension of Paul Atreides, sisters Valya and Tula Harkonnen establish the fabled sect and female order that would become known as the Bene Gesserit to control the future of humankind.", "poster": "assets/tv-dune-prophecy-2024-poster.jpg", "backdrop": "assets/tv-dune-prophecy-2024-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-11-04T18:23:01.2908568Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 90228, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 64, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.213, "tmdbVotes": 677}}, {"id": "tv-east-of-eden-2026", "title": "East of Eden", "year": 2026, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.931, "criticRating": null, "genres": ["Drama", "Crime"], "overview": "In the early 1900s, a fiercely independent woman drives a rift between two brothers, igniting a saga that unfolds across two generations.", "poster": "assets/tv-east-of-eden-2026-poster.jpg", "backdrop": "assets/tv-east-of-eden-2026-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-23T18:07:11.3965096Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 258165, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 88, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.982, "tmdbVotes": 139}}, {"id": "tv-fallout-2024", "title": "Fallout", "year": 2024, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 8.124, "criticRating": null, "genres": ["Action & Adventure", "Sci-Fi & Fantasy", "Drama"], "overview": "The story of haves and have-nots in a world in which there's almost nothing left to have. 200 years after the apocalypse, the gentle denizens of luxury fallout shelters are forced to return to the irradiated\u2026", "poster": "assets/tv-fallout-2024-poster.jpg", "backdrop": "assets/tv-fallout-2024-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0669262Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 106379, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 94, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.106, "tmdbVotes": 3233}}, {"id": "tv-fawlty-towers-1975", "title": "Fawlty Towers", "year": 1975, "type": "tv", "runtimeSeconds": 1800, "seasonCount": 2, "communityRating": 8.295, "criticRating": null, "genres": ["Comedy"], "overview": "Owner Basil Fawlty, his wife Sybil, a chambermaid Polly, and Spanish waiter Manuel attempt to run their hotel amidst farcical situations and an array of demanding guests.", "poster": "assets/tv-fawlty-towers-1975-poster.jpg", "backdrop": "assets/tv-fawlty-towers-1975-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-19T23:26:39.7658344Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 2207, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.296, "tmdbVotes": 867}}, {"id": "tv-the-final-problem-2026", "title": "The Final Problem", "year": 2026, "type": "tv", "runtimeSeconds": 3420, "seasonCount": 1, "communityRating": 7.041, "criticRating": null, "genres": ["Crime", "Mystery"], "overview": "In the spring of 1959, a retired actor, who previously played Sherlock Holmes, must investigate a murder at a small hotel on a tiny island near Mallorca.", "poster": "assets/tv-the-final-problem-2026-poster.jpg", "backdrop": "assets/tv-the-final-problem-2026-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-11-04T19:16:12.4822791Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 290720, "artwork": {"posterKind": "center_crop", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.041, "tmdbVotes": 49}}, {"id": "tv-the-four-seasons-2025", "title": "The Four Seasons", "year": 2025, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 6.879, "criticRating": null, "genres": ["Comedy"], "overview": "The decades-long friendship between three married couples is tested when one divorces, complicating their tradition of quarterly weekend getaways.", "poster": "assets/tv-the-four-seasons-2025-poster.jpg", "backdrop": "assets/tv-the-four-seasons-2025-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-28T18:36:42.5326384Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 243316, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 81, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.892, "tmdbVotes": 204}}, {"id": "tv-the-get-down-2016", "title": "The Get Down", "year": 2016, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.3, "criticRating": null, "genres": ["Drama"], "overview": "In 1977 New York City, the talented and soulful youth of the South Bronx chase dreams and breakneck beats to transform music history.", "poster": "assets/tv-the-get-down-2016-poster.jpg", "backdrop": "assets/tv-the-get-down-2016-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0497737Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 65345, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 81, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.3, "tmdbVotes": 196}}, {"id": "tv-how-to-change-your-mind-2022", "title": "How to Change Your Mind", "year": 2022, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.76, "criticRating": null, "genres": ["Documentary", "Mystery"], "overview": "Author Michael Pollan leads the way in this docuseries exploring the history and uses of psychedelics, including LSD, psilocybin, MDMA and mescaline.", "poster": "assets/tv-how-to-change-your-mind-2022-poster.jpg", "backdrop": "assets/tv-how-to-change-your-mind-2022-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0601399Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 204304, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.8, "tmdbVotes": 53}}, {"id": "tv-interrogation-2020", "title": "Interrogation", "year": 2020, "type": "tv", "runtimeSeconds": 2700, "seasonCount": 1, "communityRating": 6.788, "criticRating": null, "genres": ["Drama", "Crime"], "overview": "Based on a true story that spanned more than 30 years in which a young man was charged and convicted of brutally murdering his mother. Each episode is structured around an interrogation taken directly from the real\u2026", "poster": "assets/tv-interrogation-2020-poster.jpg", "backdrop": "assets/tv-interrogation-2020-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-26T05:52:06.1068207Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 96488, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 57, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.9, "tmdbVotes": 54}}, {"id": "tv-it-s-always-sunny-in-philadelphia-2005", "title": "It's Always Sunny in Philadelphia", "year": 2005, "type": "tv", "runtimeSeconds": null, "seasonCount": 18, "communityRating": 8.2, "criticRating": null, "genres": ["Comedy"], "overview": "Four egocentric friends run a neighborhood Irish pub in Philadelphia and try to find their way through the adult world of work and relationships. Unfortunately, their warped views and precarious judgments often lead\u2026", "poster": "assets/tv-it-s-always-sunny-in-philadelphia-2005-poster.jpg", "backdrop": "assets/tv-it-s-always-sunny-in-philadelphia-2005-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0485774Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 2710, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 94, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.253, "tmdbVotes": 1457}}, {"id": "tv-a-knight-of-the-seven-kingdoms-2026", "title": "A Knight of the Seven Kingdoms", "year": 2026, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 8.431, "criticRating": null, "genres": ["Drama", "Sci-Fi & Fantasy", "Action & Adventure"], "overview": "A century before the events of Game of Thrones, two unlikely heroes wandered Westeros: a young, naive but courageous knight, Ser Duncan the Tall, and his diminutive squire, Egg. Set in an age when the Targaryen line\u2026", "poster": "assets/tv-a-knight-of-the-seven-kingdoms-2026-poster.jpg", "backdrop": "assets/tv-a-knight-of-the-seven-kingdoms-2026-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0493344Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 224372, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 94, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.4, "tmdbVotes": 1266}}, {"id": "tv-landman-2024", "title": "Landman", "year": 2024, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 8.194, "criticRating": null, "genres": ["Drama"], "overview": "Set in the proverbial boomtowns of West-Texas and a modern-day tale of fortune-seeking in the world of oil rigs, the series is an upstairs/downstairs story of roughnecks and wildcat billionaires that are fueling a\u2026", "poster": "assets/tv-landman-2024-poster.jpg", "backdrop": "assets/tv-landman-2024-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-22T21:38:18.0973875Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 157741, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 80, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.209, "tmdbVotes": 668}}, {"id": "tv-the-last-of-us-2023", "title": "The Last of Us", "year": 2023, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 8.419, "criticRating": null, "genres": ["Drama"], "overview": "Twenty years after modern civilization has been destroyed, Joel, a hardened survivor, is hired to smuggle Ellie, a 14-year-old girl, out of an oppressive quarantine zone. What starts as a small job soon becomes a\u2026", "poster": "assets/tv-the-last-of-us-2023-poster.jpg", "backdrop": "assets/tv-the-last-of-us-2023-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0640955Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 100088, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 94, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.4, "tmdbVotes": 7485}}, {"id": "tv-last-samurai-standing-2025", "title": "Last Samurai Standing", "year": 2025, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.805, "criticRating": null, "genres": ["Action & Adventure", "Drama"], "overview": "In the early Meiji era, Shujiro, once known as an undefeated samurai, decides to participate in a deadly survival game to save his family and villagers.", "poster": "assets/tv-last-samurai-standing-2025-poster.jpg", "backdrop": "assets/tv-last-samurai-standing-2025-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0622526Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 252193, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.812, "tmdbVotes": 271}}, {"id": "tv-lord-of-the-flies-2026", "title": "Lord of the Flies", "year": 2026, "type": "tv", "runtimeSeconds": 3480, "seasonCount": 1, "communityRating": 7.571, "criticRating": null, "genres": ["Drama", "Action & Adventure"], "overview": "After a plane crash, a group of schoolboys find themselves stranded on a tropical island without adults. Ralph is elected leader and, with the help of the intelligent Piggy, strives to maintain order and\u2026", "poster": "assets/tv-lord-of-the-flies-2026-poster.jpg", "backdrop": "assets/tv-lord-of-the-flies-2026-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0645648Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 270572, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 91, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.58, "tmdbVotes": 138}}, {"id": "tv-the-lord-of-the-rings-the-rings-of-power-2022", "title": "The Lord of the Rings: The Rings of Power", "year": 2022, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 7.233, "criticRating": null, "genres": ["Action & Adventure", "Sci-Fi & Fantasy", "Drama"], "overview": "Beginning in a time of relative peace, we follow an ensemble cast of characters as they confront the re-emergence of evil to Middle-earth. From the darkest depths of the Misty Mountains, to the majestic forests of\u2026", "poster": "assets/tv-the-lord-of-the-rings-the-rings-of-power-2022-poster.jpg", "backdrop": "assets/tv-the-lord-of-the-rings-the-rings-of-power-2022-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0618904Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 84773, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 84, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.211, "tmdbVotes": 4003}}, {"id": "tv-love-death-robots-2019", "title": "Love, Death & Robots", "year": 2019, "type": "tv", "runtimeSeconds": null, "seasonCount": 4, "communityRating": 8.21, "criticRating": null, "genres": ["Animation", "Sci-Fi & Fantasy"], "overview": "Terrifying creatures, wicked surprises and dark comedy converge in this NSFW anthology of animated stories presented by Tim Miller and David Fincher.", "poster": "assets/tv-love-death-robots-2019-poster.jpg", "backdrop": "assets/tv-love-death-robots-2019-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0460964Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 86831, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 89, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.203, "tmdbVotes": 4238}}, {"id": "tv-maniac-2018", "title": "Maniac", "year": 2018, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.338, "criticRating": null, "genres": ["Comedy", "Drama", "Sci-Fi & Fantasy"], "overview": "Two strangers are drawn to a mysterious pharmaceutical trial that will, they're assured, with no complications or side-effects whatsoever, solve all of their problems, permanently. Things do not go as planned.", "poster": "assets/tv-maniac-2018-poster.jpg", "backdrop": "assets/tv-maniac-2018-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-20T19:42:30.955548Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 73411, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 84, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.323, "tmdbVotes": 1073}}, {"id": "tv-mussolini-son-of-the-century-2024", "title": "Mussolini: Son of the Century", "year": 2024, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 8.135, "criticRating": null, "genres": ["Drama", "War & Politics"], "overview": "The rise of Benito Mussolini and the birth of fascism in Italy, chronicling a country\u2019s surrender to dictatorship and the relentless ascent of a man who rose from his ashes time and again.", "poster": "assets/tv-mussolini-son-of-the-century-2024-poster.jpg", "backdrop": "assets/tv-mussolini-son-of-the-century-2024-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0586588Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 212333, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 96, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.1, "tmdbVotes": 179}}, {"id": "tv-the-night-manager-2016", "title": "The Night Manager", "year": 2016, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 7.74, "criticRating": null, "genres": ["Drama", "Mystery", "Crime"], "overview": "Former British soldier Jonathan Pine navigates the shadowy recesses of Whitehall and Washington where an unholy alliance operates between the intelligence community and the secret arms trade. To infiltrate the inner\u2026", "poster": "assets/tv-the-night-manager-2016-poster.jpg", "backdrop": "assets/tv-the-night-manager-2016-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0663672Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 61859, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 88, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.7, "tmdbVotes": 1422}}, {"id": "tv-one-piece-2023", "title": "ONE PIECE", "year": 2023, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 8.118, "criticRating": null, "genres": ["Action & Adventure", "Sci-Fi & Fantasy"], "overview": "With his straw hat and ragtag crew, young pirate Monkey D. Luffy goes on an epic voyage for treasure.", "poster": "assets/tv-one-piece-2023-poster.jpg", "backdrop": "assets/tv-one-piece-2023-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0439378Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 111110, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 93, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.104, "tmdbVotes": 1988}}, {"id": "tv-only-murders-in-the-building-2021", "title": "Only Murders in the Building", "year": 2021, "type": "tv", "runtimeSeconds": null, "seasonCount": 5, "communityRating": 8.438, "criticRating": null, "genres": ["Comedy", "Mystery", "Crime"], "overview": "Three strangers share an obsession with true crime and suddenly find themselves wrapped up in one.", "poster": "assets/tv-only-murders-in-the-building-2021-poster.jpg", "backdrop": "assets/tv-only-murders-in-the-building-2021-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0594614Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 107113, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 95, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.398, "tmdbVotes": 2333}}, {"id": "tv-over-the-garden-wall-2014", "title": "Over the Garden Wall", "year": 2014, "type": "tv", "runtimeSeconds": 720, "seasonCount": 1, "communityRating": 8.6, "criticRating": null, "genres": ["Mystery", "Sci-Fi & Fantasy", "Animation"], "overview": "Two brothers, Wirt and Greg, find themselves lost in the Unknown; a strange forest adrift in time. With the help of a wise old Woodsman and a foul-tempered bluebird named Beatrice, Wirt and Greg must travel across\u2026", "poster": "assets/tv-over-the-garden-wall-2014-poster.jpg", "backdrop": "assets/tv-over-the-garden-wall-2014-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0455757Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 61617, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.6, "tmdbVotes": 1854}}, {"id": "tv-peaky-blinders-2013", "title": "Peaky Blinders", "year": 2013, "type": "tv", "runtimeSeconds": null, "seasonCount": 6, "communityRating": 8.5, "criticRating": null, "genres": ["Drama", "Crime"], "overview": "A gangster family epic set in 1919 Birmingham, England and centered on a gang who sew razor blades in the peaks of their caps, and their fierce boss Tommy Shelby, who means to move up in the world.", "poster": "assets/tv-peaky-blinders-2013-poster.jpg", "backdrop": "assets/tv-peaky-blinders-2013-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0539185Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 60574, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 93, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.5, "tmdbVotes": 11506}}, {"id": "tv-the-pitt-2025", "title": "The Pitt", "year": 2025, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 8.729, "criticRating": null, "genres": ["Drama"], "overview": "The staff of Pittsburgh's Trauma Medical Center work around the clock to save lives in an overcrowded and underfunded emergency department.", "poster": "assets/tv-the-pitt-2025-poster.jpg", "backdrop": "assets/tv-the-pitt-2025-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0582942Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 250307, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 96, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.731, "tmdbVotes": 1046}}, {"id": "tv-pluribus-2025", "title": "Pluribus", "year": 2025, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.936, "criticRating": null, "genres": ["Drama", "Sci-Fi & Fantasy"], "overview": "The most miserable person on Earth must save the world from happiness.", "poster": "assets/tv-pluribus-2025-poster.jpg", "backdrop": "assets/tv-pluribus-2025-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0674462Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 225171, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 99, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.921, "tmdbVotes": 1191}}, {"id": "tv-ripley-2024", "title": "RIPLEY", "year": 2024, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.911, "criticRating": null, "genres": ["Drama"], "overview": "A grifter in 1960s New York is hired to convince a wealthy man's son to return home from Italy and begins a life of deceit, fraud and murder.", "poster": "assets/tv-ripley-2024-poster.jpg", "backdrop": "assets/tv-ripley-2024-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0658942Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 94028, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 86, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.909, "tmdbVotes": 453}}, {"id": "tv-say-nothing-2024", "title": "Say Nothing", "year": 2024, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.462, "criticRating": null, "genres": ["Drama", "Crime"], "overview": "Through the eyes of various Irish Republican Army (IRA) members, explore the extremes some people will go to in the name of their beliefs, the way a deeply divided society can suddenly tip over into armed conflict,\u2026", "poster": "assets/tv-say-nothing-2024-poster.jpg", "backdrop": "assets/tv-say-nothing-2024-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0499752Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 245256, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 94, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.475, "tmdbVotes": 122}}, {"id": "tv-severance-2022", "title": "Severance", "year": 2022, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 8.396, "criticRating": null, "genres": ["Drama", "Mystery", "Sci-Fi & Fantasy"], "overview": "Mark leads a team of office workers whose memories have been surgically divided between their work and personal lives. When a mysterious colleague appears outside of work, it begins a journey to discover the truth\u2026", "poster": "assets/tv-severance-2022-poster.jpg", "backdrop": "assets/tv-severance-2022-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0598478Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 95396, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 95, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.389, "tmdbVotes": 3095}}, {"id": "tv-six-feet-under-2001", "title": "Six Feet Under", "year": 2001, "type": "tv", "runtimeSeconds": null, "seasonCount": 5, "communityRating": 8.115, "criticRating": null, "genres": ["Drama"], "overview": "When death is your business, what is your life? For the Fisher family, the world outside of their family-owned funeral home continues to be at least as challenging as\u2014and far less predictable than\u2014the one inside.", "poster": "assets/tv-six-feet-under-2001-poster.jpg", "backdrop": "assets/tv-six-feet-under-2001-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-26T04:04:05.108787Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 1274, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 81, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.1, "tmdbVotes": 1192}}, {"id": "tv-undercover-2019", "title": "Undercover", "year": 2019, "type": "tv", "runtimeSeconds": 3000, "seasonCount": 3, "communityRating": 7.5, "criticRating": null, "genres": ["Crime", "Drama"], "overview": "Undercover agents infiltrate a drug kingpin's operation by posing as a couple at the campground where he spends his weekends. Inspired by real events.", "poster": "assets/tv-undercover-2019-poster.jpg", "backdrop": "assets/tv-undercover-2019-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0570153Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 86942, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.5, "tmdbVotes": 186}}, {"id": "tv-upload-2020", "title": "Upload", "year": 2020, "type": "tv", "runtimeSeconds": null, "seasonCount": 4, "communityRating": 7.781, "criticRating": null, "genres": ["Comedy", "Sci-Fi & Fantasy", "Drama"], "overview": "In 2033, people who are near death can be \u201cuploaded\u201d into virtual reality hotels run by 6 tech firms. Cash-strapped Nora lives in Brooklyn and works customer service for the luxurious \u201cLakeview\u201d digital afterlife.\u2026", "poster": "assets/tv-upload-2020-poster.jpg", "backdrop": "assets/tv-upload-2020-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0566967Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 86248, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 87, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.757, "tmdbVotes": 1511}}, {"id": "tv-the-walk-in-2022", "title": "The Walk-In", "year": 2022, "type": "tv", "runtimeSeconds": 2760, "seasonCount": 1, "communityRating": 6.9, "criticRating": null, "genres": ["Drama", "Crime"], "overview": "Forced to flee the UK and go into hiding in the 1990s, when he became a mole from within the BNP, Matthew Collins returned to Britain to make a new life for himself, working with young white men in the fight against\u2026", "poster": "assets/tv-the-walk-in-2022-poster.jpg", "backdrop": "assets/tv-the-walk-in-2022-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0654569Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 210664, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 80, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7, "tmdbVotes": 50}}, {"id": "tv-the-white-lotus-2021", "title": "The White Lotus", "year": 2021, "type": "tv", "runtimeSeconds": null, "seasonCount": 3, "communityRating": 7.596, "criticRating": null, "genres": ["Comedy", "Drama", "Mystery"], "overview": "Follow the exploits of various guests and employees at an exclusive tropical resort over the span of a week as with each passing day, a darker complexity emerges in these picture-perfect travelers, the hotel\u2019s\u2026", "poster": "assets/tv-the-white-lotus-2021-poster.jpg", "backdrop": "assets/tv-the-white-lotus-2021-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0546438Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 111803, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 90, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.599, "tmdbVotes": 1604}}, {"id": "tv-widow-s-bay-2026", "title": "Widow's Bay", "year": 2026, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 8.172, "criticRating": null, "genres": ["Drama", "Comedy", "Mystery"], "overview": "The mayor of a New England town resolves to turn it into the next tourist hot spot, despite local warnings that it's cursed.", "poster": "assets/tv-widow-s-bay-2026-poster.jpg", "backdrop": "assets/tv-widow-s-bay-2026-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-20T10:27:12.7003979Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 270476, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 98, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8, "tmdbVotes": 585}}, {"id": "tv-the-witcher-2019", "title": "The Witcher", "year": 2019, "type": "tv", "runtimeSeconds": null, "seasonCount": 4, "communityRating": 7.905, "criticRating": null, "genres": ["Drama", "Action & Adventure"], "overview": "Geralt of Rivia, a mutated monster-hunter for hire, journeys toward his destiny in a turbulent world where people often prove more wicked than beasts.", "poster": "assets/tv-the-witcher-2019-poster.jpg", "backdrop": "assets/tv-the-witcher-2019-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0552369Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 71912, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 75, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.88, "tmdbVotes": 6997}}, {"id": "tv-yellowjackets-2021", "title": "Yellowjackets", "year": 2021, "type": "tv", "runtimeSeconds": null, "seasonCount": 3, "communityRating": 7.449, "criticRating": null, "genres": ["Drama", "Mystery"], "overview": "This equal parts survival epic, psychological horror story and coming-of-age drama tells the saga of a team of wildly talented high school girls soccer players who become the (un)lucky survivors of a plane crash deep\u2026", "poster": "assets/tv-yellowjackets-2021-poster.jpg", "backdrop": "assets/tv-yellowjackets-2021-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0577612Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 117488, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 92, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.431, "tmdbVotes": 1070}}], "anime": [], "resume": [{"id": "episode-episode-1-2025-s1e1", "title": "Episode 1", "year": 2025, "type": "episode", "runtimeSeconds": 3618, "seasonCount": null, "communityRating": 7.357, "criticRating": null, "genres": ["Biography", "Drama", "History"], "overview": "Mussolini had just founded the Fasci di Combattimento when D'Annunzio took Fiume. Mussolini hoped to capitalize on the poet's achievement in the electoral arena.", "poster": "assets/episode-episode-1-2025-s1e1-poster.jpg", "backdrop": "assets/episode-episode-1-2025-s1e1-backdrop.jpg", "played": false, "playCount": 1, "resumeSeconds": 489.55, "progress": 0.1353, "addedAt": "2026-07-17T13:52:48.0000000Z", "lastPlayedAt": "2026-11-04T19:01:49.9731435Z", "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "Mussolini: Son of the Century", "seasonNumber": 1, "episodeNumber": 1, "artwork": {"posterKind": "center_crop", "backdropKind": "backdrop"}}], "nextUp": [{"id": "episode-no-honeymoon-2020-s1e8", "title": "No Honeymoon", "year": 2020, "type": "episode", "runtimeSeconds": 1499, "seasonCount": null, "communityRating": 6.7, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "Paul attempts to focus on his upcoming wedding but is constantly thwarted by his family; Ally is distracted by work, Luke is misbehaving at school and Leah (Stella Gonet) doesn\u2019t understand why they are even\u2026", "poster": "assets/episode-no-honeymoon-2020-s1e8-poster.jpg", "backdrop": "assets/episode-no-honeymoon-2020-s1e8-backdrop.jpg", "played": false, "playCount": 1, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-28T19:00:50.0000000Z", "lastPlayedAt": "2026-11-03T20:57:58.3314947Z", "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "Breeders", "seasonNumber": 1, "episodeNumber": 8, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}, {"id": "episode-notapusy-2005-s3e4", "title": "Notapusy", "year": 2005, "type": "episode", "runtimeSeconds": 1334, "seasonCount": null, "communityRating": 8.125, "criticRating": null, "genres": ["Comedy"], "overview": "To prove he's a man to Rita, Michael and his nephew Steve Holt compete in the Church And State Fair's annual triathlon. George Sr. tries to scare some at-risk youth, and Tobias tries to help George-Michael's\u2026", "poster": "assets/episode-notapusy-2005-s3e4-poster.jpg", "backdrop": "assets/episode-notapusy-2005-s3e4-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T12:17:58.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "Arrested Development", "seasonNumber": 3, "episodeNumber": 4, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}, {"id": "episode-the-man-who-would-be-vogue-2018-s2e1", "title": "The Man Who Would Be Vogue", "year": 2018, "type": "episode", "runtimeSeconds": 3203, "seasonCount": null, "communityRating": 7.459, "criticRating": null, "genres": ["Biography", "Crime", "Drama"], "overview": "The murder of Gianni Versace turns the eyes of the world onto Miami Beach.", "poster": "assets/episode-the-man-who-would-be-vogue-2018-s2e1-poster.jpg", "backdrop": "assets/episode-the-man-who-would-be-vogue-2018-s2e1-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T13:29:35.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "American Crime Story", "seasonNumber": 2, "episodeNumber": 1, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}, {"id": "episode-tomorrow-2024-s3e1", "title": "Tomorrow", "year": 2024, "type": "episode", "runtimeSeconds": 2207, "seasonCount": null, "communityRating": 7.6, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "The next day and the days that led to it.", "poster": "assets/episode-tomorrow-2024-s3e1-poster.jpg", "backdrop": "assets/episode-tomorrow-2024-s3e1-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T13:46:39.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "The Bear", "seasonNumber": 3, "episodeNumber": 1, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}, {"id": "episode-episode-1-2025-s1e1", "title": "Episode 1", "year": 2025, "type": "episode", "runtimeSeconds": 3618, "seasonCount": null, "communityRating": 7.357, "criticRating": null, "genres": ["Biography", "Drama", "History"], "overview": "Mussolini had just founded the Fasci di Combattimento when D'Annunzio took Fiume. Mussolini hoped to capitalize on the poet's achievement in the electoral arena.", "poster": "assets/episode-episode-1-2025-s1e1-poster.jpg", "backdrop": "assets/episode-episode-1-2025-s1e1-backdrop.jpg", "played": false, "playCount": 1, "resumeSeconds": 489.55, "progress": 0.1353, "addedAt": "2026-07-17T13:52:48.0000000Z", "lastPlayedAt": "2026-11-04T19:01:49.9731435Z", "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "Mussolini: Son of the Century", "seasonNumber": 1, "episodeNumber": 1, "artwork": {"posterKind": "center_crop", "backdropKind": "backdrop"}}], "recentlyAdded": [{"id": "tv-the-final-problem-2026", "title": "The Final Problem", "year": 2026, "type": "tv", "runtimeSeconds": 3420, "seasonCount": 1, "communityRating": 7.041, "criticRating": null, "genres": ["Crime", "Mystery"], "overview": "In the spring of 1959, a retired actor, who previously played Sherlock Holmes, must investigate a murder at a small hotel on a tiny island near Mallorca.", "poster": "assets/tv-the-final-problem-2026-poster.jpg", "backdrop": "assets/tv-the-final-problem-2026-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-11-04T19:16:12.4822791Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 290720, "artwork": {"posterKind": "center_crop", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.041, "tmdbVotes": 49}}, {"id": "tv-dead-end-job-2026", "title": "Dead-End Job", "year": 2026, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.7, "criticRating": null, "genres": ["Mystery"], "overview": "Drowning in debt, a young man takes lucrative part-time jobs from a mysterious agency \u2014 only to find his hustle for more money turning nightmarish.", "poster": "assets/tv-dead-end-job-2026-poster.jpg", "backdrop": "assets/tv-dead-end-job-2026-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-11-04T18:58:01.8352857Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 289517, "artwork": {"posterKind": "center_crop", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.7, "tmdbVotes": 38}}, {"id": "tv-dune-prophecy-2024", "title": "Dune: Prophecy", "year": 2024, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.213, "criticRating": null, "genres": ["Sci-Fi & Fantasy", "Drama", "Action & Adventure"], "overview": "Ten thousand years before the ascension of Paul Atreides, sisters Valya and Tula Harkonnen establish the fabled sect and female order that would become known as the Bene Gesserit to control the future of humankind.", "poster": "assets/tv-dune-prophecy-2024-poster.jpg", "backdrop": "assets/tv-dune-prophecy-2024-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-11-04T18:23:01.2908568Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 90228, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 64, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.213, "tmdbVotes": 677}}, {"id": "movie-the-uprising-2026", "title": "The Uprising", "year": 2026, "type": "movie", "runtimeSeconds": 7684, "seasonCount": null, "communityRating": 7.846, "criticRating": 55, "genres": ["Action", "Drama", "History"], "overview": "As war and plague devastate 14th-century England, a humble peasant ignites a rebellion, uniting an army of commoners to defy the King\u2019s might in a desperate fight for justice and survival.", "poster": "assets/movie-the-uprising-2026-poster.jpg", "backdrop": "assets/movie-the-uprising-2026-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-11-02T16:37:03.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 977942, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 57, "rtCriticsLabel": "Rotten", "rtAudience": 65, "rtAudienceLabel": "Upright", "imdb": 6.2, "imdbVotes": 6622, "tmdb": 7.8, "tmdbVotes": 404}}, {"id": "movie-jennifer-s-body-2009", "title": "Jennifer's Body", "year": 2009, "type": "movie", "runtimeSeconds": 6450, "seasonCount": null, "communityRating": 6.012, "criticRating": 47, "genres": ["Horror", "Comedy"], "overview": "Jennifer, a gorgeous, seductive cheerleader takes evil to a whole new level after she's possessed by a sinister demon. Now it's up to her best friend to stop Jennifer's reign of terror before it's too late.", "poster": "assets/movie-jennifer-s-body-2009-poster.jpg", "backdrop": "assets/movie-jennifer-s-body-2009-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-30T03:37:04.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 19994, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 47, "rtCriticsLabel": "Rotten", "rtAudience": 36, "rtAudienceLabel": "Spilled", "imdb": 5.6, "imdbVotes": 179710, "tmdb": 6.013, "tmdbVotes": 4481}}, {"id": "tv-breeders-2020", "title": "Breeders", "year": 2020, "type": "tv", "runtimeSeconds": 1500, "seasonCount": 4, "communityRating": 6.8, "criticRating": null, "genres": ["Comedy"], "overview": "Exposing the parental-paradox that it is possible, in the very same moment, to love your child to the horizon of the universe, while being apoplectically angry enough to want to send them there.", "poster": "assets/tv-breeders-2020-poster.jpg", "backdrop": "assets/tv-breeders-2020-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-28T18:54:21.8885435Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 93290, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 92, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.8, "tmdbVotes": 104}}, {"id": "tv-the-four-seasons-2025", "title": "The Four Seasons", "year": 2025, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 6.879, "criticRating": null, "genres": ["Comedy"], "overview": "The decades-long friendship between three married couples is tested when one divorces, complicating their tradition of quarterly weekend getaways.", "poster": "assets/tv-the-four-seasons-2025-poster.jpg", "backdrop": "assets/tv-the-four-seasons-2025-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-28T18:36:42.5326384Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 243316, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 81, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.892, "tmdbVotes": 204}}, {"id": "tv-adults-2025", "title": "Adults", "year": 2025, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 6.495, "criticRating": null, "genres": ["Comedy"], "overview": "A group of twenty-somethings in New York trying to be good people, despite being neither \"good\" nor \"people\" yet.", "poster": "assets/tv-adults-2025-poster.jpg", "backdrop": "assets/tv-adults-2025-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-23T21:01:41.1814765Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 260458, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 88, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.5, "tmdbVotes": 102}}, {"id": "tv-east-of-eden-2026", "title": "East of Eden", "year": 2026, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.931, "criticRating": null, "genres": ["Drama", "Crime"], "overview": "In the early 1900s, a fiercely independent woman drives a rift between two brothers, igniting a saga that unfolds across two generations.", "poster": "assets/tv-east-of-eden-2026-poster.jpg", "backdrop": "assets/tv-east-of-eden-2026-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-23T18:07:11.3965096Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 258165, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 88, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.982, "tmdbVotes": 139}}, {"id": "movie-fighting-with-my-family-2019", "title": "Fighting with My Family", "year": 2019, "type": "movie", "runtimeSeconds": 6509, "seasonCount": null, "communityRating": 6.84, "criticRating": 93, "genres": ["Comedy"], "overview": "Born into a tight-knit wrestling family, Paige and her brother Zak are ecstatic when they get the once-in-a-lifetime opportunity to try out for the WWE. But when only Paige earns a spot in the competitive training\u2026", "poster": "assets/movie-fighting-with-my-family-2019-poster.jpg", "backdrop": "assets/movie-fighting-with-my-family-2019-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-22T22:10:59.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 445629, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 93, "rtCriticsLabel": "Certified Fresh", "rtAudience": 86, "rtAudienceLabel": "Upright", "imdb": 7, "imdbVotes": 94576, "tmdb": 6.837, "tmdbVotes": 1952}}, {"id": "tv-landman-2024", "title": "Landman", "year": 2024, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 8.194, "criticRating": null, "genres": ["Drama"], "overview": "Set in the proverbial boomtowns of West-Texas and a modern-day tale of fortune-seeking in the world of oil rigs, the series is an upstairs/downstairs story of roughnecks and wildcat billionaires that are fueling a\u2026", "poster": "assets/tv-landman-2024-poster.jpg", "backdrop": "assets/tv-landman-2024-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-22T21:38:18.0973875Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 157741, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 80, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.209, "tmdbVotes": 668}}, {"id": "tv-fawlty-towers-1975", "title": "Fawlty Towers", "year": 1975, "type": "tv", "runtimeSeconds": 1800, "seasonCount": 2, "communityRating": 8.295, "criticRating": null, "genres": ["Comedy"], "overview": "Owner Basil Fawlty, his wife Sybil, a chambermaid Polly, and Spanish waiter Manuel attempt to run their hotel amidst farcical situations and an array of demanding guests.", "poster": "assets/tv-fawlty-towers-1975-poster.jpg", "backdrop": "assets/tv-fawlty-towers-1975-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-19T23:26:39.7658344Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 2207, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.296, "tmdbVotes": 867}}], "discover": [{"id": "movie-12-years-a-slave-2013", "title": "12 Years a Slave", "year": 2013, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "History"], "overview": "In the pre-Civil War United States, Solomon Northup, a free black man from upstate New York, is abducted and sold into slavery. Facing cruelty as well as unexpected kindnesses Solomon struggles not only to stay\u2026", "poster": "assets/movie-12-years-a-slave-2013-poster.jpg", "backdrop": "assets/movie-12-years-a-slave-2013-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 76203, "ratings": {"rtCritics": 95, "rtCriticsLabel": "Certified Fresh", "rtAudience": 90, "rtAudienceLabel": "Upright", "imdb": 8.1, "imdbVotes": 796313, "tmdb": 7.927, "tmdbVotes": 12335}}, {"id": "movie-101-dalmatians-ii-patch-s-london-adventure-2003", "title": "101 Dalmatians II: Patch's London Adventure", "year": 2003, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Family", "Animation", "Adventure"], "overview": "Being one of 101 takes its toll on Patch, who doesn't feel unique. When he's accidentally left behind on moving day, he meets his idol, Thunderbolt, who enlists him on a publicity campaign.", "poster": "assets/movie-101-dalmatians-ii-patch-s-london-adventure-2003-poster.jpg", "backdrop": "assets/movie-101-dalmatians-ii-patch-s-london-adventure-2003-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 13654, "ratings": {"rtCritics": 67, "rtCriticsLabel": "Fresh", "rtAudience": 41, "rtAudienceLabel": "Spilled", "imdb": 5.7, "imdbVotes": 12343, "tmdb": 6.025, "tmdbVotes": 1274}}, {"id": "movie-absolution-2024", "title": "Absolution", "year": 2024, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action", "Crime", "Thriller"], "overview": "An aging ex-boxer gangster working as muscle for a Boston crime boss receives an upsetting diagnosis. Despite a faltering memory, he attempts to rectify the sins of his past and reconnect with his estranged\u2026", "poster": "assets/movie-absolution-2024-poster.jpg", "backdrop": "assets/movie-absolution-2024-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 974453, "ratings": {"rtCritics": 56, "rtCriticsLabel": "Rotten", "rtAudience": 32, "rtAudienceLabel": "Spilled", "imdb": 5.2, "imdbVotes": 11413, "tmdb": 5.906, "tmdbVotes": 564}}, {"id": "movie-addicted-2014", "title": "Addicted", "year": 2014, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Thriller"], "overview": "A gallerist risks her family and flourishing career when she enters into an affair with a talented painter and slowly loses control of her life.", "poster": "assets/movie-addicted-2014-poster.jpg", "backdrop": "assets/movie-addicted-2014-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 235271, "ratings": {"rtCritics": 7, "rtCriticsLabel": "Rotten", "rtAudience": 51, "rtAudienceLabel": "Spilled", "imdb": 5, "imdbVotes": 13131, "tmdb": 5.928, "tmdbVotes": 466}}, {"id": "movie-all-the-money-in-the-world-2017", "title": "All the Money in the World", "year": 2017, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Crime", "History", "Thriller"], "overview": "The story of the kidnapping of 16-year-old John Paul Getty III and the desperate attempt by his devoted mother to convince his billionaire grandfather Jean Paul Getty to pay the ransom.", "poster": "assets/movie-all-the-money-in-the-world-2017-poster.jpg", "backdrop": "assets/movie-all-the-money-in-the-world-2017-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 446791, "ratings": {"rtCritics": 79, "rtCriticsLabel": "Certified Fresh", "rtAudience": 66, "rtAudienceLabel": "Upright", "imdb": 6.8, "imdbVotes": 97448, "tmdb": 6.493, "tmdbVotes": 2493}}, {"id": "movie-the-alto-knights-2025", "title": "The Alto Knights", "year": 2025, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Crime", "Drama"], "overview": "Two of New York's most notorious organized crime bosses, Frank Costello and Vito Genovese, vie for control of the city's streets. Once the best of friends, petty jealousies and a series of betrayals place them on a\u2026", "poster": "assets/movie-the-alto-knights-2025-poster.jpg", "backdrop": "assets/movie-the-alto-knights-2025-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 1013601, "ratings": {"rtCritics": 39, "rtCriticsLabel": "Rotten", "rtAudience": 71, "rtAudienceLabel": "Upright", "imdb": 5.9, "imdbVotes": 23017, "tmdb": 6.223, "tmdbVotes": 485}}, {"id": "movie-the-amazing-spider-man-2-2014", "title": "The Amazing Spider-Man 2", "year": 2014, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action", "Adventure", "Science Fiction"], "overview": "For Peter Parker, life is busy. Between taking out the bad guys as Spider-Man and spending time with the person he loves, Gwen Stacy, high school graduation cannot come quickly enough. Peter has not forgotten about\u2026", "poster": "assets/movie-the-amazing-spider-man-2-2014-poster.jpg", "backdrop": "assets/movie-the-amazing-spider-man-2-2014-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 102382, "ratings": {"rtCritics": 51, "rtCriticsLabel": "Rotten", "rtAudience": 64, "rtAudienceLabel": "Upright", "imdb": 6.6, "imdbVotes": 611981, "tmdb": 6.567, "tmdbVotes": 14919}}, {"id": "movie-ant-man-and-the-wasp-quantumania-2023", "title": "Ant-Man and the Wasp: Quantumania", "year": 2023, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action", "Adventure", "Science Fiction"], "overview": "Super-Hero partners Scott Lang and Hope van Dyne, along with with Hope's parents Janet van Dyne and Hank Pym, and Scott's daughter Cassie Lang, find themselves exploring the Quantum Realm, interacting with strange\u2026", "poster": "assets/movie-ant-man-and-the-wasp-quantumania-2023-poster.jpg", "backdrop": "assets/movie-ant-man-and-the-wasp-quantumania-2023-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 640146, "ratings": {"rtCritics": 46, "rtCriticsLabel": "Rotten", "rtAudience": 81, "rtAudienceLabel": "Upright", "imdb": 6, "imdbVotes": 281738, "tmdb": 6.197, "tmdbVotes": 6236}}, {"id": "movie-arctic-dogs-2019", "title": "Arctic Dogs", "year": 2019, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Animation", "Adventure", "Comedy"], "overview": "Animals band together to save the day when the evil Otto Von Walrus hatches a sinister scheme to accelerate global warming and melt the Arctic Circle.", "poster": "assets/movie-arctic-dogs-2019-poster.jpg", "backdrop": "assets/movie-arctic-dogs-2019-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 416153, "ratings": {"rtCritics": 13, "rtCriticsLabel": "Rotten", "rtAudience": 60, "rtAudienceLabel": "Upright", "imdb": 4.9, "imdbVotes": 2580, "tmdb": 5.9, "tmdbVotes": 148}}, {"id": "movie-the-avengers-2012", "title": "The Avengers", "year": 2012, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Science Fiction", "Action", "Adventure"], "overview": "When an unexpected enemy emerges and threatens global safety and security, Nick Fury, director of the international peacekeeping agency known as S.H.I.E.L.D., finds himself in need of a team to pull the world back\u2026", "poster": "assets/movie-the-avengers-2012-poster.jpg", "backdrop": "assets/movie-the-avengers-2012-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 24428, "ratings": {"rtCritics": 91, "rtCriticsLabel": "Certified Fresh", "rtAudience": 91, "rtAudienceLabel": "Upright", "imdb": 8, "imdbVotes": 1590693, "tmdb": 8.084, "tmdbVotes": 40262}}, {"id": "movie-black-bear-2020", "title": "Black Bear", "year": 2020, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Thriller"], "overview": "At a remote lake house in the Adirondack Mountains, a couple entertains an out-of-town guest looking for inspiration in her filmmaking. The group quickly falls into a calculated game of desire, manipulation, and\u2026", "poster": "assets/movie-black-bear-2020-poster.jpg", "backdrop": "assets/movie-black-bear-2020-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 624788, "ratings": {"rtCritics": 89, "rtCriticsLabel": "Certified Fresh", "rtAudience": 64, "rtAudienceLabel": "Upright", "imdb": 6.5, "imdbVotes": 18408, "tmdb": 6.337, "tmdbVotes": 315}}, {"id": "movie-black-panther-2018", "title": "Black Panther", "year": 2018, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action", "Adventure", "Science Fiction"], "overview": "King T'Challa returns home to the reclusive, technologically advanced African nation of Wakanda to serve as his country's new leader. However, T'Challa soon finds that he is challenged for the throne by factions\u2026", "poster": "assets/movie-black-panther-2018-poster.jpg", "backdrop": "assets/movie-black-panther-2018-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 284054, "ratings": {"rtCritics": 96, "rtCriticsLabel": "Certified Fresh", "rtAudience": null, "rtAudienceLabel": "Spilled", "imdb": 7.3, "imdbVotes": 931120, "tmdb": 7.36, "tmdbVotes": 24190}}, {"id": "movie-a-bronx-tale-1993", "title": "A Bronx Tale", "year": 1993, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Crime"], "overview": "Set in the Bronx during the tumultuous 1960s, an adolescent boy is torn between his honest, working-class father and a violent yet charismatic crime boss. Complicating matters is the youngster's growing attraction -\u2026", "poster": "assets/movie-a-bronx-tale-1993-poster.jpg", "backdrop": "assets/movie-a-bronx-tale-1993-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 1607, "ratings": {"rtCritics": 97, "rtCriticsLabel": "Fresh", "rtAudience": 92, "rtAudienceLabel": "Upright", "imdb": 7.8, "imdbVotes": 191930, "tmdb": 7.913, "tmdbVotes": 3075}}, {"id": "movie-by-any-means-2026", "title": "By Any Means", "year": 2026, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Thriller", "History", "Crime"], "overview": "In 1960s Mississippi, a young Black FBI agent is forced to partner with a notorious mafia hitman to investigate a wave of brutal civil rights murders.", "poster": "assets/movie-by-any-means-2026-poster.jpg", "backdrop": "assets/movie-by-any-means-2026-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 1380417, "ratings": {"rtCritics": 49, "rtCriticsLabel": "Rotten", "rtAudience": 88, "rtAudienceLabel": "Upright", "imdb": 6.8, "imdbVotes": 3534, "tmdb": 6.6, "tmdbVotes": 92}}, {"id": "movie-captain-america-civil-war-2016", "title": "Captain America: Civil War", "year": 2016, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Adventure", "Action", "Science Fiction"], "overview": "Following the events of Age of Ultron, the collective governments of the world pass an act designed to regulate all superhuman activity. This polarizes opinion amongst the Avengers, causing two factions to side with\u2026", "poster": "assets/movie-captain-america-civil-war-2016-poster.jpg", "backdrop": "assets/movie-captain-america-civil-war-2016-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 271110, "ratings": {"rtCritics": 90, "rtCriticsLabel": "Certified Fresh", "rtAudience": 89, "rtAudienceLabel": "Upright", "imdb": 7.8, "imdbVotes": 954943, "tmdb": 7.455, "tmdbVotes": 24808}}, {"id": "movie-captain-america-the-first-avenger-2011", "title": "Captain America: The First Avenger", "year": 2011, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action", "Adventure", "Science Fiction"], "overview": "During World War II, Steve Rogers is a sickly man from Brooklyn who's transformed into super-soldier Captain America to aid in the war effort. Rogers must stop the Red Skull \u2013 Adolf Hitler's ruthless head of\u2026", "poster": "assets/movie-captain-america-the-first-avenger-2011-poster.jpg", "backdrop": "assets/movie-captain-america-the-first-avenger-2011-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 1771, "ratings": {"rtCritics": 80, "rtCriticsLabel": "Certified Fresh", "rtAudience": 75, "rtAudienceLabel": "Upright", "imdb": 6.9, "imdbVotes": 993453, "tmdb": 7.012, "tmdbVotes": 23473}}, {"id": "movie-captain-america-the-winter-soldier-2014", "title": "Captain America: The Winter Soldier", "year": 2014, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action", "Adventure", "Science Fiction"], "overview": "After the cataclysmic events in New York with The Avengers, Steve Rogers, aka Captain America is living quietly in Washington, D.C. and trying to adjust to the modern world. But when a S.H.I.E.L.D. colleague comes\u2026", "poster": "assets/movie-captain-america-the-winter-soldier-2014-poster.jpg", "backdrop": "assets/movie-captain-america-the-winter-soldier-2014-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 100402, "ratings": {"rtCritics": 90, "rtCriticsLabel": "Certified Fresh", "rtAudience": 92, "rtAudienceLabel": "Upright", "imdb": 7.7, "imdbVotes": 995514, "tmdb": 7.654, "tmdbVotes": 20688}}, {"id": "movie-careful-what-you-wish-for-2015", "title": "Careful What You Wish For", "year": 2015, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Thriller"], "overview": "A guy gets more than he bargained for after entering into an affair with the wife of an investment banker. Soon, a suspicious death and substantial life insurance policy embroil him in a scandal.", "poster": "assets/movie-careful-what-you-wish-for-2015-poster.jpg", "backdrop": "assets/movie-careful-what-you-wish-for-2015-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 317744, "ratings": {"rtCritics": 17, "rtCriticsLabel": "Rotten", "rtAudience": 61, "rtAudienceLabel": "Upright", "imdb": 5.6, "imdbVotes": 9452, "tmdb": 6.129, "tmdbVotes": 446}}, {"id": "tv-12-monkeys-2015", "title": "12 Monkeys", "year": 2015, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Sci-Fi & Fantasy", "Drama"], "overview": "The provocative story of Cole, a time traveler from a decimated future in a high-stakes race against the clock. Utilizing a dangerous and untested method of time travel, he journeys from 2043 to the present day on a\u2026", "poster": "assets/tv-12-monkeys-2015-poster.jpg", "backdrop": "assets/tv-12-monkeys-2015-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 60948, "ratings": {"rtCritics": 88, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.441, "tmdbVotes": 1245}}, {"id": "tv-90-day-fiance-before-the-90-days-2017", "title": "90 Day Fianc\u00e9: Before the 90 Days", "year": 2017, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Reality"], "overview": "Follow couples who have an existing relationship online, but haven't met in person. We'll experience their journey as they travel to the other's foreign country for the first time in an attempt to establish an\u2026", "poster": "assets/tv-90-day-fiance-before-the-90-days-2017-poster.jpg", "backdrop": "assets/tv-90-day-fiance-before-the-90-days-2017-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 73319, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.5, "tmdbVotes": 45}}, {"id": "tv-90-day-fiance-the-other-way-2019", "title": "90 Day Fianc\u00e9: The Other Way", "year": 2019, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Reality"], "overview": "Americans move to a foreign country all in the name of love. Will uprooting their lives in the United States pay off, or will they be packing up and moving back to America brokenhearted?", "poster": "assets/tv-90-day-fiance-the-other-way-2019-poster.jpg", "backdrop": "assets/tv-90-day-fiance-the-other-way-2019-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 90046, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.8, "tmdbVotes": 34}}, {"id": "tv-the-100-2014", "title": "The 100", "year": 2014, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Sci-Fi & Fantasy", "Action & Adventure", "Drama"], "overview": "100 years in the future, when the Earth has been abandoned due to radioactivity, the last surviving humans live on an ark orbiting the planet \u2014 but the ark won't last forever. So the repressive regime picks 100\u2026", "poster": "assets/tv-the-100-2014-poster.jpg", "backdrop": "assets/tv-the-100-2014-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 48866, "ratings": {"rtCritics": 93, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.878, "tmdbVotes": 8855}}, {"id": "tv-accused-2023", "title": "Accused", "year": 2023, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Crime", "Drama"], "overview": "In this crime anthology series, viewers discover how an ordinary person got caught up in an extraordinary situation, ultimately revealing how one wrong turn leads to another, until it\u2019s too late to turn back. Told\u2026", "poster": "assets/tv-accused-2023-poster.jpg", "backdrop": "assets/tv-accused-2023-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 202097, "ratings": {"rtCritics": 61, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.851, "tmdbVotes": 87}}, {"id": "tv-after-life-2019", "title": "After Life", "year": 2019, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "Tony had a perfect life. But after his wife Lisa suddenly dies, Tony changes. After contemplating taking his own life, he decides instead to live long enough to punish the world by saying and doing whatever he likes\u2026", "poster": "assets/tv-after-life-2019-poster.jpg", "backdrop": "assets/tv-after-life-2019-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 79410, "ratings": {"rtCritics": 72, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.902, "tmdbVotes": 1169}}, {"id": "tv-all-creatures-great-and-small-1978", "title": "All Creatures Great and Small", "year": 1978, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "The trials and misadventures of the staff at a country veterinary office in Yorkshire. James Herriot, a young animal surgeon, moves to a small Yorkshire town to begin his first job.", "poster": "assets/tv-all-creatures-great-and-small-1978-poster.jpg", "backdrop": "assets/tv-all-creatures-great-and-small-1978-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "center_crop", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 7406, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.8, "tmdbVotes": 50}}, {"id": "tv-american-housewife-2016", "title": "American Housewife", "year": 2016, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Comedy"], "overview": "A family comedy narrated by Katie, a strong-willed mother, raising her flawed family in a wealthy town filled with perfect wives and their perfect offspring.", "poster": "assets/tv-american-housewife-2016-poster.jpg", "backdrop": "assets/tv-american-housewife-2016-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 67117, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.7, "tmdbVotes": 187}}, {"id": "tv-atlantis-2013", "title": "Atlantis", "year": 2013, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action & Adventure", "Sci-Fi & Fantasy"], "overview": "A fantasy drama set in a world of legendary heroes and mythical creatures. Far from home and desperate for answers, Jason washes up on the shores of an ancient land. A mysterious place; a world of bull leaping, of\u2026", "poster": "assets/tv-atlantis-2013-poster.jpg", "backdrop": "assets/tv-atlantis-2013-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 47054, "ratings": {"rtCritics": 62, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.4, "tmdbVotes": 155}}, {"id": "tv-avatar-the-last-airbender-2024", "title": "Avatar: The Last Airbender", "year": 2024, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action & Adventure", "Sci-Fi & Fantasy", "Drama"], "overview": "A young boy known as the Avatar must master the four elemental powers to save a world at war \u2014 and fight a ruthless enemy bent on stopping him.", "poster": "assets/tv-avatar-the-last-airbender-2024-poster.jpg", "backdrop": "assets/tv-avatar-the-last-airbender-2024-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 82452, "ratings": {"rtCritics": 66, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.8, "tmdbVotes": 1406}}, {"id": "tv-avatar-seven-havens-2026", "title": "Avatar: Seven Havens", "year": 2026, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "In a world shattered by a devastating cataclysm, a young Earthbender discovers she's the new Avatar after Korra. But in this dangerous era, that title marks her as humanity's destroyer, not its savior. Hunted by both\u2026", "poster": "assets/tv-avatar-seven-havens-2026-poster.jpg", "backdrop": "assets/tv-avatar-seven-havens-2026-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 284833, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.9, "tmdbVotes": 183}}, {"id": "tv-batman-the-brave-and-the-bold-2008", "title": "Batman: The Brave and the Bold", "year": 2008, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "The Caped Crusader is teamed up with Blue Beetle, Green Arrow, Aquaman and countless others in his quest to uphold justice.", "poster": "assets/tv-batman-the-brave-and-the-bold-2008-poster.jpg", "backdrop": "assets/tv-batman-the-brave-and-the-bold-2008-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 15804, "ratings": {"rtCritics": 96, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.6, "tmdbVotes": 295}}, {"id": "tv-beat-bobby-flay-2013", "title": "Beat Bobby Flay", "year": 2013, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Reality"], "overview": "Two talented chefs go head-to-head for the chance to Beat Bobby Flay. To get to Bobby the chefs must first face off against each other, creating a spectacular dish with a secret ingredient of Bobby's choice. Judges\u2026", "poster": "assets/tv-beat-bobby-flay-2013-poster.jpg", "backdrop": "assets/tv-beat-bobby-flay-2013-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 62481, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.5, "tmdbVotes": 31}}, {"id": "tv-benson-1979", "title": "Benson", "year": 1979, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Comedy"], "overview": "A butler deals with life at the governor's mansion.", "poster": "assets/tv-benson-1979-poster.jpg", "backdrop": "assets/tv-benson-1979-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 1849, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7, "tmdbVotes": 46}}, {"id": "tv-black-rabbit-2025", "title": "Black Rabbit", "year": 2025, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Crime"], "overview": "A rising-star restaurateur is forced into New York's criminal underworld when his chaotic brother returns to town with loan sharks on his trail.", "poster": "assets/tv-black-rabbit-2025-poster.jpg", "backdrop": "assets/tv-black-rabbit-2025-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 249039, "ratings": {"rtCritics": 66, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.983, "tmdbVotes": 240}}, {"id": "tv-black-ish-2014", "title": "black-ish", "year": 2014, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Comedy"], "overview": "A family man struggles to gain a sense of cultural identity while raising his kids in a predominantly white, upper-middle-class neighborhood.", "poster": "assets/tv-black-ish-2014-poster.jpg", "backdrop": "assets/tv-black-ish-2014-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 61381, "ratings": {"rtCritics": 92, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.1, "tmdbVotes": 352}}, {"id": "tv-breaking-bad-2008", "title": "Breaking Bad", "year": 2008, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Crime"], "overview": "Walter White, a New Mexico chemistry teacher, is diagnosed with Stage III cancer and given a prognosis of only two years left to live. He becomes filled with a sense of fearlessness and an unrelenting desire to\u2026", "poster": "assets/tv-breaking-bad-2008-poster.jpg", "backdrop": "assets/tv-breaking-bad-2008-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "center_crop", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 1396, "ratings": {"rtCritics": 96, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.959, "tmdbVotes": 19030}}, {"id": "tv-the-capture-2019", "title": "The Capture", "year": 2019, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Crime", "Drama", "Mystery"], "overview": "When soldier Shaun Emery's conviction for a murder in Afghanistan is overturned due to flawed video evidence, he returns to life as a free man with his young daughter. But when damning CCTV footage from a night out\u2026", "poster": "assets/tv-the-capture-2019-poster.jpg", "backdrop": "assets/tv-the-capture-2019-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 93166, "ratings": {"rtCritics": 97, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.634, "tmdbVotes": 361}}], "discoverySource": {"state": "materialized_snapshot", "rankKnown": false, "selection": "First 18 alphabetically indexed movie stubs and first 18 series stubs, not ranked feed order", "observedAt": "2026-11-04T20:42:12.030108+00:00"}}], "notes": {"real": ["Profile keys and library membership derive from deployed Conductarr configuration and viewer-scoped Jellyfin GET responses.", "Names, years, overviews, library ratings, genres, season-container counts and watch/resume data come from actual Jellyfin metadata.", "All local artwork is copied from Jellyfin; discovery posters are the real materialized Conductarr recommendation artwork.", "Discover contains 36 real materialized personal recommendations per profile, with profile-scoped availability from read-only retained membership/request state.", "Justin has no playable resume items. Mindel and Shared each have one. No progress or watched flags were invented."], "approximations": ["Default profile initials and display capitalization are frontend presentation choices; colours reuse the existing PWA palette, not live user appearance configuration.", "Conductarr catalogue and recommendation GET endpoints both returned 401. Authentication was not bypassed. SQLite contains no retained recommendation feed/candidate snapshot tables; the source keeps ranked feeds in memory. Discovery therefore reads existing per-profile Jellyfin recommendation stubs as metadata only.", "Discovery selection is 18 alphabetically indexed movie suggestions plus 18 series suggestions, not personal rank order; rank is null. Materialization age and original feed ordering are unknown.", "Discovery stub runtime is synthetic (two seconds), so runtime, season count, rating and watch state are null rather than presenting stub values as real media facts.", "93 selected identities lack original landscape art in the materialized metadata; their backdrop is a marked crop of the real poster, not an original film backdrop. Existing artwork is reused; new artwork is centre-cropped to the required dimensions.", "Jellyfin server-side resizing requests maxWidth/maxHeight/quality. When the result had the wrong aspect ratio, sequential single-thread ffmpeg center crops produce exact 300x450 and 1280x720 JPEGs.", "Series seasonCount is Jellyfin ChildCount, including season containers for specials when present.", "Discover availability is a retained profile-membership/request snapshot, not a fresh playback or acquisition guarantee. not_here means absent from that profile in retained state, not absent from the entire household."], "missing": ["Justin Movies has seven live viewer-scoped films, not 200. No titles are invented or borrowed across profile boundaries.", "Discovery community/critic ratings, reliable runtime/season counts, personal feed ranking/freshness and 93 original landscape backdrops are unavailable through the permitted read paths."], "unmetRequirements": ["At least 200 real profile-scoped Justin Movies"], "mustNotBeShown": ["Recs Movies", "Recs Shows", "Justin Movies", "Justin Shows", "Mindel Movies", "Mindel Shows", "Shared Movies", "Shared Shows", "DGiC", "Stored names padded with spaces", "Internal bucket and acquisition-instance names"], "privacy": ["Includes actual watched and resume history for Justin, Mindel and Shared independently; Shared is not a union.", "No Jellyfin item/user IDs, source paths, service URLs, credentials, request actor identities, or private hotlinks are included. tmdbId is a public external title identity.", "Only the seven configured playable libraries are selected. Extra observed DGiC views and all recommendation stubs are excluded from playable collections.", "No profile is assumed to be a child or age-restricted. Each playable list and episode shelf is limited to that profile\u2019s configured libraries."], "sourceReadPaths": ["GET /Users (allowlisted mapping only)", "GET /Users/{userId}/Views", "GET /Users/{userId}/Items (parent-scoped playable libraries and metadata-only recommendation libraries)", "GET /Users/{userId}/Items/Resume", "GET /Shows/NextUp?UserId=...", "GET /Items/{itemId}/Images/Primary", "GET /Items/{itemId}/Images/Backdrop/0", "Conductarr GET /catalogue/discover?page=1&media_type=movie (401)", "Conductarr GET /api/v1/recommendations/profiles/justin/feed (401)", "SQLite file URI mode=ro plus PRAGMA query_only=ON: catalogue_metadata, bucket_memberships, native_requests; sqlite_master for schema inspection"], "productionMutations": 0, "artwork": {"files": 632, "bytes": 35201423, "posterDimensions": [300, 450], "backdropDimensions": [1280, 720], "posterQualityRequested": 70, "backdropQualityRequested": 65, "posterDerivedBackdropCount": 93}, "consumerContract": ["profiles is an array in Justin/Mindel/Shared order. Every profile owns movies/shows/anime/resume/nextUp/recentlyAdded/discover item arrays; empty shelves are intentional.", "Item paths are relative to fixture.json. Resolve assets against the fixture directory, not against the outer prototype document.", "Use destinations for visible navigation. No raw library display/stored names belong in UI.", "Deduplicate library/resume/discover by stable local id or (type,tmdbId). Never treat a materialized recommendation as a playable library item.", "played=null and watchStateKnown=false mean unknown, not unwatched. Null ratings are missing, not zero; communityRating source is Jellyfin, not an asserted TMDB/IMDb score.", "availability values are in_library/requested/not_here. No requested examples were observed in the selected discover set; do not fabricate them.", "Render artwork.backdropKind=poster_crop as a poster-derived landscape; prefer the poster over pretending it is original landscape art.", "This static fixture preserves actual current collection sizes, viewer-scoped watch state and unknown discovery ranking; request and playback demonstrations must remain labelled simulations."]}} \ No newline at end of file diff --git a/app/src/debug/assets/wolfskin-requests.json b/app/src/debug/assets/wolfskin-requests.json new file mode 100644 index 00000000..507ebd65 --- /dev/null +++ b/app/src/debug/assets/wolfskin-requests.json @@ -0,0 +1,217 @@ +{ + "source": "Locked Opus fixture/fixture.json; index.html EXAMPLES() pool order", + "items": [ + { + "id": "x1", + "mediaType": "tv", + "tmdbId": 11821, + "title": "The Abbott and Costello Show", + "requesterKey": "justin", + "requesterName": "Justin", + "actorKey": "justin", + "actorName": "Justin", + "stage": "Downloading", + "poster": "tv-the-abbott-and-costello-show-1952-poster.jpg", + "ratings": [ + { + "mark": "tmdb", + "score": 7.2, + "percentage": false + } + ] + }, + { + "id": "x2", + "mediaType": "movie", + "tmdbId": 13483, + "title": "Awake", + "requesterKey": "mindel", + "requesterName": "Mindel", + "actorKey": "justin", + "actorName": "Justin", + "stage": "AlmostReady", + "poster": "movie-awake-2007-poster.jpg", + "ratings": [ + { + "mark": "rotten", + "score": 23, + "percentage": true + }, + { + "mark": "spilled", + "score": 55, + "percentage": true + }, + { + "mark": "imdb", + "score": 6.5, + "percentage": false + } + ] + }, + { + "id": "x3", + "mediaType": "tv", + "tmdbId": 70785, + "title": "Anne with an E", + "requesterKey": "shared", + "requesterName": "Shared", + "actorKey": "mindel", + "actorName": "Mindel", + "stage": "Searching", + "poster": "tv-anne-with-an-e-2017-poster.jpg", + "ratings": [ + { + "mark": "fresh", + "score": 63, + "percentage": true + } + ] + }, + { + "id": "x4", + "mediaType": "movie", + "tmdbId": 99861, + "title": "Avengers: Age of Ultron", + "requesterKey": "justin", + "requesterName": "Justin", + "actorKey": "justin", + "actorName": "Justin", + "stage": "Waiting", + "poster": "movie-avengers-age-of-ultron-2015-poster.jpg", + "ratings": [ + { + "mark": "fresh", + "score": 75, + "percentage": true + }, + { + "mark": "upright", + "score": 82, + "percentage": true + }, + { + "mark": "imdb", + "score": 7.3, + "percentage": false + } + ] + }, + { + "id": "x5", + "mediaType": "movie", + "tmdbId": 1033148, + "title": "Die My Love", + "requesterKey": "mindel", + "requesterName": "Mindel", + "actorKey": "mindel", + "actorName": "Mindel", + "stage": "Failed", + "poster": "movie-die-my-love-2025-poster.jpg", + "ratings": [ + { + "mark": "fresh", + "score": 74, + "percentage": true + }, + { + "mark": "spilled", + "score": 45, + "percentage": true + }, + { + "mark": "imdb", + "score": 6, + "percentage": false + } + ] + }, + { + "id": "x6", + "mediaType": "movie", + "tmdbId": 416153, + "title": "Arctic Dogs", + "requesterKey": "shared", + "requesterName": "Shared", + "actorKey": "justin", + "actorName": "Justin", + "stage": "Ready", + "poster": "movie-arctic-dogs-2019-poster.jpg", + "ratings": [ + { + "mark": "rotten", + "score": 13, + "percentage": true + }, + { + "mark": "upright", + "score": 60, + "percentage": true + }, + { + "mark": "imdb", + "score": 4.9, + "percentage": false + } + ] + }, + { + "id": "x7", + "mediaType": "movie", + "tmdbId": 76203, + "title": "12 Years a Slave", + "requesterKey": "justin", + "requesterName": "Justin", + "actorKey": "shared", + "actorName": "Shared", + "stage": "Waiting", + "poster": "movie-12-years-a-slave-2013-poster.jpg", + "ratings": [ + { + "mark": "fresh", + "score": 95, + "percentage": true + }, + { + "mark": "upright", + "score": 90, + "percentage": true + }, + { + "mark": "imdb", + "score": 8.1, + "percentage": false + } + ] + }, + { + "id": "x8", + "mediaType": "movie", + "tmdbId": 14069, + "title": "The Girl Who Leapt Through Time", + "requesterKey": "mindel", + "requesterName": "Mindel", + "actorKey": "justin", + "actorName": "Justin", + "stage": "Waiting", + "poster": "movie-the-girl-who-leapt-through-time-2006-poster.jpg", + "ratings": [ + { + "mark": "fresh", + "score": 87, + "percentage": true + }, + { + "mark": "upright", + "score": 90, + "percentage": true + }, + { + "mark": "imdb", + "score": 7.6, + "percentage": false + } + ] + } + ] +} diff --git a/app/src/debug/assets/wolfskin-requests/movie-12-years-a-slave-2013-poster.jpg b/app/src/debug/assets/wolfskin-requests/movie-12-years-a-slave-2013-poster.jpg new file mode 100644 index 00000000..ff1f55e6 Binary files /dev/null and b/app/src/debug/assets/wolfskin-requests/movie-12-years-a-slave-2013-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-requests/movie-arctic-dogs-2019-poster.jpg b/app/src/debug/assets/wolfskin-requests/movie-arctic-dogs-2019-poster.jpg new file mode 100644 index 00000000..07552443 Binary files /dev/null and b/app/src/debug/assets/wolfskin-requests/movie-arctic-dogs-2019-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-requests/movie-avengers-age-of-ultron-2015-poster.jpg b/app/src/debug/assets/wolfskin-requests/movie-avengers-age-of-ultron-2015-poster.jpg new file mode 100644 index 00000000..300840ff Binary files /dev/null and b/app/src/debug/assets/wolfskin-requests/movie-avengers-age-of-ultron-2015-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-requests/movie-awake-2007-poster.jpg b/app/src/debug/assets/wolfskin-requests/movie-awake-2007-poster.jpg new file mode 100644 index 00000000..32595897 Binary files /dev/null and b/app/src/debug/assets/wolfskin-requests/movie-awake-2007-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-requests/movie-die-my-love-2025-poster.jpg b/app/src/debug/assets/wolfskin-requests/movie-die-my-love-2025-poster.jpg new file mode 100644 index 00000000..f6eca818 Binary files /dev/null and b/app/src/debug/assets/wolfskin-requests/movie-die-my-love-2025-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-requests/movie-the-girl-who-leapt-through-time-2006-poster.jpg b/app/src/debug/assets/wolfskin-requests/movie-the-girl-who-leapt-through-time-2006-poster.jpg new file mode 100644 index 00000000..ded6cd2a Binary files /dev/null and b/app/src/debug/assets/wolfskin-requests/movie-the-girl-who-leapt-through-time-2006-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-requests/tv-anne-with-an-e-2017-poster.jpg b/app/src/debug/assets/wolfskin-requests/tv-anne-with-an-e-2017-poster.jpg new file mode 100644 index 00000000..55d443b4 Binary files /dev/null and b/app/src/debug/assets/wolfskin-requests/tv-anne-with-an-e-2017-poster.jpg differ diff --git a/app/src/debug/assets/wolfskin-requests/tv-the-abbott-and-costello-show-1952-poster.jpg b/app/src/debug/assets/wolfskin-requests/tv-the-abbott-and-costello-show-1952-poster.jpg new file mode 100644 index 00000000..7d6768ea Binary files /dev/null and b/app/src/debug/assets/wolfskin-requests/tv-the-abbott-and-costello-show-1952-poster.jpg differ diff --git a/app/src/test/java/org/justinvisser/wolfskin/tv/DiscoverLoadingFocusTest.kt b/app/src/test/java/org/justinvisser/wolfskin/tv/DiscoverLoadingFocusTest.kt new file mode 100644 index 00000000..b18f6a22 --- /dev/null +++ b/app/src/test/java/org/justinvisser/wolfskin/tv/DiscoverLoadingFocusTest.kt @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-2.0-only +package org.justinvisser.wolfskin.tv + +import android.app.Application +import androidx.activity.ComponentActivity +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.performKeyInput +import androidx.compose.ui.test.pressKey +import kotlinx.coroutines.CompletableDeferred +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertTrue +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.justinvisser.wolfskin.design.DiscoverFixture +import org.justinvisser.wolfskin.design.DiscoverScreen +import org.justinvisser.wolfskin.design.DiscoverState +import org.justinvisser.wolfskin.design.DiscoverStatus +import org.justinvisser.wolfskin.design.ShellPerson +import org.justinvisser.wolfskin.design.ShellState +import org.justinvisser.wolfskin.design.ShellSurface +import org.justinvisser.wolfskin.design.WolfskinTheme +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import org.robolectric.annotation.GraphicsMode + +/** The real asynchronous host must return focus from its loading nav to the first loaded action. */ +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [34], application = Application::class, qualifiers = "w960dp-h540dp-xhdpi") +@GraphicsMode(GraphicsMode.Mode.NATIVE) +class DiscoverLoadingFocusTest { + @get:Rule val rule = createAndroidComposeRule() + + @Test fun firstOkAfterLoadingOpensTheHeroInsteadOfSelectingTheNav() { + val pending = CompletableDeferred() + val shell = ShellState(listOf(ShellPerson("justin", "Justin", "J", true)), "justin").apply { + pickerVisible = false + enterDestination("discover") + } + lateinit var current: DiscoverState + rule.setContent { + val state = rememberDiscoverState(shell, shell::enterDestination, { shell.enterDestination("discover") }, loadFixture = { pending.await() }) + current = state + WolfskinTheme { ShellSurface(shell) { DiscoverScreen(state, it) } } + } + rule.runOnIdle { + assertEquals(DiscoverStatus.Loading, current.snapshot.status) + assertTrue(shell.navFocused) + val json = rule.activity.assets.open("wolfskin-discover/fixture.json").bufferedReader().use { it.readText() } + pending.complete(DiscoverFixture(json) { "file:///android_asset/wolfskin-discover/$it" }) + } + rule.waitForIdle() + rule.runOnIdle { + assertEquals(DiscoverStatus.Ready, current.snapshot.status) + assertFalse(shell.navFocused) + assertEquals(0, current.focusedRow) + } + rule.onNodeWithTag("wolfskin-shell").performKeyInput { pressKey(Key.DirectionCenter) } + rule.runOnIdle { + assertNotNull(current.title) + assertEquals("The Amazing Spider-Man", current.title!!.title) + assertEquals("discover-title/${current.title!!.key}", shell.route) + } + } + + @Test fun finishingLoadPreservesANavTargetChosenWhileLoading() { + val pending = CompletableDeferred() + val shell = ShellState(listOf(ShellPerson("justin", "Justin", "J", true)), "justin").apply { + pickerVisible = false + enterDestination("discover") + } + lateinit var current: DiscoverState + rule.setContent { + val state = rememberDiscoverState(shell, shell::enterDestination, { shell.enterDestination("discover") }, loadFixture = { pending.await() }) + current = state + WolfskinTheme { ShellSurface(shell) { DiscoverScreen(state, it) } } + } + rule.onNodeWithTag("wolfskin-shell").performKeyInput { pressKey(Key.DirectionRight) } + rule.runOnIdle { + assertEquals(DiscoverStatus.Loading, current.snapshot.status) + assertEquals(2, shell.navIndex) + val json = rule.activity.assets.open("wolfskin-discover/fixture.json").bufferedReader().use { it.readText() } + pending.complete(DiscoverFixture(json) { "file:///android_asset/wolfskin-discover/$it" }) + } + rule.waitForIdle() + rule.runOnIdle { + assertEquals(DiscoverStatus.Ready, current.snapshot.status) + assertTrue(shell.navFocused) + assertEquals(2, shell.navIndex) + } + rule.onNodeWithTag("wolfskin-shell").performKeyInput { pressKey(Key.DirectionCenter) } + rule.runOnIdle { + assertEquals("movies", shell.route) + assertEquals(null, current.title) + } + } +} diff --git a/app/src/test/java/org/justinvisser/wolfskin/tv/RequestsRefreshTest.kt b/app/src/test/java/org/justinvisser/wolfskin/tv/RequestsRefreshTest.kt new file mode 100644 index 00000000..a90bb4b1 --- /dev/null +++ b/app/src/test/java/org/justinvisser/wolfskin/tv/RequestsRefreshTest.kt @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-only +package org.justinvisser.wolfskin.tv + +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.advanceTimeBy +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertEquals +import org.junit.Test +import org.justinvisser.wolfskin.design.ComingInRequest +import org.justinvisser.wolfskin.design.ComingInSnapshot +import org.justinvisser.wolfskin.design.ComingInStage +import org.justinvisser.wolfskin.design.ComingInStatus +import org.justinvisser.wolfskin.design.ShellPerson +import org.justinvisser.wolfskin.design.ShellState + +/** The fake clock checks refresh boundaries and cancellation without a server or a real wait. */ +@OptIn(ExperimentalCoroutinesApi::class) +class RequestsRefreshTest { + @Test fun foregroundOpenUnsettledSettledAndStoppedTransitions() = runTest { + val state = ShellState(listOf(ShellPerson("justin", "Justin", "J", false)), "justin") + val request = ComingInRequest("one", "movie", 1, "One", "justin", "Justin", "shared", "Shared", ComingInStage.Waiting) + state.requests.update(ComingInSnapshot(status = ComingInStatus.Ready, signedIn = true)) + var reads = 0 + val active = launch { refreshRequestsWhileStarted(state) { reads++ } } + runCurrent() + assertEquals(1, reads) + advanceTimeBy(30_000); runCurrent() + assertEquals("Closed and settled does not poll", 1, reads) + state.openRequests() + advanceTimeBy(29_999); runCurrent() + assertEquals(1, reads) + advanceTimeBy(1); runCurrent() + assertEquals("An open empty sheet still refreshes at 30 seconds", 2, reads) + state.closeRequests() + state.requests.update(ComingInSnapshot(listOf(request), ComingInStatus.Ready, signedIn = true)) + advanceTimeBy(30_000); runCurrent() + assertEquals("An unsettled request refreshes after the sheet closes", 3, reads) + state.requests.update(ComingInSnapshot(listOf(request.copy(stage = ComingInStage.Ready)), ComingInStatus.Ready, signedIn = true)) + advanceTimeBy(30_000); runCurrent() + assertEquals("Ready requests stop polling", 3, reads) + active.cancel(); runCurrent() + state.openRequests() + advanceTimeBy(60_000); runCurrent() + assertEquals("A stopped lifecycle cannot poll", 3, reads) + val resumed = launch { refreshRequestsWhileStarted(state) { reads++ } } + runCurrent() + assertEquals("Returning to the foreground refreshes immediately", 4, reads) + resumed.cancel() + } +} diff --git a/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/ConductarrDiscoverOperations.kt b/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/ConductarrDiscoverOperations.kt new file mode 100644 index 00000000..0c8a7067 --- /dev/null +++ b/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/ConductarrDiscoverOperations.kt @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0-only +package org.justinvisser.wolfskin.tv + +import kotlinx.coroutines.flow.last +import org.justinvisser.wolfskin.conductarr.ClientError +import org.justinvisser.wolfskin.conductarr.ConductarrClient +import org.justinvisser.wolfskin.conductarr.MediaType +import org.justinvisser.wolfskin.conductarr.MutationResult +import org.justinvisser.wolfskin.conductarr.ReadResult +import org.justinvisser.wolfskin.conductarr.TvRoutes +import org.justinvisser.wolfskin.conductarr.model.RecommendationHideInputBody +import org.justinvisser.wolfskin.conductarr.model.RequestIntentBody +import org.justinvisser.wolfskin.conductarr.model.RequestReceipt +import org.justinvisser.wolfskin.design.DiscoverOperations +import org.justinvisser.wolfskin.design.DiscoverRequest +import org.justinvisser.wolfskin.design.DiscoverRequestResult +import org.justinvisser.wolfskin.design.DiscoverSnapshot +import org.justinvisser.wolfskin.design.DiscoverTitle +import org.justinvisser.wolfskin.design.RequestPhase + +/** An injected signed-in client is the only real mutation path; the fixture host never constructs one. */ +class ConductarrDiscoverOperations( + private val client: ConductarrClient, + private val refreshSnapshot: suspend () -> DiscoverSnapshot, +) : DiscoverOperations { + override suspend fun request(intent: DiscoverRequest): DiscoverRequestResult { + val title = intent.title + require(title.type != "tv" || !title.seasons.isNullOrEmpty()) { "Whole-series season metadata is required" } + val body = RequestIntentBody( + mediaType = if (title.type == "movie") RequestIntentBody.MediaType.MOVIE else RequestIntentBody.MediaType.TV, + recommendationProfile = intent.profile.takeIf { intent.fromRecommendation }, requester = intent.requester, + seasons = title.seasons?.map(Int::toLong), + source = if (intent.fromRecommendation) RequestIntentBody.Source.RECOMMENDATION else RequestIntentBody.Source.TITLE, + tmdbId = title.tmdb, + ) + return when (val result = client.mutate(TvRoutes.request(body))) { + is MutationResult.Success -> result.value.outcome() + is MutationResult.Failure -> DiscoverRequestResult(if (result.error == ClientError.Http(403)) RequestPhase.Refused else RequestPhase.NotConfirmed) + } + } + + override suspend fun check(intent: DiscoverRequest): DiscoverRequestResult { + return when (val result = client.read(TvRoutes.requests(intent.title.tmdb, intent.title.mediaType()), org.justinvisser.wolfskin.conductarr.ReadTrigger.FORCE).last()) { + is ReadResult.Value -> result.saved.value?.requests?.firstOrNull { it.requester == intent.requester }?.outcome() ?: DiscoverRequestResult(RequestPhase.NotConfirmed) + is ReadResult.Failure -> DiscoverRequestResult(RequestPhase.NotConfirmed) + } + } + + override suspend fun hide(profile: String, title: DiscoverTitle): Boolean { + val result = client.mutate(TvRoutes.hide(profile, + RecommendationHideInputBody(if (title.type == "movie") RecommendationHideInputBody.MediaType.MOVIE else RecommendationHideInputBody.MediaType.TV, + title = title.title, tmdbId = title.tmdb, year = title.year?.toLong()))) + return result is MutationResult.Success || observedHidden(profile, title) == true + } + + override suspend fun restore(profile: String, title: DiscoverTitle): Boolean { + val result = client.mutate(TvRoutes.restore(profile, title.tmdb, title.mediaType())) + return result is MutationResult.Success || observedHidden(profile, title) == false + } + + private suspend fun observedHidden(profile: String, title: DiscoverTitle): Boolean? { + return when (val result = client.read(TvRoutes.dismissals(profile), org.justinvisser.wolfskin.conductarr.ReadTrigger.FORCE).last()) { + is ReadResult.Value -> result.saved.value?.dismissals?.any { + it.tmdbId == title.tmdb && it.mediaType.name == title.mediaType().name + } + is ReadResult.Failure -> null + } + } + override suspend fun refresh(): DiscoverSnapshot = refreshSnapshot() +} + +private fun DiscoverTitle.mediaType(): MediaType = if (type == "movie") MediaType.MOVIE else MediaType.TV +private fun RequestReceipt.outcome(): DiscoverRequestResult = DiscoverRequestResult( + phase = if (state == RequestReceipt.State.PENDING) RequestPhase.Pending else if (state == RequestReceipt.State.RETIRED) RequestPhase.Refused else RequestPhase.Receipt, + actor = actor.name, + requester = requester, +) diff --git a/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/DiscoverHost.kt b/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/DiscoverHost.kt new file mode 100644 index 00000000..b107bd97 --- /dev/null +++ b/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/DiscoverHost.kt @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0-only +package org.justinvisser.wolfskin.tv + +import android.content.Context +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.compositionLocalOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.platform.LocalContext +import com.github.damontecres.wholphin.BuildConfig +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.justinvisser.wolfskin.design.DiscoverFixture +import org.justinvisser.wolfskin.design.DiscoverOperations +import org.justinvisser.wolfskin.design.DiscoverRequest +import org.justinvisser.wolfskin.design.DiscoverRequestResult +import org.justinvisser.wolfskin.design.DiscoverSnapshot +import org.justinvisser.wolfskin.design.DiscoverState +import org.justinvisser.wolfskin.design.DiscoverTitle +import org.justinvisser.wolfskin.design.DiscoverStatus +import org.justinvisser.wolfskin.design.RequestPhase +import org.justinvisser.wolfskin.design.ShellState + +internal val LocalDiscoverState = compositionLocalOf { error("Discover requires the Wolfskin shell") } + +/** No client or endpoint is constructed before TV sign-in exists; release stays explicitly signed out. */ +@Composable +internal fun rememberDiscoverState( + shell: ShellState, + push: (String) -> Unit, + pop: () -> Unit, + loadFixture: suspend (Context) -> DiscoverFixture = { context -> withContext(Dispatchers.IO) { readDiscoverFixture(context) } }, +): DiscoverState { + val context = LocalContext.current + val scope = rememberCoroutineScope() + var state by remember(shell, shell.person.key) { mutableStateOf(DiscoverState(shell, DiscoverSnapshot(status = if (BuildConfig.DEBUG) DiscoverStatus.Loading else DiscoverStatus.NeedsSignIn), SignedOutDiscoverOperations, scope, push, pop)) } + LaunchedEffect(shell, shell.person.key) { + if (BuildConfig.DEBUG) { + val fixture = loadFixture(context) + val snapshot = fixture.snapshot(shell.person.key) + val resumeContent = shell.route == "discover" && (!shell.navFocused || shell.navIndex == shell.currentTab) + val loaded = DiscoverState(shell, snapshot, DebugDiscoverOperations(snapshot, shell.person.name), scope, push, pop, fixture::service) + state = loaded + if (resumeContent) loaded.focus(if (loaded.hasHero || !loaded.canRequest) 0 else 1) + } + } + LaunchedEffect(shell.route, state) { state.enterRoute(shell.route) } + return state +} + +private fun readDiscoverFixture(context: Context): DiscoverFixture { + val json = context.assets.open("wolfskin-discover/fixture.json").bufferedReader().use { it.readText() } + return DiscoverFixture(json) { "file:///android_asset/wolfskin-discover/$it" } +} + +/** Fixture writes are in-memory only and cannot reach a Conductarr client. */ +private class DebugDiscoverOperations(private val snapshot: DiscoverSnapshot, private val actor: String) : DiscoverOperations { + override suspend fun request(intent: DiscoverRequest) = DiscoverRequestResult(RequestPhase.Receipt, actor) + override suspend fun check(intent: DiscoverRequest) = DiscoverRequestResult(RequestPhase.NotConfirmed) + override suspend fun hide(profile: String, title: DiscoverTitle) = true + override suspend fun restore(profile: String, title: DiscoverTitle) = true + override suspend fun refresh() = snapshot +} + +private object SignedOutDiscoverOperations : DiscoverOperations { + override suspend fun request(intent: DiscoverRequest) = DiscoverRequestResult(RequestPhase.Refused) + override suspend fun check(intent: DiscoverRequest) = DiscoverRequestResult(RequestPhase.NotConfirmed) + override suspend fun hide(profile: String, title: DiscoverTitle) = false + override suspend fun restore(profile: String, title: DiscoverTitle) = false + override suspend fun refresh() = DiscoverSnapshot() +} diff --git a/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/RequestsRefresh.kt b/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/RequestsRefresh.kt new file mode 100644 index 00000000..50397a67 --- /dev/null +++ b/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/RequestsRefresh.kt @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-only +package org.justinvisser.wolfskin.tv + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LocalLifecycleOwner +import androidx.lifecycle.repeatOnLifecycle +import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import org.justinvisser.wolfskin.design.ShellState +import org.justinvisser.wolfskin.design.WolfskinTokens + +/** Foreground refreshes once; only an open sheet or an unsettled request keeps polling. */ +@Composable +internal fun RequestsRefresh(state: ShellState, refresh: () -> Unit) { + val owner = LocalLifecycleOwner.current + LaunchedEffect(state, owner) { + owner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { + refreshRequestsWhileStarted(state, refresh) + } + } +} + +/** Cancellation on leaving STARTED stops polling; reentry performs a fresh foreground read. */ +internal suspend fun refreshRequestsWhileStarted(state: ShellState, refresh: () -> Unit) { + refresh() + while (currentCoroutineContext().isActive) { + delay(WolfskinTokens.Behaviour.requestsPollSeconds.toLong() * 1000L) + if (state.requestsVisible || state.requests.snapshot.openCount > 0) refresh() + } +} diff --git a/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/ShellRequests.kt b/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/ShellRequests.kt new file mode 100644 index 00000000..a5b32953 --- /dev/null +++ b/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/ShellRequests.kt @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0-only +package org.justinvisser.wolfskin.tv + +import android.content.Context +import com.github.damontecres.wholphin.BuildConfig +import org.justinvisser.wolfskin.design.ComingInSnapshot +import org.justinvisser.wolfskin.design.ComingInStatus +import org.justinvisser.wolfskin.design.readComingInFixture + +/** Release stays unavailable until Conductarr sign-in supplies an authenticated generated client. */ +fun readShellRequests(context: Context): ComingInSnapshot { + if (!BuildConfig.DEBUG) return ComingInSnapshot() + val json = context.assets.open("wolfskin-requests.json").bufferedReader().use { it.readText() } + val items = readComingInFixture(json) { name -> + context.assets.open("wolfskin-requests/$name").use { it.readBytes() } + } + return ComingInSnapshot(items.take(6), ComingInStatus.Ready, signedIn = true) +} diff --git a/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/WolfskinApp.kt b/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/WolfskinApp.kt index 38c63214..88af5ebb 100644 --- a/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/WolfskinApp.kt +++ b/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/WolfskinApp.kt @@ -51,6 +51,8 @@ import org.justinvisser.wolfskin.design.ShellState import org.justinvisser.wolfskin.design.ShellSurface import org.justinvisser.wolfskin.design.WolfskinTheme import org.justinvisser.wolfskin.tv.read.LibraryReadFacade +import org.justinvisser.wolfskin.design.DiscoverOverlay +import org.justinvisser.wolfskin.design.DiscoverScreen /** View memory must outlive the upstream setup entry replaced by a user switch. */ @Singleton @@ -73,6 +75,7 @@ class WolfskinShellViewModel @Inject constructor( val seeAll: WolfskinSeeAllLists, ) : ViewModel() { val backdropFlow: StateFlow = backdrop.backdropFlow + private val requestsSnapshot by lazy { readShellRequests(context) } var libraryDestinations by mutableStateOf>(emptyMap()) private set var signedInPerson by mutableStateOf("") @@ -98,6 +101,14 @@ class WolfskinShellViewModel @Inject constructor( fun clearBackdrop() { viewModelScope.launch { backdrop.clearBackdrop() } } + /** The debug fixture is the only Requests supplier until TV sign-in is enabled. */ + fun refreshRequests() { + viewModelScope.launch { + val snapshot = withContext(Dispatchers.IO) { requestsSnapshot } + session.state?.requests?.update(snapshot) + } + } + private fun handleAction(action: ShellAction, server: JellyfinServer, user: JellyfinUser) { when (action) { is ShellAction.Destination -> if (action.route == "settings") navigation.navigateTo(Destination.Wolfskin("settings")) else navigation.replace(if (action.route == "home") Destination.Home() else Destination.Wolfskin(action.route)) @@ -105,6 +116,8 @@ class WolfskinShellViewModel @Inject constructor( is ShellAction.HandOff -> navigation.navigateTo(handOffDestination(action.group)) is ShellAction.Person -> selectPerson(action.person, server, user) ShellAction.RetryPeople -> viewModelScope.launch { load(server, user) } + ShellAction.RetryRequests -> refreshRequests() + is ShellAction.RequestTitle -> navigation.navigateTo(Destination.Wolfskin(titleRoute("${action.request.mediaType}:${action.request.tmdbId}"))) } } @@ -155,8 +168,14 @@ fun WolfskinApp( ShellState(listOf(ShellPerson(user.name.orEmpty().lowercase(), user.name.orEmpty(), user.name.orEmpty().take(1), false)), user.name.orEmpty().lowercase()).apply { peopleStatus = PeopleStatus.Loading } } val state = viewModel.session.state ?: loading + val discover = rememberDiscoverState( + state, + { route -> navigationManager.navigateTo(Destination.Wolfskin(route)) }, + navigationManager::goBack, + ) + RequestsRefresh(state, viewModel::refreshRequests) WolfskinTheme { - CompositionLocalProvider(LocalShellState provides state) { + CompositionLocalProvider(LocalShellState provides state, LocalDiscoverState provides discover) { Box(modifier) { val current = navigationManager.backStack.last() if (!state.pickerVisible && resolveUpstreamDestination(current, WolfskinScreenPreference.enabled, viewModel.libraryDestinations) != null) { @@ -190,7 +209,8 @@ fun WolfskinApp( ShellSurface(state, handlesContentKeys = false) { ShellPlaceholder("Sign in as ${state.person.name} to open their Jellyfin library", it) } } else TitleDestination(route, state, preferences) } else { - ShellSurface(state, handlesContentKeys = upstream == null) { contentModifier -> + ShellSurface(state, handlesContentKeys = upstream == null, + overlay = { if (route == "discover" || route.startsWith("discover-")) DiscoverOverlay(discover) }) { contentModifier -> when { upstream != null && state.person.key != viewModel.signedInPerson -> ShellPlaceholder("Sign in as ${state.person.name} to open their Jellyfin library", contentModifier) upstream != null -> DestinationContent(upstream, preferences, viewModel::clearBackdrop, contentModifier) @@ -213,5 +233,9 @@ fun WolfskinApp( /** The single registered destination branch delegates Wolfskin-only screens back to the shell. */ @Composable fun WolfskinDestinationContent(route: String, modifier: Modifier = Modifier) { - if (route == "settings") SettingsList(LocalShellState.current, modifier) else ShellPlaceholder(route, modifier) + when { + route == "settings" -> SettingsList(LocalShellState.current, modifier) + route == "discover" || route.startsWith("discover-") -> DiscoverScreen(LocalDiscoverState.current, modifier) + else -> ShellPlaceholder(route, modifier) + } } diff --git a/docker/README.md b/docker/README.md index c4cb03fa..1b3c6544 100644 --- a/docker/README.md +++ b/docker/README.md @@ -59,8 +59,8 @@ cover the Wolfskin nav. The independent `:wolfskin-design` module owns the shell components, fixed person colours, focus policy, per-person root-view memory, palette and motion. Its `tokens.json` is the copied approved TV-spec source, retaining the locked prototype -SHA and provenance. `shell-tokens.json` adds shell and grid measurements; -`title-tokens.json` adds title, performer and playback-sheet measurements from +SHA and provenance. `shell-tokens.json` adds shell/grid/request measurements; +`title-tokens.json` and `discover-tokens.json` add their screen measurements from that same prototype. Generate the committed Kotlin, CSS and checksum manifest together: ```sh @@ -84,8 +84,8 @@ padding includes it. The 6 px dash / 4 px gap follows the browser's 3:2 stroke-w ratios. Resting borders are white at 22% opacity; focused borders use ink at 40%. The small gear under the final person opens Settings with Requests focused. -Settings groups hand off to existing upstream pages; the Requests entry explicitly -has no request data yet. **Advanced settings → Wolfskin screens** is the persistent +Settings groups hand off to existing upstream pages; Requests opens the household +Coming-in sheet described below. **Advanced settings → Wolfskin screens** is the persistent rollout switch: turning it off renders upstream Home and library destinations with the upstream Backdrop and top scrim, while retaining Wolfskin's nav, picker and Settings list. Native Up traversal stays in content until the first focusable row, @@ -97,6 +97,51 @@ prototype fixture in `app/src/debug/assets/wolfskin-shell.json`; release resourc do not package it. Saved Jellyfin identities use existing upstream services. The shell makes no Conductarr calls and does not add Conductarr sign-in. +Discover adds the picks hero, type chips, catalogue shelves, service pages, continuous +See all grids, Hidden picks, request chooser and receipts. Its `discover-title/...` +destination is an explicit title-family integration stub exposing only Discover's +request and pick actions. Unheld titles have no trailer control until the pinned +Conductarr title contract supplies a trailer. + +The debug-only fixture and local art live in +`app/src/debug/assets/wolfskin-discover/`. Release builds remain on the explicit +Conductarr sign-in state. The fixture host cannot construct a network client: +debug request outcomes and dismissals stay in memory. The real mutation adapter +accepts an injected `ConductarrClient` and uses only its typed routes; it is not +wired to a live server. Unknown request outcomes force a fresh title-requests read +instead of blindly submitting again, including after closing a sheet or choosing +the same uncertain recipient from another chooser. Whole-series requests require every season. + +Discover hero and Restore text use explicit per-line CSS allocations so native +paragraph overflow cannot enlarge the bottom-anchored stack. Restore subtitles +retain the sheet's negative top margin and bottom gap. Recovery panels may +overflow their fixed-height scroll row so sign-in copy never shrinks the retry +button. Native regressions cover those three layout boundaries. + +Discover's native capture suite covers its 28 named acceptance states and emits +text-layout metrics beside the PNGs. Data-driven Android key replays cover opening +Discover in 4 presses, requesting for yourself in 7, requesting for Mindel in 9 and +Not interested in 8. Additional regressions exercise held-OK safety, uncertain +receipts, per-recipient outcomes, collection recovery and Undo, unknown seasons, +Dutch decimal formatting and short-row/tail clamping. Discover reuses the Libraries +grid, poster, ratings plate, See all and recovery cards, bounded image loader, caption +separator token and the shell's single hold guard. Both frames share the 180 ms scroll tween. + +The TV keeps platform Roboto with the approved type sizes and weights. Browser +system-font wrapping is not a per-title contract: no bundled font, forced title +breaks or width compensation is applied. Discover's own stacked text preserves +untrimmed CSS line boxes without changing merged shell/library typography. Hero, +sheet and shared focused-content shadows use tokenized drop shadows, not Material +elevation; their alpha animates in the draw phase. Native text-layout measurements +accompany each acceptance capture; remaining font-driven wrapping and text-block +positions must be listed explicitly in its comparison report. + +The asynchronous debug fixture seam hands loading focus back to the first loaded +action when Discover is still the selected destination. A JVM regression releases +the actual host's suspended fixture load and presses Android OK: it must open the +hero rather than activate the nav. Fixture completion does not steal a different +destination or nav selection. + Native Roborazzi captures use Robolectric SDK 34 and `w960dp-h540dp-xhdpi` (1920×1080 at density 2.0). `make test` writes capture PNGs and the sampled Compose nav-spring curve to `build/wolfskin-captures/`. Key replays @@ -121,6 +166,68 @@ artifact provider and AAPT2. It checks all string, plural and array configuratio rejecting user-visible upstream branding while preserving the upstream legal attribution and project URL. The check does not depend on incremental merge files. +## Requests under Settings + +The Requests row and Coming-in sheet share one immutable household snapshot. +Ready requests do not count as open. Rows sort Failed, Downloading/Almost ready, +Checking/Searching, Waiting, Ready. The bounded window shows five rows, or four +with readiness expanded. Right expands Request/Download/Library/Playback; Left +collapses; Up/Down collapse while moving. Opening a title preserves the request +for Back, and closing the sheet restores its opener. Selecting a root or changing +person clears the title-return link. Shared's authenticated actor always reads +“by Shared”, never “by you”. + +The debug-only provider loads `app/src/debug/assets/wolfskin-requests.json` and +its eight local posters, projected from the locked prototype fixture. Six stage +rows feed the running debug shell; the Shared and focused-rating records feed +native acceptance states. The identical projection is in design-test resources. +No “sample” or “example stages” pill is drawn. Release builds package none of +these assets and show Requests unavailable until Conductarr sign-in supplies +the generated-client data path. There is no Seerr fallback or handwritten HTTP. +Foreground refreshes once; an open sheet or unsettled requests refresh every +30 seconds while started. The immutable debug projection is loaded once, not +decoded again on every poll. +Thumbnails reuse `LibraryImages` with a 96×144-pixel request, RGB565, no crossfade, +the shared 64 MB memory/128 MB disk cache and four-read limit. Actor separator +dots use the surrounding text colour; the 40% rule applies only to poster captions. + +Uncached focused ratings wait 350 ms; moving away cancels the read. Read ratings +show immediately on return; sign-out clears visibility. This follows the frozen +page and spec 6.12, resolving the contradictory no-dwell sentence in spec 7.8. +Deterministic coroutine-clock tests cover cancellation, reentry, cached return, +sign-out and refreshed locale scores; a thin Compose test verifies actual Dutch +rendering and sign-out suppression. The approved nine-key Requests path is JSON +data replayed through the real shell. Home's hero content is not implemented +here: its launch-to-nav shell suffix is the acceptance seam. + +Sheet dimensions, padding, glyphs, scales, colours and durations are generated +tokens. Scoped sheet line boxes retain CSS heights and baselines independently +of native leading rounding; shared shell/library typography is unchanged. Platform +Roboto remains required: the host browser used DejaVu Sans, so its width, +wrapping, ellipsis and text-block positions are not a wrapping-parity target. +For example, the 44-pixel “Coming in” ink is 239 pixels wide in the reference +and 194 pixels in native captures. No per-title compensation or font bundle is +used. Native comparisons must report those structural differences explicitly. +Readiness glyphs sit inline on the text baseline with no flex gap, matching the +frozen block-span cascade. Items wrap intrinsically: Roboto fits the expanded +readiness into two lines where the reference uses three; no grouping is forced. + +The six rating marks retain the exact locked SVG source in +`wolfskin-design/request-marks/`; native-size PNGs and their hashes are generated +from the JSON dimensions with the locked contain fit (`.rr .rb` outranks `.crow img`). +Inside Coming-in rows they retain the prototype's dark `surface3` chips with 12-pixel rounded corners. +Loaded poster thumbnails have no placeholder fill beneath their clipped edge. + +```sh +uv run --with cairosvg==2.9.1 python wolfskin-design/generate_request_marks.py +``` + +`RequestsCaptureTest` writes `st-081`…`st-086`, `st-088` and `st-111` at 1920×1080, +density 2.0. Title selection opens the shared native title destination by media +type and TMDB identity. Back restores the exact Coming-in row and expansion; +the temporary `request-title/...` placeholder is removed. TV sign-in, release +and device deployment remain separate. + ## Libraries and See all Movies, Shows and Anime use the shell's retained view and a continuous six-column @@ -135,8 +242,8 @@ its unknown item count. Caption separators use 40% of the existing muted colour's alpha. The rail follows the locked prototype CSS: bold 24 px letters, with the focused letter bold 28 px, overriding the spec table's generic caption style for this component. -Library header initials use the muted colour over the person's fill; navigation, -picker and Settings avatars retain their dark initials. +Library and Settings header initials use the muted colour over the person's fill; +navigation and picker avatars retain their dark initials. The debug-only `wolfskin-library.json` and `library/assets/` contain the locked prototype's titles and local poster art. Release libraries read through diff --git a/wolfskin-design/build.gradle.kts b/wolfskin-design/build.gradle.kts index 4f77aa75..8cd51743 100644 --- a/wolfskin-design/build.gradle.kts +++ b/wolfskin-design/build.gradle.kts @@ -30,7 +30,7 @@ val cacheRobolectricSdk by tasks.registering(Copy::class) { } val checkTokenDrift by tasks.registering { - inputs.files(fileTree(projectDir) { include("tokens.json", "shell-tokens.json", "title-tokens.json", "generate_tokens.py", "tokens.css", "tokens.sha256", "src/main/java/**/WolfskinTokens.kt") }) + inputs.files(fileTree(projectDir) { include("tokens.json", "shell-tokens.json", "title-tokens.json", "discover-tokens.json", "generate_tokens.py", "tokens.css", "tokens.sha256", "src/main/java/**/WolfskinTokens.kt") }) doLast { file("tokens.sha256").readLines().forEach { line -> val (expected, name) = line.split(" ", limit = 2) @@ -59,6 +59,7 @@ dependencies { implementation(libs.coil.compose) debugImplementation(libs.androidx.compose.ui.test.manifest) testImplementation(libs.junit) + testImplementation(libs.kotlinx.coroutines.test) testImplementation(libs.robolectric) implementation(libs.androidx.compose.material3) testImplementation(libs.androidx.compose.ui.test.junit4) diff --git a/wolfskin-design/discover-tokens.json b/wolfskin-design/discover-tokens.json new file mode 100644 index 00000000..83266ff5 --- /dev/null +++ b/wolfskin-design/discover-tokens.json @@ -0,0 +1,279 @@ +{ + "discover": { + "sheetPaddingX": { + "$type": "dimension", + "px": 64, + "dp": 32, + "source": "locked index.html:227" + }, + "sheetPaddingY": { + "$type": "dimension", + "px": 80, + "dp": 40, + "source": "locked index.html:227" + }, + "sheetGap": { + "$type": "dimension", + "px": 18, + "dp": 9, + "source": "locked index.html:227" + }, + "sheetSubGap": { + "$type": "dimension", + "px": 14, + "dp": 7, + "source": "locked index.html:229" + }, + "sheetRowPadding": { + "$type": "dimension", + "px": 26, + "dp": 13, + "source": "locked index.html:231" + }, + "sheetAvatar": { + "$type": "dimension", + "px": 60, + "dp": 30, + "source": "locked index.html:232" + }, + "sheetRadio": { + "$type": "dimension", + "px": 40, + "dp": 20, + "source": "locked index.html:234" + }, + "sheetRadioDot": { + "$type": "dimension", + "px": 16, + "dp": 8, + "source": "locked index.html:236" + }, + "sheetPersonGap": { + "$type": "dimension", + "px": 24, + "dp": 12, + "source": "locked index.html:231" + }, + "sheetButtonTop": { + "$type": "dimension", + "px": 12, + "dp": 6, + "source": "locked index.html:241" + }, + "chipTop": { + "$type": "dimension", + "px": 16, + "dp": 8, + "source": "locked index.html:184" + }, + "chipHeight": { + "$type": "dimension", + "px": 68, + "dp": 34, + "source": "locked index.html:185" + }, + "chipPadding": { + "$type": "dimension", + "px": 28, + "dp": 14, + "source": "locked index.html:185" + }, + "chipGap": { + "$type": "dimension", + "px": 16, + "dp": 8, + "source": "locked index.html:184" + }, + "heroTitleGap": { + "$type": "dimension", + "px": 16, + "dp": 8, + "source": "locked index.html:103" + }, + "heroKickerGap": { + "$type": "dimension", + "px": 10, + "dp": 5, + "source": "locked index.html:101" + }, + "heroMetaGap": { + "$type": "dimension", + "px": 14, + "dp": 7, + "source": "locked index.html:104" + }, + "heroOverviewGap": { + "$type": "dimension", + "px": 30, + "dp": 15, + "source": "locked index.html:105" + }, + "heroFactsGap": { + "$type": "dimension", + "px": 22, + "dp": 11, + "source": "locked index.html:104" + }, + "heroFactsHeight": { + "$type": "dimension", + "px": 38, + "dp": 19, + "source": "locked index.html:104" + }, + "heroSheetRight": { + "$type": "dimension", + "px": 180, + "dp": 90, + "source": "locked index.html:97" + }, + "heroSheetTop": { + "$type": "dimension", + "px": 140, + "dp": 70, + "source": "locked index.html:98" + }, + "heroSheetWidth": { + "$type": "dimension", + "px": 360, + "dp": 180, + "source": "locked index.html:98" + }, + "heroSheetHeight": { + "$type": "dimension", + "px": 540, + "dp": 270, + "source": "locked index.html:98" + }, + "dotSize": { + "$type": "dimension", + "px": 14, + "dp": 7, + "source": "locked index.html:114" + }, + "dotActive": { + "$type": "dimension", + "px": 44, + "dp": 22, + "source": "locked index.html:115" + }, + "dotGap": { + "$type": "dimension", + "px": 14, + "dp": 7, + "source": "locked index.html:113" + }, + "dotMargin": { + "$type": "dimension", + "px": 18, + "dp": 9, + "source": "locked index.html:113" + }, + "dotChevron": { + "$type": "dimension", + "px": 40, + "dp": 20, + "source": "locked index.html:116" + }, + "stateTop": { + "$type": "dimension", + "px": 20, + "dp": 10, + "source": "locked index.html:194" + }, + "statePaddingX": { + "$type": "dimension", + "px": 40, + "dp": 20, + "source": "locked index.html:194" + }, + "statePaddingY": { + "$type": "dimension", + "px": 34, + "dp": 17, + "source": "locked index.html:194" + }, + "stateTextGap": { + "$type": "dimension", + "px": 24, + "dp": 12, + "source": "locked index.html:198" + }, + "noteTop": { + "$type": "dimension", + "px": 14, + "dp": 7, + "source": "locked index.html:191" + }, + "headerTop": { + "$type": "dimension", + "px": 150, + "dp": 75, + "source": "locked index.html:199" + }, + "headerGap": { + "$type": "dimension", + "px": 24, + "dp": 12, + "source": "locked index.html:202" + }, + "entryPaddingX": { + "$type": "dimension", + "px": 22, + "dp": 11, + "source": "locked index.html:149" + }, + "entryPaddingY": { + "$type": "dimension", + "px": 20, + "dp": 10, + "source": "locked index.html:149" + }, + "entryText": { + "$type": "typography", + "px": 30, + "sp": 15, + "weight": 800, + "letterSpacingEm": -0.01, + "$value": { + "fontSize": "15sp", + "fontWeight": 800 + }, + "source": "locked index.html:149" + }, + "sheetRadioRing": { + "$type": "dimension", + "px": 3, + "dp": 1.5, + "source": "locked index.html:234" + }, + "skeletonNameWidth": { + "$type": "dimension", + "px": 260, + "dp": 130, + "source": "locked index.html:1851" + }, + "skeletonNameHeight": { + "$type": "dimension", + "px": 34, + "dp": 17, + "source": "locked index.html:1851" + }, + "sheetSubTop": {"$type": "dimension", "px": -8, "dp": -4, "source": "locked index.html:229"}, + "sheetRatingTop": {"$type": "dimension", "px": -10, "dp": -5, "source": "locked index.html:168"}, + "sheetRatingBottom": {"$type": "dimension", "px": 6, "dp": 3, "source": "locked index.html:168"}, + "sheetLabelTop": {"$type": "dimension", "px": 8, "dp": 4, "source": "locked index.html:230"}, + "noteIcon": {"$type": "dimension", "px": 30, "dp": 15, "source": "locked index.html:192"}, + "noteGap": {"$type": "dimension", "px": 16, "dp": 8, "source": "locked index.html:191"}, + "frameBottomMin": {"$type": "dimension", "px": 80, "dp": 40, "source": "locked index.html:1042"}, + "anchorFitTop": {"$type": "dimension", "px": 700, "dp": 350, "source": "locked index.html:1062"}, + "anchorBottomClearance": {"$type": "dimension", "px": 40, "dp": 20, "source": "locked index.html:1062"}, + "reasonOpacity": {"$type": "number", "$value": 0.8, "source": "locked index.html:240"}, + "stageGap": {"$type": "dimension", "px": 10, "dp": 5, "source": "locked index.html:220"}, + "entryBorderOpacity": {"$type": "number", "$value": 0.08, "source": "locked index.html:149"}, + "ambientInset": {"$type": "dimension", "px": 80, "dp": 40, "source": "locked index.html:96"}, + "ambientBlur": {"$type": "dimension", "px": 60, "dp": 30, "source": "locked index.html:96"}, + "ambientScale": {"$type": "number", "$value": 1.1, "source": "locked index.html:96"}, + "ambientSaturation": {"$type": "number", "$value": 1.2, "source": "locked index.html:96"}, + "ambientBrightness": {"$type": "number", "$value": 0.55, "source": "locked index.html:96"} + } +} diff --git a/wolfskin-design/generate_request_marks.py b/wolfskin-design/generate_request_marks.py new file mode 100755 index 00000000..bba6a960 --- /dev/null +++ b/wolfskin-design/generate_request_marks.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +"""Rasterize locked prototype rating marks at the Coming-in row's native pixel size.""" +from pathlib import Path +import hashlib +import json +import cairosvg + +ROOT = Path(__file__).resolve().parent +SOURCE = ROOT / "request-marks" +OUTPUT = ROOT / "src/main/res/drawable-nodpi" + + +def main() -> None: + """Keep the original SVGs and record hashes alongside generated television-size PNGs.""" + OUTPUT.mkdir(parents=True, exist_ok=True) + tokens = json.loads((ROOT / "tokens.json").read_text()) + supplement = json.loads((ROOT / "shell-tokens.json").read_text()) + height = int(tokens["type"]["label"]["px"]) + wordmark_ratio = supplement["requests"]["ratingsWordmarkRatio"]["$value"] + manifest = [] + for source in sorted(SOURCE.glob("*.svg")): + target = OUTPUT / f"coming_in_rating_{source.stem}.png" + width = round(height * wordmark_ratio) if source.stem in ("imdb", "tmdb") else height + cairosvg.svg2png(url=str(source), write_to=str(target), output_width=width, output_height=height) + for path in (source, target): + manifest.append(f"{hashlib.sha256(path.read_bytes()).hexdigest()} {path.relative_to(ROOT)}") + (ROOT / "request-marks.sha256").write_text("\n".join(manifest) + "\n") + + +if __name__ == "__main__": + main() diff --git a/wolfskin-design/generate_tokens.py b/wolfskin-design/generate_tokens.py index a62239e4..98393200 100755 --- a/wolfskin-design/generate_tokens.py +++ b/wolfskin-design/generate_tokens.py @@ -114,9 +114,10 @@ def main() -> None: tokens = json.loads((ROOT / "tokens.json").read_text()) tokens.update(json.loads((ROOT / "shell-tokens.json").read_text())) tokens.update(json.loads((ROOT / "title-tokens.json").read_text())) + tokens.update(json.loads((ROOT / "discover-tokens.json").read_text())) kotlin, css = render_tokens(tokens) outputs = {str(KOTLIN): kotlin, "tokens.css": css} - inputs = ["tokens.json", "shell-tokens.json", "title-tokens.json", "generate_tokens.py"] + inputs = ["tokens.json", "shell-tokens.json", "title-tokens.json", "discover-tokens.json", "generate_tokens.py"] checksums = {name: hashlib.sha256((ROOT / name).read_bytes()).hexdigest() for name in inputs} checksums.update({name: hashlib.sha256(content.encode()).hexdigest() for name, content in outputs.items()}) outputs["tokens.sha256"] = "".join(f"{checksum} {name}\n" for name, checksum in sorted(checksums.items())) diff --git a/wolfskin-design/request-marks.sha256 b/wolfskin-design/request-marks.sha256 new file mode 100644 index 00000000..c29ddefc --- /dev/null +++ b/wolfskin-design/request-marks.sha256 @@ -0,0 +1,12 @@ +01aa8a3c3e9db48f0e1c6af25056940986784315bf66f48a8120a7317bf6d75a request-marks/fresh.svg +743b6139dc12019d0940568869065722593f44ce416a7e9c7b8d9ef92942bb3f src/main/res/drawable-nodpi/coming_in_rating_fresh.png +27f93b15a075b015e2448eca7949f7a3da03c37fbf26cecf6df3bdab45fda993 request-marks/imdb.svg +a738afd57519b1b4a38192927b48bd45755ce75e4673c6b12b9b3e0c2e388372 src/main/res/drawable-nodpi/coming_in_rating_imdb.png +510d0f9fb77d72190ebf03208f04215a4e86f71e165488518ac75aa49cf4a79d request-marks/rotten.svg +0aade711ba57b6b201c7395ece97108df928b6076d8120404cdf4155a4242632 src/main/res/drawable-nodpi/coming_in_rating_rotten.png +b8dcf4d27eba2446380980c96d82b1f64aa6859fa2ea1cdab867f849f9fec4fd request-marks/spilled.svg +fa4f50258009cd1d16f0d0f5a3c216dc88a57409b1bccc379512ae914cc9d714 src/main/res/drawable-nodpi/coming_in_rating_spilled.png +d537fb228cf3ded904ef09b136fe3fec72548ebc1fea3fbbd1ad9e36364db38b request-marks/tmdb.svg +8e1cb507f876f324bb5c53bf4e60775712f1fdddbffe94fd249e6e74099e045e src/main/res/drawable-nodpi/coming_in_rating_tmdb.png +2ad511c7a101a7f2f8810434911df8c13cc126fa691272242037237172600bc3 request-marks/upright.svg +6ff8a05501bc1c15b96c0130271c41baaa2ddb74f64916f58b9e7f918931d789 src/main/res/drawable-nodpi/coming_in_rating_upright.png diff --git a/wolfskin-design/request-marks/fresh.svg b/wolfskin-design/request-marks/fresh.svg new file mode 100644 index 00000000..977253a9 --- /dev/null +++ b/wolfskin-design/request-marks/fresh.svg @@ -0,0 +1,20 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/wolfskin-design/request-marks/imdb.svg b/wolfskin-design/request-marks/imdb.svg new file mode 100644 index 00000000..b2a908bc --- /dev/null +++ b/wolfskin-design/request-marks/imdb.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/wolfskin-design/request-marks/rotten.svg b/wolfskin-design/request-marks/rotten.svg new file mode 100644 index 00000000..04bef05b --- /dev/null +++ b/wolfskin-design/request-marks/rotten.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/wolfskin-design/request-marks/spilled.svg b/wolfskin-design/request-marks/spilled.svg new file mode 100644 index 00000000..af36063b --- /dev/null +++ b/wolfskin-design/request-marks/spilled.svg @@ -0,0 +1,46 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/wolfskin-design/request-marks/tmdb.svg b/wolfskin-design/request-marks/tmdb.svg new file mode 100644 index 00000000..62a66055 --- /dev/null +++ b/wolfskin-design/request-marks/tmdb.svg @@ -0,0 +1 @@ +Asset 4 \ No newline at end of file diff --git a/wolfskin-design/request-marks/upright.svg b/wolfskin-design/request-marks/upright.svg new file mode 100644 index 00000000..bf1cc85a --- /dev/null +++ b/wolfskin-design/request-marks/upright.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/wolfskin-design/shell-tokens.json b/wolfskin-design/shell-tokens.json index e13046b6..5ae0b699 100644 --- a/wolfskin-design/shell-tokens.json +++ b/wolfskin-design/shell-tokens.json @@ -1,4 +1,48 @@ { + "requests": { + "sheetPaddingY": { "$type": "dimension", "px": 80, "dp": 40, "source": "index.html:227" }, + "sheetPaddingX": { "$type": "dimension", "px": 64, "dp": 32, "source": "index.html:227" }, + "sheetGap": { "$type": "dimension", "px": 18, "dp": 9, "source": "index.html:227" }, + "sheetTitleLineHeight": { "$type": "dimension", "px": 56, "dp": 28, "source": "index.html:228; locked st-083/st-086: content begins at 154px after 80px padding and 18px gap" }, + "sheetTitleBaseline": { "$type": "dimension", "px": 44, "dp": 22, "source": "locked st-083: modal ink-bottom 123px, native baseline boundary 124px minus 80px padding" }, + "titleLineHeight": { "$type": "dimension", "px": 39, "dp": 19.5, "source": "index.html:250; 30px font with 1.3 line box" }, + "titleBaseline": { "$type": "dimension", "px": 31, "dp": 15.5, "source": "locked st-082/st-086: ink-bottom 45px, 15px text-block inset, baseline boundary 1px below ink" }, + "factsLineHeight": { "$type": "dimension", "px": 31.2, "dp": 15.6, "source": "index.html:251; 24px font with 1.3 line box" }, + "factsBaseline": { "$type": "dimension", "px": 24, "dp": 12, "source": "locked st-082/st-086: ink-bottom 77px, 15px block inset, 39px title box and 1px baseline boundary" }, + "stageLineHeight": { "$type": "dimension", "px": 39.6, "dp": 19.8, "source": "index.html:251-252; inline-flex stage within the 28px/1.3 parent strut" }, + "stageLabelLineHeight": { "$type": "dimension", "px": 33.8, "dp": 16.9, "source": "index.html:220; 26px font with 1.3 leading" }, + "stageBaseline": { "$type": "dimension", "px": 24, "dp": 12, "source": "locked st-082/st-086: ink-bottom 112px, 15px block inset, 39px title, 31.2px facts, 4px margin and 1px baseline boundary" }, + "emptyMargin": { "$type": "dimension", "px": 20, "dp": 10, "source": "index.html:1905" }, + "rowPaddingY": { "$type": "dimension", "px": 14, "dp": 7, "source": "index.html:247" }, + "rowPaddingX": { "$type": "dimension", "px": 18, "dp": 9, "source": "index.html:247" }, + "rowGap": { "$type": "dimension", "px": 24, "dp": 12, "source": "index.html:247" }, + "listGap": { "$type": "dimension", "px": 6, "dp": 3, "source": "index.html:255" }, + "stageMargin": { "$type": "dimension", "px": 4, "dp": 2, "source": "index.html:252" }, + "stageGlyph": { "$type": "dimension", "px": 28, "dp": 14, "source": "index.html:221" }, + "stageGap": { "$type": "dimension", "px": 10, "dp": 5, "source": "index.html:220" }, + "readinessGapX": { "$type": "dimension", "px": 22, "dp": 11, "source": "index.html:312" }, + "readinessGapY": { "$type": "dimension", "px": 10, "dp": 5, "source": "index.html:312" }, + "readinessMargin": { "$type": "dimension", "px": 8, "dp": 4, "source": "index.html:312" }, + "readinessGlyph": { "$type": "dimension", "px": 26, "dp": 13, "source": "index.html:314" }, + "chevron": { "$type": "dimension", "px": 30, "dp": 15, "source": "index.html:317" }, + "chevronAlpha": { "$type": "number", "$value": 0.6, "source": "index.html:317" }, + "receiptPaddingY": { "$type": "dimension", "px": 28, "dp": 14, "source": "index.html:243" }, + "receiptPaddingX": { "$type": "dimension", "px": 30, "dp": 15, "source": "index.html:243" }, + "receiptGap": { "$type": "dimension", "px": 10, "dp": 5, "source": "index.html:243" }, + "receiptGlyph": { "$type": "dimension", "px": 36, "dp": 18, "source": "index.html:245" }, + "buttonMargin": { "$type": "dimension", "px": 12, "dp": 6, "source": "index.html:241" }, + "skeletonTitleWidth": { "$type": "dimension", "px": 360, "dp": 180, "source": "index.html:1902" }, + "skeletonTitleHeight": { "$type": "dimension", "px": 34, "dp": 17, "source": "index.html:1902" }, + "skeletonSubtitleWidth": { "$type": "dimension", "px": 240, "dp": 120, "source": "index.html:1902" }, + "skeletonSubtitleHeight": { "$type": "dimension", "px": 26, "dp": 13, "source": "index.html:1902" }, + "skeletonGap": { "$type": "dimension", "px": 12, "dp": 6, "source": "index.html:1902" }, + "ratingsGap": { "$type": "dimension", "px": 20, "dp": 10, "source": "index.html:156" }, + "ratingsEntryGap": { "$type": "dimension", "px": 8, "dp": 4, "source": "index.html:157" }, + "shadowBlur": { "$type": "dimension", "px": 80, "dp": 40, "source": "index.html:227" }, + "shadowOffset": { "$type": "dimension", "px": -30, "dp": -15, "source": "index.html:227" }, + "shadowColor": { "$type": "color", "$value": "#00000080", "source": "index.html:227" }, + "ratingsWordmarkRatio": { "$type": "number", "$value": 1.8, "source": "index.html:160" } + }, "shell": { "navLabelFade": { "$type": "duration", @@ -327,7 +371,6 @@ "captionSeparatorOpacity": {"$type":"number","$value":0.4,"source":"index.html:1207; spec 4.4"}, "captionFactsGap": {"$type":"dimension","px":18,"dp":9,"source":"index.html:154"}, "badgeIcon": {"$type":"dimension","px":28,"dp":14,"source":"index.html:142"}, - "badgeBorder": {"$type":"dimension","px":3,"dp":1.5,"source":"index.html:144"}, "plateTop": {"$type":"dimension","px":14,"dp":7,"source":"index.html:162"}, "plateSide": {"$type":"dimension","px":16,"dp":8,"source":"index.html:162"}, "plateBottom": {"$type":"dimension","px":46,"dp":23,"source":"index.html:162"}, @@ -356,6 +399,11 @@ "tailIcon": {"$type":"dimension","px":44,"dp":22,"source":"index.html:300"}, "tailPadding": {"$type":"dimension","px":20,"dp":10,"source":"index.html:299"}, "tailGap": {"$type":"dimension","px":10,"dp":5,"source":"index.html:299"}, + "badgeRing": {"$type":"dimension","px":3,"dp":1.5,"source":"index.html:144"}, + "badgeIcon": {"$type":"dimension","px":28,"dp":14,"source":"index.html:142"}, + "seeAllGap": {"$type":"dimension","px":8,"dp":4,"source":"index.html:146"}, + "tailDescription": {"$type":"typography","px":24,"sp":12,"weight":500,"$value":{"fontWeight":500},"source":"index.html:301"}, + "headerSubtitle": {"$type":"typography","px":30,"sp":15,"weight":400,"$value":{"fontWeight":400},"source":"index.html:201"}, "imageReads": {"$type":"number","$value":4,"source":"spec 4.9, tokens.json image.concurrentReads"}, "unknownCriticOpacity": {"$type":"number","$value":0.55,"source":"spec 4.7, index.html:161"}, "fallbackPadding": {"$type":"dimension","px":22,"dp":11,"source":"index.html:130"}, @@ -366,5 +414,19 @@ "fallbackSaturationBottom": {"$type":"number","$value":0.26,"source":"index.html:130"}, "fallbackLightnessBottom": {"$type":"number","$value":0.1,"source":"index.html:130"}, "fallbackIconOpacity": {"$type":"number","$value":0.45,"source":"index.html:132"} + }, + "lift": { + "cardBlur": {"$type":"dimension","px":50,"dp":25,"source":"index.html:134; spec 4.5"}, + "cardY": {"$type":"dimension","px":18,"dp":9,"source":"index.html:134"}, + "cardOpacity": {"$type":"number","$value":0.65,"source":"index.html:134"}, + "buttonBlur": {"$type":"dimension","px":40,"dp":20,"source":"index.html:110; spec 4.5"}, + "buttonY": {"$type":"dimension","px":12,"dp":6,"source":"spec 4.5"}, + "buttonOpacity": {"$type":"number","$value":0.5,"source":"spec 4.5"}, + "heroBlur": {"$type":"dimension","px":80,"dp":40,"source":"index.html:97"}, + "heroY": {"$type":"dimension","px":30,"dp":15,"source":"index.html:97"}, + "heroOpacity": {"$type":"number","$value":0.6,"source":"index.html:97"}, + "sheetBlur": {"$type":"dimension","px":80,"dp":40,"source":"index.html:227"}, + "sheetX": {"$type":"dimension","px":-30,"dp":-15,"source":"index.html:227"}, + "sheetOpacity": {"$type":"number","$value":0.5,"source":"index.html:227"} } } diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/Caption.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/Caption.kt index de702d29..5ab4bbb8 100644 --- a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/Caption.kt +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/Caption.kt @@ -1,12 +1,12 @@ package org.justinvisser.wolfskin.design +import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.CubicBezierEasing import androidx.compose.animation.core.tween -import androidx.compose.animation.core.Animatable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.remember import androidx.compose.foundation.layout.Column import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.text.style.TextOverflow diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/CatalogueRecoveryCard.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/CatalogueRecoveryCard.kt new file mode 100644 index 00000000..db0a22f6 --- /dev/null +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/CatalogueRecoveryCard.kt @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0-only +package org.justinvisser.wolfskin.design + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.tv.material3.Text +import org.justinvisser.wolfskin.design.WolfskinTokens.Color as C +import org.justinvisser.wolfskin.design.WolfskinTokens.Library as G + +/** Shelves and continuous grids share the same recoverable tail instead of a page selector. */ +@Composable +fun CatalogueRecoveryCard(retry: Boolean, focused: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier) { + val shape = RoundedCornerShape(WolfskinTokens.Radius.card) + Column(modifier.size(WolfskinTokens.Card.posterW, WolfskinTokens.Card.posterH) + .focusScale(focused, WolfskinTokens.Focus.scaleCard) + .focusRing(focused, WolfskinTokens.Radius.card, WolfskinTokens.Focus.ringCardGap, WolfskinTokens.Focus.ringCardRing, C.accent, FocusLift.Card) + .background(C.surface, shape).border(G.borderWidth, C.border, shape).clickable(onClick = onClick).padding(G.tailPadding), + verticalArrangement = Arrangement.spacedBy(G.tailGap, Alignment.CenterVertically), horizontalAlignment = Alignment.CenterHorizontally) { + Glyph(if (retry) "alert" else "down", if (retry) C.warn else C.accentText, Modifier.size(G.tailIcon)) + Text(if (retry) "Try again" else "Show more", color = C.fg, style = WolfskinTokens.Type.button, textAlign = TextAlign.Center) + Text(if (retry) "More titles couldn’t load" else "Only titles already shown came back", color = C.muted, + style = G.tailDescription, textAlign = TextAlign.Center) + } +} diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/ComingInFixture.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/ComingInFixture.kt new file mode 100644 index 00000000..50608f0b --- /dev/null +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/ComingInFixture.kt @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-2.0-only +package org.justinvisser.wolfskin.design + +import org.json.JSONObject + +/** Debug assets and native screenshots consume the same projection of the frozen prototype. */ +fun readComingInFixture(json: String, poster: (String) -> Any?): List { + val items = JSONObject(json).getJSONArray("items") + return List(items.length()) { index -> + val item = items.getJSONObject(index) + val scores = item.getJSONArray("ratings") + ComingInRequest( + id = item.getString("id"), mediaType = item.getString("mediaType"), tmdbId = item.getLong("tmdbId"), + title = item.getString("title"), requesterKey = item.getString("requesterKey"), requesterName = item.getString("requesterName"), + actorKey = item.getString("actorKey"), actorName = item.getString("actorName"), stage = ComingInStage.valueOf(item.getString("stage")), + poster = poster(item.getString("poster")), + ratings = List(scores.length()) { scoreIndex -> + val score = scores.getJSONObject(scoreIndex) + ComingInRating(score.getString("mark"), score.getDouble("score"), score.getBoolean("percentage")) + }, + ) + } +} diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/ComingInLineBox.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/ComingInLineBox.kt new file mode 100644 index 00000000..e7ae86a9 --- /dev/null +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/ComingInLineBox.kt @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: GPL-2.0-only +package org.justinvisser.wolfskin.design + +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.FirstBaseline +import androidx.compose.ui.layout.layout +import androidx.compose.ui.unit.Dp + +/** A CSS line box has its own height and baseline, independent of native leading rounding. */ +internal fun Modifier.comingInLineBox(height: Dp, baseline: Dp): Modifier = layout { measurable, constraints -> + val text = measurable.measure(constraints.copy(minHeight = 0)) + val baselineOffset = baseline.roundToPx() - text[FirstBaseline] + layout(text.width, height.roundToPx()) { + text.placeRelative(0, baselineOffset) + } +} diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/ComingInRatings.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/ComingInRatings.kt new file mode 100644 index 00000000..3f2b0da1 --- /dev/null +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/ComingInRatings.kt @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-2.0-only +package org.justinvisser.wolfskin.design + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.foundation.background +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.ContentScale +import androidx.tv.material3.Text +import java.util.Locale +import org.justinvisser.wolfskin.design.WolfskinTokens.Requests as T + +/** Percentages remain whole; ten-point scores use the television's locale. */ +fun formatComingInRating(rating: ComingInRating, locale: Locale = Locale.getDefault()): String = + if (rating.percentage) "${rating.score.toInt()}%" else String.format(locale, "%.1f", rating.score) + +/** This row always displays the full supplier set rather than the card plate's shortened prefix. */ +@Composable +fun ComingInRatings(ratings: List, color: Color) { + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(T.ratingsGap)) { + ratings.forEach { rating -> + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(T.ratingsEntryGap)) { + val mark = when (rating.mark) { + "fresh" -> R.drawable.coming_in_rating_fresh + "rotten" -> R.drawable.coming_in_rating_rotten + "upright" -> R.drawable.coming_in_rating_upright + "spilled" -> R.drawable.coming_in_rating_spilled + "imdb" -> R.drawable.coming_in_rating_imdb + else -> R.drawable.coming_in_rating_tmdb + } + val wide = rating.mark == "imdb" || rating.mark == "tmdb" + Image(painterResource(mark), contentDescription = rating.mark, + modifier = Modifier.height(T.readinessGlyph).width(T.readinessGlyph * if (wide) T.ratingsWordmarkRatio else 1f) + .clip(ratingMarkShape).background(WolfskinTokens.Color.surface3), + contentScale = ContentScale.Fit) + Text(formatComingInRating(rating), style = ComingInTypography.rating, color = color) + } + } + } +} + +private val ratingMarkShape = RoundedCornerShape(WolfskinTokens.Radius.thumb) diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/ComingInSheet.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/ComingInSheet.kt new file mode 100644 index 00000000..c9b5bd98 --- /dev/null +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/ComingInSheet.kt @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: GPL-2.0-only +package org.justinvisser.wolfskin.design + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.graphics.drawscope.drawIntoCanvas +import androidx.compose.ui.graphics.nativeCanvas +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.input.pointer.PointerEventType +import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.text.style.TextOverflow +import androidx.tv.material3.Text +import coil3.compose.AsyncImage +import coil3.compose.AsyncImagePainter +import androidx.compose.runtime.mutableStateOf +import org.justinvisser.wolfskin.design.WolfskinTokens.Color as C +import org.justinvisser.wolfskin.design.WolfskinTokens.Layout as L +import org.justinvisser.wolfskin.design.WolfskinTokens.Requests as T + +/** The overlay intercepts pointer input as well as the shell's virtual D-pad focus. */ +@Composable +fun ComingInSheet(shell: ShellState, modifier: Modifier = Modifier) { + val state = shell.requests + val data = state.snapshot + LaunchedEffect(state.focusedId, data.signedIn, data.status) { + state.awaitFocusedRatings() + } + Box(modifier.fillMaxSize().background(C.sheetScrim).clickable { shell.closeRequests() }.testTag("coming-in-scrim")) + Column(modifier.fillMaxHeight().width(L.sheetWidth) + .offset(x = WolfskinTokens.Shell.stageWidth - L.sheetWidth) + .drawWithCache { + val panel = android.graphics.Paint(android.graphics.Paint.ANTI_ALIAS_FLAG).apply { + color = C.surface.toArgb() + setShadowLayer(T.shadowBlur.toPx(), T.shadowOffset.toPx(), 0f, T.shadowColor.toArgb()) + } + onDrawBehind { + drawIntoCanvas { it.nativeCanvas.drawRect(0f, 0f, size.width, size.height, panel) } + } + }.pointerInput(Unit) { + awaitPointerEventScope { + while (true) awaitPointerEvent(PointerEventPass.Final).changes.forEach { it.consume() } + } + } + .padding(horizontal = T.sheetPaddingX, vertical = T.sheetPaddingY) + .testTag("coming-in"), verticalArrangement = Arrangement.spacedBy(T.sheetGap)) { + Text("Coming in", Modifier.comingInLineBox(T.sheetTitleLineHeight, T.sheetTitleBaseline), style = ComingInTypography.title, color = C.fg) + when { + data.status == ComingInStatus.Loading && data.items.isEmpty() -> { + LazyColumn(verticalArrangement = Arrangement.spacedBy(T.listGap)) { + items(3, key = { "request-skeleton-$it" }, contentType = { "skeleton" }) { ComingInSkeleton() } + } + SheetButton("Close") { shell.closeRequests() } + } + data.status == ComingInStatus.Unavailable && data.items.isEmpty() -> { + Column(Modifier.fillMaxWidth().background(C.listRowRest, RoundedCornerShape(WolfskinTokens.Radius.panel)) + .padding(horizontal = T.receiptPaddingX, vertical = T.receiptPaddingY), + verticalArrangement = Arrangement.spacedBy(T.receiptGap)) { + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(T.rowPaddingY)) { + Glyph("alert", C.warn, Modifier.size(T.receiptGlyph)) + Text("Requests unavailable", style = ComingInTypography.stateTitle, color = C.warn) + } + Text("Conductarr didn’t answer.", style = ComingInTypography.body, color = C.muted) + } + SheetButton("Try again", primary = true) { shell.retryRequests() } + } + data.items.isEmpty() -> Text("Nothing coming in.", Modifier.padding(top = T.emptyMargin), style = ComingInTypography.body, color = C.muted) + else -> { + if (data.partial) Text("Some requests may be missing: Conductarr answered only in part.", style = ComingInTypography.caption, color = C.warn) + val start = state.windowStart + val visibleRows = remember(data, start, state.windowSize) { + data.ordered.subList(start, (start + state.windowSize).coerceAtMost(data.ordered.size)) + } + LazyColumn(Modifier.clipToBounds(), verticalArrangement = Arrangement.spacedBy(T.listGap)) { + itemsIndexed(visibleRows, + key = { _, item -> item.id }, contentType = { _, _ -> "request" }) { _, item -> + ComingInRow(item, shell.person, item.id == state.focusedId, state.expanded && item.id == state.focusedId, + state.ratingsVisible && data.signedIn && item.id == state.focusedId, + onFocus = { state.focus(item.id) }, onClick = { state.focus(item.id); shell.activateRequest() }) + } + } + } + } + } +} + +@Composable +private fun SheetButton(label: String, primary: Boolean = false, onClick: () -> Unit) { + Box(Modifier.padding(top = T.buttonMargin)) { PickerButton(label, true, primary, onClick) } +} + +@Composable +private fun ComingInSkeleton() { + Row(Modifier.fillMaxWidth().padding(horizontal = T.rowPaddingX, vertical = T.rowPaddingY), + verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(T.rowGap)) { + Box(Modifier.size(L.thumbWidth, L.thumbHeight).background(C.surface3, RoundedCornerShape(WolfskinTokens.Radius.thumb))) + Column(verticalArrangement = Arrangement.spacedBy(T.skeletonGap)) { + Box(Modifier.size(T.skeletonTitleWidth, T.skeletonTitleHeight).background(C.surface, RoundedCornerShape(WolfskinTokens.Shell.skeletonRadius))) + Box(Modifier.size(T.skeletonSubtitleWidth, T.skeletonSubtitleHeight).background(C.surface, RoundedCornerShape(WolfskinTokens.Radius.card))) + } + } +} + +/** Thumbnail slots do not resize when the text or the readiness block grows. */ +@OptIn(androidx.compose.ui.ExperimentalComposeUiApi::class, ExperimentalLayoutApi::class) +@Composable +fun ComingInRow(item: ComingInRequest, viewer: ShellPerson, focused: Boolean, expanded: Boolean, + showRatings: Boolean, onFocus: () -> Unit, onClick: () -> Unit) { + val foreground = if (focused) C.onFocus else C.fg + val secondary = if (focused) C.onFocus else C.muted + val context = LocalContext.current + val request = remember(item.poster, context) { LibraryImages.request(context, item.poster, (L.thumbWidth.value * 2).toInt(), (L.thumbHeight.value * 2).toInt()) } + val posterReady = remember(request) { mutableStateOf(false) } + Row(Modifier.fillMaxWidth().focusScale(focused, WolfskinTokens.Focus.scaleRow) + .background(if (focused) C.fg else Color.Transparent, RoundedCornerShape(WolfskinTokens.Radius.panel)) + .pointerInput(item.id) { + awaitPointerEventScope { + while (true) if (awaitPointerEvent().type == PointerEventType.Enter) onFocus() + } + }.clickable(onClick = onClick) + .padding(horizontal = T.rowPaddingX, vertical = T.rowPaddingY).testTag("coming-row-${item.id}"), + verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(T.rowGap)) { + AsyncImage(model = request, imageLoader = LibraryImages.loader(context), contentDescription = null, + modifier = Modifier.size(L.thumbWidth, L.thumbHeight).clip(RoundedCornerShape(WolfskinTokens.Radius.thumb)) + .background(if (posterReady.value) Color.Transparent else C.surface3), + onState = { posterReady.value = it is AsyncImagePainter.State.Success }, + contentScale = androidx.compose.ui.layout.ContentScale.Crop) + Column(Modifier.weight(1f)) { + Text(item.title, Modifier.comingInLineBox(T.titleLineHeight, T.titleBaseline), + style = ComingInTypography.listTitle, color = foreground, maxLines = 1, overflow = TextOverflow.Ellipsis) + Text("for ${item.recipient(viewer)} · by ${item.actor(viewer)}", Modifier.comingInLineBox(T.factsLineHeight, T.factsBaseline), + style = ComingInTypography.caption, color = secondary) + FlowRow(Modifier.padding(top = T.stageMargin).heightIn(min = T.stageLineHeight), horizontalArrangement = Arrangement.spacedBy(T.readinessGlyph)) { + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(T.stageGap)) { + Glyph(item.stage.glyph, secondary, Modifier.size(T.stageGlyph)) + Text(item.stage.label, Modifier.comingInLineBox(T.stageLabelLineHeight, T.stageBaseline), style = ComingInTypography.label, color = secondary) + } + if (showRatings) ComingInRatings(item.ratings, foreground) + } + if (expanded) FlowRow(Modifier.padding(top = T.readinessMargin), + horizontalArrangement = Arrangement.spacedBy(T.readinessGapX), verticalArrangement = Arrangement.spacedBy(T.readinessGapY)) { + item.stage.readiness.forEachIndexed { index, word -> + Row { + val glyph = when (word) { "Done" -> "check"; "Failed" -> "alert"; "Not yet", "Unknown" -> null; else -> "clock" } + if (glyph != null) Glyph(glyph, secondary, Modifier.size(T.readinessGlyph).alignBy { it.measuredHeight }) + Text("${READINESS_NAMES[index]} · $word", Modifier.alignByBaseline(), style = ComingInTypography.readiness, color = secondary) + } + } + } + } + if (focused && !expanded) Glyph("right", foreground.copy(alpha = T.chevronAlpha), Modifier.size(T.chevron)) + } +} + +private val READINESS_NAMES = listOf("Request", "Download", "Library", "Playback") diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/ComingInState.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/ComingInState.kt new file mode 100644 index 00000000..86d6c5c3 --- /dev/null +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/ComingInState.kt @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-2.0-only +package org.justinvisser.wolfskin.design + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue + +import kotlinx.coroutines.delay +/** Household acquisition stages, ordered independently from their display wording. */ +enum class ComingInStage(val label: String, val rank: Int, val glyph: String, val readiness: List) { + Failed("Failed", 0, "alert", listOf("Done", "Failed", "Not yet", "Not yet")), + Downloading("Downloading", 1, "download", listOf("Done", "Downloading", "Not yet", "Not yet")), + AlmostReady("Almost ready", 1, "download", listOf("Done", "Done", "Being added", "Not yet")), + Checking("Checking", 2, "clock", listOf("Checking", "Unknown", "Not yet", "Not yet")), + Searching("Searching", 2, "clock", listOf("Done", "Searching", "Not yet", "Not yet")), + Waiting("Waiting", 3, "clock", listOf("Waiting", "Not yet", "Not yet", "Not yet")), + Ready("Ready", 4, "check", listOf("Done", "Done", "Done", "Not checked yet")), +} + +/** Ratings have already passed the supplier's observed-score and vote checks. */ +data class ComingInRating(val mark: String, val score: Double, val percentage: Boolean = false) + +/** Requests carry authenticated actor evidence, never the current viewer as a substitute. */ +data class ComingInRequest( + val id: String, + val mediaType: String, + val tmdbId: Long, + val title: String, + val requesterKey: String, + val requesterName: String, + val actorKey: String?, + val actorName: String?, + val stage: ComingInStage, + val poster: Any? = null, + val ratings: List = emptyList(), +) { + val ratingKey = "$mediaType:$tmdbId" + fun recipient(viewer: ShellPerson): String = if (requesterKey == viewer.key) "you" else requesterName + fun actor(viewer: ShellPerson): String = when { + actorKey == "shared" -> "Shared" + actorKey != null && actorKey == viewer.key -> "you" + else -> actorName ?: "Unknown" + } +} + +enum class ComingInStatus { Ready, Loading, Unavailable } + +/** The provider retains saved rows when refresh fails; an unavailable empty result is not empty success. */ +data class ComingInSnapshot( + val items: List = emptyList(), + val status: ComingInStatus = ComingInStatus.Unavailable, + val partial: Boolean = false, + val signedIn: Boolean = false, +) { + val ordered = items.sortedBy { it.stage.rank } + val openCount: Int get() = items.count { it.stage != ComingInStage.Ready } +} + +/** A bounded window and stable request id preserve focus across refreshes and title round trips. */ +class ComingInState { + var snapshot by mutableStateOf(ComingInSnapshot()) + private set + var focusedId by mutableStateOf(null) + private set + var expanded by mutableStateOf(false) + private set + var ratingsVisible by mutableStateOf(false) + private set + private val cachedRatings = mutableSetOf() + val index: Int get() = snapshot.ordered.indexOfFirst { it.id == focusedId }.coerceAtLeast(0) + val focused: ComingInRequest? get() = snapshot.ordered.getOrNull(index) + val windowSize: Int get() = if (expanded) 4 else 5 + val windowStart: Int get() = (index - 2).coerceIn(0, (snapshot.ordered.size - windowSize).coerceAtLeast(0)) + + fun update(value: ComingInSnapshot) { + snapshot = value + if (!value.signedIn) cachedRatings.clear() + if (value.ordered.none { it.id == focusedId }) focus(value.ordered.firstOrNull()?.id) + ratingsVisible = value.signedIn && focused?.ratingKey in cachedRatings + } + + fun focus(id: String?) { + focusedId = id + expanded = false + ratingsVisible = snapshot.signedIn && focused?.ratingKey in cachedRatings + } + + /** Coroutine cancellation and the captured request id prevent stale focus reads from painting. */ + suspend fun awaitFocusedRatings() { + val item = focused ?: return + if (!snapshot.signedIn || snapshot.status != ComingInStatus.Ready || item.ratings.isEmpty() || ratingsVisible) return + delay(WolfskinTokens.Motion.ratingDwell.toLong()) + if (focusedId != item.id || !snapshot.signedIn || snapshot.status != ComingInStatus.Ready) return + cachedRatings.add(item.ratingKey) + ratingsVisible = true + } + + /** Return true only when a row requests a title; every direction remains trapped in the sheet. */ + fun key(key: ShellKey): Boolean { + when (key) { + ShellKey.Up -> focus(snapshot.ordered.getOrNull((index - 1).coerceAtLeast(0))?.id) + ShellKey.Down -> focus(snapshot.ordered.getOrNull((index + 1).coerceAtMost(snapshot.ordered.lastIndex))?.id) + ShellKey.Right -> if (focused != null) expanded = true + ShellKey.Left -> expanded = false + ShellKey.Ok -> return focused != null + else -> Unit + } + return false + } +} diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/ComingInTypography.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/ComingInTypography.kt new file mode 100644 index 00000000..a8f3b3e0 --- /dev/null +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/ComingInTypography.kt @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: GPL-2.0-only +package org.justinvisser.wolfskin.design + +import androidx.compose.ui.text.style.LineHeightStyle +import androidx.compose.ui.text.font.FontWeight + +/** CSS line boxes retain their leading even on single lines; trimming changes sheet geometry. */ +internal object ComingInTypography { + private val lineBox = LineHeightStyle(LineHeightStyle.Alignment.Center, LineHeightStyle.Trim.None) + val title = WolfskinTokens.Type.sheetTitle.copy(lineHeightStyle = lineBox) + val listTitle = WolfskinTokens.Type.listTitle.copy(lineHeightStyle = lineBox) + val body = WolfskinTokens.Type.body.copy(lineHeightStyle = lineBox) + val label = WolfskinTokens.Type.label.copy(lineHeightStyle = lineBox) + val caption = WolfskinTokens.Type.caption.copy(lineHeightStyle = lineBox) + val stateTitle = WolfskinTokens.Type.stateTitle.copy(lineHeightStyle = lineBox, fontWeight = FontWeight.Bold) + val readiness = caption.copy(fontWeight = FontWeight.Bold) + val rating = WolfskinTokens.Type.label.copy(fontFeatureSettings = "tnum") +} diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverArtwork.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverArtwork.kt new file mode 100644 index 00000000..670e473b --- /dev/null +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverArtwork.kt @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0-only +package org.justinvisser.wolfskin.design + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.ui.Alignment +import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.runtime.Composable +import androidx.compose.runtime.compositionLocalOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.blur +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.dropShadow +import androidx.compose.ui.graphics.shadow.Shadow +import androidx.compose.ui.unit.DpOffset +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.ColorMatrix +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.painter.BitmapPainter +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import coil3.compose.AsyncImage +import coil3.request.ImageRequest +import coil3.request.allowRgb565 +import org.justinvisser.wolfskin.design.WolfskinTokens.Color as C +import org.justinvisser.wolfskin.design.WolfskinTokens.Discover as D +import org.justinvisser.wolfskin.design.WolfskinTokens.Layout as L + +/** Native captures provide decoded local art; neither posters nor heroes perform image HTTP reads. */ +val LocalDiscoverArt = compositionLocalOf<((String) -> ImageBitmap?)?> { null } + +/** Decode requests use the actual art slot, with RGB565 only for upright posters. */ +@Composable +fun DiscoverArtwork(path: String?, modifier: Modifier, poster: Boolean = false, dim: Boolean = false) { + if (path == null) return + val bitmap = LocalDiscoverArt.current?.invoke(path) + val painter = remember(bitmap) { bitmap?.let(::BitmapPainter) } + val filter = remember(dim) { if (dim) ColorFilter.colorMatrix(ColorMatrix().apply { + setToSaturation(D.ambientSaturation) + this *= ColorMatrix().apply { setToScale(D.ambientBrightness, D.ambientBrightness, D.ambientBrightness, 1f) } + }) else null } + val context = LocalContext.current + val request = remember(context, path, poster) { + ImageRequest.Builder(context).data(path).size(if (poster) 256 else 1280, if (poster) 384 else 720).allowRgb565(poster).build() + } + if (painter != null) Image(painter, null, modifier, contentScale = ContentScale.Crop, colorFilter = filter) + else AsyncImage( + model = request, + imageLoader = LibraryImages.loader(context), + contentDescription = null, modifier = modifier, contentScale = ContentScale.Crop, colorFilter = filter, + ) +} + +/** Poster-only titles retain the one-sheet over a blurred wash rather than stretching the poster. */ +@Composable +fun DiscoverBackdrop(title: DiscoverTitle, detail: Boolean = false, modifier: Modifier = Modifier) { + Box(modifier.size(WolfskinTokens.Shell.stageWidth, WolfskinTokens.Shell.stageHeight).clipToBounds().background(C.surface3)) { + if (title.posterOnly) { + DiscoverArtwork(title.poster, Modifier.offset(-D.ambientInset, -D.ambientInset) + .wrapContentSize(Alignment.TopStart, unbounded = true) + .size(WolfskinTokens.Shell.stageWidth + D.ambientInset * 2, WolfskinTokens.Shell.stageHeight + D.ambientInset * 2) + .graphicsLayer { scaleX = D.ambientScale; scaleY = D.ambientScale }.blur(D.ambientBlur), dim = true) + DiscoverArtwork(title.poster, Modifier.offset( + WolfskinTokens.Shell.stageWidth - D.heroSheetRight - if (detail) L.heroSheetWidth else D.heroSheetWidth, + if (detail) L.navBand else D.heroSheetTop, + ).size(if (detail) L.heroSheetWidth else D.heroSheetWidth, if (detail) L.heroSheetHeight else D.heroSheetHeight) + .dropShadow(RoundedCornerShape(WolfskinTokens.Radius.heroSheet), Shadow( + radius = WolfskinTokens.Lift.heroBlur, color = Color.Black.copy(alpha = WolfskinTokens.Lift.heroOpacity), + offset = DpOffset(WolfskinTokens.Shell.zero, WolfskinTokens.Lift.heroY))) + .clip(RoundedCornerShape(WolfskinTokens.Radius.heroSheet)), poster = true) + } else DiscoverArtwork(title.backdrop ?: title.poster, Modifier.fillMaxSize()) + Box(Modifier.fillMaxSize().drawWithCache { + val scrim = Brush.horizontalGradient(0f to C.bg.copy(alpha = .92f), .42f to C.bg.copy(alpha = .55f), .70f to C.bg.copy(alpha = 0f), endX = size.width) + onDrawBehind { drawRect(scrim) } + }) + Box(Modifier.fillMaxSize().background(Brush.verticalGradient(0f to C.bg.copy(alpha = 0f), .52f to C.bg.copy(alpha = 0f), .74f to C.bg.copy(alpha = .6f), 1f to C.bg))) + } +} + +/** Missing art is deterministic and quiet; it does not imply a provider read succeeded. */ +fun discoverFallback(title: String): Brush { + val hue = title.fold(0) { hash, char -> (hash * 31 + char.code) % 360 }.toFloat() + return Brush.linearGradient(listOf(Color.hsl(hue, .22f, .22f), Color.hsl(hue, .26f, .10f))) +} diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverCardProjection.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverCardProjection.kt new file mode 100644 index 00000000..a638c6e6 --- /dev/null +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverCardProjection.kt @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-only +package org.justinvisser.wolfskin.design + +/** Discover metadata projects into the same poster and grid contract as Libraries. */ +internal fun DiscoverTitle.libraryCard(state: DiscoverState): LibraryTitle { + val receipts = state.requests[key].orEmpty() + val held = state.shell.person.key in heldBy + val availability = when { + held -> "In your library" + receipts.isNotEmpty() -> { + val recipients = receipts.joinToString(", ") { request -> + if (request.requester == state.shell.person.key) "you" + else state.shell.people.firstOrNull { it.key == request.requester }?.name ?: request.requester.orEmpty() + } + "Requested for $recipients · ${receipts.first().stage}" + } + heldBy.isNotEmpty() -> "In ${heldBy.joinToString(" and ") { key -> state.shell.people.firstOrNull { it.key == key }?.name ?: key }}’s library" + else -> "" + } + return LibraryTitle(id = key, title = title, type = type, year = year, + runtimeSeconds = runtimeSeconds?.toLong(), seasons = seasonCount, poster = poster, + ratings = ratings.map { LibraryRating(it.source, it.score, it.label) }, availability = availability, + badge = when { + held -> "held" + receipts.isEmpty() -> null + receipts.all { it.stage == "Failed" } -> "failed" + else -> "requested" + }) +} diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverFixture.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverFixture.kt new file mode 100644 index 00000000..0b8d52e7 --- /dev/null +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverFixture.kt @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0-only +package org.justinvisser.wolfskin.design + +import org.json.JSONArray +import org.json.JSONObject + +/** Projects supplied prototype data; this class never loads a bundled fixture or contacts a server. */ +class DiscoverFixture(json: String, private val artPath: (String) -> String = { it }) { + private val profiles = JSONObject(json).getJSONArray("profiles").objects() + private val holdings = profiles.associate { profile -> + profile.getString("key") to listOf("movies", "shows", "anime").flatMap { profile.getJSONArray(it).objects() } + .map { identity(it) }.toSet() + } + private val pool = profiles.flatMap { it.getJSONArray("discover").objects() }.distinctBy(::identity).map(::title) + val services = listOf("Netflix", "Prime Video", "Disney+", "HBO Max", "Apple TV+", "Videoland", "Hulu", "NPO Start", "Crunchyroll") + .mapIndexed { index, name -> DiscoverService(index.toLong(), name, listOf(355f, 200f, 230f, 265f, 0f, 20f, 140f, 25f, 30f)[index]) } + + fun snapshot(person: String): DiscoverSnapshot { + val profile = profiles.first { it.getString("key") == person } + val picks = profile.getJSONArray("discover").objects().map(::title) + val played = listOf("movies", "shows", "anime").flatMap { profile.getJSONArray(it).objects() } + .filter { it.optBoolean("played") } + val groups = linkedMapOf>>() + picks.sortedBy { if (it.type == "movie") 0 else 1 }.forEach { pick -> + played.map { it to normalizedGenres(it.getJSONArray("genres").strings()).intersect(normalizedGenres(pick.genres)).size } + .filter { it.second > 0 }.sortedByDescending { it.second }.take(2).forEach { (seed, _) -> + groups.getOrPut(identity(seed)) { seed.getString("title") to mutableListOf() }.second.add(pick) + } + } + val lanes = services.mapIndexed { index, service -> + val source = rotate(pool, index * 7).sortedByDescending { it.year } + DiscoverLane("new-$index", "New on ${service.name}", source.take(12), fullTitles = source) + } + listOf(DiscoverLane("trending", "Trending", pool.take(20), pool.size, fullTitles = pool)) + + listOf("Comedy", "Animation", "Science Fiction", "Horror", "Romance").map { genre -> + val items = pool.filter { genre in normalizedGenres(it.genres) && (genre !in setOf("Science Fiction", "Horror", "Romance") || it.type == "movie") } + DiscoverLane("genre-$genre", genre, items.take(20), items.size, fullTitles = items) + } + return DiscoverSnapshot(picks, groups.values.map { DiscoverSeed(it.first, it.second.toList()) }, services, lanes, status = DiscoverStatus.Ready, reusesFixtureLanes = true) + } + + fun service(service: DiscoverService, type: String): List { + val base = rotate(pool.filter { it.type == type }, service.id.toInt() * 5) + val noun = if (type == "movie") "movies" else "series" + val genres = base.flatMap { it.genres }.groupingBy { it }.eachCount().entries.sortedByDescending { it.value }.take(3) + return listOf( + DiscoverLane("sv-pop", "Popular $noun", base.drop(1).take(20)), + DiscoverLane("sv-new", "New $noun", base.sortedByDescending { it.year }.take(20)), + DiscoverLane("sv-top", "Top rated $noun", rotate(base, 11).take(20)), + ) + genres.map { genre -> DiscoverLane("sv-g-${genre.key}", "${genre.key} $noun", base.filter { genre.key in it.genres }.take(20)) } + } + + fun find(name: String): DiscoverTitle = (pool + profiles.flatMap { p -> listOf("movies", "shows", "anime").flatMap { p.getJSONArray(it).objects() }.map(::title) }).first { it.title == name } + + private fun title(item: JSONObject): DiscoverTitle = DiscoverTitle( + tmdb = item.optLong("tmdbId", item.getString("id").hashCode().toLong().let { if (it < 0) -it else it }), + type = item.getString("type"), title = item.getString("title"), year = item.nullableInt("year"), + genres = item.getJSONArray("genres").strings(), overview = item.optString("overview"), + poster = item.nullableString("poster")?.let(artPath), backdrop = item.nullableString("backdrop")?.let(artPath), + posterOnly = item.optJSONObject("artwork")?.optString("backdropKind") == "poster_crop", + runtimeSeconds = item.nullableInt("runtimeSeconds"), seasonCount = item.nullableInt("seasonCount"), + ratings = ratings(item.optJSONObject("ratings")), heldBy = holdings.filterValues { identity(item) in it }.keys, + ) + + private fun ratings(json: JSONObject?): List = buildList { + if (json == null) return@buildList + for ((field, source, label) in listOf(Triple("rtCritics", "rtCritics", "rtCriticsLabel"), Triple("rtAudience", "rtAudience", "rtAudienceLabel"), Triple("imdb", "imdb", ""))) { + if (!json.isNull(field)) add(DiscoverRating(source, json.getDouble(field), json.nullableString(label))) + } + if (isEmpty() && !json.isNull("tmdb") && json.optInt("tmdbVotes") > 0) add(DiscoverRating("tmdb", json.getDouble("tmdb"))) + } + + private fun identity(json: JSONObject): String = "${json.getString("type")}:${json.optLong("tmdbId", json.getString("id").hashCode().toLong().let { if (it < 0) -it else it })}" + private fun rotate(items: List, offset: Int): List = if (items.isEmpty()) items else items.drop(offset % items.size) + items.take(offset % items.size) + private fun normalizedGenres(genres: List): Set = genres.flatMap { + when (it) { "Sci-Fi & Fantasy" -> listOf("Science Fiction", "Fantasy"); "Action & Adventure" -> listOf("Action", "Adventure"); "Sci-Fi" -> listOf("Science Fiction"); else -> listOf(it) } + }.toSet() +} + +private fun JSONArray.objects(): List = List(length()) { getJSONObject(it) } +private fun JSONArray.strings(): List = List(length()) { getString(it) } +private fun JSONObject.nullableString(key: String): String? = if (isNull(key)) null else optString(key).takeIf { it.isNotEmpty() } +private fun JSONObject.nullableInt(key: String): Int? = if (isNull(key)) null else getInt(key) diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverLaneProjection.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverLaneProjection.kt new file mode 100644 index 00000000..bda06cdc --- /dev/null +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverLaneProjection.kt @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-2.0-only +package org.justinvisser.wolfskin.design + +import kotlin.math.ceil + +/** Dropped seed groups must not suppress titles from the later catalogue lanes. */ +internal fun projectDiscoverLanes(snapshot: DiscoverSnapshot, heroes: List, filter: String, hidden: Map): List { + fun matches(item: DiscoverTitle) = filter == "all" || item.type == filter + val seen = heroes.mapTo(mutableSetOf()) { it.key } + val result = mutableListOf() + fun take(items: List, limit: Int) = items.filter { matches(it) && it.key !in hidden && it.key !in seen }.take(limit).filter { seen.add(it.key) } + if (snapshot.status == DiscoverStatus.Ready) { + val groups = snapshot.seeds.map { seed -> seed.copy(items = seed.items.filter { matches(it) && it.key !in hidden }) }.filter { it.items.isNotEmpty() }.take(3) + for ((type, name) in listOf("movie" to "Movies", "tv" to "Series")) { + val all = snapshot.picks.filter { it.type == type && matches(it) && it.key !in hidden } + val count = all.count { it.key !in seen } + val items = take(all, minOf(12, ceil(count.toDouble() / (groups.size + 1)).toInt())) + if (items.isNotEmpty()) result += DiscoverLane("pk-$type", "$name for you", items, all.size, pick = true, fullTitles = all) + } + val size = if (groups.isEmpty()) 0 else minOf(8, ceil(snapshot.picks.count { matches(it) && it.key !in seen && it.key !in hidden }.toDouble() / groups.size).toInt()) + groups.forEachIndexed { index, seed -> + val items = take(seed.items, size) + if (items.size >= 3) result += DiscoverLane("pk-w$index", "Because you watched ${seed.title}", items, seed.items.size, pick = true, fullTitles = seed.items) + } + } + val displayed = heroes.mapTo(mutableSetOf()) { it.key } + result.forEach { lane -> lane.titles.forEach { displayed.add(it.key) } } + if (snapshot.services.isNotEmpty()) result += DiscoverLane("svc", "Services", emptyList()) + snapshot.lanes.forEach { lane -> + val source = lane.fullTitles.filter { matches(it) && it.key !in displayed && it.key !in hidden } + val items = source.take(if (lane.id.startsWith("new-")) 12 else WolfskinTokens.Behaviour.rowLimit.toInt()) + if (items.isNotEmpty() || lane.tail == DiscoverTail.Retry || lane.tail == DiscoverTail.ShowMore) { + result += lane.copy(titles = items, fullTitles = source, total = if (lane.id.startsWith("new-")) items.size else source.size, + tail = if (lane.tail == DiscoverTail.Retry || lane.tail == DiscoverTail.ShowMore) lane.tail else if (!lane.id.startsWith("new-") && source.size > items.size) DiscoverTail.SeeAll else DiscoverTail.None) + if (!snapshot.reusesFixtureLanes) items.forEach { displayed.add(it.key) } + } + } + val hiddenItems = (snapshot.hidden + snapshot.picks).distinctBy { it.key }.filter { it.key in hidden && matches(it) } + if (hiddenItems.isNotEmpty()) result += DiscoverLane("hidden", "Hidden picks", hiddenItems, hidden = true) + return result +} diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverModel.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverModel.kt new file mode 100644 index 00000000..3fc3b296 --- /dev/null +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverModel.kt @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0-only +package org.justinvisser.wolfskin.design + +/** Catalogue identity is stable across lanes, filters, requests and dismissals. */ +data class DiscoverTitle( + val tmdb: Long, + val type: String, + val title: String, + val year: Int? = null, + val genres: List = emptyList(), + val overview: String = "", + val poster: String? = null, + val backdrop: String? = null, + val posterOnly: Boolean = false, + val runtimeSeconds: Int? = null, + val seasonCount: Int? = null, + val seasons: List? = null, + val ratings: List = emptyList(), + val heldBy: Set = emptySet(), + val score: Double = 0.0, +) { + val key: String get() = "$type:$tmdb" + val typeWord: String get() = if (type == "movie") "Movie" else "Series" +} + +/** Observed source marks keep labels separate from score values. */ +data class DiscoverRating(val source: String, val score: Double, val label: String? = null) + +/** A contribution group preserves feed order rather than assigning a visible rank. */ +data class DiscoverSeed(val title: String, val items: List) + +/** Services carry their catalogue id and a text wordmark, never third-party logos. */ +data class DiscoverService(val id: Long, val name: String, val hue: Float) + +enum class DiscoverStatus { Ready, NeedsSignIn, Unavailable, Loading, NoSeeds, PicksUnavailable } +enum class DiscoverTail { None, SeeAll, Retry, ShowMore } + +/** Provider data is immutable; screen focus never mutates source ordering. */ +data class DiscoverSnapshot( + val picks: List = emptyList(), + val seeds: List = emptyList(), + val services: List = emptyList(), + val lanes: List = emptyList(), + val hidden: List = emptyList(), + val status: DiscoverStatus = DiscoverStatus.NeedsSignIn, + val savedAt: String? = null, + val reusesFixtureLanes: Boolean = false, +) + +/** Lane identity survives lazy recycling and provider refreshes. */ +data class DiscoverLane( + val id: String, + val title: String, + val titles: List, + val total: Int = titles.size, + val tail: DiscoverTail = if (total > titles.size) DiscoverTail.SeeAll else DiscoverTail.None, + val pick: Boolean = false, + val hidden: Boolean = false, + val fullTitles: List = titles, +) + +/** Caller identity is deliberately absent: the authenticated client supplies the actor. */ +data class DiscoverRequest(val title: DiscoverTitle, val requester: String, val profile: String, val fromRecommendation: Boolean = true) + +/** Request outcomes distinguish uncertainty from an acknowledged receipt. */ +data class DiscoverRequestResult( + val phase: RequestPhase, + val actor: String? = null, + val stage: String = "Waiting", + val requester: String? = null, +) + +enum class RequestPhase { Choose, LoadingRecipients, RecipientsUnavailable, Sending, Receipt, NotConfirmed, Pending, Refused } + +/** Production adapters use the generated Conductarr client; fixture adapters cannot send HTTP. */ +interface DiscoverOperations { + suspend fun request(intent: DiscoverRequest): DiscoverRequestResult + suspend fun check(intent: DiscoverRequest): DiscoverRequestResult + suspend fun hide(profile: String, title: DiscoverTitle): Boolean + suspend fun restore(profile: String, title: DiscoverTitle): Boolean + suspend fun refresh(): DiscoverSnapshot +} diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverOverlay.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverOverlay.kt new file mode 100644 index 00000000..83cacb79 --- /dev/null +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverOverlay.kt @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-2.0-only +package org.justinvisser.wolfskin.design + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.dropShadow +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.graphics.shadow.Shadow +import androidx.compose.ui.unit.DpOffset +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.layout.layout +import androidx.tv.material3.Text +import org.justinvisser.wolfskin.design.WolfskinTokens.Color as C +import org.justinvisser.wolfskin.design.WolfskinTokens.Discover as D +import org.justinvisser.wolfskin.design.WolfskinTokens.Layout as L + +/** Sheets draw after the shell nav: their scrim consumes pointers and focus stays inside. */ +@Composable +fun DiscoverOverlay(state: DiscoverState) { + val overlay = state.overlay + if (overlay != null && !state.shell.pickerVisible) { + Box(Modifier.fillMaxSize().background(C.sheetScrim).clickable(onClick = {}).testTag("discover-overlay")) { + Column(Modifier.align(Alignment.CenterEnd).width(L.sheetWidth).fillMaxSize() + .dropShadow(RectangleShape, Shadow(radius = WolfskinTokens.Lift.sheetBlur, + color = Color.Black.copy(alpha = WolfskinTokens.Lift.sheetOpacity), + offset = DpOffset(WolfskinTokens.Lift.sheetX, WolfskinTokens.Shell.zero))) + .background(C.surface).padding(D.sheetPaddingX, D.sheetPaddingY), verticalArrangement = Arrangement.spacedBy(D.sheetGap)) { + when (overlay) { + is DiscoverOverlayState.Request -> RequestSheet(state, overlay.state) + is DiscoverOverlayState.Hidden -> { + ReceiptPanel("Hidden from your picks", "${overlay.title.title} leaves every row of ${state.shell.person.name}’s picks. It waits under Hidden picks at the end of Discover.") + Row(Modifier.padding(top = D.sheetButtonTop), horizontalArrangement = Arrangement.spacedBy(L.buttonGap)) { + DiscoverButton("Undo", overlay.focus == 0) { state.restore(overlay.title) } + DiscoverButton("Done", overlay.focus == 1, primary = false) { overlay.focus = 1; state.key(ShellKey.Ok) } + } + } + is DiscoverOverlayState.Restore -> { + Text(overlay.title.title, Modifier.discoverLineBox(DiscoverType.sheetTitle), color = C.fg, style = DiscoverType.sheetTitle) + Text("Hidden from ${state.shell.person.name}’s picks.", + Modifier.offset(y = D.sheetSubTop).padding(bottom = D.sheetSubGap + D.sheetSubTop).discoverLineBox(DiscoverType.body), + color = C.muted, style = DiscoverType.body) + Row(Modifier.padding(top = D.sheetButtonTop), horizontalArrangement = Arrangement.spacedBy(L.buttonGap)) { + DiscoverButton("Show again", overlay.focus == 0) { state.restore(overlay.title) } + DiscoverButton("Back", overlay.focus == 1, primary = false) { state.overlay = null } + } + } + } + } + } + } + state.toast?.let { text -> + Box(Modifier.offset(WolfskinTokens.Shell.stageWidth - L.safeSide - WolfskinTokens.Library.captionWidth, L.navBand).width(WolfskinTokens.Library.captionWidth).background(C.surface2, RoundedCornerShape(WolfskinTokens.Radius.panel)).padding(L.buttonGap)) { + Text(text, color = C.fg, style = DiscoverType.body) + } + } +} + +@Composable +private fun RequestSheet(state: DiscoverState, sheet: RequestSheetState) { + Text(if (sheet.held) "Add for someone" else "Request", color = C.fg, style = DiscoverType.sheetTitle) + Text("${sheet.title.title} · ${sheet.title.typeWord}${sheet.title.year?.let { " · $it" }.orEmpty()}", + Modifier.offset(y = D.sheetSubTop).padding(bottom = D.sheetSubGap + D.sheetSubTop), color = C.muted, style = DiscoverType.body) + if (state.isSignedIn) DiscoverRatings(sheet.title.ratings, Modifier.layout { measurable, constraints -> + val placeable = measurable.measure(constraints) + layout(placeable.width, placeable.height + (D.sheetRatingTop + D.sheetRatingBottom).roundToPx()) { + placeable.placeRelative(0, D.sheetRatingTop.roundToPx()) + } + }) + when (sheet.phase) { + RequestPhase.Choose, RequestPhase.Sending -> RequestChoices(state, sheet) + RequestPhase.LoadingRecipients -> { + Text("For", Modifier.padding(top = D.sheetLabelTop), color = C.dim, style = DiscoverType.label) + repeat(3) { + Row(Modifier.fillMaxWidth().height(L.sheetRow).background(C.listRowRest, RoundedCornerShape(WolfskinTokens.Radius.listRow)).padding(horizontal = D.sheetRowPadding), + horizontalArrangement = Arrangement.spacedBy(D.sheetPersonGap), verticalAlignment = Alignment.CenterVertically) { + Box(Modifier.size(D.sheetAvatar).background(C.surface, CircleShape)) + Box(Modifier.width(D.skeletonNameWidth).height(D.skeletonNameHeight).background(C.surface, RoundedCornerShape(WolfskinTokens.Shell.skeletonRadius))) + } + } + Box(Modifier.padding(top = D.sheetButtonTop)) { + DiscoverButton("Close", true, primary = false) { state.overlay = null } + } + } + else -> RequestOutcome(state, sheet) + } +} + +@Composable +private fun RequestChoices(state: DiscoverState, sheet: RequestSheetState) { + Text("For", Modifier.padding(top = D.sheetLabelTop), color = C.dim, style = DiscoverType.label) + sheet.people.forEachIndexed { index, person -> + val focused = sheet.focus == index + val disabled = person.key in sheet.title.heldBy + Row(Modifier.fillMaxWidth().height(L.sheetRow).alpha(if (disabled) .5f else 1f) + .focusScale(focused, WolfskinTokens.Focus.scaleRow) + .background(if (focused) C.fg else C.listRowRest, RoundedCornerShape(WolfskinTokens.Radius.listRow)) + .clickable(enabled = !disabled && sheet.phase == RequestPhase.Choose) { sheet.selected = index; sheet.focus = sheet.buttonIndex } + .padding(horizontal = D.sheetRowPadding), horizontalArrangement = Arrangement.spacedBy(D.sheetPersonGap), verticalAlignment = Alignment.CenterVertically) { + Avatar(person, D.sheetAvatar, WolfskinTokens.Type.avatarLetterSheetRow) + Text(person.name + if (person.key == sheet.viewer.key) " (you)" else "", Modifier.weight(1f), color = if (focused) C.onFocus else C.fg, style = DiscoverType.listTitle) + if (disabled) Text("In library", color = (if (focused) C.onFocus else C.fg).copy(alpha = D.reasonOpacity), style = DiscoverType.reason) + else Box(Modifier.size(D.sheetRadio).background(if (sheet.selected == index) C.accent else C.listRowRest.copy(alpha = 0f), CircleShape) + .border(if (sheet.selected != index || focused) D.sheetRadioRing else WolfskinTokens.Shell.zero, if (focused) C.onFocus.copy(alpha = .5f) else C.fg.copy(alpha = .35f), CircleShape), contentAlignment = Alignment.Center) { + if (sheet.selected == index) Box(Modifier.size(D.sheetRadioDot).background(C.onFocus, CircleShape)) + } + } + } + if (sheet.blocked) Text("Season details are unknown right now, so this series can’t be requested yet. Nothing was sent.", color = C.warn, style = DiscoverType.caption) + else if (sheet.title.type == "tv" && !sheet.held) Text("Whole series: every listed season. Other seasons may download too.", color = C.dim, style = DiscoverType.caption) + Box(Modifier.padding(top = D.sheetButtonTop)) { + DiscoverButton(if (sheet.blocked) "Close" else sheet.buttonLabel, sheet.focus == sheet.buttonIndex, glyph = if (sheet.blocked) null else "plus", primary = !sheet.blocked) { sheet.focus = sheet.buttonIndex; state.key(ShellKey.Ok) } + } +} + +@Composable +private fun RequestOutcome(state: DiscoverState, sheet: RequestSheetState) { + val phase = sheet.phase + val name = sheet.selectedPerson.name + val actor = sheet.result?.actor ?: state.shell.person.name + val text = when (phase) { + RequestPhase.Receipt -> "for ${if (sheet.selectedPerson.key == state.shell.person.key) "you" else name} · by $actor" + RequestPhase.NotConfirmed -> "The request service didn’t answer. Nothing is assumed filed." + RequestPhase.Pending -> "Seerr hasn’t confirmed it yet. Checking again looks first, so nothing is filed twice." + RequestPhase.Refused -> "$name can’t receive requests from this TV." + else -> "Conductarr didn’t say who this TV may request for. Nothing was sent." + } + val heading = when (phase) { + RequestPhase.Receipt -> if (sheet.held) "Adding to $name’s library" else "Requested" + RequestPhase.NotConfirmed -> "Not confirmed" + RequestPhase.Pending -> "Request unresolved" + RequestPhase.Refused -> "Refused" + else -> "Can’t read who can receive requests" + } + ReceiptPanel(heading, text, if (phase == RequestPhase.Receipt) C.fg else if (phase == RequestPhase.Refused) C.fail else C.warn, + if (phase == RequestPhase.Receipt) sheet.result?.stage ?: "Waiting" else null) + if (phase == RequestPhase.Receipt) Text("Follow it under Requests in Settings.", color = C.dim, style = DiscoverType.caption) + Row(Modifier.padding(top = D.sheetButtonTop), horizontalArrangement = Arrangement.spacedBy(L.buttonGap)) { + if (phase in setOf(RequestPhase.NotConfirmed, RequestPhase.Pending, RequestPhase.RecipientsUnavailable)) { + DiscoverButton(if (phase == RequestPhase.RecipientsUnavailable) "Try again" else "Check again", sheet.focus == sheet.buttonIndex) { sheet.focus = sheet.buttonIndex; state.key(ShellKey.Ok) } + DiscoverButton("Close", sheet.focus != sheet.buttonIndex, primary = false) { state.overlay = null } + } else DiscoverButton(if (phase == RequestPhase.Receipt) "Done" else "Close", true) { state.overlay = null } + } +} + +@Composable +private fun ReceiptPanel(title: String, text: String, color: androidx.compose.ui.graphics.Color = C.fg, stage: String? = null) { + Column(Modifier.fillMaxWidth().background(C.listRowRest, RoundedCornerShape(WolfskinTokens.Radius.panel)).padding(WolfskinTokens.Shell.panelPaddingX, WolfskinTokens.Shell.panelPaddingY), verticalArrangement = Arrangement.spacedBy(WolfskinTokens.Shell.panelGap)) { + Row(horizontalArrangement = Arrangement.spacedBy(L.badgeInset), verticalAlignment = Alignment.CenterVertically) { + Glyph(if (title == "Requested" || title == "Hidden from your picks" || title.startsWith("Adding")) "check" else if (title == "Request unresolved") "clock" else "alert", color, Modifier.size(WolfskinTokens.Shell.panelIcon)) + Text(title, color = color, style = DiscoverType.stateTitle) + } + Text(text, color = C.muted, style = DiscoverType.body) + if (stage != null) Row(horizontalArrangement = Arrangement.spacedBy(D.stageGap), verticalAlignment = Alignment.CenterVertically) { + val stageColor = when (stage) { "Ready" -> C.ok; "Failed" -> C.fail; "Downloading", "Almost" -> C.accent; else -> C.muted } + Glyph("clock", stageColor, Modifier.size(L.buttonIcon)) + Text(stage, color = stageColor, style = DiscoverType.label) + } + } +} diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverRatings.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverRatings.kt new file mode 100644 index 00000000..62a55b57 --- /dev/null +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverRatings.kt @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-only +package org.justinvisser.wolfskin.design + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.Alignment +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.ColorMatrix +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.painterResource +import androidx.tv.material3.Text +import java.util.Locale +import org.justinvisser.wolfskin.design.WolfskinTokens.Color as C + +/** Localized score formatting never infers a tomato label from its percentage. */ +fun formatDiscoverRating(rating: DiscoverRating, locale: Locale): String = + if (rating.source.startsWith("rt")) "${rating.score.toInt()}%" else String.format(locale, "%.1f", rating.score) + +/** Heroes, title destinations and sheets show every observed score without a dwell or truncation. */ +@Composable +fun DiscoverRatings(ratings: List, modifier: Modifier = Modifier) { + if (ratings.isEmpty()) return + val locale = LocalConfiguration.current.locales[0] + val labels = remember(ratings, locale) { ratings.map { formatDiscoverRating(it, locale) } } + val style = DiscoverType.ratings + val markSize = with(LocalDensity.current) { style.fontSize.toDp() } + val desaturated = remember { ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(0f) }) } + Row(modifier, horizontalArrangement = Arrangement.spacedBy(markSize * .7142857f), verticalAlignment = Alignment.CenterVertically) { + ratings.forEachIndexed { index, rating -> + val resource = when (rating.source) { + "rtCritics" -> if (rating.label == "Rotten") R.drawable.ic_rotten_tomatoes_rotten else R.drawable.ic_rotten_tomatoes_fresh + "rtAudience" -> if (rating.label == "Spilled") R.drawable.ic_rt_spilled else R.drawable.ic_rt_upright + "imdb" -> R.drawable.ic_rating_imdb + else -> R.drawable.ic_rating_tmdb + } + val muted = rating.source == "rtCritics" && rating.label != "Rotten" && rating.label != "Fresh" && rating.label != "Certified Fresh" + val wordmark = rating.source == "imdb" || rating.source == "tmdb" + Row(horizontalArrangement = Arrangement.spacedBy(markSize * .2857143f), verticalAlignment = Alignment.CenterVertically) { + Image(painterResource(resource), null, Modifier.height(markSize).width(markSize * if (wordmark) 1.8f else 1f), + alpha = if (muted) .55f else 1f, colorFilter = if (muted) desaturated else null) + Text(labels[index], color = C.fg, style = style, maxLines = 1) + } + } + } +} diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverScreen.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverScreen.kt new file mode 100644 index 00000000..68ac7436 --- /dev/null +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverScreen.kt @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: GPL-2.0-only +package org.justinvisser.wolfskin.design + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.text.style.TextOverflow +import androidx.tv.material3.Text +import org.justinvisser.wolfskin.design.WolfskinTokens.Color as C +import org.justinvisser.wolfskin.design.WolfskinTokens.Discover as D +import org.justinvisser.wolfskin.design.WolfskinTokens.Layout as L + +/** Discover uses the shell's real scroller, Back ladder and per-person focus memory. */ +@Composable +fun DiscoverScreen(state: DiscoverState, modifier: Modifier = Modifier) { + val shell = state.shell + val focus = remember(state) { object : ShellContentFocus by state {} } + DisposableEffect(focus) { + shell.contentFocus = focus + onDispose { if (shell.contentFocus === focus) { shell.contentFocus = null; shell.contentOverlayVisible = false } } + } + LaunchedEffect(state.toast) { if (state.toast != null) { kotlinx.coroutines.delay(WolfskinTokens.Motion.toast.toLong()); state.toast = null } } + val focusedTitle = if (state.collection != null) null else state.focusedLane?.titles?.getOrNull(state.column) + LaunchedEffect(focusedTitle?.key, state.contentFocused) { + state.plateKey = null + if (state.contentFocused && focusedTitle != null) { + if (focusedTitle.key !in state.observedRatings) kotlinx.coroutines.delay(WolfskinTokens.Motion.ratingDwell.toLong()) + state.observedRatings.add(focusedTitle.key); state.plateKey = focusedTitle.key + } + } + if (state.title != null) { DiscoverTitleDestination(state, modifier); return } + state.collection?.let { + LibraryScreen(shell, it, modifier, ratingsSignedIn = state.isSignedIn, offlineArt = LocalDiscoverArt.current, registerFocus = false) + return + } + if (state.snapshot.status == DiscoverStatus.Loading) { + val loading = remember(state) { LibraryGridState("Discover", emptyList(), catalogue = true).apply { status = LibraryStatus.Loading } } + LibraryScreen(shell, loading, modifier, offlineArt = LocalDiscoverArt.current, registerFocus = false) + return + } + val density = LocalDensity.current + val list = shell.view.listState + val note = state.snapshot.status in setOf(DiscoverStatus.NoSeeds, DiscoverStatus.PicksUnavailable) + val heights = remember(state.hasHero, state.lanes, note, state.canRequest) { + buildList { + add(if (state.hasHero) L.row_phero else L.row_lhead) + add(if (state.canRequest) L.row_chips else WolfskinTokens.Shell.zero) + if (!state.canRequest) add(L.row_state) else { + if (note) add(L.row_note) + state.lanes.forEach { lane -> add(L.rowHeadingBand + (if (lane.id == "svc") WolfskinTokens.Card.entryH else WolfskinTokens.Card.posterH + WolfskinTokens.Card.posterCaptionBand) + L.rowGapAfter) } + } + } + } + LaunchedEffect(state.focusedRow, shell.navFocused, heights) { + if (!shell.navFocused) { + val row = state.focusedRow + val physical = row + if (row >= state.firstShelf && note) 1 else 0 + var top = WolfskinTokens.Shell.zero + for (index in 0 until physical.coerceAtMost(heights.size)) top += heights[index] + val fits = top < D.anchorFitTop && top + heights[physical.coerceAtMost(heights.lastIndex)] <= WolfskinTokens.Shell.stageHeight - D.anchorBottomClearance + val target = if (row <= 1 || fits) 0f else with(density) { (top - L.pinnedLine).toPx() } + var current = WolfskinTokens.Shell.zero + for (index in 0 until list.firstVisibleItemIndex.coerceAtMost(heights.size)) current += heights[index] + animateFrameScroll(list, target - with(density) { current.toPx() } - list.firstVisibleItemScrollOffset) + } + } + Box(modifier.fillMaxSize().testTag("discover-screen")) { + if (state.hasHero && state.service == null && list.firstVisibleItemIndex == 0) state.hero?.let { DiscoverBackdrop(it, modifier = Modifier.graphicsLayer { translationY = -list.firstVisibleItemScrollOffset.toFloat() }) } + LazyColumn(state = list, contentPadding = PaddingValues(bottom = (WolfskinTokens.Shell.stageHeight - L.pinnedLine - heights.last()).coerceAtLeast(D.frameBottomMin)), modifier = Modifier.fillMaxSize()) { + item(key = "discover-header", contentType = if (state.hasHero && state.service == null) "hero" else "header") { + if (state.hasHero && state.service == null) DiscoverHero(state) else LibraryHeader(state.shell.person, state.service?.name ?: "Discover", if (state.service != null) "General interest" else "") + } + item(key = "discover-chips", contentType = "chips") { + if (state.snapshot.status !in setOf(DiscoverStatus.NeedsSignIn, DiscoverStatus.Unavailable, DiscoverStatus.Loading)) DiscoverChips(state) + } + if (state.snapshot.status in setOf(DiscoverStatus.NeedsSignIn, DiscoverStatus.Unavailable)) item(key = "discover-state", contentType = "state") { DiscoverStatePanel(state) } + else { + if (state.snapshot.status in setOf(DiscoverStatus.NoSeeds, DiscoverStatus.PicksUnavailable)) item(key = "discover-note", contentType = "note") { + Box(Modifier.height(L.row_note).fillMaxWidth()) { + Row(Modifier.offset(L.safeSide, D.noteTop), horizontalArrangement = Arrangement.spacedBy(D.noteGap), verticalAlignment = Alignment.CenterVertically) { + val color = if (state.snapshot.status == DiscoverStatus.NoSeeds) C.muted else C.warn + Glyph(if (state.snapshot.status == DiscoverStatus.NoSeeds) "eye" else "alert", color, Modifier.size(D.noteIcon)) + Text(if (state.snapshot.status == DiscoverStatus.NoSeeds) "Watch something to get picks for ${shell.person.name}." else "Picks unavailable right now.", color = color, style = DiscoverType.body) + } + } + } + itemsIndexed(state.lanes, key = { _, lane -> lane.id }, contentType = { _, _ -> "shelf" }) { index, lane -> DiscoverShelf(state, lane, index + state.firstShelf) } + } + } + } +} + +@Composable +private fun DiscoverHero(state: DiscoverState) { + val title = state.hero ?: return + Column(Modifier.fillMaxWidth().height(L.row_phero).padding(start = L.safeSide, bottom = L.heroCopyBottom), verticalArrangement = Arrangement.Bottom) { + Row(horizontalArrangement = Arrangement.spacedBy(WolfskinTokens.Card.posterGap), verticalAlignment = Alignment.CenterVertically) { + Glyph("seerr", C.accentText, Modifier.size(L.buttonIcon)) + Text(if (state.snapshot.status == DiscoverStatus.Ready) "For you" else "Trending", color = C.accentText, style = DiscoverType.label) + } + Spacer(Modifier.height(D.heroKickerGap)) + Text(title.title, Modifier.width(L.heroCopyWidth).testTag("discover-hero-title-box").discoverLineBox(DiscoverType.hero), color = C.fg, style = DiscoverType.hero, maxLines = 2, overflow = TextOverflow.Ellipsis) + Spacer(Modifier.height(D.heroTitleGap)) + Row(Modifier.height(D.heroFactsHeight), horizontalArrangement = Arrangement.spacedBy(D.heroFactsGap), verticalAlignment = Alignment.CenterVertically) { + Text("${title.typeWord}${title.year?.let { " · $it" }.orEmpty()}", color = C.muted, style = DiscoverType.body) + if (title.genres.isNotEmpty()) Text(title.genres.take(2).joinToString(", "), color = C.muted, style = DiscoverType.body) + if (state.isSignedIn) DiscoverRatings(title.ratings) + if (state.shell.person.key in title.heldBy) Text("In your library", color = C.accentText, style = DiscoverType.body) + } + Spacer(Modifier.height(D.heroMetaGap)) + Text(title.overview, Modifier.width(L.heroOverviewMax), color = C.muted, style = DiscoverType.body, maxLines = 2, overflow = TextOverflow.Ellipsis) + Spacer(Modifier.height(D.heroOverviewGap)) + Row(verticalAlignment = Alignment.CenterVertically) { + DiscoverButton("Details", state.contentFocused && state.focusedRow == 0, "about") { state.openTitle(title) } + Row(Modifier.padding(start = D.dotMargin), horizontalArrangement = Arrangement.spacedBy(D.dotGap), verticalAlignment = Alignment.CenterVertically) { + Glyph("left", C.fg.copy(alpha = if (state.contentFocused && state.focusedRow == 0) 1f else 0f), Modifier.size(D.dotChevron)) + state.heroes.forEachIndexed { index, _ -> Box(Modifier.size(if (index == state.slide) D.dotActive else D.dotSize, D.dotSize).background(if (index == state.slide) C.fg else C.fg.copy(alpha = .3f), CircleShape)) } + Glyph("right", C.fg.copy(alpha = if (state.contentFocused && state.focusedRow == 0) 1f else 0f), Modifier.size(D.dotChevron)) + } + } + } +} + +@Composable +private fun DiscoverChips(state: DiscoverState) { + Row(Modifier.fillMaxWidth().height(L.row_chips).padding(start = L.safeSide, top = D.chipTop), horizontalArrangement = Arrangement.spacedBy(D.chipGap)) { + (if (state.service == null) listOf("all" to "All", "movie" to "Movies", "tv" to "Series") else listOf("movie" to "Movies", "tv" to "Series")).forEachIndexed { index, (value, label) -> + val focused = state.contentFocused && state.focusedRow == 1 && state.column == index + Box(Modifier.height(D.chipHeight).focusScale(focused, WolfskinTokens.Focus.scaleControl) + .background(if (focused) C.fg else if (state.filter == value) C.accentSoft else C.chipRest, RoundedCornerShape(WolfskinTokens.Radius.chip)) + .border(if (!focused && state.filter == value) WolfskinTokens.Focus.ringCardRing / 2 else WolfskinTokens.Shell.zero, if (!focused && state.filter == value) C.accent else C.chipRest.copy(alpha = 0f), RoundedCornerShape(WolfskinTokens.Radius.chip)) + .clickable { state.focus(1, index); state.key(ShellKey.Ok) }.padding(horizontal = D.chipPadding), contentAlignment = Alignment.Center) { + Text(label, color = if (focused) C.onFocus else if (state.filter == value) C.accentText else C.fg, style = DiscoverType.label) + } + } + } +} + +@Composable +private fun DiscoverStatePanel(state: DiscoverState) { + val signIn = state.snapshot.status == DiscoverStatus.NeedsSignIn + Box(Modifier.fillMaxWidth().height(L.row_state)) { + Column(Modifier.padding(start = L.safeSide, top = D.stateTop).wrapContentHeight(Alignment.Top, unbounded = true).width(L.stateWidth) + .background(C.surface, RoundedCornerShape(WolfskinTokens.Radius.panel)) + .border(WolfskinTokens.Library.borderWidth, C.border, RoundedCornerShape(WolfskinTokens.Radius.panel)) + .padding(WolfskinTokens.Library.borderWidth).padding(D.statePaddingX, D.statePaddingY)) { + Row(horizontalArrangement = Arrangement.spacedBy(L.badgeInset), verticalAlignment = Alignment.CenterVertically) { + Glyph(if (signIn) "profile" else "alert", if (signIn) C.accentText else C.warn, Modifier.size(L.buttonIcon)) + Text(if (signIn) "Discover needs sign-in" else "Discover is resting", color = C.fg, style = DiscoverType.stateTitle) + } + Text(if (signIn) "Sign this TV in to Conductarr to see picks and request titles. Your library plays as usual." else "Conductarr can’t be reached. Your library still plays.", Modifier.padding(top = L.navBlobAvatarPad, bottom = D.stateTextGap), color = C.muted, style = DiscoverType.body) + DiscoverButton("Try again", state.contentFocused, primary = signIn) { state.refresh() } + } + } +} + +/** Hero and sheet buttons reuse the shell's scale and outside-ring primitives. */ +@Composable +fun DiscoverButton(label: String, focused: Boolean, glyph: String? = null, primary: Boolean = true, onClick: () -> Unit) { + Row(Modifier.height(L.buttonHeight).focusScale(focused, WolfskinTokens.Focus.scaleControl) + .focusRing(focused, WolfskinTokens.Radius.button, WolfskinTokens.Focus.ringButtonGap, WolfskinTokens.Focus.ringButtonRing, C.accent, FocusLift.Button) + .background(if (primary) C.fg else if (focused) C.buttonSecondaryFocused else C.buttonSecondary, RoundedCornerShape(WolfskinTokens.Radius.button)) + .clickable(onClick = onClick).padding(horizontal = L.buttonPaddingX), horizontalArrangement = Arrangement.spacedBy(L.badgeInset), verticalAlignment = Alignment.CenterVertically) { + if (glyph != null) Glyph(glyph, if (primary) C.onFocus else C.fg, Modifier.size(L.buttonIcon)) + Text(label, color = if (primary) C.onFocus else C.fg, style = DiscoverType.button, maxLines = 1) + } +} diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverShelf.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverShelf.kt new file mode 100644 index 00000000..8883d432 --- /dev/null +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverShelf.kt @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0-only +package org.justinvisser.wolfskin.design + +import androidx.compose.animation.core.tween +import androidx.compose.animation.core.CubicBezierEasing +import androidx.compose.foundation.gestures.animateScrollBy +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.zIndex +import androidx.tv.material3.Text +import org.justinvisser.wolfskin.design.WolfskinTokens.Card as K +import org.justinvisser.wolfskin.design.WolfskinTokens.Color as C +import org.justinvisser.wolfskin.design.WolfskinTokens.Discover as D +import org.justinvisser.wolfskin.design.WolfskinTokens.Layout as L + +/** Every shelf is lazy and keyed; the focused caption is outside the scaled art slot. */ +@Composable +fun DiscoverShelf(state: DiscoverState, lane: DiscoverLane, row: Int) { + val services = lane.id == "svc" + val height = if (services) K.entryH else K.posterH + val cards = rememberLazyListState() + val count = if (services) state.snapshot.services.size else lane.titles.size + if (lane.tail == DiscoverTail.None) 0 else 1 + val column = (state.shell.view.cards[lane.id] ?: 0).coerceIn(0, (count - 1).coerceAtLeast(0)) + val active = state.contentFocused && state.focusedRow == row + val slots = ((L.contentWidth + K.posterGap) / K.posterPitch).toInt() + val first = state.shell.view.cards["first:${lane.id}"] ?: 0 + var initialized by remember { mutableStateOf(false) } + LaunchedEffect(column, count) { + var target = first + if (column > target + slots - 2) target = column - (slots - 2) + if (column < target + 1) target = (column - 1).coerceAtLeast(0) + target = target.coerceIn(0, (count - slots).coerceAtLeast(0)) + state.shell.view.cards["first:${lane.id}"] = target + val offset = cards.layoutInfo.visibleItemsInfo.firstOrNull { it.index == target }?.offset + if (initialized && offset != null) { + if (offset != 0) cards.animateScrollBy(offset.toFloat(), tween(WolfskinTokens.Motion.base, easing = CubicBezierEasing(.25f, .1f, .25f, 1f))) + } else cards.scrollToItem(target) + initialized = true + } + Box(Modifier.fillMaxWidth().height(L.rowHeadingBand + height + (if (services) WolfskinTokens.Shell.zero else K.posterCaptionBand) + L.rowGapAfter).testTag("discover-lane-${lane.id}")) { + Text(lane.title, Modifier.offset(L.safeSide), color = C.fg, style = DiscoverType.rowHeading, maxLines = 1) + LazyRow(state = cards, userScrollEnabled = false, + contentPadding = PaddingValues(start = L.safeSide, end = WolfskinTokens.Shell.stageWidth - L.safeSide - (K.posterW * slots + K.posterGap * (slots - 1)), top = K.posterGap, bottom = K.posterGap), + horizontalArrangement = Arrangement.spacedBy(K.posterGap), + modifier = Modifier.offset(y = L.rowHeadingBand - K.posterGap).height(height + K.posterGap * 2)) { + if (services) itemsIndexed(state.snapshot.services, key = { _, service -> "service:${service.id}" }, contentType = { _, _ -> "entry" }) { index, service -> + Box(Modifier.size(K.entryW, K.entryH).focusScale(active && index == column, WolfskinTokens.Focus.scaleCard) + .focusRing(active && index == column, WolfskinTokens.Radius.card, WolfskinTokens.Focus.ringCardGap, WolfskinTokens.Focus.ringCardRing, C.accent, FocusLift.Card) + .background(Brush.linearGradient(listOf(Color.hsl(service.hue, .30f, .26f), Color.hsl(service.hue, .30f, .12f))), RoundedCornerShape(WolfskinTokens.Radius.card)) + .border(WolfskinTokens.Library.borderWidth, C.border.copy(alpha = D.entryBorderOpacity), RoundedCornerShape(WolfskinTokens.Radius.card)) + .clickable { state.focus(row, index); state.openService(service) }, contentAlignment = Alignment.BottomStart) { + Text(service.name, Modifier.padding(start = D.entryPaddingX, bottom = D.entryPaddingY), style = DiscoverType.entry, color = C.fg) + } + } else { + itemsIndexed(lane.titles, key = { _, item -> item.key }, contentType = { _, _ -> "poster" }) { index, item -> + DiscoverPoster(state, item, active && index == column, Modifier.clickable { + state.focus(row, index) + if (lane.hidden) state.overlay = DiscoverOverlayState.Restore(item) else state.openTitle(item) + }) + } + if (lane.tail != DiscoverTail.None) item(key = "tail:${lane.id}", contentType = if (lane.tail == DiscoverTail.SeeAll) "seeAll" else "tail") { + DiscoverTailCard(lane, active && column == lane.titles.size) { state.focus(row, lane.titles.size); state.key(ShellKey.Ok) } + } + } + } + if (!services && active) { + val item = lane.titles.getOrNull(column) + Caption( + item?.title ?: if (lane.tail == DiscoverTail.SeeAll) "See all ${lane.title}" else if (lane.tail == DiscoverTail.Retry) "Try again" else "Show more", + if (item != null) buildDiscoverCaptionFacts(item, state, lane.hidden) else if (lane.tail == DiscoverTail.SeeAll) "${lane.total} titles" else if (lane.tail == DiscoverTail.Retry) "${lane.title} stopped loading" else "The last read only repeated titles", + true, Modifier.offset(L.safeSide + K.posterPitch * (column - first), L.rowHeadingBand + K.posterH + L.captionOffset).width(WolfskinTokens.Library.captionWidth), + accentFirstFact = lane.hidden, + accentLastFact = item != null && (item.heldBy.isNotEmpty() || state.requests[item.key].orEmpty().isNotEmpty()), + ) + } + } +} + +/** Only the focused poster reads the ratings plate; cached observations paint immediately. */ +@Composable +fun DiscoverPoster(state: DiscoverState, title: DiscoverTitle, focused: Boolean, modifier: Modifier = Modifier) { + val receipts = state.requests[title.key] + val card = remember(title, receipts, state.shell.person.key) { title.libraryCard(state) } + LibraryPoster(card, focused, focused && state.plateKey == title.key && state.isSignedIn, true, + modifier.zIndex(if (focused) 1f else 0f), offlineArt = LocalDiscoverArt.current) +} + +@Composable +internal fun DiscoverTailCard(lane: DiscoverLane, focused: Boolean, onClick: () -> Unit) { + if (lane.tail == DiscoverTail.SeeAll) LibrarySeeAllCard(lane.total, focused, onClick) + else CatalogueRecoveryCard(lane.tail == DiscoverTail.Retry, focused, onClick) +} + +internal fun buildDiscoverCaptionFacts(title: DiscoverTitle, state: DiscoverState, hidden: Boolean): String { + val card = title.libraryCard(state) + return (if (hidden) "Hidden from your picks · " else "") + card.facts(true) +} diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverState.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverState.kt new file mode 100644 index 00000000..23b11148 --- /dev/null +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverState.kt @@ -0,0 +1,339 @@ +// SPDX-License-Identifier: GPL-2.0-only +package org.justinvisser.wolfskin.design + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateMapOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock + +/** One person owns a Discover view; the shell retains its root list and card memory. */ +class DiscoverState( + val shell: ShellState, + initial: DiscoverSnapshot, + private val operations: DiscoverOperations, + private val scope: CoroutineScope, + private val onPush: (String) -> Unit = {}, + private val onPop: () -> Unit = {}, + private val serviceLanes: (DiscoverService, String) -> List = { _, _ -> emptyList() }, +) : ShellContentFocus { + var snapshot by mutableStateOf(initial) + private set + private var serviceFilter by mutableStateOf("movie") + var filter: String + get() = if (service != null) serviceFilter else DISCOVER_FILTERS[shell.rememberedView("discover").cards["filter"] ?: 0] + set(value) { + if (service != null) serviceFilter = value + else shell.rememberedView("discover").cards["filter"] = DISCOVER_FILTERS.indexOf(value) + } + var slide: Int + get() = (shell.rememberedView("discover").cards["slide"] ?: 0).coerceIn(0, (heroes.size - 1).coerceAtLeast(0)) + set(value) { shell.rememberedView("discover").cards["slide"] = value } + var title by mutableStateOf(null) + var service by mutableStateOf(null) + var collection by mutableStateOf(null) + var titleButton by mutableStateOf(0) + private var activeOverlay by mutableStateOf(null) + var overlay: DiscoverOverlayState? + get() = activeOverlay + set(value) { activeOverlay = value; shell.contentOverlayVisible = value != null } + var toast by mutableStateOf(null) + val observedRatings = mutableSetOf() + var plateKey by mutableStateOf(null) + val requests = mutableStateMapOf>() + private val requestSheets = mutableMapOf, RequestSheetState>() + private val hidden = mutableStateMapOf().apply { initial.hidden.forEach { put(it.key, it) } } + private lateinit var collectionCards: LinkedHashMap + private var revision by mutableStateOf(0) + private var cachedRevision = -1 + private var cachedFilter = "" + private var cachedService: DiscoverService? = null + private var cachedLanes = emptyList() + private var cachedHeroesRevision = -1 + private var cachedHeroesFilter = "" + private var cachedHeroes = emptyList() + private val dismissalMutex = Mutex() + private val dismissalVersions = mutableMapOf() + private var rootFilter = "all" + + val isSignedIn: Boolean get() = snapshot.status != DiscoverStatus.NeedsSignIn + val canRequest: Boolean get() = when (snapshot.status) { + DiscoverStatus.Ready, DiscoverStatus.NoSeeds, DiscoverStatus.PicksUnavailable -> true + else -> false + } + val hasHero: Boolean get() = service == null && collection == null && heroes.isNotEmpty() && canRequest + val heroes: List get() { + if (cachedHeroesRevision != revision || cachedHeroesFilter != filter) { + val source = if (snapshot.status == DiscoverStatus.Ready) snapshot.picks else snapshot.lanes.firstOrNull { it.id == "trending" }?.titles.orEmpty() + cachedHeroes = source.asSequence().filter { it.key !in hidden && matches(it) } + .sortedWith(compareByDescending { it.score }.thenBy { if (it.type == "movie") 0 else 1 }) + .take(if (snapshot.status == DiscoverStatus.Ready) 5 else 1).toList() + cachedHeroesRevision = revision; cachedHeroesFilter = filter + } + return cachedHeroes + } + val hero: DiscoverTitle? get() = heroes.getOrNull(slide) + val lanes: List get() { + if (cachedRevision != revision || cachedFilter != filter || cachedService != service) { + cachedLanes = buildLanes(); cachedRevision = revision; cachedFilter = filter; cachedService = service + } + return cachedLanes + } + val firstShelf: Int get() = 2 + val rowCount: Int get() = firstShelf + lanes.size + val focusedRow: Int get() = shell.view.row.coerceIn(0, (rowCount - 1).coerceAtLeast(0)) + val focusedLane: DiscoverLane? get() = lanes.getOrNull(focusedRow - firstShelf) + val column: Int get() = shell.view.cards[rowKey].orZero().coerceAtMost(maxColumn()) + val rowKey: String get() = when (focusedRow) { 0 -> "phero"; 1 -> "dchips"; else -> focusedLane?.id.orEmpty() } + val isCard: Boolean get() = title == null && focusedRow >= firstShelf && focusedLane != null + val contentFocused: Boolean get() = !shell.navFocused && !shell.pickerVisible && overlay == null + override val cardKey: String? get() = when { + overlay != null || title != null -> null + collection != null -> collection?.cardKey + isCard -> "$rowKey:$column" + else -> null + } + + fun focus(row: Int, column: Int = 0) { + shell.navFocused = false; shell.view.row = row + shell.view.cards[rowKey] = column.coerceIn(0, maxColumn()) + } + + private fun moveRow(direction: Int) { + val target = focusedRow + direction + if (target < if (hasHero) 0 else 1) { shell.focusNav(shell.currentTab); return } + if (target >= rowCount) return + val key = when (target) { 0 -> "phero"; 1 -> "dchips"; else -> lanes[target - firstShelf].id } + val nearest = if (focusedRow >= firstShelf && target >= firstShelf) + column - shell.view.cards["first:$rowKey"].orZero() + shell.view.cards["first:$key"].orZero() + else 0 + focus(target, shell.view.cards[key] ?: nearest.coerceAtLeast(0)) + } + + fun openTitle(item: DiscoverTitle) { + title = item; titleButton = 0 + if (isSignedIn && item.ratings.isNotEmpty()) observedRatings.add(item.key) + onPush("discover-title/${item.key}") + } + fun openCollection(lane: DiscoverLane) { + var titles = lane.fullTitles.associateBy { it.key } + collectionCards = LinkedHashMap(titles.size).apply { + titles.values.forEach { put(it.key, it.libraryCard(this@DiscoverState)) } + } + lateinit var grid: LibraryGridState + grid = LibraryGridState(lane.title, collectionCards.values.filter { it.id !in hidden }, catalogue = true, observedRatings = observedRatings, + onTitle = { openTitle(titles.getValue(it.id)) }, onDiscover = { collection = null; onPop() }, onRecover = { + scope.launch { + snapshot = operations.refresh(); revision++ + if (collection !== grid) return@launch + val fresh = lanes.firstOrNull { it.id == lane.id } + if (canRequest && fresh != null) { + titles = fresh.fullTitles.associateBy { it.key } + collectionCards = LinkedHashMap(titles.size).apply { + titles.values.forEach { put(it.key, it.libraryCard(this@DiscoverState)) } + } + grid.tail = when (fresh.tail) { DiscoverTail.Retry -> GridTail.Retry; DiscoverTail.ShowMore -> GridTail.ShowMore; else -> GridTail.None } + updateCollectionCards() + } else grid.tail = GridTail.Retry + } + }).apply { + tail = when (lane.tail) { DiscoverTail.Retry -> GridTail.Retry; DiscoverTail.ShowMore -> GridTail.ShowMore; else -> GridTail.None } + } + collection = grid + onPush("discover-see-all/${lane.id}") + } + + private fun updateCollectionCards(changed: DiscoverTitle? = null) { + val grid = collection ?: return + val focused = title?.key ?: grid.focusedTitle?.id + if (changed != null && changed.key in collectionCards) collectionCards[changed.key] = changed.libraryCard(this) + grid.source = collectionCards.values.filter { it.id !in hidden } + val restored = grid.items.indexOfFirst { it.id == focused } + grid.index = if (restored >= 0) restored else grid.index.coerceIn(0, + if (grid.tail == GridTail.None) grid.items.lastIndex.coerceAtLeast(0) else grid.tailIndex) + } + fun isHidden(item: DiscoverTitle): Boolean = item.key in hidden + fun isPick(item: DiscoverTitle): Boolean = snapshot.picks.any { it.key == item.key } || isHidden(item) + + fun openService(item: DiscoverService) { + rootFilter = filter; service = item; filter = "movie"; revision++ + onPush("discover-service/${item.id}") + focus(firstShelf) + } + + fun enterRoute(route: String) { + if (route == "discover") { title = null; collection = null; if (service != null) filter = rootFilter; service = null } + else if (route.startsWith("discover-service") || route.startsWith("discover-see-all")) title = null + } + + /** Card hold/release handling belongs to the shell, including the shared catalogue grid. */ + override fun key(key: ShellKey): Boolean { + if (overlay != null) { overlayKey(key); return true } + if (shell.route != "discover" && !shell.route.startsWith("discover-")) return false + if (snapshot.status == DiscoverStatus.Loading) { shell.focusNav(shell.currentTab); return true } + if (title == null) collection?.let { + if (key == ShellKey.Back) { collection = null; onPop(); return true } + return it.key(key) + } + if (key == ShellKey.Back) { + if (title != null) { title = null; onPop() } + else if (service != null) { service = null; filter = rootFilter; revision++; onPop() } + else shell.focusNav(shell.currentTab) + return true + } + if (title != null) { titleKey(key); return true } + if (!canRequest) { + if (key == ShellKey.Up) shell.focusNav(shell.currentTab) else if (key == ShellKey.Ok) refresh() + return true + } + when (key) { + ShellKey.Up -> moveRow(-1) + ShellKey.Down -> moveRow(1) + ShellKey.Left, ShellKey.Right -> { + val direction = if (key == ShellKey.Left) -1 else 1 + if (focusedRow == 0) slide = (slide + direction).coerceIn(0, (heroes.size - 1).coerceAtLeast(0)) + else shell.view.cards[rowKey] = (column + direction).coerceIn(0, maxColumn()) + } + ShellKey.Ok -> when { + isCard -> activateCard() + focusedRow == 0 -> hero?.let(::openTitle) + focusedRow == 1 -> { filter = (if (service == null) DISCOVER_FILTERS else listOf("movie", "tv"))[column]; if (service == null) slide = 0; revision++ } + } + } + return true + } + + private fun maxColumn(): Int = when (focusedRow) { + 0 -> heroes.lastIndex + 1 -> if (service == null) 2 else 1 + else -> if (focusedLane?.id == "svc") snapshot.services.lastIndex else focusedLane?.let { it.titles.size - 1 + if (it.tail != DiscoverTail.None) 1 else 0 } ?: 0 + }.coerceAtLeast(0) + + private fun activateCard() { + val lane = focusedLane ?: return + if (lane.id == "svc") { snapshot.services.getOrNull(column)?.let(::openService); return } + val item = lane.titles.getOrNull(column) + if (item == null) { + if (lane.tail in setOf(DiscoverTail.Retry, DiscoverTail.ShowMore)) refresh() + else openCollection(lane) + } else if (lane.hidden) overlay = DiscoverOverlayState.Restore(item) + else openTitle(item) + } + + private fun titleKey(key: ShellKey) { + if (!canRequest) { + if (key == ShellKey.Up) shell.focusNav(shell.currentTab) else if (key == ShellKey.Ok) refresh() + return + } + when (key) { + ShellKey.Up -> shell.focusNav(shell.currentTab) + ShellKey.Left -> titleButton = (titleButton - 1).coerceAtLeast(0) + ShellKey.Right -> titleButton = (titleButton + 1).coerceAtMost(if (title?.let(::isPick) == true) 2 else 1) + ShellKey.Ok -> when (titleButton) { + 0 -> if (requests[requireNotNull(title).key]?.any { it.requester == shell.person.key } == true) shell.requestsVisible = true else openRequest(false) + 1 -> openRequest(true) + 2 -> title?.let { if (it.key in hidden) restore(it) else hide(it) } + } + else -> Unit + } + } + + fun openRequest(other: Boolean, item: DiscoverTitle = requireNotNull(title), phase: RequestPhase = RequestPhase.Choose) { + if (!canRequest) { refresh(); return } + val choice = RequestSheetState(item, shell.person, shell.people, other, phase) + val previous = requestSheets[item.key to choice.selectedPerson.key] + overlay = DiscoverOverlayState.Request( + if (phase == RequestPhase.Choose && previous?.phase in setOf(RequestPhase.Sending, RequestPhase.NotConfirmed, RequestPhase.Pending)) requireNotNull(previous) else choice, + ) + } + + private fun overlayKey(key: ShellKey) { + when (val sheet = overlay) { + is DiscoverOverlayState.Request -> sheet.state.key(key, ::send, { overlay = null }) { + scope.launch { + snapshot = operations.refresh(); revision++ + sheet.state.phase = if (snapshot.status == DiscoverStatus.Ready) RequestPhase.Choose else RequestPhase.RecipientsUnavailable + sheet.state.focus = sheet.state.buttonIndex + } + } + is DiscoverOverlayState.Hidden -> when (key) { + ShellKey.Left, ShellKey.Up -> sheet.focus = 0 + ShellKey.Right, ShellKey.Down -> sheet.focus = 1 + ShellKey.Back -> overlay = null + ShellKey.Ok -> if (sheet.focus == 0) restore(sheet.title) else { overlay = null; title = null; onPop() } + } + is DiscoverOverlayState.Restore -> when (key) { + ShellKey.Left, ShellKey.Up -> sheet.focus = 0 + ShellKey.Right, ShellKey.Down -> sheet.focus = 1 + ShellKey.Back -> overlay = null + ShellKey.Ok -> if (sheet.focus == 0) restore(sheet.title) else overlay = null + } + null -> Unit + } + } + + private fun send(sheet: RequestSheetState, check: Boolean) { + if (!canRequest || (!check && !sheet.canSubmit)) return + if (!check) { + val previous = requestSheets[sheet.title.key to sheet.selectedPerson.key] + if (previous?.phase in setOf(RequestPhase.Sending, RequestPhase.NotConfirmed, RequestPhase.Pending)) { + overlay = DiscoverOverlayState.Request(requireNotNull(previous)) + return + } + } + val intent = DiscoverRequest(sheet.title, sheet.selectedPerson.key, sheet.viewer.key, isPick(sheet.title)) + requestSheets[sheet.title.key to sheet.selectedPerson.key] = sheet + sheet.phase = RequestPhase.Sending + scope.launch { + val result = (if (check) operations.check(intent) else operations.request(intent)).copy(requester = intent.requester) + sheet.result = result; sheet.phase = result.phase; sheet.focus = sheet.buttonIndex + if (result.phase == RequestPhase.Receipt) { + requests[sheet.title.key] = buildList { + requests[sheet.title.key].orEmpty().forEach { if (it.requester != result.requester) add(it) } + add(result) + } + updateCollectionCards(sheet.title) + } + } + } + + fun hide(item: DiscoverTitle) { + if (!canRequest) { refresh(); return } + hidden[item.key] = item; revision++; overlay = DiscoverOverlayState.Hidden(item) + updateCollectionCards() + val profile = shell.person.key + val version = (dismissalVersions[item.key] ?: 0) + 1 + dismissalVersions[item.key] = version + scope.launch { + val success = dismissalMutex.withLock { operations.hide(profile, item) } + if (!success && dismissalVersions[item.key] == version) { hidden.remove(item.key); revision++; updateCollectionCards(); overlay = null; toast = "Couldn’t hide this pick. Try again." } + } + } + + fun restore(item: DiscoverTitle) { + if (!canRequest) { refresh(); return } + hidden.remove(item.key); revision++; overlay = null; toast = "Back in your picks." + updateCollectionCards() + val profile = shell.person.key + val version = (dismissalVersions[item.key] ?: 0) + 1 + dismissalVersions[item.key] = version + scope.launch { + val success = dismissalMutex.withLock { operations.restore(profile, item) } + if (!success && dismissalVersions[item.key] == version) { hidden[item.key] = item; revision++; updateCollectionCards(); toast = "Couldn’t restore this pick. Try again." } + } + } + + fun refresh() { scope.launch { snapshot = operations.refresh(); revision++; toast = if (snapshot.status == DiscoverStatus.Unavailable) "Still can’t reach it. The last saved view stays on screen." else null } } + private fun matches(item: DiscoverTitle): Boolean = filter == "all" || item.type == filter + + private fun buildLanes(): List = service?.let { + serviceLanes(it, filter).filter { lane -> lane.titles.isNotEmpty() } + } ?: projectDiscoverLanes(snapshot, heroes, filter, hidden) +} + +private fun Int?.orZero(): Int = this ?: 0 + +private val DISCOVER_FILTERS = listOf("all", "movie", "tv") diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverTitleDestination.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverTitleDestination.kt new file mode 100644 index 00000000..81247975 --- /dev/null +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverTitleDestination.kt @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0-only +package org.justinvisser.wolfskin.design + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.tv.material3.Text +import org.justinvisser.wolfskin.design.WolfskinTokens.Color as C +import org.justinvisser.wolfskin.design.WolfskinTokens.Discover as D +import org.justinvisser.wolfskin.design.WolfskinTokens.Layout as L + +/** Explicit destination stub exposes only the Discover-owned request and pick actions. */ +@Composable +fun DiscoverTitleDestination(state: DiscoverState, modifier: Modifier = Modifier) { + val title = state.title ?: return + val focused = state.contentFocused + Box(modifier.fillMaxSize().testTag("discover-title-destination-stub")) { + DiscoverBackdrop(title, detail = true) + Column(Modifier.offset(L.safeSide, L.detailCopyTop).width(L.detailCopyWidth)) { + Text(title.title, color = C.fg, style = WolfskinTokens.Type.displayDetail, maxLines = 2) + Spacer(Modifier.height(D.heroTitleGap)) + Text("${title.typeWord}${title.year?.let { " · $it" }.orEmpty()} · ${title.genres.take(2).joinToString(", ")}", color = C.muted, style = WolfskinTokens.Type.body) + Spacer(Modifier.height(D.heroMetaGap)) + if (state.isSignedIn) DiscoverRatings(title.ratings) + Spacer(Modifier.height(D.heroMetaGap)) + Text(title.overview, color = C.muted, style = WolfskinTokens.Type.body, maxLines = 3) + Spacer(Modifier.height(D.heroOverviewGap)) + Row(horizontalArrangement = Arrangement.spacedBy(L.buttonGap)) { + if (!state.canRequest) DiscoverButton("Try again", focused) { state.refresh() } else { + val receipt = state.requests[title.key]?.firstOrNull { it.requester == state.shell.person.key } + DiscoverButton(if (receipt == null) "Request" else "Requested · ${receipt.stage}", focused && state.titleButton == 0, glyph = if (receipt == null) "plus" else "clock") { + state.titleButton = 0; state.key(ShellKey.Ok) + } + DiscoverButton(if (state.shell.person.key in title.heldBy) "Add for someone" else "For someone else", focused && state.titleButton == 1, glyph = "profile", primary = false) { state.openRequest(true) } + if (state.isPick(title)) DiscoverButton(if (state.isHidden(title)) "Show again" else "Not interested", focused && state.titleButton == 2, glyph = if (state.isHidden(title)) "undo" else "hide", primary = false) { state.titleButton = 2; state.key(ShellKey.Ok) } + } + } + } + } +} diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverType.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverType.kt new file mode 100644 index 00000000..45df9669 --- /dev/null +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverType.kt @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-2.0-only +package org.justinvisser.wolfskin.design + +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.FirstBaseline +import androidx.compose.ui.layout.LastBaseline +import androidx.compose.ui.layout.layout +import androidx.compose.ui.text.TextStyle +import kotlin.math.roundToInt +import androidx.compose.ui.text.PlatformTextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.LineHeightStyle + +/** CSS line boxes survive native first/last-line trimming only within Discover's text stacks. */ +internal object DiscoverType { + private val lineBox = LineHeightStyle(LineHeightStyle.Alignment.Center, LineHeightStyle.Trim.None) + private val platform = PlatformTextStyle(includeFontPadding = false) + val hero = WolfskinTokens.Type.displayHero.copy(lineHeightStyle = lineBox, platformStyle = platform) + val body = WolfskinTokens.Type.body.copy(lineHeightStyle = lineBox, platformStyle = platform) + val label = WolfskinTokens.Type.label.copy(lineHeightStyle = lineBox, platformStyle = platform) + val button = WolfskinTokens.Type.button.copy(lineHeightStyle = lineBox, platformStyle = platform) + val sheetTitle = WolfskinTokens.Type.sheetTitle.copy(lineHeightStyle = lineBox, platformStyle = platform) + val stateTitle = WolfskinTokens.Type.stateTitle.copy(fontWeight = FontWeight.Bold, lineHeightStyle = lineBox, platformStyle = platform) + val listTitle = WolfskinTokens.Type.listTitle.copy(lineHeightStyle = lineBox, platformStyle = platform) + val caption = WolfskinTokens.Type.caption.copy(lineHeightStyle = lineBox, platformStyle = platform) + val reason = WolfskinTokens.Type.caption.copy(fontWeight = FontWeight.Medium, lineHeightStyle = lineBox, platformStyle = platform) + val rowHeading = WolfskinTokens.Type.rowHeading.copy(lineHeightStyle = lineBox, platformStyle = platform) + val entry = WolfskinTokens.Discover.entryText.copy(lineHeightStyle = lineBox, platformStyle = platform) + val ratings = WolfskinTokens.Type.ratingPlate.copy(fontFeatureSettings = "tnum", lineHeightStyle = lineBox, platformStyle = platform) +} + +/** Paragraph overflow must not expand the CSS block height. */ +internal fun Modifier.discoverLineBox(style: TextStyle): Modifier = layout { measurable, constraints -> + val paragraph = measurable.measure(constraints) + val lineHeight = style.lineHeight.toPx() + val lines = ((paragraph[LastBaseline] - paragraph[FirstBaseline]) / lineHeight).roundToInt() + 1 + val height = (lines * lineHeight).roundToInt().coerceIn(constraints.minHeight, constraints.maxHeight) + layout(paragraph.width, height) { + paragraph.placeRelative(0, (height - paragraph.height) / 2) + } +} diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/Focus.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/Focus.kt index 14a1cc76..ddb37ea7 100644 --- a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/Focus.kt +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/Focus.kt @@ -5,6 +5,10 @@ import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.tween import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.ui.draw.dropShadow +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.CornerRadius @@ -25,16 +29,31 @@ fun Modifier.focusScale(focused: Boolean, scale: Float): Modifier { return graphicsLayer { scaleX = animated; scaleY = animated } } +/** Content lifts are distinct from the white person/gear rings. */ +enum class FocusLift { None, Card, Button } + /** Draw the ring outside the slot; neither the gap nor the ring obscures content. */ @Composable -fun Modifier.focusRing(focused: Boolean, radius: Dp, gap: Dp, width: Dp, color: Color): Modifier { - val alpha by animateFloatAsState(if (focused) 1f else 0f, +fun Modifier.focusRing(focused: Boolean, radius: Dp, gap: Dp, width: Dp, color: Color, lift: FocusLift = FocusLift.None): Modifier { + val ringAlpha by animateFloatAsState(if (focused) 1f else 0f, tween(WolfskinTokens.Motion.fast, easing = CubicBezierEasing(.25f, .1f, .25f, 1f)), label = "focus-ring") - return drawBehind { - if (alpha > 0f) { + val shadow = if (lift == FocusLift.None) this else { + val shape = remember(radius) { RoundedCornerShape(radius) } + val density = LocalDensity.current + val blur = with(density) { (if (lift == FocusLift.Card) WolfskinTokens.Lift.cardBlur else WolfskinTokens.Lift.buttonBlur).toPx() } + val shift = with(density) { (if (lift == FocusLift.Card) WolfskinTokens.Lift.cardY else WolfskinTokens.Lift.buttonY).toPx() } + dropShadow(shape) { + this.radius = blur + offset = Offset(0f, shift) + this.color = Color.Black + alpha = ringAlpha * if (lift == FocusLift.Card) WolfskinTokens.Lift.cardOpacity else WolfskinTokens.Lift.buttonOpacity + } + } + return shadow.drawBehind { + if (ringAlpha > 0f) { val outer = (gap + width).toPx() val inner = gap.toPx() - drawRoundRect(color.copy(alpha = color.alpha * alpha), Offset(-outer, -outer), Size(size.width + 2 * outer, size.height + 2 * outer), CornerRadius(radius.toPx() + outer)) + drawRoundRect(color.copy(alpha = color.alpha * ringAlpha), Offset(-outer, -outer), Size(size.width + 2 * outer, size.height + 2 * outer), CornerRadius(radius.toPx() + outer)) drawRoundRect(WolfskinTokens.Color.bg, Offset(-inner, -inner), Size(size.width + 2 * inner, size.height + 2 * inner), CornerRadius(radius.toPx() + inner)) } } diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/FrameScroll.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/FrameScroll.kt new file mode 100644 index 00000000..18b83e5f --- /dev/null +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/FrameScroll.kt @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0-only +package org.justinvisser.wolfskin.design + +import androidx.compose.animation.core.CubicBezierEasing +import androidx.compose.animation.core.animate +import androidx.compose.animation.core.tween +import androidx.compose.foundation.lazy.LazyListState + +/** Every frame follows focus with the same bounded scroll tween. */ +internal suspend fun animateFrameScroll(list: LazyListState, distance: Float) { + if (distance == 0f) return + list.scroll { + var previous = 0f + animate(0f, distance, animationSpec = tween(WolfskinTokens.Motion.scroll, + easing = CubicBezierEasing(.33f, 1f, .68f, 1f))) { value, _ -> + scrollBy(value - previous) + previous = value + } + } +} diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/Glyph.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/Glyph.kt index 244d60b1..0149a0b0 100644 --- a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/Glyph.kt +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/Glyph.kt @@ -20,6 +20,13 @@ fun Glyph(name: String, color: Color, modifier: Modifier = Modifier) { "sparkle", "seerr" -> R.drawable.wolfskin_sparkle "advanced" -> R.drawable.wolfskin_sort "alert" -> R.drawable.wolfskin_alert + "plus" -> R.drawable.wolfskin_plus + "check" -> R.drawable.wolfskin_check + "clock" -> R.drawable.wolfskin_clock + "hide" -> R.drawable.wolfskin_hide + "undo" -> R.drawable.wolfskin_undo + "left" -> R.drawable.wolfskin_left + "download" -> R.drawable.wolfskin_download "film" -> R.drawable.wolfskin_film "eye" -> R.drawable.wolfskin_eye "down" -> R.drawable.wolfskin_down diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/LibraryGridState.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/LibraryGridState.kt index e782be6f..9be8b1f1 100644 --- a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/LibraryGridState.kt +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/LibraryGridState.kt @@ -72,6 +72,7 @@ class LibraryGridState( val onRecover: () -> Unit = {}, val onDiscover: () -> Unit = {}, val onReadAhead: (Int) -> Unit = {}, + val observedRatings: MutableSet = mutableSetOf(), ) : ShellContentFocus { var source by mutableStateOf(items) var status by mutableStateOf(LibraryStatus.Ready) diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/LibraryImages.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/LibraryImages.kt index 609a5dc6..05914387 100644 --- a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/LibraryImages.kt +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/LibraryImages.kt @@ -26,7 +26,7 @@ object LibraryImages { .components { add(Interceptor { chain -> reads.withPermit { chain.proceed() } }) } .crossfade(false).build().also { loader = it } - fun request(context: Context, path: String, width: Int = WolfskinTokens.Image.posterRequestWidth, + fun request(context: Context, data: Any?, width: Int = WolfskinTokens.Image.posterRequestWidth, height: Int = WolfskinTokens.Image.posterRequestHeight, poster: Boolean = true): ImageRequest = - ImageRequest.Builder(context).data(path).size(width, height).allowRgb565(poster).crossfade(false).build() + ImageRequest.Builder(context).data(data).size(width, height).allowRgb565(poster).crossfade(false).build() } diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/LibraryPoster.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/LibraryPoster.kt index dc1d57f8..e7cb553b 100644 --- a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/LibraryPoster.kt +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/LibraryPoster.kt @@ -32,7 +32,7 @@ fun LibraryPoster( val shape = RoundedCornerShape(WolfskinTokens.Radius.card) Box(modifier.size(WolfskinTokens.Card.posterW, WolfskinTokens.Card.posterH) .focusScale(focused, WolfskinTokens.Focus.scaleCard) - .focusRing(focused, WolfskinTokens.Radius.card, WolfskinTokens.Focus.ringCardGap, WolfskinTokens.Focus.ringCardRing, C.accent) + .focusRing(focused, WolfskinTokens.Radius.card, WolfskinTokens.Focus.ringCardGap, WolfskinTokens.Focus.ringCardRing, C.accent, FocusLift.Card) .testTag("poster-${title.id}")) { Box(Modifier.fillMaxSize().clip(shape).background(C.surface2)) { val bitmap = remember(poster, offlineArt) { poster?.let { offlineArt?.invoke(it) } } @@ -53,12 +53,15 @@ fun LibraryPoster( Box(Modifier.fillMaxHeight().fillMaxWidth(progress).background(C.accent, CircleShape)) } } - if (catalogue && title.badge != null) Box(Modifier.align(Alignment.BottomEnd).padding(WolfskinTokens.Layout.badgeInset) - .size(WolfskinTokens.Layout.badge).background(if (title.badge == "held") C.ok else C.badgeRest, CircleShape) - .then(if (title.badge != "held") Modifier.border(L.badgeBorder, if (title.badge == "failed") C.fail else C.accent, CircleShape) else Modifier), - contentAlignment = Alignment.Center) { - Glyph(if (title.badge == "held") "check" else if (title.badge == "failed") "alert" else "clock", - if (title.badge == "held") C.onFocus else if (title.badge == "failed") C.fail else C.accent, Modifier.size(L.badgeIcon)) + if (catalogue && title.badge != null) { + val held = title.badge == "held" + val color = if (title.badge == "failed") C.fail else C.accent + Box(Modifier.align(Alignment.BottomEnd).padding(WolfskinTokens.Layout.badgeInset) + .size(WolfskinTokens.Layout.badge).background(if (held) C.ok else C.badgeRest, CircleShape) + .then(if (held) Modifier else Modifier.border(L.badgeRing, color, CircleShape)), contentAlignment = Alignment.Center) { + Glyph(if (held) "check" else if (title.badge == "failed") "alert" else "clock", + if (held) C.onFocus else color, Modifier.size(L.badgeIcon)) + } } } } diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/LibraryScreen.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/LibraryScreen.kt index 34c1e116..f9ada462 100644 --- a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/LibraryScreen.kt +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/LibraryScreen.kt @@ -5,7 +5,6 @@ import androidx.compose.foundation.border import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.withStyle -import androidx.compose.ui.text.style.TextAlign import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn @@ -40,6 +39,7 @@ fun LibraryScreen( cachedRatings: Set = emptySet(), ratingsReachable: Boolean = true, offlineArt: ((String) -> ImageBitmap?)? = null, + registerFocus: Boolean = true, ) { val view = shell.view val density = LocalDensity.current @@ -47,10 +47,11 @@ fun LibraryScreen( val active = !shell.navFocused && !shell.pickerVisible val items = grid.items val focused = grid.focusedTitle + val count = if (grid.catalogue) items.size else grid.source.size var plateKey by remember { mutableStateOf(null) } - DisposableEffect(shell, grid) { - shell.contentFocus = grid - onDispose { if (shell.contentFocus === grid) shell.contentFocus = null } + DisposableEffect(shell, grid, registerFocus) { + if (registerFocus) shell.contentFocus = grid + onDispose { if (registerFocus && shell.contentFocus === grid) shell.contentFocus = null } } LaunchedEffect(grid) { var revision = grid.pageRevision @@ -69,8 +70,9 @@ fun LibraryScreen( } LaunchedEffect(focused?.id, active, ratingsSignedIn, ratingsReachable) { plateKey = null - if (active && ratingsSignedIn && focused != null && (ratingsReachable || focused.id in cachedRatings)) { - if (focused.id !in cachedRatings) delay(WolfskinTokens.Motion.ratingDwell.toLong()) + if (active && ratingsSignedIn && focused != null && (ratingsReachable || focused.id in cachedRatings || focused.id in grid.observedRatings)) { + if (focused.id !in cachedRatings && focused.id !in grid.observedRatings) delay(WolfskinTokens.Motion.ratingDwell.toLong()) + if (focused.ratings.isNotEmpty()) grid.observedRatings.add(focused.id) plateKey = focused.id } } @@ -102,7 +104,11 @@ fun LibraryScreen( } } }, contentPadding = PaddingValues(bottom = WolfskinTokens.Shell.stageHeight - L.pinnedLine - rowHeight)) { - item(key = "header", contentType = "header") { LibraryHeader(grid, shell.person) } + item(key = "header", contentType = "header") { + LibraryHeader(shell.person, grid.title, + if (grid.status != LibraryStatus.Ready) "" else "$count ${if (grid.catalogue) "titles" else if (grid.title == "Movies") if (count == 1) "film" else "films" else "series"}", + grid.savedAt) + } if (headerCount == 2) item(key = "toolbar", contentType = "chips") { LibraryToolbar(grid, active) } when (grid.status) { LibraryStatus.Loading -> items(3, key = { "skeleton-$it" }, contentType = { "grid" }) { @@ -155,15 +161,15 @@ fun LibraryScreen( } } +/** Library, catalogue and service headers share one baseline and person treatment. */ @Composable -private fun LibraryHeader(grid: LibraryGridState, person: ShellPerson) { - val count = if (grid.catalogue) grid.items.size else grid.source.size +fun LibraryHeader(person: ShellPerson, title: String, subtitle: String = "", savedAt: String? = null) { Box(Modifier.fillMaxWidth().height(L.row_lhead)) { Row(Modifier.offset(L.safeSide, L.navBand), horizontalArrangement = Arrangement.spacedBy(WolfskinTokens.Shell.settingsHeaderGap), verticalAlignment = Alignment.CenterVertically) { Avatar(person, WolfskinTokens.Shell.settingsHeaderAvatar, WolfskinTokens.Type.avatarLetterLibHeader, letterColor = C.muted) - Text(grid.title, modifier = Modifier.alignByBaseline(), color = C.fg, style = WolfskinTokens.Type.screenTitle) - Text(if (grid.status != LibraryStatus.Ready) "" else "$count ${if (grid.catalogue) "titles" else if (grid.title == "Movies") if (count == 1) "film" else "films" else "series"}", modifier = Modifier.alignByBaseline(), color = C.muted, style = WolfskinTokens.Type.nav.copy(fontWeight = FontWeight.Normal)) - grid.savedAt?.let { Text("Saved $it", color = C.warn, style = WolfskinTokens.Type.caption) } + Text(title, modifier = Modifier.alignByBaseline(), color = C.fg, style = WolfskinTokens.Type.screenTitle) + if (subtitle.isNotEmpty()) Text(subtitle, modifier = Modifier.alignByBaseline(), color = C.muted, style = G.headerSubtitle) + savedAt?.let { Text("Saved $it", color = C.warn, style = WolfskinTokens.Type.caption) } } } } @@ -222,15 +228,7 @@ private fun LibraryRecovery(grid: LibraryGridState, focused: Boolean) { private fun LibraryTail(grid: LibraryGridState, focused: Boolean) { val retry = grid.tail == GridTail.Retry Box(Modifier.fillMaxWidth().height(K.posterH + K.posterCaptionBand + L.gridRowGap)) { - Column(Modifier.offset(L.safeSide).size(K.posterW, K.posterH).focusScale(focused, WolfskinTokens.Focus.scaleCard) - .focusRing(focused, WolfskinTokens.Radius.card, WolfskinTokens.Focus.ringCardGap, WolfskinTokens.Focus.ringCardRing, C.accent) - .background(C.surface, RoundedCornerShape(WolfskinTokens.Radius.card)).border(G.borderWidth, C.border, RoundedCornerShape(WolfskinTokens.Radius.card)) - .clickable { grid.index = grid.tailIndex; grid.key(ShellKey.Ok) }.padding(G.tailPadding), - verticalArrangement = Arrangement.spacedBy(G.tailGap, Alignment.CenterVertically), horizontalAlignment = Alignment.CenterHorizontally) { - Glyph(if (retry) "alert" else "down", if (retry) C.warn else C.accentText, Modifier.size(G.tailIcon)) - Text(if (retry) "Try again" else "Show more", color = C.fg, style = WolfskinTokens.Type.button, textAlign = TextAlign.Center) - Text(if (retry) "More titles couldn’t load" else "Only titles already shown came back", color = C.muted, style = WolfskinTokens.Type.caption, textAlign = TextAlign.Center) - } + CatalogueRecoveryCard(retry, focused, { grid.index = grid.tailIndex; grid.key(ShellKey.Ok) }, Modifier.offset(L.safeSide)) Caption(if (retry) "Try again" else "Show more", if (retry) "${grid.title} stopped loading" else "The last read only repeated titles", focused, Modifier.offset(L.safeSide, K.posterH + L.captionOffset).width(G.captionWidth)) } @@ -260,15 +258,5 @@ private suspend fun animateLibraryScroll(view: RememberedView, headerCount: Int, if (first < headerCount) WolfskinTokens.Shell.zero else (if (headerCount == 2) L.row_chips else WolfskinTokens.Shell.zero) + rowHeight * (first - headerCount)).toPx() } + view.listState.firstVisibleItemScrollOffset - val distance = target - current - if (distance == 0f) return - view.listState.scroll { - var previous = 0f - androidx.compose.animation.core.animate(0f, distance, - animationSpec = androidx.compose.animation.core.tween(WolfskinTokens.Motion.scroll, - easing = androidx.compose.animation.core.CubicBezierEasing(.33f, 1f, .68f, 1f))) { value, _ -> - scrollBy(value - previous) - previous = value - } - } + animateFrameScroll(view.listState, target - current) } diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/LibrarySeeAllCard.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/LibrarySeeAllCard.kt index 4f4e6a83..cba21b48 100644 --- a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/LibrarySeeAllCard.kt +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/LibrarySeeAllCard.kt @@ -17,9 +17,9 @@ fun LibrarySeeAllCard(count: Int, focused: Boolean, onClick: () -> Unit, modifie width: Dp = WolfskinTokens.Card.posterW, height: Dp = WolfskinTokens.Card.posterH) { val shape = RoundedCornerShape(WolfskinTokens.Radius.card) Column(modifier.size(width, height).focusScale(focused, WolfskinTokens.Focus.scaleCard) - .focusRing(focused, WolfskinTokens.Radius.card, WolfskinTokens.Focus.ringCardGap, WolfskinTokens.Focus.ringCardRing, WolfskinTokens.Color.accent) + .focusRing(focused, WolfskinTokens.Radius.card, WolfskinTokens.Focus.ringCardGap, WolfskinTokens.Focus.ringCardRing, WolfskinTokens.Color.accent, FocusLift.Card) .background(WolfskinTokens.Color.surface, shape).border(WolfskinTokens.Library.borderWidth, WolfskinTokens.Color.border, shape) - .clickable(onClick = onClick), verticalArrangement = Arrangement.spacedBy(WolfskinTokens.Library.tailGap, Alignment.CenterVertically), horizontalAlignment = Alignment.CenterHorizontally) { + .clickable(onClick = onClick), verticalArrangement = Arrangement.spacedBy(WolfskinTokens.Library.seeAllGap, Alignment.CenterVertically), horizontalAlignment = Alignment.CenterHorizontally) { Glyph("interface", WolfskinTokens.Color.accent, Modifier.size(WolfskinTokens.Layout.badge)) Text("See all", color = WolfskinTokens.Color.fg, style = WolfskinTokens.Type.listTitle) Text(count.toString(), color = WolfskinTokens.Color.muted, style = WolfskinTokens.Type.label) diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/RequestSheetState.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/RequestSheetState.kt new file mode 100644 index 00000000..6e9bd517 --- /dev/null +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/RequestSheetState.kt @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-only +package org.justinvisser.wolfskin.design + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue + +/** Overlay focus is independent of the screen below and never reaches the navigation bar. */ +sealed interface DiscoverOverlayState { + class Request(val state: RequestSheetState) : DiscoverOverlayState + class Hidden(val title: DiscoverTitle) : DiscoverOverlayState { var focus by mutableStateOf(0) } + class Restore(val title: DiscoverTitle) : DiscoverOverlayState { var focus by mutableStateOf(0) } +} + +/** Request selection cannot supply an actor and cannot submit an unknown-season series. */ +class RequestSheetState( + val title: DiscoverTitle, + val viewer: ShellPerson, + people: List, + other: Boolean, + initialPhase: RequestPhase = RequestPhase.Choose, +) { + val people = listOf(viewer) + people.filter { it.key != viewer.key } + val held = viewer.key in title.heldBy + val blocked = title.type == "tv" && !held && title.seasons.isNullOrEmpty() + val buttonIndex = this.people.size + var selected by mutableStateOf(if (other || held) this.people.indexOfFirst { it.key != viewer.key && it.key !in title.heldBy }.coerceAtLeast(0) else 0) + var phase by mutableStateOf(initialPhase) + var focus by mutableStateOf(if (initialPhase != RequestPhase.Choose || blocked || !other) buttonIndex else selected) + var result by mutableStateOf(null) + val selectedPerson: ShellPerson get() = people[selected] + val canSubmit: Boolean get() = phase == RequestPhase.Choose && !blocked && selectedPerson.key !in title.heldBy + val buttonLabel: String get() = when { + held -> "Add to ${selectedPerson.name}’s library" + selectedPerson.key == viewer.key -> "Request" + else -> "Request for ${selectedPerson.name}" + } + + fun key(key: ShellKey, send: (RequestSheetState, Boolean) -> Unit, close: () -> Unit, retryRecipients: () -> Unit) { + if (key == ShellKey.Back) { close(); return } + when (phase) { + RequestPhase.Choose -> { + val order = people.indices.filter { people[it].key !in title.heldBy } + buttonIndex + val index = order.indexOf(focus) + when (key) { + ShellKey.Up -> focus = order[(index - 1).coerceAtLeast(0)] + ShellKey.Down -> focus = order[(index + 1).coerceAtMost(order.lastIndex)] + ShellKey.Ok -> if (focus == buttonIndex) { if (blocked) close() else if (canSubmit) send(this, false) } + else { selected = focus; focus = buttonIndex } + else -> Unit + } + } + RequestPhase.Sending -> Unit + RequestPhase.LoadingRecipients -> if (key == ShellKey.Ok) close() + RequestPhase.Receipt, RequestPhase.Refused -> if (key == ShellKey.Ok) close() + RequestPhase.NotConfirmed, RequestPhase.Pending, RequestPhase.RecipientsUnavailable -> { + when (key) { + ShellKey.Left, ShellKey.Up -> focus = buttonIndex + ShellKey.Right, ShellKey.Down -> focus = buttonIndex + 1 + ShellKey.Ok -> if (focus != buttonIndex) close() else if (phase == RequestPhase.RecipientsUnavailable) retryRecipients() else send(this, true) + else -> Unit + } + } + } + } +} diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/SettingsList.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/SettingsList.kt index 91bf3bb3..48a8220b 100644 --- a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/SettingsList.kt +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/SettingsList.kt @@ -52,7 +52,7 @@ val SETTINGS_GROUPS = listOf( SettingsGroup("advanced", "Advanced settings", "Ratings display · Backdrop style · Playback backend (ExoPlayer, MPV) · Max resolution and bitrate · Skip segments · Logs and image cache"), ) -/** Requests is intentionally a no-data Wolfskin entry until the Requests slice. */ +/** The household request count shares the sheet's snapshot and excludes Ready. */ @Composable fun SettingsList(state: ShellState, modifier: Modifier = Modifier, openRequests: Int? = null) { val list = state.view.listState @@ -70,14 +70,14 @@ fun SettingsList(state: ShellState, modifier: Modifier = Modifier, openRequests: item(key = "settings-header") { Row(Modifier.height(L.row_lhead).padding(start = L.safeSide, top = S.settingsHeaderTop), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(S.settingsHeaderGap)) { - Avatar(state.person, S.settingsHeaderAvatar, WolfskinTokens.Type.avatarLetterLibHeader) + Avatar(state.person, S.settingsHeaderAvatar, WolfskinTokens.Type.avatarLetterLibHeader, letterColor = C.muted) Text("Settings", Modifier.alignByBaseline(), style = WolfskinTokens.Type.screenTitle, color = C.fg) Text("Requests and Wholphin settings on this TV · ${state.person.name}", Modifier.alignByBaseline(), style = WolfskinTokens.Type.nav.copy(fontWeight = FontWeight.Normal), color = C.muted) } } itemsIndexed(SETTINGS_GROUPS, key = { _, group -> group.key }) { index, group -> Box(Modifier.height(L.row_srow)) { - SettingsRow(group, !state.navFocused && index == state.settingsRow, + SettingsRow(group, !state.navFocused && !state.requestsVisible && index == state.settingsRow, if (group.key == "requests") openRequests else null, Modifier.offset(L.safeSide, S.settingsRowTop)) { state.navFocused = false; state.settingsRow = index; state.key(ShellKey.Ok) diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/ShellState.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/ShellState.kt index 78b636cd..1f4f1270 100644 --- a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/ShellState.kt +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/ShellState.kt @@ -16,6 +16,8 @@ sealed interface ShellAction { data class HandOff(val group: String) : ShellAction data object Pop : ShellAction data object RetryPeople : ShellAction + data object RetryRequests : ShellAction + data class RequestTitle(val request: ComingInRequest) : ShellAction } /** D-pad inputs are shared by Android events and deterministic JVM replays. */ @@ -55,8 +57,11 @@ class ShellState( var peopleStatus by mutableStateOf(PeopleStatus.Ready) var recoveryIndex by mutableStateOf(0) var requestsVisible by mutableStateOf(false) + val requests = ComingInState() + private var requestReturnRoute: String? = null var handOff by mutableStateOf(null) var settingsRow by mutableStateOf(0) + var contentOverlayVisible by mutableStateOf(false) private var rootRoute by mutableStateOf("home") private var pickerReturnRoute = "home" private val views = mutableStateMapOf, RememberedView>() @@ -66,11 +71,19 @@ class ShellState( val currentTab: Int get() = tabs.indexOf(rootRoute).coerceAtLeast(0) val view: RememberedView get() = views.getOrPut(person.key to route) { RememberedView() } + /** Screen options share the same per-person memory as their root's focus and scroller. */ + fun rememberedView(destination: String): RememberedView = + views.getOrPut(person.key to destination) { RememberedView() } + /** External intents and upstream pops remain authoritative over the screen. */ fun enterDestination(destination: String) { route = destination if (destination in tabs) rootRoute = destination handOff = null + if (requestReturnRoute == destination) { + requestsVisible = true + requestReturnRoute = null + } } /** Pointer navigation uses the same transitions as D-pad selection. */ @@ -78,7 +91,12 @@ class ShellState( fun focusPerson(index: Int) { pickerIndex = index; gearFocused = false } /** Repeats are consumed, and a row activation is cancelled when focus moves. */ - fun key(key: ShellKey, down: Boolean = true, repeat: Int = 0, card: String? = if (!pickerVisible && !navFocused && !requestsVisible) contentFocus?.cardKey else null): Boolean { + fun key(key: ShellKey, down: Boolean = true, repeat: Int = 0, card: String? = if (!pickerVisible && !navFocused && !requestsVisible && !contentOverlayVisible) contentFocus?.cardKey else null): Boolean { + if (key == ShellKey.Ok && repeat > 0) return true + if (!pickerVisible && !requestsVisible && contentOverlayVisible) { + if (down) contentFocus?.key(key) + return true + } if (key == ShellKey.Ok) { if (repeat > 0) return true if (card != null) { @@ -94,7 +112,12 @@ class ShellState( armedCard = null } if (requestsVisible) { - if (key == ShellKey.Back || key == ShellKey.Ok) requestsVisible = false + when { + key == ShellKey.Back -> closeRequests() + key == ShellKey.Ok && requests.snapshot.status == ComingInStatus.Unavailable && requests.snapshot.items.isEmpty() -> retryRequests() + key == ShellKey.Ok && requests.snapshot.status == ComingInStatus.Loading && requests.snapshot.items.isEmpty() -> closeRequests() + requests.key(key) -> activateRequest() + } return true } if (pickerVisible) pickerKey(key) else if (key == ShellKey.Back) back() else if (navFocused) navKey(key) else contentKey(key) @@ -143,6 +166,8 @@ class ShellState( navFocused = false navIndex = 0 rootRoute = "home" + requestReturnRoute = null + requestsVisible = false views[person.key to route] = RememberedView() onAction(ShellAction.Person(selected)) } @@ -181,6 +206,7 @@ class ShellState( gearFocused = false } else { val target = tabs[navIndex] + requestReturnRoute = null if (target == route) views[person.key to target] = RememberedView() route = target rootRoute = target @@ -189,7 +215,7 @@ class ShellState( } } else if (route == "settings") { val group = SETTINGS_GROUPS[settingsRow].key - if (group == "requests") requestsVisible = true else { + if (group == "requests") openRequests() else { handOff = group onAction(ShellAction.HandOff(group)) } @@ -197,8 +223,34 @@ class ShellState( else contentFocus?.key(ShellKey.Ok) } + /** Requested buttons may name an existing request without changing its authenticated actor. */ + fun openRequests(requestId: String? = null) { + requests.focus(requestId ?: requests.snapshot.ordered.firstOrNull()?.id) + requestsVisible = true + navFocused = false + } + + fun closeRequests() { requestsVisible = false } + fun retryRequests() { onAction(ShellAction.RetryRequests) } + + fun activateRequest() { + val request = requests.focused ?: return + requestReturnRoute = route + requestsVisible = false + onAction(ShellAction.RequestTitle(request)) + } + /** Back never discards a root view, including the card below a focused nav. */ fun back() { + if (requestsVisible) { closeRequests(); return } + val returnRoute = requestReturnRoute + if (returnRoute != null && route != returnRoute) { + route = returnRoute + requestsVisible = true + requestReturnRoute = null + onAction(ShellAction.Pop) + return + } if (!pickerVisible && !navFocused && contentFocus?.key(ShellKey.Back) == true) return if (route == "settings") { route = pickerReturnRoute diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/ShellSurface.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/ShellSurface.kt index 8765ec54..f3aa275f 100644 --- a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/ShellSurface.kt +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/ShellSurface.kt @@ -9,8 +9,6 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.offset -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember @@ -59,6 +57,7 @@ fun ShellSurface( handlesContentKeys: Boolean = true, settingsOpenRequests: Int? = null, savedAt: String? = null, + overlay: @Composable () -> Unit = {}, content: @Composable (Modifier) -> Unit = { ShellPlaceholder(state.route) }, ) { val focus = remember { FocusRequester() } @@ -86,20 +85,10 @@ fun ShellSurface( if (state.pickerVisible) { WhosWatching(state) } else { - if (state.route == "settings") SettingsList(state, openRequests = settingsOpenRequests) else content(Modifier.fillMaxSize().focusRequester(contentFocus).focusRestorer().focusGroup()) + if (state.route == "settings") SettingsList(state, openRequests = settingsOpenRequests ?: state.requests.snapshot.openCount) else content(Modifier.fillMaxSize().focusRequester(contentFocus).focusRestorer().focusGroup()) NavBar(state, state.view.listState.firstVisibleItemIndex > 0 || state.view.listState.firstVisibleItemScrollOffset > L.navSolidAfter.value * 2, savedAt = savedAt) - if (state.requestsVisible) { - Box(Modifier.fillMaxSize().background(C.sheetScrim)) { - Column(Modifier.offset(x = WolfskinTokens.Shell.stageWidth - L.sheetWidth) - .width(L.sheetWidth).fillMaxSize().background(C.surface).padding(L.safeSide), - verticalArrangement = Arrangement.spacedBy(L.buttonGap)) { - Text("Requests", color = C.fg, style = WolfskinTokens.Type.sheetTitle) - Text("Wolfskin screen", color = C.accentText, style = WolfskinTokens.Type.label) - Text("No request data yet. Conductarr sign-in is not available on this TV.", color = C.muted, style = WolfskinTokens.Type.body) - PickerButton("Close", true, true) { state.requestsVisible = false } - } - } - } + if (state.requestsVisible) ComingInSheet(state) + overlay() } } } diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/WhosWatching.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/WhosWatching.kt index 1bef6689..df3eb325 100644 --- a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/WhosWatching.kt +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/WhosWatching.kt @@ -105,7 +105,7 @@ private fun RecoveryPicker(state: ShellState) { fun PickerButton(label: String, focused: Boolean, primary: Boolean, onClick: () -> Unit) { val background = if (primary) C.fg else if (focused) C.buttonSecondaryFocused else C.buttonSecondary Box(Modifier.height(L.buttonHeight).focusScale(focused, WolfskinTokens.Focus.scaleControl) - .focusRing(focused, WolfskinTokens.Radius.button, WolfskinTokens.Focus.ringButtonGap, WolfskinTokens.Focus.ringButtonRing, C.accent) + .focusRing(focused, WolfskinTokens.Radius.button, WolfskinTokens.Focus.ringButtonGap, WolfskinTokens.Focus.ringButtonRing, C.accent, FocusLift.Button) .background(background, RoundedCornerShape(WolfskinTokens.Radius.button)).clickable(onClick = onClick) .padding(horizontal = L.buttonPaddingX), contentAlignment = Alignment.Center) { Text(label, color = if (primary) C.onFocus else C.fg, style = WolfskinTokens.Type.button) diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/WolfskinTokens.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/WolfskinTokens.kt index b9d03090..d23a79bb 100644 --- a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/WolfskinTokens.kt +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/WolfskinTokens.kt @@ -246,6 +246,50 @@ object WolfskinTokens { const val requestsPollSeconds = 30.0f const val comingInVisibleRows = 5.0f } + object Requests { + val sheetPaddingY = 40.0.dp + val sheetPaddingX = 32.0.dp + val sheetGap = 9.0.dp + val sheetTitleLineHeight = 28.0.dp + val sheetTitleBaseline = 22.0.dp + val titleLineHeight = 19.5.dp + val titleBaseline = 15.5.dp + val factsLineHeight = 15.6.dp + val factsBaseline = 12.0.dp + val stageLineHeight = 19.8.dp + val stageLabelLineHeight = 16.9.dp + val stageBaseline = 12.0.dp + val emptyMargin = 10.0.dp + val rowPaddingY = 7.0.dp + val rowPaddingX = 9.0.dp + val rowGap = 12.0.dp + val listGap = 3.0.dp + val stageMargin = 2.0.dp + val stageGlyph = 14.0.dp + val stageGap = 5.0.dp + val readinessGapX = 11.0.dp + val readinessGapY = 5.0.dp + val readinessMargin = 4.0.dp + val readinessGlyph = 13.0.dp + val chevron = 15.0.dp + const val chevronAlpha = 0.6f + val receiptPaddingY = 14.0.dp + val receiptPaddingX = 15.0.dp + val receiptGap = 5.0.dp + val receiptGlyph = 18.0.dp + val buttonMargin = 6.0.dp + val skeletonTitleWidth = 180.0.dp + val skeletonTitleHeight = 17.0.dp + val skeletonSubtitleWidth = 120.0.dp + val skeletonSubtitleHeight = 13.0.dp + val skeletonGap = 6.0.dp + val ratingsGap = 10.0.dp + val ratingsEntryGap = 4.0.dp + val shadowBlur = 40.0.dp + val shadowOffset = -15.0.dp + val shadowColor = ComposeColor(0x80000000) + const val ratingsWordmarkRatio = 1.8f + } object Shell { const val navLabelFade = 200 val zero = 0.0.dp @@ -312,7 +356,6 @@ object WolfskinTokens { const val captionSeparatorOpacity = 0.4f val captionFactsGap = 9.0.dp val badgeIcon = 14.0.dp - val badgeBorder = 1.5.dp val plateTop = 7.0.dp val plateSide = 8.0.dp val plateBottom = 23.0.dp @@ -341,6 +384,10 @@ object WolfskinTokens { val tailIcon = 22.0.dp val tailPadding = 10.0.dp val tailGap = 5.0.dp + val badgeRing = 1.5.dp + val seeAllGap = 4.0.dp + val tailDescription = TextStyle(fontSize = 12.0.sp, fontWeight = FontWeight(500), lineHeight = 15.600000000000001.sp, letterSpacing = 0.0.em) + val headerSubtitle = TextStyle(fontSize = 15.0.sp, fontWeight = FontWeight(400), lineHeight = 19.5.sp, letterSpacing = 0.0.em) const val imageReads = 4.0f const val unknownCriticOpacity = 0.55f val fallbackPadding = 11.0.dp @@ -352,6 +399,20 @@ object WolfskinTokens { const val fallbackLightnessBottom = 0.1f const val fallbackIconOpacity = 0.45f } + object Lift { + val cardBlur = 25.0.dp + val cardY = 9.0.dp + const val cardOpacity = 0.65f + val buttonBlur = 20.0.dp + val buttonY = 6.0.dp + const val buttonOpacity = 0.5f + val heroBlur = 40.0.dp + val heroY = 15.0.dp + const val heroOpacity = 0.6f + val sheetBlur = 40.0.dp + val sheetX = -15.0.dp + const val sheetOpacity = 0.5f + } object Title { val lineGap = 11.0.dp val lineLift = -2.0.dp @@ -475,4 +536,65 @@ object WolfskinTokens { val border = 1.0.dp const val borderOpacity = 0.5f } + object Discover { + val sheetPaddingX = 32.0.dp + val sheetPaddingY = 40.0.dp + val sheetGap = 9.0.dp + val sheetSubGap = 7.0.dp + val sheetRowPadding = 13.0.dp + val sheetAvatar = 30.0.dp + val sheetRadio = 20.0.dp + val sheetRadioDot = 8.0.dp + val sheetPersonGap = 12.0.dp + val sheetButtonTop = 6.0.dp + val chipTop = 8.0.dp + val chipHeight = 34.0.dp + val chipPadding = 14.0.dp + val chipGap = 8.0.dp + val heroTitleGap = 8.0.dp + val heroKickerGap = 5.0.dp + val heroMetaGap = 7.0.dp + val heroOverviewGap = 15.0.dp + val heroFactsGap = 11.0.dp + val heroFactsHeight = 19.0.dp + val heroSheetRight = 90.0.dp + val heroSheetTop = 70.0.dp + val heroSheetWidth = 180.0.dp + val heroSheetHeight = 270.0.dp + val dotSize = 7.0.dp + val dotActive = 22.0.dp + val dotGap = 7.0.dp + val dotMargin = 9.0.dp + val dotChevron = 20.0.dp + val stateTop = 10.0.dp + val statePaddingX = 20.0.dp + val statePaddingY = 17.0.dp + val stateTextGap = 12.0.dp + val noteTop = 7.0.dp + val headerTop = 75.0.dp + val headerGap = 12.0.dp + val entryPaddingX = 11.0.dp + val entryPaddingY = 10.0.dp + val entryText = TextStyle(fontSize = 15.0.sp, fontWeight = FontWeight(800), lineHeight = 19.5.sp, letterSpacing = -0.01.em) + val sheetRadioRing = 1.5.dp + val skeletonNameWidth = 130.0.dp + val skeletonNameHeight = 17.0.dp + val sheetSubTop = -4.0.dp + val sheetRatingTop = -5.0.dp + val sheetRatingBottom = 3.0.dp + val sheetLabelTop = 4.0.dp + val noteIcon = 15.0.dp + val noteGap = 8.0.dp + val frameBottomMin = 40.0.dp + val anchorFitTop = 350.0.dp + val anchorBottomClearance = 20.0.dp + const val reasonOpacity = 0.8f + val stageGap = 5.0.dp + const val entryBorderOpacity = 0.08f + val ambientInset = 40.0.dp + val ambientBlur = 30.0.dp + const val ambientScale = 1.1f + const val ambientSaturation = 1.2f + const val ambientBrightness = 0.55f + } } diff --git a/wolfskin-design/src/main/res/drawable-nodpi/coming_in_rating_fresh.png b/wolfskin-design/src/main/res/drawable-nodpi/coming_in_rating_fresh.png new file mode 100644 index 00000000..ef0a98a6 Binary files /dev/null and b/wolfskin-design/src/main/res/drawable-nodpi/coming_in_rating_fresh.png differ diff --git a/wolfskin-design/src/main/res/drawable-nodpi/coming_in_rating_imdb.png b/wolfskin-design/src/main/res/drawable-nodpi/coming_in_rating_imdb.png new file mode 100644 index 00000000..bb7becf5 Binary files /dev/null and b/wolfskin-design/src/main/res/drawable-nodpi/coming_in_rating_imdb.png differ diff --git a/wolfskin-design/src/main/res/drawable-nodpi/coming_in_rating_rotten.png b/wolfskin-design/src/main/res/drawable-nodpi/coming_in_rating_rotten.png new file mode 100644 index 00000000..678af063 Binary files /dev/null and b/wolfskin-design/src/main/res/drawable-nodpi/coming_in_rating_rotten.png differ diff --git a/wolfskin-design/src/main/res/drawable-nodpi/coming_in_rating_spilled.png b/wolfskin-design/src/main/res/drawable-nodpi/coming_in_rating_spilled.png new file mode 100644 index 00000000..988d6554 Binary files /dev/null and b/wolfskin-design/src/main/res/drawable-nodpi/coming_in_rating_spilled.png differ diff --git a/wolfskin-design/src/main/res/drawable-nodpi/coming_in_rating_tmdb.png b/wolfskin-design/src/main/res/drawable-nodpi/coming_in_rating_tmdb.png new file mode 100644 index 00000000..48a14150 Binary files /dev/null and b/wolfskin-design/src/main/res/drawable-nodpi/coming_in_rating_tmdb.png differ diff --git a/wolfskin-design/src/main/res/drawable-nodpi/coming_in_rating_upright.png b/wolfskin-design/src/main/res/drawable-nodpi/coming_in_rating_upright.png new file mode 100644 index 00000000..2a2bfa89 Binary files /dev/null and b/wolfskin-design/src/main/res/drawable-nodpi/coming_in_rating_upright.png differ diff --git a/wolfskin-design/src/main/res/drawable/wolfskin_check.xml b/wolfskin-design/src/main/res/drawable/wolfskin_check.xml new file mode 100644 index 00000000..948e1e20 --- /dev/null +++ b/wolfskin-design/src/main/res/drawable/wolfskin_check.xml @@ -0,0 +1,4 @@ + + + + diff --git a/wolfskin-design/src/main/res/drawable/wolfskin_clock.xml b/wolfskin-design/src/main/res/drawable/wolfskin_clock.xml new file mode 100644 index 00000000..be066ab9 --- /dev/null +++ b/wolfskin-design/src/main/res/drawable/wolfskin_clock.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/wolfskin-design/src/main/res/drawable/wolfskin_down.xml b/wolfskin-design/src/main/res/drawable/wolfskin_down.xml index 4cc75d23..e55cd1cb 100644 --- a/wolfskin-design/src/main/res/drawable/wolfskin_down.xml +++ b/wolfskin-design/src/main/res/drawable/wolfskin_down.xml @@ -1 +1,4 @@ - + + + + diff --git a/wolfskin-design/src/main/res/drawable/wolfskin_download.xml b/wolfskin-design/src/main/res/drawable/wolfskin_download.xml new file mode 100644 index 00000000..4df16c9e --- /dev/null +++ b/wolfskin-design/src/main/res/drawable/wolfskin_download.xml @@ -0,0 +1,3 @@ + + + diff --git a/wolfskin-design/src/main/res/drawable/wolfskin_hide.xml b/wolfskin-design/src/main/res/drawable/wolfskin_hide.xml new file mode 100644 index 00000000..fe20e18d --- /dev/null +++ b/wolfskin-design/src/main/res/drawable/wolfskin_hide.xml @@ -0,0 +1,4 @@ + + + + diff --git a/wolfskin-design/src/main/res/drawable/wolfskin_left.xml b/wolfskin-design/src/main/res/drawable/wolfskin_left.xml new file mode 100644 index 00000000..62060544 --- /dev/null +++ b/wolfskin-design/src/main/res/drawable/wolfskin_left.xml @@ -0,0 +1,4 @@ + + + + diff --git a/wolfskin-design/src/main/res/drawable/wolfskin_plus.xml b/wolfskin-design/src/main/res/drawable/wolfskin_plus.xml new file mode 100644 index 00000000..dc780c85 --- /dev/null +++ b/wolfskin-design/src/main/res/drawable/wolfskin_plus.xml @@ -0,0 +1,4 @@ + + + + diff --git a/wolfskin-design/src/main/res/drawable/wolfskin_undo.xml b/wolfskin-design/src/main/res/drawable/wolfskin_undo.xml new file mode 100644 index 00000000..a600fb81 --- /dev/null +++ b/wolfskin-design/src/main/res/drawable/wolfskin_undo.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/ComingInRatingsGateTest.kt b/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/ComingInRatingsGateTest.kt new file mode 100644 index 00000000..c79d10db --- /dev/null +++ b/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/ComingInRatingsGateTest.kt @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-2.0-only +package org.justinvisser.wolfskin.design + +import java.util.Locale +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.advanceTimeBy +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Assert.* +import org.junit.Test + +/** The fake coroutine clock proves the production dwell/cache decision without a Compose scheduler. */ +@OptIn(ExperimentalCoroutinesApi::class) +class ComingInRatingsGateTest { + private val first = ComingInRequest("first", "movie", 1, "First", "justin", "Justin", "justin", "Justin", ComingInStage.Waiting, + ratings = listOf(ComingInRating("imdb", 6.0))) + private val second = first.copy(id = "second", tmdbId = 2, title = "Second", ratings = listOf(ComingInRating("tmdb", 7.2))) + + @Test fun uncachedDwellCancelsOnLeaveAndReentryStartsAFreshRead() = runTest { + val state = ComingInState() + state.update(ComingInSnapshot(listOf(first, second), ComingInStatus.Ready, signedIn = true)) + val abandoned = launch { state.awaitFocusedRatings() } + runCurrent() + advanceTimeBy(100) + assertFalse(state.ratingsVisible) + abandoned.cancel() + state.focus(second.id) + val next = launch { state.awaitFocusedRatings() } + runCurrent() + advanceTimeBy(349) + runCurrent() + assertFalse(state.ratingsVisible) + advanceTimeBy(1) + runCurrent() + assertTrue(state.ratingsVisible) + next.join() + state.focus(first.id) + assertFalse(state.ratingsVisible) + launch { state.awaitFocusedRatings() } + runCurrent() + advanceTimeBy(350) + runCurrent() + assertTrue("Returning to the abandoned uncached title must complete a new dwell", state.ratingsVisible) + state.focus(second.id) + assertTrue("A previously read title must be visible on the focus transition itself", state.ratingsVisible) + } + + @Test fun signOutClearsCachedVisibilityAndSignInUsesFreshRatingAndLocale() = runTest { + val state = ComingInState() + val snapshot = ComingInSnapshot(listOf(first), ComingInStatus.Ready, signedIn = true) + state.update(snapshot) + launch { state.awaitFocusedRatings() } + runCurrent(); advanceTimeBy(350); runCurrent() + assertTrue(state.ratingsVisible) + state.update(snapshot.copy(signedIn = false)) + assertFalse(state.ratingsVisible) + val refreshed = first.copy(ratings = listOf(ComingInRating("imdb", 7.6))) + state.update(snapshot.copy(items = listOf(refreshed))) + assertFalse("Sign-out invalidates the previous session's rating gate", state.ratingsVisible) + launch { state.awaitFocusedRatings() } + runCurrent(); advanceTimeBy(350); runCurrent() + assertTrue(state.ratingsVisible) + val rating = state.focused!!.ratings.single() + assertEquals("7,6", formatComingInRating(rating, Locale.forLanguageTag("nl-NL"))) + assertEquals("7.6", formatComingInRating(rating, Locale.ENGLISH)) + assertEquals("87%", formatComingInRating(ComingInRating("fresh", 87.9, true), Locale.forLanguageTag("nl-NL"))) + } + + @Test fun savedOfflineRowsOnlyShowPreviouslyReadRatingsAcrossHouseholdRequests() = runTest { + val state = ComingInState() + val sharedCopy = first.copy(id = "other-request", requesterKey = "shared", requesterName = "Shared") + val online = ComingInSnapshot(listOf(first, second, sharedCopy), ComingInStatus.Ready, signedIn = true) + state.update(online) + launch { state.awaitFocusedRatings() } + runCurrent(); advanceTimeBy(350); runCurrent() + state.update(online.copy(status = ComingInStatus.Unavailable)) + state.focus(second.id) + state.awaitFocusedRatings() + assertFalse("An unread title cannot acquire ratings while offline", state.ratingsVisible) + state.focus(sharedCopy.id) + assertTrue("The title cache is shared across request rows for the same media type and TMDB id", state.ratingsVisible) + } +} diff --git a/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/DiscoverCaptureTest.kt b/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/DiscoverCaptureTest.kt new file mode 100644 index 00000000..032f59f8 --- /dev/null +++ b/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/DiscoverCaptureTest.kt @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: GPL-2.0-only +package org.justinvisser.wolfskin.design + +import android.app.Application +import android.graphics.BitmapFactory +import android.view.View +import android.view.WindowManager +import androidx.activity.ComponentActivity +import androidx.compose.foundation.layout.requiredSize +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onRoot +import androidx.compose.ui.test.SemanticsMatcher +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.onAllNodesWithText +import androidx.compose.ui.semantics.SemanticsProperties +import androidx.compose.ui.semantics.SemanticsActions +import androidx.compose.ui.text.TextLayoutResult +import org.json.JSONArray +import org.json.JSONObject +import com.github.takahirom.roborazzi.captureRoboImage +import java.io.File +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.Assert.assertTrue +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import org.robolectric.annotation.GraphicsMode + +/** Captures execute the shipped Compose surface with offline fixture artwork and actual shell focus. */ +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [34], application = Application::class, qualifiers = "w960dp-h540dp-xhdpi") +@GraphicsMode(GraphicsMode.Mode.NATIVE) +class DiscoverCaptureTest { + @get:Rule val rule = createAndroidComposeRule() + private val fixture = discoverTestFixture() + + @Before fun fullscreen() { + rule.activity.window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN) + @Suppress("DEPRECATION") + rule.activity.window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + } + + private fun capture(name: String, person: String = "justin", snapshotTransform: (DiscoverSnapshot) -> DiscoverSnapshot = { it }, configure: (DiscoverState) -> Unit = {}) { + val titleState = name.substringAfter("st-").toInt() in setOf(58, 59, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 87, 110) + val shell = ShellState(fixturePeople(), person).apply { pickerVisible = false; enterDestination(if (titleState) "home" else "discover") } + val snapshot = snapshotTransform(fixture.snapshot(person)) + val state = DiscoverState(shell, snapshot, RecordingDiscoverOperations(snapshot, person), CoroutineScope(Dispatchers.Main.immediate), + onPush = shell::enterDestination, onPop = { shell.enterDestination("discover") }, serviceLanes = fixture::service) + configure(state) + val painters = mutableMapOf() + rule.setContent { + WolfskinTheme { + CompositionLocalProvider(LocalDiscoverArt provides { path -> painters.getOrPut(path) { BitmapFactory.decodeFile(path).asImageBitmap() } }) { + ShellSurface(shell, Modifier.requiredSize(WolfskinTokens.Shell.stageWidth, WolfskinTokens.Shell.stageHeight), overlay = { DiscoverOverlay(state) }) { DiscoverScreen(state, it) } + } + } + } + rule.mainClock.advanceTimeBy(1000) + rule.waitForIdle() + val directory = File(requireNotNull(System.getProperty("wolfskin.captureDirectory"))).apply { mkdirs() } + rule.onRoot().captureRoboImage(File(directory, "$name.png").absolutePath) + recordTypography(directory, name) + } + + private fun recordTypography(directory: File, name: String) { + val rows = JSONArray() + rule.onAllNodes(SemanticsMatcher.keyIsDefined(SemanticsProperties.Text), useUnmergedTree = true).fetchSemanticsNodes().forEach { node -> + val layouts = mutableListOf() + if (SemanticsActions.GetTextLayoutResult in node.config) node.config[SemanticsActions.GetTextLayoutResult].action?.invoke(layouts) + layouts.forEach { layout -> + val text = layout.layoutInput.text.text + val lines = JSONArray() + repeat(layout.lineCount) { index -> lines.put(text.substring(layout.getLineStart(index), layout.getLineEnd(index, visibleEnd = true))) } + rows.put(JSONObject().put("text", text).put("lines", layout.lineCount).put("lineTexts", lines) + .put("fontSizeSp", layout.layoutInput.style.fontSize.value).put("fontWeight", layout.layoutInput.style.fontWeight?.weight) + .put("lineHeightSp", layout.layoutInput.style.lineHeight.value.takeIf { it.isFinite() } ?: JSONObject.NULL).put("trim", layout.layoutInput.style.lineHeightStyle?.trim.toString()) + .put("density", layout.layoutInput.density.density).put("fontScale", layout.layoutInput.density.fontScale) + .put("left", node.boundsInRoot.left).put("top", node.boundsInRoot.top).put("width", node.boundsInRoot.width).put("height", node.boundsInRoot.height)) + } + } + File(directory, "$name-type-metrics.json").writeText(rows.toString(2)) + } + + @Test fun st029_discover() = capture("st-029") + @Test fun st030_picksCollection() = capture("st-030") { state -> state.openCollection(state.lanes.first { it.id == "pk-movie" }) } + @Test fun st031_hidden() = capture("st-031") { hidden(it) } + @Test fun st032_restore() { + capture("st-032") { state -> hidden(state); state.overlay = DiscoverOverlayState.Restore(state.snapshot.picks[7]) } + val title = rule.onAllNodesWithText(fixture.snapshot("justin").picks[7].title, useUnmergedTree = true) + .fetchSemanticsNodes().maxBy { it.boundsInRoot.left }.boundsInRoot + val sub = rule.onNodeWithText("Hidden from Justin’s picks.", useUnmergedTree = true).fetchSemanticsNode().boundsInRoot + val gap = with(rule.density) { (WolfskinTokens.Discover.sheetGap + WolfskinTokens.Discover.sheetSubTop).toPx() } + assertTrue("Restore subtitle must retain the negative top margin", kotlin.math.abs(sub.top - title.bottom - gap) <= 1f) + } + @Test fun st033_failedBadge() = capture("st-033") { state -> + val lane = state.lanes.first { it.id == "pk-movie" } + state.requests[lane.titles[1].key] = listOf(DiscoverRequestResult(RequestPhase.Receipt, "Justin", "Failed", requester = "justin")) + state.focus(state.lanes.indexOf(lane) + 2, 1) + } + @Test fun st034_retryTail() = capture("st-034", snapshotTransform = { snapshot -> + snapshot.copy(lanes = snapshot.lanes.map { if (it.id == "trending") it.copy(tail = DiscoverTail.Retry) else it }) + }) { state -> val lane = state.lanes.first { it.id == "trending" }; state.focus(state.lanes.indexOf(lane) + 2, lane.titles.size) } + @Test fun st035_duplicateTail() = capture("st-035") { state -> + val titles = state.snapshot.lanes.first { it.id == "trending" }.fullTitles.take(36) + state.openCollection(DiscoverLane("trending", "Trending", titles, tail = DiscoverTail.ShowMore)) + state.collection!!.index = state.collection!!.tailIndex + } + @Test fun st036_needsSignIn() { + capture("st-036", snapshotTransform = { DiscoverSnapshot(status = DiscoverStatus.NeedsSignIn) }) + val button = rule.onNodeWithText("Try again").fetchSemanticsNode().boundsInRoot + val minimum = with(rule.density) { WolfskinTokens.Layout.buttonHeight.toPx() } + assertTrue("The sign-in button must not shrink to fit the scroll row", button.height >= minimum) + } + @Test fun st037_series() { + capture("st-037", person = "mindel") { it.filter = "tv" } + val title = rule.onNodeWithText("56 Days", useUnmergedTree = true).fetchSemanticsNode() + val layouts = mutableListOf() + title.config[SemanticsActions.GetTextLayoutResult].action?.invoke(layouts) + val layout = layouts.single() + val cssHeight = with(rule.density) { DiscoverType.hero.lineHeight.toPx() } * layout.lineCount + assertTrue("Natural font height must not inflate the hero CSS line box", kotlin.math.abs(title.boundsInRoot.height - cssHeight) <= 1f) + } + @Test fun st038_noSeeds() = capture("st-038", snapshotTransform = { it.copy(status = DiscoverStatus.NoSeeds) }) + @Test fun st039_trendingCollection() = capture("st-039") { state -> state.openCollection(state.lanes.first { it.id == "trending" }) } + @Test fun st040_service() = capture("st-040") { state -> state.openService(state.snapshot.services.first()) } + @Test fun st041_unreachable() = capture("st-041", snapshotTransform = { DiscoverSnapshot(status = DiscoverStatus.Unavailable) }) + @Test fun st058_hiddenReceipt() = capture("st-058") { state -> state.openTitle(state.snapshot.picks.first()); state.titleButton = 2; state.hide(state.title!!) } + @Test fun st059_hiddenTitle() = capture("st-059") { state -> state.hide(state.snapshot.picks.first()); state.overlay = null; state.openTitle(state.snapshot.picks.first()); state.titleButton = 2 } + @Test fun st071_requestSelf() = capture("st-071") { request(it) } + @Test fun st072_requestOther() = capture("st-072") { request(it, other = true) } + @Test fun st073_addHeld() = capture("st-073") { state -> request(state, fixture.find("The Girl Who Leapt Through Time"), other = true) } + @Test fun st074_receipt() = capture("st-074") { request(it, other = true, phase = RequestPhase.Receipt) } + @Test fun st075_notConfirmed() = capture("st-075") { request(it, phase = RequestPhase.NotConfirmed) } + @Test fun st076_pending() = capture("st-076") { request(it, phase = RequestPhase.Pending) } + @Test fun st077_refused() = capture("st-077", person = "mindel") { request(it, other = true, phase = RequestPhase.Refused) } + @Test fun st078_recipientsLoading() = capture("st-078") { request(it, other = true, phase = RequestPhase.LoadingRecipients) } + @Test fun st079_recipientsUnavailable() = capture("st-079") { request(it, other = true, phase = RequestPhase.RecipientsUnavailable) } + @Test fun st080_seasonsUnknown() = capture("st-080") { state -> request(state, state.snapshot.picks.first { it.type == "tv" }) } + @Test fun st087_sharedReceipt() = capture("st-087", person = "shared") { request(it, phase = RequestPhase.Receipt) } + @Test fun st105_pickPlate() = capture("st-105") { state -> val lane = state.lanes.first { it.id == "pk-movie" }; state.focus(state.lanes.indexOf(lane) + 2, 1) } + @Test fun st110_heldRatings() = capture("st-110") { state -> request(state, fixture.find("The Girl Who Leapt Through Time"), other = true) } + + private fun hidden(state: DiscoverState) { + state.hide(state.snapshot.picks[7]); state.hide(state.snapshot.picks[20]); state.overlay = null + val lane = state.lanes.first { it.hidden } + state.focus(state.lanes.indexOf(lane) + 2, lane.titles.indexOfFirst { it.key == state.snapshot.picks[7].key }) + } + + private fun request(state: DiscoverState, title: DiscoverTitle = state.snapshot.picks.first(), other: Boolean = false, phase: RequestPhase = RequestPhase.Choose) { + state.openTitle(title); state.openRequest(other, phase = phase) + if (phase == RequestPhase.Receipt) { + val sheet = (state.overlay as DiscoverOverlayState.Request).state + val receipt = DiscoverRequestResult(phase, state.shell.person.name, requester = sheet.selectedPerson.key) + sheet.result = receipt; state.requests[title.key] = listOf(receipt) + } + } +} + +internal fun discoverTestFixture(): DiscoverFixture { + val json = DiscoverCaptureTest::class.java.getResourceAsStream("/wolfskin-discover.json")!!.bufferedReader().use { it.readText() } + val assets = File("../app/src/debug/assets/wolfskin-discover").canonicalFile + return DiscoverFixture(json) { File(assets, it).absolutePath } +} + +/** In-process effects are inspectable without exposing any live client or endpoint. */ +internal class RecordingDiscoverOperations(private val snapshot: DiscoverSnapshot, private val actor: String) : DiscoverOperations { + val sent = mutableListOf() + var checked = 0 + var next = RequestPhase.Receipt + var mutationSucceeds = true + override suspend fun request(intent: DiscoverRequest): DiscoverRequestResult { sent += intent; return DiscoverRequestResult(next, actor.replaceFirstChar { it.uppercase() }) } + override suspend fun check(intent: DiscoverRequest): DiscoverRequestResult { checked++; return DiscoverRequestResult(next, actor.replaceFirstChar { it.uppercase() }) } + override suspend fun hide(profile: String, title: DiscoverTitle) = mutationSucceeds + override suspend fun restore(profile: String, title: DiscoverTitle) = mutationSucceeds + override suspend fun refresh() = snapshot +} diff --git a/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/DiscoverKeysTest.kt b/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/DiscoverKeysTest.kt new file mode 100644 index 00000000..8d95e376 --- /dev/null +++ b/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/DiscoverKeysTest.kt @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: GPL-2.0-only +package org.justinvisser.wolfskin.design + +import android.app.Application +import android.view.KeyEvent as AndroidKeyEvent +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.KeyEvent +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performKeyInput +import androidx.compose.ui.test.pressKey +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import org.json.JSONObject +import org.junit.Assert.* +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +/** Acceptance data drives real Android events through the shell's production focus adapter. */ +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [34], application = Application::class, qualifiers = "w960dp-h540dp-xhdpi") +class DiscoverKeysTest { + @get:Rule val rule = createComposeRule() + private val fixture = discoverTestFixture() + private val shell = ShellState(fixturePeople(), "justin") + private val snapshot = fixture.snapshot("justin") + private val operations = RecordingDiscoverOperations(snapshot, "justin") + private val state = DiscoverState(shell, snapshot, operations, CoroutineScope(Dispatchers.Main.immediate), shell::enterDestination, + { shell.enterDestination("discover") }, fixture::service) + + private fun launch() { + rule.setContent { + WolfskinTheme { + ShellSurface(shell, overlay = { DiscoverOverlay(state) }) { modifier -> + if (shell.route == "discover" || shell.route.startsWith("discover-")) DiscoverScreen(state, modifier) else ShellPlaceholder(shell.route, modifier) + } + } + } + } + + private fun press(first: Key, second: Key? = null) = listOfNotNull(first, second).forEach { key -> rule.onNodeWithTag("wolfskin-shell").performKeyInput { pressKey(key) } } + + private fun replay(goal: String) { + val paths = JSONObject(javaClass.getResourceAsStream("/discover-keys.json")!!.bufferedReader().use { it.readText() }).getJSONArray("pressPaths") + val path = (0 until paths.length()).map { paths.getJSONObject(it) }.single { it.getString("goal") == goal } + val keys = path.getJSONArray("keys") + assertEquals(path.getInt("presses"), keys.length()) + launch() + repeat(keys.length()) { index -> press(when (keys.getString(index)) { + "OK" -> Key.DirectionCenter; "UP" -> Key.DirectionUp; "DOWN" -> Key.DirectionDown; "LEFT" -> Key.DirectionLeft; "RIGHT" -> Key.DirectionRight; else -> Key.Back + }) } + rule.waitForIdle() + } + + @Test fun openDiscoverInFourPresses() { + replay("Open Discover") + assertEquals("discover", shell.route) + assertFalse(shell.navFocused) + assertEquals("The Amazing Spider-Man", state.hero?.title) + } + + @Test fun requestForYourselfInSevenPresses() { + replay("Request a pick for yourself") + val sheet = (state.overlay as DiscoverOverlayState.Request).state + assertEquals(RequestPhase.Receipt, sheet.phase) + assertEquals("justin", operations.sent.single().requester) + assertEquals("Justin", sheet.result?.actor) + assertEquals("Waiting", state.requests.getValue(requireNotNull(state.title).key).single().stage) + } + + @Test fun requestForMindelInNinePresses() { + replay("Request a pick for Mindel") + val sheet = (state.overlay as DiscoverOverlayState.Request).state + assertEquals(RequestPhase.Receipt, sheet.phase) + assertEquals("mindel", operations.sent.single().requester) + assertEquals("Justin", sheet.result?.actor) + } + + @Test fun notInterestedInEightPressesAndUndoRestoresEveryIndex() { + val original = state.lanes.map { lane -> lane.id to lane.titles.map { it.key } } + replay("Round 3 · Not interested on a pick (Discover hero)") + val hidden = state.overlay as DiscoverOverlayState.Hidden + assertTrue(state.isHidden(hidden.title)) + assertTrue(state.heroes.none { it.key == hidden.title.key }) + assertTrue(state.lanes.filterNot { it.hidden }.none { lane -> lane.titles.any { it.key == hidden.title.key } }) + press(Key.DirectionCenter) + assertNull(state.overlay) + assertEquals("The Amazing Spider-Man", state.hero?.title) + assertEquals(original, state.lanes.map { lane -> lane.id to lane.titles.map { it.key } }) + } + + @Test fun heldOkOpensSheetButNeverSubmits() { + replay("Open Discover") + press(Key.DirectionCenter) + rule.runOnIdle { + shell.handleKey(KeyEvent(AndroidKeyEvent(0, 0, AndroidKeyEvent.ACTION_DOWN, AndroidKeyEvent.KEYCODE_DPAD_CENTER, 0))) + repeat(8) { index -> shell.handleKey(KeyEvent(AndroidKeyEvent(0, 0, AndroidKeyEvent.ACTION_DOWN, AndroidKeyEvent.KEYCODE_DPAD_CENTER, index + 1))) } + shell.handleKey(KeyEvent(AndroidKeyEvent(0, 0, AndroidKeyEvent.ACTION_UP, AndroidKeyEvent.KEYCODE_DPAD_CENTER, 0))) + } + assertEquals(RequestPhase.Choose, (state.overlay as DiscoverOverlayState.Request).state.phase) + assertTrue(operations.sent.isEmpty()) + } + + @Test fun uncertainOutcomeChecksWithoutResending() { + operations.next = RequestPhase.NotConfirmed + replay("Request a pick for yourself") + assertEquals(1, operations.sent.size) + assertEquals(RequestPhase.NotConfirmed, (state.overlay as DiscoverOverlayState.Request).state.phase) + operations.next = RequestPhase.Receipt + press(Key.DirectionCenter) + assertEquals(1, operations.sent.size) + assertEquals(1, operations.checked) + assertEquals(RequestPhase.Receipt, (state.overlay as DiscoverOverlayState.Request).state.phase) + } + + @Test fun closingAnUncertainReceiptNeverTurnsReopeningIntoAnotherPost() { + operations.next = RequestPhase.NotConfirmed + replay("Request a pick for yourself") + press(Key.Back, Key.DirectionCenter) + assertEquals(RequestPhase.NotConfirmed, (state.overlay as DiscoverOverlayState.Request).state.phase) + operations.next = RequestPhase.Receipt + press(Key.DirectionCenter) + assertEquals(1, operations.sent.size) + assertEquals(1, operations.checked) + assertEquals(RequestPhase.Receipt, (state.overlay as DiscoverOverlayState.Request).state.phase) + } + + @Test fun requestingForAnotherPersonKeepsYourOwnRequestedAction() { + replay("Request a pick for yourself") + press(Key.DirectionCenter) + press(Key.DirectionRight, Key.DirectionCenter) + press(Key.DirectionCenter, Key.DirectionCenter) + assertEquals(setOf("justin", "mindel"), state.requests.getValue(state.title!!.key).map { it.requester }.toSet()) + press(Key.DirectionCenter) + press(Key.DirectionLeft, Key.DirectionCenter) + assertTrue(shell.requestsVisible) + assertEquals(2, operations.sent.size) + } + + @Test fun choosingAnUncertainRecipientAgainNeverResubmits() { + operations.next = RequestPhase.NotConfirmed + replay("Open Discover") + press(Key.DirectionCenter) + press(Key.DirectionRight, Key.DirectionCenter) + press(Key.DirectionDown, Key.DirectionCenter) + press(Key.DirectionCenter) + assertEquals("shared", operations.sent.single().requester) + press(Key.Back, Key.DirectionCenter) + press(Key.DirectionDown, Key.DirectionCenter) + press(Key.DirectionCenter) + assertEquals(1, operations.sent.size) + assertEquals(RequestPhase.NotConfirmed, (state.overlay as DiscoverOverlayState.Request).state.phase) + operations.next = RequestPhase.Receipt + press(Key.DirectionCenter) + assertEquals(1, operations.sent.size) + assertEquals(1, operations.checked) + assertEquals(RequestPhase.Receipt, (state.overlay as DiscoverOverlayState.Request).state.phase) + } + + @Test fun unknownSeriesSeasonsNeverSubmitAndBackRestoresOpener() { + replay("Open Discover") + rule.runOnIdle { state.openTitle(snapshot.picks.first { it.type == "tv" }); state.openRequest(false) } + press(Key.DirectionCenter) + assertTrue(operations.sent.isEmpty()) + assertNull(state.overlay) + assertNotNull(state.title) + assertFalse(shell.navFocused) + } + + @Test fun cardOkWaitsForReleaseAndFocusMoveCancelsActivation() { + replay("Open Discover") + press(Key.DirectionDown, Key.DirectionDown) + rule.runOnIdle { shell.handleKey(KeyEvent(AndroidKeyEvent(0, 0, AndroidKeyEvent.ACTION_DOWN, AndroidKeyEvent.KEYCODE_DPAD_CENTER, 0))) } + assertNull(state.title) + press(Key.DirectionRight) + rule.runOnIdle { shell.handleKey(KeyEvent(AndroidKeyEvent(0, 0, AndroidKeyEvent.ACTION_UP, AndroidKeyEvent.KEYCODE_DPAD_CENTER, 0))) } + assertNull(state.title) + press(Key.DirectionCenter) + assertNotNull(state.title) + } + @Test fun downClampsToOccupiedCardsAndThenTheTail() { + shell.pickerVisible = false + val items = snapshot.picks.take(8) + state.openCollection(DiscoverLane("short", "Picks", items, tail = DiscoverTail.ShowMore)) + launch() + repeat(5) { press(Key.DirectionRight) } + assertEquals(5, state.collection!!.index % state.collection!!.columns) + press(Key.DirectionDown) + assertEquals(1, state.collection!!.index / state.collection!!.columns) + assertEquals(1, state.collection!!.index % state.collection!!.columns) + assertEquals(items.last().key, state.collection!!.items[state.collection!!.index].id) + press(Key.DirectionDown, Key.DirectionRight) + assertEquals(2, state.collection!!.index / state.collection!!.columns) + assertEquals(0, state.collection!!.index % state.collection!!.columns) + assertEquals(GridTail.ShowMore, state.collection!!.tail) + } + + @Test fun collectionRetryReadsNewTitlesAndOpensTheFirstAppendedTitle() { + shell.pickerVisible = false + val lane = state.lanes.first { it.id == "trending" } + state.openCollection(lane.copy(titles = lane.fullTitles.take(6), fullTitles = lane.fullTitles.take(6), tail = DiscoverTail.Retry)) + val grid = state.collection!! + grid.index = grid.tailIndex + launch() + press(Key.DirectionCenter) + assertEquals(GridTail.None, grid.tail) + assertEquals(lane.fullTitles.map { it.key }, grid.items.map { it.id }) + assertEquals(lane.fullTitles[6].key, grid.focusedTitle?.id) + press(Key.DirectionCenter) + assertEquals(lane.fullTitles[6], state.title) + } + + @Test fun collectionDismissalAndUndoKeepTheOriginalOrderAndFocus() { + shell.pickerVisible = false + state.openCollection(state.lanes.first { it.id == "pk-movie" }) + val grid = state.collection!! + val original = grid.items.map { it.id } + launch() + press(Key.DirectionCenter) + val opener = state.title!! + press(Key.DirectionRight, Key.DirectionRight) + press(Key.DirectionCenter) + assertFalse(grid.items.any { it.id == opener.key }) + press(Key.DirectionCenter) + assertEquals(original, grid.items.map { it.id }) + assertEquals(opener.key, grid.focusedTitle?.id) + } + + @Test fun collectionBackRestoresTheExactOrigin() { + shell.pickerVisible = false; shell.enterDestination("discover") + state.focus(3, 2) + val origin = shell.view + state.openCollection(state.lanes.first { it.id == "pk-movie" }) + launch() + press(Key.DirectionDown, Key.Back) + assertNull(state.collection) + assertSame(origin, shell.view) + assertEquals(3, shell.view.row) + assertEquals(2, shell.view.cards[state.rowKey]) + } + + @Test + @Config(qualifiers = "nl-rNL-w960dp-h540dp-xhdpi") + fun dutchPlateUsesLocaleAndStaysOnOneLine() { + shell.pickerVisible = false + val title = snapshot.picks.first().copy(ratings = listOf(DiscoverRating("tmdb", 7.8))) + state.openCollection(DiscoverLane("locale", "Picks", listOf(title))) + launch() + rule.mainClock.advanceTimeBy(1000) + rule.onNodeWithText("7,8").assertExists() + assertEquals("7.8", formatDiscoverRating(title.ratings.single(), java.util.Locale.ENGLISH)) + assertEquals("91%", formatDiscoverRating(DiscoverRating("rtCritics", 91.7), java.util.Locale.forLanguageTag("nl-NL"))) + } +} diff --git a/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/DiscoverLaneProjectionTest.kt b/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/DiscoverLaneProjectionTest.kt new file mode 100644 index 00000000..ed62d6f4 --- /dev/null +++ b/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/DiscoverLaneProjectionTest.kt @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-only +package org.justinvisser.wolfskin.design + +import org.junit.Assert.assertEquals +import org.junit.Test + +/** Feed projection must not lose titles when a contribution row is too short to display. */ +class DiscoverLaneProjectionTest { + @Test fun discardedSeedGroupRemainsAvailableInCatalogue() { + val titles = (1L..6L).map { DiscoverTitle(it, "movie", "Film $it") } + val snapshot = DiscoverSnapshot(picks = titles, seeds = listOf(DiscoverSeed("Seed", titles.subList(3, 5))), + lanes = listOf(DiscoverLane("trending", "Trending", titles)), status = DiscoverStatus.Ready) + val rows = projectDiscoverLanes(snapshot, emptyList(), "all", emptyMap()) + assertEquals(listOf("pk-movie", "trending"), rows.map { it.id }) + assertEquals(titles.take(3), rows.first().titles) + assertEquals(titles.drop(3), rows.last().titles) + } + + @Test fun catalogueDeduplicationKeepsMediaTypesDistinctAndHiddenPicksLast() { + val movie = DiscoverTitle(1, "movie", "Film") + val series = DiscoverTitle(1, "tv", "Series") + val hidden = DiscoverTitle(2, "movie", "Hidden") + val later = DiscoverTitle(3, "movie", "Later") + val snapshot = DiscoverSnapshot(hidden = listOf(hidden), status = DiscoverStatus.Ready, lanes = listOf( + DiscoverLane("first", "First", listOf(movie, series, hidden)), + DiscoverLane("second", "Second", listOf(movie, series, later)))) + val rows = projectDiscoverLanes(snapshot, emptyList(), "all", mapOf(hidden.key to hidden)) + assertEquals(listOf(listOf(movie, series), listOf(later), listOf(hidden)), rows.map { it.titles }) + assertEquals("hidden", rows.last().id) + val filtered = projectDiscoverLanes(snapshot, emptyList(), "tv", mapOf(hidden.key to hidden)) + assertEquals(listOf(listOf(series)), filtered.map { it.titles }) + } +} diff --git a/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/RequestsCaptureTest.kt b/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/RequestsCaptureTest.kt new file mode 100644 index 00000000..7f114a64 --- /dev/null +++ b/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/RequestsCaptureTest.kt @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-2.0-only +package org.justinvisser.wolfskin.design + +import android.app.Application +import android.view.View +import android.view.WindowManager +import androidx.activity.ComponentActivity +import androidx.compose.foundation.layout.requiredSize +import androidx.compose.ui.Modifier +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onRoot +import com.github.takahirom.roborazzi.captureRoboImage +import java.io.File +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import org.robolectric.annotation.GraphicsMode + +/** Every approved Requests state uses the actual shell overlay at native television density. */ +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [34], application = Application::class, qualifiers = "w960dp-h540dp-xhdpi") +@GraphicsMode(GraphicsMode.Mode.NATIVE) +class RequestsCaptureTest { + @get:Rule val rule = createAndroidComposeRule() + + @Before fun fullscreen() { + rule.activity.window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN) + @Suppress("DEPRECATION") + rule.activity.window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + } + + private fun capture(name: String, snapshot: ComingInSnapshot, person: String = "justin", expanded: Boolean = false) { + val state = ShellState(fixturePeople(), person) + state.pickerVisible = false + state.enterDestination("settings") + state.requests.update(snapshot) + state.openRequests() + if (expanded) { state.requests.key(ShellKey.Down); state.requests.key(ShellKey.Right) } + rule.setContent { + WolfskinTheme { ShellSurface(state, Modifier.requiredSize(WolfskinTokens.Shell.stageWidth, WolfskinTokens.Shell.stageHeight)) } + } + rule.mainClock.advanceTimeBy(1000) + rule.waitForIdle() + val directory = File(System.getProperty("wolfskin.captureDirectory")) + directory.mkdirs() + rule.onRoot().captureRoboImage(File(directory, "$name.png").absolutePath) + } + + @Test fun st081_empty() = capture("st-081", ComingInSnapshot(status = ComingInStatus.Ready, signedIn = true)) + @Test fun st082_stages() = capture("st-082", requestsFixture()) + @Test fun st083_unavailable() = capture("st-083", ComingInSnapshot()) + @Test fun st084_loading() = capture("st-084", ComingInSnapshot(status = ComingInStatus.Loading)) + @Test fun st085_partial() = capture("st-085", requestsFixture().copy(items = requestsFixture().items.take(3), partial = true)) + @Test fun st086_readiness() = capture("st-086", requestsFixture(), expanded = true) + @Test fun st088_sharedActor() = capture("st-088", requestsFixture(shared = true), person = "shared") + @Test fun st111_focusedRatings() = capture("st-111", requestsFixture(ratings = true)) +} + +/** Shared actor and stage fixtures are projections, never network calls or synthetic UI redraws. */ +fun requestsFixture(shared: Boolean = false, ratings: Boolean = false): ComingInSnapshot { + val source = RequestsCaptureTest::class.java.getResourceAsStream("/wolfskin-requests.json")!!.bufferedReader().use { it.readText() } + val items = readComingInFixture(source) { name -> + RequestsCaptureTest::class.java.getResourceAsStream("/wolfskin-requests/$name")!!.use { it.readBytes() } + } + val selected = when { shared -> items.filter { it.id == "x7" }; ratings -> items.filter { it.id == "x8" }; else -> items.take(6) } + return ComingInSnapshot(selected, ComingInStatus.Ready, signedIn = true) +} diff --git a/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/RequestsKeysTest.kt b/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/RequestsKeysTest.kt new file mode 100644 index 00000000..780a6575 --- /dev/null +++ b/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/RequestsKeysTest.kt @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: GPL-2.0-only +package org.justinvisser.wolfskin.design + +import android.app.Application +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.test.* +import androidx.compose.ui.test.junit4.createComposeRule +import java.util.Locale +import org.json.JSONArray +import org.junit.Assert.* +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +/** Key replays exercise real shell handling, including trapped overlays and title-return memory. */ +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [34], application = Application::class, qualifiers = "w960dp-h540dp-xhdpi") +class RequestsKeysTest { + @get:Rule val rule = createComposeRule() + + private fun mount(state: ShellState) { + rule.setContent { WolfskinTheme { ShellSurface(state) } } + rule.mainClock.advanceTimeByFrame() + } + + private fun send(first: Key, second: Key? = null, third: Key? = null, fourth: Key? = null) { + listOfNotNull(first, second, third, fourth).forEach { key -> + rule.onNodeWithTag("wolfskin-shell").performKeyInput { pressKey(key) } + rule.mainClock.advanceTimeByFrame() + } + } + + private fun sheet(snapshot: ComingInSnapshot = requestsFixture(), actions: (ShellAction) -> Unit = {}): ShellState = + ShellState(fixturePeople(), "justin", actions).apply { + pickerVisible = false + enterDestination("settings") + requests.update(snapshot) + openRequests() + } + + @Test fun approvedNinePressPathUsesRealShellFocus() { + val paths = RequestsKeysTest::class.java.getResourceAsStream("/requests-press-paths.json")!!.bufferedReader().use { JSONArray(it.readText()) } + val keys = mapOf("OK" to Key.DirectionCenter, "UP" to Key.DirectionUp, "LEFT" to Key.DirectionLeft, + "RIGHT" to Key.DirectionRight, "DOWN" to Key.DirectionDown) + val state = ShellState(fixturePeople(), "justin") + state.requests.update(requestsFixture()) + mount(state) + val path = paths.getJSONObject(0) + val presses = path.getJSONArray("keys") + assertEquals(9, path.getInt("presses")) + assertEquals(path.getInt("presses"), presses.length()) + for (index in 0 until presses.length()) send(keys.getValue(presses.getString(index))) + rule.onNodeWithTag("coming-in").assertExists() + rule.onNodeWithText("Requests · 5 open").assertExists() + assertEquals("settings", state.route) + assertEquals("x5", state.requests.focusedId) + assertFalse(state.navFocused) + } + + @Test fun readinessWindowAndTitleRoundTripPreserveRequest() { + lateinit var state: ShellState + state = sheet { action -> if (action is ShellAction.RequestTitle) state.enterDestination("title/${action.request.mediaType}:${action.request.tmdbId}") } + mount(state) + send(Key.DirectionDown, Key.DirectionRight) + rule.onNodeWithText("Download · Downloading").assertExists() + assertEquals(4, state.requests.windowSize) + assertEquals("x1", state.requests.focusedId) + send(Key.DirectionLeft) + rule.onNodeWithText("Download · Downloading").assertDoesNotExist() + send(Key.DirectionDown, Key.DirectionDown, Key.DirectionDown, Key.DirectionDown) + assertEquals("x6", state.requests.focusedId) + assertEquals(1, state.requests.windowStart) + send(Key.DirectionDown) + assertEquals("x6", state.requests.focusedId) + send(Key.DirectionCenter) + assertFalse(state.requestsVisible) + assertTrue(state.route.startsWith("title/")) + send(Key.Back) + rule.onNodeWithTag("coming-row-x6").assertExists() + assertEquals("x6", state.requests.focusedId) + assertTrue(state.requestsVisible) + send(Key.Back) + assertFalse(state.requestsVisible) + assertEquals("settings", state.route) + assertEquals(0, state.settingsRow) + } + + @Test fun emptyAndLoadingTrapDirectionsWhileUnavailableRetries() { + var retries = 0 + val state = sheet(ComingInSnapshot(status = ComingInStatus.Loading)) { if (it == ShellAction.RetryRequests) retries++ } + mount(state) + send(Key.DirectionUp, Key.DirectionLeft) + assertFalse(state.navFocused) + assertTrue(state.requestsVisible) + send(Key.DirectionCenter) + assertFalse(state.requestsVisible) + rule.runOnIdle { state.requests.update(ComingInSnapshot()); state.openRequests() } + send(Key.DirectionCenter) + assertEquals(1, retries) + assertTrue(state.requestsVisible) + rule.runOnIdle { state.requests.update(ComingInSnapshot(status = ComingInStatus.Ready)) } + send(Key.DirectionDown, Key.DirectionCenter) + rule.onNodeWithText("Nothing coming in.").assertExists() + assertTrue(state.requestsVisible) + } + + @Test fun cachedRatingsRenderInTheTelevisionLocale() { + val original = Locale.getDefault() + try { + Locale.setDefault(Locale.forLanguageTag("nl-NL")) + val state = sheet(requestsFixture(ratings = true)) + kotlinx.coroutines.test.runTest { state.requests.awaitFocusedRatings() } + mount(state) + rule.onNodeWithText("7,6").assertExists() + rule.onNodeWithText("87%").assertExists() + rule.runOnIdle { state.requests.update(state.requests.snapshot.copy(signedIn = false)) } + rule.onNodeWithText("7,6").assertDoesNotExist() + rule.onNodeWithText("87%").assertDoesNotExist() + } finally { Locale.setDefault(original) } + } + + @Test fun selectingARootAfterARequestTitleDiscardsTheSheetReturnLink() { + lateinit var state: ShellState + state = sheet { action -> if (action is ShellAction.RequestTitle) state.enterDestination("title/${action.request.mediaType}:${action.request.tmdbId}") } + mount(state) + send(Key.DirectionCenter, Key.DirectionUp, Key.DirectionCenter) + assertEquals("home", state.route) + send(Key.Back) + assertFalse("Root Back must focus navigation, not reopen the abandoned sheet", state.requestsVisible) + assertTrue(state.navFocused) + } + + @Test fun refreshKeepsStableFocusAndSharedActorNeverBecomesYou() { + val snapshot = requestsFixture() + val state = sheet(snapshot) + mount(state) + send(Key.DirectionDown) + rule.runOnIdle { state.requests.update(snapshot.copy(items = snapshot.items.reversed(), partial = true)) } + assertEquals("x1", state.requests.focusedId) + rule.onNodeWithText("Some requests may be missing: Conductarr answered only in part.").assertExists() + val shared = requestsFixture(shared = true).items.single() + val viewer = fixturePeople().single { it.key == "shared" } + assertEquals("Shared", shared.actor(viewer)) + assertEquals("Justin", shared.recipient(viewer)) + assertEquals(5, snapshot.openCount) + assertEquals(listOf(0, 1, 1, 2, 3, 4), snapshot.ordered.map { it.stage.rank }) + } +} diff --git a/wolfskin-design/src/test/resources/discover-keys.json b/wolfskin-design/src/test/resources/discover-keys.json new file mode 100644 index 00000000..d1328e10 --- /dev/null +++ b/wolfskin-design/src/test/resources/discover-keys.json @@ -0,0 +1,92 @@ +{ + "pressPaths": [ + { + "goal": "Open Discover", + "keys": [ + "OK", + "UP", + "RIGHT", + "OK" + ], + "presses": 4, + "note": "", + "start": "app launch: Who's watching with Justin focused (index.html:357)", + "replayed": "PASS", + "source": "index.html:2152-2174 PRESSES, replayed 2026-11-05" + }, + { + "goal": "Request a pick for yourself", + "keys": [ + "OK", + "UP", + "RIGHT", + "OK", + "OK", + "OK", + "OK" + ], + "presses": 7, + "note": "", + "start": "app launch: Who's watching with Justin focused (index.html:357)", + "replayed": "PASS", + "source": "index.html:2152-2174 PRESSES, replayed 2026-11-05" + }, + { + "goal": "Request a pick for Mindel", + "keys": [ + "OK", + "UP", + "RIGHT", + "OK", + "OK", + "RIGHT", + "OK", + "OK", + "OK" + ], + "presses": 9, + "note": "", + "start": "app launch: Who's watching with Justin focused (index.html:357)", + "replayed": "PASS", + "source": "index.html:2152-2174 PRESSES, replayed 2026-11-05" + }, + { + "goal": "Round 3 · Not interested on a pick (Discover hero)", + "keys": [ + "OK", + "UP", + "RIGHT", + "OK", + "OK", + "RIGHT", + "RIGHT", + "OK" + ], + "presses": 8, + "note": "", + "start": "app launch: Who's watching with Justin focused (index.html:357)", + "replayed": "PASS", + "source": "index.html:2152-2174 PRESSES, replayed 2026-11-05" + }, + { + "goal": "Round 4 · Discover pick nobody holds → its Trailer (sample hand-off)", + "keys": [ + "OK", + "UP", + "RIGHT", + "OK", + "OK", + "RIGHT", + "RIGHT", + "RIGHT", + "OK" + ], + "presses": 9, + "note": "4 on the title page (3 Rights to Trailer, OK); The Amazing Spider-Man", + "start": "app launch: Who's watching with Justin focused (index.html:357)", + "replayed": "PASS", + "source": "index.html:2152-2174 PRESSES, replayed 2026-11-05", + "requires": "Conductarr trailer field (spec 3.2, fifth addition); the press path runs once it lands" + } + ] +} diff --git a/wolfskin-design/src/test/resources/requests-press-paths.json b/wolfskin-design/src/test/resources/requests-press-paths.json new file mode 100644 index 00000000..5d76c663 --- /dev/null +++ b/wolfskin-design/src/test/resources/requests-press-paths.json @@ -0,0 +1,21 @@ +[ + { + "goal": "Round 4 \u00b7 Home hero \u2192 Requests (Coming in) in Settings", + "keys": [ + "OK", + "UP", + "LEFT", + "OK", + "RIGHT", + "RIGHT", + "DOWN", + "OK", + "OK" + ], + "presses": 9, + "note": "8 from the hero", + "start": "app launch: Who's watching with Justin focused (index.html:357)", + "replayed": "PASS", + "source": "index.html:2152-2174 PRESSES, replayed 2026-11-05" + } +] diff --git a/wolfskin-design/src/test/resources/wolfskin-discover.json b/wolfskin-design/src/test/resources/wolfskin-discover.json new file mode 100644 index 00000000..450f0ece --- /dev/null +++ b/wolfskin-design/src/test/resources/wolfskin-discover.json @@ -0,0 +1 @@ +{"schemaVersion": 1, "generatedAt": "2026-11-04T20:42:12.030108+00:00", "profiles": [{"key": "justin", "name": "Justin", "initial": "J", "color": "#62c4ff", "destinations": {"Home": {"kind": "home"}, "Movies": {"kind": "library", "collection": "movies"}, "Shows": {"kind": "library", "collection": "shows"}, "Anime": {"kind": "library", "collection": "anime"}, "Discover": {"kind": "discover", "collection": "discover"}}, "movies": [{"id": "movie-the-girl-who-leapt-through-time-2006", "title": "The Girl Who Leapt Through Time", "year": 2006, "type": "movie", "runtimeSeconds": 5886, "seasonCount": null, "communityRating": 7.772, "criticRating": 87, "genres": ["Fantasy", "Animation", "Drama"], "overview": "When 17-year-old Makoto Konno gains the ability to 'leap' backwards through time, she immediately sets about improving her grades and preventing personal mishaps. However, she soon realises that changing the past\u2026", "poster": "assets/movie-the-girl-who-leapt-through-time-2006-poster.jpg", "backdrop": "assets/movie-the-girl-who-leapt-through-time-2006-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T17:39:11.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 14069, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 87, "rtCriticsLabel": "Fresh", "rtAudience": 90, "rtAudienceLabel": "Upright", "imdb": 7.6, "imdbVotes": 76917, "tmdb": 7.775, "tmdbVotes": 2345}}, {"id": "movie-locke-2014", "title": "Locke", "year": 2014, "type": "movie", "runtimeSeconds": 5090, "seasonCount": null, "communityRating": 6.891, "criticRating": 91, "genres": ["Drama", "Thriller"], "overview": "Ivan Locke has worked hard to craft a good life for himself. Tonight, that life will collapse around him. On the eve of the biggest challenge of his career, Ivan receives a phone call that sets in motion a series of\u2026", "poster": "assets/movie-locke-2014-poster.jpg", "backdrop": "assets/movie-locke-2014-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T17:38:48.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 210479, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 90, "rtCriticsLabel": "Certified Fresh", "rtAudience": 72, "rtAudienceLabel": "Upright", "imdb": 7.1, "imdbVotes": 171265, "tmdb": 6.889, "tmdbVotes": 3088}}, {"id": "movie-look-who-s-back-2015", "title": "Look Who's Back", "year": 2015, "type": "movie", "runtimeSeconds": 6936, "seasonCount": null, "communityRating": 6.793, "criticRating": null, "genres": ["Comedy"], "overview": "When Adolf Hitler reawakens at the site of his former bunker in present-day Berlin, he is mistaken for a comedian and quickly becomes a media phenomenon.", "poster": "assets/movie-look-who-s-back-2015-poster.jpg", "backdrop": "assets/movie-look-who-s-back-2015-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:27:48.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 318917, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": 81, "rtAudienceLabel": "Upright", "imdb": 7, "imdbVotes": 54672, "tmdb": 6.796, "tmdbVotes": 1990}}, {"id": "movie-memories-1995", "title": "Memories", "year": 1995, "type": "movie", "runtimeSeconds": 6805, "seasonCount": null, "communityRating": 7.465, "criticRating": null, "genres": ["Fantasy", "Animation", "Science Fiction"], "overview": "Three back-to-back anime films by three different directors make up this sci-fi trilogy three years in the making.", "poster": "assets/movie-memories-1995-poster.jpg", "backdrop": "assets/movie-memories-1995-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-30T21:36:03.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 42994, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": 82, "rtAudienceLabel": "Upright", "imdb": 7.5, "imdbVotes": 26638, "tmdb": 7.459, "tmdbVotes": 622}}, {"id": "movie-reincarnated-2012", "title": "Reincarnated", "year": 2012, "type": "movie", "runtimeSeconds": 5759, "seasonCount": null, "communityRating": 6.145, "criticRating": 28, "genres": ["Music", "Documentary"], "overview": "Evolution as an artist is often times what separates legends from the more mundane. After being heavily influenced by his experience in Jamaica \u2013 and his subsequent name change from Snoop Dogg to Snoop Lion \u2013 the LBC\u2026", "poster": "assets/movie-reincarnated-2012-poster.jpg", "backdrop": "assets/movie-reincarnated-2012-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:27:26.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 123431, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 28, "rtCriticsLabel": "Rotten", "rtAudience": 41, "rtAudienceLabel": "Spilled", "imdb": 6.2, "imdbVotes": 2563, "tmdb": 6.1, "tmdbVotes": 69}}, {"id": "movie-shipwrecked-nightmare-at-sea-2026", "title": "Shipwrecked: Nightmare at Sea", "year": 2026, "type": "movie", "runtimeSeconds": 5385, "seasonCount": null, "communityRating": 7.181, "criticRating": null, "genres": ["Documentary"], "overview": "Never-before-seen footage and survivor accounts trace the 2012 shipwreck of a luxury cruise and the disaster that ensued, in this immersive documentary.", "poster": "assets/movie-shipwrecked-nightmare-at-sea-2026-poster.jpg", "backdrop": "assets/movie-shipwrecked-nightmare-at-sea-2026-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-20T20:11:43.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 1715492, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": 6.5, "imdbVotes": 5080, "tmdb": 7.172, "tmdbVotes": 87}}, {"id": "movie-steins-gate-the-movie-load-region-of-deja-vu-2013", "title": "Steins;Gate: The Movie - Load Region of D\u00e9j\u00e0 Vu", "year": 2013, "type": "movie", "runtimeSeconds": 5403, "seasonCount": null, "communityRating": 7.8, "criticRating": null, "genres": ["Animation", "Mystery", "Sci-Fi"], "overview": "After the events of the anime, Rintarou begins to feel the repercussions of extensive time travel, and eventually completely fades from reality. Kurisu, being the only companion to remember him, now must find a way\u2026", "poster": "assets/movie-steins-gate-the-movie-load-region-of-deja-vu-2013-poster.jpg", "backdrop": "assets/movie-steins-gate-the-movie-load-region-of-deja-vu-2013-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-22T14:55:54.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}], "shows": [{"id": "tv-andor-2022", "title": "Andor", "year": 2022, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 8.293, "criticRating": null, "genres": ["Sci-Fi & Fantasy", "Action & Adventure", "Drama"], "overview": "In an era filled with danger, deception and intrigue, Cassian Andor will discover the difference he can make in the struggle against the tyrannical Galactic Empire. He embarks on a path that is destined to turn him\u2026", "poster": "assets/tv-andor-2022-poster.jpg", "backdrop": "assets/tv-andor-2022-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0432996Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 83867, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 96, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.321, "tmdbVotes": 2312}}, {"id": "tv-the-boys-2019", "title": "The Boys", "year": 2019, "type": "tv", "runtimeSeconds": null, "seasonCount": 5, "communityRating": 8.443, "criticRating": null, "genres": ["Sci-Fi & Fantasy", "Action & Adventure"], "overview": "A group of vigilantes known informally as \u201cThe Boys\u201d set out to take down corrupt superheroes with no more than blue-collar grit and a willingness to fight dirty.", "poster": "assets/tv-the-boys-2019-poster.jpg", "backdrop": "assets/tv-the-boys-2019-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0410131Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 76479, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 92, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.4, "tmdbVotes": 13628}}, {"id": "tv-californication-2007", "title": "Californication", "year": 2007, "type": "tv", "runtimeSeconds": 1680, "seasonCount": 7, "communityRating": 7.632, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "A self-loathing, alcoholic writer attempts to repair his damaged relationships with his daughter and her mother while combating sex addiction, a budding drug problem, and the seeming inability to avoid making bad\u2026", "poster": "assets/tv-californication-2007-poster.jpg", "backdrop": "assets/tv-californication-2007-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0428037Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 1215, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 57, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.62, "tmdbVotes": 1406}}, {"id": "tv-firefly-2002", "title": "Firefly", "year": 2002, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 8.347, "criticRating": null, "genres": ["Drama", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "In the year 2517, the universe is a vast frontier where lawlessness reigns and adventure awaits! Join Captain Malcolm Reynolds and his ragtag crew aboard the spaceship Serenity as they navigate the fringes of\u2026", "poster": "assets/tv-firefly-2002-poster.jpg", "backdrop": "assets/tv-firefly-2002-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-20T11:40:08.4202369Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 1437, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 77, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.351, "tmdbVotes": 2550}}, {"id": "tv-fleabag-2016", "title": "Fleabag", "year": 2016, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 8.278, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "A portrait into the mind of a dry-witted, sexual, angry, porn-watching, grief-riddled woman, trying to make sense of the world. As she hurls herself headlong at modern living, Fleabag is thrown roughly up against the\u2026", "poster": "assets/tv-fleabag-2016-poster.jpg", "backdrop": "assets/tv-fleabag-2016-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T17:42:25.4848214Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 67070, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.3, "tmdbVotes": 1993}}, {"id": "tv-gravity-falls-2012", "title": "Gravity Falls", "year": 2012, "type": "tv", "runtimeSeconds": 1320, "seasonCount": 2, "communityRating": 8.619, "criticRating": null, "genres": ["Action & Adventure", "Animation", "Comedy"], "overview": "Twin brother and sister Dipper and Mabel Pines are in for an unexpected adventure when they spend the summer helping their great uncle Stan run a tourist trap in the mysterious town of Gravity Falls, Oregon.", "poster": "assets/tv-gravity-falls-2012-poster.jpg", "backdrop": "assets/tv-gravity-falls-2012-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0435827Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 40075, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.624, "tmdbVotes": 3647}}, {"id": "tv-invincible-2021", "title": "INVINCIBLE", "year": 2021, "type": "tv", "runtimeSeconds": null, "seasonCount": 4, "communityRating": 8.638, "criticRating": null, "genres": ["Animation", "Drama", "Sci-Fi & Fantasy"], "overview": "Mark Grayson is a normal teenager except for the fact that his father is the most powerful superhero on the planet. Shortly after his seventeenth birthday, Mark begins to develop powers of his own and enters into his\u2026", "poster": "assets/tv-invincible-2021-poster.jpg", "backdrop": "assets/tv-invincible-2021-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.040159Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 95557, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 99, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.6, "tmdbVotes": 6155}}, {"id": "tv-the-legend-of-korra-2012", "title": "The Legend of Korra", "year": 2012, "type": "tv", "runtimeSeconds": 1380, "seasonCount": 4, "communityRating": 8.2, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "Avatar Korra, a headstrong, rebellious, feisty young woman who continually challenges and breaks with tradition, is on her quest to become a fully realized Avatar. In this story, the Avatar struggles to find balance\u2026", "poster": "assets/tv-the-legend-of-korra-2012-poster.jpg", "backdrop": "assets/tv-the-legend-of-korra-2012-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-20T00:19:23.2494945Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 33880, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 88, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.221, "tmdbVotes": 2482}}, {"id": "tv-love-death-robots-2019", "title": "Love, Death & Robots", "year": 2019, "type": "tv", "runtimeSeconds": null, "seasonCount": 4, "communityRating": 8.205, "criticRating": null, "genres": ["Animation", "Sci-Fi & Fantasy"], "overview": "Terrifying creatures, wicked surprises and dark comedy converge in this NSFW anthology of animated stories presented by Tim Miller and David Fincher.", "poster": "assets/tv-love-death-robots-2019-poster.jpg", "backdrop": "assets/tv-love-death-robots-2019-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-20T11:31:20.8818757Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 86831, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 89, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.203, "tmdbVotes": 4238}}, {"id": "tv-norsemen-2016", "title": "Norsemen", "year": 2016, "type": "tv", "runtimeSeconds": 1800, "seasonCount": 3, "communityRating": 7.177, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "Norsemen is an epic and humorous drama series set in the Viking Age. The residents of an 8th-century Viking village experience political rivalry, social change and innovations that upend their culture and way of life.", "poster": "assets/tv-norsemen-2016-poster.jpg", "backdrop": "assets/tv-norsemen-2016-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.034182Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 68126, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.2, "tmdbVotes": 233}}, {"id": "tv-rick-and-morty-2013", "title": "Rick and Morty", "year": 2013, "type": "tv", "runtimeSeconds": null, "seasonCount": 9, "communityRating": 8.679, "criticRating": null, "genres": ["Animation", "Comedy", "Sci-Fi & Fantasy"], "overview": "Follows a sociopathic genius scientist who drags his inherently timid grandson on adventures across the universe.", "poster": "assets/tv-rick-and-morty-2013-poster.jpg", "backdrop": "assets/tv-rick-and-morty-2013-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0419581Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 60625, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 91, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.7, "tmdbVotes": 11548}}, {"id": "tv-secret-level-2024", "title": "Secret Level", "year": 2024, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.411, "criticRating": null, "genres": ["Animation", "Sci-Fi & Fantasy", "Action & Adventure"], "overview": "Adult animated series of original short stories which are set within the worlds of beloved video games. Each episode serves as a gateway to a new adventure, unlocking exciting worlds from beloved gaming classics and\u2026", "poster": "assets/tv-secret-level-2024-poster.jpg", "backdrop": "assets/tv-secret-level-2024-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0336417Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 261579, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 65, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.392, "tmdbVotes": 612}}, {"id": "tv-silo-2023", "title": "Silo", "year": 2023, "type": "tv", "runtimeSeconds": null, "seasonCount": 3, "communityRating": 8.178, "criticRating": null, "genres": ["Sci-Fi & Fantasy", "Drama"], "overview": "In a ruined and toxic future, thousands live in a giant silo deep underground. After its sheriff breaks a cardinal rule and residents die mysteriously, engineer Juliette starts to uncover shocking secrets and the\u2026", "poster": "assets/tv-silo-2023-poster.jpg", "backdrop": "assets/tv-silo-2023-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0346526Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 125988, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 92, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.232, "tmdbVotes": 2881}}, {"id": "tv-south-park-1997", "title": "South Park", "year": 1997, "type": "tv", "runtimeSeconds": null, "seasonCount": 29, "communityRating": 8.336, "criticRating": null, "genres": ["Animation", "Comedy"], "overview": "Follow the misadventures of four irreverent grade-schoolers in the quiet, dysfunctional town of South Park, Colorado.", "poster": "assets/tv-south-park-1997-poster.jpg", "backdrop": "assets/tv-south-park-1997-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0456124Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 2190, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 80, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.332, "tmdbVotes": 5250}}], "anime": [{"id": "tv-bleach-2004", "title": "Bleach", "year": 2004, "type": "tv", "runtimeSeconds": 1440, "seasonCount": 17, "communityRating": 8.363, "criticRating": null, "genres": ["Action & Adventure", "Animation", "Sci-Fi & Fantasy"], "overview": "For as long as he can remember, Ichigo Kurosaki has been able to see ghosts. But when he meets Rukia, a Soul Reaper who battles evil spirits known as Hollows, he finds his life is changed forever. Now, with a\u2026", "poster": "assets/tv-bleach-2004-poster.jpg", "backdrop": "assets/tv-bleach-2004-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0359827Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 30984, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.4, "tmdbVotes": 2331}}, {"id": "tv-chainsaw-man-2022", "title": "Chainsaw Man", "year": 2022, "type": "tv", "runtimeSeconds": 1440, "seasonCount": 1, "communityRating": 8.515, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "Denji has a simple dream\u2014to live a happy and peaceful life, spending time with a girl he likes. This is a far cry from reality, however, as Denji is forced by the yakuza into killing devils in order to pay off his\u2026", "poster": "assets/tv-chainsaw-man-2022-poster.jpg", "backdrop": "assets/tv-chainsaw-man-2022-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-20T22:28:13.2095501Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 114410, "artwork": {"posterKind": "center_crop", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 97, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.508, "tmdbVotes": 2364}}, {"id": "tv-cyberpunk-edgerunners-2022", "title": "Cyberpunk: Edgerunners", "year": 2022, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 8.466, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "In a dystopia riddled with corruption and cybernetic implants, a talented but reckless street kid strives to become a mercenary outlaw \u2014 an edgerunner.", "poster": "assets/tv-cyberpunk-edgerunners-2022-poster.jpg", "backdrop": "assets/tv-cyberpunk-edgerunners-2022-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-20T22:28:13.2093107Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 105248, "artwork": {"posterKind": "center_crop", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 89, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.507, "tmdbVotes": 2132}}, {"id": "tv-dan-da-dan-2024", "title": "Dan Da Dan", "year": 2024, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 8.538, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Comedy"], "overview": "In a bet to prove whether ghosts or aliens exist, two high schoolers face terrifying paranormal threats, gain superpowers and maybe even fall in love?!", "poster": "assets/tv-dan-da-dan-2024-poster.jpg", "backdrop": "assets/tv-dan-da-dan-2024-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-22T10:31:16.2652252Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 240411, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.517, "tmdbVotes": 986}}, {"id": "tv-dr-stone-2019", "title": "Dr. STONE", "year": 2019, "type": "tv", "runtimeSeconds": 1440, "seasonCount": 4, "communityRating": 8.541, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Comedy"], "overview": "One fateful day, all of humanity was petrified by a blinding flash of light. After several millennia, high schooler Taiju awakens and finds himself lost in a world of statues. However, he\u2019s not alone! His\u2026", "poster": "assets/tv-dr-stone-2019-poster.jpg", "backdrop": "assets/tv-dr-stone-2019-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-20T22:28:13.2091579Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 86031, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.53, "tmdbVotes": 1814}}, {"id": "tv-frieren-beyond-journey-s-end-2023", "title": "Frieren: Beyond Journey's End", "year": 2023, "type": "tv", "runtimeSeconds": 1500, "seasonCount": 2, "communityRating": 8.803, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Drama"], "overview": "After the party of heroes defeated the Demon King, they restored peace to the land and returned to lives of solitude. Generations pass, and the elven mage Frieren comes face to face with humanity\u2019s mortality. She\u2026", "poster": "assets/tv-frieren-beyond-journey-s-end-2023-poster.jpg", "backdrop": "assets/tv-frieren-beyond-journey-s-end-2023-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-20T22:28:13.2094435Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 209867, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.826, "tmdbVotes": 1016}}, {"id": "tv-fullmetal-alchemist-brotherhood-2009", "title": "Fullmetal Alchemist: Brotherhood", "year": 2009, "type": "tv", "runtimeSeconds": 1500, "seasonCount": 1, "communityRating": 8.7, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "Disregard for alchemy\u2019s laws ripped half of Edward Elric\u2019s limbs from his body and left his brother Alphonse\u2019s soul clinging to a suit of armor. To restore what was lost, the brothers seek the Philosopher\u2019s Stone.\u2026", "poster": "assets/tv-fullmetal-alchemist-brotherhood-2009-poster.jpg", "backdrop": "assets/tv-fullmetal-alchemist-brotherhood-2009-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-20T10:27:12.3573939Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 31911, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.7, "tmdbVotes": 2633}}, {"id": "tv-jujutsu-kaisen-2020", "title": "JUJUTSU KAISEN", "year": 2020, "type": "tv", "runtimeSeconds": 1440, "seasonCount": 3, "communityRating": 8.583, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "Yuji Itadori is a boy with tremendous physical strength, though he lives a completely ordinary high school life. One day, to save a classmate who has been attacked by curses, he eats the finger of Ryomen Sukuna,\u2026", "poster": "assets/tv-jujutsu-kaisen-2020-poster.jpg", "backdrop": "assets/tv-jujutsu-kaisen-2020-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-20T22:28:13.207573Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 95479, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.574, "tmdbVotes": 4735}}, {"id": "tv-kaiju-no-8-2024", "title": "Kaiju No. 8", "year": 2024, "type": "tv", "runtimeSeconds": 1440, "seasonCount": 2, "communityRating": 8.436, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "In a world plagued by creatures known as Kaiju, Kafka Hibino aspired to enlist in The Defense Force. He makes a promise to enlist with his childhood friend, Mina Ashiro. Soon, life takes them in separate ways. While\u2026", "poster": "assets/tv-kaiju-no-8-2024-poster.jpg", "backdrop": "assets/tv-kaiju-no-8-2024-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-22T10:31:16.2646566Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 207468, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.4, "tmdbVotes": 836}}, {"id": "tv-my-hero-academia-2016", "title": "My Hero Academia", "year": 2016, "type": "tv", "runtimeSeconds": 1440, "seasonCount": 8, "communityRating": 8.607, "criticRating": null, "genres": ["Action & Adventure", "Animation", "Sci-Fi & Fantasy"], "overview": "After he saves a bully from a Villain, a normal student is granted a superpower that allows him to attend a high school training academy for Heroes.", "poster": "assets/tv-my-hero-academia-2016-poster.jpg", "backdrop": "assets/tv-my-hero-academia-2016-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-20T22:28:13.2089315Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 65930, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.6, "tmdbVotes": 5426}}, {"id": "tv-neon-genesis-evangelion-1995", "title": "Neon Genesis Evangelion", "year": 1995, "type": "tv", "runtimeSeconds": 1500, "seasonCount": 1, "communityRating": 8.568, "criticRating": null, "genres": ["Sci-Fi & Fantasy", "Animation", "Drama"], "overview": "At the turn of the century, the Angels returned to Earth, seeking to wipe out humanity in an apocalyptic fury. Devastated, mankind's last remnants moved underground to wait for the day when the Angels would come back\u2026", "poster": "assets/tv-neon-genesis-evangelion-1995-poster.jpg", "backdrop": "assets/tv-neon-genesis-evangelion-1995-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0340629Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 890, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.56, "tmdbVotes": 2205}}, {"id": "tv-record-of-ragnarok-2021", "title": "Record of Ragnarok", "year": 2021, "type": "tv", "runtimeSeconds": null, "seasonCount": 3, "communityRating": 8.402, "criticRating": null, "genres": ["Action & Adventure", "Animation", "Sci-Fi & Fantasy"], "overview": "Before eradicating humankind from the world, the gods give them one last chance to prove themselves worthy of survival. Let the Ragnarok battles begin.", "poster": "assets/tv-record-of-ragnarok-2021-poster.jpg", "backdrop": "assets/tv-record-of-ragnarok-2021-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-20T22:28:13.2085766Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 114868, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.403, "tmdbVotes": 1929}}, {"id": "tv-sakamoto-days-2025", "title": "SAKAMOTO DAYS", "year": 2025, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 8.071, "criticRating": null, "genres": ["Animation", "Comedy", "Action & Adventure"], "overview": "Once the greatest hitman of all, Taro Sakamoto retired in the name of love. But when his past catches up, he must fight to protect his beloved family.", "poster": "assets/tv-sakamoto-days-2025-poster.jpg", "backdrop": "assets/tv-sakamoto-days-2025-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-22T10:31:16.266066Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 207332, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.057, "tmdbVotes": 375}}, {"id": "tv-sentenced-to-be-a-hero-2025", "title": "Sentenced to Be a Hero", "year": 2025, "type": "tv", "runtimeSeconds": 1440, "seasonCount": 1, "communityRating": 8.338, "criticRating": null, "genres": ["Animation", "Sci-Fi & Fantasy", "Action & Adventure"], "overview": "In a world where heroism is a punishment, Xylo Forbartz, a condemned goddess killer, battles endless hordes of monstrous abominations as part of Penal Hero Unit 9004. Death is no escape, only a cycle of resurrection\u2026", "poster": "assets/tv-sentenced-to-be-a-hero-2025-poster.jpg", "backdrop": "assets/tv-sentenced-to-be-a-hero-2025-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-22T10:31:16.2666791Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 249907, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.244, "tmdbVotes": 129}}, {"id": "tv-spy-x-family-2022", "title": "SPY x FAMILY", "year": 2022, "type": "tv", "runtimeSeconds": 1440, "seasonCount": 3, "communityRating": 8.506, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Comedy"], "overview": "A spy, an assassin and a telepath come together to pose as a family, each for their own reasons, while hiding their true identities from each other.", "poster": "assets/tv-spy-x-family-2022-poster.jpg", "backdrop": "assets/tv-spy-x-family-2022-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-22T10:31:16.2670364Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 120089, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.5, "tmdbVotes": 2382}}, {"id": "tv-the-tatami-galaxy-2010", "title": "The Tatami Galaxy", "year": 2010, "type": "tv", "runtimeSeconds": 1500, "seasonCount": 2, "communityRating": 8.271, "criticRating": null, "genres": ["Animation", "Comedy", "Drama"], "overview": "When a college dropout stops for a late night bite at a mysterious ramen stand, he crosses paths with a self-proclaimed deity of matrimony. This bizarre meeting sends the young man hurtling through a horrifying\u2026", "poster": "assets/tv-the-tatami-galaxy-2010-poster.jpg", "backdrop": "assets/tv-the-tatami-galaxy-2010-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-18T05:52:51.0023256Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 36243, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.3, "tmdbVotes": 152}}, {"id": "tv-vinland-saga-2019", "title": "Vinland Saga", "year": 2019, "type": "tv", "runtimeSeconds": 1500, "seasonCount": 2, "communityRating": 8.527, "criticRating": null, "genres": ["Animation", "War & Politics", "Action & Adventure"], "overview": "For a thousand years, the Vikings have made quite a name and reputation for themselves as the strongest families with a thirst for violence. Thorfinn, the son of one of the Vikings' greatest warriors, spends his\u2026", "poster": "assets/tv-vinland-saga-2019-poster.jpg", "backdrop": "assets/tv-vinland-saga-2019-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-20T22:28:13.207389Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 88803, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.509, "tmdbVotes": 1064}}, {"id": "tv-yu-yu-hakusho-1992", "title": "Yu Yu Hakusho", "year": 1992, "type": "tv", "runtimeSeconds": 1440, "seasonCount": 4, "communityRating": 8.5, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "After dying to save a boy, delinquent tough guy Yusuke Urameshi is granted another chance at life by redeeming himself as a \"Spirit Detective.\"", "poster": "assets/tv-yu-yu-hakusho-1992-poster.jpg", "backdrop": "assets/tv-yu-yu-hakusho-1992-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-19T21:46:01.5733049Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 30669, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.475, "tmdbVotes": 519}}], "resume": [], "nextUp": [{"id": "episode-three-monsters-1992-s1e6", "title": "Three Monsters", "year": 1992, "type": "episode", "runtimeSeconds": 1421, "seasonCount": null, "communityRating": 7.4, "criticRating": null, "genres": ["Animation", "Action", "Adventure"], "overview": "Yusuke receives his first real case as Spirit Detective. Three demon thieves have muscled their way into King Yama's sacred vault, stealing the three Artifacts of Darkness. Yusuke must recover the artifacts and bring\u2026", "poster": "assets/episode-three-monsters-1992-s1e6-poster.jpg", "backdrop": "assets/episode-three-monsters-1992-s1e6-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-19T21:59:21.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "Yu Yu Hakusho", "seasonNumber": 1, "episodeNumber": 6, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}, {"id": "episode-all-hands-on-deck-class-1-a-2021-s5e1", "title": "All Hands on Deck! Class 1-A", "year": 2021, "type": "episode", "runtimeSeconds": 1433, "seasonCount": null, "communityRating": 6.938, "criticRating": null, "genres": ["Animation", "Action", "Adventure"], "overview": "There's an emergency drill at U.A. High School, and Class 1-A is requested to report for duty!", "poster": "assets/episode-all-hands-on-deck-class-1-a-2021-s5e1-poster.jpg", "backdrop": "assets/episode-all-hands-on-deck-class-1-a-2021-s5e1-backdrop.jpg", "played": false, "playCount": 2, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-22T04:04:50.0000000Z", "lastPlayedAt": "2026-10-19T17:47:09.3028025Z", "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "My Hero Academia", "seasonNumber": 5, "episodeNumber": 1, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}, {"id": "episode-erickerhead-2026-s9e6", "title": "Erickerhead", "year": 2026, "type": "episode", "runtimeSeconds": 1357, "seasonCount": null, "communityRating": 8.196, "criticRating": null, "genres": [], "overview": "Rick's stomach is mad, broh. But it's all-you-can-eat-and-drink here, broh!", "poster": "assets/episode-erickerhead-2026-s9e6-poster.jpg", "backdrop": "assets/episode-erickerhead-2026-s9e6-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-28T10:23:23.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "Rick and Morty", "seasonNumber": 9, "episodeNumber": 6, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}, {"id": "episode-girls-interrupted-2007-s1e7", "title": "Girls, Interrupted", "year": 2007, "type": "episode", "runtimeSeconds": 1642, "seasonCount": null, "communityRating": 7.4, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "Hank is disturbed by Becca's negative reaction towards the concept of happy endings, even more so when he finds out that it is partially his fault.\nMarcy comes up with the idea of a threesome with Charlie and Dani,\u2026", "poster": "assets/episode-girls-interrupted-2007-s1e7-poster.jpg", "backdrop": "assets/episode-girls-interrupted-2007-s1e7-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-05T02:46:18.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "Californication", "seasonNumber": 1, "episodeNumber": 7, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}, {"id": "episode-announcement-2022-s1e7", "title": "Announcement", "year": 2022, "type": "episode", "runtimeSeconds": 3012, "seasonCount": null, "communityRating": 7.8, "criticRating": null, "genres": ["Action", "Adventure", "Drama"], "overview": "The repercussions of the Aldhani heist reach all the way to Coruscant, while Cassian returns home.", "poster": "assets/episode-announcement-2022-s1e7-poster.jpg", "backdrop": "assets/episode-announcement-2022-s1e7-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T12:56:24.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "Andor", "seasonNumber": 1, "episodeNumber": 7, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}, {"id": "episode-south-american-biker-gangs-2026-s29e1", "title": "South American Biker Gangs", "year": 2026, "type": "episode", "runtimeSeconds": 1335, "seasonCount": null, "communityRating": 6.4, "criticRating": null, "genres": ["Animation", "Comedy"], "overview": "When e-bikes arrive in South America, the boys take over the streets and the parents of South America can hardly recognize South America.", "poster": "assets/episode-south-american-biker-gangs-2026-s29e1-poster.jpg", "backdrop": "assets/episode-south-american-biker-gangs-2026-s29e1-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-01T06:40:46.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "South Park", "seasonNumber": 29, "episodeNumber": 1, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}], "recentlyAdded": [{"id": "movie-steins-gate-the-movie-load-region-of-deja-vu-2013", "title": "Steins;Gate: The Movie - Load Region of D\u00e9j\u00e0 Vu", "year": 2013, "type": "movie", "runtimeSeconds": 5403, "seasonCount": null, "communityRating": 7.8, "criticRating": null, "genres": ["Animation", "Mystery", "Sci-Fi"], "overview": "After the events of the anime, Rintarou begins to feel the repercussions of extensive time travel, and eventually completely fades from reality. Kurisu, being the only companion to remember him, now must find a way\u2026", "poster": "assets/movie-steins-gate-the-movie-load-region-of-deja-vu-2013-poster.jpg", "backdrop": "assets/movie-steins-gate-the-movie-load-region-of-deja-vu-2013-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-22T14:55:54.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}, {"id": "tv-yu-yu-hakusho-1992", "title": "Yu Yu Hakusho", "year": 1992, "type": "tv", "runtimeSeconds": 1440, "seasonCount": 4, "communityRating": 8.5, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "After dying to save a boy, delinquent tough guy Yusuke Urameshi is granted another chance at life by redeeming himself as a \"Spirit Detective.\"", "poster": "assets/tv-yu-yu-hakusho-1992-poster.jpg", "backdrop": "assets/tv-yu-yu-hakusho-1992-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-19T21:46:01.5733049Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 30669, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.475, "tmdbVotes": 519}}, {"id": "movie-memories-1995", "title": "Memories", "year": 1995, "type": "movie", "runtimeSeconds": 6805, "seasonCount": null, "communityRating": 7.465, "criticRating": null, "genres": ["Fantasy", "Animation", "Science Fiction"], "overview": "Three back-to-back anime films by three different directors make up this sci-fi trilogy three years in the making.", "poster": "assets/movie-memories-1995-poster.jpg", "backdrop": "assets/movie-memories-1995-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-30T21:36:03.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 42994, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": 82, "rtAudienceLabel": "Upright", "imdb": 7.5, "imdbVotes": 26638, "tmdb": 7.459, "tmdbVotes": 622}}, {"id": "tv-firefly-2002", "title": "Firefly", "year": 2002, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 8.347, "criticRating": null, "genres": ["Drama", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "In the year 2517, the universe is a vast frontier where lawlessness reigns and adventure awaits! Join Captain Malcolm Reynolds and his ragtag crew aboard the spaceship Serenity as they navigate the fringes of\u2026", "poster": "assets/tv-firefly-2002-poster.jpg", "backdrop": "assets/tv-firefly-2002-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-20T11:40:08.4202369Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 1437, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 77, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.351, "tmdbVotes": 2550}}, {"id": "tv-love-death-robots-2019", "title": "Love, Death & Robots", "year": 2019, "type": "tv", "runtimeSeconds": null, "seasonCount": 4, "communityRating": 8.205, "criticRating": null, "genres": ["Animation", "Sci-Fi & Fantasy"], "overview": "Terrifying creatures, wicked surprises and dark comedy converge in this NSFW anthology of animated stories presented by Tim Miller and David Fincher.", "poster": "assets/tv-love-death-robots-2019-poster.jpg", "backdrop": "assets/tv-love-death-robots-2019-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-20T11:31:20.8818757Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 86831, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 89, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.203, "tmdbVotes": 4238}}, {"id": "tv-the-legend-of-korra-2012", "title": "The Legend of Korra", "year": 2012, "type": "tv", "runtimeSeconds": 1380, "seasonCount": 4, "communityRating": 8.2, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "Avatar Korra, a headstrong, rebellious, feisty young woman who continually challenges and breaks with tradition, is on her quest to become a fully realized Avatar. In this story, the Avatar struggles to find balance\u2026", "poster": "assets/tv-the-legend-of-korra-2012-poster.jpg", "backdrop": "assets/tv-the-legend-of-korra-2012-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-20T00:19:23.2494945Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 33880, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 88, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.221, "tmdbVotes": 2482}}, {"id": "tv-spy-x-family-2022", "title": "SPY x FAMILY", "year": 2022, "type": "tv", "runtimeSeconds": 1440, "seasonCount": 3, "communityRating": 8.506, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Comedy"], "overview": "A spy, an assassin and a telepath come together to pose as a family, each for their own reasons, while hiding their true identities from each other.", "poster": "assets/tv-spy-x-family-2022-poster.jpg", "backdrop": "assets/tv-spy-x-family-2022-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-22T10:31:16.2670364Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 120089, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.5, "tmdbVotes": 2382}}, {"id": "tv-sentenced-to-be-a-hero-2025", "title": "Sentenced to Be a Hero", "year": 2025, "type": "tv", "runtimeSeconds": 1440, "seasonCount": 1, "communityRating": 8.338, "criticRating": null, "genres": ["Animation", "Sci-Fi & Fantasy", "Action & Adventure"], "overview": "In a world where heroism is a punishment, Xylo Forbartz, a condemned goddess killer, battles endless hordes of monstrous abominations as part of Penal Hero Unit 9004. Death is no escape, only a cycle of resurrection\u2026", "poster": "assets/tv-sentenced-to-be-a-hero-2025-poster.jpg", "backdrop": "assets/tv-sentenced-to-be-a-hero-2025-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-22T10:31:16.2666791Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 249907, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.244, "tmdbVotes": 129}}, {"id": "tv-sakamoto-days-2025", "title": "SAKAMOTO DAYS", "year": 2025, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 8.071, "criticRating": null, "genres": ["Animation", "Comedy", "Action & Adventure"], "overview": "Once the greatest hitman of all, Taro Sakamoto retired in the name of love. But when his past catches up, he must fight to protect his beloved family.", "poster": "assets/tv-sakamoto-days-2025-poster.jpg", "backdrop": "assets/tv-sakamoto-days-2025-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-22T10:31:16.266066Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 207332, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.057, "tmdbVotes": 375}}, {"id": "tv-dan-da-dan-2024", "title": "Dan Da Dan", "year": 2024, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 8.538, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Comedy"], "overview": "In a bet to prove whether ghosts or aliens exist, two high schoolers face terrifying paranormal threats, gain superpowers and maybe even fall in love?!", "poster": "assets/tv-dan-da-dan-2024-poster.jpg", "backdrop": "assets/tv-dan-da-dan-2024-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-22T10:31:16.2652252Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 240411, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.517, "tmdbVotes": 986}}, {"id": "tv-kaiju-no-8-2024", "title": "Kaiju No. 8", "year": 2024, "type": "tv", "runtimeSeconds": 1440, "seasonCount": 2, "communityRating": 8.436, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "In a world plagued by creatures known as Kaiju, Kafka Hibino aspired to enlist in The Defense Force. He makes a promise to enlist with his childhood friend, Mina Ashiro. Soon, life takes them in separate ways. While\u2026", "poster": "assets/tv-kaiju-no-8-2024-poster.jpg", "backdrop": "assets/tv-kaiju-no-8-2024-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-22T10:31:16.2646566Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 207468, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.4, "tmdbVotes": 836}}, {"id": "tv-chainsaw-man-2022", "title": "Chainsaw Man", "year": 2022, "type": "tv", "runtimeSeconds": 1440, "seasonCount": 1, "communityRating": 8.515, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "Denji has a simple dream\u2014to live a happy and peaceful life, spending time with a girl he likes. This is a far cry from reality, however, as Denji is forced by the yakuza into killing devils in order to pay off his\u2026", "poster": "assets/tv-chainsaw-man-2022-poster.jpg", "backdrop": "assets/tv-chainsaw-man-2022-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-20T22:28:13.2095501Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 114410, "artwork": {"posterKind": "center_crop", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 97, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.508, "tmdbVotes": 2364}}], "discover": [{"id": "movie-the-amazing-spider-man-2012", "title": "The Amazing Spider-Man", "year": 2012, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action", "Adventure", "Science Fiction"], "overview": "A teenage Peter Parker grapples with both high school and amazing super-human crises as his alter-ego Spider-Man.", "poster": "assets/movie-the-amazing-spider-man-2012-poster.jpg", "backdrop": "assets/movie-the-amazing-spider-man-2012-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 1930, "ratings": {"rtCritics": 71, "rtCriticsLabel": "Certified Fresh", "rtAudience": 77, "rtAudienceLabel": "Upright", "imdb": 7, "imdbVotes": 791938, "tmdb": 6.768, "tmdbVotes": 19312}}, {"id": "movie-the-amazing-spider-man-2-2014", "title": "The Amazing Spider-Man 2", "year": 2014, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action", "Adventure", "Science Fiction"], "overview": "For Peter Parker, life is busy. Between taking out the bad guys as Spider-Man and spending time with the person he loves, Gwen Stacy, high school graduation cannot come quickly enough. Peter has not forgotten about\u2026", "poster": "assets/movie-the-amazing-spider-man-2-2014-poster.jpg", "backdrop": "assets/movie-the-amazing-spider-man-2-2014-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 102382, "ratings": {"rtCritics": 51, "rtCriticsLabel": "Rotten", "rtAudience": 64, "rtAudienceLabel": "Upright", "imdb": 6.6, "imdbVotes": 611981, "tmdb": 6.567, "tmdbVotes": 14919}}, {"id": "movie-the-animatrix-2003", "title": "The Animatrix", "year": 2003, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Animation", "Science Fiction"], "overview": "Straight from the creators of the groundbreaking Matrix trilogy, this collection of short animated films from the world's leading anime directors fuses computer graphics and Japanese anime to provide the background\u2026", "poster": "assets/movie-the-animatrix-2003-poster.jpg", "backdrop": "assets/movie-the-animatrix-2003-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 55931, "ratings": {"rtCritics": 86, "rtCriticsLabel": "Fresh", "rtAudience": 78, "rtAudienceLabel": "Upright", "imdb": 7.3, "imdbVotes": 91929, "tmdb": 7.221, "tmdbVotes": 1872}}, {"id": "movie-ant-man-and-the-wasp-quantumania-2023", "title": "Ant-Man and the Wasp: Quantumania", "year": 2023, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action", "Adventure", "Science Fiction"], "overview": "Super-Hero partners Scott Lang and Hope van Dyne, along with with Hope's parents Janet van Dyne and Hank Pym, and Scott's daughter Cassie Lang, find themselves exploring the Quantum Realm, interacting with strange\u2026", "poster": "assets/movie-ant-man-and-the-wasp-quantumania-2023-poster.jpg", "backdrop": "assets/movie-ant-man-and-the-wasp-quantumania-2023-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 640146, "ratings": {"rtCritics": 46, "rtCriticsLabel": "Rotten", "rtAudience": 81, "rtAudienceLabel": "Upright", "imdb": 6, "imdbVotes": 281738, "tmdb": 6.197, "tmdbVotes": 6236}}, {"id": "movie-appleseed-alpha-2014", "title": "Appleseed Alpha", "year": 2014, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Animation", "Science Fiction", "Action"], "overview": "The Third World War (2099\u20132126) devastated part of the Earth. Former nations are reforming while new powers emerge, and groups of humans struggle to survive in ruined cities. The story follows Deunan Knute and her\u2026", "poster": "assets/movie-appleseed-alpha-2014-poster.jpg", "backdrop": "assets/movie-appleseed-alpha-2014-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "center_crop", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 269650, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": 59, "rtAudienceLabel": "Spilled", "imdb": 6.5, "imdbVotes": 9689, "tmdb": 6.808, "tmdbVotes": 436}}, {"id": "movie-aquaman-and-the-lost-kingdom-2023", "title": "Aquaman and the Lost Kingdom", "year": 2023, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action", "Adventure", "Fantasy"], "overview": "Black Manta seeks revenge on Aquaman for his father's death. Wielding the Black Trident's power, he becomes a formidable foe. To defend Atlantis, Arthur (Aquaman) forges an alliance with his imprisoned brother. They\u2026", "poster": "assets/movie-aquaman-and-the-lost-kingdom-2023-poster.jpg", "backdrop": "assets/movie-aquaman-and-the-lost-kingdom-2023-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 572802, "ratings": {"rtCritics": 33, "rtCriticsLabel": "Rotten", "rtAudience": 79, "rtAudienceLabel": "Upright", "imdb": 5.6, "imdbVotes": 122422, "tmdb": 6.48, "tmdbVotes": 3748}}, {"id": "movie-the-avengers-2012", "title": "The Avengers", "year": 2012, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Science Fiction", "Action", "Adventure"], "overview": "When an unexpected enemy emerges and threatens global safety and security, Nick Fury, director of the international peacekeeping agency known as S.H.I.E.L.D., finds himself in need of a team to pull the world back\u2026", "poster": "assets/movie-the-avengers-2012-poster.jpg", "backdrop": "assets/movie-the-avengers-2012-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 24428, "ratings": {"rtCritics": 91, "rtCriticsLabel": "Certified Fresh", "rtAudience": 91, "rtAudienceLabel": "Upright", "imdb": 8, "imdbVotes": 1590693, "tmdb": 8.084, "tmdbVotes": 40262}}, {"id": "movie-avengers-confidential-black-widow-punisher-2014", "title": "Avengers Confidential: Black Widow & Punisher", "year": 2014, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Animation", "Science Fiction", "Action"], "overview": "When the Punisher takes out a black-market weapons dealer, he stumbles upon a far-reaching terrorist plot devised by a group known as Leviathan.", "poster": "assets/movie-avengers-confidential-black-widow-punisher-2014-poster.jpg", "backdrop": "assets/movie-avengers-confidential-black-widow-punisher-2014-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 257346, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": 55, "rtAudienceLabel": "Spilled", "imdb": 5.7, "imdbVotes": 5166, "tmdb": 6.368, "tmdbVotes": 303}}, {"id": "movie-avengers-age-of-ultron-2015", "title": "Avengers: Age of Ultron", "year": 2015, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action", "Adventure", "Science Fiction"], "overview": "When Tony Stark tries to jumpstart a dormant peacekeeping program, things go awry and Earth\u2019s Mightiest Heroes are put to the ultimate test as the fate of the planet hangs in the balance. As the villainous Ultron\u2026", "poster": "assets/movie-avengers-age-of-ultron-2015-poster.jpg", "backdrop": "assets/movie-avengers-age-of-ultron-2015-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 99861, "ratings": {"rtCritics": 75, "rtCriticsLabel": "Certified Fresh", "rtAudience": 82, "rtAudienceLabel": "Upright", "imdb": 7.3, "imdbVotes": 1025876, "tmdb": 7.278, "tmdbVotes": 25289}}, {"id": "movie-avengers-infinity-war-2018", "title": "Avengers: Infinity War", "year": 2018, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Adventure", "Action", "Science Fiction"], "overview": "As the Avengers and their allies have continued to protect the world from threats too large for any one hero to handle, a new danger has emerged from the cosmic shadows: Thanos. A despot of intergalactic infamy, his\u2026", "poster": "assets/movie-avengers-infinity-war-2018-poster.jpg", "backdrop": "assets/movie-avengers-infinity-war-2018-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 299536, "ratings": {"rtCritics": 85, "rtCriticsLabel": "Certified Fresh", "rtAudience": 92, "rtAudienceLabel": "Upright", "imdb": 8.4, "imdbVotes": 1421954, "tmdb": 8.246, "tmdbVotes": 33331}}, {"id": "movie-batman-ninja-2018", "title": "Batman Ninja", "year": 2018, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Animation", "Action", "Science Fiction"], "overview": "Batman, along with many of his allies and adversaries, finds himself transported to feudal Japan by Gorilla Grodd's time displacement machine.", "poster": "assets/movie-batman-ninja-2018-poster.jpg", "backdrop": "assets/movie-batman-ninja-2018-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 485942, "ratings": {"rtCritics": 84, "rtCriticsLabel": "Fresh", "rtAudience": 60, "rtAudienceLabel": "Upright", "imdb": 5.6, "imdbVotes": 24579, "tmdb": 5.9, "tmdbVotes": 1106}}, {"id": "movie-batman-v-superman-dawn-of-justice-2016", "title": "Batman v Superman: Dawn of Justice", "year": 2016, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action", "Adventure", "Fantasy"], "overview": "Fearing the actions of a god-like Super Hero left unchecked, Gotham City\u2019s own formidable, forceful vigilante takes on Metropolis\u2019s most revered, modern-day savior, while the world wrestles with what sort of hero it\u2026", "poster": "assets/movie-batman-v-superman-dawn-of-justice-2016-poster.jpg", "backdrop": "assets/movie-batman-v-superman-dawn-of-justice-2016-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 209112, "ratings": {"rtCritics": 28, "rtCriticsLabel": "Rotten", "rtAudience": 63, "rtAudienceLabel": "Upright", "imdb": 6.4, "imdbVotes": 801382, "tmdb": 6.014, "tmdbVotes": 19527}}, {"id": "movie-batman-the-dark-knight-returns-part-1-2012", "title": "Batman: The Dark Knight Returns, Part 1", "year": 2012, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Science Fiction", "Action", "Animation"], "overview": "Batman has not been seen for ten years. A new breed of criminal ravages Gotham City, forcing 55-year-old Bruce Wayne back into the cape and cowl. But, does he still have what it takes to fight crime in a new era?", "poster": "assets/movie-batman-the-dark-knight-returns-part-1-2012-poster.jpg", "backdrop": "assets/movie-batman-the-dark-knight-returns-part-1-2012-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 123025, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": 93, "rtAudienceLabel": "Upright", "imdb": 7.9, "imdbVotes": 68943, "tmdb": 7.742, "tmdbVotes": 1766}}, {"id": "movie-black-adam-2022", "title": "Black Adam", "year": 2022, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action", "Adventure", "Science Fiction"], "overview": "Nearly 5,000 years after he was bestowed with the almighty powers of the Egyptian gods\u2014and imprisoned just as quickly\u2014Black Adam is freed from his earthly tomb, ready to unleash his unique form of justice on the\u2026", "poster": "assets/movie-black-adam-2022-poster.jpg", "backdrop": "assets/movie-black-adam-2022-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 436270, "ratings": {"rtCritics": 39, "rtCriticsLabel": "Rotten", "rtAudience": 87, "rtAudienceLabel": "Upright", "imdb": 6.1, "imdbVotes": 309624, "tmdb": 6.747, "tmdbVotes": 7370}}, {"id": "movie-black-panther-2018", "title": "Black Panther", "year": 2018, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action", "Adventure", "Science Fiction"], "overview": "King T'Challa returns home to the reclusive, technologically advanced African nation of Wakanda to serve as his country's new leader. However, T'Challa soon finds that he is challenged for the throne by factions\u2026", "poster": "assets/movie-black-panther-2018-poster.jpg", "backdrop": "assets/movie-black-panther-2018-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 284054, "ratings": {"rtCritics": 96, "rtCriticsLabel": "Certified Fresh", "rtAudience": null, "rtAudienceLabel": "Spilled", "imdb": 7.3, "imdbVotes": 931120, "tmdb": 7.36, "tmdbVotes": 24190}}, {"id": "movie-black-panther-wakanda-forever-2022", "title": "Black Panther: Wakanda Forever", "year": 2022, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action", "Adventure", "Science Fiction"], "overview": "Queen Ramonda, Shuri, M\u2019Baku, Okoye and the Dora Milaje fight to protect their nation from intervening world powers in the wake of King T\u2019Challa\u2019s death. As the Wakandans strive to embrace their next chapter, the\u2026", "poster": "assets/movie-black-panther-wakanda-forever-2022-poster.jpg", "backdrop": "assets/movie-black-panther-wakanda-forever-2022-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 505642, "ratings": {"rtCritics": 84, "rtCriticsLabel": "Certified Fresh", "rtAudience": 93, "rtAudienceLabel": "Upright", "imdb": 6.6, "imdbVotes": 405088, "tmdb": 6.984, "tmdbVotes": 7920}}, {"id": "movie-blame-2017", "title": "BLAME!", "year": 2017, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action", "Drama", "Science Fiction"], "overview": "In the distant technological future, civilization has reached its ultimate Net-based form. An \"infection\" in the past caused the automated systems to spiral out of order, resulting in a multi-leveled city structure\u2026", "poster": "assets/movie-blame-2017-poster.jpg", "backdrop": "assets/movie-blame-2017-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 409421, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": 61, "rtAudienceLabel": "Upright", "imdb": 6.6, "imdbVotes": 10258, "tmdb": 6.523, "tmdbVotes": 370}}, {"id": "movie-bleach-the-movie-fade-to-black-2008", "title": "Bleach the Movie: Fade to Black", "year": 2008, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action", "Animation", "Fantasy"], "overview": "A strange disturbance in the World of the Living sends Ichigo and Kon to the Soul Society, where the Seireitei has been devastated by an explosion of reishi and Rukia is missing. Stranger still, none of the Soul\u2026", "poster": "assets/movie-bleach-the-movie-fade-to-black-2008-poster.jpg", "backdrop": "assets/movie-bleach-the-movie-fade-to-black-2008-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 17165, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": 67, "rtAudienceLabel": "Upright", "imdb": 7.1, "imdbVotes": 3693, "tmdb": 7.1, "tmdbVotes": 206}}, {"id": "tv-90-day-fiance-before-the-90-days-2017", "title": "90 Day Fianc\u00e9: Before the 90 Days", "year": 2017, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Reality"], "overview": "Follow couples who have an existing relationship online, but haven't met in person. We'll experience their journey as they travel to the other's foreign country for the first time in an attempt to establish an\u2026", "poster": "assets/tv-90-day-fiance-before-the-90-days-2017-poster.jpg", "backdrop": "assets/tv-90-day-fiance-before-the-90-days-2017-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 73319, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.5, "tmdbVotes": 45}}, {"id": "tv-90-day-fiance-the-other-way-2019", "title": "90 Day Fianc\u00e9: The Other Way", "year": 2019, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Reality"], "overview": "Americans move to a foreign country all in the name of love. Will uprooting their lives in the United States pay off, or will they be packing up and moving back to America brokenhearted?", "poster": "assets/tv-90-day-fiance-the-other-way-2019-poster.jpg", "backdrop": "assets/tv-90-day-fiance-the-other-way-2019-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 90046, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.8, "tmdbVotes": 34}}, {"id": "tv-the-100-2014", "title": "The 100", "year": 2014, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Sci-Fi & Fantasy", "Action & Adventure", "Drama"], "overview": "100 years in the future, when the Earth has been abandoned due to radioactivity, the last surviving humans live on an ark orbiting the planet \u2014 but the ark won't last forever. So the repressive regime picks 100\u2026", "poster": "assets/tv-the-100-2014-poster.jpg", "backdrop": "assets/tv-the-100-2014-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 48866, "ratings": {"rtCritics": 93, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.878, "tmdbVotes": 8855}}, {"id": "tv-the-abbott-and-costello-show-1952", "title": "The Abbott and Costello Show", "year": 1952, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Comedy"], "overview": "Bud and Lou are unemployed actors living in Mr. Fields\u2019 boarding house. Lou\u2019s girlfriend Hillary lives across the hall. Many situations arise leading to slapstick and puns.", "poster": "assets/tv-the-abbott-and-costello-show-1952-poster.jpg", "backdrop": "assets/tv-the-abbott-and-costello-show-1952-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 11821, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.2, "tmdbVotes": 22}}, {"id": "tv-accel-world-2012", "title": "Accel World", "year": 2012, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Drama"], "overview": "A short, overweight student uses the Neuro Link, an evolution of the Nerve Gear, to escape himself from the reality. But his skills in the virtual world brings him to the attention of Kuroyukihime, a popular student\u2026", "poster": "assets/tv-accel-world-2012-poster.jpg", "backdrop": "assets/tv-accel-world-2012-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "center_crop", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 43167, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.429, "tmdbVotes": 163}}, {"id": "tv-ahsoka-2023", "title": "Ahsoka", "year": 2023, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Sci-Fi & Fantasy", "Action & Adventure", "Drama"], "overview": "Former Jedi Knight Ahsoka Tano investigates an emerging threat to a vulnerable galaxy.", "poster": "assets/tv-ahsoka-2023-poster.jpg", "backdrop": "assets/tv-ahsoka-2023-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 114461, "ratings": {"rtCritics": 85, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.4, "tmdbVotes": 1351}}, {"id": "tv-american-dad-2005", "title": "American Dad!", "year": 2005, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Animation", "Comedy"], "overview": "The series focuses on an eccentric motley crew that is the Smith family and their three housemates: Father, husband, and breadwinner Stan Smith; his better half housewife, Francine Smith; their college-aged daughter,\u2026", "poster": "assets/tv-american-dad-2005-poster.jpg", "backdrop": "assets/tv-american-dad-2005-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 1433, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.022, "tmdbVotes": 2481}}, {"id": "tv-american-gods-2017", "title": "American Gods", "year": 2017, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Mystery", "Sci-Fi & Fantasy"], "overview": "An ex-con becomes the traveling partner of a conman who turns out to be one of the older gods trying to recruit troops to battle the upstart deities. Based on Neil Gaiman's fantasy novel.", "poster": "assets/tv-american-gods-2017-poster.jpg", "backdrop": "assets/tv-american-gods-2017-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 46639, "ratings": {"rtCritics": 75, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.1, "tmdbVotes": 1852}}, {"id": "tv-andromeda-2000", "title": "Andromeda", "year": 2000, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action & Adventure", "Drama", "Sci-Fi & Fantasy"], "overview": "Captain Dylan Hunt and his crew quest to restore a government that once presided over an extended peace and prosperity.", "poster": "assets/tv-andromeda-2000-poster.jpg", "backdrop": "assets/tv-andromeda-2000-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 2985, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.068, "tmdbVotes": 315}}, {"id": "tv-aqua-teen-hunger-force-2000", "title": "Aqua Teen Hunger Force", "year": 2000, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Animation", "Comedy"], "overview": "The surreal adventures of three anthropomorphic fast food items: Master Shake, Frylock and Meatwad, and their human nextdoor neighbor, Carl Brutananadilewski.", "poster": "assets/tv-aqua-teen-hunger-force-2000-poster.jpg", "backdrop": "assets/tv-aqua-teen-hunger-force-2000-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 251, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.5, "tmdbVotes": 272}}, {"id": "tv-arifureta-from-commonplace-to-world-s-strongest-2019", "title": "Arifureta: From Commonplace to World's Strongest", "year": 2019, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "Hajime Nagumo and his high school class are suddenly summoned to a fantastical land as heroes. But while most of his classmates have powerful stats and abilities, Hajime does not. Underappreciated and unprepared, he\u2026", "poster": "assets/tv-arifureta-from-commonplace-to-world-s-strongest-2019-poster.jpg", "backdrop": "assets/tv-arifureta-from-commonplace-to-world-s-strongest-2019-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 86034, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.382, "tmdbVotes": 662}}, {"id": "tv-arrow-2012", "title": "Arrow", "year": 2012, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Crime", "Drama", "Action & Adventure"], "overview": "Spoiled billionaire playboy Oliver Queen is missing and presumed dead when his yacht is lost at sea. He returns five years later a changed man, determined to clean up the city as a hooded vigilante armed with a bow.", "poster": "assets/tv-arrow-2012-poster.jpg", "backdrop": "assets/tv-arrow-2012-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 1412, "ratings": {"rtCritics": 86, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.834, "tmdbVotes": 6537}}, {"id": "tv-avatar-the-last-airbender-2024", "title": "Avatar: The Last Airbender", "year": 2024, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action & Adventure", "Sci-Fi & Fantasy", "Drama"], "overview": "A young boy known as the Avatar must master the four elemental powers to save a world at war \u2014 and fight a ruthless enemy bent on stopping him.", "poster": "assets/tv-avatar-the-last-airbender-2024-poster.jpg", "backdrop": "assets/tv-avatar-the-last-airbender-2024-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 82452, "ratings": {"rtCritics": 66, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.8, "tmdbVotes": 1406}}, {"id": "tv-avatar-seven-havens-2026", "title": "Avatar: Seven Havens", "year": 2026, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "In a world shattered by a devastating cataclysm, a young Earthbender discovers she's the new Avatar after Korra. But in this dangerous era, that title marks her as humanity's destroyer, not its savior. Hunted by both\u2026", "poster": "assets/tv-avatar-seven-havens-2026-poster.jpg", "backdrop": "assets/tv-avatar-seven-havens-2026-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 284833, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.9, "tmdbVotes": 183}}, {"id": "tv-batman-the-brave-and-the-bold-2008", "title": "Batman: The Brave and the Bold", "year": 2008, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "The Caped Crusader is teamed up with Blue Beetle, Green Arrow, Aquaman and countless others in his quest to uphold justice.", "poster": "assets/tv-batman-the-brave-and-the-bold-2008-poster.jpg", "backdrop": "assets/tv-batman-the-brave-and-the-bold-2008-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 15804, "ratings": {"rtCritics": 96, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.6, "tmdbVotes": 295}}, {"id": "tv-batwoman-2019", "title": "Batwoman", "year": 2019, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Sci-Fi & Fantasy", "Action & Adventure"], "overview": "Kate Kane, armed with a passion for social justice and a flair for speaking her mind, soars onto the streets of Gotham as Batwoman, an out lesbian and highly trained street fighter primed to snuff out the failing\u2026", "poster": "assets/tv-batwoman-2019-poster.jpg", "backdrop": "assets/tv-batwoman-2019-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 89247, "ratings": {"rtCritics": 83, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.8, "tmdbVotes": 1619}}, {"id": "tv-big-mouth-2017", "title": "Big Mouth", "year": 2017, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Comedy", "Animation"], "overview": "Teenage friends find their lives upended by the wonders and horrors of puberty in this edgy comedy from real-life pals Nick Kroll and Andrew Goldberg.", "poster": "assets/tv-big-mouth-2017-poster.jpg", "backdrop": "assets/tv-big-mouth-2017-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 74204, "ratings": {"rtCritics": 95, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.591, "tmdbVotes": 1274}}, {"id": "tv-black-lightning-2018", "title": "Black Lightning", "year": 2018, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action & Adventure", "Sci-Fi & Fantasy", "Drama"], "overview": "Jefferson Pierce is a man wrestling with a secret. As the father of two daughters and principal of a charter high school that also serves as a safe haven for young people in a New Orleans neighborhood overrun by gang\u2026", "poster": "assets/tv-black-lightning-2018-poster.jpg", "backdrop": "assets/tv-black-lightning-2018-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 71663, "ratings": {"rtCritics": 92, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.907, "tmdbVotes": 991}}], "discoverySource": {"state": "materialized_snapshot", "rankKnown": false, "selection": "First 18 alphabetically indexed movie stubs and first 18 series stubs, not ranked feed order", "observedAt": "2026-11-04T20:42:12.030108+00:00"}}, {"key": "mindel", "name": "Mindel", "initial": "M", "color": "#ff9d72", "destinations": {"Home": {"kind": "home"}, "Movies": {"kind": "library", "collection": "movies"}, "Shows": {"kind": "library", "collection": "shows"}, "Discover": {"kind": "discover", "collection": "discover"}}, "movies": [{"id": "movie-chicago-2002", "title": "Chicago", "year": 2002, "type": "movie", "runtimeSeconds": 6808, "seasonCount": null, "communityRating": 7.131, "criticRating": 87, "genres": ["Comedy", "Crime", "Drama"], "overview": "Murderesses Velma Kelly and Roxie Hart find themselves on death row together and fight for the fame that will keep them from the gallows in 1920s Chicago.", "poster": "assets/movie-chicago-2002-poster.jpg", "backdrop": "assets/movie-chicago-2002-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:24:53.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 1574, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 87, "rtCriticsLabel": "Certified Fresh", "rtAudience": 83, "rtAudienceLabel": "Upright", "imdb": 7.2, "imdbVotes": 259272, "tmdb": 7.135, "tmdbVotes": 3025}}, {"id": "movie-coherence-2014", "title": "Coherence", "year": 2014, "type": "movie", "runtimeSeconds": 5286, "seasonCount": null, "communityRating": 7.21, "criticRating": 89, "genres": ["Thriller", "Science Fiction"], "overview": "Four couples gather for dinner the night a mysterious and powerful comet passes overhead.", "poster": "assets/movie-coherence-2014-poster.jpg", "backdrop": "assets/movie-coherence-2014-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2017-08-12T23:49:00.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 220289, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 89, "rtCriticsLabel": "Certified Fresh", "rtAudience": 81, "rtAudienceLabel": "Upright", "imdb": 7.1, "imdbVotes": 180354, "tmdb": 7.208, "tmdbVotes": 3702}}, {"id": "movie-the-killing-of-a-sacred-deer-2017", "title": "The Killing of a Sacred Deer", "year": 2017, "type": "movie", "runtimeSeconds": 7262, "seasonCount": null, "communityRating": 7.003, "criticRating": 79, "genres": ["Drama", "Thriller", "Mystery"], "overview": "Dr. Steven Murphy is a renowned cardiovascular surgeon who presides over a spotless household with his wife and two children. Lurking at the margins of his idyllic suburban existence is Martin, a fatherless teen who\u2026", "poster": "assets/movie-the-killing-of-a-sacred-deer-2017-poster.jpg", "backdrop": "assets/movie-the-killing-of-a-sacred-deer-2017-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-22T21:49:26.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 399057, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 79, "rtCriticsLabel": "Certified Fresh", "rtAudience": 63, "rtAudienceLabel": "Upright", "imdb": 7, "imdbVotes": 219488, "tmdb": 7.001, "tmdbVotes": 4640}}, {"id": "movie-night-is-short-walk-on-girl-2017", "title": "Night Is Short, Walk On Girl", "year": 2017, "type": "movie", "runtimeSeconds": 5548, "seasonCount": null, "communityRating": 7.421, "criticRating": 90, "genres": ["Comedy", "Romance", "Animation"], "overview": "As a group of university students go out for a night on the town, a sophomore known only as \"The Girl with Black Hair\" experiences a series of surreal encounters with the local nightlife \u2013 all the while unaware of\u2026", "poster": "assets/movie-night-is-short-walk-on-girl-2017-poster.jpg", "backdrop": "assets/movie-night-is-short-walk-on-girl-2017-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T17:39:14.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 430214, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 90, "rtCriticsLabel": "Fresh", "rtAudience": 84, "rtAudienceLabel": "Upright", "imdb": 7.5, "imdbVotes": 7640, "tmdb": 7.4, "tmdbVotes": 267}}, {"id": "movie-thelma-louise-1991", "title": "Thelma & Louise", "year": 1991, "type": "movie", "runtimeSeconds": 7786, "seasonCount": null, "communityRating": 7.596, "criticRating": 87, "genres": ["Drama", "Crime", "Adventure"], "overview": "Taking a break from their dreary lives, close friends Thelma and Louise embark on a short weekend trip that ends in unforeseen incriminating circumstances. As fugitives, both women rediscover the strength of their\u2026", "poster": "assets/movie-thelma-louise-1991-poster.jpg", "backdrop": "assets/movie-thelma-louise-1991-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:24:40.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 1541, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 87, "rtCriticsLabel": "Certified Fresh", "rtAudience": 82, "rtAudienceLabel": "Upright", "imdb": 7.6, "imdbVotes": 197947, "tmdb": 7.594, "tmdbVotes": 4063}}, {"id": "movie-vicky-cristina-barcelona-2008", "title": "Vicky Cristina Barcelona", "year": 2008, "type": "movie", "runtimeSeconds": 5804, "seasonCount": null, "communityRating": 6.798, "criticRating": 80, "genres": ["Drama", "Romance"], "overview": "Two girlfriends on a summer holiday in Spain become enamored with the same painter, unaware that his ex-wife, with whom he has a tempestuous relationship, is about to re-enter the picture.", "poster": "assets/movie-vicky-cristina-barcelona-2008-poster.jpg", "backdrop": "assets/movie-vicky-cristina-barcelona-2008-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:25:34.0000000Z", "lastPlayedAt": "2026-08-11T20:12:57.3064089Z", "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 5038, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 80, "rtCriticsLabel": "Certified Fresh", "rtAudience": 74, "rtAudienceLabel": "Upright", "imdb": 7.1, "imdbVotes": 283286, "tmdb": 6.797, "tmdbVotes": 4164}}], "shows": [{"id": "tv-all-creatures-great-small-2020", "title": "All Creatures Great & Small", "year": 2020, "type": "tv", "runtimeSeconds": 3000, "seasonCount": 7, "communityRating": 8.2, "criticRating": null, "genres": ["Drama", "Comedy"], "overview": "The heartwarming and humorous adventures of a young country vet in the Yorkshire Dales in the 1930s. A remake of the 1978 series.", "poster": "assets/tv-all-creatures-great-small-2020-poster.jpg", "backdrop": "assets/tv-all-creatures-great-small-2020-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-11-04T20:13:03.4266156Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 108255, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 99, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.2, "tmdbVotes": 168}}, {"id": "tv-back-to-life-2019", "title": "Back to Life", "year": 2019, "type": "tv", "runtimeSeconds": 1500, "seasonCount": 2, "communityRating": 7.367, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "When Miri Matteson returns home after eighteen years, can she integrate back into her old life? With a terrible event from her past hanging over her, it won\u2019t be easy.", "poster": "assets/tv-back-to-life-2019-poster.jpg", "backdrop": "assets/tv-back-to-life-2019-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0688758Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 88510, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.4, "tmdbVotes": 65}}, {"id": "tv-big-boys-2022", "title": "Big Boys", "year": 2022, "type": "tv", "runtimeSeconds": 1440, "seasonCount": 3, "communityRating": 7.6, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "Two boys from very different ends of the 'spectrum of masculinity' become best friends at Brent University freshers' week; in their first year of uni, they explore, experiment and try to discover themselves, helping\u2026", "poster": "assets/tv-big-boys-2022-poster.jpg", "backdrop": "assets/tv-big-boys-2022-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-11-04T20:25:58.2156837Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 202851, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.6, "tmdbVotes": 49}}, {"id": "tv-call-the-midwife-2012", "title": "Call the Midwife", "year": 2012, "type": "tv", "runtimeSeconds": 3120, "seasonCount": 15, "communityRating": 7.5, "criticRating": null, "genres": ["Drama"], "overview": "Drama following the lives of a group of midwives working in the poverty-stricken East End of London during the 1950s, based on the best-selling memoirs of Jennifer Worth.", "poster": "assets/tv-call-the-midwife-2012-poster.jpg", "backdrop": "assets/tv-call-the-midwife-2012-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0377216Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 39793, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.577, "tmdbVotes": 267}}, {"id": "tv-derry-girls-2018", "title": "Derry Girls", "year": 2018, "type": "tv", "runtimeSeconds": 1380, "seasonCount": 3, "communityRating": 8.144, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "Amidst the political conflict of Northern Ireland in the 1990s, five secondary school students square off with the universal challenges of being a teenager.", "poster": "assets/tv-derry-girls-2018-poster.jpg", "backdrop": "assets/tv-derry-girls-2018-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-02T20:01:55.8955537Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 76148, "artwork": {"posterKind": "center_crop", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 99, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.138, "tmdbVotes": 454}}, {"id": "tv-desperate-housewives-2004", "title": "Desperate Housewives", "year": 2004, "type": "tv", "runtimeSeconds": 2520, "seasonCount": 8, "communityRating": 7.896, "criticRating": null, "genres": ["Mystery", "Drama", "Comedy"], "overview": "Looking down on her friends and family isn't a way of life for Mary Alice Young... it's a way of death. One day, in her perfect house, in the loveliest of suburbs, Mary Alice ended it all. Now she's taking us into\u2026", "poster": "assets/tv-desperate-housewives-2004-poster.jpg", "backdrop": "assets/tv-desperate-housewives-2004-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-29T20:56:40.3505596Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 693, "artwork": {"posterKind": "poster", "backdropKind": "center_crop"}, "ratings": {"rtCritics": 72, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.896, "tmdbVotes": 2171}}, {"id": "tv-the-diplomat-2023", "title": "The Diplomat", "year": 2023, "type": "tv", "runtimeSeconds": null, "seasonCount": 4, "communityRating": 7.569, "criticRating": null, "genres": ["Drama", "War & Politics"], "overview": "Amid an international crisis, a US diplomat contends with her high-profile job as ambassador to the UK and her strained marriage to a political star.", "poster": "assets/tv-the-diplomat-2023-poster.jpg", "backdrop": "assets/tv-the-diplomat-2023-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0640348Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 203857, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 93, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.652, "tmdbVotes": 521}}, {"id": "tv-the-durrells-2016", "title": "The Durrells", "year": 2016, "type": "tv", "runtimeSeconds": 2760, "seasonCount": 4, "communityRating": 7.402, "criticRating": null, "genres": ["Drama", "Comedy", "Family"], "overview": "In 1935, financially strapped widow Louisa Durrell, whose life has fallen apart, decides to move from England, with her four children (three sons, one daughter), to the island of Corfu, Greece. Once there, the family\u2026", "poster": "assets/tv-the-durrells-2016-poster.jpg", "backdrop": "assets/tv-the-durrells-2016-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0619436Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 66134, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 94, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.4, "tmdbVotes": 127}}, {"id": "tv-etoile-2025", "title": "\u00c9toile", "year": 2025, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.197, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "In an ambitious gambit to save their storied institutions, two world-renowned ballet companies in New York City and Paris swap their most talented stars.", "poster": "assets/tv-etoile-2025-poster.jpg", "backdrop": "assets/tv-etoile-2025-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0601383Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 225383, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 86, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.3, "tmdbVotes": 40}}, {"id": "tv-gilmore-girls-2000", "title": "Gilmore Girls", "year": 2000, "type": "tv", "runtimeSeconds": 2640, "seasonCount": 7, "communityRating": 7.893, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "Set in the charming town of Stars Hollow, Connecticut, the series follows the captivating lives of Lorelai and Rory Gilmore, a mother/daughter pair who have a relationship most people only dream of.", "poster": "assets/tv-gilmore-girls-2000-poster.jpg", "backdrop": "assets/tv-gilmore-girls-2000-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0631035Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 4586, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.9, "tmdbVotes": 1339}}, {"id": "tv-ginny-georgia-2021", "title": "Ginny & Georgia", "year": 2021, "type": "tv", "runtimeSeconds": null, "seasonCount": 3, "communityRating": 8.026, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "Free-spirited Georgia and her two kids, Ginny and Austin, move north in search of a fresh start but find that the road to new beginnings can be bumpy.", "poster": "assets/tv-ginny-georgia-2021-poster.jpg", "backdrop": "assets/tv-ginny-georgia-2021-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0694828Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 117581, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 68, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8, "tmdbVotes": 1840}}, {"id": "tv-hellcats-2010", "title": "Hellcats", "year": 2010, "type": "tv", "runtimeSeconds": 2460, "seasonCount": 1, "communityRating": 7.5, "criticRating": null, "genres": ["Drama"], "overview": "Pre-law student Marti Perkins' world is flipped upside down when she loses her scholarship, and realizes the only way she can stay in school is by reigniting her dormant teen gymnastic skills to win a place on Lancer\u2026", "poster": "assets/tv-hellcats-2010-poster.jpg", "backdrop": "assets/tv-hellcats-2010-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-22T20:39:02.7719318Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 32504, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 65, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.5, "tmdbVotes": 116}}, {"id": "tv-high-potential-2024", "title": "High Potential", "year": 2024, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 8.201, "criticRating": null, "genres": ["Drama", "Crime", "Mystery"], "overview": "Morgan is a single mom with an exceptional mind, whose unconventional knack for solving crimes leads to an unusual and unstoppable partnership with a by-the-book seasoned detective.", "poster": "assets/tv-high-potential-2024-poster.jpg", "backdrop": "assets/tv-high-potential-2024-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0686091Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 226637, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 98, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.3, "tmdbVotes": 707}}, {"id": "tv-how-to-get-to-heaven-from-belfast-2026", "title": "How to Get to Heaven from Belfast", "year": 2026, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.027, "criticRating": null, "genres": ["Mystery", "Comedy", "Drama"], "overview": "Three lifelong pals embark on a chaotic quest to solve the mystery of their old friend's suspicious death and keep their own dark secret under wraps.", "poster": "assets/tv-how-to-get-to-heaven-from-belfast-2026-poster.jpg", "backdrop": "assets/tv-how-to-get-to-heaven-from-belfast-2026-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0683192Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 233079, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 95, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.063, "tmdbVotes": 95}}, {"id": "tv-the-hunting-wives-2025", "title": "The Hunting Wives", "year": 2025, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 6.932, "criticRating": null, "genres": ["Drama", "Mystery"], "overview": "Sophie trades New England for East Texas and falls into a wealthy socialite's magnetic orbit \u2014 where a clique of housewives hide deadly secrets.", "poster": "assets/tv-the-hunting-wives-2025-poster.jpg", "backdrop": "assets/tv-the-hunting-wives-2025-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-20T19:42:46.3227892Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 244447, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 81, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.964, "tmdbVotes": 112}}, {"id": "tv-the-lost-flowers-of-alice-hart-2023", "title": "The Lost Flowers of Alice Hart", "year": 2023, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.634, "criticRating": null, "genres": ["Drama", "Mystery"], "overview": "After losing her parents in a mysterious fire, nine-year-old Alice Hart is raised by her grandmother June on a flower farm where she learns there are secrets within secrets. But years on, an unearthed betrayal sees\u2026", "poster": "assets/tv-the-lost-flowers-of-alice-hart-2023-poster.jpg", "backdrop": "assets/tv-the-lost-flowers-of-alice-hart-2023-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0607236Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 125816, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 82, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.64, "tmdbVotes": 161}}, {"id": "tv-love-is-blind-2020", "title": "Love Is Blind", "year": 2020, "type": "tv", "runtimeSeconds": null, "seasonCount": 11, "communityRating": 6.261, "criticRating": null, "genres": ["Reality"], "overview": "Nick and Vanessa Lachey host this social experiment where single men and women look for love and get engaged, all before meeting in person.", "poster": "assets/tv-love-is-blind-2020-poster.jpg", "backdrop": "assets/tv-love-is-blind-2020-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-22T17:11:08.2447226Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 99353, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 66, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.233, "tmdbVotes": 118}}, {"id": "tv-maximum-pleasure-guaranteed-2026", "title": "Maximum Pleasure Guaranteed", "year": 2026, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 6.676, "criticRating": null, "genres": ["Comedy"], "overview": "A newly divorced mom falls down a dangerous rabbit hole of blackmail, murder, and youth soccer.", "poster": "assets/tv-maximum-pleasure-guaranteed-2026-poster.jpg", "backdrop": "assets/tv-maximum-pleasure-guaranteed-2026-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-07T21:04:02.8277793Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 285404, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 93, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.799, "tmdbVotes": 92}}, {"id": "tv-minx-2022", "title": "Minx", "year": 2022, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 6.833, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "An earnest young feminist joins forces with a low-rent publisher to make the world's first erotic magazine for women.", "poster": "assets/tv-minx-2022-poster.jpg", "backdrop": "assets/tv-minx-2022-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0653613Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 118303, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 93, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.9, "tmdbVotes": 123}}, {"id": "tv-offspring-2010", "title": "Offspring", "year": 2010, "type": "tv", "runtimeSeconds": 3600, "seasonCount": 7, "communityRating": 6.9, "criticRating": null, "genres": ["Drama"], "overview": "An exuberant drama set in Melbourne's Fitzroy, centering on Nina Proudman and her struggle to deal with her fabulously messy family, her hunt for a decent love life and her tendency to overthink and fly off into fantasy.", "poster": "assets/tv-offspring-2010-poster.jpg", "backdrop": "assets/tv-offspring-2010-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0664581Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 33155, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.9, "tmdbVotes": 27}}, {"id": "tv-only-murders-in-the-building-2021", "title": "Only Murders in the Building", "year": 2021, "type": "tv", "runtimeSeconds": null, "seasonCount": 5, "communityRating": 8.398, "criticRating": null, "genres": ["Comedy", "Mystery", "Crime"], "overview": "Three strangers share an obsession with true crime and suddenly find themselves wrapped up in one.", "poster": "assets/tv-only-murders-in-the-building-2021-poster.jpg", "backdrop": "assets/tv-only-murders-in-the-building-2021-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-11-04T20:25:58.2163301Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 107113, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 95, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.398, "tmdbVotes": 2333}}, {"id": "tv-outlander-blood-of-my-blood-2025", "title": "Outlander: Blood of My Blood", "year": 2025, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 8.542, "criticRating": null, "genres": ["Drama"], "overview": "From the battlefields of World War I to the rugged Highlands of 18th century Scotland, two fated couples must defy the forces that seek to tear them apart, unfolding in surprising and unforeseen ways in this romantic\u2026", "poster": "assets/tv-outlander-blood-of-my-blood-2025-poster.jpg", "backdrop": "assets/tv-outlander-blood-of-my-blood-2025-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0691373Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 207484, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 94, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.738, "tmdbVotes": 124}}, {"id": "tv-rivals-2024", "title": "Rivals", "year": 2024, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 7.44, "criticRating": null, "genres": ["Drama"], "overview": "It is 1986 and Britain is booming. \"Rivals\" dives headfirst into the cutthroat world of TV where hair-dos are big and ambitions are even bigger. Deals are brokered in boardrooms, as well as bedrooms. Nobody can be\u2026", "poster": "assets/tv-rivals-2024-poster.jpg", "backdrop": "assets/tv-rivals-2024-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0645815Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 208921, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 96, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.4, "tmdbVotes": 93}}, {"id": "tv-rooster-2026", "title": "Rooster", "year": 2026, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.431, "criticRating": null, "genres": ["Comedy"], "overview": "On a college campus, an author navigates a complicated relationship with his daughter.", "poster": "assets/tv-rooster-2026-poster.jpg", "backdrop": "assets/tv-rooster-2026-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.063389Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 254953, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 88, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.423, "tmdbVotes": 144}}, {"id": "tv-sex-and-the-city-1998", "title": "Sex and the City", "year": 1998, "type": "tv", "runtimeSeconds": null, "seasonCount": 6, "communityRating": 7.365, "criticRating": null, "genres": ["Drama", "Comedy"], "overview": "Based on the bestselling book by Candace Bushnell, Sex and the City tells the story of four best friends, all single and in their late thirties, as they pursue their careers and talk about their sex lives, all while\u2026", "poster": "assets/tv-sex-and-the-city-1998-poster.jpg", "backdrop": "assets/tv-sex-and-the-city-1998-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0679324Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 105, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 70, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.351, "tmdbVotes": 2220}}, {"id": "tv-skymed-2022", "title": "SkyMed", "year": 2022, "type": "tv", "runtimeSeconds": 2640, "seasonCount": 4, "communityRating": 6.665, "criticRating": null, "genres": ["Drama"], "overview": "Life, death and drama at 20,000 feet. The series weaves together intense character journeys and high-stakes medical rescues, as we follow the triumphs, heartbreaks and tribulations of budding nurses and pilots flying\u2026", "poster": "assets/tv-skymed-2022-poster.jpg", "backdrop": "assets/tv-skymed-2022-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-22T19:44:29.2431354Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 133908, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.699, "tmdbVotes": 98}}, {"id": "tv-so-you-think-you-can-dance-2008", "title": "So You Think You Can Dance", "year": 2008, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 9.8, "criticRating": null, "genres": ["Family", "Reality"], "overview": "So You Think You Can Dance is a joint Belgian-Dutch dance competition show broadcast on RTL 5 and vtm television stations. The first season was broadcast only in the Netherlands, whereas starting with season 2, the\u2026", "poster": "assets/tv-so-you-think-you-can-dance-2008-poster.jpg", "backdrop": "assets/tv-so-you-think-you-can-dance-2008-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-23T11:11:35.4124418Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 40231, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 9.8, "tmdbVotes": 4}}, {"id": "tv-the-white-lotus-2021", "title": "The White Lotus", "year": 2021, "type": "tv", "runtimeSeconds": null, "seasonCount": 3, "communityRating": 7.597, "criticRating": null, "genres": ["Comedy", "Drama", "Mystery"], "overview": "Follow the exploits of various guests and employees at an exclusive tropical resort over the span of a week as with each passing day, a darker complexity emerges in these picture-perfect travelers, the hotel\u2019s\u2026", "poster": "assets/tv-the-white-lotus-2021-poster.jpg", "backdrop": "assets/tv-the-white-lotus-2021-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-20T02:45:52.8742674Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 111803, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 90, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.599, "tmdbVotes": 1604}}, {"id": "tv-yellowjackets-2021", "title": "Yellowjackets", "year": 2021, "type": "tv", "runtimeSeconds": null, "seasonCount": 3, "communityRating": 7.431, "criticRating": null, "genres": ["Drama", "Mystery"], "overview": "This equal parts survival epic, psychological horror story and coming-of-age drama tells the saga of a team of wildly talented high school girls soccer players who become the (un)lucky survivors of a plane crash deep\u2026", "poster": "assets/tv-yellowjackets-2021-poster.jpg", "backdrop": "assets/tv-yellowjackets-2021-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-11-04T20:31:47.4592756Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 117488, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 92, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.431, "tmdbVotes": 1070}}], "anime": [], "resume": [{"id": "episode-sex-and-the-country-2001-s4e9", "title": "Sex and the Country", "year": 2001, "type": "episode", "runtimeSeconds": 1751, "seasonCount": null, "communityRating": 5.9, "criticRating": null, "genres": ["Comedy", "Drama", "Romance"], "overview": "Carrie is dragged to Aidan's country home in Suffern, New York. Charlotte visits the MacDougal \"compound\" in Connecticut. Miranda yells at Steve when he tells her he has testicular cancer but has no clue about his\u2026", "poster": "assets/episode-sex-and-the-country-2001-s4e9-poster.jpg", "backdrop": "assets/episode-sex-and-the-country-2001-s4e9-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 96.08, "progress": 0.0549, "addedAt": "2026-07-17T10:53:15.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "Sex and the City", "seasonNumber": 4, "episodeNumber": 9, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}], "nextUp": [{"id": "episode-what-more-do-i-need-2008-s5e7", "title": "What More Do I Need?", "year": 2008, "type": "episode", "runtimeSeconds": 2521, "seasonCount": null, "communityRating": 8, "criticRating": null, "genres": ["Comedy", "Drama", "Mystery"], "overview": "Lynette and Tom uncover the unsettling truth about son Porter. Gabrielle suspects Carlos' best client, Virginia, has ulterior motives. Bree's moment of weakness could bring her great embarrassment and shame.\u2026", "poster": "assets/episode-what-more-do-i-need-2008-s5e7-poster.jpg", "backdrop": "assets/episode-what-more-do-i-need-2008-s5e7-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-29T14:37:28.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "Desperate Housewives", "seasonNumber": 5, "episodeNumber": 7, "artwork": {"posterKind": "center_crop", "backdropKind": "backdrop"}}, {"id": "episode-new-day-2021-s1e2", "title": "New Day", "year": 2021, "type": "episode", "runtimeSeconds": 3445, "seasonCount": null, "communityRating": 7.143, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "A cautiously optimistic Belinda walks a fine line with Tanya, while Armond brainstorms ways to deal with Shane. After her husband belittles her work, Rachel approaches Nicole for advice. Paula and Olivia lose track\u2026", "poster": "assets/episode-new-day-2021-s1e2-poster.jpg", "backdrop": "assets/episode-new-day-2021-s1e2-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-19T22:18:57.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "The White Lotus", "seasonNumber": 1, "episodeNumber": 2, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}, {"id": "episode-the-uncomfortability-2010-s1e2", "title": "The Uncomfortability", "year": 2010, "type": "episode", "runtimeSeconds": 2687, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "Nina's insecurities are revealed when a patient poses an awkward request. Meanwhile Geraldine decides to try online dating, and Jimmy is an unexpected saviour to Cherie and Darcy.", "poster": "assets/episode-the-uncomfortability-2010-s1e2-poster.jpg", "backdrop": "assets/episode-the-uncomfortability-2010-s1e2-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T11:47:42.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "Offspring", "seasonNumber": 1, "episodeNumber": 2, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}, {"id": "episode-episode-2-2016-s1e2", "title": "Episode 2", "year": 2016, "type": "episode", "runtimeSeconds": 2766, "seasonCount": null, "communityRating": 8.3, "criticRating": null, "genres": ["Biography", "Comedy", "Drama"], "overview": "Larry falls dangerously ill, and it falls to Theo and his neighbour Sven to put their rudimentary medical skills to the task of saving his life. The family's money worries continue, but Spiros brings them good news\u2026", "poster": "assets/episode-episode-2-2016-s1e2-poster.jpg", "backdrop": "assets/episode-episode-2-2016-s1e2-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-31T07:29:17.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "The Durrells", "seasonNumber": 1, "episodeNumber": 2, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}, {"id": "episode-remembrance-day-2026-s2e1", "title": "Remembrance Day", "year": 2026, "type": "episode", "runtimeSeconds": 3822, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Fantasy", "Romance"], "overview": "The fiery crosses have been lit across the Highlands; lovers must part, as the call to arms brings friends and enemies home and the Jacobite Rebellion begins in earnest.", "poster": "assets/episode-remembrance-day-2026-s2e1-poster.jpg", "backdrop": "assets/episode-remembrance-day-2026-s2e1-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-19T03:50:07.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "Outlander: Blood of My Blood", "seasonNumber": 2, "episodeNumber": 1, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}, {"id": "episode-one-of-us-2024-s1e7", "title": "One of Us", "year": 2024, "type": "episode", "runtimeSeconds": 2555, "seasonCount": null, "communityRating": 7.7, "criticRating": null, "genres": [], "overview": "Morgan agrees to show Ava around the LAPD, but things quickly take a dangerous turn when the precinct is held hostage by the friends of a recently convicted man. Can Morgan and the detectives outsmart their captors\u2026", "poster": "assets/episode-one-of-us-2024-s1e7-poster.jpg", "backdrop": "assets/episode-one-of-us-2024-s1e7-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T13:57:22.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "High Potential", "seasonNumber": 1, "episodeNumber": 7, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}, {"id": "episode-the-breakup-part-2-2001-s1e17", "title": "The Breakup, Part 2", "year": 2001, "type": "episode", "runtimeSeconds": 2669, "seasonCount": null, "communityRating": 7.4, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "After recent events, Rory keeps busy, refusing to give in to self-pity. Meanwhile, Stars Hollow's residents rally around her, much to her dismay.", "poster": "assets/episode-the-breakup-part-2-2001-s1e17-poster.jpg", "backdrop": "assets/episode-the-breakup-part-2-2001-s1e17-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T11:28:05.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "Gilmore Girls", "seasonNumber": 1, "episodeNumber": 17, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}, {"id": "episode-i-heart-ny-2002-s4e18", "title": "I Heart NY", "year": 2002, "type": "episode", "runtimeSeconds": 1905, "seasonCount": null, "communityRating": 6.667, "criticRating": null, "genres": ["Comedy", "Drama", "Romance"], "overview": "Carrie is shocked to find out that Big is moving to Napa, California and plans to give him a New York send-off. Charlotte begins dating another recent divorc\u00e9. Miranda prepares for the new baby as her delivery date\u2026", "poster": "assets/episode-i-heart-ny-2002-s4e18-poster.jpg", "backdrop": "assets/episode-i-heart-ny-2002-s4e18-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T12:35:14.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "Sex and the City", "seasonNumber": 4, "episodeNumber": 18, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}], "recentlyAdded": [{"id": "tv-yellowjackets-2021", "title": "Yellowjackets", "year": 2021, "type": "tv", "runtimeSeconds": null, "seasonCount": 3, "communityRating": 7.431, "criticRating": null, "genres": ["Drama", "Mystery"], "overview": "This equal parts survival epic, psychological horror story and coming-of-age drama tells the saga of a team of wildly talented high school girls soccer players who become the (un)lucky survivors of a plane crash deep\u2026", "poster": "assets/tv-yellowjackets-2021-poster.jpg", "backdrop": "assets/tv-yellowjackets-2021-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-11-04T20:31:47.4592756Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 117488, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 92, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.431, "tmdbVotes": 1070}}, {"id": "tv-only-murders-in-the-building-2021", "title": "Only Murders in the Building", "year": 2021, "type": "tv", "runtimeSeconds": null, "seasonCount": 5, "communityRating": 8.398, "criticRating": null, "genres": ["Comedy", "Mystery", "Crime"], "overview": "Three strangers share an obsession with true crime and suddenly find themselves wrapped up in one.", "poster": "assets/tv-only-murders-in-the-building-2021-poster.jpg", "backdrop": "assets/tv-only-murders-in-the-building-2021-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-11-04T20:25:58.2163301Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 107113, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 95, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.398, "tmdbVotes": 2333}}, {"id": "tv-big-boys-2022", "title": "Big Boys", "year": 2022, "type": "tv", "runtimeSeconds": 1440, "seasonCount": 3, "communityRating": 7.6, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "Two boys from very different ends of the 'spectrum of masculinity' become best friends at Brent University freshers' week; in their first year of uni, they explore, experiment and try to discover themselves, helping\u2026", "poster": "assets/tv-big-boys-2022-poster.jpg", "backdrop": "assets/tv-big-boys-2022-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-11-04T20:25:58.2156837Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 202851, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.6, "tmdbVotes": 49}}, {"id": "tv-all-creatures-great-small-2020", "title": "All Creatures Great & Small", "year": 2020, "type": "tv", "runtimeSeconds": 3000, "seasonCount": 7, "communityRating": 8.2, "criticRating": null, "genres": ["Drama", "Comedy"], "overview": "The heartwarming and humorous adventures of a young country vet in the Yorkshire Dales in the 1930s. A remake of the 1978 series.", "poster": "assets/tv-all-creatures-great-small-2020-poster.jpg", "backdrop": "assets/tv-all-creatures-great-small-2020-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-11-04T20:13:03.4266156Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 108255, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 99, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.2, "tmdbVotes": 168}}, {"id": "movie-the-killing-of-a-sacred-deer-2017", "title": "The Killing of a Sacred Deer", "year": 2017, "type": "movie", "runtimeSeconds": 7262, "seasonCount": null, "communityRating": 7.003, "criticRating": 79, "genres": ["Drama", "Thriller", "Mystery"], "overview": "Dr. Steven Murphy is a renowned cardiovascular surgeon who presides over a spotless household with his wife and two children. Lurking at the margins of his idyllic suburban existence is Martin, a fatherless teen who\u2026", "poster": "assets/movie-the-killing-of-a-sacred-deer-2017-poster.jpg", "backdrop": "assets/movie-the-killing-of-a-sacred-deer-2017-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-22T21:49:26.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 399057, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 79, "rtCriticsLabel": "Certified Fresh", "rtAudience": 63, "rtAudienceLabel": "Upright", "imdb": 7, "imdbVotes": 219488, "tmdb": 7.001, "tmdbVotes": 4640}}, {"id": "tv-derry-girls-2018", "title": "Derry Girls", "year": 2018, "type": "tv", "runtimeSeconds": 1380, "seasonCount": 3, "communityRating": 8.144, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "Amidst the political conflict of Northern Ireland in the 1990s, five secondary school students square off with the universal challenges of being a teenager.", "poster": "assets/tv-derry-girls-2018-poster.jpg", "backdrop": "assets/tv-derry-girls-2018-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-02T20:01:55.8955537Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 76148, "artwork": {"posterKind": "center_crop", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 99, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.138, "tmdbVotes": 454}}, {"id": "tv-so-you-think-you-can-dance-2008", "title": "So You Think You Can Dance", "year": 2008, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 9.8, "criticRating": null, "genres": ["Family", "Reality"], "overview": "So You Think You Can Dance is a joint Belgian-Dutch dance competition show broadcast on RTL 5 and vtm television stations. The first season was broadcast only in the Netherlands, whereas starting with season 2, the\u2026", "poster": "assets/tv-so-you-think-you-can-dance-2008-poster.jpg", "backdrop": "assets/tv-so-you-think-you-can-dance-2008-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-23T11:11:35.4124418Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 40231, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 9.8, "tmdbVotes": 4}}, {"id": "tv-hellcats-2010", "title": "Hellcats", "year": 2010, "type": "tv", "runtimeSeconds": 2460, "seasonCount": 1, "communityRating": 7.5, "criticRating": null, "genres": ["Drama"], "overview": "Pre-law student Marti Perkins' world is flipped upside down when she loses her scholarship, and realizes the only way she can stay in school is by reigniting her dormant teen gymnastic skills to win a place on Lancer\u2026", "poster": "assets/tv-hellcats-2010-poster.jpg", "backdrop": "assets/tv-hellcats-2010-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-22T20:39:02.7719318Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 32504, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 65, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.5, "tmdbVotes": 116}}, {"id": "tv-skymed-2022", "title": "SkyMed", "year": 2022, "type": "tv", "runtimeSeconds": 2640, "seasonCount": 4, "communityRating": 6.665, "criticRating": null, "genres": ["Drama"], "overview": "Life, death and drama at 20,000 feet. The series weaves together intense character journeys and high-stakes medical rescues, as we follow the triumphs, heartbreaks and tribulations of budding nurses and pilots flying\u2026", "poster": "assets/tv-skymed-2022-poster.jpg", "backdrop": "assets/tv-skymed-2022-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-22T19:44:29.2431354Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 133908, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.699, "tmdbVotes": 98}}, {"id": "tv-love-is-blind-2020", "title": "Love Is Blind", "year": 2020, "type": "tv", "runtimeSeconds": null, "seasonCount": 11, "communityRating": 6.261, "criticRating": null, "genres": ["Reality"], "overview": "Nick and Vanessa Lachey host this social experiment where single men and women look for love and get engaged, all before meeting in person.", "poster": "assets/tv-love-is-blind-2020-poster.jpg", "backdrop": "assets/tv-love-is-blind-2020-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-22T17:11:08.2447226Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 99353, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 66, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.233, "tmdbVotes": 118}}, {"id": "tv-the-hunting-wives-2025", "title": "The Hunting Wives", "year": 2025, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 6.932, "criticRating": null, "genres": ["Drama", "Mystery"], "overview": "Sophie trades New England for East Texas and falls into a wealthy socialite's magnetic orbit \u2014 where a clique of housewives hide deadly secrets.", "poster": "assets/tv-the-hunting-wives-2025-poster.jpg", "backdrop": "assets/tv-the-hunting-wives-2025-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-20T19:42:46.3227892Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 244447, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 81, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.964, "tmdbVotes": 112}}, {"id": "tv-the-white-lotus-2021", "title": "The White Lotus", "year": 2021, "type": "tv", "runtimeSeconds": null, "seasonCount": 3, "communityRating": 7.597, "criticRating": null, "genres": ["Comedy", "Drama", "Mystery"], "overview": "Follow the exploits of various guests and employees at an exclusive tropical resort over the span of a week as with each passing day, a darker complexity emerges in these picture-perfect travelers, the hotel\u2019s\u2026", "poster": "assets/tv-the-white-lotus-2021-poster.jpg", "backdrop": "assets/tv-the-white-lotus-2021-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-20T02:45:52.8742674Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 111803, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 90, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.599, "tmdbVotes": 1604}}], "discover": [{"id": "movie-across-the-hall-2009", "title": "Across the Hall", "year": 2009, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Thriller", "Mystery"], "overview": "Convinced that his fiancee is cheating on him, a man follows her to a hotel and calls his best friend to help him avoid a calamity.", "poster": "assets/movie-across-the-hall-2009-poster.jpg", "backdrop": "assets/movie-across-the-hall-2009-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 21469, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": 39, "rtAudienceLabel": "Spilled", "imdb": 5.8, "imdbVotes": 5130, "tmdb": 6, "tmdbVotes": 129}}, {"id": "movie-addicted-2014", "title": "Addicted", "year": 2014, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Thriller"], "overview": "A gallerist risks her family and flourishing career when she enters into an affair with a talented painter and slowly loses control of her life.", "poster": "assets/movie-addicted-2014-poster.jpg", "backdrop": "assets/movie-addicted-2014-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 235271, "ratings": {"rtCritics": 7, "rtCriticsLabel": "Rotten", "rtAudience": 51, "rtAudienceLabel": "Spilled", "imdb": 5, "imdbVotes": 13131, "tmdb": 5.928, "tmdbVotes": 466}}, {"id": "movie-adulterers-2016", "title": "Adulterers", "year": 2016, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Thriller", "Drama"], "overview": "On a steamy New Orleans afternoon, Sam Deuprey decides to surprise his beautiful wife, Ashley, for their anniversary and comes home early. But Ashley is not alone in the bedroom. She and her lover, Damien are about\u2026", "poster": "assets/movie-adulterers-2016-poster.jpg", "backdrop": "assets/movie-adulterers-2016-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 376004, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": 37, "rtAudienceLabel": "Spilled", "imdb": 5.1, "imdbVotes": 2144, "tmdb": 6.2, "tmdbVotes": 116}}, {"id": "movie-alone-with-her-2006", "title": "Alone With Her", "year": 2006, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Crime", "Drama", "Thriller"], "overview": "The harrowing story of a disturbed young man's attempts to win the affections of an unsuspecting young woman. When Doug first sees Amy, he instantly falls for her and begins to watch her every move, going so far as\u2026", "poster": "assets/movie-alone-with-her-2006-poster.jpg", "backdrop": "assets/movie-alone-with-her-2006-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 18206, "ratings": {"rtCritics": 70, "rtCriticsLabel": "Fresh", "rtAudience": 46, "rtAudienceLabel": "Spilled", "imdb": 6, "imdbVotes": 4359, "tmdb": 5.9, "tmdbVotes": 94}}, {"id": "movie-awake-2007", "title": "Awake", "year": 2007, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Thriller", "Crime", "Mystery"], "overview": "While undergoing heart surgery, a man experiences a phenomenon called \u2018anesthetic awareness\u2019, which leaves him awake but paralyzed throughout the operation. As various obstacles present themselves, his wife must make\u2026", "poster": "assets/movie-awake-2007-poster.jpg", "backdrop": "assets/movie-awake-2007-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 13483, "ratings": {"rtCritics": 23, "rtCriticsLabel": "Rotten", "rtAudience": 55, "rtAudienceLabel": "Spilled", "imdb": 6.5, "imdbVotes": 89389, "tmdb": 6.586, "tmdbVotes": 1649}}, {"id": "movie-black-bear-2020", "title": "Black Bear", "year": 2020, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Thriller"], "overview": "At a remote lake house in the Adirondack Mountains, a couple entertains an out-of-town guest looking for inspiration in her filmmaking. The group quickly falls into a calculated game of desire, manipulation, and\u2026", "poster": "assets/movie-black-bear-2020-poster.jpg", "backdrop": "assets/movie-black-bear-2020-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 624788, "ratings": {"rtCritics": 89, "rtCriticsLabel": "Certified Fresh", "rtAudience": 64, "rtAudienceLabel": "Upright", "imdb": 6.5, "imdbVotes": 18408, "tmdb": 6.337, "tmdbVotes": 315}}, {"id": "movie-butterfly-on-a-wheel-2007", "title": "Butterfly on a Wheel", "year": 2007, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Crime", "Drama", "Thriller"], "overview": "A sociopathic kidnapper methodically pushes a desperate pair of parents to their absolute breaking point.", "poster": "assets/movie-butterfly-on-a-wheel-2007-poster.jpg", "backdrop": "assets/movie-butterfly-on-a-wheel-2007-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 13250, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": 61, "rtAudienceLabel": "Upright", "imdb": 6.6, "imdbVotes": 35409, "tmdb": 6.457, "tmdbVotes": 595}}, {"id": "movie-careful-what-you-wish-for-2015", "title": "Careful What You Wish For", "year": 2015, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Thriller"], "overview": "A guy gets more than he bargained for after entering into an affair with the wife of an investment banker. Soon, a suspicious death and substantial life insurance policy embroil him in a scandal.", "poster": "assets/movie-careful-what-you-wish-for-2015-poster.jpg", "backdrop": "assets/movie-careful-what-you-wish-for-2015-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 317744, "ratings": {"rtCritics": 17, "rtCriticsLabel": "Rotten", "rtAudience": 61, "rtAudienceLabel": "Upright", "imdb": 5.6, "imdbVotes": 9452, "tmdb": 6.129, "tmdbVotes": 446}}, {"id": "movie-crime-of-passion-1956", "title": "Crime of Passion", "year": 1956, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Crime", "Thriller"], "overview": "Kathy leaves the newspaper business to marry homicide detective Bill, but is frustrated by his lack of ambition and the banality of life in the suburbs. Her drive to advance Bill's career soon takes her down a\u2026", "poster": "assets/movie-crime-of-passion-1956-poster.jpg", "backdrop": "assets/movie-crime-of-passion-1956-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "center_crop", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 1920, "ratings": {"rtCritics": 80, "rtCriticsLabel": "Fresh", "rtAudience": 34, "rtAudienceLabel": "Spilled", "imdb": 6.4, "imdbVotes": 3750, "tmdb": 6.1, "tmdbVotes": 57}}, {"id": "movie-deep-water-2022", "title": "Deep Water", "year": 2022, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Mystery", "Thriller"], "overview": "A well-to-do husband who allows his wife to have affairs in order to avoid a divorce becomes a prime suspect in the disappearance of her lovers.", "poster": "assets/movie-deep-water-2022-poster.jpg", "backdrop": "assets/movie-deep-water-2022-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 619979, "ratings": {"rtCritics": 35, "rtCriticsLabel": "Rotten", "rtAudience": 25, "rtAudienceLabel": "Spilled", "imdb": 5.5, "imdbVotes": 74098, "tmdb": 5.834, "tmdbVotes": 1755}}, {"id": "movie-derailed-2005", "title": "Derailed", "year": 2005, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Thriller"], "overview": "When two married business executives having an affair are blackmailed by a violent criminal, they are forced to turn the tables on him to save their families.", "poster": "assets/movie-derailed-2005-poster.jpg", "backdrop": "assets/movie-derailed-2005-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 8999, "ratings": {"rtCritics": 20, "rtCriticsLabel": "Rotten", "rtAudience": 58, "rtAudienceLabel": "Spilled", "imdb": 6.6, "imdbVotes": 79563, "tmdb": 6.405, "tmdbVotes": 1193}}, {"id": "movie-die-my-love-2025", "title": "Die My Love", "year": 2025, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama"], "overview": "After inheriting a remote Montana house, Jackson moves there from New York with his partner Grace, and the couple soon welcome a child. As Jackson becomes increasingly absent and rural isolation sets in, Grace\u2026", "poster": "assets/movie-die-my-love-2025-poster.jpg", "backdrop": "assets/movie-die-my-love-2025-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 1033148, "ratings": {"rtCritics": 74, "rtCriticsLabel": "Certified Fresh", "rtAudience": 45, "rtAudienceLabel": "Spilled", "imdb": 6, "imdbVotes": 50664, "tmdb": 6.1, "tmdbVotes": 683}}, {"id": "movie-dream-house-2011", "title": "Dream House", "year": 2011, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Mystery", "Drama", "Thriller"], "overview": "Publisher Will Atenton quits a lucrative job in New York to relocate his wife, Libby, and their daughters to a quaint town in New England. However, as they settle into their home the Atentons discover that a woman\u2026", "poster": "assets/movie-dream-house-2011-poster.jpg", "backdrop": "assets/movie-dream-house-2011-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 69668, "ratings": {"rtCritics": 7, "rtCriticsLabel": "Rotten", "rtAudience": 36, "rtAudienceLabel": "Spilled", "imdb": 6, "imdbVotes": 71386, "tmdb": 6.212, "tmdbVotes": 1480}}, {"id": "movie-dream-lover-1994", "title": "Dream Lover", "year": 1994, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Mystery", "Thriller"], "overview": "Not long after they cross paths at an art gallery, architect Ray Reardon and hypnotically sensual Lena are married with children. But as strange incidents occur, Ray begins to realize he may not really know the woman\u2026", "poster": "assets/movie-dream-lover-1994-poster.jpg", "backdrop": "assets/movie-dream-lover-1994-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 18190, "ratings": {"rtCritics": 57, "rtCriticsLabel": "Rotten", "rtAudience": 38, "rtAudienceLabel": "Spilled", "imdb": 6.2, "imdbVotes": 7865, "tmdb": 6.844, "tmdbVotes": 167}}, {"id": "movie-drop-2025", "title": "Drop", "year": 2025, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Mystery", "Thriller"], "overview": "Violet, a widowed mother on her first date in years, arrives at an upscale restaurant where she is relieved that her date, Henry, is more charming and handsome than she expected. But their chemistry begins to curdle\u2026", "poster": "assets/movie-drop-2025-poster.jpg", "backdrop": "assets/movie-drop-2025-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 1249213, "ratings": {"rtCritics": 83, "rtCriticsLabel": "Certified Fresh", "rtAudience": 79, "rtAudienceLabel": "Upright", "imdb": 6.1, "imdbVotes": 61018, "tmdb": 6.275, "tmdbVotes": 1023}}, {"id": "movie-eastern-promises-2007", "title": "Eastern Promises", "year": 2007, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Thriller", "Crime", "Mystery"], "overview": "A Russian teenager living in London dies during childbirth but leaves clues in her diary that could tie her child to a rape involving a violent Russian mob family.", "poster": "assets/movie-eastern-promises-2007-poster.jpg", "backdrop": "assets/movie-eastern-promises-2007-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 2252, "ratings": {"rtCritics": 89, "rtCriticsLabel": "Certified Fresh", "rtAudience": 83, "rtAudienceLabel": "Upright", "imdb": 7.6, "imdbVotes": 281015, "tmdb": 7.349, "tmdbVotes": 3900}}, {"id": "movie-faces-1968", "title": "Faces", "year": 1968, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama"], "overview": "Middle-aged suburban husband Richard abruptly tells his wife, Maria, that he wants a divorce. As Richard takes up with a younger woman, Maria enjoys a night on the town with her friends and meets a younger man. As\u2026", "poster": "assets/movie-faces-1968-poster.jpg", "backdrop": "assets/movie-faces-1968-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "center_crop", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 753, "ratings": {"rtCritics": 83, "rtCriticsLabel": "Fresh", "rtAudience": 86, "rtAudienceLabel": "Upright", "imdb": 7.4, "imdbVotes": 12950, "tmdb": 7.093, "tmdbVotes": 227}}, {"id": "movie-fatale-2020", "title": "Fatale", "year": 2020, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Thriller"], "overview": "After a wild one-night stand, successful sports agent Derrick Tyler watches his perfect life slowly disappear when he discovers the sexy and mysterious woman he risked everything for is determined police detective\u2026", "poster": "assets/movie-fatale-2020-poster.jpg", "backdrop": "assets/movie-fatale-2020-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 549514, "ratings": {"rtCritics": 46, "rtCriticsLabel": "Rotten", "rtAudience": 87, "rtAudienceLabel": "Upright", "imdb": 5.5, "imdbVotes": 16392, "tmdb": 6, "tmdbVotes": 401}}, {"id": "tv-56-days-2026", "title": "56 Days", "year": 2026, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Crime", "Mystery"], "overview": "When an unidentified body is found in a luxury apartment linked to Oliver Kennedy and his girlfriend Ciara Wyse, Detectives Lee Reardon and Karl Connolly reconstruct the couple's deadly romance across the past 56 days.", "poster": "assets/tv-56-days-2026-poster.jpg", "backdrop": "assets/tv-56-days-2026-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 241372, "ratings": {"rtCritics": 60, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.388, "tmdbVotes": 245}}, {"id": "tv-90-day-fiance-before-the-90-days-2017", "title": "90 Day Fianc\u00e9: Before the 90 Days", "year": 2017, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Reality"], "overview": "Follow couples who have an existing relationship online, but haven't met in person. We'll experience their journey as they travel to the other's foreign country for the first time in an attempt to establish an\u2026", "poster": "assets/tv-90-day-fiance-before-the-90-days-2017-poster.jpg", "backdrop": "assets/tv-90-day-fiance-before-the-90-days-2017-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 73319, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.5, "tmdbVotes": 45}}, {"id": "tv-90-day-fiance-the-other-way-2019", "title": "90 Day Fianc\u00e9: The Other Way", "year": 2019, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Reality"], "overview": "Americans move to a foreign country all in the name of love. Will uprooting their lives in the United States pay off, or will they be packing up and moving back to America brokenhearted?", "poster": "assets/tv-90-day-fiance-the-other-way-2019-poster.jpg", "backdrop": "assets/tv-90-day-fiance-the-other-way-2019-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 90046, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.8, "tmdbVotes": 34}}, {"id": "tv-absolutely-fabulous-1992", "title": "Absolutely Fabulous", "year": 1992, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Comedy"], "overview": "Set in the world of fashion and PR, immature fun-loving mother Edina Monsoon and her best friend Patsy drive Eddie's sensible daughter, Saffron, up the wall with their constant drug abuse and outrageous selfishness.\u2026", "poster": "assets/tv-absolutely-fabulous-1992-poster.jpg", "backdrop": "assets/tv-absolutely-fabulous-1992-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 4493, "ratings": {"rtCritics": 96, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.6, "tmdbVotes": 198}}, {"id": "tv-the-afterparty-2022", "title": "The Afterparty", "year": 2022, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Mystery", "Comedy", "Crime"], "overview": "Nothing is as it seems in this hilarious murder mystery where each suspect's story is told through a different film genre.", "poster": "assets/tv-the-afterparty-2022-poster.jpg", "backdrop": "assets/tv-the-afterparty-2022-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 106454, "ratings": {"rtCritics": 92, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.071, "tmdbVotes": 245}}, {"id": "tv-agatha-christie-s-marple-2004", "title": "Agatha Christie's Marple", "year": 2004, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Crime", "Mystery"], "overview": "The adventures of Miss Jane Marple, an elderly spinster living in the quiet little village of St Mary Mead. During her many visits to friends and relatives in other villages, Miss Marple often stumbles upon\u2026", "poster": "assets/tv-agatha-christie-s-marple-2004-poster.jpg", "backdrop": "assets/tv-agatha-christie-s-marple-2004-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 6127, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.9, "tmdbVotes": 152}}, {"id": "tv-all-american-homecoming-2022", "title": "All American: Homecoming", "year": 2022, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama"], "overview": "Follow the journeys of Simone Hicks, a tennis hopeful from Beverly Hills, and elite baseball player Damon Sims from Chicago, as they navigate life at the prestigious HBCU Bringston University.", "poster": "assets/tv-all-american-homecoming-2022-poster.jpg", "backdrop": "assets/tv-all-american-homecoming-2022-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 153723, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.3, "tmdbVotes": 70}}, {"id": "tv-and-just-like-that-2021", "title": "And Just Like That\u2026", "year": 2021, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "This new chapter of \u201cSex and the City\u201d follows Carrie, Miranda and Charlotte as they navigate the journey from the complicated reality of life and friendship in their 30s to the even more complicated reality of life\u2026", "poster": "assets/tv-and-just-like-that-2021-poster.jpg", "backdrop": "assets/tv-and-just-like-that-2021-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 116450, "ratings": {"rtCritics": 52, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.211, "tmdbVotes": 556}}, {"id": "tv-anne-with-an-e-2017", "title": "Anne with an E", "year": 2017, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Family"], "overview": "A coming-of-age story about an outsider who, against all odds and numerous challenges, fights for love and acceptance and for her place in the world. The series centers on a young orphaned girl in the late 1890s,\u2026", "poster": "assets/tv-anne-with-an-e-2017-poster.jpg", "backdrop": "assets/tv-anne-with-an-e-2017-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 70785, "ratings": {"rtCritics": 63, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.7, "tmdbVotes": 5019}}, {"id": "tv-awkward-2011", "title": "Awkward.", "year": 2011, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "An irreverent look at the conflict, chaos and humor that defines teenage life through the eyes of 15-year-old Jenna Hamilton whose life begins to change when a simple accident becomes an epic misunderstanding and is\u2026", "poster": "assets/tv-awkward-2011-poster.jpg", "backdrop": "assets/tv-awkward-2011-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 39859, "ratings": {"rtCritics": 78, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.4, "tmdbVotes": 427}}, {"id": "tv-the-baby-sitters-club-2020", "title": "The Baby-Sitters Club", "year": 2020, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Kids", "Drama"], "overview": "In this contemporary take on the beloved book series, five best friends launch a baby-sitting business that's big on fun and adventure.", "poster": "assets/tv-the-baby-sitters-club-2020-poster.jpg", "backdrop": "assets/tv-the-baby-sitters-club-2020-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 100412, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.9, "tmdbVotes": 506}}, {"id": "tv-ballers-2015", "title": "Ballers", "year": 2015, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Comedy"], "overview": "Looking at the lives of former and current football players, the show follows former superstar Spencer Strasmore as he gets his life on track in retirement while mentoring other current and former players through the\u2026", "poster": "assets/tv-ballers-2015-poster.jpg", "backdrop": "assets/tv-ballers-2015-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 62704, "ratings": {"rtCritics": 72, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.809, "tmdbVotes": 820}}, {"id": "tv-baretta-1975", "title": "Baretta", "year": 1975, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Crime", "Drama", "Mystery"], "overview": "Baretta is an American detective television series which ran on ABC from 1975 to 1978. The show was a milder version of a successful 1973\u201374 ABC series, Toma, starring Tony Musante as chameleon-like, real-life New\u2026", "poster": "assets/tv-baretta-1975-poster.jpg", "backdrop": "assets/tv-baretta-1975-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 3216, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.5, "tmdbVotes": 43}}, {"id": "tv-based-on-a-true-story-2023", "title": "Based on a True Story", "year": 2023, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "The lives of a realtor, a plumber and a former tennis star unexpectedly collide, exposing America\u2019s obsession with true crime, murder and the slow-close toilet seat.", "poster": "assets/tv-based-on-a-true-story-2023-poster.jpg", "backdrop": "assets/tv-based-on-a-true-story-2023-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 197373, "ratings": {"rtCritics": 79, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7, "tmdbVotes": 160}}, {"id": "tv-the-beast-in-me-2025", "title": "The Beast in Me", "year": 2025, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Mystery"], "overview": "A famous author is pulled into a twisted mind game with her rich, powerful new neighbor \u2014 who might be a murderer.", "poster": "assets/tv-the-beast-in-me-2025-poster.jpg", "backdrop": "assets/tv-the-beast-in-me-2025-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 250504, "ratings": {"rtCritics": 85, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.072, "tmdbVotes": 334}}, {"id": "tv-big-city-greens-2018", "title": "Big City Greens", "year": 2018, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Family", "Animation", "Comedy"], "overview": "The offbeat adventures of 10-year-old Cricket Green, a mischievous and optimistic country boy who moves to the big city with his wildly out of place family \u2013 older sister Tilly, father Bill and Gramma Alice.", "poster": "assets/tv-big-city-greens-2018-poster.jpg", "backdrop": "assets/tv-big-city-greens-2018-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 80587, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.555, "tmdbVotes": 82}}, {"id": "tv-the-bold-type-2017", "title": "The Bold Type", "year": 2017, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Comedy"], "overview": "A glimpse into the outrageous lives of Jane, Kat and Sutton, who are working at the nation's top women's magazine, Scarlet, while navigating their careers, identities and individual voices.", "poster": "assets/tv-the-bold-type-2017-poster.jpg", "backdrop": "assets/tv-the-bold-type-2017-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 69630, "ratings": {"rtCritics": 89, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.7, "tmdbVotes": 204}}, {"id": "tv-brassic-2019", "title": "Brassic", "year": 2019, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Comedy", "Drama", "Crime"], "overview": "A group of working-class friends finding unconventional ways to win at life in suburban northern England. These lads have dealt, scammed, bribed and conned their way through adolescence, but now, their dealing and\u2026", "poster": "assets/tv-brassic-2019-poster.jpg", "backdrop": "assets/tv-brassic-2019-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 92621, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.336, "tmdbVotes": 263}}], "discoverySource": {"state": "materialized_snapshot", "rankKnown": false, "selection": "First 18 alphabetically indexed movie stubs and first 18 series stubs, not ranked feed order", "observedAt": "2026-11-04T20:42:12.030108+00:00"}}, {"key": "shared", "name": "Shared", "initial": "S", "color": "#9be38f", "destinations": {"Home": {"kind": "home"}, "Movies": {"kind": "library", "collection": "movies"}, "Shows": {"kind": "library", "collection": "shows"}, "Discover": {"kind": "discover", "collection": "discover"}}, "movies": [{"id": "movie-american-gangster-2007", "title": "American Gangster", "year": 2007, "type": "movie", "runtimeSeconds": 10546, "seasonCount": null, "communityRating": 7.595, "criticRating": 81, "genres": ["Drama", "Crime"], "overview": "Loosely based on the criminal career of Frank Lucas, a gangster from La Grange, North Carolina, who smuggled heroin into the United States on American service planes returning from the Vietnam War, before being\u2026", "poster": "assets/movie-american-gangster-2007-poster.jpg", "backdrop": "assets/movie-american-gangster-2007-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-08-23T10:22:36.0000000Z", "lastPlayedAt": "2026-08-30T21:05:39.7952451Z", "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 4982, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 81, "rtCriticsLabel": "Certified Fresh", "rtAudience": 90, "rtAudienceLabel": "Upright", "imdb": 7.8, "imdbVotes": 495656, "tmdb": 7.599, "tmdbVotes": 6350}}, {"id": "movie-annie-hall-1977", "title": "Annie Hall", "year": 1977, "type": "movie", "runtimeSeconds": 5604, "seasonCount": null, "communityRating": 7.712, "criticRating": 97, "genres": ["Comedy", "Drama", "Romance"], "overview": "New York comedian Alvy Singer falls in love with the ditsy Annie Hall.", "poster": "assets/movie-annie-hall-1977-poster.jpg", "backdrop": "assets/movie-annie-hall-1977-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:24:23.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 703, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 97, "rtCriticsLabel": "Certified Fresh", "rtAudience": 92, "rtAudienceLabel": "Upright", "imdb": 7.9, "imdbVotes": 293349, "tmdb": 7.71, "tmdbVotes": 4391}}, {"id": "movie-apocalypse-now-1979", "title": "Apocalypse Now", "year": 1979, "type": "movie", "runtimeSeconds": 8835, "seasonCount": null, "communityRating": 8.261, "criticRating": 91, "genres": ["Drama", "War"], "overview": "At the height of the Vietnam war, Captain Benjamin Willard is sent on a dangerous mission that, officially, \"does not exist, nor will it ever exist.\" His goal is to locate - and eliminate - a mysterious Green Beret\u2026", "poster": "assets/movie-apocalypse-now-1979-poster.jpg", "backdrop": "assets/movie-apocalypse-now-1979-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:22:25.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 28, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 91, "rtCriticsLabel": "Certified Fresh", "rtAudience": 94, "rtAudienceLabel": "Upright", "imdb": 8.4, "imdbVotes": 781424, "tmdb": 8.3, "tmdbVotes": 9526}}, {"id": "movie-arrival-2016", "title": "Arrival", "year": 2016, "type": "movie", "runtimeSeconds": 6967, "seasonCount": null, "communityRating": 7.636, "criticRating": 94, "genres": ["Drama", "Science Fiction", "Mystery"], "overview": "Taking place after alien crafts land around the world, an expert linguist is recruited by the military to determine whether they come in peace or are a threat.", "poster": "assets/movie-arrival-2016-poster.jpg", "backdrop": "assets/movie-arrival-2016-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-14T19:54:12.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 329865, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 94, "rtCriticsLabel": "Certified Fresh", "rtAudience": 83, "rtAudienceLabel": "Upright", "imdb": 7.9, "imdbVotes": 883621, "tmdb": 7.638, "tmdbVotes": 20125}}, {"id": "movie-avatar-aang-the-last-airbender-2026", "title": "Avatar Aang: The Last Airbender", "year": 2026, "type": "movie", "runtimeSeconds": 5930, "seasonCount": null, "communityRating": 9.147, "criticRating": 90, "genres": ["Animation", "Adventure", "Fantasy"], "overview": "Avatar Aang, the world's last Airbender, learns of an ancient power that could save his culture from extinction. With the help of his friends, he embarks on a global quest to find it before it falls into the wrong\u2026", "poster": "assets/movie-avatar-aang-the-last-airbender-2026-poster.jpg", "backdrop": "assets/movie-avatar-aang-the-last-airbender-2026-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-25T09:53:09.0000000Z", "lastPlayedAt": "2026-08-02T19:22:33.6155383Z", "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 980431, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 90, "rtCriticsLabel": "Certified Fresh", "rtAudience": 99, "rtAudienceLabel": "Upright", "imdb": 7.7, "imdbVotes": 30792, "tmdb": 9, "tmdbVotes": 1417}}, {"id": "movie-bohemian-rhapsody-2018", "title": "Bohemian Rhapsody", "year": 2018, "type": "movie", "runtimeSeconds": 8077, "seasonCount": null, "communityRating": 7.95, "criticRating": 60, "genres": ["Music", "Drama"], "overview": "Singer Freddie Mercury, guitarist Brian May, drummer Roger Taylor and bass guitarist John Deacon take the music world by storm when they form the rock 'n' roll band Queen in 1970. Hit songs become instant classics.\u2026", "poster": "assets/movie-bohemian-rhapsody-2018-poster.jpg", "backdrop": "assets/movie-bohemian-rhapsody-2018-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:28:20.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 424694, "artwork": {"posterKind": "center_crop", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 60, "rtCriticsLabel": "Fresh", "rtAudience": 85, "rtAudienceLabel": "Upright", "imdb": 7.9, "imdbVotes": 655894, "tmdb": 7.947, "tmdbVotes": 18196}}, {"id": "movie-the-boy-and-the-heron-2023", "title": "The Boy and the Heron", "year": 2023, "type": "movie", "runtimeSeconds": 7437, "seasonCount": null, "communityRating": 7.4, "criticRating": 96, "genres": ["Animation", "Fantasy", "Drama"], "overview": "While the Second World War rages, the teenage Mahito, haunted by his mother's tragic death, is relocated from Tokyo to the serene rural home of his new stepmother Natsuko, a woman who bears a striking resemblance to\u2026", "poster": "assets/movie-the-boy-and-the-heron-2023-poster.jpg", "backdrop": "assets/movie-the-boy-and-the-heron-2023-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:25:15.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 508883, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 96, "rtCriticsLabel": "Certified Fresh", "rtAudience": 88, "rtAudienceLabel": "Upright", "imdb": 7.3, "imdbVotes": 152169, "tmdb": 7.387, "tmdbVotes": 2893}}, {"id": "movie-braveheart-1995", "title": "Braveheart", "year": 1995, "type": "movie", "runtimeSeconds": 10665, "seasonCount": null, "communityRating": 7.945, "criticRating": 74, "genres": ["Drama", "History", "War"], "overview": "Enraged at the slaughter of Murron, his new bride and childhood love, Scottish warrior William Wallace slays a platoon of the local English lord's soldiers. This leads the village to revolt and, eventually, the\u2026", "poster": "assets/movie-braveheart-1995-poster.jpg", "backdrop": "assets/movie-braveheart-1995-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-10T21:24:28.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 197, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 74, "rtCriticsLabel": "Certified Fresh", "rtAudience": 85, "rtAudienceLabel": "Upright", "imdb": 8.3, "imdbVotes": 1174990, "tmdb": 7.947, "tmdbVotes": 11584}}, {"id": "movie-the-breakfast-club-1985", "title": "The Breakfast Club", "year": 1985, "type": "movie", "runtimeSeconds": 5833, "seasonCount": null, "communityRating": 7.7, "criticRating": 87, "genres": ["Comedy", "Drama"], "overview": "Five high school students from different walks of life endure a Saturday detention under a power-hungry principal. The disparate group includes rebel John, princess Claire, outcast Allison, brainy Brian and Andrew,\u2026", "poster": "assets/movie-the-breakfast-club-1985-poster.jpg", "backdrop": "assets/movie-the-breakfast-club-1985-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:25:03.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 2108, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 87, "rtCriticsLabel": "Certified Fresh", "rtAudience": 92, "rtAudienceLabel": "Upright", "imdb": 7.8, "imdbVotes": 478743, "tmdb": 7.713, "tmdbVotes": 8815}}, {"id": "movie-castle-in-the-sky-1986", "title": "Castle in the Sky", "year": 1986, "type": "movie", "runtimeSeconds": 7474, "seasonCount": null, "communityRating": 8, "criticRating": 97, "genres": ["Adventure", "Fantasy", "Animation"], "overview": "A young boy and a girl with a magic crystal must race against pirates and foreign agents in a search for a legendary floating castle.", "poster": "assets/movie-castle-in-the-sky-1986-poster.jpg", "backdrop": "assets/movie-castle-in-the-sky-1986-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:23:09.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 10515, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 96, "rtCriticsLabel": "Fresh", "rtAudience": 91, "rtAudienceLabel": "Upright", "imdb": 8, "imdbVotes": 201545, "tmdb": 7.972, "tmdbVotes": 4868}}, {"id": "movie-citizen-kane-1941", "title": "Citizen Kane", "year": 1941, "type": "movie", "runtimeSeconds": 7173, "seasonCount": null, "communityRating": 7.966, "criticRating": 99, "genres": ["Mystery", "Drama"], "overview": "Newspaper magnate Charles Foster Kane is taken from his mother as a boy and made the ward of a rich industrialist. As a result, every well-meaning, tyrannical or self-destructive move he makes for the rest of his\u2026", "poster": "assets/movie-citizen-kane-1941-poster.jpg", "backdrop": "assets/movie-citizen-kane-1941-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:22:16.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 15, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 99, "rtCriticsLabel": "Certified Fresh", "rtAudience": 98, "rtAudienceLabel": "Upright", "imdb": 8.2, "imdbVotes": 498321, "tmdb": 7.962, "tmdbVotes": 6324}}, {"id": "movie-conclave-2024", "title": "Conclave", "year": 2024, "type": "movie", "runtimeSeconds": 7230, "seasonCount": null, "communityRating": 7.19, "criticRating": 93, "genres": ["Drama", "Thriller"], "overview": "After the unexpected death of the Pope, Cardinal Lawrence is tasked with managing the covert and ancient ritual of electing a new one. Sequestered in the Vatican with the Catholic Church\u2019s most powerful leaders until\u2026", "poster": "assets/movie-conclave-2024-poster.jpg", "backdrop": "assets/movie-conclave-2024-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:30:41.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 974576, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 93, "rtCriticsLabel": "Certified Fresh", "rtAudience": 86, "rtAudienceLabel": "Upright", "imdb": 7.4, "imdbVotes": 290498, "tmdb": 7.19, "tmdbVotes": 3744}}, {"id": "movie-the-dry-2021", "title": "The Dry", "year": 2021, "type": "movie", "runtimeSeconds": 7037, "seasonCount": null, "communityRating": 6.779, "criticRating": null, "genres": ["Crime", "Thriller", "Mystery"], "overview": "Aaron Falk returns to his drought-stricken hometown to attend a tragic funeral. But his return opens a decades-old wound - the unsolved death of a teenage girl.", "poster": "assets/movie-the-dry-2021-poster.jpg", "backdrop": "assets/movie-the-dry-2021-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:29:20.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 567797, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 90, "rtCriticsLabel": "Certified Fresh", "rtAudience": 89, "rtAudienceLabel": "Upright", "imdb": 6.8, "imdbVotes": 41935, "tmdb": 6.8, "tmdbVotes": 892}}, {"id": "movie-easy-a-2010", "title": "Easy A", "year": 2010, "type": "movie", "runtimeSeconds": 5545, "seasonCount": null, "communityRating": 6.8, "criticRating": 85, "genres": ["Comedy"], "overview": "Olive, an average high school student, sees her below-the-radar existence turn around overnight once she decides to use the school's gossip grapevine to advance her social standing. Now her classmates are turning\u2026", "poster": "assets/movie-easy-a-2010-poster.jpg", "backdrop": "assets/movie-easy-a-2010-backdrop.jpg", "played": true, "playCount": 1, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-17T19:59:04.0000000Z", "lastPlayedAt": "2026-10-17T20:02:51.1083337Z", "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 37735, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 85, "rtCriticsLabel": "Certified Fresh", "rtAudience": 77, "rtAudienceLabel": "Upright", "imdb": 7, "imdbVotes": 448780, "tmdb": 6.8, "tmdbVotes": 7619}}, {"id": "movie-el-camino-a-breaking-bad-movie-2019", "title": "El Camino: A Breaking Bad Movie", "year": 2019, "type": "movie", "runtimeSeconds": 7362, "seasonCount": null, "communityRating": 7.055, "criticRating": 92, "genres": ["Crime", "Drama", "Thriller"], "overview": "In the wake of his dramatic escape from captivity, Jesse Pinkman must come to terms with his past in order to forge some kind of future.", "poster": "assets/movie-el-camino-a-breaking-bad-movie-2019-poster.jpg", "backdrop": "assets/movie-el-camino-a-breaking-bad-movie-2019-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-21T23:31:11.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 559969, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 92, "rtCriticsLabel": "Certified Fresh", "rtAudience": 81, "rtAudienceLabel": "Upright", "imdb": 7.3, "imdbVotes": 354488, "tmdb": 7.056, "tmdbVotes": 5950}}, {"id": "movie-emancipation-2022", "title": "Emancipation", "year": 2022, "type": "movie", "runtimeSeconds": 7949, "seasonCount": null, "communityRating": 7.7, "criticRating": 45, "genres": ["Drama", "War", "History"], "overview": "Inspired by the gripping true story of a man who would do anything for his family\u2014and for freedom. When Peter, an enslaved man, risks his life to escape and return to his family, he embarks on a perilous journey of\u2026", "poster": "assets/movie-emancipation-2022-poster.jpg", "backdrop": "assets/movie-emancipation-2022-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-10T20:37:34.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 715931, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 45, "rtCriticsLabel": "Rotten", "rtAudience": 58, "rtAudienceLabel": "Spilled", "imdb": 6.3, "imdbVotes": 32079, "tmdb": 7.744, "tmdbVotes": 1319}}, {"id": "movie-fighting-with-my-family-2019", "title": "Fighting with My Family", "year": 2019, "type": "movie", "runtimeSeconds": 6509, "seasonCount": null, "communityRating": 6.84, "criticRating": 93, "genres": ["Comedy"], "overview": "Born into a tight-knit wrestling family, Paige and her brother Zak are ecstatic when they get the once-in-a-lifetime opportunity to try out for the WWE. But when only Paige earns a spot in the competitive training\u2026", "poster": "assets/movie-fighting-with-my-family-2019-poster.jpg", "backdrop": "assets/movie-fighting-with-my-family-2019-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-22T22:10:59.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 445629, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 93, "rtCriticsLabel": "Certified Fresh", "rtAudience": 86, "rtAudienceLabel": "Upright", "imdb": 7, "imdbVotes": 94576, "tmdb": 6.837, "tmdbVotes": 1952}}, {"id": "movie-force-of-nature-the-dry-2-2024", "title": "Force of Nature: The Dry 2", "year": 2024, "type": "movie", "runtimeSeconds": 6753, "seasonCount": null, "communityRating": 6.3, "criticRating": 66, "genres": ["Mystery", "Thriller"], "overview": "When five women take part in a corporate hiking retreat and only four come out on the other side, Federal Agents Aaron Falk and Carmen Cooper head deep into the Victorian mountain ranges to investigate in the hopes\u2026", "poster": "assets/movie-force-of-nature-the-dry-2-2024-poster.jpg", "backdrop": "assets/movie-force-of-nature-the-dry-2-2024-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:29:49.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 832262, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 66, "rtCriticsLabel": "Fresh", "rtAudience": 43, "rtAudienceLabel": "Spilled", "imdb": 5.9, "imdbVotes": 10343, "tmdb": 6.269, "tmdbVotes": 305}}, {"id": "movie-free-state-of-jones-2016", "title": "Free State of Jones", "year": 2016, "type": "movie", "runtimeSeconds": 8376, "seasonCount": null, "communityRating": 6.79, "criticRating": 48, "genres": ["Action", "Drama", "History"], "overview": "In 1863, Mississippi farmer Newt Knight serves as a medic for the Confederate Army. Opposed to slavery, Knight would rather help the wounded than fight the Union. After his nephew dies in battle, Newt returns home to\u2026", "poster": "assets/movie-free-state-of-jones-2016-poster.jpg", "backdrop": "assets/movie-free-state-of-jones-2016-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-11T17:56:53.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 316152, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 48, "rtCriticsLabel": "Rotten", "rtAudience": 64, "rtAudienceLabel": "Upright", "imdb": 6.9, "imdbVotes": 68931, "tmdb": 6.788, "tmdbVotes": 1730}}, {"id": "movie-from-up-on-poppy-hill-2011", "title": "From Up on Poppy Hill", "year": 2011, "type": "movie", "runtimeSeconds": 5482, "seasonCount": null, "communityRating": 7.5, "criticRating": 87, "genres": ["Animation", "Drama"], "overview": "Yokohama, 1963. Japan is picking itself up from the devastation of World War II and preparing to host the 1964 Olympics\u2014and the mood is one of both optimism and conflict as the young generation struggles to throw off\u2026", "poster": "assets/movie-from-up-on-poppy-hill-2011-poster.jpg", "backdrop": "assets/movie-from-up-on-poppy-hill-2011-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-07T14:41:03.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 83389, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 87, "rtCriticsLabel": "Certified Fresh", "rtAudience": 80, "rtAudienceLabel": "Upright", "imdb": 7.4, "imdbVotes": 60769, "tmdb": 7.509, "tmdbVotes": 2026}}, {"id": "movie-ghost-in-the-shell-1995", "title": "Ghost in the Shell", "year": 1995, "type": "movie", "runtimeSeconds": 4959, "seasonCount": null, "communityRating": 7.929, "criticRating": 95, "genres": ["Action", "Animation", "Science Fiction"], "overview": "In the year 2029, the barriers of our world have been broken down by the net and by cybernetics, but this brings new vulnerability to humans in the form of brain-hacking. When a highly-wanted hacker known as 'The\u2026", "poster": "assets/movie-ghost-in-the-shell-1995-poster.jpg", "backdrop": "assets/movie-ghost-in-the-shell-1995-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:22:57.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 9323, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 97, "rtCriticsLabel": "Certified Fresh", "rtAudience": 90, "rtAudienceLabel": "Upright", "imdb": 7.9, "imdbVotes": 178061, "tmdb": 7.928, "tmdbVotes": 4130}}, {"id": "movie-ghost-in-the-shell-2-innocence-2004", "title": "Ghost in the Shell 2: Innocence", "year": 2004, "type": "movie", "runtimeSeconds": 5950, "seasonCount": null, "communityRating": 7.352, "criticRating": 65, "genres": ["Animation", "Drama", "Science Fiction"], "overview": "Cyborg detective Batou is assigned to investigate a series of murders committed by gynoids\u2014doll-like cyborgs, which all malfunctioned, killed, then self-destructed afterwards. The brains of the gynoids initialize in\u2026", "poster": "assets/movie-ghost-in-the-shell-2-innocence-2004-poster.jpg", "backdrop": "assets/movie-ghost-in-the-shell-2-innocence-2004-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:23:38.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 12140, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 65, "rtCriticsLabel": "Fresh", "rtAudience": 80, "rtAudienceLabel": "Upright", "imdb": 7.4, "imdbVotes": 43280, "tmdb": 7.356, "tmdbVotes": 1049}}, {"id": "movie-the-godfather-1972", "title": "The Godfather", "year": 1972, "type": "movie", "runtimeSeconds": 10603, "seasonCount": null, "communityRating": 8.687, "criticRating": 97, "genres": ["Drama", "Crime"], "overview": "Spanning the years 1945 to 1955, a chronicle of the fictional Italian-American Corleone crime family. When organized crime family patriarch, Vito Corleone barely survives an attempt on his life, his youngest son,\u2026", "poster": "assets/movie-the-godfather-1972-poster.jpg", "backdrop": "assets/movie-the-godfather-1972-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:24:12.0000000Z", "lastPlayedAt": "2026-09-18T19:24:44.9855867Z", "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 238, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 97, "rtCriticsLabel": "Certified Fresh", "rtAudience": 98, "rtAudienceLabel": "Upright", "imdb": 9.2, "imdbVotes": 2262024, "tmdb": 8.688, "tmdbVotes": 23907}}, {"id": "movie-the-godfather-part-ii-1974", "title": "The Godfather Part II", "year": 1974, "type": "movie", "runtimeSeconds": 12031, "seasonCount": null, "communityRating": 8.573, "criticRating": 96, "genres": ["Drama", "Crime"], "overview": "In the continuing saga of the Corleone crime family, a young Vito Corleone grows up in Sicily and in 1910s New York. In the 1950s, Michael Corleone attempts to expand the family business into Las Vegas, Hollywood and\u2026", "poster": "assets/movie-the-godfather-part-ii-1974-poster.jpg", "backdrop": "assets/movie-the-godfather-part-ii-1974-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:24:18.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 240, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 96, "rtCriticsLabel": "Certified Fresh", "rtAudience": 97, "rtAudienceLabel": "Upright", "imdb": 9, "imdbVotes": 1521308, "tmdb": 8.571, "tmdbVotes": 14559}}, {"id": "movie-the-godfather-part-iii-1990", "title": "The Godfather Part III", "year": 1990, "type": "movie", "runtimeSeconds": 9476, "seasonCount": null, "communityRating": 7.416, "criticRating": 67, "genres": ["Crime", "Drama", "Thriller"], "overview": "In the midst of trying to legitimize his business dealings in 1979 New York and Italy, aging mafia don, Michael Corleone seeks forgiveness for his sins while taking a young protege under his wing.", "poster": "assets/movie-the-godfather-part-iii-1990-poster.jpg", "backdrop": "assets/movie-the-godfather-part-iii-1990-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:26:29.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 242, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 68, "rtCriticsLabel": "Fresh", "rtAudience": 68, "rtAudienceLabel": "Upright", "imdb": 7.5, "imdbVotes": 462448, "tmdb": 7.413, "tmdbVotes": 7159}}, {"id": "movie-gosford-park-2001", "title": "Gosford Park", "year": 2001, "type": "movie", "runtimeSeconds": 8247, "seasonCount": null, "communityRating": 6.815, "criticRating": 87, "genres": ["Drama", "Mystery", "Thriller"], "overview": "In 1930s England, a group of pretentious rich and famous gather together for a weekend of relaxation at a hunting resort. But when a murder occurs, each one of these interesting characters becomes a suspect.", "poster": "assets/movie-gosford-park-2001-poster.jpg", "backdrop": "assets/movie-gosford-park-2001-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:25:49.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 5279, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 87, "rtCriticsLabel": "Certified Fresh", "rtAudience": 78, "rtAudienceLabel": "Upright", "imdb": 7.2, "imdbVotes": 103883, "tmdb": 6.822, "tmdbVotes": 1317}}, {"id": "movie-grave-of-the-fireflies-1988", "title": "Grave of the Fireflies", "year": 1988, "type": "movie", "runtimeSeconds": 5313, "seasonCount": null, "communityRating": 8.45, "criticRating": 100, "genres": ["Animation", "Drama", "War"], "overview": "In the final months of World War II, 14-year-old Seita and his sister Setsuko are orphaned when their mother is killed during an air raid in Kobe, Japan. After a falling out with their aunt, they move into an\u2026", "poster": "assets/movie-grave-of-the-fireflies-1988-poster.jpg", "backdrop": "assets/movie-grave-of-the-fireflies-1988-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:24:14.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 12477, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Certified Fresh", "rtAudience": 95, "rtAudienceLabel": "Upright", "imdb": 8.5, "imdbVotes": 399802, "tmdb": 8.45, "tmdbVotes": 6938}}, {"id": "movie-the-great-gatsby-2013", "title": "The Great Gatsby", "year": 2013, "type": "movie", "runtimeSeconds": 8525, "seasonCount": null, "communityRating": 7.346, "criticRating": 49, "genres": ["Drama", "Romance"], "overview": "Nick Carraway arrives in New York in 1922, an era of loose morals, glittering jazz and bootleg kings. Chasing his own American Dream, Nick encounters the mysterious millionaire Gatsby and his bewitching cousin Daisy.\u2026", "poster": "assets/movie-the-great-gatsby-2013-poster.jpg", "backdrop": "assets/movie-the-great-gatsby-2013-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:26:50.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 64682, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 48, "rtCriticsLabel": "Rotten", "rtAudience": 67, "rtAudienceLabel": "Upright", "imdb": 7.2, "imdbVotes": 651882, "tmdb": 7.346, "tmdbVotes": 13290}}, {"id": "movie-the-hours-2002", "title": "The Hours", "year": 2002, "type": "movie", "runtimeSeconds": 6887, "seasonCount": null, "communityRating": 7.25, "criticRating": 79, "genres": ["Drama"], "overview": "The story of three women searching for more potent, meaningful lives. Each is alive at a different time and place, all are linked by their yearnings and their fears. Their stories intertwine, and finally come\u2026", "poster": "assets/movie-the-hours-2002-poster.jpg", "backdrop": "assets/movie-the-hours-2002-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:23:27.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 590, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 79, "rtCriticsLabel": "Certified Fresh", "rtAudience": 84, "rtAudienceLabel": "Upright", "imdb": 7.5, "imdbVotes": 148367, "tmdb": 7.249, "tmdbVotes": 1931}}, {"id": "movie-howl-s-moving-castle-2004", "title": "Howl's Moving Castle", "year": 2004, "type": "movie", "runtimeSeconds": 7149, "seasonCount": null, "communityRating": 8.4, "criticRating": 88, "genres": ["Fantasy", "Animation", "Adventure"], "overview": "Sophie, a young milliner, is turned into an elderly woman by a witch who enters her shop and curses her. She encounters a wizard named Howl and gets caught up in his resistance to fighting for the king.", "poster": "assets/movie-howl-s-moving-castle-2004-poster.jpg", "backdrop": "assets/movie-howl-s-moving-castle-2004-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:22:47.0000000Z", "lastPlayedAt": "2026-05-21T18:46:08.0000000Z", "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 4935, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 87, "rtCriticsLabel": "Certified Fresh", "rtAudience": 93, "rtAudienceLabel": "Upright", "imdb": 8.2, "imdbVotes": 524520, "tmdb": 8.391, "tmdbVotes": 11642}}, {"id": "movie-in-the-heights-2021", "title": "In the Heights", "year": 2021, "type": "movie", "runtimeSeconds": 8579, "seasonCount": null, "communityRating": 7.111, "criticRating": null, "genres": ["Drama", "Romance"], "overview": "The story of Usnavi, a bodega owner who has mixed feelings about closing his store and retiring to the Dominican Republic or staying in Washington Heights.", "poster": "assets/movie-in-the-heights-2021-poster.jpg", "backdrop": "assets/movie-in-the-heights-2021-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:29:11.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 467909, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 94, "rtCriticsLabel": "Certified Fresh", "rtAudience": 94, "rtAudienceLabel": "Upright", "imdb": 7.2, "imdbVotes": 56513, "tmdb": 7.104, "tmdbVotes": 826}}, {"id": "movie-jennifer-s-body-2009", "title": "Jennifer's Body", "year": 2009, "type": "movie", "runtimeSeconds": 6450, "seasonCount": null, "communityRating": 6.012, "criticRating": 47, "genres": ["Horror", "Comedy"], "overview": "Jennifer, a gorgeous, seductive cheerleader takes evil to a whole new level after she's possessed by a sinister demon. Now it's up to her best friend to stop Jennifer's reign of terror before it's too late.", "poster": "assets/movie-jennifer-s-body-2009-poster.jpg", "backdrop": "assets/movie-jennifer-s-body-2009-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-30T03:37:04.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 19994, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 47, "rtCriticsLabel": "Rotten", "rtAudience": 36, "rtAudienceLabel": "Spilled", "imdb": 5.6, "imdbVotes": 179710, "tmdb": 6.013, "tmdbVotes": 4481}}, {"id": "movie-kiki-s-delivery-service-1989", "title": "Kiki's Delivery Service", "year": 1989, "type": "movie", "runtimeSeconds": 6183, "seasonCount": null, "communityRating": 7.839, "criticRating": null, "genres": ["Animation", "Family", "Fantasy"], "overview": "A young witch, on her mandatory year of independent life, finds fitting into a new community difficult while she supports herself by running an air courier service.", "poster": "assets/movie-kiki-s-delivery-service-1989-poster.jpg", "backdrop": "assets/movie-kiki-s-delivery-service-1989-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:24:20.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 16859, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 98, "rtCriticsLabel": "Certified Fresh", "rtAudience": 89, "rtAudienceLabel": "Upright", "imdb": 7.8, "imdbVotes": 192477, "tmdb": 7.839, "tmdbVotes": 4811}}, {"id": "movie-the-last-of-the-mohicans-1992", "title": "The Last of the Mohicans", "year": 1992, "type": "movie", "runtimeSeconds": 6874, "seasonCount": null, "communityRating": 7.388, "criticRating": 88, "genres": ["History", "War", "Drama"], "overview": "In war-torn colonial America, in the midst of a bloody battle between British, the French and Native American allies, the aristocratic daughter of a British Colonel and her party are captured by a group of Huron\u2026", "poster": "assets/movie-the-last-of-the-mohicans-1992-poster.jpg", "backdrop": "assets/movie-the-last-of-the-mohicans-1992-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-10T20:59:22.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 9361, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 88, "rtCriticsLabel": "Certified Fresh", "rtAudience": 88, "rtAudienceLabel": "Upright", "imdb": 7.6, "imdbVotes": 210791, "tmdb": 7.39, "tmdbVotes": 3593}}, {"id": "movie-the-last-samurai-2003", "title": "The Last Samurai", "year": 2003, "type": "movie", "runtimeSeconds": 9251, "seasonCount": null, "communityRating": 7.61, "criticRating": 65, "genres": ["Drama", "Action", "War"], "overview": "Nathan Algren is an American hired to instruct the Japanese army in the ways of modern warfare, which finds him learning to respect the samurai and the honorable principles that rule them. Pressed to destroy the\u2026", "poster": "assets/movie-the-last-samurai-2003-poster.jpg", "backdrop": "assets/movie-the-last-samurai-2003-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-26T05:25:10.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 616, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 66, "rtCriticsLabel": "Fresh", "rtAudience": 83, "rtAudienceLabel": "Upright", "imdb": 7.8, "imdbVotes": 512982, "tmdb": 7.614, "tmdbVotes": 7887}}, {"id": "movie-lemony-snicket-s-a-series-of-unfortunate-events-2004", "title": "Lemony Snicket's A Series of Unfortunate Events", "year": 2004, "type": "movie", "runtimeSeconds": 6479, "seasonCount": null, "communityRating": 6.8, "criticRating": 72, "genres": ["Adventure", "Comedy", "Family"], "overview": "Three wealthy children's parents are killed in a fire. When they are sent to a distant relative, they find out that he is plotting to kill them and seize their fortune.", "poster": "assets/movie-lemony-snicket-s-a-series-of-unfortunate-events-2004-poster.jpg", "backdrop": "assets/movie-lemony-snicket-s-a-series-of-unfortunate-events-2004-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:26:09.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 11774, "artwork": {"posterKind": "center_crop", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 72, "rtCriticsLabel": "Fresh", "rtAudience": 63, "rtAudienceLabel": "Upright", "imdb": 6.8, "imdbVotes": 233636, "tmdb": 6.82, "tmdbVotes": 5447}}, {"id": "movie-the-lighthouse-2019", "title": "The Lighthouse", "year": 2019, "type": "movie", "runtimeSeconds": 6549, "seasonCount": null, "communityRating": 7.463, "criticRating": 90, "genres": ["Drama", "Fantasy", "Thriller"], "overview": "Two lighthouse keepers try to maintain their sanity while living on a remote and mysterious New England island in the 1890s.", "poster": "assets/movie-the-lighthouse-2019-poster.jpg", "backdrop": "assets/movie-the-lighthouse-2019-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:29:01.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 503919, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 90, "rtCriticsLabel": "Certified Fresh", "rtAudience": 72, "rtAudienceLabel": "Upright", "imdb": 7.3, "imdbVotes": 312344, "tmdb": 7.46, "tmdbVotes": 5895}}, {"id": "movie-locke-2014", "title": "Locke", "year": 2014, "type": "movie", "runtimeSeconds": 5090, "seasonCount": null, "communityRating": 6.888, "criticRating": 91, "genres": ["Drama", "Thriller"], "overview": "Ivan Locke has worked hard to craft a good life for himself. Tonight, that life will collapse around him. On the eve of the biggest challenge of his career, Ivan receives a phone call that sets in motion a series of\u2026", "poster": "assets/movie-locke-2014-poster.jpg", "backdrop": "assets/movie-locke-2014-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T17:38:48.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 210479, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 90, "rtCriticsLabel": "Certified Fresh", "rtAudience": 72, "rtAudienceLabel": "Upright", "imdb": 7.1, "imdbVotes": 171265, "tmdb": 6.889, "tmdbVotes": 3088}}, {"id": "movie-the-matrix-1999", "title": "The Matrix", "year": 1999, "type": "movie", "runtimeSeconds": 8179, "seasonCount": null, "communityRating": 8.259, "criticRating": 83, "genres": ["Action", "Science Fiction"], "overview": "Set in the 22nd century, The Matrix tells the story of a computer hacker who joins a group of underground insurgents fighting the vast and powerful computers who now rule the earth.", "poster": "assets/movie-the-matrix-1999-poster.jpg", "backdrop": "assets/movie-the-matrix-1999-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:18:12.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 603, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 83, "rtCriticsLabel": "Certified Fresh", "rtAudience": 85, "rtAudienceLabel": "Upright", "imdb": 8.7, "imdbVotes": 2289808, "tmdb": 8.262, "tmdbVotes": 29153}}, {"id": "movie-the-matrix-reloaded-2003", "title": "The Matrix Reloaded", "year": 2003, "type": "movie", "runtimeSeconds": 8298, "seasonCount": null, "communityRating": 7.085, "criticRating": 74, "genres": ["Adventure", "Action", "Thriller"], "overview": "The Resistance builds in numbers as humans are freed from the Matrix and brought to the city of Zion. Neo discovers his superpowers, including the ability to see the code inside the Matrix. With machine sentinels\u2026", "poster": "assets/movie-the-matrix-reloaded-2003-poster.jpg", "backdrop": "assets/movie-the-matrix-reloaded-2003-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:24:03.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 604, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 73, "rtCriticsLabel": "Certified Fresh", "rtAudience": 72, "rtAudienceLabel": "Upright", "imdb": 7.2, "imdbVotes": 678013, "tmdb": 7.087, "tmdbVotes": 12504}}, {"id": "movie-the-matrix-resurrections-2021", "title": "The Matrix Resurrections", "year": 2021, "type": "movie", "runtimeSeconds": 8875, "seasonCount": null, "communityRating": 6.303, "criticRating": 63, "genres": ["Science Fiction", "Action", "Adventure"], "overview": "Plagued by strange memories, Neo's life takes an unexpected turn when he finds himself back inside the Matrix.", "poster": "assets/movie-the-matrix-resurrections-2021-poster.jpg", "backdrop": "assets/movie-the-matrix-resurrections-2021-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:31:22.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 624860, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 63, "rtCriticsLabel": "Fresh", "rtAudience": 63, "rtAudienceLabel": "Upright", "imdb": 5.6, "imdbVotes": 307780, "tmdb": 6.294, "tmdbVotes": 6842}}, {"id": "movie-the-matrix-revolutions-2003", "title": "The Matrix Revolutions", "year": 2003, "type": "movie", "runtimeSeconds": 7756, "seasonCount": null, "communityRating": 6.758, "criticRating": 33, "genres": ["Adventure", "Action", "Thriller"], "overview": "The human city of Zion defends itself against the massive invasion of the machines as Neo fights to end the war at another front while also opposing the rogue Agent Smith.", "poster": "assets/movie-the-matrix-revolutions-2003-poster.jpg", "backdrop": "assets/movie-the-matrix-revolutions-2003-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:25:47.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 605, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 34, "rtCriticsLabel": "Rotten", "rtAudience": 60, "rtAudienceLabel": "Upright", "imdb": 6.8, "imdbVotes": 581553, "tmdb": 6.762, "tmdbVotes": 11292}}, {"id": "movie-mercy-2026", "title": "Mercy", "year": 2026, "type": "movie", "runtimeSeconds": 5968, "seasonCount": null, "communityRating": 7.077, "criticRating": null, "genres": ["Science Fiction", "Thriller", "Crime"], "overview": "In the near future, a detective stands on trial accused of murdering his wife. He has ninety minutes to prove his innocence to the advanced AI Judge he once championed, before it determines his fate.", "poster": "assets/movie-mercy-2026-poster.jpg", "backdrop": "assets/movie-mercy-2026-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:31:40.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 1236153, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 24, "rtCriticsLabel": "Rotten", "rtAudience": 81, "rtAudienceLabel": "Upright", "imdb": 6.2, "imdbVotes": 83878, "tmdb": 7.054, "tmdbVotes": 1812}}, {"id": "movie-normal-2026", "title": "Normal", "year": 2026, "type": "movie", "runtimeSeconds": 5514, "seasonCount": null, "communityRating": 7.267, "criticRating": 77, "genres": ["Action", "Crime", "Thriller"], "overview": "An unassuming substitute sheriff with a troubled past responds to a bank robbery in a small, sleepy town and unknowingly uncovers something far more explosive.", "poster": "assets/movie-normal-2026-poster.jpg", "backdrop": "assets/movie-normal-2026-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-08-21T08:33:18.0000000Z", "lastPlayedAt": "2026-09-24T23:07:52.1161581Z", "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 1242332, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 78, "rtCriticsLabel": "Certified Fresh", "rtAudience": 75, "rtAudienceLabel": "Upright", "imdb": 6.3, "imdbVotes": 37501, "tmdb": 7.241, "tmdbVotes": 786}}, {"id": "movie-outlaw-king-2018", "title": "Outlaw King", "year": 2018, "type": "movie", "runtimeSeconds": 7297, "seasonCount": null, "communityRating": 6.829, "criticRating": 63, "genres": ["Action", "History", "War"], "overview": "Forced into exile by the English after being crowned King of Scotland, legendary warrior Robert the Bruce fights to reclaim the throne.", "poster": "assets/movie-outlaw-king-2018-poster.jpg", "backdrop": "assets/movie-outlaw-king-2018-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:28:32.0000000Z", "lastPlayedAt": "2026-07-26T22:35:35.0000000Z", "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 454227, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 63, "rtCriticsLabel": "Fresh", "rtAudience": 69, "rtAudienceLabel": "Upright", "imdb": 6.9, "imdbVotes": 89876, "tmdb": 6.844, "tmdbVotes": 1780}}, {"id": "movie-panic-room-2002", "title": "Panic Room", "year": 2002, "type": "movie", "runtimeSeconds": 6717, "seasonCount": null, "communityRating": 6.794, "criticRating": 76, "genres": ["Crime", "Drama", "Thriller"], "overview": "Trapped in their New York brownstone's panic room, a hidden chamber built as a sanctuary in the event of break-ins, newly divorced Meg Altman and her young daughter Sarah play a deadly game of cat-and-mouse with\u2026", "poster": "assets/movie-panic-room-2002-poster.jpg", "backdrop": "assets/movie-panic-room-2002-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-19T20:50:01.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 4547, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 76, "rtCriticsLabel": "Certified Fresh", "rtAudience": 64, "rtAudienceLabel": "Upright", "imdb": 6.8, "imdbVotes": 317468, "tmdb": 6.796, "tmdbVotes": 5653}}, {"id": "movie-the-patriot-2000", "title": "The Patriot", "year": 2000, "type": "movie", "runtimeSeconds": 9887, "seasonCount": null, "communityRating": 7.214, "criticRating": 61, "genres": ["Drama", "History", "War"], "overview": "After proving himself on the field of battle in the French and Indian War, Benjamin Martin wants nothing more to do with such things, preferring the simple life of a farmer. But when his son Gabriel enlists in the\u2026", "poster": "assets/movie-the-patriot-2000-poster.jpg", "backdrop": "assets/movie-the-patriot-2000-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-10T20:50:00.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 2024, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 61, "rtCriticsLabel": "Fresh", "rtAudience": 81, "rtAudienceLabel": "Upright", "imdb": 7.2, "imdbVotes": 317639, "tmdb": 7.215, "tmdbVotes": 4535}}, {"id": "movie-peaky-blinders-the-immortal-man-2026", "title": "Peaky Blinders: The Immortal Man", "year": 2026, "type": "movie", "runtimeSeconds": 6844, "seasonCount": null, "communityRating": 7.187, "criticRating": 90, "genres": ["Crime", "Drama"], "overview": "After his estranged son gets embroiled in a Nazi plot, self-exiled gangster Tommy Shelby must return to Birmingham to save his family \u2014 and his nation.", "poster": "assets/movie-peaky-blinders-the-immortal-man-2026-poster.jpg", "backdrop": "assets/movie-peaky-blinders-the-immortal-man-2026-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:30:01.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 875828, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 90, "rtCriticsLabel": "Certified Fresh", "rtAudience": 87, "rtAudienceLabel": "Upright", "imdb": 6.5, "imdbVotes": 92130, "tmdb": 7.189, "tmdbVotes": 1304}}, {"id": "movie-ponyo-2008", "title": "Ponyo", "year": 2008, "type": "movie", "runtimeSeconds": 6530, "seasonCount": null, "communityRating": 7.763, "criticRating": 91, "genres": ["Animation", "Fantasy", "Family"], "overview": "When Sosuke, a young boy who lives on a clifftop overlooking the sea, rescues a stranded goldfish named Ponyo, he discovers more than he bargained for. Ponyo is a curious, energetic young creature who yearns to be\u2026", "poster": "assets/movie-ponyo-2008-poster.jpg", "backdrop": "assets/movie-ponyo-2008-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:23:53.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 12429, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 91, "rtCriticsLabel": "Certified Fresh", "rtAudience": 84, "rtAudienceLabel": "Upright", "imdb": 7.6, "imdbVotes": 190294, "tmdb": 7.759, "tmdbVotes": 5050}}, {"id": "movie-porco-rosso-1992", "title": "Porco Rosso", "year": 1992, "type": "movie", "runtimeSeconds": 5600, "seasonCount": null, "communityRating": 7.794, "criticRating": 96, "genres": ["Animation", "Adventure", "Fantasy"], "overview": "In Italy in the 1930s, sky pirates in biplanes terrorize wealthy cruise ships as they sail the Adriatic Sea. The only pilot brave enough to stop the scourge is the mysterious Porco Rosso, a former World War I flying\u2026", "poster": "assets/movie-porco-rosso-1992-poster.jpg", "backdrop": "assets/movie-porco-rosso-1992-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:23:22.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 11621, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 96, "rtCriticsLabel": "Fresh", "rtAudience": 87, "rtAudienceLabel": "Upright", "imdb": 7.7, "imdbVotes": 119396, "tmdb": 7.789, "tmdbVotes": 3879}}, {"id": "movie-princess-mononoke-1997", "title": "Princess Mononoke", "year": 1997, "type": "movie", "runtimeSeconds": 8003, "seasonCount": null, "communityRating": 8.321, "criticRating": 93, "genres": ["Adventure", "Fantasy", "Animation"], "overview": "Ashitaka, a prince of the disappearing Emishi people, is cursed by a demonized boar god and must journey to the west to find a cure. Along the way, he encounters San, a young human woman fighting to protect the\u2026", "poster": "assets/movie-princess-mononoke-1997-poster.jpg", "backdrop": "assets/movie-princess-mononoke-1997-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:22:28.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 128, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 94, "rtCriticsLabel": "Certified Fresh", "rtAudience": 99, "rtAudienceLabel": "Upright", "imdb": 8.3, "imdbVotes": 493735, "tmdb": 8.321, "tmdbVotes": 9438}}, {"id": "movie-prisoners-2013", "title": "Prisoners", "year": 2013, "type": "movie", "runtimeSeconds": 9192, "seasonCount": null, "communityRating": 8.106, "criticRating": 81, "genres": ["Drama", "Thriller", "Crime"], "overview": "After his six-year-old daughter is abducted, a desperate father takes justice into his own hands when he loses faith in the detective leading the investigation, setting both men on a collision course as the search\u2026", "poster": "assets/movie-prisoners-2013-poster.jpg", "backdrop": "assets/movie-prisoners-2013-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:27:33.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 146233, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 82, "rtCriticsLabel": "Certified Fresh", "rtAudience": 87, "rtAudienceLabel": "Upright", "imdb": 8.2, "imdbVotes": 963062, "tmdb": 8.105, "tmdbVotes": 13722}}, {"id": "movie-racing-stripes-2005", "title": "Racing Stripes", "year": 2005, "type": "movie", "runtimeSeconds": 6117, "seasonCount": null, "communityRating": 5.945, "criticRating": 35, "genres": ["Comedy", "Family"], "overview": "Shattered illusions are hard to repair -- especially for a good-hearted zebra named Stripes who's spent his life on a Kentucky farm amidst the sorely mistaken notion that he's a debonair thoroughbred. Once he faces\u2026", "poster": "assets/movie-racing-stripes-2005-poster.jpg", "backdrop": "assets/movie-racing-stripes-2005-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:26:02.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 6439, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 35, "rtCriticsLabel": "Rotten", "rtAudience": 42, "rtAudienceLabel": "Spilled", "imdb": 5.3, "imdbVotes": 19569, "tmdb": 5.962, "tmdbVotes": 705}}, {"id": "movie-requiem-for-a-dream-2000", "title": "Requiem for a Dream", "year": 2000, "type": "movie", "runtimeSeconds": 6086, "seasonCount": null, "communityRating": 8.005, "criticRating": 80, "genres": ["Crime", "Drama"], "overview": "The drug-induced utopias of four Coney Island residents are shattered when their addictions run deep.", "poster": "assets/movie-requiem-for-a-dream-2000-poster.jpg", "backdrop": "assets/movie-requiem-for-a-dream-2000-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:24:15.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 641, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 81, "rtCriticsLabel": "Certified Fresh", "rtAudience": 93, "rtAudienceLabel": "Upright", "imdb": 8.3, "imdbVotes": 989090, "tmdb": 8, "tmdbVotes": 11454}}, {"id": "movie-rogue-one-a-star-wars-story-2016", "title": "Rogue One: A Star Wars Story", "year": 2016, "type": "movie", "runtimeSeconds": 8038, "seasonCount": null, "communityRating": 7.51, "criticRating": 84, "genres": ["Action", "Adventure", "Science Fiction"], "overview": "A rogue band of resistance fighters unite for a mission to steal the Death Star plans and bring a new hope to the galaxy.", "poster": "assets/movie-rogue-one-a-star-wars-story-2016-poster.jpg", "backdrop": "assets/movie-rogue-one-a-star-wars-story-2016-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:28:00.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 330459, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 84, "rtCriticsLabel": "Certified Fresh", "rtAudience": 87, "rtAudienceLabel": "Upright", "imdb": 7.8, "imdbVotes": 768365, "tmdb": 7.512, "tmdbVotes": 16937}}, {"id": "movie-secondhand-lions-2003", "title": "Secondhand Lions", "year": 2003, "type": "movie", "runtimeSeconds": 6547, "seasonCount": null, "communityRating": 7.237, "criticRating": 60, "genres": ["Family", "Comedy", "Drama"], "overview": "The comedic adventures of an introverted boy left on the doorstep of a pair of reluctant, eccentric great-uncles, whose exotic remembrances stir the boy's spirit and re-ignite the men's lives.", "poster": "assets/movie-secondhand-lions-2003-poster.jpg", "backdrop": "assets/movie-secondhand-lions-2003-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-12T07:16:13.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 13156, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 59, "rtCriticsLabel": "Rotten", "rtAudience": 84, "rtAudienceLabel": "Upright", "imdb": 7.5, "imdbVotes": 66911, "tmdb": 7.242, "tmdbVotes": 872}}, {"id": "movie-the-secret-life-of-pets-2-2019", "title": "The Secret Life of Pets 2", "year": 2019, "type": "movie", "runtimeSeconds": 5164, "seasonCount": null, "communityRating": 6.95, "criticRating": 60, "genres": ["Animation", "Comedy", "Family"], "overview": "Max the terrier must cope with some major life changes when his owner gets married and has a baby. When the family takes a trip to the countryside, nervous Max has numerous run-ins with canine-intolerant cows,\u2026", "poster": "assets/movie-the-secret-life-of-pets-2-2019-poster.jpg", "backdrop": "assets/movie-the-secret-life-of-pets-2-2019-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-05T07:00:53.0000000Z", "lastPlayedAt": "2026-09-17T18:07:00.7024231Z", "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 412117, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 60, "rtCriticsLabel": "Fresh", "rtAudience": 90, "rtAudienceLabel": "Upright", "imdb": 6.4, "imdbVotes": 80464, "tmdb": 6.948, "tmdbVotes": 3450}}, {"id": "movie-self-reliance-2024", "title": "Self Reliance", "year": 2024, "type": "movie", "runtimeSeconds": 5461, "seasonCount": null, "communityRating": 5.888, "criticRating": 73, "genres": ["Comedy", "Thriller"], "overview": "When a man is offered a million dollars to play a game in which hunters try to kill him, he thinks he has found the perfect loophole: they can only attack when he\u2019s alone. His only problem is that none of his friends\u2026", "poster": "assets/movie-self-reliance-2024-poster.jpg", "backdrop": "assets/movie-self-reliance-2024-backdrop.jpg", "played": false, "playCount": 1, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:31:00.0000000Z", "lastPlayedAt": "2026-10-02T19:59:16.9286868Z", "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 1072876, "artwork": {"posterKind": "center_crop", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 73, "rtCriticsLabel": "Certified Fresh", "rtAudience": 56, "rtAudienceLabel": "Spilled", "imdb": 6, "imdbVotes": 23771, "tmdb": 5.866, "tmdbVotes": 346}}, {"id": "movie-serpico-1973", "title": "Serpico", "year": 1973, "type": "movie", "runtimeSeconds": 7805, "seasonCount": null, "communityRating": 7.5, "criticRating": 93, "genres": ["Crime", "Drama", "History"], "overview": "New York cop Frank Serpico blows the whistle on the rampant corruption in the force only to have his comrades turn against him.", "poster": "assets/movie-serpico-1973-poster.jpg", "backdrop": "assets/movie-serpico-1973-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-10T21:45:02.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 9040, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 93, "rtCriticsLabel": "Certified Fresh", "rtAudience": 88, "rtAudienceLabel": "Upright", "imdb": 7.7, "imdbVotes": 149750, "tmdb": 7.525, "tmdbVotes": 2323}}, {"id": "movie-shaft-2000", "title": "Shaft", "year": 2000, "type": "movie", "runtimeSeconds": 5960, "seasonCount": null, "communityRating": 6.026, "criticRating": 67, "genres": ["Action", "Adventure", "Crime"], "overview": "New York police detective John Shaft arrests Walter Wade Jr. for a racially motivated slaying. But the only eyewitness disappears, and Wade jumps bail for Switzerland. Two years later Wade returns to face trial,\u2026", "poster": "assets/movie-shaft-2000-poster.jpg", "backdrop": "assets/movie-shaft-2000-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-10T21:30:40.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 479, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 68, "rtCriticsLabel": "Fresh", "rtAudience": 40, "rtAudienceLabel": "Spilled", "imdb": 6, "imdbVotes": 86257, "tmdb": 6.024, "tmdbVotes": 1531}}, {"id": "movie-a-silent-voice-the-movie-2016", "title": "A Silent Voice: The Movie", "year": 2016, "type": "movie", "runtimeSeconds": 7804, "seasonCount": null, "communityRating": 8.398, "criticRating": null, "genres": ["Animation", "Drama", "Romance"], "overview": "Shouya Ishida starts bullying the new girl in class, Shouko Nishimiya, because she is deaf. But as the teasing continues, the rest of the class starts to turn on Shouya for his lack of compassion. When they leave\u2026", "poster": "assets/movie-a-silent-voice-the-movie-2016-poster.jpg", "backdrop": "assets/movie-a-silent-voice-the-movie-2016-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T17:39:46.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 378064, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 95, "rtCriticsLabel": "Fresh", "rtAudience": 94, "rtAudienceLabel": "Upright", "imdb": 8.2, "imdbVotes": 151926, "tmdb": 8.396, "tmdbVotes": 4764}}, {"id": "movie-sinbad-legend-of-the-seven-seas-2003", "title": "Sinbad: Legend of the Seven Seas", "year": 2003, "type": "movie", "runtimeSeconds": 5143, "seasonCount": null, "communityRating": 7.067, "criticRating": 44, "genres": ["Action", "Adventure", "Animation"], "overview": "The sailor of legend is framed by the goddess Eris for the theft of the Book of Peace, and must travel to her realm at the end of the world to retrieve it and save the life of his childhood friend Prince Proteus.", "poster": "assets/movie-sinbad-legend-of-the-seven-seas-2003-poster.jpg", "backdrop": "assets/movie-sinbad-legend-of-the-seven-seas-2003-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:26:25.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 14411, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 46, "rtCriticsLabel": "Rotten", "rtAudience": 57, "rtAudienceLabel": "Spilled", "imdb": 6.8, "imdbVotes": 65298, "tmdb": 7.071, "tmdbVotes": 2323}}, {"id": "movie-sleeping-with-other-people-2015", "title": "Sleeping with Other People", "year": 2015, "type": "movie", "runtimeSeconds": 6076, "seasonCount": null, "communityRating": 6.274, "criticRating": 64, "genres": ["Comedy", "Romance"], "overview": "Can two serial cheaters get a second chance at love? After a one-night stand in college, New Yorkers Lainey and Jake meet by chance twelve years later and discover they each have the same problem: because of their\u2026", "poster": "assets/movie-sleeping-with-other-people-2015-poster.jpg", "backdrop": "assets/movie-sleeping-with-other-people-2015-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:27:42.0000000Z", "lastPlayedAt": "2026-07-17T17:50:44.0000000Z", "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 288036, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 64, "rtCriticsLabel": "Fresh", "rtAudience": 56, "rtAudienceLabel": "Spilled", "imdb": 6.5, "imdbVotes": 52275, "tmdb": 6.275, "tmdbVotes": 1020}}, {"id": "movie-snowpiercer-2013", "title": "Snowpiercer", "year": 2013, "type": "movie", "runtimeSeconds": 7573, "seasonCount": null, "communityRating": 6.909, "criticRating": 94, "genres": ["Action", "Science Fiction", "Drama"], "overview": "In a future where a failed global-warming experiment kills off most life on the planet, a class system evolves aboard the Snowpiercer; a train that travels around the globe via a perpetual-motion engine.", "poster": "assets/movie-snowpiercer-2013-poster.jpg", "backdrop": "assets/movie-snowpiercer-2013-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:27:07.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 110415, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 94, "rtCriticsLabel": "Certified Fresh", "rtAudience": 72, "rtAudienceLabel": "Upright", "imdb": 7.1, "imdbVotes": 426976, "tmdb": 6.9, "tmdbVotes": 10807}}, {"id": "movie-spider-man-brand-new-day-2026", "title": "Spider-Man: Brand New Day", "year": 2026, "type": "movie", "runtimeSeconds": 8701, "seasonCount": null, "communityRating": 8.374, "criticRating": 90, "genres": ["Science Fiction", "Action", "Adventure"], "overview": "Fighting crime full-time as Spider-Man in a world that doesn't remember him\u2014and the pressure of seeing his old friends move on without him\u2014sparks a change in Peter Parker he may not have the power to control. But\u2026", "poster": "assets/movie-spider-man-brand-new-day-2026-poster.jpg", "backdrop": "assets/movie-spider-man-brand-new-day-2026-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-10T14:14:27.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 969681, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 90, "rtCriticsLabel": "Certified Fresh", "rtAudience": 97, "rtAudienceLabel": "Upright", "imdb": 7.9, "imdbVotes": 366886, "tmdb": 8.5, "tmdbVotes": 6537}}, {"id": "movie-spider-man-far-from-home-2019", "title": "Spider-Man: Far From Home", "year": 2019, "type": "movie", "runtimeSeconds": 7762, "seasonCount": null, "communityRating": 7.389, "criticRating": 91, "genres": ["Action", "Adventure", "Science Fiction"], "overview": "Peter Parker and his friends go on a summer trip to Europe. However, they will hardly be able to rest - Peter will have to agree to help Nick Fury uncover the mystery of creatures that cause natural disasters and\u2026", "poster": "assets/movie-spider-man-far-from-home-2019-poster.jpg", "backdrop": "assets/movie-spider-man-far-from-home-2019-backdrop.jpg", "played": true, "playCount": 1, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-10T14:25:47.0000000Z", "lastPlayedAt": "2026-10-10T17:50:35.6193119Z", "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 429617, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 91, "rtCriticsLabel": "Certified Fresh", "rtAudience": 95, "rtAudienceLabel": "Upright", "imdb": 7.3, "imdbVotes": 682887, "tmdb": 7.387, "tmdbVotes": 17847}}, {"id": "movie-spider-man-homecoming-2017", "title": "Spider-Man: Homecoming", "year": 2017, "type": "movie", "runtimeSeconds": 8008, "seasonCount": null, "communityRating": 7.333, "criticRating": 92, "genres": ["Action", "Adventure", "Science Fiction"], "overview": "Following the events of Captain America: Civil War, Peter Parker, with the help of his mentor Tony Stark, tries to balance his life as an ordinary high school student in Queens, New York City, with fighting crime as\u2026", "poster": "assets/movie-spider-man-homecoming-2017-poster.jpg", "backdrop": "assets/movie-spider-man-homecoming-2017-backdrop.jpg", "played": true, "playCount": 1, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-10T14:33:13.0000000Z", "lastPlayedAt": "2026-10-10T20:31:23.4444213Z", "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 315635, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 92, "rtCriticsLabel": "Certified Fresh", "rtAudience": 87, "rtAudienceLabel": "Upright", "imdb": 7.4, "imdbVotes": 840974, "tmdb": 7.332, "tmdbVotes": 24183}}, {"id": "movie-spider-man-no-way-home-2021", "title": "Spider-Man: No Way Home", "year": 2021, "type": "movie", "runtimeSeconds": 8890, "seasonCount": null, "communityRating": 7.947, "criticRating": 93, "genres": ["Action", "Adventure", "Science Fiction"], "overview": "Peter Parker is unmasked and no longer able to separate his normal life from the high-stakes of being a super-hero. When he asks for help from Doctor Strange the stakes become even more dangerous, forcing him to\u2026", "poster": "assets/movie-spider-man-no-way-home-2021-poster.jpg", "backdrop": "assets/movie-spider-man-no-way-home-2021-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-10T14:21:05.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 634649, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 93, "rtCriticsLabel": "Certified Fresh", "rtAudience": 97, "rtAudienceLabel": "Upright", "imdb": 8.1, "imdbVotes": 1082660, "tmdb": 7.947, "tmdbVotes": 23505}}, {"id": "movie-spirited-away-2001", "title": "Spirited Away", "year": 2001, "type": "movie", "runtimeSeconds": 7472, "seasonCount": null, "communityRating": 8.535, "criticRating": 96, "genres": ["Animation", "Family", "Fantasy"], "overview": "A young girl, Chihiro, becomes trapped in a strange new world of spirits. When her parents undergo a mysterious transformation, she must call upon the courage she never knew she had to free her family.", "poster": "assets/movie-spirited-away-2001-poster.jpg", "backdrop": "assets/movie-spirited-away-2001-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:22:42.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 129, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 96, "rtCriticsLabel": "Certified Fresh", "rtAudience": 99, "rtAudienceLabel": "Upright", "imdb": 8.6, "imdbVotes": 987658, "tmdb": 8.536, "tmdbVotes": 19176}}, {"id": "movie-the-substance-2024", "title": "The Substance", "year": 2024, "type": "movie", "runtimeSeconds": 8531, "seasonCount": null, "communityRating": 7.135, "criticRating": 89, "genres": ["Horror", "Science Fiction"], "overview": "A fading celebrity decides to use a black market drug, a cell-replicating substance that temporarily creates a younger, better version of herself.", "poster": "assets/movie-the-substance-2024-poster.jpg", "backdrop": "assets/movie-the-substance-2024-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:30:22.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 933260, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 89, "rtCriticsLabel": "Certified Fresh", "rtAudience": 76, "rtAudienceLabel": "Upright", "imdb": 7.2, "imdbVotes": 466542, "tmdb": 7.13, "tmdbVotes": 6746}}, {"id": "movie-the-tale-of-the-princess-kaguya-2013", "title": "The Tale of The Princess Kaguya", "year": 2013, "type": "movie", "runtimeSeconds": 8218, "seasonCount": null, "communityRating": 8.1, "criticRating": 100, "genres": ["Animation", "Drama", "Fantasy"], "overview": "Found inside a shining stalk of bamboo by an old bamboo cutter and his wife, a tiny girl grows rapidly into an exquisite young lady. The mysterious young princess enthrals all who encounter her. But, ultimately, she\u2026", "poster": "assets/movie-the-tale-of-the-princess-kaguya-2013-poster.jpg", "backdrop": "assets/movie-the-tale-of-the-princess-kaguya-2013-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-07T13:40:15.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 149871, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Certified Fresh", "rtAudience": 90, "rtAudienceLabel": "Upright", "imdb": 8, "imdbVotes": 63574, "tmdb": 8.083, "tmdbVotes": 2079}}, {"id": "movie-taxi-driver-1976", "title": "Taxi Driver", "year": 1976, "type": "movie", "runtimeSeconds": 6831, "seasonCount": null, "communityRating": 8.121, "criticRating": 89, "genres": ["Crime", "Drama"], "overview": "Suffering from insomnia, disturbed loner Travis Bickle takes a job as a New York City cabbie, haunting the streets nightly, growing increasingly detached from reality as he dreams of cleaning up the filthy city.", "poster": "assets/movie-taxi-driver-1976-poster.jpg", "backdrop": "assets/movie-taxi-driver-1976-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:23:04.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 103, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 89, "rtCriticsLabel": "Certified Fresh", "rtAudience": 93, "rtAudienceLabel": "Upright", "imdb": 8.2, "imdbVotes": 1046700, "tmdb": 8.116, "tmdbVotes": 13973}}, {"id": "movie-triangle-of-sadness-2022", "title": "Triangle of Sadness", "year": 2022, "type": "movie", "runtimeSeconds": 8833, "seasonCount": null, "communityRating": 7.005, "criticRating": 71, "genres": ["Comedy", "Drama"], "overview": "A celebrity model couple are invited on a luxury cruise for the uber-rich, helmed by an unhinged, alcoholic captain. What first appears Instagrammable ends catastrophically, leaving the survivors stranded on a desert\u2026", "poster": "assets/movie-triangle-of-sadness-2022-poster.jpg", "backdrop": "assets/movie-triangle-of-sadness-2022-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:29:00.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 497828, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 72, "rtCriticsLabel": "Fresh", "rtAudience": 80, "rtAudienceLabel": "Upright", "imdb": 7.2, "imdbVotes": 221069, "tmdb": 7, "tmdbVotes": 3104}}, {"id": "movie-tsotsi-2005", "title": "Tsotsi", "year": 2005, "type": "movie", "runtimeSeconds": 5663, "seasonCount": null, "communityRating": 6.937, "criticRating": 82, "genres": ["Crime", "Drama"], "overview": "A young South African boy from the Johannesburg ghetto named Tsotsi, meaning Gangster, leaves home as a child to get away from his helpless parents. Now a teenage thug, Tsotsi finds a baby in the back seat of a car\u2026", "poster": "assets/movie-tsotsi-2005-poster.jpg", "backdrop": "assets/movie-tsotsi-2005-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:24:58.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 868, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 82, "rtCriticsLabel": "Certified Fresh", "rtAudience": 86, "rtAudienceLabel": "Upright", "imdb": 7.2, "imdbVotes": 32526, "tmdb": 6.941, "tmdbVotes": 372}}, {"id": "movie-unknown-number-the-high-school-catfish-2025", "title": "Unknown Number: The High School Catfish", "year": 2025, "type": "movie", "runtimeSeconds": 5691, "seasonCount": null, "communityRating": 6.746, "criticRating": 85, "genres": ["Documentary"], "overview": "Vulgar, taunting texts blow up the phones of a teen and her boyfriend. Who's sending them \u2014 and why? This twisty documentary reveals the shocking answer.", "poster": "assets/movie-unknown-number-the-high-school-catfish-2025-poster.jpg", "backdrop": "assets/movie-unknown-number-the-high-school-catfish-2025-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:32:10.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 1517772, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 85, "rtCriticsLabel": "Fresh", "rtAudience": 69, "rtAudienceLabel": "Upright", "imdb": 6.7, "imdbVotes": 23039, "tmdb": 6.737, "tmdbVotes": 257}}, {"id": "movie-the-uprising-2026", "title": "The Uprising", "year": 2026, "type": "movie", "runtimeSeconds": 7684, "seasonCount": null, "communityRating": 7.846, "criticRating": 55, "genres": ["Action", "Drama", "History"], "overview": "As war and plague devastate 14th-century England, a humble peasant ignites a rebellion, uniting an army of commoners to defy the King\u2019s might in a desperate fight for justice and survival.", "poster": "assets/movie-the-uprising-2026-poster.jpg", "backdrop": "assets/movie-the-uprising-2026-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-11-02T16:37:03.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 977942, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 57, "rtCriticsLabel": "Rotten", "rtAudience": 65, "rtAudienceLabel": "Upright", "imdb": 6.2, "imdbVotes": 6622, "tmdb": 7.8, "tmdbVotes": 404}}, {"id": "movie-vice-2018", "title": "Vice", "year": 2018, "type": "movie", "runtimeSeconds": 7948, "seasonCount": null, "communityRating": 7.013, "criticRating": 64, "genres": ["Drama", "Comedy", "History"], "overview": "George W. Bush picks Dick Cheney, the CEO of Halliburton Co., to be his Republican running mate in the 2000 presidential election. No stranger to politics, Cheney's impressive r\u00e9sum\u00e9 includes stints as White House\u2026", "poster": "assets/movie-vice-2018-poster.jpg", "backdrop": "assets/movie-vice-2018-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:28:30.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 429197, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 64, "rtCriticsLabel": "Fresh", "rtAudience": 61, "rtAudienceLabel": "Upright", "imdb": 7.2, "imdbVotes": 177299, "tmdb": 7.014, "tmdbVotes": 3723}}, {"id": "movie-warriors-of-the-wind-1984", "title": "Warriors of the Wind", "year": 1984, "type": "movie", "runtimeSeconds": 7022, "seasonCount": null, "communityRating": 7.941, "criticRating": 87, "genres": ["Adventure", "Animation", "Fantasy"], "overview": "After a global war, the seaside kingdom known as the Valley of the Wind remains one of the last strongholds on Earth untouched by a poisonous jungle and the powerful insects that guard it. Led by the courageous\u2026", "poster": "assets/movie-warriors-of-the-wind-1984-poster.jpg", "backdrop": "assets/movie-warriors-of-the-wind-1984-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:22:20.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 81, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 87, "rtCriticsLabel": "Fresh", "rtAudience": 91, "rtAudienceLabel": "Upright", "imdb": 8, "imdbVotes": 200154, "tmdb": 7.94, "tmdbVotes": 4260}}, {"id": "movie-we-bury-the-dead-2026", "title": "We Bury the Dead", "year": 2026, "type": "movie", "runtimeSeconds": 5693, "seasonCount": null, "communityRating": 6.153, "criticRating": 87, "genres": ["Horror", "Thriller"], "overview": "After a catastrophic military disaster reanimates the dead, Ava searches for her missing husband amidst an increasingly hostile world.", "poster": "assets/movie-we-bury-the-dead-2026-poster.jpg", "backdrop": "assets/movie-we-bury-the-dead-2026-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-08-26T05:36:00.4272328Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 1198984, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 87, "rtCriticsLabel": "Certified Fresh", "rtAudience": 45, "rtAudienceLabel": "Spilled", "imdb": 5.6, "imdbVotes": 20693, "tmdb": 6.137, "tmdbVotes": 433}}, {"id": "movie-white-hot-the-rise-fall-of-abercrombie-fitch-2022", "title": "White Hot: The Rise & Fall of Abercrombie & Fitch", "year": 2022, "type": "movie", "runtimeSeconds": 5187, "seasonCount": null, "communityRating": 5.663, "criticRating": 66, "genres": ["Documentary"], "overview": "All the cool kids were wearing it. This documentary explores A&F's pop culture reign in the late '90s and early 2000s and how it thrived on exclusion.", "poster": "assets/movie-white-hot-the-rise-fall-of-abercrombie-fitch-2022-poster.jpg", "backdrop": "assets/movie-white-hot-the-rise-fall-of-abercrombie-fitch-2022-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:30:34.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 953371, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 66, "rtCriticsLabel": "Fresh", "rtAudience": 29, "rtAudienceLabel": "Spilled", "imdb": 5.8, "imdbVotes": 5655, "tmdb": 5.7, "tmdbVotes": 102}}, {"id": "movie-the-wild-robot-2024", "title": "The Wild Robot", "year": 2024, "type": "movie", "runtimeSeconds": 6101, "seasonCount": null, "communityRating": 8.308, "criticRating": 97, "genres": ["Family", "Animation", "Science Fiction"], "overview": "After a shipwreck, an intelligent robot called Roz is stranded on an uninhabited island. To survive the harsh environment, Roz bonds with the island's animals and cares for an orphaned baby goose.", "poster": "assets/movie-the-wild-robot-2024-poster.jpg", "backdrop": "assets/movie-the-wild-robot-2024-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:31:49.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 1184918, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 97, "rtCriticsLabel": "Certified Fresh", "rtAudience": 98, "rtAudienceLabel": "Upright", "imdb": 8.1, "imdbVotes": 241134, "tmdb": 8.309, "tmdbVotes": 6819}}, {"id": "movie-the-wind-rises-2013", "title": "The Wind Rises", "year": 2013, "type": "movie", "runtimeSeconds": 8031, "seasonCount": null, "communityRating": 7.784, "criticRating": 88, "genres": ["Animation", "Drama", "History"], "overview": "A lifelong love of flight inspires Japanese aviation engineer Jiro Horikoshi, whose storied career includes the creation of the A-6M World War II fighter plane.", "poster": "assets/movie-the-wind-rises-2013-poster.jpg", "backdrop": "assets/movie-the-wind-rises-2013-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T10:24:38.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 149870, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 89, "rtCriticsLabel": "Certified Fresh", "rtAudience": 85, "rtAudienceLabel": "Upright", "imdb": 7.8, "imdbVotes": 115756, "tmdb": 7.783, "tmdbVotes": 3536}}, {"id": "movie-the-witcher-nightmare-of-the-wolf-2021", "title": "The Witcher: Nightmare of the Wolf", "year": 2021, "type": "movie", "runtimeSeconds": 5015, "seasonCount": null, "communityRating": 7.422, "criticRating": 100, "genres": ["Animation", "Action", "Fantasy"], "overview": "Escaping from poverty to become a witcher, Vesemir slays monsters for coin and glory, but when a new menace rises, he must face the demons of his past.", "poster": "assets/movie-the-witcher-nightmare-of-the-wolf-2021-poster.jpg", "backdrop": "assets/movie-the-witcher-nightmare-of-the-wolf-2021-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-26T04:42:03.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 666243, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": 83, "rtAudienceLabel": "Upright", "imdb": 7.2, "imdbVotes": 55759, "tmdb": 7.407, "tmdbVotes": 1159}}, {"id": "movie-the-witcher-sirens-of-the-deep-2025", "title": "The Witcher: Sirens of the Deep", "year": 2025, "type": "movie", "runtimeSeconds": 5517, "seasonCount": null, "communityRating": 6.8, "criticRating": 60, "genres": ["Animation", "Fantasy", "Action"], "overview": "When human sailors are attacked by mysterious creatures of the deep, only one person can stop the war between land and sea: the Witcher, Geralt of Rivia.", "poster": "assets/movie-the-witcher-sirens-of-the-deep-2025-poster.jpg", "backdrop": "assets/movie-the-witcher-sirens-of-the-deep-2025-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-26T05:03:26.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 1203329, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 60, "rtCriticsLabel": "Fresh", "rtAudience": 37, "rtAudienceLabel": "Spilled", "imdb": 6.1, "imdbVotes": 11799, "tmdb": 6.741, "tmdbVotes": 303}}], "shows": [{"id": "tv-adults-2025", "title": "Adults", "year": 2025, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 6.495, "criticRating": null, "genres": ["Comedy"], "overview": "A group of twenty-somethings in New York trying to be good people, despite being neither \"good\" nor \"people\" yet.", "poster": "assets/tv-adults-2025-poster.jpg", "backdrop": "assets/tv-adults-2025-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-23T21:01:41.1814765Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 260458, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 88, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.5, "tmdbVotes": 102}}, {"id": "tv-american-crime-story-2016", "title": "American Crime Story", "year": 2016, "type": "tv", "runtimeSeconds": null, "seasonCount": 3, "communityRating": 7.573, "criticRating": null, "genres": ["Crime", "Drama"], "overview": "An anthology series centered around some of history's most famous criminal investigations.", "poster": "assets/tv-american-crime-story-2016-poster.jpg", "backdrop": "assets/tv-american-crime-story-2016-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0490488Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 64513, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 84, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.585, "tmdbVotes": 1094}}, {"id": "tv-american-primeval-2025", "title": "American Primeval", "year": 2025, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.691, "criticRating": null, "genres": ["Western", "Drama", "Action & Adventure"], "overview": "A mother and son fleeing from their past form a found family while confronting a harsh landscape of freedom and cruelty in the American West.", "poster": "assets/tv-american-primeval-2025-poster.jpg", "backdrop": "assets/tv-american-primeval-2025-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0573221Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 215866, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 71, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.741, "tmdbVotes": 538}}, {"id": "tv-arrested-development-2003", "title": "Arrested Development", "year": 2003, "type": "tv", "runtimeSeconds": null, "seasonCount": 5, "communityRating": 7.925, "criticRating": null, "genres": ["Comedy"], "overview": "The story of a wealthy family that lost everything, and the one son who had no choice but to keep them all together.", "poster": "assets/tv-arrested-development-2003-poster.jpg", "backdrop": "assets/tv-arrested-development-2003-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0513048Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 4589, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 75, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.906, "tmdbVotes": 1793}}, {"id": "tv-avatar-the-last-airbender-2005", "title": "Avatar: The Last Airbender", "year": 2005, "type": "tv", "runtimeSeconds": 1440, "seasonCount": 3, "communityRating": 8.78, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "In a war-torn world of elemental magic, a young boy reawakens to undertake a dangerous mystic quest to fulfill his destiny as the Avatar, and bring peace to the world.", "poster": "assets/tv-avatar-the-last-airbender-2005-poster.jpg", "backdrop": "assets/tv-avatar-the-last-airbender-2005-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0504536Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 246, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.787, "tmdbVotes": 5215}}, {"id": "tv-b-b-vol-liefde-2021", "title": "B&B Vol Liefde", "year": 2021, "type": "tv", "runtimeSeconds": 3720, "seasonCount": 1, "communityRating": 7.2, "criticRating": null, "genres": ["Reality"], "overview": "B&B Vol Liefde is a heartwarming adventure, full of romance, excitement, and unforgettable moments. Adventurous Dutch B&B owners abroad throw their doors wide open and let their hearts speak, determined to find that\u2026", "poster": "assets/tv-b-b-vol-liefde-2021-poster.jpg", "backdrop": "assets/tv-b-b-vol-liefde-2021-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-31T22:30:11.129657Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 124428, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.2, "tmdbVotes": 10}}, {"id": "tv-the-bear-2022", "title": "The Bear", "year": 2022, "type": "tv", "runtimeSeconds": null, "seasonCount": 5, "communityRating": 8.139, "criticRating": null, "genres": ["Drama", "Comedy"], "overview": "Carmy, a young fine-dining chef, comes home to Chicago to run his family sandwich shop. As he fights to transform the shop and himself, he works alongside a rough-around-the-edges crew that ultimately reveal\u2026", "poster": "assets/tv-the-bear-2022-poster.jpg", "backdrop": "assets/tv-the-bear-2022-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0467371Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 136315, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 93, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.158, "tmdbVotes": 2025}}, {"id": "tv-blue-planet-ii-2017", "title": "Blue Planet II", "year": 2017, "type": "tv", "runtimeSeconds": 3600, "seasonCount": 1, "communityRating": 8.64, "criticRating": null, "genres": ["Documentary", "Family"], "overview": "There is nowhere more powerful and unforgiving yet more beautiful and compelling than the ocean. Join us and explore the greatest yet least known parts of our planet.", "poster": "assets/tv-blue-planet-ii-2017-poster.jpg", "backdrop": "assets/tv-blue-planet-ii-2017-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0634849Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 74313, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 97, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.6, "tmdbVotes": 380}}, {"id": "tv-breeders-2020", "title": "Breeders", "year": 2020, "type": "tv", "runtimeSeconds": 1500, "seasonCount": 4, "communityRating": 6.8, "criticRating": null, "genres": ["Comedy"], "overview": "Exposing the parental-paradox that it is possible, in the very same moment, to love your child to the horizon of the universe, while being apoplectically angry enough to want to send them there.", "poster": "assets/tv-breeders-2020-poster.jpg", "backdrop": "assets/tv-breeders-2020-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-28T18:54:21.8885435Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 93290, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 92, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.8, "tmdbVotes": 104}}, {"id": "tv-chernobyl-2019", "title": "Chernobyl", "year": 2019, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 8.718, "criticRating": null, "genres": ["Drama"], "overview": "The true story of one of the worst man-made catastrophes in history: the catastrophic nuclear accident at Chernobyl. A tale of the brave men and women who sacrificed to save Europe from unimaginable disaster.", "poster": "assets/tv-chernobyl-2019-poster.jpg", "backdrop": "assets/tv-chernobyl-2019-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0469903Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 87108, "artwork": {"posterKind": "poster", "backdropKind": "center_crop"}, "ratings": {"rtCritics": 95, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.731, "tmdbVotes": 8496}}, {"id": "tv-community-2009", "title": "Community", "year": 2009, "type": "tv", "runtimeSeconds": null, "seasonCount": 6, "communityRating": 7.997, "criticRating": null, "genres": ["Comedy"], "overview": "Follow the lives of a group of students at what is possibly the world\u2019s worst community college in the fictional locale of Greendale, Colorado.", "poster": "assets/tv-community-2009-poster.jpg", "backdrop": "assets/tv-community-2009-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0531416Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 18347, "artwork": {"posterKind": "center_crop", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 88, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.994, "tmdbVotes": 2226}}, {"id": "tv-dead-end-job-2026", "title": "Dead-End Job", "year": 2026, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.7, "criticRating": null, "genres": ["Mystery"], "overview": "Drowning in debt, a young man takes lucrative part-time jobs from a mysterious agency \u2014 only to find his hustle for more money turning nightmarish.", "poster": "assets/tv-dead-end-job-2026-poster.jpg", "backdrop": "assets/tv-dead-end-job-2026-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-11-04T18:58:01.8352857Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 289517, "artwork": {"posterKind": "center_crop", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.7, "tmdbVotes": 38}}, {"id": "tv-dopesick-2021", "title": "Dopesick", "year": 2021, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 8.015, "criticRating": null, "genres": ["Crime", "Drama"], "overview": "The story of how one company triggered the worst drug epidemic in American history. Look into the epicenter of America's struggle with opioid addiction, from a distressed Virginia mining community, to the hallways of\u2026", "poster": "assets/tv-dopesick-2021-poster.jpg", "backdrop": "assets/tv-dopesick-2021-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0580168Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 110695, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 89, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.035, "tmdbVotes": 565}}, {"id": "tv-dune-prophecy-2024", "title": "Dune: Prophecy", "year": 2024, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.213, "criticRating": null, "genres": ["Sci-Fi & Fantasy", "Drama", "Action & Adventure"], "overview": "Ten thousand years before the ascension of Paul Atreides, sisters Valya and Tula Harkonnen establish the fabled sect and female order that would become known as the Bene Gesserit to control the future of humankind.", "poster": "assets/tv-dune-prophecy-2024-poster.jpg", "backdrop": "assets/tv-dune-prophecy-2024-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-11-04T18:23:01.2908568Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 90228, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 64, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.213, "tmdbVotes": 677}}, {"id": "tv-east-of-eden-2026", "title": "East of Eden", "year": 2026, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.931, "criticRating": null, "genres": ["Drama", "Crime"], "overview": "In the early 1900s, a fiercely independent woman drives a rift between two brothers, igniting a saga that unfolds across two generations.", "poster": "assets/tv-east-of-eden-2026-poster.jpg", "backdrop": "assets/tv-east-of-eden-2026-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-23T18:07:11.3965096Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 258165, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 88, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.982, "tmdbVotes": 139}}, {"id": "tv-fallout-2024", "title": "Fallout", "year": 2024, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 8.124, "criticRating": null, "genres": ["Action & Adventure", "Sci-Fi & Fantasy", "Drama"], "overview": "The story of haves and have-nots in a world in which there's almost nothing left to have. 200 years after the apocalypse, the gentle denizens of luxury fallout shelters are forced to return to the irradiated\u2026", "poster": "assets/tv-fallout-2024-poster.jpg", "backdrop": "assets/tv-fallout-2024-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0669262Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 106379, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 94, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.106, "tmdbVotes": 3233}}, {"id": "tv-fawlty-towers-1975", "title": "Fawlty Towers", "year": 1975, "type": "tv", "runtimeSeconds": 1800, "seasonCount": 2, "communityRating": 8.295, "criticRating": null, "genres": ["Comedy"], "overview": "Owner Basil Fawlty, his wife Sybil, a chambermaid Polly, and Spanish waiter Manuel attempt to run their hotel amidst farcical situations and an array of demanding guests.", "poster": "assets/tv-fawlty-towers-1975-poster.jpg", "backdrop": "assets/tv-fawlty-towers-1975-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-19T23:26:39.7658344Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 2207, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.296, "tmdbVotes": 867}}, {"id": "tv-the-final-problem-2026", "title": "The Final Problem", "year": 2026, "type": "tv", "runtimeSeconds": 3420, "seasonCount": 1, "communityRating": 7.041, "criticRating": null, "genres": ["Crime", "Mystery"], "overview": "In the spring of 1959, a retired actor, who previously played Sherlock Holmes, must investigate a murder at a small hotel on a tiny island near Mallorca.", "poster": "assets/tv-the-final-problem-2026-poster.jpg", "backdrop": "assets/tv-the-final-problem-2026-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-11-04T19:16:12.4822791Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 290720, "artwork": {"posterKind": "center_crop", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.041, "tmdbVotes": 49}}, {"id": "tv-the-four-seasons-2025", "title": "The Four Seasons", "year": 2025, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 6.879, "criticRating": null, "genres": ["Comedy"], "overview": "The decades-long friendship between three married couples is tested when one divorces, complicating their tradition of quarterly weekend getaways.", "poster": "assets/tv-the-four-seasons-2025-poster.jpg", "backdrop": "assets/tv-the-four-seasons-2025-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-28T18:36:42.5326384Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 243316, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 81, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.892, "tmdbVotes": 204}}, {"id": "tv-the-get-down-2016", "title": "The Get Down", "year": 2016, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.3, "criticRating": null, "genres": ["Drama"], "overview": "In 1977 New York City, the talented and soulful youth of the South Bronx chase dreams and breakneck beats to transform music history.", "poster": "assets/tv-the-get-down-2016-poster.jpg", "backdrop": "assets/tv-the-get-down-2016-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0497737Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 65345, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 81, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.3, "tmdbVotes": 196}}, {"id": "tv-how-to-change-your-mind-2022", "title": "How to Change Your Mind", "year": 2022, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.76, "criticRating": null, "genres": ["Documentary", "Mystery"], "overview": "Author Michael Pollan leads the way in this docuseries exploring the history and uses of psychedelics, including LSD, psilocybin, MDMA and mescaline.", "poster": "assets/tv-how-to-change-your-mind-2022-poster.jpg", "backdrop": "assets/tv-how-to-change-your-mind-2022-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0601399Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 204304, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.8, "tmdbVotes": 53}}, {"id": "tv-interrogation-2020", "title": "Interrogation", "year": 2020, "type": "tv", "runtimeSeconds": 2700, "seasonCount": 1, "communityRating": 6.788, "criticRating": null, "genres": ["Drama", "Crime"], "overview": "Based on a true story that spanned more than 30 years in which a young man was charged and convicted of brutally murdering his mother. Each episode is structured around an interrogation taken directly from the real\u2026", "poster": "assets/tv-interrogation-2020-poster.jpg", "backdrop": "assets/tv-interrogation-2020-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-26T05:52:06.1068207Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 96488, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 57, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.9, "tmdbVotes": 54}}, {"id": "tv-it-s-always-sunny-in-philadelphia-2005", "title": "It's Always Sunny in Philadelphia", "year": 2005, "type": "tv", "runtimeSeconds": null, "seasonCount": 18, "communityRating": 8.2, "criticRating": null, "genres": ["Comedy"], "overview": "Four egocentric friends run a neighborhood Irish pub in Philadelphia and try to find their way through the adult world of work and relationships. Unfortunately, their warped views and precarious judgments often lead\u2026", "poster": "assets/tv-it-s-always-sunny-in-philadelphia-2005-poster.jpg", "backdrop": "assets/tv-it-s-always-sunny-in-philadelphia-2005-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0485774Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 2710, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 94, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.253, "tmdbVotes": 1457}}, {"id": "tv-a-knight-of-the-seven-kingdoms-2026", "title": "A Knight of the Seven Kingdoms", "year": 2026, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 8.431, "criticRating": null, "genres": ["Drama", "Sci-Fi & Fantasy", "Action & Adventure"], "overview": "A century before the events of Game of Thrones, two unlikely heroes wandered Westeros: a young, naive but courageous knight, Ser Duncan the Tall, and his diminutive squire, Egg. Set in an age when the Targaryen line\u2026", "poster": "assets/tv-a-knight-of-the-seven-kingdoms-2026-poster.jpg", "backdrop": "assets/tv-a-knight-of-the-seven-kingdoms-2026-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0493344Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 224372, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 94, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.4, "tmdbVotes": 1266}}, {"id": "tv-landman-2024", "title": "Landman", "year": 2024, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 8.194, "criticRating": null, "genres": ["Drama"], "overview": "Set in the proverbial boomtowns of West-Texas and a modern-day tale of fortune-seeking in the world of oil rigs, the series is an upstairs/downstairs story of roughnecks and wildcat billionaires that are fueling a\u2026", "poster": "assets/tv-landman-2024-poster.jpg", "backdrop": "assets/tv-landman-2024-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-22T21:38:18.0973875Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 157741, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 80, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.209, "tmdbVotes": 668}}, {"id": "tv-the-last-of-us-2023", "title": "The Last of Us", "year": 2023, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 8.419, "criticRating": null, "genres": ["Drama"], "overview": "Twenty years after modern civilization has been destroyed, Joel, a hardened survivor, is hired to smuggle Ellie, a 14-year-old girl, out of an oppressive quarantine zone. What starts as a small job soon becomes a\u2026", "poster": "assets/tv-the-last-of-us-2023-poster.jpg", "backdrop": "assets/tv-the-last-of-us-2023-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0640955Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 100088, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 94, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.4, "tmdbVotes": 7485}}, {"id": "tv-last-samurai-standing-2025", "title": "Last Samurai Standing", "year": 2025, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.805, "criticRating": null, "genres": ["Action & Adventure", "Drama"], "overview": "In the early Meiji era, Shujiro, once known as an undefeated samurai, decides to participate in a deadly survival game to save his family and villagers.", "poster": "assets/tv-last-samurai-standing-2025-poster.jpg", "backdrop": "assets/tv-last-samurai-standing-2025-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0622526Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 252193, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.812, "tmdbVotes": 271}}, {"id": "tv-lord-of-the-flies-2026", "title": "Lord of the Flies", "year": 2026, "type": "tv", "runtimeSeconds": 3480, "seasonCount": 1, "communityRating": 7.571, "criticRating": null, "genres": ["Drama", "Action & Adventure"], "overview": "After a plane crash, a group of schoolboys find themselves stranded on a tropical island without adults. Ralph is elected leader and, with the help of the intelligent Piggy, strives to maintain order and\u2026", "poster": "assets/tv-lord-of-the-flies-2026-poster.jpg", "backdrop": "assets/tv-lord-of-the-flies-2026-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0645648Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 270572, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 91, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.58, "tmdbVotes": 138}}, {"id": "tv-the-lord-of-the-rings-the-rings-of-power-2022", "title": "The Lord of the Rings: The Rings of Power", "year": 2022, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 7.233, "criticRating": null, "genres": ["Action & Adventure", "Sci-Fi & Fantasy", "Drama"], "overview": "Beginning in a time of relative peace, we follow an ensemble cast of characters as they confront the re-emergence of evil to Middle-earth. From the darkest depths of the Misty Mountains, to the majestic forests of\u2026", "poster": "assets/tv-the-lord-of-the-rings-the-rings-of-power-2022-poster.jpg", "backdrop": "assets/tv-the-lord-of-the-rings-the-rings-of-power-2022-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0618904Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 84773, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 84, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.211, "tmdbVotes": 4003}}, {"id": "tv-love-death-robots-2019", "title": "Love, Death & Robots", "year": 2019, "type": "tv", "runtimeSeconds": null, "seasonCount": 4, "communityRating": 8.21, "criticRating": null, "genres": ["Animation", "Sci-Fi & Fantasy"], "overview": "Terrifying creatures, wicked surprises and dark comedy converge in this NSFW anthology of animated stories presented by Tim Miller and David Fincher.", "poster": "assets/tv-love-death-robots-2019-poster.jpg", "backdrop": "assets/tv-love-death-robots-2019-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0460964Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 86831, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 89, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.203, "tmdbVotes": 4238}}, {"id": "tv-maniac-2018", "title": "Maniac", "year": 2018, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.338, "criticRating": null, "genres": ["Comedy", "Drama", "Sci-Fi & Fantasy"], "overview": "Two strangers are drawn to a mysterious pharmaceutical trial that will, they're assured, with no complications or side-effects whatsoever, solve all of their problems, permanently. Things do not go as planned.", "poster": "assets/tv-maniac-2018-poster.jpg", "backdrop": "assets/tv-maniac-2018-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-20T19:42:30.955548Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 73411, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 84, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.323, "tmdbVotes": 1073}}, {"id": "tv-mussolini-son-of-the-century-2024", "title": "Mussolini: Son of the Century", "year": 2024, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 8.135, "criticRating": null, "genres": ["Drama", "War & Politics"], "overview": "The rise of Benito Mussolini and the birth of fascism in Italy, chronicling a country\u2019s surrender to dictatorship and the relentless ascent of a man who rose from his ashes time and again.", "poster": "assets/tv-mussolini-son-of-the-century-2024-poster.jpg", "backdrop": "assets/tv-mussolini-son-of-the-century-2024-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0586588Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 212333, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 96, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.1, "tmdbVotes": 179}}, {"id": "tv-the-night-manager-2016", "title": "The Night Manager", "year": 2016, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 7.74, "criticRating": null, "genres": ["Drama", "Mystery", "Crime"], "overview": "Former British soldier Jonathan Pine navigates the shadowy recesses of Whitehall and Washington where an unholy alliance operates between the intelligence community and the secret arms trade. To infiltrate the inner\u2026", "poster": "assets/tv-the-night-manager-2016-poster.jpg", "backdrop": "assets/tv-the-night-manager-2016-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0663672Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 61859, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 88, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.7, "tmdbVotes": 1422}}, {"id": "tv-one-piece-2023", "title": "ONE PIECE", "year": 2023, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 8.118, "criticRating": null, "genres": ["Action & Adventure", "Sci-Fi & Fantasy"], "overview": "With his straw hat and ragtag crew, young pirate Monkey D. Luffy goes on an epic voyage for treasure.", "poster": "assets/tv-one-piece-2023-poster.jpg", "backdrop": "assets/tv-one-piece-2023-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0439378Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 111110, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 93, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.104, "tmdbVotes": 1988}}, {"id": "tv-only-murders-in-the-building-2021", "title": "Only Murders in the Building", "year": 2021, "type": "tv", "runtimeSeconds": null, "seasonCount": 5, "communityRating": 8.438, "criticRating": null, "genres": ["Comedy", "Mystery", "Crime"], "overview": "Three strangers share an obsession with true crime and suddenly find themselves wrapped up in one.", "poster": "assets/tv-only-murders-in-the-building-2021-poster.jpg", "backdrop": "assets/tv-only-murders-in-the-building-2021-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0594614Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 107113, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 95, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.398, "tmdbVotes": 2333}}, {"id": "tv-over-the-garden-wall-2014", "title": "Over the Garden Wall", "year": 2014, "type": "tv", "runtimeSeconds": 720, "seasonCount": 1, "communityRating": 8.6, "criticRating": null, "genres": ["Mystery", "Sci-Fi & Fantasy", "Animation"], "overview": "Two brothers, Wirt and Greg, find themselves lost in the Unknown; a strange forest adrift in time. With the help of a wise old Woodsman and a foul-tempered bluebird named Beatrice, Wirt and Greg must travel across\u2026", "poster": "assets/tv-over-the-garden-wall-2014-poster.jpg", "backdrop": "assets/tv-over-the-garden-wall-2014-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0455757Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 61617, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.6, "tmdbVotes": 1854}}, {"id": "tv-peaky-blinders-2013", "title": "Peaky Blinders", "year": 2013, "type": "tv", "runtimeSeconds": null, "seasonCount": 6, "communityRating": 8.5, "criticRating": null, "genres": ["Drama", "Crime"], "overview": "A gangster family epic set in 1919 Birmingham, England and centered on a gang who sew razor blades in the peaks of their caps, and their fierce boss Tommy Shelby, who means to move up in the world.", "poster": "assets/tv-peaky-blinders-2013-poster.jpg", "backdrop": "assets/tv-peaky-blinders-2013-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0539185Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 60574, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 93, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.5, "tmdbVotes": 11506}}, {"id": "tv-the-pitt-2025", "title": "The Pitt", "year": 2025, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 8.729, "criticRating": null, "genres": ["Drama"], "overview": "The staff of Pittsburgh's Trauma Medical Center work around the clock to save lives in an overcrowded and underfunded emergency department.", "poster": "assets/tv-the-pitt-2025-poster.jpg", "backdrop": "assets/tv-the-pitt-2025-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0582942Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 250307, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 96, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.731, "tmdbVotes": 1046}}, {"id": "tv-pluribus-2025", "title": "Pluribus", "year": 2025, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.936, "criticRating": null, "genres": ["Drama", "Sci-Fi & Fantasy"], "overview": "The most miserable person on Earth must save the world from happiness.", "poster": "assets/tv-pluribus-2025-poster.jpg", "backdrop": "assets/tv-pluribus-2025-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0674462Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 225171, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 99, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.921, "tmdbVotes": 1191}}, {"id": "tv-ripley-2024", "title": "RIPLEY", "year": 2024, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.911, "criticRating": null, "genres": ["Drama"], "overview": "A grifter in 1960s New York is hired to convince a wealthy man's son to return home from Italy and begins a life of deceit, fraud and murder.", "poster": "assets/tv-ripley-2024-poster.jpg", "backdrop": "assets/tv-ripley-2024-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0658942Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 94028, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 86, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.909, "tmdbVotes": 453}}, {"id": "tv-say-nothing-2024", "title": "Say Nothing", "year": 2024, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.462, "criticRating": null, "genres": ["Drama", "Crime"], "overview": "Through the eyes of various Irish Republican Army (IRA) members, explore the extremes some people will go to in the name of their beliefs, the way a deeply divided society can suddenly tip over into armed conflict,\u2026", "poster": "assets/tv-say-nothing-2024-poster.jpg", "backdrop": "assets/tv-say-nothing-2024-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0499752Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 245256, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 94, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.475, "tmdbVotes": 122}}, {"id": "tv-severance-2022", "title": "Severance", "year": 2022, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 8.396, "criticRating": null, "genres": ["Drama", "Mystery", "Sci-Fi & Fantasy"], "overview": "Mark leads a team of office workers whose memories have been surgically divided between their work and personal lives. When a mysterious colleague appears outside of work, it begins a journey to discover the truth\u2026", "poster": "assets/tv-severance-2022-poster.jpg", "backdrop": "assets/tv-severance-2022-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0598478Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 95396, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 95, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.389, "tmdbVotes": 3095}}, {"id": "tv-six-feet-under-2001", "title": "Six Feet Under", "year": 2001, "type": "tv", "runtimeSeconds": null, "seasonCount": 5, "communityRating": 8.115, "criticRating": null, "genres": ["Drama"], "overview": "When death is your business, what is your life? For the Fisher family, the world outside of their family-owned funeral home continues to be at least as challenging as\u2014and far less predictable than\u2014the one inside.", "poster": "assets/tv-six-feet-under-2001-poster.jpg", "backdrop": "assets/tv-six-feet-under-2001-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-09-26T04:04:05.108787Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 1274, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 81, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.1, "tmdbVotes": 1192}}, {"id": "tv-undercover-2019", "title": "Undercover", "year": 2019, "type": "tv", "runtimeSeconds": 3000, "seasonCount": 3, "communityRating": 7.5, "criticRating": null, "genres": ["Crime", "Drama"], "overview": "Undercover agents infiltrate a drug kingpin's operation by posing as a couple at the campground where he spends his weekends. Inspired by real events.", "poster": "assets/tv-undercover-2019-poster.jpg", "backdrop": "assets/tv-undercover-2019-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0570153Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 86942, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.5, "tmdbVotes": 186}}, {"id": "tv-upload-2020", "title": "Upload", "year": 2020, "type": "tv", "runtimeSeconds": null, "seasonCount": 4, "communityRating": 7.781, "criticRating": null, "genres": ["Comedy", "Sci-Fi & Fantasy", "Drama"], "overview": "In 2033, people who are near death can be \u201cuploaded\u201d into virtual reality hotels run by 6 tech firms. Cash-strapped Nora lives in Brooklyn and works customer service for the luxurious \u201cLakeview\u201d digital afterlife.\u2026", "poster": "assets/tv-upload-2020-poster.jpg", "backdrop": "assets/tv-upload-2020-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0566967Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 86248, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 87, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.757, "tmdbVotes": 1511}}, {"id": "tv-the-walk-in-2022", "title": "The Walk-In", "year": 2022, "type": "tv", "runtimeSeconds": 2760, "seasonCount": 1, "communityRating": 6.9, "criticRating": null, "genres": ["Drama", "Crime"], "overview": "Forced to flee the UK and go into hiding in the 1990s, when he became a mole from within the BNP, Matthew Collins returned to Britain to make a new life for himself, working with young white men in the fight against\u2026", "poster": "assets/tv-the-walk-in-2022-poster.jpg", "backdrop": "assets/tv-the-walk-in-2022-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0654569Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 210664, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 80, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7, "tmdbVotes": 50}}, {"id": "tv-the-white-lotus-2021", "title": "The White Lotus", "year": 2021, "type": "tv", "runtimeSeconds": null, "seasonCount": 3, "communityRating": 7.596, "criticRating": null, "genres": ["Comedy", "Drama", "Mystery"], "overview": "Follow the exploits of various guests and employees at an exclusive tropical resort over the span of a week as with each passing day, a darker complexity emerges in these picture-perfect travelers, the hotel\u2019s\u2026", "poster": "assets/tv-the-white-lotus-2021-poster.jpg", "backdrop": "assets/tv-the-white-lotus-2021-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0546438Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 111803, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 90, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.599, "tmdbVotes": 1604}}, {"id": "tv-widow-s-bay-2026", "title": "Widow's Bay", "year": 2026, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 8.172, "criticRating": null, "genres": ["Drama", "Comedy", "Mystery"], "overview": "The mayor of a New England town resolves to turn it into the next tourist hot spot, despite local warnings that it's cursed.", "poster": "assets/tv-widow-s-bay-2026-poster.jpg", "backdrop": "assets/tv-widow-s-bay-2026-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-20T10:27:12.7003979Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 270476, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 98, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8, "tmdbVotes": 585}}, {"id": "tv-the-witcher-2019", "title": "The Witcher", "year": 2019, "type": "tv", "runtimeSeconds": null, "seasonCount": 4, "communityRating": 7.905, "criticRating": null, "genres": ["Drama", "Action & Adventure"], "overview": "Geralt of Rivia, a mutated monster-hunter for hire, journeys toward his destiny in a turbulent world where people often prove more wicked than beasts.", "poster": "assets/tv-the-witcher-2019-poster.jpg", "backdrop": "assets/tv-the-witcher-2019-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0552369Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 71912, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 75, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.88, "tmdbVotes": 6997}}, {"id": "tv-yellowjackets-2021", "title": "Yellowjackets", "year": 2021, "type": "tv", "runtimeSeconds": null, "seasonCount": 3, "communityRating": 7.449, "criticRating": null, "genres": ["Drama", "Mystery"], "overview": "This equal parts survival epic, psychological horror story and coming-of-age drama tells the saga of a team of wildly talented high school girls soccer players who become the (un)lucky survivors of a plane crash deep\u2026", "poster": "assets/tv-yellowjackets-2021-poster.jpg", "backdrop": "assets/tv-yellowjackets-2021-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T15:45:22.0577612Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 117488, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 92, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.431, "tmdbVotes": 1070}}], "anime": [], "resume": [{"id": "episode-episode-1-2025-s1e1", "title": "Episode 1", "year": 2025, "type": "episode", "runtimeSeconds": 3618, "seasonCount": null, "communityRating": 7.357, "criticRating": null, "genres": ["Biography", "Drama", "History"], "overview": "Mussolini had just founded the Fasci di Combattimento when D'Annunzio took Fiume. Mussolini hoped to capitalize on the poet's achievement in the electoral arena.", "poster": "assets/episode-episode-1-2025-s1e1-poster.jpg", "backdrop": "assets/episode-episode-1-2025-s1e1-backdrop.jpg", "played": false, "playCount": 1, "resumeSeconds": 489.55, "progress": 0.1353, "addedAt": "2026-07-17T13:52:48.0000000Z", "lastPlayedAt": "2026-11-04T19:01:49.9731435Z", "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "Mussolini: Son of the Century", "seasonNumber": 1, "episodeNumber": 1, "artwork": {"posterKind": "center_crop", "backdropKind": "backdrop"}}], "nextUp": [{"id": "episode-no-honeymoon-2020-s1e8", "title": "No Honeymoon", "year": 2020, "type": "episode", "runtimeSeconds": 1499, "seasonCount": null, "communityRating": 6.7, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "Paul attempts to focus on his upcoming wedding but is constantly thwarted by his family; Ally is distracted by work, Luke is misbehaving at school and Leah (Stella Gonet) doesn\u2019t understand why they are even\u2026", "poster": "assets/episode-no-honeymoon-2020-s1e8-poster.jpg", "backdrop": "assets/episode-no-honeymoon-2020-s1e8-backdrop.jpg", "played": false, "playCount": 1, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-28T19:00:50.0000000Z", "lastPlayedAt": "2026-11-03T20:57:58.3314947Z", "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "Breeders", "seasonNumber": 1, "episodeNumber": 8, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}, {"id": "episode-notapusy-2005-s3e4", "title": "Notapusy", "year": 2005, "type": "episode", "runtimeSeconds": 1334, "seasonCount": null, "communityRating": 8.125, "criticRating": null, "genres": ["Comedy"], "overview": "To prove he's a man to Rita, Michael and his nephew Steve Holt compete in the Church And State Fair's annual triathlon. George Sr. tries to scare some at-risk youth, and Tobias tries to help George-Michael's\u2026", "poster": "assets/episode-notapusy-2005-s3e4-poster.jpg", "backdrop": "assets/episode-notapusy-2005-s3e4-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T12:17:58.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "Arrested Development", "seasonNumber": 3, "episodeNumber": 4, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}, {"id": "episode-the-man-who-would-be-vogue-2018-s2e1", "title": "The Man Who Would Be Vogue", "year": 2018, "type": "episode", "runtimeSeconds": 3203, "seasonCount": null, "communityRating": 7.459, "criticRating": null, "genres": ["Biography", "Crime", "Drama"], "overview": "The murder of Gianni Versace turns the eyes of the world onto Miami Beach.", "poster": "assets/episode-the-man-who-would-be-vogue-2018-s2e1-poster.jpg", "backdrop": "assets/episode-the-man-who-would-be-vogue-2018-s2e1-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T13:29:35.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "American Crime Story", "seasonNumber": 2, "episodeNumber": 1, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}, {"id": "episode-tomorrow-2024-s3e1", "title": "Tomorrow", "year": 2024, "type": "episode", "runtimeSeconds": 2207, "seasonCount": null, "communityRating": 7.6, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "The next day and the days that led to it.", "poster": "assets/episode-tomorrow-2024-s3e1-poster.jpg", "backdrop": "assets/episode-tomorrow-2024-s3e1-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-07-17T13:46:39.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "The Bear", "seasonNumber": 3, "episodeNumber": 1, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}}, {"id": "episode-episode-1-2025-s1e1", "title": "Episode 1", "year": 2025, "type": "episode", "runtimeSeconds": 3618, "seasonCount": null, "communityRating": 7.357, "criticRating": null, "genres": ["Biography", "Drama", "History"], "overview": "Mussolini had just founded the Fasci di Combattimento when D'Annunzio took Fiume. Mussolini hoped to capitalize on the poet's achievement in the electoral arena.", "poster": "assets/episode-episode-1-2025-s1e1-poster.jpg", "backdrop": "assets/episode-episode-1-2025-s1e1-backdrop.jpg", "played": false, "playCount": 1, "resumeSeconds": 489.55, "progress": 0.1353, "addedAt": "2026-07-17T13:52:48.0000000Z", "lastPlayedAt": "2026-11-04T19:01:49.9731435Z", "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "seriesTitle": "Mussolini: Son of the Century", "seasonNumber": 1, "episodeNumber": 1, "artwork": {"posterKind": "center_crop", "backdropKind": "backdrop"}}], "recentlyAdded": [{"id": "tv-the-final-problem-2026", "title": "The Final Problem", "year": 2026, "type": "tv", "runtimeSeconds": 3420, "seasonCount": 1, "communityRating": 7.041, "criticRating": null, "genres": ["Crime", "Mystery"], "overview": "In the spring of 1959, a retired actor, who previously played Sherlock Holmes, must investigate a murder at a small hotel on a tiny island near Mallorca.", "poster": "assets/tv-the-final-problem-2026-poster.jpg", "backdrop": "assets/tv-the-final-problem-2026-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-11-04T19:16:12.4822791Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 290720, "artwork": {"posterKind": "center_crop", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.041, "tmdbVotes": 49}}, {"id": "tv-dead-end-job-2026", "title": "Dead-End Job", "year": 2026, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.7, "criticRating": null, "genres": ["Mystery"], "overview": "Drowning in debt, a young man takes lucrative part-time jobs from a mysterious agency \u2014 only to find his hustle for more money turning nightmarish.", "poster": "assets/tv-dead-end-job-2026-poster.jpg", "backdrop": "assets/tv-dead-end-job-2026-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-11-04T18:58:01.8352857Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 289517, "artwork": {"posterKind": "center_crop", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.7, "tmdbVotes": 38}}, {"id": "tv-dune-prophecy-2024", "title": "Dune: Prophecy", "year": 2024, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.213, "criticRating": null, "genres": ["Sci-Fi & Fantasy", "Drama", "Action & Adventure"], "overview": "Ten thousand years before the ascension of Paul Atreides, sisters Valya and Tula Harkonnen establish the fabled sect and female order that would become known as the Bene Gesserit to control the future of humankind.", "poster": "assets/tv-dune-prophecy-2024-poster.jpg", "backdrop": "assets/tv-dune-prophecy-2024-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-11-04T18:23:01.2908568Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 90228, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 64, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.213, "tmdbVotes": 677}}, {"id": "movie-the-uprising-2026", "title": "The Uprising", "year": 2026, "type": "movie", "runtimeSeconds": 7684, "seasonCount": null, "communityRating": 7.846, "criticRating": 55, "genres": ["Action", "Drama", "History"], "overview": "As war and plague devastate 14th-century England, a humble peasant ignites a rebellion, uniting an army of commoners to defy the King\u2019s might in a desperate fight for justice and survival.", "poster": "assets/movie-the-uprising-2026-poster.jpg", "backdrop": "assets/movie-the-uprising-2026-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-11-02T16:37:03.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 977942, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 57, "rtCriticsLabel": "Rotten", "rtAudience": 65, "rtAudienceLabel": "Upright", "imdb": 6.2, "imdbVotes": 6622, "tmdb": 7.8, "tmdbVotes": 404}}, {"id": "movie-jennifer-s-body-2009", "title": "Jennifer's Body", "year": 2009, "type": "movie", "runtimeSeconds": 6450, "seasonCount": null, "communityRating": 6.012, "criticRating": 47, "genres": ["Horror", "Comedy"], "overview": "Jennifer, a gorgeous, seductive cheerleader takes evil to a whole new level after she's possessed by a sinister demon. Now it's up to her best friend to stop Jennifer's reign of terror before it's too late.", "poster": "assets/movie-jennifer-s-body-2009-poster.jpg", "backdrop": "assets/movie-jennifer-s-body-2009-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-30T03:37:04.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 19994, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 47, "rtCriticsLabel": "Rotten", "rtAudience": 36, "rtAudienceLabel": "Spilled", "imdb": 5.6, "imdbVotes": 179710, "tmdb": 6.013, "tmdbVotes": 4481}}, {"id": "tv-breeders-2020", "title": "Breeders", "year": 2020, "type": "tv", "runtimeSeconds": 1500, "seasonCount": 4, "communityRating": 6.8, "criticRating": null, "genres": ["Comedy"], "overview": "Exposing the parental-paradox that it is possible, in the very same moment, to love your child to the horizon of the universe, while being apoplectically angry enough to want to send them there.", "poster": "assets/tv-breeders-2020-poster.jpg", "backdrop": "assets/tv-breeders-2020-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-28T18:54:21.8885435Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 93290, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 92, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.8, "tmdbVotes": 104}}, {"id": "tv-the-four-seasons-2025", "title": "The Four Seasons", "year": 2025, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 6.879, "criticRating": null, "genres": ["Comedy"], "overview": "The decades-long friendship between three married couples is tested when one divorces, complicating their tradition of quarterly weekend getaways.", "poster": "assets/tv-the-four-seasons-2025-poster.jpg", "backdrop": "assets/tv-the-four-seasons-2025-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-28T18:36:42.5326384Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 243316, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 81, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.892, "tmdbVotes": 204}}, {"id": "tv-adults-2025", "title": "Adults", "year": 2025, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 6.495, "criticRating": null, "genres": ["Comedy"], "overview": "A group of twenty-somethings in New York trying to be good people, despite being neither \"good\" nor \"people\" yet.", "poster": "assets/tv-adults-2025-poster.jpg", "backdrop": "assets/tv-adults-2025-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-23T21:01:41.1814765Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 260458, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 88, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.5, "tmdbVotes": 102}}, {"id": "tv-east-of-eden-2026", "title": "East of Eden", "year": 2026, "type": "tv", "runtimeSeconds": null, "seasonCount": 1, "communityRating": 7.931, "criticRating": null, "genres": ["Drama", "Crime"], "overview": "In the early 1900s, a fiercely independent woman drives a rift between two brothers, igniting a saga that unfolds across two generations.", "poster": "assets/tv-east-of-eden-2026-poster.jpg", "backdrop": "assets/tv-east-of-eden-2026-backdrop.jpg", "played": true, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-23T18:07:11.3965096Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 258165, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 88, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.982, "tmdbVotes": 139}}, {"id": "movie-fighting-with-my-family-2019", "title": "Fighting with My Family", "year": 2019, "type": "movie", "runtimeSeconds": 6509, "seasonCount": null, "communityRating": 6.84, "criticRating": 93, "genres": ["Comedy"], "overview": "Born into a tight-knit wrestling family, Paige and her brother Zak are ecstatic when they get the once-in-a-lifetime opportunity to try out for the WWE. But when only Paige earns a spot in the competitive training\u2026", "poster": "assets/movie-fighting-with-my-family-2019-poster.jpg", "backdrop": "assets/movie-fighting-with-my-family-2019-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-22T22:10:59.0000000Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 445629, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 93, "rtCriticsLabel": "Certified Fresh", "rtAudience": 86, "rtAudienceLabel": "Upright", "imdb": 7, "imdbVotes": 94576, "tmdb": 6.837, "tmdbVotes": 1952}}, {"id": "tv-landman-2024", "title": "Landman", "year": 2024, "type": "tv", "runtimeSeconds": null, "seasonCount": 2, "communityRating": 8.194, "criticRating": null, "genres": ["Drama"], "overview": "Set in the proverbial boomtowns of West-Texas and a modern-day tale of fortune-seeking in the world of oil rigs, the series is an upstairs/downstairs story of roughnecks and wildcat billionaires that are fueling a\u2026", "poster": "assets/tv-landman-2024-poster.jpg", "backdrop": "assets/tv-landman-2024-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-22T21:38:18.0973875Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 157741, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 80, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.209, "tmdbVotes": 668}}, {"id": "tv-fawlty-towers-1975", "title": "Fawlty Towers", "year": 1975, "type": "tv", "runtimeSeconds": 1800, "seasonCount": 2, "communityRating": 8.295, "criticRating": null, "genres": ["Comedy"], "overview": "Owner Basil Fawlty, his wife Sybil, a chambermaid Polly, and Spanish waiter Manuel attempt to run their hotel amidst farcical situations and an array of demanding guests.", "poster": "assets/tv-fawlty-towers-1975-poster.jpg", "backdrop": "assets/tv-fawlty-towers-1975-backdrop.jpg", "played": false, "playCount": 0, "resumeSeconds": 0.0, "progress": 0, "addedAt": "2026-10-19T23:26:39.7658344Z", "lastPlayedAt": null, "availability": "in_library", "availabilityEvidence": "viewer_scoped_jellyfin_library", "source": "jellyfin_library", "tmdbId": 2207, "artwork": {"posterKind": "poster", "backdropKind": "backdrop"}, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.296, "tmdbVotes": 867}}], "discover": [{"id": "movie-12-years-a-slave-2013", "title": "12 Years a Slave", "year": 2013, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "History"], "overview": "In the pre-Civil War United States, Solomon Northup, a free black man from upstate New York, is abducted and sold into slavery. Facing cruelty as well as unexpected kindnesses Solomon struggles not only to stay\u2026", "poster": "assets/movie-12-years-a-slave-2013-poster.jpg", "backdrop": "assets/movie-12-years-a-slave-2013-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 76203, "ratings": {"rtCritics": 95, "rtCriticsLabel": "Certified Fresh", "rtAudience": 90, "rtAudienceLabel": "Upright", "imdb": 8.1, "imdbVotes": 796313, "tmdb": 7.927, "tmdbVotes": 12335}}, {"id": "movie-101-dalmatians-ii-patch-s-london-adventure-2003", "title": "101 Dalmatians II: Patch's London Adventure", "year": 2003, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Family", "Animation", "Adventure"], "overview": "Being one of 101 takes its toll on Patch, who doesn't feel unique. When he's accidentally left behind on moving day, he meets his idol, Thunderbolt, who enlists him on a publicity campaign.", "poster": "assets/movie-101-dalmatians-ii-patch-s-london-adventure-2003-poster.jpg", "backdrop": "assets/movie-101-dalmatians-ii-patch-s-london-adventure-2003-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 13654, "ratings": {"rtCritics": 67, "rtCriticsLabel": "Fresh", "rtAudience": 41, "rtAudienceLabel": "Spilled", "imdb": 5.7, "imdbVotes": 12343, "tmdb": 6.025, "tmdbVotes": 1274}}, {"id": "movie-absolution-2024", "title": "Absolution", "year": 2024, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action", "Crime", "Thriller"], "overview": "An aging ex-boxer gangster working as muscle for a Boston crime boss receives an upsetting diagnosis. Despite a faltering memory, he attempts to rectify the sins of his past and reconnect with his estranged\u2026", "poster": "assets/movie-absolution-2024-poster.jpg", "backdrop": "assets/movie-absolution-2024-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 974453, "ratings": {"rtCritics": 56, "rtCriticsLabel": "Rotten", "rtAudience": 32, "rtAudienceLabel": "Spilled", "imdb": 5.2, "imdbVotes": 11413, "tmdb": 5.906, "tmdbVotes": 564}}, {"id": "movie-addicted-2014", "title": "Addicted", "year": 2014, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Thriller"], "overview": "A gallerist risks her family and flourishing career when she enters into an affair with a talented painter and slowly loses control of her life.", "poster": "assets/movie-addicted-2014-poster.jpg", "backdrop": "assets/movie-addicted-2014-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 235271, "ratings": {"rtCritics": 7, "rtCriticsLabel": "Rotten", "rtAudience": 51, "rtAudienceLabel": "Spilled", "imdb": 5, "imdbVotes": 13131, "tmdb": 5.928, "tmdbVotes": 466}}, {"id": "movie-all-the-money-in-the-world-2017", "title": "All the Money in the World", "year": 2017, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Crime", "History", "Thriller"], "overview": "The story of the kidnapping of 16-year-old John Paul Getty III and the desperate attempt by his devoted mother to convince his billionaire grandfather Jean Paul Getty to pay the ransom.", "poster": "assets/movie-all-the-money-in-the-world-2017-poster.jpg", "backdrop": "assets/movie-all-the-money-in-the-world-2017-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 446791, "ratings": {"rtCritics": 79, "rtCriticsLabel": "Certified Fresh", "rtAudience": 66, "rtAudienceLabel": "Upright", "imdb": 6.8, "imdbVotes": 97448, "tmdb": 6.493, "tmdbVotes": 2493}}, {"id": "movie-the-alto-knights-2025", "title": "The Alto Knights", "year": 2025, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Crime", "Drama"], "overview": "Two of New York's most notorious organized crime bosses, Frank Costello and Vito Genovese, vie for control of the city's streets. Once the best of friends, petty jealousies and a series of betrayals place them on a\u2026", "poster": "assets/movie-the-alto-knights-2025-poster.jpg", "backdrop": "assets/movie-the-alto-knights-2025-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 1013601, "ratings": {"rtCritics": 39, "rtCriticsLabel": "Rotten", "rtAudience": 71, "rtAudienceLabel": "Upright", "imdb": 5.9, "imdbVotes": 23017, "tmdb": 6.223, "tmdbVotes": 485}}, {"id": "movie-the-amazing-spider-man-2-2014", "title": "The Amazing Spider-Man 2", "year": 2014, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action", "Adventure", "Science Fiction"], "overview": "For Peter Parker, life is busy. Between taking out the bad guys as Spider-Man and spending time with the person he loves, Gwen Stacy, high school graduation cannot come quickly enough. Peter has not forgotten about\u2026", "poster": "assets/movie-the-amazing-spider-man-2-2014-poster.jpg", "backdrop": "assets/movie-the-amazing-spider-man-2-2014-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 102382, "ratings": {"rtCritics": 51, "rtCriticsLabel": "Rotten", "rtAudience": 64, "rtAudienceLabel": "Upright", "imdb": 6.6, "imdbVotes": 611981, "tmdb": 6.567, "tmdbVotes": 14919}}, {"id": "movie-ant-man-and-the-wasp-quantumania-2023", "title": "Ant-Man and the Wasp: Quantumania", "year": 2023, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action", "Adventure", "Science Fiction"], "overview": "Super-Hero partners Scott Lang and Hope van Dyne, along with with Hope's parents Janet van Dyne and Hank Pym, and Scott's daughter Cassie Lang, find themselves exploring the Quantum Realm, interacting with strange\u2026", "poster": "assets/movie-ant-man-and-the-wasp-quantumania-2023-poster.jpg", "backdrop": "assets/movie-ant-man-and-the-wasp-quantumania-2023-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 640146, "ratings": {"rtCritics": 46, "rtCriticsLabel": "Rotten", "rtAudience": 81, "rtAudienceLabel": "Upright", "imdb": 6, "imdbVotes": 281738, "tmdb": 6.197, "tmdbVotes": 6236}}, {"id": "movie-arctic-dogs-2019", "title": "Arctic Dogs", "year": 2019, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Animation", "Adventure", "Comedy"], "overview": "Animals band together to save the day when the evil Otto Von Walrus hatches a sinister scheme to accelerate global warming and melt the Arctic Circle.", "poster": "assets/movie-arctic-dogs-2019-poster.jpg", "backdrop": "assets/movie-arctic-dogs-2019-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 416153, "ratings": {"rtCritics": 13, "rtCriticsLabel": "Rotten", "rtAudience": 60, "rtAudienceLabel": "Upright", "imdb": 4.9, "imdbVotes": 2580, "tmdb": 5.9, "tmdbVotes": 148}}, {"id": "movie-the-avengers-2012", "title": "The Avengers", "year": 2012, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Science Fiction", "Action", "Adventure"], "overview": "When an unexpected enemy emerges and threatens global safety and security, Nick Fury, director of the international peacekeeping agency known as S.H.I.E.L.D., finds himself in need of a team to pull the world back\u2026", "poster": "assets/movie-the-avengers-2012-poster.jpg", "backdrop": "assets/movie-the-avengers-2012-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 24428, "ratings": {"rtCritics": 91, "rtCriticsLabel": "Certified Fresh", "rtAudience": 91, "rtAudienceLabel": "Upright", "imdb": 8, "imdbVotes": 1590693, "tmdb": 8.084, "tmdbVotes": 40262}}, {"id": "movie-black-bear-2020", "title": "Black Bear", "year": 2020, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Thriller"], "overview": "At a remote lake house in the Adirondack Mountains, a couple entertains an out-of-town guest looking for inspiration in her filmmaking. The group quickly falls into a calculated game of desire, manipulation, and\u2026", "poster": "assets/movie-black-bear-2020-poster.jpg", "backdrop": "assets/movie-black-bear-2020-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 624788, "ratings": {"rtCritics": 89, "rtCriticsLabel": "Certified Fresh", "rtAudience": 64, "rtAudienceLabel": "Upright", "imdb": 6.5, "imdbVotes": 18408, "tmdb": 6.337, "tmdbVotes": 315}}, {"id": "movie-black-panther-2018", "title": "Black Panther", "year": 2018, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action", "Adventure", "Science Fiction"], "overview": "King T'Challa returns home to the reclusive, technologically advanced African nation of Wakanda to serve as his country's new leader. However, T'Challa soon finds that he is challenged for the throne by factions\u2026", "poster": "assets/movie-black-panther-2018-poster.jpg", "backdrop": "assets/movie-black-panther-2018-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 284054, "ratings": {"rtCritics": 96, "rtCriticsLabel": "Certified Fresh", "rtAudience": null, "rtAudienceLabel": "Spilled", "imdb": 7.3, "imdbVotes": 931120, "tmdb": 7.36, "tmdbVotes": 24190}}, {"id": "movie-a-bronx-tale-1993", "title": "A Bronx Tale", "year": 1993, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Crime"], "overview": "Set in the Bronx during the tumultuous 1960s, an adolescent boy is torn between his honest, working-class father and a violent yet charismatic crime boss. Complicating matters is the youngster's growing attraction -\u2026", "poster": "assets/movie-a-bronx-tale-1993-poster.jpg", "backdrop": "assets/movie-a-bronx-tale-1993-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 1607, "ratings": {"rtCritics": 97, "rtCriticsLabel": "Fresh", "rtAudience": 92, "rtAudienceLabel": "Upright", "imdb": 7.8, "imdbVotes": 191930, "tmdb": 7.913, "tmdbVotes": 3075}}, {"id": "movie-by-any-means-2026", "title": "By Any Means", "year": 2026, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Thriller", "History", "Crime"], "overview": "In 1960s Mississippi, a young Black FBI agent is forced to partner with a notorious mafia hitman to investigate a wave of brutal civil rights murders.", "poster": "assets/movie-by-any-means-2026-poster.jpg", "backdrop": "assets/movie-by-any-means-2026-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 1380417, "ratings": {"rtCritics": 49, "rtCriticsLabel": "Rotten", "rtAudience": 88, "rtAudienceLabel": "Upright", "imdb": 6.8, "imdbVotes": 3534, "tmdb": 6.6, "tmdbVotes": 92}}, {"id": "movie-captain-america-civil-war-2016", "title": "Captain America: Civil War", "year": 2016, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Adventure", "Action", "Science Fiction"], "overview": "Following the events of Age of Ultron, the collective governments of the world pass an act designed to regulate all superhuman activity. This polarizes opinion amongst the Avengers, causing two factions to side with\u2026", "poster": "assets/movie-captain-america-civil-war-2016-poster.jpg", "backdrop": "assets/movie-captain-america-civil-war-2016-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 271110, "ratings": {"rtCritics": 90, "rtCriticsLabel": "Certified Fresh", "rtAudience": 89, "rtAudienceLabel": "Upright", "imdb": 7.8, "imdbVotes": 954943, "tmdb": 7.455, "tmdbVotes": 24808}}, {"id": "movie-captain-america-the-first-avenger-2011", "title": "Captain America: The First Avenger", "year": 2011, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action", "Adventure", "Science Fiction"], "overview": "During World War II, Steve Rogers is a sickly man from Brooklyn who's transformed into super-soldier Captain America to aid in the war effort. Rogers must stop the Red Skull \u2013 Adolf Hitler's ruthless head of\u2026", "poster": "assets/movie-captain-america-the-first-avenger-2011-poster.jpg", "backdrop": "assets/movie-captain-america-the-first-avenger-2011-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 1771, "ratings": {"rtCritics": 80, "rtCriticsLabel": "Certified Fresh", "rtAudience": 75, "rtAudienceLabel": "Upright", "imdb": 6.9, "imdbVotes": 993453, "tmdb": 7.012, "tmdbVotes": 23473}}, {"id": "movie-captain-america-the-winter-soldier-2014", "title": "Captain America: The Winter Soldier", "year": 2014, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action", "Adventure", "Science Fiction"], "overview": "After the cataclysmic events in New York with The Avengers, Steve Rogers, aka Captain America is living quietly in Washington, D.C. and trying to adjust to the modern world. But when a S.H.I.E.L.D. colleague comes\u2026", "poster": "assets/movie-captain-america-the-winter-soldier-2014-poster.jpg", "backdrop": "assets/movie-captain-america-the-winter-soldier-2014-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 100402, "ratings": {"rtCritics": 90, "rtCriticsLabel": "Certified Fresh", "rtAudience": 92, "rtAudienceLabel": "Upright", "imdb": 7.7, "imdbVotes": 995514, "tmdb": 7.654, "tmdbVotes": 20688}}, {"id": "movie-careful-what-you-wish-for-2015", "title": "Careful What You Wish For", "year": 2015, "type": "movie", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Thriller"], "overview": "A guy gets more than he bargained for after entering into an affair with the wife of an investment banker. Soon, a suspicious death and substantial life insurance policy embroil him in a scandal.", "poster": "assets/movie-careful-what-you-wish-for-2015-poster.jpg", "backdrop": "assets/movie-careful-what-you-wish-for-2015-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 317744, "ratings": {"rtCritics": 17, "rtCriticsLabel": "Rotten", "rtAudience": 61, "rtAudienceLabel": "Upright", "imdb": 5.6, "imdbVotes": 9452, "tmdb": 6.129, "tmdbVotes": 446}}, {"id": "tv-12-monkeys-2015", "title": "12 Monkeys", "year": 2015, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Sci-Fi & Fantasy", "Drama"], "overview": "The provocative story of Cole, a time traveler from a decimated future in a high-stakes race against the clock. Utilizing a dangerous and untested method of time travel, he journeys from 2043 to the present day on a\u2026", "poster": "assets/tv-12-monkeys-2015-poster.jpg", "backdrop": "assets/tv-12-monkeys-2015-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 60948, "ratings": {"rtCritics": 88, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.441, "tmdbVotes": 1245}}, {"id": "tv-90-day-fiance-before-the-90-days-2017", "title": "90 Day Fianc\u00e9: Before the 90 Days", "year": 2017, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Reality"], "overview": "Follow couples who have an existing relationship online, but haven't met in person. We'll experience their journey as they travel to the other's foreign country for the first time in an attempt to establish an\u2026", "poster": "assets/tv-90-day-fiance-before-the-90-days-2017-poster.jpg", "backdrop": "assets/tv-90-day-fiance-before-the-90-days-2017-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 73319, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.5, "tmdbVotes": 45}}, {"id": "tv-90-day-fiance-the-other-way-2019", "title": "90 Day Fianc\u00e9: The Other Way", "year": 2019, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Reality"], "overview": "Americans move to a foreign country all in the name of love. Will uprooting their lives in the United States pay off, or will they be packing up and moving back to America brokenhearted?", "poster": "assets/tv-90-day-fiance-the-other-way-2019-poster.jpg", "backdrop": "assets/tv-90-day-fiance-the-other-way-2019-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 90046, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.8, "tmdbVotes": 34}}, {"id": "tv-the-100-2014", "title": "The 100", "year": 2014, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Sci-Fi & Fantasy", "Action & Adventure", "Drama"], "overview": "100 years in the future, when the Earth has been abandoned due to radioactivity, the last surviving humans live on an ark orbiting the planet \u2014 but the ark won't last forever. So the repressive regime picks 100\u2026", "poster": "assets/tv-the-100-2014-poster.jpg", "backdrop": "assets/tv-the-100-2014-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 48866, "ratings": {"rtCritics": 93, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.878, "tmdbVotes": 8855}}, {"id": "tv-accused-2023", "title": "Accused", "year": 2023, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Crime", "Drama"], "overview": "In this crime anthology series, viewers discover how an ordinary person got caught up in an extraordinary situation, ultimately revealing how one wrong turn leads to another, until it\u2019s too late to turn back. Told\u2026", "poster": "assets/tv-accused-2023-poster.jpg", "backdrop": "assets/tv-accused-2023-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 202097, "ratings": {"rtCritics": 61, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.851, "tmdbVotes": 87}}, {"id": "tv-after-life-2019", "title": "After Life", "year": 2019, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "Tony had a perfect life. But after his wife Lisa suddenly dies, Tony changes. After contemplating taking his own life, he decides instead to live long enough to punish the world by saying and doing whatever he likes\u2026", "poster": "assets/tv-after-life-2019-poster.jpg", "backdrop": "assets/tv-after-life-2019-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 79410, "ratings": {"rtCritics": 72, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.902, "tmdbVotes": 1169}}, {"id": "tv-all-creatures-great-and-small-1978", "title": "All Creatures Great and Small", "year": 1978, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Comedy", "Drama"], "overview": "The trials and misadventures of the staff at a country veterinary office in Yorkshire. James Herriot, a young animal surgeon, moves to a small Yorkshire town to begin his first job.", "poster": "assets/tv-all-creatures-great-and-small-1978-poster.jpg", "backdrop": "assets/tv-all-creatures-great-and-small-1978-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "center_crop", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 7406, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.8, "tmdbVotes": 50}}, {"id": "tv-american-housewife-2016", "title": "American Housewife", "year": 2016, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Comedy"], "overview": "A family comedy narrated by Katie, a strong-willed mother, raising her flawed family in a wealthy town filled with perfect wives and their perfect offspring.", "poster": "assets/tv-american-housewife-2016-poster.jpg", "backdrop": "assets/tv-american-housewife-2016-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 67117, "ratings": {"rtCritics": null, "rtCriticsLabel": "Rotten", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.7, "tmdbVotes": 187}}, {"id": "tv-atlantis-2013", "title": "Atlantis", "year": 2013, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action & Adventure", "Sci-Fi & Fantasy"], "overview": "A fantasy drama set in a world of legendary heroes and mythical creatures. Far from home and desperate for answers, Jason washes up on the shores of an ancient land. A mysterious place; a world of bull leaping, of\u2026", "poster": "assets/tv-atlantis-2013-poster.jpg", "backdrop": "assets/tv-atlantis-2013-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 47054, "ratings": {"rtCritics": 62, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.4, "tmdbVotes": 155}}, {"id": "tv-avatar-the-last-airbender-2024", "title": "Avatar: The Last Airbender", "year": 2024, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Action & Adventure", "Sci-Fi & Fantasy", "Drama"], "overview": "A young boy known as the Avatar must master the four elemental powers to save a world at war \u2014 and fight a ruthless enemy bent on stopping him.", "poster": "assets/tv-avatar-the-last-airbender-2024-poster.jpg", "backdrop": "assets/tv-avatar-the-last-airbender-2024-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 82452, "ratings": {"rtCritics": 66, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.8, "tmdbVotes": 1406}}, {"id": "tv-avatar-seven-havens-2026", "title": "Avatar: Seven Havens", "year": 2026, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "In a world shattered by a devastating cataclysm, a young Earthbender discovers she's the new Avatar after Korra. But in this dangerous era, that title marks her as humanity's destroyer, not its savior. Hunted by both\u2026", "poster": "assets/tv-avatar-seven-havens-2026-poster.jpg", "backdrop": "assets/tv-avatar-seven-havens-2026-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 284833, "ratings": {"rtCritics": 100, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.9, "tmdbVotes": 183}}, {"id": "tv-batman-the-brave-and-the-bold-2008", "title": "Batman: The Brave and the Bold", "year": 2008, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Animation", "Action & Adventure", "Sci-Fi & Fantasy"], "overview": "The Caped Crusader is teamed up with Blue Beetle, Green Arrow, Aquaman and countless others in his quest to uphold justice.", "poster": "assets/tv-batman-the-brave-and-the-bold-2008-poster.jpg", "backdrop": "assets/tv-batman-the-brave-and-the-bold-2008-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 15804, "ratings": {"rtCritics": 96, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.6, "tmdbVotes": 295}}, {"id": "tv-beat-bobby-flay-2013", "title": "Beat Bobby Flay", "year": 2013, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Reality"], "overview": "Two talented chefs go head-to-head for the chance to Beat Bobby Flay. To get to Bobby the chefs must first face off against each other, creating a spectacular dish with a secret ingredient of Bobby's choice. Judges\u2026", "poster": "assets/tv-beat-bobby-flay-2013-poster.jpg", "backdrop": "assets/tv-beat-bobby-flay-2013-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 62481, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.5, "tmdbVotes": 31}}, {"id": "tv-benson-1979", "title": "Benson", "year": 1979, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Comedy"], "overview": "A butler deals with life at the governor's mansion.", "poster": "assets/tv-benson-1979-poster.jpg", "backdrop": "assets/tv-benson-1979-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 1849, "ratings": {"rtCritics": null, "rtCriticsLabel": null, "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7, "tmdbVotes": 46}}, {"id": "tv-black-rabbit-2025", "title": "Black Rabbit", "year": 2025, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Crime"], "overview": "A rising-star restaurateur is forced into New York's criminal underworld when his chaotic brother returns to town with loan sharks on his trail.", "poster": "assets/tv-black-rabbit-2025-poster.jpg", "backdrop": "assets/tv-black-rabbit-2025-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 249039, "ratings": {"rtCritics": 66, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 6.983, "tmdbVotes": 240}}, {"id": "tv-black-ish-2014", "title": "black-ish", "year": 2014, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Comedy"], "overview": "A family man struggles to gain a sense of cultural identity while raising his kids in a predominantly white, upper-middle-class neighborhood.", "poster": "assets/tv-black-ish-2014-poster.jpg", "backdrop": "assets/tv-black-ish-2014-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 61381, "ratings": {"rtCritics": 92, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.1, "tmdbVotes": 352}}, {"id": "tv-breaking-bad-2008", "title": "Breaking Bad", "year": 2008, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Drama", "Crime"], "overview": "Walter White, a New Mexico chemistry teacher, is diagnosed with Stage III cancer and given a prognosis of only two years left to live. He becomes filled with a sense of fearlessness and an unrelenting desire to\u2026", "poster": "assets/tv-breaking-bad-2008-poster.jpg", "backdrop": "assets/tv-breaking-bad-2008-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "center_crop", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 1396, "ratings": {"rtCritics": 96, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 8.959, "tmdbVotes": 19030}}, {"id": "tv-the-capture-2019", "title": "The Capture", "year": 2019, "type": "tv", "runtimeSeconds": null, "seasonCount": null, "communityRating": null, "criticRating": null, "genres": ["Crime", "Drama", "Mystery"], "overview": "When soldier Shaun Emery's conviction for a murder in Afghanistan is overturned due to flawed video evidence, he returns to life as a free man with his young daughter. But when damning CCTV footage from a night out\u2026", "poster": "assets/tv-the-capture-2019-poster.jpg", "backdrop": "assets/tv-the-capture-2019-backdrop.jpg", "played": null, "playCount": null, "resumeSeconds": null, "progress": null, "addedAt": null, "lastPlayedAt": null, "availability": "not_here", "availabilityEvidence": "retained_profile_membership_and_native_request_ledger", "source": "materialized_personal_recommendation", "rank": null, "watchStateKnown": false, "artwork": {"posterKind": "poster", "backdropKind": "poster_crop"}, "availabilityIsSnapshot": true, "tmdbId": 93166, "ratings": {"rtCritics": 97, "rtCriticsLabel": "Fresh", "rtAudience": null, "rtAudienceLabel": null, "imdb": null, "imdbVotes": null, "tmdb": 7.634, "tmdbVotes": 361}}], "discoverySource": {"state": "materialized_snapshot", "rankKnown": false, "selection": "First 18 alphabetically indexed movie stubs and first 18 series stubs, not ranked feed order", "observedAt": "2026-11-04T20:42:12.030108+00:00"}}], "notes": {"real": ["Profile keys and library membership derive from deployed Conductarr configuration and viewer-scoped Jellyfin GET responses.", "Names, years, overviews, library ratings, genres, season-container counts and watch/resume data come from actual Jellyfin metadata.", "All local artwork is copied from Jellyfin; discovery posters are the real materialized Conductarr recommendation artwork.", "Discover contains 36 real materialized personal recommendations per profile, with profile-scoped availability from read-only retained membership/request state.", "Justin has no playable resume items. Mindel and Shared each have one. No progress or watched flags were invented."], "approximations": ["Default profile initials and display capitalization are frontend presentation choices; colours reuse the existing PWA palette, not live user appearance configuration.", "Conductarr catalogue and recommendation GET endpoints both returned 401. Authentication was not bypassed. SQLite contains no retained recommendation feed/candidate snapshot tables; the source keeps ranked feeds in memory. Discovery therefore reads existing per-profile Jellyfin recommendation stubs as metadata only.", "Discovery selection is 18 alphabetically indexed movie suggestions plus 18 series suggestions, not personal rank order; rank is null. Materialization age and original feed ordering are unknown.", "Discovery stub runtime is synthetic (two seconds), so runtime, season count, rating and watch state are null rather than presenting stub values as real media facts.", "93 selected identities lack original landscape art in the materialized metadata; their backdrop is a marked crop of the real poster, not an original film backdrop. Existing artwork is reused; new artwork is centre-cropped to the required dimensions.", "Jellyfin server-side resizing requests maxWidth/maxHeight/quality. When the result had the wrong aspect ratio, sequential single-thread ffmpeg center crops produce exact 300x450 and 1280x720 JPEGs.", "Series seasonCount is Jellyfin ChildCount, including season containers for specials when present.", "Discover availability is a retained profile-membership/request snapshot, not a fresh playback or acquisition guarantee. not_here means absent from that profile in retained state, not absent from the entire household."], "missing": ["Justin Movies has seven live viewer-scoped films, not 200. No titles are invented or borrowed across profile boundaries.", "Discovery community/critic ratings, reliable runtime/season counts, personal feed ranking/freshness and 93 original landscape backdrops are unavailable through the permitted read paths."], "unmetRequirements": ["At least 200 real profile-scoped Justin Movies"], "mustNotBeShown": ["Recs Movies", "Recs Shows", "Justin Movies", "Justin Shows", "Mindel Movies", "Mindel Shows", "Shared Movies", "Shared Shows", "DGiC", "Stored names padded with spaces", "Internal bucket and acquisition-instance names"], "privacy": ["Includes actual watched and resume history for Justin, Mindel and Shared independently; Shared is not a union.", "No Jellyfin item/user IDs, source paths, service URLs, credentials, request actor identities, or private hotlinks are included. tmdbId is a public external title identity.", "Only the seven configured playable libraries are selected. Extra observed DGiC views and all recommendation stubs are excluded from playable collections.", "No profile is assumed to be a child or age-restricted. Each playable list and episode shelf is limited to that profile\u2019s configured libraries."], "sourceReadPaths": ["GET /Users (allowlisted mapping only)", "GET /Users/{userId}/Views", "GET /Users/{userId}/Items (parent-scoped playable libraries and metadata-only recommendation libraries)", "GET /Users/{userId}/Items/Resume", "GET /Shows/NextUp?UserId=...", "GET /Items/{itemId}/Images/Primary", "GET /Items/{itemId}/Images/Backdrop/0", "Conductarr GET /catalogue/discover?page=1&media_type=movie (401)", "Conductarr GET /api/v1/recommendations/profiles/justin/feed (401)", "SQLite file URI mode=ro plus PRAGMA query_only=ON: catalogue_metadata, bucket_memberships, native_requests; sqlite_master for schema inspection"], "productionMutations": 0, "artwork": {"files": 632, "bytes": 35201423, "posterDimensions": [300, 450], "backdropDimensions": [1280, 720], "posterQualityRequested": 70, "backdropQualityRequested": 65, "posterDerivedBackdropCount": 93}, "consumerContract": ["profiles is an array in Justin/Mindel/Shared order. Every profile owns movies/shows/anime/resume/nextUp/recentlyAdded/discover item arrays; empty shelves are intentional.", "Item paths are relative to fixture.json. Resolve assets against the fixture directory, not against the outer prototype document.", "Use destinations for visible navigation. No raw library display/stored names belong in UI.", "Deduplicate library/resume/discover by stable local id or (type,tmdbId). Never treat a materialized recommendation as a playable library item.", "played=null and watchStateKnown=false mean unknown, not unwatched. Null ratings are missing, not zero; communityRating source is Jellyfin, not an asserted TMDB/IMDb score.", "availability values are in_library/requested/not_here. No requested examples were observed in the selected discover set; do not fabricate them.", "Render artwork.backdropKind=poster_crop as a poster-derived landscape; prefer the poster over pretending it is original landscape art.", "This static fixture preserves actual current collection sizes, viewer-scoped watch state and unknown discovery ranking; request and playback demonstrations must remain labelled simulations."]}} \ No newline at end of file diff --git a/wolfskin-design/src/test/resources/wolfskin-requests.json b/wolfskin-design/src/test/resources/wolfskin-requests.json new file mode 100644 index 00000000..507ebd65 --- /dev/null +++ b/wolfskin-design/src/test/resources/wolfskin-requests.json @@ -0,0 +1,217 @@ +{ + "source": "Locked Opus fixture/fixture.json; index.html EXAMPLES() pool order", + "items": [ + { + "id": "x1", + "mediaType": "tv", + "tmdbId": 11821, + "title": "The Abbott and Costello Show", + "requesterKey": "justin", + "requesterName": "Justin", + "actorKey": "justin", + "actorName": "Justin", + "stage": "Downloading", + "poster": "tv-the-abbott-and-costello-show-1952-poster.jpg", + "ratings": [ + { + "mark": "tmdb", + "score": 7.2, + "percentage": false + } + ] + }, + { + "id": "x2", + "mediaType": "movie", + "tmdbId": 13483, + "title": "Awake", + "requesterKey": "mindel", + "requesterName": "Mindel", + "actorKey": "justin", + "actorName": "Justin", + "stage": "AlmostReady", + "poster": "movie-awake-2007-poster.jpg", + "ratings": [ + { + "mark": "rotten", + "score": 23, + "percentage": true + }, + { + "mark": "spilled", + "score": 55, + "percentage": true + }, + { + "mark": "imdb", + "score": 6.5, + "percentage": false + } + ] + }, + { + "id": "x3", + "mediaType": "tv", + "tmdbId": 70785, + "title": "Anne with an E", + "requesterKey": "shared", + "requesterName": "Shared", + "actorKey": "mindel", + "actorName": "Mindel", + "stage": "Searching", + "poster": "tv-anne-with-an-e-2017-poster.jpg", + "ratings": [ + { + "mark": "fresh", + "score": 63, + "percentage": true + } + ] + }, + { + "id": "x4", + "mediaType": "movie", + "tmdbId": 99861, + "title": "Avengers: Age of Ultron", + "requesterKey": "justin", + "requesterName": "Justin", + "actorKey": "justin", + "actorName": "Justin", + "stage": "Waiting", + "poster": "movie-avengers-age-of-ultron-2015-poster.jpg", + "ratings": [ + { + "mark": "fresh", + "score": 75, + "percentage": true + }, + { + "mark": "upright", + "score": 82, + "percentage": true + }, + { + "mark": "imdb", + "score": 7.3, + "percentage": false + } + ] + }, + { + "id": "x5", + "mediaType": "movie", + "tmdbId": 1033148, + "title": "Die My Love", + "requesterKey": "mindel", + "requesterName": "Mindel", + "actorKey": "mindel", + "actorName": "Mindel", + "stage": "Failed", + "poster": "movie-die-my-love-2025-poster.jpg", + "ratings": [ + { + "mark": "fresh", + "score": 74, + "percentage": true + }, + { + "mark": "spilled", + "score": 45, + "percentage": true + }, + { + "mark": "imdb", + "score": 6, + "percentage": false + } + ] + }, + { + "id": "x6", + "mediaType": "movie", + "tmdbId": 416153, + "title": "Arctic Dogs", + "requesterKey": "shared", + "requesterName": "Shared", + "actorKey": "justin", + "actorName": "Justin", + "stage": "Ready", + "poster": "movie-arctic-dogs-2019-poster.jpg", + "ratings": [ + { + "mark": "rotten", + "score": 13, + "percentage": true + }, + { + "mark": "upright", + "score": 60, + "percentage": true + }, + { + "mark": "imdb", + "score": 4.9, + "percentage": false + } + ] + }, + { + "id": "x7", + "mediaType": "movie", + "tmdbId": 76203, + "title": "12 Years a Slave", + "requesterKey": "justin", + "requesterName": "Justin", + "actorKey": "shared", + "actorName": "Shared", + "stage": "Waiting", + "poster": "movie-12-years-a-slave-2013-poster.jpg", + "ratings": [ + { + "mark": "fresh", + "score": 95, + "percentage": true + }, + { + "mark": "upright", + "score": 90, + "percentage": true + }, + { + "mark": "imdb", + "score": 8.1, + "percentage": false + } + ] + }, + { + "id": "x8", + "mediaType": "movie", + "tmdbId": 14069, + "title": "The Girl Who Leapt Through Time", + "requesterKey": "mindel", + "requesterName": "Mindel", + "actorKey": "justin", + "actorName": "Justin", + "stage": "Waiting", + "poster": "movie-the-girl-who-leapt-through-time-2006-poster.jpg", + "ratings": [ + { + "mark": "fresh", + "score": 87, + "percentage": true + }, + { + "mark": "upright", + "score": 90, + "percentage": true + }, + { + "mark": "imdb", + "score": 7.6, + "percentage": false + } + ] + } + ] +} diff --git a/wolfskin-design/src/test/resources/wolfskin-requests/movie-12-years-a-slave-2013-poster.jpg b/wolfskin-design/src/test/resources/wolfskin-requests/movie-12-years-a-slave-2013-poster.jpg new file mode 100644 index 00000000..ff1f55e6 Binary files /dev/null and b/wolfskin-design/src/test/resources/wolfskin-requests/movie-12-years-a-slave-2013-poster.jpg differ diff --git a/wolfskin-design/src/test/resources/wolfskin-requests/movie-arctic-dogs-2019-poster.jpg b/wolfskin-design/src/test/resources/wolfskin-requests/movie-arctic-dogs-2019-poster.jpg new file mode 100644 index 00000000..07552443 Binary files /dev/null and b/wolfskin-design/src/test/resources/wolfskin-requests/movie-arctic-dogs-2019-poster.jpg differ diff --git a/wolfskin-design/src/test/resources/wolfskin-requests/movie-avengers-age-of-ultron-2015-poster.jpg b/wolfskin-design/src/test/resources/wolfskin-requests/movie-avengers-age-of-ultron-2015-poster.jpg new file mode 100644 index 00000000..300840ff Binary files /dev/null and b/wolfskin-design/src/test/resources/wolfskin-requests/movie-avengers-age-of-ultron-2015-poster.jpg differ diff --git a/wolfskin-design/src/test/resources/wolfskin-requests/movie-awake-2007-poster.jpg b/wolfskin-design/src/test/resources/wolfskin-requests/movie-awake-2007-poster.jpg new file mode 100644 index 00000000..32595897 Binary files /dev/null and b/wolfskin-design/src/test/resources/wolfskin-requests/movie-awake-2007-poster.jpg differ diff --git a/wolfskin-design/src/test/resources/wolfskin-requests/movie-die-my-love-2025-poster.jpg b/wolfskin-design/src/test/resources/wolfskin-requests/movie-die-my-love-2025-poster.jpg new file mode 100644 index 00000000..f6eca818 Binary files /dev/null and b/wolfskin-design/src/test/resources/wolfskin-requests/movie-die-my-love-2025-poster.jpg differ diff --git a/wolfskin-design/src/test/resources/wolfskin-requests/movie-the-girl-who-leapt-through-time-2006-poster.jpg b/wolfskin-design/src/test/resources/wolfskin-requests/movie-the-girl-who-leapt-through-time-2006-poster.jpg new file mode 100644 index 00000000..ded6cd2a Binary files /dev/null and b/wolfskin-design/src/test/resources/wolfskin-requests/movie-the-girl-who-leapt-through-time-2006-poster.jpg differ diff --git a/wolfskin-design/src/test/resources/wolfskin-requests/tv-anne-with-an-e-2017-poster.jpg b/wolfskin-design/src/test/resources/wolfskin-requests/tv-anne-with-an-e-2017-poster.jpg new file mode 100644 index 00000000..55d443b4 Binary files /dev/null and b/wolfskin-design/src/test/resources/wolfskin-requests/tv-anne-with-an-e-2017-poster.jpg differ diff --git a/wolfskin-design/src/test/resources/wolfskin-requests/tv-the-abbott-and-costello-show-1952-poster.jpg b/wolfskin-design/src/test/resources/wolfskin-requests/tv-the-abbott-and-costello-show-1952-poster.jpg new file mode 100644 index 00000000..7d6768ea Binary files /dev/null and b/wolfskin-design/src/test/resources/wolfskin-requests/tv-the-abbott-and-costello-show-1952-poster.jpg differ diff --git a/wolfskin-design/tokens.css b/wolfskin-design/tokens.css index e5ce8e97..ecea63e1 100644 --- a/wolfskin-design/tokens.css +++ b/wolfskin-design/tokens.css @@ -127,6 +127,46 @@ --motion-scroll: 180ms; --motion-ratingDwell: 350ms; --motion-toast: 3200ms; + --requests-sheetPaddingY: 80.0px; + --requests-sheetPaddingX: 64.0px; + --requests-sheetGap: 18.0px; + --requests-sheetTitleLineHeight: 56.0px; + --requests-sheetTitleBaseline: 44.0px; + --requests-titleLineHeight: 39.0px; + --requests-titleBaseline: 31.0px; + --requests-factsLineHeight: 31.2px; + --requests-factsBaseline: 24.0px; + --requests-stageLineHeight: 39.6px; + --requests-stageLabelLineHeight: 33.8px; + --requests-stageBaseline: 24.0px; + --requests-emptyMargin: 20.0px; + --requests-rowPaddingY: 14.0px; + --requests-rowPaddingX: 18.0px; + --requests-rowGap: 24.0px; + --requests-listGap: 6.0px; + --requests-stageMargin: 4.0px; + --requests-stageGlyph: 28.0px; + --requests-stageGap: 10.0px; + --requests-readinessGapX: 22.0px; + --requests-readinessGapY: 10.0px; + --requests-readinessMargin: 8.0px; + --requests-readinessGlyph: 26.0px; + --requests-chevron: 30.0px; + --requests-receiptPaddingY: 28.0px; + --requests-receiptPaddingX: 30.0px; + --requests-receiptGap: 10.0px; + --requests-receiptGlyph: 36.0px; + --requests-buttonMargin: 12.0px; + --requests-skeletonTitleWidth: 360.0px; + --requests-skeletonTitleHeight: 34.0px; + --requests-skeletonSubtitleWidth: 240.0px; + --requests-skeletonSubtitleHeight: 26.0px; + --requests-skeletonGap: 12.0px; + --requests-ratingsGap: 20.0px; + --requests-ratingsEntryGap: 8.0px; + --requests-shadowBlur: 80.0px; + --requests-shadowOffset: -30.0px; + --requests-shadowColor: #00000080; --shell-navLabelFade: 200ms; --shell-zero: 0.0px; --shell-stageWidth: 1920.0px; @@ -184,7 +224,6 @@ --library-captionWidth: 640.0px; --library-captionFactsGap: 18.0px; --library-badgeIcon: 28.0px; - --library-badgeBorder: 3.0px; --library-plateTop: 14.0px; --library-plateSide: 16.0px; --library-plateBottom: 46.0px; @@ -205,8 +244,20 @@ --library-tailIcon: 44.0px; --library-tailPadding: 20.0px; --library-tailGap: 10.0px; + --library-badgeRing: 3.0px; + --library-seeAllGap: 8.0px; + --library-tailDescription: 24.0px; + --library-headerSubtitle: 30.0px; --library-fallbackPadding: 22.0px; --library-fallbackIcon: 44.0px; + --lift-cardBlur: 50.0px; + --lift-cardY: 18.0px; + --lift-buttonBlur: 40.0px; + --lift-buttonY: 12.0px; + --lift-heroBlur: 80.0px; + --lift-heroY: 30.0px; + --lift-sheetBlur: 80.0px; + --lift-sheetX: -30.0px; --title-lineGap: 22.0px; --title-lineLift: -4.0px; --title-overviewGap: 34.0px; @@ -316,4 +367,58 @@ --savedIndicator-gap: 10.0px; --savedIndicator-icon: 28.0px; --savedIndicator-border: 2.0px; + --discover-sheetPaddingX: 64.0px; + --discover-sheetPaddingY: 80.0px; + --discover-sheetGap: 18.0px; + --discover-sheetSubGap: 14.0px; + --discover-sheetRowPadding: 26.0px; + --discover-sheetAvatar: 60.0px; + --discover-sheetRadio: 40.0px; + --discover-sheetRadioDot: 16.0px; + --discover-sheetPersonGap: 24.0px; + --discover-sheetButtonTop: 12.0px; + --discover-chipTop: 16.0px; + --discover-chipHeight: 68.0px; + --discover-chipPadding: 28.0px; + --discover-chipGap: 16.0px; + --discover-heroTitleGap: 16.0px; + --discover-heroKickerGap: 10.0px; + --discover-heroMetaGap: 14.0px; + --discover-heroOverviewGap: 30.0px; + --discover-heroFactsGap: 22.0px; + --discover-heroFactsHeight: 38.0px; + --discover-heroSheetRight: 180.0px; + --discover-heroSheetTop: 140.0px; + --discover-heroSheetWidth: 360.0px; + --discover-heroSheetHeight: 540.0px; + --discover-dotSize: 14.0px; + --discover-dotActive: 44.0px; + --discover-dotGap: 14.0px; + --discover-dotMargin: 18.0px; + --discover-dotChevron: 40.0px; + --discover-stateTop: 20.0px; + --discover-statePaddingX: 40.0px; + --discover-statePaddingY: 34.0px; + --discover-stateTextGap: 24.0px; + --discover-noteTop: 14.0px; + --discover-headerTop: 150.0px; + --discover-headerGap: 24.0px; + --discover-entryPaddingX: 22.0px; + --discover-entryPaddingY: 20.0px; + --discover-entryText: 30.0px; + --discover-sheetRadioRing: 3.0px; + --discover-skeletonNameWidth: 260.0px; + --discover-skeletonNameHeight: 34.0px; + --discover-sheetSubTop: -8.0px; + --discover-sheetRatingTop: -10.0px; + --discover-sheetRatingBottom: 6.0px; + --discover-sheetLabelTop: 8.0px; + --discover-noteIcon: 30.0px; + --discover-noteGap: 16.0px; + --discover-frameBottomMin: 80.0px; + --discover-anchorFitTop: 700.0px; + --discover-anchorBottomClearance: 40.0px; + --discover-stageGap: 10.0px; + --discover-ambientInset: 80.0px; + --discover-ambientBlur: 60.0px; } diff --git a/wolfskin-design/tokens.sha256 b/wolfskin-design/tokens.sha256 index 9dbe745c..c3b6aade 100644 --- a/wolfskin-design/tokens.sha256 +++ b/wolfskin-design/tokens.sha256 @@ -1,6 +1,7 @@ -811d6397d71d7771cad62e6745e8d85acd22a60878a4d2719792831de58d2fdb generate_tokens.py -b36140ec4745837b9e912deb6eba5d9ce94871a18512d7fdaddb37b7dd79ce25 shell-tokens.json -c56109dc854d03d74baf0384e09451461ad50ca266b1cc4d598b310475927d11 src/main/java/org/justinvisser/wolfskin/design/WolfskinTokens.kt +1f22175c1ce3987c4efa14d573601641a61e1a915e2c464c943b6c400356adb4 discover-tokens.json +407ec3f8cd78f4a59d037879ef066921fa0cf1c4ee81c14c8c2913d815c02305 generate_tokens.py +276e8972052059732d19f2f19af6f10b4220b2f87237890a8f306f93f674a7a7 shell-tokens.json +4c429a7d5aac5c62f558d7091776b2fe94e3ad9ddc40df74798962543728574e src/main/java/org/justinvisser/wolfskin/design/WolfskinTokens.kt bc82f477611669fbc6dd0d3e6559b1e05b0ad280531a883102803ffb0ebcdecb title-tokens.json -ae9ca44fc6e1d3bd4ecce133ba56f2d1bce267f1754b30028a6cc0124539a6f7 tokens.css +a13b3dc369e8d5b1ca1d6de4f49daa8eafbeb822bf4984290f66e7c0e692d911 tokens.css 5b939f1f3eb90b428008fda75720f6e3a4e844c486668931db3d52bffb987b53 tokens.json