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.mjs CHANGED
@@ -1,6 +1,7 @@
1
- import React2, { memo, createContext, useState, useRef, useEffect, useMemo, useCallback, useContext } from 'react';
2
- import { Play, Pause, RotateCcw, Rewind, Save, Download, Camera, Video, Circle, Monitor, ChevronDown, RefreshCw, HelpCircle, Maximize, Gamepad2, Joystick, Code, Settings, Power, Square, Keyboard, X, Palette, ChevronUp, Gauge, VolumeX, Volume1, Volume2, Loader2, Trophy, AlertTriangle, Minimize2, List, PauseCircle, Check, Clock, Move, User, Copy, Lock, Zap, HardDrive, Trash2, Cpu, AlertCircle, FileCode, Globe, ExternalLink, EyeOff, Eye, Shield, CheckCircle, LogOut, Info, XCircle } from 'lucide-react';
1
+ import React2, { memo, useState, useEffect, useRef, createContext, useMemo, useCallback, useContext } from 'react';
2
+ import { Gauge, Play, Pause, RotateCcw, Rewind, Save, Download, Camera, Video, Circle, Monitor, ChevronDown, RefreshCw, HelpCircle, Maximize, Gamepad2, Joystick, Code, Settings, Power, Square, Keyboard, X, ChevronUp, VolumeX, Volume1, Volume2, Loader2, Trophy, AlertTriangle, Minimize2, List, PauseCircle, Check, Clock, Move, User, Copy, Lock, Zap, HardDrive, Trash2, Cpu, AlertCircle, FileCode, Globe, ExternalLink, EyeOff, Eye, Shield, CheckCircle, LogOut, Info, XCircle } from 'lucide-react';
3
3
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
+ import { createPortal } from 'react-dom';
4
5
  import { Nostalgist } from 'nostalgist';
5
6
 
6
7
  var __defProp = Object.defineProperty;
@@ -55,18 +56,43 @@ var ControlButton = memo(function ControlButton2({
55
56
  );
56
57
  });
57
58
  var SPEED_OPTIONS = [1, 2];
58
- function SpeedMenu({ speed, onSpeedChange, disabled = false }) {
59
+ var SpeedMenu = memo(function SpeedMenu2({ speed, onSpeedChange, disabled = false }) {
59
60
  const [showMenu, setShowMenu] = useState(false);
60
61
  const buttonRef = React2.useRef(null);
61
- const getMenuPosition = () => {
62
- if (!buttonRef.current) return {};
62
+ const menuRef = React2.useRef(null);
63
+ const [menuPosition, setMenuPosition] = useState({ bottom: "0px", left: "0px", transform: "translateX(-50%)" });
64
+ const updateMenuPosition = () => {
65
+ if (!buttonRef.current) return;
63
66
  const rect = buttonRef.current.getBoundingClientRect();
64
- return {
67
+ setMenuPosition({
65
68
  bottom: `${window.innerHeight - rect.top + 8}px`,
66
69
  left: `${rect.left + rect.width / 2}px`,
67
70
  transform: "translateX(-50%)"
68
- };
71
+ });
69
72
  };
73
+ useEffect(() => {
74
+ if (showMenu) {
75
+ updateMenuPosition();
76
+ window.addEventListener("resize", updateMenuPosition);
77
+ window.addEventListener("scroll", updateMenuPosition);
78
+ return () => {
79
+ window.removeEventListener("resize", updateMenuPosition);
80
+ window.removeEventListener("scroll", updateMenuPosition);
81
+ };
82
+ }
83
+ }, [showMenu]);
84
+ useEffect(() => {
85
+ if (!showMenu) return;
86
+ const handleClickOutside = (e) => {
87
+ const target = e.target;
88
+ if (buttonRef.current?.contains(target) || menuRef.current?.contains(target)) {
89
+ return;
90
+ }
91
+ setShowMenu(false);
92
+ };
93
+ document.addEventListener("mousedown", handleClickOutside);
94
+ return () => document.removeEventListener("mousedown", handleClickOutside);
95
+ }, [showMenu]);
70
96
  return /* @__PURE__ */ jsxs("div", { className: "relative", children: [
71
97
  /* @__PURE__ */ jsxs(
72
98
  "button",
@@ -97,36 +123,41 @@ function SpeedMenu({ speed, onSpeedChange, disabled = false }) {
97
123
  ]
98
124
  }
99
125
  ),
100
- showMenu && /* @__PURE__ */ jsxs(Fragment, { children: [
101
- /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-[9998]", onClick: () => setShowMenu(false) }),
102
- /* @__PURE__ */ jsx(
103
- "div",
104
- {
105
- 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]",
106
- style: getMenuPosition(),
107
- children: SPEED_OPTIONS.map((s) => /* @__PURE__ */ jsxs(
108
- "button",
109
- {
110
- onClick: () => {
111
- onSpeedChange(s);
112
- setShowMenu(false);
113
- },
114
- className: `
126
+ showMenu && typeof document !== "undefined" && createPortal(
127
+ /* @__PURE__ */ jsxs(Fragment, { children: [
128
+ /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-[9998]", onClick: () => setShowMenu(false) }),
129
+ /* @__PURE__ */ jsx(
130
+ "div",
131
+ {
132
+ ref: menuRef,
133
+ 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]",
134
+ style: menuPosition,
135
+ children: SPEED_OPTIONS.map((s) => /* @__PURE__ */ jsxs(
136
+ "button",
137
+ {
138
+ onClick: () => {
139
+ onSpeedChange(s);
140
+ setShowMenu(false);
141
+ },
142
+ className: `
115
143
  px-3 py-2 rounded text-xs font-mono font-bold text-center transition-colors
116
144
  ${speed === s ? "bg-white/20 text-white" : "hover:bg-white/10 text-gray-400 hover:text-white"}
117
145
  `,
118
- children: [
119
- s,
120
- "x"
121
- ]
122
- },
123
- s
124
- ))
125
- }
126
- )
127
- ] })
146
+ children: [
147
+ s,
148
+ "x"
149
+ ]
150
+ },
151
+ s
152
+ ))
153
+ }
154
+ )
155
+ ] }),
156
+ document.body
157
+ )
128
158
  ] });
129
- }
159
+ });
160
+ var SpeedMenu_default = SpeedMenu;
130
161
  function VolumeControl({
131
162
  volume,
132
163
  isMuted,
@@ -185,13 +216,72 @@ function VolumeControl({
185
216
  ] })
186
217
  ] });
187
218
  }
188
- function HardcoreTooltip({ show, message = "Disabled in Hardcore mode", children }) {
189
- if (!show) return children ? /* @__PURE__ */ jsx(Fragment, { children }) : null;
219
+ var PortalTooltip = memo(function PortalTooltip2({
220
+ content,
221
+ children,
222
+ className = "",
223
+ show = true,
224
+ tooltipClassName = "bg-black/90 text-white"
225
+ }) {
226
+ const [isHovered, setIsHovered] = useState(false);
227
+ const containerRef = useRef(null);
228
+ const [tooltipPosition, setTooltipPosition] = useState({ bottom: "0px", left: "0px" });
229
+ useEffect(() => {
230
+ if (!isHovered || !show) return;
231
+ const updatePosition = () => {
232
+ if (!containerRef.current) return;
233
+ const rect = containerRef.current.getBoundingClientRect();
234
+ setTooltipPosition({
235
+ bottom: `${window.innerHeight - rect.top + 8}px`,
236
+ left: `${rect.left + rect.width / 2}px`
237
+ });
238
+ };
239
+ updatePosition();
240
+ window.addEventListener("resize", updatePosition);
241
+ window.addEventListener("scroll", updatePosition);
242
+ return () => {
243
+ window.removeEventListener("resize", updatePosition);
244
+ window.removeEventListener("scroll", updatePosition);
245
+ };
246
+ }, [isHovered, show]);
247
+ if (!show) return /* @__PURE__ */ jsx(Fragment, { children });
190
248
  return /* @__PURE__ */ jsxs(Fragment, { children: [
191
- children,
192
- /* @__PURE__ */ 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 })
249
+ /* @__PURE__ */ jsx(
250
+ "div",
251
+ {
252
+ ref: containerRef,
253
+ onMouseEnter: () => setIsHovered(true),
254
+ onMouseLeave: () => setIsHovered(false),
255
+ className,
256
+ children
257
+ }
258
+ ),
259
+ isHovered && typeof document !== "undefined" && createPortal(
260
+ /* @__PURE__ */ jsx(
261
+ "div",
262
+ {
263
+ className: `fixed px-2 py-1 text-xs rounded whitespace-nowrap z-[9999] ${tooltipClassName}`,
264
+ style: { ...tooltipPosition, transform: "translateX(-50%)" },
265
+ children: content
266
+ }
267
+ ),
268
+ document.body
269
+ )
193
270
  ] });
194
- }
271
+ });
272
+ var HardcoreTooltip = memo(function HardcoreTooltip2({ show, message = "Disabled in Hardcore mode", children }) {
273
+ if (!show) return children ? /* @__PURE__ */ jsx(Fragment, { children }) : null;
274
+ return /* @__PURE__ */ jsx(
275
+ PortalTooltip,
276
+ {
277
+ content: message,
278
+ show,
279
+ tooltipClassName: "bg-amber-500/90 text-black",
280
+ children
281
+ }
282
+ );
283
+ });
284
+ var HardcoreTooltip_default = HardcoreTooltip;
195
285
 
196
286
  // src/locales/en.ts
197
287
  var en = {
@@ -489,33 +579,40 @@ var PlaybackControls = memo(function PlaybackControls2({
489
579
  systemColor
490
580
  }
491
581
  ),
492
- /* @__PURE__ */ jsx(SpeedMenu, { speed, onSpeedChange, disabled }),
493
- /* @__PURE__ */ jsxs("div", { className: "relative group", children: [
494
- /* @__PURE__ */ jsx(
495
- ControlButton,
496
- {
497
- onMouseDown: hasRewindHistory && hardcoreRestrictions?.canUseRewind !== false ? onRewindStart : void 0,
498
- onMouseUp: hasRewindHistory && hardcoreRestrictions?.canUseRewind !== false ? onRewindStop : void 0,
499
- onMouseLeave: hasRewindHistory && hardcoreRestrictions?.canUseRewind !== false ? onRewindStop : void 0,
500
- onTouchStart: hasRewindHistory && hardcoreRestrictions?.canUseRewind !== false ? onRewindStart : void 0,
501
- onTouchEnd: hasRewindHistory && hardcoreRestrictions?.canUseRewind !== false ? onRewindStop : void 0,
502
- icon: Rewind,
503
- label: t.controls.rewind,
504
- active: isRewinding,
505
- disabled: disabled || !hasRewindHistory || hardcoreRestrictions?.canUseRewind === false,
506
- systemColor
507
- }
508
- ),
509
- hasRewindHistory && !isRewinding && hardcoreRestrictions?.canUseRewind !== false && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 rounded-lg animate-pulse pointer-events-none", style: { backgroundColor: `${systemColor}20` } }),
510
- /* @__PURE__ */ jsx(
511
- HardcoreTooltip,
512
- {
513
- show: hardcoreRestrictions?.canUseRewind === false,
514
- message: hardcoreRestrictions?.isHardcore ? t.common.disabledInHardcore : t.common.notSupported
515
- }
516
- ),
517
- hardcoreRestrictions?.canUseRewind !== false && !hasRewindHistory && /* @__PURE__ */ 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 })
518
- ] }),
582
+ /* @__PURE__ */ jsx(SpeedMenu_default, { speed, onSpeedChange, disabled }),
583
+ /* @__PURE__ */ jsxs(
584
+ PortalTooltip,
585
+ {
586
+ content: t.common.playToEnableRewind,
587
+ show: hardcoreRestrictions?.canUseRewind !== false && !hasRewindHistory,
588
+ className: "relative group",
589
+ children: [
590
+ /* @__PURE__ */ jsx(
591
+ ControlButton,
592
+ {
593
+ onMouseDown: hasRewindHistory && hardcoreRestrictions?.canUseRewind !== false ? onRewindStart : void 0,
594
+ onMouseUp: hasRewindHistory && hardcoreRestrictions?.canUseRewind !== false ? onRewindStop : void 0,
595
+ onMouseLeave: hasRewindHistory && hardcoreRestrictions?.canUseRewind !== false ? onRewindStop : void 0,
596
+ onTouchStart: hasRewindHistory && hardcoreRestrictions?.canUseRewind !== false ? onRewindStart : void 0,
597
+ onTouchEnd: hasRewindHistory && hardcoreRestrictions?.canUseRewind !== false ? onRewindStop : void 0,
598
+ icon: Rewind,
599
+ label: t.controls.rewind,
600
+ active: isRewinding,
601
+ disabled: disabled || !hasRewindHistory || hardcoreRestrictions?.canUseRewind === false,
602
+ systemColor
603
+ }
604
+ ),
605
+ hasRewindHistory && !isRewinding && hardcoreRestrictions?.canUseRewind !== false && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 rounded-lg animate-pulse pointer-events-none", style: { backgroundColor: `${systemColor}20` } }),
606
+ /* @__PURE__ */ jsx(
607
+ HardcoreTooltip_default,
608
+ {
609
+ show: hardcoreRestrictions?.canUseRewind === false,
610
+ message: hardcoreRestrictions?.isHardcore ? t.common.disabledInHardcore : t.common.notSupported
611
+ }
612
+ )
613
+ ]
614
+ }
615
+ ),
519
616
  /* @__PURE__ */ jsx("div", { className: "w-px h-8 bg-white/10 mx-1" }),
520
617
  /* @__PURE__ */ jsx(
521
618
  VolumeControl,
@@ -741,7 +838,7 @@ var SaveLoadControls = memo(function SaveLoadControls2({
741
838
  }
742
839
  ),
743
840
  /* @__PURE__ */ jsx(
744
- HardcoreTooltip,
841
+ HardcoreTooltip_default,
745
842
  {
746
843
  show: hardcoreRestrictions?.canUseSaveStates === false,
747
844
  message: hardcoreRestrictions?.isHardcore ? t.common.disabledInHardcore : t.common.notSupported
@@ -760,7 +857,7 @@ var SaveLoadControls = memo(function SaveLoadControls2({
760
857
  }
761
858
  ),
762
859
  /* @__PURE__ */ jsx(
763
- HardcoreTooltip,
860
+ HardcoreTooltip_default,
764
861
  {
765
862
  show: hardcoreRestrictions?.canUseSaveStates === false,
766
863
  message: hardcoreRestrictions?.isHardcore ? t.common.disabledInHardcore : t.common.notSupported
@@ -818,17 +915,41 @@ var ShaderDropdown = memo(function ShaderDropdown2({
818
915
  }) {
819
916
  const [isOpen, setIsOpen] = useState(false);
820
917
  const [pendingShader, setPendingShader] = useState(null);
821
- const dropdownRef = useRef(null);
918
+ const buttonRef = useRef(null);
919
+ const menuRef = useRef(null);
920
+ const [dropdownPosition, setDropdownPosition] = useState({ bottom: "0px", left: "0px" });
921
+ const updateDropdownPosition = () => {
922
+ if (!buttonRef.current) return;
923
+ const rect = buttonRef.current.getBoundingClientRect();
924
+ setDropdownPosition({
925
+ bottom: `${window.innerHeight - rect.top + 8}px`,
926
+ left: `${rect.left}px`
927
+ });
928
+ };
822
929
  useEffect(() => {
930
+ if (isOpen) {
931
+ updateDropdownPosition();
932
+ window.addEventListener("resize", updateDropdownPosition);
933
+ window.addEventListener("scroll", updateDropdownPosition);
934
+ return () => {
935
+ window.removeEventListener("resize", updateDropdownPosition);
936
+ window.removeEventListener("scroll", updateDropdownPosition);
937
+ };
938
+ }
939
+ }, [isOpen]);
940
+ useEffect(() => {
941
+ if (!isOpen) return;
823
942
  const handleClickOutside = (e) => {
824
- if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
825
- setIsOpen(false);
826
- setPendingShader(null);
943
+ const target = e.target;
944
+ if (buttonRef.current?.contains(target) || menuRef.current?.contains(target)) {
945
+ return;
827
946
  }
947
+ setIsOpen(false);
948
+ setPendingShader(null);
828
949
  };
829
950
  document.addEventListener("mousedown", handleClickOutside);
830
951
  return () => document.removeEventListener("mousedown", handleClickOutside);
831
- }, []);
952
+ }, [isOpen]);
832
953
  const currentLabel = QUICK_PRESETS.find((p) => p.id === currentShader)?.label || "Custom";
833
954
  if (!onShaderChange) return null;
834
955
  const handleSelect = (id) => {
@@ -853,10 +974,11 @@ var ShaderDropdown = memo(function ShaderDropdown2({
853
974
  const cancelRestart = () => {
854
975
  setPendingShader(null);
855
976
  };
856
- return /* @__PURE__ */ jsxs("div", { ref: dropdownRef, className: "relative hidden sm:block", children: [
977
+ return /* @__PURE__ */ jsxs("div", { className: "relative hidden sm:block", children: [
857
978
  /* @__PURE__ */ jsxs(
858
979
  "button",
859
980
  {
981
+ ref: buttonRef,
860
982
  onClick: () => !disabled && setIsOpen(!isOpen),
861
983
  disabled,
862
984
  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" : ""}`,
@@ -868,52 +990,60 @@ var ShaderDropdown = memo(function ShaderDropdown2({
868
990
  ]
869
991
  }
870
992
  ),
871
- isOpen && /* @__PURE__ */ jsx(
872
- "div",
873
- {
874
- className: "absolute bottom-full left-0 mb-2 py-1 rounded-lg shadow-xl z-50 min-w-[160px]",
875
- style: {
876
- backgroundColor: "rgba(0, 0, 0, 0.95)",
877
- border: `1px solid ${systemColor}40`
878
- },
879
- children: pendingShader !== null ? /* @__PURE__ */ jsxs("div", { className: "p-3 space-y-2", children: [
880
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs text-orange-400", children: [
881
- /* @__PURE__ */ jsx(RefreshCw, { size: 14 }),
882
- /* @__PURE__ */ jsx("span", { className: "font-bold", children: "Restart Required" })
883
- ] }),
884
- /* @__PURE__ */ jsx("p", { className: "text-[10px] text-white/60", children: "Shader change requires game restart. Your progress since last save will be lost." }),
885
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2 pt-1", children: [
886
- /* @__PURE__ */ jsx(
887
- "button",
888
- {
889
- onClick: confirmRestart,
890
- className: "flex-1 px-2 py-1 text-xs font-bold rounded bg-orange-500/20 text-orange-400 hover:bg-orange-500/30",
891
- children: "Restart"
892
- }
893
- ),
894
- /* @__PURE__ */ jsx(
895
- "button",
896
- {
897
- onClick: cancelRestart,
898
- className: "flex-1 px-2 py-1 text-xs font-bold rounded bg-white/10 text-white/60 hover:bg-white/20",
899
- children: "Cancel"
900
- }
901
- )
902
- ] })
903
- ] }) : (
904
- // Shader list
905
- QUICK_PRESETS.map(({ id, label }) => /* @__PURE__ */ jsx(
906
- "button",
907
- {
908
- onClick: () => handleSelect(id),
909
- 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"}`,
910
- style: currentShader === id ? { backgroundColor: `${systemColor}30`, color: systemColor } : void 0,
911
- children: label
993
+ isOpen && typeof document !== "undefined" && createPortal(
994
+ /* @__PURE__ */ jsxs(Fragment, { children: [
995
+ /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-[9998]", onClick: () => setIsOpen(false) }),
996
+ /* @__PURE__ */ jsx(
997
+ "div",
998
+ {
999
+ ref: menuRef,
1000
+ className: "fixed py-1 rounded-lg shadow-xl z-[9999] min-w-[160px]",
1001
+ style: {
1002
+ backgroundColor: "rgba(0, 0, 0, 0.95)",
1003
+ border: `1px solid ${systemColor}40`,
1004
+ ...dropdownPosition
912
1005
  },
913
- id
914
- ))
1006
+ children: pendingShader !== null ? /* @__PURE__ */ jsxs("div", { className: "p-3 space-y-2", children: [
1007
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs text-orange-400", children: [
1008
+ /* @__PURE__ */ jsx(RefreshCw, { size: 14 }),
1009
+ /* @__PURE__ */ jsx("span", { className: "font-bold", children: "Restart Required" })
1010
+ ] }),
1011
+ /* @__PURE__ */ jsx("p", { className: "text-[10px] text-white/60", children: "Shader change requires game restart. Your progress since last save will be lost." }),
1012
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2 pt-1", children: [
1013
+ /* @__PURE__ */ jsx(
1014
+ "button",
1015
+ {
1016
+ onClick: confirmRestart,
1017
+ className: "flex-1 px-2 py-1 text-xs font-bold rounded bg-orange-500/20 text-orange-400 hover:bg-orange-500/30",
1018
+ children: "Restart"
1019
+ }
1020
+ ),
1021
+ /* @__PURE__ */ jsx(
1022
+ "button",
1023
+ {
1024
+ onClick: cancelRestart,
1025
+ className: "flex-1 px-2 py-1 text-xs font-bold rounded bg-white/10 text-white/60 hover:bg-white/20",
1026
+ children: "Cancel"
1027
+ }
1028
+ )
1029
+ ] })
1030
+ ] }) : (
1031
+ // Shader list
1032
+ QUICK_PRESETS.map(({ id, label }) => /* @__PURE__ */ jsx(
1033
+ "button",
1034
+ {
1035
+ onClick: () => handleSelect(id),
1036
+ 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"}`,
1037
+ style: currentShader === id ? { backgroundColor: `${systemColor}30`, color: systemColor } : void 0,
1038
+ children: label
1039
+ },
1040
+ id
1041
+ ))
1042
+ )
1043
+ }
915
1044
  )
916
- }
1045
+ ] }),
1046
+ document.body
917
1047
  )
918
1048
  ] });
919
1049
  });
@@ -930,41 +1060,48 @@ function RAButton({
930
1060
  const t = useKoinTranslation();
931
1061
  const title = isGameFound ? `${t.retroAchievements.title} (${achievementCount} achievements)` : isConnected ? t.retroAchievements.connected : t.retroAchievements.title;
932
1062
  const tooltip = isIdentifying ? t.retroAchievements.identifying : isGameFound ? t.retroAchievements.achievementsAvailable.replace("{{count}}", achievementCount.toString()) : isConnected ? t.retroAchievements.gameNotSupported : t.retroAchievements.connect;
933
- return /* @__PURE__ */ jsxs("div", { className: `relative group ${className}`, children: [
934
- /* @__PURE__ */ jsxs(
935
- "button",
936
- {
937
- onClick,
938
- disabled,
939
- className: `
1063
+ return /* @__PURE__ */ jsxs(
1064
+ PortalTooltip,
1065
+ {
1066
+ content: tooltip,
1067
+ className: `relative group ${className}`,
1068
+ tooltipClassName: "bg-gray-900/95 text-white border border-white/10",
1069
+ children: [
1070
+ /* @__PURE__ */ jsxs(
1071
+ "button",
1072
+ {
1073
+ onClick,
1074
+ disabled,
1075
+ className: `
940
1076
  group relative flex flex-col items-center gap-1 px-3 py-2 rounded-lg
941
1077
  transition-all duration-200 disabled:opacity-40 disabled:cursor-not-allowed
942
1078
  select-none
943
1079
  ${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"}
944
1080
  `,
945
- title,
946
- children: [
947
- isIdentifying ? /* @__PURE__ */ jsx(Loader2, { size: 20, className: "animate-spin text-yellow-400" }) : /* @__PURE__ */ jsx(
948
- Trophy,
949
- {
950
- size: 20,
951
- className: `
1081
+ title,
1082
+ children: [
1083
+ isIdentifying ? /* @__PURE__ */ jsx(Loader2, { size: 20, className: "animate-spin text-yellow-400" }) : /* @__PURE__ */ jsx(
1084
+ Trophy,
1085
+ {
1086
+ size: 20,
1087
+ className: `
952
1088
  transition-all group-hover:scale-110
953
1089
  ${isGameFound ? "drop-shadow-[0_0_8px_rgba(234,179,8,0.7)] text-yellow-400" : ""}
954
1090
  ${isConnected && !isGameFound ? "opacity-70" : ""}
955
1091
  `
956
- }
957
- ),
958
- /* @__PURE__ */ jsx("span", { className: "text-[9px] font-bold uppercase tracking-wider opacity-70", children: isIdentifying ? "..." : isGameFound ? achievementCount : "RA" })
959
- ]
960
- }
961
- ),
962
- isConnected && /* @__PURE__ */ jsx("div", { className: `
1092
+ }
1093
+ ),
1094
+ /* @__PURE__ */ jsx("span", { className: "text-[9px] font-bold uppercase tracking-wider opacity-70", children: isIdentifying ? "..." : isGameFound ? achievementCount : "RA" })
1095
+ ]
1096
+ }
1097
+ ),
1098
+ isConnected && /* @__PURE__ */ jsx("div", { className: `
963
1099
  absolute -top-1 -right-1 w-3 h-3 rounded-full border-2 border-black
964
1100
  ${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)]"}
965
- `, children: isGameFound && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-green-400 rounded-full animate-ping opacity-50" }) }),
966
- /* @__PURE__ */ 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 })
967
- ] });
1101
+ `, children: isGameFound && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-green-400 rounded-full animate-ping opacity-50" }) })
1102
+ ]
1103
+ }
1104
+ );
968
1105
  }
969
1106
  var SettingsControls = memo(function SettingsControls2({
970
1107
  onFullscreen,
@@ -1056,7 +1193,7 @@ var SettingsControls = memo(function SettingsControls2({
1056
1193
  }
1057
1194
  ),
1058
1195
  /* @__PURE__ */ jsx(
1059
- HardcoreTooltip,
1196
+ HardcoreTooltip_default,
1060
1197
  {
1061
1198
  show: hardcoreRestrictions?.canUseCheats === false,
1062
1199
  message: hardcoreRestrictions?.isHardcore ? t.common.disabledInHardcore : t.common.notSupported
@@ -9334,6 +9471,8 @@ function AchievementPopup({
9334
9471
  }
9335
9472
  );
9336
9473
  }
9474
+
9475
+ // src/lib/shader-presets.ts
9337
9476
  var SHADER_PRESETS = [
9338
9477
  { id: "", name: "None", description: "No shader - sharp pixels" },
9339
9478
  { id: "crt/crt-lottes", name: "CRT Lottes", description: "High-quality arcade monitor look" },
@@ -9346,81 +9485,6 @@ var SHADER_PRESETS = [
9346
9485
  { id: "handheld/lcd-grid-v2", name: "LCD Grid", description: "Game Boy style LCD effect" },
9347
9486
  { id: "scanlines", name: "Scanlines", description: "Simple horizontal scanlines" }
9348
9487
  ];
9349
- var ShaderSelector = memo(function ShaderSelector2({
9350
- currentShader,
9351
- onShaderChange,
9352
- disabled = false,
9353
- systemColor = "#00FF41"
9354
- }) {
9355
- const [isOpen, setIsOpen] = useState(false);
9356
- const dropdownRef = useRef(null);
9357
- useEffect(() => {
9358
- const handleClickOutside = (e) => {
9359
- if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
9360
- setIsOpen(false);
9361
- }
9362
- };
9363
- document.addEventListener("mousedown", handleClickOutside);
9364
- return () => document.removeEventListener("mousedown", handleClickOutside);
9365
- }, []);
9366
- const currentPreset = SHADER_PRESETS.find((p) => p.id === currentShader) || SHADER_PRESETS[0];
9367
- return /* @__PURE__ */ jsxs("div", { ref: dropdownRef, className: "relative", children: [
9368
- /* @__PURE__ */ jsxs(
9369
- "button",
9370
- {
9371
- onClick: () => !disabled && setIsOpen(!isOpen),
9372
- disabled,
9373
- className: "flex items-center gap-2 px-3 py-2 rounded text-sm font-medium transition-all",
9374
- style: {
9375
- backgroundColor: currentShader ? `${systemColor}20` : "rgba(255,255,255,0.1)",
9376
- color: currentShader ? systemColor : "rgba(255,255,255,0.7)",
9377
- border: `1px solid ${currentShader ? systemColor : "rgba(255,255,255,0.2)"}`
9378
- },
9379
- children: [
9380
- /* @__PURE__ */ jsx(Palette, { size: 16 }),
9381
- /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: currentPreset.name }),
9382
- /* @__PURE__ */ jsx(ChevronDown, { size: 14, className: `transition-transform ${isOpen ? "rotate-180" : ""}` })
9383
- ]
9384
- }
9385
- ),
9386
- isOpen && /* @__PURE__ */ jsxs(
9387
- "div",
9388
- {
9389
- 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",
9390
- style: { backdropFilter: "blur(8px)" },
9391
- children: [
9392
- /* @__PURE__ */ jsx("div", { className: "p-2 border-b border-white/10", children: /* @__PURE__ */ jsx("div", { className: "text-xs font-bold text-white/60 uppercase tracking-wide", children: "Video Shader" }) }),
9393
- /* @__PURE__ */ jsx("div", { className: "max-h-64 overflow-y-auto", children: SHADER_PRESETS.map((preset) => /* @__PURE__ */ jsxs(
9394
- "button",
9395
- {
9396
- onClick: () => {
9397
- onShaderChange(preset.id);
9398
- setIsOpen(false);
9399
- },
9400
- className: "w-full px-3 py-2 text-left hover:bg-white/10 transition-colors flex flex-col gap-0.5",
9401
- style: {
9402
- backgroundColor: currentShader === preset.id ? `${systemColor}20` : void 0
9403
- },
9404
- children: [
9405
- /* @__PURE__ */ jsx(
9406
- "span",
9407
- {
9408
- className: "text-sm font-medium",
9409
- style: { color: currentShader === preset.id ? systemColor : "white" },
9410
- children: preset.name
9411
- }
9412
- ),
9413
- /* @__PURE__ */ jsx("span", { className: "text-xs text-white/50", children: preset.description })
9414
- ]
9415
- },
9416
- preset.id
9417
- )) })
9418
- ]
9419
- }
9420
- )
9421
- ] });
9422
- });
9423
- var ShaderSelector_default = ShaderSelector;
9424
9488
  var SHORTCUTS = [
9425
9489
  { key: "F1", description: "Help" },
9426
9490
  { key: "F3", description: "FPS Overlay" },
@@ -9477,6 +9541,6 @@ var ShortcutsReference = memo(function ShortcutsReference2({
9477
9541
  });
9478
9542
  var ShortcutsReference_default = ShortcutsReference;
9479
9543
 
9480
- export { ALL_BUTTONS, AchievementPopup, BUTTON_GROUPS, BUTTON_LABELS, CONSOLE_CAPABILITIES, CONSOLE_KEYBOARD_OVERRIDES, DEFAULT_CONTROLS, DEFAULT_GAMEPAD, DEFAULT_KEYBOARD, DPAD_BUTTONS2 as DPAD_BUTTONS, FACE_BUTTONS, GamePlayer_default as GamePlayer, KoinI18nProvider, PERFORMANCE_TIER_1_SYSTEMS, PERFORMANCE_TIER_2_SYSTEMS, RASidebar, RA_MEDIA_BASE, SHADER_PRESETS, SHOULDER_BUTTONS, STANDARD_AXIS_MAP, STANDARD_GAMEPAD_BUTTONS, STICK_BUTTONS, SUPPORTED_EXTENSIONS, SYSTEMS, SYSTEM_BUTTONS, ShaderSelector_default as ShaderSelector, ShortcutsReference_default as ShortcutsReference, TRIGGER_BUTTONS, ToastContainer, buildRetroArchConfig, clearAllControls, consoleHasButton, detectControllerBrand, detectSystem, en, es, fetchAndCacheRom, formatGamepadButton, formatKeyCode, fr, gamepadToRetroArchConfig, getAchievementBadgeUrl, getCachedRom, getConsoleButtons, getConsoleCapabilities, getConsoleKeyboardDefaults, getCore, getDBSystemNames, getFullGamepadMapping, getFullKeyboardMapping, getSupportedExtensions, getSystem, getSystemByDbName, getSystemByKey, getSystemFromExtension, getSystemsList, getUserAvatarUrl, isSystemSupported, keyboardToRetroArchConfig, loadAllGamepadMappings, loadGamepadMapping, loadKeyboardMapping, normalizeSystemKey, saveGamepadMapping, saveKeyboardMapping, systemsMatch, useGameRecording, useGamepad, useKoinTranslation, useNostalgist, useToast };
9544
+ export { ALL_BUTTONS, AchievementPopup, BUTTON_GROUPS, BUTTON_LABELS, CONSOLE_CAPABILITIES, CONSOLE_KEYBOARD_OVERRIDES, DEFAULT_CONTROLS, DEFAULT_GAMEPAD, DEFAULT_KEYBOARD, DPAD_BUTTONS2 as DPAD_BUTTONS, FACE_BUTTONS, GamePlayer_default as GamePlayer, KoinI18nProvider, PERFORMANCE_TIER_1_SYSTEMS, PERFORMANCE_TIER_2_SYSTEMS, RASidebar, RA_MEDIA_BASE, SHADER_PRESETS, SHOULDER_BUTTONS, STANDARD_AXIS_MAP, STANDARD_GAMEPAD_BUTTONS, STICK_BUTTONS, SUPPORTED_EXTENSIONS, SYSTEMS, SYSTEM_BUTTONS, ShortcutsReference_default as ShortcutsReference, TRIGGER_BUTTONS, ToastContainer, buildRetroArchConfig, clearAllControls, consoleHasButton, detectControllerBrand, detectSystem, en, es, fetchAndCacheRom, formatGamepadButton, formatKeyCode, fr, gamepadToRetroArchConfig, getAchievementBadgeUrl, getCachedRom, getConsoleButtons, getConsoleCapabilities, getConsoleKeyboardDefaults, getCore, getDBSystemNames, getFullGamepadMapping, getFullKeyboardMapping, getSupportedExtensions, getSystem, getSystemByDbName, getSystemByKey, getSystemFromExtension, getSystemsList, getUserAvatarUrl, isSystemSupported, keyboardToRetroArchConfig, loadAllGamepadMappings, loadGamepadMapping, loadKeyboardMapping, normalizeSystemKey, saveGamepadMapping, saveKeyboardMapping, systemsMatch, useGameRecording, useGamepad, useKoinTranslation, useNostalgist, useToast };
9481
9545
  //# sourceMappingURL=index.mjs.map
9482
9546
  //# sourceMappingURL=index.mjs.map