@burdenoff/microfe-hookmovies 2026.706.2 → 2026.706.3

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.
Files changed (48) hide show
  1. package/dist/hookmovies/HookMoviesRoutes.js +54 -6
  2. package/dist/hookmovies/HookMoviesRoutes.js.map +1 -1
  3. package/dist/hookmovies/components/index.js +1 -1
  4. package/dist/hookmovies/context/HookMoviesContext.js +64 -40
  5. package/dist/hookmovies/context/HookMoviesContext.js.map +1 -1
  6. package/dist/hookmovies/hooks/useHookMoviesApi.js +592 -134
  7. package/dist/hookmovies/hooks/useHookMoviesApi.js.map +1 -1
  8. package/dist/hookmovies/pages/ai/ConciergePage.js +330 -0
  9. package/dist/hookmovies/pages/ai/ConciergePage.js.map +1 -0
  10. package/dist/hookmovies/pages/analytics/AnalyticsPage.js +450 -0
  11. package/dist/hookmovies/pages/analytics/AnalyticsPage.js.map +1 -0
  12. package/dist/hookmovies/pages/analytics/PayoutsPage.js +499 -0
  13. package/dist/hookmovies/pages/analytics/PayoutsPage.js.map +1 -0
  14. package/dist/hookmovies/pages/auditions/AuditionsPage.js +3 -3
  15. package/dist/hookmovies/pages/catalog/PeoplePage.js +3 -3
  16. package/dist/hookmovies/pages/catalog/StudiosPage.js +3 -3
  17. package/dist/hookmovies/pages/catalog/TitleDetailPage.js +7 -7
  18. package/dist/hookmovies/pages/catalog/TitlesPage.js +4 -4
  19. package/dist/hookmovies/pages/commerce/CheckoutPage.js +430 -0
  20. package/dist/hookmovies/pages/commerce/CheckoutPage.js.map +1 -0
  21. package/dist/hookmovies/pages/consumer/DiscoveryPage.js +7 -7
  22. package/dist/hookmovies/pages/consumer/HooksPage.js +5 -5
  23. package/dist/hookmovies/pages/consumer/TitleViewPage.js +27 -27
  24. package/dist/hookmovies/pages/consumer/TitleViewPage.js.map +1 -1
  25. package/dist/hookmovies/pages/developer/DevPortalPage.js +815 -0
  26. package/dist/hookmovies/pages/developer/DevPortalPage.js.map +1 -0
  27. package/dist/hookmovies/pages/distribution/RightsWindowsPage.js +3 -3
  28. package/dist/hookmovies/pages/engagement/ReviewsPage.js +201 -143
  29. package/dist/hookmovies/pages/engagement/ReviewsPage.js.map +1 -1
  30. package/dist/hookmovies/pages/library/EntitlementsPage.js +3 -3
  31. package/dist/hookmovies/pages/library/EntitlementsPage.js.map +1 -1
  32. package/dist/hookmovies/pages/library/ListsPage.js +3 -3
  33. package/dist/hookmovies/pages/live/LivePage.js +608 -0
  34. package/dist/hookmovies/pages/live/LivePage.js.map +1 -0
  35. package/dist/hookmovies/pages/localization/LocalizationPage.js +656 -0
  36. package/dist/hookmovies/pages/localization/LocalizationPage.js.map +1 -0
  37. package/dist/hookmovies/pages/onboarding/OnboardingPage.js +366 -0
  38. package/dist/hookmovies/pages/onboarding/OnboardingPage.js.map +1 -0
  39. package/dist/hookmovies/pages/productions/ProductionsPage.js +3 -3
  40. package/dist/hookmovies/pages/social/ProfilesPage.js +701 -0
  41. package/dist/hookmovies/pages/social/ProfilesPage.js.map +1 -0
  42. package/dist/hookmovies/pages/social/WatchPartyListPage.js +281 -0
  43. package/dist/hookmovies/pages/social/WatchPartyListPage.js.map +1 -0
  44. package/dist/hookmovies/pages/social/WatchPartyRoomPage.js +567 -0
  45. package/dist/hookmovies/pages/social/WatchPartyRoomPage.js.map +1 -0
  46. package/dist/hookmovies/pages/trust/ModerationPage.js +320 -0
  47. package/dist/hookmovies/pages/trust/ModerationPage.js.map +1 -0
  48. package/package.json +1 -1
@@ -2,8 +2,8 @@ import { useHookMovies as e } from "../../context/HookMoviesContext.js";
2
2
  import { cn as t } from "../../utils/cn.js";
3
3
  import { useClips as n } from "../../hooks/useHookMoviesApi.js";
4
4
  import { useFileUrl as r } from "../../hooks/useFiles.js";
5
- import { ErrorState as i, LoadingState as a } from "../../components/StateViews.js";
6
- import { EmptyState as o } from "../../components/EmptyState.js";
5
+ import { EmptyState as i } from "../../components/EmptyState.js";
6
+ import { ErrorState as a, LoadingState as o } from "../../components/StateViews.js";
7
7
  import "../../components/index.js";
8
8
  import { useCallback as s, useEffect as c, useRef as l, useState as u } from "react";
9
9
  import { ArrowLeft as d, Film as f, Heart as p, Loader2 as m, MessageCircle as h, Volume2 as g, VolumeX as _ } from "lucide-react";
@@ -194,17 +194,17 @@ var x = 30, S = .6, C = "rounded-full border border-border-seam bg-bg-surface/70
194
194
  }), [m, h] = u(!1), g = s(() => h((e) => !e), []), _ = s((e) => r(`title/${e}`), [r]), b = c?.items ?? [];
195
195
  return /* @__PURE__ */ v("div", {
196
196
  className: "h-[100dvh] w-full overflow-hidden bg-bg-canvas",
197
- children: l ? /* @__PURE__ */ v(a, {
197
+ children: l ? /* @__PURE__ */ v(o, {
198
198
  label: "Loading Hooks…",
199
199
  className: "min-h-[100dvh]"
200
- }) : f ? /* @__PURE__ */ v(i, {
200
+ }) : f ? /* @__PURE__ */ v(a, {
201
201
  title: "Couldn't load Hooks",
202
202
  message: "We couldn't reach the clip feed. Check your connection and try again.",
203
203
  onRetry: () => void p(),
204
204
  className: "min-h-[100dvh]"
205
205
  }) : b.length === 0 ? /* @__PURE__ */ v("div", {
206
206
  className: "flex min-h-[100dvh] items-center justify-center px-6",
207
- children: /* @__PURE__ */ v(o, {
207
+ children: /* @__PURE__ */ v(i, {
208
208
  illustration: "empty-media",
209
209
  title: "No Hooks yet",
210
210
  description: "Short-form clips promoting titles will appear here as soon as they're published.",
@@ -3,11 +3,11 @@ import { cn as t } from "../../utils/cn.js";
3
3
  import { useCheckEntitlement as n, useMyWatchProgress as r, useReviews as i, useTitle as a } from "../../hooks/useHookMoviesApi.js";
4
4
  import { formatEnumLabel as o } from "../../utils/formatters.js";
5
5
  import { useFileUrl as s } from "../../hooks/useFiles.js";
6
- import { ErrorState as c, LoadingState as l } from "../../components/StateViews.js";
7
- import { EmptyState as ee } from "../../components/EmptyState.js";
8
- import { RatingDisplay as te } from "../../components/RatingDisplay.js";
9
- import { useCallback as u, useEffect as d, useMemo as f, useState as p } from "react";
10
- import { ArrowLeft as ne, BadgeCheck as m, Calendar as re, ChevronRight as ie, Clapperboard as ae, Clock as oe, Film as h, Globe as g, Languages as _, Layers as v, Lock as se, Play as y, ShieldCheck as ce, Sparkles as le, Star as b, Ticket as x, Users as S, X as C } from "lucide-react";
6
+ import { EmptyState as c } from "../../components/EmptyState.js";
7
+ import { ErrorState as l, LoadingState as u } from "../../components/StateViews.js";
8
+ import { RatingDisplay as ee } from "../../components/RatingDisplay.js";
9
+ import { useCallback as d, useEffect as f, useMemo as p, useState as m } from "react";
10
+ import { ArrowLeft as te, BadgeCheck as h, Calendar as ne, ChevronRight as re, Clapperboard as ie, Clock as ae, Film as g, Globe as _, Languages as v, Layers as oe, Lock as se, Play as y, ShieldCheck as ce, Sparkles as le, Star as b, Ticket as x, Users as S, X as C } from "lucide-react";
11
11
  import { useParams as ue } from "react-router-dom";
12
12
  import { jsx as w, jsxs as T } from "react/jsx-runtime";
13
13
  import { GlassCard as de } from "@burdenoff/fe-libs/ui";
@@ -89,7 +89,7 @@ var pe = ({ credit: e }) => {
89
89
  children: /* @__PURE__ */ w(C, { className: "h-5 w-5" })
90
90
  }), /* @__PURE__ */ w("div", {
91
91
  className: "flex aspect-video w-full items-center justify-center bg-bg-canvas",
92
- children: t ? /* @__PURE__ */ w(l, { label: "Loading trailer…" }) : e ? /* @__PURE__ */ w("video", {
92
+ children: t ? /* @__PURE__ */ w(u, { label: "Loading trailer…" }) : e ? /* @__PURE__ */ w("video", {
93
93
  src: e,
94
94
  controls: !0,
95
95
  autoPlay: !0,
@@ -106,25 +106,25 @@ var pe = ({ credit: e }) => {
106
106
  let { id: C } = ue(), { navigateTo: D } = e(), { data: A, isLoading: j, isError: M, refetch: N } = a(C), P = n(C), F = r(C), I = i({ titleId: C }, {
107
107
  limit: 12,
108
108
  offset: 0
109
- }), L = s(A?.backdropFileId), R = s(A?.posterFileId), [z, B] = p(!1), V = s(z ? A?.trailerFileId : void 0);
110
- d(() => {
109
+ }), L = s(A?.backdropFileId), R = s(A?.posterFileId), [z, B] = m(!1), V = s(z ? A?.trailerFileId : void 0);
110
+ f(() => {
111
111
  if (!z) return;
112
112
  let e = (e) => {
113
113
  e.key === "Escape" && B(!1);
114
114
  };
115
115
  return window.addEventListener("keydown", e), () => window.removeEventListener("keydown", e);
116
116
  }, [z]);
117
- let H = f(() => {
117
+ let H = p(() => {
118
118
  let e = (F.data ?? []).find((e) => e.titleId === C && !e.episodeId && !e.completed);
119
119
  return e && e.positionSeconds > 0 ? e.positionSeconds : 0;
120
- }, [F.data, C]), U = u(() => D("browse"), [D]);
120
+ }, [F.data, C]), U = d(() => D("browse"), [D]);
121
121
  if (j) return /* @__PURE__ */ w("div", {
122
122
  className: "flex h-full flex-col bg-bg-canvas",
123
- children: /* @__PURE__ */ w(l, { label: "Loading title…" })
123
+ children: /* @__PURE__ */ w(u, { label: "Loading title…" })
124
124
  });
125
125
  if (M || !A) return /* @__PURE__ */ w("div", {
126
126
  className: "flex h-full flex-col bg-bg-canvas",
127
- children: /* @__PURE__ */ w(c, {
127
+ children: /* @__PURE__ */ w(l, {
128
128
  title: "Title not found",
129
129
  message: "This title may have been unpublished, or you may not have access to it.",
130
130
  onRetry: () => void N()
@@ -135,19 +135,19 @@ var pe = ({ credit: e }) => {
135
135
  items: J.filter((t) => t.seasonId === e.id)
136
136
  })), Z = J.filter((e) => !e.seasonId || !Y.some((t) => t.id === e.seasonId)), Q = [];
137
137
  A.releaseYear && Q.push({
138
- icon: re,
138
+ icon: ne,
139
139
  label: String(A.releaseYear)
140
140
  }), A.runtimeMinutes && Q.push({
141
- icon: oe,
141
+ icon: ae,
142
142
  label: E(A.runtimeMinutes)
143
143
  }), A.certification && Q.push({
144
144
  icon: ce,
145
145
  label: A.certification
146
146
  }), A.originalLanguage && Q.push({
147
- icon: _,
147
+ icon: v,
148
148
  label: A.originalLanguage.toUpperCase()
149
149
  }), A.originCountry && Q.push({
150
- icon: g,
150
+ icon: _,
151
151
  label: A.originCountry
152
152
  });
153
153
  let $ = (e) => D(`watch/${A.id}/episode/${e}`);
@@ -167,7 +167,7 @@ var pe = ({ credit: e }) => {
167
167
  className: "h-full w-full object-cover object-center"
168
168
  }) : /* @__PURE__ */ w("div", {
169
169
  className: "flex h-full w-full items-center justify-center bg-bg-sunken",
170
- children: /* @__PURE__ */ w(ae, {
170
+ children: /* @__PURE__ */ w(ie, {
171
171
  className: "h-16 w-16 text-text-muted",
172
172
  "aria-hidden": "true"
173
173
  })
@@ -182,7 +182,7 @@ var pe = ({ credit: e }) => {
182
182
  type: "button",
183
183
  onClick: U,
184
184
  className: "inline-flex items-center gap-2 rounded-full border border-border-seam bg-bg-surface/60 px-3 py-1.5 text-sm font-medium text-text-primary backdrop-blur transition-colors hover:bg-bg-surface focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]",
185
- children: [/* @__PURE__ */ w(ne, { className: "h-4 w-4" }), "Browse"]
185
+ children: [/* @__PURE__ */ w(te, { className: "h-4 w-4" }), "Browse"]
186
186
  })
187
187
  }),
188
188
  /* @__PURE__ */ T("div", {
@@ -197,7 +197,7 @@ var pe = ({ credit: e }) => {
197
197
  className: "h-full w-full object-cover"
198
198
  }) : /* @__PURE__ */ w("div", {
199
199
  className: "flex h-full w-full items-center justify-center",
200
- children: /* @__PURE__ */ w(h, {
200
+ children: /* @__PURE__ */ w(g, {
201
201
  className: "h-10 w-10 text-text-muted",
202
202
  "aria-hidden": "true"
203
203
  })
@@ -266,7 +266,7 @@ var pe = ({ credit: e }) => {
266
266
  type: "button",
267
267
  onClick: () => B(!0),
268
268
  className: "inline-flex items-center gap-2 rounded-xl border border-border-seam bg-bg-surface/60 px-5 py-2.5 text-sm font-semibold text-text-primary backdrop-blur transition-colors hover:bg-bg-surface focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]",
269
- children: [/* @__PURE__ */ w(h, { className: "h-5 w-5" }), "Watch Trailer"]
269
+ children: [/* @__PURE__ */ w(g, { className: "h-5 w-5" }), "Watch Trailer"]
270
270
  })]
271
271
  }),
272
272
  !P.isLoading && !W && /* @__PURE__ */ T("p", {
@@ -280,7 +280,7 @@ var pe = ({ credit: e }) => {
280
280
  className: "mt-3 flex flex-wrap gap-1.5",
281
281
  children: P.data.modes.map((e) => /* @__PURE__ */ T("span", {
282
282
  className: "inline-flex items-center gap-1 rounded-full bg-status-success-bg-subtle px-2.5 py-0.5 text-xs text-status-success-text",
283
- children: [/* @__PURE__ */ w(m, {
283
+ children: [/* @__PURE__ */ w(h, {
284
284
  className: "h-3.5 w-3.5",
285
285
  "aria-hidden": "true"
286
286
  }), o(e)]
@@ -313,7 +313,7 @@ var pe = ({ credit: e }) => {
313
313
  })] }),
314
314
  J.length > 0 && /* @__PURE__ */ T("section", { children: [/* @__PURE__ */ T("h2", {
315
315
  className: "mb-4 flex items-center gap-2 text-lg font-semibold text-text-primary",
316
- children: [/* @__PURE__ */ w(v, {
316
+ children: [/* @__PURE__ */ w(oe, {
317
317
  className: "h-5 w-5",
318
318
  "aria-hidden": "true"
319
319
  }), me ? "Seasons & Episodes" : "Episodes"]
@@ -347,14 +347,14 @@ var pe = ({ credit: e }) => {
347
347
  className: "h-5 w-5",
348
348
  "aria-hidden": "true"
349
349
  }), "Reviews"]
350
- }), K !== null && /* @__PURE__ */ w(te, {
350
+ }), K !== null && /* @__PURE__ */ w(ee, {
351
351
  value: K / 2,
352
352
  count: G.length
353
353
  })]
354
- }), I.isLoading ? /* @__PURE__ */ w(l, { label: "Loading reviews…" }) : I.isError ? /* @__PURE__ */ w(c, {
354
+ }), I.isLoading ? /* @__PURE__ */ w(u, { label: "Loading reviews…" }) : I.isError ? /* @__PURE__ */ w(l, {
355
355
  title: "Couldn't load reviews",
356
356
  onRetry: () => void I.refetch()
357
- }) : G.length === 0 ? /* @__PURE__ */ w(ee, {
357
+ }) : G.length === 0 ? /* @__PURE__ */ w(c, {
358
358
  title: "No reviews yet",
359
359
  description: "Be the first to share what you thought once you've watched.",
360
360
  illustration: "empty-data"
@@ -378,7 +378,7 @@ var pe = ({ credit: e }) => {
378
378
  ]
379
379
  }), e.verifiedWatch && /* @__PURE__ */ T("span", {
380
380
  className: "inline-flex items-center gap-1 rounded-full bg-status-success-bg-subtle px-2 py-0.5 text-[10px] font-medium text-status-success-text",
381
- children: [/* @__PURE__ */ w(m, {
381
+ children: [/* @__PURE__ */ w(h, {
382
382
  className: "h-3 w-3",
383
383
  "aria-hidden": "true"
384
384
  }), "Verified"]
@@ -406,7 +406,7 @@ var pe = ({ credit: e }) => {
406
406
  type: "button",
407
407
  onClick: U,
408
408
  className: "inline-flex items-center gap-1.5 text-sm font-medium text-text-secondary transition-colors hover:text-text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] rounded",
409
- children: ["Explore more titles", /* @__PURE__ */ w(ie, { className: "h-4 w-4" })]
409
+ children: ["Explore more titles", /* @__PURE__ */ w(re, { className: "h-4 w-4" })]
410
410
  }) })
411
411
  ]
412
412
  }),
@@ -1 +1 @@
1
- {"version":3,"file":"TitleViewPage.js","names":[],"sources":["../../../../src/hookmovies/pages/consumer/TitleViewPage.tsx"],"sourcesContent":["/**\n * Title view (Consumer)\n * @module hookmovies/pages/consumer\n *\n * Audience-facing detail page for a single title (route `/hookmovies/title/:id`).\n * This is the discovery → playback bridge: a cinematic, dark, full-bleed hero\n * built from the title's backdrop / poster file references, an entitlement-gated\n * Play/Resume call-to-action, an optional trailer, cast & crew, the season /\n * episode structure, and a reviews rail.\n *\n * Data comes exclusively from the live `hookmovies` subgraph via the shared\n * hand-written hooks — this page reads, it never mutates:\n * - {@link useTitle} — the title (with credits, seasons, episodes).\n * - {@link useCheckEntitlement} — whether the caller may watch, and how.\n * - {@link useMyWatchProgress} — resume position for the Play/Resume CTA.\n * - {@link useReviews} — the audience reviews rail.\n * - {@link useFileUrl} — resolves backdrop / poster / trailer file ids\n * to presigned URLs (no media is stored here).\n *\n * Entitlement branches:\n * - Entitled → Play/Resume navigates to the player (`watch/:id`), showing the\n * resume timecode when watch progress exists.\n * - Not entitled → a \"Where to watch\" CTA links to the (separate) checkout route\n * (`checkout/:id`) — this page only links there, it never builds\n * or invokes checkout.\n */\n\nimport { type FC, useCallback, useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n ArrowLeft,\n BadgeCheck,\n Calendar,\n ChevronRight,\n Clapperboard,\n Clock,\n Film,\n Globe,\n Languages,\n Layers,\n Lock,\n Play,\n ShieldCheck,\n Sparkles,\n Star,\n Ticket,\n Users,\n X,\n} from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingState, ErrorState } from '../../components/StateViews';\nimport { RatingDisplay } from '../../components/RatingDisplay';\nimport { useHookMovies } from '../../context/HookMoviesContext';\nimport {\n useTitle,\n useCheckEntitlement,\n useMyWatchProgress,\n useReviews,\n type HokCredit,\n type HokEpisodeSummary,\n} from '../../hooks/useHookMoviesApi';\nimport { useFileUrl } from '../../hooks/useFiles';\nimport { formatEnumLabel } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\n\n// ── Small display helpers ────────────────────────────────────────────────────\n\n/** Format a runtime in minutes as \"1h 58m\" / \"45m\". */\nfunction formatRuntime(minutes: number): string {\n if (minutes <= 0) return '';\n const hours = Math.floor(minutes / 60);\n const rest = minutes % 60;\n if (hours === 0) return `${rest}m`;\n if (rest === 0) return `${hours}h`;\n return `${hours}h ${rest}m`;\n}\n\n/** Format a seconds offset as a \"h:mm:ss\" / \"m:ss\" timecode for the resume CTA. */\nfunction formatTimecode(totalSeconds: number): string {\n const s = Math.max(0, Math.floor(totalSeconds));\n const hours = Math.floor(s / 3600);\n const minutes = Math.floor((s % 3600) / 60);\n const seconds = s % 60;\n const mm = String(minutes).padStart(2, '0');\n const ss = String(seconds).padStart(2, '0');\n return hours > 0 ? `${hours}:${mm}:${ss}` : `${minutes}:${ss}`;\n}\n\n/** Two-letter initials for a person avatar fallback (people carry no image ref). */\nfunction initials(name: string): string {\n const parts = name.trim().split(/\\s+/).filter(Boolean);\n if (parts.length === 0) return '?';\n if (parts.length === 1) return parts[0]!.slice(0, 2).toUpperCase();\n return (parts[0]![0]! + parts[parts.length - 1]![0]!).toUpperCase();\n}\n\n// ── Cast & crew ──────────────────────────────────────────────────────────────\n\nconst CreditCard: FC<{ credit: HokCredit }> = ({ credit }) => {\n const name = credit.person?.name ?? 'Unknown';\n const secondary = credit.characterName\n ? `as ${credit.characterName}`\n : formatEnumLabel(credit.role);\n return (\n <div className=\"flex items-center gap-3 rounded-xl border border-border-seam bg-bg-surface/60 p-3\">\n <div\n className=\"flex size-11 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-sm font-semibold text-text-secondary\"\n aria-hidden=\"true\"\n >\n {initials(name)}\n </div>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{name}</p>\n <p className=\"truncate text-xs text-text-secondary\">{secondary}</p>\n </div>\n </div>\n );\n};\n\n// ── Episodes ─────────────────────────────────────────────────────────────────\n\nconst EpisodeRow: FC<{ episode: HokEpisodeSummary; onPlay: () => void }> = ({\n episode,\n onPlay,\n}) => (\n <button\n type=\"button\"\n onClick={onPlay}\n className=\"group flex w-full items-center gap-4 rounded-xl border border-border-seam bg-bg-surface/60 p-3 text-left transition-colors hover:bg-bg-sunken focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <div className=\"flex size-10 shrink-0 items-center justify-center rounded-lg bg-bg-sunken text-sm font-semibold text-text-secondary\">\n {episode.number}\n </div>\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{episode.name}</p>\n {episode.synopsis && (\n <p className=\"line-clamp-1 text-xs text-text-secondary\">{episode.synopsis}</p>\n )}\n </div>\n {episode.runtimeMinutes ? (\n <span className=\"shrink-0 text-xs text-text-secondary\">\n {formatRuntime(episode.runtimeMinutes)}\n </span>\n ) : null}\n <span className=\"flex size-8 shrink-0 items-center justify-center rounded-full bg-action-primary-bg/0 text-text-secondary transition-colors group-hover:bg-action-primary-bg group-hover:text-action-primary-text\">\n <Play className=\"h-4 w-4\" />\n </span>\n </button>\n);\n\n// ── Trailer modal ────────────────────────────────────────────────────────────\n\nconst TrailerModal: FC<{ url: string | null; isLoading: boolean; onClose: () => void }> = ({\n url,\n isLoading,\n onClose,\n}) => (\n <div\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-canvas/90 p-4 backdrop-blur-sm\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Trailer\"\n onClick={onClose}\n >\n <div\n className=\"relative w-full max-w-4xl overflow-hidden rounded-2xl border border-border-seam bg-bg-surface shadow-2xl\"\n onClick={(e) => e.stopPropagation()}\n >\n <button\n type=\"button\"\n onClick={onClose}\n className=\"absolute right-3 top-3 z-10 flex size-9 items-center justify-center rounded-full bg-bg-canvas/70 text-text-primary transition-colors hover:bg-bg-canvas focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n aria-label=\"Close trailer\"\n >\n <X className=\"h-5 w-5\" />\n </button>\n <div className=\"flex aspect-video w-full items-center justify-center bg-bg-canvas\">\n {isLoading ? (\n <LoadingState label=\"Loading trailer…\" />\n ) : url ? (\n <video src={url} controls autoPlay className=\"h-full w-full\" aria-label=\"Title trailer\">\n <track kind=\"captions\" />\n </video>\n ) : (\n <p className=\"p-8 text-sm text-text-secondary\">Trailer is unavailable right now.</p>\n )}\n </div>\n </div>\n </div>\n);\n\n// ── Page ─────────────────────────────────────────────────────────────────────\n\nexport const TitleViewPage: FC = () => {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useHookMovies();\n\n const { data: title, isLoading, isError, refetch } = useTitle(id);\n const entitlement = useCheckEntitlement(id);\n const watchProgress = useMyWatchProgress(id);\n const reviews = useReviews({ titleId: id }, { limit: 12, offset: 0 });\n\n const backdrop = useFileUrl(title?.backdropFileId);\n const poster = useFileUrl(title?.posterFileId);\n\n const [showTrailer, setShowTrailer] = useState(false);\n const trailer = useFileUrl(showTrailer ? title?.trailerFileId : undefined);\n\n // Close the trailer modal on Escape.\n useEffect(() => {\n if (!showTrailer) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key === 'Escape') setShowTrailer(false);\n };\n window.addEventListener('keydown', onKey);\n return () => window.removeEventListener('keydown', onKey);\n }, [showTrailer]);\n\n // Resume position: the title-level (episodeId === null) progress row, if any.\n const resumeSeconds = useMemo(() => {\n const rows = watchProgress.data ?? [];\n const row = rows.find((r) => r.titleId === id && !r.episodeId && !r.completed);\n return row && row.positionSeconds > 0 ? row.positionSeconds : 0;\n }, [watchProgress.data, id]);\n\n const goBack = useCallback(() => navigateTo('browse'), [navigateTo]);\n\n if (isLoading) {\n return (\n <div className=\"flex h-full flex-col bg-bg-canvas\">\n <LoadingState label=\"Loading title…\" />\n </div>\n );\n }\n\n if (isError || !title) {\n return (\n <div className=\"flex h-full flex-col bg-bg-canvas\">\n <ErrorState\n title=\"Title not found\"\n message=\"This title may have been unpublished, or you may not have access to it.\"\n onRetry={() => void refetch()}\n />\n </div>\n );\n }\n\n const entitled = entitlement.data?.entitled ?? false;\n const reviewItems = reviews.data?.items ?? [];\n const avgScore =\n reviewItems.length > 0\n ? reviewItems.reduce((sum, r) => sum + r.score, 0) / reviewItems.length\n : null;\n\n const credits = [...title.credits].sort((a, b) => a.billingOrder - b.billingOrder);\n const episodes = [...title.episodes].sort((a, b) => a.number - b.number);\n const seasons = [...title.seasons].sort((a, b) => a.number - b.number);\n const isSeries = title.type === 'SERIES';\n\n // Group episodes by season (falling back to a single \"ungrouped\" bucket).\n const episodesBySeason = seasons.map((season) => ({\n season,\n items: episodes.filter((ep) => ep.seasonId === season.id),\n }));\n const ungroupedEpisodes = episodes.filter(\n (ep) => !ep.seasonId || !seasons.some((s) => s.id === ep.seasonId)\n );\n\n // Compact meta chips under the title.\n const metaItems: Array<{ icon: typeof Calendar; label: string }> = [];\n if (title.releaseYear) metaItems.push({ icon: Calendar, label: String(title.releaseYear) });\n if (title.runtimeMinutes)\n metaItems.push({ icon: Clock, label: formatRuntime(title.runtimeMinutes) });\n if (title.certification) metaItems.push({ icon: ShieldCheck, label: title.certification });\n if (title.originalLanguage)\n metaItems.push({ icon: Languages, label: title.originalLanguage.toUpperCase() });\n if (title.originCountry) metaItems.push({ icon: Globe, label: title.originCountry });\n\n const playEpisode = (episodeId: string) => navigateTo(`watch/${title.id}/episode/${episodeId}`);\n\n return (\n <div className=\"flex h-full flex-col overflow-y-auto bg-bg-canvas\">\n {/* ── Cinematic backdrop hero ─────────────────────────────────────────── */}\n <section className=\"relative isolate\">\n {/* Backdrop image / fallback */}\n <div className=\"absolute inset-0 -z-10 overflow-hidden bg-bg-sunken\">\n {backdrop.data ? (\n <img\n src={backdrop.data}\n alt=\"\"\n aria-hidden=\"true\"\n className=\"h-full w-full object-cover object-center\"\n />\n ) : (\n <div className=\"flex h-full w-full items-center justify-center bg-bg-sunken\">\n <Clapperboard className=\"h-16 w-16 text-text-muted\" aria-hidden=\"true\" />\n </div>\n )}\n {/* Legibility scrims (bottom + left), token-only gradients */}\n <div className=\"absolute inset-0 bg-gradient-to-t from-bg-canvas via-bg-canvas/70 to-bg-canvas/20\" />\n <div className=\"absolute inset-0 bg-gradient-to-r from-bg-canvas/90 via-bg-canvas/40 to-transparent\" />\n </div>\n\n {/* Back control */}\n <div className=\"px-4 pt-4 sm:px-8\">\n <button\n type=\"button\"\n onClick={goBack}\n className=\"inline-flex items-center gap-2 rounded-full border border-border-seam bg-bg-surface/60 px-3 py-1.5 text-sm font-medium text-text-primary backdrop-blur transition-colors hover:bg-bg-surface focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <ArrowLeft className=\"h-4 w-4\" />\n Browse\n </button>\n </div>\n\n {/* Hero content */}\n <div className=\"mx-auto flex max-w-6xl flex-col gap-6 px-4 pb-10 pt-24 sm:px-8 sm:pt-36 md:flex-row md:items-end lg:pt-48\">\n {/* Poster */}\n <div className=\"w-32 shrink-0 overflow-hidden rounded-xl border border-border-seam bg-bg-sunken shadow-2xl sm:w-40 md:w-48\">\n <div className=\"aspect-[2/3] w-full\">\n {poster.data ? (\n <img\n src={poster.data}\n alt={`${title.title} poster`}\n className=\"h-full w-full object-cover\"\n />\n ) : (\n <div className=\"flex h-full w-full items-center justify-center\">\n <Film className=\"h-10 w-10 text-text-muted\" aria-hidden=\"true\" />\n </div>\n )}\n </div>\n </div>\n\n {/* Title + meta + CTAs */}\n <div className=\"min-w-0 flex-1\">\n <p className=\"mb-2 text-xs font-semibold uppercase tracking-widest text-text-secondary\">\n {formatEnumLabel(title.type)}\n {title.studio ? ` · ${title.studio.name}` : ''}\n </p>\n <h1 className=\"text-3xl font-bold leading-tight tracking-tight text-text-primary sm:text-4xl lg:text-5xl\">\n {title.title}\n </h1>\n\n {metaItems.length > 0 && (\n <div className=\"mt-3 flex flex-wrap items-center gap-x-4 gap-y-2 text-sm text-text-secondary\">\n {metaItems.map((m) => {\n const Icon = m.icon;\n return (\n <span key={m.label} className=\"inline-flex items-center gap-1.5\">\n <Icon className=\"h-4 w-4\" aria-hidden=\"true\" />\n {m.label}\n </span>\n );\n })}\n {avgScore !== null && (\n <span className=\"inline-flex items-center gap-1.5\">\n <Star\n className=\"h-4 w-4 fill-status-warning-text text-status-warning-text\"\n aria-hidden=\"true\"\n />\n {(avgScore / 2).toFixed(1)}\n </span>\n )}\n </div>\n )}\n\n {title.genres.length > 0 && (\n <div className=\"mt-3 flex flex-wrap gap-1.5\">\n {title.genres.map((g) => (\n <span\n key={g}\n className=\"rounded-full border border-border-seam bg-bg-surface/60 px-2.5 py-0.5 text-xs text-text-primary backdrop-blur\"\n >\n {g}\n </span>\n ))}\n </div>\n )}\n\n {title.aiDisclosure && (\n <p className=\"mt-3 inline-flex items-center gap-1.5 text-xs text-text-secondary\">\n <Sparkles className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n Contains AI-generated or AI-altered content\n </p>\n )}\n\n {/* Call to action row */}\n <div className=\"mt-6 flex flex-wrap items-center gap-3\">\n {entitlement.isLoading ? (\n <span className=\"inline-flex items-center gap-2 rounded-xl bg-bg-sunken px-5 py-2.5 text-sm font-medium text-text-secondary\">\n <span className=\"size-4 animate-spin rounded-full border-2 border-border-strong border-t-transparent\" />\n Checking access…\n </span>\n ) : entitled ? (\n <button\n type=\"button\"\n onClick={() => navigateTo(`watch/${title.id}`)}\n className=\"inline-flex items-center gap-2 rounded-xl bg-action-primary-bg px-6 py-2.5 text-sm font-semibold text-action-primary-text shadow-lg transition-opacity hover:opacity-90 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <Play className=\"h-5 w-5 fill-current\" />\n {resumeSeconds > 0 ? `Resume from ${formatTimecode(resumeSeconds)}` : 'Play'}\n </button>\n ) : (\n <button\n type=\"button\"\n onClick={() => navigateTo(`checkout/${title.id}`)}\n className=\"inline-flex items-center gap-2 rounded-xl bg-action-primary-bg px-6 py-2.5 text-sm font-semibold text-action-primary-text shadow-lg transition-opacity hover:opacity-90 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <Ticket className=\"h-5 w-5\" />\n Where to watch\n </button>\n )}\n\n {title.trailerFileId && (\n <button\n type=\"button\"\n onClick={() => setShowTrailer(true)}\n className=\"inline-flex items-center gap-2 rounded-xl border border-border-seam bg-bg-surface/60 px-5 py-2.5 text-sm font-semibold text-text-primary backdrop-blur transition-colors hover:bg-bg-surface focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <Film className=\"h-5 w-5\" />\n Watch Trailer\n </button>\n )}\n </div>\n\n {/* Access hint when not entitled */}\n {!entitlement.isLoading && !entitled && (\n <p className=\"mt-3 inline-flex items-center gap-1.5 text-xs text-text-secondary\">\n <Lock className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n You don&apos;t have access yet — see your viewing options.\n </p>\n )}\n {entitled && entitlement.data && 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=\"inline-flex items-center gap-1 rounded-full bg-status-success-bg-subtle px-2.5 py-0.5 text-xs text-status-success-text\"\n >\n <BadgeCheck className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n {formatEnumLabel(mode)}\n </span>\n ))}\n </div>\n )}\n </div>\n </div>\n </section>\n\n {/* ── Body ────────────────────────────────────────────────────────────── */}\n <div className=\"mx-auto w-full max-w-6xl space-y-10 px-4 pb-16 sm:px-8\">\n {/* Synopsis */}\n {title.synopsis && (\n <section>\n <h2 className=\"mb-3 text-lg font-semibold text-text-primary\">Synopsis</h2>\n <p className=\"max-w-3xl text-sm leading-relaxed text-text-secondary sm:text-base\">\n {title.synopsis}\n </p>\n </section>\n )}\n\n {/* Cast & Crew */}\n {credits.length > 0 && (\n <section>\n <h2 className=\"mb-4 flex items-center gap-2 text-lg font-semibold text-text-primary\">\n <Users className=\"h-5 w-5\" aria-hidden=\"true\" />\n Cast &amp; Crew\n </h2>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3\">\n {credits.map((credit) => (\n <CreditCard key={credit.id} credit={credit} />\n ))}\n </div>\n </section>\n )}\n\n {/* Seasons & Episodes */}\n {episodes.length > 0 && (\n <section>\n <h2 className=\"mb-4 flex items-center gap-2 text-lg font-semibold text-text-primary\">\n <Layers className=\"h-5 w-5\" aria-hidden=\"true\" />\n {isSeries ? 'Seasons & Episodes' : 'Episodes'}\n </h2>\n <div className=\"space-y-6\">\n {episodesBySeason\n .filter((group) => group.items.length > 0)\n .map((group) => (\n <div key={group.season.id}>\n <h3 className=\"mb-2 text-sm font-semibold text-text-secondary\">\n {group.season.name\n ? `${group.season.number}. ${group.season.name}`\n : `Season ${group.season.number}`}\n </h3>\n <div className=\"space-y-2\">\n {group.items.map((ep) => (\n <EpisodeRow key={ep.id} episode={ep} onPlay={() => playEpisode(ep.id)} />\n ))}\n </div>\n </div>\n ))}\n {ungroupedEpisodes.length > 0 && (\n <div>\n {episodesBySeason.some((g) => g.items.length > 0) && (\n <h3 className=\"mb-2 text-sm font-semibold text-text-secondary\">\n Other episodes\n </h3>\n )}\n <div className=\"space-y-2\">\n {ungroupedEpisodes.map((ep) => (\n <EpisodeRow key={ep.id} episode={ep} onPlay={() => playEpisode(ep.id)} />\n ))}\n </div>\n </div>\n )}\n </div>\n </section>\n )}\n\n {/* Reviews rail */}\n <section>\n <div className=\"mb-4 flex items-center justify-between gap-3\">\n <h2 className=\"flex items-center gap-2 text-lg font-semibold text-text-primary\">\n <Star className=\"h-5 w-5\" aria-hidden=\"true\" />\n Reviews\n </h2>\n {avgScore !== null && <RatingDisplay value={avgScore / 2} count={reviewItems.length} />}\n </div>\n\n {reviews.isLoading ? (\n <LoadingState label=\"Loading reviews…\" />\n ) : reviews.isError ? (\n <ErrorState title=\"Couldn't load reviews\" onRetry={() => void reviews.refetch()} />\n ) : reviewItems.length === 0 ? (\n <EmptyState\n title=\"No reviews yet\"\n description=\"Be the first to share what you thought once you've watched.\"\n illustration=\"empty-data\"\n />\n ) : (\n <div\n className={cn(\n 'flex snap-x snap-mandatory gap-4 overflow-x-auto pb-2',\n '[scrollbar-width:thin]'\n )}\n >\n {reviewItems.map((review) => (\n <GlassCard\n key={review.id}\n treatment=\"metric\"\n className=\"w-72 shrink-0 snap-start border-border-seam p-4\"\n >\n <div className=\"mb-2 flex items-center justify-between gap-2\">\n <span className=\"inline-flex items-center gap-1 text-sm font-semibold text-text-primary\">\n <Star\n className=\"h-4 w-4 fill-status-warning-text text-status-warning-text\"\n aria-hidden=\"true\"\n />\n {review.score}/10\n </span>\n {review.verifiedWatch && (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-status-success-bg-subtle px-2 py-0.5 text-[10px] font-medium text-status-success-text\">\n <BadgeCheck className=\"h-3 w-3\" aria-hidden=\"true\" />\n Verified\n </span>\n )}\n </div>\n {review.hasSpoilers && (\n <span className=\"mb-1.5 inline-block rounded-full bg-status-warning-bg-subtle px-2 py-0.5 text-[10px] font-medium text-status-warning-text\">\n Spoilers\n </span>\n )}\n {review.body ? (\n <p className=\"line-clamp-5 text-sm leading-relaxed text-text-secondary\">\n {review.body}\n </p>\n ) : (\n <p className=\"text-sm italic text-text-muted\">No written review.</p>\n )}\n {review.helpfulCount > 0 && (\n <p className=\"mt-3 text-xs text-text-secondary\">\n {review.helpfulCount} found this helpful\n </p>\n )}\n </GlassCard>\n ))}\n </div>\n )}\n </section>\n\n {/* Nudge back into the catalog */}\n <div>\n <button\n type=\"button\"\n onClick={goBack}\n className=\"inline-flex items-center gap-1.5 text-sm font-medium text-text-secondary transition-colors hover:text-text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] rounded\"\n >\n Explore more titles\n <ChevronRight className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n\n {/* Trailer modal */}\n {showTrailer && (\n <TrailerModal\n url={trailer.data ?? null}\n isLoading={trailer.isLoading}\n onClose={() => setShowTrailer(false)}\n />\n )}\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"TitleViewPage.js","names":[],"sources":["../../../../src/hookmovies/pages/consumer/TitleViewPage.tsx"],"sourcesContent":["/**\n * Title view (Consumer)\n * @module hookmovies/pages/consumer\n *\n * Audience-facing detail page for a single title (route `/hookmovies/title/:id`).\n * This is the discovery → playback bridge: a cinematic, dark, full-bleed hero\n * built from the title's backdrop / poster file references, an entitlement-gated\n * Play/Resume call-to-action, an optional trailer, cast & crew, the season /\n * episode structure, and a reviews rail.\n *\n * Data comes exclusively from the live `hookmovies` subgraph via the shared\n * hand-written hooks — this page reads, it never mutates:\n * - {@link useTitle} — the title (with credits, seasons, episodes).\n * - {@link useCheckEntitlement} — whether the caller may watch, and how.\n * - {@link useMyWatchProgress} — resume position for the Play/Resume CTA.\n * - {@link useReviews} — the audience reviews rail.\n * - {@link useFileUrl} — resolves backdrop / poster / trailer file ids\n * to presigned URLs (no media is stored here).\n *\n * Entitlement branches:\n * - Entitled → Play/Resume navigates to the player (`watch/:id`), showing the\n * resume timecode when watch progress exists.\n * - Not entitled → a \"Where to watch\" CTA links to the (separate) checkout route\n * (`checkout/:id`) — this page only links there, it never builds\n * or invokes checkout.\n */\n\nimport { type FC, useCallback, useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n ArrowLeft,\n BadgeCheck,\n Calendar,\n ChevronRight,\n Clapperboard,\n Clock,\n Film,\n Globe,\n Languages,\n Layers,\n Lock,\n Play,\n ShieldCheck,\n Sparkles,\n Star,\n Ticket,\n Users,\n X,\n} from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingState, ErrorState } from '../../components/StateViews';\nimport { RatingDisplay } from '../../components/RatingDisplay';\nimport { useHookMovies } from '../../context/HookMoviesContext';\nimport {\n useTitle,\n useCheckEntitlement,\n useMyWatchProgress,\n useReviews,\n type HokCredit,\n type HokEpisodeSummary,\n} from '../../hooks/useHookMoviesApi';\nimport { useFileUrl } from '../../hooks/useFiles';\nimport { formatEnumLabel } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\n\n// ── Small display helpers ────────────────────────────────────────────────────\n\n/** Format a runtime in minutes as \"1h 58m\" / \"45m\". */\nfunction formatRuntime(minutes: number): string {\n if (minutes <= 0) return '';\n const hours = Math.floor(minutes / 60);\n const rest = minutes % 60;\n if (hours === 0) return `${rest}m`;\n if (rest === 0) return `${hours}h`;\n return `${hours}h ${rest}m`;\n}\n\n/** Format a seconds offset as a \"h:mm:ss\" / \"m:ss\" timecode for the resume CTA. */\nfunction formatTimecode(totalSeconds: number): string {\n const s = Math.max(0, Math.floor(totalSeconds));\n const hours = Math.floor(s / 3600);\n const minutes = Math.floor((s % 3600) / 60);\n const seconds = s % 60;\n const mm = String(minutes).padStart(2, '0');\n const ss = String(seconds).padStart(2, '0');\n return hours > 0 ? `${hours}:${mm}:${ss}` : `${minutes}:${ss}`;\n}\n\n/** Two-letter initials for a person avatar fallback (people carry no image ref). */\nfunction initials(name: string): string {\n const parts = name.trim().split(/\\s+/).filter(Boolean);\n if (parts.length === 0) return '?';\n if (parts.length === 1) return parts[0]!.slice(0, 2).toUpperCase();\n return (parts[0]![0]! + parts[parts.length - 1]![0]!).toUpperCase();\n}\n\n// ── Cast & crew ──────────────────────────────────────────────────────────────\n\nconst CreditCard: FC<{ credit: HokCredit }> = ({ credit }) => {\n const name = credit.person?.name ?? 'Unknown';\n const secondary = credit.characterName\n ? `as ${credit.characterName}`\n : formatEnumLabel(credit.role);\n return (\n <div className=\"flex items-center gap-3 rounded-xl border border-border-seam bg-bg-surface/60 p-3\">\n <div\n className=\"flex size-11 shrink-0 items-center justify-center rounded-full bg-bg-sunken text-sm font-semibold text-text-secondary\"\n aria-hidden=\"true\"\n >\n {initials(name)}\n </div>\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{name}</p>\n <p className=\"truncate text-xs text-text-secondary\">{secondary}</p>\n </div>\n </div>\n );\n};\n\n// ── Episodes ─────────────────────────────────────────────────────────────────\n\nconst EpisodeRow: FC<{ episode: HokEpisodeSummary; onPlay: () => void }> = ({\n episode,\n onPlay,\n}) => (\n <button\n type=\"button\"\n onClick={onPlay}\n className=\"group flex w-full items-center gap-4 rounded-xl border border-border-seam bg-bg-surface/60 p-3 text-left transition-colors hover:bg-bg-sunken focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <div className=\"flex size-10 shrink-0 items-center justify-center rounded-lg bg-bg-sunken text-sm font-semibold text-text-secondary\">\n {episode.number}\n </div>\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{episode.name}</p>\n {episode.synopsis && (\n <p className=\"line-clamp-1 text-xs text-text-secondary\">{episode.synopsis}</p>\n )}\n </div>\n {episode.runtimeMinutes ? (\n <span className=\"shrink-0 text-xs text-text-secondary\">\n {formatRuntime(episode.runtimeMinutes)}\n </span>\n ) : null}\n <span className=\"flex size-8 shrink-0 items-center justify-center rounded-full bg-action-primary-bg/0 text-text-secondary transition-colors group-hover:bg-action-primary-bg group-hover:text-action-primary-text\">\n <Play className=\"h-4 w-4\" />\n </span>\n </button>\n);\n\n// ── Trailer modal ────────────────────────────────────────────────────────────\n\nconst TrailerModal: FC<{ url: string | null; isLoading: boolean; onClose: () => void }> = ({\n url,\n isLoading,\n onClose,\n}) => (\n <div\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-canvas/90 p-4 backdrop-blur-sm\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Trailer\"\n onClick={onClose}\n >\n <div\n className=\"relative w-full max-w-4xl overflow-hidden rounded-2xl border border-border-seam bg-bg-surface shadow-2xl\"\n onClick={(e) => e.stopPropagation()}\n >\n <button\n type=\"button\"\n onClick={onClose}\n className=\"absolute right-3 top-3 z-10 flex size-9 items-center justify-center rounded-full bg-bg-canvas/70 text-text-primary transition-colors hover:bg-bg-canvas focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n aria-label=\"Close trailer\"\n >\n <X className=\"h-5 w-5\" />\n </button>\n <div className=\"flex aspect-video w-full items-center justify-center bg-bg-canvas\">\n {isLoading ? (\n <LoadingState label=\"Loading trailer…\" />\n ) : url ? (\n <video src={url} controls autoPlay className=\"h-full w-full\" aria-label=\"Title trailer\">\n <track kind=\"captions\" />\n </video>\n ) : (\n <p className=\"p-8 text-sm text-text-secondary\">Trailer is unavailable right now.</p>\n )}\n </div>\n </div>\n </div>\n);\n\n// ── Page ─────────────────────────────────────────────────────────────────────\n\nexport const TitleViewPage: FC = () => {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useHookMovies();\n\n const { data: title, isLoading, isError, refetch } = useTitle(id);\n const entitlement = useCheckEntitlement(id);\n const watchProgress = useMyWatchProgress(id);\n const reviews = useReviews({ titleId: id }, { limit: 12, offset: 0 });\n\n const backdrop = useFileUrl(title?.backdropFileId);\n const poster = useFileUrl(title?.posterFileId);\n\n const [showTrailer, setShowTrailer] = useState(false);\n const trailer = useFileUrl(showTrailer ? title?.trailerFileId : undefined);\n\n // Close the trailer modal on Escape.\n useEffect(() => {\n if (!showTrailer) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key === 'Escape') setShowTrailer(false);\n };\n window.addEventListener('keydown', onKey);\n return () => window.removeEventListener('keydown', onKey);\n }, [showTrailer]);\n\n // Resume position: the title-level (episodeId === null) progress row, if any.\n const resumeSeconds = useMemo(() => {\n const rows = watchProgress.data ?? [];\n const row = rows.find((r) => r.titleId === id && !r.episodeId && !r.completed);\n return row && row.positionSeconds > 0 ? row.positionSeconds : 0;\n }, [watchProgress.data, id]);\n\n const goBack = useCallback(() => navigateTo('browse'), [navigateTo]);\n\n if (isLoading) {\n return (\n <div className=\"flex h-full flex-col bg-bg-canvas\">\n <LoadingState label=\"Loading title…\" />\n </div>\n );\n }\n\n if (isError || !title) {\n return (\n <div className=\"flex h-full flex-col bg-bg-canvas\">\n <ErrorState\n title=\"Title not found\"\n message=\"This title may have been unpublished, or you may not have access to it.\"\n onRetry={() => void refetch()}\n />\n </div>\n );\n }\n\n const entitled = entitlement.data?.entitled ?? false;\n const reviewItems = reviews.data?.items ?? [];\n const avgScore =\n reviewItems.length > 0\n ? reviewItems.reduce((sum, r) => sum + r.score, 0) / reviewItems.length\n : null;\n\n const credits = [...title.credits].sort((a, b) => a.billingOrder - b.billingOrder);\n const episodes = [...title.episodes].sort((a, b) => a.number - b.number);\n const seasons = [...title.seasons].sort((a, b) => a.number - b.number);\n const isSeries = title.type === 'SERIES';\n\n // Group episodes by season (falling back to a single \"ungrouped\" bucket).\n const episodesBySeason = seasons.map((season) => ({\n season,\n items: episodes.filter((ep) => ep.seasonId === season.id),\n }));\n const ungroupedEpisodes = episodes.filter(\n (ep) => !ep.seasonId || !seasons.some((s) => s.id === ep.seasonId)\n );\n\n // Compact meta chips under the title.\n const metaItems: Array<{ icon: typeof Calendar; label: string }> = [];\n if (title.releaseYear) metaItems.push({ icon: Calendar, label: String(title.releaseYear) });\n if (title.runtimeMinutes)\n metaItems.push({ icon: Clock, label: formatRuntime(title.runtimeMinutes) });\n if (title.certification) metaItems.push({ icon: ShieldCheck, label: title.certification });\n if (title.originalLanguage)\n metaItems.push({ icon: Languages, label: title.originalLanguage.toUpperCase() });\n if (title.originCountry) metaItems.push({ icon: Globe, label: title.originCountry });\n\n const playEpisode = (episodeId: string) => navigateTo(`watch/${title.id}/episode/${episodeId}`);\n\n return (\n <div className=\"flex h-full flex-col overflow-y-auto bg-bg-canvas\">\n {/* ── Cinematic backdrop hero ─────────────────────────────────────────── */}\n <section className=\"relative isolate\">\n {/* Backdrop image / fallback */}\n <div className=\"absolute inset-0 -z-10 overflow-hidden bg-bg-sunken\">\n {backdrop.data ? (\n <img\n src={backdrop.data}\n alt=\"\"\n aria-hidden=\"true\"\n className=\"h-full w-full object-cover object-center\"\n />\n ) : (\n <div className=\"flex h-full w-full items-center justify-center bg-bg-sunken\">\n <Clapperboard className=\"h-16 w-16 text-text-muted\" aria-hidden=\"true\" />\n </div>\n )}\n {/* Legibility scrims (bottom + left), token-only gradients */}\n <div className=\"absolute inset-0 bg-gradient-to-t from-bg-canvas via-bg-canvas/70 to-bg-canvas/20\" />\n <div className=\"absolute inset-0 bg-gradient-to-r from-bg-canvas/90 via-bg-canvas/40 to-transparent\" />\n </div>\n\n {/* Back control */}\n <div className=\"px-4 pt-4 sm:px-8\">\n <button\n type=\"button\"\n onClick={goBack}\n className=\"inline-flex items-center gap-2 rounded-full border border-border-seam bg-bg-surface/60 px-3 py-1.5 text-sm font-medium text-text-primary backdrop-blur transition-colors hover:bg-bg-surface focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <ArrowLeft className=\"h-4 w-4\" />\n Browse\n </button>\n </div>\n\n {/* Hero content */}\n <div className=\"mx-auto flex max-w-6xl flex-col gap-6 px-4 pb-10 pt-24 sm:px-8 sm:pt-36 md:flex-row md:items-end lg:pt-48\">\n {/* Poster */}\n <div className=\"w-32 shrink-0 overflow-hidden rounded-xl border border-border-seam bg-bg-sunken shadow-2xl sm:w-40 md:w-48\">\n <div className=\"aspect-[2/3] w-full\">\n {poster.data ? (\n <img\n src={poster.data}\n alt={`${title.title} poster`}\n className=\"h-full w-full object-cover\"\n />\n ) : (\n <div className=\"flex h-full w-full items-center justify-center\">\n <Film className=\"h-10 w-10 text-text-muted\" aria-hidden=\"true\" />\n </div>\n )}\n </div>\n </div>\n\n {/* Title + meta + CTAs */}\n <div className=\"min-w-0 flex-1\">\n <p className=\"mb-2 text-xs font-semibold uppercase tracking-widest text-text-secondary\">\n {formatEnumLabel(title.type)}\n {title.studio ? ` · ${title.studio.name}` : ''}\n </p>\n <h1 className=\"text-3xl font-bold leading-tight tracking-tight text-text-primary sm:text-4xl lg:text-5xl\">\n {title.title}\n </h1>\n\n {metaItems.length > 0 && (\n <div className=\"mt-3 flex flex-wrap items-center gap-x-4 gap-y-2 text-sm text-text-secondary\">\n {metaItems.map((m) => {\n const Icon = m.icon;\n return (\n <span key={m.label} className=\"inline-flex items-center gap-1.5\">\n <Icon className=\"h-4 w-4\" aria-hidden=\"true\" />\n {m.label}\n </span>\n );\n })}\n {avgScore !== null && (\n <span className=\"inline-flex items-center gap-1.5\">\n <Star\n className=\"h-4 w-4 fill-status-warning-text text-status-warning-text\"\n aria-hidden=\"true\"\n />\n {(avgScore / 2).toFixed(1)}\n </span>\n )}\n </div>\n )}\n\n {title.genres.length > 0 && (\n <div className=\"mt-3 flex flex-wrap gap-1.5\">\n {title.genres.map((g) => (\n <span\n key={g}\n className=\"rounded-full border border-border-seam bg-bg-surface/60 px-2.5 py-0.5 text-xs text-text-primary backdrop-blur\"\n >\n {g}\n </span>\n ))}\n </div>\n )}\n\n {title.aiDisclosure && (\n <p className=\"mt-3 inline-flex items-center gap-1.5 text-xs text-text-secondary\">\n <Sparkles className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n Contains AI-generated or AI-altered content\n </p>\n )}\n\n {/* Call to action row */}\n <div className=\"mt-6 flex flex-wrap items-center gap-3\">\n {entitlement.isLoading ? (\n <span className=\"inline-flex items-center gap-2 rounded-xl bg-bg-sunken px-5 py-2.5 text-sm font-medium text-text-secondary\">\n <span className=\"size-4 animate-spin rounded-full border-2 border-border-strong border-t-transparent\" />\n Checking access…\n </span>\n ) : entitled ? (\n <button\n type=\"button\"\n onClick={() => navigateTo(`watch/${title.id}`)}\n className=\"inline-flex items-center gap-2 rounded-xl bg-action-primary-bg px-6 py-2.5 text-sm font-semibold text-action-primary-text shadow-lg transition-opacity hover:opacity-90 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <Play className=\"h-5 w-5 fill-current\" />\n {resumeSeconds > 0 ? `Resume from ${formatTimecode(resumeSeconds)}` : 'Play'}\n </button>\n ) : (\n <button\n type=\"button\"\n onClick={() => navigateTo(`checkout/${title.id}`)}\n className=\"inline-flex items-center gap-2 rounded-xl bg-action-primary-bg px-6 py-2.5 text-sm font-semibold text-action-primary-text shadow-lg transition-opacity hover:opacity-90 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <Ticket className=\"h-5 w-5\" />\n Where to watch\n </button>\n )}\n\n {title.trailerFileId && (\n <button\n type=\"button\"\n onClick={() => setShowTrailer(true)}\n className=\"inline-flex items-center gap-2 rounded-xl border border-border-seam bg-bg-surface/60 px-5 py-2.5 text-sm font-semibold text-text-primary backdrop-blur transition-colors hover:bg-bg-surface focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <Film className=\"h-5 w-5\" />\n Watch Trailer\n </button>\n )}\n </div>\n\n {/* Access hint when not entitled */}\n {!entitlement.isLoading && !entitled && (\n <p className=\"mt-3 inline-flex items-center gap-1.5 text-xs text-text-secondary\">\n <Lock className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n You don&apos;t have access yet — see your viewing options.\n </p>\n )}\n {entitled && entitlement.data && 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=\"inline-flex items-center gap-1 rounded-full bg-status-success-bg-subtle px-2.5 py-0.5 text-xs text-status-success-text\"\n >\n <BadgeCheck className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n {formatEnumLabel(mode)}\n </span>\n ))}\n </div>\n )}\n </div>\n </div>\n </section>\n\n {/* ── Body ────────────────────────────────────────────────────────────── */}\n <div className=\"mx-auto w-full max-w-6xl space-y-10 px-4 pb-16 sm:px-8\">\n {/* Synopsis */}\n {title.synopsis && (\n <section>\n <h2 className=\"mb-3 text-lg font-semibold text-text-primary\">Synopsis</h2>\n <p className=\"max-w-3xl text-sm leading-relaxed text-text-secondary sm:text-base\">\n {title.synopsis}\n </p>\n </section>\n )}\n\n {/* Cast & Crew */}\n {credits.length > 0 && (\n <section>\n <h2 className=\"mb-4 flex items-center gap-2 text-lg font-semibold text-text-primary\">\n <Users className=\"h-5 w-5\" aria-hidden=\"true\" />\n Cast &amp; Crew\n </h2>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3\">\n {credits.map((credit) => (\n <CreditCard key={credit.id} credit={credit} />\n ))}\n </div>\n </section>\n )}\n\n {/* Seasons & Episodes */}\n {episodes.length > 0 && (\n <section>\n <h2 className=\"mb-4 flex items-center gap-2 text-lg font-semibold text-text-primary\">\n <Layers className=\"h-5 w-5\" aria-hidden=\"true\" />\n {isSeries ? 'Seasons & Episodes' : 'Episodes'}\n </h2>\n <div className=\"space-y-6\">\n {episodesBySeason\n .filter((group) => group.items.length > 0)\n .map((group) => (\n <div key={group.season.id}>\n <h3 className=\"mb-2 text-sm font-semibold text-text-secondary\">\n {group.season.name\n ? `${group.season.number}. ${group.season.name}`\n : `Season ${group.season.number}`}\n </h3>\n <div className=\"space-y-2\">\n {group.items.map((ep) => (\n <EpisodeRow key={ep.id} episode={ep} onPlay={() => playEpisode(ep.id)} />\n ))}\n </div>\n </div>\n ))}\n {ungroupedEpisodes.length > 0 && (\n <div>\n {episodesBySeason.some((g) => g.items.length > 0) && (\n <h3 className=\"mb-2 text-sm font-semibold text-text-secondary\">\n Other episodes\n </h3>\n )}\n <div className=\"space-y-2\">\n {ungroupedEpisodes.map((ep) => (\n <EpisodeRow key={ep.id} episode={ep} onPlay={() => playEpisode(ep.id)} />\n ))}\n </div>\n </div>\n )}\n </div>\n </section>\n )}\n\n {/* Reviews rail */}\n <section>\n <div className=\"mb-4 flex items-center justify-between gap-3\">\n <h2 className=\"flex items-center gap-2 text-lg font-semibold text-text-primary\">\n <Star className=\"h-5 w-5\" aria-hidden=\"true\" />\n Reviews\n </h2>\n {avgScore !== null && <RatingDisplay value={avgScore / 2} count={reviewItems.length} />}\n </div>\n\n {reviews.isLoading ? (\n <LoadingState label=\"Loading reviews…\" />\n ) : reviews.isError ? (\n <ErrorState title=\"Couldn't load reviews\" onRetry={() => void reviews.refetch()} />\n ) : reviewItems.length === 0 ? (\n <EmptyState\n title=\"No reviews yet\"\n description=\"Be the first to share what you thought once you've watched.\"\n illustration=\"empty-data\"\n />\n ) : (\n <div\n className={cn(\n 'flex snap-x snap-mandatory gap-4 overflow-x-auto pb-2',\n '[scrollbar-width:thin]'\n )}\n >\n {reviewItems.map((review) => (\n <GlassCard\n key={review.id}\n treatment=\"metric\"\n className=\"w-72 shrink-0 snap-start border-border-seam p-4\"\n >\n <div className=\"mb-2 flex items-center justify-between gap-2\">\n <span className=\"inline-flex items-center gap-1 text-sm font-semibold text-text-primary\">\n <Star\n className=\"h-4 w-4 fill-status-warning-text text-status-warning-text\"\n aria-hidden=\"true\"\n />\n {review.score}/10\n </span>\n {review.verifiedWatch && (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-status-success-bg-subtle px-2 py-0.5 text-[10px] font-medium text-status-success-text\">\n <BadgeCheck className=\"h-3 w-3\" aria-hidden=\"true\" />\n Verified\n </span>\n )}\n </div>\n {review.hasSpoilers && (\n <span className=\"mb-1.5 inline-block rounded-full bg-status-warning-bg-subtle px-2 py-0.5 text-[10px] font-medium text-status-warning-text\">\n Spoilers\n </span>\n )}\n {review.body ? (\n <p className=\"line-clamp-5 text-sm leading-relaxed text-text-secondary\">\n {review.body}\n </p>\n ) : (\n <p className=\"text-sm italic text-text-muted\">No written review.</p>\n )}\n {review.helpfulCount > 0 && (\n <p className=\"mt-3 text-xs text-text-secondary\">\n {review.helpfulCount} found this helpful\n </p>\n )}\n </GlassCard>\n ))}\n </div>\n )}\n </section>\n\n {/* Nudge back into the catalog */}\n <div>\n <button\n type=\"button\"\n onClick={goBack}\n className=\"inline-flex items-center gap-1.5 text-sm font-medium text-text-secondary transition-colors hover:text-text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] rounded\"\n >\n Explore more titles\n <ChevronRight className=\"h-4 w-4\" />\n </button>\n </div>\n </div>\n\n {/* Trailer modal */}\n {showTrailer && (\n <TrailerModal\n url={trailer.data ?? null}\n isLoading={trailer.isLoading}\n onClose={() => setShowTrailer(false)}\n />\n )}\n </div>\n );\n};\n"],"mappings":"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"}