@zezosoft/react-player 1.0.3 → 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
@@ -37,7 +37,7 @@ function styleInject(css, ref) {
37
37
  }
38
38
  }
39
39
 
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-40 {\n z-index: 40;\n }\n .z-50 {\n z-index: 50;\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 .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";
41
41
  styleInject(css_248z$4,{"insertAt":"top"});
42
42
 
43
43
  const createVideoRefsSlice = (set) => ({
@@ -121,6 +121,8 @@ const createIntroSlice = (set) => ({
121
121
  const createAdsSlice = (set, get) => ({
122
122
  isAdPlaying: false,
123
123
  setIsAdPlaying: (isAdPlaying) => set({ isAdPlaying }),
124
+ adProvider: null,
125
+ setAdProvider: (adProvider) => set({ adProvider }),
124
126
  currentAd: null,
125
127
  setCurrentAd: (currentAd) => set({ currentAd }),
126
128
  adType: null,
@@ -139,6 +141,16 @@ const createAdsSlice = (set, get) => ({
139
141
  setMidRollQueue: (midRollQueue) => set({ midRollQueue }),
140
142
  adVideoRef: null,
141
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 }),
142
154
  });
143
155
 
144
156
  const createErrorSlice = (set) => ({
@@ -163,7 +175,8 @@ const createResetSlice = (set, get) => ({
163
175
  media.removeAttribute("src");
164
176
  media.load();
165
177
  };
166
- const { videoRef, adVideoRef, hlsInstance, dashInstance } = get();
178
+ const { videoRef, adVideoRef, hlsInstance, dashInstance, imaDestroy } = get();
179
+ imaDestroy?.();
167
180
  safeStopMediaElement(videoRef);
168
181
  safeStopMediaElement(adVideoRef);
169
182
  if (hlsInstance && typeof hlsInstance.destroy === "function") {
@@ -198,6 +211,7 @@ const createResetSlice = (set, get) => ({
198
211
  autoPlayNext: false,
199
212
  showIntroSkip: false,
200
213
  isAdPlaying: false,
214
+ adProvider: null,
201
215
  currentAd: null,
202
216
  adType: null,
203
217
  adCurrentTime: 0,
@@ -206,6 +220,11 @@ const createResetSlice = (set, get) => ({
206
220
  playedAdBreaks: [],
207
221
  midRollQueue: [],
208
222
  adVideoRef: null,
223
+ imaAdContainerRef: null,
224
+ imaPlayback: null,
225
+ imaDestroy: null,
226
+ imaSkipEnabled: false,
227
+ imaPreRollGateComplete: false,
209
228
  });
210
229
  },
211
230
  });
@@ -1048,7 +1067,7 @@ const Settings = ({ iconClassName, qualityConfig, }) => {
1048
1067
 
1049
1068
  const ControlsHeader = ({ config }) => {
1050
1069
  const iconClassName = "icon-button";
1051
- 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) => ({
1052
1071
  videoWrapperRef: state.videoWrapperRef,
1053
1072
  videoRef: state.videoRef,
1054
1073
  adVideoRef: state.adVideoRef,
@@ -1056,13 +1075,24 @@ const ControlsHeader = ({ config }) => {
1056
1075
  currentEpisodeIndex: state.currentEpisodeIndex,
1057
1076
  resetStore: state.resetStore,
1058
1077
  isAdPlaying: state.isAdPlaying,
1078
+ adProvider: state.adProvider,
1079
+ imaPlayback: state.imaPlayback,
1059
1080
  muted: state.muted,
1060
1081
  setMuted: state.setMuted,
1061
1082
  adCurrentTime: state.adCurrentTime,
1062
1083
  })));
1063
1084
  const [adDuration, setAdDuration] = React.useState(0);
1064
1085
  React.useEffect(() => {
1065
- 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) {
1066
1096
  setAdDuration(0);
1067
1097
  return;
1068
1098
  }
@@ -1078,7 +1108,7 @@ const ControlsHeader = ({ config }) => {
1078
1108
  adVideoRef.removeEventListener("loadedmetadata", updateDuration);
1079
1109
  adVideoRef.removeEventListener("durationchange", updateDuration);
1080
1110
  };
1081
- }, [adVideoRef, isAdPlaying]);
1111
+ }, [adVideoRef, isAdPlaying, adProvider, imaPlayback]);
1082
1112
  const formatTime = React.useCallback((seconds) => {
1083
1113
  if (isNaN(seconds) || seconds < 0)
1084
1114
  return "0:00";
@@ -1113,6 +1143,16 @@ const ControlsHeader = ({ config }) => {
1113
1143
  };
1114
1144
  }, []);
1115
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
+ }
1116
1156
  const targetElement = isAdPlaying ? adVideoRef ?? videoRef : videoRef;
1117
1157
  if (!targetElement)
1118
1158
  return;
@@ -1178,18 +1218,21 @@ const MiddleControls = ({ config }) => {
1178
1218
  borderRadius: ppConfig?.borderRadius ?? "50%",
1179
1219
  padding: ppConfig?.padding,
1180
1220
  }), [ppConfig?.backgroundColor, ppConfig?.borderRadius, ppConfig?.padding]);
1181
- const { videoRef, adVideoRef, isPlaying, setIsPlaying, isAdPlaying } = useVideoStore(useShallow((state) => ({
1221
+ const { videoRef, adVideoRef, isPlaying, setIsPlaying, isAdPlaying, adProvider, imaPlayback, } = useVideoStore(useShallow((state) => ({
1182
1222
  videoRef: state.videoRef,
1183
1223
  adVideoRef: state.adVideoRef,
1184
1224
  isPlaying: state.isPlaying,
1185
1225
  setIsPlaying: state.setIsPlaying,
1186
1226
  isAdPlaying: state.isAdPlaying,
1227
+ adProvider: state.adProvider,
1228
+ imaPlayback: state.imaPlayback,
1187
1229
  })));
1188
1230
  const { setIsBuffering } = useVideoStore(useShallow((state) => ({
1189
1231
  setIsBuffering: state.setIsBuffering,
1190
1232
  })));
1191
1233
  const [isBuffering, setIsBufferingLocal] = useState(false);
1192
- const videoElement = isAdPlaying ? adVideoRef : videoRef;
1234
+ const isImaAd = isAdPlaying && adProvider === "ima";
1235
+ const videoElement = isAdPlaying && !isImaAd ? adVideoRef : videoRef;
1193
1236
  const resetControlsVisibility = useCallback(() => {
1194
1237
  if (typeof window === "undefined") {
1195
1238
  return;
@@ -1197,6 +1240,18 @@ const MiddleControls = ({ config }) => {
1197
1240
  window.dispatchEvent(new Event(CONTROL_INTERACTION_EVENT));
1198
1241
  }, []);
1199
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
+ }
1200
1255
  if (!videoElement)
1201
1256
  return;
1202
1257
  if (videoElement.paused) {
@@ -1213,7 +1268,14 @@ const MiddleControls = ({ config }) => {
1213
1268
  setIsPlaying(false);
1214
1269
  resetControlsVisibility();
1215
1270
  }
1216
- }, [videoElement, setIsPlaying, resetControlsVisibility]);
1271
+ }, [
1272
+ isImaAd,
1273
+ imaPlayback,
1274
+ isPlaying,
1275
+ videoElement,
1276
+ setIsPlaying,
1277
+ resetControlsVisibility,
1278
+ ]);
1217
1279
  const handleBackward = useCallback(() => {
1218
1280
  if (!videoElement)
1219
1281
  return;
@@ -1227,7 +1289,7 @@ const MiddleControls = ({ config }) => {
1227
1289
  resetControlsVisibility();
1228
1290
  }, [videoElement, resetControlsVisibility]);
1229
1291
  useEffect(() => {
1230
- if (!videoElement)
1292
+ if (isImaAd || !videoElement)
1231
1293
  return;
1232
1294
  const handleWaiting = () => {
1233
1295
  setIsBufferingLocal(true);
@@ -1255,7 +1317,7 @@ const MiddleControls = ({ config }) => {
1255
1317
  videoElement.removeEventListener("canplay", handleCanPlay);
1256
1318
  videoElement.removeEventListener("stalled", handleStalled);
1257
1319
  };
1258
- }, [videoElement, isAdPlaying, setIsBuffering]);
1320
+ }, [videoElement, isAdPlaying, isImaAd, setIsBuffering]);
1259
1321
  useEffect(() => {
1260
1322
  const handleKeyDown = (e) => {
1261
1323
  if (!videoElement || isAdPlaying)
@@ -2374,6 +2436,12 @@ const useVideoEvents = () => {
2374
2436
  };
2375
2437
  const onPlay = () => {
2376
2438
  const state = useVideoStore.getState();
2439
+ if (state.adProvider === "ima") {
2440
+ if (!state.isPlaying) {
2441
+ setIsPlaying(true);
2442
+ }
2443
+ return;
2444
+ }
2377
2445
  if (state.adVideoRef) {
2378
2446
  // Defensive guard: ensure any ad media tears down before the primary stream resumes so stray audio cannot continue.
2379
2447
  stopMediaElement(state.adVideoRef);
@@ -2428,15 +2496,31 @@ const useVideoEvents = () => {
2428
2496
  };
2429
2497
  };
2430
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
+ };
2431
2516
  const useAdManager = (adConfig) => {
2432
- 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) => ({
2433
2518
  videoRef: state.videoRef,
2434
2519
  setPlaying: state.setPlaying,
2435
2520
  setIsPlaying: state.setIsPlaying,
2436
- currentTime: state.currentTime,
2437
- duration: state.duration,
2438
2521
  isAdPlaying: state.isAdPlaying,
2439
2522
  setIsAdPlaying: state.setIsAdPlaying,
2523
+ setAdProvider: state.setAdProvider,
2440
2524
  currentAd: state.currentAd,
2441
2525
  setCurrentAd: state.setCurrentAd,
2442
2526
  adType: state.adType,
@@ -2475,33 +2559,8 @@ const useAdManager = (adConfig) => {
2475
2559
  media.load();
2476
2560
  }, []);
2477
2561
  useEffect(() => {
2478
- if (!adConfig?.midRoll || adConfig.midRoll.length === 0) {
2479
- setMidRollQueue([]);
2480
- return;
2481
- }
2482
- // Filter out invalid ads and ensure all required fields are present
2483
- const validAds = adConfig.midRoll.filter((ad) => ad &&
2484
- typeof ad.time === "number" &&
2485
- ad.time >= 0 &&
2486
- ad.time < Number.MAX_SAFE_INTEGER &&
2487
- typeof ad.id === "string" &&
2488
- ad.id.trim() !== "" &&
2489
- typeof ad.adUrl === "string" &&
2490
- ad.adUrl.trim() !== "" &&
2491
- typeof ad.type === "string" &&
2492
- ad.type === "mid-roll");
2493
- if (validAds.length === 0) {
2494
- setMidRollQueue([]);
2495
- return;
2496
- }
2497
- // Sort ads by time to ensure they play in order
2498
- const sortedMidRolls = [...validAds].sort((a, b) => a.time - b.time);
2499
- // Remove duplicate IDs (keep first occurrence)
2500
- const uniqueAds = sortedMidRolls.filter((ad, index, self) => index === self.findIndex((a) => a.id === ad.id));
2501
- setMidRollQueue(uniqueAds);
2562
+ setMidRollQueue(normalizeMidRollQueue(adConfig?.midRoll));
2502
2563
  }, [adConfig?.midRoll, setMidRollQueue]);
2503
- // Removed smartPlacement - users should configure exact ad times
2504
- // This ensures ads appear exactly when specified
2505
2564
  const playPreRollAd = async () => {
2506
2565
  if (!adConfig?.preRoll || preRollPlayedRef.current || !videoRef)
2507
2566
  return;
@@ -2513,6 +2572,7 @@ const useAdManager = (adConfig) => {
2513
2572
  setPlaying(false);
2514
2573
  setIsPlaying(false);
2515
2574
  setIsAdPlaying(true);
2575
+ setAdProvider("custom");
2516
2576
  setCurrentAd(adBreak);
2517
2577
  setAdType("pre-roll");
2518
2578
  adConfig.onAdStart?.(adBreak);
@@ -2535,6 +2595,7 @@ const useAdManager = (adConfig) => {
2535
2595
  resumeAfterAdRef.current = wasPlaying;
2536
2596
  stopMediaElement(useVideoStore.getState().adVideoRef);
2537
2597
  setIsAdPlaying(true);
2598
+ setAdProvider("custom");
2538
2599
  setCurrentAd(adBreak);
2539
2600
  setAdType("mid-roll");
2540
2601
  adConfig?.onAdStart?.(adBreak);
@@ -2565,6 +2626,7 @@ const useAdManager = (adConfig) => {
2565
2626
  resumeAfterAdRef.current = false;
2566
2627
  stopMediaElement(useVideoStore.getState().adVideoRef);
2567
2628
  setIsAdPlaying(true);
2629
+ setAdProvider("custom");
2568
2630
  setCurrentAd(adBreak);
2569
2631
  setAdType("post-roll");
2570
2632
  adConfig.onAdStart?.(adBreak);
@@ -2585,6 +2647,7 @@ const useAdManager = (adConfig) => {
2585
2647
  }
2586
2648
  // Reset ad state
2587
2649
  setIsAdPlaying(false);
2650
+ setAdProvider(null);
2588
2651
  setCurrentAd(null);
2589
2652
  setAdType(null);
2590
2653
  setAdCurrentTime(0);
@@ -2613,6 +2676,7 @@ const useAdManager = (adConfig) => {
2613
2676
  }, [
2614
2677
  adConfig,
2615
2678
  setIsAdPlaying,
2679
+ setAdProvider,
2616
2680
  setCurrentAd,
2617
2681
  setAdType,
2618
2682
  setAdCurrentTime,
@@ -2624,7 +2688,7 @@ const useAdManager = (adConfig) => {
2624
2688
  stopMediaElement,
2625
2689
  ]);
2626
2690
  const skipAd = () => {
2627
- if (!currentAd || !currentAd.skipable)
2691
+ if (!currentAd?.skipable)
2628
2692
  return;
2629
2693
  adConfig?.onAdSkip?.(currentAd);
2630
2694
  endAd();
@@ -2673,6 +2737,9 @@ const useAdManager = (adConfig) => {
2673
2737
  useEffect(() => {
2674
2738
  if (!videoRef || !adConfig?.preRoll || preRollPlayedRef.current)
2675
2739
  return;
2740
+ const imaPreRollEnabled = Boolean(adConfig?.ima?.adTagUrl) && adConfig.ima?.preRoll !== false;
2741
+ if (imaPreRollEnabled)
2742
+ return;
2676
2743
  const handleCanPlay = () => {
2677
2744
  playPreRollAd();
2678
2745
  };
@@ -2768,6 +2835,9 @@ const useAdManager = (adConfig) => {
2768
2835
  useEffect(() => {
2769
2836
  if (!videoRef || !adConfig?.postRoll || postRollPlayedRef.current)
2770
2837
  return;
2838
+ const imaPostRollEnabled = Boolean(adConfig?.ima?.adTagUrl) && adConfig.ima?.postRoll !== false;
2839
+ if (imaPostRollEnabled)
2840
+ return;
2771
2841
  const handleVideoEnded = () => {
2772
2842
  setTimeout(() => {
2773
2843
  playPostRollAd();
@@ -2793,33 +2863,10 @@ const useAdManager = (adConfig) => {
2793
2863
  adCheckThrottleRef.current = null;
2794
2864
  }
2795
2865
  setIsAdPlaying(false);
2866
+ setAdProvider(null);
2796
2867
  setCurrentAd(null);
2797
2868
  setAdType(null);
2798
- // Re-initialize mid-roll queue with strict validation
2799
- if (adConfig?.midRoll && adConfig.midRoll.length > 0) {
2800
- // Filter and validate ads
2801
- const validAds = adConfig.midRoll.filter((ad) => ad &&
2802
- typeof ad.time === "number" &&
2803
- ad.time >= 0 &&
2804
- typeof ad.id === "string" &&
2805
- ad.id.trim() !== "" &&
2806
- typeof ad.adUrl === "string" &&
2807
- ad.adUrl.trim() !== "" &&
2808
- typeof ad.type === "string" &&
2809
- ad.type === "mid-roll");
2810
- if (validAds.length > 0) {
2811
- // Sort by time and remove duplicates
2812
- const sortedMidRolls = [...validAds].sort((a, b) => a.time - b.time);
2813
- const uniqueAds = sortedMidRolls.filter((ad, index, self) => index === self.findIndex((a) => a.id === ad.id));
2814
- setMidRollQueue(uniqueAds);
2815
- }
2816
- else {
2817
- setMidRollQueue([]);
2818
- }
2819
- }
2820
- else {
2821
- setMidRollQueue([]);
2822
- }
2869
+ setMidRollQueue(normalizeMidRollQueue(adConfig?.midRoll));
2823
2870
  // Clean up any lingering ad video
2824
2871
  const lingeringAdRef = useVideoStore.getState().adVideoRef;
2825
2872
  if (lingeringAdRef) {
@@ -2830,6 +2877,7 @@ const useAdManager = (adConfig) => {
2830
2877
  videoRef?.src,
2831
2878
  adConfig?.midRoll,
2832
2879
  setIsAdPlaying,
2880
+ setAdProvider,
2833
2881
  setCurrentAd,
2834
2882
  setAdType,
2835
2883
  setMidRollQueue,
@@ -2845,13 +2893,1047 @@ const useAdManager = (adConfig) => {
2845
2893
  };
2846
2894
  };
2847
2895
 
2848
- const usePrimaryVideoLifecycle = ({ hasPreRoll, trackSrc, }) => {
2849
- 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) => ({
2850
3931
  videoRef: state.videoRef,
2851
3932
  setVideoRef: state.setVideoRef,
2852
3933
  isAdPlaying: state.isAdPlaying,
2853
3934
  currentAd: state.currentAd,
2854
3935
  adType: state.adType,
3936
+ imaPreRollGateComplete: state.imaPreRollGateComplete,
2855
3937
  setMuted: state.setMuted,
2856
3938
  setPlaying: state.setPlaying,
2857
3939
  setIsPlaying: state.setIsPlaying,
@@ -2888,6 +3970,12 @@ const usePrimaryVideoLifecycle = ({ hasPreRoll, trackSrc, }) => {
2888
3970
  }
2889
3971
  previousIsAdPlayingRef.current = isAdPlaying;
2890
3972
  }, [hasPreRoll, initialAdStarted, initialAdFinished, isAdPlaying]);
3973
+ useEffect(() => {
3974
+ if (hasImaPreRoll && imaPreRollGateComplete && !initialAdFinished) {
3975
+ setInitialAdFinished(true);
3976
+ setInitialAdStarted(true);
3977
+ }
3978
+ }, [hasImaPreRoll, imaPreRollGateComplete, initialAdFinished]);
2891
3979
  useEffect(() => {
2892
3980
  if (!videoRef) {
2893
3981
  return;
@@ -3096,6 +4184,64 @@ const useVideoError = () => {
3096
4184
  };
3097
4185
  };
3098
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
+
3099
4245
  const AdOverlay = React__default.memo(({ adBreak, onSkip, config }) => {
3100
4246
  const { adVideoRef, setAdVideoRef, adCurrentTime, setAdCurrentTime, canSkipAd, setCanSkipAd, skipCountdown, setSkipCountdown, videoRef, muted, setIsPlaying, } = useVideoStore(useShallow((state) => ({
3101
4247
  adVideoRef: state.adVideoRef,
@@ -3110,12 +4256,10 @@ const AdOverlay = React__default.memo(({ adBreak, onSkip, config }) => {
3110
4256
  muted: state.muted,
3111
4257
  setIsPlaying: state.setIsPlaying,
3112
4258
  })));
3113
- const [showControls, setShowControls] = useState(true);
3114
- const [isHovered, setIsHovered] = useState(false);
4259
+ const { showControls, onMouseEnter, onMouseLeave, onMouseMove } = useOverlayAutoHide();
3115
4260
  const [adDuration, setAdDuration] = useState(0);
3116
4261
  const [requiresInteraction, setRequiresInteraction] = useState(false);
3117
4262
  const [adLoadError, setAdLoadError] = useState(false);
3118
- const controlsTimeoutRef = useRef(null);
3119
4263
  const loadTimeoutRef = useRef(null);
3120
4264
  const safelySetCanSkipAd = useCallback((value) => {
3121
4265
  if (useVideoStore.getState().canSkipAd !== value) {
@@ -3127,23 +4271,6 @@ const AdOverlay = React__default.memo(({ adBreak, onSkip, config }) => {
3127
4271
  setSkipCountdown(value);
3128
4272
  }
3129
4273
  }, [setSkipCountdown]);
3130
- useEffect(() => {
3131
- if (isHovered) {
3132
- setShowControls(true);
3133
- if (controlsTimeoutRef.current) {
3134
- clearTimeout(controlsTimeoutRef.current);
3135
- }
3136
- return;
3137
- }
3138
- controlsTimeoutRef.current = setTimeout(() => {
3139
- setShowControls(false);
3140
- }, 3000);
3141
- return () => {
3142
- if (controlsTimeoutRef.current) {
3143
- clearTimeout(controlsTimeoutRef.current);
3144
- }
3145
- };
3146
- }, [isHovered]);
3147
4274
  const skipAfter = useMemo(() => {
3148
4275
  const rawSkipAfter = Number.isFinite(adBreak.skipAfter)
3149
4276
  ? Math.max(0, Number(adBreak.skipAfter))
@@ -3205,8 +4332,7 @@ const AdOverlay = React__default.memo(({ adBreak, onSkip, config }) => {
3205
4332
  }
3206
4333
  const playPromise = adVideoRef.play();
3207
4334
  if (playPromise && "catch" in playPromise) {
3208
- playPromise.catch((error) => {
3209
- console.warn("Ad play failed:", error);
4335
+ playPromise.catch(() => {
3210
4336
  setRequiresInteraction(true);
3211
4337
  setIsPlaying(false);
3212
4338
  });
@@ -3262,7 +4388,6 @@ const AdOverlay = React__default.memo(({ adBreak, onSkip, config }) => {
3262
4388
  }
3263
4389
  loadTimeoutRef.current = setTimeout(() => {
3264
4390
  if (adVideoRef && adVideoRef.readyState < 2) {
3265
- console.warn("Ad load timeout:", adBreak.id);
3266
4391
  setAdLoadError(true);
3267
4392
  setRequiresInteraction(true);
3268
4393
  }
@@ -3281,20 +4406,11 @@ const AdOverlay = React__default.memo(({ adBreak, onSkip, config }) => {
3281
4406
  setIsPlaying(true);
3282
4407
  setRequiresInteraction(false);
3283
4408
  };
3284
- const handleError = (e) => {
4409
+ const handleError = () => {
3285
4410
  if (loadTimeoutRef.current) {
3286
4411
  clearTimeout(loadTimeoutRef.current);
3287
4412
  loadTimeoutRef.current = null;
3288
4413
  }
3289
- const error = e.target;
3290
- const errorCode = error.error?.code;
3291
- const errorMessage = error.error?.message || "Unknown ad error";
3292
- console.error("Ad playback error:", {
3293
- adId: adBreak.id,
3294
- errorCode,
3295
- errorMessage,
3296
- src: adVideoRef.src,
3297
- });
3298
4414
  setAdLoadError(true);
3299
4415
  setRequiresInteraction(true);
3300
4416
  setIsPlaying(false);
@@ -3356,8 +4472,7 @@ const AdOverlay = React__default.memo(({ adBreak, onSkip, config }) => {
3356
4472
  adVideoRef.src = adBreak.adUrl;
3357
4473
  adVideoRef.load();
3358
4474
  }
3359
- catch (error) {
3360
- console.warn("Error loading ad:", error);
4475
+ catch {
3361
4476
  setAdLoadError(true);
3362
4477
  }
3363
4478
  }
@@ -3412,10 +4527,7 @@ const AdOverlay = React__default.memo(({ adBreak, onSkip, config }) => {
3412
4527
  }
3413
4528
  };
3414
4529
  const progressPercent = adDuration > 0 ? (adCurrentTime / adDuration) * 100 : 0;
3415
- 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: () => {
3416
- setIsHovered(true);
3417
- setShowControls(true);
3418
- } },
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 },
3419
4531
  React__default.createElement("div", { className: "relative flex-1 w-full flex items-center justify-center" },
3420
4532
  React__default.createElement("video", { ref: (ref) => {
3421
4533
  if (!ref)
@@ -3438,35 +4550,36 @@ const AdOverlay = React__default.memo(({ adBreak, onSkip, config }) => {
3438
4550
  React__default.createElement("div", { className: "flex flex-col items-center gap-4" },
3439
4551
  adLoadError && (React__default.createElement("p", { className: "text-red-400 text-sm" }, "Ad failed to load")),
3440
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"))))),
3441
- React__default.createElement("div", { className: `absolute inset-0 transition-all duration-300 ${showControls ? "opacity-100" : "opacity-0 pointer-events-none"}` },
3442
- 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" },
3443
- React__default.createElement("div", { className: "shrink-0 relative" },
3444
- React__default.createElement(ControlsHeader, { config: {
3445
- title: config?.config?.headerConfig?.config?.title ||
3446
- "Advertisement",
3447
- isTrailer: config?.config?.headerConfig?.config?.isTrailer,
3448
- onClose: config?.config?.headerConfig?.config?.onClose,
3449
- } })),
3450
- React__default.createElement("div", { className: "flex-1 flex items-center justify-center" },
3451
- React__default.createElement(MiddleControls, null)),
3452
- React__default.createElement("div", { className: "shrink-0 relative" },
3453
- adBreak.skipable && (React__default.createElement("div", { className: "px-10 pb-3 flex justify-end" },
3454
- React__default.createElement("button", { onClick: handleSkip, disabled: !canSkipAd, className: `flex items-center gap-2 px-4 py-2 rounded transition-all duration-200 ${canSkipAd
3455
- ? "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"
3456
- : "bg-black/60 text-gray-400 cursor-not-allowed border border-gray-700/60"}`, style: { borderRadius: "4px" } },
3457
- React__default.createElement(SkipForward, { className: "w-4 h-4" }),
3458
- React__default.createElement("span", { className: "text-sm font-medium" }, canSkipAd
3459
- ? "Skip Ad"
3460
- : `Skip in ${Math.max(skipCountdown, 0)}s`)))),
3461
- React__default.createElement("div", { className: "px-10 pb-4" },
3462
- React__default.createElement("div", { className: "relative h-1 bg-white/20 rounded-full overflow-hidden pointer-events-none select-none" },
3463
- 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}%` } }),
3464
- 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)` } }))),
3465
- sponsoredUrl && (React__default.createElement("div", { className: "px-10 pb-6 flex items-center justify-end" },
3466
- 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 })));
3467
4554
  });
3468
4555
  AdOverlay.displayName = "AdOverlay";
3469
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
+
3470
4583
  const ErrorOverlay = React__default.memo(({ error, onRetry }) => {
3471
4584
  const getIcon = () => {
3472
4585
  switch (error.type) {
@@ -3501,7 +4614,7 @@ ErrorOverlay.displayName = "ErrorOverlay";
3501
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";
3502
4615
  styleInject(css_248z$1,{"insertAt":"top"});
3503
4616
 
3504
- 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";
3505
4618
  styleInject(css_248z,{"insertAt":"top"});
3506
4619
 
3507
4620
  const VideoPlayer = React__default.memo(({ video, style, events, features }) => {
@@ -3509,10 +4622,12 @@ const VideoPlayer = React__default.memo(({ video, style, events, features }) =>
3509
4622
  const { className, width, height, subtitleStyle, qualityConfig, seekBarConfig: styleSeekBarConfig, playPauseButtonConfig, } = style || {};
3510
4623
  const { onEnded, onError, onClose, onWatchHistoryUpdate } = events || {};
3511
4624
  const { timeCodes, getPreviewScreenUrl, tracking, subtitles, episodeList, currentEpisodeIndex = 0, intro, nextEpisodeConfig, ads, } = features || {};
3512
- const { setVideoWrapperRef, setActiveQuality, setIsLive, } = useVideoStore(useShallow((state) => ({
4625
+ const { setVideoWrapperRef, setActiveQuality, setIsLive, setImaAdContainerRef, adProvider, } = useVideoStore(useShallow((state) => ({
3513
4626
  setVideoWrapperRef: state.setVideoWrapperRef,
3514
4627
  setActiveQuality: state.setActiveQuality,
3515
4628
  setIsLive: state.setIsLive,
4629
+ setImaAdContainerRef: state.setImaAdContainerRef,
4630
+ adProvider: state.adProvider,
3516
4631
  })));
3517
4632
  React__default.useEffect(() => {
3518
4633
  setIsLive(isLiveProp);
@@ -3523,9 +4638,21 @@ const VideoPlayer = React__default.memo(({ video, style, events, features }) =>
3523
4638
  }
3524
4639
  }, [qualityConfig?.defaultQuality, setActiveQuality]);
3525
4640
  const effectiveAds = React__default.useMemo(() => (isTrailer ? undefined : ads), [ads, isTrailer]);
3526
- 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]);
3527
4653
  const { registerVideoRef, videoRef, isAdPlaying, currentAd, initialAdFinished, shouldCoverMainVideo, shouldShowPlaceholder, } = usePrimaryVideoLifecycle({
3528
4654
  hasPreRoll,
4655
+ hasImaPreRoll,
3529
4656
  trackSrc,
3530
4657
  });
3531
4658
  const onWatchHistoryUpdateRef = React__default.useRef(onWatchHistoryUpdate);
@@ -3562,7 +4689,7 @@ const VideoPlayer = React__default.memo(({ video, style, events, features }) =>
3562
4689
  isTrailer: isTrailer,
3563
4690
  title: trackTitle,
3564
4691
  onClose: handleClose,
3565
- videoRef: videoRef,
4692
+ videoRef: videoRef ?? undefined,
3566
4693
  qualityConfig,
3567
4694
  },
3568
4695
  },
@@ -3621,6 +4748,7 @@ const VideoPlayer = React__default.memo(({ video, style, events, features }) =>
3621
4748
  useEpisodes(episodeList, currentEpisodeIndex, nextEpisodeConfig);
3622
4749
  const { onSeeked, onTimeUpdate, onLoadedMetadata, onProgress, onPlay, onPause, onEnded: onEndedHook, } = useVideoEvents();
3623
4750
  const { skipAd } = useAdManager(effectiveAds);
4751
+ const { startImaPreRoll, imaPreRollNeedsGesture } = useImaAds(effectiveAds);
3624
4752
  const { error, handleVideoError, retry } = useVideoError();
3625
4753
  const hasResumedRef = React__default.useRef(false);
3626
4754
  React__default.useEffect(() => {
@@ -3645,19 +4773,32 @@ const VideoPlayer = React__default.memo(({ video, style, events, features }) =>
3645
4773
  }, [videoRef, startFrom]);
3646
4774
  return (React__default.createElement("div", { ref: setVideoWrapperRef, className: `video-player ${height || "h-full"} ${width || "w-full"} mx-auto absolute` },
3647
4775
  trackPoster && (React__default.createElement("div", { className: "poster-bg absolute inset-0 bg-center bg-cover hidden", style: { backgroundImage: `url(${trackPoster})` } })),
3648
- 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) => {
3649
4777
  onEndedHook(e);
3650
4778
  onEnded?.(e);
3651
4779
  }, onError: (e) => {
3652
4780
  handleVideoError(e);
3653
4781
  onError?.(e);
3654
4782
  }, autoPlay: !hasPreRoll, muted: isMute, className: `w-full h-full relative ${className || ""} ${shouldCoverMainVideo ? "opacity-0" : "opacity-100"} transition-opacity duration-200 ease-out` }),
3655
- shouldShowPlaceholder && (React__default.createElement("div", { className: "absolute inset-0 z-40 flex flex-col items-center justify-center bg-black/90 backdrop-blur-sm" },
3656
- 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")))),
3657
4797
  showControls && initialAdFinished && (React__default.createElement(Overlay, { config: overlayConfig })),
3658
4798
  React__default.createElement(SubtitleOverlay, { styleConfig: subtitleStyle }),
3659
4799
  showSkipIntro && !isAdPlaying && initialAdFinished && (React__default.createElement(VideoActionButton, { text: "Skip Intro", onClick: handleSkipIntro, position: "left" })),
3660
- 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 })),
3661
4802
  error && onError && React__default.createElement(ErrorOverlay, { error: error, onRetry: retry })));
3662
4803
  });
3663
4804
  VideoPlayer.displayName = "VideoPlayer";