@lightbird/ui 0.2.0 → 0.3.0

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.cjs CHANGED
@@ -21,6 +21,8 @@ var SelectPrimitive = require('@radix-ui/react-select');
21
21
  var core = require('@lightbird/core');
22
22
  var DialogPrimitive = require('@radix-ui/react-dialog');
23
23
  var react = require('@lightbird/core/react');
24
+ var reactSdk = require('@openfeature/react-sdk');
25
+ var AlertDialogPrimitive = require('@radix-ui/react-alert-dialog');
24
26
  var ToastPrimitives = require('@radix-ui/react-toast');
25
27
 
26
28
  function _interopNamespace(e) {
@@ -50,6 +52,7 @@ var RadioGroupPrimitive__namespace = /*#__PURE__*/_interopNamespace(RadioGroupPr
50
52
  var ScrollAreaPrimitive__namespace = /*#__PURE__*/_interopNamespace(ScrollAreaPrimitive);
51
53
  var SelectPrimitive__namespace = /*#__PURE__*/_interopNamespace(SelectPrimitive);
52
54
  var DialogPrimitive__namespace = /*#__PURE__*/_interopNamespace(DialogPrimitive);
55
+ var AlertDialogPrimitive__namespace = /*#__PURE__*/_interopNamespace(AlertDialogPrimitive);
53
56
  var ToastPrimitives__namespace = /*#__PURE__*/_interopNamespace(ToastPrimitives);
54
57
 
55
58
  function cn(...inputs) {
@@ -658,7 +661,13 @@ function formatTime2(seconds) {
658
661
  return `${m}:${String(s).padStart(2, "0")}`;
659
662
  }
660
663
  var VIDEO_EXTENSIONS_RE = /\.(mp4|mkv|webm|mov|avi|wmv|flv|m4v)$/i;
661
- function SortablePlaylistItem({ item, index, isActive, onSelect, onRemove }) {
664
+ function formatBytes(bytes) {
665
+ if (bytes < 1024) return `${bytes} B`;
666
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
667
+ if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
668
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
669
+ }
670
+ function SortablePlaylistItem({ item, index, isActive, downloadReady, onSelect, onRemove }) {
662
671
  const {
663
672
  attributes,
664
673
  listeners: listeners2,
@@ -702,12 +711,24 @@ function SortablePlaylistItem({ item, index, isActive, onSelect, onRemove }) {
702
711
  className: "flex-1 flex items-center gap-1.5 min-w-0 text-left",
703
712
  "aria-label": `Play ${item.name}`,
704
713
  children: [
705
- item.type === "video" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ListVideo, { className: "w-3.5 h-3.5 shrink-0" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Tv, { className: "w-3.5 h-3.5 shrink-0" }),
714
+ item.source === "torrent" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Globe, { className: "w-3.5 h-3.5 shrink-0 text-emerald-500" }) : item.type === "video" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ListVideo, { className: "w-3.5 h-3.5 shrink-0" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Tv, { className: "w-3.5 h-3.5 shrink-0" }),
706
715
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: item.name }),
707
716
  duration && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground ml-auto shrink-0 pl-1", children: duration })
708
717
  ]
709
718
  }
710
719
  ),
720
+ item.source === "torrent" && downloadReady && /* @__PURE__ */ jsxRuntime.jsx(
721
+ "a",
722
+ {
723
+ href: item.url,
724
+ download: item.name,
725
+ onClick: (e) => e.stopPropagation(),
726
+ className: "shrink-0 opacity-0 group-hover:opacity-100 transition-opacity p-0.5",
727
+ "aria-label": `Download ${item.name}`,
728
+ title: "Download",
729
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "w-3 h-3 text-emerald-500 hover:text-emerald-400" })
730
+ }
731
+ ),
711
732
  /* @__PURE__ */ jsxRuntime.jsx(
712
733
  "button",
713
734
  {
@@ -731,6 +752,9 @@ var PlaylistPanel = ({
731
752
  onFilesAdded,
732
753
  onFolderFilesAdded,
733
754
  onAddStream,
755
+ onAddMagnet,
756
+ torrentStatus,
757
+ showMagnet = true,
734
758
  onRemoveItem,
735
759
  onReorder,
736
760
  onImportM3U,
@@ -745,6 +769,9 @@ var PlaylistPanel = ({
745
769
  const folderInputRef = React10.useRef(null);
746
770
  const m3uInputRef = React10.useRef(null);
747
771
  const [streamUrl, setStreamUrl] = React10.useState("");
772
+ const [magnetUri, setMagnetUri] = React10.useState("");
773
+ const [showMagnetInput, setShowMagnetInput] = React10.useState(false);
774
+ const [magnetError, setMagnetError] = React10.useState(null);
748
775
  const [sortKey, setSortKey] = React10.useState("");
749
776
  const handleStreamUrlSubmit = (e) => {
750
777
  e.preventDefault();
@@ -753,6 +780,21 @@ var PlaylistPanel = ({
753
780
  setStreamUrl("");
754
781
  }
755
782
  };
783
+ const handleMagnetSubmit = async (e) => {
784
+ e.preventDefault();
785
+ const uri = magnetUri.trim();
786
+ if (!uri) return;
787
+ setMagnetError(null);
788
+ try {
789
+ const added = await onAddMagnet(uri);
790
+ if (added) {
791
+ setMagnetUri("");
792
+ setShowMagnetInput(false);
793
+ }
794
+ } catch (err) {
795
+ setMagnetError(err instanceof Error ? err.message : "Failed to load magnet link");
796
+ }
797
+ };
756
798
  const handleFolderSelect = (e) => {
757
799
  const files = Array.from(e.target.files ?? []).filter((f) => VIDEO_EXTENSIONS_RE.test(f.name)).sort((a, b) => a.name.localeCompare(b.name, void 0, { numeric: true }));
758
800
  if (files.length > 0) onFolderFilesAdded(files);
@@ -967,8 +1009,89 @@ var PlaylistPanel = ({
967
1009
  className: "h-8 text-xs"
968
1010
  }
969
1011
  ),
970
- /* @__PURE__ */ jsxRuntime.jsx(Button, { type: "submit", size: "icon", variant: "secondary", className: "h-8 w-8 shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Link, { className: "h-3.5 w-3.5" }) })
1012
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { type: "submit", size: "icon", variant: "secondary", className: "h-8 w-8 shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Link, { className: "h-3.5 w-3.5" }) }),
1013
+ showMagnet && /* @__PURE__ */ jsxRuntime.jsxs(Tooltip, { children: [
1014
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
1015
+ Button,
1016
+ {
1017
+ type: "button",
1018
+ size: "icon",
1019
+ variant: showMagnetInput ? "default" : "outline",
1020
+ className: "h-8 w-8 shrink-0",
1021
+ onClick: () => {
1022
+ setShowMagnetInput((v) => !v);
1023
+ setMagnetError(null);
1024
+ },
1025
+ "aria-label": "Add magnet link",
1026
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Link2, { className: "h-3.5 w-3.5" })
1027
+ }
1028
+ ) }),
1029
+ /* @__PURE__ */ jsxRuntime.jsx(TooltipContent, { side: "bottom", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Add Magnet Link" }) })
1030
+ ] })
971
1031
  ] }),
1032
+ showMagnet && showMagnetInput && /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleMagnetSubmit, className: "space-y-1.5", children: [
1033
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-1.5", children: [
1034
+ /* @__PURE__ */ jsxRuntime.jsx(
1035
+ Input,
1036
+ {
1037
+ placeholder: "magnet:?xt=urn:btih:\u2026",
1038
+ value: magnetUri,
1039
+ onChange: (e) => {
1040
+ setMagnetUri(e.target.value);
1041
+ setMagnetError(null);
1042
+ },
1043
+ className: "h-8 text-xs font-mono",
1044
+ disabled: torrentStatus.status === "loading-metadata"
1045
+ }
1046
+ ),
1047
+ /* @__PURE__ */ jsxRuntime.jsx(
1048
+ Button,
1049
+ {
1050
+ type: "submit",
1051
+ size: "icon",
1052
+ variant: "default",
1053
+ className: "h-8 w-8 shrink-0",
1054
+ disabled: !magnetUri.trim() || torrentStatus.status === "loading-metadata",
1055
+ "aria-label": "Load magnet link",
1056
+ children: torrentStatus.status === "loading-metadata" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Globe, { className: "h-3.5 w-3.5" })
1057
+ }
1058
+ )
1059
+ ] }),
1060
+ torrentStatus.status === "loading-metadata" && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-[11px] text-muted-foreground flex items-center gap-1", children: [
1061
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-3 w-3 animate-spin" }),
1062
+ "Fetching torrent info\u2026"
1063
+ ] }),
1064
+ magnetError && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-[11px] text-destructive flex items-center gap-1", children: [
1065
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircle, { className: "h-3 w-3 shrink-0" }),
1066
+ magnetError
1067
+ ] })
1068
+ ] }),
1069
+ showMagnet && torrentStatus.status === "ready" && (torrentStatus.progress >= 1 ? /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-[10px] text-emerald-500 flex items-center gap-1", children: [
1070
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "h-3 w-3 shrink-0" }),
1071
+ "Download ready \u2014 hover a file to save it"
1072
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
1073
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full bg-muted rounded-full h-1 overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
1074
+ "div",
1075
+ {
1076
+ className: "bg-emerald-500 h-1 rounded-full transition-all duration-500",
1077
+ style: { width: `${Math.round(torrentStatus.progress * 100)}%` }
1078
+ }
1079
+ ) }),
1080
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-[10px] text-muted-foreground flex items-center justify-between", children: [
1081
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1082
+ "\u2193 ",
1083
+ formatBytes(torrentStatus.downloadSpeed),
1084
+ "/s \xB7 ",
1085
+ torrentStatus.numPeers,
1086
+ " peer",
1087
+ torrentStatus.numPeers !== 1 ? "s" : ""
1088
+ ] }),
1089
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1090
+ Math.round(torrentStatus.progress * 100),
1091
+ "%"
1092
+ ] })
1093
+ ] })
1094
+ ] })),
972
1095
  playlist.length > 1 && /* @__PURE__ */ jsxRuntime.jsxs(Select, { value: sortKey, onValueChange: handleSort, children: [
973
1096
  /* @__PURE__ */ jsxRuntime.jsx(SelectTrigger, { className: "h-7 text-xs", "aria-label": "Sort playlist", children: /* @__PURE__ */ jsxRuntime.jsx(SelectValue, { placeholder: "Sort by\u2026" }) }),
974
1097
  /* @__PURE__ */ jsxRuntime.jsxs(SelectContent, { children: [
@@ -993,6 +1116,7 @@ var PlaylistPanel = ({
993
1116
  item,
994
1117
  index,
995
1118
  isActive: index === currentVideoIndex,
1119
+ downloadReady: item.source === "torrent" && torrentStatus.progress >= 1,
996
1120
  onSelect: onSelectVideo,
997
1121
  onRemove: onRemoveItem
998
1122
  },
@@ -1470,6 +1594,103 @@ function SubtitleOverlay({ videoRef, activeSubtitle }) {
1470
1594
  }
1471
1595
  ) });
1472
1596
  }
1597
+ var AlertDialog = AlertDialogPrimitive__namespace.Root;
1598
+ var AlertDialogPortal = AlertDialogPrimitive__namespace.Portal;
1599
+ var AlertDialogOverlay = React10__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
1600
+ AlertDialogPrimitive__namespace.Overlay,
1601
+ {
1602
+ className: cn(
1603
+ "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
1604
+ className
1605
+ ),
1606
+ ...props,
1607
+ ref
1608
+ }
1609
+ ));
1610
+ AlertDialogOverlay.displayName = AlertDialogPrimitive__namespace.Overlay.displayName;
1611
+ var AlertDialogContent = React10__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs(AlertDialogPortal, { children: [
1612
+ /* @__PURE__ */ jsxRuntime.jsx(AlertDialogOverlay, {}),
1613
+ /* @__PURE__ */ jsxRuntime.jsx(
1614
+ AlertDialogPrimitive__namespace.Content,
1615
+ {
1616
+ ref,
1617
+ className: cn(
1618
+ "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
1619
+ className
1620
+ ),
1621
+ ...props
1622
+ }
1623
+ )
1624
+ ] }));
1625
+ AlertDialogContent.displayName = AlertDialogPrimitive__namespace.Content.displayName;
1626
+ var AlertDialogHeader = ({
1627
+ className,
1628
+ ...props
1629
+ }) => /* @__PURE__ */ jsxRuntime.jsx(
1630
+ "div",
1631
+ {
1632
+ className: cn(
1633
+ "flex flex-col space-y-2 text-center sm:text-left",
1634
+ className
1635
+ ),
1636
+ ...props
1637
+ }
1638
+ );
1639
+ AlertDialogHeader.displayName = "AlertDialogHeader";
1640
+ var AlertDialogFooter = ({
1641
+ className,
1642
+ ...props
1643
+ }) => /* @__PURE__ */ jsxRuntime.jsx(
1644
+ "div",
1645
+ {
1646
+ className: cn(
1647
+ "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
1648
+ className
1649
+ ),
1650
+ ...props
1651
+ }
1652
+ );
1653
+ AlertDialogFooter.displayName = "AlertDialogFooter";
1654
+ var AlertDialogTitle = React10__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
1655
+ AlertDialogPrimitive__namespace.Title,
1656
+ {
1657
+ ref,
1658
+ className: cn("text-lg font-semibold", className),
1659
+ ...props
1660
+ }
1661
+ ));
1662
+ AlertDialogTitle.displayName = AlertDialogPrimitive__namespace.Title.displayName;
1663
+ var AlertDialogDescription = React10__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
1664
+ AlertDialogPrimitive__namespace.Description,
1665
+ {
1666
+ ref,
1667
+ className: cn("text-sm text-muted-foreground", className),
1668
+ ...props
1669
+ }
1670
+ ));
1671
+ AlertDialogDescription.displayName = AlertDialogPrimitive__namespace.Description.displayName;
1672
+ var AlertDialogAction = React10__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
1673
+ AlertDialogPrimitive__namespace.Action,
1674
+ {
1675
+ ref,
1676
+ className: cn(buttonVariants(), className),
1677
+ ...props
1678
+ }
1679
+ ));
1680
+ AlertDialogAction.displayName = AlertDialogPrimitive__namespace.Action.displayName;
1681
+ var AlertDialogCancel = React10__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
1682
+ AlertDialogPrimitive__namespace.Cancel,
1683
+ {
1684
+ ref,
1685
+ className: cn(
1686
+ buttonVariants({ variant: "outline" }),
1687
+ "mt-2 sm:mt-0",
1688
+ className
1689
+ ),
1690
+ ...props
1691
+ }
1692
+ ));
1693
+ AlertDialogCancel.displayName = AlertDialogPrimitive__namespace.Cancel.displayName;
1473
1694
  var MAX_RETRIES = 3;
1474
1695
  var LightBirdPlayer = () => {
1475
1696
  const videoRef = React10.useRef(null);
@@ -1495,6 +1716,9 @@ var LightBirdPlayer = () => {
1495
1716
  const { metadata: videoMetadata } = react.useVideoInfo(videoRef, playlist.currentItem?.file ?? null);
1496
1717
  react.useProgressPersistence(videoRef, playlist.currentItem?.name ?? null);
1497
1718
  const { chapters, currentChapter, goToChapter } = react.useChapters(videoRef, playerRef);
1719
+ const magnetLinkEnabled = reactSdk.useBooleanFlagValue(core.FLAG_MAGNET_LINK, true);
1720
+ const magnet = react.useMagnet();
1721
+ const [disclaimerPendingUri, setDisclaimerPendingUri] = React10.useState(null);
1498
1722
  const [shortcuts, setShortcuts] = React10.useState(() => core.loadShortcuts());
1499
1723
  const [showShortcutsHelp, setShowShortcutsHelp] = React10.useState(false);
1500
1724
  const [showShortcutsDialog, setShowShortcutsDialog] = React10.useState(false);
@@ -1884,6 +2108,40 @@ var LightBirdPlayer = () => {
1884
2108
  startStallDetection();
1885
2109
  }
1886
2110
  }, [playlist, subtitles]);
2111
+ const handleAddMagnet = React10.useCallback(async (uri) => {
2112
+ if (!core.hasAcceptedDisclaimer()) {
2113
+ setDisclaimerPendingUri(uri);
2114
+ return false;
2115
+ }
2116
+ const items = await magnet.addMagnet(uri);
2117
+ const startIndex = playlist.playlist.length;
2118
+ items.forEach((item) => playlist.appendItem(item));
2119
+ if (playlist.currentIndex === null && items.length > 0) {
2120
+ playlist.selectItem(startIndex);
2121
+ if (videoRef.current) videoRef.current.src = items[0].url;
2122
+ subtitles.reset();
2123
+ setAudioTracks([]);
2124
+ setActiveAudioTrack("0");
2125
+ isStreamRef.current = true;
2126
+ startStallDetection();
2127
+ }
2128
+ if (items.length > 1) {
2129
+ toast2({ title: `${items.length} videos added from torrent`, description: magnet.torrentStatus.torrentName });
2130
+ }
2131
+ return true;
2132
+ }, [magnet, playlist, subtitles, toast2]);
2133
+ const handleDisclaimerAccepted = React10.useCallback(async () => {
2134
+ core.acceptDisclaimer();
2135
+ const uri = disclaimerPendingUri;
2136
+ setDisclaimerPendingUri(null);
2137
+ if (uri) {
2138
+ try {
2139
+ await handleAddMagnet(uri);
2140
+ } catch (err) {
2141
+ toast2({ title: "Magnet link failed", description: err instanceof Error ? err.message : "Unknown error", variant: "destructive" });
2142
+ }
2143
+ }
2144
+ }, [disclaimerPendingUri, handleAddMagnet, toast2]);
1887
2145
  const handleSubtitleChange = React10.useCallback(async (id) => {
1888
2146
  subtitles.switchSubtitle(id);
1889
2147
  if (playerRef.current) {
@@ -2132,6 +2390,9 @@ var LightBirdPlayer = () => {
2132
2390
  onFilesAdded: handleFileChange,
2133
2391
  onFolderFilesAdded: handleFolderFilesAdded,
2134
2392
  onAddStream: handleAddStream,
2393
+ onAddMagnet: handleAddMagnet,
2394
+ torrentStatus: magnet.torrentStatus,
2395
+ showMagnet: magnetLinkEnabled,
2135
2396
  onRemoveItem: handleRemoveItem,
2136
2397
  onReorder: handleReorder,
2137
2398
  onImportM3U: handleImportM3U,
@@ -2142,7 +2403,29 @@ var LightBirdPlayer = () => {
2142
2403
  onTogglePin: () => setPlaylistPinned((v) => !v),
2143
2404
  onSizeChange: setPlaylistSize
2144
2405
  }
2145
- )
2406
+ ),
2407
+ /* @__PURE__ */ jsxRuntime.jsx(AlertDialog, { open: magnetLinkEnabled && disclaimerPendingUri !== null, onOpenChange: (open) => {
2408
+ if (!open) setDisclaimerPendingUri(null);
2409
+ }, children: /* @__PURE__ */ jsxRuntime.jsxs(AlertDialogContent, { children: [
2410
+ /* @__PURE__ */ jsxRuntime.jsxs(AlertDialogHeader, { children: [
2411
+ /* @__PURE__ */ jsxRuntime.jsx(AlertDialogTitle, { children: "Magnet Link Streaming" }),
2412
+ /* @__PURE__ */ jsxRuntime.jsx(AlertDialogDescription, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2 text-sm text-muted-foreground", children: [
2413
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
2414
+ "LightBird streams content via ",
2415
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { className: "text-foreground", children: "BitTorrent" }),
2416
+ " (WebRTC/WebSocket) \u2014 the same technology used by applications like VLC and qBittorrent."
2417
+ ] }),
2418
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
2419
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { className: "text-foreground", children: "You are responsible" }),
2420
+ " for ensuring you have the legal right to access any content you stream. LightBird does not host, index, or endorse any content."
2421
+ ] })
2422
+ ] }) })
2423
+ ] }),
2424
+ /* @__PURE__ */ jsxRuntime.jsxs(AlertDialogFooter, { children: [
2425
+ /* @__PURE__ */ jsxRuntime.jsx(AlertDialogCancel, { onClick: () => setDisclaimerPendingUri(null), children: "Cancel" }),
2426
+ /* @__PURE__ */ jsxRuntime.jsx(AlertDialogAction, { onClick: handleDisclaimerAccepted, children: "I Understand \u2014 Continue" })
2427
+ ] })
2428
+ ] }) })
2146
2429
  ] });
2147
2430
  };
2148
2431
  var lightbird_player_default = LightBirdPlayer;
@@ -2273,7 +2556,17 @@ function Toaster() {
2273
2556
  /* @__PURE__ */ jsxRuntime.jsx(ToastViewport, {})
2274
2557
  ] });
2275
2558
  }
2559
+ var started = false;
2560
+ function FeatureFlagsProvider({ children }) {
2561
+ React10.useEffect(() => {
2562
+ if (started) return;
2563
+ started = true;
2564
+ core.initFeatureFlags().catch(console.error);
2565
+ }, []);
2566
+ return /* @__PURE__ */ jsxRuntime.jsx(reactSdk.OpenFeatureProvider, { suspendUntilReady: false, suspendWhileReconciling: false, children });
2567
+ }
2276
2568
 
2569
+ exports.FeatureFlagsProvider = FeatureFlagsProvider;
2277
2570
  exports.LightBirdPlayer = lightbird_player_default;
2278
2571
  exports.PlayerControls = player_controls_default;
2279
2572
  exports.PlayerErrorBoundary = PlayerErrorBoundary;
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import React$1, { RefObject, Component, ReactNode } from 'react';
3
- import { VideoFilters, Subtitle, AudioTrack, Chapter, PlaylistItem, ParsedMediaError, VideoMetadata, ShortcutBinding } from '@lightbird/core';
3
+ import { VideoFilters, Subtitle, AudioTrack, Chapter, PlaylistItem, TorrentStatus, ParsedMediaError, VideoMetadata, ShortcutBinding } from '@lightbird/core';
4
4
 
5
5
  declare const LightBirdPlayer: () => react_jsx_runtime.JSX.Element;
6
6
 
@@ -56,6 +56,11 @@ interface PlaylistPanelProps {
56
56
  onFilesAdded: (files: FileList) => void;
57
57
  onFolderFilesAdded: (files: File[]) => void;
58
58
  onAddStream: (url: string, name?: string) => void;
59
+ /** Returns true if items were added, false if a disclaimer prompt was triggered. Throws on error. */
60
+ onAddMagnet: (uri: string) => Promise<boolean>;
61
+ torrentStatus: TorrentStatus;
62
+ /** When false, hides the magnet link button and input entirely. Defaults to true. */
63
+ showMagnet?: boolean;
59
64
  onRemoveItem: (index: number) => void;
60
65
  onReorder: (newPlaylist: PlaylistItem[]) => void;
61
66
  onImportM3U: (items: Omit<PlaylistItem, "id">[]) => void;
@@ -120,4 +125,8 @@ declare function ShortcutSettingsDialog({ shortcuts, onSave, onClose, }: Shortcu
120
125
 
121
126
  declare function Toaster(): react_jsx_runtime.JSX.Element;
122
127
 
123
- export { LightBirdPlayer, PlayerControls, PlayerErrorBoundary, PlayerErrorDisplay, PlaylistPanel, ShortcutSettingsDialog, SubtitleOverlay, Toaster, VideoInfoPanel, VideoOverlay };
128
+ declare function FeatureFlagsProvider({ children }: {
129
+ children: React$1.ReactNode;
130
+ }): react_jsx_runtime.JSX.Element;
131
+
132
+ export { FeatureFlagsProvider, LightBirdPlayer, PlayerControls, PlayerErrorBoundary, PlayerErrorDisplay, PlaylistPanel, ShortcutSettingsDialog, SubtitleOverlay, Toaster, VideoInfoPanel, VideoOverlay };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import React$1, { RefObject, Component, ReactNode } from 'react';
3
- import { VideoFilters, Subtitle, AudioTrack, Chapter, PlaylistItem, ParsedMediaError, VideoMetadata, ShortcutBinding } from '@lightbird/core';
3
+ import { VideoFilters, Subtitle, AudioTrack, Chapter, PlaylistItem, TorrentStatus, ParsedMediaError, VideoMetadata, ShortcutBinding } from '@lightbird/core';
4
4
 
5
5
  declare const LightBirdPlayer: () => react_jsx_runtime.JSX.Element;
6
6
 
@@ -56,6 +56,11 @@ interface PlaylistPanelProps {
56
56
  onFilesAdded: (files: FileList) => void;
57
57
  onFolderFilesAdded: (files: File[]) => void;
58
58
  onAddStream: (url: string, name?: string) => void;
59
+ /** Returns true if items were added, false if a disclaimer prompt was triggered. Throws on error. */
60
+ onAddMagnet: (uri: string) => Promise<boolean>;
61
+ torrentStatus: TorrentStatus;
62
+ /** When false, hides the magnet link button and input entirely. Defaults to true. */
63
+ showMagnet?: boolean;
59
64
  onRemoveItem: (index: number) => void;
60
65
  onReorder: (newPlaylist: PlaylistItem[]) => void;
61
66
  onImportM3U: (items: Omit<PlaylistItem, "id">[]) => void;
@@ -120,4 +125,8 @@ declare function ShortcutSettingsDialog({ shortcuts, onSave, onClose, }: Shortcu
120
125
 
121
126
  declare function Toaster(): react_jsx_runtime.JSX.Element;
122
127
 
123
- export { LightBirdPlayer, PlayerControls, PlayerErrorBoundary, PlayerErrorDisplay, PlaylistPanel, ShortcutSettingsDialog, SubtitleOverlay, Toaster, VideoInfoPanel, VideoOverlay };
128
+ declare function FeatureFlagsProvider({ children }: {
129
+ children: React$1.ReactNode;
130
+ }): react_jsx_runtime.JSX.Element;
131
+
132
+ export { FeatureFlagsProvider, LightBirdPlayer, PlayerControls, PlayerErrorBoundary, PlayerErrorDisplay, PlaylistPanel, ShortcutSettingsDialog, SubtitleOverlay, Toaster, VideoInfoPanel, VideoOverlay };
package/dist/index.js CHANGED
@@ -10,16 +10,18 @@ import { cva } from 'class-variance-authority';
10
10
  import * as PopoverPrimitive from '@radix-ui/react-popover';
11
11
  import * as TooltipPrimitive from '@radix-ui/react-tooltip';
12
12
  import * as LabelPrimitive from '@radix-ui/react-label';
13
- import { Circle, SkipBack, Pause, Play, SkipForward, VolumeX, Volume2, Rewind, FastForward, AudioLines, Loader2, Subtitles, Plus, X, List, Settings2, Info, Keyboard, Camera, RotateCcw, PictureInPicture2, Minimize, Maximize, ChevronDown, ChevronUp, Check, ChevronLeft, ListVideo, Download, Upload, Pin, PinOff, Minimize2, Maximize2, ChevronRight, FilePlus, FolderOpen, Link, AlertCircle, GripVertical, Tv } from 'lucide-react';
13
+ import { Circle, SkipBack, Pause, Play, SkipForward, VolumeX, Volume2, Rewind, FastForward, AudioLines, Loader2, Subtitles, Plus, X, List, Settings2, Info, Keyboard, Camera, RotateCcw, PictureInPicture2, Minimize, Maximize, ChevronDown, ChevronUp, Check, ChevronLeft, ListVideo, Download, Upload, Pin, PinOff, Minimize2, Maximize2, ChevronRight, FilePlus, FolderOpen, Link, Link2, Globe, AlertCircle, GripVertical, Tv } from 'lucide-react';
14
14
  import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
15
15
  import { DndContext, closestCenter } from '@dnd-kit/core';
16
16
  import { SortableContext, verticalListSortingStrategy, arrayMove, useSortable } from '@dnd-kit/sortable';
17
17
  import { CSS } from '@dnd-kit/utilities';
18
18
  import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
19
19
  import * as SelectPrimitive from '@radix-ui/react-select';
20
- import { exportPlaylist, formatShortcutKey, loadShortcuts, ProgressEstimator, createVideoPlayer, CancellationError, parseMediaError, captureVideoThumbnail, validateFile, parseM3U8, matchesShortcut, saveShortcuts, DEFAULT_SHORTCUTS } from '@lightbird/core';
20
+ import { exportPlaylist, formatShortcutKey, FLAG_MAGNET_LINK, loadShortcuts, ProgressEstimator, createVideoPlayer, CancellationError, hasAcceptedDisclaimer, acceptDisclaimer, initFeatureFlags, parseMediaError, captureVideoThumbnail, validateFile, parseM3U8, matchesShortcut, saveShortcuts, DEFAULT_SHORTCUTS } from '@lightbird/core';
21
21
  import * as DialogPrimitive from '@radix-ui/react-dialog';
22
- import { usePlaylist, useVideoPlayback, useVideoFilters, useSubtitles, useFullscreen, usePictureInPicture, useVideoInfo, useProgressPersistence, useChapters, useKeyboardShortcuts, useMediaSession } from '@lightbird/core/react';
22
+ import { usePlaylist, useVideoPlayback, useVideoFilters, useSubtitles, useFullscreen, usePictureInPicture, useVideoInfo, useProgressPersistence, useChapters, useMagnet, useKeyboardShortcuts, useMediaSession } from '@lightbird/core/react';
23
+ import { useBooleanFlagValue, OpenFeatureProvider } from '@openfeature/react-sdk';
24
+ import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';
23
25
  import * as ToastPrimitives from '@radix-ui/react-toast';
24
26
 
25
27
  function cn(...inputs) {
@@ -628,7 +630,13 @@ function formatTime2(seconds) {
628
630
  return `${m}:${String(s).padStart(2, "0")}`;
629
631
  }
630
632
  var VIDEO_EXTENSIONS_RE = /\.(mp4|mkv|webm|mov|avi|wmv|flv|m4v)$/i;
631
- function SortablePlaylistItem({ item, index, isActive, onSelect, onRemove }) {
633
+ function formatBytes(bytes) {
634
+ if (bytes < 1024) return `${bytes} B`;
635
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
636
+ if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
637
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
638
+ }
639
+ function SortablePlaylistItem({ item, index, isActive, downloadReady, onSelect, onRemove }) {
632
640
  const {
633
641
  attributes,
634
642
  listeners: listeners2,
@@ -672,12 +680,24 @@ function SortablePlaylistItem({ item, index, isActive, onSelect, onRemove }) {
672
680
  className: "flex-1 flex items-center gap-1.5 min-w-0 text-left",
673
681
  "aria-label": `Play ${item.name}`,
674
682
  children: [
675
- item.type === "video" ? /* @__PURE__ */ jsx(ListVideo, { className: "w-3.5 h-3.5 shrink-0" }) : /* @__PURE__ */ jsx(Tv, { className: "w-3.5 h-3.5 shrink-0" }),
683
+ item.source === "torrent" ? /* @__PURE__ */ jsx(Globe, { className: "w-3.5 h-3.5 shrink-0 text-emerald-500" }) : item.type === "video" ? /* @__PURE__ */ jsx(ListVideo, { className: "w-3.5 h-3.5 shrink-0" }) : /* @__PURE__ */ jsx(Tv, { className: "w-3.5 h-3.5 shrink-0" }),
676
684
  /* @__PURE__ */ jsx("span", { className: "truncate", children: item.name }),
677
685
  duration && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground ml-auto shrink-0 pl-1", children: duration })
678
686
  ]
679
687
  }
680
688
  ),
689
+ item.source === "torrent" && downloadReady && /* @__PURE__ */ jsx(
690
+ "a",
691
+ {
692
+ href: item.url,
693
+ download: item.name,
694
+ onClick: (e) => e.stopPropagation(),
695
+ className: "shrink-0 opacity-0 group-hover:opacity-100 transition-opacity p-0.5",
696
+ "aria-label": `Download ${item.name}`,
697
+ title: "Download",
698
+ children: /* @__PURE__ */ jsx(Download, { className: "w-3 h-3 text-emerald-500 hover:text-emerald-400" })
699
+ }
700
+ ),
681
701
  /* @__PURE__ */ jsx(
682
702
  "button",
683
703
  {
@@ -701,6 +721,9 @@ var PlaylistPanel = ({
701
721
  onFilesAdded,
702
722
  onFolderFilesAdded,
703
723
  onAddStream,
724
+ onAddMagnet,
725
+ torrentStatus,
726
+ showMagnet = true,
704
727
  onRemoveItem,
705
728
  onReorder,
706
729
  onImportM3U,
@@ -715,6 +738,9 @@ var PlaylistPanel = ({
715
738
  const folderInputRef = useRef(null);
716
739
  const m3uInputRef = useRef(null);
717
740
  const [streamUrl, setStreamUrl] = useState("");
741
+ const [magnetUri, setMagnetUri] = useState("");
742
+ const [showMagnetInput, setShowMagnetInput] = useState(false);
743
+ const [magnetError, setMagnetError] = useState(null);
718
744
  const [sortKey, setSortKey] = useState("");
719
745
  const handleStreamUrlSubmit = (e) => {
720
746
  e.preventDefault();
@@ -723,6 +749,21 @@ var PlaylistPanel = ({
723
749
  setStreamUrl("");
724
750
  }
725
751
  };
752
+ const handleMagnetSubmit = async (e) => {
753
+ e.preventDefault();
754
+ const uri = magnetUri.trim();
755
+ if (!uri) return;
756
+ setMagnetError(null);
757
+ try {
758
+ const added = await onAddMagnet(uri);
759
+ if (added) {
760
+ setMagnetUri("");
761
+ setShowMagnetInput(false);
762
+ }
763
+ } catch (err) {
764
+ setMagnetError(err instanceof Error ? err.message : "Failed to load magnet link");
765
+ }
766
+ };
726
767
  const handleFolderSelect = (e) => {
727
768
  const files = Array.from(e.target.files ?? []).filter((f) => VIDEO_EXTENSIONS_RE.test(f.name)).sort((a, b) => a.name.localeCompare(b.name, void 0, { numeric: true }));
728
769
  if (files.length > 0) onFolderFilesAdded(files);
@@ -937,8 +978,89 @@ var PlaylistPanel = ({
937
978
  className: "h-8 text-xs"
938
979
  }
939
980
  ),
940
- /* @__PURE__ */ jsx(Button, { type: "submit", size: "icon", variant: "secondary", className: "h-8 w-8 shrink-0", children: /* @__PURE__ */ jsx(Link, { className: "h-3.5 w-3.5" }) })
981
+ /* @__PURE__ */ jsx(Button, { type: "submit", size: "icon", variant: "secondary", className: "h-8 w-8 shrink-0", children: /* @__PURE__ */ jsx(Link, { className: "h-3.5 w-3.5" }) }),
982
+ showMagnet && /* @__PURE__ */ jsxs(Tooltip, { children: [
983
+ /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
984
+ Button,
985
+ {
986
+ type: "button",
987
+ size: "icon",
988
+ variant: showMagnetInput ? "default" : "outline",
989
+ className: "h-8 w-8 shrink-0",
990
+ onClick: () => {
991
+ setShowMagnetInput((v) => !v);
992
+ setMagnetError(null);
993
+ },
994
+ "aria-label": "Add magnet link",
995
+ children: /* @__PURE__ */ jsx(Link2, { className: "h-3.5 w-3.5" })
996
+ }
997
+ ) }),
998
+ /* @__PURE__ */ jsx(TooltipContent, { side: "bottom", children: /* @__PURE__ */ jsx("p", { children: "Add Magnet Link" }) })
999
+ ] })
941
1000
  ] }),
1001
+ showMagnet && showMagnetInput && /* @__PURE__ */ jsxs("form", { onSubmit: handleMagnetSubmit, className: "space-y-1.5", children: [
1002
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-1.5", children: [
1003
+ /* @__PURE__ */ jsx(
1004
+ Input,
1005
+ {
1006
+ placeholder: "magnet:?xt=urn:btih:\u2026",
1007
+ value: magnetUri,
1008
+ onChange: (e) => {
1009
+ setMagnetUri(e.target.value);
1010
+ setMagnetError(null);
1011
+ },
1012
+ className: "h-8 text-xs font-mono",
1013
+ disabled: torrentStatus.status === "loading-metadata"
1014
+ }
1015
+ ),
1016
+ /* @__PURE__ */ jsx(
1017
+ Button,
1018
+ {
1019
+ type: "submit",
1020
+ size: "icon",
1021
+ variant: "default",
1022
+ className: "h-8 w-8 shrink-0",
1023
+ disabled: !magnetUri.trim() || torrentStatus.status === "loading-metadata",
1024
+ "aria-label": "Load magnet link",
1025
+ children: torrentStatus.status === "loading-metadata" ? /* @__PURE__ */ jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx(Globe, { className: "h-3.5 w-3.5" })
1026
+ }
1027
+ )
1028
+ ] }),
1029
+ torrentStatus.status === "loading-metadata" && /* @__PURE__ */ jsxs("p", { className: "text-[11px] text-muted-foreground flex items-center gap-1", children: [
1030
+ /* @__PURE__ */ jsx(Loader2, { className: "h-3 w-3 animate-spin" }),
1031
+ "Fetching torrent info\u2026"
1032
+ ] }),
1033
+ magnetError && /* @__PURE__ */ jsxs("p", { className: "text-[11px] text-destructive flex items-center gap-1", children: [
1034
+ /* @__PURE__ */ jsx(AlertCircle, { className: "h-3 w-3 shrink-0" }),
1035
+ magnetError
1036
+ ] })
1037
+ ] }),
1038
+ showMagnet && torrentStatus.status === "ready" && (torrentStatus.progress >= 1 ? /* @__PURE__ */ jsxs("p", { className: "text-[10px] text-emerald-500 flex items-center gap-1", children: [
1039
+ /* @__PURE__ */ jsx(Download, { className: "h-3 w-3 shrink-0" }),
1040
+ "Download ready \u2014 hover a file to save it"
1041
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
1042
+ /* @__PURE__ */ jsx("div", { className: "w-full bg-muted rounded-full h-1 overflow-hidden", children: /* @__PURE__ */ jsx(
1043
+ "div",
1044
+ {
1045
+ className: "bg-emerald-500 h-1 rounded-full transition-all duration-500",
1046
+ style: { width: `${Math.round(torrentStatus.progress * 100)}%` }
1047
+ }
1048
+ ) }),
1049
+ /* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground flex items-center justify-between", children: [
1050
+ /* @__PURE__ */ jsxs("span", { children: [
1051
+ "\u2193 ",
1052
+ formatBytes(torrentStatus.downloadSpeed),
1053
+ "/s \xB7 ",
1054
+ torrentStatus.numPeers,
1055
+ " peer",
1056
+ torrentStatus.numPeers !== 1 ? "s" : ""
1057
+ ] }),
1058
+ /* @__PURE__ */ jsxs("span", { children: [
1059
+ Math.round(torrentStatus.progress * 100),
1060
+ "%"
1061
+ ] })
1062
+ ] })
1063
+ ] })),
942
1064
  playlist.length > 1 && /* @__PURE__ */ jsxs(Select, { value: sortKey, onValueChange: handleSort, children: [
943
1065
  /* @__PURE__ */ jsx(SelectTrigger, { className: "h-7 text-xs", "aria-label": "Sort playlist", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Sort by\u2026" }) }),
944
1066
  /* @__PURE__ */ jsxs(SelectContent, { children: [
@@ -963,6 +1085,7 @@ var PlaylistPanel = ({
963
1085
  item,
964
1086
  index,
965
1087
  isActive: index === currentVideoIndex,
1088
+ downloadReady: item.source === "torrent" && torrentStatus.progress >= 1,
966
1089
  onSelect: onSelectVideo,
967
1090
  onRemove: onRemoveItem
968
1091
  },
@@ -1440,6 +1563,103 @@ function SubtitleOverlay({ videoRef, activeSubtitle }) {
1440
1563
  }
1441
1564
  ) });
1442
1565
  }
1566
+ var AlertDialog = AlertDialogPrimitive.Root;
1567
+ var AlertDialogPortal = AlertDialogPrimitive.Portal;
1568
+ var AlertDialogOverlay = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
1569
+ AlertDialogPrimitive.Overlay,
1570
+ {
1571
+ className: cn(
1572
+ "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
1573
+ className
1574
+ ),
1575
+ ...props,
1576
+ ref
1577
+ }
1578
+ ));
1579
+ AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
1580
+ var AlertDialogContent = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxs(AlertDialogPortal, { children: [
1581
+ /* @__PURE__ */ jsx(AlertDialogOverlay, {}),
1582
+ /* @__PURE__ */ jsx(
1583
+ AlertDialogPrimitive.Content,
1584
+ {
1585
+ ref,
1586
+ className: cn(
1587
+ "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
1588
+ className
1589
+ ),
1590
+ ...props
1591
+ }
1592
+ )
1593
+ ] }));
1594
+ AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
1595
+ var AlertDialogHeader = ({
1596
+ className,
1597
+ ...props
1598
+ }) => /* @__PURE__ */ jsx(
1599
+ "div",
1600
+ {
1601
+ className: cn(
1602
+ "flex flex-col space-y-2 text-center sm:text-left",
1603
+ className
1604
+ ),
1605
+ ...props
1606
+ }
1607
+ );
1608
+ AlertDialogHeader.displayName = "AlertDialogHeader";
1609
+ var AlertDialogFooter = ({
1610
+ className,
1611
+ ...props
1612
+ }) => /* @__PURE__ */ jsx(
1613
+ "div",
1614
+ {
1615
+ className: cn(
1616
+ "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
1617
+ className
1618
+ ),
1619
+ ...props
1620
+ }
1621
+ );
1622
+ AlertDialogFooter.displayName = "AlertDialogFooter";
1623
+ var AlertDialogTitle = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
1624
+ AlertDialogPrimitive.Title,
1625
+ {
1626
+ ref,
1627
+ className: cn("text-lg font-semibold", className),
1628
+ ...props
1629
+ }
1630
+ ));
1631
+ AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
1632
+ var AlertDialogDescription = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
1633
+ AlertDialogPrimitive.Description,
1634
+ {
1635
+ ref,
1636
+ className: cn("text-sm text-muted-foreground", className),
1637
+ ...props
1638
+ }
1639
+ ));
1640
+ AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName;
1641
+ var AlertDialogAction = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
1642
+ AlertDialogPrimitive.Action,
1643
+ {
1644
+ ref,
1645
+ className: cn(buttonVariants(), className),
1646
+ ...props
1647
+ }
1648
+ ));
1649
+ AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;
1650
+ var AlertDialogCancel = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx(
1651
+ AlertDialogPrimitive.Cancel,
1652
+ {
1653
+ ref,
1654
+ className: cn(
1655
+ buttonVariants({ variant: "outline" }),
1656
+ "mt-2 sm:mt-0",
1657
+ className
1658
+ ),
1659
+ ...props
1660
+ }
1661
+ ));
1662
+ AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
1443
1663
  var MAX_RETRIES = 3;
1444
1664
  var LightBirdPlayer = () => {
1445
1665
  const videoRef = useRef(null);
@@ -1465,6 +1685,9 @@ var LightBirdPlayer = () => {
1465
1685
  const { metadata: videoMetadata } = useVideoInfo(videoRef, playlist.currentItem?.file ?? null);
1466
1686
  useProgressPersistence(videoRef, playlist.currentItem?.name ?? null);
1467
1687
  const { chapters, currentChapter, goToChapter } = useChapters(videoRef, playerRef);
1688
+ const magnetLinkEnabled = useBooleanFlagValue(FLAG_MAGNET_LINK, true);
1689
+ const magnet = useMagnet();
1690
+ const [disclaimerPendingUri, setDisclaimerPendingUri] = useState(null);
1468
1691
  const [shortcuts, setShortcuts] = useState(() => loadShortcuts());
1469
1692
  const [showShortcutsHelp, setShowShortcutsHelp] = useState(false);
1470
1693
  const [showShortcutsDialog, setShowShortcutsDialog] = useState(false);
@@ -1854,6 +2077,40 @@ var LightBirdPlayer = () => {
1854
2077
  startStallDetection();
1855
2078
  }
1856
2079
  }, [playlist, subtitles]);
2080
+ const handleAddMagnet = useCallback(async (uri) => {
2081
+ if (!hasAcceptedDisclaimer()) {
2082
+ setDisclaimerPendingUri(uri);
2083
+ return false;
2084
+ }
2085
+ const items = await magnet.addMagnet(uri);
2086
+ const startIndex = playlist.playlist.length;
2087
+ items.forEach((item) => playlist.appendItem(item));
2088
+ if (playlist.currentIndex === null && items.length > 0) {
2089
+ playlist.selectItem(startIndex);
2090
+ if (videoRef.current) videoRef.current.src = items[0].url;
2091
+ subtitles.reset();
2092
+ setAudioTracks([]);
2093
+ setActiveAudioTrack("0");
2094
+ isStreamRef.current = true;
2095
+ startStallDetection();
2096
+ }
2097
+ if (items.length > 1) {
2098
+ toast2({ title: `${items.length} videos added from torrent`, description: magnet.torrentStatus.torrentName });
2099
+ }
2100
+ return true;
2101
+ }, [magnet, playlist, subtitles, toast2]);
2102
+ const handleDisclaimerAccepted = useCallback(async () => {
2103
+ acceptDisclaimer();
2104
+ const uri = disclaimerPendingUri;
2105
+ setDisclaimerPendingUri(null);
2106
+ if (uri) {
2107
+ try {
2108
+ await handleAddMagnet(uri);
2109
+ } catch (err) {
2110
+ toast2({ title: "Magnet link failed", description: err instanceof Error ? err.message : "Unknown error", variant: "destructive" });
2111
+ }
2112
+ }
2113
+ }, [disclaimerPendingUri, handleAddMagnet, toast2]);
1857
2114
  const handleSubtitleChange = useCallback(async (id) => {
1858
2115
  subtitles.switchSubtitle(id);
1859
2116
  if (playerRef.current) {
@@ -2102,6 +2359,9 @@ var LightBirdPlayer = () => {
2102
2359
  onFilesAdded: handleFileChange,
2103
2360
  onFolderFilesAdded: handleFolderFilesAdded,
2104
2361
  onAddStream: handleAddStream,
2362
+ onAddMagnet: handleAddMagnet,
2363
+ torrentStatus: magnet.torrentStatus,
2364
+ showMagnet: magnetLinkEnabled,
2105
2365
  onRemoveItem: handleRemoveItem,
2106
2366
  onReorder: handleReorder,
2107
2367
  onImportM3U: handleImportM3U,
@@ -2112,7 +2372,29 @@ var LightBirdPlayer = () => {
2112
2372
  onTogglePin: () => setPlaylistPinned((v) => !v),
2113
2373
  onSizeChange: setPlaylistSize
2114
2374
  }
2115
- )
2375
+ ),
2376
+ /* @__PURE__ */ jsx(AlertDialog, { open: magnetLinkEnabled && disclaimerPendingUri !== null, onOpenChange: (open) => {
2377
+ if (!open) setDisclaimerPendingUri(null);
2378
+ }, children: /* @__PURE__ */ jsxs(AlertDialogContent, { children: [
2379
+ /* @__PURE__ */ jsxs(AlertDialogHeader, { children: [
2380
+ /* @__PURE__ */ jsx(AlertDialogTitle, { children: "Magnet Link Streaming" }),
2381
+ /* @__PURE__ */ jsx(AlertDialogDescription, { asChild: true, children: /* @__PURE__ */ jsxs("div", { className: "space-y-2 text-sm text-muted-foreground", children: [
2382
+ /* @__PURE__ */ jsxs("p", { children: [
2383
+ "LightBird streams content via ",
2384
+ /* @__PURE__ */ jsx("strong", { className: "text-foreground", children: "BitTorrent" }),
2385
+ " (WebRTC/WebSocket) \u2014 the same technology used by applications like VLC and qBittorrent."
2386
+ ] }),
2387
+ /* @__PURE__ */ jsxs("p", { children: [
2388
+ /* @__PURE__ */ jsx("strong", { className: "text-foreground", children: "You are responsible" }),
2389
+ " for ensuring you have the legal right to access any content you stream. LightBird does not host, index, or endorse any content."
2390
+ ] })
2391
+ ] }) })
2392
+ ] }),
2393
+ /* @__PURE__ */ jsxs(AlertDialogFooter, { children: [
2394
+ /* @__PURE__ */ jsx(AlertDialogCancel, { onClick: () => setDisclaimerPendingUri(null), children: "Cancel" }),
2395
+ /* @__PURE__ */ jsx(AlertDialogAction, { onClick: handleDisclaimerAccepted, children: "I Understand \u2014 Continue" })
2396
+ ] })
2397
+ ] }) })
2116
2398
  ] });
2117
2399
  };
2118
2400
  var lightbird_player_default = LightBirdPlayer;
@@ -2243,5 +2525,14 @@ function Toaster() {
2243
2525
  /* @__PURE__ */ jsx(ToastViewport, {})
2244
2526
  ] });
2245
2527
  }
2528
+ var started = false;
2529
+ function FeatureFlagsProvider({ children }) {
2530
+ useEffect(() => {
2531
+ if (started) return;
2532
+ started = true;
2533
+ initFeatureFlags().catch(console.error);
2534
+ }, []);
2535
+ return /* @__PURE__ */ jsx(OpenFeatureProvider, { suspendUntilReady: false, suspendWhileReconciling: false, children });
2536
+ }
2246
2537
 
2247
- export { lightbird_player_default as LightBirdPlayer, player_controls_default as PlayerControls, PlayerErrorBoundary, PlayerErrorDisplay, playlist_panel_default as PlaylistPanel, ShortcutSettingsDialog, SubtitleOverlay, Toaster, VideoInfoPanel, VideoOverlay };
2538
+ export { FeatureFlagsProvider, lightbird_player_default as LightBirdPlayer, player_controls_default as PlayerControls, PlayerErrorBoundary, PlayerErrorDisplay, playlist_panel_default as PlaylistPanel, ShortcutSettingsDialog, SubtitleOverlay, Toaster, VideoInfoPanel, VideoOverlay };
package/dist/styles.css CHANGED
@@ -1 +1 @@
1
- *,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.invisible{visibility:hidden}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{inset:0}.bottom-0{bottom:0}.bottom-4{bottom:1rem}.left-0{left:0}.left-2{left:.5rem}.left-\[50\%\]{left:50%}.right-0{right:0}.right-2{right:.5rem}.right-4{right:1rem}.top-0{top:0}.top-2{top:.5rem}.top-4{top:1rem}.top-\[50\%\]{top:50%}.z-10{z-index:10}.z-40{z-index:40}.z-50{z-index:50}.z-\[100\]{z-index:100}.-mx-1{margin-left:-.25rem;margin-right:-.25rem}.my-1{margin-top:.25rem;margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.ml-4{margin-left:1rem}.ml-auto{margin-left:auto}.mr-1{margin-right:.25rem}.mr-1\.5{margin-right:.375rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.block{display:block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.aspect-square{aspect-ratio:1/1}.h-10{height:2.5rem}.h-11{height:2.75rem}.h-12{height:3rem}.h-16{height:4rem}.h-2{height:.5rem}.h-2\.5{height:.625rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-\[var\(--radix-select-trigger-height\)\]{height:var(--radix-select-trigger-height)}.h-full{height:100%}.h-px{height:1px}.h-screen{height:100vh}.max-h-48{max-height:12rem}.max-h-64{max-height:16rem}.max-h-80{max-height:20rem}.max-h-96{max-height:24rem}.max-h-screen{max-height:100vh}.w-10{width:2.5rem}.w-11{width:2.75rem}.w-12{width:3rem}.w-16{width:4rem}.w-2{width:.5rem}.w-2\.5{width:.625rem}.w-24{width:6rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-4{width:1rem}.w-40{width:10rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-60{width:15rem}.w-64{width:16rem}.w-7{width:1.75rem}.w-72{width:18rem}.w-8{width:2rem}.w-80{width:20rem}.w-9{width:2.25rem}.w-96{width:24rem}.w-full{width:100%}.min-w-0{min-width:0}.min-w-\[8rem\]{min-width:8rem}.min-w-\[var\(--radix-select-trigger-width\)\]{min-width:var(--radix-select-trigger-width)}.max-w-lg{max-width:32rem}.max-w-md{max-width:28rem}.max-w-sm{max-width:24rem}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.translate-x-\[-50\%\]{--tw-translate-x:-50%}.translate-x-\[-50\%\],.translate-y-0{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-0{--tw-translate-y:0px}.translate-y-\[-50\%\]{--tw-translate-y:-50%}.transform,.translate-y-\[-50\%\]{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(1turn)}}.animate-spin{animation:spin 1s linear infinite}.cursor-default{cursor:default}.cursor-grab{cursor:grab}.touch-none{touch-action:none}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.5rem*var(--tw-space-x-reverse));margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.space-y-0\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.125rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.125rem*var(--tw-space-y-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem*var(--tw-space-y-reverse))}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.375rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem*var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem*var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.overscroll-contain{overscroll-behavior:contain}.truncate{overflow:hidden;text-overflow:ellipsis}.truncate,.whitespace-nowrap{white-space:nowrap}.whitespace-pre-line{white-space:pre-line}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-\[inherit\]{border-radius:inherit}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:var(--radius)}.rounded-md{border-radius:calc(var(--radius) - 2px)}.rounded-sm{border-radius:calc(var(--radius) - 4px)}.border{border-width:1px}.border-2{border-width:2px}.border-4{border-width:4px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-t{border-top-width:1px}.border-border{border-color:hsl(var(--border))}.border-destructive{border-color:hsl(var(--destructive))}.border-input{border-color:hsl(var(--input))}.border-muted-foreground{border-color:hsl(var(--muted-foreground))}.border-primary{border-color:hsl(var(--primary))}.border-white\/10{border-color:hsla(0,0%,100%,.1)}.border-white\/30{border-color:hsla(0,0%,100%,.3)}.border-l-transparent{border-left-color:transparent}.border-t-transparent{border-top-color:transparent}.bg-accent{background-color:hsl(var(--accent))}.bg-background{background-color:hsl(var(--background))}.bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity,1))}.bg-black\/80{background-color:rgba(0,0,0,.8)}.bg-black\/85{background-color:rgba(0,0,0,.85)}.bg-black\/90{background-color:rgba(0,0,0,.9)}.bg-border{background-color:hsl(var(--border))}.bg-card{background-color:hsl(var(--card))}.bg-destructive{background-color:hsl(var(--destructive))}.bg-gray-700{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity,1))}.bg-muted{background-color:hsl(var(--muted))}.bg-popover{background-color:hsl(var(--popover))}.bg-primary{background-color:hsl(var(--primary))}.bg-primary\/10{background-color:hsl(var(--primary)/.1)}.bg-primary\/20{background-color:hsl(var(--primary)/.2)}.bg-secondary{background-color:hsl(var(--secondary))}.bg-transparent{background-color:transparent}.bg-opacity-70{--tw-bg-opacity:0.7}.bg-gradient-to-t{background-image:linear-gradient(to top,var(--tw-gradient-stops))}.from-black\/70{--tw-gradient-from:rgba(0,0,0,.7) var(--tw-gradient-from-position);--tw-gradient-to:transparent var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.to-transparent{--tw-gradient-to:transparent var(--tw-gradient-to-position)}.fill-current{fill:currentColor}.object-contain{-o-object-fit:contain;object-fit:contain}.p-0{padding:0}.p-0\.5{padding:.125rem}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.p-\[1px\]{padding:1px}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-8{padding-left:2rem;padding-right:2rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-10{padding-top:2.5rem;padding-bottom:2.5rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.pl-1{padding-left:.25rem}.pl-8{padding-left:2rem}.pr-1{padding-right:.25rem}.pr-2{padding-right:.5rem}.pr-3{padding-right:.75rem}.pr-8{padding-right:2rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-\[10px\]{font-size:10px}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.leading-none{line-height:1}.tracking-tight{letter-spacing:-.025em}.tracking-widest{letter-spacing:.1em}.text-current{color:currentColor}.text-destructive-foreground{color:hsl(var(--destructive-foreground))}.text-foreground{color:hsl(var(--foreground))}.text-foreground\/50{color:hsl(var(--foreground)/.5)}.text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity,1))}.text-muted-foreground{color:hsl(var(--muted-foreground))}.text-popover-foreground{color:hsl(var(--popover-foreground))}.text-primary{color:hsl(var(--primary))}.text-primary-foreground{color:hsl(var(--primary-foreground))}.text-red-500{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity,1))}.text-secondary-foreground{color:hsl(var(--secondary-foreground))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.text-white\/60{color:hsla(0,0%,100%,.6)}.text-white\/80{color:hsla(0,0%,100%,.8)}.underline{text-decoration-line:underline}.underline-offset-4{text-underline-offset:4px}.opacity-0{opacity:0}.opacity-50{opacity:.5}.opacity-70{opacity:.7}.opacity-90{opacity:.9}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow-lg,.shadow-md{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color)}.outline-none{outline:2px solid transparent;outline-offset:2px}.outline{outline-style:solid}.ring-2{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-background{--tw-ring-color:hsl(var(--background))}.ring-offset-background{--tw-ring-offset-color:hsl(var(--background))}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[width\]{transition-property:width;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}.duration-500{transition-duration:.5s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}@keyframes enter{0%{opacity:var(--tw-enter-opacity,1);transform:translate3d(var(--tw-enter-translate-x,0),var(--tw-enter-translate-y,0),0) scale3d(var(--tw-enter-scale,1),var(--tw-enter-scale,1),var(--tw-enter-scale,1)) rotate(var(--tw-enter-rotate,0))}}@keyframes exit{to{opacity:var(--tw-exit-opacity,1);transform:translate3d(var(--tw-exit-translate-x,0),var(--tw-exit-translate-y,0),0) scale3d(var(--tw-exit-scale,1),var(--tw-exit-scale,1),var(--tw-exit-scale,1)) rotate(var(--tw-exit-rotate,0))}}.animate-in{animation-name:enter;animation-duration:.15s;--tw-enter-opacity:initial;--tw-enter-scale:initial;--tw-enter-rotate:initial;--tw-enter-translate-x:initial;--tw-enter-translate-y:initial}.fade-in-0{--tw-enter-opacity:0}.zoom-in-95{--tw-enter-scale:.95}.duration-200{animation-duration:.2s}.duration-300{animation-duration:.3s}.duration-500{animation-duration:.5s}.ease-in-out{animation-timing-function:cubic-bezier(.4,0,.2,1)}.paused{animation-play-state:paused}:root{--background:0 0% 13%;--foreground:0 0% 87%;--card:0 0% 10%;--card-foreground:0 0% 87%;--popover:0 0% 8%;--popover-foreground:0 0% 87%;--primary:207 100% 40%;--primary-foreground:0 0% 100%;--secondary:0 0% 20%;--secondary-foreground:0 0% 98%;--muted:0 0% 20%;--muted-foreground:0 0% 64%;--accent:207 100% 50%;--accent-foreground:0 0% 100%;--destructive:0 63% 31%;--destructive-foreground:0 0% 98%;--border:0 0% 20%;--input:0 0% 20%;--ring:207 100% 40%;--radius:0.5rem}.file\:border-0::file-selector-button{border-width:0}.file\:bg-transparent::file-selector-button{background-color:transparent}.file\:text-sm::file-selector-button{font-size:.875rem;line-height:1.25rem}.file\:font-medium::file-selector-button{font-weight:500}.file\:text-foreground::file-selector-button{color:hsl(var(--foreground))}.placeholder\:text-muted-foreground::-moz-placeholder{color:hsl(var(--muted-foreground))}.placeholder\:text-muted-foreground::placeholder{color:hsl(var(--muted-foreground))}.last\:border-0:last-child{border-width:0}.hover\:bg-accent:hover{background-color:hsl(var(--accent))}.hover\:bg-destructive\/90:hover{background-color:hsl(var(--destructive)/.9)}.hover\:bg-muted:hover{background-color:hsl(var(--muted))}.hover\:bg-primary\/90:hover{background-color:hsl(var(--primary)/.9)}.hover\:bg-secondary:hover{background-color:hsl(var(--secondary))}.hover\:bg-secondary\/80:hover{background-color:hsl(var(--secondary)/.8)}.hover\:bg-white\/10:hover{background-color:hsla(0,0%,100%,.1)}.hover\:text-accent-foreground:hover{color:hsl(var(--accent-foreground))}.hover\:text-destructive:hover{color:hsl(var(--destructive))}.hover\:text-foreground:hover{color:hsl(var(--foreground))}.hover\:text-gray-300:hover{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity,1))}.hover\:text-white:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-100:hover{opacity:1}.focus\:bg-accent:focus{background-color:hsl(var(--accent))}.focus\:text-accent-foreground:focus{color:hsl(var(--accent-foreground))}.focus\:opacity-100:focus{opacity:1}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-ring:focus{--tw-ring-color:hsl(var(--ring))}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px}.focus-visible\:outline-none:focus-visible{outline:2px solid transparent;outline-offset:2px}.focus-visible\:ring-2:focus-visible{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus-visible\:ring-ring:focus-visible{--tw-ring-color:hsl(var(--ring))}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width:2px}.active\:cursor-grabbing:active{cursor:grabbing}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}.group:hover .group-hover\:-translate-y-24{--tw-translate-y:-6rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group:hover .group-hover\:opacity-100{opacity:1}.group.destructive .group-\[\.destructive\]\:border-muted\/40{border-color:hsl(var(--muted)/.4)}.group.destructive .group-\[\.destructive\]\:text-red-300{--tw-text-opacity:1;color:rgb(252 165 165/var(--tw-text-opacity,1))}.group.destructive .group-\[\.destructive\]\:hover\:border-destructive\/30:hover{border-color:hsl(var(--destructive)/.3)}.group.destructive .group-\[\.destructive\]\:hover\:bg-destructive:hover{background-color:hsl(var(--destructive))}.group.destructive .group-\[\.destructive\]\:hover\:text-destructive-foreground:hover{color:hsl(var(--destructive-foreground))}.group.destructive .group-\[\.destructive\]\:hover\:text-red-50:hover{--tw-text-opacity:1;color:rgb(254 242 242/var(--tw-text-opacity,1))}.group.destructive .group-\[\.destructive\]\:focus\:ring-destructive:focus{--tw-ring-color:hsl(var(--destructive))}.group.destructive .group-\[\.destructive\]\:focus\:ring-red-400:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(248 113 113/var(--tw-ring-opacity,1))}.group.destructive .group-\[\.destructive\]\:focus\:ring-offset-red-600:focus{--tw-ring-offset-color:#dc2626}.peer:disabled~.peer-disabled\:cursor-not-allowed{cursor:not-allowed}.peer:disabled~.peer-disabled\:opacity-70{opacity:.7}.data-\[disabled\]\:pointer-events-none[data-disabled]{pointer-events:none}.data-\[side\=bottom\]\:translate-y-1[data-side=bottom]{--tw-translate-y:0.25rem}.data-\[side\=bottom\]\:translate-y-1[data-side=bottom],.data-\[side\=left\]\:-translate-x-1[data-side=left]{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.data-\[side\=left\]\:-translate-x-1[data-side=left]{--tw-translate-x:-0.25rem}.data-\[side\=right\]\:translate-x-1[data-side=right]{--tw-translate-x:0.25rem}.data-\[side\=right\]\:translate-x-1[data-side=right],.data-\[side\=top\]\:-translate-y-1[data-side=top]{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.data-\[side\=top\]\:-translate-y-1[data-side=top]{--tw-translate-y:-0.25rem}.data-\[swipe\=cancel\]\:translate-x-0[data-swipe=cancel]{--tw-translate-x:0px}.data-\[swipe\=cancel\]\:translate-x-0[data-swipe=cancel],.data-\[swipe\=end\]\:translate-x-\[var\(--radix-toast-swipe-end-x\)\][data-swipe=end]{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.data-\[swipe\=end\]\:translate-x-\[var\(--radix-toast-swipe-end-x\)\][data-swipe=end]{--tw-translate-x:var(--radix-toast-swipe-end-x)}.data-\[swipe\=move\]\:translate-x-\[var\(--radix-toast-swipe-move-x\)\][data-swipe=move]{--tw-translate-x:var(--radix-toast-swipe-move-x);transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.data-\[state\=open\]\:bg-accent[data-state=open]{background-color:hsl(var(--accent))}.data-\[active\=true\]\:text-primary[data-active=true]{color:hsl(var(--primary))}.data-\[state\=open\]\:text-muted-foreground[data-state=open]{color:hsl(var(--muted-foreground))}.data-\[disabled\]\:opacity-50[data-disabled]{opacity:.5}.data-\[swipe\=move\]\:transition-none[data-swipe=move]{transition-property:none}.data-\[state\=open\]\:animate-in[data-state=open]{animation-name:enter;animation-duration:.15s;--tw-enter-opacity:initial;--tw-enter-scale:initial;--tw-enter-rotate:initial;--tw-enter-translate-x:initial;--tw-enter-translate-y:initial}.data-\[state\=closed\]\:animate-out[data-state=closed],.data-\[swipe\=end\]\:animate-out[data-swipe=end]{animation-name:exit;animation-duration:.15s;--tw-exit-opacity:initial;--tw-exit-scale:initial;--tw-exit-rotate:initial;--tw-exit-translate-x:initial;--tw-exit-translate-y:initial}.data-\[state\=closed\]\:fade-out-0[data-state=closed]{--tw-exit-opacity:0}.data-\[state\=closed\]\:fade-out-80[data-state=closed]{--tw-exit-opacity:0.8}.data-\[state\=open\]\:fade-in-0[data-state=open]{--tw-enter-opacity:0}.data-\[state\=closed\]\:zoom-out-95[data-state=closed]{--tw-exit-scale:.95}.data-\[state\=open\]\:zoom-in-95[data-state=open]{--tw-enter-scale:.95}.data-\[side\=bottom\]\:slide-in-from-top-2[data-side=bottom]{--tw-enter-translate-y:-0.5rem}.data-\[side\=left\]\:slide-in-from-right-2[data-side=left]{--tw-enter-translate-x:0.5rem}.data-\[side\=right\]\:slide-in-from-left-2[data-side=right]{--tw-enter-translate-x:-0.5rem}.data-\[side\=top\]\:slide-in-from-bottom-2[data-side=top]{--tw-enter-translate-y:0.5rem}.data-\[state\=closed\]\:slide-out-to-left-1\/2[data-state=closed]{--tw-exit-translate-x:-50%}.data-\[state\=closed\]\:slide-out-to-right-full[data-state=closed]{--tw-exit-translate-x:100%}.data-\[state\=closed\]\:slide-out-to-top-\[48\%\][data-state=closed]{--tw-exit-translate-y:-48%}.data-\[state\=open\]\:slide-in-from-left-1\/2[data-state=open]{--tw-enter-translate-x:-50%}.data-\[state\=open\]\:slide-in-from-top-\[48\%\][data-state=open]{--tw-enter-translate-y:-48%}.data-\[state\=open\]\:slide-in-from-top-full[data-state=open]{--tw-enter-translate-y:-100%}@media (min-width:640px){.sm\:bottom-0{bottom:0}.sm\:right-0{right:0}.sm\:top-auto{top:auto}.sm\:flex-row{flex-direction:row}.sm\:flex-col{flex-direction:column}.sm\:justify-end{justify-content:flex-end}.sm\:space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.5rem*var(--tw-space-x-reverse));margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)))}.sm\:rounded-lg{border-radius:var(--radius)}.sm\:text-left{text-align:left}.data-\[state\=open\]\:sm\:slide-in-from-bottom-full[data-state=open]{--tw-enter-translate-y:100%}}@media (min-width:768px){.md\:max-w-\[420px\]{max-width:420px}.md\:text-sm{font-size:.875rem;line-height:1.25rem}}.\[\&\>span\]\:line-clamp-1>span{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:1}.\[\&_svg\]\:pointer-events-none svg{pointer-events:none}.\[\&_svg\]\:size-4 svg{width:1rem;height:1rem}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0}
1
+ *,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.invisible{visibility:hidden}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{inset:0}.bottom-0{bottom:0}.bottom-4{bottom:1rem}.left-0{left:0}.left-2{left:.5rem}.left-\[50\%\]{left:50%}.right-0{right:0}.right-2{right:.5rem}.right-4{right:1rem}.top-0{top:0}.top-2{top:.5rem}.top-4{top:1rem}.top-\[50\%\]{top:50%}.z-10{z-index:10}.z-40{z-index:40}.z-50{z-index:50}.z-\[100\]{z-index:100}.-mx-1{margin-left:-.25rem;margin-right:-.25rem}.my-1{margin-top:.25rem;margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.ml-4{margin-left:1rem}.ml-auto{margin-left:auto}.mr-1{margin-right:.25rem}.mr-1\.5{margin-right:.375rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.block{display:block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.aspect-square{aspect-ratio:1/1}.h-1{height:.25rem}.h-10{height:2.5rem}.h-11{height:2.75rem}.h-12{height:3rem}.h-16{height:4rem}.h-2{height:.5rem}.h-2\.5{height:.625rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-\[var\(--radix-select-trigger-height\)\]{height:var(--radix-select-trigger-height)}.h-full{height:100%}.h-px{height:1px}.h-screen{height:100vh}.max-h-48{max-height:12rem}.max-h-64{max-height:16rem}.max-h-80{max-height:20rem}.max-h-96{max-height:24rem}.max-h-screen{max-height:100vh}.w-10{width:2.5rem}.w-11{width:2.75rem}.w-12{width:3rem}.w-16{width:4rem}.w-2{width:.5rem}.w-2\.5{width:.625rem}.w-24{width:6rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-4{width:1rem}.w-40{width:10rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-60{width:15rem}.w-64{width:16rem}.w-7{width:1.75rem}.w-72{width:18rem}.w-8{width:2rem}.w-80{width:20rem}.w-9{width:2.25rem}.w-96{width:24rem}.w-full{width:100%}.min-w-0{min-width:0}.min-w-\[8rem\]{min-width:8rem}.min-w-\[var\(--radix-select-trigger-width\)\]{min-width:var(--radix-select-trigger-width)}.max-w-lg{max-width:32rem}.max-w-md{max-width:28rem}.max-w-sm{max-width:24rem}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.grow{flex-grow:1}.translate-x-\[-50\%\]{--tw-translate-x:-50%}.translate-x-\[-50\%\],.translate-y-0{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-0{--tw-translate-y:0px}.translate-y-\[-50\%\]{--tw-translate-y:-50%}.transform,.translate-y-\[-50\%\]{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(1turn)}}.animate-spin{animation:spin 1s linear infinite}.cursor-default{cursor:default}.cursor-grab{cursor:grab}.touch-none{touch-action:none}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.5rem*var(--tw-space-x-reverse));margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.space-y-0\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.125rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.125rem*var(--tw-space-y-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem*var(--tw-space-y-reverse))}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.375rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem*var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem*var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem*var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.overscroll-contain{overscroll-behavior:contain}.truncate{overflow:hidden;text-overflow:ellipsis}.truncate,.whitespace-nowrap{white-space:nowrap}.whitespace-pre-line{white-space:pre-line}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-\[inherit\]{border-radius:inherit}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:var(--radius)}.rounded-md{border-radius:calc(var(--radius) - 2px)}.rounded-sm{border-radius:calc(var(--radius) - 4px)}.border{border-width:1px}.border-2{border-width:2px}.border-4{border-width:4px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-t{border-top-width:1px}.border-border{border-color:hsl(var(--border))}.border-destructive{border-color:hsl(var(--destructive))}.border-input{border-color:hsl(var(--input))}.border-muted-foreground{border-color:hsl(var(--muted-foreground))}.border-primary{border-color:hsl(var(--primary))}.border-white\/10{border-color:hsla(0,0%,100%,.1)}.border-white\/30{border-color:hsla(0,0%,100%,.3)}.border-l-transparent{border-left-color:transparent}.border-t-transparent{border-top-color:transparent}.bg-accent{background-color:hsl(var(--accent))}.bg-background{background-color:hsl(var(--background))}.bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity,1))}.bg-black\/80{background-color:rgba(0,0,0,.8)}.bg-black\/85{background-color:rgba(0,0,0,.85)}.bg-black\/90{background-color:rgba(0,0,0,.9)}.bg-border{background-color:hsl(var(--border))}.bg-card{background-color:hsl(var(--card))}.bg-destructive{background-color:hsl(var(--destructive))}.bg-emerald-500{--tw-bg-opacity:1;background-color:rgb(16 185 129/var(--tw-bg-opacity,1))}.bg-gray-700{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity,1))}.bg-muted{background-color:hsl(var(--muted))}.bg-popover{background-color:hsl(var(--popover))}.bg-primary{background-color:hsl(var(--primary))}.bg-primary\/10{background-color:hsl(var(--primary)/.1)}.bg-primary\/20{background-color:hsl(var(--primary)/.2)}.bg-secondary{background-color:hsl(var(--secondary))}.bg-transparent{background-color:transparent}.bg-opacity-70{--tw-bg-opacity:0.7}.bg-gradient-to-t{background-image:linear-gradient(to top,var(--tw-gradient-stops))}.from-black\/70{--tw-gradient-from:rgba(0,0,0,.7) var(--tw-gradient-from-position);--tw-gradient-to:transparent var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.to-transparent{--tw-gradient-to:transparent var(--tw-gradient-to-position)}.fill-current{fill:currentColor}.object-contain{-o-object-fit:contain;object-fit:contain}.p-0{padding:0}.p-0\.5{padding:.125rem}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.p-\[1px\]{padding:1px}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-8{padding-left:2rem;padding-right:2rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-10{padding-top:2.5rem;padding-bottom:2.5rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.pl-1{padding-left:.25rem}.pl-8{padding-left:2rem}.pr-1{padding-right:.25rem}.pr-2{padding-right:.5rem}.pr-3{padding-right:.75rem}.pr-8{padding-right:2rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.leading-none{line-height:1}.tracking-tight{letter-spacing:-.025em}.tracking-widest{letter-spacing:.1em}.text-current{color:currentColor}.text-destructive{color:hsl(var(--destructive))}.text-destructive-foreground{color:hsl(var(--destructive-foreground))}.text-emerald-500{--tw-text-opacity:1;color:rgb(16 185 129/var(--tw-text-opacity,1))}.text-foreground{color:hsl(var(--foreground))}.text-foreground\/50{color:hsl(var(--foreground)/.5)}.text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity,1))}.text-muted-foreground{color:hsl(var(--muted-foreground))}.text-popover-foreground{color:hsl(var(--popover-foreground))}.text-primary{color:hsl(var(--primary))}.text-primary-foreground{color:hsl(var(--primary-foreground))}.text-red-500{--tw-text-opacity:1;color:rgb(239 68 68/var(--tw-text-opacity,1))}.text-secondary-foreground{color:hsl(var(--secondary-foreground))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.text-white\/60{color:hsla(0,0%,100%,.6)}.text-white\/80{color:hsla(0,0%,100%,.8)}.underline{text-decoration-line:underline}.underline-offset-4{text-underline-offset:4px}.opacity-0{opacity:0}.opacity-50{opacity:.5}.opacity-70{opacity:.7}.opacity-90{opacity:.9}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow-lg,.shadow-md{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color)}.outline-none{outline:2px solid transparent;outline-offset:2px}.outline{outline-style:solid}.ring-2{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-background{--tw-ring-color:hsl(var(--background))}.ring-offset-background{--tw-ring-offset-color:hsl(var(--background))}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-\[width\]{transition-property:width;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}.duration-500{transition-duration:.5s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}@keyframes enter{0%{opacity:var(--tw-enter-opacity,1);transform:translate3d(var(--tw-enter-translate-x,0),var(--tw-enter-translate-y,0),0) scale3d(var(--tw-enter-scale,1),var(--tw-enter-scale,1),var(--tw-enter-scale,1)) rotate(var(--tw-enter-rotate,0))}}@keyframes exit{to{opacity:var(--tw-exit-opacity,1);transform:translate3d(var(--tw-exit-translate-x,0),var(--tw-exit-translate-y,0),0) scale3d(var(--tw-exit-scale,1),var(--tw-exit-scale,1),var(--tw-exit-scale,1)) rotate(var(--tw-exit-rotate,0))}}.animate-in{animation-name:enter;animation-duration:.15s;--tw-enter-opacity:initial;--tw-enter-scale:initial;--tw-enter-rotate:initial;--tw-enter-translate-x:initial;--tw-enter-translate-y:initial}.fade-in-0{--tw-enter-opacity:0}.zoom-in-95{--tw-enter-scale:.95}.duration-200{animation-duration:.2s}.duration-300{animation-duration:.3s}.duration-500{animation-duration:.5s}.ease-in-out{animation-timing-function:cubic-bezier(.4,0,.2,1)}.paused{animation-play-state:paused}:root{--background:0 0% 13%;--foreground:0 0% 87%;--card:0 0% 10%;--card-foreground:0 0% 87%;--popover:0 0% 8%;--popover-foreground:0 0% 87%;--primary:207 100% 40%;--primary-foreground:0 0% 100%;--secondary:0 0% 20%;--secondary-foreground:0 0% 98%;--muted:0 0% 20%;--muted-foreground:0 0% 64%;--accent:207 100% 50%;--accent-foreground:0 0% 100%;--destructive:0 63% 31%;--destructive-foreground:0 0% 98%;--border:0 0% 20%;--input:0 0% 20%;--ring:207 100% 40%;--radius:0.5rem}.file\:border-0::file-selector-button{border-width:0}.file\:bg-transparent::file-selector-button{background-color:transparent}.file\:text-sm::file-selector-button{font-size:.875rem;line-height:1.25rem}.file\:font-medium::file-selector-button{font-weight:500}.file\:text-foreground::file-selector-button{color:hsl(var(--foreground))}.placeholder\:text-muted-foreground::-moz-placeholder{color:hsl(var(--muted-foreground))}.placeholder\:text-muted-foreground::placeholder{color:hsl(var(--muted-foreground))}.last\:border-0:last-child{border-width:0}.hover\:bg-accent:hover{background-color:hsl(var(--accent))}.hover\:bg-destructive\/90:hover{background-color:hsl(var(--destructive)/.9)}.hover\:bg-muted:hover{background-color:hsl(var(--muted))}.hover\:bg-primary\/90:hover{background-color:hsl(var(--primary)/.9)}.hover\:bg-secondary:hover{background-color:hsl(var(--secondary))}.hover\:bg-secondary\/80:hover{background-color:hsl(var(--secondary)/.8)}.hover\:bg-white\/10:hover{background-color:hsla(0,0%,100%,.1)}.hover\:text-accent-foreground:hover{color:hsl(var(--accent-foreground))}.hover\:text-destructive:hover{color:hsl(var(--destructive))}.hover\:text-emerald-400:hover{--tw-text-opacity:1;color:rgb(52 211 153/var(--tw-text-opacity,1))}.hover\:text-foreground:hover{color:hsl(var(--foreground))}.hover\:text-gray-300:hover{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity,1))}.hover\:text-white:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-100:hover{opacity:1}.focus\:bg-accent:focus{background-color:hsl(var(--accent))}.focus\:text-accent-foreground:focus{color:hsl(var(--accent-foreground))}.focus\:opacity-100:focus{opacity:1}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-ring:focus{--tw-ring-color:hsl(var(--ring))}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px}.focus-visible\:outline-none:focus-visible{outline:2px solid transparent;outline-offset:2px}.focus-visible\:ring-2:focus-visible{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus-visible\:ring-ring:focus-visible{--tw-ring-color:hsl(var(--ring))}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width:2px}.active\:cursor-grabbing:active{cursor:grabbing}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-50:disabled{opacity:.5}.group:hover .group-hover\:-translate-y-24{--tw-translate-y:-6rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group:hover .group-hover\:opacity-100{opacity:1}.group.destructive .group-\[\.destructive\]\:border-muted\/40{border-color:hsl(var(--muted)/.4)}.group.destructive .group-\[\.destructive\]\:text-red-300{--tw-text-opacity:1;color:rgb(252 165 165/var(--tw-text-opacity,1))}.group.destructive .group-\[\.destructive\]\:hover\:border-destructive\/30:hover{border-color:hsl(var(--destructive)/.3)}.group.destructive .group-\[\.destructive\]\:hover\:bg-destructive:hover{background-color:hsl(var(--destructive))}.group.destructive .group-\[\.destructive\]\:hover\:text-destructive-foreground:hover{color:hsl(var(--destructive-foreground))}.group.destructive .group-\[\.destructive\]\:hover\:text-red-50:hover{--tw-text-opacity:1;color:rgb(254 242 242/var(--tw-text-opacity,1))}.group.destructive .group-\[\.destructive\]\:focus\:ring-destructive:focus{--tw-ring-color:hsl(var(--destructive))}.group.destructive .group-\[\.destructive\]\:focus\:ring-red-400:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(248 113 113/var(--tw-ring-opacity,1))}.group.destructive .group-\[\.destructive\]\:focus\:ring-offset-red-600:focus{--tw-ring-offset-color:#dc2626}.peer:disabled~.peer-disabled\:cursor-not-allowed{cursor:not-allowed}.peer:disabled~.peer-disabled\:opacity-70{opacity:.7}.data-\[disabled\]\:pointer-events-none[data-disabled]{pointer-events:none}.data-\[side\=bottom\]\:translate-y-1[data-side=bottom]{--tw-translate-y:0.25rem}.data-\[side\=bottom\]\:translate-y-1[data-side=bottom],.data-\[side\=left\]\:-translate-x-1[data-side=left]{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.data-\[side\=left\]\:-translate-x-1[data-side=left]{--tw-translate-x:-0.25rem}.data-\[side\=right\]\:translate-x-1[data-side=right]{--tw-translate-x:0.25rem}.data-\[side\=right\]\:translate-x-1[data-side=right],.data-\[side\=top\]\:-translate-y-1[data-side=top]{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.data-\[side\=top\]\:-translate-y-1[data-side=top]{--tw-translate-y:-0.25rem}.data-\[swipe\=cancel\]\:translate-x-0[data-swipe=cancel]{--tw-translate-x:0px}.data-\[swipe\=cancel\]\:translate-x-0[data-swipe=cancel],.data-\[swipe\=end\]\:translate-x-\[var\(--radix-toast-swipe-end-x\)\][data-swipe=end]{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.data-\[swipe\=end\]\:translate-x-\[var\(--radix-toast-swipe-end-x\)\][data-swipe=end]{--tw-translate-x:var(--radix-toast-swipe-end-x)}.data-\[swipe\=move\]\:translate-x-\[var\(--radix-toast-swipe-move-x\)\][data-swipe=move]{--tw-translate-x:var(--radix-toast-swipe-move-x);transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.data-\[state\=open\]\:bg-accent[data-state=open]{background-color:hsl(var(--accent))}.data-\[active\=true\]\:text-primary[data-active=true]{color:hsl(var(--primary))}.data-\[state\=open\]\:text-muted-foreground[data-state=open]{color:hsl(var(--muted-foreground))}.data-\[disabled\]\:opacity-50[data-disabled]{opacity:.5}.data-\[swipe\=move\]\:transition-none[data-swipe=move]{transition-property:none}.data-\[state\=open\]\:animate-in[data-state=open]{animation-name:enter;animation-duration:.15s;--tw-enter-opacity:initial;--tw-enter-scale:initial;--tw-enter-rotate:initial;--tw-enter-translate-x:initial;--tw-enter-translate-y:initial}.data-\[state\=closed\]\:animate-out[data-state=closed],.data-\[swipe\=end\]\:animate-out[data-swipe=end]{animation-name:exit;animation-duration:.15s;--tw-exit-opacity:initial;--tw-exit-scale:initial;--tw-exit-rotate:initial;--tw-exit-translate-x:initial;--tw-exit-translate-y:initial}.data-\[state\=closed\]\:fade-out-0[data-state=closed]{--tw-exit-opacity:0}.data-\[state\=closed\]\:fade-out-80[data-state=closed]{--tw-exit-opacity:0.8}.data-\[state\=open\]\:fade-in-0[data-state=open]{--tw-enter-opacity:0}.data-\[state\=closed\]\:zoom-out-95[data-state=closed]{--tw-exit-scale:.95}.data-\[state\=open\]\:zoom-in-95[data-state=open]{--tw-enter-scale:.95}.data-\[side\=bottom\]\:slide-in-from-top-2[data-side=bottom]{--tw-enter-translate-y:-0.5rem}.data-\[side\=left\]\:slide-in-from-right-2[data-side=left]{--tw-enter-translate-x:0.5rem}.data-\[side\=right\]\:slide-in-from-left-2[data-side=right]{--tw-enter-translate-x:-0.5rem}.data-\[side\=top\]\:slide-in-from-bottom-2[data-side=top]{--tw-enter-translate-y:0.5rem}.data-\[state\=closed\]\:slide-out-to-left-1\/2[data-state=closed]{--tw-exit-translate-x:-50%}.data-\[state\=closed\]\:slide-out-to-right-full[data-state=closed]{--tw-exit-translate-x:100%}.data-\[state\=closed\]\:slide-out-to-top-\[48\%\][data-state=closed]{--tw-exit-translate-y:-48%}.data-\[state\=open\]\:slide-in-from-left-1\/2[data-state=open]{--tw-enter-translate-x:-50%}.data-\[state\=open\]\:slide-in-from-top-\[48\%\][data-state=open]{--tw-enter-translate-y:-48%}.data-\[state\=open\]\:slide-in-from-top-full[data-state=open]{--tw-enter-translate-y:-100%}@media (min-width:640px){.sm\:bottom-0{bottom:0}.sm\:right-0{right:0}.sm\:top-auto{top:auto}.sm\:mt-0{margin-top:0}.sm\:flex-row{flex-direction:row}.sm\:flex-col{flex-direction:column}.sm\:justify-end{justify-content:flex-end}.sm\:space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.5rem*var(--tw-space-x-reverse));margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)))}.sm\:rounded-lg{border-radius:var(--radius)}.sm\:text-left{text-align:left}.data-\[state\=open\]\:sm\:slide-in-from-bottom-full[data-state=open]{--tw-enter-translate-y:100%}}@media (min-width:768px){.md\:max-w-\[420px\]{max-width:420px}.md\:text-sm{font-size:.875rem;line-height:1.25rem}}.\[\&\>span\]\:line-clamp-1>span{overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:1}.\[\&_svg\]\:pointer-events-none svg{pointer-events:none}.\[\&_svg\]\:size-4 svg{width:1rem;height:1rem}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightbird/ui",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Drop-in React video player component powered by LightBird. Full controls, playlist, subtitles, chapters — one import.",
5
5
  "license": "MIT",
6
6
  "author": "Punyam Singh",
@@ -48,6 +48,8 @@
48
48
  "@dnd-kit/core": "^6.3.1",
49
49
  "@dnd-kit/sortable": "^10.0.0",
50
50
  "@dnd-kit/utilities": "^3.2.2",
51
+ "@openfeature/react-sdk": "^1.2.1",
52
+ "@radix-ui/react-alert-dialog": "^1.1.6",
51
53
  "@radix-ui/react-dialog": "^1.1.6",
52
54
  "@radix-ui/react-label": "^2.1.2",
53
55
  "@radix-ui/react-popover": "^1.1.6",
@@ -63,7 +65,7 @@
63
65
  "clsx": "^2.1.1",
64
66
  "lucide-react": "^0.475.0",
65
67
  "tailwind-merge": "^3.0.1",
66
- "@lightbird/core": "0.2.0"
68
+ "@lightbird/core": "0.3.0"
67
69
  },
68
70
  "peerDependencies": {
69
71
  "react": "^18.0.0 || ^19.0.0",