@burdenoff/microfe-hookmovies 2026.705.2 → 2026.706.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hookmovies/HookMoviesRoutes.js +26 -6
- package/dist/hookmovies/HookMoviesRoutes.js.map +1 -1
- package/dist/hookmovies/components/index.js +7 -0
- package/dist/hookmovies/hooks/index.js +1 -0
- package/dist/hookmovies/hooks/useFiles.js +97 -0
- package/dist/hookmovies/hooks/useFiles.js.map +1 -0
- package/dist/hookmovies/hooks/useHookMoviesApi.js +204 -52
- package/dist/hookmovies/hooks/useHookMoviesApi.js.map +1 -1
- package/dist/hookmovies/pages/HomePage.js +2 -2
- package/dist/hookmovies/pages/HomePage.js.map +1 -1
- package/dist/hookmovies/pages/auditions/AuditionDetailPage.js +100 -45
- package/dist/hookmovies/pages/auditions/AuditionDetailPage.js.map +1 -1
- package/dist/hookmovies/pages/catalog/TitleDetailPage.js +1340 -266
- package/dist/hookmovies/pages/catalog/TitleDetailPage.js.map +1 -1
- package/dist/hookmovies/pages/catalog/TitlesPage.js +95 -74
- package/dist/hookmovies/pages/catalog/TitlesPage.js.map +1 -1
- package/dist/hookmovies/pages/consumer/DiscoveryPage.js +422 -0
- package/dist/hookmovies/pages/consumer/DiscoveryPage.js.map +1 -0
- package/dist/hookmovies/pages/consumer/HooksPage.js +252 -0
- package/dist/hookmovies/pages/consumer/HooksPage.js.map +1 -0
- package/dist/hookmovies/pages/consumer/PlayerPage.js +542 -0
- package/dist/hookmovies/pages/consumer/PlayerPage.js.map +1 -0
- package/dist/hookmovies/pages/consumer/TitleViewPage.js +424 -0
- package/dist/hookmovies/pages/consumer/TitleViewPage.js.map +1 -0
- package/dist/hookmovies/pages/distribution/RightsWindowsPage.js +1 -1
- package/dist/hookmovies/pages/distribution/RightsWindowsPage.js.map +1 -1
- package/dist/hookmovies/pages/library/EntitlementsPage.js +273 -75
- package/dist/hookmovies/pages/library/EntitlementsPage.js.map +1 -1
- package/dist/hookmovies/pages/library/ListsPage.js +186 -106
- package/dist/hookmovies/pages/library/ListsPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TitlesPage.js","names":[],"sources":["../../../../src/hookmovies/pages/catalog/TitlesPage.tsx"],"sourcesContent":["/**\n * Titles list (Catalog)\n * @module hookmovies/pages/catalog\n *\n * Browse the workspace catalog via `hokListTitles` with type/status filters,\n * client-side text search, and offset pagination. Each row links to the title\n * detail page.\n */\n\nimport { type FC, useState } from 'react';\nimport { Search, Plus, Film } from 'lucide-react';\nimport { EmphasisPanel } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingState, ErrorState } from '../../components/StateViews';\nimport { Pagination } from '../../components/Pagination';\nimport { useHookMovies } from '../../context/HookMoviesContext';\nimport { useTitles, type HokTitleStatus, type HokTitleType } from '../../hooks/useHookMoviesApi';\nimport { cn } from '../../utils/cn';\nimport { formatEnumLabel } from '../../utils/formatters';\n\nconst TYPE_OPTIONS: HokTitleType[] = [\n 'MOVIE',\n 'SERIES',\n 'SHORT_FILM',\n 'DOCUMENTARY',\n 'ANIME',\n 'SHORT_FORM',\n 'LONG_FORM',\n 'LIVE_EVENT',\n 'TRAILER',\n 'COLLECTION',\n 'FAST_CHANNEL',\n];\n\nconst STATUS_OPTIONS: HokTitleStatus[] = [\n 'DRAFT',\n 'IN_REVIEW',\n 'SCHEDULED',\n 'PUBLISHED',\n 'UNPUBLISHED',\n 'ARCHIVED',\n];\n\nconst PAGE_LIMIT = 20;\n\nexport const TitlesPage: FC = () => {\n const { navigateTo } = useHookMovies();\n const [search, setSearch] = useState('');\n const [typeFilter, setTypeFilter] = useState<HokTitleType | ''>('');\n const [statusFilter, setStatusFilter] = useState<HokTitleStatus | ''>('');\n const [page, setPage] = useState(0);\n\n const { data, isLoading, isError, refetch } = useTitles(\n {\n type: typeFilter || undefined,\n status: statusFilter || undefined,\n },\n { limit: PAGE_LIMIT, offset: page * PAGE_LIMIT }\n );\n\n const items = data?.items ?? [];\n const filtered = search.trim()\n ? items.filter((t) => t.title.toLowerCase().includes(search.trim().toLowerCase()))\n : items;\n\n return (\n <PageLayout\n title=\"Catalog\"\n description=\"Movies, series, anime, documentaries & more\"\n purpose=\"The single source of truth for everything streamable in this workspace. Filter by type or status, then open a title to manage its credits, episodes, and publication lifecycle.\"\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('catalog/titles/new')}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 transition-opacity\"\n >\n <Plus className=\"h-4 w-4\" />\n New Title\n </button>\n }\n >\n <EmphasisPanel>\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <input\n type=\"text\"\n placeholder=\"Search titles by name…\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n className=\"w-full rounded-lg border border-border-subtle bg-bg-surface py-2.5 pl-10 pr-4 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n </EmphasisPanel>\n\n <div className=\"mt-4 flex flex-wrap items-center gap-3\">\n <select\n value={typeFilter}\n onChange={(e) => {\n setTypeFilter(e.target.value as HokTitleType | '');\n setPage(0);\n }}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"\">Any Type</option>\n {TYPE_OPTIONS.map((t) => (\n <option key={t} value={t}>\n {formatEnumLabel(t)}\n </option>\n ))}\n </select>\n <select\n value={statusFilter}\n onChange={(e) => {\n setStatusFilter(e.target.value as HokTitleStatus | '');\n setPage(0);\n }}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"\">Any Status</option>\n {STATUS_OPTIONS.map((s) => (\n <option key={s} value={s}>\n {formatEnumLabel(s)}\n </option>\n ))}\n </select>\n </div>\n\n <div className=\"mt-6\">\n {isLoading ? (\n <LoadingState label=\"Loading catalog…\" />\n ) : isError ? (\n <ErrorState\n title=\"Could not load titles\"\n message=\"The catalog service did not respond.\"\n onRetry={() => void refetch()}\n />\n ) : filtered.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Film className=\"h-12 w-12\" />}\n title=\"No titles found\"\n description={\n search\n ? `No titles match \"${search}\".`\n : 'No titles match these filters yet. Create your first title.'\n }\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('catalog/titles/new')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 transition-opacity\"\n >\n New Title\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {filtered.map((title) => (\n <button\n key={title.id}\n type=\"button\"\n onClick={() => navigateTo(`catalog/titles/${title.id}`)}\n className={cn(\n 'flex flex-col rounded-lg border border-border-seam bg-bg-surface p-4 text-left',\n 'hover:border-border-strong transition-colors'\n )}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <h3 className=\"line-clamp-2 text-sm font-semibold text-text-primary\">\n {title.title}\n </h3>\n <StatusBadge status={title.status} />\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">\n {formatEnumLabel(title.type)}\n {title.releaseYear ? ` · ${title.releaseYear}` : ''}\n {title.runtimeMinutes ? ` · ${title.runtimeMinutes} min` : ''}\n </p>\n {title.synopsis && (\n <p className=\"mt-2 line-clamp-2 text-xs text-text-secondary\">{title.synopsis}</p>\n )}\n {title.genres.length > 0 && (\n <div className=\"mt-3 flex flex-wrap gap-1\">\n {title.genres.slice(0, 3).map((g) => (\n <span\n key={g}\n className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-[10px] text-text-secondary\"\n >\n {g}\n </span>\n ))}\n </div>\n )}\n </button>\n ))}\n </div>\n )}\n\n {!isLoading && !isError && !search && (\n <Pagination\n page={page}\n hasMore={data?.pageInfo.hasMore ?? false}\n onPageChange={setPage}\n shownCount={items.length}\n />\n )}\n </div>\n </PageLayout>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TitlesPage.js","names":[],"sources":["../../../../src/hookmovies/pages/catalog/TitlesPage.tsx"],"sourcesContent":["/**\n * Titles list (Catalog)\n * @module hookmovies/pages/catalog\n *\n * Browse the workspace catalog via `hokListTitles` with type/status filters,\n * server-side text search (debounced), and offset pagination. Each row links to\n * the title detail page.\n */\n\nimport { type FC, useEffect, useState } from 'react';\nimport { Search, Plus, Film } from 'lucide-react';\nimport { EmphasisPanel } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingState, ErrorState } from '../../components/StateViews';\nimport { Pagination } from '../../components/Pagination';\nimport { useHookMovies } from '../../context/HookMoviesContext';\nimport { useTitles, type HokTitleStatus, type HokTitleType } from '../../hooks/useHookMoviesApi';\nimport { cn } from '../../utils/cn';\nimport { formatEnumLabel } from '../../utils/formatters';\n\nconst TYPE_OPTIONS: HokTitleType[] = [\n 'MOVIE',\n 'SERIES',\n 'SHORT_FILM',\n 'DOCUMENTARY',\n 'ANIME',\n 'SHORT_FORM',\n 'LONG_FORM',\n 'LIVE_EVENT',\n 'TRAILER',\n 'COLLECTION',\n 'FAST_CHANNEL',\n];\n\nconst STATUS_OPTIONS: HokTitleStatus[] = [\n 'DRAFT',\n 'IN_REVIEW',\n 'SCHEDULED',\n 'PUBLISHED',\n 'UNPUBLISHED',\n 'ARCHIVED',\n];\n\nconst PAGE_LIMIT = 20;\n\nexport const TitlesPage: FC = () => {\n const { navigateTo } = useHookMovies();\n const [search, setSearch] = useState('');\n const [debouncedSearch, setDebouncedSearch] = useState('');\n const [typeFilter, setTypeFilter] = useState<HokTitleType | ''>('');\n const [statusFilter, setStatusFilter] = useState<HokTitleStatus | ''>('');\n const [page, setPage] = useState(0);\n\n // Debounce the search box (~300ms) so each keystroke doesn't fire a query.\n useEffect(() => {\n const handle = window.setTimeout(() => setDebouncedSearch(search.trim()), 300);\n return () => window.clearTimeout(handle);\n }, [search]);\n\n // Reset to the first page whenever the effective (debounced) search changes,\n // so server-side results always start from the top of the filtered set.\n useEffect(() => {\n setPage(0);\n }, [debouncedSearch]);\n\n const { data, isLoading, isError, refetch } = useTitles(\n {\n type: typeFilter || undefined,\n status: statusFilter || undefined,\n },\n { limit: PAGE_LIMIT, offset: page * PAGE_LIMIT },\n debouncedSearch || undefined\n );\n\n const items = data?.items ?? [];\n const totalCount = data?.totalCount ?? 0;\n\n return (\n <PageLayout\n title=\"Catalog\"\n description=\"Movies, series, anime, documentaries & more\"\n purpose=\"The single source of truth for everything streamable in this workspace. Filter by type or status, then open a title to manage its credits, episodes, and publication lifecycle.\"\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('catalog/titles/new')}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 transition-opacity\"\n >\n <Plus className=\"h-4 w-4\" />\n New Title\n </button>\n }\n >\n <EmphasisPanel>\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <input\n type=\"search\"\n aria-label=\"Search titles\"\n placeholder=\"Search titles by name…\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n className=\"w-full rounded-lg border border-border-subtle bg-bg-surface py-2.5 pl-10 pr-4 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n </EmphasisPanel>\n\n <div className=\"mt-4 flex flex-wrap items-center gap-3\">\n <select\n value={typeFilter}\n onChange={(e) => {\n setTypeFilter(e.target.value as HokTitleType | '');\n setPage(0);\n }}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"\">Any Type</option>\n {TYPE_OPTIONS.map((t) => (\n <option key={t} value={t}>\n {formatEnumLabel(t)}\n </option>\n ))}\n </select>\n <select\n value={statusFilter}\n onChange={(e) => {\n setStatusFilter(e.target.value as HokTitleStatus | '');\n setPage(0);\n }}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-xs text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"\">Any Status</option>\n {STATUS_OPTIONS.map((s) => (\n <option key={s} value={s}>\n {formatEnumLabel(s)}\n </option>\n ))}\n </select>\n {!isLoading && !isError && (\n <p className=\"ml-auto text-xs text-text-secondary\" aria-live=\"polite\">\n {totalCount} {totalCount === 1 ? 'title' : 'titles'}\n {debouncedSearch ? ` matching “${debouncedSearch}”` : ''}\n </p>\n )}\n </div>\n\n <div className=\"mt-6\">\n {isLoading ? (\n <LoadingState label=\"Loading catalog…\" />\n ) : isError ? (\n <ErrorState\n title=\"Could not load titles\"\n message=\"The catalog service did not respond.\"\n onRetry={() => void refetch()}\n />\n ) : items.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Film className=\"h-12 w-12\" />}\n title=\"No titles found\"\n description={\n debouncedSearch\n ? `No titles match \"${debouncedSearch}\".`\n : 'No titles match these filters yet. Create your first title.'\n }\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('catalog/titles/new')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:opacity-90 transition-opacity\"\n >\n New Title\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {items.map((title) => (\n <button\n key={title.id}\n type=\"button\"\n onClick={() => navigateTo(`catalog/titles/${title.id}`)}\n className={cn(\n 'flex flex-col rounded-lg border border-border-seam bg-bg-surface p-4 text-left',\n 'hover:border-border-strong transition-colors'\n )}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <h3 className=\"line-clamp-2 text-sm font-semibold text-text-primary\">\n {title.title}\n </h3>\n <StatusBadge status={title.status} />\n </div>\n <p className=\"mt-1 text-xs text-text-secondary\">\n {formatEnumLabel(title.type)}\n {title.releaseYear ? ` · ${title.releaseYear}` : ''}\n {title.runtimeMinutes ? ` · ${title.runtimeMinutes} min` : ''}\n </p>\n {title.synopsis && (\n <p className=\"mt-2 line-clamp-2 text-xs text-text-secondary\">{title.synopsis}</p>\n )}\n {title.genres.length > 0 && (\n <div className=\"mt-3 flex flex-wrap gap-1\">\n {title.genres.slice(0, 3).map((g) => (\n <span\n key={g}\n className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-[10px] text-text-secondary\"\n >\n {g}\n </span>\n ))}\n </div>\n )}\n </button>\n ))}\n </div>\n )}\n\n {!isLoading && !isError && (\n <Pagination\n page={page}\n hasMore={data?.pageInfo.hasMore ?? false}\n onPageChange={setPage}\n shownCount={items.length}\n />\n )}\n </div>\n </PageLayout>\n );\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
import { useHookMovies as e } from "../../context/HookMoviesContext.js";
|
|
2
|
+
import { cn as t } from "../../utils/cn.js";
|
|
3
|
+
import { useCheckEntitlement as n, useMyWatchProgress as r, useTitles as i } from "../../hooks/useHookMoviesApi.js";
|
|
4
|
+
import { formatEnumLabel as a } from "../../utils/formatters.js";
|
|
5
|
+
import { useFileUrl as o } from "../../hooks/useFiles.js";
|
|
6
|
+
import { ErrorState as s, LoadingState as c } from "../../components/StateViews.js";
|
|
7
|
+
import { EmptyState as l } from "../../components/EmptyState.js";
|
|
8
|
+
import { useEffect as u, useMemo as d, useState as f } from "react";
|
|
9
|
+
import { ChevronRight as p, Clapperboard as m, Film as h, Flame as g, History as _, Info as v, Lock as y, Play as b, Search as x, Sparkles as S, Tv as C } from "lucide-react";
|
|
10
|
+
import { Fragment as w, jsx as T, jsxs as E } from "react/jsx-runtime";
|
|
11
|
+
import { AuroraBackground as D } from "@burdenoff/fe-libs/ui";
|
|
12
|
+
//#region src/hookmovies/pages/consumer/DiscoveryPage.tsx
|
|
13
|
+
var O = {
|
|
14
|
+
limit: 20,
|
|
15
|
+
offset: 0
|
|
16
|
+
}, k = {
|
|
17
|
+
limit: 24,
|
|
18
|
+
offset: 0
|
|
19
|
+
}, A = {
|
|
20
|
+
limit: 12,
|
|
21
|
+
offset: 0
|
|
22
|
+
}, j = ({ fileId: e, alt: n, fallbackIcon: r }) => {
|
|
23
|
+
let { data: i, isLoading: a } = o(e), [s, c] = f(!1);
|
|
24
|
+
return /* @__PURE__ */ T("div", {
|
|
25
|
+
className: "absolute inset-0 flex items-center justify-center bg-bg-sunken",
|
|
26
|
+
children: e && i && !s ? /* @__PURE__ */ T("img", {
|
|
27
|
+
src: i ?? void 0,
|
|
28
|
+
alt: n,
|
|
29
|
+
loading: "lazy",
|
|
30
|
+
onError: () => c(!0),
|
|
31
|
+
className: "h-full w-full object-cover"
|
|
32
|
+
}) : /* @__PURE__ */ T("div", {
|
|
33
|
+
className: t("flex h-full w-full items-center justify-center text-text-muted", a && !!e && "motion-safe:animate-pulse"),
|
|
34
|
+
"aria-hidden": "true",
|
|
35
|
+
children: r
|
|
36
|
+
})
|
|
37
|
+
});
|
|
38
|
+
}, M = ({ title: e, onOpen: n }) => {
|
|
39
|
+
let r = [
|
|
40
|
+
a(e.type),
|
|
41
|
+
e.releaseYear ? String(e.releaseYear) : null,
|
|
42
|
+
e.originalLanguage ? e.originalLanguage.toUpperCase() : null
|
|
43
|
+
].filter(Boolean).join(" · ");
|
|
44
|
+
return /* @__PURE__ */ E("button", {
|
|
45
|
+
type: "button",
|
|
46
|
+
onClick: () => n(e.id),
|
|
47
|
+
"aria-label": `Open ${e.title}`,
|
|
48
|
+
className: "group w-36 shrink-0 snap-start text-left focus:outline-none sm:w-44",
|
|
49
|
+
children: [
|
|
50
|
+
/* @__PURE__ */ E("div", {
|
|
51
|
+
className: t("relative aspect-[2/3] overflow-hidden rounded-xl border border-border-seam", "shadow-elevation-1 transition-[border-color,box-shadow] duration-200", "group-hover:border-border-strong group-hover:shadow-elevation-2", "group-focus-visible:ring-2 group-focus-visible:ring-[var(--color-focus-ring)]"),
|
|
52
|
+
children: [
|
|
53
|
+
/* @__PURE__ */ T("div", {
|
|
54
|
+
className: "motion-safe:transition-transform motion-safe:duration-300 motion-safe:group-hover:scale-105 absolute inset-0",
|
|
55
|
+
children: /* @__PURE__ */ T(j, {
|
|
56
|
+
fileId: e.posterFileId,
|
|
57
|
+
alt: `${e.title} poster`,
|
|
58
|
+
fallbackIcon: /* @__PURE__ */ T(h, { className: "h-8 w-8" })
|
|
59
|
+
})
|
|
60
|
+
}),
|
|
61
|
+
/* @__PURE__ */ T("span", {
|
|
62
|
+
className: "absolute left-2 top-2 rounded-full bg-bg-canvas/80 px-2 py-0.5 text-[10px] font-medium text-text-secondary backdrop-blur-sm",
|
|
63
|
+
children: a(e.type)
|
|
64
|
+
}),
|
|
65
|
+
e.aiDisclosure && /* @__PURE__ */ E("span", {
|
|
66
|
+
className: "absolute right-2 top-2 inline-flex items-center gap-1 rounded-full bg-bg-canvas/80 px-2 py-0.5 text-[10px] font-medium text-text-secondary backdrop-blur-sm",
|
|
67
|
+
title: "Contains AI-generated or AI-altered content",
|
|
68
|
+
children: [/* @__PURE__ */ T(S, { className: "h-3 w-3" }), "AI"]
|
|
69
|
+
}),
|
|
70
|
+
/* @__PURE__ */ E("span", {
|
|
71
|
+
className: t("pointer-events-none absolute inset-x-0 bottom-0 flex items-center gap-1.5", "bg-gradient-to-t from-bg-canvas/90 to-transparent px-3 pb-2 pt-8", "text-xs font-medium text-text-primary opacity-0 transition-opacity duration-200", "group-hover:opacity-100 group-focus-visible:opacity-100"),
|
|
72
|
+
children: [/* @__PURE__ */ T(b, { className: "h-3.5 w-3.5 fill-current" }), "Watch"]
|
|
73
|
+
})
|
|
74
|
+
]
|
|
75
|
+
}),
|
|
76
|
+
/* @__PURE__ */ T("p", {
|
|
77
|
+
className: "mt-2 line-clamp-1 text-sm font-medium text-text-primary",
|
|
78
|
+
children: e.title
|
|
79
|
+
}),
|
|
80
|
+
r && /* @__PURE__ */ T("p", {
|
|
81
|
+
className: "mt-0.5 line-clamp-1 text-xs text-text-secondary",
|
|
82
|
+
children: r
|
|
83
|
+
})
|
|
84
|
+
]
|
|
85
|
+
});
|
|
86
|
+
}, N = ({ progress: e, posterFileId: n, onResume: r }) => {
|
|
87
|
+
let i = Math.max(0, Math.round(e.positionSeconds / 60)), o = e.title?.title ?? "Untitled";
|
|
88
|
+
return /* @__PURE__ */ T("button", {
|
|
89
|
+
type: "button",
|
|
90
|
+
onClick: () => r(e.titleId),
|
|
91
|
+
"aria-label": `Resume ${o}`,
|
|
92
|
+
className: "group w-64 shrink-0 snap-start text-left focus:outline-none sm:w-72",
|
|
93
|
+
children: /* @__PURE__ */ E("div", {
|
|
94
|
+
className: t("relative aspect-video overflow-hidden rounded-xl border border-border-seam", "shadow-elevation-1 transition-[border-color,box-shadow] duration-200", "group-hover:border-border-strong group-hover:shadow-elevation-2", "group-focus-visible:ring-2 group-focus-visible:ring-[var(--color-focus-ring)]"),
|
|
95
|
+
children: [
|
|
96
|
+
/* @__PURE__ */ T(j, {
|
|
97
|
+
fileId: n,
|
|
98
|
+
alt: `${o} artwork`,
|
|
99
|
+
fallbackIcon: /* @__PURE__ */ T(m, { className: "h-8 w-8" })
|
|
100
|
+
}),
|
|
101
|
+
/* @__PURE__ */ T("span", {
|
|
102
|
+
className: t("absolute inset-0 flex items-center justify-center", "bg-bg-canvas/40 opacity-0 transition-opacity duration-200", "group-hover:opacity-100 group-focus-visible:opacity-100"),
|
|
103
|
+
children: /* @__PURE__ */ E("span", {
|
|
104
|
+
className: "inline-flex items-center gap-1.5 rounded-full bg-action-primary-bg px-3 py-1.5 text-xs font-semibold text-action-primary-text",
|
|
105
|
+
children: [/* @__PURE__ */ T(b, { className: "h-3.5 w-3.5 fill-current" }), "Resume"]
|
|
106
|
+
})
|
|
107
|
+
}),
|
|
108
|
+
/* @__PURE__ */ E("span", {
|
|
109
|
+
className: "absolute inset-x-0 bottom-0 bg-gradient-to-t from-bg-canvas/90 to-transparent px-3 pb-2 pt-8",
|
|
110
|
+
children: [/* @__PURE__ */ T("span", {
|
|
111
|
+
className: "line-clamp-1 block text-sm font-semibold text-text-primary",
|
|
112
|
+
children: o
|
|
113
|
+
}), /* @__PURE__ */ E("span", {
|
|
114
|
+
className: "text-xs text-text-secondary",
|
|
115
|
+
children: [
|
|
116
|
+
e.title ? `${a(e.title.type)} · ` : "",
|
|
117
|
+
i,
|
|
118
|
+
" min in"
|
|
119
|
+
]
|
|
120
|
+
})]
|
|
121
|
+
})
|
|
122
|
+
]
|
|
123
|
+
})
|
|
124
|
+
});
|
|
125
|
+
}, P = ({ title: e, icon: n, loading: r = !1, children: i, skeletonCount: a = 6, skeletonShape: o = "poster" }) => /* @__PURE__ */ E("section", {
|
|
126
|
+
className: "mt-8 first:mt-6",
|
|
127
|
+
children: [/* @__PURE__ */ E("div", {
|
|
128
|
+
className: "mb-3 flex items-center gap-2 px-4 sm:px-6",
|
|
129
|
+
children: [/* @__PURE__ */ T("span", {
|
|
130
|
+
className: "text-action-primary-bg",
|
|
131
|
+
children: n
|
|
132
|
+
}), /* @__PURE__ */ T("h2", {
|
|
133
|
+
className: "text-base font-semibold text-text-primary sm:text-lg",
|
|
134
|
+
children: e
|
|
135
|
+
})]
|
|
136
|
+
}), /* @__PURE__ */ T("div", {
|
|
137
|
+
className: t("flex gap-3 overflow-x-auto px-4 pb-3 sm:px-6", "snap-x scroll-px-4 sm:scroll-px-6", "[scrollbar-width:thin]"),
|
|
138
|
+
children: r ? Array.from({ length: a }).map((e, n) => /* @__PURE__ */ T("div", {
|
|
139
|
+
className: t("shrink-0 rounded-xl border border-border-seam bg-bg-sunken", "motion-safe:animate-pulse", o === "poster" ? "aspect-[2/3] w-36 sm:w-44" : "aspect-video w-64 sm:w-72"),
|
|
140
|
+
"aria-hidden": "true"
|
|
141
|
+
}, n)) : i
|
|
142
|
+
})]
|
|
143
|
+
}), F = ({ title: e, onPlay: t, onMoreInfo: r }) => {
|
|
144
|
+
let { data: i } = o(e.backdropFileId ?? e.posterFileId), s = n(e.id), [c, l] = f(!1), u = !!i && !c;
|
|
145
|
+
return /* @__PURE__ */ E("section", {
|
|
146
|
+
className: "relative overflow-hidden border-b border-border-seam bg-hero-mesh",
|
|
147
|
+
children: [
|
|
148
|
+
!u && /* @__PURE__ */ T(D, { intensity: "normal" }),
|
|
149
|
+
u && /* @__PURE__ */ T("img", {
|
|
150
|
+
src: i ?? void 0,
|
|
151
|
+
alt: "",
|
|
152
|
+
onError: () => l(!0),
|
|
153
|
+
className: "absolute inset-0 h-full w-full object-cover",
|
|
154
|
+
"aria-hidden": "true"
|
|
155
|
+
}),
|
|
156
|
+
/* @__PURE__ */ T("div", {
|
|
157
|
+
className: "absolute inset-0 bg-gradient-to-t from-bg-canvas via-bg-canvas/60 to-transparent",
|
|
158
|
+
"aria-hidden": "true"
|
|
159
|
+
}),
|
|
160
|
+
/* @__PURE__ */ T("div", {
|
|
161
|
+
className: "absolute inset-0 bg-gradient-to-r from-bg-canvas/90 via-bg-canvas/40 to-transparent",
|
|
162
|
+
"aria-hidden": "true"
|
|
163
|
+
}),
|
|
164
|
+
/* @__PURE__ */ T("div", {
|
|
165
|
+
className: "relative flex min-h-[22rem] flex-col justify-end gap-4 px-4 py-8 sm:min-h-[26rem] sm:px-6 sm:py-10 lg:min-h-[30rem]",
|
|
166
|
+
children: /* @__PURE__ */ E("div", {
|
|
167
|
+
className: "max-w-2xl",
|
|
168
|
+
children: [
|
|
169
|
+
/* @__PURE__ */ E("div", {
|
|
170
|
+
className: "mb-3 flex flex-wrap items-center gap-2",
|
|
171
|
+
children: [
|
|
172
|
+
/* @__PURE__ */ E("span", {
|
|
173
|
+
className: "inline-flex items-center gap-1.5 rounded-full bg-action-primary-bg/15 px-2.5 py-0.5 text-xs font-medium text-action-primary-bg",
|
|
174
|
+
children: [/* @__PURE__ */ T(g, { className: "h-3.5 w-3.5" }), "Featured"]
|
|
175
|
+
}),
|
|
176
|
+
/* @__PURE__ */ T("span", {
|
|
177
|
+
className: "rounded-full bg-bg-canvas/70 px-2.5 py-0.5 text-xs font-medium text-text-secondary backdrop-blur-sm",
|
|
178
|
+
children: a(e.type)
|
|
179
|
+
}),
|
|
180
|
+
e.releaseYear && /* @__PURE__ */ T("span", {
|
|
181
|
+
className: "rounded-full bg-bg-canvas/70 px-2.5 py-0.5 text-xs font-medium text-text-secondary backdrop-blur-sm",
|
|
182
|
+
children: e.releaseYear
|
|
183
|
+
}),
|
|
184
|
+
e.certification && /* @__PURE__ */ T("span", {
|
|
185
|
+
className: "rounded-full border border-border-seam px-2 py-0.5 text-xs font-medium text-text-secondary",
|
|
186
|
+
children: e.certification
|
|
187
|
+
})
|
|
188
|
+
]
|
|
189
|
+
}),
|
|
190
|
+
/* @__PURE__ */ T("h1", {
|
|
191
|
+
className: "text-3xl font-bold tracking-tight text-text-primary sm:text-4xl lg:text-5xl",
|
|
192
|
+
children: e.title
|
|
193
|
+
}),
|
|
194
|
+
e.synopsis && /* @__PURE__ */ T("p", {
|
|
195
|
+
className: "mt-3 line-clamp-3 max-w-xl text-sm text-text-secondary sm:text-base",
|
|
196
|
+
children: e.synopsis
|
|
197
|
+
}),
|
|
198
|
+
e.genres.length > 0 && /* @__PURE__ */ T("div", {
|
|
199
|
+
className: "mt-3 flex flex-wrap gap-1.5",
|
|
200
|
+
children: e.genres.slice(0, 4).map((e) => /* @__PURE__ */ T("span", {
|
|
201
|
+
className: "rounded-full bg-bg-canvas/60 px-2.5 py-0.5 text-xs text-text-secondary backdrop-blur-sm",
|
|
202
|
+
children: e
|
|
203
|
+
}, e))
|
|
204
|
+
}),
|
|
205
|
+
s.data?.entitled && s.data.modes.length > 0 && /* @__PURE__ */ T("div", {
|
|
206
|
+
className: "mt-3 flex flex-wrap gap-1.5",
|
|
207
|
+
children: s.data.modes.map((e) => /* @__PURE__ */ T("span", {
|
|
208
|
+
className: "rounded-full bg-status-success-bg-subtle px-2.5 py-0.5 text-xs font-medium text-status-success-text",
|
|
209
|
+
children: a(e)
|
|
210
|
+
}, e))
|
|
211
|
+
}),
|
|
212
|
+
/* @__PURE__ */ E("div", {
|
|
213
|
+
className: "mt-5 flex flex-wrap items-center gap-3",
|
|
214
|
+
children: [
|
|
215
|
+
/* @__PURE__ */ E("button", {
|
|
216
|
+
type: "button",
|
|
217
|
+
onClick: () => t(e.id),
|
|
218
|
+
className: "inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-5 py-2.5 text-sm font-semibold text-action-primary-text shadow-elevation-1 transition-opacity hover:opacity-90",
|
|
219
|
+
children: [/* @__PURE__ */ T(b, { className: "h-4 w-4 fill-current" }), "Play"]
|
|
220
|
+
}),
|
|
221
|
+
/* @__PURE__ */ E("button", {
|
|
222
|
+
type: "button",
|
|
223
|
+
onClick: () => r(e.id),
|
|
224
|
+
className: "inline-flex items-center gap-2 rounded-lg border border-border-strong bg-bg-canvas/60 px-5 py-2.5 text-sm font-semibold text-text-primary backdrop-blur-sm transition-colors hover:bg-bg-sunken",
|
|
225
|
+
children: [/* @__PURE__ */ T(v, { className: "h-4 w-4" }), "More info"]
|
|
226
|
+
}),
|
|
227
|
+
s.data && !s.data.entitled && /* @__PURE__ */ E("span", {
|
|
228
|
+
className: "inline-flex items-center gap-1.5 text-xs text-text-secondary",
|
|
229
|
+
children: [/* @__PURE__ */ T(y, { className: "h-3.5 w-3.5" }), "Sign-in or entitlement required to play"]
|
|
230
|
+
})
|
|
231
|
+
]
|
|
232
|
+
})
|
|
233
|
+
]
|
|
234
|
+
})
|
|
235
|
+
})
|
|
236
|
+
]
|
|
237
|
+
});
|
|
238
|
+
}, I = () => {
|
|
239
|
+
let { navigateTo: t } = e(), [n, a] = f(""), [o, v] = f("");
|
|
240
|
+
u(() => {
|
|
241
|
+
let e = window.setTimeout(() => v(n.trim()), 300);
|
|
242
|
+
return () => window.clearTimeout(e);
|
|
243
|
+
}, [n]);
|
|
244
|
+
let y = o.length > 0, b = i({ status: "PUBLISHED" }, A), D = i({
|
|
245
|
+
status: "PUBLISHED",
|
|
246
|
+
type: "MOVIE"
|
|
247
|
+
}, O), j = i({
|
|
248
|
+
status: "PUBLISHED",
|
|
249
|
+
type: "SERIES"
|
|
250
|
+
}, O), I = i({
|
|
251
|
+
status: "PUBLISHED",
|
|
252
|
+
type: "ANIME"
|
|
253
|
+
}, O), L = i({
|
|
254
|
+
status: "PUBLISHED",
|
|
255
|
+
type: "DOCUMENTARY"
|
|
256
|
+
}, O), R = r(), z = i({ status: "PUBLISHED" }, k, y ? o : void 0), B = d(() => {
|
|
257
|
+
let e = /* @__PURE__ */ new Map();
|
|
258
|
+
for (let t of [
|
|
259
|
+
b.data,
|
|
260
|
+
D.data,
|
|
261
|
+
j.data,
|
|
262
|
+
I.data,
|
|
263
|
+
L.data
|
|
264
|
+
]) for (let n of t?.items ?? []) e.set(n.id, n);
|
|
265
|
+
return e;
|
|
266
|
+
}, [
|
|
267
|
+
b.data,
|
|
268
|
+
D.data,
|
|
269
|
+
j.data,
|
|
270
|
+
I.data,
|
|
271
|
+
L.data
|
|
272
|
+
]), V = d(() => {
|
|
273
|
+
let e = b.data?.items ?? [];
|
|
274
|
+
return e.find((e) => e.backdropFileId) ?? e[0] ?? null;
|
|
275
|
+
}, [b.data]), H = d(() => (R.data ?? []).filter((e) => !e.completed).slice(0, 12), [R.data]), U = (e) => t(`title/${e}`), W = (e) => t(`watch/${e}`), G = () => {
|
|
276
|
+
b.refetch(), D.refetch(), j.refetch(), I.refetch(), L.refetch(), R.refetch();
|
|
277
|
+
};
|
|
278
|
+
if (b.isLoading) return /* @__PURE__ */ T("div", {
|
|
279
|
+
className: "flex h-full flex-col bg-bg-canvas",
|
|
280
|
+
children: /* @__PURE__ */ T(c, { label: "Loading titles…" })
|
|
281
|
+
});
|
|
282
|
+
if (b.isError) return /* @__PURE__ */ T("div", {
|
|
283
|
+
className: "flex h-full flex-col bg-bg-canvas",
|
|
284
|
+
children: /* @__PURE__ */ T(s, {
|
|
285
|
+
title: "Could not load HookMovies",
|
|
286
|
+
message: "The catalog service did not respond. Please try again.",
|
|
287
|
+
onRetry: G
|
|
288
|
+
})
|
|
289
|
+
});
|
|
290
|
+
let K = (b.data?.items.length ?? 0) > 0;
|
|
291
|
+
return /* @__PURE__ */ E("div", {
|
|
292
|
+
className: "flex h-full flex-col overflow-y-auto bg-bg-canvas",
|
|
293
|
+
children: [/* @__PURE__ */ T("div", {
|
|
294
|
+
className: "sticky top-0 z-10 border-b border-border-seam bg-bg-canvas/80 px-4 py-3 backdrop-blur-md sm:px-6",
|
|
295
|
+
children: /* @__PURE__ */ E("div", {
|
|
296
|
+
className: "relative mx-auto max-w-2xl",
|
|
297
|
+
children: [/* @__PURE__ */ T(x, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary" }), /* @__PURE__ */ T("input", {
|
|
298
|
+
type: "search",
|
|
299
|
+
"aria-label": "Search titles",
|
|
300
|
+
placeholder: "Search movies, series, anime & more…",
|
|
301
|
+
value: n,
|
|
302
|
+
onChange: (e) => a(e.target.value),
|
|
303
|
+
className: "w-full rounded-full border border-border-subtle bg-bg-surface py-2.5 pl-10 pr-4 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]"
|
|
304
|
+
})]
|
|
305
|
+
})
|
|
306
|
+
}), y ? /* @__PURE__ */ E("div", {
|
|
307
|
+
className: "px-4 py-6 sm:px-6",
|
|
308
|
+
children: [/* @__PURE__ */ E("div", {
|
|
309
|
+
className: "mb-4 flex items-center gap-2",
|
|
310
|
+
children: [/* @__PURE__ */ E("h2", {
|
|
311
|
+
className: "text-base font-semibold text-text-primary sm:text-lg",
|
|
312
|
+
children: [
|
|
313
|
+
"Results for “",
|
|
314
|
+
o,
|
|
315
|
+
"”"
|
|
316
|
+
]
|
|
317
|
+
}), !z.isLoading && !z.isError && /* @__PURE__ */ E("span", {
|
|
318
|
+
className: "text-xs text-text-secondary",
|
|
319
|
+
"aria-live": "polite",
|
|
320
|
+
children: [z.data?.totalCount ?? 0, " found"]
|
|
321
|
+
})]
|
|
322
|
+
}), z.isLoading ? /* @__PURE__ */ T(c, { label: "Searching…" }) : z.isError ? /* @__PURE__ */ T(s, {
|
|
323
|
+
title: "Search failed",
|
|
324
|
+
message: "Could not run that search. Please try again.",
|
|
325
|
+
onRetry: () => void z.refetch()
|
|
326
|
+
}) : (z.data?.items.length ?? 0) === 0 ? /* @__PURE__ */ T(l, {
|
|
327
|
+
illustration: "empty-search",
|
|
328
|
+
title: "No matches",
|
|
329
|
+
description: `Nothing published matches “${o}”. Try a different title, genre, or spelling.`
|
|
330
|
+
}) : /* @__PURE__ */ T("div", {
|
|
331
|
+
className: "grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6",
|
|
332
|
+
children: (z.data?.items ?? []).map((e) => /* @__PURE__ */ T(M, {
|
|
333
|
+
title: e,
|
|
334
|
+
onOpen: U
|
|
335
|
+
}, e.id))
|
|
336
|
+
})]
|
|
337
|
+
}) : K ? /* @__PURE__ */ E(w, { children: [V && /* @__PURE__ */ T(F, {
|
|
338
|
+
title: V,
|
|
339
|
+
onPlay: W,
|
|
340
|
+
onMoreInfo: U
|
|
341
|
+
}), /* @__PURE__ */ E("div", {
|
|
342
|
+
className: "pb-10",
|
|
343
|
+
children: [
|
|
344
|
+
H.length > 0 && /* @__PURE__ */ T(P, {
|
|
345
|
+
title: "Continue Watching",
|
|
346
|
+
icon: /* @__PURE__ */ T(_, { className: "h-5 w-5" }),
|
|
347
|
+
loading: R.isLoading,
|
|
348
|
+
skeletonShape: "landscape",
|
|
349
|
+
children: H.map((e) => /* @__PURE__ */ T(N, {
|
|
350
|
+
progress: e,
|
|
351
|
+
posterFileId: B.get(e.titleId)?.posterFileId ?? null,
|
|
352
|
+
onResume: W
|
|
353
|
+
}, e.id))
|
|
354
|
+
}),
|
|
355
|
+
(b.data?.items.length ?? 0) > 0 && /* @__PURE__ */ T(P, {
|
|
356
|
+
title: "New & Notable",
|
|
357
|
+
icon: /* @__PURE__ */ T(g, { className: "h-5 w-5" }),
|
|
358
|
+
children: (b.data?.items ?? []).map((e) => /* @__PURE__ */ T(M, {
|
|
359
|
+
title: e,
|
|
360
|
+
onOpen: U
|
|
361
|
+
}, e.id))
|
|
362
|
+
}),
|
|
363
|
+
(D.isLoading || (D.data?.items.length ?? 0) > 0) && /* @__PURE__ */ T(P, {
|
|
364
|
+
title: "Movies",
|
|
365
|
+
icon: /* @__PURE__ */ T(m, { className: "h-5 w-5" }),
|
|
366
|
+
loading: D.isLoading,
|
|
367
|
+
children: (D.data?.items ?? []).map((e) => /* @__PURE__ */ T(M, {
|
|
368
|
+
title: e,
|
|
369
|
+
onOpen: U
|
|
370
|
+
}, e.id))
|
|
371
|
+
}),
|
|
372
|
+
(j.isLoading || (j.data?.items.length ?? 0) > 0) && /* @__PURE__ */ T(P, {
|
|
373
|
+
title: "Series",
|
|
374
|
+
icon: /* @__PURE__ */ T(C, { className: "h-5 w-5" }),
|
|
375
|
+
loading: j.isLoading,
|
|
376
|
+
children: (j.data?.items ?? []).map((e) => /* @__PURE__ */ T(M, {
|
|
377
|
+
title: e,
|
|
378
|
+
onOpen: U
|
|
379
|
+
}, e.id))
|
|
380
|
+
}),
|
|
381
|
+
(I.isLoading || (I.data?.items.length ?? 0) > 0) && /* @__PURE__ */ T(P, {
|
|
382
|
+
title: "Anime",
|
|
383
|
+
icon: /* @__PURE__ */ T(S, { className: "h-5 w-5" }),
|
|
384
|
+
loading: I.isLoading,
|
|
385
|
+
children: (I.data?.items ?? []).map((e) => /* @__PURE__ */ T(M, {
|
|
386
|
+
title: e,
|
|
387
|
+
onOpen: U
|
|
388
|
+
}, e.id))
|
|
389
|
+
}),
|
|
390
|
+
(L.isLoading || (L.data?.items.length ?? 0) > 0) && /* @__PURE__ */ T(P, {
|
|
391
|
+
title: "Documentaries",
|
|
392
|
+
icon: /* @__PURE__ */ T(h, { className: "h-5 w-5" }),
|
|
393
|
+
loading: L.isLoading,
|
|
394
|
+
children: (L.data?.items ?? []).map((e) => /* @__PURE__ */ T(M, {
|
|
395
|
+
title: e,
|
|
396
|
+
onOpen: U
|
|
397
|
+
}, e.id))
|
|
398
|
+
}),
|
|
399
|
+
/* @__PURE__ */ T("div", {
|
|
400
|
+
className: "mt-8 px-4 sm:px-6",
|
|
401
|
+
children: /* @__PURE__ */ E("button", {
|
|
402
|
+
type: "button",
|
|
403
|
+
onClick: () => t("catalog/titles"),
|
|
404
|
+
className: "inline-flex items-center gap-1.5 text-sm font-medium text-action-primary-bg transition-opacity hover:opacity-80",
|
|
405
|
+
children: ["Browse the full catalog", /* @__PURE__ */ T(p, { className: "h-4 w-4" })]
|
|
406
|
+
})
|
|
407
|
+
})
|
|
408
|
+
]
|
|
409
|
+
})] }) : /* @__PURE__ */ T("div", {
|
|
410
|
+
className: "px-4 py-16 sm:px-6",
|
|
411
|
+
children: /* @__PURE__ */ T(l, {
|
|
412
|
+
illustration: "empty-data",
|
|
413
|
+
title: "Nothing to watch yet",
|
|
414
|
+
description: "No titles have been published to this workspace. Once a title is published it will appear here to browse and stream."
|
|
415
|
+
})
|
|
416
|
+
})]
|
|
417
|
+
});
|
|
418
|
+
};
|
|
419
|
+
//#endregion
|
|
420
|
+
export { I as DiscoveryPage };
|
|
421
|
+
|
|
422
|
+
//# sourceMappingURL=DiscoveryPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DiscoveryPage.js","names":[],"sources":["../../../../src/hookmovies/pages/consumer/DiscoveryPage.tsx"],"sourcesContent":["/**\n * Discovery / Browse (Consumer)\n * @module hookmovies/pages/consumer\n *\n * The audience-facing OTT home. A dark, cinematic browse surface built entirely\n * from the live `hookmovies` subgraph — no mocks, no fabricated data:\n *\n * - A full-bleed hero backed by the first PUBLISHED title that has a backdrop\n * (`useFileUrl(backdropFileId)`), with a Play CTA (→ player) and a More-info\n * CTA (→ title view). Monetization badges on the hero come from the caller's\n * real entitlement (`useCheckEntitlement`) — nothing is invented.\n * - Horizontally-scrolling rails per title type (Movies, Series, Anime,\n * Documentaries) plus \"New & Notable\", each fed by `useTitles({ type, status:\n * 'PUBLISHED' })`. Rails scroll inside their own `overflow-x-auto` container\n * so the page never overflows horizontally.\n * - A \"Continue Watching\" rail from `useMyWatchProgress` joined (client-side,\n * zero extra queries) to the already-loaded published titles for artwork.\n * - A server-side search box (`useTitles({ search })`, debounced) that swaps the\n * rails for a poster grid of matches.\n *\n * Poster / backdrop artwork is resolved to short-lived presigned URLs via\n * `useFileUrl`; this MFE stores no media, only file references. Every card\n * deep-links to the consumer title view at `title/:id`.\n *\n * Reduce-motion friendly (all transforms are `motion-safe:`), mobile-first, and\n * fully tokenized (fe-libs semantic tokens only — no raw colors).\n */\n\nimport { type FC, type ReactNode, useEffect, useMemo, useState } from 'react';\nimport {\n Search,\n Play,\n Info,\n Film,\n Tv,\n Sparkles,\n Clapperboard,\n Flame,\n History,\n Lock,\n ChevronRight,\n} from 'lucide-react';\nimport { AuroraBackground } from '@burdenoff/fe-libs/ui';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingState, ErrorState } from '../../components/StateViews';\nimport { useHookMovies } from '../../context/HookMoviesContext';\nimport {\n useTitles,\n useMyWatchProgress,\n useCheckEntitlement,\n type HokTitle,\n type HokWatchProgress,\n} from '../../hooks/useHookMoviesApi';\nimport { useFileUrl } from '../../hooks/useFiles';\nimport { cn } from '../../utils/cn';\nimport { formatEnumLabel } from '../../utils/formatters';\n\n// A generous page size per rail — the rail scrolls, so more posters is fine.\nconst RAIL_PAGE = { limit: 20, offset: 0 } as const;\nconst SEARCH_PAGE = { limit: 24, offset: 0 } as const;\nconst FEATURED_PAGE = { limit: 12, offset: 0 } as const;\n\n// ============================================================================\n// PosterImage — resolves a file id to a presigned URL with a graceful fallback\n// ============================================================================\n\ninterface PosterImageProps {\n fileId: string | null;\n alt: string;\n /** Fallback glyph shown when there is no artwork / it fails to load. */\n fallbackIcon: ReactNode;\n}\n\n/**\n * Renders poster/backdrop artwork from a wspace-files id. Falls back to a calm\n * tokenized tile (never a broken image) when the id is absent, the presigned\n * lookup fails, or the `<img>` errors at load time.\n */\nconst PosterImage: FC<PosterImageProps> = ({ fileId, alt, fallbackIcon }) => {\n const { data: url, isLoading } = useFileUrl(fileId);\n const [errored, setErrored] = useState(false);\n\n const showImage = Boolean(fileId) && Boolean(url) && !errored;\n\n return (\n <div className=\"absolute inset-0 flex items-center justify-center bg-bg-sunken\">\n {showImage ? (\n <img\n src={url ?? undefined}\n alt={alt}\n loading=\"lazy\"\n onError={() => setErrored(true)}\n className=\"h-full w-full object-cover\"\n />\n ) : (\n <div\n className={cn(\n 'flex h-full w-full items-center justify-center text-text-muted',\n isLoading && Boolean(fileId) && 'motion-safe:animate-pulse'\n )}\n aria-hidden=\"true\"\n >\n {fallbackIcon}\n </div>\n )}\n </div>\n );\n};\n\n// ============================================================================\n// PosterCard — a single portrait poster in a rail / grid\n// ============================================================================\n\ninterface PosterCardProps {\n title: HokTitle;\n onOpen: (id: string) => void;\n}\n\nconst PosterCard: FC<PosterCardProps> = ({ title, onOpen }) => {\n const meta = [\n formatEnumLabel(title.type),\n title.releaseYear ? String(title.releaseYear) : null,\n title.originalLanguage ? title.originalLanguage.toUpperCase() : null,\n ]\n .filter(Boolean)\n .join(' · ');\n\n return (\n <button\n type=\"button\"\n onClick={() => onOpen(title.id)}\n aria-label={`Open ${title.title}`}\n className=\"group w-36 shrink-0 snap-start text-left focus:outline-none sm:w-44\"\n >\n <div\n className={cn(\n 'relative aspect-[2/3] overflow-hidden rounded-xl border border-border-seam',\n 'shadow-elevation-1 transition-[border-color,box-shadow] duration-200',\n 'group-hover:border-border-strong group-hover:shadow-elevation-2',\n 'group-focus-visible:ring-2 group-focus-visible:ring-[var(--color-focus-ring)]'\n )}\n >\n <div className=\"motion-safe:transition-transform motion-safe:duration-300 motion-safe:group-hover:scale-105 absolute inset-0\">\n <PosterImage\n fileId={title.posterFileId}\n alt={`${title.title} poster`}\n fallbackIcon={<Film className=\"h-8 w-8\" />}\n />\n </div>\n\n {/* Type chip — real metadata, not fabricated monetization. */}\n <span className=\"absolute left-2 top-2 rounded-full bg-bg-canvas/80 px-2 py-0.5 text-[10px] font-medium text-text-secondary backdrop-blur-sm\">\n {formatEnumLabel(title.type)}\n </span>\n\n {title.aiDisclosure && (\n <span\n className=\"absolute right-2 top-2 inline-flex items-center gap-1 rounded-full bg-bg-canvas/80 px-2 py-0.5 text-[10px] font-medium text-text-secondary backdrop-blur-sm\"\n title=\"Contains AI-generated or AI-altered content\"\n >\n <Sparkles className=\"h-3 w-3\" />\n AI\n </span>\n )}\n\n {/* Play affordance on hover/focus. */}\n <span\n className={cn(\n 'pointer-events-none absolute inset-x-0 bottom-0 flex items-center gap-1.5',\n 'bg-gradient-to-t from-bg-canvas/90 to-transparent px-3 pb-2 pt-8',\n 'text-xs font-medium text-text-primary opacity-0 transition-opacity duration-200',\n 'group-hover:opacity-100 group-focus-visible:opacity-100'\n )}\n >\n <Play className=\"h-3.5 w-3.5 fill-current\" />\n Watch\n </span>\n </div>\n\n <p className=\"mt-2 line-clamp-1 text-sm font-medium text-text-primary\">{title.title}</p>\n {meta && <p className=\"mt-0.5 line-clamp-1 text-xs text-text-secondary\">{meta}</p>}\n </button>\n );\n};\n\n// ============================================================================\n// ContinueCard — a landscape \"resume\" card for Continue Watching\n// ============================================================================\n\ninterface ContinueCardProps {\n progress: HokWatchProgress;\n /** Poster file id resolved (client-side) from the loaded catalog, if known. */\n posterFileId: string | null;\n onResume: (titleId: string) => void;\n}\n\nconst ContinueCard: FC<ContinueCardProps> = ({ progress, posterFileId, onResume }) => {\n const minutesIn = Math.max(0, Math.round(progress.positionSeconds / 60));\n const name = progress.title?.title ?? 'Untitled';\n\n return (\n <button\n type=\"button\"\n onClick={() => onResume(progress.titleId)}\n aria-label={`Resume ${name}`}\n className=\"group w-64 shrink-0 snap-start text-left focus:outline-none sm:w-72\"\n >\n <div\n className={cn(\n 'relative aspect-video overflow-hidden rounded-xl border border-border-seam',\n 'shadow-elevation-1 transition-[border-color,box-shadow] duration-200',\n 'group-hover:border-border-strong group-hover:shadow-elevation-2',\n 'group-focus-visible:ring-2 group-focus-visible:ring-[var(--color-focus-ring)]'\n )}\n >\n <PosterImage\n fileId={posterFileId}\n alt={`${name} artwork`}\n fallbackIcon={<Clapperboard className=\"h-8 w-8\" />}\n />\n\n {/* Resume overlay. */}\n <span\n className={cn(\n 'absolute inset-0 flex items-center justify-center',\n 'bg-bg-canvas/40 opacity-0 transition-opacity duration-200',\n 'group-hover:opacity-100 group-focus-visible:opacity-100'\n )}\n >\n <span className=\"inline-flex items-center gap-1.5 rounded-full bg-action-primary-bg px-3 py-1.5 text-xs font-semibold text-action-primary-text\">\n <Play className=\"h-3.5 w-3.5 fill-current\" />\n Resume\n </span>\n </span>\n\n <span className=\"absolute inset-x-0 bottom-0 bg-gradient-to-t from-bg-canvas/90 to-transparent px-3 pb-2 pt-8\">\n <span className=\"line-clamp-1 block text-sm font-semibold text-text-primary\">{name}</span>\n <span className=\"text-xs text-text-secondary\">\n {progress.title ? `${formatEnumLabel(progress.title.type)} · ` : ''}\n {minutesIn} min in\n </span>\n </span>\n </div>\n </button>\n );\n};\n\n// ============================================================================\n// RailRow — a titled, horizontally-scrolling shelf\n// ============================================================================\n\ninterface RailRowProps {\n title: string;\n icon: ReactNode;\n loading?: boolean;\n children: ReactNode;\n /** How many skeleton tiles to show while loading. */\n skeletonCount?: number;\n /** Skeleton tile aspect — portrait posters vs landscape resume cards. */\n skeletonShape?: 'poster' | 'landscape';\n}\n\nconst RailRow: FC<RailRowProps> = ({\n title,\n icon,\n loading = false,\n children,\n skeletonCount = 6,\n skeletonShape = 'poster',\n}) => (\n <section className=\"mt-8 first:mt-6\">\n <div className=\"mb-3 flex items-center gap-2 px-4 sm:px-6\">\n <span className=\"text-action-primary-bg\">{icon}</span>\n <h2 className=\"text-base font-semibold text-text-primary sm:text-lg\">{title}</h2>\n </div>\n <div\n className={cn(\n 'flex gap-3 overflow-x-auto px-4 pb-3 sm:px-6',\n 'snap-x scroll-px-4 sm:scroll-px-6',\n '[scrollbar-width:thin]'\n )}\n >\n {loading\n ? Array.from({ length: skeletonCount }).map((_, i) => (\n <div\n key={i}\n className={cn(\n 'shrink-0 rounded-xl border border-border-seam bg-bg-sunken',\n 'motion-safe:animate-pulse',\n skeletonShape === 'poster'\n ? 'aspect-[2/3] w-36 sm:w-44'\n : 'aspect-video w-64 sm:w-72'\n )}\n aria-hidden=\"true\"\n />\n ))\n : children}\n </div>\n </section>\n);\n\n// ============================================================================\n// HeroSection\n// ============================================================================\n\ninterface HeroSectionProps {\n title: HokTitle;\n onPlay: (id: string) => void;\n onMoreInfo: (id: string) => void;\n}\n\nconst HeroSection: FC<HeroSectionProps> = ({ title, onPlay, onMoreInfo }) => {\n const { data: backdropUrl } = useFileUrl(title.backdropFileId ?? title.posterFileId);\n const entitlement = useCheckEntitlement(title.id);\n const [bgErrored, setBgErrored] = useState(false);\n\n const hasImage = Boolean(backdropUrl) && !bgErrored;\n\n return (\n <section className=\"relative overflow-hidden border-b border-border-seam bg-hero-mesh\">\n {!hasImage && <AuroraBackground intensity=\"normal\" />}\n\n {/* Backdrop image (object-cover, behind a legibility scrim). */}\n {hasImage && (\n <img\n src={backdropUrl ?? undefined}\n alt=\"\"\n onError={() => setBgErrored(true)}\n className=\"absolute inset-0 h-full w-full object-cover\"\n aria-hidden=\"true\"\n />\n )}\n {/* Scrims: darken bottom + left for text legibility over any artwork. */}\n <div\n className=\"absolute inset-0 bg-gradient-to-t from-bg-canvas via-bg-canvas/60 to-transparent\"\n aria-hidden=\"true\"\n />\n <div\n className=\"absolute inset-0 bg-gradient-to-r from-bg-canvas/90 via-bg-canvas/40 to-transparent\"\n aria-hidden=\"true\"\n />\n\n <div className=\"relative flex min-h-[22rem] flex-col justify-end gap-4 px-4 py-8 sm:min-h-[26rem] sm:px-6 sm:py-10 lg:min-h-[30rem]\">\n <div className=\"max-w-2xl\">\n <div className=\"mb-3 flex flex-wrap items-center gap-2\">\n <span className=\"inline-flex items-center gap-1.5 rounded-full bg-action-primary-bg/15 px-2.5 py-0.5 text-xs font-medium text-action-primary-bg\">\n <Flame className=\"h-3.5 w-3.5\" />\n Featured\n </span>\n <span className=\"rounded-full bg-bg-canvas/70 px-2.5 py-0.5 text-xs font-medium text-text-secondary backdrop-blur-sm\">\n {formatEnumLabel(title.type)}\n </span>\n {title.releaseYear && (\n <span className=\"rounded-full bg-bg-canvas/70 px-2.5 py-0.5 text-xs font-medium text-text-secondary backdrop-blur-sm\">\n {title.releaseYear}\n </span>\n )}\n {title.certification && (\n <span className=\"rounded-full border border-border-seam px-2 py-0.5 text-xs font-medium text-text-secondary\">\n {title.certification}\n </span>\n )}\n </div>\n\n <h1 className=\"text-3xl font-bold tracking-tight text-text-primary sm:text-4xl lg:text-5xl\">\n {title.title}\n </h1>\n\n {title.synopsis && (\n <p className=\"mt-3 line-clamp-3 max-w-xl text-sm text-text-secondary sm:text-base\">\n {title.synopsis}\n </p>\n )}\n\n {title.genres.length > 0 && (\n <div className=\"mt-3 flex flex-wrap gap-1.5\">\n {title.genres.slice(0, 4).map((g) => (\n <span\n key={g}\n className=\"rounded-full bg-bg-canvas/60 px-2.5 py-0.5 text-xs text-text-secondary backdrop-blur-sm\"\n >\n {g}\n </span>\n ))}\n </div>\n )}\n\n {/* Real entitlement → monetization badges (never fabricated). */}\n {entitlement.data?.entitled && entitlement.data.modes.length > 0 && (\n <div className=\"mt-3 flex flex-wrap gap-1.5\">\n {entitlement.data.modes.map((mode) => (\n <span\n key={mode}\n className=\"rounded-full bg-status-success-bg-subtle px-2.5 py-0.5 text-xs font-medium text-status-success-text\"\n >\n {formatEnumLabel(mode)}\n </span>\n ))}\n </div>\n )}\n\n <div className=\"mt-5 flex flex-wrap items-center gap-3\">\n <button\n type=\"button\"\n onClick={() => onPlay(title.id)}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-5 py-2.5 text-sm font-semibold text-action-primary-text shadow-elevation-1 transition-opacity hover:opacity-90\"\n >\n <Play className=\"h-4 w-4 fill-current\" />\n Play\n </button>\n <button\n type=\"button\"\n onClick={() => onMoreInfo(title.id)}\n className=\"inline-flex items-center gap-2 rounded-lg border border-border-strong bg-bg-canvas/60 px-5 py-2.5 text-sm font-semibold text-text-primary backdrop-blur-sm transition-colors hover:bg-bg-sunken\"\n >\n <Info className=\"h-4 w-4\" />\n More info\n </button>\n {entitlement.data && !entitlement.data.entitled && (\n <span className=\"inline-flex items-center gap-1.5 text-xs text-text-secondary\">\n <Lock className=\"h-3.5 w-3.5\" />\n Sign-in or entitlement required to play\n </span>\n )}\n </div>\n </div>\n </div>\n </section>\n );\n};\n\n// ============================================================================\n// DiscoveryPage\n// ============================================================================\n\nexport const DiscoveryPage: FC = () => {\n const { navigateTo } = useHookMovies();\n\n const [search, setSearch] = useState('');\n const [debouncedSearch, setDebouncedSearch] = useState('');\n\n // Debounce the search box (~300ms) so each keystroke doesn't fire a query.\n useEffect(() => {\n const handle = window.setTimeout(() => setDebouncedSearch(search.trim()), 300);\n return () => window.clearTimeout(handle);\n }, [search]);\n\n const isSearching = debouncedSearch.length > 0;\n\n // Published catalog by type — one rail each.\n const featured = useTitles({ status: 'PUBLISHED' }, FEATURED_PAGE);\n const movies = useTitles({ status: 'PUBLISHED', type: 'MOVIE' }, RAIL_PAGE);\n const series = useTitles({ status: 'PUBLISHED', type: 'SERIES' }, RAIL_PAGE);\n const anime = useTitles({ status: 'PUBLISHED', type: 'ANIME' }, RAIL_PAGE);\n const documentaries = useTitles({ status: 'PUBLISHED', type: 'DOCUMENTARY' }, RAIL_PAGE);\n\n // Self-scoped resume list.\n const watchProgress = useMyWatchProgress();\n\n // Search runs against published titles; only surfaced when a term is present.\n const searchResults = useTitles(\n { status: 'PUBLISHED' },\n SEARCH_PAGE,\n isSearching ? debouncedSearch : undefined\n );\n\n // Build a client-side poster lookup from every published title we've loaded,\n // so Continue Watching can show real artwork with ZERO extra queries.\n const posterLookup = useMemo(() => {\n const map = new Map<string, HokTitle>();\n for (const conn of [featured.data, movies.data, series.data, anime.data, documentaries.data]) {\n for (const t of conn?.items ?? []) {\n map.set(t.id, t);\n }\n }\n return map;\n }, [featured.data, movies.data, series.data, anime.data, documentaries.data]);\n\n // Hero = first published title with a backdrop, else the first published title.\n const heroTitle = useMemo(() => {\n const items = featured.data?.items ?? [];\n return items.find((t) => t.backdropFileId) ?? items[0] ?? null;\n }, [featured.data]);\n\n const continueWatching = useMemo(\n () => (watchProgress.data ?? []).filter((p) => !p.completed).slice(0, 12),\n [watchProgress.data]\n );\n\n const openTitle = (id: string) => navigateTo(`title/${id}`);\n const playTitle = (id: string) => navigateTo(`watch/${id}`);\n\n const handleRetry = () => {\n void featured.refetch();\n void movies.refetch();\n void series.refetch();\n void anime.refetch();\n void documentaries.refetch();\n void watchProgress.refetch();\n };\n\n // ── Primary gates driven by the featured query (the hero + first rail) ──────\n if (featured.isLoading) {\n return (\n <div className=\"flex h-full flex-col bg-bg-canvas\">\n <LoadingState label=\"Loading titles…\" />\n </div>\n );\n }\n\n if (featured.isError) {\n return (\n <div className=\"flex h-full flex-col bg-bg-canvas\">\n <ErrorState\n title=\"Could not load HookMovies\"\n message=\"The catalog service did not respond. Please try again.\"\n onRetry={handleRetry}\n />\n </div>\n );\n }\n\n const hasAnyPublished = (featured.data?.items.length ?? 0) > 0;\n\n const searchBox = (\n <div className=\"sticky top-0 z-10 border-b border-border-seam bg-bg-canvas/80 px-4 py-3 backdrop-blur-md sm:px-6\">\n <div className=\"relative mx-auto max-w-2xl\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <input\n type=\"search\"\n aria-label=\"Search titles\"\n placeholder=\"Search movies, series, anime & more…\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n className=\"w-full rounded-full border border-border-subtle bg-bg-surface py-2.5 pl-10 pr-4 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n </div>\n );\n\n return (\n <div className=\"flex h-full flex-col overflow-y-auto bg-bg-canvas\">\n {searchBox}\n\n {isSearching ? (\n // ── Search results ──────────────────────────────────────────────────\n <div className=\"px-4 py-6 sm:px-6\">\n <div className=\"mb-4 flex items-center gap-2\">\n <h2 className=\"text-base font-semibold text-text-primary sm:text-lg\">\n Results for “{debouncedSearch}”\n </h2>\n {!searchResults.isLoading && !searchResults.isError && (\n <span className=\"text-xs text-text-secondary\" aria-live=\"polite\">\n {searchResults.data?.totalCount ?? 0} found\n </span>\n )}\n </div>\n\n {searchResults.isLoading ? (\n <LoadingState label=\"Searching…\" />\n ) : searchResults.isError ? (\n <ErrorState\n title=\"Search failed\"\n message=\"Could not run that search. Please try again.\"\n onRetry={() => void searchResults.refetch()}\n />\n ) : (searchResults.data?.items.length ?? 0) === 0 ? (\n <EmptyState\n illustration=\"empty-search\"\n title=\"No matches\"\n description={`Nothing published matches “${debouncedSearch}”. Try a different title, genre, or spelling.`}\n />\n ) : (\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6\">\n {(searchResults.data?.items ?? []).map((title) => (\n <PosterCard key={title.id} title={title} onOpen={openTitle} />\n ))}\n </div>\n )}\n </div>\n ) : !hasAnyPublished ? (\n // ── Nothing published yet ───────────────────────────────────────────\n <div className=\"px-4 py-16 sm:px-6\">\n <EmptyState\n illustration=\"empty-data\"\n title=\"Nothing to watch yet\"\n description=\"No titles have been published to this workspace. Once a title is published it will appear here to browse and stream.\"\n />\n </div>\n ) : (\n // ── Browse home ─────────────────────────────────────────────────────\n <>\n {heroTitle && <HeroSection title={heroTitle} onPlay={playTitle} onMoreInfo={openTitle} />}\n\n <div className=\"pb-10\">\n {/* Continue Watching — only when the user has in-progress titles. */}\n {continueWatching.length > 0 && (\n <RailRow\n title=\"Continue Watching\"\n icon={<History className=\"h-5 w-5\" />}\n loading={watchProgress.isLoading}\n skeletonShape=\"landscape\"\n >\n {continueWatching.map((p) => (\n <ContinueCard\n key={p.id}\n progress={p}\n posterFileId={posterLookup.get(p.titleId)?.posterFileId ?? null}\n onResume={playTitle}\n />\n ))}\n </RailRow>\n )}\n\n {/* New & Notable — the freshest published titles across all types. */}\n {(featured.data?.items.length ?? 0) > 0 && (\n <RailRow title=\"New & Notable\" icon={<Flame className=\"h-5 w-5\" />}>\n {(featured.data?.items ?? []).map((title) => (\n <PosterCard key={title.id} title={title} onOpen={openTitle} />\n ))}\n </RailRow>\n )}\n\n {/* Type rails — hidden entirely when a type has nothing published. */}\n {(movies.isLoading || (movies.data?.items.length ?? 0) > 0) && (\n <RailRow\n title=\"Movies\"\n icon={<Clapperboard className=\"h-5 w-5\" />}\n loading={movies.isLoading}\n >\n {(movies.data?.items ?? []).map((title) => (\n <PosterCard key={title.id} title={title} onOpen={openTitle} />\n ))}\n </RailRow>\n )}\n\n {(series.isLoading || (series.data?.items.length ?? 0) > 0) && (\n <RailRow title=\"Series\" icon={<Tv className=\"h-5 w-5\" />} loading={series.isLoading}>\n {(series.data?.items ?? []).map((title) => (\n <PosterCard key={title.id} title={title} onOpen={openTitle} />\n ))}\n </RailRow>\n )}\n\n {(anime.isLoading || (anime.data?.items.length ?? 0) > 0) && (\n <RailRow\n title=\"Anime\"\n icon={<Sparkles className=\"h-5 w-5\" />}\n loading={anime.isLoading}\n >\n {(anime.data?.items ?? []).map((title) => (\n <PosterCard key={title.id} title={title} onOpen={openTitle} />\n ))}\n </RailRow>\n )}\n\n {(documentaries.isLoading || (documentaries.data?.items.length ?? 0) > 0) && (\n <RailRow\n title=\"Documentaries\"\n icon={<Film className=\"h-5 w-5\" />}\n loading={documentaries.isLoading}\n >\n {(documentaries.data?.items ?? []).map((title) => (\n <PosterCard key={title.id} title={title} onOpen={openTitle} />\n ))}\n </RailRow>\n )}\n\n {/* Browse-all affordance into the full studio catalog. */}\n <div className=\"mt-8 px-4 sm:px-6\">\n <button\n type=\"button\"\n onClick={() => navigateTo('catalog/titles')}\n className=\"inline-flex items-center gap-1.5 text-sm font-medium text-action-primary-bg transition-opacity hover:opacity-80\"\n >\n Browse the full catalog\n <ChevronRight className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n </>\n )}\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;AA0DA,IAAM,IAAY;CAAE,OAAO;CAAI,QAAQ;CAAG,EACpC,IAAc;CAAE,OAAO;CAAI,QAAQ;CAAG,EACtC,IAAgB;CAAE,OAAO;CAAI,QAAQ;CAAG,EAkBxC,KAAqC,EAAE,WAAQ,QAAK,sBAAmB;CAC3E,IAAM,EAAE,MAAM,GAAK,iBAAc,EAAW,EAAO,EAC7C,CAAC,GAAS,KAAc,EAAS,GAAM;AAI7C,QACE,kBAAC,OAAD;EAAK,WAAU;YAHS,KAAmB,KAAQ,CAAC,IAKhD,kBAAC,OAAD;GACE,KAAK,KAAO,KAAA;GACP;GACL,SAAQ;GACR,eAAe,EAAW,GAAK;GAC/B,WAAU;GACV,CAAA,GAEF,kBAAC,OAAD;GACE,WAAW,EACT,kEACA,KAAa,EAAQ,KAAW,4BACjC;GACD,eAAY;aAEX;GACG,CAAA;EAEJ,CAAA;GAaJ,KAAmC,EAAE,UAAO,gBAAa;CAC7D,IAAM,IAAO;EACX,EAAgB,EAAM,KAAK;EAC3B,EAAM,cAAc,OAAO,EAAM,YAAY,GAAG;EAChD,EAAM,mBAAmB,EAAM,iBAAiB,aAAa,GAAG;EACjE,CACE,OAAO,QAAQ,CACf,KAAK,MAAM;AAEd,QACE,kBAAC,UAAD;EACE,MAAK;EACL,eAAe,EAAO,EAAM,GAAG;EAC/B,cAAY,QAAQ,EAAM;EAC1B,WAAU;YAJZ;GAME,kBAAC,OAAD;IACE,WAAW,EACT,8EACA,wEACA,mEACA,gFACD;cANH;KAQE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD;OACE,QAAQ,EAAM;OACd,KAAK,GAAG,EAAM,MAAM;OACpB,cAAc,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;OAC1C,CAAA;MACE,CAAA;KAGN,kBAAC,QAAD;MAAM,WAAU;gBACb,EAAgB,EAAM,KAAK;MACvB,CAAA;KAEN,EAAM,gBACL,kBAAC,QAAD;MACE,WAAU;MACV,OAAM;gBAFR,CAIE,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA,EAAA,KAE3B;;KAIT,kBAAC,QAAD;MACE,WAAW,EACT,6EACA,oEACA,mFACA,0DACD;gBANH,CAQE,kBAAC,GAAD,EAAM,WAAU,4BAA6B,CAAA,EAAA,QAExC;;KACH;;GAEN,kBAAC,KAAD;IAAG,WAAU;cAA2D,EAAM;IAAU,CAAA;GACvF,KAAQ,kBAAC,KAAD;IAAG,WAAU;cAAmD;IAAS,CAAA;GAC3E;;GAeP,KAAuC,EAAE,aAAU,iBAAc,kBAAe;CACpF,IAAM,IAAY,KAAK,IAAI,GAAG,KAAK,MAAM,EAAS,kBAAkB,GAAG,CAAC,EAClE,IAAO,EAAS,OAAO,SAAS;AAEtC,QACE,kBAAC,UAAD;EACE,MAAK;EACL,eAAe,EAAS,EAAS,QAAQ;EACzC,cAAY,UAAU;EACtB,WAAU;YAEV,kBAAC,OAAD;GACE,WAAW,EACT,8EACA,wEACA,mEACA,gFACD;aANH;IAQE,kBAAC,GAAD;KACE,QAAQ;KACR,KAAK,GAAG,EAAK;KACb,cAAc,kBAAC,GAAD,EAAc,WAAU,WAAY,CAAA;KAClD,CAAA;IAGF,kBAAC,QAAD;KACE,WAAW,EACT,qDACA,6DACA,0DACD;eAED,kBAAC,QAAD;MAAM,WAAU;gBAAhB,CACE,kBAAC,GAAD,EAAM,WAAU,4BAA6B,CAAA,EAAA,SAExC;;KACF,CAAA;IAEP,kBAAC,QAAD;KAAM,WAAU;eAAhB,CACE,kBAAC,QAAD;MAAM,WAAU;gBAA8D;MAAY,CAAA,EAC1F,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OACG,EAAS,QAAQ,GAAG,EAAgB,EAAS,MAAM,KAAK,CAAC,OAAO;OAChE;OAAU;OACN;QACF;;IACH;;EACC,CAAA;GAmBP,KAA6B,EACjC,UACA,SACA,aAAU,IACV,aACA,mBAAgB,GAChB,mBAAgB,eAEhB,kBAAC,WAAD;CAAS,WAAU;WAAnB,CACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,QAAD;GAAM,WAAU;aAA0B;GAAY,CAAA,EACtD,kBAAC,MAAD;GAAI,WAAU;aAAwD;GAAW,CAAA,CAC7E;KACN,kBAAC,OAAD;EACE,WAAW,EACT,gDACA,qCACA,yBACD;YAEA,IACG,MAAM,KAAK,EAAE,QAAQ,GAAe,CAAC,CAAC,KAAK,GAAG,MAC5C,kBAAC,OAAD;GAEE,WAAW,EACT,8DACA,6BACA,MAAkB,WACd,8BACA,4BACL;GACD,eAAY;GACZ,EATK,EASL,CACF,GACF;EACA,CAAA,CACE;IAaN,KAAqC,EAAE,UAAO,WAAQ,oBAAiB;CAC3E,IAAM,EAAE,MAAM,MAAgB,EAAW,EAAM,kBAAkB,EAAM,aAAa,EAC9E,IAAc,EAAoB,EAAM,GAAG,EAC3C,CAAC,GAAW,KAAgB,EAAS,GAAM,EAE3C,IAAW,EAAQ,KAAgB,CAAC;AAE1C,QACE,kBAAC,WAAD;EAAS,WAAU;YAAnB;GACG,CAAC,KAAY,kBAAC,GAAD,EAAkB,WAAU,UAAW,CAAA;GAGpD,KACC,kBAAC,OAAD;IACE,KAAK,KAAe,KAAA;IACpB,KAAI;IACJ,eAAe,EAAa,GAAK;IACjC,WAAU;IACV,eAAY;IACZ,CAAA;GAGJ,kBAAC,OAAD;IACE,WAAU;IACV,eAAY;IACZ,CAAA;GACF,kBAAC,OAAD;IACE,WAAU;IACV,eAAY;IACZ,CAAA;GAEF,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,QAAD;SAAM,WAAU;mBAAhB,CACE,kBAAC,GAAD,EAAO,WAAU,eAAgB,CAAA,EAAA,WAE5B;;QACP,kBAAC,QAAD;SAAM,WAAU;mBACb,EAAgB,EAAM,KAAK;SACvB,CAAA;QACN,EAAM,eACL,kBAAC,QAAD;SAAM,WAAU;mBACb,EAAM;SACF,CAAA;QAER,EAAM,iBACL,kBAAC,QAAD;SAAM,WAAU;mBACb,EAAM;SACF,CAAA;QAEL;;MAEN,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAM;OACJ,CAAA;MAEJ,EAAM,YACL,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAM;OACL,CAAA;MAGL,EAAM,OAAO,SAAS,KACrB,kBAAC,OAAD;OAAK,WAAU;iBACZ,EAAM,OAAO,MAAM,GAAG,EAAE,CAAC,KAAK,MAC7B,kBAAC,QAAD;QAEE,WAAU;kBAET;QACI,EAJA,EAIA,CACP;OACE,CAAA;MAIP,EAAY,MAAM,YAAY,EAAY,KAAK,MAAM,SAAS,KAC7D,kBAAC,OAAD;OAAK,WAAU;iBACZ,EAAY,KAAK,MAAM,KAAK,MAC3B,kBAAC,QAAD;QAEE,WAAU;kBAET,EAAgB,EAAK;QACjB,EAJA,EAIA,CACP;OACE,CAAA;MAGR,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,UAAD;SACE,MAAK;SACL,eAAe,EAAO,EAAM,GAAG;SAC/B,WAAU;mBAHZ,CAKE,kBAAC,GAAD,EAAM,WAAU,wBAAyB,CAAA,EAAA,OAElC;;QACT,kBAAC,UAAD;SACE,MAAK;SACL,eAAe,EAAW,EAAM,GAAG;SACnC,WAAU;mBAHZ,CAKE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAAA,YAErB;;QACR,EAAY,QAAQ,CAAC,EAAY,KAAK,YACrC,kBAAC,QAAD;SAAM,WAAU;mBAAhB,CACE,kBAAC,GAAD,EAAM,WAAU,eAAgB,CAAA,EAAA,0CAE3B;;QAEL;;MACF;;IACF,CAAA;GACE;;GAQD,UAA0B;CACrC,IAAM,EAAE,kBAAe,GAAe,EAEhC,CAAC,GAAQ,KAAa,EAAS,GAAG,EAClC,CAAC,GAAiB,KAAsB,EAAS,GAAG;AAG1D,SAAgB;EACd,IAAM,IAAS,OAAO,iBAAiB,EAAmB,EAAO,MAAM,CAAC,EAAE,IAAI;AAC9E,eAAa,OAAO,aAAa,EAAO;IACvC,CAAC,EAAO,CAAC;CAEZ,IAAM,IAAc,EAAgB,SAAS,GAGvC,IAAW,EAAU,EAAE,QAAQ,aAAa,EAAE,EAAc,EAC5D,IAAS,EAAU;EAAE,QAAQ;EAAa,MAAM;EAAS,EAAE,EAAU,EACrE,IAAS,EAAU;EAAE,QAAQ;EAAa,MAAM;EAAU,EAAE,EAAU,EACtE,IAAQ,EAAU;EAAE,QAAQ;EAAa,MAAM;EAAS,EAAE,EAAU,EACpE,IAAgB,EAAU;EAAE,QAAQ;EAAa,MAAM;EAAe,EAAE,EAAU,EAGlF,IAAgB,GAAoB,EAGpC,IAAgB,EACpB,EAAE,QAAQ,aAAa,EACvB,GACA,IAAc,IAAkB,KAAA,EACjC,EAIK,IAAe,QAAc;EACjC,IAAM,oBAAM,IAAI,KAAuB;AACvC,OAAK,IAAM,KAAQ;GAAC,EAAS;GAAM,EAAO;GAAM,EAAO;GAAM,EAAM;GAAM,EAAc;GAAK,CAC1F,MAAK,IAAM,KAAK,GAAM,SAAS,EAAE,CAC/B,GAAI,IAAI,EAAE,IAAI,EAAE;AAGpB,SAAO;IACN;EAAC,EAAS;EAAM,EAAO;EAAM,EAAO;EAAM,EAAM;EAAM,EAAc;EAAK,CAAC,EAGvE,IAAY,QAAc;EAC9B,IAAM,IAAQ,EAAS,MAAM,SAAS,EAAE;AACxC,SAAO,EAAM,MAAM,MAAM,EAAE,eAAe,IAAI,EAAM,MAAM;IACzD,CAAC,EAAS,KAAK,CAAC,EAEb,IAAmB,SAChB,EAAc,QAAQ,EAAE,EAAE,QAAQ,MAAM,CAAC,EAAE,UAAU,CAAC,MAAM,GAAG,GAAG,EACzE,CAAC,EAAc,KAAK,CACrB,EAEK,KAAa,MAAe,EAAW,SAAS,IAAK,EACrD,KAAa,MAAe,EAAW,SAAS,IAAK,EAErD,UAAoB;AAMnB,EALA,EAAS,SAAS,EAClB,EAAO,SAAS,EAChB,EAAO,SAAS,EAChB,EAAM,SAAS,EACf,EAAc,SAAS,EACvB,EAAc,SAAS;;AAI9B,KAAI,EAAS,UACX,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAc,OAAM,mBAAoB,CAAA;EACpC,CAAA;AAIV,KAAI,EAAS,QACX,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD;GACE,OAAM;GACN,SAAQ;GACR,SAAS;GACT,CAAA;EACE,CAAA;CAIV,IAAM,KAAmB,EAAS,MAAM,MAAM,UAAU,KAAK;AAkB7D,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CAhBA,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAQ,WAAU,wEAAyE,CAAA,EAC3F,kBAAC,SAAD;KACE,MAAK;KACL,cAAW;KACX,aAAY;KACZ,OAAO;KACP,WAAW,MAAM,EAAU,EAAE,OAAO,MAAM;KAC1C,WAAU;KACV,CAAA,CACE;;GACF,CAAA,EAOH,IAEC,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eAAd;MAAqE;MACrD;MAAgB;MAC3B;QACJ,CAAC,EAAc,aAAa,CAAC,EAAc,WAC1C,kBAAC,QAAD;KAAM,WAAU;KAA8B,aAAU;eAAxD,CACG,EAAc,MAAM,cAAc,GAAE,SAChC;OAEL;OAEL,EAAc,YACb,kBAAC,GAAD,EAAc,OAAM,cAAe,CAAA,GACjC,EAAc,UAChB,kBAAC,GAAD;IACE,OAAM;IACN,SAAQ;IACR,eAAe,KAAK,EAAc,SAAS;IAC3C,CAAA,IACC,EAAc,MAAM,MAAM,UAAU,OAAO,IAC9C,kBAAC,GAAD;IACE,cAAa;IACb,OAAM;IACN,aAAa,8BAA8B,EAAgB;IAC3D,CAAA,GAEF,kBAAC,OAAD;IAAK,WAAU;eACX,EAAc,MAAM,SAAS,EAAE,EAAE,KAAK,MACtC,kBAAC,GAAD;KAAkC;KAAO,QAAQ;KAAa,EAA7C,EAAM,GAAuC,CAC9D;IACE,CAAA,CAEJ;OACH,IAWH,kBAAA,GAAA,EAAA,UAAA,CACG,KAAa,kBAAC,GAAD;GAAa,OAAO;GAAW,QAAQ;GAAW,YAAY;GAAa,CAAA,EAEzF,kBAAC,OAAD;GAAK,WAAU;aAAf;IAEG,EAAiB,SAAS,KACzB,kBAAC,GAAD;KACE,OAAM;KACN,MAAM,kBAAC,GAAD,EAAS,WAAU,WAAY,CAAA;KACrC,SAAS,EAAc;KACvB,eAAc;eAEb,EAAiB,KAAK,MACrB,kBAAC,GAAD;MAEE,UAAU;MACV,cAAc,EAAa,IAAI,EAAE,QAAQ,EAAE,gBAAgB;MAC3D,UAAU;MACV,EAJK,EAAE,GAIP,CACF;KACM,CAAA;KAIV,EAAS,MAAM,MAAM,UAAU,KAAK,KACpC,kBAAC,GAAD;KAAS,OAAM;KAAgB,MAAM,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA;gBAC9D,EAAS,MAAM,SAAS,EAAE,EAAE,KAAK,MACjC,kBAAC,GAAD;MAAkC;MAAO,QAAQ;MAAa,EAA7C,EAAM,GAAuC,CAC9D;KACM,CAAA;KAIV,EAAO,cAAc,EAAO,MAAM,MAAM,UAAU,KAAK,MACvD,kBAAC,GAAD;KACE,OAAM;KACN,MAAM,kBAAC,GAAD,EAAc,WAAU,WAAY,CAAA;KAC1C,SAAS,EAAO;gBAEd,EAAO,MAAM,SAAS,EAAE,EAAE,KAAK,MAC/B,kBAAC,GAAD;MAAkC;MAAO,QAAQ;MAAa,EAA7C,EAAM,GAAuC,CAC9D;KACM,CAAA;KAGV,EAAO,cAAc,EAAO,MAAM,MAAM,UAAU,KAAK,MACvD,kBAAC,GAAD;KAAS,OAAM;KAAS,MAAM,kBAAC,GAAD,EAAI,WAAU,WAAY,CAAA;KAAE,SAAS,EAAO;gBACtE,EAAO,MAAM,SAAS,EAAE,EAAE,KAAK,MAC/B,kBAAC,GAAD;MAAkC;MAAO,QAAQ;MAAa,EAA7C,EAAM,GAAuC,CAC9D;KACM,CAAA;KAGV,EAAM,cAAc,EAAM,MAAM,MAAM,UAAU,KAAK,MACrD,kBAAC,GAAD;KACE,OAAM;KACN,MAAM,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;KACtC,SAAS,EAAM;gBAEb,EAAM,MAAM,SAAS,EAAE,EAAE,KAAK,MAC9B,kBAAC,GAAD;MAAkC;MAAO,QAAQ;MAAa,EAA7C,EAAM,GAAuC,CAC9D;KACM,CAAA;KAGV,EAAc,cAAc,EAAc,MAAM,MAAM,UAAU,KAAK,MACrE,kBAAC,GAAD;KACE,OAAM;KACN,MAAM,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;KAClC,SAAS,EAAc;gBAErB,EAAc,MAAM,SAAS,EAAE,EAAE,KAAK,MACtC,kBAAC,GAAD;MAAkC;MAAO,QAAQ;MAAa,EAA7C,EAAM,GAAuC,CAC9D;KACM,CAAA;IAIZ,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAW,iBAAiB;MAC3C,WAAU;gBAHZ,CAIC,2BAEC,kBAAC,GAAD,EAAc,WAAU,WAAY,CAAA,CAC7B;;KACL,CAAA;IACF;KACL,EAAA,CAAA,GAlGH,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,GAAD;IACE,cAAa;IACb,OAAM;IACN,aAAY;IACZ,CAAA;GACE,CAAA,CA8FJ"}
|