@flamingo-stack/openframe-frontend-core 0.0.177-snapshot.20260514141929 → 0.0.177

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/{chunk-AAX27BCR.js → chunk-6LDN3CIY.js} +348 -189
  2. package/dist/chunk-6LDN3CIY.js.map +1 -0
  3. package/dist/{chunk-L4T24AN4.cjs → chunk-C6ZMI4UB.cjs} +272 -113
  4. package/dist/chunk-C6ZMI4UB.cjs.map +1 -0
  5. package/dist/{chunk-FMWHOUFE.js → chunk-KB2N44BY.js} +2 -61
  6. package/dist/chunk-KB2N44BY.js.map +1 -0
  7. package/dist/{chunk-ALW3D72O.cjs → chunk-WX7PT5C7.cjs} +2 -61
  8. package/dist/chunk-WX7PT5C7.cjs.map +1 -0
  9. package/dist/components/features/index.cjs +5 -3
  10. package/dist/components/features/index.cjs.map +1 -1
  11. package/dist/components/features/index.js +4 -2
  12. package/dist/components/features/video-player.d.ts +20 -17
  13. package/dist/components/features/video-player.d.ts.map +1 -1
  14. package/dist/components/features/youtube-embed.d.ts +4 -18
  15. package/dist/components/features/youtube-embed.d.ts.map +1 -1
  16. package/dist/components/index.cjs +5 -3
  17. package/dist/components/index.cjs.map +1 -1
  18. package/dist/components/index.js +4 -2
  19. package/dist/components/navigation/index.cjs +3 -3
  20. package/dist/components/navigation/index.js +2 -2
  21. package/dist/components/ui/index.cjs +3 -3
  22. package/dist/components/ui/index.js +2 -2
  23. package/dist/hooks/index.cjs +2 -4
  24. package/dist/hooks/index.cjs.map +1 -1
  25. package/dist/hooks/index.d.ts +0 -1
  26. package/dist/hooks/index.d.ts.map +1 -1
  27. package/dist/hooks/index.js +1 -3
  28. package/dist/index.cjs +3 -3
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.js +4 -4
  31. package/package.json +1 -1
  32. package/src/components/features/__tests__/video-player.test.tsx +142 -0
  33. package/src/components/features/video-player.tsx +176 -39
  34. package/src/components/features/youtube-embed.tsx +224 -107
  35. package/src/hooks/index.ts +0 -3
  36. package/dist/chunk-AAX27BCR.js.map +0 -1
  37. package/dist/chunk-ALW3D72O.cjs.map +0 -1
  38. package/dist/chunk-FMWHOUFE.js.map +0 -1
  39. package/dist/chunk-L4T24AN4.cjs.map +0 -1
  40. package/dist/hooks/use-near-viewport.d.ts +0 -42
  41. package/dist/hooks/use-near-viewport.d.ts.map +0 -1
  42. package/src/hooks/use-near-viewport.ts +0 -118
@@ -16,7 +16,7 @@ import {
16
16
  useMdUp,
17
17
  useOnboardingState,
18
18
  useToast
19
- } from "./chunk-FMWHOUFE.js";
19
+ } from "./chunk-KB2N44BY.js";
20
20
  import {
21
21
  Button,
22
22
  Checkbox,
@@ -413,7 +413,7 @@ function useDynamicTheme() {
413
413
  }
414
414
 
415
415
  // src/components/features/array-entry-manager.tsx
416
- import { useState as useState58, useEffect as useEffect42 } from "react";
416
+ import { useState as useState58, useEffect as useEffect43 } from "react";
417
417
 
418
418
  // src/components/ui/allowed-domains-input.tsx
419
419
  init_cn();
@@ -16397,7 +16397,7 @@ function ProductReleaseCardSkeleton({ className, size = "default" }) {
16397
16397
  }
16398
16398
 
16399
16399
  // src/components/shared/product-release/release-detail-page.tsx
16400
- import { useState as useState37, useEffect as useEffect26 } from "react";
16400
+ import { useState as useState37, useEffect as useEffect27 } from "react";
16401
16401
  import Link4 from "next/link";
16402
16402
 
16403
16403
  // src/components/layout/article-detail-layout.tsx
@@ -16616,20 +16616,58 @@ function ImageGalleryModal({
16616
16616
  import { AlertTriangle, ExternalLink, BookMarked } from "lucide-react";
16617
16617
 
16618
16618
  // src/components/features/youtube-embed.tsx
16619
- import { useRef as useRef21, useState as useState34 } from "react";
16619
+ import { useState as useState34, useEffect as useEffect25 } from "react";
16620
+ import ReactPlayer from "react-player/youtube";
16620
16621
  import { jsx as jsx138, jsxs as jsxs111 } from "react/jsx-runtime";
16622
+ var Play = ({ size = 16, className }) => /* @__PURE__ */ jsx138("svg", { width: size, height: size, fill: "currentColor", viewBox: "0 0 24 24", className, children: /* @__PURE__ */ jsx138("polygon", { points: "5,3 19,12 5,21" }) });
16623
+ var Loader = ({ size = 16, className }) => /* @__PURE__ */ jsxs111("svg", { width: size, height: size, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", className, children: [
16624
+ /* @__PURE__ */ jsx138("line", { x1: "12", y1: "2", x2: "12", y2: "6" }),
16625
+ /* @__PURE__ */ jsx138("line", { x1: "12", y1: "18", x2: "12", y2: "22" }),
16626
+ /* @__PURE__ */ jsx138("line", { x1: "4.93", y1: "4.93", x2: "7.76", y2: "7.76" }),
16627
+ /* @__PURE__ */ jsx138("line", { x1: "16.24", y1: "16.24", x2: "19.07", y2: "19.07" }),
16628
+ /* @__PURE__ */ jsx138("line", { x1: "2", y1: "12", x2: "6", y2: "12" }),
16629
+ /* @__PURE__ */ jsx138("line", { x1: "18", y1: "12", x2: "22", y2: "12" }),
16630
+ /* @__PURE__ */ jsx138("line", { x1: "4.93", y1: "19.07", x2: "7.76", y2: "16.24" }),
16631
+ /* @__PURE__ */ jsx138("line", { x1: "16.24", y1: "7.76", x2: "19.07", y2: "4.93" })
16632
+ ] });
16621
16633
  var YouTubeEmbed = ({
16622
16634
  videoId,
16623
16635
  title = "YouTube Video",
16624
16636
  className = "",
16625
16637
  showTitle = true,
16626
16638
  showMeta = true,
16627
- minimalControls = false,
16628
- aboveTheFold = false
16639
+ minimalControls = false
16629
16640
  }) => {
16630
- const [activated, setActivated] = useState34(false);
16631
- const iframeSlotRef = useRef21(null);
16632
- const embedParams = new URLSearchParams({ autoplay: "1", rel: "0", modestbranding: "1", playsinline: "1" });
16641
+ const [isLoading, setIsLoading] = useState34(true);
16642
+ const [hasError, setHasError] = useState34(false);
16643
+ const [isPlaying, setIsPlaying] = useState34(false);
16644
+ const [useIframe, setUseIframe] = useState34(false);
16645
+ const [mounted, setMounted] = useState34(false);
16646
+ useEffect25(() => {
16647
+ setMounted(true);
16648
+ setUseIframe(true);
16649
+ setIsLoading(false);
16650
+ }, []);
16651
+ const handleReady = () => {
16652
+ setIsLoading(false);
16653
+ };
16654
+ const handleError = () => {
16655
+ setIsLoading(false);
16656
+ setHasError(true);
16657
+ setUseIframe(true);
16658
+ };
16659
+ const handlePlay = () => {
16660
+ setIsPlaying(true);
16661
+ };
16662
+ const handlePause = () => {
16663
+ setIsPlaying(false);
16664
+ };
16665
+ const videoUrl = `https://www.youtube.com/watch?v=${videoId}`;
16666
+ const embedParams = new URLSearchParams({
16667
+ rel: "0",
16668
+ modestbranding: "1",
16669
+ playsinline: "1"
16670
+ });
16633
16671
  if (minimalControls) {
16634
16672
  embedParams.set("controls", "0");
16635
16673
  embedParams.set("showinfo", "0");
@@ -16637,60 +16675,96 @@ var YouTubeEmbed = ({
16637
16675
  embedParams.set("iv_load_policy", "3");
16638
16676
  embedParams.set("cc_load_policy", "0");
16639
16677
  embedParams.set("disablekb", "1");
16678
+ embedParams.set("rel", "0");
16679
+ }
16680
+ const embedUrl = `https://www.youtube.com/embed/${videoId}?${embedParams.toString()}`;
16681
+ if (!mounted) {
16682
+ return /* @__PURE__ */ jsx138("div", { className: `youtube-embed-container my-6 ${className}`, children: /* @__PURE__ */ jsx138("div", { className: "video-wrapper relative w-full", style: { paddingBottom: "56.25%" }, children: /* @__PURE__ */ jsx138("div", { className: "loading-overlay absolute inset-0 bg-ods-card border border-ods-border rounded-lg flex items-center justify-center", children: /* @__PURE__ */ jsxs111("div", { className: "loading-content flex flex-col items-center gap-3", children: [
16683
+ /* @__PURE__ */ jsx138(Loader, { className: "animate-spin text-ods-accent", size: 32 }),
16684
+ /* @__PURE__ */ jsx138("span", { className: "font-sans text-sm text-ods-text-secondary", children: "Loading video..." })
16685
+ ] }) }) }) });
16686
+ }
16687
+ if (hasError) {
16688
+ return /* @__PURE__ */ jsx138("div", { className: `youtube-embed-error ${className}`, children: /* @__PURE__ */ jsxs111("div", { className: "error-state bg-ods-card border border-ods-error rounded-lg p-6 text-center my-6", children: [
16689
+ /* @__PURE__ */ jsx138("div", { className: "error-icon flex justify-center mb-4", children: /* @__PURE__ */ jsx138("svg", { width: "48", height: "48", fill: "currentColor", viewBox: "0 0 24 24", className: "text-ods-error", children: /* @__PURE__ */ jsx138("path", { d: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" }) }) }),
16690
+ /* @__PURE__ */ jsx138("div", { className: "error-title font-sans font-semibold text-lg text-ods-error mb-2", children: "Video Unavailable" }),
16691
+ /* @__PURE__ */ jsx138("div", { className: "error-description font-sans text-sm text-ods-text-secondary mb-4", children: "Unable to load YouTube video. The video may be private or deleted." }),
16692
+ /* @__PURE__ */ jsx138(
16693
+ "a",
16694
+ {
16695
+ href: videoUrl,
16696
+ target: "_blank",
16697
+ rel: "noopener noreferrer",
16698
+ className: "error-retry-button bg-ods-error hover:bg-ods-error-hover text-ods-text-on-error border-none rounded px-4 py-2 font-sans font-medium text-sm cursor-pointer transition-colors duration-200",
16699
+ children: "Watch on YouTube"
16700
+ }
16701
+ )
16702
+ ] }) });
16640
16703
  }
16641
- const embedUrl = `https://www.youtube-nocookie.com/embed/${videoId}?${embedParams.toString()}`;
16642
- const watchUrl = `https://www.youtube.com/watch?v=${videoId}`;
16643
- const posterJpg = `https://i.ytimg.com/vi/${videoId}/mqdefault.jpg`;
16644
- const posterWebp = `https://i.ytimg.com/vi_webp/${videoId}/mqdefault.webp`;
16645
- const handleActivate = () => {
16646
- const slot = iframeSlotRef.current;
16647
- if (!slot || activated) return;
16648
- const iframe = document.createElement("iframe");
16649
- iframe.setAttribute("allow", "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share");
16650
- iframe.setAttribute("allowfullscreen", "");
16651
- iframe.setAttribute("title", title);
16652
- iframe.className = "absolute inset-0 w-full h-full border-0";
16653
- iframe.src = embedUrl;
16654
- slot.appendChild(iframe);
16655
- setActivated(true);
16656
- };
16657
16704
  return /* @__PURE__ */ jsxs111("div", { className: `youtube-embed-container my-6 ${className}`, children: [
16658
16705
  title && showTitle && /* @__PURE__ */ jsx138("div", { className: "video-title font-sans text-lg font-medium text-ods-text-primary mb-3", children: title }),
16659
- /* @__PURE__ */ jsx138("div", { className: "video-wrapper relative w-full", style: { paddingBottom: "56.25%" }, children: /* @__PURE__ */ jsxs111("div", { className: "absolute inset-0 rounded-lg overflow-hidden border border-ods-border bg-ods-card", children: [
16660
- /* @__PURE__ */ jsx138("div", { ref: iframeSlotRef, className: "absolute inset-0", "aria-hidden": !activated }),
16661
- !activated && /* @__PURE__ */ jsxs111(
16706
+ /* @__PURE__ */ jsxs111("div", { className: "video-wrapper relative w-full", style: { paddingBottom: "56.25%" }, children: [
16707
+ isLoading && /* @__PURE__ */ jsx138("div", { className: "loading-overlay absolute inset-0 bg-ods-card border border-ods-border rounded-lg flex items-center justify-center", children: /* @__PURE__ */ jsxs111("div", { className: "loading-content flex flex-col items-center gap-3", children: [
16708
+ /* @__PURE__ */ jsx138(Loader, { className: "animate-spin text-ods-accent", size: 32 }),
16709
+ /* @__PURE__ */ jsx138("span", { className: "font-sans text-sm text-ods-text-secondary", children: "Loading video..." })
16710
+ ] }) }),
16711
+ /* @__PURE__ */ jsx138("div", { className: "video-player absolute inset-0 rounded-lg overflow-hidden border border-ods-border bg-ods-bg-inverse", children: useIframe ? (
16712
+ // Iframe fallback for mobile and error cases
16713
+ /* @__PURE__ */ jsx138(
16714
+ "iframe",
16715
+ {
16716
+ src: embedUrl,
16717
+ title,
16718
+ className: "w-full h-full border-0",
16719
+ allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share",
16720
+ allowFullScreen: true,
16721
+ style: { border: "none" }
16722
+ }
16723
+ )
16724
+ ) : /* @__PURE__ */ jsx138(
16725
+ ReactPlayer,
16726
+ {
16727
+ url: videoUrl,
16728
+ width: "100%",
16729
+ height: "100%",
16730
+ onReady: handleReady,
16731
+ onError: handleError,
16732
+ onPlay: handlePlay,
16733
+ onPause: handlePause,
16734
+ config: {
16735
+ youtube: {
16736
+ playerVars: {
16737
+ autoplay: 0,
16738
+ controls: 1,
16739
+ rel: 0,
16740
+ showinfo: 0,
16741
+ modestbranding: 1,
16742
+ iv_load_policy: 3,
16743
+ cc_load_policy: 0,
16744
+ playsinline: 1
16745
+ }
16746
+ }
16747
+ },
16748
+ light: false,
16749
+ playing: false
16750
+ }
16751
+ ) }),
16752
+ !useIframe && !isPlaying && !isLoading && /* @__PURE__ */ jsx138("div", { className: "play-overlay absolute inset-0 flex items-center justify-center bg-ods-bg-inverse bg-opacity-20 rounded-lg transition-opacity duration-300 hover:bg-opacity-30", children: /* @__PURE__ */ jsx138(
16662
16753
  "button",
16663
16754
  {
16664
- type: "button",
16665
- "aria-label": `Play: ${title}`,
16666
- onClick: handleActivate,
16667
- className: "group absolute inset-0 p-0 m-0 border-0 cursor-pointer bg-transparent",
16668
- children: [
16669
- /* @__PURE__ */ jsxs111("picture", { children: [
16670
- /* @__PURE__ */ jsx138("source", { type: "image/webp", srcSet: posterWebp }),
16671
- /* @__PURE__ */ jsx138(
16672
- "img",
16673
- {
16674
- src: posterJpg,
16675
- alt: title,
16676
- loading: "lazy",
16677
- fetchPriority: aboveTheFold ? "high" : "low",
16678
- decoding: aboveTheFold ? "sync" : "async",
16679
- className: "absolute inset-0 w-full h-full object-cover"
16680
- }
16681
- )
16682
- ] }),
16683
- /* @__PURE__ */ jsx138("div", { className: "absolute inset-0 flex items-center justify-center bg-ods-bg-inverse bg-opacity-20 transition-opacity duration-200 group-hover:bg-opacity-30", children: /* @__PURE__ */ jsx138("span", { className: "flex items-center justify-center w-16 h-16 rounded-full bg-ods-accent text-ods-text-on-accent shadow-lg transition-transform duration-200 group-hover:scale-110", children: /* @__PURE__ */ jsx138("svg", { width: 24, height: 24, fill: "currentColor", viewBox: "0 0 24 24", className: "ml-1", children: /* @__PURE__ */ jsx138("polygon", { points: "5,3 19,12 5,21" }) }) }) })
16684
- ]
16755
+ onClick: () => setIsPlaying(true),
16756
+ className: "play-button bg-ods-accent hover:bg-ods-accent-hover text-ods-text-on-accent w-16 h-16 rounded-full flex items-center justify-center transition-all duration-200 transform hover:scale-110 shadow-lg",
16757
+ "aria-label": "Play video",
16758
+ children: /* @__PURE__ */ jsx138(Play, { size: 24, className: "ml-1" })
16685
16759
  }
16686
- )
16687
- ] }) }),
16760
+ ) })
16761
+ ] }),
16688
16762
  showMeta && /* @__PURE__ */ jsxs111("div", { className: "video-meta flex items-center justify-between mt-3 text-sm text-ods-text-secondary", children: [
16689
16763
  /* @__PURE__ */ jsx138("div", { className: "video-platform font-sans", children: "YouTube" }),
16690
16764
  /* @__PURE__ */ jsx138(
16691
16765
  "a",
16692
16766
  {
16693
- href: watchUrl,
16767
+ href: videoUrl,
16694
16768
  target: "_blank",
16695
16769
  rel: "noopener noreferrer",
16696
16770
  className: "video-link font-sans text-ods-accent hover:text-ods-accent-hover transition-colors duration-200",
@@ -16700,37 +16774,111 @@ var YouTubeEmbed = ({
16700
16774
  ] })
16701
16775
  ] });
16702
16776
  };
16703
- var YT_HOSTS = /* @__PURE__ */ new Set([
16704
- "youtube.com",
16705
- "www.youtube.com",
16706
- "m.youtube.com",
16707
- "youtu.be",
16708
- "youtube-nocookie.com",
16709
- "www.youtube-nocookie.com"
16710
- ]);
16711
- var YT_PATH_RE = /^\/(?:embed|v|shorts)\/([^/]+)\/?$/;
16712
16777
  var extractYouTubeId = (url) => {
16713
- let u;
16714
- try {
16715
- u = new URL(url);
16716
- } catch {
16717
- return null;
16778
+ const patterns = [
16779
+ /(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/,
16780
+ /youtube\.com\/v\/([^&\n?#]+)/,
16781
+ /youtube\.com\/watch\?.*v=([^&\n?#]+)/
16782
+ ];
16783
+ for (const pattern of patterns) {
16784
+ const match = url.match(pattern);
16785
+ if (match) {
16786
+ return match[1];
16787
+ }
16718
16788
  }
16719
- if (!YT_HOSTS.has(u.hostname.toLowerCase())) return null;
16720
- if (u.hostname.toLowerCase().endsWith("youtu.be")) {
16721
- return u.pathname.split("/").filter(Boolean)[0] ?? null;
16789
+ return null;
16790
+ };
16791
+ var YouTubeLinkParser = ({ href, children }) => {
16792
+ const videoId = extractYouTubeId(href);
16793
+ if (videoId) {
16794
+ return /* @__PURE__ */ jsx138(YouTubeEmbed, { videoId, title: typeof children === "string" ? children : void 0 });
16722
16795
  }
16723
- const v = u.searchParams.get("v");
16724
- if (v) return v;
16725
- const m = u.pathname.match(YT_PATH_RE);
16726
- return m ? m[1] : null;
16796
+ return /* @__PURE__ */ jsx138(
16797
+ "a",
16798
+ {
16799
+ href,
16800
+ target: "_blank",
16801
+ rel: "noopener noreferrer",
16802
+ className: "text-ods-accent hover:text-ods-accent-hover transition-colors duration-200",
16803
+ children
16804
+ }
16805
+ );
16727
16806
  };
16728
16807
 
16729
16808
  // src/components/features/video-player.tsx
16730
- import { useState as useState35, useEffect as useEffect25, useRef as useRef22, useMemo as useMemo11, useCallback as useCallback19 } from "react";
16731
- import ReactPlayer from "react-player";
16809
+ import { useState as useState35, useEffect as useEffect26, useRef as useRef21, useMemo as useMemo11, useCallback as useCallback19 } from "react";
16810
+ import ReactPlayer2 from "react-player";
16732
16811
  init_button2();
16733
16812
  import { jsx as jsx139, jsxs as jsxs112 } from "react/jsx-runtime";
16813
+ function useVideoFirstFramePoster(url, enabled) {
16814
+ const [poster, setPoster] = useState35(null);
16815
+ useEffect26(() => {
16816
+ if (!enabled || !url) {
16817
+ setPoster(null);
16818
+ return;
16819
+ }
16820
+ const isDirectFile = /\.(mp4|webm|mov|m4v)(\?|#|$)/i.test(url);
16821
+ if (!isDirectFile) {
16822
+ setPoster(null);
16823
+ return;
16824
+ }
16825
+ let cancelled = false;
16826
+ const video = document.createElement("video");
16827
+ video.crossOrigin = "anonymous";
16828
+ video.preload = "metadata";
16829
+ video.muted = true;
16830
+ video.playsInline = true;
16831
+ video.setAttribute("data-poster-extractor", "true");
16832
+ const cleanup = () => {
16833
+ video.removeAttribute("src");
16834
+ try {
16835
+ video.load();
16836
+ } catch {
16837
+ }
16838
+ };
16839
+ const onLoadedMetadata = () => {
16840
+ if (cancelled) return;
16841
+ const dur = video.duration || 10;
16842
+ const target = Math.max(2, Math.min(dur * 0.1, 30));
16843
+ video.currentTime = target;
16844
+ };
16845
+ const onSeeked = () => {
16846
+ if (cancelled) return;
16847
+ try {
16848
+ const canvas = document.createElement("canvas");
16849
+ canvas.width = video.videoWidth;
16850
+ canvas.height = video.videoHeight;
16851
+ const ctx = canvas.getContext("2d");
16852
+ if (!ctx || !canvas.width || !canvas.height) {
16853
+ cleanup();
16854
+ return;
16855
+ }
16856
+ ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
16857
+ const dataUrl = canvas.toDataURL("image/jpeg", 0.82);
16858
+ if (!cancelled) setPoster(dataUrl);
16859
+ } catch (err) {
16860
+ console.warn("[VideoPlayer] first-frame extraction failed", err);
16861
+ } finally {
16862
+ cleanup();
16863
+ }
16864
+ };
16865
+ const onError = () => {
16866
+ cleanup();
16867
+ };
16868
+ video.addEventListener("loadedmetadata", onLoadedMetadata);
16869
+ video.addEventListener("seeked", onSeeked);
16870
+ video.addEventListener("error", onError);
16871
+ video.src = url;
16872
+ return () => {
16873
+ cancelled = true;
16874
+ video.removeEventListener("loadedmetadata", onLoadedMetadata);
16875
+ video.removeEventListener("seeked", onSeeked);
16876
+ video.removeEventListener("error", onError);
16877
+ cleanup();
16878
+ };
16879
+ }, [url, enabled]);
16880
+ return poster;
16881
+ }
16734
16882
  var webkitCaptionCSSInjected = false;
16735
16883
  function ensureWebkitCaptionCSS() {
16736
16884
  if (webkitCaptionCSSInjected || typeof document === "undefined") return;
@@ -16781,6 +16929,7 @@ function useSubtitleOverlay(srtContent) {
16781
16929
  return { activeText, updateTime, hasCues: cues.length > 0 };
16782
16930
  }
16783
16931
  var SPEED_OPTIONS = [0.5, 0.75, 1, 1.25, 1.5, 2];
16932
+ var LAZY_MOUNT_PLAY_FAILURE_GRACE_MS = 2e3;
16784
16933
  function formatTime(secs) {
16785
16934
  if (!secs || !isFinite(secs)) return "0:00";
16786
16935
  const h = Math.floor(secs / 3600);
@@ -16802,14 +16951,14 @@ var VideoPlayer = ({
16802
16951
  srtContent,
16803
16952
  captionsUrl,
16804
16953
  subtitleLabel,
16805
- preloadStrategy = "metadata"
16954
+ lazyMount = false
16806
16955
  }) => {
16807
16956
  const [hasError, setHasError] = useState35(false);
16808
16957
  const [isPlaying, setIsPlaying] = useState35(autoPlay);
16809
16958
  const [mounted, setMounted] = useState35(false);
16810
16959
  const [hasStarted, setHasStarted] = useState35(autoPlay);
16811
- const playerRef = useRef22(null);
16812
- const containerRef = useRef22(null);
16960
+ const playerRef = useRef21(null);
16961
+ const containerRef = useRef21(null);
16813
16962
  const [played, setPlayed] = useState35(0);
16814
16963
  const [loaded, setLoaded] = useState35(0);
16815
16964
  const [duration, setDuration] = useState35(0);
@@ -16818,13 +16967,14 @@ var VideoPlayer = ({
16818
16967
  const [isMuted, setIsMuted] = useState35(muted);
16819
16968
  const [isBuffering, setIsBuffering] = useState35(false);
16820
16969
  const [showControls, setShowControls] = useState35(true);
16821
- const hideTimeoutRef = useRef22(void 0);
16822
- const clickTimerRef = useRef22(void 0);
16823
- const iosFullscreenTimerRef = useRef22(void 0);
16970
+ const hideTimeoutRef = useRef21(void 0);
16971
+ const clickTimerRef = useRef21(void 0);
16972
+ const iosFullscreenTimerRef = useRef21(void 0);
16973
+ const lazyMountFailureTimerRef = useRef21(void 0);
16824
16974
  const [captionsEnabled, setCaptionsEnabled] = useState35(true);
16825
16975
  const [isFullscreen, setIsFullscreen] = useState35(false);
16826
16976
  const { activeText, updateTime, hasCues } = useSubtitleOverlay(srtContent);
16827
- useEffect25(() => {
16977
+ useEffect26(() => {
16828
16978
  const onChange = () => {
16829
16979
  const fsEl = document.fullscreenElement || document.webkitFullscreenElement;
16830
16980
  setIsFullscreen(!!fsEl);
@@ -16938,7 +17088,7 @@ var VideoPlayer = ({
16938
17088
  return next;
16939
17089
  });
16940
17090
  }, [hasStarted, isPlaying]);
16941
- useEffect25(() => {
17091
+ useEffect26(() => {
16942
17092
  if (!hasStarted) return;
16943
17093
  const el = containerRef.current;
16944
17094
  if (!el) return;
@@ -17002,9 +17152,9 @@ var VideoPlayer = ({
17002
17152
  return SPEED_OPTIONS[(idx + 1) % SPEED_OPTIONS.length];
17003
17153
  });
17004
17154
  }, []);
17005
- const progressBarRef = useRef22(null);
17006
- const isDraggingRef = useRef22(false);
17007
- const dragListenersRef = useRef22(null);
17155
+ const progressBarRef = useRef21(null);
17156
+ const isDraggingRef = useRef21(false);
17157
+ const dragListenersRef = useRef21(null);
17008
17158
  const seekToClientX = useCallback19((clientX) => {
17009
17159
  const rect = progressBarRef.current?.getBoundingClientRect();
17010
17160
  if (!rect) return;
@@ -17074,7 +17224,7 @@ var VideoPlayer = ({
17074
17224
  const x = Math.max(30, Math.min(rect.width - 30, e.clientX - rect.left));
17075
17225
  setSeekPreview({ fraction, x });
17076
17226
  }, []);
17077
- const isTouchRef = useRef22(false);
17227
+ const isTouchRef = useRef21(false);
17078
17228
  const handleContainerClick = useCallback19((e) => {
17079
17229
  if (isTouchRef.current) {
17080
17230
  isTouchRef.current = false;
@@ -17099,14 +17249,16 @@ var VideoPlayer = ({
17099
17249
  if (!hasStarted) return;
17100
17250
  handleTouchToggle();
17101
17251
  }, [hasStarted, handleTouchToggle]);
17102
- const effectivePoster = poster || void 0;
17252
+ const extractedPoster = useVideoFirstFramePoster(url, !lazyMount && !hasStarted && !poster);
17253
+ const effectivePoster = poster || extractedPoster || void 0;
17103
17254
  const posterBgColor = useImageEdgeColor(effectivePoster);
17104
- useEffect25(() => {
17255
+ useEffect26(() => {
17105
17256
  setMounted(true);
17106
17257
  return () => {
17107
17258
  clearTimeout(clickTimerRef.current);
17108
17259
  clearTimeout(hideTimeoutRef.current);
17109
17260
  clearTimeout(iosFullscreenTimerRef.current);
17261
+ clearTimeout(lazyMountFailureTimerRef.current);
17110
17262
  isDraggingRef.current = false;
17111
17263
  if (dragListenersRef.current) {
17112
17264
  document.removeEventListener("mousemove", dragListenersRef.current.move);
@@ -17115,7 +17267,7 @@ var VideoPlayer = ({
17115
17267
  }
17116
17268
  };
17117
17269
  }, []);
17118
- useEffect25(() => {
17270
+ useEffect26(() => {
17119
17271
  if (!hasStarted) return;
17120
17272
  const video = playerRef.current?.getInternalPlayer();
17121
17273
  if (!video) return;
@@ -17131,16 +17283,22 @@ var VideoPlayer = ({
17131
17283
  const handlePause = useCallback19(() => setIsPlaying(false), []);
17132
17284
  const handleEnded = useCallback19(() => setIsPlaying(false), []);
17133
17285
  const handlePlayClick = useCallback19(() => {
17134
- const native = playerRef.current?.getInternalPlayer();
17135
- if (native instanceof HTMLVideoElement) {
17136
- native.play().catch(() => {
17137
- });
17138
- } else if (process.env.NODE_ENV !== "production") {
17139
- console.warn("[VideoPlayer] sync play(): no native HTMLVideoElement yet");
17286
+ if (lazyMount) {
17287
+ const native = playerRef.current?.getInternalPlayer();
17288
+ if (native instanceof HTMLVideoElement) {
17289
+ native.play().catch(() => {
17290
+ clearTimeout(lazyMountFailureTimerRef.current);
17291
+ lazyMountFailureTimerRef.current = setTimeout(() => {
17292
+ if (native.paused && native.error) setHasError(true);
17293
+ }, LAZY_MOUNT_PLAY_FAILURE_GRACE_MS);
17294
+ });
17295
+ } else if (process.env.NODE_ENV !== "production") {
17296
+ console.warn("[VideoPlayer] lazyMount sync play(): no native HTMLVideoElement yet");
17297
+ }
17140
17298
  }
17141
17299
  setHasStarted(true);
17142
17300
  setIsPlaying(true);
17143
- }, []);
17301
+ }, [lazyMount]);
17144
17302
  const handleProgress = useCallback19(({ played: p, loaded: l, playedSeconds }) => {
17145
17303
  setPlayed(p);
17146
17304
  setLoaded(l);
@@ -17194,7 +17352,7 @@ var VideoPlayer = ({
17194
17352
  /* @__PURE__ */ jsx139("div", { className: `absolute inset-0 ${effectivePoster ? "bg-black/40" : "bg-black/20"} group-hover:bg-black/50 transition-all flex items-center justify-center rounded-md`, children: /* @__PURE__ */ jsx139("div", { className: "w-16 h-16 rounded-full bg-ods-accent hover:bg-ods-accent/90 transition-all flex items-center justify-center shadow-lg", children: /* @__PURE__ */ jsx139(PlayIcon, { size: 24, className: "ml-1 text-ods-text-on-accent" }) }) })
17195
17353
  ] }),
17196
17354
  /* @__PURE__ */ jsx139("div", { className: isFullscreen ? "video-player absolute inset-0" : useNativeAspectRatio ? "video-player rounded-md overflow-hidden border border-ods-border bg-ods-background" : "video-player absolute inset-0 rounded-md overflow-hidden border border-ods-border bg-ods-background", children: /* @__PURE__ */ jsx139(
17197
- ReactPlayer,
17355
+ ReactPlayer2,
17198
17356
  {
17199
17357
  ref: playerRef,
17200
17358
  url,
@@ -17215,7 +17373,7 @@ var VideoPlayer = ({
17215
17373
  onBufferEnd: handleBufferEnd,
17216
17374
  onProgress: handleProgress,
17217
17375
  progressInterval: 200,
17218
- config: { file: { attributes: { controlsList: "nodownload", playsInline: true, preload: hasStarted ? "auto" : preloadStrategy } } },
17376
+ config: { file: { attributes: { controlsList: "nodownload", playsInline: true, preload: lazyMount && !hasStarted ? "none" : hasStarted ? "auto" : "metadata" } } },
17219
17377
  light: false,
17220
17378
  playsinline: true
17221
17379
  }
@@ -18200,7 +18358,7 @@ function ReleaseDetailPage({
18200
18358
  const [deliveryData, setDeliveryData] = useState37(null);
18201
18359
  const [roadmapLoading, setRoadmapLoading] = useState37(false);
18202
18360
  const [deliveryLoading, setDeliveryLoading] = useState37(false);
18203
- useEffect26(() => {
18361
+ useEffect27(() => {
18204
18362
  async function fetchLinkedTasks() {
18205
18363
  if (!release) return;
18206
18364
  try {
@@ -18755,7 +18913,7 @@ function CompactPageLoader({
18755
18913
  }
18756
18914
 
18757
18915
  // src/components/ui/progress-bar.tsx
18758
- import { useEffect as useEffect28, useRef as useRef23, useState as useState39 } from "react";
18916
+ import { useEffect as useEffect29, useRef as useRef22, useState as useState39 } from "react";
18759
18917
  import { jsx as jsx152 } from "react/jsx-runtime";
18760
18918
  var ProgressBar = ({
18761
18919
  progress,
@@ -18771,9 +18929,9 @@ var ProgressBar = ({
18771
18929
  const isMdUp = useMdUp() ?? true;
18772
18930
  const effectiveSegmentWidth = isMdUp ? segmentWidth : mobileSegmentWidth;
18773
18931
  const effectiveHeight = isMdUp ? height : mobileHeight;
18774
- const containerRef = useRef23(null);
18932
+ const containerRef = useRef22(null);
18775
18933
  const [segmentCount, setSegmentCount] = useState39(0);
18776
- useEffect28(() => {
18934
+ useEffect29(() => {
18777
18935
  if (!containerRef.current) return;
18778
18936
  const resizeObserver = new ResizeObserver(() => {
18779
18937
  if (containerRef.current) {
@@ -19597,11 +19755,11 @@ DialogDescription.displayName = DialogPrimitive3.Description.displayName;
19597
19755
  // src/components/ui/modal.tsx
19598
19756
  init_cn();
19599
19757
  import * as React56 from "react";
19600
- import { useEffect as useEffect29 } from "react";
19758
+ import { useEffect as useEffect30 } from "react";
19601
19759
  import { jsx as jsx158, jsxs as jsxs129 } from "react/jsx-runtime";
19602
19760
  var Modal = React56.forwardRef(
19603
19761
  ({ isOpen, onClose, children, className }, ref) => {
19604
- useEffect29(() => {
19762
+ useEffect30(() => {
19605
19763
  const handleKeyDown = (event) => {
19606
19764
  if (event.key === "Escape") {
19607
19765
  onClose();
@@ -19683,13 +19841,13 @@ ModalFooter.displayName = "ModalFooter";
19683
19841
 
19684
19842
  // src/components/ui/modal-v2.tsx
19685
19843
  import * as React57 from "react";
19686
- import { useEffect as useEffect30 } from "react";
19844
+ import { useEffect as useEffect31 } from "react";
19687
19845
  init_cn();
19688
19846
  import { jsx as jsx159, jsxs as jsxs130 } from "react/jsx-runtime";
19689
19847
  var ModalContext = React57.createContext({});
19690
19848
  var Modal2 = React57.forwardRef(
19691
19849
  ({ isOpen, onClose, children, className }, ref) => {
19692
- useEffect30(() => {
19850
+ useEffect31(() => {
19693
19851
  const handleKeyDown = (event) => {
19694
19852
  if (event.key === "Escape") {
19695
19853
  onClose();
@@ -20363,7 +20521,7 @@ function TabContent({
20363
20521
 
20364
20522
  // src/components/ui/tab-navigation.tsx
20365
20523
  init_cn();
20366
- import { useState as useState42, useEffect as useEffect31, useMemo as useMemo12, useRef as useRef25, useCallback as useCallback22 } from "react";
20524
+ import { useState as useState42, useEffect as useEffect32, useMemo as useMemo12, useRef as useRef24, useCallback as useCallback22 } from "react";
20367
20525
  import { useSearchParams as useSearchParams3, useRouter as useRouter5, usePathname as usePathname3 } from "next/navigation";
20368
20526
  import { Fragment as Fragment22, jsx as jsx168, jsxs as jsxs137 } from "react/jsx-runtime";
20369
20527
  function TabNavigation({
@@ -20396,7 +20554,7 @@ function TabNavigation({
20396
20554
  };
20397
20555
  const [internalActiveTab, setInternalActiveTab] = useState42(getInitialTab);
20398
20556
  const activeTab = isUrlSyncEnabled ? internalActiveTab : controlledActiveTab || "";
20399
- useEffect31(() => {
20557
+ useEffect32(() => {
20400
20558
  if (!isUrlSyncEnabled) return;
20401
20559
  const fromUrl = searchParams?.get(paramName) || "";
20402
20560
  const nextTab = validTabIds.has(fromUrl) ? fromUrl : defaultTab || tabs[0]?.id || "";
@@ -20416,7 +20574,7 @@ function TabNavigation({
20416
20574
  controlledOnTabChange?.(tabId);
20417
20575
  }
20418
20576
  };
20419
- const scrollRef = useRef25(null);
20577
+ const scrollRef = useRef24(null);
20420
20578
  const [canScrollLeft, setCanScrollLeft] = useState42(false);
20421
20579
  const [canScrollRight, setCanScrollRight] = useState42(false);
20422
20580
  const updateScrollShadows = useCallback22(() => {
@@ -20425,7 +20583,7 @@ function TabNavigation({
20425
20583
  setCanScrollLeft(el.scrollLeft > 0);
20426
20584
  setCanScrollRight(el.scrollLeft + el.clientWidth < el.scrollWidth - 1);
20427
20585
  }, []);
20428
- useEffect31(() => {
20586
+ useEffect32(() => {
20429
20587
  const el = scrollRef.current;
20430
20588
  if (!el) return;
20431
20589
  updateScrollShadows();
@@ -20614,11 +20772,11 @@ function StatusIndicator({ status, label, href }) {
20614
20772
 
20615
20773
  // src/components/layout/list-page-layout.tsx
20616
20774
  init_cn();
20617
- import { useEffect as useEffect33, useState as useState44 } from "react";
20775
+ import { useEffect as useEffect34, useState as useState44 } from "react";
20618
20776
 
20619
20777
  // src/components/ui/filter-modal.tsx
20620
20778
  init_cn();
20621
- import { useEffect as useEffect32, useState as useState43 } from "react";
20779
+ import { useEffect as useEffect33, useState as useState43 } from "react";
20622
20780
  init_button2();
20623
20781
 
20624
20782
  // src/components/ui/filter-checkbox-item.tsx
@@ -20818,7 +20976,7 @@ function FilterModal({
20818
20976
  return { ...currentFilters };
20819
20977
  });
20820
20978
  const [pendingTags, setPendingTags] = useState43(selectedTags ?? []);
20821
- useEffect32(() => {
20979
+ useEffect33(() => {
20822
20980
  if (isOpen) {
20823
20981
  setSelectedFilters({ ...currentFilters });
20824
20982
  setPendingTags(selectedTags ?? []);
@@ -20964,10 +21122,10 @@ function ListPageLayout({
20964
21122
  const [mobileFilterOpen, setMobileFilterOpen] = useState44(false);
20965
21123
  const [localSearchValue, setLocalSearchValue] = useState44(searchValue);
20966
21124
  const debouncedSearchValue = useDebounce(localSearchValue, 500);
20967
- useEffect33(() => {
21125
+ useEffect34(() => {
20968
21126
  setLocalSearchValue(searchValue);
20969
21127
  }, [searchValue]);
20970
- useEffect33(() => {
21128
+ useEffect34(() => {
20971
21129
  if (debouncedSearchValue !== searchValue) {
20972
21130
  onSearch(debouncedSearchValue);
20973
21131
  }
@@ -23144,7 +23302,7 @@ var ListLoader = (props) => /* @__PURE__ */ jsx206(ContentLoader, { ...props, va
23144
23302
 
23145
23303
  // src/components/ui/table/table.tsx
23146
23304
  init_cn();
23147
- import { useEffect as useEffect34, useRef as useRef28 } from "react";
23305
+ import { useEffect as useEffect35, useRef as useRef27 } from "react";
23148
23306
  init_pagination();
23149
23307
  init_button2();
23150
23308
 
@@ -23895,10 +24053,10 @@ function Table({
23895
24053
  };
23896
24054
  const allSelected = selectedRows.length > 0 && selectedRows.length === data.length;
23897
24055
  const someSelected = selectedRows.length > 0 && selectedRows.length < data.length;
23898
- const sentinelRef = useRef28(null);
23899
- const onLoadMoreRef = useRef28(infiniteScroll?.onLoadMore);
24056
+ const sentinelRef = useRef27(null);
24057
+ const onLoadMoreRef = useRef27(infiniteScroll?.onLoadMore);
23900
24058
  onLoadMoreRef.current = infiniteScroll?.onLoadMore;
23901
- useEffect34(() => {
24059
+ useEffect35(() => {
23902
24060
  if (!infiniteScroll?.hasNextPage || infiniteScroll.isFetchingNextPage) return;
23903
24061
  const sentinel = sentinelRef.current;
23904
24062
  if (!sentinel) return;
@@ -24110,7 +24268,7 @@ import { useMemo as useMemo16 } from "react";
24110
24268
 
24111
24269
  // src/components/ui/query-report-table/query-report-table-header.tsx
24112
24270
  init_cn();
24113
- import { useRef as useRef29, useState as useState47, useCallback as useCallback23 } from "react";
24271
+ import { useRef as useRef28, useState as useState47, useCallback as useCallback23 } from "react";
24114
24272
  import { jsx as jsx217, jsxs as jsxs176 } from "react/jsx-runtime";
24115
24273
  function QueryReportTableHeader({
24116
24274
  columns,
@@ -24139,7 +24297,7 @@ function QueryReportTableHeader({
24139
24297
  );
24140
24298
  }
24141
24299
  function TruncatedHeaderCell({ value, width }) {
24142
- const textRef = useRef29(null);
24300
+ const textRef = useRef28(null);
24143
24301
  const [isTruncated, setIsTruncated] = useState47(false);
24144
24302
  const checkTruncation = useCallback23(() => {
24145
24303
  const el = textRef.current;
@@ -24169,7 +24327,7 @@ function TruncatedHeaderCell({ value, width }) {
24169
24327
 
24170
24328
  // src/components/ui/query-report-table/query-report-table-row.tsx
24171
24329
  init_cn();
24172
- import { useRef as useRef30, useState as useState48, useCallback as useCallback24 } from "react";
24330
+ import { useRef as useRef29, useState as useState48, useCallback as useCallback24 } from "react";
24173
24331
  import { jsx as jsx218, jsxs as jsxs177 } from "react/jsx-runtime";
24174
24332
  function QueryReportTableRow({
24175
24333
  row,
@@ -24215,7 +24373,7 @@ function QueryReportTableRow({
24215
24373
  );
24216
24374
  }
24217
24375
  function TruncatedCell({ value, className }) {
24218
- const textRef = useRef30(null);
24376
+ const textRef = useRef29(null);
24219
24377
  const [isTruncated, setIsTruncated] = useState48(false);
24220
24378
  const checkTruncation = useCallback24(() => {
24221
24379
  const el = textRef.current;
@@ -24952,7 +25110,7 @@ function DataTableBody({
24952
25110
  }
24953
25111
 
24954
25112
  // src/components/ui/data-table/data-table-infinite-footer.tsx
24955
- import { useEffect as useEffect35, useRef as useRef31 } from "react";
25113
+ import { useEffect as useEffect36, useRef as useRef30 } from "react";
24956
25114
  import { Fragment as Fragment34, jsx as jsx228, jsxs as jsxs186 } from "react/jsx-runtime";
24957
25115
  function DataTableInfiniteFooter({
24958
25116
  hasNextPage,
@@ -24961,10 +25119,10 @@ function DataTableInfiniteFooter({
24961
25119
  skeletonRows = 3,
24962
25120
  rootMargin = "200px"
24963
25121
  }) {
24964
- const sentinelRef = useRef31(null);
24965
- const onLoadMoreRef = useRef31(onLoadMore);
25122
+ const sentinelRef = useRef30(null);
25123
+ const onLoadMoreRef = useRef30(onLoadMore);
24966
25124
  onLoadMoreRef.current = onLoadMore;
24967
- useEffect35(() => {
25125
+ useEffect36(() => {
24968
25126
  if (!hasNextPage || isFetchingNextPage) return;
24969
25127
  const sentinel = sentinelRef.current;
24970
25128
  if (!sentinel) return;
@@ -25089,7 +25247,7 @@ var DataTable = Object.assign(DataTableRoot, {
25089
25247
  });
25090
25248
 
25091
25249
  // src/components/ui/phone-input.tsx
25092
- import { useCallback as useCallback27, useEffect as useEffect36, useMemo as useMemo18, useRef as useRef32, useState as useState49 } from "react";
25250
+ import { useCallback as useCallback27, useEffect as useEffect37, useMemo as useMemo18, useRef as useRef31, useState as useState49 } from "react";
25093
25251
  import { jsx as jsx230, jsxs as jsxs188 } from "react/jsx-runtime";
25094
25252
  function PhoneInput({
25095
25253
  value,
@@ -25107,7 +25265,7 @@ function PhoneInput({
25107
25265
  [countryCode, priority, others]
25108
25266
  );
25109
25267
  const [isInvalid, setIsInvalid] = useState49(false);
25110
- const debounceRef = useRef32(null);
25268
+ const debounceRef = useRef31(null);
25111
25269
  const digitCount = useCallback27((val) => val.replace(/[^0-9]/g, "").length, []);
25112
25270
  const runValidation = useCallback27((phone) => {
25113
25271
  if (!phone || digitCount(phone) === 0) {
@@ -25123,7 +25281,7 @@ function PhoneInput({
25123
25281
  if (debounceRef.current) clearTimeout(debounceRef.current);
25124
25282
  debounceRef.current = setTimeout(() => runValidation(phone), 300);
25125
25283
  }, [runValidation]);
25126
- useEffect36(() => {
25284
+ useEffect37(() => {
25127
25285
  return () => {
25128
25286
  if (debounceRef.current) clearTimeout(debounceRef.current);
25129
25287
  };
@@ -25685,8 +25843,8 @@ function FilterList({
25685
25843
 
25686
25844
  // src/components/ui/tag-search-input.tsx
25687
25845
  import {
25688
- useEffect as useEffect38,
25689
- useRef as useRef34,
25846
+ useEffect as useEffect39,
25847
+ useRef as useRef33,
25690
25848
  useState as useState51
25691
25849
  } from "react";
25692
25850
  init_cn();
@@ -25721,11 +25879,11 @@ function TagSearchInput({
25721
25879
  limitTags,
25722
25880
  placeholder: currentPlaceholder
25723
25881
  });
25724
- const wrapperRef = useRef34(null);
25725
- const hiddenTagsRef = useRef34(null);
25726
- const hiddenTagsPopupRef = useRef34(null);
25882
+ const wrapperRef = useRef33(null);
25883
+ const hiddenTagsRef = useRef33(null);
25884
+ const hiddenTagsPopupRef = useRef33(null);
25727
25885
  const [showHiddenTags, setShowHiddenTags] = useState51(false);
25728
- useEffect38(() => {
25886
+ useEffect39(() => {
25729
25887
  if (!showHiddenTags) return;
25730
25888
  const handleClick = (e) => {
25731
25889
  const target = e.target;
@@ -25881,7 +26039,7 @@ function TagSearchInput({
25881
26039
 
25882
26040
  // src/components/ui/markdown-editor.tsx
25883
26041
  init_cn();
25884
- import { useRef as useRef35, useCallback as useCallback28, useState as useState52, useEffect as useEffect39 } from "react";
26042
+ import { useRef as useRef34, useCallback as useCallback28, useState as useState52, useEffect as useEffect40 } from "react";
25885
26043
  import dynamic from "next/dynamic";
25886
26044
  import { Loader2 as Loader23, Upload as Upload2 } from "lucide-react";
25887
26045
  import { jsx as jsx234, jsxs as jsxs192 } from "react/jsx-runtime";
@@ -25912,7 +26070,7 @@ body .w-md-editor .w-md-editor-bar::after { content: '' !important; display: blo
25912
26070
  body .w-md-editor .w-md-editor-bar:hover::after { border-color: var(--color-text-secondary) !important; }
25913
26071
  `;
25914
26072
  function MarkdownEditorStyles() {
25915
- useEffect39(() => {
26073
+ useEffect40(() => {
25916
26074
  if (document.getElementById(MARKDOWN_EDITOR_STYLE_ID)) return;
25917
26075
  const style = document.createElement("style");
25918
26076
  style.id = MARKDOWN_EDITOR_STYLE_ID;
@@ -25936,11 +26094,11 @@ function MarkdownEditor({
25936
26094
  onFileUploaded,
25937
26095
  renderPreview
25938
26096
  }) {
25939
- const fileInputRef = useRef35(null);
26097
+ const fileInputRef = useRef34(null);
25940
26098
  const [isUploading, setIsUploading] = useState52(false);
25941
26099
  const [uploadProgress, setUploadProgress] = useState52("");
25942
26100
  const [defaultExtraCommands, setDefaultExtraCommands] = useState52([]);
25943
- useEffect39(() => {
26101
+ useEffect40(() => {
25944
26102
  import("@uiw/react-md-editor").then((mod) => {
25945
26103
  if (mod.commands?.getExtraCommands) {
25946
26104
  setDefaultExtraCommands(mod.commands.getExtraCommands());
@@ -26029,11 +26187,11 @@ function MarkdownEditor({
26029
26187
  }
26030
26188
  } : null;
26031
26189
  const extraCommands = uploadCommand ? [...defaultExtraCommands, uploadCommand] : defaultExtraCommands;
26032
- const wrapperRef = useRef35(null);
26033
- const isDraggingRef = useRef35(false);
26034
- const mouseYRef = useRef35(0);
26035
- const rafRef = useRef35(0);
26036
- const scrollParentRef = useRef35(window);
26190
+ const wrapperRef = useRef34(null);
26191
+ const isDraggingRef = useRef34(false);
26192
+ const mouseYRef = useRef34(0);
26193
+ const rafRef = useRef34(0);
26194
+ const scrollParentRef = useRef34(window);
26037
26195
  const EDGE_ZONE = 60;
26038
26196
  const MAX_SCROLL_SPEED = 15;
26039
26197
  const findScrollParent = useCallback28((el) => {
@@ -26063,7 +26221,7 @@ function MarkdownEditor({
26063
26221
  }
26064
26222
  rafRef.current = requestAnimationFrame(scrollLoop);
26065
26223
  }, []);
26066
- useEffect39(() => {
26224
+ useEffect40(() => {
26067
26225
  const wrapper = wrapperRef.current;
26068
26226
  if (!wrapper) return;
26069
26227
  const onMouseMove = (e) => {
@@ -27345,12 +27503,12 @@ function ArrayEntryManager({
27345
27503
  }) {
27346
27504
  const [draftItems, setDraftItems] = useState58(items);
27347
27505
  const [isDirty, setIsDirty] = useState58(false);
27348
- useEffect42(() => {
27506
+ useEffect43(() => {
27349
27507
  if (!isDirty && !isSaving) {
27350
27508
  setDraftItems(items);
27351
27509
  }
27352
27510
  }, [items, isDirty, isSaving]);
27353
- useEffect42(() => {
27511
+ useEffect43(() => {
27354
27512
  if (onDirtyChange) {
27355
27513
  onDirtyChange(isDirty);
27356
27514
  }
@@ -27565,7 +27723,7 @@ function AuthProvidersList({
27565
27723
 
27566
27724
  // src/components/features/changelog-manager.tsx
27567
27725
  import { Trash2 as Trash23, Plus as Plus3, ChevronDown as ChevronDown7, ChevronUp as ChevronUp3, Eye, EyeOff } from "lucide-react";
27568
- import { useState as useState60, useEffect as useEffect43 } from "react";
27726
+ import { useState as useState60, useEffect as useEffect44 } from "react";
27569
27727
  import { jsx as jsx247, jsxs as jsxs204 } from "react/jsx-runtime";
27570
27728
  function ChangelogManager({
27571
27729
  title,
@@ -27576,7 +27734,7 @@ function ChangelogManager({
27576
27734
  showVisibilityToggle = false
27577
27735
  }) {
27578
27736
  const [expandedIndices, setExpandedIndices] = useState60(/* @__PURE__ */ new Set());
27579
- useEffect43(() => {
27737
+ useEffect44(() => {
27580
27738
  if (expandAll && entries.length > 0) {
27581
27739
  setExpandedIndices(new Set(entries.map((_, i) => i)));
27582
27740
  }
@@ -27888,7 +28046,7 @@ var ErrorBoundary = class extends Component {
27888
28046
 
27889
28047
  // src/components/features/figma-prototype-viewer.tsx
27890
28048
  init_cn();
27891
- import { useState as useState61, useRef as useRef38, useEffect as useEffect44, useCallback as useCallback30, useMemo as useMemo22 } from "react";
28049
+ import { useState as useState61, useRef as useRef37, useEffect as useEffect45, useCallback as useCallback30, useMemo as useMemo22 } from "react";
27892
28050
 
27893
28051
  // src/components/features/section-selector.tsx
27894
28052
  init_cn();
@@ -28257,10 +28415,10 @@ var FigmaPrototypeViewer = ({
28257
28415
  }) => {
28258
28416
  const clientId = process.env.NEXT_PUBLIC_FIGMA_CLIENT_ID || "UTQPwZHR9OZp68TTGPFFi5";
28259
28417
  const showDebugPanel = process.env.NEXT_PUBLIC_FIGMA_DEBUG === "true";
28260
- const iframeRef = useRef38(null);
28261
- const containerRef = useRef38(null);
28262
- const navTimerRef = useRef38(null);
28263
- const touchTimerRef = useRef38(null);
28418
+ const iframeRef = useRef37(null);
28419
+ const containerRef = useRef37(null);
28420
+ const navTimerRef = useRef37(null);
28421
+ const touchTimerRef = useRef37(null);
28264
28422
  const [screenWidth, setScreenWidth] = useState61(
28265
28423
  typeof window !== "undefined" ? window.innerWidth : DESKTOP_BREAKPOINT
28266
28424
  );
@@ -28326,7 +28484,7 @@ var FigmaPrototypeViewer = ({
28326
28484
  embedUrl,
28327
28485
  iframeKey
28328
28486
  ]);
28329
- useEffect44(() => {
28487
+ useEffect45(() => {
28330
28488
  const handleResize = () => {
28331
28489
  const newWidth = window.innerWidth;
28332
28490
  setScreenWidth(newWidth);
@@ -28340,7 +28498,7 @@ var FigmaPrototypeViewer = ({
28340
28498
  return () => window.removeEventListener("resize", handleResize);
28341
28499
  }, []);
28342
28500
  const [lastViewMode, setLastViewMode] = useState61(viewMode);
28343
- useEffect44(() => {
28501
+ useEffect45(() => {
28344
28502
  if (lastViewMode !== viewMode && iframeState === "READY") {
28345
28503
  console.log("[ViewMode Change]", lastViewMode, "\u2192", viewMode);
28346
28504
  setIframeState("RELOADING");
@@ -28348,7 +28506,7 @@ var FigmaPrototypeViewer = ({
28348
28506
  }
28349
28507
  setLastViewMode(viewMode);
28350
28508
  }, [viewMode, lastViewMode, iframeState]);
28351
- useEffect44(() => {
28509
+ useEffect45(() => {
28352
28510
  const handleVisibilityChange = () => {
28353
28511
  if (document.visibilityState === "visible" && iframeState === "READY") {
28354
28512
  console.log("[Sleep Recovery] Reloading iframe after sleep");
@@ -28359,7 +28517,7 @@ var FigmaPrototypeViewer = ({
28359
28517
  document.addEventListener("visibilitychange", handleVisibilityChange);
28360
28518
  return () => document.removeEventListener("visibilitychange", handleVisibilityChange);
28361
28519
  }, [iframeState]);
28362
- useEffect44(() => {
28520
+ useEffect45(() => {
28363
28521
  const handleMessage = (event) => {
28364
28522
  if (event.origin !== "https://www.figma.com") return;
28365
28523
  const validEvents = ["INITIAL_LOAD", "NEW_STATE", "PRESENTED_NODE_CHANGED"];
@@ -28443,12 +28601,12 @@ var FigmaPrototypeViewer = ({
28443
28601
  }
28444
28602
  }, 500);
28445
28603
  }, []);
28446
- useEffect44(() => {
28604
+ useEffect45(() => {
28447
28605
  if (externalActiveSection && externalActiveSection !== activeSection && isInitialized) {
28448
28606
  navigateToSection(externalActiveSection);
28449
28607
  }
28450
28608
  }, [externalActiveSection, activeSection, isInitialized, navigateToSection]);
28451
- useEffect44(() => {
28609
+ useEffect45(() => {
28452
28610
  return () => {
28453
28611
  if (navTimerRef.current) clearTimeout(navTimerRef.current);
28454
28612
  if (touchTimerRef.current) clearTimeout(touchTimerRef.current);
@@ -28551,7 +28709,7 @@ var FigmaPrototypeViewer = ({
28551
28709
 
28552
28710
  // src/components/features/filters-dropdown.tsx
28553
28711
  init_cn();
28554
- import { useEffect as useEffect45, useRef as useRef39, useState as useState62 } from "react";
28712
+ import { useEffect as useEffect46, useRef as useRef38, useState as useState62 } from "react";
28555
28713
  import { jsx as jsx253, jsxs as jsxs209 } from "react/jsx-runtime";
28556
28714
  var FilterCheckbox = ({ checked, disabled = false, className }) => {
28557
28715
  return /* @__PURE__ */ jsx253(
@@ -28588,10 +28746,10 @@ var FiltersDropdown = ({
28588
28746
  const [isVisible, setIsVisible] = useState62(false);
28589
28747
  const [isMobile, setIsMobile] = useState62(false);
28590
28748
  const [actualPlacement, setActualPlacement] = useState62(placement);
28591
- const dropdownRef = useRef39(null);
28592
- const triggerRef = useRef39(null);
28593
- const containerRef = useRef39(null);
28594
- useEffect45(() => {
28749
+ const dropdownRef = useRef38(null);
28750
+ const triggerRef = useRef38(null);
28751
+ const containerRef = useRef38(null);
28752
+ useEffect46(() => {
28595
28753
  if (isOpen) {
28596
28754
  setShouldRender(true);
28597
28755
  let id2 = 0;
@@ -28607,7 +28765,7 @@ var FiltersDropdown = ({
28607
28765
  const t = setTimeout(() => setShouldRender(false), ANIMATION_MS);
28608
28766
  return () => clearTimeout(t);
28609
28767
  }, [isOpen]);
28610
- useEffect45(() => {
28768
+ useEffect46(() => {
28611
28769
  if (!responsive) {
28612
28770
  setIsMobile(false);
28613
28771
  return;
@@ -28619,7 +28777,7 @@ var FiltersDropdown = ({
28619
28777
  window.addEventListener("resize", checkMobile);
28620
28778
  return () => window.removeEventListener("resize", checkMobile);
28621
28779
  }, [responsive]);
28622
- useEffect45(() => {
28780
+ useEffect46(() => {
28623
28781
  if (!isOpen || isMobile || !triggerRef.current) return;
28624
28782
  const calculateOptimalPlacement = () => {
28625
28783
  const trigger = triggerRef.current;
@@ -28656,12 +28814,12 @@ var FiltersDropdown = ({
28656
28814
  return initial;
28657
28815
  });
28658
28816
  const currentFiltersStr = currentFilters ? JSON.stringify(currentFilters) : "";
28659
- useEffect45(() => {
28817
+ useEffect46(() => {
28660
28818
  if (currentFilters) {
28661
28819
  setSelectedFilters({ ...currentFilters });
28662
28820
  }
28663
28821
  }, [currentFiltersStr]);
28664
- useEffect45(() => {
28822
+ useEffect46(() => {
28665
28823
  const handleClickOutside = (event) => {
28666
28824
  if (containerRef.current && !containerRef.current.contains(event.target)) {
28667
28825
  setIsOpen(false);
@@ -28678,7 +28836,7 @@ var FiltersDropdown = ({
28678
28836
  };
28679
28837
  }
28680
28838
  }, [isOpen, placement]);
28681
- useEffect45(() => {
28839
+ useEffect46(() => {
28682
28840
  const handleEscape = (e) => {
28683
28841
  if (e.key === "Escape" && isOpen) {
28684
28842
  setIsOpen(false);
@@ -28944,13 +29102,13 @@ function KnowledgeBaseLinksManager({
28944
29102
  }
28945
29103
 
28946
29104
  // src/components/features/loading-provider.tsx
28947
- import { createContext as createContext7, useContext as useContext7, useState as useState63, useEffect as useEffect46 } from "react";
29105
+ import { createContext as createContext7, useContext as useContext7, useState as useState63, useEffect as useEffect47 } from "react";
28948
29106
  import { jsx as jsx256, jsxs as jsxs210 } from "react/jsx-runtime";
28949
29107
  var LoadingContext = createContext7(void 0);
28950
29108
  function LoadingProvider({ children }) {
28951
29109
  const [isLoading, setIsLoading] = useState63(false);
28952
29110
  const [progress, setProgress] = useState63(0);
28953
- useEffect46(() => {
29111
+ useEffect47(() => {
28954
29112
  let interval;
28955
29113
  if (isLoading) {
28956
29114
  setProgress(10);
@@ -28995,7 +29153,7 @@ function useLoading() {
28995
29153
  }
28996
29154
 
28997
29155
  // src/components/features/media-gallery-manager.tsx
28998
- import { useState as useState64, useRef as useRef40, useCallback as useCallback31 } from "react";
29156
+ import { useState as useState64, useRef as useRef39, useCallback as useCallback31 } from "react";
28999
29157
  import {
29000
29158
  Upload as Upload3,
29001
29159
  Image as ImageIcon2,
@@ -29016,7 +29174,7 @@ function MediaGalleryManager({
29016
29174
  modalTitle = "Media Gallery",
29017
29175
  className = ""
29018
29176
  }) {
29019
- const fileInputRef = useRef40(null);
29177
+ const fileInputRef = useRef39(null);
29020
29178
  const [deletingIndex, setDeletingIndex] = useState64(null);
29021
29179
  const [draggedIndex, setDraggedIndex] = useState64(null);
29022
29180
  const handleFileSelect = useCallback31(async (event) => {
@@ -29228,7 +29386,7 @@ function OSTypeBadgeGroup({
29228
29386
  }
29229
29387
 
29230
29388
  // src/components/features/parallax-image-showcase.tsx
29231
- import { useEffect as useEffect47, useState as useState65, useRef as useRef41 } from "react";
29389
+ import { useEffect as useEffect48, useState as useState65, useRef as useRef40 } from "react";
29232
29390
  import Image12 from "next/image";
29233
29391
  import { motion as motion2, useScroll, useTransform, useMotionValue, useSpring } from "framer-motion";
29234
29392
  import { jsx as jsx260, jsxs as jsxs213 } from "react/jsx-runtime";
@@ -29248,8 +29406,8 @@ var ParallaxImageShowcase = ({
29248
29406
  const mouseXSpring = useSpring(mouseX, springConfig);
29249
29407
  const mouseYSpring = useSpring(mouseY, springConfig);
29250
29408
  const [componentRect, setComponentRect] = useState65(null);
29251
- const componentRef = useRef41(null);
29252
- useEffect47(() => {
29409
+ const componentRef = useRef40(null);
29410
+ useEffect48(() => {
29253
29411
  const updateRect = () => {
29254
29412
  if (componentRef.current) {
29255
29413
  setComponentRect(componentRef.current.getBoundingClientRect());
@@ -29263,7 +29421,7 @@ var ParallaxImageShowcase = ({
29263
29421
  window.removeEventListener("scroll", updateRect);
29264
29422
  };
29265
29423
  }, []);
29266
- useEffect47(() => {
29424
+ useEffect48(() => {
29267
29425
  const handleGlobalMouseMove = (e) => {
29268
29426
  if (!componentRect) return;
29269
29427
  const centerX = componentRect.left + componentRect.width / 2;
@@ -29907,7 +30065,7 @@ function PushButtonSelector({
29907
30065
  }
29908
30066
 
29909
30067
  // src/components/features/release-media-manager.tsx
29910
- import { useState as useState66, useRef as useRef42 } from "react";
30068
+ import { useState as useState66, useRef as useRef41 } from "react";
29911
30069
  import { Trash2 as Trash25, Plus as Plus5, Image as ImageIcon3, Video as Video4, Upload as Upload4, Loader2 as Loader28, GripVertical as GripVertical2 } from "lucide-react";
29912
30070
  import Image13 from "next/image";
29913
30071
  import { jsx as jsx265, jsxs as jsxs218 } from "react/jsx-runtime";
@@ -29917,7 +30075,7 @@ function ReleaseMediaManager({
29917
30075
  onUpload,
29918
30076
  className = ""
29919
30077
  }) {
29920
- const fileInputRef = useRef42(null);
30078
+ const fileInputRef = useRef41(null);
29921
30079
  const [uploadingIndex, setUploadingIndex] = useState66(null);
29922
30080
  const handleFileSelect = async (event) => {
29923
30081
  const file = event.target.files?.[0];
@@ -32353,7 +32511,7 @@ function ViewToggle({
32353
32511
 
32354
32512
  // src/components/features/policy-configuration-panel.tsx
32355
32513
  init_cn();
32356
- import { useRef as useRef43, useEffect as useEffect48, useState as useState71 } from "react";
32514
+ import { useRef as useRef42, useEffect as useEffect49, useState as useState71 } from "react";
32357
32515
  import { ChevronDown as ChevronDown8 } from "lucide-react";
32358
32516
  init_button2();
32359
32517
 
@@ -32535,8 +32693,8 @@ var PolicyRow = ({ policy, categoryId, editMode, onPermissionChange }) => {
32535
32693
  };
32536
32694
  var useAnimatedHeight = (isExpanded) => {
32537
32695
  const [height, setHeight] = useState71(0);
32538
- const contentRef = useRef43(null);
32539
- useEffect48(() => {
32696
+ const contentRef = useRef42(null);
32697
+ useEffect49(() => {
32540
32698
  if (contentRef.current) {
32541
32699
  const contentHeight = contentRef.current.scrollHeight;
32542
32700
  setHeight(isExpanded ? contentHeight : 0);
@@ -32692,7 +32850,7 @@ PolicyConfigurationPanel.displayName = "PolicyConfigurationPanel";
32692
32850
  init_button2();
32693
32851
  init_cn();
32694
32852
  import { getCountries as getCountries2 } from "libphonenumber-js";
32695
- import { useEffect as useEffect49, useState as useState72 } from "react";
32853
+ import { useEffect as useEffect50, useState as useState72 } from "react";
32696
32854
  import { Fragment as Fragment41, jsx as jsx292, jsxs as jsxs240 } from "react/jsx-runtime";
32697
32855
  function WaitlistForm({
32698
32856
  id = "waitlist-form",
@@ -32721,12 +32879,12 @@ function WaitlistForm({
32721
32879
  const [isPhoneInvalid, setIsPhoneInvalid] = useState72(false);
32722
32880
  const [showConsentError, setShowConsentError] = useState72(false);
32723
32881
  const isMailDomainGeneric = hasGenericEmailDomain(email);
32724
- useEffect49(() => {
32882
+ useEffect50(() => {
32725
32883
  if (defaultEmail) {
32726
32884
  setEmail(defaultEmail);
32727
32885
  }
32728
32886
  }, [defaultEmail]);
32729
- useEffect49(() => {
32887
+ useEffect50(() => {
32730
32888
  setIsClient(true);
32731
32889
  if (!geoApiUrl) return;
32732
32890
  const supportedCountries = new Set(getCountries2());
@@ -33792,6 +33950,7 @@ export {
33792
33950
  ViewToggle,
33793
33951
  YouTubeEmbed,
33794
33952
  extractYouTubeId,
33953
+ YouTubeLinkParser,
33795
33954
  PolicyConfigurationPanel,
33796
33955
  PhoneInput,
33797
33956
  WaitlistForm,
@@ -34186,4 +34345,4 @@ export {
34186
34345
  TMCG_SOCIAL_PLATFORMS,
34187
34346
  assets
34188
34347
  };
34189
- //# sourceMappingURL=chunk-AAX27BCR.js.map
34348
+ //# sourceMappingURL=chunk-6LDN3CIY.js.map