@zezosoft/react-player 1.0.2 → 1.0.4

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/index.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import * as React from 'react';
2
- import React__default, { memo, useCallback, useEffect, useRef, useState, useMemo } from 'react';
2
+ import React__default, { memo, useCallback, useEffect, useRef, useState, useMemo, useLayoutEffect } from 'react';
3
3
  import { useShallow } from 'zustand/react/shallow';
4
4
  import { Settings as Settings$1, ChevronRight, Check, Loader, ArrowRight, SkipForward } from 'lucide-react';
5
5
  import { create } from 'zustand';
6
6
  import { IoVolumeMuteOutline, IoVolumeHighOutline } from 'react-icons/io5';
7
7
  import { IoMdClose } from 'react-icons/io';
8
+ import { createPortal } from 'react-dom';
8
9
  import screenfull from 'screenfull';
9
10
  import Hls from 'hls.js';
10
11
  import * as dashjs from 'dashjs';
@@ -36,7 +37,7 @@ function styleInject(css, ref) {
36
37
  }
37
38
  }
38
39
 
39
- var css_248z$4 = "/*! tailwindcss v4.1.17 | MIT License | https://tailwindcss.com */\n@layer properties;\n@layer theme, base, components, utilities;\n@layer theme {\n :root, :host {\n --font-sans: ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\",\n \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\",\n \"Courier New\", monospace;\n --color-red-400: oklch(70.4% 0.191 22.216);\n --color-red-500: oklch(63.7% 0.237 25.331);\n --color-red-600: oklch(57.7% 0.245 27.325);\n --color-red-700: oklch(50.5% 0.213 27.518);\n --color-green-500: oklch(72.3% 0.219 149.579);\n --color-sky-300: oklch(82.8% 0.111 230.318);\n --color-blue-500: oklch(62.3% 0.214 259.815);\n --color-purple-500: oklch(62.7% 0.265 303.9);\n --color-gray-200: oklch(92.8% 0.006 264.531);\n --color-gray-300: oklch(87.2% 0.01 258.338);\n --color-gray-400: oklch(70.7% 0.022 261.325);\n --color-gray-500: oklch(55.1% 0.027 264.364);\n --color-gray-600: oklch(44.6% 0.03 256.802);\n --color-gray-700: oklch(37.3% 0.034 259.733);\n --color-gray-900: oklch(21% 0.034 264.665);\n --color-black: #000;\n --color-white: #fff;\n --spacing: 0.25rem;\n --container-md: 28rem;\n --text-xs: 0.75rem;\n --text-xs--line-height: calc(1 / 0.75);\n --text-sm: 0.875rem;\n --text-sm--line-height: calc(1.25 / 0.875);\n --text-base: 1rem;\n --text-base--line-height: calc(1.5 / 1);\n --text-lg: 1.125rem;\n --text-lg--line-height: calc(1.75 / 1.125);\n --text-xl: 1.25rem;\n --text-xl--line-height: calc(1.75 / 1.25);\n --text-2xl: 1.5rem;\n --text-2xl--line-height: calc(2 / 1.5);\n --text-3xl: 1.875rem;\n --text-3xl--line-height: calc(2.25 / 1.875);\n --font-weight-normal: 400;\n --font-weight-medium: 500;\n --font-weight-semibold: 600;\n --font-weight-bold: 700;\n --tracking-wider: 0.05em;\n --radius-md: 0.375rem;\n --radius-lg: 0.5rem;\n --ease-out: cubic-bezier(0, 0, 0.2, 1);\n --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);\n --animate-spin: spin 1s linear infinite;\n --animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;\n --blur-sm: 8px;\n --blur-md: 12px;\n --default-transition-duration: 150ms;\n --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n --default-font-family: var(--font-sans);\n --default-mono-font-family: var(--font-mono);\n }\n}\n@layer base {\n *, ::after, ::before, ::backdrop, ::file-selector-button {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n border: 0 solid;\n }\n html, :host {\n line-height: 1.5;\n -webkit-text-size-adjust: 100%;\n tab-size: 4;\n font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\");\n font-feature-settings: var(--default-font-feature-settings, normal);\n font-variation-settings: var(--default-font-variation-settings, normal);\n -webkit-tap-highlight-color: transparent;\n }\n hr {\n height: 0;\n color: inherit;\n border-top-width: 1px;\n }\n abbr:where([title]) {\n -webkit-text-decoration: underline dotted;\n text-decoration: underline dotted;\n }\n h1, h2, h3, h4, h5, h6 {\n font-size: inherit;\n font-weight: inherit;\n }\n a {\n color: inherit;\n -webkit-text-decoration: inherit;\n text-decoration: inherit;\n }\n b, strong {\n font-weight: bolder;\n }\n code, kbd, samp, pre {\n font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace);\n font-feature-settings: var(--default-mono-font-feature-settings, normal);\n font-variation-settings: var(--default-mono-font-variation-settings, normal);\n font-size: 1em;\n }\n small {\n font-size: 80%;\n }\n sub, sup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n }\n sub {\n bottom: -0.25em;\n }\n sup {\n top: -0.5em;\n }\n table {\n text-indent: 0;\n border-color: inherit;\n border-collapse: collapse;\n }\n :-moz-focusring {\n outline: auto;\n }\n progress {\n vertical-align: baseline;\n }\n summary {\n display: list-item;\n }\n ol, ul, menu {\n list-style: none;\n }\n img, svg, video, canvas, audio, iframe, embed, object {\n display: block;\n vertical-align: middle;\n }\n img, video {\n max-width: 100%;\n height: auto;\n }\n button, input, select, optgroup, textarea, ::file-selector-button {\n font: inherit;\n font-feature-settings: inherit;\n font-variation-settings: inherit;\n letter-spacing: inherit;\n color: inherit;\n border-radius: 0;\n background-color: transparent;\n opacity: 1;\n }\n :where(select:is([multiple], [size])) optgroup {\n font-weight: bolder;\n }\n :where(select:is([multiple], [size])) optgroup option {\n padding-inline-start: 20px;\n }\n ::file-selector-button {\n margin-inline-end: 4px;\n }\n ::placeholder {\n opacity: 1;\n }\n @supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) {\n ::placeholder {\n color: currentcolor;\n @supports (color: color-mix(in lab, red, red)) {\n color: color-mix(in oklab, currentcolor 50%, transparent);\n }\n }\n }\n textarea {\n resize: vertical;\n }\n ::-webkit-search-decoration {\n -webkit-appearance: none;\n }\n ::-webkit-date-and-time-value {\n min-height: 1lh;\n text-align: inherit;\n }\n ::-webkit-datetime-edit {\n display: inline-flex;\n }\n ::-webkit-datetime-edit-fields-wrapper {\n padding: 0;\n }\n ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field {\n padding-block: 0;\n }\n ::-webkit-calendar-picker-indicator {\n line-height: 1;\n }\n :-moz-ui-invalid {\n box-shadow: none;\n }\n button, input:where([type=\"button\"], [type=\"reset\"], [type=\"submit\"]), ::file-selector-button {\n appearance: button;\n }\n ::-webkit-inner-spin-button, ::-webkit-outer-spin-button {\n height: auto;\n }\n [hidden]:where(:not([hidden=\"until-found\"])) {\n display: none !important;\n }\n}\n@layer utilities {\n .pointer-events-none {\n pointer-events: none;\n }\n .visible {\n visibility: visible;\n }\n .absolute {\n position: absolute;\n }\n .fixed {\n position: fixed;\n }\n .relative {\n position: relative;\n }\n .static {\n position: static;\n }\n .inset-0 {\n inset: calc(var(--spacing) * 0);\n }\n .-top-2 {\n top: calc(var(--spacing) * -2);\n }\n .top-0 {\n top: calc(var(--spacing) * 0);\n }\n .top-1 {\n top: calc(var(--spacing) * 1);\n }\n .top-1\\/2 {\n top: calc(1/2 * 100%);\n }\n .top-full {\n top: 100%;\n }\n .right-0 {\n right: calc(var(--spacing) * 0);\n }\n .right-32 {\n right: calc(var(--spacing) * 32);\n }\n .right-full {\n right: 100%;\n }\n .bottom-36 {\n bottom: calc(var(--spacing) * 36);\n }\n .bottom-full {\n bottom: 100%;\n }\n .left-0 {\n left: calc(var(--spacing) * 0);\n }\n .left-1 {\n left: calc(var(--spacing) * 1);\n }\n .left-1\\/2 {\n left: calc(1/2 * 100%);\n }\n .left-32 {\n left: calc(var(--spacing) * 32);\n }\n .left-full {\n left: 100%;\n }\n .z-40 {\n z-index: 40;\n }\n .z-50 {\n z-index: 50;\n }\n .z-\\[-1\\] {\n z-index: -1;\n }\n .mx-2 {\n margin-inline: calc(var(--spacing) * 2);\n }\n .mx-auto {\n margin-inline: auto;\n }\n .mt-1 {\n margin-top: calc(var(--spacing) * 1);\n }\n .mt-2 {\n margin-top: calc(var(--spacing) * 2);\n }\n .mt-4 {\n margin-top: calc(var(--spacing) * 4);\n }\n .-mr-1 {\n margin-right: calc(var(--spacing) * -1);\n }\n .mr-2 {\n margin-right: calc(var(--spacing) * 2);\n }\n .mb-1 {\n margin-bottom: calc(var(--spacing) * 1);\n }\n .mb-2 {\n margin-bottom: calc(var(--spacing) * 2);\n }\n .mb-4 {\n margin-bottom: calc(var(--spacing) * 4);\n }\n .ml-1 {\n margin-left: calc(var(--spacing) * 1);\n }\n .ml-2 {\n margin-left: calc(var(--spacing) * 2);\n }\n .contents {\n display: contents;\n }\n .flex {\n display: flex;\n }\n .hidden {\n display: none;\n }\n .inline {\n display: inline;\n }\n .inline-block {\n display: inline-block;\n }\n .inline-flex {\n display: inline-flex;\n }\n .table {\n display: table;\n }\n .h-1 {\n height: calc(var(--spacing) * 1);\n }\n .h-1\\.5 {\n height: calc(var(--spacing) * 1.5);\n }\n .h-2 {\n height: calc(var(--spacing) * 2);\n }\n .h-2\\.5 {\n height: calc(var(--spacing) * 2.5);\n }\n .h-3 {\n height: calc(var(--spacing) * 3);\n }\n .h-4 {\n height: calc(var(--spacing) * 4);\n }\n .h-5 {\n height: calc(var(--spacing) * 5);\n }\n .h-6 {\n height: calc(var(--spacing) * 6);\n }\n .h-10 {\n height: calc(var(--spacing) * 10);\n }\n .h-12 {\n height: calc(var(--spacing) * 12);\n }\n .h-14 {\n height: calc(var(--spacing) * 14);\n }\n .h-full {\n height: 100%;\n }\n .max-h-80 {\n max-height: calc(var(--spacing) * 80);\n }\n .max-h-\\[min\\(80vh\\,480px\\)\\] {\n max-height: min(80vh, 480px);\n }\n .min-h-0 {\n min-height: calc(var(--spacing) * 0);\n }\n .w-1 {\n width: calc(var(--spacing) * 1);\n }\n .w-1\\.5 {\n width: calc(var(--spacing) * 1.5);\n }\n .w-2 {\n width: calc(var(--spacing) * 2);\n }\n .w-2\\.5 {\n width: calc(var(--spacing) * 2.5);\n }\n .w-3 {\n width: calc(var(--spacing) * 3);\n }\n .w-4 {\n width: calc(var(--spacing) * 4);\n }\n .w-5 {\n width: calc(var(--spacing) * 5);\n }\n .w-6 {\n width: calc(var(--spacing) * 6);\n }\n .w-12 {\n width: calc(var(--spacing) * 12);\n }\n .w-14 {\n width: calc(var(--spacing) * 14);\n }\n .w-80 {\n width: calc(var(--spacing) * 80);\n }\n .w-\\[2px\\] {\n width: 2px;\n }\n .w-\\[10vw\\] {\n width: 10vw;\n }\n .w-\\[15vw\\] {\n width: 15vw;\n }\n .w-fit {\n width: fit-content;\n }\n .w-full {\n width: 100%;\n }\n .max-w-\\[min\\(90vw\\,320px\\)\\] {\n max-width: min(90vw, 320px);\n }\n .max-w-md {\n max-width: var(--container-md);\n }\n .flex-1 {\n flex: 1;\n }\n .flex-shrink {\n flex-shrink: 1;\n }\n .shrink-0 {\n flex-shrink: 0;\n }\n .border-collapse {\n border-collapse: collapse;\n }\n .-translate-x-1 {\n --tw-translate-x: calc(var(--spacing) * -1);\n translate: var(--tw-translate-x) var(--tw-translate-y);\n }\n .-translate-x-1\\/2 {\n --tw-translate-x: calc(calc(1/2 * 100%) * -1);\n translate: var(--tw-translate-x) var(--tw-translate-y);\n }\n .-translate-y-1 {\n --tw-translate-y: calc(var(--spacing) * -1);\n translate: var(--tw-translate-x) var(--tw-translate-y);\n }\n .-translate-y-1\\/2 {\n --tw-translate-y: calc(calc(1/2 * 100%) * -1);\n translate: var(--tw-translate-x) var(--tw-translate-y);\n }\n .rotate-45 {\n rotate: 45deg;\n }\n .rotate-180 {\n rotate: 180deg;\n }\n .transform {\n transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);\n }\n .animate-pulse {\n animation: var(--animate-pulse);\n }\n .animate-spin {\n animation: var(--animate-spin);\n }\n .cursor-default {\n cursor: default;\n }\n .cursor-not-allowed {\n cursor: not-allowed;\n }\n .cursor-pointer {\n cursor: pointer;\n }\n .resize {\n resize: both;\n }\n .flex-col {\n flex-direction: column;\n }\n .flex-wrap {\n flex-wrap: wrap;\n }\n .items-center {\n align-items: center;\n }\n .items-start {\n align-items: flex-start;\n }\n .justify-between {\n justify-content: space-between;\n }\n .justify-center {\n justify-content: center;\n }\n .justify-end {\n justify-content: flex-end;\n }\n .gap-1 {\n gap: calc(var(--spacing) * 1);\n }\n .gap-2 {\n gap: calc(var(--spacing) * 2);\n }\n .gap-3 {\n gap: calc(var(--spacing) * 3);\n }\n .gap-4 {\n gap: calc(var(--spacing) * 4);\n }\n .gap-7 {\n gap: calc(var(--spacing) * 7);\n }\n .space-y-0 {\n :where(& > :not(:last-child)) {\n --tw-space-y-reverse: 0;\n margin-block-start: calc(calc(var(--spacing) * 0) * var(--tw-space-y-reverse));\n margin-block-end: calc(calc(var(--spacing) * 0) * calc(1 - var(--tw-space-y-reverse)));\n }\n }\n .space-y-3 {\n :where(& > :not(:last-child)) {\n --tw-space-y-reverse: 0;\n margin-block-start: calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));\n margin-block-end: calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)));\n }\n }\n .overflow-hidden {\n overflow: hidden;\n }\n .overflow-y-auto {\n overflow-y: auto;\n }\n .rounded {\n border-radius: 0.25rem;\n }\n .rounded-\\[5px\\] {\n border-radius: 5px;\n }\n .rounded-\\[7px\\] {\n border-radius: 7px;\n }\n .rounded-full {\n border-radius: calc(infinity * 1px);\n }\n .rounded-lg {\n border-radius: var(--radius-lg);\n }\n .rounded-md {\n border-radius: var(--radius-md);\n }\n .border {\n border-style: var(--tw-border-style);\n border-width: 1px;\n }\n .border-t {\n border-top-style: var(--tw-border-style);\n border-top-width: 1px;\n }\n .border-b {\n border-bottom-style: var(--tw-border-style);\n border-bottom-width: 1px;\n }\n .border-l {\n border-left-style: var(--tw-border-style);\n border-left-width: 1px;\n }\n .border-gray-600 {\n border-color: var(--color-gray-600);\n }\n .border-gray-700 {\n border-color: var(--color-gray-700);\n }\n .border-gray-700\\/60 {\n border-color: color-mix(in srgb, oklch(37.3% 0.034 259.733) 60%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n border-color: color-mix(in oklab, var(--color-gray-700) 60%, transparent);\n }\n }\n .border-white {\n border-color: var(--color-white);\n }\n .border-white\\/10 {\n border-color: color-mix(in srgb, #fff 10%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n border-color: color-mix(in oklab, var(--color-white) 10%, transparent);\n }\n }\n .border-white\\/30 {\n border-color: color-mix(in srgb, #fff 30%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n border-color: color-mix(in oklab, var(--color-white) 30%, transparent);\n }\n }\n .border-white\\/40 {\n border-color: color-mix(in srgb, #fff 40%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n border-color: color-mix(in oklab, var(--color-white) 40%, transparent);\n }\n }\n .bg-\\[\\#3a4049\\] {\n background-color: #3a4049;\n }\n .bg-\\[\\#454545\\] {\n background-color: #454545;\n }\n .bg-\\[rgba\\(0\\,0\\,0\\,0\\.5\\)\\] {\n background-color: rgba(0,0,0,0.5);\n }\n .bg-black {\n background-color: var(--color-black);\n }\n .bg-black\\/60 {\n background-color: color-mix(in srgb, #000 60%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-black) 60%, transparent);\n }\n }\n .bg-black\\/90 {\n background-color: color-mix(in srgb, #000 90%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-black) 90%, transparent);\n }\n }\n .bg-blue-500 {\n background-color: var(--color-blue-500);\n }\n .bg-gray-500 {\n background-color: var(--color-gray-500);\n }\n .bg-gray-900 {\n background-color: var(--color-gray-900);\n }\n .bg-green-500 {\n background-color: var(--color-green-500);\n }\n .bg-purple-500 {\n background-color: var(--color-purple-500);\n }\n .bg-red-500 {\n background-color: var(--color-red-500);\n }\n .bg-red-500\\/15 {\n background-color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 15%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-red-500) 15%, transparent);\n }\n }\n .bg-red-600 {\n background-color: var(--color-red-600);\n }\n .bg-transparent {\n background-color: transparent;\n }\n .bg-white {\n background-color: var(--color-white);\n }\n .bg-white\\/10 {\n background-color: color-mix(in srgb, #fff 10%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 10%, transparent);\n }\n }\n .bg-white\\/20 {\n background-color: color-mix(in srgb, #fff 20%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 20%, transparent);\n }\n }\n .bg-white\\/80 {\n background-color: color-mix(in srgb, #fff 80%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 80%, transparent);\n }\n }\n .bg-linear-to-b {\n --tw-gradient-position: to bottom;\n @supports (background-image: linear-gradient(in lab, red, red)) {\n --tw-gradient-position: to bottom in oklab;\n }\n background-image: linear-gradient(var(--tw-gradient-stops));\n }\n .from-black {\n --tw-gradient-from: var(--color-black);\n --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));\n }\n .from-black\\/80 {\n --tw-gradient-from: color-mix(in srgb, #000 80%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n --tw-gradient-from: color-mix(in oklab, var(--color-black) 80%, transparent);\n }\n --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));\n }\n .via-transparent {\n --tw-gradient-via: transparent;\n --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);\n --tw-gradient-stops: var(--tw-gradient-via-stops);\n }\n .to-black {\n --tw-gradient-to: var(--color-black);\n --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));\n }\n .to-black\\/90 {\n --tw-gradient-to: color-mix(in srgb, #000 90%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n --tw-gradient-to: color-mix(in oklab, var(--color-black) 90%, transparent);\n }\n --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));\n }\n .bg-cover {\n background-size: cover;\n }\n .bg-center {\n background-position: center;\n }\n .object-contain {\n object-fit: contain;\n }\n .p-0 {\n padding: calc(var(--spacing) * 0);\n }\n .p-1 {\n padding: calc(var(--spacing) * 1);\n }\n .p-2 {\n padding: calc(var(--spacing) * 2);\n }\n .p-4 {\n padding: calc(var(--spacing) * 4);\n }\n .p-6 {\n padding: calc(var(--spacing) * 6);\n }\n .p-10 {\n padding: calc(var(--spacing) * 10);\n }\n .px-2 {\n padding-inline: calc(var(--spacing) * 2);\n }\n .px-3 {\n padding-inline: calc(var(--spacing) * 3);\n }\n .px-4 {\n padding-inline: calc(var(--spacing) * 4);\n }\n .px-5 {\n padding-inline: calc(var(--spacing) * 5);\n }\n .px-6 {\n padding-inline: calc(var(--spacing) * 6);\n }\n .px-10 {\n padding-inline: calc(var(--spacing) * 10);\n }\n .py-1 {\n padding-block: calc(var(--spacing) * 1);\n }\n .py-1\\.5 {\n padding-block: calc(var(--spacing) * 1.5);\n }\n .py-2 {\n padding-block: calc(var(--spacing) * 2);\n }\n .py-3 {\n padding-block: calc(var(--spacing) * 3);\n }\n .py-4 {\n padding-block: calc(var(--spacing) * 4);\n }\n .pt-6 {\n padding-top: calc(var(--spacing) * 6);\n }\n .pr-1 {\n padding-right: calc(var(--spacing) * 1);\n }\n .pb-3 {\n padding-bottom: calc(var(--spacing) * 3);\n }\n .pb-4 {\n padding-bottom: calc(var(--spacing) * 4);\n }\n .pb-6 {\n padding-bottom: calc(var(--spacing) * 6);\n }\n .pb-10 {\n padding-bottom: calc(var(--spacing) * 10);\n }\n .pb-16 {\n padding-bottom: calc(var(--spacing) * 16);\n }\n .text-center {\n text-align: center;\n }\n .text-left {\n text-align: left;\n }\n .text-lg {\n font-size: var(--text-lg);\n line-height: var(--tw-leading, var(--text-lg--line-height));\n }\n .text-sm {\n font-size: var(--text-sm);\n line-height: var(--tw-leading, var(--text-sm--line-height));\n }\n .text-xl {\n font-size: var(--text-xl);\n line-height: var(--tw-leading, var(--text-xl--line-height));\n }\n .text-xs {\n font-size: var(--text-xs);\n line-height: var(--tw-leading, var(--text-xs--line-height));\n }\n .font-bold {\n --tw-font-weight: var(--font-weight-bold);\n font-weight: var(--font-weight-bold);\n }\n .font-medium {\n --tw-font-weight: var(--font-weight-medium);\n font-weight: var(--font-weight-medium);\n }\n .font-normal {\n --tw-font-weight: var(--font-weight-normal);\n font-weight: var(--font-weight-normal);\n }\n .font-semibold {\n --tw-font-weight: var(--font-weight-semibold);\n font-weight: var(--font-weight-semibold);\n }\n .tracking-wider {\n --tw-tracking: var(--tracking-wider);\n letter-spacing: var(--tracking-wider);\n }\n .whitespace-nowrap {\n white-space: nowrap;\n }\n .text-gray-200 {\n color: var(--color-gray-200);\n }\n .text-gray-300 {\n color: var(--color-gray-300);\n }\n .text-gray-400 {\n color: var(--color-gray-400);\n }\n .text-gray-500 {\n color: var(--color-gray-500);\n }\n .text-gray-900 {\n color: var(--color-gray-900);\n }\n .text-red-400 {\n color: var(--color-red-400);\n }\n .text-red-500 {\n color: var(--color-red-500);\n }\n .text-red-600 {\n color: var(--color-red-600);\n }\n .text-sky-300 {\n color: var(--color-sky-300);\n }\n .text-white {\n color: var(--color-white);\n }\n .normal-case {\n text-transform: none;\n }\n .underline {\n text-decoration-line: underline;\n }\n .opacity-0 {\n opacity: 0%;\n }\n .opacity-100 {\n opacity: 100%;\n }\n .shadow-2xl {\n --tw-shadow: 0 25px 50px -12px var(--tw-shadow-color, rgb(0 0 0 / 0.25));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n .shadow-lg {\n --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n .shadow-md {\n --tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n .ring-1 {\n --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n .ring-red-500 {\n --tw-ring-color: var(--color-red-500);\n }\n .ring-red-500\\/25 {\n --tw-ring-color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 25%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n --tw-ring-color: color-mix(in oklab, var(--color-red-500) 25%, transparent);\n }\n }\n .outline {\n outline-style: var(--tw-outline-style);\n outline-width: 1px;\n }\n .backdrop-blur-md {\n --tw-backdrop-blur: blur(var(--blur-md));\n -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);\n backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);\n }\n .backdrop-blur-sm {\n --tw-backdrop-blur: blur(var(--blur-sm));\n -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);\n backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);\n }\n .backdrop-filter {\n -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);\n backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);\n }\n .transition {\n transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, content-visibility, overlay, pointer-events;\n transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--default-transition-duration));\n }\n .transition-all {\n transition-property: all;\n transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--default-transition-duration));\n }\n .transition-colors {\n transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to;\n transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--default-transition-duration));\n }\n .transition-opacity {\n transition-property: opacity;\n transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--default-transition-duration));\n }\n .duration-200 {\n --tw-duration: 200ms;\n transition-duration: 200ms;\n }\n .duration-300 {\n --tw-duration: 300ms;\n transition-duration: 300ms;\n }\n .ease-in-out {\n --tw-ease: var(--ease-in-out);\n transition-timing-function: var(--ease-in-out);\n }\n .ease-out {\n --tw-ease: var(--ease-out);\n transition-timing-function: var(--ease-out);\n }\n .select-none {\n -webkit-user-select: none;\n user-select: none;\n }\n .hover\\:scale-105 {\n &:hover {\n @media (hover: hover) {\n --tw-scale-x: 105%;\n --tw-scale-y: 105%;\n --tw-scale-z: 105%;\n scale: var(--tw-scale-x) var(--tw-scale-y);\n }\n }\n }\n .hover\\:border-white\\/50 {\n &:hover {\n @media (hover: hover) {\n border-color: color-mix(in srgb, #fff 50%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n border-color: color-mix(in oklab, var(--color-white) 50%, transparent);\n }\n }\n }\n }\n .hover\\:bg-gray-300 {\n &:hover {\n @media (hover: hover) {\n background-color: var(--color-gray-300);\n }\n }\n }\n .hover\\:bg-red-700 {\n &:hover {\n @media (hover: hover) {\n background-color: var(--color-red-700);\n }\n }\n }\n .hover\\:bg-white\\/5 {\n &:hover {\n @media (hover: hover) {\n background-color: color-mix(in srgb, #fff 5%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 5%, transparent);\n }\n }\n }\n }\n .hover\\:bg-white\\/10 {\n &:hover {\n @media (hover: hover) {\n background-color: color-mix(in srgb, #fff 10%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 10%, transparent);\n }\n }\n }\n }\n .hover\\:bg-white\\/30 {\n &:hover {\n @media (hover: hover) {\n background-color: color-mix(in srgb, #fff 30%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 30%, transparent);\n }\n }\n }\n }\n .hover\\:bg-white\\/90 {\n &:hover {\n @media (hover: hover) {\n background-color: color-mix(in srgb, #fff 90%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 90%, transparent);\n }\n }\n }\n }\n .hover\\:text-gray-200 {\n &:hover {\n @media (hover: hover) {\n color: var(--color-gray-200);\n }\n }\n }\n .hover\\:text-white {\n &:hover {\n @media (hover: hover) {\n color: var(--color-white);\n }\n }\n }\n .hover\\:shadow-lg {\n &:hover {\n @media (hover: hover) {\n --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n }\n }\n .focus\\:ring-2 {\n &:focus {\n --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n }\n .focus\\:ring-gray-400 {\n &:focus {\n --tw-ring-color: var(--color-gray-400);\n }\n }\n .focus\\:ring-offset-1 {\n &:focus {\n --tw-ring-offset-width: 1px;\n --tw-ring-offset-shadow: var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);\n }\n }\n .focus\\:outline-none {\n &:focus {\n --tw-outline-style: none;\n outline-style: none;\n }\n }\n .active\\:scale-95 {\n &:active {\n --tw-scale-x: 95%;\n --tw-scale-y: 95%;\n --tw-scale-z: 95%;\n scale: var(--tw-scale-x) var(--tw-scale-y);\n }\n }\n .disabled\\:cursor-not-allowed {\n &:disabled {\n cursor: not-allowed;\n }\n }\n .disabled\\:bg-white\\/50 {\n &:disabled {\n background-color: color-mix(in srgb, #fff 50%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 50%, transparent);\n }\n }\n }\n .disabled\\:opacity-50 {\n &:disabled {\n opacity: 50%;\n }\n }\n .lg\\:h-18 {\n @media (width >= 64rem) {\n height: calc(var(--spacing) * 18);\n }\n }\n .lg\\:w-18 {\n @media (width >= 64rem) {\n width: calc(var(--spacing) * 18);\n }\n }\n .lg\\:pb-12 {\n @media (width >= 64rem) {\n padding-bottom: calc(var(--spacing) * 12);\n }\n }\n .lg\\:text-2xl {\n @media (width >= 64rem) {\n font-size: var(--text-2xl);\n line-height: var(--tw-leading, var(--text-2xl--line-height));\n }\n }\n .lg\\:text-3xl {\n @media (width >= 64rem) {\n font-size: var(--text-3xl);\n line-height: var(--tw-leading, var(--text-3xl--line-height));\n }\n }\n .lg\\:text-base {\n @media (width >= 64rem) {\n font-size: var(--text-base);\n line-height: var(--tw-leading, var(--text-base--line-height));\n }\n }\n}\n@keyframes live-dot-blink {\n 0%, 100% {\n opacity: 1;\n transform: scale(1);\n box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.6);\n }\n 50% {\n opacity: 0.35;\n transform: scale(0.85);\n box-shadow: 0 0 0 4px rgba(239, 68, 68, 0);\n }\n}\n.animate-live-blink {\n animation: live-dot-blink 1.2s ease-in-out infinite;\n}\n.noCursor {\n cursor: none !important;\n}\n.icon-class {\n height: calc(var(--spacing) * 14);\n width: calc(var(--spacing) * 14);\n cursor: pointer;\n color: var(--color-gray-400);\n transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to;\n transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--default-transition-duration));\n --tw-duration: 200ms;\n transition-duration: 200ms;\n &:hover {\n @media (hover: hover) {\n color: var(--color-gray-200);\n }\n }\n @media (width >= 64rem) {\n height: calc(var(--spacing) * 18);\n }\n @media (width >= 64rem) {\n width: calc(var(--spacing) * 18);\n }\n}\n@property --tw-translate-x {\n syntax: \"*\";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-translate-y {\n syntax: \"*\";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-translate-z {\n syntax: \"*\";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-rotate-x {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-rotate-y {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-rotate-z {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-skew-x {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-skew-y {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-space-y-reverse {\n syntax: \"*\";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-border-style {\n syntax: \"*\";\n inherits: false;\n initial-value: solid;\n}\n@property --tw-gradient-position {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-gradient-from {\n syntax: \"<color>\";\n inherits: false;\n initial-value: #0000;\n}\n@property --tw-gradient-via {\n syntax: \"<color>\";\n inherits: false;\n initial-value: #0000;\n}\n@property --tw-gradient-to {\n syntax: \"<color>\";\n inherits: false;\n initial-value: #0000;\n}\n@property --tw-gradient-stops {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-gradient-via-stops {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-gradient-from-position {\n syntax: \"<length-percentage>\";\n inherits: false;\n initial-value: 0%;\n}\n@property --tw-gradient-via-position {\n syntax: \"<length-percentage>\";\n inherits: false;\n initial-value: 50%;\n}\n@property --tw-gradient-to-position {\n syntax: \"<length-percentage>\";\n inherits: false;\n initial-value: 100%;\n}\n@property --tw-font-weight {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-tracking {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-shadow {\n syntax: \"*\";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-shadow-color {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-shadow-alpha {\n syntax: \"<percentage>\";\n inherits: false;\n initial-value: 100%;\n}\n@property --tw-inset-shadow {\n syntax: \"*\";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-inset-shadow-color {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-inset-shadow-alpha {\n syntax: \"<percentage>\";\n inherits: false;\n initial-value: 100%;\n}\n@property --tw-ring-color {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-ring-shadow {\n syntax: \"*\";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-inset-ring-color {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-inset-ring-shadow {\n syntax: \"*\";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-ring-inset {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-ring-offset-width {\n syntax: \"<length>\";\n inherits: false;\n initial-value: 0px;\n}\n@property --tw-ring-offset-color {\n syntax: \"*\";\n inherits: false;\n initial-value: #fff;\n}\n@property --tw-ring-offset-shadow {\n syntax: \"*\";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-outline-style {\n syntax: \"*\";\n inherits: false;\n initial-value: solid;\n}\n@property --tw-backdrop-blur {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-brightness {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-contrast {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-grayscale {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-hue-rotate {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-invert {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-opacity {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-saturate {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-sepia {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-duration {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-ease {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-scale-x {\n syntax: \"*\";\n inherits: false;\n initial-value: 1;\n}\n@property --tw-scale-y {\n syntax: \"*\";\n inherits: false;\n initial-value: 1;\n}\n@property --tw-scale-z {\n syntax: \"*\";\n inherits: false;\n initial-value: 1;\n}\n@keyframes spin {\n to {\n transform: rotate(360deg);\n }\n}\n@keyframes pulse {\n 50% {\n opacity: 0.5;\n }\n}\n@layer properties {\n @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {\n *, ::before, ::after, ::backdrop {\n --tw-translate-x: 0;\n --tw-translate-y: 0;\n --tw-translate-z: 0;\n --tw-rotate-x: initial;\n --tw-rotate-y: initial;\n --tw-rotate-z: initial;\n --tw-skew-x: initial;\n --tw-skew-y: initial;\n --tw-space-y-reverse: 0;\n --tw-border-style: solid;\n --tw-gradient-position: initial;\n --tw-gradient-from: #0000;\n --tw-gradient-via: #0000;\n --tw-gradient-to: #0000;\n --tw-gradient-stops: initial;\n --tw-gradient-via-stops: initial;\n --tw-gradient-from-position: 0%;\n --tw-gradient-via-position: 50%;\n --tw-gradient-to-position: 100%;\n --tw-font-weight: initial;\n --tw-tracking: initial;\n --tw-shadow: 0 0 #0000;\n --tw-shadow-color: initial;\n --tw-shadow-alpha: 100%;\n --tw-inset-shadow: 0 0 #0000;\n --tw-inset-shadow-color: initial;\n --tw-inset-shadow-alpha: 100%;\n --tw-ring-color: initial;\n --tw-ring-shadow: 0 0 #0000;\n --tw-inset-ring-color: initial;\n --tw-inset-ring-shadow: 0 0 #0000;\n --tw-ring-inset: initial;\n --tw-ring-offset-width: 0px;\n --tw-ring-offset-color: #fff;\n --tw-ring-offset-shadow: 0 0 #0000;\n --tw-outline-style: solid;\n --tw-backdrop-blur: initial;\n --tw-backdrop-brightness: initial;\n --tw-backdrop-contrast: initial;\n --tw-backdrop-grayscale: initial;\n --tw-backdrop-hue-rotate: initial;\n --tw-backdrop-invert: initial;\n --tw-backdrop-opacity: initial;\n --tw-backdrop-saturate: initial;\n --tw-backdrop-sepia: initial;\n --tw-duration: initial;\n --tw-ease: initial;\n --tw-scale-x: 1;\n --tw-scale-y: 1;\n --tw-scale-z: 1;\n }\n }\n}\n";
40
+ var css_248z$4 = "/*! tailwindcss v4.1.17 | MIT License | https://tailwindcss.com */\n@layer properties;\n@layer theme, base, components, utilities;\n@layer theme {\n :root, :host {\n --font-sans: ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\",\n \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\",\n \"Courier New\", monospace;\n --color-red-400: oklch(70.4% 0.191 22.216);\n --color-red-500: oklch(63.7% 0.237 25.331);\n --color-red-600: oklch(57.7% 0.245 27.325);\n --color-red-700: oklch(50.5% 0.213 27.518);\n --color-green-500: oklch(72.3% 0.219 149.579);\n --color-sky-300: oklch(82.8% 0.111 230.318);\n --color-blue-500: oklch(62.3% 0.214 259.815);\n --color-purple-500: oklch(62.7% 0.265 303.9);\n --color-gray-200: oklch(92.8% 0.006 264.531);\n --color-gray-300: oklch(87.2% 0.01 258.338);\n --color-gray-400: oklch(70.7% 0.022 261.325);\n --color-gray-500: oklch(55.1% 0.027 264.364);\n --color-gray-600: oklch(44.6% 0.03 256.802);\n --color-gray-700: oklch(37.3% 0.034 259.733);\n --color-gray-900: oklch(21% 0.034 264.665);\n --color-black: #000;\n --color-white: #fff;\n --spacing: 0.25rem;\n --container-md: 28rem;\n --text-xs: 0.75rem;\n --text-xs--line-height: calc(1 / 0.75);\n --text-sm: 0.875rem;\n --text-sm--line-height: calc(1.25 / 0.875);\n --text-base: 1rem;\n --text-base--line-height: calc(1.5 / 1);\n --text-lg: 1.125rem;\n --text-lg--line-height: calc(1.75 / 1.125);\n --text-xl: 1.25rem;\n --text-xl--line-height: calc(1.75 / 1.25);\n --text-2xl: 1.5rem;\n --text-2xl--line-height: calc(2 / 1.5);\n --text-3xl: 1.875rem;\n --text-3xl--line-height: calc(2.25 / 1.875);\n --font-weight-normal: 400;\n --font-weight-medium: 500;\n --font-weight-semibold: 600;\n --font-weight-bold: 700;\n --tracking-wider: 0.05em;\n --radius-md: 0.375rem;\n --radius-lg: 0.5rem;\n --ease-out: cubic-bezier(0, 0, 0.2, 1);\n --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);\n --animate-spin: spin 1s linear infinite;\n --animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;\n --blur-sm: 8px;\n --blur-md: 12px;\n --default-transition-duration: 150ms;\n --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n --default-font-family: var(--font-sans);\n --default-mono-font-family: var(--font-mono);\n }\n}\n@layer base {\n *, ::after, ::before, ::backdrop, ::file-selector-button {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n border: 0 solid;\n }\n html, :host {\n line-height: 1.5;\n -webkit-text-size-adjust: 100%;\n tab-size: 4;\n font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\");\n font-feature-settings: var(--default-font-feature-settings, normal);\n font-variation-settings: var(--default-font-variation-settings, normal);\n -webkit-tap-highlight-color: transparent;\n }\n hr {\n height: 0;\n color: inherit;\n border-top-width: 1px;\n }\n abbr:where([title]) {\n -webkit-text-decoration: underline dotted;\n text-decoration: underline dotted;\n }\n h1, h2, h3, h4, h5, h6 {\n font-size: inherit;\n font-weight: inherit;\n }\n a {\n color: inherit;\n -webkit-text-decoration: inherit;\n text-decoration: inherit;\n }\n b, strong {\n font-weight: bolder;\n }\n code, kbd, samp, pre {\n font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace);\n font-feature-settings: var(--default-mono-font-feature-settings, normal);\n font-variation-settings: var(--default-mono-font-variation-settings, normal);\n font-size: 1em;\n }\n small {\n font-size: 80%;\n }\n sub, sup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n }\n sub {\n bottom: -0.25em;\n }\n sup {\n top: -0.5em;\n }\n table {\n text-indent: 0;\n border-color: inherit;\n border-collapse: collapse;\n }\n :-moz-focusring {\n outline: auto;\n }\n progress {\n vertical-align: baseline;\n }\n summary {\n display: list-item;\n }\n ol, ul, menu {\n list-style: none;\n }\n img, svg, video, canvas, audio, iframe, embed, object {\n display: block;\n vertical-align: middle;\n }\n img, video {\n max-width: 100%;\n height: auto;\n }\n button, input, select, optgroup, textarea, ::file-selector-button {\n font: inherit;\n font-feature-settings: inherit;\n font-variation-settings: inherit;\n letter-spacing: inherit;\n color: inherit;\n border-radius: 0;\n background-color: transparent;\n opacity: 1;\n }\n :where(select:is([multiple], [size])) optgroup {\n font-weight: bolder;\n }\n :where(select:is([multiple], [size])) optgroup option {\n padding-inline-start: 20px;\n }\n ::file-selector-button {\n margin-inline-end: 4px;\n }\n ::placeholder {\n opacity: 1;\n }\n @supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) {\n ::placeholder {\n color: currentcolor;\n @supports (color: color-mix(in lab, red, red)) {\n color: color-mix(in oklab, currentcolor 50%, transparent);\n }\n }\n }\n textarea {\n resize: vertical;\n }\n ::-webkit-search-decoration {\n -webkit-appearance: none;\n }\n ::-webkit-date-and-time-value {\n min-height: 1lh;\n text-align: inherit;\n }\n ::-webkit-datetime-edit {\n display: inline-flex;\n }\n ::-webkit-datetime-edit-fields-wrapper {\n padding: 0;\n }\n ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field {\n padding-block: 0;\n }\n ::-webkit-calendar-picker-indicator {\n line-height: 1;\n }\n :-moz-ui-invalid {\n box-shadow: none;\n }\n button, input:where([type=\"button\"], [type=\"reset\"], [type=\"submit\"]), ::file-selector-button {\n appearance: button;\n }\n ::-webkit-inner-spin-button, ::-webkit-outer-spin-button {\n height: auto;\n }\n [hidden]:where(:not([hidden=\"until-found\"])) {\n display: none !important;\n }\n}\n@layer utilities {\n .pointer-events-auto {\n pointer-events: auto;\n }\n .pointer-events-none {\n pointer-events: none;\n }\n .absolute {\n position: absolute;\n }\n .fixed {\n position: fixed;\n }\n .relative {\n position: relative;\n }\n .static {\n position: static;\n }\n .inset-0 {\n inset: calc(var(--spacing) * 0);\n }\n .top-0 {\n top: calc(var(--spacing) * 0);\n }\n .top-1 {\n top: calc(var(--spacing) * 1);\n }\n .top-1\\/2 {\n top: calc(1/2 * 100%);\n }\n .right-32 {\n right: calc(var(--spacing) * 32);\n }\n .bottom-36 {\n bottom: calc(var(--spacing) * 36);\n }\n .left-0 {\n left: calc(var(--spacing) * 0);\n }\n .left-32 {\n left: calc(var(--spacing) * 32);\n }\n .z-0 {\n z-index: 0;\n }\n .z-40 {\n z-index: 40;\n }\n .z-50 {\n z-index: 50;\n }\n .z-\\[46\\] {\n z-index: 46;\n }\n .z-\\[1200\\] {\n z-index: 1200;\n }\n .\\!container {\n width: 100% !important;\n @media (width >= 40rem) {\n max-width: 40rem !important;\n }\n @media (width >= 48rem) {\n max-width: 48rem !important;\n }\n @media (width >= 64rem) {\n max-width: 64rem !important;\n }\n @media (width >= 80rem) {\n max-width: 80rem !important;\n }\n @media (width >= 96rem) {\n max-width: 96rem !important;\n }\n }\n .container {\n width: 100%;\n @media (width >= 40rem) {\n max-width: 40rem;\n }\n @media (width >= 48rem) {\n max-width: 48rem;\n }\n @media (width >= 64rem) {\n max-width: 64rem;\n }\n @media (width >= 80rem) {\n max-width: 80rem;\n }\n @media (width >= 96rem) {\n max-width: 96rem;\n }\n }\n .mx-2 {\n margin-inline: calc(var(--spacing) * 2);\n }\n .mx-auto {\n margin-inline: auto;\n }\n .mt-1 {\n margin-top: calc(var(--spacing) * 1);\n }\n .mt-4 {\n margin-top: calc(var(--spacing) * 4);\n }\n .-mr-1 {\n margin-right: calc(var(--spacing) * -1);\n }\n .mr-2 {\n margin-right: calc(var(--spacing) * 2);\n }\n .mb-1 {\n margin-bottom: calc(var(--spacing) * 1);\n }\n .mb-4 {\n margin-bottom: calc(var(--spacing) * 4);\n }\n .ml-1 {\n margin-left: calc(var(--spacing) * 1);\n }\n .contents {\n display: contents;\n }\n .flex {\n display: flex;\n }\n .hidden {\n display: none;\n }\n .inline {\n display: inline;\n }\n .inline-flex {\n display: inline-flex;\n }\n .table {\n display: table;\n }\n .h-1 {\n height: calc(var(--spacing) * 1);\n }\n .h-1\\.5 {\n height: calc(var(--spacing) * 1.5);\n }\n .h-2 {\n height: calc(var(--spacing) * 2);\n }\n .h-2\\.5 {\n height: calc(var(--spacing) * 2.5);\n }\n .h-3 {\n height: calc(var(--spacing) * 3);\n }\n .h-4 {\n height: calc(var(--spacing) * 4);\n }\n .h-5 {\n height: calc(var(--spacing) * 5);\n }\n .h-6 {\n height: calc(var(--spacing) * 6);\n }\n .h-10 {\n height: calc(var(--spacing) * 10);\n }\n .h-12 {\n height: calc(var(--spacing) * 12);\n }\n .h-14 {\n height: calc(var(--spacing) * 14);\n }\n .h-full {\n height: 100%;\n }\n .max-h-80 {\n max-height: calc(var(--spacing) * 80);\n }\n .max-h-\\[min\\(80vh\\,480px\\)\\] {\n max-height: min(80vh, 480px);\n }\n .min-h-0 {\n min-height: calc(var(--spacing) * 0);\n }\n .w-1 {\n width: calc(var(--spacing) * 1);\n }\n .w-1\\.5 {\n width: calc(var(--spacing) * 1.5);\n }\n .w-2 {\n width: calc(var(--spacing) * 2);\n }\n .w-2\\.5 {\n width: calc(var(--spacing) * 2.5);\n }\n .w-3 {\n width: calc(var(--spacing) * 3);\n }\n .w-4 {\n width: calc(var(--spacing) * 4);\n }\n .w-5 {\n width: calc(var(--spacing) * 5);\n }\n .w-6 {\n width: calc(var(--spacing) * 6);\n }\n .w-12 {\n width: calc(var(--spacing) * 12);\n }\n .w-14 {\n width: calc(var(--spacing) * 14);\n }\n .w-80 {\n width: calc(var(--spacing) * 80);\n }\n .w-\\[2px\\] {\n width: 2px;\n }\n .w-\\[10vw\\] {\n width: 10vw;\n }\n .w-\\[15vw\\] {\n width: 15vw;\n }\n .w-fit {\n width: fit-content;\n }\n .w-full {\n width: 100%;\n }\n .w-max {\n width: max-content;\n }\n .max-w-\\[min\\(90vw\\,320px\\)\\] {\n max-width: min(90vw, 320px);\n }\n .max-w-\\[min\\(calc\\(100vw-16px\\)\\,320px\\)\\] {\n max-width: min(calc(100vw - 16px), 320px);\n }\n .max-w-full {\n max-width: 100%;\n }\n .max-w-md {\n max-width: var(--container-md);\n }\n .flex-1 {\n flex: 1;\n }\n .flex-shrink {\n flex-shrink: 1;\n }\n .shrink-0 {\n flex-shrink: 0;\n }\n .border-collapse {\n border-collapse: collapse;\n }\n .-translate-y-1 {\n --tw-translate-y: calc(var(--spacing) * -1);\n translate: var(--tw-translate-x) var(--tw-translate-y);\n }\n .-translate-y-1\\/2 {\n --tw-translate-y: calc(calc(1/2 * 100%) * -1);\n translate: var(--tw-translate-x) var(--tw-translate-y);\n }\n .rotate-180 {\n rotate: 180deg;\n }\n .transform {\n transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);\n }\n .animate-pulse {\n animation: var(--animate-pulse);\n }\n .animate-spin {\n animation: var(--animate-spin);\n }\n .cursor-default {\n cursor: default;\n }\n .cursor-not-allowed {\n cursor: not-allowed;\n }\n .cursor-pointer {\n cursor: pointer;\n }\n .touch-manipulation {\n touch-action: manipulation;\n }\n .resize {\n resize: both;\n }\n .flex-col {\n flex-direction: column;\n }\n .flex-wrap {\n flex-wrap: wrap;\n }\n .items-center {\n align-items: center;\n }\n .items-start {\n align-items: flex-start;\n }\n .justify-between {\n justify-content: space-between;\n }\n .justify-center {\n justify-content: center;\n }\n .justify-end {\n justify-content: flex-end;\n }\n .gap-1 {\n gap: calc(var(--spacing) * 1);\n }\n .gap-2 {\n gap: calc(var(--spacing) * 2);\n }\n .gap-3 {\n gap: calc(var(--spacing) * 3);\n }\n .gap-4 {\n gap: calc(var(--spacing) * 4);\n }\n .gap-7 {\n gap: calc(var(--spacing) * 7);\n }\n .space-y-0 {\n :where(& > :not(:last-child)) {\n --tw-space-y-reverse: 0;\n margin-block-start: calc(calc(var(--spacing) * 0) * var(--tw-space-y-reverse));\n margin-block-end: calc(calc(var(--spacing) * 0) * calc(1 - var(--tw-space-y-reverse)));\n }\n }\n .space-y-3 {\n :where(& > :not(:last-child)) {\n --tw-space-y-reverse: 0;\n margin-block-start: calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));\n margin-block-end: calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)));\n }\n }\n .overflow-hidden {\n overflow: hidden;\n }\n .overflow-y-auto {\n overflow-y: auto;\n }\n .rounded {\n border-radius: 0.25rem;\n }\n .rounded-\\[5px\\] {\n border-radius: 5px;\n }\n .rounded-\\[7px\\] {\n border-radius: 7px;\n }\n .rounded-full {\n border-radius: calc(infinity * 1px);\n }\n .rounded-lg {\n border-radius: var(--radius-lg);\n }\n .rounded-md {\n border-radius: var(--radius-md);\n }\n .border {\n border-style: var(--tw-border-style);\n border-width: 1px;\n }\n .border-t {\n border-top-style: var(--tw-border-style);\n border-top-width: 1px;\n }\n .border-b {\n border-bottom-style: var(--tw-border-style);\n border-bottom-width: 1px;\n }\n .border-l {\n border-left-style: var(--tw-border-style);\n border-left-width: 1px;\n }\n .border-gray-600 {\n border-color: var(--color-gray-600);\n }\n .border-gray-700 {\n border-color: var(--color-gray-700);\n }\n .border-gray-700\\/60 {\n border-color: color-mix(in srgb, oklch(37.3% 0.034 259.733) 60%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n border-color: color-mix(in oklab, var(--color-gray-700) 60%, transparent);\n }\n }\n .border-white {\n border-color: var(--color-white);\n }\n .border-white\\/10 {\n border-color: color-mix(in srgb, #fff 10%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n border-color: color-mix(in oklab, var(--color-white) 10%, transparent);\n }\n }\n .border-white\\/30 {\n border-color: color-mix(in srgb, #fff 30%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n border-color: color-mix(in oklab, var(--color-white) 30%, transparent);\n }\n }\n .border-white\\/40 {\n border-color: color-mix(in srgb, #fff 40%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n border-color: color-mix(in oklab, var(--color-white) 40%, transparent);\n }\n }\n .bg-\\[\\#3a4049\\] {\n background-color: #3a4049;\n }\n .bg-\\[\\#454545\\] {\n background-color: #454545;\n }\n .bg-\\[rgba\\(0\\,0\\,0\\,0\\.5\\)\\] {\n background-color: rgba(0,0,0,0.5);\n }\n .bg-black {\n background-color: var(--color-black);\n }\n .bg-black\\/60 {\n background-color: color-mix(in srgb, #000 60%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-black) 60%, transparent);\n }\n }\n .bg-black\\/90 {\n background-color: color-mix(in srgb, #000 90%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-black) 90%, transparent);\n }\n }\n .bg-blue-500 {\n background-color: var(--color-blue-500);\n }\n .bg-gray-500 {\n background-color: var(--color-gray-500);\n }\n .bg-green-500 {\n background-color: var(--color-green-500);\n }\n .bg-purple-500 {\n background-color: var(--color-purple-500);\n }\n .bg-red-500 {\n background-color: var(--color-red-500);\n }\n .bg-red-500\\/15 {\n background-color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 15%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-red-500) 15%, transparent);\n }\n }\n .bg-red-600 {\n background-color: var(--color-red-600);\n }\n .bg-transparent {\n background-color: transparent;\n }\n .bg-white {\n background-color: var(--color-white);\n }\n .bg-white\\/10 {\n background-color: color-mix(in srgb, #fff 10%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 10%, transparent);\n }\n }\n .bg-white\\/20 {\n background-color: color-mix(in srgb, #fff 20%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 20%, transparent);\n }\n }\n .bg-white\\/80 {\n background-color: color-mix(in srgb, #fff 80%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 80%, transparent);\n }\n }\n .bg-linear-to-b {\n --tw-gradient-position: to bottom;\n @supports (background-image: linear-gradient(in lab, red, red)) {\n --tw-gradient-position: to bottom in oklab;\n }\n background-image: linear-gradient(var(--tw-gradient-stops));\n }\n .from-black {\n --tw-gradient-from: var(--color-black);\n --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));\n }\n .from-black\\/80 {\n --tw-gradient-from: color-mix(in srgb, #000 80%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n --tw-gradient-from: color-mix(in oklab, var(--color-black) 80%, transparent);\n }\n --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));\n }\n .via-transparent {\n --tw-gradient-via: transparent;\n --tw-gradient-via-stops: var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-via) var(--tw-gradient-via-position), var(--tw-gradient-to) var(--tw-gradient-to-position);\n --tw-gradient-stops: var(--tw-gradient-via-stops);\n }\n .to-black {\n --tw-gradient-to: var(--color-black);\n --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));\n }\n .to-black\\/90 {\n --tw-gradient-to: color-mix(in srgb, #000 90%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n --tw-gradient-to: color-mix(in oklab, var(--color-black) 90%, transparent);\n }\n --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));\n }\n .bg-cover {\n background-size: cover;\n }\n .bg-center {\n background-position: center;\n }\n .object-contain {\n object-fit: contain;\n }\n .p-0 {\n padding: calc(var(--spacing) * 0);\n }\n .p-1 {\n padding: calc(var(--spacing) * 1);\n }\n .p-2 {\n padding: calc(var(--spacing) * 2);\n }\n .p-4 {\n padding: calc(var(--spacing) * 4);\n }\n .p-6 {\n padding: calc(var(--spacing) * 6);\n }\n .p-10 {\n padding: calc(var(--spacing) * 10);\n }\n .px-2 {\n padding-inline: calc(var(--spacing) * 2);\n }\n .px-3 {\n padding-inline: calc(var(--spacing) * 3);\n }\n .px-4 {\n padding-inline: calc(var(--spacing) * 4);\n }\n .px-5 {\n padding-inline: calc(var(--spacing) * 5);\n }\n .px-6 {\n padding-inline: calc(var(--spacing) * 6);\n }\n .px-10 {\n padding-inline: calc(var(--spacing) * 10);\n }\n .py-1 {\n padding-block: calc(var(--spacing) * 1);\n }\n .py-1\\.5 {\n padding-block: calc(var(--spacing) * 1.5);\n }\n .py-2 {\n padding-block: calc(var(--spacing) * 2);\n }\n .py-3 {\n padding-block: calc(var(--spacing) * 3);\n }\n .py-4 {\n padding-block: calc(var(--spacing) * 4);\n }\n .pt-6 {\n padding-top: calc(var(--spacing) * 6);\n }\n .pr-1 {\n padding-right: calc(var(--spacing) * 1);\n }\n .pb-3 {\n padding-bottom: calc(var(--spacing) * 3);\n }\n .pb-4 {\n padding-bottom: calc(var(--spacing) * 4);\n }\n .pb-6 {\n padding-bottom: calc(var(--spacing) * 6);\n }\n .pb-10 {\n padding-bottom: calc(var(--spacing) * 10);\n }\n .pb-16 {\n padding-bottom: calc(var(--spacing) * 16);\n }\n .text-center {\n text-align: center;\n }\n .text-left {\n text-align: left;\n }\n .text-lg {\n font-size: var(--text-lg);\n line-height: var(--tw-leading, var(--text-lg--line-height));\n }\n .text-sm {\n font-size: var(--text-sm);\n line-height: var(--tw-leading, var(--text-sm--line-height));\n }\n .text-xl {\n font-size: var(--text-xl);\n line-height: var(--tw-leading, var(--text-xl--line-height));\n }\n .text-xs {\n font-size: var(--text-xs);\n line-height: var(--tw-leading, var(--text-xs--line-height));\n }\n .font-bold {\n --tw-font-weight: var(--font-weight-bold);\n font-weight: var(--font-weight-bold);\n }\n .font-medium {\n --tw-font-weight: var(--font-weight-medium);\n font-weight: var(--font-weight-medium);\n }\n .font-normal {\n --tw-font-weight: var(--font-weight-normal);\n font-weight: var(--font-weight-normal);\n }\n .font-semibold {\n --tw-font-weight: var(--font-weight-semibold);\n font-weight: var(--font-weight-semibold);\n }\n .tracking-wider {\n --tw-tracking: var(--tracking-wider);\n letter-spacing: var(--tracking-wider);\n }\n .text-gray-200 {\n color: var(--color-gray-200);\n }\n .text-gray-300 {\n color: var(--color-gray-300);\n }\n .text-gray-400 {\n color: var(--color-gray-400);\n }\n .text-gray-500 {\n color: var(--color-gray-500);\n }\n .text-gray-900 {\n color: var(--color-gray-900);\n }\n .text-red-400 {\n color: var(--color-red-400);\n }\n .text-red-500 {\n color: var(--color-red-500);\n }\n .text-red-600 {\n color: var(--color-red-600);\n }\n .text-sky-300 {\n color: var(--color-sky-300);\n }\n .text-white {\n color: var(--color-white);\n }\n .text-white\\/80 {\n color: color-mix(in srgb, #fff 80%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n color: color-mix(in oklab, var(--color-white) 80%, transparent);\n }\n }\n .normal-case {\n text-transform: none;\n }\n .underline {\n text-decoration-line: underline;\n }\n .opacity-0 {\n opacity: 0%;\n }\n .opacity-100 {\n opacity: 100%;\n }\n .shadow-2xl {\n --tw-shadow: 0 25px 50px -12px var(--tw-shadow-color, rgb(0 0 0 / 0.25));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n .shadow-lg {\n --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n .shadow-md {\n --tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n .ring-1 {\n --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n .ring-red-500 {\n --tw-ring-color: var(--color-red-500);\n }\n .ring-red-500\\/25 {\n --tw-ring-color: color-mix(in srgb, oklch(63.7% 0.237 25.331) 25%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n --tw-ring-color: color-mix(in oklab, var(--color-red-500) 25%, transparent);\n }\n }\n .outline {\n outline-style: var(--tw-outline-style);\n outline-width: 1px;\n }\n .backdrop-blur-md {\n --tw-backdrop-blur: blur(var(--blur-md));\n -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);\n backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);\n }\n .backdrop-blur-sm {\n --tw-backdrop-blur: blur(var(--blur-sm));\n -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);\n backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);\n }\n .backdrop-filter {\n -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);\n backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);\n }\n .transition {\n transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, content-visibility, overlay, pointer-events;\n transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--default-transition-duration));\n }\n .transition-all {\n transition-property: all;\n transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--default-transition-duration));\n }\n .transition-colors {\n transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to;\n transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--default-transition-duration));\n }\n .transition-opacity {\n transition-property: opacity;\n transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--default-transition-duration));\n }\n .duration-200 {\n --tw-duration: 200ms;\n transition-duration: 200ms;\n }\n .duration-300 {\n --tw-duration: 300ms;\n transition-duration: 300ms;\n }\n .ease-in-out {\n --tw-ease: var(--ease-in-out);\n transition-timing-function: var(--ease-in-out);\n }\n .ease-out {\n --tw-ease: var(--ease-out);\n transition-timing-function: var(--ease-out);\n }\n .select-none {\n -webkit-user-select: none;\n user-select: none;\n }\n .hover\\:scale-105 {\n &:hover {\n @media (hover: hover) {\n --tw-scale-x: 105%;\n --tw-scale-y: 105%;\n --tw-scale-z: 105%;\n scale: var(--tw-scale-x) var(--tw-scale-y);\n }\n }\n }\n .hover\\:border-white\\/50 {\n &:hover {\n @media (hover: hover) {\n border-color: color-mix(in srgb, #fff 50%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n border-color: color-mix(in oklab, var(--color-white) 50%, transparent);\n }\n }\n }\n }\n .hover\\:bg-gray-300 {\n &:hover {\n @media (hover: hover) {\n background-color: var(--color-gray-300);\n }\n }\n }\n .hover\\:bg-red-700 {\n &:hover {\n @media (hover: hover) {\n background-color: var(--color-red-700);\n }\n }\n }\n .hover\\:bg-white\\/5 {\n &:hover {\n @media (hover: hover) {\n background-color: color-mix(in srgb, #fff 5%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 5%, transparent);\n }\n }\n }\n }\n .hover\\:bg-white\\/10 {\n &:hover {\n @media (hover: hover) {\n background-color: color-mix(in srgb, #fff 10%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 10%, transparent);\n }\n }\n }\n }\n .hover\\:bg-white\\/30 {\n &:hover {\n @media (hover: hover) {\n background-color: color-mix(in srgb, #fff 30%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 30%, transparent);\n }\n }\n }\n }\n .hover\\:bg-white\\/90 {\n &:hover {\n @media (hover: hover) {\n background-color: color-mix(in srgb, #fff 90%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 90%, transparent);\n }\n }\n }\n }\n .hover\\:text-gray-200 {\n &:hover {\n @media (hover: hover) {\n color: var(--color-gray-200);\n }\n }\n }\n .hover\\:text-white {\n &:hover {\n @media (hover: hover) {\n color: var(--color-white);\n }\n }\n }\n .hover\\:shadow-lg {\n &:hover {\n @media (hover: hover) {\n --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1));\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n }\n }\n .focus\\:ring-2 {\n &:focus {\n --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);\n box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);\n }\n }\n .focus\\:ring-gray-400 {\n &:focus {\n --tw-ring-color: var(--color-gray-400);\n }\n }\n .focus\\:ring-offset-1 {\n &:focus {\n --tw-ring-offset-width: 1px;\n --tw-ring-offset-shadow: var(--tw-ring-inset,) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);\n }\n }\n .focus\\:outline-none {\n &:focus {\n --tw-outline-style: none;\n outline-style: none;\n }\n }\n .active\\:scale-95 {\n &:active {\n --tw-scale-x: 95%;\n --tw-scale-y: 95%;\n --tw-scale-z: 95%;\n scale: var(--tw-scale-x) var(--tw-scale-y);\n }\n }\n .disabled\\:cursor-not-allowed {\n &:disabled {\n cursor: not-allowed;\n }\n }\n .disabled\\:bg-white\\/50 {\n &:disabled {\n background-color: color-mix(in srgb, #fff 50%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-white) 50%, transparent);\n }\n }\n }\n .disabled\\:opacity-50 {\n &:disabled {\n opacity: 50%;\n }\n }\n .lg\\:h-18 {\n @media (width >= 64rem) {\n height: calc(var(--spacing) * 18);\n }\n }\n .lg\\:w-18 {\n @media (width >= 64rem) {\n width: calc(var(--spacing) * 18);\n }\n }\n .lg\\:pb-12 {\n @media (width >= 64rem) {\n padding-bottom: calc(var(--spacing) * 12);\n }\n }\n .lg\\:text-2xl {\n @media (width >= 64rem) {\n font-size: var(--text-2xl);\n line-height: var(--tw-leading, var(--text-2xl--line-height));\n }\n }\n .lg\\:text-3xl {\n @media (width >= 64rem) {\n font-size: var(--text-3xl);\n line-height: var(--tw-leading, var(--text-3xl--line-height));\n }\n }\n .lg\\:text-base {\n @media (width >= 64rem) {\n font-size: var(--text-base);\n line-height: var(--tw-leading, var(--text-base--line-height));\n }\n }\n}\n@keyframes live-dot-blink {\n 0%, 100% {\n opacity: 1;\n transform: scale(1);\n box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.6);\n }\n 50% {\n opacity: 0.35;\n transform: scale(0.85);\n box-shadow: 0 0 0 4px rgba(239, 68, 68, 0);\n }\n}\n.animate-live-blink {\n animation: live-dot-blink 1.2s ease-in-out infinite;\n}\n.noCursor {\n cursor: none !important;\n}\n.icon-class {\n height: calc(var(--spacing) * 14);\n width: calc(var(--spacing) * 14);\n cursor: pointer;\n color: var(--color-gray-400);\n transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to;\n transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));\n transition-duration: var(--tw-duration, var(--default-transition-duration));\n --tw-duration: 200ms;\n transition-duration: 200ms;\n &:hover {\n @media (hover: hover) {\n color: var(--color-gray-200);\n }\n }\n @media (width >= 64rem) {\n height: calc(var(--spacing) * 18);\n }\n @media (width >= 64rem) {\n width: calc(var(--spacing) * 18);\n }\n}\n@property --tw-translate-x {\n syntax: \"*\";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-translate-y {\n syntax: \"*\";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-translate-z {\n syntax: \"*\";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-rotate-x {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-rotate-y {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-rotate-z {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-skew-x {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-skew-y {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-space-y-reverse {\n syntax: \"*\";\n inherits: false;\n initial-value: 0;\n}\n@property --tw-border-style {\n syntax: \"*\";\n inherits: false;\n initial-value: solid;\n}\n@property --tw-gradient-position {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-gradient-from {\n syntax: \"<color>\";\n inherits: false;\n initial-value: #0000;\n}\n@property --tw-gradient-via {\n syntax: \"<color>\";\n inherits: false;\n initial-value: #0000;\n}\n@property --tw-gradient-to {\n syntax: \"<color>\";\n inherits: false;\n initial-value: #0000;\n}\n@property --tw-gradient-stops {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-gradient-via-stops {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-gradient-from-position {\n syntax: \"<length-percentage>\";\n inherits: false;\n initial-value: 0%;\n}\n@property --tw-gradient-via-position {\n syntax: \"<length-percentage>\";\n inherits: false;\n initial-value: 50%;\n}\n@property --tw-gradient-to-position {\n syntax: \"<length-percentage>\";\n inherits: false;\n initial-value: 100%;\n}\n@property --tw-font-weight {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-tracking {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-shadow {\n syntax: \"*\";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-shadow-color {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-shadow-alpha {\n syntax: \"<percentage>\";\n inherits: false;\n initial-value: 100%;\n}\n@property --tw-inset-shadow {\n syntax: \"*\";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-inset-shadow-color {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-inset-shadow-alpha {\n syntax: \"<percentage>\";\n inherits: false;\n initial-value: 100%;\n}\n@property --tw-ring-color {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-ring-shadow {\n syntax: \"*\";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-inset-ring-color {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-inset-ring-shadow {\n syntax: \"*\";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-ring-inset {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-ring-offset-width {\n syntax: \"<length>\";\n inherits: false;\n initial-value: 0px;\n}\n@property --tw-ring-offset-color {\n syntax: \"*\";\n inherits: false;\n initial-value: #fff;\n}\n@property --tw-ring-offset-shadow {\n syntax: \"*\";\n inherits: false;\n initial-value: 0 0 #0000;\n}\n@property --tw-outline-style {\n syntax: \"*\";\n inherits: false;\n initial-value: solid;\n}\n@property --tw-backdrop-blur {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-brightness {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-contrast {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-grayscale {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-hue-rotate {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-invert {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-opacity {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-saturate {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-backdrop-sepia {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-duration {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-ease {\n syntax: \"*\";\n inherits: false;\n}\n@property --tw-scale-x {\n syntax: \"*\";\n inherits: false;\n initial-value: 1;\n}\n@property --tw-scale-y {\n syntax: \"*\";\n inherits: false;\n initial-value: 1;\n}\n@property --tw-scale-z {\n syntax: \"*\";\n inherits: false;\n initial-value: 1;\n}\n@keyframes spin {\n to {\n transform: rotate(360deg);\n }\n}\n@keyframes pulse {\n 50% {\n opacity: 0.5;\n }\n}\n@layer properties {\n @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {\n *, ::before, ::after, ::backdrop {\n --tw-translate-x: 0;\n --tw-translate-y: 0;\n --tw-translate-z: 0;\n --tw-rotate-x: initial;\n --tw-rotate-y: initial;\n --tw-rotate-z: initial;\n --tw-skew-x: initial;\n --tw-skew-y: initial;\n --tw-space-y-reverse: 0;\n --tw-border-style: solid;\n --tw-gradient-position: initial;\n --tw-gradient-from: #0000;\n --tw-gradient-via: #0000;\n --tw-gradient-to: #0000;\n --tw-gradient-stops: initial;\n --tw-gradient-via-stops: initial;\n --tw-gradient-from-position: 0%;\n --tw-gradient-via-position: 50%;\n --tw-gradient-to-position: 100%;\n --tw-font-weight: initial;\n --tw-tracking: initial;\n --tw-shadow: 0 0 #0000;\n --tw-shadow-color: initial;\n --tw-shadow-alpha: 100%;\n --tw-inset-shadow: 0 0 #0000;\n --tw-inset-shadow-color: initial;\n --tw-inset-shadow-alpha: 100%;\n --tw-ring-color: initial;\n --tw-ring-shadow: 0 0 #0000;\n --tw-inset-ring-color: initial;\n --tw-inset-ring-shadow: 0 0 #0000;\n --tw-ring-inset: initial;\n --tw-ring-offset-width: 0px;\n --tw-ring-offset-color: #fff;\n --tw-ring-offset-shadow: 0 0 #0000;\n --tw-outline-style: solid;\n --tw-backdrop-blur: initial;\n --tw-backdrop-brightness: initial;\n --tw-backdrop-contrast: initial;\n --tw-backdrop-grayscale: initial;\n --tw-backdrop-hue-rotate: initial;\n --tw-backdrop-invert: initial;\n --tw-backdrop-opacity: initial;\n --tw-backdrop-saturate: initial;\n --tw-backdrop-sepia: initial;\n --tw-duration: initial;\n --tw-ease: initial;\n --tw-scale-x: 1;\n --tw-scale-y: 1;\n --tw-scale-z: 1;\n }\n }\n}\n";
40
41
  styleInject(css_248z$4,{"insertAt":"top"});
41
42
 
42
43
  const createVideoRefsSlice = (set) => ({
@@ -120,6 +121,8 @@ const createIntroSlice = (set) => ({
120
121
  const createAdsSlice = (set, get) => ({
121
122
  isAdPlaying: false,
122
123
  setIsAdPlaying: (isAdPlaying) => set({ isAdPlaying }),
124
+ adProvider: null,
125
+ setAdProvider: (adProvider) => set({ adProvider }),
123
126
  currentAd: null,
124
127
  setCurrentAd: (currentAd) => set({ currentAd }),
125
128
  adType: null,
@@ -138,6 +141,16 @@ const createAdsSlice = (set, get) => ({
138
141
  setMidRollQueue: (midRollQueue) => set({ midRollQueue }),
139
142
  adVideoRef: null,
140
143
  setAdVideoRef: (adVideoRef) => set({ adVideoRef }),
144
+ imaAdContainerRef: null,
145
+ setImaAdContainerRef: (imaAdContainerRef) => set({ imaAdContainerRef }),
146
+ imaPlayback: null,
147
+ setImaPlayback: (imaPlayback) => set({ imaPlayback }),
148
+ imaDestroy: null,
149
+ setImaDestroy: (imaDestroy) => set({ imaDestroy }),
150
+ imaSkipEnabled: false,
151
+ setImaSkipEnabled: (imaSkipEnabled) => set({ imaSkipEnabled }),
152
+ imaPreRollGateComplete: false,
153
+ setImaPreRollGateComplete: (imaPreRollGateComplete) => set({ imaPreRollGateComplete }),
141
154
  });
142
155
 
143
156
  const createErrorSlice = (set) => ({
@@ -162,7 +175,8 @@ const createResetSlice = (set, get) => ({
162
175
  media.removeAttribute("src");
163
176
  media.load();
164
177
  };
165
- const { videoRef, adVideoRef, hlsInstance, dashInstance } = get();
178
+ const { videoRef, adVideoRef, hlsInstance, dashInstance, imaDestroy } = get();
179
+ imaDestroy?.();
166
180
  safeStopMediaElement(videoRef);
167
181
  safeStopMediaElement(adVideoRef);
168
182
  if (hlsInstance && typeof hlsInstance.destroy === "function") {
@@ -197,6 +211,7 @@ const createResetSlice = (set, get) => ({
197
211
  autoPlayNext: false,
198
212
  showIntroSkip: false,
199
213
  isAdPlaying: false,
214
+ adProvider: null,
200
215
  currentAd: null,
201
216
  adType: null,
202
217
  adCurrentTime: 0,
@@ -205,6 +220,11 @@ const createResetSlice = (set, get) => ({
205
220
  playedAdBreaks: [],
206
221
  midRollQueue: [],
207
222
  adVideoRef: null,
223
+ imaAdContainerRef: null,
224
+ imaPlayback: null,
225
+ imaDestroy: null,
226
+ imaSkipEnabled: false,
227
+ imaPreRollGateComplete: false,
208
228
  });
209
229
  },
210
230
  });
@@ -735,23 +755,34 @@ const FullScreenToggle = ({ isFullScreen, onClick, className = "fullscreen-icon"
735
755
  React__default.createElement("path", { fill: "currentColor", d: "M4.746 8.056V6.294L18.73 20.278h-1.762a1.111 1.111 0 1 0 0 2.222h4.444a1.11 1.11 0 0 0 1.111-1.111v-4.445a1.111 1.111 0 0 0-2.222 0v1.762L6.317 4.722h1.762a1.111 1.111 0 1 0 0-2.222H3.635c-.614 0-1.112.497-1.112 1.111v4.445a1.111 1.111 0 1 0 2.223 0z" })))));
736
756
  };
737
757
 
738
- const Popover = ({ button, children, closeOnButtonClick = false, className = "", align = "left", }) => {
758
+ const VIEW_MARGIN = 8;
759
+ const GAP_BELOW_TRIGGER = 6;
760
+ const POPOVER_Z = 100060;
761
+ const Popover = ({ button, children, closeOnButtonClick = false, className = "", align = "left", onOpenChange, triggerAriaLabel, }) => {
739
762
  const [isOpen, setIsOpen] = useState(false);
763
+ const [placement, setPlacement] = useState({ top: 0, left: 0, arrowLeft: 0, ready: false });
740
764
  const popoverRef = useRef(null);
741
765
  const buttonRef = useRef(null);
742
766
  useEffect(() => {
767
+ onOpenChange?.(isOpen);
768
+ }, [isOpen, onOpenChange]);
769
+ /** Only while open: closing the menu must not steal pointer events from the panel. */
770
+ useEffect(() => {
771
+ if (!isOpen)
772
+ return;
743
773
  const handleClickOutside = (event) => {
744
- if (popoverRef.current &&
745
- !popoverRef.current.contains(event.target) &&
746
- buttonRef.current &&
747
- !buttonRef.current.contains(event.target)) {
748
- setIsOpen(false);
749
- }
774
+ const panel = popoverRef.current;
775
+ const trigger = buttonRef.current;
776
+ const node = event.target;
777
+ if (!node)
778
+ return;
779
+ if (panel?.contains(node) || trigger?.contains(node))
780
+ return;
781
+ setIsOpen(false);
750
782
  };
751
783
  const handleEscape = (event) => {
752
- if (event.key === "Escape") {
784
+ if (event.key === "Escape")
753
785
  setIsOpen(false);
754
- }
755
786
  };
756
787
  document.addEventListener("mousedown", handleClickOutside);
757
788
  document.addEventListener("keydown", handleEscape);
@@ -759,42 +790,87 @@ const Popover = ({ button, children, closeOnButtonClick = false, className = "",
759
790
  document.removeEventListener("mousedown", handleClickOutside);
760
791
  document.removeEventListener("keydown", handleEscape);
761
792
  };
762
- }, []);
793
+ }, [isOpen]);
794
+ useLayoutEffect(() => {
795
+ if (!isOpen) {
796
+ setPlacement((p) => ({ ...p, ready: false }));
797
+ return;
798
+ }
799
+ let rafAttempts = 0;
800
+ const MAX_RAF_ATTEMPTS = 12;
801
+ const updatePlacement = () => {
802
+ const btn = buttonRef.current;
803
+ const panel = popoverRef.current;
804
+ if (!btn || !panel) {
805
+ if (rafAttempts < MAX_RAF_ATTEMPTS) {
806
+ rafAttempts += 1;
807
+ requestAnimationFrame(updatePlacement);
808
+ }
809
+ return;
810
+ }
811
+ const br = btn.getBoundingClientRect();
812
+ const pr = panel.getBoundingClientRect();
813
+ const vw = window.innerWidth;
814
+ const buttonCenterX = br.left + br.width / 2;
815
+ let left;
816
+ if (align === "center") {
817
+ left = buttonCenterX - pr.width / 2;
818
+ }
819
+ else if (align === "right") {
820
+ left = br.right - pr.width;
821
+ }
822
+ else {
823
+ left = br.left;
824
+ }
825
+ left = Math.max(VIEW_MARGIN, Math.min(left, vw - pr.width - VIEW_MARGIN));
826
+ const top = br.bottom + GAP_BELOW_TRIGGER;
827
+ const arrowLeft = buttonCenterX - left;
828
+ setPlacement({
829
+ top,
830
+ left,
831
+ arrowLeft,
832
+ ready: true,
833
+ });
834
+ };
835
+ updatePlacement();
836
+ const raf = requestAnimationFrame(updatePlacement);
837
+ const ro = new ResizeObserver(() => {
838
+ updatePlacement();
839
+ });
840
+ if (buttonRef.current)
841
+ ro.observe(buttonRef.current);
842
+ if (popoverRef.current)
843
+ ro.observe(popoverRef.current);
844
+ window.addEventListener("resize", updatePlacement);
845
+ window.addEventListener("scroll", updatePlacement, true);
846
+ return () => {
847
+ cancelAnimationFrame(raf);
848
+ ro.disconnect();
849
+ window.removeEventListener("resize", updatePlacement);
850
+ window.removeEventListener("scroll", updatePlacement, true);
851
+ };
852
+ }, [isOpen, align]);
763
853
  const togglePopover = () => {
764
854
  setIsOpen((prev) => (closeOnButtonClick ? !prev : true));
765
855
  };
766
- const getAlignmentClasses = () => {
767
- switch (align) {
768
- case "center":
769
- return "left-1/2 -translate-x-1/2";
770
- case "right":
771
- return "right-0";
772
- case "left":
773
- default:
774
- return "left-0";
775
- }
776
- };
777
- const getArrowPositionClasses = () => {
778
- return "left-1/2 -translate-x-1/2";
779
- };
780
- return (React__default.createElement("div", { className: "relative inline-block" },
781
- React__default.createElement("div", { ref: buttonRef, onClick: togglePopover, tabIndex: 0, role: "button" }, button),
782
- isOpen && (React__default.createElement("div", { ref: popoverRef, className: `absolute ${getAlignmentClasses()} mt-2 w-fit bg-[#3a4049] text-white shadow-2xl rounded-lg border border-white/10 z-50 p-0 transition-all duration-200 ${className}` },
783
- React__default.createElement("div", { className: `absolute -top-2 ${getArrowPositionClasses()} w-3 h-3 bg-[#3a4049] transform rotate-45 border-l border-t border-white/10 z-[-1]` }),
784
- children))));
785
- };
786
-
787
- const Tooltip = ({ children, title, position = "top", className, }) => {
788
- const [visible, setVisible] = useState(false);
789
- const positionStyles = {
790
- top: "bottom-full left-1/2 transform -translate-x-1/2 mb-2",
791
- bottom: "top-full left-1/2 transform -translate-x-1/2 mt-2",
792
- left: "right-full top-1/2 transform -translate-y-1/2 mr-2",
793
- right: "left-full top-1/2 transform -translate-y-1/2 ml-2",
856
+ const stopBubblingToDocument = (e) => {
857
+ e.stopPropagation();
794
858
  };
795
- return (React__default.createElement("div", { className: `relative inline-block cursor-pointer ${className || ""}`, onMouseEnter: () => setVisible(true), onMouseLeave: () => setVisible(false) },
796
- children,
797
- visible && (React__default.createElement("div", { className: `absolute z-50 px-3 py-1 text-sm text-white bg-gray-900 rounded-md shadow-md transition-opacity duration-200 ease-in-out whitespace-nowrap ${positionStyles[position]}` }, title))));
859
+ return (React__default.createElement("div", { className: "relative inline-flex w-max max-w-full shrink-0" },
860
+ React__default.createElement("div", { ref: buttonRef, onClick: togglePopover, tabIndex: 0, role: "button", "aria-label": triggerAriaLabel, "aria-expanded": isOpen, "aria-haspopup": "dialog" }, button),
861
+ isOpen &&
862
+ typeof document !== "undefined" &&
863
+ createPortal(React__default.createElement("div", { ref: popoverRef, role: "dialog", "aria-modal": false, className: `fixed w-fit max-w-[min(calc(100vw-16px),320px)] bg-[#3a4049] text-white shadow-2xl rounded-lg border border-white/10 p-0 transition-opacity duration-200 pointer-events-auto touch-manipulation ${className}`, style: {
864
+ top: placement.top,
865
+ left: placement.left,
866
+ zIndex: POPOVER_Z,
867
+ }, onMouseDown: stopBubblingToDocument, onTouchStart: stopBubblingToDocument },
868
+ React__default.createElement("div", { className: "absolute w-3 h-3 bg-[#3a4049] border-l border-t border-white/10 pointer-events-none", style: {
869
+ top: -6,
870
+ left: placement.ready ? placement.arrowLeft : "50%",
871
+ transform: "translateX(-50%) rotate(45deg)",
872
+ } }),
873
+ children), document.body)));
798
874
  };
799
875
 
800
876
  const speedOptions = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2];
@@ -815,6 +891,11 @@ const Settings = ({ iconClassName, qualityConfig, }) => {
815
891
  };
816
892
  const [speed, setSpeed] = React.useState(getStoredPlaybackSpeed());
817
893
  const [activeMenu, setActiveMenu] = React.useState("main");
894
+ const [settingsMenuOpen, setSettingsMenuOpen] = React.useState(false);
895
+ React.useEffect(() => {
896
+ if (!settingsMenuOpen)
897
+ setActiveMenu("main");
898
+ }, [settingsMenuOpen]);
818
899
  React.useEffect(() => {
819
900
  if (videoRef) {
820
901
  const storedSpeed = getStoredPlaybackSpeed();
@@ -899,14 +980,14 @@ const Settings = ({ iconClassName, qualityConfig, }) => {
899
980
  return "Uses about 0.50 GB per hour";
900
981
  return "Uses about 0.30 GB per hour";
901
982
  };
902
- return (React.createElement(Tooltip, { title: "Settings" },
903
- React.createElement(Popover, { button: React.createElement(Settings$1, { className: iconClassName }), align: "center" },
983
+ return (React.createElement("div", { className: "inline-flex w-max max-w-full shrink-0" },
984
+ React.createElement(Popover, { button: React.createElement(Settings$1, { className: iconClassName }), align: "center", onOpenChange: setSettingsMenuOpen, triggerAriaLabel: "Settings" },
904
985
  React.createElement("div", { className: "bg-[#3a4049] text-white rounded-[7px] w-80 max-w-[min(90vw,320px)] overflow-hidden" },
905
986
  activeMenu === "main" && (React.createElement("div", { className: "p-4" },
906
987
  React.createElement("h3", { className: "text-white font-bold text-xl mb-4" }, "Settings"),
907
988
  React.createElement("p", { className: "text-gray-300 text-sm mb-4" }, "Customize playback"),
908
989
  React.createElement("div", { className: "space-y-0 border-t border-gray-600" },
909
- showQualityInSettings && (React.createElement("button", { onClick: () => setActiveMenu("quality"), className: "w-full flex items-center justify-between py-4 border-b border-gray-600 rounded-[5px] transition-colors" },
990
+ showQualityInSettings && (React.createElement("button", { type: "button", onClick: () => setActiveMenu("quality"), className: "w-full flex items-center justify-between py-4 border-b border-gray-600 rounded-[5px] transition-colors" },
910
991
  React.createElement("div", { className: "flex items-center gap-3" },
911
992
  React.createElement("div", { className: "p-2 bg-blue-500 rounded-md" },
912
993
  React.createElement("svg", { className: "w-5 h-5 text-white", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24" },
@@ -915,7 +996,7 @@ const Settings = ({ iconClassName, qualityConfig, }) => {
915
996
  React.createElement("div", { className: "text-white font-semibold" }, "Quality"),
916
997
  React.createElement("div", { className: "text-gray-400 text-sm" }, getQualityLabel()))),
917
998
  React.createElement(ChevronRight, { className: "w-5 h-5 text-gray-400" }))),
918
- React.createElement("button", { onClick: () => setActiveMenu("subtitles"), className: "w-full flex items-center justify-between py-4 border-b border-gray-600 rounded-[5px] transition-colors" },
999
+ React.createElement("button", { type: "button", onClick: () => setActiveMenu("subtitles"), className: "w-full flex items-center justify-between py-4 border-b border-gray-600 rounded-[5px] transition-colors" },
919
1000
  React.createElement("div", { className: "flex items-center gap-3" },
920
1001
  React.createElement("div", { className: "p-2 bg-purple-500 rounded-md" },
921
1002
  React.createElement("svg", { className: "w-5 h-5 text-white", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24" },
@@ -924,7 +1005,7 @@ const Settings = ({ iconClassName, qualityConfig, }) => {
924
1005
  React.createElement("div", { className: "text-white font-semibold" }, "Subtitles"),
925
1006
  React.createElement("div", { className: "text-gray-400 text-sm" }, !activeSubtitle ? "Off" : activeSubtitle.label))),
926
1007
  React.createElement(ChevronRight, { className: "w-5 h-5 text-gray-400" })),
927
- React.createElement("button", { onClick: () => setActiveMenu("speed"), className: "w-full flex items-center justify-between py-4 rounded-[5px] transition-colors" },
1008
+ React.createElement("button", { type: "button", onClick: () => setActiveMenu("speed"), className: "w-full flex items-center justify-between py-4 rounded-[5px] transition-colors" },
928
1009
  React.createElement("div", { className: "flex items-center gap-3" },
929
1010
  React.createElement("div", { className: "p-2 bg-green-500 rounded-md" },
930
1011
  React.createElement("svg", { className: "w-5 h-5 text-white", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24" },
@@ -937,11 +1018,11 @@ const Settings = ({ iconClassName, qualityConfig, }) => {
937
1018
  React.createElement(ChevronRight, { className: "w-5 h-5 text-gray-400" }))))),
938
1019
  activeMenu === "quality" && (React.createElement("div", { className: "p-4 flex flex-col max-h-[min(80vh,480px)]" },
939
1020
  React.createElement("div", { className: "flex items-center gap-3 mb-4 shrink-0" },
940
- React.createElement("button", { onClick: handleBack, className: "p-1 rounded-md transition-colors hover:bg-white/10" },
1021
+ React.createElement("button", { type: "button", onClick: handleBack, className: "p-1 rounded-md transition-colors hover:bg-white/10" },
941
1022
  React.createElement(ChevronRight, { className: "w-6 h-6 text-white rotate-180" })),
942
1023
  React.createElement("h3", { className: "text-white font-bold text-xl" }, "Video Quality")),
943
1024
  React.createElement("div", { className: "space-y-3 overflow-y-auto min-h-0 flex-1 pr-1 -mr-1" }, hasQualityOptions ? (React.createElement(React.Fragment, null,
944
- React.createElement("button", { onClick: () => QualityManager.setQuality(streamType, "auto"), className: `w-full text-left px-4 py-3 rounded-md transition-all ${activeQuality === "auto"
1025
+ React.createElement("button", { type: "button", onClick: () => QualityManager.setQuality(streamType, "auto"), className: `w-full text-left px-4 py-3 rounded-md transition-all ${activeQuality === "auto"
945
1026
  ? "bg-white/10"
946
1027
  : "hover:bg-white/5"}` },
947
1028
  React.createElement("div", { className: "flex items-start justify-between" },
@@ -949,44 +1030,44 @@ const Settings = ({ iconClassName, qualityConfig, }) => {
949
1030
  React.createElement("div", { className: "text-white font-semibold text-lg mb-1" }, "Auto"),
950
1031
  React.createElement("div", { className: "text-gray-400 text-sm" }, "Adjust to your connection")),
951
1032
  activeQuality === "auto" && (React.createElement(Check, { className: "w-6 h-6 text-white mt-1" })))),
952
- qualityOptions.map((level) => (React.createElement("button", { key: level.value, onClick: () => QualityManager.setQuality(streamType, level.value), className: `w-full text-left px-4 py-3 rounded-md transition-all ${activeQuality === level.value
1033
+ qualityOptions.map((level) => (React.createElement("button", { type: "button", key: level.value, onClick: () => QualityManager.setQuality(streamType, level.value), className: `w-full text-left px-4 py-3 rounded-md transition-all ${activeQuality === level.value
953
1034
  ? "bg-white/10"
954
1035
  : "hover:bg-white/5"}` },
955
1036
  React.createElement("div", { className: "flex items-start justify-between" },
956
1037
  React.createElement("div", null,
957
1038
  React.createElement("div", { className: "text-white font-semibold text-lg mb-1" }, getQualityName(level.height, level.bitrate)),
958
1039
  React.createElement("div", { className: "text-gray-400 text-sm" }, getDataUsage(level.height, level.bitrate))),
959
- activeQuality === level.value && (React.createElement(Check, { className: "w-6 h-6 text-white mt-1" })))))))) : (React.createElement("button", { className: "w-full text-left px-4 py-3 rounded-md bg-white/10 cursor-default" },
1040
+ activeQuality === level.value && (React.createElement(Check, { className: "w-6 h-6 text-white mt-1" })))))))) : (React.createElement("button", { type: "button", className: "w-full text-left px-4 py-3 rounded-md bg-white/10 cursor-default" },
960
1041
  React.createElement("div", { className: "flex items-start justify-between" },
961
1042
  React.createElement("span", { className: "text-white font-semibold text-lg" }, "Off"),
962
1043
  React.createElement(Check, { className: "w-6 h-6 text-white mt-1" }))))))),
963
1044
  activeMenu === "subtitles" && (React.createElement("div", { className: "p-4" },
964
1045
  React.createElement("div", { className: "flex items-center gap-3 mb-4" },
965
- React.createElement("button", { onClick: handleBack, className: "p-1 hover:bg-white/10 rounded-md transition-colors" },
1046
+ React.createElement("button", { type: "button", onClick: handleBack, className: "p-1 hover:bg-white/10 rounded-md transition-colors" },
966
1047
  React.createElement(ChevronRight, { className: "w-6 h-6 text-white rotate-180" })),
967
1048
  React.createElement("h3", { className: "text-white font-bold text-xl" }, "Subtitles")),
968
1049
  React.createElement("div", { className: "space-y-3" },
969
- React.createElement("button", { onClick: () => setActiveSubtitle(null), className: `w-full text-left px-4 py-3 rounded-[5px] transition-all flex items-center justify-between ${!activeSubtitle ? "bg-[#454545]" : ""}` },
1050
+ React.createElement("button", { type: "button", onClick: () => setActiveSubtitle(null), className: `w-full text-left px-4 py-3 rounded-[5px] transition-all flex items-center justify-between ${!activeSubtitle ? "bg-[#454545]" : ""}` },
970
1051
  React.createElement("span", { className: "text-white font-semibold text-lg" }, "Off"),
971
1052
  !activeSubtitle && (React.createElement(Check, { className: "w-6 h-6 text-white" }))),
972
- subtitles?.map((subtitle, index) => (React.createElement("button", { key: index, onClick: () => setActiveSubtitle(subtitle), className: `w-full text-left px-4 py-3 rounded-md transition-all flex items-center justify-between ${activeSubtitle?.label === subtitle.label
1053
+ subtitles?.map((subtitle, index) => (React.createElement("button", { type: "button", key: index, onClick: () => setActiveSubtitle(subtitle), className: `w-full text-left px-4 py-3 rounded-md transition-all flex items-center justify-between ${activeSubtitle?.label === subtitle.label
973
1054
  ? "bg-white/10"
974
1055
  : "hover:bg-white/5"}` },
975
1056
  React.createElement("span", { className: "text-white font-semibold text-lg" }, subtitle.label),
976
1057
  activeSubtitle?.label === subtitle.label && (React.createElement(Check, { className: "w-6 h-6 text-white" })))))))),
977
1058
  activeMenu === "speed" && (React.createElement("div", { className: "p-4" },
978
1059
  React.createElement("div", { className: "flex items-center gap-3 mb-4" },
979
- React.createElement("button", { onClick: handleBack, className: "p-1 hover:bg-white/10 rounded-md transition-colors" },
1060
+ React.createElement("button", { type: "button", onClick: handleBack, className: "p-1 hover:bg-white/10 rounded-md transition-colors" },
980
1061
  React.createElement(ChevronRight, { className: "w-6 h-6 text-white rotate-180" })),
981
1062
  React.createElement("h3", { className: "text-white font-bold text-xl" }, "Playback Speed")),
982
- React.createElement("div", { className: "space-y-3 max-h-80 overflow-y-auto" }, speedOptions.map((s) => (React.createElement("button", { key: s, onClick: () => handleSpeedChange(s), className: `w-full text-left px-4 py-3 rounded-[5px] transition-all flex items-center justify-between ${speed === s ? "bg-[#454545]" : ""}` },
1063
+ React.createElement("div", { className: "space-y-3 max-h-80 overflow-y-auto" }, speedOptions.map((s) => (React.createElement("button", { type: "button", key: s, onClick: () => handleSpeedChange(s), className: `w-full text-left px-4 py-3 rounded-[5px] transition-all flex items-center justify-between ${speed === s ? "bg-[#454545]" : ""}` },
983
1064
  React.createElement("span", { className: "text-white font-semibold text-lg" }, s === 1 ? "Normal" : `${s}x`),
984
1065
  speed === s && (React.createElement(Check, { className: "w-6 h-6 text-white" }))))))))))));
985
1066
  };
986
1067
 
987
1068
  const ControlsHeader = ({ config }) => {
988
1069
  const iconClassName = "icon-button";
989
- const { videoWrapperRef, videoRef, adVideoRef, episodeList, currentEpisodeIndex, resetStore, isAdPlaying, muted, setMuted, adCurrentTime, } = useVideoStore(useShallow((state) => ({
1070
+ const { videoWrapperRef, videoRef, adVideoRef, episodeList, currentEpisodeIndex, resetStore, isAdPlaying, adProvider, imaPlayback, muted, setMuted, adCurrentTime, } = useVideoStore(useShallow((state) => ({
990
1071
  videoWrapperRef: state.videoWrapperRef,
991
1072
  videoRef: state.videoRef,
992
1073
  adVideoRef: state.adVideoRef,
@@ -994,13 +1075,24 @@ const ControlsHeader = ({ config }) => {
994
1075
  currentEpisodeIndex: state.currentEpisodeIndex,
995
1076
  resetStore: state.resetStore,
996
1077
  isAdPlaying: state.isAdPlaying,
1078
+ adProvider: state.adProvider,
1079
+ imaPlayback: state.imaPlayback,
997
1080
  muted: state.muted,
998
1081
  setMuted: state.setMuted,
999
1082
  adCurrentTime: state.adCurrentTime,
1000
1083
  })));
1001
1084
  const [adDuration, setAdDuration] = React.useState(0);
1002
1085
  React.useEffect(() => {
1003
- if (!adVideoRef || !isAdPlaying) {
1086
+ if (!isAdPlaying) {
1087
+ setAdDuration(0);
1088
+ return;
1089
+ }
1090
+ if (adProvider === "ima" && imaPlayback) {
1091
+ const duration = imaPlayback.getDuration();
1092
+ setAdDuration(Number.isFinite(duration) && duration > 0 ? duration : 0);
1093
+ return;
1094
+ }
1095
+ if (!adVideoRef) {
1004
1096
  setAdDuration(0);
1005
1097
  return;
1006
1098
  }
@@ -1016,7 +1108,7 @@ const ControlsHeader = ({ config }) => {
1016
1108
  adVideoRef.removeEventListener("loadedmetadata", updateDuration);
1017
1109
  adVideoRef.removeEventListener("durationchange", updateDuration);
1018
1110
  };
1019
- }, [adVideoRef, isAdPlaying]);
1111
+ }, [adVideoRef, isAdPlaying, adProvider, imaPlayback]);
1020
1112
  const formatTime = React.useCallback((seconds) => {
1021
1113
  if (isNaN(seconds) || seconds < 0)
1022
1114
  return "0:00";
@@ -1051,6 +1143,16 @@ const ControlsHeader = ({ config }) => {
1051
1143
  };
1052
1144
  }, []);
1053
1145
  const handleMute = () => {
1146
+ if (isAdPlaying && adProvider === "ima" && imaPlayback) {
1147
+ const currentVolume = imaPlayback.getVolume();
1148
+ const nextMuted = currentVolume > 0;
1149
+ imaPlayback.setVolume(nextMuted ? 0 : 1);
1150
+ if (videoRef) {
1151
+ videoRef.muted = nextMuted;
1152
+ }
1153
+ setMuted(nextMuted);
1154
+ return;
1155
+ }
1054
1156
  const targetElement = isAdPlaying ? adVideoRef ?? videoRef : videoRef;
1055
1157
  if (!targetElement)
1056
1158
  return;
@@ -1081,17 +1183,13 @@ const ControlsHeader = ({ config }) => {
1081
1183
  isAdPlaying ? renderAdHeader() : renderVideoHeader(),
1082
1184
  React.createElement("div", { className: "flex items-center gap-7 text-white" },
1083
1185
  !isAdPlaying && (React.createElement(Settings, { iconClassName: iconClassName, qualityConfig: config?.qualityConfig })),
1084
- React.createElement("div", { onClick: handleMute }, muted ? (React.createElement(Tooltip, { title: "Unmute" },
1085
- React.createElement(IoVolumeMuteOutline, { className: iconClassName }))) : (React.createElement(Tooltip, { title: "Mute" },
1086
- React.createElement(IoVolumeHighOutline, { className: iconClassName })))),
1087
- React.createElement(Tooltip, { title: isFullscreen ? "Exit Fullscreen" : "Fullscreen", className: iconClassName },
1088
- React.createElement("div", { onClick: handleFullscreen },
1089
- React.createElement(FullScreenToggle, { isFullScreen: isFullscreen, className: iconClassName }))),
1186
+ React.createElement("div", { onClick: handleMute, role: "button", tabIndex: 0, "aria-label": muted ? "Unmute" : "Mute" }, muted ? (React.createElement(IoVolumeMuteOutline, { className: iconClassName })) : (React.createElement(IoVolumeHighOutline, { className: iconClassName }))),
1187
+ React.createElement("div", { onClick: handleFullscreen, role: "button", tabIndex: 0, "aria-label": isFullscreen ? "Exit fullscreen" : "Fullscreen" },
1188
+ React.createElement(FullScreenToggle, { isFullScreen: isFullscreen, className: iconClassName })),
1090
1189
  config?.onClose && (React.createElement(React.Fragment, null,
1091
1190
  React.createElement("div", { className: "w-[2px] h-10 bg-gray-500 hover:bg-gray-300 mx-2" }),
1092
- React.createElement("div", { onClick: handleClose },
1093
- React.createElement(Tooltip, { title: "Close" },
1094
- React.createElement(IoMdClose, { className: iconClassName }))))))));
1191
+ React.createElement("div", { onClick: handleClose, role: "button", tabIndex: 0, "aria-label": "Close" },
1192
+ React.createElement(IoMdClose, { className: iconClassName })))))));
1095
1193
  };
1096
1194
 
1097
1195
  const CONTROL_INTERACTION_EVENT = "video-controls:interaction";
@@ -1120,18 +1218,21 @@ const MiddleControls = ({ config }) => {
1120
1218
  borderRadius: ppConfig?.borderRadius ?? "50%",
1121
1219
  padding: ppConfig?.padding,
1122
1220
  }), [ppConfig?.backgroundColor, ppConfig?.borderRadius, ppConfig?.padding]);
1123
- const { videoRef, adVideoRef, isPlaying, setIsPlaying, isAdPlaying } = useVideoStore(useShallow((state) => ({
1221
+ const { videoRef, adVideoRef, isPlaying, setIsPlaying, isAdPlaying, adProvider, imaPlayback, } = useVideoStore(useShallow((state) => ({
1124
1222
  videoRef: state.videoRef,
1125
1223
  adVideoRef: state.adVideoRef,
1126
1224
  isPlaying: state.isPlaying,
1127
1225
  setIsPlaying: state.setIsPlaying,
1128
1226
  isAdPlaying: state.isAdPlaying,
1227
+ adProvider: state.adProvider,
1228
+ imaPlayback: state.imaPlayback,
1129
1229
  })));
1130
1230
  const { setIsBuffering } = useVideoStore(useShallow((state) => ({
1131
1231
  setIsBuffering: state.setIsBuffering,
1132
1232
  })));
1133
1233
  const [isBuffering, setIsBufferingLocal] = useState(false);
1134
- const videoElement = isAdPlaying ? adVideoRef : videoRef;
1234
+ const isImaAd = isAdPlaying && adProvider === "ima";
1235
+ const videoElement = isAdPlaying && !isImaAd ? adVideoRef : videoRef;
1135
1236
  const resetControlsVisibility = useCallback(() => {
1136
1237
  if (typeof window === "undefined") {
1137
1238
  return;
@@ -1139,6 +1240,18 @@ const MiddleControls = ({ config }) => {
1139
1240
  window.dispatchEvent(new Event(CONTROL_INTERACTION_EVENT));
1140
1241
  }, []);
1141
1242
  const handlePlayPause = useCallback(() => {
1243
+ if (isImaAd && imaPlayback) {
1244
+ if (isPlaying) {
1245
+ imaPlayback.pause();
1246
+ setIsPlaying(false);
1247
+ }
1248
+ else {
1249
+ imaPlayback.resume();
1250
+ setIsPlaying(true);
1251
+ }
1252
+ resetControlsVisibility();
1253
+ return;
1254
+ }
1142
1255
  if (!videoElement)
1143
1256
  return;
1144
1257
  if (videoElement.paused) {
@@ -1155,7 +1268,14 @@ const MiddleControls = ({ config }) => {
1155
1268
  setIsPlaying(false);
1156
1269
  resetControlsVisibility();
1157
1270
  }
1158
- }, [videoElement, setIsPlaying, resetControlsVisibility]);
1271
+ }, [
1272
+ isImaAd,
1273
+ imaPlayback,
1274
+ isPlaying,
1275
+ videoElement,
1276
+ setIsPlaying,
1277
+ resetControlsVisibility,
1278
+ ]);
1159
1279
  const handleBackward = useCallback(() => {
1160
1280
  if (!videoElement)
1161
1281
  return;
@@ -1169,7 +1289,7 @@ const MiddleControls = ({ config }) => {
1169
1289
  resetControlsVisibility();
1170
1290
  }, [videoElement, resetControlsVisibility]);
1171
1291
  useEffect(() => {
1172
- if (!videoElement)
1292
+ if (isImaAd || !videoElement)
1173
1293
  return;
1174
1294
  const handleWaiting = () => {
1175
1295
  setIsBufferingLocal(true);
@@ -1197,7 +1317,7 @@ const MiddleControls = ({ config }) => {
1197
1317
  videoElement.removeEventListener("canplay", handleCanPlay);
1198
1318
  videoElement.removeEventListener("stalled", handleStalled);
1199
1319
  };
1200
- }, [videoElement, isAdPlaying, setIsBuffering]);
1320
+ }, [videoElement, isAdPlaying, isImaAd, setIsBuffering]);
1201
1321
  useEffect(() => {
1202
1322
  const handleKeyDown = (e) => {
1203
1323
  if (!videoElement || isAdPlaying)
@@ -2316,6 +2436,12 @@ const useVideoEvents = () => {
2316
2436
  };
2317
2437
  const onPlay = () => {
2318
2438
  const state = useVideoStore.getState();
2439
+ if (state.adProvider === "ima") {
2440
+ if (!state.isPlaying) {
2441
+ setIsPlaying(true);
2442
+ }
2443
+ return;
2444
+ }
2319
2445
  if (state.adVideoRef) {
2320
2446
  // Defensive guard: ensure any ad media tears down before the primary stream resumes so stray audio cannot continue.
2321
2447
  stopMediaElement(state.adVideoRef);
@@ -2370,15 +2496,31 @@ const useVideoEvents = () => {
2370
2496
  };
2371
2497
  };
2372
2498
 
2499
+ const isValidMidRollAd = (ad) => Boolean(ad &&
2500
+ typeof ad.time === "number" &&
2501
+ ad.time >= 0 &&
2502
+ typeof ad.id === "string" &&
2503
+ ad.id.trim() !== "" &&
2504
+ typeof ad.adUrl === "string" &&
2505
+ ad.adUrl.trim() !== "" &&
2506
+ ad.type === "mid-roll");
2507
+ const normalizeMidRollQueue = (midRoll) => {
2508
+ if (!midRoll?.length)
2509
+ return [];
2510
+ const valid = midRoll.filter(isValidMidRollAd);
2511
+ if (!valid.length)
2512
+ return [];
2513
+ const sorted = [...valid].sort((a, b) => a.time - b.time);
2514
+ return sorted.filter((ad, index, self) => index === self.findIndex((item) => item.id === ad.id));
2515
+ };
2373
2516
  const useAdManager = (adConfig) => {
2374
- const { videoRef, setPlaying, setIsPlaying, currentTime, duration, isAdPlaying, setIsAdPlaying, currentAd, setCurrentAd, adType, setAdType, adVideoRef, setAdVideoRef, setAdCurrentTime, setCanSkipAd, setSkipCountdown, playedAdBreaks, addPlayedAdBreak, midRollQueue, setMidRollQueue, } = useVideoStore(useShallow((state) => ({
2517
+ const { videoRef, setPlaying, setIsPlaying, isAdPlaying, setIsAdPlaying, setAdProvider, currentAd, setCurrentAd, adType, setAdType, adVideoRef, setAdVideoRef, setAdCurrentTime, setCanSkipAd, setSkipCountdown, playedAdBreaks, addPlayedAdBreak, midRollQueue, setMidRollQueue, } = useVideoStore(useShallow((state) => ({
2375
2518
  videoRef: state.videoRef,
2376
2519
  setPlaying: state.setPlaying,
2377
2520
  setIsPlaying: state.setIsPlaying,
2378
- currentTime: state.currentTime,
2379
- duration: state.duration,
2380
2521
  isAdPlaying: state.isAdPlaying,
2381
2522
  setIsAdPlaying: state.setIsAdPlaying,
2523
+ setAdProvider: state.setAdProvider,
2382
2524
  currentAd: state.currentAd,
2383
2525
  setCurrentAd: state.setCurrentAd,
2384
2526
  adType: state.adType,
@@ -2417,33 +2559,8 @@ const useAdManager = (adConfig) => {
2417
2559
  media.load();
2418
2560
  }, []);
2419
2561
  useEffect(() => {
2420
- if (!adConfig?.midRoll || adConfig.midRoll.length === 0) {
2421
- setMidRollQueue([]);
2422
- return;
2423
- }
2424
- // Filter out invalid ads and ensure all required fields are present
2425
- const validAds = adConfig.midRoll.filter((ad) => ad &&
2426
- typeof ad.time === "number" &&
2427
- ad.time >= 0 &&
2428
- ad.time < Number.MAX_SAFE_INTEGER &&
2429
- typeof ad.id === "string" &&
2430
- ad.id.trim() !== "" &&
2431
- typeof ad.adUrl === "string" &&
2432
- ad.adUrl.trim() !== "" &&
2433
- typeof ad.type === "string" &&
2434
- ad.type === "mid-roll");
2435
- if (validAds.length === 0) {
2436
- setMidRollQueue([]);
2437
- return;
2438
- }
2439
- // Sort ads by time to ensure they play in order
2440
- const sortedMidRolls = [...validAds].sort((a, b) => a.time - b.time);
2441
- // Remove duplicate IDs (keep first occurrence)
2442
- const uniqueAds = sortedMidRolls.filter((ad, index, self) => index === self.findIndex((a) => a.id === ad.id));
2443
- setMidRollQueue(uniqueAds);
2562
+ setMidRollQueue(normalizeMidRollQueue(adConfig?.midRoll));
2444
2563
  }, [adConfig?.midRoll, setMidRollQueue]);
2445
- // Removed smartPlacement - users should configure exact ad times
2446
- // This ensures ads appear exactly when specified
2447
2564
  const playPreRollAd = async () => {
2448
2565
  if (!adConfig?.preRoll || preRollPlayedRef.current || !videoRef)
2449
2566
  return;
@@ -2455,6 +2572,7 @@ const useAdManager = (adConfig) => {
2455
2572
  setPlaying(false);
2456
2573
  setIsPlaying(false);
2457
2574
  setIsAdPlaying(true);
2575
+ setAdProvider("custom");
2458
2576
  setCurrentAd(adBreak);
2459
2577
  setAdType("pre-roll");
2460
2578
  adConfig.onAdStart?.(adBreak);
@@ -2477,6 +2595,7 @@ const useAdManager = (adConfig) => {
2477
2595
  resumeAfterAdRef.current = wasPlaying;
2478
2596
  stopMediaElement(useVideoStore.getState().adVideoRef);
2479
2597
  setIsAdPlaying(true);
2598
+ setAdProvider("custom");
2480
2599
  setCurrentAd(adBreak);
2481
2600
  setAdType("mid-roll");
2482
2601
  adConfig?.onAdStart?.(adBreak);
@@ -2507,6 +2626,7 @@ const useAdManager = (adConfig) => {
2507
2626
  resumeAfterAdRef.current = false;
2508
2627
  stopMediaElement(useVideoStore.getState().adVideoRef);
2509
2628
  setIsAdPlaying(true);
2629
+ setAdProvider("custom");
2510
2630
  setCurrentAd(adBreak);
2511
2631
  setAdType("post-roll");
2512
2632
  adConfig.onAdStart?.(adBreak);
@@ -2527,6 +2647,7 @@ const useAdManager = (adConfig) => {
2527
2647
  }
2528
2648
  // Reset ad state
2529
2649
  setIsAdPlaying(false);
2650
+ setAdProvider(null);
2530
2651
  setCurrentAd(null);
2531
2652
  setAdType(null);
2532
2653
  setAdCurrentTime(0);
@@ -2555,6 +2676,7 @@ const useAdManager = (adConfig) => {
2555
2676
  }, [
2556
2677
  adConfig,
2557
2678
  setIsAdPlaying,
2679
+ setAdProvider,
2558
2680
  setCurrentAd,
2559
2681
  setAdType,
2560
2682
  setAdCurrentTime,
@@ -2566,7 +2688,7 @@ const useAdManager = (adConfig) => {
2566
2688
  stopMediaElement,
2567
2689
  ]);
2568
2690
  const skipAd = () => {
2569
- if (!currentAd || !currentAd.skipable)
2691
+ if (!currentAd?.skipable)
2570
2692
  return;
2571
2693
  adConfig?.onAdSkip?.(currentAd);
2572
2694
  endAd();
@@ -2615,6 +2737,9 @@ const useAdManager = (adConfig) => {
2615
2737
  useEffect(() => {
2616
2738
  if (!videoRef || !adConfig?.preRoll || preRollPlayedRef.current)
2617
2739
  return;
2740
+ const imaPreRollEnabled = Boolean(adConfig?.ima?.adTagUrl) && adConfig.ima?.preRoll !== false;
2741
+ if (imaPreRollEnabled)
2742
+ return;
2618
2743
  const handleCanPlay = () => {
2619
2744
  playPreRollAd();
2620
2745
  };
@@ -2710,6 +2835,9 @@ const useAdManager = (adConfig) => {
2710
2835
  useEffect(() => {
2711
2836
  if (!videoRef || !adConfig?.postRoll || postRollPlayedRef.current)
2712
2837
  return;
2838
+ const imaPostRollEnabled = Boolean(adConfig?.ima?.adTagUrl) && adConfig.ima?.postRoll !== false;
2839
+ if (imaPostRollEnabled)
2840
+ return;
2713
2841
  const handleVideoEnded = () => {
2714
2842
  setTimeout(() => {
2715
2843
  playPostRollAd();
@@ -2735,33 +2863,10 @@ const useAdManager = (adConfig) => {
2735
2863
  adCheckThrottleRef.current = null;
2736
2864
  }
2737
2865
  setIsAdPlaying(false);
2866
+ setAdProvider(null);
2738
2867
  setCurrentAd(null);
2739
2868
  setAdType(null);
2740
- // Re-initialize mid-roll queue with strict validation
2741
- if (adConfig?.midRoll && adConfig.midRoll.length > 0) {
2742
- // Filter and validate ads
2743
- const validAds = adConfig.midRoll.filter((ad) => ad &&
2744
- typeof ad.time === "number" &&
2745
- ad.time >= 0 &&
2746
- typeof ad.id === "string" &&
2747
- ad.id.trim() !== "" &&
2748
- typeof ad.adUrl === "string" &&
2749
- ad.adUrl.trim() !== "" &&
2750
- typeof ad.type === "string" &&
2751
- ad.type === "mid-roll");
2752
- if (validAds.length > 0) {
2753
- // Sort by time and remove duplicates
2754
- const sortedMidRolls = [...validAds].sort((a, b) => a.time - b.time);
2755
- const uniqueAds = sortedMidRolls.filter((ad, index, self) => index === self.findIndex((a) => a.id === ad.id));
2756
- setMidRollQueue(uniqueAds);
2757
- }
2758
- else {
2759
- setMidRollQueue([]);
2760
- }
2761
- }
2762
- else {
2763
- setMidRollQueue([]);
2764
- }
2869
+ setMidRollQueue(normalizeMidRollQueue(adConfig?.midRoll));
2765
2870
  // Clean up any lingering ad video
2766
2871
  const lingeringAdRef = useVideoStore.getState().adVideoRef;
2767
2872
  if (lingeringAdRef) {
@@ -2772,6 +2877,7 @@ const useAdManager = (adConfig) => {
2772
2877
  videoRef?.src,
2773
2878
  adConfig?.midRoll,
2774
2879
  setIsAdPlaying,
2880
+ setAdProvider,
2775
2881
  setCurrentAd,
2776
2882
  setAdType,
2777
2883
  setMidRollQueue,
@@ -2787,13 +2893,1047 @@ const useAdManager = (adConfig) => {
2787
2893
  };
2788
2894
  };
2789
2895
 
2790
- const usePrimaryVideoLifecycle = ({ hasPreRoll, trackSrc, }) => {
2791
- const { videoRef, setVideoRef, isAdPlaying, currentAd, adType, setMuted, setPlaying, setIsPlaying, } = useVideoStore(useShallow((state) => ({
2896
+ const createImaRenderingSettings = () => {
2897
+ const settings = new google.ima.AdsRenderingSettings();
2898
+ settings.restoreCustomPlaybackStateOnAdBreakComplete = true;
2899
+ settings.useStyledLinearAds = false;
2900
+ settings.uiElements = [];
2901
+ return settings;
2902
+ };
2903
+
2904
+ const IMA_SDK_URL = "https://imasdk.googleapis.com/js/sdkloader/ima3.js";
2905
+ const loadPromises = new Map();
2906
+ const isImaSdkLoaded = () => typeof window !== "undefined" && typeof google !== "undefined" && !!google.ima;
2907
+ const isScriptLoaded = (script) => script.dataset.zezoImaLoaded === "true" || isImaSdkLoaded();
2908
+ /**
2909
+ * Loads the Google IMA HTML5 SDK once per URL per page.
2910
+ */
2911
+ const loadImaSdk = (sdkUrl = IMA_SDK_URL) => {
2912
+ if (typeof window === "undefined") {
2913
+ return Promise.reject(new Error("IMA SDK can only load in a browser"));
2914
+ }
2915
+ const url = sdkUrl.trim() || IMA_SDK_URL;
2916
+ if (isImaSdkLoaded()) {
2917
+ return Promise.resolve();
2918
+ }
2919
+ const existing = loadPromises.get(url);
2920
+ if (existing) {
2921
+ return existing;
2922
+ }
2923
+ const promise = new Promise((resolve, reject) => {
2924
+ const selector = `script[data-zezo-ima-sdk="${url}"]`;
2925
+ const scriptEl = document.querySelector(selector);
2926
+ if (scriptEl) {
2927
+ if (isScriptLoaded(scriptEl)) {
2928
+ resolve();
2929
+ return;
2930
+ }
2931
+ scriptEl.addEventListener("load", () => resolve(), { once: true });
2932
+ scriptEl.addEventListener("error", () => reject(new Error("Failed to load Google IMA SDK")), { once: true });
2933
+ return;
2934
+ }
2935
+ const script = document.createElement("script");
2936
+ script.src = url;
2937
+ script.async = true;
2938
+ script.dataset.zezoImaSdk = url;
2939
+ script.onload = () => {
2940
+ script.dataset.zezoImaLoaded = "true";
2941
+ resolve();
2942
+ };
2943
+ script.onerror = () => {
2944
+ loadPromises.delete(url);
2945
+ reject(new Error("Failed to load Google IMA SDK"));
2946
+ };
2947
+ document.head.appendChild(script);
2948
+ });
2949
+ loadPromises.set(url, promise);
2950
+ return promise;
2951
+ };
2952
+ /**
2953
+ * Fire-and-forget IMA SDK preload. Safe to call before the player mounts.
2954
+ */
2955
+ const preloadImaSdk = (sdkUrl) => {
2956
+ loadImaSdk(sdkUrl).catch(() => undefined);
2957
+ };
2958
+
2959
+ /**
2960
+ * Synthetic ad break used for IMA callbacks and UI state (no direct MP4 URL).
2961
+ */
2962
+ const createImaAdBreak = (type, id) => ({
2963
+ id: id ?? `ima-${type}-${Date.now()}`,
2964
+ type,
2965
+ time: 0,
2966
+ adUrl: "",
2967
+ provider: "ima",
2968
+ skipable: true,
2969
+ });
2970
+
2971
+ /** Syncs ad progress + skip countdown from the active IMA AdsManager. */
2972
+ const syncImaAdUi = (manager, currentAd, adDurationSeconds, setters) => {
2973
+ const remaining = manager.getRemainingTime();
2974
+ const elapsed = adDurationSeconds > 0 ? Math.max(0, adDurationSeconds - remaining) : 0;
2975
+ setters.setAdCurrentTime(elapsed);
2976
+ const canSkipNow = manager.getAdSkippableState();
2977
+ const skipOffset = currentAd && typeof currentAd.getSkipTimeOffset === "function"
2978
+ ? currentAd.getSkipTimeOffset()
2979
+ : -1;
2980
+ const isSkippableAd = canSkipNow || (Number.isFinite(skipOffset) && skipOffset >= 0);
2981
+ setters.setImaSkipEnabled(isSkippableAd);
2982
+ setters.setCanSkipAd(canSkipNow);
2983
+ if (!isSkippableAd || canSkipNow) {
2984
+ setters.setSkipCountdown(0);
2985
+ return;
2986
+ }
2987
+ setters.setSkipCountdown(Math.max(0, Math.ceil(skipOffset - elapsed)));
2988
+ };
2989
+
2990
+ /** Selectors for IMA chrome we hide — skip controls are excluded (clicked programmatically). */
2991
+ const HIDE_SELECTORS = [
2992
+ ".ima-controls-div",
2993
+ ".ima-countdown-div",
2994
+ ".ima-seek-bar-div",
2995
+ ".ima-mute-div",
2996
+ ".ima-fullscreen-div",
2997
+ ".videoAdUiAttribution",
2998
+ ".videoAdUiLearnMore",
2999
+ ".videoAdUiTopBar",
3000
+ ".videoAdUiBottomBar",
3001
+ ".videoAdUiPreSkipButton",
3002
+ ];
3003
+ const hide = (el) => {
3004
+ el.style.setProperty("display", "none", "important");
3005
+ el.style.setProperty("visibility", "hidden", "important");
3006
+ el.style.setProperty("pointer-events", "none", "important");
3007
+ };
3008
+ /** Visually hides skip UI but keeps it in the DOM so programmatic click works. */
3009
+ const tuckAwaySkip = (el) => {
3010
+ el.style.setProperty("position", "fixed", "important");
3011
+ el.style.setProperty("left", "-9999px", "important");
3012
+ el.style.setProperty("top", "-9999px", "important");
3013
+ el.style.setProperty("width", "1px", "important");
3014
+ el.style.setProperty("height", "1px", "important");
3015
+ el.style.setProperty("opacity", "0", "important");
3016
+ el.style.setProperty("overflow", "hidden", "important");
3017
+ el.style.setProperty("pointer-events", "auto", "important");
3018
+ };
3019
+ const isSkipElement = (el) => {
3020
+ const label = el.getAttribute("aria-label") ?? el.textContent ?? "";
3021
+ return (el.classList.contains("videoAdUiSkipButton") ||
3022
+ el.classList.contains("videoAdUiSkipContainer") ||
3023
+ /skip(\s+ad)?/i.test(label.trim()));
3024
+ };
3025
+ const suppressInRoot = (root) => {
3026
+ for (const selector of HIDE_SELECTORS) {
3027
+ root.querySelectorAll(selector).forEach(hide);
3028
+ }
3029
+ root.querySelectorAll('[class*="videoAdUi"]').forEach((el) => {
3030
+ if (isSkipElement(el)) {
3031
+ tuckAwaySkip(el);
3032
+ }
3033
+ else if (!el.classList.contains("videoAdUiSkipButton") &&
3034
+ !el.classList.contains("videoAdUiSkipContainer")) {
3035
+ hide(el);
3036
+ }
3037
+ });
3038
+ root.querySelectorAll("iframe").forEach((iframe) => {
3039
+ try {
3040
+ if (iframe.contentDocument?.body) {
3041
+ suppressInRoot(iframe.contentDocument.body);
3042
+ }
3043
+ }
3044
+ catch {
3045
+ /* cross-origin — leave iframe; skip may still be reachable via stop() */
3046
+ }
3047
+ });
3048
+ };
3049
+ const suppressImaUi = (roots) => {
3050
+ roots.forEach((root) => suppressInRoot(root));
3051
+ };
3052
+ const getImaUiRoots = (container, wrapper) => {
3053
+ const roots = [];
3054
+ if (wrapper)
3055
+ roots.push(wrapper);
3056
+ if (container && container !== wrapper)
3057
+ roots.push(container);
3058
+ return roots;
3059
+ };
3060
+ const watchImaUi = (roots) => {
3061
+ const apply = () => suppressImaUi(roots);
3062
+ apply();
3063
+ const observer = new MutationObserver(apply);
3064
+ for (const root of roots) {
3065
+ observer.observe(root, { childList: true, subtree: true });
3066
+ }
3067
+ return () => observer.disconnect();
3068
+ };
3069
+
3070
+ const SKIP_SELECTORS = [
3071
+ ".videoAdUiSkipButton",
3072
+ ".videoAdUiSkipContainer button",
3073
+ ".videoAdUiSkipContainer",
3074
+ '[id*="skip_button" i]',
3075
+ '[aria-label*="Skip ad" i]',
3076
+ '[aria-label*="Skip Ad" i]',
3077
+ ];
3078
+ const isSkipControl = (el) => {
3079
+ if (SKIP_SELECTORS.some((sel) => el.matches(sel)))
3080
+ return true;
3081
+ const label = el.getAttribute("aria-label") ?? el.textContent ?? "";
3082
+ return /skip(\s+ad)?/i.test(label.trim());
3083
+ };
3084
+ const findNativeSkip = (root) => {
3085
+ for (const selector of SKIP_SELECTORS) {
3086
+ const match = root.querySelector(selector);
3087
+ if (match)
3088
+ return match;
3089
+ }
3090
+ for (const el of root.querySelectorAll("button, [role='button']")) {
3091
+ if (isSkipControl(el))
3092
+ return el;
3093
+ }
3094
+ for (const iframe of root.querySelectorAll("iframe")) {
3095
+ try {
3096
+ if (!iframe.contentDocument?.body)
3097
+ continue;
3098
+ const inner = findNativeSkip(iframe.contentDocument.body);
3099
+ if (inner)
3100
+ return inner;
3101
+ }
3102
+ catch {
3103
+ /* cross-origin */
3104
+ }
3105
+ }
3106
+ return null;
3107
+ };
3108
+ /** Clears hide styles so IMA's skip handler receives a real activation. */
3109
+ const activateNativeSkip = (element) => {
3110
+ const chain = [element];
3111
+ let parent = element.parentElement;
3112
+ while (parent) {
3113
+ chain.push(parent);
3114
+ if (parent.tagName === "IFRAME")
3115
+ break;
3116
+ parent = parent.parentElement;
3117
+ }
3118
+ for (const el of chain) {
3119
+ el.style.removeProperty("display");
3120
+ el.style.removeProperty("visibility");
3121
+ el.style.removeProperty("pointer-events");
3122
+ el.style.removeProperty("opacity");
3123
+ el.style.removeProperty("clip");
3124
+ el.style.removeProperty("width");
3125
+ el.style.removeProperty("height");
3126
+ el.style.removeProperty("overflow");
3127
+ el.style.removeProperty("position");
3128
+ }
3129
+ element.focus?.();
3130
+ const opts = {
3131
+ bubbles: true,
3132
+ cancelable: true,
3133
+ view: window,
3134
+ };
3135
+ element.dispatchEvent(new MouseEvent("mousedown", opts));
3136
+ element.dispatchEvent(new MouseEvent("mouseup", opts));
3137
+ element.dispatchEvent(new MouseEvent("click", opts));
3138
+ element.click();
3139
+ };
3140
+ /** Skips the current ad when skippable. */
3141
+ const triggerImaSkip = (manager, roots) => {
3142
+ if (!manager?.getAdSkippableState())
3143
+ return "failed";
3144
+ for (const root of roots) {
3145
+ const nativeSkip = findNativeSkip(root);
3146
+ if (nativeSkip) {
3147
+ activateNativeSkip(nativeSkip);
3148
+ return "native";
3149
+ }
3150
+ }
3151
+ try {
3152
+ manager.focus();
3153
+ }
3154
+ catch {
3155
+ /* optional */
3156
+ }
3157
+ try {
3158
+ manager.skip();
3159
+ return "api";
3160
+ }
3161
+ catch {
3162
+ /* fall through */
3163
+ }
3164
+ try {
3165
+ manager.stop();
3166
+ return "stop";
3167
+ }
3168
+ catch {
3169
+ return "failed";
3170
+ }
3171
+ };
3172
+
3173
+ /** Appends a fresh correlator so each ad request is unique (required by many GAM tags). */
3174
+ const buildAdTagUrl = (adTagUrl) => {
3175
+ const correlator = String(Date.now());
3176
+ if (/[?&]correlator=/i.test(adTagUrl)) {
3177
+ return adTagUrl.replace(/([?&]correlator=)[^&]*/i, `$1${correlator}`);
3178
+ }
3179
+ const separator = adTagUrl.includes("?") ? "&" : "?";
3180
+ return `${adTagUrl}${separator}correlator=${correlator}`;
3181
+ };
3182
+
3183
+ const MIN_WIDTH = 640;
3184
+ const MIN_HEIGHT = 360;
3185
+ const getImaSlotDimensions = (video, wrapper) => {
3186
+ const wrapperRect = wrapper?.getBoundingClientRect();
3187
+ const videoRect = video.getBoundingClientRect();
3188
+ const width = Math.max(MIN_WIDTH, Math.round(wrapperRect?.width || 0), Math.round(videoRect.width || 0), video.clientWidth || 0, video.offsetWidth || 0);
3189
+ const height = Math.max(MIN_HEIGHT, Math.round(wrapperRect?.height || 0), Math.round(videoRect.height || 0), video.clientHeight || 0, video.offsetHeight || 0);
3190
+ return { width, height };
3191
+ };
3192
+
3193
+ /** Detect VMAP / ad-rule tags that require a single persistent IMA session. */
3194
+ const isVmapAdTag = (adTagUrl, imaConfig) => {
3195
+ if (imaConfig?.adTagFormat === "vmap")
3196
+ return true;
3197
+ if (imaConfig?.adTagFormat === "vast")
3198
+ return false;
3199
+ return /[?&](output=vmap|ad_rule=1)/i.test(adTagUrl);
3200
+ };
3201
+
3202
+ const IMA_PREROLL_TIMEOUT_MS = 20000;
3203
+ const useImaAds = (adConfig) => {
3204
+ const imaConfig = adConfig?.ima;
3205
+ const adTagUrl = imaConfig?.adTagUrl?.trim();
3206
+ const { videoRef, imaAdContainerRef, setIsAdPlaying, setAdProvider, setCurrentAd, setAdType, setAdCurrentTime, setCanSkipAd, setSkipCountdown, setPlaying, setIsPlaying, setMuted, setImaPlayback, setImaDestroy, setImaSkipEnabled, setImaPreRollGateComplete, addPlayedAdBreak, } = useVideoStore(useShallow((state) => ({
3207
+ videoRef: state.videoRef,
3208
+ imaAdContainerRef: state.imaAdContainerRef,
3209
+ setIsAdPlaying: state.setIsAdPlaying,
3210
+ setAdProvider: state.setAdProvider,
3211
+ setCurrentAd: state.setCurrentAd,
3212
+ setAdType: state.setAdType,
3213
+ setAdCurrentTime: state.setAdCurrentTime,
3214
+ setCanSkipAd: state.setCanSkipAd,
3215
+ setSkipCountdown: state.setSkipCountdown,
3216
+ setPlaying: state.setPlaying,
3217
+ setIsPlaying: state.setIsPlaying,
3218
+ setMuted: state.setMuted,
3219
+ setImaPlayback: state.setImaPlayback,
3220
+ setImaDestroy: state.setImaDestroy,
3221
+ setImaSkipEnabled: state.setImaSkipEnabled,
3222
+ setImaPreRollGateComplete: state.setImaPreRollGateComplete,
3223
+ addPlayedAdBreak: state.addPlayedAdBreak,
3224
+ })));
3225
+ const adsLoaderRef = useRef(null);
3226
+ const adsManagerRef = useRef(null);
3227
+ const adDisplayContainerRef = useRef(null);
3228
+ const initializedRef = useRef(false);
3229
+ const preRollRequestedRef = useRef(false);
3230
+ const preRollCompletedRef = useRef(false);
3231
+ const contentCompleteSentRef = useRef(false);
3232
+ const contentHasStartedRef = useRef(false);
3233
+ const vmapSessionRef = useRef(false);
3234
+ const adBreakActiveRef = useRef(false);
3235
+ const playedCuePointsRef = useRef(new Set());
3236
+ const currentBreakTypeRef = useRef("pre-roll");
3237
+ const currentAdBreakRef = useRef(createImaAdBreak("pre-roll"));
3238
+ const resumeContentAfterAdRef = useRef(true);
3239
+ const adDurationRef = useRef(0);
3240
+ const currentImaAdRef = useRef(null);
3241
+ const preRollTimeoutRef = useRef(null);
3242
+ const imaUiCleanupRef = useRef(null);
3243
+ const nativeSkipRef = useRef(null);
3244
+ const clearImaUiWatcher = useCallback(() => {
3245
+ imaUiCleanupRef.current?.();
3246
+ imaUiCleanupRef.current = null;
3247
+ }, []);
3248
+ const startImaUiWatcher = useCallback(() => {
3249
+ clearImaUiWatcher();
3250
+ const roots = getImaUiRoots(useVideoStore.getState().imaAdContainerRef, useVideoStore.getState().videoWrapperRef);
3251
+ if (!roots.length)
3252
+ return;
3253
+ imaUiCleanupRef.current = watchImaUi(roots);
3254
+ }, [clearImaUiWatcher]);
3255
+ const clearPreRollTimeout = useCallback(() => {
3256
+ if (preRollTimeoutRef.current) {
3257
+ clearTimeout(preRollTimeoutRef.current);
3258
+ preRollTimeoutRef.current = null;
3259
+ }
3260
+ }, []);
3261
+ const completeImaPreRollGate = useCallback(() => {
3262
+ if (preRollCompletedRef.current)
3263
+ return;
3264
+ preRollCompletedRef.current = true;
3265
+ clearPreRollTimeout();
3266
+ setImaPreRollGateComplete(true);
3267
+ }, [clearPreRollTimeout, setImaPreRollGateComplete]);
3268
+ const resumeContentAfterPreRollFailure = useCallback(() => {
3269
+ const video = useVideoStore.getState().videoRef;
3270
+ if (!video)
3271
+ return;
3272
+ setTimeout(() => {
3273
+ if (useVideoStore.getState().isAdPlaying)
3274
+ return;
3275
+ video
3276
+ .play()
3277
+ .then(() => {
3278
+ setPlaying(true);
3279
+ setIsPlaying(true);
3280
+ })
3281
+ .catch(() => {
3282
+ setPlaying(false);
3283
+ setIsPlaying(false);
3284
+ });
3285
+ }, 100);
3286
+ }, [setPlaying, setIsPlaying]);
3287
+ const resetImaAdUiState = useCallback(() => {
3288
+ clearImaUiWatcher();
3289
+ setIsAdPlaying(false);
3290
+ setAdProvider(null);
3291
+ setCurrentAd(null);
3292
+ setAdType(null);
3293
+ setAdCurrentTime(0);
3294
+ setCanSkipAd(false);
3295
+ setSkipCountdown(0);
3296
+ setImaSkipEnabled(false);
3297
+ nativeSkipRef.current = null;
3298
+ currentImaAdRef.current = null;
3299
+ }, [
3300
+ setIsAdPlaying,
3301
+ setAdProvider,
3302
+ setCurrentAd,
3303
+ setAdType,
3304
+ setAdCurrentTime,
3305
+ clearImaUiWatcher,
3306
+ setCanSkipAd,
3307
+ setSkipCountdown,
3308
+ setImaSkipEnabled,
3309
+ ]);
3310
+ const syncAdUi = useCallback(() => {
3311
+ const manager = adsManagerRef.current;
3312
+ if (!manager)
3313
+ return;
3314
+ syncImaAdUi(manager, currentImaAdRef.current, adDurationRef.current, {
3315
+ setAdCurrentTime,
3316
+ setCanSkipAd,
3317
+ setSkipCountdown,
3318
+ setImaSkipEnabled,
3319
+ });
3320
+ }, [setAdCurrentTime, setCanSkipAd, setSkipCountdown, setImaSkipEnabled]);
3321
+ const destroyAdsManager = useCallback(() => {
3322
+ if (adsManagerRef.current) {
3323
+ try {
3324
+ adsManagerRef.current.destroy();
3325
+ }
3326
+ catch (_error) {
3327
+ /* ignore */
3328
+ }
3329
+ adsManagerRef.current = null;
3330
+ }
3331
+ }, []);
3332
+ const destroyIma = useCallback(() => {
3333
+ destroyAdsManager();
3334
+ if (adsLoaderRef.current) {
3335
+ try {
3336
+ adsLoaderRef.current.destroy();
3337
+ }
3338
+ catch (_error) {
3339
+ /* ignore */
3340
+ }
3341
+ adsLoaderRef.current = null;
3342
+ }
3343
+ if (adDisplayContainerRef.current) {
3344
+ try {
3345
+ adDisplayContainerRef.current.destroy();
3346
+ }
3347
+ catch (_error) {
3348
+ /* ignore */
3349
+ }
3350
+ adDisplayContainerRef.current = null;
3351
+ }
3352
+ initializedRef.current = false;
3353
+ preRollRequestedRef.current = false;
3354
+ contentCompleteSentRef.current = false;
3355
+ contentHasStartedRef.current = false;
3356
+ vmapSessionRef.current = false;
3357
+ adBreakActiveRef.current = false;
3358
+ playedCuePointsRef.current.clear();
3359
+ resetImaAdUiState();
3360
+ setImaPlayback(null);
3361
+ }, [destroyAdsManager, resetImaAdUiState, setImaPlayback]);
3362
+ const resumeContentIfNeeded = useCallback(() => {
3363
+ if (!resumeContentAfterAdRef.current)
3364
+ return;
3365
+ const video = useVideoStore.getState().videoRef;
3366
+ if (!video)
3367
+ return;
3368
+ setTimeout(() => {
3369
+ if (useVideoStore.getState().isAdPlaying)
3370
+ return;
3371
+ video
3372
+ .play()
3373
+ .then(() => {
3374
+ setPlaying(true);
3375
+ setIsPlaying(true);
3376
+ })
3377
+ .catch(() => {
3378
+ setPlaying(false);
3379
+ setIsPlaying(false);
3380
+ });
3381
+ }, 100);
3382
+ }, [setPlaying, setIsPlaying]);
3383
+ const inferVmapBreakType = useCallback(() => {
3384
+ if (!contentHasStartedRef.current)
3385
+ return "pre-roll";
3386
+ if (contentCompleteSentRef.current)
3387
+ return "post-roll";
3388
+ return "mid-roll";
3389
+ }, []);
3390
+ const endImaBreak = useCallback((reason, options) => {
3391
+ const isVmap = vmapSessionRef.current;
3392
+ const finalizeSession = options?.finalizeSession ??
3393
+ (!isVmap || reason === "close" || reason === "error");
3394
+ const breakWasActive = adBreakActiveRef.current;
3395
+ if (!breakWasActive && !finalizeSession) {
3396
+ return;
3397
+ }
3398
+ if (breakWasActive) {
3399
+ adBreakActiveRef.current = false;
3400
+ const adBreak = currentAdBreakRef.current;
3401
+ if (reason === "skip") {
3402
+ adConfig?.onAdSkip?.(adBreak);
3403
+ }
3404
+ else if (reason === "error") ;
3405
+ else {
3406
+ adConfig?.onAdEnd?.(adBreak);
3407
+ }
3408
+ if (currentBreakTypeRef.current === "pre-roll") {
3409
+ completeImaPreRollGate();
3410
+ }
3411
+ resetImaAdUiState();
3412
+ resumeContentIfNeeded();
3413
+ }
3414
+ else if (currentBreakTypeRef.current === "pre-roll" &&
3415
+ preRollRequestedRef.current &&
3416
+ !preRollCompletedRef.current) {
3417
+ completeImaPreRollGate();
3418
+ resumeContentIfNeeded();
3419
+ }
3420
+ if (finalizeSession) {
3421
+ destroyAdsManager();
3422
+ }
3423
+ }, [
3424
+ adConfig,
3425
+ completeImaPreRollGate,
3426
+ destroyAdsManager,
3427
+ resetImaAdUiState,
3428
+ resumeContentIfNeeded,
3429
+ ]);
3430
+ const cacheNativeSkip = useCallback(() => {
3431
+ if (!adsManagerRef.current?.getAdSkippableState())
3432
+ return;
3433
+ const roots = getImaUiRoots(useVideoStore.getState().imaAdContainerRef, useVideoStore.getState().videoWrapperRef);
3434
+ for (const root of roots) {
3435
+ const btn = findNativeSkip(root);
3436
+ if (btn) {
3437
+ nativeSkipRef.current = btn;
3438
+ return;
3439
+ }
3440
+ }
3441
+ }, []);
3442
+ const performImaSkip = useCallback(() => {
3443
+ const manager = adsManagerRef.current;
3444
+ if (!manager?.getAdSkippableState())
3445
+ return false;
3446
+ const cached = nativeSkipRef.current;
3447
+ if (cached?.isConnected) {
3448
+ activateNativeSkip(cached);
3449
+ return true;
3450
+ }
3451
+ const roots = getImaUiRoots(useVideoStore.getState().imaAdContainerRef, useVideoStore.getState().videoWrapperRef);
3452
+ const result = triggerImaSkip(manager, roots);
3453
+ if (result === "failed")
3454
+ return false;
3455
+ if (result === "stop") {
3456
+ window.setTimeout(() => {
3457
+ if (adBreakActiveRef.current) {
3458
+ endImaBreak("skip");
3459
+ }
3460
+ }, 200);
3461
+ }
3462
+ return true;
3463
+ }, [endImaBreak]);
3464
+ const beginImaBreak = useCallback((type) => {
3465
+ const video = useVideoStore.getState().videoRef;
3466
+ if (video) {
3467
+ video.pause();
3468
+ setPlaying(false);
3469
+ setIsPlaying(false);
3470
+ }
3471
+ currentBreakTypeRef.current = type;
3472
+ const adBreak = createImaAdBreak(type);
3473
+ currentAdBreakRef.current = adBreak;
3474
+ addPlayedAdBreak(adBreak.id);
3475
+ setIsAdPlaying(true);
3476
+ setAdProvider("ima");
3477
+ startImaUiWatcher();
3478
+ setCurrentAd(adBreak);
3479
+ setAdType(type);
3480
+ adBreakActiveRef.current = true;
3481
+ adConfig?.onAdStart?.(adBreak);
3482
+ }, [
3483
+ adConfig,
3484
+ addPlayedAdBreak,
3485
+ setAdProvider,
3486
+ setAdType,
3487
+ setCurrentAd,
3488
+ setIsAdPlaying,
3489
+ setIsPlaying,
3490
+ setPlaying,
3491
+ startImaUiWatcher,
3492
+ ]);
3493
+ const onAdsManagerLoaded = useCallback((event) => {
3494
+ const video = useVideoStore.getState().videoRef;
3495
+ const container = useVideoStore.getState().imaAdContainerRef;
3496
+ if (!video || !container)
3497
+ return;
3498
+ destroyAdsManager();
3499
+ const manager = event.getAdsManager(video, createImaRenderingSettings());
3500
+ adsManagerRef.current = manager;
3501
+ const handleAdError = (adErrorEvent) => {
3502
+ const err = adErrorEvent.getError?.();
3503
+ const message = err?.getMessage?.() ?? "Unknown IMA ad error";
3504
+ adConfig?.onAdError?.(currentAdBreakRef.current, new Error(message));
3505
+ endImaBreak("error");
3506
+ };
3507
+ manager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, handleAdError);
3508
+ manager.addEventListener(google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, () => {
3509
+ const type = vmapSessionRef.current
3510
+ ? inferVmapBreakType()
3511
+ : currentBreakTypeRef.current;
3512
+ currentBreakTypeRef.current = type;
3513
+ resumeContentAfterAdRef.current = type !== "post-roll";
3514
+ currentAdBreakRef.current = createImaAdBreak(type);
3515
+ beginImaBreak(type);
3516
+ });
3517
+ manager.addEventListener(google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, () => {
3518
+ if (vmapSessionRef.current) {
3519
+ contentHasStartedRef.current = true;
3520
+ endImaBreak("complete", { finalizeSession: false });
3521
+ }
3522
+ else {
3523
+ endImaBreak("complete", { finalizeSession: true });
3524
+ }
3525
+ });
3526
+ manager.addEventListener(google.ima.AdEvent.Type.STARTED, (adEvent) => {
3527
+ const ad = adEvent.getAd();
3528
+ currentImaAdRef.current = ad ?? null;
3529
+ adDurationRef.current =
3530
+ ad && Number.isFinite(ad.getDuration()) ? ad.getDuration() : 0;
3531
+ setIsPlaying(true);
3532
+ syncAdUi();
3533
+ });
3534
+ manager.addEventListener(google.ima.AdEvent.Type.SKIPPABLE_STATE_CHANGED, () => {
3535
+ syncAdUi();
3536
+ cacheNativeSkip();
3537
+ });
3538
+ manager.addEventListener(google.ima.AdEvent.Type.PAUSED, () => {
3539
+ setIsPlaying(false);
3540
+ });
3541
+ manager.addEventListener(google.ima.AdEvent.Type.RESUMED, () => {
3542
+ setIsPlaying(true);
3543
+ });
3544
+ manager.addEventListener(google.ima.AdEvent.Type.SKIPPED, () => {
3545
+ endImaBreak("skip");
3546
+ });
3547
+ manager.addEventListener(google.ima.AdEvent.Type.ALL_ADS_COMPLETED, () => {
3548
+ endImaBreak("complete", { finalizeSession: true });
3549
+ });
3550
+ manager.addEventListener(google.ima.AdEvent.Type.LOADED, (adEvent) => {
3551
+ const ad = adEvent.getAd();
3552
+ currentImaAdRef.current = ad ?? null;
3553
+ if (ad && Number.isFinite(ad.getDuration())) {
3554
+ adDurationRef.current = ad.getDuration();
3555
+ }
3556
+ syncAdUi();
3557
+ if (ad && !ad.isLinear()) {
3558
+ const videoEl = useVideoStore.getState().videoRef;
3559
+ videoEl?.play().catch(() => undefined);
3560
+ }
3561
+ });
3562
+ manager.addEventListener(google.ima.AdEvent.Type.AD_PROGRESS, syncAdUi);
3563
+ if (!initializedRef.current && adDisplayContainerRef.current) {
3564
+ try {
3565
+ adDisplayContainerRef.current.initialize();
3566
+ initializedRef.current = true;
3567
+ }
3568
+ catch (_error) {
3569
+ /* continue; start may still work on desktop */
3570
+ }
3571
+ }
3572
+ const { width, height } = getImaSlotDimensions(video, useVideoStore.getState().videoWrapperRef);
3573
+ try {
3574
+ manager.init(width, height, google.ima.ViewMode.NORMAL);
3575
+ manager.start();
3576
+ }
3577
+ catch (error) {
3578
+ adConfig?.onAdError?.(currentAdBreakRef.current, error instanceof Error ? error : new Error(String(error)));
3579
+ if (currentBreakTypeRef.current === "pre-roll") {
3580
+ completeImaPreRollGate();
3581
+ resumeContentAfterPreRollFailure();
3582
+ }
3583
+ endImaBreak("error");
3584
+ }
3585
+ }, [
3586
+ adConfig,
3587
+ beginImaBreak,
3588
+ completeImaPreRollGate,
3589
+ destroyAdsManager,
3590
+ endImaBreak,
3591
+ inferVmapBreakType,
3592
+ resumeContentAfterPreRollFailure,
3593
+ setIsPlaying,
3594
+ syncAdUi,
3595
+ cacheNativeSkip,
3596
+ ]);
3597
+ const onAdLoaderError = useCallback((event) => {
3598
+ const err = event.getError?.();
3599
+ const message = err?.getMessage?.() ?? "IMA ads loader error";
3600
+ adConfig?.onAdError?.(currentAdBreakRef.current, new Error(message));
3601
+ if (currentBreakTypeRef.current === "pre-roll") {
3602
+ completeImaPreRollGate();
3603
+ resumeContentAfterPreRollFailure();
3604
+ }
3605
+ endImaBreak("error");
3606
+ }, [
3607
+ adConfig,
3608
+ completeImaPreRollGate,
3609
+ endImaBreak,
3610
+ resumeContentAfterPreRollFailure,
3611
+ ]);
3612
+ const ensureAdDisplayContainer = useCallback(() => {
3613
+ const video = useVideoStore.getState().videoRef;
3614
+ const container = useVideoStore.getState().imaAdContainerRef;
3615
+ if (!video || !container || adDisplayContainerRef.current)
3616
+ return;
3617
+ adDisplayContainerRef.current = new google.ima.AdDisplayContainer(container, video);
3618
+ adsLoaderRef.current = new google.ima.AdsLoader(adDisplayContainerRef.current);
3619
+ adsLoaderRef.current.addEventListener(google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, onAdsManagerLoaded, false);
3620
+ adsLoaderRef.current.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, onAdLoaderError, false);
3621
+ }, [onAdLoaderError, onAdsManagerLoaded]);
3622
+ const requestImaAds = useCallback((type) => {
3623
+ if (!adTagUrl || !adsLoaderRef.current)
3624
+ return;
3625
+ const video = useVideoStore.getState().videoRef;
3626
+ if (!video)
3627
+ return;
3628
+ const isVmap = isVmapAdTag(adTagUrl, imaConfig);
3629
+ vmapSessionRef.current = isVmap;
3630
+ if (!isVmap) {
3631
+ destroyAdsManager();
3632
+ }
3633
+ adDurationRef.current = 0;
3634
+ currentBreakTypeRef.current = type;
3635
+ currentAdBreakRef.current = createImaAdBreak(type);
3636
+ resumeContentAfterAdRef.current = type !== "post-roll";
3637
+ const request = new google.ima.AdsRequest();
3638
+ request.adTagUrl = buildAdTagUrl(adTagUrl);
3639
+ const { width, height } = getImaSlotDimensions(video, useVideoStore.getState().videoWrapperRef);
3640
+ request.linearAdSlotWidth = width;
3641
+ request.linearAdSlotHeight = height;
3642
+ request.nonLinearAdSlotWidth = width;
3643
+ request.nonLinearAdSlotHeight = Math.round(height * 0.25);
3644
+ if (imaConfig?.contentDuration != null) {
3645
+ request.contentDuration = imaConfig.contentDuration;
3646
+ }
3647
+ if (imaConfig?.contentTitle) {
3648
+ request.contentTitle = imaConfig.contentTitle;
3649
+ }
3650
+ adsLoaderRef.current.requestAds(request);
3651
+ }, [adTagUrl, destroyAdsManager, imaConfig]);
3652
+ const requestPreRoll = useCallback(() => {
3653
+ if (!adTagUrl || preRollRequestedRef.current)
3654
+ return;
3655
+ if (imaConfig?.preRoll === false) {
3656
+ completeImaPreRollGate();
3657
+ return;
3658
+ }
3659
+ preRollRequestedRef.current = true;
3660
+ currentBreakTypeRef.current = "pre-roll";
3661
+ clearPreRollTimeout();
3662
+ preRollTimeoutRef.current = setTimeout(() => {
3663
+ const state = useVideoStore.getState();
3664
+ if (preRollCompletedRef.current)
3665
+ return;
3666
+ if (state.isAdPlaying)
3667
+ return;
3668
+ const error = new Error("IMA pre-roll timed out. Tap the player to start ads, or check your ad tag and player size.");
3669
+ adConfig?.onAdError?.(currentAdBreakRef.current, error);
3670
+ completeImaPreRollGate();
3671
+ resumeContentAfterPreRollFailure();
3672
+ }, IMA_PREROLL_TIMEOUT_MS);
3673
+ requestImaAds("pre-roll");
3674
+ }, [
3675
+ adTagUrl,
3676
+ adConfig,
3677
+ clearPreRollTimeout,
3678
+ completeImaPreRollGate,
3679
+ imaConfig?.preRoll,
3680
+ requestImaAds,
3681
+ resumeContentAfterPreRollFailure,
3682
+ ]);
3683
+ const initializeIma = useCallback(() => {
3684
+ if (initializedRef.current)
3685
+ return true;
3686
+ ensureAdDisplayContainer();
3687
+ if (!adDisplayContainerRef.current)
3688
+ return false;
3689
+ try {
3690
+ adDisplayContainerRef.current.initialize();
3691
+ initializedRef.current = true;
3692
+ return true;
3693
+ }
3694
+ catch (_error) {
3695
+ return false;
3696
+ }
3697
+ }, [ensureAdDisplayContainer]);
3698
+ const tryStartPreRoll = useCallback(() => {
3699
+ if (!adTagUrl || preRollRequestedRef.current) {
3700
+ return preRollRequestedRef.current ? "started" : "pending";
3701
+ }
3702
+ const { videoRef: video, imaAdContainerRef: container } = useVideoStore.getState();
3703
+ if (!video || !container)
3704
+ return "pending";
3705
+ if (typeof google === "undefined" || !google.ima)
3706
+ return "pending";
3707
+ ensureAdDisplayContainer();
3708
+ if (!initializeIma())
3709
+ return "needs-gesture";
3710
+ requestPreRoll();
3711
+ return "started";
3712
+ }, [adTagUrl, ensureAdDisplayContainer, initializeIma, requestPreRoll]);
3713
+ const [imaSdkReady, setImaSdkReady] = useState(isImaSdkLoaded);
3714
+ const [imaPreRollNeedsGesture, setImaPreRollNeedsGesture] = useState(false);
3715
+ const attemptAutoStartPreRoll = useCallback(() => {
3716
+ if (!adTagUrl || preRollCompletedRef.current)
3717
+ return "pending";
3718
+ if (!useVideoStore.getState().videoRef)
3719
+ return "pending";
3720
+ if (!useVideoStore.getState().imaAdContainerRef)
3721
+ return "pending";
3722
+ if (!imaSdkReady || typeof google === "undefined" || !google.ima) {
3723
+ return "pending";
3724
+ }
3725
+ const result = tryStartPreRoll();
3726
+ if (result === "needs-gesture") {
3727
+ setImaPreRollNeedsGesture(true);
3728
+ }
3729
+ return result;
3730
+ }, [adTagUrl, imaSdkReady, tryStartPreRoll]);
3731
+ const handleUserGesture = useCallback(() => {
3732
+ setImaPreRollNeedsGesture(false);
3733
+ if (initializeIma()) {
3734
+ tryStartPreRoll();
3735
+ }
3736
+ }, [initializeIma, tryStartPreRoll]);
3737
+ useEffect(() => {
3738
+ if (!adTagUrl)
3739
+ return;
3740
+ let cancelled = false;
3741
+ const sdkUrl = imaConfig?.sdkUrl ?? IMA_SDK_URL;
3742
+ const loadSdk = async () => {
3743
+ try {
3744
+ await loadImaSdk(sdkUrl);
3745
+ if (cancelled)
3746
+ return;
3747
+ ensureAdDisplayContainer();
3748
+ setImaSdkReady(true);
3749
+ attemptAutoStartPreRoll();
3750
+ }
3751
+ catch (error) {
3752
+ completeImaPreRollGate();
3753
+ adConfig?.onAdError?.(createImaAdBreak("pre-roll"), error instanceof Error ? error : new Error(String(error)));
3754
+ resumeContentAfterPreRollFailure();
3755
+ }
3756
+ };
3757
+ if (isImaSdkLoaded()) {
3758
+ setImaSdkReady(true);
3759
+ }
3760
+ loadSdk();
3761
+ return () => {
3762
+ cancelled = true;
3763
+ };
3764
+ }, [
3765
+ adTagUrl,
3766
+ adConfig,
3767
+ attemptAutoStartPreRoll,
3768
+ completeImaPreRollGate,
3769
+ ensureAdDisplayContainer,
3770
+ imaConfig?.sdkUrl,
3771
+ resumeContentAfterPreRollFailure,
3772
+ ]);
3773
+ useEffect(() => {
3774
+ if (!adTagUrl || !imaSdkReady || !videoRef || !imaAdContainerRef)
3775
+ return;
3776
+ attemptAutoStartPreRoll();
3777
+ }, [
3778
+ adTagUrl,
3779
+ imaSdkReady,
3780
+ videoRef,
3781
+ videoRef?.src,
3782
+ imaAdContainerRef,
3783
+ attemptAutoStartPreRoll,
3784
+ ]);
3785
+ useEffect(() => {
3786
+ if (!adTagUrl)
3787
+ return;
3788
+ preRollRequestedRef.current = false;
3789
+ preRollCompletedRef.current = false;
3790
+ contentCompleteSentRef.current = false;
3791
+ contentHasStartedRef.current = false;
3792
+ vmapSessionRef.current = false;
3793
+ adBreakActiveRef.current = false;
3794
+ playedCuePointsRef.current.clear();
3795
+ setImaPreRollNeedsGesture(false);
3796
+ setImaPreRollGateComplete(false);
3797
+ clearPreRollTimeout();
3798
+ }, [
3799
+ adTagUrl,
3800
+ videoRef?.src,
3801
+ setImaPreRollGateComplete,
3802
+ clearPreRollTimeout,
3803
+ ]);
3804
+ useEffect(() => {
3805
+ setImaDestroy(() => {
3806
+ destroyIma();
3807
+ });
3808
+ return () => {
3809
+ setImaDestroy(null);
3810
+ destroyIma();
3811
+ };
3812
+ }, [destroyIma, setImaDestroy]);
3813
+ useEffect(() => {
3814
+ if (!adTagUrl)
3815
+ return;
3816
+ const api = {
3817
+ pause: () => adsManagerRef.current?.pause(),
3818
+ resume: () => adsManagerRef.current?.resume(),
3819
+ setVolume: (volume) => {
3820
+ adsManagerRef.current?.setVolume(volume);
3821
+ setMuted(volume === 0);
3822
+ },
3823
+ getVolume: () => adsManagerRef.current?.getVolume() ?? 1,
3824
+ skip: () => performImaSkip(),
3825
+ isSkippable: () => adsManagerRef.current?.getAdSkippableState() ?? false,
3826
+ getRemainingTime: () => adsManagerRef.current?.getRemainingTime() ?? 0,
3827
+ getDuration: () => adDurationRef.current,
3828
+ };
3829
+ setImaPlayback(api);
3830
+ return () => setImaPlayback(null);
3831
+ }, [adTagUrl, performImaSkip, setImaPlayback, setMuted]);
3832
+ useEffect(() => {
3833
+ if (!adTagUrl || !videoRef || !imaConfig?.midRollCuePoints?.length) {
3834
+ return;
3835
+ }
3836
+ if (isVmapAdTag(adTagUrl, imaConfig)) {
3837
+ return;
3838
+ }
3839
+ const cues = [...imaConfig.midRollCuePoints].sort((a, b) => a - b);
3840
+ const onTimeUpdate = () => {
3841
+ if (useVideoStore.getState().isAdPlaying)
3842
+ return;
3843
+ const t = videoRef.currentTime;
3844
+ for (const cue of cues) {
3845
+ if (playedCuePointsRef.current.has(cue))
3846
+ continue;
3847
+ if (t >= cue && t <= cue + 1) {
3848
+ playedCuePointsRef.current.add(cue);
3849
+ initializeIma();
3850
+ currentBreakTypeRef.current = "mid-roll";
3851
+ requestImaAds("mid-roll");
3852
+ break;
3853
+ }
3854
+ }
3855
+ };
3856
+ videoRef.addEventListener("timeupdate", onTimeUpdate);
3857
+ return () => videoRef.removeEventListener("timeupdate", onTimeUpdate);
3858
+ }, [
3859
+ adTagUrl,
3860
+ videoRef,
3861
+ imaConfig,
3862
+ initializeIma,
3863
+ requestImaAds,
3864
+ ]);
3865
+ useEffect(() => {
3866
+ if (!adTagUrl || !videoRef || imaConfig?.postRoll === false)
3867
+ return;
3868
+ const onEnded = () => {
3869
+ if (contentCompleteSentRef.current)
3870
+ return;
3871
+ if (useVideoStore.getState().isAdPlaying)
3872
+ return;
3873
+ contentCompleteSentRef.current = true;
3874
+ resumeContentAfterAdRef.current = false;
3875
+ initializeIma();
3876
+ if (!adsLoaderRef.current)
3877
+ return;
3878
+ if (isVmapAdTag(adTagUrl, imaConfig)) {
3879
+ currentBreakTypeRef.current = "post-roll";
3880
+ currentAdBreakRef.current = createImaAdBreak("post-roll");
3881
+ adsLoaderRef.current.contentComplete();
3882
+ }
3883
+ else {
3884
+ requestImaAds("post-roll");
3885
+ }
3886
+ };
3887
+ videoRef.addEventListener("ended", onEnded);
3888
+ return () => videoRef.removeEventListener("ended", onEnded);
3889
+ }, [
3890
+ adTagUrl,
3891
+ videoRef,
3892
+ imaConfig,
3893
+ initializeIma,
3894
+ requestImaAds,
3895
+ ]);
3896
+ useEffect(() => {
3897
+ if (!adTagUrl)
3898
+ return;
3899
+ const resize = () => {
3900
+ const manager = adsManagerRef.current;
3901
+ const video = useVideoStore.getState().videoRef;
3902
+ if (!manager || !video)
3903
+ return;
3904
+ const { width, height } = getImaSlotDimensions(video, useVideoStore.getState().videoWrapperRef);
3905
+ try {
3906
+ manager.resize(width, height, google.ima.ViewMode.NORMAL);
3907
+ }
3908
+ catch (_error) {
3909
+ /* ignore */
3910
+ }
3911
+ };
3912
+ window.addEventListener("resize", resize);
3913
+ return () => window.removeEventListener("resize", resize);
3914
+ }, [adTagUrl]);
3915
+ useEffect(() => {
3916
+ return () => {
3917
+ clearPreRollTimeout();
3918
+ };
3919
+ }, [clearPreRollTimeout]);
3920
+ return {
3921
+ hasIma: Boolean(adTagUrl),
3922
+ hasImaPreRoll: Boolean(adTagUrl) && imaConfig?.preRoll !== false,
3923
+ imaPreRollNeedsGesture,
3924
+ initializeIma,
3925
+ startImaPreRoll: handleUserGesture,
3926
+ };
3927
+ };
3928
+
3929
+ const usePrimaryVideoLifecycle = ({ hasPreRoll, hasImaPreRoll = false, trackSrc, }) => {
3930
+ const { videoRef, setVideoRef, isAdPlaying, currentAd, adType, imaPreRollGateComplete, setMuted, setPlaying, setIsPlaying, } = useVideoStore(useShallow((state) => ({
2792
3931
  videoRef: state.videoRef,
2793
3932
  setVideoRef: state.setVideoRef,
2794
3933
  isAdPlaying: state.isAdPlaying,
2795
3934
  currentAd: state.currentAd,
2796
3935
  adType: state.adType,
3936
+ imaPreRollGateComplete: state.imaPreRollGateComplete,
2797
3937
  setMuted: state.setMuted,
2798
3938
  setPlaying: state.setPlaying,
2799
3939
  setIsPlaying: state.setIsPlaying,
@@ -2830,6 +3970,12 @@ const usePrimaryVideoLifecycle = ({ hasPreRoll, trackSrc, }) => {
2830
3970
  }
2831
3971
  previousIsAdPlayingRef.current = isAdPlaying;
2832
3972
  }, [hasPreRoll, initialAdStarted, initialAdFinished, isAdPlaying]);
3973
+ useEffect(() => {
3974
+ if (hasImaPreRoll && imaPreRollGateComplete && !initialAdFinished) {
3975
+ setInitialAdFinished(true);
3976
+ setInitialAdStarted(true);
3977
+ }
3978
+ }, [hasImaPreRoll, imaPreRollGateComplete, initialAdFinished]);
2833
3979
  useEffect(() => {
2834
3980
  if (!videoRef) {
2835
3981
  return;
@@ -3038,6 +4184,64 @@ const useVideoError = () => {
3038
4184
  };
3039
4185
  };
3040
4186
 
4187
+ const HIDE_DELAY_MS = 3000;
4188
+ const useOverlayAutoHide = () => {
4189
+ const [showControls, setShowControls] = useState(true);
4190
+ const [isHovered, setIsHovered] = useState(false);
4191
+ const timeoutRef = useRef(null);
4192
+ const onMouseEnter = useCallback(() => setIsHovered(true), []);
4193
+ const onMouseLeave = useCallback(() => setIsHovered(false), []);
4194
+ const onMouseMove = useCallback(() => {
4195
+ setIsHovered(true);
4196
+ setShowControls(true);
4197
+ }, []);
4198
+ useEffect(() => {
4199
+ if (isHovered) {
4200
+ setShowControls(true);
4201
+ if (timeoutRef.current) {
4202
+ clearTimeout(timeoutRef.current);
4203
+ }
4204
+ return;
4205
+ }
4206
+ timeoutRef.current = setTimeout(() => setShowControls(false), HIDE_DELAY_MS);
4207
+ return () => {
4208
+ if (timeoutRef.current) {
4209
+ clearTimeout(timeoutRef.current);
4210
+ }
4211
+ };
4212
+ }, [isHovered]);
4213
+ return { showControls, onMouseEnter, onMouseLeave, onMouseMove };
4214
+ };
4215
+
4216
+ const AdOverlayChrome = ({ config, showControls, progressPercent, skipable = false, canSkipAd = false, skipCountdown = 0, onSkip, sponsoredUrl, fadeClassName = "", }) => {
4217
+ const headerConfig = {
4218
+ title: config?.config?.headerConfig?.config?.title || "Advertisement",
4219
+ isTrailer: config?.config?.headerConfig?.config?.isTrailer,
4220
+ onClose: config?.config?.headerConfig?.config?.onClose,
4221
+ };
4222
+ return (React__default.createElement("div", { className: `absolute inset-0 transition-all duration-300 ${fadeClassName} ${showControls ? "opacity-100" : "opacity-0 pointer-events-none"}` },
4223
+ React__default.createElement("div", { className: "absolute inset-0 bg-linear-to-b from-black/80 via-transparent to-black/90 flex flex-col justify-between" },
4224
+ React__default.createElement("div", { className: "shrink-0 relative" },
4225
+ React__default.createElement(ControlsHeader, { config: headerConfig })),
4226
+ React__default.createElement("div", { className: "flex-1 flex items-center justify-center" },
4227
+ React__default.createElement(MiddleControls, null)),
4228
+ React__default.createElement("div", { className: "shrink-0 relative" },
4229
+ skipable && (React__default.createElement("div", { className: "px-10 pb-3 flex justify-end" },
4230
+ React__default.createElement("button", { type: "button", onClick: onSkip, disabled: !canSkipAd, className: `flex items-center gap-2 px-4 py-2 rounded transition-all duration-200 ${canSkipAd
4231
+ ? "bg-white/20 hover:bg-white/30 text-white cursor-pointer hover:scale-105 active:scale-95 shadow-md hover:shadow-lg border border-white/30 hover:border-white/50 backdrop-blur-md"
4232
+ : "bg-black/60 text-gray-400 cursor-not-allowed border border-gray-700/60"}`, style: { borderRadius: "4px" } },
4233
+ React__default.createElement(SkipForward, { className: "w-4 h-4" }),
4234
+ React__default.createElement("span", { className: "text-sm font-medium" }, canSkipAd
4235
+ ? "Skip Ad"
4236
+ : `Skip in ${Math.max(skipCountdown, 0)}s`)))),
4237
+ React__default.createElement("div", { className: "px-10 pb-4" },
4238
+ React__default.createElement("div", { className: "relative h-1 bg-white/20 rounded-full overflow-hidden pointer-events-none select-none" },
4239
+ React__default.createElement("div", { className: "absolute left-0 top-0 h-full bg-white rounded-full transition-all duration-300 ease-out", style: { width: `${progressPercent}%` } }),
4240
+ React__default.createElement("div", { className: "absolute top-1/2 -translate-y-1/2 w-3 h-3 bg-white rounded-full shadow-lg transition-all duration-300 ease-out pointer-events-none", style: { left: `calc(${progressPercent}% - 6px)` } }))),
4241
+ sponsoredUrl && (React__default.createElement("div", { className: "px-10 pb-6 flex items-center justify-end" },
4242
+ React__default.createElement("a", { href: sponsoredUrl, target: "_blank", rel: "noopener noreferrer", className: "text-sm font-semibold text-sky-300 hover:text-white transition-colors" }, "Learn More")))))));
4243
+ };
4244
+
3041
4245
  const AdOverlay = React__default.memo(({ adBreak, onSkip, config }) => {
3042
4246
  const { adVideoRef, setAdVideoRef, adCurrentTime, setAdCurrentTime, canSkipAd, setCanSkipAd, skipCountdown, setSkipCountdown, videoRef, muted, setIsPlaying, } = useVideoStore(useShallow((state) => ({
3043
4247
  adVideoRef: state.adVideoRef,
@@ -3052,12 +4256,10 @@ const AdOverlay = React__default.memo(({ adBreak, onSkip, config }) => {
3052
4256
  muted: state.muted,
3053
4257
  setIsPlaying: state.setIsPlaying,
3054
4258
  })));
3055
- const [showControls, setShowControls] = useState(true);
3056
- const [isHovered, setIsHovered] = useState(false);
4259
+ const { showControls, onMouseEnter, onMouseLeave, onMouseMove } = useOverlayAutoHide();
3057
4260
  const [adDuration, setAdDuration] = useState(0);
3058
4261
  const [requiresInteraction, setRequiresInteraction] = useState(false);
3059
4262
  const [adLoadError, setAdLoadError] = useState(false);
3060
- const controlsTimeoutRef = useRef(null);
3061
4263
  const loadTimeoutRef = useRef(null);
3062
4264
  const safelySetCanSkipAd = useCallback((value) => {
3063
4265
  if (useVideoStore.getState().canSkipAd !== value) {
@@ -3069,23 +4271,6 @@ const AdOverlay = React__default.memo(({ adBreak, onSkip, config }) => {
3069
4271
  setSkipCountdown(value);
3070
4272
  }
3071
4273
  }, [setSkipCountdown]);
3072
- useEffect(() => {
3073
- if (isHovered) {
3074
- setShowControls(true);
3075
- if (controlsTimeoutRef.current) {
3076
- clearTimeout(controlsTimeoutRef.current);
3077
- }
3078
- return;
3079
- }
3080
- controlsTimeoutRef.current = setTimeout(() => {
3081
- setShowControls(false);
3082
- }, 3000);
3083
- return () => {
3084
- if (controlsTimeoutRef.current) {
3085
- clearTimeout(controlsTimeoutRef.current);
3086
- }
3087
- };
3088
- }, [isHovered]);
3089
4274
  const skipAfter = useMemo(() => {
3090
4275
  const rawSkipAfter = Number.isFinite(adBreak.skipAfter)
3091
4276
  ? Math.max(0, Number(adBreak.skipAfter))
@@ -3147,8 +4332,7 @@ const AdOverlay = React__default.memo(({ adBreak, onSkip, config }) => {
3147
4332
  }
3148
4333
  const playPromise = adVideoRef.play();
3149
4334
  if (playPromise && "catch" in playPromise) {
3150
- playPromise.catch((error) => {
3151
- console.warn("Ad play failed:", error);
4335
+ playPromise.catch(() => {
3152
4336
  setRequiresInteraction(true);
3153
4337
  setIsPlaying(false);
3154
4338
  });
@@ -3204,7 +4388,6 @@ const AdOverlay = React__default.memo(({ adBreak, onSkip, config }) => {
3204
4388
  }
3205
4389
  loadTimeoutRef.current = setTimeout(() => {
3206
4390
  if (adVideoRef && adVideoRef.readyState < 2) {
3207
- console.warn("Ad load timeout:", adBreak.id);
3208
4391
  setAdLoadError(true);
3209
4392
  setRequiresInteraction(true);
3210
4393
  }
@@ -3223,20 +4406,11 @@ const AdOverlay = React__default.memo(({ adBreak, onSkip, config }) => {
3223
4406
  setIsPlaying(true);
3224
4407
  setRequiresInteraction(false);
3225
4408
  };
3226
- const handleError = (e) => {
4409
+ const handleError = () => {
3227
4410
  if (loadTimeoutRef.current) {
3228
4411
  clearTimeout(loadTimeoutRef.current);
3229
4412
  loadTimeoutRef.current = null;
3230
4413
  }
3231
- const error = e.target;
3232
- const errorCode = error.error?.code;
3233
- const errorMessage = error.error?.message || "Unknown ad error";
3234
- console.error("Ad playback error:", {
3235
- adId: adBreak.id,
3236
- errorCode,
3237
- errorMessage,
3238
- src: adVideoRef.src,
3239
- });
3240
4414
  setAdLoadError(true);
3241
4415
  setRequiresInteraction(true);
3242
4416
  setIsPlaying(false);
@@ -3298,8 +4472,7 @@ const AdOverlay = React__default.memo(({ adBreak, onSkip, config }) => {
3298
4472
  adVideoRef.src = adBreak.adUrl;
3299
4473
  adVideoRef.load();
3300
4474
  }
3301
- catch (error) {
3302
- console.warn("Error loading ad:", error);
4475
+ catch {
3303
4476
  setAdLoadError(true);
3304
4477
  }
3305
4478
  }
@@ -3354,10 +4527,7 @@ const AdOverlay = React__default.memo(({ adBreak, onSkip, config }) => {
3354
4527
  }
3355
4528
  };
3356
4529
  const progressPercent = adDuration > 0 ? (adCurrentTime / adDuration) * 100 : 0;
3357
- return (React__default.createElement("div", { className: "absolute inset-0 bg-black z-50 flex flex-col overflow-hidden transition-opacity duration-300", onMouseEnter: () => setIsHovered(true), onMouseLeave: () => setIsHovered(false), onMouseMove: () => {
3358
- setIsHovered(true);
3359
- setShowControls(true);
3360
- } },
4530
+ return (React__default.createElement("div", { className: "absolute inset-0 bg-black z-50 flex flex-col overflow-hidden transition-opacity duration-300", onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, onMouseMove: onMouseMove },
3361
4531
  React__default.createElement("div", { className: "relative flex-1 w-full flex items-center justify-center" },
3362
4532
  React__default.createElement("video", { ref: (ref) => {
3363
4533
  if (!ref)
@@ -3380,35 +4550,36 @@ const AdOverlay = React__default.memo(({ adBreak, onSkip, config }) => {
3380
4550
  React__default.createElement("div", { className: "flex flex-col items-center gap-4" },
3381
4551
  adLoadError && (React__default.createElement("p", { className: "text-red-400 text-sm" }, "Ad failed to load")),
3382
4552
  React__default.createElement("button", { onClick: attemptAdPlayback, className: "px-5 py-3 rounded bg-white/20 text-white font-semibold border border-white/40 hover:bg-white/30 transition" }, adLoadError ? "Retry Ad" : "Tap to Play Ad"))))),
3383
- React__default.createElement("div", { className: `absolute inset-0 transition-all duration-300 ${showControls ? "opacity-100" : "opacity-0 pointer-events-none"}` },
3384
- React__default.createElement("div", { className: "absolute inset-0 bg-linear-to-b from-black/80 via-transparent to-black/90 flex flex-col justify-between" },
3385
- React__default.createElement("div", { className: "shrink-0 relative" },
3386
- React__default.createElement(ControlsHeader, { config: {
3387
- title: config?.config?.headerConfig?.config?.title ||
3388
- "Advertisement",
3389
- isTrailer: config?.config?.headerConfig?.config?.isTrailer,
3390
- onClose: config?.config?.headerConfig?.config?.onClose,
3391
- } })),
3392
- React__default.createElement("div", { className: "flex-1 flex items-center justify-center" },
3393
- React__default.createElement(MiddleControls, null)),
3394
- React__default.createElement("div", { className: "shrink-0 relative" },
3395
- adBreak.skipable && (React__default.createElement("div", { className: "px-10 pb-3 flex justify-end" },
3396
- React__default.createElement("button", { onClick: handleSkip, disabled: !canSkipAd, className: `flex items-center gap-2 px-4 py-2 rounded transition-all duration-200 ${canSkipAd
3397
- ? "bg-white/20 hover:bg-white/30 text-white cursor-pointer hover:scale-105 active:scale-95 shadow-md hover:shadow-lg border border-white/30 hover:border-white/50 backdrop-blur-md"
3398
- : "bg-black/60 text-gray-400 cursor-not-allowed border border-gray-700/60"}`, style: { borderRadius: "4px" } },
3399
- React__default.createElement(SkipForward, { className: "w-4 h-4" }),
3400
- React__default.createElement("span", { className: "text-sm font-medium" }, canSkipAd
3401
- ? "Skip Ad"
3402
- : `Skip in ${Math.max(skipCountdown, 0)}s`)))),
3403
- React__default.createElement("div", { className: "px-10 pb-4" },
3404
- React__default.createElement("div", { className: "relative h-1 bg-white/20 rounded-full overflow-hidden pointer-events-none select-none" },
3405
- React__default.createElement("div", { className: "absolute left-0 top-0 h-full bg-white rounded-full transition-all duration-300 ease-out", style: { width: `${progressPercent}%` } }),
3406
- React__default.createElement("div", { className: "absolute top-1/2 -translate-y-1/2 w-3 h-3 bg-white rounded-full shadow-lg transition-all duration-300 ease-out pointer-events-none", style: { left: `calc(${progressPercent}% - 6px)` } }))),
3407
- sponsoredUrl && (React__default.createElement("div", { className: "px-10 pb-6 flex items-center justify-end" },
3408
- React__default.createElement("a", { href: sponsoredUrl, target: "_blank", rel: "noopener noreferrer", className: "text-sm font-semibold text-sky-300 hover:text-white transition-colors" }, "Learn More"))))))));
4553
+ React__default.createElement(AdOverlayChrome, { config: config, showControls: showControls, progressPercent: progressPercent, skipable: adBreak.skipable, canSkipAd: canSkipAd, skipCountdown: skipCountdown, onSkip: handleSkip, sponsoredUrl: sponsoredUrl })));
3409
4554
  });
3410
4555
  AdOverlay.displayName = "AdOverlay";
3411
4556
 
4557
+ const ImaAdOverlay = React__default.memo(({ config }) => {
4558
+ const { adCurrentTime, canSkipAd, skipCountdown, imaPlayback, imaSkipEnabled, } = useVideoStore(useShallow((state) => ({
4559
+ adCurrentTime: state.adCurrentTime,
4560
+ canSkipAd: state.canSkipAd,
4561
+ skipCountdown: state.skipCountdown,
4562
+ imaPlayback: state.imaPlayback,
4563
+ imaSkipEnabled: state.imaSkipEnabled,
4564
+ })));
4565
+ const { showControls, onMouseEnter, onMouseLeave, onMouseMove } = useOverlayAutoHide();
4566
+ const adDuration = imaPlayback?.getDuration() ?? 0;
4567
+ const progressPercent = useMemo(() => {
4568
+ if (adDuration <= 0)
4569
+ return 0;
4570
+ return Math.min(100, (adCurrentTime / adDuration) * 100);
4571
+ }, [adCurrentTime, adDuration]);
4572
+ const handleSkip = useCallback(() => {
4573
+ const playback = useVideoStore.getState().imaPlayback;
4574
+ if (playback?.isSkippable()) {
4575
+ playback.skip();
4576
+ }
4577
+ }, []);
4578
+ return (React__default.createElement("div", { className: "absolute inset-0 z-[1200] flex flex-col overflow-hidden pointer-events-none", onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, onMouseMove: onMouseMove },
4579
+ React__default.createElement(AdOverlayChrome, { config: config, showControls: showControls, progressPercent: progressPercent, skipable: imaSkipEnabled, canSkipAd: canSkipAd, skipCountdown: skipCountdown, onSkip: handleSkip, fadeClassName: "pointer-events-auto" })));
4580
+ });
4581
+ ImaAdOverlay.displayName = "ImaAdOverlay";
4582
+
3412
4583
  const ErrorOverlay = React__default.memo(({ error, onRetry }) => {
3413
4584
  const getIcon = () => {
3414
4585
  switch (error.type) {
@@ -3443,7 +4614,7 @@ ErrorOverlay.displayName = "ErrorOverlay";
3443
4614
  var css_248z$1 = ".video-player video::cue {\n display: none !important;\n opacity: 0 !important;\n visibility: hidden !important;\n}\n\n.custom-subtitle-overlay {\n position: absolute;\n bottom: 10%;\n left: 50%;\n transform: translateX(-50%);\n\n font-size: 1.5rem;\n font-weight: 600;\n line-height: 1.4;\n text-align: center;\n\n color: #000;\n background: rgba(255, 255, 255, 0.6);\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n\n padding: 10px 16px;\n border-radius: 10px;\n border: 1px solid rgba(0, 0, 0, 0.1);\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);\n\n max-width: 70%;\n min-width: fit-content;\n\n transition: all 0.2s ease-in-out;\n}\n\n.custom-subtitle-overlay:hover {\n transform: translateX(-50%) scale(1.02);\n box-shadow: 0 6px 16px rgba(0, 0, 0, 0.35);\n}\n\n@media (max-width: 768px) {\n .custom-subtitle-overlay {\n font-size: 1.25rem;\n padding: 8px 14px;\n bottom: 8%;\n max-width: 85%;\n }\n}\n\n@media (max-width: 480px) {\n .custom-subtitle-overlay {\n font-size: 1rem;\n padding: 6px 10px;\n bottom: 6%;\n max-width: 90%;\n }\n}\n\n@media (prefers-contrast: high) {\n .custom-subtitle-overlay {\n background: #ffff00;\n color: #000;\n border: 3px solid #000;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .custom-subtitle-overlay {\n transition: none;\n }\n\n .custom-subtitle-overlay:hover {\n transform: translateX(-50%);\n }\n}\n";
3444
4615
  styleInject(css_248z$1,{"insertAt":"top"});
3445
4616
 
3446
- var css_248z = "\n.loader {\n width: 64px;\n height: 64px;\n border-radius: 50%;\n display: inline-block;\n border-top: 3px solid #fff;\n border-right: 3px solid transparent;\n box-sizing: border-box;\n animation: rotation 1s linear infinite;\n}\n\n@keyframes rotation {\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(360deg);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .loader {\n animation: none;\n }\n}\n";
4617
+ var css_248z = "/* Hide IMA playback chrome skip UI stays in DOM for programmatic activation */\n.video-player .ima-controls-div,\n.video-player .ima-countdown-div {\n display: none !important;\n visibility: hidden !important;\n pointer-events: none !important;\n}\n";
3447
4618
  styleInject(css_248z,{"insertAt":"top"});
3448
4619
 
3449
4620
  const VideoPlayer = React__default.memo(({ video, style, events, features }) => {
@@ -3451,10 +4622,12 @@ const VideoPlayer = React__default.memo(({ video, style, events, features }) =>
3451
4622
  const { className, width, height, subtitleStyle, qualityConfig, seekBarConfig: styleSeekBarConfig, playPauseButtonConfig, } = style || {};
3452
4623
  const { onEnded, onError, onClose, onWatchHistoryUpdate } = events || {};
3453
4624
  const { timeCodes, getPreviewScreenUrl, tracking, subtitles, episodeList, currentEpisodeIndex = 0, intro, nextEpisodeConfig, ads, } = features || {};
3454
- const { setVideoWrapperRef, setActiveQuality, setIsLive, } = useVideoStore(useShallow((state) => ({
4625
+ const { setVideoWrapperRef, setActiveQuality, setIsLive, setImaAdContainerRef, adProvider, } = useVideoStore(useShallow((state) => ({
3455
4626
  setVideoWrapperRef: state.setVideoWrapperRef,
3456
4627
  setActiveQuality: state.setActiveQuality,
3457
4628
  setIsLive: state.setIsLive,
4629
+ setImaAdContainerRef: state.setImaAdContainerRef,
4630
+ adProvider: state.adProvider,
3458
4631
  })));
3459
4632
  React__default.useEffect(() => {
3460
4633
  setIsLive(isLiveProp);
@@ -3465,9 +4638,21 @@ const VideoPlayer = React__default.memo(({ video, style, events, features }) =>
3465
4638
  }
3466
4639
  }, [qualityConfig?.defaultQuality, setActiveQuality]);
3467
4640
  const effectiveAds = React__default.useMemo(() => (isTrailer ? undefined : ads), [ads, isTrailer]);
3468
- const hasPreRoll = React__default.useMemo(() => Boolean(effectiveAds?.preRoll), [effectiveAds?.preRoll]);
4641
+ const hasImaPreRoll = React__default.useMemo(() => Boolean(effectiveAds?.ima?.adTagUrl) &&
4642
+ effectiveAds?.ima?.preRoll !== false, [effectiveAds?.ima]);
4643
+ const hasPreRoll = React__default.useMemo(() => {
4644
+ const hasCustomPreRoll = Boolean(effectiveAds?.preRoll?.adUrl);
4645
+ return hasCustomPreRoll || hasImaPreRoll;
4646
+ }, [effectiveAds?.preRoll, hasImaPreRoll]);
4647
+ React__default.useEffect(() => {
4648
+ const ima = effectiveAds?.ima;
4649
+ if (ima?.adTagUrl) {
4650
+ preloadImaSdk(ima.sdkUrl);
4651
+ }
4652
+ }, [effectiveAds?.ima?.adTagUrl, effectiveAds?.ima?.sdkUrl]);
3469
4653
  const { registerVideoRef, videoRef, isAdPlaying, currentAd, initialAdFinished, shouldCoverMainVideo, shouldShowPlaceholder, } = usePrimaryVideoLifecycle({
3470
4654
  hasPreRoll,
4655
+ hasImaPreRoll,
3471
4656
  trackSrc,
3472
4657
  });
3473
4658
  const onWatchHistoryUpdateRef = React__default.useRef(onWatchHistoryUpdate);
@@ -3504,7 +4689,7 @@ const VideoPlayer = React__default.memo(({ video, style, events, features }) =>
3504
4689
  isTrailer: isTrailer,
3505
4690
  title: trackTitle,
3506
4691
  onClose: handleClose,
3507
- videoRef: videoRef,
4692
+ videoRef: videoRef ?? undefined,
3508
4693
  qualityConfig,
3509
4694
  },
3510
4695
  },
@@ -3563,6 +4748,7 @@ const VideoPlayer = React__default.memo(({ video, style, events, features }) =>
3563
4748
  useEpisodes(episodeList, currentEpisodeIndex, nextEpisodeConfig);
3564
4749
  const { onSeeked, onTimeUpdate, onLoadedMetadata, onProgress, onPlay, onPause, onEnded: onEndedHook, } = useVideoEvents();
3565
4750
  const { skipAd } = useAdManager(effectiveAds);
4751
+ const { startImaPreRoll, imaPreRollNeedsGesture } = useImaAds(effectiveAds);
3566
4752
  const { error, handleVideoError, retry } = useVideoError();
3567
4753
  const hasResumedRef = React__default.useRef(false);
3568
4754
  React__default.useEffect(() => {
@@ -3587,19 +4773,32 @@ const VideoPlayer = React__default.memo(({ video, style, events, features }) =>
3587
4773
  }, [videoRef, startFrom]);
3588
4774
  return (React__default.createElement("div", { ref: setVideoWrapperRef, className: `video-player ${height || "h-full"} ${width || "w-full"} mx-auto absolute` },
3589
4775
  trackPoster && (React__default.createElement("div", { className: "poster-bg absolute inset-0 bg-center bg-cover hidden", style: { backgroundImage: `url(${trackPoster})` } })),
3590
- React__default.createElement("video", { playsInline: true, preload: hasPreRoll ? "metadata" : "auto", ref: registerVideoRef, onSeeked: onSeeked, poster: trackPoster, crossOrigin: "anonymous", controls: false, disableRemotePlayback: true, controlsList: "nodownload", onContextMenu: (e) => e.preventDefault(), onTimeUpdate: onTimeUpdate, onLoadedMetadata: onLoadedMetadata, onProgress: onProgress, onPlay: onPlay, onPause: onPause, onEnded: (e) => {
4776
+ React__default.createElement("video", { playsInline: true, preload: "auto", ref: registerVideoRef, onSeeked: onSeeked, poster: trackPoster, crossOrigin: "anonymous", controls: false, disableRemotePlayback: true, controlsList: "nodownload", onContextMenu: (e) => e.preventDefault(), onTimeUpdate: onTimeUpdate, onLoadedMetadata: onLoadedMetadata, onProgress: onProgress, onPlay: onPlay, onPause: onPause, onEnded: (e) => {
3591
4777
  onEndedHook(e);
3592
4778
  onEnded?.(e);
3593
4779
  }, onError: (e) => {
3594
4780
  handleVideoError(e);
3595
4781
  onError?.(e);
3596
4782
  }, autoPlay: !hasPreRoll, muted: isMute, className: `w-full h-full relative ${className || ""} ${shouldCoverMainVideo ? "opacity-0" : "opacity-100"} transition-opacity duration-200 ease-out` }),
3597
- shouldShowPlaceholder && (React__default.createElement("div", { className: "absolute inset-0 z-40 flex flex-col items-center justify-center bg-black/90 backdrop-blur-sm" },
3598
- React__default.createElement(Loader, { className: "w-14 h-14 lg:w-18 lg:h-18 animate-spin text-white" }))),
4783
+ effectiveAds?.ima?.adTagUrl && (React__default.createElement("div", { ref: setImaAdContainerRef, className: `ima-ad-slot absolute inset-0 ${isAdPlaying && adProvider === "ima"
4784
+ ? "z-[46]"
4785
+ : "z-0 pointer-events-none"}`, "aria-hidden": !(isAdPlaying && adProvider === "ima") })),
4786
+ shouldShowPlaceholder && (React__default.createElement("div", { className: `absolute inset-0 z-40 flex flex-col items-center justify-center gap-4 bg-black/90 backdrop-blur-sm ${imaPreRollNeedsGesture ? "cursor-pointer" : ""}`, role: imaPreRollNeedsGesture ? "button" : undefined, tabIndex: imaPreRollNeedsGesture ? 0 : undefined, "aria-label": imaPreRollNeedsGesture
4787
+ ? "Start advertisement playback"
4788
+ : "Loading advertisement", onClick: imaPreRollNeedsGesture ? startImaPreRoll : undefined, onKeyDown: (e) => {
4789
+ if (imaPreRollNeedsGesture &&
4790
+ (e.key === "Enter" || e.key === " ")) {
4791
+ e.preventDefault();
4792
+ startImaPreRoll();
4793
+ }
4794
+ } },
4795
+ React__default.createElement(Loader, { className: "w-14 h-14 lg:w-18 lg:h-18 animate-spin text-white pointer-events-none" }),
4796
+ imaPreRollNeedsGesture && (React__default.createElement("p", { className: "text-sm text-white/80 pointer-events-none" }, "Tap to start")))),
3599
4797
  showControls && initialAdFinished && (React__default.createElement(Overlay, { config: overlayConfig })),
3600
4798
  React__default.createElement(SubtitleOverlay, { styleConfig: subtitleStyle }),
3601
4799
  showSkipIntro && !isAdPlaying && initialAdFinished && (React__default.createElement(VideoActionButton, { text: "Skip Intro", onClick: handleSkipIntro, position: "left" })),
3602
- isAdPlaying && currentAd && (React__default.createElement(AdOverlay, { adBreak: currentAd, onSkip: skipAd, config: adOverlayConfig })),
4800
+ isAdPlaying && currentAd && adProvider === "ima" && (React__default.createElement(ImaAdOverlay, { adBreak: currentAd, config: adOverlayConfig })),
4801
+ isAdPlaying && currentAd && adProvider !== "ima" && (React__default.createElement(AdOverlay, { adBreak: currentAd, onSkip: skipAd, config: adOverlayConfig })),
3603
4802
  error && onError && React__default.createElement(ErrorOverlay, { error: error, onRetry: retry })));
3604
4803
  });
3605
4804
  VideoPlayer.displayName = "VideoPlayer";