{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "video",
  "type": "registry:block",
  "title": "Video",
  "description": "Video playback with controls and poster.",
  "dependencies": [
    "lucide-react",
    "zod"
  ],
  "registryDependencies": [
    "button"
  ],
  "files": [
    {
      "path": "components/tool-ui/shared/contract.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/shared/contract.ts",
      "content": "import { z } from \"zod\";\nimport { parseWithSchema, safeParseWithSchema } from \"./parse\";\n\nexport interface ToolUiContract<T> {\n  schema: z.ZodType<T>;\n  parse: (input: unknown) => T;\n  safeParse: (input: unknown) => T | null;\n}\n\nexport function defineToolUiContract<T>(\n  componentName: string,\n  schema: z.ZodType<T>,\n): ToolUiContract<T> {\n  return {\n    schema,\n    parse: (input: unknown) => parseWithSchema(schema, input, componentName),\n    safeParse: (input: unknown) => safeParseWithSchema(schema, input),\n  };\n}\n"
    },
    {
      "path": "components/tool-ui/shared/media/aspect-ratio.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/shared/media/aspect-ratio.ts",
      "content": "import { z } from \"zod\";\n\nexport const AspectRatioSchema = z\n  .enum([\"auto\", \"1:1\", \"4:3\", \"16:9\", \"9:16\"])\n  .default(\"auto\");\n\nexport type AspectRatio = z.infer<typeof AspectRatioSchema>;\n\nexport const MediaFitSchema = z.enum([\"cover\", \"contain\"]).default(\"cover\");\n\nexport type MediaFit = z.infer<typeof MediaFitSchema>;\n\nexport const RATIO_CLASS_MAP: Record<AspectRatio, string> = {\n  auto: \"\",\n  \"1:1\": \"aspect-square\",\n  \"4:3\": \"aspect-[4/3]\",\n  \"16:9\": \"aspect-video\",\n  \"9:16\": \"aspect-[9/16]\",\n};\n\nexport function getRatioClass(ratio: AspectRatio): string {\n  return RATIO_CLASS_MAP[ratio];\n}\n\nexport function getFitClass(fit: MediaFit): string {\n  return fit === \"cover\" ? \"object-cover\" : \"object-contain\";\n}\n"
    },
    {
      "path": "components/tool-ui/shared/media/format-utils.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/shared/media/format-utils.ts",
      "content": "/**\n * Format duration in milliseconds to human-readable string.\n * @example formatDuration(128000) => \"2:08\"\n * @example formatDuration(3661000) => \"1:01:01\"\n */\nexport function formatDuration(durationMs: number): string {\n  const totalSeconds = Math.round(durationMs / 1000);\n  const hours = Math.floor(totalSeconds / 3600);\n  const minutes = Math.floor((totalSeconds % 3600) / 60);\n  const seconds = totalSeconds % 60;\n\n  if (hours > 0) {\n    return `${hours}:${minutes.toString().padStart(2, \"0\")}:${seconds\n      .toString()\n      .padStart(2, \"0\")}`;\n  }\n  return `${minutes}:${seconds.toString().padStart(2, \"0\")}`;\n}\n\n/**\n * Format file size in bytes to human-readable string.\n * @example formatFileSize(1024) => \"1 KB\"\n * @example formatFileSize(1536000) => \"1.5 MB\"\n */\nexport function formatFileSize(bytes: number): string {\n  if (bytes < 1024) return `${bytes} B`;\n  const units = [\"KB\", \"MB\", \"GB\"];\n  let size = bytes / 1024;\n  let unit = 0;\n  while (size >= 1024 && unit < units.length - 1) {\n    size /= 1024;\n    unit += 1;\n  }\n  return `${size.toFixed(size >= 10 ? 0 : 1)} ${units[unit]}`;\n}\n"
    },
    {
      "path": "components/tool-ui/shared/media/index.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/shared/media/index.ts",
      "content": "export {\n  AspectRatioSchema,\n  MediaFitSchema,\n  RATIO_CLASS_MAP,\n  getRatioClass,\n  getFitClass,\n  type AspectRatio,\n  type MediaFit,\n} from \"./aspect-ratio\";\n\nexport { OVERLAY_GRADIENT } from \"./overlay-gradient\";\n\nexport { formatDuration, formatFileSize } from \"./format-utils\";\n\nexport { sanitizeHref } from \"./sanitize-href\";\nexport {\n  resolveSafeNavigationHref,\n  openSafeNavigationHref,\n} from \"./safe-navigation\";\n"
    },
    {
      "path": "components/tool-ui/shared/media/overlay-gradient.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/shared/media/overlay-gradient.ts",
      "content": "/**\n * Eased gradient for hover overlays on media elements.\n * Creates a smooth fade from opaque black at top to transparent.\n *\n * @see https://larsenwork.com/easing-gradients/\n */\nexport const OVERLAY_GRADIENT = `linear-gradient(\n  to bottom,\n  hsl(0, 0%, 0%) 0%,\n  hsla(0, 0%, 0%, 0.987) 8.3%,\n  hsla(0, 0%, 0%, 0.951) 16.6%,\n  hsla(0, 0%, 0%, 0.896) 24.6%,\n  hsla(0, 0%, 0%, 0.825) 32.5%,\n  hsla(0, 0%, 0%, 0.741) 40.1%,\n  hsla(0, 0%, 0%, 0.648) 47.6%,\n  hsla(0, 0%, 0%, 0.55) 54.8%,\n  hsla(0, 0%, 0%, 0.45) 61.7%,\n  hsla(0, 0%, 0%, 0.352) 68.3%,\n  hsla(0, 0%, 0%, 0.259) 74.5%,\n  hsla(0, 0%, 0%, 0.175) 80.4%,\n  hsla(0, 0%, 0%, 0.104) 86%,\n  hsla(0, 0%, 0%, 0.049) 91.1%,\n  hsla(0, 0%, 0%, 0.013) 95.8%,\n  hsla(0, 0%, 0%, 0) 100%\n)` as const;\n"
    },
    {
      "path": "components/tool-ui/shared/media/safe-navigation.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/shared/media/safe-navigation.ts",
      "content": "import { sanitizeHref } from \"./sanitize-href\";\n\nexport function resolveSafeNavigationHref(\n  ...candidates: Array<string | null | undefined>\n): string | undefined {\n  for (const candidate of candidates) {\n    const safeHref = sanitizeHref(candidate ?? undefined);\n    if (safeHref) {\n      return safeHref;\n    }\n  }\n\n  return undefined;\n}\n\nexport function openSafeNavigationHref(href: string | undefined): boolean {\n  if (!href || typeof window === \"undefined\") {\n    return false;\n  }\n\n  window.open(href, \"_blank\", \"noopener,noreferrer\");\n  return true;\n}\n"
    },
    {
      "path": "components/tool-ui/shared/media/sanitize-href.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/shared/media/sanitize-href.ts",
      "content": "/**\n * Sanitize a URL to ensure it's safe for use in href attributes.\n * Allows:\n * - Absolute http(s) URLs\n * - Relative URLs (/path, ./path, ../path, ?query, #hash)\n *\n * @returns The sanitized URL string, or undefined if invalid/unsafe\n */\nexport function sanitizeHref(href?: string): string | undefined {\n  if (!href) return undefined;\n  const candidate = href.trim();\n  if (!candidate) return undefined;\n\n  if (\n    candidate.startsWith(\"/\") ||\n    candidate.startsWith(\"./\") ||\n    candidate.startsWith(\"../\") ||\n    candidate.startsWith(\"?\") ||\n    candidate.startsWith(\"#\")\n  ) {\n    if (candidate.startsWith(\"//\")) return undefined;\n    // eslint-disable-next-line no-control-regex -- intentionally matching control characters\n    if (/[\\u0000-\\u001F\\u007F]/.test(candidate)) return undefined;\n    return candidate;\n  }\n\n  try {\n    const url = new URL(candidate);\n    if (url.protocol === \"http:\" || url.protocol === \"https:\") {\n      return url.toString();\n    }\n  } catch {\n    return undefined;\n  }\n  return undefined;\n}\n"
    },
    {
      "path": "components/tool-ui/shared/parse.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/shared/parse.ts",
      "content": "import { z } from \"zod\";\n\nfunction formatZodPath(path: Array<string | number | symbol>): string {\n  if (path.length === 0) return \"root\";\n  return path\n    .map((segment) =>\n      typeof segment === \"number\" ? `[${segment}]` : String(segment),\n    )\n    .join(\".\");\n}\n\n/**\n * Format Zod errors into a compact `path: message` string.\n */\nexport function formatZodError(error: z.ZodError): string {\n  const parts = error.issues.map((issue) => {\n    const path = formatZodPath(issue.path);\n    return `${path}: ${issue.message}`;\n  });\n\n  return Array.from(new Set(parts)).join(\"; \");\n}\n\n/**\n * Parse unknown input and throw a readable error.\n */\nexport function parseWithSchema<T>(\n  schema: z.ZodType<T>,\n  input: unknown,\n  name: string,\n): T {\n  const res = schema.safeParse(input);\n  if (!res.success) {\n    throw new Error(`Invalid ${name} payload: ${formatZodError(res.error)}`);\n  }\n  return res.data;\n}\n\n/**\n * Parse unknown input, returning `null` instead of throwing on failure.\n *\n * Use this in assistant-ui `render` functions where `args` stream in\n * incrementally and may be incomplete until the tool call finishes.\n */\nexport function safeParseWithSchema<T>(\n  schema: z.ZodType<T>,\n  input: unknown,\n): T | null {\n  const res = schema.safeParse(input);\n  return res.success ? res.data : null;\n}\n"
    },
    {
      "path": "components/tool-ui/shared/schema.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/shared/schema.ts",
      "content": "import { z } from \"zod\";\nimport type { ReactNode } from \"react\";\n\n/**\n * Tool UI conventions:\n * - Serializable schemas are JSON-safe (no callbacks/ReactNode/`className`).\n * - Schema: `SerializableXSchema`\n * - Parser: `parseSerializableX(input: unknown)` (throws on invalid)\n * - Safe parser: `safeParseSerializableX(input: unknown)` (returns `null` on invalid)\n * - Actions: `LocalActions` for non-receipt actions and `DecisionActions` for consequential actions\n * - Root attrs: `data-tool-ui-id` + `data-slot`\n */\n\n/**\n * Schema for tool UI identity.\n *\n * Every tool UI should have a unique identifier that:\n * - Is stable across re-renders\n * - Is meaningful (not auto-generated)\n * - Is unique within the conversation\n *\n * Format recommendation: `{component-type}-{semantic-identifier}`\n * Examples: \"data-table-expenses-q3\", \"option-list-deploy-target\"\n */\nexport const ToolUIIdSchema = z.string().min(1);\n\nexport type ToolUIId = z.infer<typeof ToolUIIdSchema>;\n\n/**\n * Primary role of a Tool UI surface in a chat context.\n */\nexport const ToolUIRoleSchema = z.enum([\n  \"information\",\n  \"decision\",\n  \"control\",\n  \"state\",\n  \"composite\",\n]);\n\nexport type ToolUIRole = z.infer<typeof ToolUIRoleSchema>;\n\nexport const ToolUIReceiptOutcomeSchema = z.enum([\n  \"success\",\n  \"partial\",\n  \"failed\",\n  \"cancelled\",\n]);\n\nexport type ToolUIReceiptOutcome = z.infer<typeof ToolUIReceiptOutcomeSchema>;\n\n/**\n * Optional receipt metadata: a durable summary of an outcome.\n */\nexport const ToolUIReceiptSchema = z.object({\n  outcome: ToolUIReceiptOutcomeSchema,\n  summary: z.string().min(1),\n  identifiers: z.record(z.string(), z.string()).optional(),\n  at: z.string().datetime(),\n});\n\nexport type ToolUIReceipt = z.infer<typeof ToolUIReceiptSchema>;\n\n/**\n * Base schema for Tool UI payloads (id + optional role/receipt).\n */\nexport const ToolUISurfaceSchema = z.object({\n  id: ToolUIIdSchema,\n  role: ToolUIRoleSchema.optional(),\n  receipt: ToolUIReceiptSchema.optional(),\n});\n\nexport type ToolUISurface = z.infer<typeof ToolUISurfaceSchema>;\n\nexport const ActionSchema = z.object({\n  id: z.string().min(1),\n  label: z.string().min(1),\n  /**\n   * Canonical narration the assistant can use after this action is taken.\n   *\n   * Example: \"I exported the table as CSV.\" / \"I opened the link in a new tab.\"\n   */\n  sentence: z.string().optional(),\n  confirmLabel: z.string().optional(),\n  variant: z\n    .enum([\"default\", \"destructive\", \"secondary\", \"ghost\", \"outline\"])\n    .optional(),\n  icon: z.custom<ReactNode>().optional(),\n  loading: z.boolean().optional(),\n  disabled: z.boolean().optional(),\n  shortcut: z.string().optional(),\n});\n\nexport type Action = z.infer<typeof ActionSchema>;\nexport type LocalAction = Action;\nexport type DecisionAction = Action;\n\nexport const DecisionResultSchema = z.object({\n  kind: z.literal(\"decision\"),\n  version: z.literal(1),\n  decisionId: z.string().min(1),\n  actionId: z.string().min(1),\n  actionLabel: z.string().min(1),\n  at: z.string().datetime(),\n  payload: z.record(z.string(), z.unknown()).optional(),\n});\n\nexport type DecisionResult<\n  TPayload extends Record<string, unknown> = Record<string, unknown>,\n> = Omit<z.infer<typeof DecisionResultSchema>, \"payload\"> & {\n  payload?: TPayload;\n};\n\nexport function createDecisionResult<\n  TPayload extends Record<string, unknown> = Record<string, unknown>,\n>(args: {\n  decisionId: string;\n  action: { id: string; label: string };\n  payload?: TPayload;\n}): DecisionResult<TPayload> {\n  return {\n    kind: \"decision\",\n    version: 1,\n    decisionId: args.decisionId,\n    actionId: args.action.id,\n    actionLabel: args.action.label,\n    at: new Date().toISOString(),\n    payload: args.payload,\n  };\n}\n\nexport const ActionButtonsPropsSchema = z.object({\n  actions: z.array(ActionSchema).min(1),\n  align: z.enum([\"left\", \"center\", \"right\"]).optional(),\n  confirmTimeout: z.number().positive().optional(),\n  className: z.string().optional(),\n});\n\nexport const SerializableActionSchema = ActionSchema.omit({ icon: true });\nexport const SerializableActionsSchema = ActionButtonsPropsSchema.extend({\n  actions: z.array(SerializableActionSchema),\n}).omit({ className: true });\n\nexport interface ActionsConfig {\n  items: Action[];\n  align?: \"left\" | \"center\" | \"right\";\n  confirmTimeout?: number;\n}\n\nexport const SerializableActionsConfigSchema = z.object({\n  items: z.array(SerializableActionSchema).min(1),\n  align: z.enum([\"left\", \"center\", \"right\"]).optional(),\n  confirmTimeout: z.number().positive().optional(),\n});\n\nexport type SerializableActionsConfig = z.infer<\n  typeof SerializableActionsConfigSchema\n>;\n\nexport type SerializableAction = z.infer<typeof SerializableActionSchema>;\n"
    },
    {
      "path": "components/tool-ui/video/_adapter.tsx",
      "type": "registry:component",
      "target": "components/tool-ui/video/_adapter.tsx",
      "content": "/**\n * Adapter: UI and utility re-exports for copy-standalone portability.\n */\n\"use client\";\n\nexport { cn } from \"@/lib/utils\";\nexport { Button } from \"@/components/ui/button\";\n"
    },
    {
      "path": "components/tool-ui/video/context.tsx",
      "type": "registry:component",
      "target": "components/tool-ui/video/context.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nexport interface VideoPlaybackState {\n  playing: boolean;\n  muted: boolean;\n}\n\nexport interface VideoContextValue {\n  state: VideoPlaybackState;\n  setState: (patch: Partial<VideoPlaybackState>) => void;\n  videoElement: HTMLVideoElement | null;\n  setVideoElement: (node: HTMLVideoElement | null) => void;\n}\n\nconst VideoContext = React.createContext<VideoContextValue | null>(null);\n\nexport function useVideo() {\n  const ctx = React.use(VideoContext);\n  if (!ctx) {\n    throw new Error(\"useVideo must be used within a <VideoProvider />\");\n  }\n  return ctx;\n}\n\nexport interface VideoProviderProps {\n  children: React.ReactNode;\n  defaultState?: Partial<VideoPlaybackState>;\n}\n\nexport function VideoProvider({ children, defaultState }: VideoProviderProps) {\n  const [state, setStateInternal] = React.useState<VideoPlaybackState>({\n    playing: defaultState?.playing ?? false,\n    muted: defaultState?.muted ?? true,\n  });\n\n  const [videoElement, setVideoElement] =\n    React.useState<HTMLVideoElement | null>(null);\n\n  const setState = React.useCallback((patch: Partial<VideoPlaybackState>) => {\n    setStateInternal((prev) => ({ ...prev, ...patch }));\n  }, []);\n\n  const value = React.useMemo(\n    () => ({ state, setState, videoElement, setVideoElement }),\n    [state, setState, videoElement],\n  );\n\n  return (\n    <VideoContext.Provider value={value}>{children}</VideoContext.Provider>\n  );\n}\n"
    },
    {
      "path": "components/tool-ui/video/index.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/video/index.ts",
      "content": "export { Video } from \"./video\";\nexport type { VideoProps } from \"./video\";\nexport type { SerializableVideo, Source } from \"./schema\";\n"
    },
    {
      "path": "components/tool-ui/video/README.md",
      "type": "registry:file",
      "target": "components/tool-ui/video/README.md",
      "content": "# Video\n\nImplementation for the \"video\" Tool UI surface.\n\n## Files\n\n- public exports: components/tool-ui/video/index.ts\n- serializable schema + parse helpers: components/tool-ui/video/schema.ts\n\n## Companion assets\n\n- Docs page: app/docs/video/content.mdx\n- Preset payload: lib/presets/video.ts\n\n## Quick check\n\nRun this after edits:\n\npnpm test\n"
    },
    {
      "path": "components/tool-ui/video/schema.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/video/schema.ts",
      "content": "import { z } from \"zod\";\nimport { defineToolUiContract } from \"../shared/contract\";\nimport {\n  ToolUIIdSchema,\n  ToolUIReceiptSchema,\n  ToolUIRoleSchema,\n} from \"../shared/schema\";\n\nimport { AspectRatioSchema, MediaFitSchema } from \"../shared/media\";\n\nexport const SourceSchema = z.object({\n  label: z.string(),\n  iconUrl: z.url().optional(),\n  url: z.url().optional(),\n});\n\nexport type Source = z.infer<typeof SourceSchema>;\n\nexport const SerializableVideoSchema = z.object({\n  id: ToolUIIdSchema,\n  role: ToolUIRoleSchema.optional(),\n  receipt: ToolUIReceiptSchema.optional(),\n  assetId: z.string(),\n  src: z.url(),\n  poster: z.url().optional(),\n  title: z.string().optional(),\n  description: z.string().optional(),\n  href: z.url().optional(),\n  domain: z.string().optional(),\n  durationMs: z.number().int().positive().optional(),\n  ratio: AspectRatioSchema.optional(),\n  fit: MediaFitSchema.optional(),\n  createdAt: z.string().datetime().optional(),\n  locale: z.string().optional(),\n  source: SourceSchema.optional(),\n});\n\nexport type SerializableVideo = z.infer<typeof SerializableVideoSchema>;\n\nconst SerializableVideoSchemaContract = defineToolUiContract(\n  \"Video\",\n  SerializableVideoSchema,\n);\n\nexport const parseSerializableVideo: (input: unknown) => SerializableVideo =\n  SerializableVideoSchemaContract.parse;\n\nexport const safeParseSerializableVideo: (\n  input: unknown,\n) => SerializableVideo | null = SerializableVideoSchemaContract.safeParse;\n"
    },
    {
      "path": "components/tool-ui/video/video-helpers.ts",
      "type": "registry:lib",
      "target": "components/tool-ui/video/video-helpers.ts",
      "content": "import type { SerializableVideo } from \"./schema\";\nimport { resolveSafeNavigationHref, sanitizeHref } from \"../shared/media\";\n\nexport type VideoMediaEvent = \"mute\" | \"unmute\";\n\nexport interface ResolvedVideoNavigation {\n  sanitizedHref: string | undefined;\n  sanitizedSourceUrl: string | undefined;\n  primaryHref: string | undefined;\n}\n\nexport function getMuteMediaEvent(\n  previousMuted: boolean,\n  nextMuted: boolean,\n): VideoMediaEvent | null {\n  if (previousMuted === nextMuted) {\n    return null;\n  }\n\n  return nextMuted ? \"mute\" : \"unmute\";\n}\n\nexport function resolveVideoNavigation(\n  rawHref: string | undefined,\n  rawSourceUrl: string | undefined,\n): ResolvedVideoNavigation {\n  const sanitizedHref = sanitizeHref(rawHref);\n  const sanitizedSourceUrl = sanitizeHref(rawSourceUrl);\n\n  return {\n    sanitizedHref,\n    sanitizedSourceUrl,\n    primaryHref: resolveSafeNavigationHref(sanitizedHref, sanitizedSourceUrl),\n  };\n}\n\nexport function normalizeVideoDataForCallback(\n  video: SerializableVideo,\n  normalized: {\n    ratio: NonNullable<SerializableVideo[\"ratio\"]>;\n    fit: NonNullable<SerializableVideo[\"fit\"]>;\n    locale: string;\n    sanitizedHref: string | undefined;\n    sanitizedSourceUrl: string | undefined;\n  },\n): SerializableVideo {\n  return {\n    ...video,\n    ratio: normalized.ratio,\n    fit: normalized.fit,\n    href: normalized.sanitizedHref,\n    source: video.source\n      ? { ...video.source, url: normalized.sanitizedSourceUrl }\n      : undefined,\n    locale: normalized.locale,\n  };\n}\n"
    },
    {
      "path": "components/tool-ui/video/video.tsx",
      "type": "registry:component",
      "target": "components/tool-ui/video/video.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { ExternalLink, Play } from \"lucide-react\";\nimport { cn, Button } from \"./_adapter\";\n\nimport {\n  formatDuration,\n  getFitClass,\n  openSafeNavigationHref,\n  OVERLAY_GRADIENT,\n  RATIO_CLASS_MAP,\n} from \"../shared/media\";\nimport { VideoProvider, useVideo } from \"./context\";\nimport type { SerializableVideo } from \"./schema\";\nimport {\n  getMuteMediaEvent,\n  normalizeVideoDataForCallback,\n  resolveVideoNavigation,\n} from \"./video-helpers\";\n\nconst FALLBACK_LOCALE = \"en-US\";\n\nexport interface VideoProps extends SerializableVideo {\n  className?: string;\n  // Keep behavior flags intentionally minimal; prefer explicit variants over more booleans.\n  autoPlay?: boolean;\n  defaultMuted?: boolean;\n  onNavigate?: (href: string, video: SerializableVideo) => void;\n  onMediaEvent?: (type: \"play\" | \"pause\" | \"mute\" | \"unmute\") => void;\n}\n\nfunction VideoRoot(props: VideoProps) {\n  const { defaultMuted = true, ...rest } = props;\n\n  return (\n    <VideoProvider defaultState={{ muted: defaultMuted }}>\n      <VideoInner {...rest} />\n    </VideoProvider>\n  );\n}\n\nfunction VideoInner(props: Omit<VideoProps, \"defaultMuted\">) {\n  const {\n    className,\n    autoPlay = true,\n    onNavigate,\n    onMediaEvent,\n    ...serializable\n  } = props;\n\n  const {\n    id,\n    src,\n    poster,\n    title,\n    description,\n    href: rawHref,\n    domain,\n    durationMs,\n    ratio = \"16:9\",\n    fit = \"cover\",\n    createdAt,\n    source,\n    locale: providedLocale,\n  } = serializable;\n\n  const locale = providedLocale ?? FALLBACK_LOCALE;\n  const { sanitizedHref, sanitizedSourceUrl, primaryHref } =\n    resolveVideoNavigation(rawHref, source?.url);\n\n  const videoData: SerializableVideo = normalizeVideoDataForCallback(\n    serializable,\n    {\n      ratio,\n      fit,\n      locale,\n      sanitizedHref,\n      sanitizedSourceUrl,\n    },\n  );\n\n  const { state, setState, setVideoElement } = useVideo();\n  const videoRef = React.useRef<HTMLVideoElement | null>(null);\n  const previousMutedRef = React.useRef(state.muted);\n\n  React.useEffect(() => {\n    setVideoElement(videoRef.current);\n    return () => setVideoElement(null);\n  }, [setVideoElement]);\n\n  React.useEffect(() => {\n    const video = videoRef.current;\n    if (!video) return;\n    if (video.muted !== state.muted) {\n      video.muted = state.muted;\n    }\n  }, [state.muted]);\n\n  React.useEffect(() => {\n    const video = videoRef.current;\n    if (!video) return;\n    if (state.playing && video.paused) {\n      void video.play().catch(() => undefined);\n    } else if (!state.playing && !video.paused) {\n      video.pause();\n    }\n  }, [state.playing]);\n\n  const navigate = (targetHref: string) => {\n    if (onNavigate) {\n      onNavigate(targetHref, videoData);\n    } else {\n      openSafeNavigationHref(targetHref);\n    }\n  };\n\n  const handleWatch = (event: React.MouseEvent<HTMLButtonElement>) => {\n    event.preventDefault();\n    event.stopPropagation();\n    const video = videoRef.current;\n    if (!video) return;\n    if (video.paused) {\n      void video.play().catch(() => undefined);\n    } else {\n      video.pause();\n    }\n  };\n\n  const handleOpen = (event: React.MouseEvent<HTMLButtonElement>) => {\n    event.preventDefault();\n    event.stopPropagation();\n    if (!primaryHref) return;\n    navigate(primaryHref);\n  };\n\n  const sourceLabel = source?.label;\n  const metadataDomain = domain && domain !== sourceLabel ? domain : undefined;\n  const hasMetadata = Boolean(\n    description || sourceLabel || metadataDomain || durationMs || createdAt,\n  );\n  const hasOverlay = Boolean(title || primaryHref);\n\n  return (\n    <article\n      className={cn(\"relative w-full min-w-80 max-w-md\", className)}\n      lang={locale}\n      data-tool-ui-id={id}\n      data-slot=\"video\"\n    >\n      <div\n        className={cn(\n          \"group @container relative isolate flex w-full min-w-0 flex-col overflow-hidden rounded-xl\",\n          \"border border-border bg-card text-sm shadow-xs\",\n        )}\n      >\n        <div\n          className={cn(\n            \"group relative w-full overflow-hidden bg-black\",\n            ratio !== \"auto\" ? RATIO_CLASS_MAP[ratio] : \"aspect-video\",\n          )}\n        >\n          <video\n            ref={videoRef}\n            className={cn(\n              \"relative z-10 h-full w-full transition-transform duration-200 group-hover:scale-[1.01]\",\n              getFitClass(fit),\n              ratio !== \"auto\" && \"absolute inset-0 h-full w-full\",\n            )}\n            src={src}\n            poster={poster}\n            controls\n            playsInline\n            autoPlay={autoPlay}\n            preload=\"metadata\"\n            muted={state.muted}\n            onPlay={() => {\n              setState({ playing: true });\n              onMediaEvent?.(\"play\");\n            }}\n            onPause={() => {\n              setState({ playing: false });\n              onMediaEvent?.(\"pause\");\n            }}\n            onVolumeChange={(event) => {\n              const target = event.currentTarget;\n              setState({ muted: target.muted });\n              const mediaEvent = getMuteMediaEvent(\n                previousMutedRef.current,\n                target.muted,\n              );\n              previousMutedRef.current = target.muted;\n              if (mediaEvent) {\n                onMediaEvent?.(mediaEvent);\n              }\n            }}\n          />\n          {hasOverlay && (\n            <>\n              <div\n                className=\"pointer-events-none absolute inset-x-0 top-0 z-20 h-32 opacity-0 transition-opacity duration-200 group-hover:opacity-100 group-focus-within:opacity-100\"\n                style={{ backgroundImage: OVERLAY_GRADIENT }}\n              />\n              <div className=\"absolute inset-x-0 top-0 z-30 flex items-start justify-between gap-2 px-5 pt-4 opacity-0 transition-opacity duration-200 group-hover:opacity-100 group-focus-within:opacity-100\">\n                {title ? (\n                  <div className=\"line-clamp-2 max-w-[70%] font-semibold text-white drop-shadow-sm\">\n                    {title}\n                  </div>\n                ) : (\n                  <span className=\"sr-only\">Video controls</span>\n                )}\n                <div className=\"flex items-center gap-2\">\n                  {primaryHref && (\n                    <Button\n                      variant=\"secondary\"\n                      size=\"sm\"\n                      onClick={handleOpen}\n                      className=\"bg-black/55 text-white hover:bg-black/70\"\n                    >\n                      <ExternalLink\n                        className=\"mr-1 h-4 w-4\"\n                        aria-hidden=\"true\"\n                      />\n                      Open\n                    </Button>\n                  )}\n                  <Button\n                    variant=\"default\"\n                    size=\"sm\"\n                    onClick={handleWatch}\n                    className=\"shadow-sm\"\n                  >\n                    <Play className=\"mr-1 h-4 w-4\" aria-hidden=\"true\" />\n                    Watch\n                  </Button>\n                </div>\n              </div>\n            </>\n          )}\n        </div>\n\n        {hasMetadata && (\n          <div className=\"flex flex-col gap-1.5 px-4 py-3\">\n            {description && (\n              <p className=\"text-foreground line-clamp-2 text-sm leading-snug\">\n                {description}\n              </p>\n            )}\n            <div className=\"text-muted-foreground flex flex-wrap items-center gap-x-3 gap-y-1 text-xs\">\n              {sourceLabel && <span>{sourceLabel}</span>}\n              {metadataDomain && <span>{metadataDomain}</span>}\n              {typeof durationMs === \"number\" && (\n                <span>{formatDuration(durationMs)}</span>\n              )}\n              {createdAt && (\n                <time dateTime={createdAt}>\n                  {formatCreatedAt(createdAt, locale)}\n                </time>\n              )}\n            </div>\n          </div>\n        )}\n      </div>\n    </article>\n  );\n}\n\nfunction formatCreatedAt(createdAt: string, locale: string): string {\n  const date = new Date(createdAt);\n  if (Number.isNaN(date.getTime())) {\n    return createdAt;\n  }\n\n  return new Intl.DateTimeFormat(locale, { dateStyle: \"medium\" }).format(date);\n}\n\ntype VideoComponent = typeof VideoRoot & {\n  Root: typeof VideoRoot;\n};\n\nexport const Video = Object.assign(VideoRoot, {\n  Root: VideoRoot,\n}) as VideoComponent;\n"
    }
  ]
}
