diff --git a/app/src/debug/java/org/justinvisser/wolfskin/tv/DebugHome.kt b/app/src/debug/java/org/justinvisser/wolfskin/tv/DebugHome.kt new file mode 100644 index 00000000..691f67c4 --- /dev/null +++ b/app/src/debug/java/org/justinvisser/wolfskin/tv/DebugHome.kt @@ -0,0 +1,8 @@ +package org.justinvisser.wolfskin.tv + +import android.content.Context +import org.justinvisser.wolfskin.design.HomeFixture +import org.justinvisser.wolfskin.design.HomeSnapshot + +/** Only debug builds may read the locked prototype household projection. */ +fun debugHomeSnapshot(context: Context, person: String): HomeSnapshot? = HomeFixture(context).snapshot(person) diff --git a/app/src/release/java/org/justinvisser/wolfskin/tv/DebugHome.kt b/app/src/release/java/org/justinvisser/wolfskin/tv/DebugHome.kt new file mode 100644 index 00000000..b49e7828 --- /dev/null +++ b/app/src/release/java/org/justinvisser/wolfskin/tv/DebugHome.kt @@ -0,0 +1,8 @@ +package org.justinvisser.wolfskin.tv + +import android.content.Context +import org.justinvisser.wolfskin.design.HomeSnapshot + +/** Release Home always reads the selected Jellyfin identity, never prototype data. */ +@Suppress("UNUSED_PARAMETER") +fun debugHomeSnapshot(context: Context, person: String): HomeSnapshot? = null diff --git a/app/src/test/java/org/justinvisser/wolfskin/tv/HomeLibraryCacheTest.kt b/app/src/test/java/org/justinvisser/wolfskin/tv/HomeLibraryCacheTest.kt new file mode 100644 index 00000000..49fc269f --- /dev/null +++ b/app/src/test/java/org/justinvisser/wolfskin/tv/HomeLibraryCacheTest.kt @@ -0,0 +1,29 @@ +package org.justinvisser.wolfskin.tv + +import android.app.Application +import androidx.test.core.app.ApplicationProvider +import org.junit.Assert.* +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config +import org.justinvisser.wolfskin.design.HomeLibrary +import org.justinvisser.wolfskin.design.HomeTitle +import org.justinvisser.wolfskin.tv.read.HomeLibraryCache + +/** A reopened Home cache preserves playable identity and episode/resume state without network reads. */ +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [34], application = Application::class) +class HomeLibraryCacheTest { + @Test fun savedHomeReopensWithEpisodeMetadataAndFullShelfCount() { + val context = ApplicationProvider.getApplicationContext() + val episode = HomeTitle("episode-id", "The next episode", "episode", year = 2025, runtimeSeconds = 1800, overview = "Overview", poster = "poster", backdrop = "backdrop", seriesTitle = "A series", season = 2, episode = 3, resumeSeconds = 125.5, addedAt = "2026-11-05", genres = listOf("Drama"), held = true, tmdbId = 42) + val film = HomeTitle("film-id", "A film", "movie", year = 2024, runtimeSeconds = 5400, played = true, held = true, tmdbId = 43) + val library = HomeLibrary(listOf(episode), listOf(episode.copy(id = "next", resumeSeconds = 0.0)), listOf(film), mapOf("movies" to listOf(film)), mapOf("movies" to 84)) + HomeLibraryCache(context).write("server:person", library, 12345L) + val saved = HomeLibraryCache(context).read("server:person")!! + assertEquals(library, saved.first) + assertEquals(12345L, saved.second) + assertNull(HomeLibraryCache(context).read("server:someone-else")) + } +} diff --git a/app/src/test/java/org/justinvisser/wolfskin/tv/HomeSeeAllNavigationTest.kt b/app/src/test/java/org/justinvisser/wolfskin/tv/HomeSeeAllNavigationTest.kt new file mode 100644 index 00000000..47f843b2 --- /dev/null +++ b/app/src/test/java/org/justinvisser/wolfskin/tv/HomeSeeAllNavigationTest.kt @@ -0,0 +1,130 @@ +package org.justinvisser.wolfskin.tv + +import android.app.Application +import androidx.activity.ComponentActivity +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createAndroidComposeRule +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 com.github.damontecres.wholphin.data.model.JellyfinServer +import com.github.damontecres.wholphin.data.model.JellyfinUser +import com.github.damontecres.wholphin.services.NavigationManager +import com.github.damontecres.wholphin.ui.nav.Destination +import io.mockk.mockk +import java.util.UUID +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Assert.assertSame +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.justinvisser.wolfskin.design.* +import org.justinvisser.wolfskin.tv.read.LibraryReadFacade +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +/** Home opens the complete shared grid and Back restores its original continuation card. */ +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [34], application = Application::class, qualifiers = "w960dp-h540dp-xhdpi") +class HomeSeeAllNavigationTest { + @get:Rule val rule = createAndroidComposeRule() + + @Test fun personalMovieContinuationReachesTheEighteenthTitleAndReturnsToHome() = continuation("pk-m", 5) { grid -> + assertEquals(18, grid.items.size) + repeat(2) { press(Key.DirectionDown) } + repeat(5) { press(Key.DirectionRight) } + rule.onNodeWithText("Bleach the Movie: Fade to Black").assertIsDisplayed() + assertEquals("movie-bleach-the-movie-fade-to-black-2008", grid.focusedTitle?.id) + } + + @Test fun horizontallyScrolledContinuationRestoresTheExactHomeViewport() = continuation("trending", 20) { grid -> + press(Key.DirectionDown) + assertEquals(6, grid.index) + rule.onNodeWithTag("library-grid").assertIsDisplayed() + } + + private fun continuation(rowId: String, expectedColumn: Int, exerciseGrid: (LibraryGridState) -> Unit) { + rule.mainClock.autoAdvance = false + val snapshot = HomeFixture(rule.activity).snapshot("justin") + val row = snapshot.rows.first { it.id == rowId } + val cardTag = "home-card:$rowId:$expectedColumn" + val navigation = NavigationManager() + val lists = WolfskinSeeAllLists() + val shell = ShellState(listOf(ShellPerson("justin", "Justin", "J", true)), "justin") { + if (it == ShellAction.Pop) navigation.goBack() + }.apply { + pickerVisible = false + view.row = snapshot.rows.indexOf(row) + view.home.focusedKey = row.id + view.cards[row.id] = row.cards.size + } + val server = JellyfinServer(UUID.fromString("00000000-0000-0000-0000-000000000001"), "Fixture", "https://example.invalid", null) + val user = JellyfinUser(id = UUID.fromString("00000000-0000-0000-0000-000000000002"), name = "Justin", serverId = server.id, accessToken = null) + val model = WolfskinHomeViewModel(rule.activity, mockk(), mockk(), navigation, lists) + val facade = mockk() + rule.setContent { + val destination = navigation.backStack.last() + val route = (destination as? Destination.Wolfskin)?.route ?: "home" + LaunchedEffect(destination) { shell.enterDestination(route) } + WolfskinTheme { ShellSurface(shell) { modifier -> + if (destination is Destination.Home) HomeScreen(shell, snapshot, modifier) { model.act(it, shell, server, user) } + else WolfskinLibraryContent(rule.activity, route, shell, user, "fixture", facade, navigation, lists, modifier) + } } + } + settle() + rule.onNodeWithTag(cardTag).assertIsDisplayed() + val home = shell.view + val track = home.home.tracks.getValue(rowId) + val verticalIndex = home.listState.firstVisibleItemIndex + val verticalOffset = home.listState.firstVisibleItemScrollOffset + val rowOffset = home.listState.layoutInfo.visibleItemsInfo.first { it.key == rowId }.offset + val horizontalIndex = track.firstVisibleItemIndex + val horizontalOffset = track.firstVisibleItemScrollOffset + press(Key.DirectionCenter) + assertEquals("seeall/justin/$rowId", (navigation.backStack.last() as Destination.Wolfskin).route) + val grid = requireNotNull(lists.get("justin/$rowId")) + try { + rule.onNodeWithTag("library-grid").assertIsDisplayed() + exerciseGrid(grid) + press(Key.Back) + rule.waitForIdle() + assertTrue(navigation.backStack.last() is Destination.Home) + assertEquals(row.id, snapshot.rows[shell.view.row].id) + assertEquals(expectedColumn, shell.view.cards[row.id]) + assertSame("Home retains its original route memory", home, shell.view) + assertSame("Home retains its original horizontal state", track, home.home.tracks.getValue(rowId)) + assertEquals("Vertical first item", verticalIndex, home.listState.firstVisibleItemIndex) + assertEquals("Vertical pixel offset", verticalOffset, home.listState.firstVisibleItemScrollOffset) + assertEquals("Selected row pixel offset", rowOffset, home.listState.layoutInfo.visibleItemsInfo.first { it.key == rowId }.offset) + assertEquals("Horizontal first item", horizontalIndex, track.firstVisibleItemIndex) + assertEquals("Horizontal pixel offset", horizontalOffset, track.firstVisibleItemScrollOffset) + rule.onNodeWithTag(cardTag).assertIsDisplayed() + } catch (failure: AssertionError) { + throw AssertionError( + "Expected $rowId:$expectedColumn vertical=$verticalIndex/$verticalOffset row-offset=$rowOffset horizontal=$horizontalIndex/$horizontalOffset; " + + "actual route=${shell.route} row=${home.row} card=${home.cards[rowId]} nav=${shell.navFocused} handler=${shell.contentFocus?.javaClass?.simpleName} grid=${grid.index} " + + "vertical=${home.listState.firstVisibleItemIndex}/${home.listState.firstVisibleItemScrollOffset} horizontal=${track.firstVisibleItemIndex}/${track.firstVisibleItemScrollOffset} " + + "visible-rows=${home.listState.layoutInfo.visibleItemsInfo.map { it.key to it.offset }}", + failure, + ) + } + } + + private fun press(key: Key) { + rule.onNodeWithTag("wolfskin-shell").performKeyInput { pressKey(key) } + settle() + } + + private fun settle() { + repeat(3) { + rule.mainClock.advanceTimeByFrame() + rule.waitForIdle() + } + rule.mainClock.advanceTimeBy(1_000) + rule.waitForIdle() + } +} diff --git a/app/src/test/java/org/justinvisser/wolfskin/tv/UpstreamContentFocusTest.kt b/app/src/test/java/org/justinvisser/wolfskin/tv/UpstreamContentFocusTest.kt index 78690396..d9e88778 100644 --- a/app/src/test/java/org/justinvisser/wolfskin/tv/UpstreamContentFocusTest.kt +++ b/app/src/test/java/org/justinvisser/wolfskin/tv/UpstreamContentFocusTest.kt @@ -18,11 +18,13 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toPixelMap import androidx.compose.ui.input.key.Key import androidx.compose.ui.platform.testTag +import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.assertIsFocused import androidx.compose.ui.test.assertIsNotFocused import androidx.compose.ui.test.captureToImage import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.onRoot import androidx.compose.ui.test.performKeyInput import androidx.compose.ui.test.pressKey @@ -131,6 +133,7 @@ class UpstreamContentFocusTest { rule.runOnIdle { WolfskinScreenPreference.enabled = true } rule.mainClock.advanceTimeBy(1500) rule.waitForIdle() + rule.onNodeWithText("Sign in as Justin to open their Jellyfin library").assertIsDisplayed() val on = rule.onRoot().captureToImage().toPixelMap()[1700, 900] val background = org.justinvisser.wolfskin.design.WolfskinTokens.Color.bg org.junit.Assert.assertEquals("Owned background red", background.red, on.red, 1f / 255f) diff --git a/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/HomeDestination.kt b/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/HomeDestination.kt new file mode 100644 index 00000000..91b75b04 --- /dev/null +++ b/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/HomeDestination.kt @@ -0,0 +1,133 @@ +package org.justinvisser.wolfskin.tv + +import android.content.Context +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.tv.material3.Text +import com.github.damontecres.wholphin.data.model.JellyfinServer +import com.github.damontecres.wholphin.data.model.JellyfinUser +import com.github.damontecres.wholphin.services.NavigationManager +import com.github.damontecres.wholphin.ui.nav.Destination +import dagger.hilt.android.lifecycle.HiltViewModel +import dagger.hilt.android.qualifiers.ApplicationContext +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale +import javax.inject.Inject +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.justinvisser.wolfskin.design.* +import org.justinvisser.wolfskin.tv.read.HomeLibraryCache +import org.justinvisser.wolfskin.tv.read.HomeReadFacade + +/** The Home entry owns its source reads and upstream navigation, never the design module. */ +@HiltViewModel +class WolfskinHomeViewModel @Inject constructor( + @param:ApplicationContext private val context: Context, + private val home: HomeReadFacade, + private val cache: HomeLibraryCache, + private val navigation: NavigationManager, + private val seeAll: WolfskinSeeAllLists, +) : ViewModel() { + var snapshot by mutableStateOf(HomeSnapshot(buildHomeRows(HomeLibrary(), emptyList(), loading = true), HomeAvailability.Loading)) + private set + var toast by mutableStateOf(null) + private set + + suspend fun load(server: JellyfinServer, user: JellyfinUser, person: String, retry: Boolean = false) { + withContext(Dispatchers.IO) { debugHomeSnapshot(context, person) }?.let { snapshot = it; return } + val continuation = listOf(homeDiscoverState(HomeAvailability.SignedOut)) + if (person != user.name.orEmpty().lowercase()) { + snapshot = HomeSnapshot(buildHomeRows(HomeLibrary(), continuation, libraryAvailable = false), jellyfinAvailable = false) + return + } + val scope = "${server.id}:${user.id}" + val saved = withContext(Dispatchers.IO) { cache.read(scope) } + if (saved != null) snapshot = HomeSnapshot(buildHomeRows(saved.first, continuation)) + try { + val library = withContext(Dispatchers.IO) { home.read(user.id) } + val savedAt = System.currentTimeMillis() + withContext(Dispatchers.IO) { cache.write(scope, library, savedAt) } + snapshot = HomeSnapshot(buildHomeRows(library, continuation)) + } catch (exception: Exception) { + if (exception is CancellationException) throw exception + snapshot = if (saved != null) HomeSnapshot(buildHomeRows(saved.first, continuation), jellyfinAvailable = false, savedAt = SimpleDateFormat("HH:mm", Locale.getDefault()).format(Date(saved.second))) + else HomeSnapshot(buildHomeRows(HomeLibrary(), continuation, libraryAvailable = false), jellyfinAvailable = false) + if (retry) showToast("Still can’t reach it. The last saved view stays on screen.") + } + } + + fun act(action: HomeAction, shell: ShellState, server: JellyfinServer, user: JellyfinUser) { + when (action) { + HomeAction.Discover -> openRoot(shell, "discover") + HomeAction.Retry -> viewModelScope.launch { load(server, user, shell.person.key, retry = true) } + is HomeAction.SeeAll -> if (action.row.target in shell.tabs) openRoot(shell, action.row.target!!) else seeAll.open(shell.person.key, action.row.id, action.row.title, action.row.allCards.map { it.libraryTitle() }, navigation) + is HomeAction.Details -> navigation.navigateTo(Destination.Wolfskin("title:${action.title.type}:${action.title.tmdbId ?: action.title.id}")) + is HomeAction.Play -> viewModelScope.launch { + if (!snapshot.jellyfinAvailable || shell.person.key != user.name.orEmpty().lowercase()) { + showToast("Can’t reach Jellyfin to play. Check the network and try again.") + return@launch + } + try { + val id = withContext(Dispatchers.IO) { home.playableId(user.id, action.title) } + if (id == null) showToast("Can’t reach Jellyfin to play. Check the network and try again.") + else navigation.navigateTo(Destination.Playback(id, if (action.resume) (action.title.resumeSeconds * 1000).toLong() else 0L)) + } catch (exception: Exception) { + if (exception is CancellationException) throw exception + showToast("Can’t reach Jellyfin to play. Check the network and try again.") + } + } + } + } + + private fun openRoot(shell: ShellState, route: String) { + shell.enterDestination(route) + shell.focusNav(shell.tabs.indexOf(route)) + shell.key(ShellKey.Ok) + } + + private fun showToast(message: String) { + toast = message + viewModelScope.launch { delay(WolfskinTokens.Motion.toast.toLong()); if (toast == message) toast = null } + } +} + +/** Complete catalogue projections feed the shared See all grid without another source read. */ +internal fun HomeTitle.libraryTitle(): LibraryTitle = LibraryTitle( + id = id, title = title, type = type, year = year, runtimeSeconds = runtimeSeconds?.toLong(), seasons = seasons, + poster = poster, played = played, progress = runtimeSeconds?.let { if (it > 0) (resumeSeconds / it).toFloat() else 0f } ?: 0f, + addedAt = addedAt, tmdbId = tmdbId?.toString(), + availability = if (held) "In your library" else otherOwner?.let { "In $it’s library" }.orEmpty(), + badge = if (held) "held" else null, + ratings = ratings.map { rating -> + when (rating.mark) { + "fresh" -> LibraryRating("rtCritics", rating.score, "Fresh") + "rotten" -> LibraryRating("rtCritics", rating.score, "Rotten") + "tomato" -> LibraryRating("rtCritics", rating.score) + "upright" -> LibraryRating("rtAudience", rating.score, "Upright") + "spilled" -> LibraryRating("rtAudience", rating.score, "Spilled") + else -> LibraryRating(rating.mark, rating.score) + } + }, +) + +/** The shell keeps its focus memory while playback or later-slice screens are pushed above Home. */ +@Composable +fun HomeDestination(shell: ShellState, server: JellyfinServer, user: JellyfinUser, modifier: Modifier = Modifier, model: WolfskinHomeViewModel = hiltViewModel()) { + LaunchedEffect(shell.person.key, server.id, user.id) { model.load(server, user, shell.person.key) } + Box(modifier) { + HomeScreen(shell, model.snapshot, Modifier.fillMaxSize()) { model.act(it, shell, server, user) } + model.toast?.let { message -> + Text(message, color = WolfskinTokens.Color.fg, style = WolfskinTokens.Type.body, modifier = Modifier.align(Alignment.TopEnd).padding(top = WolfskinTokens.Home.toastTop, end = WolfskinTokens.Layout.safeSide).widthIn(max = WolfskinTokens.Home.toastWidth).background(WolfskinTokens.Color.surface2).padding(WolfskinTokens.Home.toastPadding)) + } + } +} 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 f10d8cad..533cdc4e 100644 --- a/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/WolfskinApp.kt +++ b/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/WolfskinApp.kt @@ -206,8 +206,9 @@ fun WolfskinApp( 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 || destination is Destination.Home) && 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) + destination is Destination.Home -> HomeDestination(state, server, user, contentModifier) route in LIBRARY_DESTINATIONS || route.startsWith("seeall/") || route.startsWith("library-title/") -> WolfskinLibraryContent(androidx.compose.ui.platform.LocalContext.current, route, state, user, "${server.id}:${user.id}", viewModel.libraries, navigationManager, viewModel.seeAll, contentModifier) destination is Destination.Wolfskin -> DestinationContent(destination, preferences, viewModel::clearBackdrop, contentModifier) diff --git a/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/WolfskinLibrary.kt b/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/WolfskinLibrary.kt index 3bac263f..7d75c019 100644 --- a/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/WolfskinLibrary.kt +++ b/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/WolfskinLibrary.kt @@ -50,7 +50,7 @@ fun WolfskinLibraryContent( seeAll: WolfskinSeeAllLists, modifier: Modifier = Modifier, ) { - val view = shell.view + val view = shell.rememberedView(route) val fixture by produceState(null, context) { if (BuildConfig.DEBUG) value = withContext(Dispatchers.IO) { context.assets.open("wolfskin-library.json").bufferedReader().use { LibraryFixture(it.readText(), "file:///android_asset/library/") } @@ -89,7 +89,7 @@ fun WolfskinLibraryContent( if (wasLoading && shell.navFocused && shell.navIndex == shell.currentTab) shell.navFocused = false } } - LibraryScreen(shell, grid, modifier, ratingsSignedIn = fixture != null) + LibraryScreen(shell, grid, modifier, ratingsSignedIn = fixture != null, view = view) } val LIBRARY_DESTINATIONS = setOf("movies", "shows", "anime") diff --git a/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/read/HomeLibraryCache.kt b/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/read/HomeLibraryCache.kt new file mode 100644 index 00000000..6e69ac54 --- /dev/null +++ b/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/read/HomeLibraryCache.kt @@ -0,0 +1,66 @@ +package org.justinvisser.wolfskin.tv.read + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import java.io.File +import javax.inject.Inject +import javax.inject.Singleton +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject +import org.justinvisser.wolfskin.conductarr.CacheEntry +import org.justinvisser.wolfskin.conductarr.FileCacheStore +import org.justinvisser.wolfskin.design.HomeLibrary +import org.justinvisser.wolfskin.design.HomeTitle + +/** Saved Jellyfin projections reuse the app-private atomic cache and are partitioned by server/user. */ +@Singleton +class HomeLibraryCache @Inject constructor(@param:ApplicationContext context: Context) { + private val store = FileCacheStore(File(context.filesDir, "wolfskin-home")) + + fun read(scope: String): Pair? { + val entry = store.read(scope, KEY) ?: return null + return try { + val json = JSONObject(entry.body) + val destinations = json.getJSONObject("destinations") + val totals = json.getJSONObject("totals") + HomeLibrary( + decode(json.getJSONArray("resume")), decode(json.getJSONArray("nextUp")), decode(json.getJSONArray("newest")), + destinations.keys().asSequence().associateWith { decode(destinations.getJSONArray(it)) }, + totals.keys().asSequence().associateWith { totals.getInt(it) }, + ) to entry.savedAtMillis + } catch (_: JSONException) { store.remove(scope, KEY); null } + } + + fun write(scope: String, library: HomeLibrary, savedAtMillis: Long) { + val json = JSONObject().put("resume", encode(library.resume)).put("nextUp", encode(library.nextUp)).put("newest", encode(library.newest)) + .put("destinations", JSONObject().apply { library.destinations.forEach { (key, titles) -> put(key, encode(titles)) } }) + .put("totals", JSONObject(library.destinationTotals)) + store.write(scope, KEY, CacheEntry(json.toString(), null, savedAtMillis, savedAtMillis, group = "home")) + } + + private fun encode(titles: List): JSONArray = JSONArray().apply { + titles.forEach { title -> put(JSONObject().apply { + put("id", title.id); put("title", title.title); put("type", title.type); put("year", title.year) + put("runtime", title.runtimeSeconds); put("seasons", title.seasons); put("overview", title.overview) + put("poster", title.poster); put("backdrop", title.backdrop); put("posterOnly", title.posterOnly) + put("seriesTitle", title.seriesTitle); put("season", title.season); put("episode", title.episode) + put("resume", title.resumeSeconds); put("played", title.played); put("addedAt", title.addedAt) + put("genres", JSONArray(title.genres)); put("tmdb", title.tmdbId) + }) } + } + + private fun decode(items: JSONArray): List = List(items.length()) { index -> + val item = items.getJSONObject(index) + fun string(key: String): String? = if (item.isNull(key)) null else item.getString(key) + fun number(key: String): Int? = if (item.isNull(key)) null else item.getInt(key) + val genres = item.getJSONArray("genres") + HomeTitle( + item.getString("id"), item.getString("title"), item.getString("type"), number("year"), number("runtime"), number("seasons"), item.getString("overview"), + string("poster"), string("backdrop"), item.getBoolean("posterOnly"), string("seriesTitle"), number("season"), number("episode"), item.getDouble("resume"), item.getBoolean("played"), item.getString("addedAt"), + List(genres.length()) { genres.getString(it) }, held = true, tmdbId = number("tmdb"), + ) + } + + companion object { private const val KEY = "home:jellyfin" } +} diff --git a/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/read/HomeReadFacade.kt b/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/read/HomeReadFacade.kt new file mode 100644 index 00000000..1696930a --- /dev/null +++ b/app/src/wolfskin/java/org/justinvisser/wolfskin/tv/read/HomeReadFacade.kt @@ -0,0 +1,80 @@ +package org.justinvisser.wolfskin.tv.read + +import com.github.damontecres.wholphin.data.model.BaseItem +import com.github.damontecres.wholphin.services.ImageUrlService +import java.util.UUID +import javax.inject.Inject +import org.jellyfin.sdk.api.client.ApiClient +import org.jellyfin.sdk.api.client.extensions.itemsApi +import org.jellyfin.sdk.api.client.extensions.tvShowsApi +import org.jellyfin.sdk.api.client.extensions.userViewsApi +import org.jellyfin.sdk.api.client.extensions.userLibraryApi +import org.jellyfin.sdk.model.api.BaseItemDto +import org.jellyfin.sdk.model.api.BaseItemKind +import org.jellyfin.sdk.model.api.CollectionType +import org.jellyfin.sdk.model.api.ImageType +import org.jellyfin.sdk.model.api.ItemFields +import org.jellyfin.sdk.model.api.ItemSortBy +import org.jellyfin.sdk.model.api.MediaType +import org.jellyfin.sdk.model.api.SortOrder +import org.justinvisser.wolfskin.design.HomeLibrary +import org.justinvisser.wolfskin.design.HomeTitle + +/** All Home SDK calls and image URLs stay behind this facade, with their own required field list. */ +class HomeReadFacade @Inject constructor(private val api: ApiClient, private val images: ImageUrlService) { + suspend fun read(userId: UUID): HomeLibrary { + val resume = api.itemsApi.getResumeItems(userId = userId, fields = FIELDS, mediaTypes = listOf(MediaType.VIDEO), limit = 20).content.items.map(::title) + val next = api.tvShowsApi.getNextUp(userId = userId, fields = FIELDS, limit = 20, enableUserData = true).content.items.map(::title) + val libraries = api.userViewsApi.getUserViews(userId = userId).content.items + val destinations = linkedMapOf>() + val newest = mutableListOf() + val totals = linkedMapOf() + for (library in libraries) { + val key = when { + "anime" in library.name.orEmpty().lowercase() -> "anime" + library.collectionType == CollectionType.MOVIES -> "movies" + library.collectionType == CollectionType.TVSHOWS -> "shows" + else -> continue + } + val response = api.itemsApi.getItems(userId = userId, parentId = library.id, recursive = true, includeItemTypes = listOf(BaseItemKind.MOVIE, BaseItemKind.SERIES), fields = FIELDS, sortBy = listOf(ItemSortBy.IS_PLAYED, ItemSortBy.DATE_CREATED), sortOrder = listOf(SortOrder.ASCENDING, SortOrder.DESCENDING), limit = 20).content + destinations.getOrPut(key) { mutableListOf() }.addAll(response.items.map(::title)) + totals[key] = (totals[key] ?: 0) + response.totalRecordCount + newest.addAll(api.itemsApi.getItems(userId = userId, parentId = library.id, recursive = true, includeItemTypes = listOf(BaseItemKind.MOVIE, BaseItemKind.SERIES), fields = FIELDS, sortBy = listOf(ItemSortBy.DATE_CREATED), sortOrder = listOf(SortOrder.DESCENDING), limit = 12).content.items.map(::title)) + } + return HomeLibrary(resume, next, newest.sortedByDescending { it.addedAt }.take(12), destinations, totals) + } + + suspend fun playableId(userId: UUID, title: HomeTitle): UUID? { + title.id.let { runCatching { UUID.fromString(it) }.getOrNull() }?.let { id -> + val item = api.userLibraryApi.getItem(userId = userId, itemId = id).content + if (item.type == BaseItemKind.SERIES) { + return (api.tvShowsApi.getNextUp(userId = userId, seriesId = id, limit = 1).content.items.firstOrNull() + ?: api.tvShowsApi.getEpisodes(seriesId = id, userId = userId, limit = 1).content.items.firstOrNull()) + ?.takeIf { it.type == BaseItemKind.EPISODE }?.id + } + return item.id.takeIf { item.type == BaseItemKind.MOVIE || item.type == BaseItemKind.EPISODE } + } + val kinds = if (title.isEpisode) listOf(BaseItemKind.EPISODE) else listOf(BaseItemKind.MOVIE) + return api.itemsApi.getItems(userId = userId, recursive = true, searchTerm = title.title, includeItemTypes = kinds, fields = FIELDS, limit = 20).content.items.firstOrNull { + it.name == title.title && (!title.isEpisode || (it.seriesName == title.seriesTitle && it.parentIndexNumber == title.season && it.indexNumber == title.episode)) + }?.id + } + + private fun title(item: BaseItemDto): HomeTitle { + val wrapped = BaseItem(item) + return HomeTitle( + id = item.id.toString(), title = item.name.orEmpty(), type = when (item.type) { BaseItemKind.EPISODE -> "episode"; BaseItemKind.SERIES -> "tv"; else -> "movie" }, + year = item.productionYear, runtimeSeconds = item.runTimeTicks?.div(TICKS_PER_SECOND)?.toInt(), seasons = item.childCount, + overview = item.overview.orEmpty(), poster = images.getItemImageUrl(wrapped, ImageType.PRIMARY, fillWidth = 256, fillHeight = 384), + backdrop = images.getItemImageUrl(wrapped, ImageType.BACKDROP, fillWidth = 1280, fillHeight = 720), + seriesTitle = item.seriesName, season = item.parentIndexNumber, episode = item.indexNumber, + resumeSeconds = (item.userData?.playbackPositionTicks ?: 0) / TICKS_PER_SECOND.toDouble(), played = item.userData?.played == true, + addedAt = item.dateCreated?.toString().orEmpty(), genres = item.genres.orEmpty(), held = true, tmdbId = item.providerIds?.get("Tmdb")?.toIntOrNull(), + ) + } + + companion object { + private const val TICKS_PER_SECOND = 10_000_000L + private val FIELDS = listOf(ItemFields.PROVIDER_IDS, ItemFields.OVERVIEW, ItemFields.DATE_CREATED, ItemFields.GENRES, ItemFields.CHILD_COUNT) + } +} diff --git a/docker/README.md b/docker/README.md index 1c633a7d..7bf29dcb 100644 --- a/docker/README.md +++ b/docker/README.md @@ -59,8 +59,9 @@ 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` and `discover-tokens.json` add measurements -from that same prototype. Generate the committed Kotlin, CSS and checksum manifest together: +SHA and provenance. `shell-tokens.json` adds shared shell, grid and sheet measurements; +`home-tokens.json` and `discover-tokens.json` add their screen geometry from that +same locked prototype. Generate the committed Kotlin, CSS and checksum manifest together: ```sh python3 wolfskin-design/generate_tokens.py @@ -88,8 +89,8 @@ Coming-in sheet described below. **Advanced settings → Wolfskin screens** is t 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, -then enters the nav. Turning the switch on restores the labelled placeholders -until the corresponding screen slices ship. +then enters the nav. Turning the switch on renders Wolfskin Home and Libraries. +Discover and Requests are integrated Wolfskin surfaces; title hand-offs remain labelled. Debug people, tabs and initials come from the shell-only projection of the locked prototype fixture in `app/src/debug/assets/wolfskin-shell.json`; release resources @@ -143,8 +144,10 @@ 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 -exercise the Android event adapter, including the 6/8/10/11-press shell paths. +the sampled Compose nav-spring curve to `build/wolfskin-captures/`. +Home, Library and Discover captures use `home/`, `library/` and `discover/` +subdirectories so shared spec state numbers cannot overwrite another renderer's image. +Key replays exercise the Android event adapter, including the 6/8/10/11-press shell paths. The profile hand-off capture targets the real `HomeSettingsPage`; Playback is a separate press-count path. @@ -165,6 +168,78 @@ 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. +## Wolfskin Home + +Home is one lazy frame: a non-rotating resume → Next up → newest → pick hero, +Jellyfin shelves, per-destination "Your …" shelves and the Discover continuation. +The final action opens the real merged Discover screen at its top through the +shell's root re-selection transition. Home's See all cards open `WolfskinSeeAllLists`, carrying +the complete underlying list rather than the visible shelf window. Title content +remains the later-slice target. + +All Home components and immutable screen models live in `:wolfskin-design`. +The shell's remembered view owns vertical position, each row's selected card and +actual horizontal `LazyListState`, and the bounded six-hour ratings session cache. +Composing destinations bind `rememberedView(route)` before navigation effects run, so a +new See all grid cannot borrow or clamp Home's scroller. Re-entry restores the +remembered track before the page with `scrollToItem`, preserving exact indices and +pixel offsets rather than animating back. Android focus stays with the shell; +cards use its logical focus, without requests against uncomposed lazy items. +Back preserves the viewport; returning to Home does not re-run focus-follow over +a manually scrolled saved view. The next arrow re-enters visible content when +scrolling moved focus off-screen. +Pointing changes focus without activation, cards activate on key-up, and held +OK events cannot activate twice. +Between shelves, an unvisited row selects the nearest visible card center even +when card widths differ. A visited row restores its column. Horizontal movement +keeps the track still while focus remains in its visible middle. +The initial skeleton parks focus on the nav. When the first content arrives, +an untouched fresh entry focuses hero button zero; intervening nav input prevents +that automatic transfer. Home, Libraries and Discover use the same `ShellContentFocus` +release/repeat guard and Back ladder. + +`HomeReadFacade` is the only Home code importing Jellyfin API clients. Release +variants read the selected user's Resume, Next up, newest titles and destination +shelves, with app-private last-known-good projections through the shared atomic +disk cache. A missing saved library becomes a recovery panel; a saved library +stays visible when Jellyfin is unreachable. Playback resolves an actual film or +episode and checks Jellyfin before opening upstream's player. No season, +recommendation stub or different person's session is promoted to playback. +An unresolved person switch shows the same sign-in guard for Home as for native +library content, before mounting Home's source reads. + +Debug variants additionally package the locked fixture and local artwork under +`wolfskin-design/src/debug/assets/wolfskin-home/`; release variants exclude them. +The fixture provider supplies the approved household data and Home states without +network reads in tests. Conductarr sign-in is not implemented here: real signed-out +Home hides every rating and shows "Discover needs sign-in". No Seerr fallback is +used. Catalogue data and ratings in debug captures are fixture-backed. + +Home, Libraries and Discover reuse `TitleCard`, `Caption` and `LibrarySeeAllCard`; +those screens and Requests share one `WolfskinImages` loader. Exact-slot requests use RGB565 for posters, +disable crossfades, share a 64 MiB memory/128 MiB disk cache, limit image work to +four reads and prefetch the next shelf. Rating plates start at 14 sp, shrink to 12 sp +and drop trailing entries rather than wrap. Their uncached read gate is 350 ms; +cached focus returns without that delay. + +Focus rings and native lift draw outside fixed slots. The Saved nav chip uses its +own geometry; the Discover section uses a warning pill. Caption separators multiply +the existing muted colour alpha by the shared 40% token. Home-only untrimmed styles +and `exactLineBox` honor the declared CSS line boxes despite Android's intrinsic +paragraph minimums; shell and library typography retain their existing defaults. +The shared See all component uses its canonical locked 8px gap, not the generic +10px tail spacing. Focus lifts use the merged draw-only `FocusLift` implementation; +the obsolete Home shadow helper and duplicate gap/lift measurements are removed. +Vertical moves clamp to short target rows, including a final See all card. + +`make test` includes `HomeCaptureTest` (23 native acceptance states) and the +data-driven paths in `wolfskin-home-presses.json`: play next 2, resume 3, Discover +4, deep-row Back/Down 9, Trending 13 and Trending's See all card 33 presses. +Home captures are written to `build/wolfskin-captures/home/`. Compare those images +side by side with the frozen spec captures; Compose Roboto/JPEG rasterisation, +the approved button-scale consolidation and removal of prototype sample pills +are recorded differences, not a claim of browser pixel equality. + ## Requests under Settings The Requests row and Coming-in sheet share one immutable household snapshot. @@ -186,7 +261,7 @@ 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, +Thumbnails reuse `WolfskinImages` 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. @@ -196,8 +271,8 @@ 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. +data replayed through the real shell. The Requests-only harness starts at the +Home-to-nav boundary; Home's actual hero and asynchronous entry have separate key replays. Sheet dimensions, padding, glyphs, scales, colours and durations are generated tokens. Scoped sheet line boxes retain CSS heights and baselines independently @@ -262,8 +337,8 @@ the title-screen implementation; this slice does not implement title pages. `LibraryCaptureTest` renders the sixteen approved library/See all states offline. `LibraryKeysTest` reads the approved JSON path and reaches Spirited Away in exactly 20 Android key presses. Paging and snapshot tests cover duplicate-only reads, -failure/retry, stale copies, and person/server isolation. Captures are written to -the same `build/wolfskin-captures/` directory as the shell captures. +failure/retry, stale copies, and person/server isolation. Library captures are written +to `build/wolfskin-captures/library/`; Discover's independent captures use `discover/`. ## Toolchain and disk use diff --git a/wolfskin-design/build.gradle.kts b/wolfskin-design/build.gradle.kts index d89d77ae..5db509aa 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", "discover-tokens.json", "generate_tokens.py", "tokens.css", "tokens.sha256", "src/main/java/**/WolfskinTokens.kt") }) + inputs.files(fileTree(projectDir) { include("tokens.json", "shell-tokens.json", "home-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) diff --git a/wolfskin-design/generate_tokens.py b/wolfskin-design/generate_tokens.py index 27c30701..a8cd6db6 100755 --- a/wolfskin-design/generate_tokens.py +++ b/wolfskin-design/generate_tokens.py @@ -105,10 +105,11 @@ def main() -> None: arguments = parser.parse_args() tokens = json.loads((ROOT / "tokens.json").read_text()) tokens.update(json.loads((ROOT / "shell-tokens.json").read_text())) + tokens.update(json.loads((ROOT / "home-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", "discover-tokens.json", "generate_tokens.py"] + inputs = ["tokens.json", "shell-tokens.json", "home-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/home-tokens.json b/wolfskin-design/home-tokens.json new file mode 100644 index 00000000..1f82d724 --- /dev/null +++ b/wolfskin-design/home-tokens.json @@ -0,0 +1,317 @@ +{ + "home": { + "kickerGap": { + "$type": "dimension", + "px": 12, + "dp": 6.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "kickerBottom": { + "$type": "dimension", + "px": 10, + "dp": 5.0, + "source": "locked index.html:101" + }, + "titleBottom": { + "$type": "dimension", + "px": 16, + "dp": 8.0, + "source": "locked index.html:103" + }, + "factsHeight": { + "$type": "dimension", + "px": 38, + "dp": 19.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "factsBottom": { + "$type": "dimension", + "px": 14, + "dp": 7.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "factsGap": { + "$type": "dimension", + "px": 22, + "dp": 11.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "overviewBottom": { + "$type": "dimension", + "px": 30, + "dp": 15.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "cardPadding": { + "$type": "dimension", + "px": 22, + "dp": 11.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "cardOverlayTop": { + "$type": "dimension", + "px": 54, + "dp": 27.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "cardOverlayBottom": { + "$type": "dimension", + "px": 18, + "dp": 9.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "sectionTop": { + "$type": "dimension", + "px": 40, + "dp": 20.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "sectionAvatar": { + "$type": "dimension", + "px": 56, + "dp": 28.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "sectionGap": { + "$type": "dimension", + "px": 22, + "dp": 11.0, + "source": "locked index.html:180" + }, + "stateTextBottom": { + "$type": "dimension", + "px": 24, + "dp": 12.0, + "source": "locked index.html:198" + }, + "stateTop": { + "$type": "dimension", + "px": 20, + "dp": 10.0, + "source": "locked index.html:194" + }, + "statePaddingX": { + "$type": "dimension", + "px": 40, + "dp": 20.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "statePaddingY": { + "$type": "dimension", + "px": 34, + "dp": 17.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "stateGap": { + "$type": "dimension", + "px": 8, + "dp": 4.0, + "source": "locked index.html:195" + }, + "stateIcon": { + "$type": "dimension", + "px": 36, + "dp": 18.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "captionWidth": { + "$type": "dimension", + "px": 640, + "dp": 320.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "plateTop": { + "$type": "dimension", + "px": 14, + "dp": 7.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "plateSide": { + "$type": "dimension", + "px": 16, + "dp": 8.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "plateFade": { + "$type": "dimension", + "px": 46, + "dp": 23.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "ratingGap": { + "$type": "dimension", + "px": 20, + "dp": 10.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "ratingMarkGap": { + "$type": "dimension", + "px": 8, + "dp": 4.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "heroSkeletonWidth": { + "$type": "dimension", + "px": 640, + "dp": 320.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "heroSkeletonHeight": { + "$type": "dimension", + "px": 84, + "dp": 42.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "heroSkeletonFactsWidth": { + "$type": "dimension", + "px": 460, + "dp": 230.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "heroSkeletonFactsHeight": { + "$type": "dimension", + "px": 34, + "dp": 17.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "heroSkeletonButtonWidth": { + "$type": "dimension", + "px": 260, + "dp": 130.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "heroSkeletonGap": { + "$type": "dimension", + "px": 22, + "dp": 11.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "heroSkeletonButtonGap": { + "$type": "dimension", + "px": 40, + "dp": 20.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "artGlyph": { + "$type": "dimension", + "px": 44, + "dp": 22.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "cardSheetLeft": { + "$type": "dimension", + "px": 22, + "dp": 11.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "cardSheetTop": { + "$type": "dimension", + "px": 18, + "dp": 9.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "cardSheetInset": { + "$type": "dimension", + "px": 36, + "dp": 18.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "heroSheetRight": { + "$type": "dimension", + "px": 180, + "dp": 90.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "heroSheetTop": { + "$type": "dimension", + "px": 150, + "dp": 75.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "cardBlur": { + "$type": "dimension", + "px": 26, + "dp": 13.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "heroBlur": { + "$type": "dimension", + "px": 60, + "dp": 30.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "progressInset": { + "$type": "dimension", + "px": 18, + "dp": 9.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "progressBottom": { + "$type": "dimension", + "px": 10, + "dp": 5.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "endTop": { + "$type": "dimension", + "px": 40, + "dp": 20.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "frameBottom": { + "$type": "dimension", + "px": 80, + "dp": 40.0, + "source": "locked index.html:1042" + }, + "toastPadding": { + "$type": "dimension", + "px": 28, + "dp": 14.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "toastTop": { + "$type": "dimension", + "px": 150, + "dp": 75.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "toastWidth": { + "$type": "dimension", + "px": 760, + "dp": 380.0, + "source": "locked index.html:94-165,180-198,1014-1029,1276-1289" + }, + "cardWashInset": { + "$type": "dimension", + "px": 30, + "dp": 15.0, + "source": "locked index.html:129" + }, + "heroWashInset": { + "$type": "dimension", + "px": 80, + "dp": 40.0, + "source": "locked index.html:96" + }, + "noteTop": { + "$type": "dimension", + "px": 14, + "dp": 7.0, + "source": "locked index.html:191" + }, + "noteGap": { + "$type": "dimension", + "px": 16, + "dp": 8.0, + "source": "locked index.html:191" + }, + "hairline": { "$type": "dimension", "px": 2, "dp": 1.0, "source": "locked index.html:87,146,194" }, + "savedHeight": { "$type": "dimension", "px": 52, "dp": 26.0, "source": "locked index.html:87" }, + "savedPadding": { "$type": "dimension", "px": 20, "dp": 10.0, "source": "locked index.html:87" }, + "savedGap": { "$type": "dimension", "px": 10, "dp": 5.0, "source": "locked index.html:87" }, + "savedIcon": { "$type": "dimension", "px": 28, "dp": 14.0, "source": "locked index.html:88" }, + "savedRadius": { "$type": "dimension", "px": 28, "dp": 14.0, "source": "locked index.html:87" }, + "posterProgressBottom": { "$type": "dimension", "px": 12, "dp": 6.0, "source": "locked index.html:138-140" } + } +} diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-all-hands-on-deck-class-1-a-2021-s5e1-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/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/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-all-hands-on-deck-class-1-a-2021-s5e1-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-all-hands-on-deck-class-1-a-2021-s5e1-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/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/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-all-hands-on-deck-class-1-a-2021-s5e1-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-announcement-2022-s1e7-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-announcement-2022-s1e7-backdrop.jpg new file mode 100644 index 00000000..a874a0c0 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-announcement-2022-s1e7-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-announcement-2022-s1e7-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-announcement-2022-s1e7-poster.jpg new file mode 100644 index 00000000..df5dc498 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-announcement-2022-s1e7-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-episode-1-2025-s1e1-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-episode-1-2025-s1e1-backdrop.jpg new file mode 100644 index 00000000..f2519b04 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-episode-1-2025-s1e1-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-episode-1-2025-s1e1-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-episode-1-2025-s1e1-poster.jpg new file mode 100644 index 00000000..77febd79 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-episode-1-2025-s1e1-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-episode-2-2016-s1e2-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-episode-2-2016-s1e2-backdrop.jpg new file mode 100644 index 00000000..3f8bdeec Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-episode-2-2016-s1e2-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-episode-2-2016-s1e2-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-episode-2-2016-s1e2-poster.jpg new file mode 100644 index 00000000..d29448bb Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-episode-2-2016-s1e2-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-erickerhead-2026-s9e6-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-erickerhead-2026-s9e6-backdrop.jpg new file mode 100644 index 00000000..1c4c0ffc Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-erickerhead-2026-s9e6-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-erickerhead-2026-s9e6-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-erickerhead-2026-s9e6-poster.jpg new file mode 100644 index 00000000..b95c4890 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-erickerhead-2026-s9e6-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-girls-interrupted-2007-s1e7-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-girls-interrupted-2007-s1e7-backdrop.jpg new file mode 100644 index 00000000..ffff979b Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-girls-interrupted-2007-s1e7-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-girls-interrupted-2007-s1e7-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-girls-interrupted-2007-s1e7-poster.jpg new file mode 100644 index 00000000..38428e76 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-girls-interrupted-2007-s1e7-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-i-heart-ny-2002-s4e18-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-i-heart-ny-2002-s4e18-backdrop.jpg new file mode 100644 index 00000000..57beacc1 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-i-heart-ny-2002-s4e18-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-i-heart-ny-2002-s4e18-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-i-heart-ny-2002-s4e18-poster.jpg new file mode 100644 index 00000000..e546cc6d Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-i-heart-ny-2002-s4e18-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-new-day-2021-s1e2-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-new-day-2021-s1e2-backdrop.jpg new file mode 100644 index 00000000..1f747323 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-new-day-2021-s1e2-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-new-day-2021-s1e2-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-new-day-2021-s1e2-poster.jpg new file mode 100644 index 00000000..2f23d1fa Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-new-day-2021-s1e2-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-no-honeymoon-2020-s1e8-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-no-honeymoon-2020-s1e8-backdrop.jpg new file mode 100644 index 00000000..9d95ba77 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-no-honeymoon-2020-s1e8-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-no-honeymoon-2020-s1e8-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-no-honeymoon-2020-s1e8-poster.jpg new file mode 100644 index 00000000..49c61208 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-no-honeymoon-2020-s1e8-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-notapusy-2005-s3e4-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-notapusy-2005-s3e4-backdrop.jpg new file mode 100644 index 00000000..ecb7e8cd Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-notapusy-2005-s3e4-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-notapusy-2005-s3e4-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-notapusy-2005-s3e4-poster.jpg new file mode 100644 index 00000000..02b0dd43 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-notapusy-2005-s3e4-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-one-of-us-2024-s1e7-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-one-of-us-2024-s1e7-backdrop.jpg new file mode 100644 index 00000000..66bb4d00 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-one-of-us-2024-s1e7-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-one-of-us-2024-s1e7-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-one-of-us-2024-s1e7-poster.jpg new file mode 100644 index 00000000..00a07ca7 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-one-of-us-2024-s1e7-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-remembrance-day-2026-s2e1-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-remembrance-day-2026-s2e1-backdrop.jpg new file mode 100644 index 00000000..23a242da Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-remembrance-day-2026-s2e1-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-remembrance-day-2026-s2e1-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-remembrance-day-2026-s2e1-poster.jpg new file mode 100644 index 00000000..930943b0 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-remembrance-day-2026-s2e1-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-sex-and-the-country-2001-s4e9-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-sex-and-the-country-2001-s4e9-backdrop.jpg new file mode 100644 index 00000000..57beacc1 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-sex-and-the-country-2001-s4e9-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-sex-and-the-country-2001-s4e9-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-sex-and-the-country-2001-s4e9-poster.jpg new file mode 100644 index 00000000..e546cc6d Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-sex-and-the-country-2001-s4e9-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-south-american-biker-gangs-2026-s29e1-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-south-american-biker-gangs-2026-s29e1-backdrop.jpg new file mode 100644 index 00000000..cd0aec39 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-south-american-biker-gangs-2026-s29e1-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-south-american-biker-gangs-2026-s29e1-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-south-american-biker-gangs-2026-s29e1-poster.jpg new file mode 100644 index 00000000..d8568d54 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-south-american-biker-gangs-2026-s29e1-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-the-breakup-part-2-2001-s1e17-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-the-breakup-part-2-2001-s1e17-backdrop.jpg new file mode 100644 index 00000000..b42ba953 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-the-breakup-part-2-2001-s1e17-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-the-breakup-part-2-2001-s1e17-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-the-breakup-part-2-2001-s1e17-poster.jpg new file mode 100644 index 00000000..23b668cb Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-the-breakup-part-2-2001-s1e17-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-the-man-who-would-be-vogue-2018-s2e1-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/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/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-the-man-who-would-be-vogue-2018-s2e1-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-the-man-who-would-be-vogue-2018-s2e1-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/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/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-the-man-who-would-be-vogue-2018-s2e1-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-the-uncomfortability-2010-s1e2-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-the-uncomfortability-2010-s1e2-backdrop.jpg new file mode 100644 index 00000000..bc22e11a Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-the-uncomfortability-2010-s1e2-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-the-uncomfortability-2010-s1e2-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-the-uncomfortability-2010-s1e2-poster.jpg new file mode 100644 index 00000000..ae305043 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-the-uncomfortability-2010-s1e2-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-three-monsters-1992-s1e6-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-three-monsters-1992-s1e6-backdrop.jpg new file mode 100644 index 00000000..ba65ca2d Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-three-monsters-1992-s1e6-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-three-monsters-1992-s1e6-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-three-monsters-1992-s1e6-poster.jpg new file mode 100644 index 00000000..f5843766 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-three-monsters-1992-s1e6-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-tomorrow-2024-s3e1-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-tomorrow-2024-s3e1-backdrop.jpg new file mode 100644 index 00000000..9c9cca57 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-tomorrow-2024-s3e1-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-tomorrow-2024-s3e1-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-tomorrow-2024-s3e1-poster.jpg new file mode 100644 index 00000000..bbf0ab28 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-tomorrow-2024-s3e1-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-what-more-do-i-need-2008-s5e7-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-what-more-do-i-need-2008-s5e7-backdrop.jpg new file mode 100644 index 00000000..40ec3001 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-what-more-do-i-need-2008-s5e7-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-what-more-do-i-need-2008-s5e7-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-what-more-do-i-need-2008-s5e7-poster.jpg new file mode 100644 index 00000000..13f0f7b3 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/episode-what-more-do-i-need-2008-s5e7-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-101-dalmatians-ii-patch-s-london-adventure-2003-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/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/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-101-dalmatians-ii-patch-s-london-adventure-2003-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-101-dalmatians-ii-patch-s-london-adventure-2003-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/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/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-101-dalmatians-ii-patch-s-london-adventure-2003-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-12-years-a-slave-2013-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-12-years-a-slave-2013-backdrop.jpg new file mode 100644 index 00000000..d93357e5 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-12-years-a-slave-2013-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-12-years-a-slave-2013-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-12-years-a-slave-2013-poster.jpg new file mode 100644 index 00000000..ff1f55e6 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-12-years-a-slave-2013-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-a-bronx-tale-1993-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-a-bronx-tale-1993-backdrop.jpg new file mode 100644 index 00000000..15c11ff7 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-a-bronx-tale-1993-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-a-bronx-tale-1993-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-a-bronx-tale-1993-poster.jpg new file mode 100644 index 00000000..f7444271 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-a-bronx-tale-1993-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-a-silent-voice-the-movie-2016-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-a-silent-voice-the-movie-2016-backdrop.jpg new file mode 100644 index 00000000..b386b136 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-a-silent-voice-the-movie-2016-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-a-silent-voice-the-movie-2016-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-a-silent-voice-the-movie-2016-poster.jpg new file mode 100644 index 00000000..580b22f4 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-a-silent-voice-the-movie-2016-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-absolution-2024-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-absolution-2024-backdrop.jpg new file mode 100644 index 00000000..eb28037c Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-absolution-2024-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-absolution-2024-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-absolution-2024-poster.jpg new file mode 100644 index 00000000..3ce49112 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-absolution-2024-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-across-the-hall-2009-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-across-the-hall-2009-backdrop.jpg new file mode 100644 index 00000000..6c1ee8ac Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-across-the-hall-2009-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-across-the-hall-2009-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-across-the-hall-2009-poster.jpg new file mode 100644 index 00000000..52362b4e Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-across-the-hall-2009-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-addicted-2014-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-addicted-2014-backdrop.jpg new file mode 100644 index 00000000..5c016976 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-addicted-2014-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-addicted-2014-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-addicted-2014-poster.jpg new file mode 100644 index 00000000..e9a03c14 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-addicted-2014-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-adulterers-2016-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-adulterers-2016-backdrop.jpg new file mode 100644 index 00000000..331391ed Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-adulterers-2016-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-adulterers-2016-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-adulterers-2016-poster.jpg new file mode 100644 index 00000000..8b65745b Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-adulterers-2016-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-all-the-money-in-the-world-2017-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-all-the-money-in-the-world-2017-backdrop.jpg new file mode 100644 index 00000000..ac4b828a Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-all-the-money-in-the-world-2017-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-all-the-money-in-the-world-2017-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-all-the-money-in-the-world-2017-poster.jpg new file mode 100644 index 00000000..8beb2f7f Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-all-the-money-in-the-world-2017-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-alone-with-her-2006-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-alone-with-her-2006-backdrop.jpg new file mode 100644 index 00000000..ddf8f1dc Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-alone-with-her-2006-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-alone-with-her-2006-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-alone-with-her-2006-poster.jpg new file mode 100644 index 00000000..7ce6fa96 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-alone-with-her-2006-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-american-gangster-2007-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-american-gangster-2007-backdrop.jpg new file mode 100644 index 00000000..cb5c5de7 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-american-gangster-2007-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-american-gangster-2007-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-american-gangster-2007-poster.jpg new file mode 100644 index 00000000..0f41d299 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-american-gangster-2007-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-annie-hall-1977-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-annie-hall-1977-backdrop.jpg new file mode 100644 index 00000000..16d0b27d Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-annie-hall-1977-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-annie-hall-1977-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-annie-hall-1977-poster.jpg new file mode 100644 index 00000000..9023a691 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-annie-hall-1977-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-ant-man-and-the-wasp-quantumania-2023-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-ant-man-and-the-wasp-quantumania-2023-backdrop.jpg new file mode 100644 index 00000000..2a99846d Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-ant-man-and-the-wasp-quantumania-2023-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-ant-man-and-the-wasp-quantumania-2023-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-ant-man-and-the-wasp-quantumania-2023-poster.jpg new file mode 100644 index 00000000..030addf0 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-ant-man-and-the-wasp-quantumania-2023-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-apocalypse-now-1979-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-apocalypse-now-1979-backdrop.jpg new file mode 100644 index 00000000..93292da3 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-apocalypse-now-1979-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-apocalypse-now-1979-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-apocalypse-now-1979-poster.jpg new file mode 100644 index 00000000..09cd6aa8 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-apocalypse-now-1979-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-appleseed-alpha-2014-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-appleseed-alpha-2014-backdrop.jpg new file mode 100644 index 00000000..82579b86 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-appleseed-alpha-2014-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-appleseed-alpha-2014-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-appleseed-alpha-2014-poster.jpg new file mode 100644 index 00000000..62bd0afa Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-appleseed-alpha-2014-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-aquaman-and-the-lost-kingdom-2023-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-aquaman-and-the-lost-kingdom-2023-backdrop.jpg new file mode 100644 index 00000000..6693cb6d Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-aquaman-and-the-lost-kingdom-2023-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-aquaman-and-the-lost-kingdom-2023-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-aquaman-and-the-lost-kingdom-2023-poster.jpg new file mode 100644 index 00000000..4550bdaa Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-aquaman-and-the-lost-kingdom-2023-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-arctic-dogs-2019-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-arctic-dogs-2019-backdrop.jpg new file mode 100644 index 00000000..1794c99d Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-arctic-dogs-2019-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-arctic-dogs-2019-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-arctic-dogs-2019-poster.jpg new file mode 100644 index 00000000..07552443 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-arctic-dogs-2019-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-arrival-2016-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-arrival-2016-backdrop.jpg new file mode 100644 index 00000000..605724e7 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-arrival-2016-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-arrival-2016-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-arrival-2016-poster.jpg new file mode 100644 index 00000000..fe224a15 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-arrival-2016-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-avatar-aang-the-last-airbender-2026-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-avatar-aang-the-last-airbender-2026-backdrop.jpg new file mode 100644 index 00000000..d3b12fe0 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-avatar-aang-the-last-airbender-2026-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-avatar-aang-the-last-airbender-2026-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-avatar-aang-the-last-airbender-2026-poster.jpg new file mode 100644 index 00000000..f57f1052 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-avatar-aang-the-last-airbender-2026-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-avengers-age-of-ultron-2015-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-avengers-age-of-ultron-2015-backdrop.jpg new file mode 100644 index 00000000..f739d620 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-avengers-age-of-ultron-2015-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-avengers-age-of-ultron-2015-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-avengers-age-of-ultron-2015-poster.jpg new file mode 100644 index 00000000..300840ff Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-avengers-age-of-ultron-2015-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-avengers-confidential-black-widow-punisher-2014-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-avengers-confidential-black-widow-punisher-2014-backdrop.jpg new file mode 100644 index 00000000..8163931a Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-avengers-confidential-black-widow-punisher-2014-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-avengers-confidential-black-widow-punisher-2014-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-avengers-confidential-black-widow-punisher-2014-poster.jpg new file mode 100644 index 00000000..b096fff2 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-avengers-confidential-black-widow-punisher-2014-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-avengers-infinity-war-2018-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-avengers-infinity-war-2018-backdrop.jpg new file mode 100644 index 00000000..90dc4672 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-avengers-infinity-war-2018-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-avengers-infinity-war-2018-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-avengers-infinity-war-2018-poster.jpg new file mode 100644 index 00000000..57df9298 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-avengers-infinity-war-2018-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-awake-2007-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-awake-2007-backdrop.jpg new file mode 100644 index 00000000..6ddd7f59 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-awake-2007-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-awake-2007-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-awake-2007-poster.jpg new file mode 100644 index 00000000..32595897 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-awake-2007-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-batman-ninja-2018-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-batman-ninja-2018-backdrop.jpg new file mode 100644 index 00000000..9b538d9f Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-batman-ninja-2018-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-batman-ninja-2018-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-batman-ninja-2018-poster.jpg new file mode 100644 index 00000000..7f59127b Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-batman-ninja-2018-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-batman-the-dark-knight-returns-part-1-2012-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/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/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-batman-the-dark-knight-returns-part-1-2012-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-batman-the-dark-knight-returns-part-1-2012-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/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/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-batman-the-dark-knight-returns-part-1-2012-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-batman-v-superman-dawn-of-justice-2016-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-batman-v-superman-dawn-of-justice-2016-backdrop.jpg new file mode 100644 index 00000000..b4fa63b9 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-batman-v-superman-dawn-of-justice-2016-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-batman-v-superman-dawn-of-justice-2016-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-batman-v-superman-dawn-of-justice-2016-poster.jpg new file mode 100644 index 00000000..f8115170 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-batman-v-superman-dawn-of-justice-2016-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-black-adam-2022-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-black-adam-2022-backdrop.jpg new file mode 100644 index 00000000..3232fb22 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-black-adam-2022-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-black-adam-2022-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-black-adam-2022-poster.jpg new file mode 100644 index 00000000..dde008b5 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-black-adam-2022-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-black-bear-2020-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-black-bear-2020-backdrop.jpg new file mode 100644 index 00000000..4b0b262f Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-black-bear-2020-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-black-bear-2020-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-black-bear-2020-poster.jpg new file mode 100644 index 00000000..61c8860f Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-black-bear-2020-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-black-panther-2018-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-black-panther-2018-backdrop.jpg new file mode 100644 index 00000000..f0b8de8a Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-black-panther-2018-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-black-panther-2018-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-black-panther-2018-poster.jpg new file mode 100644 index 00000000..6fdaa3d2 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-black-panther-2018-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-black-panther-wakanda-forever-2022-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-black-panther-wakanda-forever-2022-backdrop.jpg new file mode 100644 index 00000000..ac7ad532 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-black-panther-wakanda-forever-2022-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-black-panther-wakanda-forever-2022-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-black-panther-wakanda-forever-2022-poster.jpg new file mode 100644 index 00000000..d0219a75 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-black-panther-wakanda-forever-2022-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-blame-2017-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-blame-2017-backdrop.jpg new file mode 100644 index 00000000..f39cf5ce Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-blame-2017-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-blame-2017-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-blame-2017-poster.jpg new file mode 100644 index 00000000..8d50255f Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-blame-2017-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-bleach-the-movie-fade-to-black-2008-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-bleach-the-movie-fade-to-black-2008-backdrop.jpg new file mode 100644 index 00000000..f426d0ce Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-bleach-the-movie-fade-to-black-2008-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-bleach-the-movie-fade-to-black-2008-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-bleach-the-movie-fade-to-black-2008-poster.jpg new file mode 100644 index 00000000..d3619fbd Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-bleach-the-movie-fade-to-black-2008-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-bohemian-rhapsody-2018-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-bohemian-rhapsody-2018-backdrop.jpg new file mode 100644 index 00000000..b6b5a7cd Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-bohemian-rhapsody-2018-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-bohemian-rhapsody-2018-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-bohemian-rhapsody-2018-poster.jpg new file mode 100644 index 00000000..20d2f9ee Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-bohemian-rhapsody-2018-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-braveheart-1995-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-braveheart-1995-backdrop.jpg new file mode 100644 index 00000000..c1c84c59 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-braveheart-1995-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-braveheart-1995-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-braveheart-1995-poster.jpg new file mode 100644 index 00000000..32fd8d4d Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-braveheart-1995-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-butterfly-on-a-wheel-2007-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-butterfly-on-a-wheel-2007-backdrop.jpg new file mode 100644 index 00000000..e49fe1d5 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-butterfly-on-a-wheel-2007-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-butterfly-on-a-wheel-2007-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-butterfly-on-a-wheel-2007-poster.jpg new file mode 100644 index 00000000..b54cc5c7 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-butterfly-on-a-wheel-2007-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-by-any-means-2026-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-by-any-means-2026-backdrop.jpg new file mode 100644 index 00000000..e536359f Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-by-any-means-2026-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-by-any-means-2026-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-by-any-means-2026-poster.jpg new file mode 100644 index 00000000..92fd601f Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-by-any-means-2026-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-captain-america-civil-war-2016-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-captain-america-civil-war-2016-backdrop.jpg new file mode 100644 index 00000000..5700c4e7 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-captain-america-civil-war-2016-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-captain-america-civil-war-2016-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-captain-america-civil-war-2016-poster.jpg new file mode 100644 index 00000000..426142ef Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-captain-america-civil-war-2016-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-captain-america-the-first-avenger-2011-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-captain-america-the-first-avenger-2011-backdrop.jpg new file mode 100644 index 00000000..411ceebe Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-captain-america-the-first-avenger-2011-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-captain-america-the-first-avenger-2011-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-captain-america-the-first-avenger-2011-poster.jpg new file mode 100644 index 00000000..bba96574 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-captain-america-the-first-avenger-2011-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-captain-america-the-winter-soldier-2014-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-captain-america-the-winter-soldier-2014-backdrop.jpg new file mode 100644 index 00000000..5661fcff Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-captain-america-the-winter-soldier-2014-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-captain-america-the-winter-soldier-2014-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-captain-america-the-winter-soldier-2014-poster.jpg new file mode 100644 index 00000000..c2dc5612 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-captain-america-the-winter-soldier-2014-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-careful-what-you-wish-for-2015-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-careful-what-you-wish-for-2015-backdrop.jpg new file mode 100644 index 00000000..988e20d5 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-careful-what-you-wish-for-2015-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-careful-what-you-wish-for-2015-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-careful-what-you-wish-for-2015-poster.jpg new file mode 100644 index 00000000..b58ce600 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-careful-what-you-wish-for-2015-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-castle-in-the-sky-1986-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-castle-in-the-sky-1986-backdrop.jpg new file mode 100644 index 00000000..f785c549 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-castle-in-the-sky-1986-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-castle-in-the-sky-1986-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-castle-in-the-sky-1986-poster.jpg new file mode 100644 index 00000000..a6a99fee Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-castle-in-the-sky-1986-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-chicago-2002-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-chicago-2002-backdrop.jpg new file mode 100644 index 00000000..6b3d02a3 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-chicago-2002-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-chicago-2002-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-chicago-2002-poster.jpg new file mode 100644 index 00000000..1def9414 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-chicago-2002-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-citizen-kane-1941-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-citizen-kane-1941-backdrop.jpg new file mode 100644 index 00000000..d41a10ef Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-citizen-kane-1941-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-citizen-kane-1941-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-citizen-kane-1941-poster.jpg new file mode 100644 index 00000000..f26414a7 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-citizen-kane-1941-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-coherence-2014-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-coherence-2014-backdrop.jpg new file mode 100644 index 00000000..37da64f9 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-coherence-2014-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-coherence-2014-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-coherence-2014-poster.jpg new file mode 100644 index 00000000..f3601b9b Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-coherence-2014-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-conclave-2024-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-conclave-2024-backdrop.jpg new file mode 100644 index 00000000..db0a7fcd Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-conclave-2024-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-conclave-2024-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-conclave-2024-poster.jpg new file mode 100644 index 00000000..c6aecefa Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-conclave-2024-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-crime-of-passion-1956-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-crime-of-passion-1956-backdrop.jpg new file mode 100644 index 00000000..6c70a452 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-crime-of-passion-1956-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-crime-of-passion-1956-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-crime-of-passion-1956-poster.jpg new file mode 100644 index 00000000..4acfa871 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-crime-of-passion-1956-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-deep-water-2022-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-deep-water-2022-backdrop.jpg new file mode 100644 index 00000000..bec98992 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-deep-water-2022-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-deep-water-2022-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-deep-water-2022-poster.jpg new file mode 100644 index 00000000..018d4f4b Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-deep-water-2022-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-derailed-2005-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-derailed-2005-backdrop.jpg new file mode 100644 index 00000000..e958449b Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-derailed-2005-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-derailed-2005-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-derailed-2005-poster.jpg new file mode 100644 index 00000000..d93f3124 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-derailed-2005-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-die-my-love-2025-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-die-my-love-2025-backdrop.jpg new file mode 100644 index 00000000..754e713f Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-die-my-love-2025-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-die-my-love-2025-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-die-my-love-2025-poster.jpg new file mode 100644 index 00000000..f6eca818 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-die-my-love-2025-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-dream-house-2011-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-dream-house-2011-backdrop.jpg new file mode 100644 index 00000000..29ecc103 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-dream-house-2011-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-dream-house-2011-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-dream-house-2011-poster.jpg new file mode 100644 index 00000000..d3444637 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-dream-house-2011-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-dream-lover-1994-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-dream-lover-1994-backdrop.jpg new file mode 100644 index 00000000..7f5a31ac Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-dream-lover-1994-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-dream-lover-1994-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-dream-lover-1994-poster.jpg new file mode 100644 index 00000000..3b563cf9 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-dream-lover-1994-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-drop-2025-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-drop-2025-backdrop.jpg new file mode 100644 index 00000000..ee642530 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-drop-2025-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-drop-2025-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-drop-2025-poster.jpg new file mode 100644 index 00000000..4cf7d66b Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-drop-2025-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-eastern-promises-2007-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-eastern-promises-2007-backdrop.jpg new file mode 100644 index 00000000..9096a0fa Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-eastern-promises-2007-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-eastern-promises-2007-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-eastern-promises-2007-poster.jpg new file mode 100644 index 00000000..01a26f3b Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-eastern-promises-2007-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-easy-a-2010-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-easy-a-2010-backdrop.jpg new file mode 100644 index 00000000..c27fa57b Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-easy-a-2010-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-easy-a-2010-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-easy-a-2010-poster.jpg new file mode 100644 index 00000000..45e13a32 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-easy-a-2010-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-el-camino-a-breaking-bad-movie-2019-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-el-camino-a-breaking-bad-movie-2019-backdrop.jpg new file mode 100644 index 00000000..8edc02a6 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-el-camino-a-breaking-bad-movie-2019-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-el-camino-a-breaking-bad-movie-2019-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-el-camino-a-breaking-bad-movie-2019-poster.jpg new file mode 100644 index 00000000..7a62df45 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-el-camino-a-breaking-bad-movie-2019-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-emancipation-2022-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-emancipation-2022-backdrop.jpg new file mode 100644 index 00000000..ac0777c7 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-emancipation-2022-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-emancipation-2022-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-emancipation-2022-poster.jpg new file mode 100644 index 00000000..c1bee971 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-emancipation-2022-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-faces-1968-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-faces-1968-backdrop.jpg new file mode 100644 index 00000000..60273dd9 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-faces-1968-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-faces-1968-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-faces-1968-poster.jpg new file mode 100644 index 00000000..43f996bd Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-faces-1968-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-fatale-2020-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-fatale-2020-backdrop.jpg new file mode 100644 index 00000000..b13c33c6 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-fatale-2020-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-fatale-2020-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-fatale-2020-poster.jpg new file mode 100644 index 00000000..dda9d622 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-fatale-2020-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-fighting-with-my-family-2019-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-fighting-with-my-family-2019-backdrop.jpg new file mode 100644 index 00000000..46835996 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-fighting-with-my-family-2019-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-fighting-with-my-family-2019-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-fighting-with-my-family-2019-poster.jpg new file mode 100644 index 00000000..169c466d Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-fighting-with-my-family-2019-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-force-of-nature-the-dry-2-2024-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-force-of-nature-the-dry-2-2024-backdrop.jpg new file mode 100644 index 00000000..3735ee63 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-force-of-nature-the-dry-2-2024-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-force-of-nature-the-dry-2-2024-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-force-of-nature-the-dry-2-2024-poster.jpg new file mode 100644 index 00000000..93ba0d71 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-force-of-nature-the-dry-2-2024-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-free-state-of-jones-2016-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-free-state-of-jones-2016-backdrop.jpg new file mode 100644 index 00000000..a7e5ddff Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-free-state-of-jones-2016-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-free-state-of-jones-2016-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-free-state-of-jones-2016-poster.jpg new file mode 100644 index 00000000..e69af7c0 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-free-state-of-jones-2016-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-from-up-on-poppy-hill-2011-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-from-up-on-poppy-hill-2011-backdrop.jpg new file mode 100644 index 00000000..32a28bd7 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-from-up-on-poppy-hill-2011-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-from-up-on-poppy-hill-2011-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-from-up-on-poppy-hill-2011-poster.jpg new file mode 100644 index 00000000..264076e2 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-from-up-on-poppy-hill-2011-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-ghost-in-the-shell-1995-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-ghost-in-the-shell-1995-backdrop.jpg new file mode 100644 index 00000000..716b8a87 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-ghost-in-the-shell-1995-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-ghost-in-the-shell-1995-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-ghost-in-the-shell-1995-poster.jpg new file mode 100644 index 00000000..58d221ba Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-ghost-in-the-shell-1995-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-ghost-in-the-shell-2-innocence-2004-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-ghost-in-the-shell-2-innocence-2004-backdrop.jpg new file mode 100644 index 00000000..4a9931ed Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-ghost-in-the-shell-2-innocence-2004-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-ghost-in-the-shell-2-innocence-2004-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-ghost-in-the-shell-2-innocence-2004-poster.jpg new file mode 100644 index 00000000..ae262147 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-ghost-in-the-shell-2-innocence-2004-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-gosford-park-2001-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-gosford-park-2001-backdrop.jpg new file mode 100644 index 00000000..42c4cea6 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-gosford-park-2001-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-gosford-park-2001-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-gosford-park-2001-poster.jpg new file mode 100644 index 00000000..6a6aa651 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-gosford-park-2001-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-grave-of-the-fireflies-1988-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-grave-of-the-fireflies-1988-backdrop.jpg new file mode 100644 index 00000000..0e1add06 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-grave-of-the-fireflies-1988-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-grave-of-the-fireflies-1988-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-grave-of-the-fireflies-1988-poster.jpg new file mode 100644 index 00000000..1b71996a Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-grave-of-the-fireflies-1988-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-howl-s-moving-castle-2004-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-howl-s-moving-castle-2004-backdrop.jpg new file mode 100644 index 00000000..0aeef2ba Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-howl-s-moving-castle-2004-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-howl-s-moving-castle-2004-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-howl-s-moving-castle-2004-poster.jpg new file mode 100644 index 00000000..042a6c80 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-howl-s-moving-castle-2004-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-in-the-heights-2021-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-in-the-heights-2021-backdrop.jpg new file mode 100644 index 00000000..7cd4ee24 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-in-the-heights-2021-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-in-the-heights-2021-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-in-the-heights-2021-poster.jpg new file mode 100644 index 00000000..e05bfdb0 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-in-the-heights-2021-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-jennifer-s-body-2009-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-jennifer-s-body-2009-backdrop.jpg new file mode 100644 index 00000000..032771a0 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-jennifer-s-body-2009-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-jennifer-s-body-2009-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-jennifer-s-body-2009-poster.jpg new file mode 100644 index 00000000..41cc1eb5 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-jennifer-s-body-2009-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-kiki-s-delivery-service-1989-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-kiki-s-delivery-service-1989-backdrop.jpg new file mode 100644 index 00000000..2a108f0c Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-kiki-s-delivery-service-1989-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-kiki-s-delivery-service-1989-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-kiki-s-delivery-service-1989-poster.jpg new file mode 100644 index 00000000..80a59b1e Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-kiki-s-delivery-service-1989-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-lemony-snicket-s-a-series-of-unfortunate-events-2004-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/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/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-lemony-snicket-s-a-series-of-unfortunate-events-2004-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-lemony-snicket-s-a-series-of-unfortunate-events-2004-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/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/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-lemony-snicket-s-a-series-of-unfortunate-events-2004-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-locke-2014-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-locke-2014-backdrop.jpg new file mode 100644 index 00000000..a24e64ab Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-locke-2014-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-locke-2014-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-locke-2014-poster.jpg new file mode 100644 index 00000000..94ebe97d Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-locke-2014-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-look-who-s-back-2015-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-look-who-s-back-2015-backdrop.jpg new file mode 100644 index 00000000..a064686f Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-look-who-s-back-2015-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-look-who-s-back-2015-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-look-who-s-back-2015-poster.jpg new file mode 100644 index 00000000..423043cf Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-look-who-s-back-2015-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-memories-1995-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-memories-1995-backdrop.jpg new file mode 100644 index 00000000..2fcaeaad Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-memories-1995-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-memories-1995-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-memories-1995-poster.jpg new file mode 100644 index 00000000..f3725b42 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-memories-1995-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-mercy-2026-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-mercy-2026-backdrop.jpg new file mode 100644 index 00000000..45591cc8 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-mercy-2026-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-mercy-2026-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-mercy-2026-poster.jpg new file mode 100644 index 00000000..cfc0d812 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-mercy-2026-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-night-is-short-walk-on-girl-2017-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-night-is-short-walk-on-girl-2017-backdrop.jpg new file mode 100644 index 00000000..da38be48 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-night-is-short-walk-on-girl-2017-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-night-is-short-walk-on-girl-2017-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-night-is-short-walk-on-girl-2017-poster.jpg new file mode 100644 index 00000000..c6fa6a26 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-night-is-short-walk-on-girl-2017-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-normal-2026-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-normal-2026-backdrop.jpg new file mode 100644 index 00000000..51711864 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-normal-2026-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-normal-2026-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-normal-2026-poster.jpg new file mode 100644 index 00000000..f4c89db7 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-normal-2026-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-outlaw-king-2018-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-outlaw-king-2018-backdrop.jpg new file mode 100644 index 00000000..54ac5a2a Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-outlaw-king-2018-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-outlaw-king-2018-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-outlaw-king-2018-poster.jpg new file mode 100644 index 00000000..d6760468 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-outlaw-king-2018-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-panic-room-2002-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-panic-room-2002-backdrop.jpg new file mode 100644 index 00000000..ec877751 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-panic-room-2002-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-panic-room-2002-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-panic-room-2002-poster.jpg new file mode 100644 index 00000000..0b2a0d30 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-panic-room-2002-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-peaky-blinders-the-immortal-man-2026-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-peaky-blinders-the-immortal-man-2026-backdrop.jpg new file mode 100644 index 00000000..e9d7155a Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-peaky-blinders-the-immortal-man-2026-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-peaky-blinders-the-immortal-man-2026-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-peaky-blinders-the-immortal-man-2026-poster.jpg new file mode 100644 index 00000000..a4814472 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-peaky-blinders-the-immortal-man-2026-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-ponyo-2008-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-ponyo-2008-backdrop.jpg new file mode 100644 index 00000000..8aa4ecbd Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-ponyo-2008-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-ponyo-2008-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-ponyo-2008-poster.jpg new file mode 100644 index 00000000..f548e831 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-ponyo-2008-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-porco-rosso-1992-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-porco-rosso-1992-backdrop.jpg new file mode 100644 index 00000000..63d8112e Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-porco-rosso-1992-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-porco-rosso-1992-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-porco-rosso-1992-poster.jpg new file mode 100644 index 00000000..4a6a2bd1 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-porco-rosso-1992-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-princess-mononoke-1997-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-princess-mononoke-1997-backdrop.jpg new file mode 100644 index 00000000..9486d2cf Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-princess-mononoke-1997-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-princess-mononoke-1997-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-princess-mononoke-1997-poster.jpg new file mode 100644 index 00000000..21dd9359 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-princess-mononoke-1997-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-prisoners-2013-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-prisoners-2013-backdrop.jpg new file mode 100644 index 00000000..724008c5 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-prisoners-2013-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-prisoners-2013-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-prisoners-2013-poster.jpg new file mode 100644 index 00000000..411b149b Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-prisoners-2013-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-racing-stripes-2005-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-racing-stripes-2005-backdrop.jpg new file mode 100644 index 00000000..1d332cec Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-racing-stripes-2005-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-racing-stripes-2005-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-racing-stripes-2005-poster.jpg new file mode 100644 index 00000000..cc2ef2ce Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-racing-stripes-2005-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-reincarnated-2012-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-reincarnated-2012-backdrop.jpg new file mode 100644 index 00000000..326e8ebc Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-reincarnated-2012-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-reincarnated-2012-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-reincarnated-2012-poster.jpg new file mode 100644 index 00000000..e9ab449c Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-reincarnated-2012-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-requiem-for-a-dream-2000-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-requiem-for-a-dream-2000-backdrop.jpg new file mode 100644 index 00000000..21842ef2 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-requiem-for-a-dream-2000-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-requiem-for-a-dream-2000-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-requiem-for-a-dream-2000-poster.jpg new file mode 100644 index 00000000..6f89abc3 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-requiem-for-a-dream-2000-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-rogue-one-a-star-wars-story-2016-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-rogue-one-a-star-wars-story-2016-backdrop.jpg new file mode 100644 index 00000000..ff0c14c0 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-rogue-one-a-star-wars-story-2016-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-rogue-one-a-star-wars-story-2016-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-rogue-one-a-star-wars-story-2016-poster.jpg new file mode 100644 index 00000000..c79e70a1 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-rogue-one-a-star-wars-story-2016-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-secondhand-lions-2003-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-secondhand-lions-2003-backdrop.jpg new file mode 100644 index 00000000..7ea43937 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-secondhand-lions-2003-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-secondhand-lions-2003-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-secondhand-lions-2003-poster.jpg new file mode 100644 index 00000000..33408fa0 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-secondhand-lions-2003-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-self-reliance-2024-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-self-reliance-2024-backdrop.jpg new file mode 100644 index 00000000..3cc0d123 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-self-reliance-2024-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-self-reliance-2024-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-self-reliance-2024-poster.jpg new file mode 100644 index 00000000..0f6017ed Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-self-reliance-2024-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-serpico-1973-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-serpico-1973-backdrop.jpg new file mode 100644 index 00000000..c9649485 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-serpico-1973-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-serpico-1973-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-serpico-1973-poster.jpg new file mode 100644 index 00000000..8ddd6e52 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-serpico-1973-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-shaft-2000-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-shaft-2000-backdrop.jpg new file mode 100644 index 00000000..13f14f93 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-shaft-2000-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-shaft-2000-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-shaft-2000-poster.jpg new file mode 100644 index 00000000..6b2b79ce Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-shaft-2000-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-shipwrecked-nightmare-at-sea-2026-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-shipwrecked-nightmare-at-sea-2026-backdrop.jpg new file mode 100644 index 00000000..6e98e242 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-shipwrecked-nightmare-at-sea-2026-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-shipwrecked-nightmare-at-sea-2026-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-shipwrecked-nightmare-at-sea-2026-poster.jpg new file mode 100644 index 00000000..f200a28d Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-shipwrecked-nightmare-at-sea-2026-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-sinbad-legend-of-the-seven-seas-2003-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-sinbad-legend-of-the-seven-seas-2003-backdrop.jpg new file mode 100644 index 00000000..d04685fe Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-sinbad-legend-of-the-seven-seas-2003-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-sinbad-legend-of-the-seven-seas-2003-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-sinbad-legend-of-the-seven-seas-2003-poster.jpg new file mode 100644 index 00000000..428bb38b Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-sinbad-legend-of-the-seven-seas-2003-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-sleeping-with-other-people-2015-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-sleeping-with-other-people-2015-backdrop.jpg new file mode 100644 index 00000000..df5bb046 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-sleeping-with-other-people-2015-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-sleeping-with-other-people-2015-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-sleeping-with-other-people-2015-poster.jpg new file mode 100644 index 00000000..a5ffff1f Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-sleeping-with-other-people-2015-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-snowpiercer-2013-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-snowpiercer-2013-backdrop.jpg new file mode 100644 index 00000000..c1281a9c Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-snowpiercer-2013-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-snowpiercer-2013-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-snowpiercer-2013-poster.jpg new file mode 100644 index 00000000..be1873fd Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-snowpiercer-2013-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-spider-man-brand-new-day-2026-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-spider-man-brand-new-day-2026-backdrop.jpg new file mode 100644 index 00000000..2fdaadb4 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-spider-man-brand-new-day-2026-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-spider-man-brand-new-day-2026-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-spider-man-brand-new-day-2026-poster.jpg new file mode 100644 index 00000000..51fa155f Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-spider-man-brand-new-day-2026-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-spider-man-far-from-home-2019-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-spider-man-far-from-home-2019-backdrop.jpg new file mode 100644 index 00000000..d09f655a Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-spider-man-far-from-home-2019-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-spider-man-far-from-home-2019-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-spider-man-far-from-home-2019-poster.jpg new file mode 100644 index 00000000..28521997 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-spider-man-far-from-home-2019-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-spider-man-homecoming-2017-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-spider-man-homecoming-2017-backdrop.jpg new file mode 100644 index 00000000..6088fadf Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-spider-man-homecoming-2017-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-spider-man-homecoming-2017-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-spider-man-homecoming-2017-poster.jpg new file mode 100644 index 00000000..97fc9c05 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-spider-man-homecoming-2017-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-spider-man-no-way-home-2021-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-spider-man-no-way-home-2021-backdrop.jpg new file mode 100644 index 00000000..a76afec9 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-spider-man-no-way-home-2021-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-spider-man-no-way-home-2021-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-spider-man-no-way-home-2021-poster.jpg new file mode 100644 index 00000000..bc92a4cd Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-spider-man-no-way-home-2021-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-spirited-away-2001-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-spirited-away-2001-backdrop.jpg new file mode 100644 index 00000000..af91c123 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-spirited-away-2001-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-spirited-away-2001-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-spirited-away-2001-poster.jpg new file mode 100644 index 00000000..5a79da62 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-spirited-away-2001-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-steins-gate-the-movie-load-region-of-deja-vu-2013-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/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/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-steins-gate-the-movie-load-region-of-deja-vu-2013-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-steins-gate-the-movie-load-region-of-deja-vu-2013-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/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/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-steins-gate-the-movie-load-region-of-deja-vu-2013-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-taxi-driver-1976-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-taxi-driver-1976-backdrop.jpg new file mode 100644 index 00000000..7946ea40 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-taxi-driver-1976-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-taxi-driver-1976-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-taxi-driver-1976-poster.jpg new file mode 100644 index 00000000..c410b981 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-taxi-driver-1976-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-alto-knights-2025-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-alto-knights-2025-backdrop.jpg new file mode 100644 index 00000000..ea619c14 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-alto-knights-2025-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-alto-knights-2025-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-alto-knights-2025-poster.jpg new file mode 100644 index 00000000..bb009cbf Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-alto-knights-2025-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-amazing-spider-man-2-2014-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-amazing-spider-man-2-2014-backdrop.jpg new file mode 100644 index 00000000..7c5878b7 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-amazing-spider-man-2-2014-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-amazing-spider-man-2-2014-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-amazing-spider-man-2-2014-poster.jpg new file mode 100644 index 00000000..4de2711f Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-amazing-spider-man-2-2014-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-amazing-spider-man-2012-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-amazing-spider-man-2012-backdrop.jpg new file mode 100644 index 00000000..75ae9b64 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-amazing-spider-man-2012-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-amazing-spider-man-2012-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-amazing-spider-man-2012-poster.jpg new file mode 100644 index 00000000..c1aec876 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-amazing-spider-man-2012-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-animatrix-2003-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-animatrix-2003-backdrop.jpg new file mode 100644 index 00000000..7d67ccb0 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-animatrix-2003-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-animatrix-2003-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-animatrix-2003-poster.jpg new file mode 100644 index 00000000..65553d47 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-animatrix-2003-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-avengers-2012-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-avengers-2012-backdrop.jpg new file mode 100644 index 00000000..83636f25 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-avengers-2012-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-avengers-2012-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-avengers-2012-poster.jpg new file mode 100644 index 00000000..be87067c Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-avengers-2012-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-boy-and-the-heron-2023-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-boy-and-the-heron-2023-backdrop.jpg new file mode 100644 index 00000000..a19dc77b Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-boy-and-the-heron-2023-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-boy-and-the-heron-2023-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-boy-and-the-heron-2023-poster.jpg new file mode 100644 index 00000000..094df64e Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-boy-and-the-heron-2023-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-breakfast-club-1985-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-breakfast-club-1985-backdrop.jpg new file mode 100644 index 00000000..9f979cd8 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-breakfast-club-1985-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-breakfast-club-1985-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-breakfast-club-1985-poster.jpg new file mode 100644 index 00000000..d1823120 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-breakfast-club-1985-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-dry-2021-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-dry-2021-backdrop.jpg new file mode 100644 index 00000000..2f016498 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-dry-2021-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-dry-2021-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-dry-2021-poster.jpg new file mode 100644 index 00000000..a0379f2f Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-dry-2021-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-girl-who-leapt-through-time-2006-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-girl-who-leapt-through-time-2006-backdrop.jpg new file mode 100644 index 00000000..6e920467 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-girl-who-leapt-through-time-2006-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-girl-who-leapt-through-time-2006-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/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/debug/assets/wolfskin-home/assets/movie-the-girl-who-leapt-through-time-2006-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-godfather-1972-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-godfather-1972-backdrop.jpg new file mode 100644 index 00000000..8fbb8f2a Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-godfather-1972-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-godfather-1972-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-godfather-1972-poster.jpg new file mode 100644 index 00000000..94111d9b Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-godfather-1972-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-godfather-part-ii-1974-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-godfather-part-ii-1974-backdrop.jpg new file mode 100644 index 00000000..6dbff534 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-godfather-part-ii-1974-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-godfather-part-ii-1974-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-godfather-part-ii-1974-poster.jpg new file mode 100644 index 00000000..1328c99e Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-godfather-part-ii-1974-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-godfather-part-iii-1990-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-godfather-part-iii-1990-backdrop.jpg new file mode 100644 index 00000000..d2080487 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-godfather-part-iii-1990-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-godfather-part-iii-1990-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-godfather-part-iii-1990-poster.jpg new file mode 100644 index 00000000..abbe5e99 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-godfather-part-iii-1990-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-great-gatsby-2013-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-great-gatsby-2013-backdrop.jpg new file mode 100644 index 00000000..7a4f8a32 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-great-gatsby-2013-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-great-gatsby-2013-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-great-gatsby-2013-poster.jpg new file mode 100644 index 00000000..e949fb3e Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-great-gatsby-2013-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-hours-2002-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-hours-2002-backdrop.jpg new file mode 100644 index 00000000..507a78c4 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-hours-2002-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-hours-2002-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-hours-2002-poster.jpg new file mode 100644 index 00000000..f87e919f Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-hours-2002-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-killing-of-a-sacred-deer-2017-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-killing-of-a-sacred-deer-2017-backdrop.jpg new file mode 100644 index 00000000..dd755ee8 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-killing-of-a-sacred-deer-2017-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-killing-of-a-sacred-deer-2017-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-killing-of-a-sacred-deer-2017-poster.jpg new file mode 100644 index 00000000..83464d4c Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-killing-of-a-sacred-deer-2017-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-last-of-the-mohicans-1992-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-last-of-the-mohicans-1992-backdrop.jpg new file mode 100644 index 00000000..3256d38d Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-last-of-the-mohicans-1992-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-last-of-the-mohicans-1992-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-last-of-the-mohicans-1992-poster.jpg new file mode 100644 index 00000000..950e9ba1 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-last-of-the-mohicans-1992-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-last-samurai-2003-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-last-samurai-2003-backdrop.jpg new file mode 100644 index 00000000..509af2d6 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-last-samurai-2003-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-last-samurai-2003-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-last-samurai-2003-poster.jpg new file mode 100644 index 00000000..0d58440c Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-last-samurai-2003-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-lighthouse-2019-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-lighthouse-2019-backdrop.jpg new file mode 100644 index 00000000..862c064a Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-lighthouse-2019-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-lighthouse-2019-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-lighthouse-2019-poster.jpg new file mode 100644 index 00000000..15f26533 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-lighthouse-2019-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-matrix-1999-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-matrix-1999-backdrop.jpg new file mode 100644 index 00000000..c3bf8f52 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-matrix-1999-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-matrix-1999-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-matrix-1999-poster.jpg new file mode 100644 index 00000000..c292dce5 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-matrix-1999-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-matrix-reloaded-2003-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-matrix-reloaded-2003-backdrop.jpg new file mode 100644 index 00000000..dcbf7b96 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-matrix-reloaded-2003-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-matrix-reloaded-2003-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-matrix-reloaded-2003-poster.jpg new file mode 100644 index 00000000..ab11f50f Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-matrix-reloaded-2003-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-matrix-resurrections-2021-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-matrix-resurrections-2021-backdrop.jpg new file mode 100644 index 00000000..99d85a40 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-matrix-resurrections-2021-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-matrix-resurrections-2021-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-matrix-resurrections-2021-poster.jpg new file mode 100644 index 00000000..4477ef1e Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-matrix-resurrections-2021-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-matrix-revolutions-2003-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-matrix-revolutions-2003-backdrop.jpg new file mode 100644 index 00000000..2cc02692 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-matrix-revolutions-2003-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-matrix-revolutions-2003-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-matrix-revolutions-2003-poster.jpg new file mode 100644 index 00000000..7236cc4e Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-matrix-revolutions-2003-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-patriot-2000-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-patriot-2000-backdrop.jpg new file mode 100644 index 00000000..ce7ce498 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-patriot-2000-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-patriot-2000-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-patriot-2000-poster.jpg new file mode 100644 index 00000000..c8582133 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-patriot-2000-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-secret-life-of-pets-2-2019-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-secret-life-of-pets-2-2019-backdrop.jpg new file mode 100644 index 00000000..698f4ad2 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-secret-life-of-pets-2-2019-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-secret-life-of-pets-2-2019-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-secret-life-of-pets-2-2019-poster.jpg new file mode 100644 index 00000000..fe50c2f8 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-secret-life-of-pets-2-2019-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-substance-2024-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-substance-2024-backdrop.jpg new file mode 100644 index 00000000..47a8b372 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-substance-2024-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-substance-2024-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-substance-2024-poster.jpg new file mode 100644 index 00000000..72accfed Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-substance-2024-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-tale-of-the-princess-kaguya-2013-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-tale-of-the-princess-kaguya-2013-backdrop.jpg new file mode 100644 index 00000000..b9157b91 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-tale-of-the-princess-kaguya-2013-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-tale-of-the-princess-kaguya-2013-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-tale-of-the-princess-kaguya-2013-poster.jpg new file mode 100644 index 00000000..882621cb Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-tale-of-the-princess-kaguya-2013-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-uprising-2026-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-uprising-2026-backdrop.jpg new file mode 100644 index 00000000..638eab3d Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-uprising-2026-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-uprising-2026-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-uprising-2026-poster.jpg new file mode 100644 index 00000000..21976395 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-uprising-2026-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-wild-robot-2024-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-wild-robot-2024-backdrop.jpg new file mode 100644 index 00000000..bb48460b Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-wild-robot-2024-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-wild-robot-2024-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-wild-robot-2024-poster.jpg new file mode 100644 index 00000000..0894754d Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-wild-robot-2024-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-wind-rises-2013-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-wind-rises-2013-backdrop.jpg new file mode 100644 index 00000000..2af40e28 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-wind-rises-2013-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-wind-rises-2013-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-wind-rises-2013-poster.jpg new file mode 100644 index 00000000..b7688c77 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-wind-rises-2013-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-witcher-nightmare-of-the-wolf-2021-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-witcher-nightmare-of-the-wolf-2021-backdrop.jpg new file mode 100644 index 00000000..cb5bfb28 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-witcher-nightmare-of-the-wolf-2021-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-witcher-nightmare-of-the-wolf-2021-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-witcher-nightmare-of-the-wolf-2021-poster.jpg new file mode 100644 index 00000000..6db33db1 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-witcher-nightmare-of-the-wolf-2021-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-witcher-sirens-of-the-deep-2025-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-witcher-sirens-of-the-deep-2025-backdrop.jpg new file mode 100644 index 00000000..aad8ea2c Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-witcher-sirens-of-the-deep-2025-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-witcher-sirens-of-the-deep-2025-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-witcher-sirens-of-the-deep-2025-poster.jpg new file mode 100644 index 00000000..8a54bc7f Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-the-witcher-sirens-of-the-deep-2025-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-thelma-louise-1991-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-thelma-louise-1991-backdrop.jpg new file mode 100644 index 00000000..a35327a9 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-thelma-louise-1991-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-thelma-louise-1991-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-thelma-louise-1991-poster.jpg new file mode 100644 index 00000000..5a4ff0ab Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-thelma-louise-1991-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-triangle-of-sadness-2022-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-triangle-of-sadness-2022-backdrop.jpg new file mode 100644 index 00000000..7c9b445d Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-triangle-of-sadness-2022-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-triangle-of-sadness-2022-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-triangle-of-sadness-2022-poster.jpg new file mode 100644 index 00000000..aee092c6 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-triangle-of-sadness-2022-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-tsotsi-2005-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-tsotsi-2005-backdrop.jpg new file mode 100644 index 00000000..e119de53 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-tsotsi-2005-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-tsotsi-2005-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-tsotsi-2005-poster.jpg new file mode 100644 index 00000000..362ddd82 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-tsotsi-2005-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-unknown-number-the-high-school-catfish-2025-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-unknown-number-the-high-school-catfish-2025-backdrop.jpg new file mode 100644 index 00000000..f456c01d Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-unknown-number-the-high-school-catfish-2025-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-unknown-number-the-high-school-catfish-2025-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-unknown-number-the-high-school-catfish-2025-poster.jpg new file mode 100644 index 00000000..e32685f3 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-unknown-number-the-high-school-catfish-2025-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-vice-2018-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-vice-2018-backdrop.jpg new file mode 100644 index 00000000..2918db3a Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-vice-2018-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-vice-2018-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-vice-2018-poster.jpg new file mode 100644 index 00000000..76855126 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-vice-2018-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-vicky-cristina-barcelona-2008-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-vicky-cristina-barcelona-2008-backdrop.jpg new file mode 100644 index 00000000..20eaa159 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-vicky-cristina-barcelona-2008-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-vicky-cristina-barcelona-2008-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-vicky-cristina-barcelona-2008-poster.jpg new file mode 100644 index 00000000..1351cad1 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-vicky-cristina-barcelona-2008-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-warriors-of-the-wind-1984-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-warriors-of-the-wind-1984-backdrop.jpg new file mode 100644 index 00000000..1fb22b96 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-warriors-of-the-wind-1984-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-warriors-of-the-wind-1984-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-warriors-of-the-wind-1984-poster.jpg new file mode 100644 index 00000000..918e424f Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-warriors-of-the-wind-1984-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-we-bury-the-dead-2026-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-we-bury-the-dead-2026-backdrop.jpg new file mode 100644 index 00000000..4a24720d Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-we-bury-the-dead-2026-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-we-bury-the-dead-2026-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-we-bury-the-dead-2026-poster.jpg new file mode 100644 index 00000000..a0d65904 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-we-bury-the-dead-2026-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-white-hot-the-rise-fall-of-abercrombie-fitch-2022-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/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/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-white-hot-the-rise-fall-of-abercrombie-fitch-2022-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-white-hot-the-rise-fall-of-abercrombie-fitch-2022-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/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/wolfskin-design/src/debug/assets/wolfskin-home/assets/movie-white-hot-the-rise-fall-of-abercrombie-fitch-2022-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-12-monkeys-2015-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-12-monkeys-2015-backdrop.jpg new file mode 100644 index 00000000..3d2825f5 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-12-monkeys-2015-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-12-monkeys-2015-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-12-monkeys-2015-poster.jpg new file mode 100644 index 00000000..96e3596e Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-12-monkeys-2015-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-56-days-2026-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-56-days-2026-backdrop.jpg new file mode 100644 index 00000000..dcda667a Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-56-days-2026-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-56-days-2026-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-56-days-2026-poster.jpg new file mode 100644 index 00000000..64ca587b Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-56-days-2026-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-90-day-fiance-before-the-90-days-2017-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/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/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-90-day-fiance-before-the-90-days-2017-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-90-day-fiance-before-the-90-days-2017-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/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/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-90-day-fiance-before-the-90-days-2017-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-90-day-fiance-the-other-way-2019-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-90-day-fiance-the-other-way-2019-backdrop.jpg new file mode 100644 index 00000000..b18891fd Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-90-day-fiance-the-other-way-2019-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-90-day-fiance-the-other-way-2019-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-90-day-fiance-the-other-way-2019-poster.jpg new file mode 100644 index 00000000..b4d256eb Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-90-day-fiance-the-other-way-2019-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-a-knight-of-the-seven-kingdoms-2026-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-a-knight-of-the-seven-kingdoms-2026-backdrop.jpg new file mode 100644 index 00000000..ce12fc71 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-a-knight-of-the-seven-kingdoms-2026-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-a-knight-of-the-seven-kingdoms-2026-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-a-knight-of-the-seven-kingdoms-2026-poster.jpg new file mode 100644 index 00000000..79f1351c Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-a-knight-of-the-seven-kingdoms-2026-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-absolutely-fabulous-1992-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-absolutely-fabulous-1992-backdrop.jpg new file mode 100644 index 00000000..2df9722d Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-absolutely-fabulous-1992-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-absolutely-fabulous-1992-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-absolutely-fabulous-1992-poster.jpg new file mode 100644 index 00000000..afd371a9 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-absolutely-fabulous-1992-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-accel-world-2012-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-accel-world-2012-backdrop.jpg new file mode 100644 index 00000000..1f5f6bdc Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-accel-world-2012-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-accel-world-2012-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-accel-world-2012-poster.jpg new file mode 100644 index 00000000..207b1dd1 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-accel-world-2012-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-accused-2023-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-accused-2023-backdrop.jpg new file mode 100644 index 00000000..d41c6397 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-accused-2023-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-accused-2023-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-accused-2023-poster.jpg new file mode 100644 index 00000000..74af010a Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-accused-2023-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-adults-2025-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-adults-2025-backdrop.jpg new file mode 100644 index 00000000..9cc0ffe2 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-adults-2025-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-adults-2025-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-adults-2025-poster.jpg new file mode 100644 index 00000000..53a7ba33 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-adults-2025-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-after-life-2019-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-after-life-2019-backdrop.jpg new file mode 100644 index 00000000..eaf9a4fa Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-after-life-2019-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-after-life-2019-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-after-life-2019-poster.jpg new file mode 100644 index 00000000..4fae4e33 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-after-life-2019-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-agatha-christie-s-marple-2004-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-agatha-christie-s-marple-2004-backdrop.jpg new file mode 100644 index 00000000..0c52f2e6 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-agatha-christie-s-marple-2004-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-agatha-christie-s-marple-2004-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-agatha-christie-s-marple-2004-poster.jpg new file mode 100644 index 00000000..ea7dda99 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-agatha-christie-s-marple-2004-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-ahsoka-2023-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-ahsoka-2023-backdrop.jpg new file mode 100644 index 00000000..7343fb32 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-ahsoka-2023-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-ahsoka-2023-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-ahsoka-2023-poster.jpg new file mode 100644 index 00000000..a29d6cb4 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-ahsoka-2023-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-all-american-homecoming-2022-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-all-american-homecoming-2022-backdrop.jpg new file mode 100644 index 00000000..09fbb9dc Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-all-american-homecoming-2022-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-all-american-homecoming-2022-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-all-american-homecoming-2022-poster.jpg new file mode 100644 index 00000000..09ccfc33 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-all-american-homecoming-2022-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-all-creatures-great-and-small-1978-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-all-creatures-great-and-small-1978-backdrop.jpg new file mode 100644 index 00000000..909ae1ba Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-all-creatures-great-and-small-1978-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-all-creatures-great-and-small-1978-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-all-creatures-great-and-small-1978-poster.jpg new file mode 100644 index 00000000..5b1666e6 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-all-creatures-great-and-small-1978-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-all-creatures-great-small-2020-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-all-creatures-great-small-2020-backdrop.jpg new file mode 100644 index 00000000..d2bcc88d Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-all-creatures-great-small-2020-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-all-creatures-great-small-2020-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-all-creatures-great-small-2020-poster.jpg new file mode 100644 index 00000000..b506f990 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-all-creatures-great-small-2020-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-american-crime-story-2016-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-american-crime-story-2016-backdrop.jpg new file mode 100644 index 00000000..84944d39 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-american-crime-story-2016-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-american-crime-story-2016-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-american-crime-story-2016-poster.jpg new file mode 100644 index 00000000..4086bf17 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-american-crime-story-2016-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-american-dad-2005-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-american-dad-2005-backdrop.jpg new file mode 100644 index 00000000..75a1948b Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-american-dad-2005-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-american-dad-2005-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-american-dad-2005-poster.jpg new file mode 100644 index 00000000..1f9e239a Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-american-dad-2005-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-american-gods-2017-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-american-gods-2017-backdrop.jpg new file mode 100644 index 00000000..4817c690 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-american-gods-2017-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-american-gods-2017-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-american-gods-2017-poster.jpg new file mode 100644 index 00000000..c02c46da Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-american-gods-2017-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-american-housewife-2016-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-american-housewife-2016-backdrop.jpg new file mode 100644 index 00000000..bf758ae0 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-american-housewife-2016-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-american-housewife-2016-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-american-housewife-2016-poster.jpg new file mode 100644 index 00000000..5f92bf05 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-american-housewife-2016-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-american-primeval-2025-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-american-primeval-2025-backdrop.jpg new file mode 100644 index 00000000..c3bfe5e1 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-american-primeval-2025-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-american-primeval-2025-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-american-primeval-2025-poster.jpg new file mode 100644 index 00000000..7f10e6aa Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-american-primeval-2025-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-and-just-like-that-2021-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-and-just-like-that-2021-backdrop.jpg new file mode 100644 index 00000000..59c981e5 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-and-just-like-that-2021-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-and-just-like-that-2021-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-and-just-like-that-2021-poster.jpg new file mode 100644 index 00000000..9904c74b Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-and-just-like-that-2021-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-andor-2022-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-andor-2022-backdrop.jpg new file mode 100644 index 00000000..a874a0c0 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-andor-2022-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-andor-2022-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-andor-2022-poster.jpg new file mode 100644 index 00000000..df5dc498 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-andor-2022-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-andromeda-2000-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-andromeda-2000-backdrop.jpg new file mode 100644 index 00000000..199249d7 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-andromeda-2000-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-andromeda-2000-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-andromeda-2000-poster.jpg new file mode 100644 index 00000000..67b766b0 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-andromeda-2000-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-anne-with-an-e-2017-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-anne-with-an-e-2017-backdrop.jpg new file mode 100644 index 00000000..15e325c9 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-anne-with-an-e-2017-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-anne-with-an-e-2017-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-anne-with-an-e-2017-poster.jpg new file mode 100644 index 00000000..55d443b4 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-anne-with-an-e-2017-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-aqua-teen-hunger-force-2000-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-aqua-teen-hunger-force-2000-backdrop.jpg new file mode 100644 index 00000000..d0e37959 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-aqua-teen-hunger-force-2000-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-aqua-teen-hunger-force-2000-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-aqua-teen-hunger-force-2000-poster.jpg new file mode 100644 index 00000000..e0c97321 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-aqua-teen-hunger-force-2000-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-arifureta-from-commonplace-to-world-s-strongest-2019-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/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/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-arifureta-from-commonplace-to-world-s-strongest-2019-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-arifureta-from-commonplace-to-world-s-strongest-2019-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/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/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-arifureta-from-commonplace-to-world-s-strongest-2019-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-arrested-development-2003-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-arrested-development-2003-backdrop.jpg new file mode 100644 index 00000000..ecb7e8cd Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-arrested-development-2003-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-arrested-development-2003-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-arrested-development-2003-poster.jpg new file mode 100644 index 00000000..02b0dd43 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-arrested-development-2003-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-arrow-2012-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-arrow-2012-backdrop.jpg new file mode 100644 index 00000000..b32f5aca Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-arrow-2012-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-arrow-2012-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-arrow-2012-poster.jpg new file mode 100644 index 00000000..adee3bb7 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-arrow-2012-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-atlantis-2013-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-atlantis-2013-backdrop.jpg new file mode 100644 index 00000000..39ecc0d6 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-atlantis-2013-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-atlantis-2013-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-atlantis-2013-poster.jpg new file mode 100644 index 00000000..5cd5f098 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-atlantis-2013-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-avatar-seven-havens-2026-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-avatar-seven-havens-2026-backdrop.jpg new file mode 100644 index 00000000..dacfd59e Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-avatar-seven-havens-2026-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-avatar-seven-havens-2026-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-avatar-seven-havens-2026-poster.jpg new file mode 100644 index 00000000..61f459c6 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-avatar-seven-havens-2026-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-avatar-the-last-airbender-2005-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-avatar-the-last-airbender-2005-backdrop.jpg new file mode 100644 index 00000000..f60120da Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-avatar-the-last-airbender-2005-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-avatar-the-last-airbender-2005-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-avatar-the-last-airbender-2005-poster.jpg new file mode 100644 index 00000000..6f99263a Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-avatar-the-last-airbender-2005-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-avatar-the-last-airbender-2024-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-avatar-the-last-airbender-2024-backdrop.jpg new file mode 100644 index 00000000..ad740218 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-avatar-the-last-airbender-2024-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-avatar-the-last-airbender-2024-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-avatar-the-last-airbender-2024-poster.jpg new file mode 100644 index 00000000..9d46c2ff Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-avatar-the-last-airbender-2024-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-awkward-2011-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-awkward-2011-backdrop.jpg new file mode 100644 index 00000000..1ca5e56c Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-awkward-2011-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-awkward-2011-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-awkward-2011-poster.jpg new file mode 100644 index 00000000..8da6e8e2 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-awkward-2011-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-b-b-vol-liefde-2021-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-b-b-vol-liefde-2021-backdrop.jpg new file mode 100644 index 00000000..57ece15e Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-b-b-vol-liefde-2021-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-b-b-vol-liefde-2021-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-b-b-vol-liefde-2021-poster.jpg new file mode 100644 index 00000000..a12ac33e Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-b-b-vol-liefde-2021-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-back-to-life-2019-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-back-to-life-2019-backdrop.jpg new file mode 100644 index 00000000..7ffef681 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-back-to-life-2019-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-back-to-life-2019-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-back-to-life-2019-poster.jpg new file mode 100644 index 00000000..b00730a8 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-back-to-life-2019-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-ballers-2015-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-ballers-2015-backdrop.jpg new file mode 100644 index 00000000..c4685e84 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-ballers-2015-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-ballers-2015-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-ballers-2015-poster.jpg new file mode 100644 index 00000000..f1c18f69 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-ballers-2015-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-baretta-1975-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-baretta-1975-backdrop.jpg new file mode 100644 index 00000000..4d61dfbe Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-baretta-1975-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-baretta-1975-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-baretta-1975-poster.jpg new file mode 100644 index 00000000..ceef0c9d Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-baretta-1975-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-based-on-a-true-story-2023-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-based-on-a-true-story-2023-backdrop.jpg new file mode 100644 index 00000000..b59c0e58 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-based-on-a-true-story-2023-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-based-on-a-true-story-2023-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-based-on-a-true-story-2023-poster.jpg new file mode 100644 index 00000000..386d3d90 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-based-on-a-true-story-2023-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-batman-the-brave-and-the-bold-2008-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-batman-the-brave-and-the-bold-2008-backdrop.jpg new file mode 100644 index 00000000..69e567cb Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-batman-the-brave-and-the-bold-2008-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-batman-the-brave-and-the-bold-2008-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-batman-the-brave-and-the-bold-2008-poster.jpg new file mode 100644 index 00000000..a864f50c Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-batman-the-brave-and-the-bold-2008-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-batwoman-2019-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-batwoman-2019-backdrop.jpg new file mode 100644 index 00000000..d04a1a20 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-batwoman-2019-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-batwoman-2019-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-batwoman-2019-poster.jpg new file mode 100644 index 00000000..7ca86592 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-batwoman-2019-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-beat-bobby-flay-2013-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-beat-bobby-flay-2013-backdrop.jpg new file mode 100644 index 00000000..e6c36ddd Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-beat-bobby-flay-2013-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-beat-bobby-flay-2013-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-beat-bobby-flay-2013-poster.jpg new file mode 100644 index 00000000..fe11122d Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-beat-bobby-flay-2013-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-benson-1979-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-benson-1979-backdrop.jpg new file mode 100644 index 00000000..32bd63a9 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-benson-1979-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-benson-1979-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-benson-1979-poster.jpg new file mode 100644 index 00000000..fe264c76 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-benson-1979-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-big-boys-2022-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-big-boys-2022-backdrop.jpg new file mode 100644 index 00000000..c7c91e2d Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-big-boys-2022-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-big-boys-2022-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-big-boys-2022-poster.jpg new file mode 100644 index 00000000..93e1e86a Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-big-boys-2022-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-big-city-greens-2018-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-big-city-greens-2018-backdrop.jpg new file mode 100644 index 00000000..381774e7 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-big-city-greens-2018-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-big-city-greens-2018-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-big-city-greens-2018-poster.jpg new file mode 100644 index 00000000..ef024946 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-big-city-greens-2018-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-big-mouth-2017-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-big-mouth-2017-backdrop.jpg new file mode 100644 index 00000000..760e0262 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-big-mouth-2017-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-big-mouth-2017-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-big-mouth-2017-poster.jpg new file mode 100644 index 00000000..4f535fec Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-big-mouth-2017-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-black-ish-2014-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-black-ish-2014-backdrop.jpg new file mode 100644 index 00000000..967fb621 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-black-ish-2014-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-black-ish-2014-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-black-ish-2014-poster.jpg new file mode 100644 index 00000000..97c885ad Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-black-ish-2014-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-black-lightning-2018-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-black-lightning-2018-backdrop.jpg new file mode 100644 index 00000000..3a0854c9 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-black-lightning-2018-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-black-lightning-2018-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-black-lightning-2018-poster.jpg new file mode 100644 index 00000000..733f5cbc Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-black-lightning-2018-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-black-rabbit-2025-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-black-rabbit-2025-backdrop.jpg new file mode 100644 index 00000000..48df9d46 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-black-rabbit-2025-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-black-rabbit-2025-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-black-rabbit-2025-poster.jpg new file mode 100644 index 00000000..3ff9aeed Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-black-rabbit-2025-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-bleach-2004-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-bleach-2004-backdrop.jpg new file mode 100644 index 00000000..ef8eaaa3 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-bleach-2004-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-bleach-2004-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-bleach-2004-poster.jpg new file mode 100644 index 00000000..b233e033 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-bleach-2004-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-blue-planet-ii-2017-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-blue-planet-ii-2017-backdrop.jpg new file mode 100644 index 00000000..8175e568 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-blue-planet-ii-2017-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-blue-planet-ii-2017-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-blue-planet-ii-2017-poster.jpg new file mode 100644 index 00000000..5e193ab2 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-blue-planet-ii-2017-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-brassic-2019-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-brassic-2019-backdrop.jpg new file mode 100644 index 00000000..86d3d806 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-brassic-2019-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-brassic-2019-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-brassic-2019-poster.jpg new file mode 100644 index 00000000..73b77df2 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-brassic-2019-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-breaking-bad-2008-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-breaking-bad-2008-backdrop.jpg new file mode 100644 index 00000000..e4ac4ae3 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-breaking-bad-2008-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-breaking-bad-2008-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-breaking-bad-2008-poster.jpg new file mode 100644 index 00000000..c4f7217c Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-breaking-bad-2008-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-breeders-2020-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-breeders-2020-backdrop.jpg new file mode 100644 index 00000000..9d95ba77 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-breeders-2020-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-breeders-2020-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-breeders-2020-poster.jpg new file mode 100644 index 00000000..49c61208 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-breeders-2020-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-californication-2007-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-californication-2007-backdrop.jpg new file mode 100644 index 00000000..ffff979b Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-californication-2007-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-californication-2007-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-californication-2007-poster.jpg new file mode 100644 index 00000000..38428e76 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-californication-2007-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-call-the-midwife-2012-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-call-the-midwife-2012-backdrop.jpg new file mode 100644 index 00000000..1ee28b8d Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-call-the-midwife-2012-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-call-the-midwife-2012-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-call-the-midwife-2012-poster.jpg new file mode 100644 index 00000000..466a1a7b Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-call-the-midwife-2012-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-chainsaw-man-2022-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-chainsaw-man-2022-backdrop.jpg new file mode 100644 index 00000000..f1fbee3e Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-chainsaw-man-2022-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-chainsaw-man-2022-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-chainsaw-man-2022-poster.jpg new file mode 100644 index 00000000..a3779772 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-chainsaw-man-2022-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-chernobyl-2019-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-chernobyl-2019-backdrop.jpg new file mode 100644 index 00000000..818a6665 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-chernobyl-2019-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-chernobyl-2019-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-chernobyl-2019-poster.jpg new file mode 100644 index 00000000..7664f4b0 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-chernobyl-2019-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-community-2009-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-community-2009-backdrop.jpg new file mode 100644 index 00000000..8deb8d5c Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-community-2009-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-community-2009-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-community-2009-poster.jpg new file mode 100644 index 00000000..e75072ae Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-community-2009-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-cyberpunk-edgerunners-2022-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-cyberpunk-edgerunners-2022-backdrop.jpg new file mode 100644 index 00000000..2920cf5a Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-cyberpunk-edgerunners-2022-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-cyberpunk-edgerunners-2022-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-cyberpunk-edgerunners-2022-poster.jpg new file mode 100644 index 00000000..8d338532 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-cyberpunk-edgerunners-2022-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-dan-da-dan-2024-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-dan-da-dan-2024-backdrop.jpg new file mode 100644 index 00000000..0cbc4b07 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-dan-da-dan-2024-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-dan-da-dan-2024-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-dan-da-dan-2024-poster.jpg new file mode 100644 index 00000000..f42cec16 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-dan-da-dan-2024-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-dead-end-job-2026-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-dead-end-job-2026-backdrop.jpg new file mode 100644 index 00000000..3525c2fc Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-dead-end-job-2026-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-dead-end-job-2026-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-dead-end-job-2026-poster.jpg new file mode 100644 index 00000000..f5546d88 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-dead-end-job-2026-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-derry-girls-2018-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-derry-girls-2018-backdrop.jpg new file mode 100644 index 00000000..76fae98c Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-derry-girls-2018-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-derry-girls-2018-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-derry-girls-2018-poster.jpg new file mode 100644 index 00000000..56a8cd79 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-derry-girls-2018-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-desperate-housewives-2004-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-desperate-housewives-2004-backdrop.jpg new file mode 100644 index 00000000..40ec3001 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-desperate-housewives-2004-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-desperate-housewives-2004-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-desperate-housewives-2004-poster.jpg new file mode 100644 index 00000000..0c4c38bc Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-desperate-housewives-2004-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-dopesick-2021-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-dopesick-2021-backdrop.jpg new file mode 100644 index 00000000..8d9f2727 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-dopesick-2021-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-dopesick-2021-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-dopesick-2021-poster.jpg new file mode 100644 index 00000000..ac0b5900 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-dopesick-2021-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-dr-stone-2019-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-dr-stone-2019-backdrop.jpg new file mode 100644 index 00000000..5ac396b6 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-dr-stone-2019-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-dr-stone-2019-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-dr-stone-2019-poster.jpg new file mode 100644 index 00000000..0783ed8c Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-dr-stone-2019-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-dune-prophecy-2024-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-dune-prophecy-2024-backdrop.jpg new file mode 100644 index 00000000..5b940b9f Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-dune-prophecy-2024-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-dune-prophecy-2024-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-dune-prophecy-2024-poster.jpg new file mode 100644 index 00000000..eef2a4f3 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-dune-prophecy-2024-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-east-of-eden-2026-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-east-of-eden-2026-backdrop.jpg new file mode 100644 index 00000000..89dfafbf Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-east-of-eden-2026-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-east-of-eden-2026-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-east-of-eden-2026-poster.jpg new file mode 100644 index 00000000..6f06f2c9 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-east-of-eden-2026-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-etoile-2025-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-etoile-2025-backdrop.jpg new file mode 100644 index 00000000..a8e9576c Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-etoile-2025-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-etoile-2025-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-etoile-2025-poster.jpg new file mode 100644 index 00000000..f078bc89 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-etoile-2025-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-fallout-2024-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-fallout-2024-backdrop.jpg new file mode 100644 index 00000000..c9d6a190 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-fallout-2024-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-fallout-2024-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-fallout-2024-poster.jpg new file mode 100644 index 00000000..5e87a07d Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-fallout-2024-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-fawlty-towers-1975-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-fawlty-towers-1975-backdrop.jpg new file mode 100644 index 00000000..2c9c258f Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-fawlty-towers-1975-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-fawlty-towers-1975-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-fawlty-towers-1975-poster.jpg new file mode 100644 index 00000000..53b406da Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-fawlty-towers-1975-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-firefly-2002-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-firefly-2002-backdrop.jpg new file mode 100644 index 00000000..fa3b7604 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-firefly-2002-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-firefly-2002-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-firefly-2002-poster.jpg new file mode 100644 index 00000000..9ad599b3 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-firefly-2002-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-fleabag-2016-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-fleabag-2016-backdrop.jpg new file mode 100644 index 00000000..e5a83462 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-fleabag-2016-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-fleabag-2016-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-fleabag-2016-poster.jpg new file mode 100644 index 00000000..bd3ccea5 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-fleabag-2016-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-frieren-beyond-journey-s-end-2023-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-frieren-beyond-journey-s-end-2023-backdrop.jpg new file mode 100644 index 00000000..a42a2c94 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-frieren-beyond-journey-s-end-2023-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-frieren-beyond-journey-s-end-2023-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-frieren-beyond-journey-s-end-2023-poster.jpg new file mode 100644 index 00000000..cb94104e Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-frieren-beyond-journey-s-end-2023-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-fullmetal-alchemist-brotherhood-2009-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-fullmetal-alchemist-brotherhood-2009-backdrop.jpg new file mode 100644 index 00000000..91ce4909 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-fullmetal-alchemist-brotherhood-2009-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-fullmetal-alchemist-brotherhood-2009-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-fullmetal-alchemist-brotherhood-2009-poster.jpg new file mode 100644 index 00000000..935e4944 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-fullmetal-alchemist-brotherhood-2009-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-gilmore-girls-2000-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-gilmore-girls-2000-backdrop.jpg new file mode 100644 index 00000000..b42ba953 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-gilmore-girls-2000-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-gilmore-girls-2000-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-gilmore-girls-2000-poster.jpg new file mode 100644 index 00000000..23b668cb Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-gilmore-girls-2000-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-ginny-georgia-2021-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-ginny-georgia-2021-backdrop.jpg new file mode 100644 index 00000000..1271d577 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-ginny-georgia-2021-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-ginny-georgia-2021-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-ginny-georgia-2021-poster.jpg new file mode 100644 index 00000000..1fca7bf6 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-ginny-georgia-2021-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-gravity-falls-2012-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-gravity-falls-2012-backdrop.jpg new file mode 100644 index 00000000..b9ef40fa Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-gravity-falls-2012-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-gravity-falls-2012-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-gravity-falls-2012-poster.jpg new file mode 100644 index 00000000..41c051e0 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-gravity-falls-2012-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-hellcats-2010-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-hellcats-2010-backdrop.jpg new file mode 100644 index 00000000..38e856b0 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-hellcats-2010-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-hellcats-2010-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-hellcats-2010-poster.jpg new file mode 100644 index 00000000..2427108b Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-hellcats-2010-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-high-potential-2024-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-high-potential-2024-backdrop.jpg new file mode 100644 index 00000000..66bb4d00 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-high-potential-2024-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-high-potential-2024-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-high-potential-2024-poster.jpg new file mode 100644 index 00000000..00a07ca7 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-high-potential-2024-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-how-to-change-your-mind-2022-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-how-to-change-your-mind-2022-backdrop.jpg new file mode 100644 index 00000000..9e4171e3 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-how-to-change-your-mind-2022-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-how-to-change-your-mind-2022-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-how-to-change-your-mind-2022-poster.jpg new file mode 100644 index 00000000..e80c7999 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-how-to-change-your-mind-2022-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-how-to-get-to-heaven-from-belfast-2026-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/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/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-how-to-get-to-heaven-from-belfast-2026-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-how-to-get-to-heaven-from-belfast-2026-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/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/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-how-to-get-to-heaven-from-belfast-2026-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-interrogation-2020-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-interrogation-2020-backdrop.jpg new file mode 100644 index 00000000..c347e016 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-interrogation-2020-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-interrogation-2020-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-interrogation-2020-poster.jpg new file mode 100644 index 00000000..f3a21b66 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-interrogation-2020-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-invincible-2021-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-invincible-2021-backdrop.jpg new file mode 100644 index 00000000..e8120387 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-invincible-2021-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-invincible-2021-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-invincible-2021-poster.jpg new file mode 100644 index 00000000..c278e37e Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-invincible-2021-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-it-s-always-sunny-in-philadelphia-2005-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-it-s-always-sunny-in-philadelphia-2005-backdrop.jpg new file mode 100644 index 00000000..cbdc6525 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-it-s-always-sunny-in-philadelphia-2005-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-it-s-always-sunny-in-philadelphia-2005-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-it-s-always-sunny-in-philadelphia-2005-poster.jpg new file mode 100644 index 00000000..652923ee Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-it-s-always-sunny-in-philadelphia-2005-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-jujutsu-kaisen-2020-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-jujutsu-kaisen-2020-backdrop.jpg new file mode 100644 index 00000000..79e7d6bd Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-jujutsu-kaisen-2020-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-jujutsu-kaisen-2020-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-jujutsu-kaisen-2020-poster.jpg new file mode 100644 index 00000000..45f5ff22 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-jujutsu-kaisen-2020-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-kaiju-no-8-2024-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-kaiju-no-8-2024-backdrop.jpg new file mode 100644 index 00000000..480b2a8e Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-kaiju-no-8-2024-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-kaiju-no-8-2024-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-kaiju-no-8-2024-poster.jpg new file mode 100644 index 00000000..062496fa Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-kaiju-no-8-2024-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-landman-2024-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-landman-2024-backdrop.jpg new file mode 100644 index 00000000..009fbfc2 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-landman-2024-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-landman-2024-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-landman-2024-poster.jpg new file mode 100644 index 00000000..3d348798 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-landman-2024-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-last-samurai-standing-2025-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-last-samurai-standing-2025-backdrop.jpg new file mode 100644 index 00000000..c85a210b Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-last-samurai-standing-2025-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-last-samurai-standing-2025-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-last-samurai-standing-2025-poster.jpg new file mode 100644 index 00000000..a9789909 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-last-samurai-standing-2025-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-lord-of-the-flies-2026-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-lord-of-the-flies-2026-backdrop.jpg new file mode 100644 index 00000000..1249a8f3 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-lord-of-the-flies-2026-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-lord-of-the-flies-2026-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-lord-of-the-flies-2026-poster.jpg new file mode 100644 index 00000000..b1229ed8 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-lord-of-the-flies-2026-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-love-death-robots-2019-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-love-death-robots-2019-backdrop.jpg new file mode 100644 index 00000000..3694bfc4 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-love-death-robots-2019-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-love-death-robots-2019-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-love-death-robots-2019-poster.jpg new file mode 100644 index 00000000..08449aa9 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-love-death-robots-2019-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-love-is-blind-2020-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-love-is-blind-2020-backdrop.jpg new file mode 100644 index 00000000..5cea5b52 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-love-is-blind-2020-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-love-is-blind-2020-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-love-is-blind-2020-poster.jpg new file mode 100644 index 00000000..62ccf357 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-love-is-blind-2020-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-maniac-2018-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-maniac-2018-backdrop.jpg new file mode 100644 index 00000000..9d46de1d Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-maniac-2018-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-maniac-2018-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-maniac-2018-poster.jpg new file mode 100644 index 00000000..a1985217 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-maniac-2018-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-maximum-pleasure-guaranteed-2026-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-maximum-pleasure-guaranteed-2026-backdrop.jpg new file mode 100644 index 00000000..20748a70 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-maximum-pleasure-guaranteed-2026-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-maximum-pleasure-guaranteed-2026-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-maximum-pleasure-guaranteed-2026-poster.jpg new file mode 100644 index 00000000..abe1b2e2 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-maximum-pleasure-guaranteed-2026-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-minx-2022-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-minx-2022-backdrop.jpg new file mode 100644 index 00000000..bd459fde Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-minx-2022-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-minx-2022-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-minx-2022-poster.jpg new file mode 100644 index 00000000..a8797fe5 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-minx-2022-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-mussolini-son-of-the-century-2024-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-mussolini-son-of-the-century-2024-backdrop.jpg new file mode 100644 index 00000000..21674163 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-mussolini-son-of-the-century-2024-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-mussolini-son-of-the-century-2024-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-mussolini-son-of-the-century-2024-poster.jpg new file mode 100644 index 00000000..925d5c49 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-mussolini-son-of-the-century-2024-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-my-hero-academia-2016-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-my-hero-academia-2016-backdrop.jpg new file mode 100644 index 00000000..f5b2f94c Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-my-hero-academia-2016-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-my-hero-academia-2016-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-my-hero-academia-2016-poster.jpg new file mode 100644 index 00000000..cf571b93 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-my-hero-academia-2016-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-neon-genesis-evangelion-1995-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-neon-genesis-evangelion-1995-backdrop.jpg new file mode 100644 index 00000000..d80e17c1 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-neon-genesis-evangelion-1995-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-neon-genesis-evangelion-1995-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-neon-genesis-evangelion-1995-poster.jpg new file mode 100644 index 00000000..f86cd698 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-neon-genesis-evangelion-1995-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-norsemen-2016-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-norsemen-2016-backdrop.jpg new file mode 100644 index 00000000..58805e67 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-norsemen-2016-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-norsemen-2016-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-norsemen-2016-poster.jpg new file mode 100644 index 00000000..e7206b88 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-norsemen-2016-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-offspring-2010-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-offspring-2010-backdrop.jpg new file mode 100644 index 00000000..bc22e11a Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-offspring-2010-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-offspring-2010-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-offspring-2010-poster.jpg new file mode 100644 index 00000000..ae305043 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-offspring-2010-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-one-piece-2023-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-one-piece-2023-backdrop.jpg new file mode 100644 index 00000000..3fa4a282 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-one-piece-2023-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-one-piece-2023-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-one-piece-2023-poster.jpg new file mode 100644 index 00000000..286e6260 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-one-piece-2023-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-only-murders-in-the-building-2021-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-only-murders-in-the-building-2021-backdrop.jpg new file mode 100644 index 00000000..0b768d54 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-only-murders-in-the-building-2021-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-only-murders-in-the-building-2021-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-only-murders-in-the-building-2021-poster.jpg new file mode 100644 index 00000000..a6b147a4 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-only-murders-in-the-building-2021-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-outlander-blood-of-my-blood-2025-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-outlander-blood-of-my-blood-2025-backdrop.jpg new file mode 100644 index 00000000..23a242da Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-outlander-blood-of-my-blood-2025-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-outlander-blood-of-my-blood-2025-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-outlander-blood-of-my-blood-2025-poster.jpg new file mode 100644 index 00000000..930943b0 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-outlander-blood-of-my-blood-2025-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-over-the-garden-wall-2014-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-over-the-garden-wall-2014-backdrop.jpg new file mode 100644 index 00000000..43ac4428 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-over-the-garden-wall-2014-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-over-the-garden-wall-2014-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-over-the-garden-wall-2014-poster.jpg new file mode 100644 index 00000000..34b1ed84 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-over-the-garden-wall-2014-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-peaky-blinders-2013-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-peaky-blinders-2013-backdrop.jpg new file mode 100644 index 00000000..da587ec3 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-peaky-blinders-2013-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-peaky-blinders-2013-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-peaky-blinders-2013-poster.jpg new file mode 100644 index 00000000..25ceea88 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-peaky-blinders-2013-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-pluribus-2025-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-pluribus-2025-backdrop.jpg new file mode 100644 index 00000000..e64c0268 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-pluribus-2025-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-pluribus-2025-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-pluribus-2025-poster.jpg new file mode 100644 index 00000000..1ea4711b Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-pluribus-2025-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-record-of-ragnarok-2021-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-record-of-ragnarok-2021-backdrop.jpg new file mode 100644 index 00000000..d68a4060 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-record-of-ragnarok-2021-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-record-of-ragnarok-2021-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-record-of-ragnarok-2021-poster.jpg new file mode 100644 index 00000000..97d481d6 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-record-of-ragnarok-2021-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-rick-and-morty-2013-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-rick-and-morty-2013-backdrop.jpg new file mode 100644 index 00000000..1c4c0ffc Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-rick-and-morty-2013-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-rick-and-morty-2013-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-rick-and-morty-2013-poster.jpg new file mode 100644 index 00000000..b95c4890 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-rick-and-morty-2013-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-ripley-2024-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-ripley-2024-backdrop.jpg new file mode 100644 index 00000000..2e7399c1 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-ripley-2024-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-ripley-2024-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-ripley-2024-poster.jpg new file mode 100644 index 00000000..de0b32dd Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-ripley-2024-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-rivals-2024-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-rivals-2024-backdrop.jpg new file mode 100644 index 00000000..fe7025e4 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-rivals-2024-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-rivals-2024-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-rivals-2024-poster.jpg new file mode 100644 index 00000000..296ac67a Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-rivals-2024-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-rooster-2026-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-rooster-2026-backdrop.jpg new file mode 100644 index 00000000..f0f7a19e Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-rooster-2026-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-rooster-2026-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-rooster-2026-poster.jpg new file mode 100644 index 00000000..b698f8a6 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-rooster-2026-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-sakamoto-days-2025-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-sakamoto-days-2025-backdrop.jpg new file mode 100644 index 00000000..3186e89a Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-sakamoto-days-2025-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-sakamoto-days-2025-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-sakamoto-days-2025-poster.jpg new file mode 100644 index 00000000..ed9358b3 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-sakamoto-days-2025-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-say-nothing-2024-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-say-nothing-2024-backdrop.jpg new file mode 100644 index 00000000..445862dc Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-say-nothing-2024-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-say-nothing-2024-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-say-nothing-2024-poster.jpg new file mode 100644 index 00000000..cf50fc09 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-say-nothing-2024-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-secret-level-2024-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-secret-level-2024-backdrop.jpg new file mode 100644 index 00000000..54d2a13e Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-secret-level-2024-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-secret-level-2024-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-secret-level-2024-poster.jpg new file mode 100644 index 00000000..1b6e67f6 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-secret-level-2024-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-sentenced-to-be-a-hero-2025-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-sentenced-to-be-a-hero-2025-backdrop.jpg new file mode 100644 index 00000000..8d081a15 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-sentenced-to-be-a-hero-2025-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-sentenced-to-be-a-hero-2025-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-sentenced-to-be-a-hero-2025-poster.jpg new file mode 100644 index 00000000..2fb1c815 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-sentenced-to-be-a-hero-2025-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-severance-2022-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-severance-2022-backdrop.jpg new file mode 100644 index 00000000..a24c03bd Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-severance-2022-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-severance-2022-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-severance-2022-poster.jpg new file mode 100644 index 00000000..5a29d6f9 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-severance-2022-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-sex-and-the-city-1998-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-sex-and-the-city-1998-backdrop.jpg new file mode 100644 index 00000000..57beacc1 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-sex-and-the-city-1998-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-sex-and-the-city-1998-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-sex-and-the-city-1998-poster.jpg new file mode 100644 index 00000000..e546cc6d Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-sex-and-the-city-1998-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-silo-2023-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-silo-2023-backdrop.jpg new file mode 100644 index 00000000..bcc1c4b7 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-silo-2023-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-silo-2023-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-silo-2023-poster.jpg new file mode 100644 index 00000000..b493bc7b Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-silo-2023-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-six-feet-under-2001-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-six-feet-under-2001-backdrop.jpg new file mode 100644 index 00000000..4d2c02ad Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-six-feet-under-2001-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-six-feet-under-2001-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-six-feet-under-2001-poster.jpg new file mode 100644 index 00000000..559d6bd1 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-six-feet-under-2001-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-skymed-2022-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-skymed-2022-backdrop.jpg new file mode 100644 index 00000000..c7ec326b Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-skymed-2022-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-skymed-2022-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-skymed-2022-poster.jpg new file mode 100644 index 00000000..2540dad7 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-skymed-2022-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-so-you-think-you-can-dance-2008-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-so-you-think-you-can-dance-2008-backdrop.jpg new file mode 100644 index 00000000..feb777b2 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-so-you-think-you-can-dance-2008-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-so-you-think-you-can-dance-2008-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-so-you-think-you-can-dance-2008-poster.jpg new file mode 100644 index 00000000..a2e91496 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-so-you-think-you-can-dance-2008-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-south-park-1997-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-south-park-1997-backdrop.jpg new file mode 100644 index 00000000..cd0aec39 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-south-park-1997-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-south-park-1997-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-south-park-1997-poster.jpg new file mode 100644 index 00000000..d8568d54 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-south-park-1997-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-spy-x-family-2022-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-spy-x-family-2022-backdrop.jpg new file mode 100644 index 00000000..8764e6eb Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-spy-x-family-2022-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-spy-x-family-2022-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-spy-x-family-2022-poster.jpg new file mode 100644 index 00000000..fc326930 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-spy-x-family-2022-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-100-2014-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-100-2014-backdrop.jpg new file mode 100644 index 00000000..8d41cb22 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-100-2014-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-100-2014-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-100-2014-poster.jpg new file mode 100644 index 00000000..4d254cb0 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-100-2014-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-abbott-and-costello-show-1952-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-abbott-and-costello-show-1952-backdrop.jpg new file mode 100644 index 00000000..53e1d593 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-abbott-and-costello-show-1952-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-abbott-and-costello-show-1952-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/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/debug/assets/wolfskin-home/assets/tv-the-abbott-and-costello-show-1952-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-afterparty-2022-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-afterparty-2022-backdrop.jpg new file mode 100644 index 00000000..72ca19db Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-afterparty-2022-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-afterparty-2022-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-afterparty-2022-poster.jpg new file mode 100644 index 00000000..9beacde2 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-afterparty-2022-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-baby-sitters-club-2020-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-baby-sitters-club-2020-backdrop.jpg new file mode 100644 index 00000000..d72d7c7e Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-baby-sitters-club-2020-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-baby-sitters-club-2020-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-baby-sitters-club-2020-poster.jpg new file mode 100644 index 00000000..7e4e7be2 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-baby-sitters-club-2020-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-bear-2022-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-bear-2022-backdrop.jpg new file mode 100644 index 00000000..9c9cca57 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-bear-2022-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-bear-2022-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-bear-2022-poster.jpg new file mode 100644 index 00000000..bbf0ab28 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-bear-2022-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-beast-in-me-2025-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-beast-in-me-2025-backdrop.jpg new file mode 100644 index 00000000..e7727a79 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-beast-in-me-2025-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-beast-in-me-2025-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-beast-in-me-2025-poster.jpg new file mode 100644 index 00000000..c78c5cf0 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-beast-in-me-2025-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-bold-type-2017-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-bold-type-2017-backdrop.jpg new file mode 100644 index 00000000..dd84deab Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-bold-type-2017-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-bold-type-2017-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-bold-type-2017-poster.jpg new file mode 100644 index 00000000..f663eb8b Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-bold-type-2017-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-boys-2019-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-boys-2019-backdrop.jpg new file mode 100644 index 00000000..a834d9fb Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-boys-2019-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-boys-2019-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-boys-2019-poster.jpg new file mode 100644 index 00000000..f67ff16f Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-boys-2019-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-capture-2019-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-capture-2019-backdrop.jpg new file mode 100644 index 00000000..42256fc1 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-capture-2019-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-capture-2019-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-capture-2019-poster.jpg new file mode 100644 index 00000000..a4039011 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-capture-2019-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-diplomat-2023-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-diplomat-2023-backdrop.jpg new file mode 100644 index 00000000..b25ab5f3 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-diplomat-2023-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-diplomat-2023-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-diplomat-2023-poster.jpg new file mode 100644 index 00000000..2eb48b62 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-diplomat-2023-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-durrells-2016-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-durrells-2016-backdrop.jpg new file mode 100644 index 00000000..3f8bdeec Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-durrells-2016-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-durrells-2016-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-durrells-2016-poster.jpg new file mode 100644 index 00000000..d29448bb Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-durrells-2016-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-final-problem-2026-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-final-problem-2026-backdrop.jpg new file mode 100644 index 00000000..61a310d8 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-final-problem-2026-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-final-problem-2026-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-final-problem-2026-poster.jpg new file mode 100644 index 00000000..f0332826 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-final-problem-2026-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-four-seasons-2025-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-four-seasons-2025-backdrop.jpg new file mode 100644 index 00000000..a358664c Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-four-seasons-2025-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-four-seasons-2025-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-four-seasons-2025-poster.jpg new file mode 100644 index 00000000..fd252088 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-four-seasons-2025-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-get-down-2016-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-get-down-2016-backdrop.jpg new file mode 100644 index 00000000..1de4a3ca Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-get-down-2016-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-get-down-2016-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-get-down-2016-poster.jpg new file mode 100644 index 00000000..7bb38abd Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-get-down-2016-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-hunting-wives-2025-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-hunting-wives-2025-backdrop.jpg new file mode 100644 index 00000000..d1758fef Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-hunting-wives-2025-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-hunting-wives-2025-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-hunting-wives-2025-poster.jpg new file mode 100644 index 00000000..034273eb Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-hunting-wives-2025-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-last-of-us-2023-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-last-of-us-2023-backdrop.jpg new file mode 100644 index 00000000..fa28e38d Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-last-of-us-2023-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-last-of-us-2023-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-last-of-us-2023-poster.jpg new file mode 100644 index 00000000..7da462da Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-last-of-us-2023-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-legend-of-korra-2012-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-legend-of-korra-2012-backdrop.jpg new file mode 100644 index 00000000..ab6086bd Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-legend-of-korra-2012-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-legend-of-korra-2012-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-legend-of-korra-2012-poster.jpg new file mode 100644 index 00000000..a7086d5e Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-legend-of-korra-2012-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-lord-of-the-rings-the-rings-of-power-2022-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/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/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-lord-of-the-rings-the-rings-of-power-2022-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-lord-of-the-rings-the-rings-of-power-2022-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/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/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-lord-of-the-rings-the-rings-of-power-2022-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-lost-flowers-of-alice-hart-2023-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-lost-flowers-of-alice-hart-2023-backdrop.jpg new file mode 100644 index 00000000..c5d17d97 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-lost-flowers-of-alice-hart-2023-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-lost-flowers-of-alice-hart-2023-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-lost-flowers-of-alice-hart-2023-poster.jpg new file mode 100644 index 00000000..71407f36 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-lost-flowers-of-alice-hart-2023-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-night-manager-2016-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-night-manager-2016-backdrop.jpg new file mode 100644 index 00000000..327aa449 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-night-manager-2016-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-night-manager-2016-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-night-manager-2016-poster.jpg new file mode 100644 index 00000000..7ab2dbf5 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-night-manager-2016-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-pitt-2025-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-pitt-2025-backdrop.jpg new file mode 100644 index 00000000..b593444a Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-pitt-2025-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-pitt-2025-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-pitt-2025-poster.jpg new file mode 100644 index 00000000..ad1ec8ff Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-pitt-2025-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-tatami-galaxy-2010-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-tatami-galaxy-2010-backdrop.jpg new file mode 100644 index 00000000..a267b4cb Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-tatami-galaxy-2010-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-tatami-galaxy-2010-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-tatami-galaxy-2010-poster.jpg new file mode 100644 index 00000000..5324dc75 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-tatami-galaxy-2010-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-walk-in-2022-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-walk-in-2022-backdrop.jpg new file mode 100644 index 00000000..e37f8d5c Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-walk-in-2022-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-walk-in-2022-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-walk-in-2022-poster.jpg new file mode 100644 index 00000000..715d0c80 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-walk-in-2022-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-white-lotus-2021-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-white-lotus-2021-backdrop.jpg new file mode 100644 index 00000000..1f747323 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-white-lotus-2021-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-white-lotus-2021-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-white-lotus-2021-poster.jpg new file mode 100644 index 00000000..2f23d1fa Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-white-lotus-2021-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-witcher-2019-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-witcher-2019-backdrop.jpg new file mode 100644 index 00000000..acca834e Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-witcher-2019-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-witcher-2019-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-witcher-2019-poster.jpg new file mode 100644 index 00000000..f5eb8f59 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-the-witcher-2019-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-undercover-2019-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-undercover-2019-backdrop.jpg new file mode 100644 index 00000000..ad972c17 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-undercover-2019-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-undercover-2019-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-undercover-2019-poster.jpg new file mode 100644 index 00000000..8611eab4 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-undercover-2019-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-upload-2020-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-upload-2020-backdrop.jpg new file mode 100644 index 00000000..f350a49c Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-upload-2020-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-upload-2020-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-upload-2020-poster.jpg new file mode 100644 index 00000000..547d3b7a Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-upload-2020-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-vinland-saga-2019-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-vinland-saga-2019-backdrop.jpg new file mode 100644 index 00000000..9ae58ec4 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-vinland-saga-2019-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-vinland-saga-2019-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-vinland-saga-2019-poster.jpg new file mode 100644 index 00000000..7d0ba17a Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-vinland-saga-2019-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-widow-s-bay-2026-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-widow-s-bay-2026-backdrop.jpg new file mode 100644 index 00000000..03b1c335 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-widow-s-bay-2026-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-widow-s-bay-2026-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-widow-s-bay-2026-poster.jpg new file mode 100644 index 00000000..8c02a0d7 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-widow-s-bay-2026-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-yellowjackets-2021-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-yellowjackets-2021-backdrop.jpg new file mode 100644 index 00000000..960c2d23 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-yellowjackets-2021-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-yellowjackets-2021-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-yellowjackets-2021-poster.jpg new file mode 100644 index 00000000..c7e16a57 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-yellowjackets-2021-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-yu-yu-hakusho-1992-backdrop.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-yu-yu-hakusho-1992-backdrop.jpg new file mode 100644 index 00000000..ba65ca2d Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-yu-yu-hakusho-1992-backdrop.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-yu-yu-hakusho-1992-poster.jpg b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-yu-yu-hakusho-1992-poster.jpg new file mode 100644 index 00000000..f5843766 Binary files /dev/null and b/wolfskin-design/src/debug/assets/wolfskin-home/assets/tv-yu-yu-hakusho-1992-poster.jpg differ diff --git a/wolfskin-design/src/debug/assets/wolfskin-home/fixture.json b/wolfskin-design/src/debug/assets/wolfskin-home/fixture.json new file mode 100644 index 00000000..450f0ece --- /dev/null +++ b/wolfskin-design/src/debug/assets/wolfskin-home/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/wolfskin-design/src/debug/java/org/justinvisser/wolfskin/design/HomeFixture.kt b/wolfskin-design/src/debug/java/org/justinvisser/wolfskin/design/HomeFixture.kt new file mode 100644 index 00000000..c046c290 --- /dev/null +++ b/wolfskin-design/src/debug/java/org/justinvisser/wolfskin/design/HomeFixture.kt @@ -0,0 +1,129 @@ +package org.justinvisser.wolfskin.design + +import android.content.Context +import org.json.JSONArray +import org.json.JSONObject + +/** The locked fixture is debug-only; release variants cannot construct synthetic household data. */ +class HomeFixture(context: Context) { + private val source = context.assets.open("wolfskin-home/fixture.json").bufferedReader().use { JSONObject(it.readText()) } + private val profiles = source.getJSONArray("profiles").objects() + private val allSeries = profiles.flatMap { it.items("shows") + it.items("anime") } + private val pool = profiles.flatMap { it.items("discover") }.distinctBy { it.identity() } + private val owners = linkedMapOf>().apply { + profiles.forEach { profile -> + listOf("movies", "shows", "anime").flatMap { profile.items(it) }.forEach { item -> + getOrPut(item.identity()) { linkedSetOf() }.add(profile.getString("name")) + } + } + } + + fun snapshot(person: String, state: String = "ready"): HomeSnapshot { + val profile = profiles.first { it.getString("key") == person } + val library = HomeLibrary(profile.items("resume").map(::title), profile.items("nextUp").map(::title), profile.items("recentlyAdded").map(::title), listOf("movies", "shows", "anime").associateWith { key -> profile.items(key).map(::title) }) + val status = when (state) { "unsigned" -> HomeAvailability.SignedOut; "cond-down" -> HomeAvailability.Unavailable; "offline", "cond-cached" -> HomeAvailability.Saved; "loading" -> HomeAvailability.Loading; else -> HomeAvailability.Ready } + val continuation = if (status == HomeAvailability.SignedOut || status == HomeAvailability.Unavailable) listOf(homeDiscoverState(status)) else continuation(profile, state) + var rows = buildHomeRows(library, continuation, state != "jf-down", state == "loading") + rows = rows.map { row -> + if (!row.catalogue) row else { + fun owned(title: HomeTitle): HomeTitle { + val names = owners[title.tmdbId?.let { "${title.type}:$it" } ?: title.id].orEmpty() + val held = title.held || profile.getString("name") in names + val other = if (held) null else names.filter { it != profile.getString("name") }.takeIf { it.isNotEmpty() }?.joinToString(" and ") + return if (held == title.held && other == title.otherOwner) title else title.copy(held = held, otherOwner = other) + } + val cards = row.cards.map(::owned) + row.copy(cards = cards, allCards = if (row.allCards === row.cards) cards else row.allCards.map(::owned)) + } + } + if (state == "missing") { + val missing = profiles.flatMap { person -> listOf("movies", "shows", "anime", "discover", "nextUp", "recentlyAdded").flatMap { key -> person.items(key).filterIndexed { index, _ -> index % 3 == 0 } } }.map { it.getString("id") }.toSet() + rows = rows.map { row -> row.copy(cards = row.cards.map { if (it.id in missing) it.copy(poster = null, backdrop = null) else it }) } + } + return HomeSnapshot(rows, status, state != "jf-down" && state != "offline", if (status == HomeAvailability.Saved) "20:42" else null) + } + + private fun continuation(profile: JSONObject, state: String): List = buildList { + val picks = profile.items("discover") + val movies = picks.filter { it.getString("type") == "movie" } + val shows = picks.filter { it.getString("type") == "tv" } + val played = listOf("movies", "shows", "anime").flatMap { profile.items(it) }.filter { it.optBoolean("played") } + val groups = linkedMapOf>>() + (movies + shows).forEach { candidate -> + played.map { it to genres(it).intersect(genres(candidate)).size }.filter { it.second > 0 }.sortedByDescending { it.second }.take(2).forEach { (seed, _) -> + groups.getOrPut(seed.identity()) { seed to mutableListOf() }.second.add(candidate) + } + } + val seen = mutableSetOf() + if (state == "picks-unavailable" || state == "no-seeds") { + add(HomeRow("picks-na", HomeRowKind.Note, text = if (state == "no-seeds") "Watch something to get picks for ${profile.getString("name")}." else "Picks unavailable right now.", calm = state == "no-seeds")) + } else { + val groupRows = groups.values.take(3) + fun take(items: List, limit: Int): List = items.filter { it.identity() !in seen }.take(limit).filter { seen.add(it.identity()) } + val movieRow = take(movies, minOf(12, kotlin.math.ceil(movies.size.toDouble() / (groupRows.size + 1)).toInt())) + val showRow = take(shows, minOf(12, kotlin.math.ceil(shows.size.toDouble() / (groupRows.size + 1)).toInt())) + val remaining = (movies + shows).count { it.identity() !in seen } + val size = if (groupRows.isEmpty()) 0 else minOf(8, kotlin.math.ceil(remaining.toDouble() / groupRows.size).toInt()) + val watchedRows = groupRows.map { (seed, items) -> Triple(seed, take(items, size), items) }.filter { it.second.size >= 3 } + if (movieRow.isNotEmpty()) add(shelf("pk-m", "Movies for you", movieRow, movies.size, movies)) + if (showRow.isNotEmpty()) add(shelf("pk-s", "Series for you", showRow, shows.size, shows)) + watchedRows.take(2).forEachIndexed { index, (seed, items, all) -> add(shelf("pk-w$index", "Because you watched ${seed.getString("title")}", items, all.size, all)) } + } + val excluded = flatMap { row -> row.cards }.map { it.id }.toSet() + val base = pool.filter { it.getString("id") !in excluded } + listOf("Netflix", "Prime Video").forEachIndexed { index, service -> + val shift = if (base.isEmpty()) 0 else index * 7 % base.size + val rotated = base.drop(shift) + base.take(shift) + add(shelf("new-$index", "New on $service", rotated.take(12).sortedByDescending { it.optInt("year") })) + } + add(shelf("trending", "Trending", base.take(WolfskinTokens.Behaviour.rowLimit.toInt()), base.size, base)) + listOf("Comedy", "Animation", "Science Fiction", "Horror", "Romance").forEach { genre -> + val titles = base.filter { genre in genres(it) && (genre !in setOf("Science Fiction", "Horror", "Romance") || it.getString("type") == "movie") } + if (titles.isNotEmpty()) add(shelf("genre-$genre", genre, titles.take(WolfskinTokens.Behaviour.rowLimit.toInt()), titles.size, titles)) + } + add(HomeRow("explore", HomeRowKind.Explore, "Explore more in Discover")) + } + + private fun shelf(id: String, name: String, items: List, total: Int = items.size, allItems: List = items): HomeRow { + val cards = items.map(::title) + return HomeRow(id, HomeRowKind.Shelf, name, cards, catalogue = true, total = total, allCards = if (allItems === items) cards else allItems.map(::title)) + } + + private fun title(item: JSONObject): HomeTitle = HomeTitle( + id = item.getString("id"), title = item.getString("title"), type = item.getString("type"), + year = item.intOrNull("year"), runtimeSeconds = item.intOrNull("runtimeSeconds"), seasons = item.intOrNull("seasonCount"), + overview = item.optString("overview"), poster = item.stringOrNull("poster")?.let { "file:///android_asset/wolfskin-home/$it" }, + backdrop = item.stringOrNull("backdrop")?.let { "file:///android_asset/wolfskin-home/$it" }, + posterOnly = item.optJSONObject("artwork")?.optString("backdropKind") == "poster_crop", + seriesTitle = item.stringOrNull("seriesTitle"), season = item.intOrNull("seasonNumber"), episode = item.intOrNull("episodeNumber"), + resumeSeconds = item.optDouble("resumeSeconds", 0.0).takeIf { it.isFinite() } ?: 0.0, + played = item.optBoolean("played"), addedAt = item.optString("addedAt", ""), genres = genres(item).toList(), ratings = ratings(item), + held = item.optString("availability") == "in_library", tmdbId = item.intOrNull("tmdbId"), + ) + + private fun ratings(item: JSONObject): List { + val episode = item.optString("type") == "episode" + val series = if (episode) allSeries.firstOrNull { it.optString("title") == item.optString("seriesTitle") } else item + val source = series?.optJSONObject("ratings") ?: JSONObject().apply { + put("rtCritics", series?.opt("criticRating")); put("tmdb", series?.opt("communityRating")) + } + val result = mutableListOf() + fun score(key: String, max: Double): Double? = if (source.isNull(key)) null else source.optDouble(key).takeIf { it.isFinite() && it in 0.0..max } + score("rtCritics", 100.0)?.let { result += HomeRating(when (source.optString("rtCriticsLabel")) { "Fresh", "Certified Fresh" -> "fresh"; "Rotten" -> "rotten"; else -> "tomato" }, it, true) } + score("rtAudience", 100.0)?.let { result += HomeRating(if (source.optString("rtAudienceLabel") == "Spilled") "spilled" else "upright", it, true) } + score("imdb", 10.0)?.takeIf { source.isNull("imdbVotes") || source.optInt("imdbVotes") > 0 }?.let { result += HomeRating("imdb", it) } + if (result.isEmpty()) score("tmdb", 10.0)?.takeIf { source.isNull("tmdbVotes") || source.optInt("tmdbVotes") > 0 }?.let { result += HomeRating("tmdb", it) } + if (episode && !item.isNull("communityRating")) { + val score = item.optDouble("communityRating") + if (score.isFinite() && score in 0.0..10.0) { result.removeAll { it.mark == "tmdb" }; result += HomeRating("tmdb", score, episode = true) } + } + return result + } + + private fun genres(item: JSONObject): Set = item.optJSONArray("genres")?.let { array -> (0 until array.length()).flatMap { index -> when (val name = array.getString(index)) { "Sci-Fi & Fantasy" -> listOf("Science Fiction", "Fantasy"); "Action & Adventure" -> listOf("Action", "Adventure"); "Sci-Fi" -> listOf("Science Fiction"); else -> listOf(name) } }.toSet() }.orEmpty() + private fun JSONObject.identity(): String = if (!isNull("tmdbId")) "${optString("type")}:${optInt("tmdbId")}" else getString("id") + private fun JSONObject.items(key: String): List = getJSONArray(key).objects() + private fun JSONArray.objects(): List = List(length()) { getJSONObject(it) } + private fun JSONObject.intOrNull(key: String): Int? = if (isNull(key)) null else optInt(key) + private fun JSONObject.stringOrNull(key: String): String? = if (isNull(key)) null else optString(key).takeIf { it.isNotEmpty() } +} 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 1d5c6f23..31a0487e 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 @@ -9,44 +9,48 @@ 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 -import androidx.tv.material3.Text + import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.style.TextOverflow +import androidx.tv.material3.Text /** Captions fade without moving the reserved band under the focused poster. */ @Composable -fun Caption(title: String, facts: String, visible: Boolean, modifier: Modifier = Modifier, accentLastFact: Boolean = false, accentFirstFact: Boolean = false) { +fun Caption(title: String, facts: String, visible: Boolean, modifier: Modifier = Modifier, accentLastFact: Boolean = false, accentFirstFact: Boolean = false, + titleStyle: TextStyle = WolfskinTokens.Type.button, factsStyle: TextStyle = WolfskinTokens.Type.caption, + titleOverflow: TextOverflow = TextOverflow.Ellipsis) { val alpha = remember(title, facts) { Animatable(0f) } - LaunchedEffect(title, facts, visible) { + LaunchedEffect(alpha, visible) { alpha.animateTo(if (visible) 1f else 0f, tween(WolfskinTokens.Motion.fast, easing = CubicBezierEasing(.25f, .1f, .25f, 1f))) } - Column(modifier.graphicsLayer { this.alpha = alpha.value }) { - Text(title, color = WolfskinTokens.Color.fg, style = WolfskinTokens.Type.button, maxLines = 1, overflow = TextOverflow.Ellipsis) - val styledFacts = remember(facts, accentLastFact, accentFirstFact) { - buildAnnotatedString { - val separator = " · " - append(facts) - if (accentFirstFact) { - val end = facts.indexOf(separator).takeIf { it >= 0 } ?: facts.length - addStyle(SpanStyle(color = WolfskinTokens.Color.accentText), 0, end) - } - if (accentLastFact) { - val suffix = facts.lastIndexOf(separator).takeIf { it >= 0 }?.plus(separator.length) ?: facts.length - if (suffix < facts.length) addStyle(SpanStyle(color = WolfskinTokens.Color.accentText), suffix, facts.length) - } - var start = facts.indexOf(separator) - if (start >= 0) { - val muted = WolfskinTokens.Color.muted - val separatorStyle = SpanStyle(color = muted.copy(alpha = muted.alpha * WolfskinTokens.Library.captionSeparatorOpacity)) - val dotOffset = separator.indexOf('·') - while (start >= 0) { - addStyle(separatorStyle, start + dotOffset, start + dotOffset + 1) - start = facts.indexOf(separator, start + separator.length) - } + val styledFacts = remember(facts, accentLastFact, accentFirstFact) { + buildAnnotatedString { + val separator = " · " + append(facts) + if (accentFirstFact) { + val end = facts.indexOf(separator).takeIf { it >= 0 } ?: facts.length + addStyle(SpanStyle(color = WolfskinTokens.Color.accentText), 0, end) + } + if (accentLastFact) { + val suffix = facts.lastIndexOf(separator).takeIf { it >= 0 }?.plus(separator.length) ?: facts.length + if (suffix < facts.length) addStyle(SpanStyle(color = WolfskinTokens.Color.accentText), suffix, facts.length) + } + var start = facts.indexOf(separator) + if (start >= 0) { + val muted = WolfskinTokens.Color.muted + val separatorStyle = SpanStyle(color = muted.copy(alpha = muted.alpha * WolfskinTokens.Library.captionSeparatorOpacity)) + val dotOffset = separator.indexOf('·') + while (start >= 0) { + addStyle(separatorStyle, start + dotOffset, start + dotOffset + 1) + start = facts.indexOf(separator, start + separator.length) } } } - Text(styledFacts, color = WolfskinTokens.Color.muted, style = WolfskinTokens.Type.caption, maxLines = 1) + } + Column(modifier.graphicsLayer { this.alpha = alpha.value }) { + Text(title, modifier = Modifier.exactLineBox(titleStyle), color = WolfskinTokens.Color.fg, style = titleStyle, maxLines = 1, overflow = titleOverflow) + Text(styledFacts, modifier = Modifier.exactLineBox(factsStyle), color = WolfskinTokens.Color.muted, style = factsStyle, maxLines = 1) } } 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 index bb2a9062..87c358a2 100644 --- a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/ComingInSheet.kt +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/ComingInSheet.kt @@ -101,7 +101,7 @@ fun ComingInSheet(shell: ShellState, modifier: Modifier = Modifier) { @Composable private fun SheetButton(label: String, primary: Boolean = false, onClick: () -> Unit) { - Box(Modifier.padding(top = T.buttonMargin)) { PickerButton(label, true, primary, onClick) } + Box(Modifier.padding(top = T.buttonMargin)) { PickerButton(label, true, primary, onClick = onClick) } } @Composable @@ -124,7 +124,9 @@ fun ComingInRow(item: ComingInRequest, viewer: ShellPerson, focused: Boolean, ex 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, L.thumbHeight) } + val request = remember(item.poster, context) { + item.poster?.let { WolfskinImages.request(context, it, (L.thumbWidth.value * 2).toInt(), (L.thumbHeight.value * 2).toInt(), true) } + } 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)) @@ -135,7 +137,7 @@ fun ComingInRow(item: ComingInRequest, viewer: ShellPerson, focused: Boolean, ex }.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, + AsyncImage(model = request, imageLoader = WolfskinImages.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 }, 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 index 670e473b..e91789d9 100644 --- a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverArtwork.kt +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/DiscoverArtwork.kt @@ -31,8 +31,6 @@ 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 @@ -52,12 +50,12 @@ fun DiscoverArtwork(path: String?, modifier: Modifier, poster: Boolean = false, }) 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() + WolfskinImages.request(context, path, if (poster) 256 else 1280, if (poster) 384 else 720, poster) } if (painter != null) Image(painter, null, modifier, contentScale = ContentScale.Crop, colorFilter = filter) else AsyncImage( model = request, - imageLoader = LibraryImages.loader(context), + imageLoader = WolfskinImages.loader(context), contentDescription = null, modifier = modifier, contentScale = ContentScale.Crop, colorFilter = filter, ) } 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 ddb37ea7..147409ea 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,7 +5,12 @@ 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.rememberUpdatedState import androidx.compose.runtime.remember +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.foundation.hoverable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsHoveredAsState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.ui.draw.dropShadow import androidx.compose.ui.platform.LocalDensity @@ -58,3 +63,13 @@ fun Modifier.focusRing(focused: Boolean, radius: Dp, gap: Dp, width: Dp, color: } } } + +/** Pointing changes the same logical focus as the D-pad without activating an action. */ +@Composable +fun Modifier.focusOnPointerEnter(onEnter: () -> Unit): Modifier { + val action by rememberUpdatedState(onEnter) + val interactions = remember { MutableInteractionSource() } + val hovered by interactions.collectIsHoveredAsState() + LaunchedEffect(hovered) { if (hovered) action() } + return hoverable(interactions) +} 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 16b78cc3..3682a759 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,8 @@ fun Glyph(name: String, color: Color, modifier: Modifier = Modifier) { "seerr" -> R.drawable.wolfskin_sparkle "advanced" -> R.drawable.wolfskin_sort "alert" -> R.drawable.wolfskin_alert + "wifi" -> R.drawable.wolfskin_wifi + "clock" -> R.drawable.wolfskin_clock "plus" -> R.drawable.wolfskin_plus "check" -> R.drawable.wolfskin_check "clock" -> R.drawable.wolfskin_clock diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/HomeAmbient.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/HomeAmbient.kt new file mode 100644 index 00000000..7f6b300a --- /dev/null +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/HomeAmbient.kt @@ -0,0 +1,32 @@ +package org.justinvisser.wolfskin.design + +import android.graphics.Matrix +import android.graphics.RadialGradient +import android.graphics.Shader +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.ShaderBrush +import androidx.compose.ui.graphics.toArgb + +/** Empty hero art keeps the locked elliptical ambient wash rather than flashing a black frame. */ +@Composable +fun HomeAmbient(modifier: Modifier = Modifier) { + val brush = remember { + object : ShaderBrush() { + override fun createShader(size: Size): Shader { + val centerX = size.width * .7f + val centerY = size.height * .2f + val radiusX = size.width * 1.2f + val radiusY = size.height * .9f + return RadialGradient(centerX, centerY, radiusX, intArrayOf(WolfskinTokens.Color.surface3.toArgb(), WolfskinTokens.Color.bg.toArgb()), null, Shader.TileMode.CLAMP).apply { + setLocalMatrix(Matrix().apply { setScale(1f, radiusY / radiusX, centerX, centerY) }) + } + } + } + } + Box(modifier.background(brush)) +} diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/HomeFocus.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/HomeFocus.kt new file mode 100644 index 00000000..4ee1fafa --- /dev/null +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/HomeFocus.kt @@ -0,0 +1,114 @@ +package org.justinvisser.wolfskin.design + +import kotlin.math.abs + +/** Home navigation mutates the shell's per-person view, including each row's remembered column. */ +class HomeFocus( + private val shell: ShellState, + private val rows: List, + private val onAction: (HomeAction) -> Unit, +) : ShellContentFocus { + private val view = shell.rememberedView("home") + override val cardKey: String? get() = row?.let { if (it.kind == HomeRowKind.Shelf) it.cards.getOrNull(column)?.id ?: it.id else null } + val row: HomeRow? get() = rows.getOrNull(view.row) + val column: Int get() = row?.let { view.cards[it.id] ?: 0 } ?: 0 + + fun focus(index: Int, column: Int) { + shell.navFocused = false + view.row = index + view.home.focusedKey = rows[index].id + val target = rows[index] + val bounded = column.coerceIn(0, (target.count - 1).coerceAtLeast(0)) + view.cards[target.id] = bounded + if (target.kind == HomeRowKind.Shelf) view.home.firstCards[target.id] = target.adjustedFirst(bounded, view.home.firstCards[target.id] ?: 0) + } + + override fun key(key: ShellKey): Boolean { + val current = row ?: return false + if (key == ShellKey.Back) return false + if (key == ShellKey.Ok) { activate(current); return true } + if (reenterVisible()) return true + when (key) { + ShellKey.Left -> focus(view.row, (column - 1).coerceAtLeast(0)) + ShellKey.Right -> focus(view.row, (column + 1).coerceAtMost((current.count - 1).coerceAtLeast(0))) + ShellKey.Up, ShellKey.Down -> { + val direction = if (key == ShellKey.Up) -1 else 1 + var next = view.row + direction + while (next in rows.indices && !rows[next].focusable) next += direction + if (next in rows.indices) { + val target = rows[next] + val nearest = nearest(current, target) + focus(next, view.cards[target.id] ?: nearest) + } else if (direction < 0) { + view.row = rows.indexOfFirst { it.focusable }.coerceAtLeast(0) + view.home.resetScroll++ + shell.navFocused = true + shell.navIndex = shell.currentTab + } + } + } + return true + } + + private fun reenterVisible(): Boolean { + val layout = view.listState.layoutInfo + if (layout.totalItemsCount == 0 || view.home.positionedKey != row?.id || view.listState.isScrollInProgress) return false + val density = layout.viewportSize.width / WolfskinTokens.Shell.stageWidth.value + val current = layout.visibleItemsInfo.firstOrNull { it.index == view.row } + if (current != null && current.offset + current.size >= WolfskinTokens.Layout.navSolidAfter.value * density && + current.offset <= layout.viewportSize.height - WolfskinTokens.Layout.rowHeadingBand.value * 2 * density) return false + val first = layout.visibleItemsInfo.firstOrNull { + rows[it.index].focusable && it.offset + it.size > WolfskinTokens.Layout.rowHeadingBand.value * density && + it.offset < layout.viewportSize.height - WolfskinTokens.Layout.row_endcta.value * density + } ?: return false + focus(first.index, view.cards[rows[first.index].id] ?: 0) + return true + } + + private fun activate(row: HomeRow) { + when (row.kind) { + HomeRowKind.Hero -> row.cards.firstOrNull()?.let { title -> + onAction(if (column == 0 && row.heroKind != "pick") HomeAction.Play(title, row.heroKind == "resume") else HomeAction.Details(title)) + } + HomeRowKind.Shelf -> if (column < row.cards.size) onAction(HomeAction.Details(row.cards[column])) else onAction(HomeAction.SeeAll(row)) + HomeRowKind.State -> onAction(HomeAction.Retry) + HomeRowKind.Explore -> onAction(HomeAction.Discover) + else -> Unit + } + } + + private fun nearest(from: HomeRow, to: HomeRow): Int { + if (to.kind != HomeRowKind.Shelf) return (column * pitch(from) / pitch(to)).toInt() + val fromFirst = if (from.kind == HomeRowKind.Shelf) view.home.firstCards[from.id] ?: from.adjustedFirst(column, 0) else 0 + val x = (column - fromFirst) * pitch(from) + from.cardWidth.value / 2 + val first = (view.home.firstCards[to.id] ?: 0).coerceIn(0, (to.count - to.focusSlots).coerceAtLeast(0)) + val last = minOf(to.count - 1, first + to.focusSlots - 1) + var best = first + var distance = Float.POSITIVE_INFINITY + for (index in first..last) { + val candidate = abs((index - first) * pitch(to) + to.cardWidth.value / 2 - x) + if (candidate < distance) { best = index; distance = candidate } + } + return best + } + + private fun pitch(row: HomeRow): Float = when (row.cardKind) { + HomeCardKind.Poster -> WolfskinTokens.Card.posterPitch.value + HomeCardKind.Tile -> WolfskinTokens.Card.tilePitch.value + HomeCardKind.Wide -> WolfskinTokens.Card.widePitch.value + } +} + +internal val HomeRow.focusSlots: Int get() = when (cardKind) { + HomeCardKind.Poster -> 6 + HomeCardKind.Tile -> 4 + HomeCardKind.Wide -> 3 +} + +/** Keep one card behind focus, but do not shift again while focus stays in the visible middle. */ +internal fun HomeRow.adjustedFirst(column: Int, previous: Int): Int { + var first = previous + if (column > first + focusSlots - 2) first = column - (focusSlots - 2) + if (column < first + 1) first = (column - 1).coerceAtLeast(0) + return first.coerceIn(0, (count - focusSlots).coerceAtLeast(0)) +} diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/HomeHeader.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/HomeHeader.kt new file mode 100644 index 00000000..931c358d --- /dev/null +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/HomeHeader.kt @@ -0,0 +1,96 @@ +package org.justinvisser.wolfskin.design + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.* +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.graphics.Brush +import androidx.compose.ui.text.font.FontWeight +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.Home as H +import org.justinvisser.wolfskin.design.WolfskinTokens.Layout as L +import org.justinvisser.wolfskin.design.HomeType as T + +/** The hero occupies one row while its backdrop extends beneath the first shelves. */ +@Composable +fun HomeHero(row: HomeRow, focused: Boolean, column: Int, ratings: List, onButton: (Int) -> Unit, onHover: (Int) -> Unit) { + Box(Modifier.fillMaxWidth().height(L.row_hero)) { + if (!row.skeleton) { + val title = row.cards.first() + val artModifier = Modifier.wrapContentSize(Alignment.TopStart, unbounded = true).requiredSize(WolfskinTokens.Shell.stageWidth, WolfskinTokens.Shell.stageHeight) + TitleArtwork(title.title, title.type, title.poster, WolfskinTokens.Shell.stageWidth, WolfskinTokens.Shell.stageHeight, artModifier, backdrop = title.backdrop, posterOnly = title.posterOnly, landscape = true, hero = true) + Box(artModifier.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))) + Box(artModifier.background(Brush.horizontalGradient(0f to C.bg.copy(alpha = .92f), .42f to C.bg.copy(alpha = .55f), .70f to C.bg.copy(alpha = 0f)))) + Column(Modifier.align(Alignment.BottomStart).padding(start = L.safeSide, bottom = L.heroCopyBottom).width(L.heroCopyWidth)) { + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(H.kickerGap)) { + Glyph(if (row.heroKind == "pick") "seerr" else "playback", C.accentText, Modifier.size(L.buttonIcon * (28f / 30f))) + Text(when (row.heroKind) { "resume" -> "Continue watching"; "next" -> "Up next"; "new" -> "New in your library"; else -> "For you" }, modifier = Modifier.exactLineBox(T.label), color = C.accentText, style = T.label) + } + Spacer(Modifier.height(H.kickerBottom)) + Text(title.displayTitle, modifier = Modifier.exactLineBox(T.displayHero), color = C.fg, style = T.displayHero, maxLines = 2, overflow = TextOverflow.Ellipsis) + Spacer(Modifier.height(H.titleBottom)) + Row(Modifier.heightIn(min = H.factsHeight), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(H.factsGap)) { + Text(if (title.isEpisode) title.subtitle else title.year?.toString().orEmpty(), modifier = Modifier.exactLineBox(T.body), color = C.muted, style = T.body, maxLines = 1) + if (row.heroKind == "resume" || title.runtimeSeconds != null) Text(if (row.heroKind == "resume") title.remaining else title.runtime, modifier = Modifier.exactLineBox(T.body), color = C.muted, style = T.body, maxLines = 1) + HomeRatingsRow(ratings) + } + Spacer(Modifier.height(H.factsBottom)) + Text(title.overview, color = C.muted, style = T.body, maxLines = 2, overflow = TextOverflow.Ellipsis, modifier = Modifier.width(L.heroOverviewMax).exactLineBox(T.body)) + Spacer(Modifier.height(H.overviewBottom)) + Row(horizontalArrangement = Arrangement.spacedBy(L.buttonGap)) { + val primary = if (row.heroKind == "pick") "Details" else (if (row.heroKind == "resume") "Resume" else "Play") + if (title.isEpisode) " ${title.episodeLabel}" else "" + PickerButton(primary, focused && column == 0, true, if (row.heroKind == "pick") "about" else "playback", onFocus = { onHover(0) }, textStyle = T.button) { onButton(0) } + if (row.heroKind != "pick") PickerButton("Details", focused && column == 1, false, "about", onFocus = { onHover(1) }, textStyle = T.button) { onButton(1) } + } + } + } else { + HomeAmbient(Modifier.wrapContentSize(Alignment.TopStart, unbounded = true).requiredSize(WolfskinTokens.Shell.stageWidth, WolfskinTokens.Shell.stageHeight)) + Column(Modifier.align(Alignment.BottomStart).padding(start = L.safeSide, bottom = L.heroCopyBottom)) { + Box(Modifier.size(H.heroSkeletonWidth, H.heroSkeletonHeight).background(C.surface, RoundedCornerShape(WolfskinTokens.Radius.card))) + Spacer(Modifier.height(H.heroSkeletonGap)) + Box(Modifier.size(H.heroSkeletonFactsWidth, H.heroSkeletonFactsHeight).background(C.surface, RoundedCornerShape(WolfskinTokens.Radius.cardSheet))) + Spacer(Modifier.height(H.heroSkeletonButtonGap)) + Box(Modifier.size(H.heroSkeletonButtonWidth, L.buttonHeight).background(C.surface, RoundedCornerShape(WolfskinTokens.Radius.button))) + } + } + } +} + +/** Discover continues in the same scroller, under the active person's identity. */ +@Composable +fun HomeSection(person: ShellPerson, savedAt: String?) { + Box(Modifier.fillMaxWidth().height(L.row_section)) { + Row(Modifier.padding(start = L.safeSide, top = H.sectionTop), horizontalArrangement = Arrangement.spacedBy(H.sectionGap), verticalAlignment = Alignment.CenterVertically) { + Text("Discover", color = C.fg, style = T.sectionTitle) + Avatar(person, H.sectionAvatar, T.avatarLetterSectionHeader) + Text("Picks for you first, then what’s out there", color = C.muted, style = T.body) + if (savedAt != null) Box(Modifier.height(WolfskinTokens.Shell.settingsPillHeight) + .border(H.hairline, C.warn.copy(alpha = .45f), RoundedCornerShape(WolfskinTokens.Radius.pill)) + .padding(horizontal = WolfskinTokens.Shell.settingsPillPadding + H.hairline), contentAlignment = Alignment.Center) { + Text("Saved $savedAt", color = C.warn, style = T.caption.copy(fontWeight = T.label.fontWeight)) + } + } + } +} + +/** Failures keep a focusable recovery action without covering working library shelves. */ +@Composable +fun HomeRecovery(row: HomeRow, focused: Boolean, retry: () -> Unit, onHover: () -> Unit) { + Box(Modifier.fillMaxWidth().height(L.row_state)) { + Column(Modifier.offset(L.safeSide, H.stateTop).width(L.stateWidth).background(C.surface, RoundedCornerShape(WolfskinTokens.Radius.panel)).border(H.hairline, C.border, RoundedCornerShape(WolfskinTokens.Radius.panel)).padding(horizontal = H.statePaddingX + H.hairline, vertical = H.statePaddingY + H.hairline)) { + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(H.plateTop)) { + Glyph(if (row.calm) "profile" else "alert", if (row.calm) C.accentText else C.warn, Modifier.size(H.stateIcon)) + Text(row.title, modifier = Modifier.exactLineBox(T.stateTitle), color = C.fg, style = T.stateTitle.copy(fontWeight = FontWeight.Bold)) + } + Spacer(Modifier.height(H.stateGap)) + Text(row.text, modifier = Modifier.exactLineBox(T.body), color = C.muted, style = T.body) + Spacer(Modifier.height(H.stateTextBottom)) + PickerButton("Try again", focused, row.calm, onFocus = onHover, textStyle = T.button, onClick = retry) + } + } +} diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/HomeModel.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/HomeModel.kt new file mode 100644 index 00000000..d2e6adb2 --- /dev/null +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/HomeModel.kt @@ -0,0 +1,92 @@ +package org.justinvisser.wolfskin.design + +import androidx.compose.runtime.Immutable +import java.util.Locale +import kotlin.math.roundToInt + +/** A screen-owned media projection, independent of Jellyfin and catalogue DTOs. */ +@Immutable +data class HomeTitle( + val id: String, + val title: String, + val type: String, + val year: Int? = null, + val runtimeSeconds: Int? = null, + val seasons: Int? = null, + val overview: String = "", + val poster: String? = null, + val backdrop: String? = null, + val posterOnly: Boolean = false, + val seriesTitle: String? = null, + val season: Int? = null, + val episode: Int? = null, + val resumeSeconds: Double = 0.0, + val played: Boolean = false, + val addedAt: String = "", + val genres: List = emptyList(), + val ratings: List = emptyList(), + val held: Boolean = false, + val otherOwner: String? = null, + val tmdbId: Int? = null, +) { + val isEpisode: Boolean get() = type == "episode" + val displayTitle: String get() = seriesTitle ?: title + val episodeLabel: String get() = "S$season E$episode" + val runtime: String get() { + val minutes = ((runtimeSeconds ?: 0) / 60.0).roundToInt() + return if (minutes >= 60) "${minutes / 60}h ${minutes % 60}m" else "${minutes}m" + } + val remaining: String get() = "${(((runtimeSeconds ?: 0) - resumeSeconds) / 60).roundToInt().coerceAtLeast(1)} min left" + val subtitle: String get() = if (isEpisode) "$episodeLabel · $title" else "${if (type == "movie") "Movie" else "Series"} · ${year ?: ""}" + val facts: String get() = listOfNotNull(year?.toString(), if (type == "movie") runtimeSeconds?.let { runtime } ?: "Movie" else seasons?.let { "$it ${if (it == 1) "season" else "seasons"}" } ?: "Series").joinToString(" · ") +} + +/** Only observed, range-checked scores reach this model; formatting follows the TV locale. */ +@Immutable +data class HomeRating(val mark: String, val score: Double, val percentage: Boolean = false, val episode: Boolean = false) { + fun format(locale: Locale = Locale.getDefault()): String = if (percentage) "${score.toInt()}%" else String.format(locale, "%.1f", score) +} + +enum class HomeCardKind { Poster, Tile, Wide } +enum class HomeRowKind { Hero, Shelf, Section, State, Note, Explore } +enum class HomeAvailability { Ready, Loading, SignedOut, Unavailable, Saved } + +/** Rows retain stable keys while a refresh replaces their immutable contents. */ +@Immutable +data class HomeRow( + val id: String, + val kind: HomeRowKind, + val title: String = "", + val cards: List = emptyList(), + val cardKind: HomeCardKind = HomeCardKind.Poster, + val catalogue: Boolean = false, + val total: Int = cards.size, + val target: String? = null, + val text: String = "", + val heroKind: String = "next", + val skeleton: Boolean = false, + val calm: Boolean = false, + val allCards: List = cards, +) { + val hasSeeAll: Boolean get() = total > cards.size + val count: Int get() = when (kind) { HomeRowKind.Hero -> if (heroKind == "pick") 1 else 2; HomeRowKind.Shelf -> cards.size + if (hasSeeAll) 1 else 0; HomeRowKind.State, HomeRowKind.Explore -> 1; else -> 0 } + val focusable: Boolean get() = !skeleton && count > 0 +} + +/** One snapshot paints immediately; source status never discards a saved view. */ +@Immutable +data class HomeSnapshot( + val rows: List, + val conductarr: HomeAvailability = HomeAvailability.SignedOut, + val jellyfinAvailable: Boolean = true, + val savedAt: String? = null, +) + +/** Home emits integration intents, never owns the upstream player or later-slice pages. */ +sealed interface HomeAction { + data class Play(val title: HomeTitle, val resume: Boolean) : HomeAction + data class Details(val title: HomeTitle) : HomeAction + data class SeeAll(val row: HomeRow) : HomeAction + data object Discover : HomeAction + data object Retry : HomeAction +} diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/HomeRatingCache.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/HomeRatingCache.kt new file mode 100644 index 00000000..89ea3093 --- /dev/null +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/HomeRatingCache.kt @@ -0,0 +1,28 @@ +package org.justinvisser.wolfskin.design + +/** The screen's ratings cache uses an injected clock and never caches failed reads. */ +class HomeRatingCache(private val clock: () -> Long = System::currentTimeMillis) { + private data class Entry(val ratings: List, val readAt: Long) + private val entries = linkedMapOf() + + fun read(title: HomeTitle): List? { + val key = key(title) + val entry = entries[key] ?: return null + if (clock() - entry.readAt >= lifetimeMillis) { entries.remove(key); return null } + return entry.ratings + } + + fun save(title: HomeTitle, ratings: List) { + val key = key(title) + entries.remove(key) + entries[key] = Entry(ratings, clock()) + while (entries.size > capacity) entries.remove(entries.keys.first()) + } + + private fun key(title: HomeTitle): String = if (title.isEpisode) "episode:${title.id}" else "${title.type}:${title.tmdbId ?: title.id}" + + companion object { + private const val capacity = 300 + private val lifetimeMillis = (WolfskinTokens.Behaviour.cacheRatingsHours * 60 * 60 * 1000).toLong() + } +} diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/HomeRatings.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/HomeRatings.kt new file mode 100644 index 00000000..11ea2f3f --- /dev/null +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/HomeRatings.kt @@ -0,0 +1,91 @@ +package org.justinvisser.wolfskin.design + +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.ColorMatrix +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.rememberTextMeasurer +import androidx.compose.ui.unit.TextUnit +import androidx.tv.material3.Text +import java.util.Locale +import org.justinvisser.wolfskin.design.WolfskinTokens.Color as C +import org.justinvisser.wolfskin.design.WolfskinTokens.Home as H +import org.justinvisser.wolfskin.design.HomeType as T + +/** Formatting is measured before fitting, so decimal commas obey the same one-line rule. */ +@Composable +fun HomeRatingsRow(ratings: List, modifier: Modifier = Modifier, fontSize: TextUnit = T.ratingPlate.fontSize, plate: Boolean = false) { + val density = LocalDensity.current + val size = with(density) { fontSize.toDp() } + val entryGap = if (plate) size * .64f else H.ratingGap + val markGap = if (plate) size * .29f else H.ratingMarkGap + Row(modifier, horizontalArrangement = Arrangement.spacedBy(entryGap), verticalAlignment = Alignment.CenterVertically) { + ratings.forEach { rating -> + Row(horizontalArrangement = Arrangement.spacedBy(markGap), verticalAlignment = Alignment.CenterVertically) { + val resource = when (rating.mark) { + "rotten" -> R.drawable.ic_rotten_tomatoes_rotten + "upright" -> R.drawable.ic_rt_upright + "spilled" -> R.drawable.ic_rt_spilled + "imdb" -> R.drawable.ic_rating_imdb + "tmdb" -> R.drawable.ic_rating_tmdb + else -> R.drawable.ic_rotten_tomatoes_fresh + } + val muted = rating.mark == "tomato" + Image(painterResource(resource), null, Modifier.size(size * if (rating.mark == "imdb" || rating.mark == "tmdb") 1.8f else 1f, size).graphicsLayer { alpha = if (muted) .55f else 1f }, colorFilter = if (muted) ColorFilter.colorMatrix(ColorMatrix().apply { setToSaturation(0f) }) else null) + if (rating.episode) Text("Ep", color = C.muted, style = T.ratingPlate.copy(fontSize = fontSize, lineHeight = fontSize * 1.2f, fontWeight = T.label.fontWeight), maxLines = 1) + Text(rating.format(), color = C.fg, style = T.ratingPlate.copy(fontSize = fontSize, lineHeight = fontSize * 1.2f, fontFeatureSettings = "tnum"), maxLines = 1, softWrap = false) + } + } + } +} + +/** The plate is a prefix of the full ratings set; its first entry is never discarded. */ +@Composable +fun HomeRatingPlate(ratings: List, visible: Boolean, modifier: Modifier = Modifier) { + val alpha by animateFloatAsState(if (visible && ratings.isNotEmpty()) 1f else 0f, tween(WolfskinTokens.Motion.base), label = "rating-plate") + if (ratings.isEmpty()) return + val measurer = rememberTextMeasurer() + val density = LocalDensity.current + val locale = Locale.getDefault() + BoxWithConstraints(modifier.graphicsLayer { this.alpha = alpha }.background(Brush.verticalGradient(0f to C.bg.copy(alpha = .9f), .52f to C.bg.copy(alpha = .72f), 1f to C.bg.copy(alpha = 0f))).padding(start = H.plateSide, end = H.plateSide, top = H.plateTop, bottom = H.plateFade)) { + val available = with(density) { maxWidth.toPx() } + val fitted = remember(ratings, available, density, locale) { + val base = T.ratingPlate.fontSize + val floor = T.caption.fontSize + fun width(size: TextUnit, entries: List): Float { + val em = with(density) { size.toPx() } + return entries.sumOf { rating -> + val mark = em * if (rating.mark == "imdb" || rating.mark == "tmdb") 1.8f else 1f + val number = measurer.measure(rating.format(locale), T.ratingPlate.copy(fontSize = size)).size.width + val episode = if (rating.episode) measurer.measure("Ep", T.ratingPlate.copy(fontSize = size, fontWeight = T.label.fontWeight)).size.width + em * .29f else 0f + (mark + em * .29f + number + episode).toDouble() + }.toFloat() + (entries.size - 1).coerceAtLeast(0) * em * .64f + } + var size = base + val initialWidth = width(base, ratings) + if (initialWidth > available) size = base * (available / initialWidth).coerceAtLeast(floor.value / base.value) + val step = (base.value - floor.value) / 8 + while (size > floor && width(size, ratings) > available) size = base * ((size.value - step).coerceAtLeast(floor.value) / base.value) + var count = ratings.size + while (count > 1 && width(size, ratings.take(count)) > available) count-- + size to ratings.take(count) + } + HomeRatingsRow(fitted.second, fontSize = fitted.first, plate = true) + } +} diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/HomeRows.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/HomeRows.kt new file mode 100644 index 00000000..4c7f8beb --- /dev/null +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/HomeRows.kt @@ -0,0 +1,55 @@ +package org.justinvisser.wolfskin.design + +/** Source reads preserve their order; the hero is removed by stable item identity. */ +data class HomeLibrary( + val resume: List = emptyList(), + val nextUp: List = emptyList(), + val newest: List = emptyList(), + val destinations: Map> = emptyMap(), + val destinationTotals: Map = emptyMap(), +) + +/** Merge the two sources into the single Home frame without inventing playable items. */ +fun buildHomeRows( + library: HomeLibrary, + continuation: List, + libraryAvailable: Boolean = true, + loading: Boolean = false, +): List = buildList { + if (loading) { + add(HomeRow("hero-sk", HomeRowKind.Hero, skeleton = true)) + listOf(HomeCardKind.Tile, HomeCardKind.Tile, HomeCardKind.Poster, HomeCardKind.Poster).forEachIndexed { index, kind -> + add(HomeRow("sk$index", HomeRowKind.Shelf, cardKind = kind, skeleton = true)) + } + return@buildList + } + val choice = when { + library.resume.isNotEmpty() -> "resume" to library.resume.first() + library.nextUp.isNotEmpty() -> "next" to library.nextUp.first() + library.newest.isNotEmpty() -> "new" to library.newest.first() + else -> continuation.firstNotNullOfOrNull { it.cards.firstOrNull() }?.let { "pick" to it } + } + val hero = choice?.second + if (hero != null) add(HomeRow("hero", HomeRowKind.Hero, cards = listOf(hero), heroKind = choice.first)) + if (!libraryAvailable) { + add(HomeRow("jf-down", HomeRowKind.State, "Your library can’t be reached", text = "Jellyfin isn’t answering, so nothing can play right now.")) + } else { + listOf(Triple("cw", "Continue watching", library.resume), Triple("nu", "Next up", library.nextUp), Triple("new", "New in your library", library.newest)).forEach { (id, title, source) -> + val cards = source.filter { it.id != hero?.id } + if (cards.isNotEmpty()) add(HomeRow(id, HomeRowKind.Shelf, title, cards, if (id == "cw") HomeCardKind.Wide else HomeCardKind.Tile)) + } + listOf("movies", "shows", "anime").forEach { destination -> + val titles = library.destinations[destination].orEmpty().sortedWith(compareBy { it.played }.thenByDescending { it.addedAt }) + if (titles.isNotEmpty()) add(HomeRow("lib-$destination", HomeRowKind.Shelf, if (destination == "anime") "Anime" else "Your $destination", titles.take(WolfskinTokens.Behaviour.rowLimit.toInt()), total = library.destinationTotals[destination] ?: titles.size, target = destination)) + } + } + add(HomeRow("disc-h", HomeRowKind.Section, "Discover")) + addAll(continuation) +} + +/** Authentication failure is distinct from an unreachable signed-in catalogue. */ +fun homeDiscoverState(status: HomeAvailability): HomeRow = if (status == HomeAvailability.SignedOut) { + HomeRow("signin", HomeRowKind.State, "Discover needs sign-in", text = "Sign this TV in to Conductarr to see picks and request titles. Your library plays as usual.", calm = true) +} else { + HomeRow("cond-down", HomeRowKind.State, "Discover is resting", text = "Conductarr can’t be reached. Your library still plays.") +} diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/HomeScreen.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/HomeScreen.kt new file mode 100644 index 00000000..2d0a646c --- /dev/null +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/HomeScreen.kt @@ -0,0 +1,129 @@ +package org.justinvisser.wolfskin.design + +import androidx.compose.animation.core.CubicBezierEasing +import androidx.compose.animation.core.tween +import androidx.compose.foundation.gestures.animateScrollBy +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.stateDescription +import androidx.tv.material3.Text +import kotlinx.coroutines.delay +import org.justinvisser.wolfskin.design.WolfskinTokens.Color as C +import org.justinvisser.wolfskin.design.WolfskinTokens.Home as H +import org.justinvisser.wolfskin.design.WolfskinTokens.Layout as L + +/** One collector owns the focused card dwell; cached scores appear on re-entry without another read. */ +@Composable +fun HomeScreen( + shell: ShellState, + snapshot: HomeSnapshot, + modifier: Modifier = Modifier, + onAction: (HomeAction) -> Unit, +) { + val view = shell.rememberedView("home") + LaunchedEffect(view, snapshot.savedAt) { view.savedAt = snapshot.savedAt } + val action by rememberUpdatedState(onAction) + val focus = remember(shell, snapshot.rows, view) { HomeFocus(shell, snapshot.rows) { action(it) } } + DisposableEffect(shell, focus) { + shell.contentFocus = focus + onDispose { if (shell.contentFocus === focus) shell.contentFocus = null } + } + LaunchedEffect(snapshot.rows, view) { + snapshot.rows.forEach { item -> + view.cards[item.id]?.let { view.cards[item.id] = it.coerceIn(0, (item.count - 1).coerceAtLeast(0)) } + } + val remembered = snapshot.rows.indexOfFirst { it.id == view.home.focusedKey && it.focusable } + if (remembered >= 0) view.row = remembered + if (snapshot.rows.none { it.focusable }) { + if (view.home.focusedKey == null && !shell.navFocused) view.home.loadingEntryRevision = shell.inputRevision + shell.navFocused = true + shell.navIndex = shell.currentTab + } else { + if (view.home.loadingEntryRevision == shell.inputRevision) focus.focus(snapshot.rows.indexOfFirst { it.focusable }, 0) + view.home.loadingEntryRevision = null + if (snapshot.rows.getOrNull(view.row)?.focusable != true) view.row = snapshot.rows.indexOfFirst { it.focusable } + } + view.home.focusedKey = snapshot.rows.getOrNull(view.row)?.id + } + val density = LocalDensity.current + val heights = remember(snapshot.rows) { snapshot.rows.map { rowHeight(it) } } + val topInset = if (snapshot.rows.firstOrNull()?.kind == HomeRowKind.Hero) WolfskinTokens.Shell.zero else L.pinnedLine + val context = LocalContext.current + LaunchedEffect(view.row, snapshot.rows) { + snapshot.rows.drop(view.row + 1).firstOrNull { it.kind == HomeRowKind.Shelf && !it.skeleton }?.let { WolfskinImages.prefetch(context, it) } + } + LaunchedEffect(view) { + if (view.home.positionedKey != null) { + view.home.focusedKey?.let { view.home.tracks[it] }?.let { track -> + track.scrollToItem(track.firstVisibleItemIndex, track.firstVisibleItemScrollOffset) + } + view.listState.scrollToItem(view.listState.firstVisibleItemIndex, view.listState.firstVisibleItemScrollOffset) + } + } + LaunchedEffect(view, view.row, view.home.resetScroll, snapshot.rows) { + val key = snapshot.rows.getOrNull(view.row)?.id + if (view.home.positionedKey == key && view.home.positionedReset == view.home.resetScroll) return@LaunchedEffect + val topRequested = shell.navFocused && view.home.resetScroll > 0 && view.home.positionedReset != view.home.resetScroll + val target = if (topRequested) 0f else with(density) { + (heights.take(view.row).fold(topInset) { total, height -> total + height } - if (snapshot.rows.getOrNull(view.row)?.kind == HomeRowKind.Hero) WolfskinTokens.Shell.zero else L.pinnedLine).toPx() + }.coerceAtLeast(0f) + view.home.positionedKey = key + view.home.positionedReset = view.home.resetScroll + val current = with(density) { heights.take(view.listState.firstVisibleItemIndex).fold(WolfskinTokens.Shell.zero) { total, height -> total + height }.toPx() } + view.listState.firstVisibleItemScrollOffset + view.listState.animateScrollBy(target - current, tween(WolfskinTokens.Motion.scroll, easing = CubicBezierEasing(.33f, 1f, .68f, 1f))) + } + val cache = view.home.ratings + val row = snapshot.rows.getOrNull(view.row) + val title = row?.cards?.getOrNull(focus.column) + val hero = snapshot.rows.firstOrNull { it.kind == HomeRowKind.Hero }?.cards?.firstOrNull() + var heroRatings by remember(hero?.id) { mutableStateOf(hero?.let(cache::read).orEmpty()) } + var focusedRatings by remember(title?.id, shell.navFocused) { mutableStateOf(title?.let(cache::read).orEmpty()) } + LaunchedEffect(title?.id, hero?.id, shell.navFocused, shell.pickerVisible, snapshot.conductarr) { + if (hero != null && snapshot.conductarr == HomeAvailability.Ready) { + heroRatings = hero.ratings + cache.save(hero, heroRatings) + } + if (title != null && !shell.navFocused && !shell.pickerVisible && snapshot.conductarr == HomeAvailability.Ready) { + val saved = cache.read(title) + if (saved == null && row.kind != HomeRowKind.Hero) delay(WolfskinTokens.Motion.ratingDwell.toLong()) + focusedRatings = saved ?: title.ratings.also { cache.save(title, it) } + } + } + val showRatings = snapshot.conductarr != HomeAvailability.SignedOut + val trailingSpace = maxOf(H.frameBottom, WolfskinTokens.Shell.stageHeight - L.pinnedLine - (heights.lastOrNull() ?: WolfskinTokens.Shell.zero)) + LazyColumn(state = view.listState, modifier = modifier.testTag("wolfskin-home").semantics { stateDescription = "${row?.id}:${focus.column}" }, contentPadding = PaddingValues(top = topInset, bottom = trailingSpace)) { + itemsIndexed(snapshot.rows, key = { _, item -> item.id }, contentType = { _, item -> item.kind.name.lowercase() }) { index, item -> + val selected = index == view.row && !shell.navFocused + fun activate(column: Int) { focus.focus(index, column); focus.key(ShellKey.Ok) } + when (item.kind) { + HomeRowKind.Hero -> HomeHero(item, selected, focus.column, if (showRatings) heroRatings else emptyList(), ::activate) { focus.focus(index, it) } + HomeRowKind.Shelf -> HomeShelf(item, view.home, selected, view.cards[item.id] ?: 0, if (showRatings && selected) focusedRatings else emptyList(), ::activate) { focus.focus(index, it) } + HomeRowKind.Section -> HomeSection(shell.person, snapshot.savedAt) + HomeRowKind.State -> HomeRecovery(item, selected, { activate(0) }) { focus.focus(index, 0) } + HomeRowKind.Note -> Row(Modifier.fillMaxWidth().height(L.row_note).padding(start = L.safeSide, top = H.noteTop), horizontalArrangement = Arrangement.spacedBy(H.noteGap)) { + Glyph(if (item.calm) "eye" else "alert", if (item.calm) C.muted else C.warn, Modifier.size(L.buttonIcon)) + Text(item.text, color = if (item.calm) C.muted else C.warn, style = HomeType.body) + } + HomeRowKind.Explore -> Box(Modifier.fillMaxWidth().height(L.row_endcta).padding(start = L.safeSide, top = H.endTop)) { + PickerButton("Explore more in Discover", selected, true, "seerr", onFocus = { focus.focus(index, 0) }, textStyle = HomeType.button) { activate(0) } + } + } + } + } +} + +private fun rowHeight(row: HomeRow) = when (row.kind) { + HomeRowKind.Hero -> L.row_hero + HomeRowKind.Shelf -> L.rowHeadingBand + row.cardHeight + (if (row.cardKind == HomeCardKind.Poster) WolfskinTokens.Card.posterCaptionBand else WolfskinTokens.Shell.zero) + L.rowGapAfter + HomeRowKind.Section -> L.row_section + HomeRowKind.State -> L.row_state + HomeRowKind.Note -> L.row_note + HomeRowKind.Explore -> L.row_endcta +} diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/HomeShelf.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/HomeShelf.kt new file mode 100644 index 00000000..9b97fe8b --- /dev/null +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/HomeShelf.kt @@ -0,0 +1,110 @@ +package org.justinvisser.wolfskin.design + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.animation.core.tween +import androidx.compose.foundation.gestures.animateScrollBy +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.remember +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.snapshotFlow +import kotlinx.coroutines.flow.collectLatest +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Dp +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.Home as H +import org.justinvisser.wolfskin.design.WolfskinTokens.Layout as L +import org.justinvisser.wolfskin.design.HomeType as T + +/** Every lazy row and card uses a stable source identity and an explicit content type. */ +@Composable +fun HomeShelf(row: HomeRow, memory: HomeViewMemory, focused: Boolean, column: Int, ratings: List, onCard: (Int) -> Unit, onHover: (Int) -> Unit) { + val width = row.cardWidth + val height = row.cardHeight + val caption = if (row.cardKind == HomeCardKind.Poster) K.posterCaptionBand else WolfskinTokens.Shell.zero + val first = row.adjustedFirst(column, memory.firstCards[row.id] ?: 0) + SideEffect { if (memory.firstCards[row.id] != first) memory.firstCards[row.id] = first } + val track = remember(memory, row.id) { memory.tracks.getOrPut(row.id) { LazyListState(first) } } + val targetFirst by rememberUpdatedState(first) + val density = LocalDensity.current + LaunchedEffect(track) { + track.scrollToItem(track.firstVisibleItemIndex, track.firstVisibleItemScrollOffset) + var positionedFirst = targetFirst + snapshotFlow { targetFirst }.collectLatest { target -> + if (target != positionedFirst) { + positionedFirst = target + val pitch = with(density) { (width + K.posterGap).toPx() } + track.animateScrollBy((target - track.firstVisibleItemIndex) * pitch - track.firstVisibleItemScrollOffset, tween(WolfskinTokens.Motion.base)) + } + } + } + Box(Modifier.fillMaxWidth().height(L.rowHeadingBand + height + caption + L.rowGapAfter)) { + if (!row.skeleton) Text(row.title, color = C.fg, style = T.rowHeading, maxLines = 1, modifier = Modifier.offset(x = L.safeSide)) + val cards = if (row.skeleton) List(if (row.cardKind == HomeCardKind.Poster) 8 else 5) { null } else row.cards + if (row.hasSeeAll) listOf(null) else emptyList() + LazyRow(state = track, modifier = Modifier.fillMaxWidth().offset(y = L.rowHeadingBand - L.rowGapAfter).height(height + L.rowGapAfter * 2), contentPadding = PaddingValues(horizontal = L.safeSide, vertical = L.rowGapAfter), horizontalArrangement = Arrangement.spacedBy(K.posterGap), userScrollEnabled = !row.skeleton) { + itemsIndexed(cards, key = { index, title -> title?.id ?: "${row.id}:tail:$index" }, contentType = { _, title -> if (row.skeleton) "skeleton" else if (title == null) "seeAll" else row.cardKind.name.lowercase() }) { index, title -> + val selected = focused && column == index + val cardModifier = Modifier.zIndex(if (selected) 2f else 0f).testTag("home-card:${row.id}:$index") + .then(if (!row.skeleton) Modifier.focusOnPointerEnter { onHover(index) } else Modifier) + if (title == null && !row.skeleton) { + LibrarySeeAllCard(row.total, selected, { onCard(index) }, cardModifier, width, height, T.listTitle, T.label) + } else TitleCard(selected, cardModifier.then(if (!row.skeleton) Modifier.clickable { onCard(index) } else Modifier), width, height) { + if (title == null) Box(Modifier.fillMaxSize().background(C.surface)) else { + TitleArtwork(title.title, title.type, title.poster, width, height, Modifier.fillMaxSize(), backdrop = title.backdrop, posterOnly = title.posterOnly, landscape = row.cardKind != HomeCardKind.Poster) + if (row.cardKind != HomeCardKind.Poster) TileOverlay(title) + if (row.catalogue && title.held) Box(Modifier.align(Alignment.BottomEnd).padding(L.badgeInset).size(L.badge).background(C.ok, CircleShape), contentAlignment = Alignment.Center) { Text("✓", color = C.onFocus, style = T.button) } + if (title.resumeSeconds > 0 && title.runtimeSeconds != null) Box(Modifier.align(Alignment.BottomCenter).padding(horizontal = H.progressInset).padding(bottom = if (row.cardKind == HomeCardKind.Poster) H.posterProgressBottom else H.progressBottom).fillMaxWidth().height(L.progressBar).clip(RoundedCornerShape(WolfskinTokens.Radius.progress)).background(C.progressTrack)) { + Box(Modifier.fillMaxHeight().fillMaxWidth((title.resumeSeconds / title.runtimeSeconds).toFloat().coerceIn(0f, 1f)).background(C.accent)) + } + HomeRatingPlate(if (selected) ratings else emptyList(), selected, Modifier.fillMaxWidth()) + } + } + } + } + if (row.cardKind == HomeCardKind.Poster && focused && !row.skeleton) { + val title = row.cards.getOrNull(column) + val availability = when { + title == null -> null + row.catalogue -> if (title.held) "In your library" else title.otherOwner?.let { "In $it’s library" } + title.played -> "Watched" + title.resumeSeconds > 0 -> title.remaining + else -> null + } + val facts = remember(title, row.total, availability) { + (title?.facts ?: "${row.total} titles").replace(" · ", " · ") + availability?.let { " · $it" }.orEmpty() + } + Caption(title?.title ?: "See all ${row.title}", facts, true, Modifier.offset(L.safeSide + (width + K.posterGap) * (column - first), L.rowHeadingBand + height + L.captionOffset).width(H.captionWidth), accentLastFact = availability != null, titleStyle = T.button, factsStyle = T.caption, titleOverflow = TextOverflow.Ellipsis) + } + } +} + +@Composable +private fun TileOverlay(title: HomeTitle) { + Box(Modifier.fillMaxSize(), contentAlignment = Alignment.BottomStart) { + Column(Modifier.fillMaxWidth().background(Brush.verticalGradient(listOf(C.bg.copy(alpha = 0f), C.bg.copy(alpha = .92f)))).padding(start = H.cardPadding, end = H.cardPadding, top = H.cardOverlayTop, bottom = H.cardOverlayBottom)) { + Text(title.displayTitle, modifier = Modifier.exactLineBox(T.button), color = C.fg, style = T.button, maxLines = 1, overflow = TextOverflow.Ellipsis) + Text(title.subtitle, modifier = Modifier.exactLineBox(T.caption), color = C.muted, style = T.caption, maxLines = 1, overflow = TextOverflow.Ellipsis) + } + } +} + +val HomeRow.cardWidth: Dp get() = when (cardKind) { HomeCardKind.Poster -> K.posterW; HomeCardKind.Tile -> K.tileW; HomeCardKind.Wide -> K.wideW } +val HomeRow.cardHeight: Dp get() = when (cardKind) { HomeCardKind.Poster -> K.posterH; HomeCardKind.Tile -> K.tileH; HomeCardKind.Wide -> K.wideH } diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/HomeType.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/HomeType.kt new file mode 100644 index 00000000..f54faeaa --- /dev/null +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/HomeType.kt @@ -0,0 +1,24 @@ +package org.justinvisser.wolfskin.design + +import androidx.compose.ui.text.PlatformTextStyle +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.style.LineHeightStyle + +/** Home keeps full CSS line boxes without moving retained shell or library typography. */ +object HomeType { + private fun fullBox(style: TextStyle): TextStyle = style.copy( + platformStyle = PlatformTextStyle(includeFontPadding = false), + lineHeightStyle = LineHeightStyle(LineHeightStyle.Alignment.Center, LineHeightStyle.Trim.None, LineHeightStyle.Mode.Tight), + ) + val body = fullBox(WolfskinTokens.Type.body) + val button = fullBox(WolfskinTokens.Type.button) + val caption = fullBox(WolfskinTokens.Type.caption) + val label = fullBox(WolfskinTokens.Type.label) + val displayHero = fullBox(WolfskinTokens.Type.displayHero) + val stateTitle = fullBox(WolfskinTokens.Type.stateTitle) + val sectionTitle = fullBox(WolfskinTokens.Type.sectionTitle) + val rowHeading = WolfskinTokens.Type.rowHeading + val ratingPlate = WolfskinTokens.Type.ratingPlate + val listTitle = fullBox(WolfskinTokens.Type.listTitle) + val avatarLetterSectionHeader = WolfskinTokens.Type.avatarLetterSectionHeader +} diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/HomeViewMemory.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/HomeViewMemory.kt new file mode 100644 index 00000000..65f379ad --- /dev/null +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/HomeViewMemory.kt @@ -0,0 +1,19 @@ +package org.justinvisser.wolfskin.design + +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateMapOf +import androidx.compose.runtime.setValue + +/** Focus-follow runs on a new row, never on re-entry to an already-positioned saved viewport. */ +class HomeViewMemory { + val ratings = HomeRatingCache() + val firstCards = mutableStateMapOf() + val tracks = mutableMapOf() + var loadingEntryRevision: Long? = null + var focusedKey: String? = null + var positionedKey: String? = null + var resetScroll by mutableIntStateOf(0) + var positionedReset = -1 +} 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 deleted file mode 100644 index 38253473..00000000 --- a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/LibraryImages.kt +++ /dev/null @@ -1,34 +0,0 @@ -package org.justinvisser.wolfskin.design - -import android.content.Context -import androidx.compose.ui.unit.Dp -import coil3.ImageLoader -import coil3.SingletonImageLoader -import coil3.disk.DiskCache -import coil3.intercept.Interceptor -import coil3.memory.MemoryCache -import coil3.request.ImageRequest -import coil3.request.allowRgb565 -import coil3.request.crossfade -import kotlinx.coroutines.sync.Semaphore -import kotlinx.coroutines.sync.withPermit -import coil3.disk.directory - -/** Every grid shares one bounded cache and four concurrent image reads. */ -object LibraryImages { - private var loader: ImageLoader? = null - private val reads = Semaphore(WolfskinTokens.Library.imageReads.toInt()) - - @Synchronized - fun loader(context: Context): ImageLoader = loader ?: SingletonImageLoader.get(context).newBuilder() - .memoryCache(MemoryCache.Builder().maxSizeBytes((WolfskinTokens.Image.memoryCacheMB * 1024 * 1024).toLong()).build()) - .diskCache(DiskCache.Builder().directory(context.cacheDir.resolve("wolfskin-library-art")) - .maxSizeBytes((WolfskinTokens.Image.diskCacheMB * 1024 * 1024).toLong()).build()) - .components { add(Interceptor { chain -> reads.withPermit { chain.proceed() } }) } - .crossfade(false).build().also { loader = it } - - fun request(context: Context, poster: Any?, width: Dp = WolfskinTokens.Card.posterW, height: Dp = WolfskinTokens.Card.posterH): ImageRequest = - ImageRequest.Builder(context).data(poster) - .size((width.value * 2).toInt(), (height.value * 2).toInt()) - .allowRgb565(true).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 7dfbca5e..92c7d2aa 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 @@ -7,11 +7,9 @@ import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter @@ -40,18 +38,13 @@ fun LibraryPoster( modifier: Modifier = Modifier, offlineArt: ((String) -> ImageBitmap?)? = null, ) { - 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, FocusLift.Card) - .testTag("poster-${title.id}")) { - Box(Modifier.fillMaxSize().clip(shape).background(C.surface2)) { + TitleCard(focused, modifier.testTag("poster-${title.id}")) { val bitmap = remember(title.poster) { title.poster?.let { offlineArt?.invoke(it) } } if (bitmap != null) Image(bitmap, title.title, Modifier.fillMaxSize(), contentScale = ContentScale.Crop) else if (title.poster != null && offlineArt == null) { val context = LocalContext.current - val request = remember(title.poster, context) { LibraryImages.request(context, title.poster) } - AsyncImage(request, title.title, imageLoader = LibraryImages.loader(context), modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Crop) + val request = remember(title.poster, context) { WolfskinImages.request(context, title.poster, (WolfskinTokens.Card.posterW.value * 2).toInt(), (WolfskinTokens.Card.posterH.value * 2).toInt(), true) } + AsyncImage(request, title.title, imageLoader = WolfskinImages.loader(context), modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Crop) } else MissingLibraryArt(title) if (title.ratings.isNotEmpty()) LibraryRatingPlate(title.ratings, visible = ratingsVisible) if (title.progress > 0f && title.progress < 1f) { @@ -70,7 +63,6 @@ fun LibraryPoster( 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 f9ada462..42cede9a 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 @@ -39,9 +39,9 @@ fun LibraryScreen( cachedRatings: Set = emptySet(), ratingsReachable: Boolean = true, offlineArt: ((String) -> ImageBitmap?)? = null, + view: RememberedView = shell.view, registerFocus: Boolean = true, ) { - val view = shell.view val density = LocalDensity.current val context = LocalContext.current val active = !shell.navFocused && !shell.pickerVisible @@ -64,7 +64,7 @@ fun LibraryScreen( if (offlineArt == null) { val start = (grid.index / grid.columns + 1) * grid.columns for (index in start until (start + grid.columns).coerceAtMost(items.size)) { - items[index].poster?.let { LibraryImages.loader(context).enqueue(LibraryImages.request(context, it)) } + items[index].poster?.let { WolfskinImages.loader(context).enqueue(WolfskinImages.request(context, it, (K.posterW.value * 2).toInt(), (K.posterH.value * 2).toInt(), true)) } } } } 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 cba21b48..0dabee9b 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 @@ -8,20 +8,23 @@ 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.TextStyle import androidx.compose.ui.unit.Dp import androidx.tv.material3.Text /** Shelves expose the full underlying list without displaying transport pagination. */ @Composable fun LibrarySeeAllCard(count: Int, focused: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier, - width: Dp = WolfskinTokens.Card.posterW, height: Dp = WolfskinTokens.Card.posterH) { + width: Dp = WolfskinTokens.Card.posterW, height: Dp = WolfskinTokens.Card.posterH, + titleStyle: TextStyle = WolfskinTokens.Type.listTitle, countStyle: TextStyle = WolfskinTokens.Type.label) { 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, FocusLift.Card) - .background(WolfskinTokens.Color.surface, shape).border(WolfskinTokens.Library.borderWidth, WolfskinTokens.Color.border, shape) - .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) + TitleCard(focused, modifier.clickable(onClick = onClick), width, height) { + Column(Modifier.fillMaxSize().background(WolfskinTokens.Color.surface, shape) + .border(WolfskinTokens.Library.borderWidth, WolfskinTokens.Color.border, shape), + verticalArrangement = Arrangement.spacedBy(WolfskinTokens.Library.seeAllGap, Alignment.CenterVertically), horizontalAlignment = Alignment.CenterHorizontally) { + Glyph("interface", WolfskinTokens.Color.accent, Modifier.size(WolfskinTokens.Layout.badge)) + Text("See all", modifier = Modifier.exactLineBox(titleStyle), color = WolfskinTokens.Color.fg, style = titleStyle) + Text(count.toString(), modifier = Modifier.exactLineBox(countStyle), color = WolfskinTokens.Color.muted, style = countStyle) + } } } diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/NavBar.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/NavBar.kt index 543ac321..b316f2df 100644 --- a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/NavBar.kt +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/NavBar.kt @@ -8,6 +8,11 @@ import androidx.compose.animation.core.spring import androidx.compose.foundation.Canvas import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -28,6 +33,7 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp import androidx.tv.material3.Text import org.justinvisser.wolfskin.design.WolfskinTokens.Color as C +import org.justinvisser.wolfskin.design.WolfskinTokens.Home as H import org.justinvisser.wolfskin.design.WolfskinTokens.Layout as L import org.justinvisser.wolfskin.design.WolfskinTokens.Shell as S @@ -94,6 +100,14 @@ fun NavBar(state: ShellState, scrolled: Boolean, modifier: Modifier = Modifier) } } } + state.view.savedAt?.let { time -> + Row(Modifier.align(Alignment.TopEnd).offset(-L.safeSide - L.navAvatar - L.navItemGap, S.navItemTop + (L.navItemHeight - H.savedHeight) / 2) + .height(H.savedHeight).border(H.hairline, C.warn.copy(alpha = .5f), RoundedCornerShape(H.savedRadius)) + .padding(horizontal = H.savedPadding), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(H.savedGap)) { + Glyph("wifi", C.warn, Modifier.size(H.savedIcon)) + Text("Saved $time", color = C.warn, style = WolfskinTokens.Type.caption) + } + } val avatar = boxes.last() Avatar(state.person, L.navAvatar, WolfskinTokens.Type.avatarLetterNav, Modifier.offset(avatar.left, avatar.top) 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 1f4f1270..58aa4c95 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 @@ -35,6 +35,8 @@ class RememberedView { val listState = LazyListState() var row by mutableStateOf(0) val cards = mutableStateMapOf() + var savedAt by mutableStateOf(null) + val home by lazy { HomeViewMemory() } var library: LibraryGridState? = null } @@ -67,9 +69,11 @@ class ShellState( private val views = mutableStateMapOf, RememberedView>() private var armedCard: String? = null var contentFocus: ShellContentFocus? = null + var inputRevision = 0L + private set val tabs: List get() = if (person.hasAnime) ANIME_TABS else BASE_TABS val currentTab: Int get() = tabs.indexOf(rootRoute).coerceAtLeast(0) - val view: RememberedView get() = views.getOrPut(person.key to route) { RememberedView() } + val view: RememberedView get() = rememberedView(route) /** Screen options share the same per-person memory as their root's focus and scroller. */ fun rememberedView(destination: String): RememberedView = @@ -87,18 +91,18 @@ class ShellState( } /** Pointer navigation uses the same transitions as D-pad selection. */ - fun focusNav(index: Int) { navIndex = index; navFocused = true } - fun focusPerson(index: Int) { pickerIndex = index; gearFocused = false } + fun focusNav(index: Int) { inputRevision++; navIndex = index; navFocused = true } + fun focusPerson(index: Int) { inputRevision++; 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 && !contentOverlayVisible) contentFocus?.cardKey else null): Boolean { + if (down) inputRevision++ 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) { if (down) armedCard = card else { if (armedCard == card) activate() diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/TextLineBox.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/TextLineBox.kt new file mode 100644 index 00000000..15315f17 --- /dev/null +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/TextLineBox.kt @@ -0,0 +1,26 @@ +package org.justinvisser.wolfskin.design + +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.AlignmentLine +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 androidx.compose.ui.text.style.LineHeightStyle +import androidx.compose.ui.unit.constrainHeight +import kotlin.math.roundToInt + +/** Android's intrinsic paragraph minimum must not enlarge an explicitly untrimmed CSS line box. */ +fun Modifier.exactLineBox(style: TextStyle): Modifier { + if (style.lineHeightStyle?.trim != LineHeightStyle.Trim.None) return this + return layout { measurable, constraints -> + val placeable = measurable.measure(constraints.copy(minHeight = 0)) + val lineHeight = style.lineHeight.toPx() + val first = placeable[FirstBaseline] + val last = placeable[LastBaseline] + val lines = if (first == AlignmentLine.Unspecified || last == AlignmentLine.Unspecified) 1 + else ((last - first) / lineHeight).roundToInt() + 1 + val height = constraints.constrainHeight((lineHeight * lines).roundToInt()) + layout(placeable.width, height) { placeable.placeRelative(0, (height - placeable.height) / 2) } + } +} diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/TitleArtwork.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/TitleArtwork.kt new file mode 100644 index 00000000..cfc66a2f --- /dev/null +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/TitleArtwork.kt @@ -0,0 +1,87 @@ +package org.justinvisser.wolfskin.design + +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.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.requiredSize +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.blur +import androidx.compose.ui.draw.clip +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.graphics.ColorMatrix +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.Dp +import androidx.tv.material3.Text +import coil3.compose.AsyncImage +import org.justinvisser.wolfskin.design.WolfskinTokens.Color as C +import org.justinvisser.wolfskin.design.WolfskinTokens.Home as H + +/** Exact decode dimensions and no crossfade keep card focus independent of image work. */ +@Composable +private fun SlotImage(source: String, width: Dp, height: Dp, modifier: Modifier = Modifier, poster: Boolean = false, colorFilter: ColorFilter? = null, onError: () -> Unit = {}) { + val context = LocalContext.current + val request = remember(context, source, width, height, poster) { + WolfskinImages.request(context, source, (width.value * 2).toInt(), (height.value * 2).toInt(), poster) + } + AsyncImage(request, imageLoader = WolfskinImages.loader(context), contentDescription = null, modifier = modifier, contentScale = ContentScale.Crop, colorFilter = colorFilter, onError = { onError() }) +} + +/** Poster-only landscapes use the upright one-sheet over a dimmed wash of the same image. */ +@Composable +fun TitleArtwork(title: String, type: String, poster: String?, width: Dp, height: Dp, modifier: Modifier = Modifier, + backdrop: String? = null, posterOnly: Boolean = false, landscape: Boolean = false, hero: Boolean = false) { + val source = if (landscape) backdrop ?: poster else poster + val failed = remember(source) { androidx.compose.runtime.mutableStateOf(false) } + val missing = source == null || failed.value + val density = LocalDensity.current + val fallback = if (missing && !hero) remember(title, width, height, density) { + val hue = title.fold(0) { hash, character -> (hash * 31 + character.code) % 360 }.toFloat() + val radians = Math.toRadians(160.0) + val directionX = kotlin.math.sin(radians).toFloat() + val directionY = -kotlin.math.cos(radians).toFloat() + val size = with(density) { Offset(width.toPx(), height.toPx()) } + val extent = size.x * directionX + size.y * directionY + val center = size / 2f + val offset = Offset(directionX * extent / 2f, directionY * extent / 2f) + Brush.linearGradient(listOf(Color.hsl(hue, .22f, .22f), Color.hsl(hue, .26f, .10f)), center - offset, center + offset) + } else null + Box(modifier.then(if (fallback != null) Modifier.background(fallback) else Modifier.background(C.surface2))) { + if (!missing) { + if (landscape && (posterOnly || backdrop == null) && poster != null) { + val wash = remember(hero) { + val matrix = ColorMatrix().apply { setToSaturation(if (hero) 1.2f else 1f) } + val brightness = if (hero) .55f else .5f + for (index in 0 until 15) matrix.values[index] *= brightness + ColorFilter.colorMatrix(matrix) + } + val inset = if (hero) H.heroWashInset else H.cardWashInset + val washModifier = Modifier.wrapContentSize(Alignment.Center, unbounded = true) + .requiredSize(width + inset * 2, height + inset * 2) + .blur(if (hero) H.heroBlur else H.cardBlur) + .graphicsLayer { if (hero) { scaleX = 1.1f; scaleY = 1.1f } } + SlotImage(poster, width, height, washModifier, colorFilter = wash, onError = { failed.value = true }) + val sheetHeight = if (hero) WolfskinTokens.Layout.heroSheetHeight else height - H.cardSheetInset + val sheetWidth = if (hero) WolfskinTokens.Layout.heroSheetWidth else sheetHeight * (2f / 3f) + val sheetModifier = if (hero) Modifier.align(Alignment.TopEnd).offset(-H.heroSheetRight, H.heroSheetTop) else Modifier.offset(H.cardSheetLeft, H.cardSheetTop) + SlotImage(poster, sheetWidth, sheetHeight, sheetModifier.size(sheetWidth, sheetHeight).clip(RoundedCornerShape(if (hero) WolfskinTokens.Radius.heroSheet else WolfskinTokens.Radius.cardSheet)), poster = true, onError = { failed.value = true }) + } else SlotImage(source, if (hero) WolfskinTokens.Card.posterW * 5 else width, if (hero) WolfskinTokens.Card.posterW * 5 * (9f / 16f) else height, Modifier.fillMaxSize(), poster = !landscape, onError = { failed.value = true }) + } else if (!hero) { + Glyph(if (type == "movie") "film" else "nextup", C.fg.copy(alpha = .45f), Modifier.offset(H.cardPadding, H.cardPadding).size(H.artGlyph)) + if (!landscape) Text(title, color = C.fg, style = WolfskinTokens.Type.button, maxLines = 4, modifier = Modifier.align(Alignment.BottomStart).padding(H.cardPadding)) + } else HomeAmbient(Modifier.fillMaxSize()) + } +} diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/TitleCard.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/TitleCard.kt new file mode 100644 index 00000000..81779ed3 --- /dev/null +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/TitleCard.kt @@ -0,0 +1,23 @@ +package org.justinvisser.wolfskin.design + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.unit.Dp + +/** Shelf, grid and continuation cards share one fixed slot and draw-only focus surface. */ +@Composable +fun TitleCard(focused: Boolean, modifier: Modifier = Modifier, width: Dp = WolfskinTokens.Card.posterW, + height: Dp = WolfskinTokens.Card.posterH, content: @Composable BoxScope.() -> Unit) { + val shape = RoundedCornerShape(WolfskinTokens.Radius.card) + Box(modifier.size(width, height).focusScale(focused, WolfskinTokens.Focus.scaleCard) + .focusRing(focused, WolfskinTokens.Radius.card, WolfskinTokens.Focus.ringCardGap, WolfskinTokens.Focus.ringCardRing, WolfskinTokens.Color.accent, FocusLift.Card)) { + Box(Modifier.fillMaxSize().clip(shape).background(WolfskinTokens.Color.surface2), content = content) + } +} 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 df3eb325..1911e346 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 @@ -102,12 +102,17 @@ private fun RecoveryPicker(state: ShellState) { /** Recovery actions remain focusable while profiles cannot be read. */ @Composable -fun PickerButton(label: String, focused: Boolean, primary: Boolean, onClick: () -> Unit) { +fun PickerButton(label: String, focused: Boolean, primary: Boolean, icon: String? = null, onFocus: (() -> Unit)? = null, + textStyle: androidx.compose.ui.text.TextStyle = WolfskinTokens.Type.button, 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, FocusLift.Button) + .then(if (onFocus != null) Modifier.focusOnPointerEnter(onFocus) else Modifier) .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) + Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(WolfskinTokens.Home.plateTop)) { + if (icon != null) Glyph(icon, if (primary) C.onFocus else C.fg, Modifier.size(L.buttonIcon)) + Text(label, modifier = Modifier.exactLineBox(textStyle), color = if (primary) C.onFocus else C.fg, style = textStyle) + } } } diff --git a/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/WolfskinImages.kt b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/WolfskinImages.kt new file mode 100644 index 00000000..d89254f9 --- /dev/null +++ b/wolfskin-design/src/main/java/org/justinvisser/wolfskin/design/WolfskinImages.kt @@ -0,0 +1,54 @@ +package org.justinvisser.wolfskin.design + +import android.content.Context +import coil3.ImageLoader +import coil3.disk.DiskCache +import coil3.intercept.Interceptor +import coil3.disk.directory +import coil3.imageLoader +import coil3.request.crossfade +import coil3.memory.MemoryCache +import coil3.request.ImageRequest +import coil3.request.allowRgb565 +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.sync.Semaphore +import kotlinx.coroutines.sync.withPermit + +/** Wolfskin screens share one bounded loader; hidden-switch screens retain the upstream loader. */ +object WolfskinImages { + private var application: Context? = null + private var loader: ImageLoader? = null + private const val MEBIBYTE = 1024L * 1024L + private const val IMAGE_READS = 4 + private val readPermits = Semaphore(IMAGE_READS) + + @Synchronized + fun loader(context: Context): ImageLoader { + val owner = context.applicationContext + if (application !== owner) { + loader?.shutdown() + application = owner + val reads = Dispatchers.IO.limitedParallelism(IMAGE_READS) + loader = owner.imageLoader.newBuilder() + .memoryCache { MemoryCache.Builder().maxSizeBytes((WolfskinTokens.Image.memoryCacheMB * MEBIBYTE).toLong()).build() } + .diskCache { DiskCache.Builder().directory(owner.cacheDir.resolve("wolfskin-images")).maxSizeBytes((WolfskinTokens.Image.diskCacheMB * MEBIBYTE).toLong()).build() } + .components { add(Interceptor { chain -> readPermits.withPermit { chain.proceed() } }) } + .fetcherCoroutineContext(reads).decoderCoroutineContext(reads).crossfade(false).build() + } + return loader!! + } + + fun request(context: Context, source: Any, width: Int, height: Int, poster: Boolean): ImageRequest = + ImageRequest.Builder(context).data(source).size(width, height).allowRgb565(poster).build() + + fun prefetch(context: Context, row: HomeRow) { + val count = when (row.cardKind) { HomeCardKind.Poster -> 7; HomeCardKind.Tile -> 5; HomeCardKind.Wide -> 4 } + val width = (row.cardWidth.value * 2).toInt() + val height = (row.cardHeight.value * 2).toInt() + row.cards.take(count).forEach { title -> + val poster = row.cardKind == HomeCardKind.Poster + val source = if (poster) title.poster else title.backdrop ?: title.poster + if (source != null) loader(context).enqueue(request(context, source, width, height, poster)) + } + } +} 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 554d06a4..546d7e8c 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 @@ -395,6 +395,66 @@ object WolfskinTokens { val sheetX = -15.0.dp const val sheetOpacity = 0.5f } + object Home { + val kickerGap = 6.0.dp + val kickerBottom = 5.0.dp + val titleBottom = 8.0.dp + val factsHeight = 19.0.dp + val factsBottom = 7.0.dp + val factsGap = 11.0.dp + val overviewBottom = 15.0.dp + val cardPadding = 11.0.dp + val cardOverlayTop = 27.0.dp + val cardOverlayBottom = 9.0.dp + val sectionTop = 20.0.dp + val sectionAvatar = 28.0.dp + val sectionGap = 11.0.dp + val stateTextBottom = 12.0.dp + val stateTop = 10.0.dp + val statePaddingX = 20.0.dp + val statePaddingY = 17.0.dp + val stateGap = 4.0.dp + val stateIcon = 18.0.dp + val captionWidth = 320.0.dp + val plateTop = 7.0.dp + val plateSide = 8.0.dp + val plateFade = 23.0.dp + val ratingGap = 10.0.dp + val ratingMarkGap = 4.0.dp + val heroSkeletonWidth = 320.0.dp + val heroSkeletonHeight = 42.0.dp + val heroSkeletonFactsWidth = 230.0.dp + val heroSkeletonFactsHeight = 17.0.dp + val heroSkeletonButtonWidth = 130.0.dp + val heroSkeletonGap = 11.0.dp + val heroSkeletonButtonGap = 20.0.dp + val artGlyph = 22.0.dp + val cardSheetLeft = 11.0.dp + val cardSheetTop = 9.0.dp + val cardSheetInset = 18.0.dp + val heroSheetRight = 90.0.dp + val heroSheetTop = 75.0.dp + val cardBlur = 13.0.dp + val heroBlur = 30.0.dp + val progressInset = 9.0.dp + val progressBottom = 5.0.dp + val endTop = 20.0.dp + val frameBottom = 40.0.dp + val toastPadding = 14.0.dp + val toastTop = 75.0.dp + val toastWidth = 380.0.dp + val cardWashInset = 15.0.dp + val heroWashInset = 40.0.dp + val noteTop = 7.0.dp + val noteGap = 8.0.dp + val hairline = 1.0.dp + val savedHeight = 26.0.dp + val savedPadding = 10.0.dp + val savedGap = 5.0.dp + val savedIcon = 14.0.dp + val savedRadius = 14.0.dp + val posterProgressBottom = 6.0.dp + } object Discover { val sheetPaddingX = 32.0.dp val sheetPaddingY = 40.0.dp diff --git a/wolfskin-design/src/main/res/drawable/wolfskin_film.xml b/wolfskin-design/src/main/res/drawable/wolfskin_film.xml index 4961e89d..45648655 100644 --- a/wolfskin-design/src/main/res/drawable/wolfskin_film.xml +++ b/wolfskin-design/src/main/res/drawable/wolfskin_film.xml @@ -1 +1,4 @@ - + + + + diff --git a/wolfskin-design/src/main/res/drawable/wolfskin_wifi.xml b/wolfskin-design/src/main/res/drawable/wolfskin_wifi.xml new file mode 100644 index 00000000..f43ded04 --- /dev/null +++ b/wolfskin-design/src/main/res/drawable/wolfskin_wifi.xml @@ -0,0 +1,4 @@ + + + + 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 index 032f59f8..a913391e 100644 --- a/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/DiscoverCaptureTest.kt +++ b/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/DiscoverCaptureTest.kt @@ -65,7 +65,7 @@ class DiscoverCaptureTest { } rule.mainClock.advanceTimeBy(1000) rule.waitForIdle() - val directory = File(requireNotNull(System.getProperty("wolfskin.captureDirectory"))).apply { mkdirs() } + val directory = File(requireNotNull(System.getProperty("wolfskin.captureDirectory")), "discover").apply { mkdirs() } rule.onRoot().captureRoboImage(File(directory, "$name.png").absolutePath) recordTypography(directory, name) } diff --git a/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/HomeArtTest.kt b/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/HomeArtTest.kt new file mode 100644 index 00000000..970ea46e --- /dev/null +++ b/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/HomeArtTest.kt @@ -0,0 +1,26 @@ +package org.justinvisser.wolfskin.design + +import android.app.Application +import androidx.compose.foundation.layout.size +import androidx.compose.ui.Modifier +import androidx.compose.ui.test.* +import androidx.compose.ui.test.junit4.createComposeRule +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +/** An unreadable local image exercises Coil's real error path without a network provider. */ +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [34], application = Application::class, qualifiers = "w960dp-h540dp-xhdpi") +class HomeArtTest { + @get:Rule val rule = createComposeRule() + + @Test fun imageFailureShowsTheTitleFallbackInsteadOfAnEmptyCard() { + val title = HomeTitle("missing-image", "A missing film", "movie", poster = "file:///android_asset/wolfskin-home/not-present.jpg") + rule.setContent { WolfskinTheme { TitleArtwork(title.title, title.type, title.poster, WolfskinTokens.Card.posterW, WolfskinTokens.Card.posterH, Modifier.size(WolfskinTokens.Card.posterW, WolfskinTokens.Card.posterH)) } } + rule.waitUntil(5000) { rule.onAllNodesWithText(title.title).fetchSemanticsNodes().isNotEmpty() } + rule.onNodeWithText(title.title).assertIsDisplayed() + } +} diff --git a/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/HomeCaptureTest.kt b/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/HomeCaptureTest.kt new file mode 100644 index 00000000..04a5e866 --- /dev/null +++ b/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/HomeCaptureTest.kt @@ -0,0 +1,119 @@ +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 androidx.compose.ui.test.SemanticsMatcher +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 java.util.Locale +import kotlinx.coroutines.runBlocking +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 frozen Home acceptance state renders the actual native Compose surface at density 2. */ +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [34], application = Application::class, qualifiers = "w960dp-h540dp-xhdpi") +@GraphicsMode(GraphicsMode.Mode.NATIVE) +class HomeCaptureTest { + @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, person: String = "justin", mode: String = "ready", row: String = "hero", column: Int = 0, nav: Int? = null) { + val previous = Locale.getDefault() + Locale.setDefault(Locale.US) + try { + val snapshot = HomeFixture(rule.activity).snapshot(person, mode) + val shell = ShellState(fixturePeople(), person).apply { + pickerVisible = false + view.row = snapshot.rows.indexOfFirst { it.id == row }.coerceAtLeast(0) + view.cards[row] = column + if (nav != null) { navFocused = true; navIndex = nav } + } + preload(snapshot, shell.view.row) + rule.setContent { WolfskinTheme { + ShellSurface(shell, Modifier.requiredSize(WolfskinTokens.Shell.stageWidth, WolfskinTokens.Shell.stageHeight)) { modifier -> HomeScreen(shell, snapshot, modifier) {} } + } } + rule.mainClock.advanceTimeBy(1200) + rule.waitForIdle() + val directory = File(System.getProperty("wolfskin.captureDirectory"), "home").apply { mkdirs() } + rule.onRoot().captureRoboImage(File(directory, "$name.png").absolutePath) + recordTextLayouts(File(directory, "$name-layout.json")) + } finally { Locale.setDefault(previous) } + } + + private fun recordTextLayouts(output: File) { + val nodes = JSONArray() + rule.onAllNodes(SemanticsMatcher.keyIsDefined(SemanticsActions.GetTextLayoutResult), useUnmergedTree = true).fetchSemanticsNodes().forEach { node -> + val results = mutableListOf() + node.config[SemanticsActions.GetTextLayoutResult].action?.invoke(results) + results.forEach { layout -> + val lines = JSONArray() + repeat(layout.lineCount) { line -> + lines.put(JSONObject().put("top", layout.getLineTop(line)).put("bottom", layout.getLineBottom(line)).put("baseline", layout.getLineBaseline(line))) + } + nodes.put(JSONObject().put("text", layout.layoutInput.text.text) + .put("left", node.boundsInRoot.left).put("top", node.boundsInRoot.top) + .put("width", layout.size.width).put("height", layout.size.height) + .put("lineHeight", layout.layoutInput.style.lineHeight.toString()) + .put("lineHeightStyle", layout.layoutInput.style.lineHeightStyle.toString()).put("lines", lines)) + } + } + output.writeText(nodes.toString(2)) + } + + private fun preload(snapshot: HomeSnapshot, focused: Int) = runBlocking { + val rows = if (focused == 0) snapshot.rows.take(2) else snapshot.rows.drop(focused).take(3) + for (row in rows) for (title in row.cards.take(8)) { + val hero = row.kind == HomeRowKind.Hero + val landscape = hero || row.cardKind != HomeCardKind.Poster + val width = if (hero) 1280 else (row.cardWidth.value * 2).toInt() + val height = if (hero) 720 else (row.cardHeight.value * 2).toInt() + val source = if (landscape) title.backdrop ?: title.poster else title.poster + if (source != null) WolfskinImages.loader(rule.activity).execute(WolfskinImages.request(rule.activity, source, width, height, !landscape)) + } + } + + @Test fun st005_loading() = capture("st-005", mode = "loading", nav = 0) + @Test fun st006_justinNext() = capture("st-006") + @Test fun st007_mindelResume() = capture("st-007", person = "mindel") + @Test fun st008_sharedResume() = capture("st-008", person = "shared") + @Test fun st009_deepTrending() = capture("st-009", row = "trending", column = 2) + @Test fun st010_heroNav() = capture("st-010", nav = 0) + @Test fun st011_deepBackNav() = capture("st-011", row = "lib-shows", column = 2, nav = 0) + @Test fun st012_explore() = capture("st-012", row = "explore") + @Test fun st013_unsigned() = capture("st-013", mode = "unsigned", row = "signin") + @Test fun st014_discoverNav() = capture("st-014", nav = 1) + @Test fun st015_offlineSaved() = capture("st-015", mode = "offline") + @Test fun st016_conductarrDown() = capture("st-016", mode = "cond-down", row = "cond-down") + @Test fun st017_conductarrSaved() = capture("st-017", mode = "cond-cached") + @Test fun st018_jellyfinDown() = capture("st-018", mode = "jf-down") + @Test fun st019_picksUnavailable() = capture("st-019", mode = "picks-unavailable", row = "trending") + @Test fun st020_missingArt() = capture("st-020", person = "shared", mode = "missing", row = "lib-movies") + @Test fun st096_widestTilePlate() = capture("st-096", person = "mindel", row = "new", column = 4) + @Test fun st100_episodePlate() = capture("st-100", person = "mindel", row = "nu") + @Test fun st099_posterPlate() = capture("st-099", row = "lib-movies", column = 1) + @Test fun st101_unratedEpisode() = capture("st-101", person = "mindel", row = "nu", column = 2) + @Test fun st102_filmTile() = capture("st-102", person = "mindel", row = "new", column = 4) + @Test fun st103_heroRatings() = capture("st-103") + @Test fun xHomeDiscoverStart() = capture("x-home-discover-start", row = "pk-m") +} diff --git a/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/HomeKeysTest.kt b/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/HomeKeysTest.kt new file mode 100644 index 00000000..01c953b8 --- /dev/null +++ b/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/HomeKeysTest.kt @@ -0,0 +1,224 @@ +package org.justinvisser.wolfskin.design + +import android.app.Application +import androidx.activity.ComponentActivity +import androidx.compose.runtime.* +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.test.* +import androidx.compose.ui.test.junit4.createAndroidComposeRule +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 + +/** Approved replay data drives the same Android event handling used by the Home surface. */ +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [34], application = Application::class, qualifiers = "w960dp-h540dp-xhdpi") +class HomeKeysTest { + @get:Rule val rule = createAndroidComposeRule() + + @Test fun approvedHomePathsMatchFocusAndCounts() { + val fixture = HomeFixture(rule.activity) + var shell by mutableStateOf(ShellState(fixturePeople(), "justin")) + var action: HomeAction? = null + rule.setContent { WolfskinTheme { + val snapshot = remember(shell, shell.person) { fixture.snapshot(shell.person.key) } + ShellSurface(shell) { modifier -> HomeScreen(shell, snapshot, modifier) { action = it } } + } } + val paths = javaClass.getResourceAsStream("/wolfskin-home-presses.json")!!.bufferedReader().use { JSONArray(it.readText()) } + for (index in 0 until paths.length()) { + val path = paths.getJSONObject(index) + val keys = path.getJSONArray("keys") + rule.runOnIdle { shell = ShellState(fixturePeople(), "justin"); action = null } + for (key in 0 until keys.length()) press(keys.getString(key)) + assertEquals(path.getString("goal"), path.getInt("presses"), keys.length()) + when (index) { + 0 -> { assertEquals("mindel", shell.person.key); assertTrue(action is HomeAction.Play); assertTrue((action as HomeAction.Play).resume) } + 1 -> { assertTrue(action is HomeAction.Play); assertFalse((action as HomeAction.Play).resume); assertEquals("Yu Yu Hakusho", (action as HomeAction.Play).title.seriesTitle) } + 2 -> assertEquals("trending", fixture.snapshot("justin").rows[shell.view.row].id) + 3 -> { val row = fixture.snapshot("justin").rows[shell.view.row]; assertEquals("trending", row.id); assertEquals(row.cards.size, shell.view.cards[row.id]); assertTrue(row.hasSeeAll) } + 4 -> { val row = fixture.snapshot("justin").rows[shell.view.row]; assertEquals("lib-shows", row.id); assertEquals(2, shell.view.cards[row.id]); assertFalse(shell.navFocused) } + 5 -> assertEquals("discover", shell.route) + } + } + } + + @Test fun cardsActivateOnlyOnReleaseAndMovementCancelsArming() { + val shell = ShellState(fixturePeople(), "justin").apply { pickerVisible = false } + val snapshot = HomeFixture(rule.activity).snapshot("justin") + var action: HomeAction? = null + rule.setContent { WolfskinTheme { ShellSurface(shell) { modifier -> HomeScreen(shell, snapshot, modifier) { action = it } } } } + press("DOWN") + rule.onNodeWithTag("wolfskin-shell").performKeyInput { keyDown(Key.DirectionCenter) } + assertNull(action) + rule.onNodeWithTag("wolfskin-shell").performKeyInput { keyUp(Key.DirectionCenter) } + assertTrue(action is HomeAction.Details) + rule.runOnIdle { action = null } + rule.onNodeWithTag("wolfskin-shell").performKeyInput { keyDown(Key.DirectionCenter); pressKey(Key.DirectionRight); keyUp(Key.DirectionCenter) } + assertNull(action) + } + + @Test fun loadingHasNavFocus() { + val shell = ShellState(fixturePeople(), "justin").apply { pickerVisible = false } + val snapshot = HomeFixture(rule.activity).snapshot("justin", "loading") + rule.setContent { WolfskinTheme { ShellSurface(shell) { modifier -> HomeScreen(shell, snapshot, modifier) {} } } } + rule.waitForIdle() + assertTrue(shell.navFocused) + assertEquals(0, shell.navIndex) + } + + @Test fun returningHomeRestoresTheExactViewportAndArrowReentersVisibleContent() { + val shell = ShellState(fixturePeople(), "justin").apply { pickerVisible = false } + val snapshot = HomeFixture(rule.activity).snapshot("justin") + rule.setContent { WolfskinTheme { ShellSurface(shell) { modifier -> + if (shell.route == "home") HomeScreen(shell, snapshot, modifier) {} else ShellPlaceholder(shell.route, modifier) + } } } + rule.onNodeWithTag("wolfskin-home").performScrollToIndex(3) + val view = shell.view + val savedIndex = view.listState.firstVisibleItemIndex + val savedOffset = view.listState.firstVisibleItemScrollOffset + assertEquals(3, savedIndex) + rule.runOnIdle { shell.enterDestination("movies") } + rule.runOnIdle { shell.enterDestination("home") } + rule.waitForIdle() + assertSame(view, shell.view) + assertEquals(savedIndex, view.listState.firstVisibleItemIndex) + assertEquals(savedOffset, view.listState.firstVisibleItemScrollOffset) + press("RIGHT") + assertEquals("lib-movies", snapshot.rows[view.row].id) + assertEquals(0, view.cards["lib-movies"]) + } + + @Test fun pointerHoverMovesFocusWithoutActivatingTheCard() { + val shell = ShellState(fixturePeople(), "justin").apply { pickerVisible = false } + val snapshot = HomeFixture(rule.activity).snapshot("justin") + var action: HomeAction? = null + rule.setContent { WolfskinTheme { ShellSurface(shell) { modifier -> HomeScreen(shell, snapshot, modifier) { action = it } } } } + press("DOWN") + rule.onNodeWithTag("home-card:nu:2").performMouseInput { enter(center) } + rule.waitForIdle() + assertEquals("nu", snapshot.rows[shell.view.row].id) + assertEquals(2, shell.view.cards["nu"]) + assertNull(action) + } + + @Test fun downClampsToShortRowsAndNeverMovesPastTheFinalSeeAll() { + val shell = ShellState(fixturePeople(), "justin").apply { pickerVisible = false; navFocused = false } + val cards = HomeFixture(rule.activity).snapshot("justin").rows.first { it.id == "lib-movies" }.cards + val long = HomeRow(id = "long", kind = HomeRowKind.Shelf, cards = cards) + val short = long.copy(id = "short", cards = cards.take(2), total = 2) + val tail = short.copy(id = "tail", total = 20) + shell.view.cards[long.id] = cards.lastIndex + var action: HomeAction? = null + rule.setContent { WolfskinTheme { + ShellSurface(shell) { modifier -> HomeScreen(shell, HomeSnapshot(listOf(long, short, tail)), modifier) { action = it } } + } } + press("DOWN") + assertEquals(short.id, listOf(long, short, tail)[shell.view.row].id) + assertEquals(1, shell.view.cards[short.id]) + rule.onNodeWithTag("home-card:short:1").assertIsDisplayed() + press("RIGHT") + assertEquals(1, shell.view.cards[short.id]) + press("DOWN") + press("RIGHT") + press("DOWN") + assertEquals(2, shell.view.row) + assertEquals(2, shell.view.cards[tail.id]) + rule.onNodeWithTag("home-card:tail:2").assertIsDisplayed() + press("OK") + assertEquals(tail.id, (action as HomeAction.SeeAll).row.id) + } + + @Test fun mixedWidthRowsUseNearestCenterAndKeepTheTrackWhenMovingBackInsideItsMiddle() { + val shell = ShellState(fixturePeople(), "justin").apply { pickerVisible = false; navFocused = false } + val cards = HomeFixture(rule.activity).snapshot("justin").rows.first { it.id == "lib-movies" }.cards + val posters = HomeRow(id = "posters", kind = HomeRowKind.Shelf, cards = cards) + val tiles = posters.copy(id = "tiles", cardKind = HomeCardKind.Tile) + shell.view.cards[posters.id] = 3 + rule.setContent { WolfskinTheme { + ShellSurface(shell) { modifier -> HomeScreen(shell, HomeSnapshot(listOf(posters, tiles)), modifier) {} } + } } + press("DOWN") + assertEquals("The third tile is nearest the fourth poster's horizontal center", 2, shell.view.cards[tiles.id]) + repeat(4) { press("RIGHT") } + val left = rule.onNodeWithTag("home-card:tiles:4").fetchSemanticsNode().boundsInRoot.left + press("LEFT") + assertEquals("Moving back within the visible middle must not shift the track", left, + rule.onNodeWithTag("home-card:tiles:4").fetchSemanticsNode().boundsInRoot.left, .1f) + press("UP") + assertEquals("A visited row retains its column", 3, shell.view.cards[posters.id]) + } + + @Test fun asynchronousFreshHomePlaysNextInTwoPresses() { + val fixture = HomeFixture(rule.activity) + val shell = ShellState(fixturePeople(), "justin") + val delivery = kotlinx.coroutines.CompletableDeferred() + var action: HomeAction? = null + rule.setContent { + val snapshot by produceState(fixture.snapshot("justin", "loading")) { value = delivery.await() } + WolfskinTheme { ShellSurface(shell) { modifier -> HomeScreen(shell, snapshot, modifier) { action = it } } } + } + press("OK") + rule.waitForIdle() + assertTrue(shell.navFocused) + rule.runOnIdle { delivery.complete(fixture.snapshot("justin")) } + rule.waitForIdle() + assertFalse(shell.navFocused) + assertEquals(0, shell.view.cards["hero"]) + press("OK") + val play = action as HomeAction.Play + assertFalse(play.resume) + assertEquals("Yu Yu Hakusho", play.title.seriesTitle) + } + + @Test fun asynchronousHomeDoesNotStealFocusAfterNavInput() { + val fixture = HomeFixture(rule.activity) + val shell = ShellState(fixturePeople(), "justin") + val delivery = kotlinx.coroutines.CompletableDeferred() + rule.setContent { + val snapshot by produceState(fixture.snapshot("justin", "loading")) { value = delivery.await() } + WolfskinTheme { ShellSurface(shell) { modifier -> HomeScreen(shell, snapshot, modifier) {} } } + } + press("OK") + press("RIGHT") + rule.runOnIdle { delivery.complete(fixture.snapshot("justin")) } + rule.waitForIdle() + assertTrue(shell.navFocused) + assertEquals(1, shell.navIndex) + } + + @Test fun heldOkRepeatEventsDoNotReplayButtonsOrActivateCardsBeforeRelease() { + val shell = ShellState(fixturePeople(), "justin").apply { pickerVisible = false } + val snapshot = HomeFixture(rule.activity).snapshot("justin") + val actions = mutableListOf() + rule.setContent { WolfskinTheme { ShellSurface(shell) { modifier -> HomeScreen(shell, snapshot, modifier) { actions += it } } } } + val input = rule.onNodeWithTag("wolfskin-shell") + fun repeatOk() = rule.runOnIdle { + rule.activity.dispatchKeyEvent(android.view.KeyEvent(0L, 0L, android.view.KeyEvent.ACTION_DOWN, android.view.KeyEvent.KEYCODE_DPAD_CENTER, 3)) + } + input.performKeyInput { keyDown(Key.DirectionCenter) } + repeatOk() + assertEquals(1, actions.size) + assertTrue(actions.single() is HomeAction.Play) + input.performKeyInput { keyUp(Key.DirectionCenter) } + press("DOWN") + input.performKeyInput { keyDown(Key.DirectionCenter) } + repeatOk() + assertEquals(1, actions.size) + input.performKeyInput { keyUp(Key.DirectionCenter) } + assertEquals(2, actions.size) + assertTrue(actions.last() is HomeAction.Details) + } + + private fun press(name: String) { + val key = when (name) { + "UP" -> Key.DirectionUp; "DOWN" -> Key.DirectionDown + "LEFT" -> Key.DirectionLeft; "RIGHT" -> Key.DirectionRight + "BACK" -> Key.Back; else -> Key.DirectionCenter + } + rule.onNodeWithTag("wolfskin-shell").performKeyInput { pressKey(key) } + } +} diff --git a/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/HomeRatingCacheTest.kt b/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/HomeRatingCacheTest.kt new file mode 100644 index 00000000..0a93e74e --- /dev/null +++ b/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/HomeRatingCacheTest.kt @@ -0,0 +1,38 @@ +package org.justinvisser.wolfskin.design + +import java.util.Locale +import org.junit.Assert.* +import org.junit.Test + +/** Cache boundaries protect visible ratings during offline browsing and fast repeated focus. */ +class HomeRatingCacheTest { + private val title = HomeTitle("jellyfin-1", "A film", "movie", tmdbId = 42) + private val ratings = listOf(HomeRating("tmdb", 7.8)) + + @Test fun cacheIsSharedByCatalogueIdentityAndExpiresAtSixHours() { + var now = 0L + val cache = HomeRatingCache { now } + cache.save(title, ratings) + assertEquals(ratings, cache.read(title.copy(id = "another-server-copy"))) + now = 6L * 60 * 60 * 1000 - 1 + assertEquals(ratings, cache.read(title)) + now++ + assertNull(cache.read(title)) + } + + @Test fun cacheKeepsObservedAbsenceAndEvictsOldestBeyondCapacity() { + var now = 0L + val cache = HomeRatingCache { now++ } + cache.save(title, emptyList()) + assertEquals(emptyList(), cache.read(title)) + repeat(300) { index -> cache.save(title.copy(id = "other-$index", tmdbId = index + 100), ratings) } + assertNull(cache.read(title)) + assertEquals(ratings, cache.read(title.copy(id = "last", tmdbId = 399))) + } + + @Test fun decimalScoresFollowLocaleButPercentagesDoNot() { + assertEquals("7,8", ratings.single().format(Locale.forLanguageTag("nl-NL"))) + assertEquals("7.8", ratings.single().format(Locale.US)) + assertEquals("87%", HomeRating("fresh", 87.9, percentage = true).format(Locale.forLanguageTag("nl-NL"))) + } +} diff --git a/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/HomeRatingsUiTest.kt b/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/HomeRatingsUiTest.kt new file mode 100644 index 00000000..664ecba6 --- /dev/null +++ b/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/HomeRatingsUiTest.kt @@ -0,0 +1,67 @@ +package org.justinvisser.wolfskin.design + +import android.app.Application +import androidx.compose.foundation.layout.width +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.semantics.SemanticsActions +import androidx.compose.ui.test.* +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.text.TextLayoutResult +import java.util.Locale +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Assert.assertFalse +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +/** Real text layout proves locale formatting and the focus dwell instead of testing copied strings. */ +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [34], application = Application::class, qualifiers = "w960dp-h540dp-xhdpi") +class HomeRatingsUiTest { + @get:Rule val rule = createComposeRule() + + @Test fun dutchDecimalPlateStaysOnOneLine() { + val previous = Locale.getDefault() + Locale.setDefault(Locale.forLanguageTag("nl-NL")) + try { + rule.setContent { WolfskinTheme { HomeRatingPlate(listOf(HomeRating("tmdb", 7.8)), true, Modifier.width(WolfskinTokens.Card.posterW)) } } + val layouts = mutableListOf() + rule.onNodeWithText("7,8").performSemanticsAction(SemanticsActions.GetTextLayoutResult) { it(layouts) } + assertEquals(1, layouts.single().lineCount) + rule.onNodeWithText("7.8").assertDoesNotExist() + } finally { Locale.setDefault(previous) } + } + + @Test fun focusDwellDefersUncachedPlateButCachedFocusReturnsImmediately() { + val shell = ShellState(fixturePeople(), "justin").apply { pickerVisible = false } + val title = HomeTitle("dwell", "A film", "movie", ratings = listOf(HomeRating("tmdb", 7.8))) + val snapshot = HomeSnapshot(listOf(HomeRow("shelf", HomeRowKind.Shelf, "Shelf", listOf(title))), HomeAvailability.Ready) + rule.mainClock.autoAdvance = false + rule.setContent { WolfskinTheme { ShellSurface(shell) { modifier -> HomeScreen(shell, snapshot, modifier) {} } } } + rule.mainClock.advanceTimeBy(100) + rule.onNodeWithText("7.8").assertDoesNotExist() + rule.mainClock.advanceTimeBy(450) + rule.onNodeWithText("7.8").assertExists() + rule.onNodeWithTag("wolfskin-shell").performKeyInput { pressKey(Key.Back) } + rule.runOnIdle { assertTrue(shell.navFocused) } + rule.mainClock.advanceTimeBy(32) + rule.onNodeWithText("7.8").assertDoesNotExist() + rule.onNodeWithTag("wolfskin-shell").performKeyInput { pressKey(Key.DirectionDown) } + rule.runOnIdle { assertFalse(shell.navFocused) } + rule.mainClock.advanceTimeBy(32) + rule.onNodeWithText("7.8").assertExists() + } + + @Test fun signedOutHomeNeverDrawsHeroOrPlateScores() { + val shell = ShellState(fixturePeople(), "justin").apply { pickerVisible = false } + val title = HomeTitle("signed-out", "A film", "movie", ratings = listOf(HomeRating("tmdb", 7.8))) + val rows = buildHomeRows(HomeLibrary(nextUp = listOf(title)), listOf(homeDiscoverState(HomeAvailability.SignedOut))) + rule.setContent { WolfskinTheme { ShellSurface(shell) { modifier -> HomeScreen(shell, HomeSnapshot(rows), modifier) {} } } } + rule.mainClock.advanceTimeBy(1000) + rule.onNodeWithText("7.8").assertDoesNotExist() + } +} diff --git a/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/HomeRowsTest.kt b/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/HomeRowsTest.kt new file mode 100644 index 00000000..9fa7e9a9 --- /dev/null +++ b/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/HomeRowsTest.kt @@ -0,0 +1,50 @@ +package org.justinvisser.wolfskin.design + +import org.junit.Assert.* +import org.junit.Test + +/** Hero precedence and empty/error transitions preserve useful, focusable Home content. */ +class HomeRowsTest { + private val resume = HomeTitle("resume", "Resume", "movie") + private val next = HomeTitle("next", "Next", "episode") + private val newest = HomeTitle("new", "New", "movie") + private val pick = HomeTitle("pick", "Pick", "movie") + private val continuation = listOf(HomeRow("pk-m", HomeRowKind.Shelf, cards = listOf(pick))) + + @Test fun heroChainUsesResumeThenNextThenNewThenPickAndNeverRepeatsHeroOnShelves() { + val cases = listOf( + HomeLibrary(listOf(resume), listOf(next), listOf(newest)) to resume, + HomeLibrary(nextUp = listOf(next), newest = listOf(newest)) to next, + HomeLibrary(newest = listOf(newest)) to newest, + HomeLibrary() to pick, + ) + for ((library, expected) in cases) { + val rows = buildHomeRows(library, continuation) + assertEquals(expected.id, rows.first().cards.single().id) + assertFalse(rows.filter { it.id in setOf("cw", "nu", "new") }.flatMap { it.cards }.any { it.id == expected.id }) + } + } + + @Test fun heldShelvesAreUnwatchedFirstAndSeeAllUsesFullDestinationCount() { + val watched = newest.copy(id = "watched", played = true, addedAt = "2026-11-05") + val older = newest.copy(id = "older", addedAt = "2026-01-01") + val recent = newest.copy(id = "recent", addedAt = "2026-10-01") + val rows = buildHomeRows(HomeLibrary(destinations = mapOf("movies" to listOf(watched, older, recent)), destinationTotals = mapOf("movies" to 84)), continuation) + val shelf = rows.single { it.id == "lib-movies" } + assertEquals(listOf("recent", "older", "watched"), shelf.cards.map { it.id }) + assertEquals(84, shelf.total) + assertTrue(shelf.hasSeeAll) + } + + @Test fun unavailableLibraryKeepsDiscoverAndFocusableRecoveryInsteadOfHeldShelves() { + val rows = buildHomeRows(HomeLibrary(listOf(resume), listOf(next), listOf(newest), mapOf("movies" to listOf(newest))), continuation, libraryAvailable = false) + assertEquals(listOf("hero", "jf-down", "disc-h", "pk-m"), rows.map { it.id }) + assertTrue(rows.single { it.id == "jf-down" }.focusable) + } + + @Test fun firstReadSkeletonHasNoFocusableContent() { + val rows = buildHomeRows(HomeLibrary(), continuation, loading = true) + assertEquals(listOf(HomeRowKind.Hero, HomeRowKind.Shelf, HomeRowKind.Shelf, HomeRowKind.Shelf, HomeRowKind.Shelf), rows.map { it.kind }) + assertTrue(rows.all { !it.focusable }) + } +} diff --git a/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/LibraryCaptureTest.kt b/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/LibraryCaptureTest.kt index b4e00332..af8387f9 100644 --- a/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/LibraryCaptureTest.kt +++ b/wolfskin-design/src/test/java/org/justinvisser/wolfskin/design/LibraryCaptureTest.kt @@ -61,7 +61,7 @@ class LibraryCaptureTest { } rule.mainClock.advanceTimeBy(1000) rule.waitForIdle() - val directory = File(System.getProperty("wolfskin.captureDirectory")).apply { mkdirs() } + val directory = File(System.getProperty("wolfskin.captureDirectory"), "library").apply { mkdirs() } rule.onRoot().captureRoboImage(File(directory, "$name.png").absolutePath) } diff --git a/wolfskin-design/src/test/resources/wolfskin-home-presses.json b/wolfskin-design/src/test/resources/wolfskin-home-presses.json new file mode 100644 index 00000000..700edd60 --- /dev/null +++ b/wolfskin-design/src/test/resources/wolfskin-home-presses.json @@ -0,0 +1,126 @@ +[ + { + "goal": "Resume (Mindel, from launch)", + "keys": [ + "RIGHT", + "OK", + "OK" + ], + "presses": 3, + "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": "Play next (Justin has no resume)", + "keys": [ + "OK", + "OK" + ], + "presses": 2, + "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 \u00b7 Home hero \u2192 Trending (D2 split)", + "keys": [ + "OK", + "DOWN", + "DOWN", + "DOWN", + "DOWN", + "DOWN", + "DOWN", + "DOWN", + "DOWN", + "DOWN", + "DOWN", + "DOWN", + "DOWN" + ], + "presses": 13, + "note": "12 Downs after the launch OK", + "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 \u00b7 Trending \u2192 its See all card (D3: 20 cards)", + "keys": [ + "OK", + "DOWN", + "DOWN", + "DOWN", + "DOWN", + "DOWN", + "DOWN", + "DOWN", + "DOWN", + "DOWN", + "DOWN", + "DOWN", + "DOWN", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT", + "RIGHT" + ], + "presses": 33, + "note": "20 Rights from Trending", + "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 \u00b7 deep Home row: Back to the nav, Down to the same card", + "keys": [ + "OK", + "DOWN", + "DOWN", + "DOWN", + "DOWN", + "RIGHT", + "RIGHT", + "BACK", + "DOWN" + ], + "presses": 9, + "note": "2: Back, Down", + "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": "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" + } +] diff --git a/wolfskin-design/tokens.css b/wolfskin-design/tokens.css index f199bfec..51ea0d33 100644 --- a/wolfskin-design/tokens.css +++ b/wolfskin-design/tokens.css @@ -257,6 +257,64 @@ --lift-heroY: 30.0px; --lift-sheetBlur: 80.0px; --lift-sheetX: -30.0px; + --home-kickerGap: 12.0px; + --home-kickerBottom: 10.0px; + --home-titleBottom: 16.0px; + --home-factsHeight: 38.0px; + --home-factsBottom: 14.0px; + --home-factsGap: 22.0px; + --home-overviewBottom: 30.0px; + --home-cardPadding: 22.0px; + --home-cardOverlayTop: 54.0px; + --home-cardOverlayBottom: 18.0px; + --home-sectionTop: 40.0px; + --home-sectionAvatar: 56.0px; + --home-sectionGap: 22.0px; + --home-stateTextBottom: 24.0px; + --home-stateTop: 20.0px; + --home-statePaddingX: 40.0px; + --home-statePaddingY: 34.0px; + --home-stateGap: 8.0px; + --home-stateIcon: 36.0px; + --home-captionWidth: 640.0px; + --home-plateTop: 14.0px; + --home-plateSide: 16.0px; + --home-plateFade: 46.0px; + --home-ratingGap: 20.0px; + --home-ratingMarkGap: 8.0px; + --home-heroSkeletonWidth: 640.0px; + --home-heroSkeletonHeight: 84.0px; + --home-heroSkeletonFactsWidth: 460.0px; + --home-heroSkeletonFactsHeight: 34.0px; + --home-heroSkeletonButtonWidth: 260.0px; + --home-heroSkeletonGap: 22.0px; + --home-heroSkeletonButtonGap: 40.0px; + --home-artGlyph: 44.0px; + --home-cardSheetLeft: 22.0px; + --home-cardSheetTop: 18.0px; + --home-cardSheetInset: 36.0px; + --home-heroSheetRight: 180.0px; + --home-heroSheetTop: 150.0px; + --home-cardBlur: 26.0px; + --home-heroBlur: 60.0px; + --home-progressInset: 18.0px; + --home-progressBottom: 10.0px; + --home-endTop: 40.0px; + --home-frameBottom: 80.0px; + --home-toastPadding: 28.0px; + --home-toastTop: 150.0px; + --home-toastWidth: 760.0px; + --home-cardWashInset: 30.0px; + --home-heroWashInset: 80.0px; + --home-noteTop: 14.0px; + --home-noteGap: 16.0px; + --home-hairline: 2.0px; + --home-savedHeight: 52.0px; + --home-savedPadding: 20.0px; + --home-savedGap: 10.0px; + --home-savedIcon: 28.0px; + --home-savedRadius: 28.0px; + --home-posterProgressBottom: 12.0px; --discover-sheetPaddingX: 64.0px; --discover-sheetPaddingY: 80.0px; --discover-sheetGap: 18.0px; diff --git a/wolfskin-design/tokens.sha256 b/wolfskin-design/tokens.sha256 index 76f4ae8e..9bdfe655 100644 --- a/wolfskin-design/tokens.sha256 +++ b/wolfskin-design/tokens.sha256 @@ -1,6 +1,7 @@ 1f22175c1ce3987c4efa14d573601641a61e1a915e2c464c943b6c400356adb4 discover-tokens.json -fcff646d59fb39e859c34c05037e05a7fb7583de2fe07315ec0176cf4d597980 generate_tokens.py +a934faf6ce6972371455b51c18be7278e4f3d9daf6e1c32e230542a09ddb94c6 generate_tokens.py +9421e7b073f8eb9d170c7011eba62a0cbc4700c94ceb9ad2cc53966a718ee1d5 home-tokens.json 1dcf1136efaf0eb0cda4ab93266abe32d68325c63d7a967d7edb66f6ccd64f29 shell-tokens.json -25e2005949fe295dafd374ceef5c9d18822f041b11765e5e2425423a2a87eb95 src/main/java/org/justinvisser/wolfskin/design/WolfskinTokens.kt -305541a6ee86c52c8a8740c9164ca0a6fbf4ec332cc22941153a666b6f671b6f tokens.css +cc7ad4add4a0e8b2eb3286ac25c75c3ba18189f5c076101f562d00ed141d4d83 src/main/java/org/justinvisser/wolfskin/design/WolfskinTokens.kt +3b675f3e71e9e1e0e5713b40b8600d5a7fcb5b19f5c4c447f778ec8f1ccfa791 tokens.css 5b939f1f3eb90b428008fda75720f6e3a4e844c486668931db3d52bffb987b53 tokens.json