koin.js 1.0.10 → 1.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@
3
3
  var React2 = require('react');
4
4
  var lucideReact = require('lucide-react');
5
5
  var jsxRuntime = require('react/jsx-runtime');
6
+ var reactDom = require('react-dom');
6
7
  var nostalgist = require('nostalgist');
7
8
 
8
9
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -61,18 +62,43 @@ var ControlButton = React2.memo(function ControlButton2({
61
62
  );
62
63
  });
63
64
  var SPEED_OPTIONS = [1, 2];
64
- function SpeedMenu({ speed, onSpeedChange, disabled = false }) {
65
+ var SpeedMenu = React2.memo(function SpeedMenu2({ speed, onSpeedChange, disabled = false }) {
65
66
  const [showMenu, setShowMenu] = React2.useState(false);
66
67
  const buttonRef = React2__default.default.useRef(null);
67
- const getMenuPosition = () => {
68
- if (!buttonRef.current) return {};
68
+ const menuRef = React2__default.default.useRef(null);
69
+ const [menuPosition, setMenuPosition] = React2.useState({ bottom: "0px", left: "0px", transform: "translateX(-50%)" });
70
+ const updateMenuPosition = () => {
71
+ if (!buttonRef.current) return;
69
72
  const rect = buttonRef.current.getBoundingClientRect();
70
- return {
73
+ setMenuPosition({
71
74
  bottom: `${window.innerHeight - rect.top + 8}px`,
72
75
  left: `${rect.left + rect.width / 2}px`,
73
76
  transform: "translateX(-50%)"
74
- };
77
+ });
75
78
  };
79
+ React2.useEffect(() => {
80
+ if (showMenu) {
81
+ updateMenuPosition();
82
+ window.addEventListener("resize", updateMenuPosition);
83
+ window.addEventListener("scroll", updateMenuPosition);
84
+ return () => {
85
+ window.removeEventListener("resize", updateMenuPosition);
86
+ window.removeEventListener("scroll", updateMenuPosition);
87
+ };
88
+ }
89
+ }, [showMenu]);
90
+ React2.useEffect(() => {
91
+ if (!showMenu) return;
92
+ const handleClickOutside = (e) => {
93
+ const target = e.target;
94
+ if (buttonRef.current?.contains(target) || menuRef.current?.contains(target)) {
95
+ return;
96
+ }
97
+ setShowMenu(false);
98
+ };
99
+ document.addEventListener("mousedown", handleClickOutside);
100
+ return () => document.removeEventListener("mousedown", handleClickOutside);
101
+ }, [showMenu]);
76
102
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
77
103
  /* @__PURE__ */ jsxRuntime.jsxs(
78
104
  "button",
@@ -103,36 +129,41 @@ function SpeedMenu({ speed, onSpeedChange, disabled = false }) {
103
129
  ]
104
130
  }
105
131
  ),
106
- showMenu && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
107
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 z-[9998]", onClick: () => setShowMenu(false) }),
108
- /* @__PURE__ */ jsxRuntime.jsx(
109
- "div",
110
- {
111
- className: "fixed z-[9999] bg-black/90 backdrop-blur-md border border-white/20 rounded-lg p-1.5 shadow-xl flex flex-col gap-1 min-w-[80px]",
112
- style: getMenuPosition(),
113
- children: SPEED_OPTIONS.map((s) => /* @__PURE__ */ jsxRuntime.jsxs(
114
- "button",
115
- {
116
- onClick: () => {
117
- onSpeedChange(s);
118
- setShowMenu(false);
119
- },
120
- className: `
132
+ showMenu && typeof document !== "undefined" && reactDom.createPortal(
133
+ /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
134
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 z-[9998]", onClick: () => setShowMenu(false) }),
135
+ /* @__PURE__ */ jsxRuntime.jsx(
136
+ "div",
137
+ {
138
+ ref: menuRef,
139
+ className: "fixed z-[9999] bg-black/90 backdrop-blur-md border border-white/20 rounded-lg p-1.5 shadow-xl flex flex-col gap-1 min-w-[80px]",
140
+ style: menuPosition,
141
+ children: SPEED_OPTIONS.map((s) => /* @__PURE__ */ jsxRuntime.jsxs(
142
+ "button",
143
+ {
144
+ onClick: () => {
145
+ onSpeedChange(s);
146
+ setShowMenu(false);
147
+ },
148
+ className: `
121
149
  px-3 py-2 rounded text-xs font-mono font-bold text-center transition-colors
122
150
  ${speed === s ? "bg-white/20 text-white" : "hover:bg-white/10 text-gray-400 hover:text-white"}
123
151
  `,
124
- children: [
125
- s,
126
- "x"
127
- ]
128
- },
129
- s
130
- ))
131
- }
132
- )
133
- ] })
152
+ children: [
153
+ s,
154
+ "x"
155
+ ]
156
+ },
157
+ s
158
+ ))
159
+ }
160
+ )
161
+ ] }),
162
+ document.body
163
+ )
134
164
  ] });
135
- }
165
+ });
166
+ var SpeedMenu_default = SpeedMenu;
136
167
  function VolumeControl({
137
168
  volume,
138
169
  isMuted,
@@ -191,13 +222,72 @@ function VolumeControl({
191
222
  ] })
192
223
  ] });
193
224
  }
194
- function HardcoreTooltip({ show, message = "Disabled in Hardcore mode", children }) {
195
- if (!show) return children ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children }) : null;
225
+ var PortalTooltip = React2.memo(function PortalTooltip2({
226
+ content,
227
+ children,
228
+ className = "",
229
+ show = true,
230
+ tooltipClassName = "bg-black/90 text-white"
231
+ }) {
232
+ const [isHovered, setIsHovered] = React2.useState(false);
233
+ const containerRef = React2.useRef(null);
234
+ const [tooltipPosition, setTooltipPosition] = React2.useState({ bottom: "0px", left: "0px" });
235
+ React2.useEffect(() => {
236
+ if (!isHovered || !show) return;
237
+ const updatePosition = () => {
238
+ if (!containerRef.current) return;
239
+ const rect = containerRef.current.getBoundingClientRect();
240
+ setTooltipPosition({
241
+ bottom: `${window.innerHeight - rect.top + 8}px`,
242
+ left: `${rect.left + rect.width / 2}px`
243
+ });
244
+ };
245
+ updatePosition();
246
+ window.addEventListener("resize", updatePosition);
247
+ window.addEventListener("scroll", updatePosition);
248
+ return () => {
249
+ window.removeEventListener("resize", updatePosition);
250
+ window.removeEventListener("scroll", updatePosition);
251
+ };
252
+ }, [isHovered, show]);
253
+ if (!show) return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
196
254
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
197
- children,
198
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 bg-amber-500/90 text-black text-xs rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-50", children: message })
255
+ /* @__PURE__ */ jsxRuntime.jsx(
256
+ "div",
257
+ {
258
+ ref: containerRef,
259
+ onMouseEnter: () => setIsHovered(true),
260
+ onMouseLeave: () => setIsHovered(false),
261
+ className,
262
+ children
263
+ }
264
+ ),
265
+ isHovered && typeof document !== "undefined" && reactDom.createPortal(
266
+ /* @__PURE__ */ jsxRuntime.jsx(
267
+ "div",
268
+ {
269
+ className: `fixed px-2 py-1 text-xs rounded whitespace-nowrap z-[9999] ${tooltipClassName}`,
270
+ style: { ...tooltipPosition, transform: "translateX(-50%)" },
271
+ children: content
272
+ }
273
+ ),
274
+ document.body
275
+ )
199
276
  ] });
200
- }
277
+ });
278
+ var HardcoreTooltip = React2.memo(function HardcoreTooltip2({ show, message = "Disabled in Hardcore mode", children }) {
279
+ if (!show) return children ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children }) : null;
280
+ return /* @__PURE__ */ jsxRuntime.jsx(
281
+ PortalTooltip,
282
+ {
283
+ content: message,
284
+ show,
285
+ tooltipClassName: "bg-amber-500/90 text-black",
286
+ children
287
+ }
288
+ );
289
+ });
290
+ var HardcoreTooltip_default = HardcoreTooltip;
201
291
 
202
292
  // src/locales/en.ts
203
293
  var en = {
@@ -495,33 +585,40 @@ var PlaybackControls = React2.memo(function PlaybackControls2({
495
585
  systemColor
496
586
  }
497
587
  ),
498
- /* @__PURE__ */ jsxRuntime.jsx(SpeedMenu, { speed, onSpeedChange, disabled }),
499
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative group", children: [
500
- /* @__PURE__ */ jsxRuntime.jsx(
501
- ControlButton,
502
- {
503
- onMouseDown: hasRewindHistory && hardcoreRestrictions?.canUseRewind !== false ? onRewindStart : void 0,
504
- onMouseUp: hasRewindHistory && hardcoreRestrictions?.canUseRewind !== false ? onRewindStop : void 0,
505
- onMouseLeave: hasRewindHistory && hardcoreRestrictions?.canUseRewind !== false ? onRewindStop : void 0,
506
- onTouchStart: hasRewindHistory && hardcoreRestrictions?.canUseRewind !== false ? onRewindStart : void 0,
507
- onTouchEnd: hasRewindHistory && hardcoreRestrictions?.canUseRewind !== false ? onRewindStop : void 0,
508
- icon: lucideReact.Rewind,
509
- label: t.controls.rewind,
510
- active: isRewinding,
511
- disabled: disabled || !hasRewindHistory || hardcoreRestrictions?.canUseRewind === false,
512
- systemColor
513
- }
514
- ),
515
- hasRewindHistory && !isRewinding && hardcoreRestrictions?.canUseRewind !== false && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 rounded-lg animate-pulse pointer-events-none", style: { backgroundColor: `${systemColor}20` } }),
516
- /* @__PURE__ */ jsxRuntime.jsx(
517
- HardcoreTooltip,
518
- {
519
- show: hardcoreRestrictions?.canUseRewind === false,
520
- message: hardcoreRestrictions?.isHardcore ? t.common.disabledInHardcore : t.common.notSupported
521
- }
522
- ),
523
- hardcoreRestrictions?.canUseRewind !== false && !hasRewindHistory && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 bg-black/90 text-white text-xs rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-50", children: t.common.playToEnableRewind })
524
- ] }),
588
+ /* @__PURE__ */ jsxRuntime.jsx(SpeedMenu_default, { speed, onSpeedChange, disabled }),
589
+ /* @__PURE__ */ jsxRuntime.jsxs(
590
+ PortalTooltip,
591
+ {
592
+ content: t.common.playToEnableRewind,
593
+ show: hardcoreRestrictions?.canUseRewind !== false && !hasRewindHistory,
594
+ className: "relative group",
595
+ children: [
596
+ /* @__PURE__ */ jsxRuntime.jsx(
597
+ ControlButton,
598
+ {
599
+ onMouseDown: hasRewindHistory && hardcoreRestrictions?.canUseRewind !== false ? onRewindStart : void 0,
600
+ onMouseUp: hasRewindHistory && hardcoreRestrictions?.canUseRewind !== false ? onRewindStop : void 0,
601
+ onMouseLeave: hasRewindHistory && hardcoreRestrictions?.canUseRewind !== false ? onRewindStop : void 0,
602
+ onTouchStart: hasRewindHistory && hardcoreRestrictions?.canUseRewind !== false ? onRewindStart : void 0,
603
+ onTouchEnd: hasRewindHistory && hardcoreRestrictions?.canUseRewind !== false ? onRewindStop : void 0,
604
+ icon: lucideReact.Rewind,
605
+ label: t.controls.rewind,
606
+ active: isRewinding,
607
+ disabled: disabled || !hasRewindHistory || hardcoreRestrictions?.canUseRewind === false,
608
+ systemColor
609
+ }
610
+ ),
611
+ hasRewindHistory && !isRewinding && hardcoreRestrictions?.canUseRewind !== false && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 rounded-lg animate-pulse pointer-events-none", style: { backgroundColor: `${systemColor}20` } }),
612
+ /* @__PURE__ */ jsxRuntime.jsx(
613
+ HardcoreTooltip_default,
614
+ {
615
+ show: hardcoreRestrictions?.canUseRewind === false,
616
+ message: hardcoreRestrictions?.isHardcore ? t.common.disabledInHardcore : t.common.notSupported
617
+ }
618
+ )
619
+ ]
620
+ }
621
+ ),
525
622
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-px h-8 bg-white/10 mx-1" }),
526
623
  /* @__PURE__ */ jsxRuntime.jsx(
527
624
  VolumeControl,
@@ -747,7 +844,7 @@ var SaveLoadControls = React2.memo(function SaveLoadControls2({
747
844
  }
748
845
  ),
749
846
  /* @__PURE__ */ jsxRuntime.jsx(
750
- HardcoreTooltip,
847
+ HardcoreTooltip_default,
751
848
  {
752
849
  show: hardcoreRestrictions?.canUseSaveStates === false,
753
850
  message: hardcoreRestrictions?.isHardcore ? t.common.disabledInHardcore : t.common.notSupported
@@ -766,7 +863,7 @@ var SaveLoadControls = React2.memo(function SaveLoadControls2({
766
863
  }
767
864
  ),
768
865
  /* @__PURE__ */ jsxRuntime.jsx(
769
- HardcoreTooltip,
866
+ HardcoreTooltip_default,
770
867
  {
771
868
  show: hardcoreRestrictions?.canUseSaveStates === false,
772
869
  message: hardcoreRestrictions?.isHardcore ? t.common.disabledInHardcore : t.common.notSupported
@@ -824,17 +921,41 @@ var ShaderDropdown = React2.memo(function ShaderDropdown2({
824
921
  }) {
825
922
  const [isOpen, setIsOpen] = React2.useState(false);
826
923
  const [pendingShader, setPendingShader] = React2.useState(null);
827
- const dropdownRef = React2.useRef(null);
924
+ const buttonRef = React2.useRef(null);
925
+ const menuRef = React2.useRef(null);
926
+ const [dropdownPosition, setDropdownPosition] = React2.useState({ bottom: "0px", left: "0px" });
927
+ const updateDropdownPosition = () => {
928
+ if (!buttonRef.current) return;
929
+ const rect = buttonRef.current.getBoundingClientRect();
930
+ setDropdownPosition({
931
+ bottom: `${window.innerHeight - rect.top + 8}px`,
932
+ left: `${rect.left}px`
933
+ });
934
+ };
828
935
  React2.useEffect(() => {
936
+ if (isOpen) {
937
+ updateDropdownPosition();
938
+ window.addEventListener("resize", updateDropdownPosition);
939
+ window.addEventListener("scroll", updateDropdownPosition);
940
+ return () => {
941
+ window.removeEventListener("resize", updateDropdownPosition);
942
+ window.removeEventListener("scroll", updateDropdownPosition);
943
+ };
944
+ }
945
+ }, [isOpen]);
946
+ React2.useEffect(() => {
947
+ if (!isOpen) return;
829
948
  const handleClickOutside = (e) => {
830
- if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
831
- setIsOpen(false);
832
- setPendingShader(null);
949
+ const target = e.target;
950
+ if (buttonRef.current?.contains(target) || menuRef.current?.contains(target)) {
951
+ return;
833
952
  }
953
+ setIsOpen(false);
954
+ setPendingShader(null);
834
955
  };
835
956
  document.addEventListener("mousedown", handleClickOutside);
836
957
  return () => document.removeEventListener("mousedown", handleClickOutside);
837
- }, []);
958
+ }, [isOpen]);
838
959
  const currentLabel = QUICK_PRESETS.find((p) => p.id === currentShader)?.label || "Custom";
839
960
  if (!onShaderChange) return null;
840
961
  const handleSelect = (id) => {
@@ -859,10 +980,11 @@ var ShaderDropdown = React2.memo(function ShaderDropdown2({
859
980
  const cancelRestart = () => {
860
981
  setPendingShader(null);
861
982
  };
862
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: dropdownRef, className: "relative hidden sm:block", children: [
983
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative hidden sm:block", children: [
863
984
  /* @__PURE__ */ jsxRuntime.jsxs(
864
985
  "button",
865
986
  {
987
+ ref: buttonRef,
866
988
  onClick: () => !disabled && setIsOpen(!isOpen),
867
989
  disabled,
868
990
  className: `flex items-center gap-1.5 px-2 py-1.5 rounded-lg transition-all duration-200 hover:bg-white/10 ${disabled ? "opacity-50 cursor-not-allowed" : ""}`,
@@ -874,52 +996,60 @@ var ShaderDropdown = React2.memo(function ShaderDropdown2({
874
996
  ]
875
997
  }
876
998
  ),
877
- isOpen && /* @__PURE__ */ jsxRuntime.jsx(
878
- "div",
879
- {
880
- className: "absolute bottom-full left-0 mb-2 py-1 rounded-lg shadow-xl z-50 min-w-[160px]",
881
- style: {
882
- backgroundColor: "rgba(0, 0, 0, 0.95)",
883
- border: `1px solid ${systemColor}40`
884
- },
885
- children: pendingShader !== null ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-3 space-y-2", children: [
886
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-xs text-orange-400", children: [
887
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { size: 14 }),
888
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-bold", children: "Restart Required" })
889
- ] }),
890
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] text-white/60", children: "Shader change requires game restart. Your progress since last save will be lost." }),
891
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2 pt-1", children: [
892
- /* @__PURE__ */ jsxRuntime.jsx(
893
- "button",
894
- {
895
- onClick: confirmRestart,
896
- className: "flex-1 px-2 py-1 text-xs font-bold rounded bg-orange-500/20 text-orange-400 hover:bg-orange-500/30",
897
- children: "Restart"
898
- }
899
- ),
900
- /* @__PURE__ */ jsxRuntime.jsx(
901
- "button",
902
- {
903
- onClick: cancelRestart,
904
- className: "flex-1 px-2 py-1 text-xs font-bold rounded bg-white/10 text-white/60 hover:bg-white/20",
905
- children: "Cancel"
906
- }
907
- )
908
- ] })
909
- ] }) : (
910
- // Shader list
911
- QUICK_PRESETS.map(({ id, label }) => /* @__PURE__ */ jsxRuntime.jsx(
912
- "button",
913
- {
914
- onClick: () => handleSelect(id),
915
- className: `w-full px-3 py-1.5 text-left text-xs font-medium transition-colors ${currentShader === id ? "text-white" : "text-white/70 hover:bg-white/10"}`,
916
- style: currentShader === id ? { backgroundColor: `${systemColor}30`, color: systemColor } : void 0,
917
- children: label
999
+ isOpen && typeof document !== "undefined" && reactDom.createPortal(
1000
+ /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1001
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 z-[9998]", onClick: () => setIsOpen(false) }),
1002
+ /* @__PURE__ */ jsxRuntime.jsx(
1003
+ "div",
1004
+ {
1005
+ ref: menuRef,
1006
+ className: "fixed py-1 rounded-lg shadow-xl z-[9999] min-w-[160px]",
1007
+ style: {
1008
+ backgroundColor: "rgba(0, 0, 0, 0.95)",
1009
+ border: `1px solid ${systemColor}40`,
1010
+ ...dropdownPosition
918
1011
  },
919
- id
920
- ))
1012
+ children: pendingShader !== null ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-3 space-y-2", children: [
1013
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-xs text-orange-400", children: [
1014
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { size: 14 }),
1015
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-bold", children: "Restart Required" })
1016
+ ] }),
1017
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] text-white/60", children: "Shader change requires game restart. Your progress since last save will be lost." }),
1018
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2 pt-1", children: [
1019
+ /* @__PURE__ */ jsxRuntime.jsx(
1020
+ "button",
1021
+ {
1022
+ onClick: confirmRestart,
1023
+ className: "flex-1 px-2 py-1 text-xs font-bold rounded bg-orange-500/20 text-orange-400 hover:bg-orange-500/30",
1024
+ children: "Restart"
1025
+ }
1026
+ ),
1027
+ /* @__PURE__ */ jsxRuntime.jsx(
1028
+ "button",
1029
+ {
1030
+ onClick: cancelRestart,
1031
+ className: "flex-1 px-2 py-1 text-xs font-bold rounded bg-white/10 text-white/60 hover:bg-white/20",
1032
+ children: "Cancel"
1033
+ }
1034
+ )
1035
+ ] })
1036
+ ] }) : (
1037
+ // Shader list
1038
+ QUICK_PRESETS.map(({ id, label }) => /* @__PURE__ */ jsxRuntime.jsx(
1039
+ "button",
1040
+ {
1041
+ onClick: () => handleSelect(id),
1042
+ className: `w-full px-3 py-1.5 text-left text-xs font-medium transition-colors ${currentShader === id ? "text-white" : "text-white/70 hover:bg-white/10"}`,
1043
+ style: currentShader === id ? { backgroundColor: `${systemColor}30`, color: systemColor } : void 0,
1044
+ children: label
1045
+ },
1046
+ id
1047
+ ))
1048
+ )
1049
+ }
921
1050
  )
922
- }
1051
+ ] }),
1052
+ document.body
923
1053
  )
924
1054
  ] });
925
1055
  });
@@ -936,41 +1066,48 @@ function RAButton({
936
1066
  const t = useKoinTranslation();
937
1067
  const title = isGameFound ? `${t.retroAchievements.title} (${achievementCount} achievements)` : isConnected ? t.retroAchievements.connected : t.retroAchievements.title;
938
1068
  const tooltip = isIdentifying ? t.retroAchievements.identifying : isGameFound ? t.retroAchievements.achievementsAvailable.replace("{{count}}", achievementCount.toString()) : isConnected ? t.retroAchievements.gameNotSupported : t.retroAchievements.connect;
939
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `relative group ${className}`, children: [
940
- /* @__PURE__ */ jsxRuntime.jsxs(
941
- "button",
942
- {
943
- onClick,
944
- disabled,
945
- className: `
1069
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1070
+ PortalTooltip,
1071
+ {
1072
+ content: tooltip,
1073
+ className: `relative group ${className}`,
1074
+ tooltipClassName: "bg-gray-900/95 text-white border border-white/10",
1075
+ children: [
1076
+ /* @__PURE__ */ jsxRuntime.jsxs(
1077
+ "button",
1078
+ {
1079
+ onClick,
1080
+ disabled,
1081
+ className: `
946
1082
  group relative flex flex-col items-center gap-1 px-3 py-2 rounded-lg
947
1083
  transition-all duration-200 disabled:opacity-40 disabled:cursor-not-allowed
948
1084
  select-none
949
1085
  ${isGameFound ? "bg-gradient-to-b from-yellow-500/30 to-orange-500/20 text-yellow-400 ring-1 ring-yellow-500/50 shadow-[0_0_12px_rgba(234,179,8,0.3)]" : isConnected ? "bg-yellow-500/10 text-yellow-400/70 ring-1 ring-yellow-500/30" : "hover:bg-white/10 text-gray-400 hover:text-white"}
950
1086
  `,
951
- title,
952
- children: [
953
- isIdentifying ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { size: 20, className: "animate-spin text-yellow-400" }) : /* @__PURE__ */ jsxRuntime.jsx(
954
- lucideReact.Trophy,
955
- {
956
- size: 20,
957
- className: `
1087
+ title,
1088
+ children: [
1089
+ isIdentifying ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { size: 20, className: "animate-spin text-yellow-400" }) : /* @__PURE__ */ jsxRuntime.jsx(
1090
+ lucideReact.Trophy,
1091
+ {
1092
+ size: 20,
1093
+ className: `
958
1094
  transition-all group-hover:scale-110
959
1095
  ${isGameFound ? "drop-shadow-[0_0_8px_rgba(234,179,8,0.7)] text-yellow-400" : ""}
960
1096
  ${isConnected && !isGameFound ? "opacity-70" : ""}
961
1097
  `
962
- }
963
- ),
964
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[9px] font-bold uppercase tracking-wider opacity-70", children: isIdentifying ? "..." : isGameFound ? achievementCount : "RA" })
965
- ]
966
- }
967
- ),
968
- isConnected && /* @__PURE__ */ jsxRuntime.jsx("div", { className: `
1098
+ }
1099
+ ),
1100
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[9px] font-bold uppercase tracking-wider opacity-70", children: isIdentifying ? "..." : isGameFound ? achievementCount : "RA" })
1101
+ ]
1102
+ }
1103
+ ),
1104
+ isConnected && /* @__PURE__ */ jsxRuntime.jsx("div", { className: `
969
1105
  absolute -top-1 -right-1 w-3 h-3 rounded-full border-2 border-black
970
1106
  ${isGameFound ? "bg-green-500 shadow-[0_0_8px_rgba(34,197,94,0.8)]" : "bg-yellow-500 shadow-[0_0_6px_rgba(234,179,8,0.6)]"}
971
- `, children: isGameFound && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-green-400 rounded-full animate-ping opacity-50" }) }),
972
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 bg-gray-900/95 text-white text-xs rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-50 border border-white/10", children: tooltip })
973
- ] });
1107
+ `, children: isGameFound && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-green-400 rounded-full animate-ping opacity-50" }) })
1108
+ ]
1109
+ }
1110
+ );
974
1111
  }
975
1112
  var SettingsControls = React2.memo(function SettingsControls2({
976
1113
  onFullscreen,
@@ -1062,7 +1199,7 @@ var SettingsControls = React2.memo(function SettingsControls2({
1062
1199
  }
1063
1200
  ),
1064
1201
  /* @__PURE__ */ jsxRuntime.jsx(
1065
- HardcoreTooltip,
1202
+ HardcoreTooltip_default,
1066
1203
  {
1067
1204
  show: hardcoreRestrictions?.canUseCheats === false,
1068
1205
  message: hardcoreRestrictions?.isHardcore ? t.common.disabledInHardcore : t.common.notSupported
@@ -9340,6 +9477,8 @@ function AchievementPopup({
9340
9477
  }
9341
9478
  );
9342
9479
  }
9480
+
9481
+ // src/lib/shader-presets.ts
9343
9482
  var SHADER_PRESETS = [
9344
9483
  { id: "", name: "None", description: "No shader - sharp pixels" },
9345
9484
  { id: "crt/crt-lottes", name: "CRT Lottes", description: "High-quality arcade monitor look" },
@@ -9352,81 +9491,6 @@ var SHADER_PRESETS = [
9352
9491
  { id: "handheld/lcd-grid-v2", name: "LCD Grid", description: "Game Boy style LCD effect" },
9353
9492
  { id: "scanlines", name: "Scanlines", description: "Simple horizontal scanlines" }
9354
9493
  ];
9355
- var ShaderSelector = React2.memo(function ShaderSelector2({
9356
- currentShader,
9357
- onShaderChange,
9358
- disabled = false,
9359
- systemColor = "#00FF41"
9360
- }) {
9361
- const [isOpen, setIsOpen] = React2.useState(false);
9362
- const dropdownRef = React2.useRef(null);
9363
- React2.useEffect(() => {
9364
- const handleClickOutside = (e) => {
9365
- if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
9366
- setIsOpen(false);
9367
- }
9368
- };
9369
- document.addEventListener("mousedown", handleClickOutside);
9370
- return () => document.removeEventListener("mousedown", handleClickOutside);
9371
- }, []);
9372
- const currentPreset = SHADER_PRESETS.find((p) => p.id === currentShader) || SHADER_PRESETS[0];
9373
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: dropdownRef, className: "relative", children: [
9374
- /* @__PURE__ */ jsxRuntime.jsxs(
9375
- "button",
9376
- {
9377
- onClick: () => !disabled && setIsOpen(!isOpen),
9378
- disabled,
9379
- className: "flex items-center gap-2 px-3 py-2 rounded text-sm font-medium transition-all",
9380
- style: {
9381
- backgroundColor: currentShader ? `${systemColor}20` : "rgba(255,255,255,0.1)",
9382
- color: currentShader ? systemColor : "rgba(255,255,255,0.7)",
9383
- border: `1px solid ${currentShader ? systemColor : "rgba(255,255,255,0.2)"}`
9384
- },
9385
- children: [
9386
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Palette, { size: 16 }),
9387
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline", children: currentPreset.name }),
9388
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { size: 14, className: `transition-transform ${isOpen ? "rotate-180" : ""}` })
9389
- ]
9390
- }
9391
- ),
9392
- isOpen && /* @__PURE__ */ jsxRuntime.jsxs(
9393
- "div",
9394
- {
9395
- className: "absolute bottom-full left-0 mb-2 w-56 bg-black/95 border border-white/20 rounded-lg shadow-xl overflow-hidden z-50",
9396
- style: { backdropFilter: "blur(8px)" },
9397
- children: [
9398
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-2 border-b border-white/10", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs font-bold text-white/60 uppercase tracking-wide", children: "Video Shader" }) }),
9399
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-h-64 overflow-y-auto", children: SHADER_PRESETS.map((preset) => /* @__PURE__ */ jsxRuntime.jsxs(
9400
- "button",
9401
- {
9402
- onClick: () => {
9403
- onShaderChange(preset.id);
9404
- setIsOpen(false);
9405
- },
9406
- className: "w-full px-3 py-2 text-left hover:bg-white/10 transition-colors flex flex-col gap-0.5",
9407
- style: {
9408
- backgroundColor: currentShader === preset.id ? `${systemColor}20` : void 0
9409
- },
9410
- children: [
9411
- /* @__PURE__ */ jsxRuntime.jsx(
9412
- "span",
9413
- {
9414
- className: "text-sm font-medium",
9415
- style: { color: currentShader === preset.id ? systemColor : "white" },
9416
- children: preset.name
9417
- }
9418
- ),
9419
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-white/50", children: preset.description })
9420
- ]
9421
- },
9422
- preset.id
9423
- )) })
9424
- ]
9425
- }
9426
- )
9427
- ] });
9428
- });
9429
- var ShaderSelector_default = ShaderSelector;
9430
9494
  var SHORTCUTS = [
9431
9495
  { key: "F1", description: "Help" },
9432
9496
  { key: "F3", description: "FPS Overlay" },
@@ -9508,7 +9572,6 @@ exports.STICK_BUTTONS = STICK_BUTTONS;
9508
9572
  exports.SUPPORTED_EXTENSIONS = SUPPORTED_EXTENSIONS;
9509
9573
  exports.SYSTEMS = SYSTEMS;
9510
9574
  exports.SYSTEM_BUTTONS = SYSTEM_BUTTONS;
9511
- exports.ShaderSelector = ShaderSelector_default;
9512
9575
  exports.ShortcutsReference = ShortcutsReference_default;
9513
9576
  exports.TRIGGER_BUTTONS = TRIGGER_BUTTONS;
9514
9577
  exports.ToastContainer = ToastContainer;