@zezosoft/react-player 0.0.9 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import * as React from 'react';
2
2
  import React__default, { memo, useCallback, useEffect, useRef, useState, useMemo } from 'react';
3
3
  import { useShallow } from 'zustand/react/shallow';
4
+ import { Settings as Settings$1, ChevronRight, Check, Loader, ArrowRight, SkipForward } from 'lucide-react';
4
5
  import { create } from 'zustand';
5
6
  import { IoVolumeMuteOutline, IoVolumeHighOutline } from 'react-icons/io5';
6
7
  import { IoMdClose } from 'react-icons/io';
7
- import { Settings as Settings$1, ChevronRight, Check, Loader, ArrowRight, SkipForward } from 'lucide-react';
8
8
  import screenfull from 'screenfull';
9
9
  import Hls from 'hls.js';
10
10
  import * as dashjs from 'dashjs';
@@ -36,7 +36,7 @@ function styleInject(css, ref) {
36
36
  }
37
37
  }
38
38
 
39
- var css_248z$4 = "/*! tailwindcss v4.1.17 | MIT License | https://tailwindcss.com */\n@layer properties;\n@layer theme, base, components, utilities;\n@layer theme {\n :root, :host {\n --font-sans: ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\",\n \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\",\n \"Courier New\", monospace;\n --color-yellow-500: oklch(79.5% 0.184 86.047);\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 --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-wide: 0.025em;\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 --blur-sm: 8px;\n --blur-md: 12px;\n --default-transition-duration: 150ms;\n --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n --default-font-family: var(--font-sans);\n --default-mono-font-family: var(--font-mono);\n }\n}\n@layer base {\n *, ::after, ::before, ::backdrop, ::file-selector-button {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n border: 0 solid;\n }\n html, :host {\n line-height: 1.5;\n -webkit-text-size-adjust: 100%;\n tab-size: 4;\n font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\");\n font-feature-settings: var(--default-font-feature-settings, normal);\n font-variation-settings: var(--default-font-variation-settings, normal);\n -webkit-tap-highlight-color: transparent;\n }\n hr {\n height: 0;\n color: inherit;\n border-top-width: 1px;\n }\n abbr:where([title]) {\n -webkit-text-decoration: underline dotted;\n text-decoration: underline dotted;\n }\n h1, h2, h3, h4, h5, h6 {\n font-size: inherit;\n font-weight: inherit;\n }\n a {\n color: inherit;\n -webkit-text-decoration: inherit;\n text-decoration: inherit;\n }\n b, strong {\n font-weight: bolder;\n }\n code, kbd, samp, pre {\n font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace);\n font-feature-settings: var(--default-mono-font-feature-settings, normal);\n font-variation-settings: var(--default-mono-font-variation-settings, normal);\n font-size: 1em;\n }\n small {\n font-size: 80%;\n }\n sub, sup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n }\n sub {\n bottom: -0.25em;\n }\n sup {\n top: -0.5em;\n }\n table {\n text-indent: 0;\n border-color: inherit;\n border-collapse: collapse;\n }\n :-moz-focusring {\n outline: auto;\n }\n progress {\n vertical-align: baseline;\n }\n summary {\n display: list-item;\n }\n ol, ul, menu {\n list-style: none;\n }\n img, svg, video, canvas, audio, iframe, embed, object {\n display: block;\n vertical-align: middle;\n }\n img, video {\n max-width: 100%;\n height: auto;\n }\n button, input, select, optgroup, textarea, ::file-selector-button {\n font: inherit;\n font-feature-settings: inherit;\n font-variation-settings: inherit;\n letter-spacing: inherit;\n color: inherit;\n border-radius: 0;\n background-color: transparent;\n opacity: 1;\n }\n :where(select:is([multiple], [size])) optgroup {\n font-weight: bolder;\n }\n :where(select:is([multiple], [size])) optgroup option {\n padding-inline-start: 20px;\n }\n ::file-selector-button {\n margin-inline-end: 4px;\n }\n ::placeholder {\n opacity: 1;\n }\n @supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) {\n ::placeholder {\n color: currentcolor;\n @supports (color: color-mix(in lab, red, red)) {\n color: color-mix(in oklab, currentcolor 50%, transparent);\n }\n }\n }\n textarea {\n resize: vertical;\n }\n ::-webkit-search-decoration {\n -webkit-appearance: none;\n }\n ::-webkit-date-and-time-value {\n min-height: 1lh;\n text-align: inherit;\n }\n ::-webkit-datetime-edit {\n display: inline-flex;\n }\n ::-webkit-datetime-edit-fields-wrapper {\n padding: 0;\n }\n ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field {\n padding-block: 0;\n }\n ::-webkit-calendar-picker-indicator {\n line-height: 1;\n }\n :-moz-ui-invalid {\n box-shadow: none;\n }\n button, input:where([type=\"button\"], [type=\"reset\"], [type=\"submit\"]), ::file-selector-button {\n appearance: button;\n }\n ::-webkit-inner-spin-button, ::-webkit-outer-spin-button {\n height: auto;\n }\n [hidden]:where(:not([hidden=\"until-found\"])) {\n display: none !important;\n }\n}\n@layer utilities {\n .pointer-events-none {\n pointer-events: none;\n }\n .absolute {\n position: absolute;\n }\n .relative {\n position: relative;\n }\n .static {\n position: static;\n }\n .inset-0 {\n inset: calc(var(--spacing) * 0);\n }\n .-top-2 {\n top: calc(var(--spacing) * -2);\n }\n .top-0 {\n top: calc(var(--spacing) * 0);\n }\n .top-1\\/2 {\n top: calc(1/2 * 100%);\n }\n .top-full {\n top: 100%;\n }\n .right-0 {\n right: calc(var(--spacing) * 0);\n }\n .right-32 {\n right: calc(var(--spacing) * 32);\n }\n .right-full {\n right: 100%;\n }\n .bottom-36 {\n bottom: calc(var(--spacing) * 36);\n }\n .bottom-full {\n bottom: 100%;\n }\n .left-0 {\n left: calc(var(--spacing) * 0);\n }\n .left-1\\/2 {\n left: calc(1/2 * 100%);\n }\n .left-32 {\n left: calc(var(--spacing) * 32);\n }\n .left-full {\n left: 100%;\n }\n .z-40 {\n z-index: 40;\n }\n .z-50 {\n z-index: 50;\n }\n .z-\\[-1\\] {\n z-index: -1;\n }\n .mx-2 {\n margin-inline: calc(var(--spacing) * 2);\n }\n .mx-auto {\n margin-inline: auto;\n }\n .mt-1 {\n margin-top: calc(var(--spacing) * 1);\n }\n .mt-2 {\n margin-top: calc(var(--spacing) * 2);\n }\n .mr-2 {\n margin-right: calc(var(--spacing) * 2);\n }\n .mb-1 {\n margin-bottom: calc(var(--spacing) * 1);\n }\n .mb-2 {\n margin-bottom: calc(var(--spacing) * 2);\n }\n .mb-4 {\n margin-bottom: calc(var(--spacing) * 4);\n }\n .ml-2 {\n margin-left: calc(var(--spacing) * 2);\n }\n .block {\n display: block;\n }\n .flex {\n display: flex;\n }\n .hidden {\n display: none;\n }\n .inline {\n display: inline;\n }\n .inline-block {\n display: inline-block;\n }\n .inline-flex {\n display: inline-flex;\n }\n .h-1 {\n height: calc(var(--spacing) * 1);\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-24 {\n height: calc(var(--spacing) * 24);\n }\n .h-full {\n height: 100%;\n }\n .max-h-80 {\n max-height: calc(var(--spacing) * 80);\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-24 {\n width: calc(var(--spacing) * 24);\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-\\[720px\\] {\n width: 720px;\n }\n .w-fit {\n width: fit-content;\n }\n .w-full {\n width: 100%;\n }\n .flex-1 {\n flex: 1;\n }\n .shrink-0 {\n flex-shrink: 0;\n }\n .-translate-x-1\\/2 {\n --tw-translate-x: calc(calc(1/2 * 100%) * -1);\n translate: var(--tw-translate-x) var(--tw-translate-y);\n }\n .-translate-y-1\\/2 {\n --tw-translate-y: calc(calc(1/2 * 100%) * -1);\n translate: var(--tw-translate-x) var(--tw-translate-y);\n }\n .rotate-45 {\n rotate: 45deg;\n }\n .rotate-180 {\n rotate: 180deg;\n }\n .transform {\n transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);\n }\n .animate-spin {\n animation: var(--animate-spin);\n }\n .cursor-not-allowed {\n cursor: not-allowed;\n }\n .cursor-pointer {\n cursor: pointer;\n }\n .flex-col {\n flex-direction: column;\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-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\\/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\\/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-\\[\\#2D2F31\\] {\n background-color: #2D2F31;\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-blue-500 {\n background-color: var(--color-blue-500);\n }\n .bg-gray-500 {\n background-color: var(--color-gray-500);\n }\n .bg-gray-900 {\n background-color: var(--color-gray-900);\n }\n .bg-green-500 {\n background-color: var(--color-green-500);\n }\n .bg-purple-500 {\n background-color: var(--color-purple-500);\n }\n .bg-white {\n background-color: var(--color-white);\n }\n .bg-white\\/5 {\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 .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\\/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-10 {\n padding: calc(var(--spacing) * 10);\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 .px-20 {\n padding-inline: calc(var(--spacing) * 20);\n }\n .py-1 {\n padding-block: calc(var(--spacing) * 1);\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 .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-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-wide {\n --tw-tracking: var(--tracking-wide);\n letter-spacing: var(--tracking-wide);\n }\n .whitespace-nowrap {\n white-space: nowrap;\n }\n .text-gray-200 {\n color: var(--color-gray-200);\n }\n .text-gray-300 {\n color: var(--color-gray-300);\n }\n .text-gray-400 {\n color: var(--color-gray-400);\n }\n .text-gray-500 {\n color: var(--color-gray-500);\n }\n .text-gray-900 {\n color: var(--color-gray-900);\n }\n .text-sky-300 {\n color: var(--color-sky-300);\n }\n .text-white {\n color: var(--color-white);\n }\n .text-yellow-500 {\n color: var(--color-yellow-500);\n }\n .uppercase {\n text-transform: uppercase;\n }\n .opacity-0 {\n opacity: 0%;\n }\n .opacity-50 {\n opacity: 50%;\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 .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 .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-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-32 {\n @media (width >= 64rem) {\n height: calc(var(--spacing) * 32);\n }\n }\n .lg\\:w-32 {\n @media (width >= 64rem) {\n width: calc(var(--spacing) * 32);\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.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-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@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-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";
39
+ var css_248z$4 = "/*! tailwindcss v4.1.17 | MIT License | https://tailwindcss.com */\n@layer properties;\n@layer theme, base, components, utilities;\n@layer theme {\n :root, :host {\n --font-sans: ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\",\n \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\",\n \"Courier New\", monospace;\n --color-red-400: oklch(70.4% 0.191 22.216);\n --color-red-500: oklch(63.7% 0.237 25.331);\n --color-red-600: oklch(57.7% 0.245 27.325);\n --color-red-700: oklch(50.5% 0.213 27.518);\n --color-green-500: oklch(72.3% 0.219 149.579);\n --color-sky-300: oklch(82.8% 0.111 230.318);\n --color-blue-500: oklch(62.3% 0.214 259.815);\n --color-purple-500: oklch(62.7% 0.265 303.9);\n --color-gray-200: oklch(92.8% 0.006 264.531);\n --color-gray-300: oklch(87.2% 0.01 258.338);\n --color-gray-400: oklch(70.7% 0.022 261.325);\n --color-gray-500: oklch(55.1% 0.027 264.364);\n --color-gray-600: oklch(44.6% 0.03 256.802);\n --color-gray-700: oklch(37.3% 0.034 259.733);\n --color-gray-900: oklch(21% 0.034 264.665);\n --color-black: #000;\n --color-white: #fff;\n --spacing: 0.25rem;\n --container-md: 28rem;\n --text-xs: 0.75rem;\n --text-xs--line-height: calc(1 / 0.75);\n --text-sm: 0.875rem;\n --text-sm--line-height: calc(1.25 / 0.875);\n --text-base: 1rem;\n --text-base--line-height: calc(1.5 / 1);\n --text-lg: 1.125rem;\n --text-lg--line-height: calc(1.75 / 1.125);\n --text-xl: 1.25rem;\n --text-xl--line-height: calc(1.75 / 1.25);\n --text-2xl: 1.5rem;\n --text-2xl--line-height: calc(2 / 1.5);\n --text-3xl: 1.875rem;\n --text-3xl--line-height: calc(2.25 / 1.875);\n --font-weight-normal: 400;\n --font-weight-medium: 500;\n --font-weight-semibold: 600;\n --font-weight-bold: 700;\n --tracking-wider: 0.05em;\n --radius-md: 0.375rem;\n --radius-lg: 0.5rem;\n --ease-out: cubic-bezier(0, 0, 0.2, 1);\n --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);\n --animate-spin: spin 1s linear infinite;\n --blur-sm: 8px;\n --blur-md: 12px;\n --default-transition-duration: 150ms;\n --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);\n --default-font-family: var(--font-sans);\n --default-mono-font-family: var(--font-mono);\n }\n}\n@layer base {\n *, ::after, ::before, ::backdrop, ::file-selector-button {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n border: 0 solid;\n }\n html, :host {\n line-height: 1.5;\n -webkit-text-size-adjust: 100%;\n tab-size: 4;\n font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\");\n font-feature-settings: var(--default-font-feature-settings, normal);\n font-variation-settings: var(--default-font-variation-settings, normal);\n -webkit-tap-highlight-color: transparent;\n }\n hr {\n height: 0;\n color: inherit;\n border-top-width: 1px;\n }\n abbr:where([title]) {\n -webkit-text-decoration: underline dotted;\n text-decoration: underline dotted;\n }\n h1, h2, h3, h4, h5, h6 {\n font-size: inherit;\n font-weight: inherit;\n }\n a {\n color: inherit;\n -webkit-text-decoration: inherit;\n text-decoration: inherit;\n }\n b, strong {\n font-weight: bolder;\n }\n code, kbd, samp, pre {\n font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace);\n font-feature-settings: var(--default-mono-font-feature-settings, normal);\n font-variation-settings: var(--default-mono-font-variation-settings, normal);\n font-size: 1em;\n }\n small {\n font-size: 80%;\n }\n sub, sup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n }\n sub {\n bottom: -0.25em;\n }\n sup {\n top: -0.5em;\n }\n table {\n text-indent: 0;\n border-color: inherit;\n border-collapse: collapse;\n }\n :-moz-focusring {\n outline: auto;\n }\n progress {\n vertical-align: baseline;\n }\n summary {\n display: list-item;\n }\n ol, ul, menu {\n list-style: none;\n }\n img, svg, video, canvas, audio, iframe, embed, object {\n display: block;\n vertical-align: middle;\n }\n img, video {\n max-width: 100%;\n height: auto;\n }\n button, input, select, optgroup, textarea, ::file-selector-button {\n font: inherit;\n font-feature-settings: inherit;\n font-variation-settings: inherit;\n letter-spacing: inherit;\n color: inherit;\n border-radius: 0;\n background-color: transparent;\n opacity: 1;\n }\n :where(select:is([multiple], [size])) optgroup {\n font-weight: bolder;\n }\n :where(select:is([multiple], [size])) optgroup option {\n padding-inline-start: 20px;\n }\n ::file-selector-button {\n margin-inline-end: 4px;\n }\n ::placeholder {\n opacity: 1;\n }\n @supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) {\n ::placeholder {\n color: currentcolor;\n @supports (color: color-mix(in lab, red, red)) {\n color: color-mix(in oklab, currentcolor 50%, transparent);\n }\n }\n }\n textarea {\n resize: vertical;\n }\n ::-webkit-search-decoration {\n -webkit-appearance: none;\n }\n ::-webkit-date-and-time-value {\n min-height: 1lh;\n text-align: inherit;\n }\n ::-webkit-datetime-edit {\n display: inline-flex;\n }\n ::-webkit-datetime-edit-fields-wrapper {\n padding: 0;\n }\n ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field {\n padding-block: 0;\n }\n ::-webkit-calendar-picker-indicator {\n line-height: 1;\n }\n :-moz-ui-invalid {\n box-shadow: none;\n }\n button, input:where([type=\"button\"], [type=\"reset\"], [type=\"submit\"]), ::file-selector-button {\n appearance: button;\n }\n ::-webkit-inner-spin-button, ::-webkit-outer-spin-button {\n height: auto;\n }\n [hidden]:where(:not([hidden=\"until-found\"])) {\n display: none !important;\n }\n}\n@layer utilities {\n .pointer-events-none {\n pointer-events: none;\n }\n .absolute {\n position: absolute;\n }\n .relative {\n position: relative;\n }\n .static {\n position: static;\n }\n .inset-0 {\n inset: calc(var(--spacing) * 0);\n }\n .-top-2 {\n top: calc(var(--spacing) * -2);\n }\n .top-0 {\n top: calc(var(--spacing) * 0);\n }\n .top-1\\/2 {\n top: calc(1/2 * 100%);\n }\n .top-full {\n top: 100%;\n }\n .right-0 {\n right: calc(var(--spacing) * 0);\n }\n .right-32 {\n right: calc(var(--spacing) * 32);\n }\n .right-full {\n right: 100%;\n }\n .bottom-36 {\n bottom: calc(var(--spacing) * 36);\n }\n .bottom-full {\n bottom: 100%;\n }\n .left-0 {\n left: calc(var(--spacing) * 0);\n }\n .left-1\\/2 {\n left: calc(1/2 * 100%);\n }\n .left-32 {\n left: calc(var(--spacing) * 32);\n }\n .left-full {\n left: 100%;\n }\n .z-40 {\n z-index: 40;\n }\n .z-50 {\n z-index: 50;\n }\n .z-\\[-1\\] {\n z-index: -1;\n }\n .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-2 {\n margin-top: calc(var(--spacing) * 2);\n }\n .mt-4 {\n margin-top: calc(var(--spacing) * 4);\n }\n .mr-2 {\n margin-right: calc(var(--spacing) * 2);\n }\n .mb-1 {\n margin-bottom: calc(var(--spacing) * 1);\n }\n .mb-2 {\n margin-bottom: calc(var(--spacing) * 2);\n }\n .mb-4 {\n margin-bottom: calc(var(--spacing) * 4);\n }\n .ml-1 {\n margin-left: calc(var(--spacing) * 1);\n }\n .ml-2 {\n margin-left: calc(var(--spacing) * 2);\n }\n .flex {\n display: flex;\n }\n .hidden {\n display: none;\n }\n .inline {\n display: inline;\n }\n .inline-block {\n display: inline-block;\n }\n .inline-flex {\n display: inline-flex;\n }\n .h-1 {\n height: calc(var(--spacing) * 1);\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-24 {\n height: calc(var(--spacing) * 24);\n }\n .h-full {\n height: 100%;\n }\n .max-h-80 {\n max-height: calc(var(--spacing) * 80);\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-24 {\n width: calc(var(--spacing) * 24);\n }\n .w-80 {\n width: calc(var(--spacing) * 80);\n }\n .w-\\[2px\\] {\n width: 2px;\n }\n .w-\\[10vw\\] {\n width: 10vw;\n }\n .w-\\[15vw\\] {\n width: 15vw;\n }\n .w-fit {\n width: fit-content;\n }\n .w-full {\n width: 100%;\n }\n .max-w-md {\n max-width: var(--container-md);\n }\n .flex-1 {\n flex: 1;\n }\n .shrink-0 {\n flex-shrink: 0;\n }\n .-translate-x-1\\/2 {\n --tw-translate-x: calc(calc(1/2 * 100%) * -1);\n translate: var(--tw-translate-x) var(--tw-translate-y);\n }\n .-translate-y-1\\/2 {\n --tw-translate-y: calc(calc(1/2 * 100%) * -1);\n translate: var(--tw-translate-x) var(--tw-translate-y);\n }\n .rotate-45 {\n rotate: 45deg;\n }\n .rotate-180 {\n rotate: 180deg;\n }\n .transform {\n transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);\n }\n .animate-spin {\n animation: var(--animate-spin);\n }\n .cursor-not-allowed {\n cursor: not-allowed;\n }\n .cursor-pointer {\n cursor: pointer;\n }\n .flex-col {\n flex-direction: column;\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\\/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\\/10 {\n border-color: color-mix(in srgb, #fff 10%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n border-color: color-mix(in oklab, var(--color-white) 10%, transparent);\n }\n }\n .border-white\\/30 {\n border-color: color-mix(in srgb, #fff 30%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n border-color: color-mix(in oklab, var(--color-white) 30%, transparent);\n }\n }\n .border-white\\/40 {\n border-color: color-mix(in srgb, #fff 40%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n border-color: color-mix(in oklab, var(--color-white) 40%, transparent);\n }\n }\n .bg-\\[\\#3a4049\\] {\n background-color: #3a4049;\n }\n .bg-\\[\\#454545\\] {\n background-color: #454545;\n }\n .bg-\\[rgba\\(0\\,0\\,0\\,0\\.5\\)\\] {\n background-color: rgba(0,0,0,0.5);\n }\n .bg-black {\n background-color: var(--color-black);\n }\n .bg-black\\/60 {\n background-color: color-mix(in srgb, #000 60%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-black) 60%, transparent);\n }\n }\n .bg-black\\/90 {\n background-color: color-mix(in srgb, #000 90%, transparent);\n @supports (color: color-mix(in lab, red, red)) {\n background-color: color-mix(in oklab, var(--color-black) 90%, transparent);\n }\n }\n .bg-blue-500 {\n background-color: var(--color-blue-500);\n }\n .bg-gray-500 {\n background-color: var(--color-gray-500);\n }\n .bg-gray-900 {\n background-color: var(--color-gray-900);\n }\n .bg-green-500 {\n background-color: var(--color-green-500);\n }\n .bg-purple-500 {\n background-color: var(--color-purple-500);\n }\n .bg-red-600 {\n background-color: var(--color-red-600);\n }\n .bg-white {\n background-color: var(--color-white);\n }\n .bg-white\\/5 {\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 .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\\/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-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 .px-20 {\n padding-inline: calc(var(--spacing) * 20);\n }\n .py-1 {\n padding-block: calc(var(--spacing) * 1);\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 .pb-3 {\n padding-bottom: calc(var(--spacing) * 3);\n }\n .pb-4 {\n padding-bottom: calc(var(--spacing) * 4);\n }\n .pb-6 {\n padding-bottom: calc(var(--spacing) * 6);\n }\n .pb-10 {\n padding-bottom: calc(var(--spacing) * 10);\n }\n .pb-16 {\n padding-bottom: calc(var(--spacing) * 16);\n }\n .text-center {\n text-align: center;\n }\n .text-left {\n text-align: left;\n }\n .text-lg {\n font-size: var(--text-lg);\n line-height: var(--tw-leading, var(--text-lg--line-height));\n }\n .text-sm {\n font-size: var(--text-sm);\n line-height: var(--tw-leading, var(--text-sm--line-height));\n }\n .text-xl {\n font-size: var(--text-xl);\n line-height: var(--tw-leading, var(--text-xl--line-height));\n }\n .text-xs {\n font-size: var(--text-xs);\n line-height: var(--tw-leading, var(--text-xs--line-height));\n }\n .font-bold {\n --tw-font-weight: var(--font-weight-bold);\n font-weight: var(--font-weight-bold);\n }\n .font-medium {\n --tw-font-weight: var(--font-weight-medium);\n font-weight: var(--font-weight-medium);\n }\n .font-normal {\n --tw-font-weight: var(--font-weight-normal);\n font-weight: var(--font-weight-normal);\n }\n .font-semibold {\n --tw-font-weight: var(--font-weight-semibold);\n font-weight: var(--font-weight-semibold);\n }\n .tracking-wider {\n --tw-tracking: var(--tracking-wider);\n letter-spacing: var(--tracking-wider);\n }\n .whitespace-nowrap {\n white-space: nowrap;\n }\n .text-gray-200 {\n color: var(--color-gray-200);\n }\n .text-gray-300 {\n color: var(--color-gray-300);\n }\n .text-gray-400 {\n color: var(--color-gray-400);\n }\n .text-gray-500 {\n color: var(--color-gray-500);\n }\n .text-gray-900 {\n color: var(--color-gray-900);\n }\n .text-red-400 {\n color: var(--color-red-400);\n }\n .text-red-500 {\n color: var(--color-red-500);\n }\n .text-red-600 {\n color: var(--color-red-600);\n }\n .text-sky-300 {\n color: var(--color-sky-300);\n }\n .text-white {\n color: var(--color-white);\n }\n .normal-case {\n text-transform: none;\n }\n .opacity-0 {\n opacity: 0%;\n }\n .opacity-50 {\n opacity: 50%;\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 .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 .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-32 {\n @media (width >= 64rem) {\n height: calc(var(--spacing) * 32);\n }\n }\n .lg\\:w-32 {\n @media (width >= 64rem) {\n width: calc(var(--spacing) * 32);\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.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-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@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-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
40
  styleInject(css_248z$4,{"insertAt":"top"});
41
41
 
42
42
  const createVideoRefsSlice = (set) => ({
@@ -138,6 +138,12 @@ const createAdsSlice = (set, get) => ({
138
138
  setAdVideoRef: (adVideoRef) => set({ adVideoRef }),
139
139
  });
140
140
 
141
+ const createErrorSlice = (set) => ({
142
+ error: null,
143
+ setError: (error) => set({ error }),
144
+ clearError: () => set({ error: null }),
145
+ });
146
+
141
147
  const createResetSlice = (set, get) => ({
142
148
  resetStore: () => {
143
149
  const safeStopMediaElement = (media) => {
@@ -211,6 +217,7 @@ const useVideoStore = create()((set, get, store) => ({
211
217
  ...createEpisodesSlice(set),
212
218
  ...createIntroSlice(set),
213
219
  ...createAdsSlice(set),
220
+ ...createErrorSlice(set),
214
221
  ...createResetSlice(set, get),
215
222
  }));
216
223
 
@@ -635,7 +642,26 @@ const VideoSeekSlider = ({ max = 1000, currentTime = 0, bufferTime = 0, hideThum
635
642
  React__default.createElement(Thumb, { max: max, currentTime: currentTime, isThumbActive: isThumbActive, trackColor: trackColor })));
636
643
  };
637
644
 
638
- const BottomControls = ({ config }) => {
645
+ // Memoized time formatter to prevent unnecessary recalculations
646
+ const formatTimeMemo = (() => {
647
+ const cache = new Map();
648
+ return (seconds) => {
649
+ if (cache.has(seconds)) {
650
+ return cache.get(seconds);
651
+ }
652
+ const formatted = timeFormat(seconds);
653
+ cache.set(seconds, formatted);
654
+ // Limit cache size to prevent memory leaks
655
+ if (cache.size > 100) {
656
+ const firstKey = cache.keys().next().value;
657
+ if (firstKey !== undefined) {
658
+ cache.delete(firstKey);
659
+ }
660
+ }
661
+ return formatted;
662
+ };
663
+ })();
664
+ const BottomControls = memo(({ config }) => {
639
665
  const { videoRef, currentTime, isFullscreen, bufferedProgress, isAdPlaying } = useVideoStore(useShallow((state) => ({
640
666
  videoRef: state.videoRef,
641
667
  currentTime: state.currentTime,
@@ -658,20 +684,27 @@ const BottomControls = ({ config }) => {
658
684
  }
659
685
  return secondsToMilliseconds(duration * (bufferedValue / 100));
660
686
  }, [bufferedValue, duration]);
661
- const durationFormatted = useMemo(() => timeFormat(duration), [duration]);
662
- const currentTimeFormatted = useMemo(() => timeFormat(currentTimeValue), [currentTimeValue]);
687
+ // Round to nearest second for time display to reduce re-renders
688
+ const roundedCurrentTime = useMemo(() => Math.floor(currentTimeValue), [currentTimeValue]);
689
+ const roundedDuration = useMemo(() => Math.floor(duration), [duration]);
690
+ const durationFormatted = useMemo(() => formatTimeMemo(roundedDuration), [roundedDuration]);
691
+ const currentTimeFormatted = useMemo(() => formatTimeMemo(roundedCurrentTime), [roundedCurrentTime]);
692
+ // Memoize seek slider props to prevent unnecessary re-renders
693
+ const seekSliderMax = useMemo(() => secondsToMilliseconds(duration), [duration]);
694
+ const seekSliderCurrentTime = useMemo(() => secondsToMilliseconds(currentTimeValue), [currentTimeValue]);
663
695
  if (isAdPlaying) {
664
696
  return null;
665
697
  }
666
698
  return (React__default.createElement("div", { className: "px-10" },
667
- React__default.createElement(VideoSeekSlider, { max: secondsToMilliseconds(duration), currentTime: secondsToMilliseconds(currentTimeValue), bufferTime: bufferTime, onChange: handleSeek, secondsPrefix: "00:00:", minutesPrefix: "00:", getPreviewScreenUrl: config?.seekBarConfig?.getPreviewScreenUrl, timeCodes: config?.seekBarConfig?.timeCodes, trackColor: config?.seekBarConfig?.trackColor }),
699
+ React__default.createElement(VideoSeekSlider, { max: seekSliderMax, currentTime: seekSliderCurrentTime, bufferTime: bufferTime, onChange: handleSeek, secondsPrefix: "00:00:", minutesPrefix: "00:", getPreviewScreenUrl: config?.seekBarConfig?.getPreviewScreenUrl, timeCodes: config?.seekBarConfig?.timeCodes, trackColor: config?.seekBarConfig?.trackColor }),
668
700
  React__default.createElement("div", { className: `pt-6 ${isFullscreen ? "pb-10" : "pb-16"} lg:pb-12 flex items-center gap-4 text-white` },
669
701
  React__default.createElement("span", { className: "text-lg lg:text-2xl font-semibold text-white cursor-pointer hover:text-gray-200 transition-colors duration-200" }, currentTimeFormatted),
670
702
  React__default.createElement("span", { className: "text-lg lg:text-3xl font-semibold text-gray-500 cursor-pointer hover:text-gray-200 transition-colors duration-200" }, "/"),
671
703
  React__default.createElement("span", { className: "text-lg lg:text-2xl font-semibold text-gray-400 cursor-pointer hover:text-gray-200 transition-colors duration-200" }, durationFormatted))));
672
- };
704
+ });
705
+ BottomControls.displayName = "BottomControls";
673
706
 
674
- var css_248z$2 = ".icon-button {\n width: 20px;\n height: 20px;\n cursor: pointer;\n color: #9ca3af;\n transition: color 0.2s ease-in-out;\n}\n\n.icon-button:hover {\n color: #e5e7eb;\n}\n\n@media (min-width: 1024px) {\n .icon-button {\n width: 32px;\n height: 32px;\n }\n}\n\n.fullscreen-icon {\n width: 20px;\n height: 20px;\n cursor: pointer;\n color: #9ca3af;\n transition: color 0.2s ease-in-out;\n}\n\n.fullscreen-icon:hover {\n color: #e5e7eb;\n}\n\n@media (min-width: 1024px) {\n .fullscreen-icon {\n width: 32px;\n height: 32px;\n }\n}\n\n.pip-toggle {\n cursor: pointer;\n color: #9ca3af;\n transition: color 0.2s ease-in-out;\n}\n\n.pip-toggle:hover {\n color: #e5e7eb;\n}\n\n.pip-icon {\n width: 15px;\n height: 15px;\n}\n\n@media (min-width: 1024px) {\n .pip-icon {\n width: 28px;\n height: 28px;\n }\n}\n";
707
+ var css_248z$2 = ".icon-button {\n width: 20px;\n height: 20px;\n cursor: pointer;\n color: #9ca3af;\n transition: color 0.2s ease-in-out;\n}\n\n.icon-button:hover {\n color: #e5e7eb;\n}\n\n@media (min-width: 1024px) {\n .icon-button {\n width: 32px;\n height: 32px;\n }\n}\n\n.fullscreen-icon {\n width: 20px;\n height: 20px;\n cursor: pointer;\n color: #9ca3af;\n transition: color 0.2s ease-in-out;\n}\n\n.fullscreen-icon:hover {\n color: #e5e7eb;\n}\n\n@media (min-width: 1024px) {\n .fullscreen-icon {\n width: 32px;\n height: 32px;\n }\n}\n\n.pip-toggle {\n cursor: pointer;\n color: #9ca3af;\n transition: color 0.2s ease-in-out;\n}\n\n.pip-toggle:hover {\n color: #e5e7eb;\n}\n\n.pip-icon {\n width: 15px;\n height: 15px;\n}\n\n@media (min-width: 1024px) {\n .pip-icon {\n width: 28px;\n height: 28px;\n }\n}\n\n/* Ad Badge Header - Dark/Black Background */\n.ad-badge-header {\n background: rgba(45, 47, 49, 0.95);\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n box-shadow: \n 0 4px 16px rgba(0, 0, 0, 0.4),\n 0 0 0 1px rgba(255, 255, 255, 0.1);\n border: 1px solid rgba(255, 255, 255, 0.1);\n position: relative;\n}\n\n/* Time display in badge - lighter gray */\n.ad-badge-header > span:last-child {\n color: rgba(209, 213, 219, 0.9);\n font-weight: 500;\n letter-spacing: 0.025em;\n}\n\n/* Pulse animation for the dot */\n@keyframes pulse-dot {\n 0%, 100% {\n opacity: 1;\n transform: scale(1);\n }\n 50% {\n opacity: 0.7;\n transform: scale(0.9);\n }\n}\n\n.ad-badge-header .animate-pulse {\n animation: pulse-dot 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;\n}\n\n/* Responsive adjustments */\n@media (max-width: 768px) {\n .ad-badge-header {\n padding: 0.375rem 0.75rem;\n font-size: 0.625rem;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .ad-badge-header .animate-pulse {\n animation: none;\n }\n}\n\n@media (prefers-contrast: high) {\n .ad-badge-header {\n background: #2d2f31;\n border: 2px solid #fff;\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.6);\n }\n}";
675
708
  styleInject(css_248z$2,{"insertAt":"top"});
676
709
 
677
710
  const FullScreenToggle = ({ isFullScreen, onClick, className = "fullscreen-icon", }) => {
@@ -754,13 +787,44 @@ const Tooltip = ({ children, title, position = "top", className, }) => {
754
787
 
755
788
  const Settings = ({ iconClassName }) => {
756
789
  const { qualityLevels, activeQuality, currentQuality, subtitles, activeSubtitle, setActiveSubtitle, videoRef, streamType, } = useVideoStore();
757
- const [speed, setSpeed] = React.useState(1);
790
+ // Load playback speed from localStorage or default to 1
791
+ const getStoredPlaybackSpeed = () => {
792
+ try {
793
+ const stored = localStorage.getItem("react-player-playback-speed");
794
+ if (stored) {
795
+ const speed = parseFloat(stored);
796
+ if (speedOptions.includes(speed)) {
797
+ return speed;
798
+ }
799
+ }
800
+ }
801
+ catch (_error) {
802
+ // Ignore localStorage errors
803
+ }
804
+ return 1;
805
+ };
806
+ const [speed, setSpeed] = React.useState(getStoredPlaybackSpeed());
758
807
  const [activeMenu, setActiveMenu] = React.useState("main");
808
+ // Initialize playback speed from localStorage on mount
809
+ React.useEffect(() => {
810
+ if (videoRef) {
811
+ const storedSpeed = getStoredPlaybackSpeed();
812
+ videoRef.playbackRate = storedSpeed;
813
+ setSpeed(storedSpeed);
814
+ }
815
+ }, [videoRef]);
759
816
  const handleSpeedChange = (newSpeed) => {
760
817
  setSpeed(newSpeed);
761
818
  if (videoRef) {
762
819
  videoRef.playbackRate = newSpeed;
763
820
  }
821
+ // Persist to localStorage
822
+ try {
823
+ localStorage.setItem("react-player-playback-speed", newSpeed.toString());
824
+ }
825
+ catch (_error) {
826
+ // Ignore localStorage errors
827
+ }
764
828
  };
765
829
  const isAdaptiveStream = streamType === "hls" || streamType === "dash";
766
830
  const qualityOptions = React.useMemo(() => {
@@ -928,7 +992,7 @@ const Settings = ({ iconClassName }) => {
928
992
 
929
993
  const ControlsHeader = ({ config }) => {
930
994
  const iconClassName = "icon-button";
931
- const { videoWrapperRef, videoRef, adVideoRef, episodeList, currentEpisodeIndex, resetStore, isAdPlaying, muted, setMuted, } = useVideoStore(useShallow((state) => ({
995
+ const { videoWrapperRef, videoRef, adVideoRef, episodeList, currentEpisodeIndex, resetStore, isAdPlaying, muted, setMuted, adCurrentTime, } = useVideoStore(useShallow((state) => ({
932
996
  videoWrapperRef: state.videoWrapperRef,
933
997
  videoRef: state.videoRef,
934
998
  adVideoRef: state.adVideoRef,
@@ -938,7 +1002,40 @@ const ControlsHeader = ({ config }) => {
938
1002
  isAdPlaying: state.isAdPlaying,
939
1003
  muted: state.muted,
940
1004
  setMuted: state.setMuted,
1005
+ adCurrentTime: state.adCurrentTime,
941
1006
  })));
1007
+ const [adDuration, setAdDuration] = React.useState(0);
1008
+ React.useEffect(() => {
1009
+ if (!adVideoRef || !isAdPlaying) {
1010
+ setAdDuration(0);
1011
+ return;
1012
+ }
1013
+ const updateDuration = () => {
1014
+ if (adVideoRef.duration && Number.isFinite(adVideoRef.duration)) {
1015
+ setAdDuration(adVideoRef.duration);
1016
+ }
1017
+ };
1018
+ updateDuration();
1019
+ adVideoRef.addEventListener("loadedmetadata", updateDuration);
1020
+ adVideoRef.addEventListener("durationchange", updateDuration);
1021
+ return () => {
1022
+ adVideoRef.removeEventListener("loadedmetadata", updateDuration);
1023
+ adVideoRef.removeEventListener("durationchange", updateDuration);
1024
+ };
1025
+ }, [adVideoRef, isAdPlaying]);
1026
+ const formatTime = React.useCallback((seconds) => {
1027
+ if (isNaN(seconds) || seconds < 0)
1028
+ return "0:00";
1029
+ const mins = Math.floor(seconds / 60);
1030
+ const secs = Math.floor(seconds % 60);
1031
+ return `${mins}:${secs.toString().padStart(2, "0")}`;
1032
+ }, []);
1033
+ const adTimeRemaining = React.useMemo(() => {
1034
+ if (adDuration <= 0 || adCurrentTime <= 0)
1035
+ return null;
1036
+ const remaining = Math.max(0, adDuration - adCurrentTime);
1037
+ return formatTime(remaining);
1038
+ }, [adDuration, adCurrentTime, formatTime]);
942
1039
  const [isPipActive, setIsPipActive] = React.useState(false);
943
1040
  const [isFullscreen, setIsFullscreen] = React.useState(false);
944
1041
  const handleFullscreen = () => {
@@ -1004,7 +1101,9 @@ const ControlsHeader = ({ config }) => {
1004
1101
  }
1005
1102
  };
1006
1103
  const renderAdHeader = () => (React.createElement("div", { className: "flex items-center gap-4" },
1007
- React.createElement("span", { className: "inline-flex items-center rounded bg-[#2D2F31] px-3 py-1 text-xs font-semibold uppercase tracking-wide text-yellow-500" }, "Ad")));
1104
+ React.createElement("span", { className: "inline-flex items-center gap-1 rounded-full px-4 py-2 font-medium text-xs tracking-wider text-red-600 border border-gray-700/60" },
1105
+ React.createElement("span", null, "Ad"),
1106
+ adTimeRemaining && (React.createElement("span", { className: "text-gray-300 font-normal normal-case ml-1 text-xs" }, adTimeRemaining)))));
1008
1107
  const renderVideoHeader = () => (React.createElement("div", { className: "flex" },
1009
1108
  React.createElement("div", null,
1010
1109
  React.createElement("h1", { className: "text-gray-200 text-lg lg:text-2xl font-semibold" }, episodeList.length > 0
@@ -1062,7 +1161,10 @@ const MiddleControls = () => {
1062
1161
  setIsPlaying: state.setIsPlaying,
1063
1162
  isAdPlaying: state.isAdPlaying,
1064
1163
  })));
1065
- const [isBuffering, setIsBuffering] = useState(false);
1164
+ const { setIsBuffering } = useVideoStore(useShallow((state) => ({
1165
+ setIsBuffering: state.setIsBuffering,
1166
+ })));
1167
+ const [isBuffering, setIsBufferingLocal] = useState(false);
1066
1168
  const videoElement = isAdPlaying ? adVideoRef : videoRef;
1067
1169
  const resetControlsVisibility = useCallback(() => {
1068
1170
  if (typeof window === "undefined") {
@@ -1103,15 +1205,33 @@ const MiddleControls = () => {
1103
1205
  useEffect(() => {
1104
1206
  if (!videoElement)
1105
1207
  return;
1106
- const handleWaiting = () => setIsBuffering(true);
1107
- const handlePlaying = () => setIsBuffering(false);
1208
+ const handleWaiting = () => {
1209
+ setIsBufferingLocal(true);
1210
+ setIsBuffering(true);
1211
+ };
1212
+ const handlePlaying = () => {
1213
+ setIsBufferingLocal(false);
1214
+ setIsBuffering(false);
1215
+ };
1216
+ const handleCanPlay = () => {
1217
+ setIsBufferingLocal(false);
1218
+ setIsBuffering(false);
1219
+ };
1220
+ const handleStalled = () => {
1221
+ setIsBufferingLocal(true);
1222
+ setIsBuffering(true);
1223
+ };
1108
1224
  videoElement.addEventListener("waiting", handleWaiting);
1109
1225
  videoElement.addEventListener("playing", handlePlaying);
1226
+ videoElement.addEventListener("canplay", handleCanPlay);
1227
+ videoElement.addEventListener("stalled", handleStalled);
1110
1228
  return () => {
1111
1229
  videoElement.removeEventListener("waiting", handleWaiting);
1112
1230
  videoElement.removeEventListener("playing", handlePlaying);
1231
+ videoElement.removeEventListener("canplay", handleCanPlay);
1232
+ videoElement.removeEventListener("stalled", handleStalled);
1113
1233
  };
1114
- }, [videoElement, isAdPlaying]);
1234
+ }, [videoElement, isAdPlaying, setIsBuffering]);
1115
1235
  useEffect(() => {
1116
1236
  const handleKeyDown = (e) => {
1117
1237
  if (!videoElement || isAdPlaying)
@@ -1143,7 +1263,8 @@ const MiddleControls = () => {
1143
1263
  ]);
1144
1264
  if (isAdPlaying) {
1145
1265
  return (React__default.createElement("div", { className: "flex justify-center items-center" },
1146
- React__default.createElement(ControlButton, { onClick: handlePlayPause, className: "w-[10vw]", icon: isBuffering ? (React__default.createElement(Loader, { className: "w-24 h-24 lg:w-32 lg:h-32 animate-spin text-white" })) : isPlaying ? (React__default.createElement(PauseIcon, null)) : (React__default.createElement(PlayIcon, null)) })));
1266
+ React__default.createElement(ControlButton, { onClick: handlePlayPause, className: "w-[10vw]", icon: isBuffering ? (React__default.createElement("div", { className: "relative" },
1267
+ React__default.createElement(Loader, { className: "w-24 h-24 lg:w-32 lg:h-32 animate-spin text-white" }))) : isPlaying ? (React__default.createElement(PauseIcon, null)) : (React__default.createElement(PlayIcon, null)) })));
1147
1268
  }
1148
1269
  return (React__default.createElement("div", { className: "flex justify-center items-center" },
1149
1270
  React__default.createElement(ControlButton, { onClick: handleBackward, className: "w-[15vw]", icon: React__default.createElement(BackwardIcon, null) }),
@@ -1159,7 +1280,7 @@ const VideoPlayerControls = ({ config }) => {
1159
1280
  React.createElement(BottomControls, { config: config?.bottomConfig?.config }))));
1160
1281
  };
1161
1282
 
1162
- const VideoActionButton = ({ text, onClick, icon, disabled = false, position = "left", }) => {
1283
+ const VideoActionButton = React__default.memo(({ text, onClick, icon, disabled = false, position = "left", }) => {
1163
1284
  // Increase icon size and apply consistent color to icon
1164
1285
  const renderedIcon = icon
1165
1286
  ? React__default.cloneElement(icon, {
@@ -1170,9 +1291,10 @@ const VideoActionButton = ({ text, onClick, icon, disabled = false, position = "
1170
1291
  React__default.createElement("button", { onClick: onClick, disabled: disabled, className: "\n bg-white/80 text-gray-900 font-semibold px-6 py-2 \n rounded-md text-sm flex items-center\n backdrop-blur-sm shadow-md\n hover:bg-white/90\n transition\n focus:outline-none focus:ring-2 focus:ring-offset-1 focus:ring-gray-400\n disabled:opacity-50 disabled:cursor-not-allowed disabled:bg-white/50\n " },
1171
1292
  renderedIcon && React__default.createElement("span", { className: "inline mr-2" }, renderedIcon),
1172
1293
  text)));
1173
- };
1294
+ });
1295
+ VideoActionButton.displayName = "VideoActionButton";
1174
1296
 
1175
- const Overlay = ({ config }) => {
1297
+ const Overlay = React__default.memo(({ config }) => {
1176
1298
  const controlsTimerRef = useRef(null);
1177
1299
  const containerRef = useRef(null);
1178
1300
  const { setControls, controls, showCountdown, countdownTime, setShowCountdown, setAutoPlayNext, setCurrentEpisodeIndex, episodeList, setCountdownTime, videoRef, currentEpisodeIndex, isAdPlaying, } = useVideoStore(useShallow((state) => ({
@@ -1219,7 +1341,9 @@ const Overlay = ({ config }) => {
1219
1341
  let timer;
1220
1342
  if (showCountdown && countdownTime > 0 && episodeList.length > 0) {
1221
1343
  timer = setInterval(() => {
1222
- setCountdownTime(countdownTime - 1);
1344
+ const currentTime = useVideoStore.getState().countdownTime;
1345
+ const next = currentTime - 1;
1346
+ setCountdownTime(next > 0 ? next : 0);
1223
1347
  }, 1000);
1224
1348
  }
1225
1349
  return () => {
@@ -1239,7 +1363,7 @@ const Overlay = ({ config }) => {
1239
1363
  window.removeEventListener(CONTROL_INTERACTION_EVENT, handleExternalInteraction);
1240
1364
  };
1241
1365
  }, [handleControlsInteraction]);
1242
- const handleNextEpisodeManually = () => {
1366
+ const handleNextEpisodeManually = useCallback(() => {
1243
1367
  const nextIndex = currentEpisodeIndex + 1;
1244
1368
  if (nextIndex < episodeList.length && videoRef && episodeList[nextIndex]) {
1245
1369
  setCurrentEpisodeIndex(nextIndex);
@@ -1253,63 +1377,89 @@ const Overlay = ({ config }) => {
1253
1377
  else if (onClose) {
1254
1378
  onClose();
1255
1379
  }
1256
- };
1380
+ }, [
1381
+ currentEpisodeIndex,
1382
+ episodeList,
1383
+ videoRef,
1384
+ setCurrentEpisodeIndex,
1385
+ setAutoPlayNext,
1386
+ setShowCountdown,
1387
+ setCountdownTime,
1388
+ handleControlsInteraction,
1389
+ onClose,
1390
+ ]);
1391
+ // Memoize countdown display to prevent unnecessary re-renders
1392
+ const shouldShowCountdown = useMemo(() => showCountdown &&
1393
+ episodeList.length > 0 &&
1394
+ currentEpisodeIndex + 1 < episodeList.length, [showCountdown, episodeList.length, currentEpisodeIndex]);
1257
1395
  return (React__default.createElement("div", { id: "videoPlayerControls", ref: containerRef, className: "absolute inset-0", onMouseMove: handleMouseEnter },
1258
1396
  controls && !isAdPlaying && React__default.createElement(VideoPlayerControls, { config: config }),
1259
- showCountdown &&
1260
- episodeList.length > 0 &&
1261
- currentEpisodeIndex + 1 < episodeList.length && (React__default.createElement(VideoActionButton, { text: "Next Episode", onClick: handleNextEpisodeManually, icon: React__default.createElement(ArrowRight, { className: "h-5 w-5 text-gray-900" }), disabled: currentEpisodeIndex + 1 >= episodeList.length, position: "right" }))));
1262
- };
1397
+ shouldShowCountdown && (React__default.createElement(VideoActionButton, { text: "Next Episode", onClick: handleNextEpisodeManually, icon: React__default.createElement(ArrowRight, { className: "h-5 w-5 text-gray-900" }), disabled: currentEpisodeIndex + 1 >= episodeList.length, position: "right" }))));
1398
+ });
1399
+ Overlay.displayName = "Overlay";
1263
1400
 
1264
- const SubtitleOverlay = ({ styleConfig }) => {
1265
- const { videoRef, activeSubtitle } = useVideoStore();
1401
+ const SubtitleOverlay = React__default.memo(({ styleConfig }) => {
1402
+ const { videoRef, activeSubtitle } = useVideoStore(useShallow((state) => ({
1403
+ videoRef: state.videoRef,
1404
+ activeSubtitle: state.activeSubtitle,
1405
+ })));
1266
1406
  const [currentSubtitle, setCurrentSubtitle] = useState("");
1267
1407
  const [isVisible, setIsVisible] = useState(false);
1408
+ const rafRef = useRef(null);
1268
1409
  useEffect(() => {
1269
1410
  if (!videoRef)
1270
1411
  return;
1271
1412
  const handleTimeUpdate = () => {
1272
- if (!activeSubtitle) {
1273
- setCurrentSubtitle("");
1274
- setIsVisible(false);
1413
+ if (rafRef.current !== null)
1275
1414
  return;
1276
- }
1277
- const currentTime = videoRef.currentTime;
1278
- const textTracks = Array.from(videoRef.textTracks);
1279
- const activeTrack = textTracks.find((track) => track.mode === "showing" && track.label === activeSubtitle.label);
1280
- if (activeTrack && activeTrack.cues) {
1281
- const activeCues = Array.from(activeTrack.cues).filter((cue) => currentTime >= cue.startTime && currentTime <= cue.endTime);
1282
- if (activeCues.length > 0) {
1283
- const cue = activeCues[0];
1284
- let cueText = "";
1285
- try {
1286
- if ("text" in cue) {
1287
- cueText = cue.text;
1288
- }
1289
- else if (typeof cue.getCueAsHTML === "function") {
1290
- const htmlElement = cue.getCueAsHTML();
1291
- cueText =
1292
- htmlElement?.textContent || htmlElement?.innerText || "";
1415
+ rafRef.current = requestAnimationFrame(() => {
1416
+ rafRef.current = null;
1417
+ if (!activeSubtitle) {
1418
+ setCurrentSubtitle("");
1419
+ setIsVisible(false);
1420
+ return;
1421
+ }
1422
+ const currentTime = videoRef.currentTime;
1423
+ const textTracks = Array.from(videoRef.textTracks);
1424
+ const activeTrack = textTracks.find((track) => track.mode === "showing" && track.label === activeSubtitle.label);
1425
+ if (activeTrack && activeTrack.cues) {
1426
+ const activeCues = Array.from(activeTrack.cues).filter((cue) => currentTime >= cue.startTime && currentTime <= cue.endTime);
1427
+ if (activeCues.length > 0) {
1428
+ const cue = activeCues[0];
1429
+ let cueText = "";
1430
+ try {
1431
+ if ("text" in cue) {
1432
+ cueText = cue.text;
1433
+ }
1434
+ else if (typeof cue.getCueAsHTML === "function") {
1435
+ const htmlElement = cue.getCueAsHTML();
1436
+ cueText =
1437
+ htmlElement?.textContent || htmlElement?.innerText || "";
1438
+ }
1439
+ else {
1440
+ cueText = cue.toString() || "";
1441
+ }
1293
1442
  }
1294
- else {
1295
- cueText = cue.toString() || "";
1443
+ catch (_error) {
1444
+ cueText = "";
1296
1445
  }
1446
+ setCurrentSubtitle(cueText);
1447
+ setIsVisible(!!cueText);
1297
1448
  }
1298
- catch (_error) {
1299
- cueText = "";
1449
+ else {
1450
+ setCurrentSubtitle("");
1451
+ setIsVisible(false);
1300
1452
  }
1301
- setCurrentSubtitle(cueText);
1302
- setIsVisible(!!cueText);
1303
- }
1304
- else {
1305
- setCurrentSubtitle("");
1306
- setIsVisible(false);
1307
1453
  }
1308
- }
1454
+ });
1309
1455
  };
1310
1456
  videoRef.addEventListener("timeupdate", handleTimeUpdate);
1311
1457
  return () => {
1312
1458
  videoRef.removeEventListener("timeupdate", handleTimeUpdate);
1459
+ if (rafRef.current !== null) {
1460
+ cancelAnimationFrame(rafRef.current);
1461
+ rafRef.current = null;
1462
+ }
1313
1463
  };
1314
1464
  }, [videoRef, activeSubtitle]);
1315
1465
  useEffect(() => {
@@ -1366,7 +1516,8 @@ const SubtitleOverlay = ({ styleConfig }) => {
1366
1516
  pointerEvents: "none",
1367
1517
  };
1368
1518
  return React__default.createElement("div", { style: subtitleStyle }, currentSubtitle);
1369
- };
1519
+ });
1520
+ SubtitleOverlay.displayName = "SubtitleOverlay";
1370
1521
 
1371
1522
  const HLS_CONFIG = {
1372
1523
  enableWorker: true,
@@ -1821,50 +1972,62 @@ const useVideoSource = (trackSrc, type) => {
1821
1972
 
1822
1973
  const useSubtitles = (subtitles) => {
1823
1974
  const { videoRef, activeSubtitle, setSubtitles } = useVideoStore();
1975
+ const timeoutIdsRef = useRef([]);
1824
1976
  useEffect(() => {
1825
- if (videoRef) {
1826
- const tracks = videoRef.getElementsByTagName("track");
1827
- while (tracks.length > 0) {
1828
- videoRef.removeChild(tracks[0]);
1829
- }
1830
- Array.from(videoRef.textTracks).forEach((track) => {
1831
- track.mode = "disabled";
1832
- });
1833
- if (activeSubtitle && subtitles) {
1834
- const index = subtitles.findIndex((s) => s.label === activeSubtitle.label);
1835
- if (index !== -1) {
1836
- const trackElement = document.createElement("track");
1837
- trackElement.kind = "subtitles";
1838
- trackElement.label = activeSubtitle.label;
1839
- trackElement.srclang = activeSubtitle.lang;
1840
- trackElement.src = activeSubtitle.url;
1841
- trackElement.default = false;
1842
- videoRef.appendChild(trackElement);
1843
- Array.from(videoRef.textTracks).find((track) => track.label === activeSubtitle.label);
1844
- const handleTrackLoad = () => {
1977
+ // Clear any pending timeouts from previous effect runs
1978
+ timeoutIdsRef.current.forEach((id) => clearTimeout(id));
1979
+ timeoutIdsRef.current = [];
1980
+ if (!videoRef)
1981
+ return;
1982
+ const tracks = videoRef.getElementsByTagName("track");
1983
+ while (tracks.length > 0) {
1984
+ videoRef.removeChild(tracks[0]);
1985
+ }
1986
+ Array.from(videoRef.textTracks).forEach((track) => {
1987
+ track.mode = "disabled";
1988
+ });
1989
+ let trackElement = null;
1990
+ let handleTrackLoad = null;
1991
+ if (activeSubtitle && subtitles) {
1992
+ const index = subtitles.findIndex((s) => s.label === activeSubtitle.label);
1993
+ if (index !== -1) {
1994
+ trackElement = document.createElement("track");
1995
+ trackElement.kind = "subtitles";
1996
+ trackElement.label = activeSubtitle.label;
1997
+ trackElement.srclang = activeSubtitle.lang;
1998
+ trackElement.src = activeSubtitle.url;
1999
+ trackElement.default = false;
2000
+ videoRef.appendChild(trackElement);
2001
+ handleTrackLoad = () => {
2002
+ const textTrack = Array.from(videoRef.textTracks).find((track) => track.label === activeSubtitle.label);
2003
+ if (textTrack) {
2004
+ textTrack.mode = "showing";
2005
+ }
2006
+ };
2007
+ trackElement.addEventListener("load", handleTrackLoad);
2008
+ // Fallback attempts with proper cleanup tracking
2009
+ const attempts = [100, 500, 1000];
2010
+ attempts.forEach((delay) => {
2011
+ const timeoutId = setTimeout(() => {
1845
2012
  const textTrack = Array.from(videoRef.textTracks).find((track) => track.label === activeSubtitle.label);
1846
- if (textTrack) {
2013
+ if (textTrack && textTrack.mode !== "showing") {
1847
2014
  textTrack.mode = "showing";
1848
2015
  }
1849
- };
1850
- trackElement.addEventListener("load", handleTrackLoad);
1851
- const attempts = [100, 500, 1000];
1852
- attempts.forEach((delay) => {
1853
- setTimeout(() => {
1854
- const textTrack = Array.from(videoRef.textTracks).find((track) => track.label === activeSubtitle.label);
1855
- if (textTrack && textTrack.mode !== "showing") {
1856
- textTrack.mode = "showing";
1857
- }
1858
- }, delay);
1859
- });
1860
- }
1861
- }
1862
- else {
1863
- Array.from(videoRef.textTracks).forEach((track) => {
1864
- track.mode = "disabled";
2016
+ }, delay);
2017
+ timeoutIdsRef.current.push(timeoutId);
1865
2018
  });
1866
2019
  }
1867
2020
  }
2021
+ // Cleanup function
2022
+ return () => {
2023
+ // Clear all pending timeouts
2024
+ timeoutIdsRef.current.forEach((id) => clearTimeout(id));
2025
+ timeoutIdsRef.current = [];
2026
+ // Remove event listener if it was added
2027
+ if (trackElement && handleTrackLoad) {
2028
+ trackElement.removeEventListener("load", handleTrackLoad);
2029
+ }
2030
+ };
1868
2031
  }, [videoRef, activeSubtitle, subtitles]);
1869
2032
  useEffect(() => {
1870
2033
  if (subtitles) {
@@ -1919,29 +2082,31 @@ const useSubtitleStyling = (config) => {
1919
2082
  };
1920
2083
 
1921
2084
  const useVideoTracking = (tracking, episodeList, currentEpisodeIndex, onClose) => {
1922
- const { videoRef, setIsPlaying, setShowCountdown } = useVideoStore();
1923
- const startTime = useRef(null);
2085
+ const { videoRef, setShowCountdown } = useVideoStore();
1924
2086
  const isViewCounted = useRef(false);
2087
+ const lastVideoSrcRef = useRef(null);
2088
+ // Reset view count when video source changes
1925
2089
  useEffect(() => {
1926
2090
  if (!videoRef)
1927
2091
  return;
2092
+ const currentSrc = videoRef.src || videoRef.currentSrc;
2093
+ // If video source changed, reset the view count
2094
+ if (lastVideoSrcRef.current !== currentSrc) {
2095
+ isViewCounted.current = false;
2096
+ lastVideoSrcRef.current = currentSrc;
2097
+ }
2098
+ }, [videoRef?.src, videoRef?.currentSrc, videoRef]);
2099
+ useEffect(() => {
2100
+ if (!videoRef)
2101
+ return;
2102
+ // Only handle view tracking on play - setIsPlaying is handled by useVideoEvents
1928
2103
  const onPlay = () => {
1929
2104
  if (!isViewCounted.current) {
1930
2105
  isViewCounted.current = true;
1931
2106
  tracking?.onViewed?.();
1932
2107
  }
1933
- startTime.current = Date.now();
1934
- setIsPlaying(true);
1935
- };
1936
- const onPause = () => {
1937
- if (startTime.current) {
1938
- const elapsedTime = (Date.now() - startTime.current) / 1000;
1939
- const getCurrentTime = localStorage.getItem("current_time");
1940
- localStorage.setItem("current_time", (Number(getCurrentTime || 0) + elapsedTime).toString());
1941
- startTime.current = null;
1942
- }
1943
- setIsPlaying(false);
1944
2108
  };
2109
+ // Handle episode end logic - playback state is handled by useVideoEvents
1945
2110
  const onEnded = () => {
1946
2111
  if (episodeList &&
1947
2112
  episodeList.length > 0 &&
@@ -1959,11 +2124,9 @@ const useVideoTracking = (tracking, episodeList, currentEpisodeIndex, onClose) =
1959
2124
  }
1960
2125
  };
1961
2126
  videoRef.addEventListener("play", onPlay);
1962
- videoRef.addEventListener("pause", onPause);
1963
2127
  videoRef.addEventListener("ended", onEnded);
1964
2128
  return () => {
1965
2129
  videoRef.removeEventListener("play", onPlay);
1966
- videoRef.removeEventListener("pause", onPause);
1967
2130
  videoRef.removeEventListener("ended", onEnded);
1968
2131
  };
1969
2132
  }, [
@@ -1972,29 +2135,8 @@ const useVideoTracking = (tracking, episodeList, currentEpisodeIndex, onClose) =
1972
2135
  currentEpisodeIndex,
1973
2136
  onClose,
1974
2137
  tracking,
1975
- setIsPlaying,
1976
2138
  setShowCountdown,
1977
2139
  ]);
1978
- useEffect(() => {
1979
- const handleUnload = () => {
1980
- if (startTime.current) {
1981
- const elapsedTime = (Date.now() - startTime.current) / 1000;
1982
- const getCurrentTime = localStorage.getItem("current_time");
1983
- localStorage.setItem("current_time", (Number(getCurrentTime || 0) + elapsedTime).toString());
1984
- }
1985
- const totalTimeWatched = Number(localStorage.getItem("current_time") || 0);
1986
- if (totalTimeWatched >= 30) {
1987
- tracking?.onWatchTimeUpdated?.({
1988
- watchTime: totalTimeWatched,
1989
- });
1990
- }
1991
- localStorage.setItem("current_time", "0");
1992
- };
1993
- window.addEventListener("unload", handleUnload);
1994
- return () => {
1995
- window.removeEventListener("unload", handleUnload);
1996
- };
1997
- }, [tracking]);
1998
2140
  };
1999
2141
 
2000
2142
  const useIntroSkip = (intro) => {
@@ -2159,7 +2301,6 @@ const useVideoEvents = () => {
2159
2301
  const onLoadedMetadata = (e) => {
2160
2302
  const duration = e?.currentTarget?.duration;
2161
2303
  if (typeof duration === "number" && !Number.isNaN(duration)) {
2162
- localStorage.setItem("current_time", "0");
2163
2304
  setDuration(duration);
2164
2305
  }
2165
2306
  };
@@ -2267,8 +2408,13 @@ const useAdManager = (adConfig) => {
2267
2408
  })));
2268
2409
  const preRollPlayedRef = useRef(false);
2269
2410
  const postRollPlayedRef = useRef(false);
2270
- const midRollCheckIntervalRef = useRef(null);
2271
2411
  const resumeAfterAdRef = useRef(false);
2412
+ // Track maximum time reached to prevent ad replay when seeking backward
2413
+ const maxTimeReachedRef = useRef(0);
2414
+ // Throttle ad checking to prevent performance issues
2415
+ const adCheckThrottleRef = useRef(null);
2416
+ // Track if we're currently processing an ad to prevent race conditions
2417
+ const isProcessingAdRef = useRef(false);
2272
2418
  const stopMediaElement = useCallback((media) => {
2273
2419
  if (!media)
2274
2420
  return;
@@ -2288,36 +2434,29 @@ const useAdManager = (adConfig) => {
2288
2434
  setMidRollQueue([]);
2289
2435
  return;
2290
2436
  }
2291
- let sortedMidRolls = [...adConfig.midRoll].sort((a, b) => a.time - b.time);
2292
- if (adConfig.smartPlacement?.enabled) {
2293
- sortedMidRolls = applySmartPlacement(sortedMidRolls, duration, adConfig.smartPlacement);
2294
- }
2295
- setMidRollQueue(sortedMidRolls);
2296
- }, [adConfig?.midRoll, adConfig?.smartPlacement, duration, setMidRollQueue]);
2297
- const applySmartPlacement = (ads, videoDuration, options) => {
2298
- const minVideoDuration = options.minVideoDuration || 60;
2299
- const minGap = options.minGapBetweenAds || 30;
2300
- const avoidNearEnd = options.avoidNearEnd || 10;
2301
- if (videoDuration < minVideoDuration) {
2302
- return [];
2303
- }
2304
- const validAds = [];
2305
- let lastAdTime = 0;
2306
- for (const ad of ads) {
2307
- if (ad.time < minGap)
2308
- continue;
2309
- if (ad.time > videoDuration - avoidNearEnd)
2310
- continue;
2311
- if (ad.time - lastAdTime < minGap)
2312
- continue;
2313
- validAds.push(ad);
2314
- const adLength = Number.isFinite(ad.duration)
2315
- ? ad.duration
2316
- : 0;
2317
- lastAdTime = ad.time + adLength;
2437
+ // Filter out invalid ads and ensure all required fields are present
2438
+ const validAds = adConfig.midRoll.filter((ad) => ad &&
2439
+ typeof ad.time === "number" &&
2440
+ ad.time >= 0 &&
2441
+ ad.time < Number.MAX_SAFE_INTEGER &&
2442
+ typeof ad.id === "string" &&
2443
+ ad.id.trim() !== "" &&
2444
+ typeof ad.adUrl === "string" &&
2445
+ ad.adUrl.trim() !== "" &&
2446
+ typeof ad.type === "string" &&
2447
+ ad.type === "mid-roll");
2448
+ if (validAds.length === 0) {
2449
+ setMidRollQueue([]);
2450
+ return;
2318
2451
  }
2319
- return validAds;
2320
- };
2452
+ // Sort ads by time to ensure they play in order
2453
+ const sortedMidRolls = [...validAds].sort((a, b) => a.time - b.time);
2454
+ // Remove duplicate IDs (keep first occurrence)
2455
+ const uniqueAds = sortedMidRolls.filter((ad, index, self) => index === self.findIndex((a) => a.id === ad.id));
2456
+ setMidRollQueue(uniqueAds);
2457
+ }, [adConfig?.midRoll, setMidRollQueue]);
2458
+ // Removed smartPlacement - users should configure exact ad times
2459
+ // This ensures ads appear exactly when specified
2321
2460
  const playPreRollAd = async () => {
2322
2461
  if (!adConfig?.preRoll || preRollPlayedRef.current || !videoRef)
2323
2462
  return;
@@ -2333,6 +2472,46 @@ const useAdManager = (adConfig) => {
2333
2472
  setAdType("pre-roll");
2334
2473
  adConfig.onAdStart?.(adBreak);
2335
2474
  };
2475
+ const playMidRollAd = useCallback(async (adBreak) => {
2476
+ if (!videoRef || isAdPlaying || isProcessingAdRef.current)
2477
+ return;
2478
+ // Prevent duplicate ad playback
2479
+ if (playedAdBreaks.includes(adBreak.id))
2480
+ return;
2481
+ // Mark as processing to prevent race conditions
2482
+ isProcessingAdRef.current = true;
2483
+ const updatedQueue = midRollQueue.filter((ad) => ad.id !== adBreak.id);
2484
+ setMidRollQueue(updatedQueue);
2485
+ addPlayedAdBreak(adBreak.id);
2486
+ const wasPlaying = !videoRef.paused;
2487
+ videoRef.pause();
2488
+ setPlaying(false);
2489
+ setIsPlaying(false);
2490
+ resumeAfterAdRef.current = wasPlaying;
2491
+ stopMediaElement(useVideoStore.getState().adVideoRef);
2492
+ setIsAdPlaying(true);
2493
+ setCurrentAd(adBreak);
2494
+ setAdType("mid-roll");
2495
+ adConfig?.onAdStart?.(adBreak);
2496
+ // Reset processing flag after a short delay
2497
+ setTimeout(() => {
2498
+ isProcessingAdRef.current = false;
2499
+ }, 100);
2500
+ }, [
2501
+ videoRef,
2502
+ isAdPlaying,
2503
+ playedAdBreaks,
2504
+ midRollQueue,
2505
+ setMidRollQueue,
2506
+ addPlayedAdBreak,
2507
+ setPlaying,
2508
+ setIsPlaying,
2509
+ setCurrentAd,
2510
+ setAdType,
2511
+ setIsAdPlaying,
2512
+ adConfig,
2513
+ stopMediaElement,
2514
+ ]);
2336
2515
  const playPostRollAd = async () => {
2337
2516
  if (!adConfig?.postRoll || postRollPlayedRef.current || !videoRef)
2338
2517
  return;
@@ -2350,23 +2529,40 @@ const useAdManager = (adConfig) => {
2350
2529
  const adTypeState = useVideoStore.getState().adType;
2351
2530
  const videoRefState = useVideoStore.getState().videoRef;
2352
2531
  const adVideoRefState = useVideoStore.getState().adVideoRef;
2353
- if (!videoRefState || !currentAdState)
2532
+ if (!currentAdState)
2354
2533
  return;
2534
+ // Reset processing flag
2535
+ isProcessingAdRef.current = false;
2536
+ // Clean up ad video element
2355
2537
  if (adVideoRefState) {
2356
2538
  stopMediaElement(adVideoRefState);
2357
2539
  setAdVideoRef(null);
2358
2540
  }
2541
+ // Reset ad state
2359
2542
  setIsAdPlaying(false);
2360
2543
  setCurrentAd(null);
2361
2544
  setAdType(null);
2362
2545
  setAdCurrentTime(0);
2363
2546
  setCanSkipAd(false);
2364
2547
  setSkipCountdown(0);
2548
+ // Call end callback
2365
2549
  adConfig?.onAdEnd?.(currentAdState);
2366
- if (resumeAfterAdRef.current && adTypeState !== "post-roll") {
2367
- videoRefState.play().catch(() => undefined);
2368
- setPlaying(true);
2369
- setIsPlaying(true);
2550
+ // Resume main video if needed
2551
+ if (resumeAfterAdRef.current &&
2552
+ adTypeState !== "post-roll" &&
2553
+ videoRefState) {
2554
+ // Small delay to ensure ad cleanup is complete
2555
+ setTimeout(() => {
2556
+ if (videoRefState && !videoRefState.paused)
2557
+ return;
2558
+ videoRefState.play().catch(() => {
2559
+ // If autoplay fails, user will need to click play
2560
+ setPlaying(false);
2561
+ setIsPlaying(false);
2562
+ });
2563
+ setPlaying(true);
2564
+ setIsPlaying(true);
2565
+ }, 100);
2370
2566
  }
2371
2567
  resumeAfterAdRef.current = false;
2372
2568
  }, [
@@ -2443,46 +2639,87 @@ const useAdManager = (adConfig) => {
2443
2639
  videoRef.removeEventListener("canplay", handleCanPlay);
2444
2640
  };
2445
2641
  }, [videoRef, adConfig?.preRoll]);
2642
+ // Precise mid-roll ad checking with accurate timing
2446
2643
  useEffect(() => {
2447
2644
  if (!videoRef || !adConfig?.midRoll || isAdPlaying) {
2448
- if (midRollCheckIntervalRef.current) {
2449
- clearInterval(midRollCheckIntervalRef.current);
2450
- midRollCheckIntervalRef.current = null;
2645
+ // Clear any pending throttle
2646
+ if (adCheckThrottleRef.current !== null) {
2647
+ cancelAnimationFrame(adCheckThrottleRef.current);
2648
+ adCheckThrottleRef.current = null;
2451
2649
  }
2452
2650
  return;
2453
2651
  }
2454
- midRollCheckIntervalRef.current = setInterval(() => {
2652
+ // Precise ad check function
2653
+ const checkMidRollAds = () => {
2654
+ // Clear throttle ref
2655
+ adCheckThrottleRef.current = null;
2455
2656
  const state = useVideoStore.getState();
2456
- if (state.isAdPlaying ||
2457
- !state.midRollQueue ||
2458
- state.midRollQueue.length === 0) {
2657
+ // Skip if ad is already playing or being processed
2658
+ if (state.isAdPlaying || isProcessingAdRef.current) {
2459
2659
  return;
2460
2660
  }
2461
- const nextAd = state.midRollQueue[0];
2462
- if (nextAd &&
2463
- state.currentTime >= nextAd.time &&
2464
- !state.playedAdBreaks.includes(nextAd.id)) {
2465
- const updatedQueue = state.midRollQueue.filter((ad) => ad.id !== nextAd.id);
2466
- state.setMidRollQueue(updatedQueue);
2467
- state.addPlayedAdBreak(nextAd.id);
2468
- const wasPlaying = !state.videoRef?.paused;
2469
- state.videoRef?.pause();
2470
- state.setPlaying(false);
2471
- state.setIsPlaying(false);
2472
- resumeAfterAdRef.current = wasPlaying;
2473
- state.setIsAdPlaying(true);
2474
- state.setCurrentAd(nextAd);
2475
- state.setAdType("mid-roll");
2476
- adConfig?.onAdStart?.(nextAd);
2477
- }
2478
- }, 1000);
2661
+ // Check if we have mid-roll ads in queue
2662
+ if (!state.midRollQueue || state.midRollQueue.length === 0) {
2663
+ return;
2664
+ }
2665
+ // Get current time directly from video element for maximum accuracy
2666
+ const currentVideoTime = videoRef.currentTime || 0;
2667
+ // Update max time reached (only forward, not backward)
2668
+ if (currentVideoTime > maxTimeReachedRef.current) {
2669
+ maxTimeReachedRef.current = currentVideoTime;
2670
+ }
2671
+ // Find the next ad in queue that should play
2672
+ // Check ads in order, but skip already-played ones
2673
+ for (const ad of state.midRollQueue) {
2674
+ // Skip if already played
2675
+ if (state.playedAdBreaks.includes(ad.id)) {
2676
+ continue;
2677
+ }
2678
+ // Precise timing check: ad should trigger when we reach or pass its time
2679
+ // Use 1 second tolerance to catch ads even if timeupdate fires slightly late
2680
+ const timeDifference = currentVideoTime - ad.time;
2681
+ const shouldTrigger = timeDifference >= 0 && timeDifference <= 1.0;
2682
+ // Also check if we've reached the max time (prevents replay on backward seek)
2683
+ // This ensures ads only play if we've actually watched past them
2684
+ const hasReachedMaxTime = maxTimeReachedRef.current >= ad.time;
2685
+ if (shouldTrigger && hasReachedMaxTime) {
2686
+ // Play the ad and break (only one ad at a time)
2687
+ playMidRollAd(ad);
2688
+ break;
2689
+ }
2690
+ }
2691
+ };
2692
+ // Throttle function using requestAnimationFrame for smooth performance
2693
+ const throttledCheck = () => {
2694
+ if (adCheckThrottleRef.current === null) {
2695
+ adCheckThrottleRef.current = requestAnimationFrame(checkMidRollAds);
2696
+ }
2697
+ };
2698
+ // Listen to timeupdate event (throttled for performance)
2699
+ videoRef.addEventListener("timeupdate", throttledCheck);
2700
+ // Also check immediately on seek to catch rapid seeks past ad times
2701
+ const handleSeeking = () => {
2702
+ // Force immediate check when seeking
2703
+ if (adCheckThrottleRef.current !== null) {
2704
+ cancelAnimationFrame(adCheckThrottleRef.current);
2705
+ adCheckThrottleRef.current = null;
2706
+ }
2707
+ // Check immediately
2708
+ checkMidRollAds();
2709
+ };
2710
+ videoRef.addEventListener("seeking", handleSeeking);
2711
+ videoRef.addEventListener("seeked", handleSeeking);
2479
2712
  return () => {
2480
- if (midRollCheckIntervalRef.current) {
2481
- clearInterval(midRollCheckIntervalRef.current);
2482
- midRollCheckIntervalRef.current = null;
2713
+ videoRef.removeEventListener("timeupdate", throttledCheck);
2714
+ videoRef.removeEventListener("seeking", handleSeeking);
2715
+ videoRef.removeEventListener("seeked", handleSeeking);
2716
+ // Clear any pending animation frame
2717
+ if (adCheckThrottleRef.current !== null) {
2718
+ cancelAnimationFrame(adCheckThrottleRef.current);
2719
+ adCheckThrottleRef.current = null;
2483
2720
  }
2484
2721
  };
2485
- }, [videoRef, isAdPlaying, adConfig]);
2722
+ }, [videoRef, isAdPlaying, adConfig, playMidRollAd]);
2486
2723
  useEffect(() => {
2487
2724
  if (!videoRef || !adConfig?.postRoll || postRollPlayedRef.current)
2488
2725
  return;
@@ -2499,19 +2736,46 @@ const useAdManager = (adConfig) => {
2499
2736
  useEffect(() => {
2500
2737
  if (!videoRef?.src)
2501
2738
  return;
2739
+ // Reset ad state when video source changes
2502
2740
  preRollPlayedRef.current = false;
2503
2741
  postRollPlayedRef.current = false;
2504
2742
  resumeAfterAdRef.current = false;
2743
+ maxTimeReachedRef.current = 0;
2744
+ isProcessingAdRef.current = false;
2745
+ // Clear any pending throttle
2746
+ if (adCheckThrottleRef.current !== null) {
2747
+ cancelAnimationFrame(adCheckThrottleRef.current);
2748
+ adCheckThrottleRef.current = null;
2749
+ }
2505
2750
  setIsAdPlaying(false);
2506
2751
  setCurrentAd(null);
2507
2752
  setAdType(null);
2753
+ // Re-initialize mid-roll queue with strict validation
2508
2754
  if (adConfig?.midRoll && adConfig.midRoll.length > 0) {
2509
- const sortedMidRolls = [...adConfig.midRoll].sort((a, b) => a.time - b.time);
2510
- setMidRollQueue(sortedMidRolls);
2755
+ // Filter and validate ads
2756
+ const validAds = adConfig.midRoll.filter((ad) => ad &&
2757
+ typeof ad.time === "number" &&
2758
+ ad.time >= 0 &&
2759
+ typeof ad.id === "string" &&
2760
+ ad.id.trim() !== "" &&
2761
+ typeof ad.adUrl === "string" &&
2762
+ ad.adUrl.trim() !== "" &&
2763
+ typeof ad.type === "string" &&
2764
+ ad.type === "mid-roll");
2765
+ if (validAds.length > 0) {
2766
+ // Sort by time and remove duplicates
2767
+ const sortedMidRolls = [...validAds].sort((a, b) => a.time - b.time);
2768
+ const uniqueAds = sortedMidRolls.filter((ad, index, self) => index === self.findIndex((a) => a.id === ad.id));
2769
+ setMidRollQueue(uniqueAds);
2770
+ }
2771
+ else {
2772
+ setMidRollQueue([]);
2773
+ }
2511
2774
  }
2512
2775
  else {
2513
2776
  setMidRollQueue([]);
2514
2777
  }
2778
+ // Clean up any lingering ad video
2515
2779
  const lingeringAdRef = useVideoStore.getState().adVideoRef;
2516
2780
  if (lingeringAdRef) {
2517
2781
  stopMediaElement(lingeringAdRef);
@@ -2721,13 +2985,93 @@ const usePrimaryVideoLifecycle = ({ hasPreRoll, trackSrc, }) => {
2721
2985
  };
2722
2986
  };
2723
2987
 
2724
- const AdOverlay = ({ adBreak, onSkip, config }) => {
2725
- const { adVideoRef, setAdVideoRef, adCurrentTime, setAdCurrentTime, canSkipAd, setCanSkipAd, skipCountdown, setSkipCountdown, videoRef, muted, setIsPlaying, } = useVideoStore();
2988
+ const getErrorType = (code) => {
2989
+ // MediaError codes: https://developer.mozilla.org/en-US/docs/Web/API/MediaError/code
2990
+ switch (code) {
2991
+ case 1: // MEDIA_ERR_ABORTED
2992
+ return "unknown";
2993
+ case 2: // MEDIA_ERR_NETWORK
2994
+ return "network";
2995
+ case 3: // MEDIA_ERR_DECODE
2996
+ return "decode";
2997
+ case 4: // MEDIA_ERR_SRC_NOT_SUPPORTED
2998
+ return "src";
2999
+ default:
3000
+ return "unknown";
3001
+ }
3002
+ };
3003
+ const getErrorMessage = (code) => {
3004
+ switch (code) {
3005
+ case 1:
3006
+ return "Video playback was aborted.";
3007
+ case 2:
3008
+ return "A network error occurred while loading the video.";
3009
+ case 3:
3010
+ return "An error occurred while decoding the video.";
3011
+ case 4:
3012
+ return "The video format is not supported.";
3013
+ default:
3014
+ return "An unknown error occurred.";
3015
+ }
3016
+ };
3017
+ const useVideoError = () => {
3018
+ const { setError, clearError, error } = useVideoStore();
3019
+ const handleVideoError = useCallback((e) => {
3020
+ const video = e.currentTarget;
3021
+ const mediaError = video.error;
3022
+ if (mediaError) {
3023
+ const errorData = {
3024
+ code: mediaError.code,
3025
+ message: mediaError.message || getErrorMessage(mediaError.code),
3026
+ type: getErrorType(mediaError.code),
3027
+ };
3028
+ setError(errorData);
3029
+ }
3030
+ else {
3031
+ setError({
3032
+ code: 0,
3033
+ message: "An unknown error occurred.",
3034
+ type: "unknown",
3035
+ });
3036
+ }
3037
+ }, [setError]);
3038
+ const retry = useCallback(() => {
3039
+ clearError();
3040
+ const { videoRef } = useVideoStore.getState();
3041
+ if (videoRef) {
3042
+ videoRef.load();
3043
+ videoRef.play().catch(() => undefined);
3044
+ }
3045
+ }, [clearError]);
3046
+ return {
3047
+ error,
3048
+ handleVideoError,
3049
+ clearError,
3050
+ retry,
3051
+ };
3052
+ };
3053
+
3054
+ const AdOverlay = React__default.memo(({ adBreak, onSkip, config }) => {
3055
+ const { adVideoRef, setAdVideoRef, adCurrentTime, setAdCurrentTime, canSkipAd, setCanSkipAd, skipCountdown, setSkipCountdown, videoRef, muted, setIsPlaying, } = useVideoStore(useShallow((state) => ({
3056
+ adVideoRef: state.adVideoRef,
3057
+ setAdVideoRef: state.setAdVideoRef,
3058
+ adCurrentTime: state.adCurrentTime,
3059
+ setAdCurrentTime: state.setAdCurrentTime,
3060
+ canSkipAd: state.canSkipAd,
3061
+ setCanSkipAd: state.setCanSkipAd,
3062
+ skipCountdown: state.skipCountdown,
3063
+ setSkipCountdown: state.setSkipCountdown,
3064
+ videoRef: state.videoRef,
3065
+ muted: state.muted,
3066
+ setIsPlaying: state.setIsPlaying,
3067
+ })));
2726
3068
  const [showControls, setShowControls] = useState(true);
2727
3069
  const [isHovered, setIsHovered] = useState(false);
2728
3070
  const [adDuration, setAdDuration] = useState(0);
2729
3071
  const [requiresInteraction, setRequiresInteraction] = useState(false);
3072
+ const [adLoadError, setAdLoadError] = useState(false);
2730
3073
  const controlsTimeoutRef = useRef(null);
3074
+ const loadTimeoutRef = useRef(null);
2731
3075
  const safelySetCanSkipAd = useCallback((value) => {
2732
3076
  if (useVideoStore.getState().canSkipAd !== value) {
2733
3077
  setCanSkipAd(value);
@@ -2768,7 +3112,23 @@ const AdOverlay = ({ adBreak, onSkip, config }) => {
2768
3112
  useEffect(() => {
2769
3113
  setAdDuration(0);
2770
3114
  setRequiresInteraction(false);
2771
- }, [adBreak.id]);
3115
+ setAdLoadError(false);
3116
+ setAdCurrentTime(0);
3117
+ if (loadTimeoutRef.current) {
3118
+ clearTimeout(loadTimeoutRef.current);
3119
+ loadTimeoutRef.current = null;
3120
+ }
3121
+ if (adBreak.skipable !== undefined) {
3122
+ setCanSkipAd(false);
3123
+ setSkipCountdown(0);
3124
+ }
3125
+ }, [
3126
+ adBreak.id,
3127
+ adBreak.skipable,
3128
+ setAdCurrentTime,
3129
+ setCanSkipAd,
3130
+ setSkipCountdown,
3131
+ ]);
2772
3132
  useEffect(() => {
2773
3133
  if (!adBreak.skipable) {
2774
3134
  safelySetCanSkipAd(false);
@@ -2792,43 +3152,76 @@ const AdOverlay = ({ adBreak, onSkip, config }) => {
2792
3152
  if (!adVideoRef)
2793
3153
  return;
2794
3154
  setRequiresInteraction(false);
3155
+ setAdLoadError(false);
3156
+ if (!adVideoRef.src && adBreak.adUrl) {
3157
+ adVideoRef.src = adBreak.adUrl;
3158
+ adVideoRef.load();
3159
+ return;
3160
+ }
2795
3161
  const playPromise = adVideoRef.play();
2796
3162
  if (playPromise && "catch" in playPromise) {
2797
- playPromise.catch(() => {
3163
+ playPromise.catch((error) => {
3164
+ console.warn("Ad play failed:", error);
2798
3165
  setRequiresInteraction(true);
2799
3166
  setIsPlaying(false);
2800
3167
  });
2801
3168
  }
2802
- }, [adVideoRef, setIsPlaying]);
3169
+ }, [adVideoRef, adBreak.adUrl, setIsPlaying]);
3170
+ const timeUpdateRafRef = useRef(null);
3171
+ const lastUpdateTimeRef = useRef(0);
2803
3172
  useEffect(() => {
2804
3173
  if (!adVideoRef)
2805
3174
  return;
2806
3175
  const handleTimeUpdate = () => {
2807
- const currentTime = adVideoRef.currentTime;
2808
- setAdCurrentTime(currentTime);
2809
- if (adBreak.skipable) {
2810
- const remaining = skipAfter - currentTime;
2811
- if (remaining <= 0) {
2812
- safelySetCanSkipAd(true);
2813
- safelySetSkipCountdown(0);
3176
+ if (timeUpdateRafRef.current !== null)
3177
+ return;
3178
+ timeUpdateRafRef.current = requestAnimationFrame(() => {
3179
+ timeUpdateRafRef.current = null;
3180
+ const currentTime = adVideoRef.currentTime;
3181
+ if (Math.abs(currentTime - lastUpdateTimeRef.current) < 0.1) {
3182
+ return;
2814
3183
  }
2815
- else {
2816
- const remainingForDisplay = Math.max(Math.ceil(remaining), 0);
2817
- safelySetSkipCountdown(remainingForDisplay);
2818
- if (canSkipAd) {
2819
- safelySetCanSkipAd(false);
3184
+ lastUpdateTimeRef.current = currentTime;
3185
+ setAdCurrentTime(currentTime);
3186
+ if (adBreak.skipable) {
3187
+ const remaining = skipAfter - currentTime;
3188
+ if (remaining <= 0) {
3189
+ safelySetCanSkipAd(true);
3190
+ safelySetSkipCountdown(0);
3191
+ }
3192
+ else {
3193
+ const remainingForDisplay = Math.max(Math.ceil(remaining), 0);
3194
+ safelySetSkipCountdown(remainingForDisplay);
3195
+ if (canSkipAd) {
3196
+ safelySetCanSkipAd(false);
3197
+ }
2820
3198
  }
2821
3199
  }
2822
- }
3200
+ });
2823
3201
  };
2824
3202
  const handleLoadedMetadata = () => {
3203
+ if (loadTimeoutRef.current) {
3204
+ clearTimeout(loadTimeoutRef.current);
3205
+ loadTimeoutRef.current = null;
3206
+ }
2825
3207
  const duration = Number.isFinite(adVideoRef.duration)
2826
3208
  ? adVideoRef.duration
2827
3209
  : 0;
2828
3210
  setAdDuration(duration);
3211
+ setAdLoadError(false);
2829
3212
  setIsPlaying(!adVideoRef.paused);
2830
3213
  attemptAdPlayback();
2831
3214
  };
3215
+ if (loadTimeoutRef.current) {
3216
+ clearTimeout(loadTimeoutRef.current);
3217
+ }
3218
+ loadTimeoutRef.current = setTimeout(() => {
3219
+ if (adVideoRef && adVideoRef.readyState < 2) {
3220
+ console.warn("Ad load timeout:", adBreak.id);
3221
+ setAdLoadError(true);
3222
+ setRequiresInteraction(true);
3223
+ }
3224
+ }, 30000);
2832
3225
  const handlePlay = () => {
2833
3226
  setIsPlaying(true);
2834
3227
  setRequiresInteraction(false);
@@ -2843,7 +3236,21 @@ const AdOverlay = ({ adBreak, onSkip, config }) => {
2843
3236
  setIsPlaying(true);
2844
3237
  setRequiresInteraction(false);
2845
3238
  };
2846
- const handleError = () => {
3239
+ const handleError = (e) => {
3240
+ if (loadTimeoutRef.current) {
3241
+ clearTimeout(loadTimeoutRef.current);
3242
+ loadTimeoutRef.current = null;
3243
+ }
3244
+ const error = e.target;
3245
+ const errorCode = error.error?.code;
3246
+ const errorMessage = error.error?.message || "Unknown ad error";
3247
+ console.error("Ad playback error:", {
3248
+ adId: adBreak.id,
3249
+ errorCode,
3250
+ errorMessage,
3251
+ src: adVideoRef.src,
3252
+ });
3253
+ setAdLoadError(true);
2847
3254
  setRequiresInteraction(true);
2848
3255
  setIsPlaying(false);
2849
3256
  };
@@ -2862,10 +3269,20 @@ const AdOverlay = ({ adBreak, onSkip, config }) => {
2862
3269
  adVideoRef.removeEventListener("waiting", handleWaiting);
2863
3270
  adVideoRef.removeEventListener("playing", handlePlaying);
2864
3271
  adVideoRef.removeEventListener("error", handleError);
3272
+ if (loadTimeoutRef.current) {
3273
+ clearTimeout(loadTimeoutRef.current);
3274
+ loadTimeoutRef.current = null;
3275
+ }
3276
+ if (timeUpdateRafRef.current !== null) {
3277
+ cancelAnimationFrame(timeUpdateRafRef.current);
3278
+ timeUpdateRafRef.current = null;
3279
+ }
3280
+ lastUpdateTimeRef.current = 0;
2865
3281
  };
2866
3282
  }, [
2867
3283
  adVideoRef,
2868
3284
  adBreak.skipable,
3285
+ adBreak.id,
2869
3286
  skipAfter,
2870
3287
  canSkipAd,
2871
3288
  setAdCurrentTime,
@@ -2877,41 +3294,78 @@ const AdOverlay = ({ adBreak, onSkip, config }) => {
2877
3294
  useEffect(() => {
2878
3295
  if (!adVideoRef || !videoRef)
2879
3296
  return;
2880
- if (adVideoRef.readyState === 0) {
2881
- adVideoRef.volume = videoRef.volume;
2882
- adVideoRef.muted = muted;
2883
- adVideoRef.load();
3297
+ // Sync volume and muted state
3298
+ adVideoRef.volume = videoRef.volume;
3299
+ adVideoRef.muted = muted;
3300
+ // Check if src needs to be updated
3301
+ const currentSrc = adVideoRef.src || adVideoRef.currentSrc || "";
3302
+ const needsReload = !currentSrc || currentSrc !== adBreak.adUrl;
3303
+ // Load ad if needed
3304
+ if (needsReload && adBreak.adUrl) {
3305
+ // Clear previous src
3306
+ try {
3307
+ adVideoRef.pause();
3308
+ adVideoRef.removeAttribute("src");
3309
+ adVideoRef.src = "";
3310
+ // Set new src
3311
+ adVideoRef.src = adBreak.adUrl;
3312
+ adVideoRef.load();
3313
+ }
3314
+ catch (error) {
3315
+ console.warn("Error loading ad:", error);
3316
+ setAdLoadError(true);
3317
+ }
2884
3318
  }
2885
3319
  const handleCanPlay = () => {
2886
- if (adVideoRef && !adVideoRef.paused)
3320
+ if (!adVideoRef || adVideoRef.paused === false)
2887
3321
  return;
2888
3322
  attemptAdPlayback();
2889
3323
  };
3324
+ const handleLoadedData = () => {
3325
+ // Ensure volume is synced after load
3326
+ if (videoRef && adVideoRef) {
3327
+ try {
3328
+ adVideoRef.volume = videoRef.volume;
3329
+ adVideoRef.muted = muted;
3330
+ }
3331
+ catch (error) {
3332
+ // Ignore errors during cleanup
3333
+ }
3334
+ }
3335
+ };
2890
3336
  adVideoRef.addEventListener("canplay", handleCanPlay);
2891
- if (adVideoRef.readyState >= 3) {
3337
+ adVideoRef.addEventListener("loadeddata", handleLoadedData);
3338
+ // Try to play if already ready and src matches
3339
+ if (adVideoRef.readyState >= 3 && !needsReload) {
2892
3340
  attemptAdPlayback();
2893
3341
  }
2894
3342
  return () => {
2895
- adVideoRef.removeEventListener("canplay", handleCanPlay);
3343
+ if (adVideoRef) {
3344
+ adVideoRef.removeEventListener("canplay", handleCanPlay);
3345
+ adVideoRef.removeEventListener("loadeddata", handleLoadedData);
3346
+ }
2896
3347
  };
2897
- }, [adVideoRef, videoRef, attemptAdPlayback]);
3348
+ }, [adVideoRef, videoRef, muted, adBreak.adUrl, attemptAdPlayback]);
2898
3349
  useEffect(() => {
2899
- if (adVideoRef) {
3350
+ if (!adVideoRef)
3351
+ return;
3352
+ try {
3353
+ // Sync muted state
2900
3354
  adVideoRef.muted = muted;
3355
+ // Sync volume with main video
3356
+ if (videoRef) {
3357
+ adVideoRef.volume = videoRef.volume;
3358
+ }
2901
3359
  }
2902
- }, [adVideoRef, muted]);
3360
+ catch (error) {
3361
+ // Ignore errors during state sync
3362
+ }
3363
+ }, [adVideoRef, muted, videoRef]);
2903
3364
  const handleSkip = () => {
2904
3365
  if (canSkipAd && onSkip) {
2905
3366
  onSkip();
2906
3367
  }
2907
3368
  };
2908
- const formatTime = (seconds) => {
2909
- if (isNaN(seconds) || seconds < 0)
2910
- return "0:00";
2911
- const mins = Math.floor(seconds / 60);
2912
- const secs = Math.floor(seconds % 60);
2913
- return `${mins}:${secs.toString().padStart(2, "0")}`;
2914
- };
2915
3369
  const progressPercent = adDuration > 0 ? (adCurrentTime / adDuration) * 100 : 0;
2916
3370
  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: () => {
2917
3371
  setIsHovered(true);
@@ -2919,23 +3373,31 @@ const AdOverlay = ({ adBreak, onSkip, config }) => {
2919
3373
  } },
2920
3374
  React__default.createElement("div", { className: "relative flex-1 w-full flex items-center justify-center" },
2921
3375
  React__default.createElement("video", { ref: (ref) => {
2922
- if (ref && ref !== adVideoRef) {
2923
- ref.muted = muted;
3376
+ if (!ref)
3377
+ return;
3378
+ if (ref !== adVideoRef) {
2924
3379
  setAdVideoRef(ref);
2925
- if (ref.src !== adBreak.adUrl) {
3380
+ }
3381
+ ref.muted = muted;
3382
+ if (videoRef) {
3383
+ ref.volume = videoRef.volume;
3384
+ }
3385
+ if (adBreak.adUrl) {
3386
+ const currentSrc = ref.src || ref.currentSrc || "";
3387
+ if (currentSrc !== adBreak.adUrl) {
2926
3388
  ref.src = adBreak.adUrl;
2927
- ref.load();
2928
3389
  }
2929
3390
  }
2930
3391
  }, className: "w-full h-full object-contain", autoPlay: true, playsInline: true, muted: muted, preload: "auto", key: adBreak.id }),
2931
- requiresInteraction && (React__default.createElement("div", { className: "absolute inset-0 flex items-center justify-center bg-black/60 backdrop-blur-sm" },
2932
- 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" }, "Tap to Play Ad")))),
3392
+ (requiresInteraction || adLoadError) && (React__default.createElement("div", { className: "absolute inset-0 flex items-center justify-center bg-black/60 backdrop-blur-sm" },
3393
+ React__default.createElement("div", { className: "flex flex-col items-center gap-4" },
3394
+ adLoadError && (React__default.createElement("p", { className: "text-red-400 text-sm" }, "Ad failed to load")),
3395
+ 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"))))),
2933
3396
  React__default.createElement("div", { className: `absolute inset-0 transition-all duration-300 ${showControls ? "opacity-100" : "opacity-0 pointer-events-none"}` },
2934
3397
  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" },
2935
3398
  React__default.createElement("div", { className: "shrink-0 relative" },
2936
3399
  React__default.createElement(ControlsHeader, { config: {
2937
- title: adBreak.title ||
2938
- config?.config?.headerConfig?.config?.title ||
3400
+ title: config?.config?.headerConfig?.config?.title ||
2939
3401
  "Advertisement",
2940
3402
  isTrailer: config?.config?.headerConfig?.config?.isTrailer,
2941
3403
  onClose: config?.config?.headerConfig?.config?.onClose,
@@ -2955,21 +3417,53 @@ const AdOverlay = ({ adBreak, onSkip, config }) => {
2955
3417
  React__default.createElement("div", { className: "relative h-1 bg-white/20 rounded-full overflow-hidden pointer-events-none select-none" },
2956
3418
  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}%` } }),
2957
3419
  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)` } }))),
2958
- React__default.createElement("div", { className: "px-10 pb-6 flex items-center justify-between" },
2959
- React__default.createElement("div", { className: "flex items-center gap-4 text-white" },
2960
- React__default.createElement("span", { className: "text-lg lg:text-2xl font-semibold" }, formatTime(adCurrentTime)),
2961
- React__default.createElement("span", { className: "text-lg lg:text-3xl font-semibold text-gray-500" }, "/"),
2962
- React__default.createElement("span", { className: "text-lg lg:text-2xl font-semibold text-gray-400" }, formatTime(adDuration))),
2963
- sponsoredUrl && (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"))))))));
2964
- };
3420
+ sponsoredUrl && (React__default.createElement("div", { className: "px-10 pb-6 flex items-center justify-end" },
3421
+ 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"))))))));
3422
+ });
3423
+ AdOverlay.displayName = "AdOverlay";
3424
+
3425
+ const ErrorOverlay = React__default.memo(({ error, onRetry }) => {
3426
+ const getIcon = () => {
3427
+ switch (error.type) {
3428
+ case "network":
3429
+ return (React__default.createElement("svg", { className: "w-12 h-12 text-red-500", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24" },
3430
+ React__default.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M18.364 5.636a9 9 0 010 12.728m0 0l-2.829-2.829m2.829 2.829L21 21M15.536 8.464a5 5 0 010 7.072m0 0l-2.829-2.829m-4.243 2.829a4.978 4.978 0 01-1.414-2.83m-1.414 5.658a9 9 0 01-2.167-9.238m7.824 2.167a1 1 0 111.414 1.414m-1.414-1.414L3 3m8.293 8.293l1.414 1.414" })));
3431
+ case "src":
3432
+ return (React__default.createElement("svg", { className: "w-12 h-12 text-red-500", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24" },
3433
+ React__default.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z" }),
3434
+ React__default.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M3 3l18 18" })));
3435
+ default:
3436
+ return (React__default.createElement("svg", { className: "w-12 h-12 text-red-500", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24" },
3437
+ React__default.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" })));
3438
+ }
3439
+ };
3440
+ return (React__default.createElement("div", { className: "absolute inset-0 z-50 flex flex-col items-center justify-center bg-black/90" },
3441
+ React__default.createElement("div", { className: "flex flex-col items-center gap-4 p-6 text-center" },
3442
+ getIcon(),
3443
+ React__default.createElement("h3", { className: "text-xl font-semibold text-white" }, error.type === "network"
3444
+ ? "Network Error"
3445
+ : error.type === "src"
3446
+ ? "Video Unavailable"
3447
+ : "Playback Error"),
3448
+ React__default.createElement("p", { className: "text-sm text-gray-400 max-w-md" }, error.message),
3449
+ React__default.createElement("button", { onClick: onRetry, className: "mt-4 px-6 py-2 bg-red-600 hover:bg-red-700 text-white font-medium rounded-lg transition-colors duration-200 flex items-center gap-2" },
3450
+ React__default.createElement("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24" },
3451
+ React__default.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" })),
3452
+ "Retry"))));
3453
+ });
3454
+ ErrorOverlay.displayName = "ErrorOverlay";
2965
3455
 
2966
3456
  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";
2967
3457
  styleInject(css_248z$1,{"insertAt":"top"});
2968
3458
 
2969
- var css_248z = "@keyframes fade-in {\n from {\n opacity: 0;\n transform: translateY(-10px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n@keyframes slide-up {\n from {\n opacity: 0;\n transform: translateY(20px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n@keyframes pulse-glow {\n 0%,\n 100% {\n box-shadow: 0 0 10px rgba(239, 68, 68, 0.5);\n }\n 50% {\n box-shadow: 0 0 20px rgba(239, 68, 68, 0.8);\n }\n}\n\n.animate-fade-in {\n animation: fade-in 0.3s ease-out;\n}\n\n.animate-slide-up {\n animation: slide-up 0.3s ease-out;\n}\n\n.ad-progress-bar {\n position: relative;\n overflow: hidden;\n}\n\n.ad-progress-bar::after {\n content: \"\";\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: linear-gradient(\n 90deg,\n transparent,\n rgba(255, 255, 255, 0.3),\n transparent\n );\n animation: shimmer 2s infinite;\n}\n\n@keyframes shimmer {\n 0% {\n transform: translateX(-100%);\n }\n 100% {\n transform: translateX(100%);\n }\n}\n\n.ad-badge {\n background: linear-gradient(\n 135deg,\n rgba(239, 68, 68, 0.9),\n rgba(249, 115, 22, 0.9)\n );\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);\n}\n\n.ad-button {\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.ad-button:hover {\n transform: translateY(-2px);\n box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);\n}\n\n.ad-button:active {\n transform: translateY(0);\n}\n\n.skip-button-enabled {\n background: linear-gradient(\n 135deg,\n rgba(255, 255, 255, 0.2),\n rgba(255, 255, 255, 0.1)\n );\n backdrop-filter: blur(12px);\n -webkit-backdrop-filter: blur(12px);\n}\n\n.skip-button-enabled:hover {\n background: linear-gradient(\n 135deg,\n rgba(255, 255, 255, 0.3),\n rgba(255, 255, 255, 0.2)\n );\n box-shadow: 0 0 20px rgba(255, 255, 255, 0.2);\n}\n\n.skip-button-disabled {\n background: rgba(0, 0, 0, 0.4);\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n}\n\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 (max-width: 768px) {\n .ad-controls {\n padding: 1rem;\n gap: 0.75rem;\n }\n\n .ad-badge {\n font-size: 0.625rem;\n padding: 0.5rem 0.75rem;\n }\n\n .skip-button {\n padding: 0.5rem 1rem;\n font-size: 0.75rem;\n }\n}\n\n@media (max-width: 480px) {\n .ad-controls {\n padding: 0.75rem;\n gap: 0.5rem;\n }\n\n .ad-badge {\n font-size: 0.5rem;\n padding: 0.375rem 0.625rem;\n }\n\n .skip-button {\n padding: 0.375rem 0.75rem;\n font-size: 0.625rem;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .animate-fade-in,\n .animate-slide-up,\n .ad-progress-bar::after {\n animation: none;\n }\n\n .ad-button {\n transition: none;\n }\n}\n\n@media (prefers-contrast: high) {\n .ad-badge {\n background: #000;\n border: 2px solid #fff;\n }\n\n .skip-button-enabled {\n background: #fff;\n color: #000;\n border: 2px solid #000;\n }\n}\n";
3459
+ 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";
2970
3460
  styleInject(css_248z,{"insertAt":"top"});
2971
3461
 
2972
- const VideoPlayer = ({ trackSrc, trackTitle, intro, onClose, onError, trackPoster, isTrailer, className, type, height, width, timeCodes, getPreviewScreenUrl, tracking, subtitles, episodeList, currentEpisodeIndex = 0, onEnded, nextEpisodeConfig, subtitleStyle, showControls = true, isMute = false, ads, }) => {
3462
+ const VideoPlayer = React__default.memo(({ video, style, events, features }) => {
3463
+ const { src: trackSrc, title: trackTitle, poster: trackPoster, type, isTrailer, showControls = true, isMute = false, startFrom, } = video;
3464
+ const { className, width, height, subtitleStyle } = style || {};
3465
+ const { onEnded, onError, onClose, onWatchHistoryUpdate } = events || {};
3466
+ const { timeCodes, getPreviewScreenUrl, tracking, subtitles, episodeList, currentEpisodeIndex = 0, intro, nextEpisodeConfig, ads, } = features || {};
2973
3467
  const { setVideoWrapperRef } = useVideoStore(useShallow((state) => ({
2974
3468
  setVideoWrapperRef: state.setVideoWrapperRef,
2975
3469
  })));
@@ -2979,65 +3473,126 @@ const VideoPlayer = ({ trackSrc, trackTitle, intro, onClose, onError, trackPoste
2979
3473
  hasPreRoll,
2980
3474
  trackSrc,
2981
3475
  });
3476
+ const onWatchHistoryUpdateRef = React__default.useRef(onWatchHistoryUpdate);
3477
+ React__default.useEffect(() => {
3478
+ onWatchHistoryUpdateRef.current = onWatchHistoryUpdate;
3479
+ }, [onWatchHistoryUpdate]);
3480
+ const getWatchHistoryData = React__default.useCallback(() => {
3481
+ const video = useVideoStore.getState().videoRef;
3482
+ if (!video || !video.duration || isNaN(video.duration))
3483
+ return null;
3484
+ const currentTime = video.currentTime || 0;
3485
+ const duration = video.duration;
3486
+ const progress = Math.round((currentTime / duration) * 100);
3487
+ const isCompleted = progress >= 90;
3488
+ return {
3489
+ currentTime,
3490
+ duration,
3491
+ progress,
3492
+ isCompleted,
3493
+ watchedAt: Date.now(),
3494
+ };
3495
+ }, []);
3496
+ const handleClose = React__default.useCallback(() => {
3497
+ const historyData = getWatchHistoryData();
3498
+ if (historyData && onWatchHistoryUpdate) {
3499
+ onWatchHistoryUpdate(historyData);
3500
+ }
3501
+ onClose?.();
3502
+ }, [getWatchHistoryData, onWatchHistoryUpdate, onClose]);
3503
+ const overlayConfig = React__default.useMemo(() => ({
3504
+ headerConfig: {
3505
+ config: {
3506
+ isTrailer: isTrailer,
3507
+ title: trackTitle,
3508
+ onClose: handleClose,
3509
+ videoRef: videoRef,
3510
+ },
3511
+ },
3512
+ bottomConfig: {
3513
+ config: {
3514
+ seekBarConfig: {
3515
+ timeCodes: timeCodes,
3516
+ trackColor: "red",
3517
+ getPreviewScreenUrl,
3518
+ },
3519
+ },
3520
+ },
3521
+ }), [
3522
+ isTrailer,
3523
+ trackTitle,
3524
+ handleClose,
3525
+ videoRef,
3526
+ timeCodes,
3527
+ getPreviewScreenUrl,
3528
+ ]);
3529
+ const adOverlayConfig = React__default.useMemo(() => ({
3530
+ config: {
3531
+ headerConfig: {
3532
+ config: {
3533
+ isTrailer: isTrailer,
3534
+ title: trackTitle,
3535
+ onClose: handleClose,
3536
+ },
3537
+ },
3538
+ bottomConfig: {
3539
+ config: {
3540
+ seekBarConfig: {
3541
+ timeCodes: timeCodes,
3542
+ trackColor: "red",
3543
+ getPreviewScreenUrl,
3544
+ },
3545
+ },
3546
+ },
3547
+ },
3548
+ }), [isTrailer, trackTitle, handleClose, timeCodes, getPreviewScreenUrl]);
2982
3549
  useVideoSource(trackSrc, type);
2983
3550
  useSubtitles(subtitles);
2984
3551
  useSubtitleStyling(subtitleStyle);
2985
- useVideoTracking(tracking, episodeList, currentEpisodeIndex, onClose);
3552
+ useVideoTracking(tracking, episodeList, currentEpisodeIndex, handleClose);
2986
3553
  const { showSkipIntro, handleSkipIntro } = useIntroSkip(intro);
2987
3554
  useEpisodes(episodeList, currentEpisodeIndex, nextEpisodeConfig);
2988
3555
  const { onSeeked, onTimeUpdate, onLoadedMetadata, onProgress, onPlay, onPause, onEnded: onEndedHook, } = useVideoEvents();
2989
3556
  const { skipAd } = useAdManager(effectiveAds);
3557
+ const { error, handleVideoError, retry } = useVideoError();
3558
+ const hasResumedRef = React__default.useRef(false);
3559
+ React__default.useEffect(() => {
3560
+ return () => {
3561
+ const historyData = getWatchHistoryData();
3562
+ if (historyData && onWatchHistoryUpdateRef.current) {
3563
+ onWatchHistoryUpdateRef.current(historyData);
3564
+ }
3565
+ };
3566
+ }, [getWatchHistoryData]);
3567
+ React__default.useEffect(() => {
3568
+ if (!videoRef || !startFrom || hasResumedRef.current)
3569
+ return;
3570
+ const handleCanPlay = () => {
3571
+ if (!hasResumedRef.current && startFrom > 0) {
3572
+ videoRef.currentTime = startFrom;
3573
+ hasResumedRef.current = true;
3574
+ }
3575
+ };
3576
+ videoRef.addEventListener("canplay", handleCanPlay);
3577
+ return () => videoRef.removeEventListener("canplay", handleCanPlay);
3578
+ }, [videoRef, startFrom]);
2990
3579
  return (React__default.createElement("div", { ref: setVideoWrapperRef, className: `video-player ${height || "h-full"} ${width || "w-full"} mx-auto absolute` },
2991
3580
  trackPoster && (React__default.createElement("div", { className: "pip-poster absolute inset-0 bg-center bg-cover hidden", style: { backgroundImage: `url(${trackPoster})` } })),
2992
3581
  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) => {
2993
3582
  onEndedHook(e);
2994
3583
  onEnded?.(e);
2995
3584
  }, onError: (e) => {
3585
+ handleVideoError(e);
2996
3586
  onError?.(e);
2997
3587
  }, autoPlay: !hasPreRoll, muted: isMute, className: `w-full h-full relative ${className || ""} ${shouldCoverMainVideo ? "opacity-0" : "opacity-100"} transition-opacity duration-200 ease-out` }),
2998
- shouldShowPlaceholder && (React__default.createElement("div", { className: "absolute inset-0 z-40 flex items-center justify-center bg-black" },
2999
- React__default.createElement("span", { className: "loader" }))),
3000
- showControls && initialAdFinished && (React__default.createElement(Overlay, { config: {
3001
- headerConfig: {
3002
- config: {
3003
- isTrailer: isTrailer,
3004
- title: trackTitle,
3005
- onClose: onClose,
3006
- videoRef: videoRef,
3007
- },
3008
- },
3009
- bottomConfig: {
3010
- config: {
3011
- seekBarConfig: {
3012
- timeCodes: timeCodes,
3013
- trackColor: "red",
3014
- getPreviewScreenUrl,
3015
- },
3016
- },
3017
- },
3018
- } })),
3588
+ shouldShowPlaceholder && (React__default.createElement("div", { className: "absolute inset-0 z-40 flex flex-col items-center justify-center bg-black/90 backdrop-blur-sm" },
3589
+ React__default.createElement(Loader, { className: "w-24 h-24 lg:w-32 lg:h-32 animate-spin text-white" }))),
3590
+ showControls && initialAdFinished && (React__default.createElement(Overlay, { config: overlayConfig })),
3019
3591
  React__default.createElement(SubtitleOverlay, { styleConfig: subtitleStyle }),
3020
3592
  showSkipIntro && !isAdPlaying && initialAdFinished && (React__default.createElement(VideoActionButton, { text: "Skip Intro", onClick: handleSkipIntro, position: "left" })),
3021
- isAdPlaying && currentAd && (React__default.createElement(AdOverlay, { adBreak: currentAd, onSkip: skipAd, config: {
3022
- config: {
3023
- headerConfig: {
3024
- config: {
3025
- isTrailer: isTrailer,
3026
- title: trackTitle,
3027
- onClose: onClose,
3028
- },
3029
- },
3030
- bottomConfig: {
3031
- config: {
3032
- seekBarConfig: {
3033
- timeCodes: timeCodes,
3034
- trackColor: "red",
3035
- getPreviewScreenUrl,
3036
- },
3037
- },
3038
- },
3039
- },
3040
- } }))));
3041
- };
3593
+ isAdPlaying && currentAd && (React__default.createElement(AdOverlay, { adBreak: currentAd, onSkip: skipAd, config: adOverlayConfig })),
3594
+ error && onError && React__default.createElement(ErrorOverlay, { error: error, onRetry: retry })));
3595
+ });
3596
+ VideoPlayer.displayName = "VideoPlayer";
3042
3597
 
3043
3598
  export { VideoPlayer, useVideoStore };