koin.js 1.0.7 → 1.0.9

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,5 +1,5 @@
1
- import React5, { memo, useState, useRef, useEffect, useCallback, useMemo } from 'react';
2
- import { Play, Pause, RotateCcw, Rewind, Save, Download, Camera, Video, Circle, Monitor, ChevronDown, RefreshCw, HelpCircle, Maximize, Gamepad2, Joystick, Code, Power, Square, Keyboard, X, Palette, ChevronUp, Gauge, VolumeX, Volume1, Volume2, Loader2, Trophy, AlertTriangle, List, Settings, PauseCircle, Check, Clock, User, Copy, Lock, Zap, HardDrive, Trash2, Cpu, AlertCircle, FileCode, ExternalLink, EyeOff, Eye, Shield, CheckCircle, LogOut, Info, XCircle } from 'lucide-react';
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';
3
3
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
4
  import { Nostalgist } from 'nostalgist';
5
5
 
@@ -33,20 +33,23 @@ var ControlButton = memo(function ControlButton2({
33
33
  onTouchEnd,
34
34
  disabled,
35
35
  className: `
36
- group relative flex flex-col items-center gap-1 px-2 sm:px-3 py-2 rounded-lg
37
- transition-all duration-200 disabled:opacity-40 disabled:cursor-not-allowed
38
- select-none flex-shrink-0
39
- ${active ? "" : danger ? "hover:bg-red-500/20 text-gray-400 hover:text-red-400" : "hover:bg-white/10 text-gray-400 hover:text-white"}
40
- ${className}
41
- `,
36
+ flex flex-col items-center justify-center gap-1.5
37
+ px-3 py-2 rounded-lg
38
+ transition-all duration-200
39
+ disabled:opacity-50 disabled:cursor-not-allowed
40
+ hover:bg-white/10 active:bg-white/20
41
+ ${active ? "bg-white/10 ring-1 ring-inset" : ""}
42
+ ${danger ? "hover:bg-red-500/20 text-red-400" : "text-gray-400 hover:text-white"}
43
+ ${className}
44
+ `,
42
45
  style: active ? {
43
46
  backgroundColor: `${systemColor}20`,
44
47
  color: systemColor
45
48
  } : {},
46
49
  title: label,
47
50
  children: [
48
- /* @__PURE__ */ jsx(Icon, { size: iconSize, className: "transition-transform group-hover:scale-110" }),
49
- /* @__PURE__ */ jsx("span", { className: "text-[9px] font-bold uppercase tracking-wider opacity-70", children: label })
51
+ /* @__PURE__ */ jsx(Icon, { size: iconSize, className: `transition-transform duration-200 ${active ? "scale-110" : "group-hover:scale-110"}`, style: active ? { color: systemColor } : void 0 }),
52
+ /* @__PURE__ */ jsx("span", { className: "text-[9px] font-bold uppercase tracking-wider whitespace-nowrap", style: { color: active ? systemColor : void 0 }, children: label })
50
53
  ]
51
54
  }
52
55
  );
@@ -54,10 +57,21 @@ var ControlButton = memo(function ControlButton2({
54
57
  var SPEED_OPTIONS = [1, 2];
55
58
  function SpeedMenu({ speed, onSpeedChange, disabled = false }) {
56
59
  const [showMenu, setShowMenu] = useState(false);
60
+ const buttonRef = React2.useRef(null);
61
+ const getMenuPosition = () => {
62
+ if (!buttonRef.current) return {};
63
+ const rect = buttonRef.current.getBoundingClientRect();
64
+ return {
65
+ bottom: `${window.innerHeight - rect.top + 8}px`,
66
+ left: `${rect.left + rect.width / 2}px`,
67
+ transform: "translateX(-50%)"
68
+ };
69
+ };
57
70
  return /* @__PURE__ */ jsxs("div", { className: "relative", children: [
58
71
  /* @__PURE__ */ jsxs(
59
72
  "button",
60
73
  {
74
+ ref: buttonRef,
61
75
  onClick: () => setShowMenu(!showMenu),
62
76
  disabled,
63
77
  className: `
@@ -84,25 +98,32 @@ function SpeedMenu({ speed, onSpeedChange, disabled = false }) {
84
98
  }
85
99
  ),
86
100
  showMenu && /* @__PURE__ */ jsxs(Fragment, { children: [
87
- /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-40", onClick: () => setShowMenu(false) }),
88
- /* @__PURE__ */ jsx("div", { className: "absolute bottom-full left-1/2 -translate-x-1/2 mb-2 bg-black/90 backdrop-blur-md border border-white/20 rounded-lg p-1.5 shadow-xl z-50 flex flex-col gap-1 min-w-[80px]", children: SPEED_OPTIONS.map((s) => /* @__PURE__ */ jsxs(
89
- "button",
101
+ /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-[9998]", onClick: () => setShowMenu(false) }),
102
+ /* @__PURE__ */ jsx(
103
+ "div",
90
104
  {
91
- onClick: () => {
92
- onSpeedChange(s);
93
- setShowMenu(false);
94
- },
95
- className: `
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: `
96
115
  px-3 py-2 rounded text-xs font-mono font-bold text-center transition-colors
97
116
  ${speed === s ? "bg-white/20 text-white" : "hover:bg-white/10 text-gray-400 hover:text-white"}
98
117
  `,
99
- children: [
100
- s,
101
- "x"
102
- ]
103
- },
104
- s
105
- )) })
118
+ children: [
119
+ s,
120
+ "x"
121
+ ]
122
+ },
123
+ s
124
+ ))
125
+ }
126
+ )
106
127
  ] })
107
128
  ] });
108
129
  }
@@ -171,6 +192,260 @@ function HardcoreTooltip({ show, message = "Disabled in Hardcore mode", children
171
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 })
172
193
  ] });
173
194
  }
195
+
196
+ // src/locales/en.ts
197
+ var en = {
198
+ controls: {
199
+ play: "Play",
200
+ pause: "Pause",
201
+ reset: "Reset",
202
+ rewind: "Rewind",
203
+ save: "Save",
204
+ load: "Load",
205
+ snap: "Snap",
206
+ rec: "Rec",
207
+ stopRec: "Stop",
208
+ startRecord: "Start recording",
209
+ stopRecord: "Stop recording",
210
+ mute: "Mute",
211
+ unmute: "Unmute",
212
+ help: "Help",
213
+ full: "Full",
214
+ keys: "Keys",
215
+ menuOpen: "Open menu",
216
+ menuClose: "Close menu",
217
+ gamepadConnected: "{{count}} controller{{plural}} connected - click to configure",
218
+ noGamepad: "No controller detected - press any button on your gamepad to connect",
219
+ press: "Press",
220
+ startBtn: "START",
221
+ selectBtn: "SEL"
222
+ },
223
+ common: {
224
+ disabledInHardcore: "Disabled in Hardcore mode",
225
+ notSupported: "Not supported on this console",
226
+ playToEnableRewind: "Play for a few seconds to enable rewind"
227
+ },
228
+ settings: {
229
+ title: "Settings",
230
+ general: "General",
231
+ audio: "Audio",
232
+ video: "Video",
233
+ input: "Input",
234
+ advanced: "Advanced",
235
+ fullscreen: "Fullscreen",
236
+ controls: "Controls",
237
+ gamepad: "Gamepad",
238
+ cheats: "Cheats",
239
+ retroAchievements: "RetroAchievements",
240
+ shortcuts: "Shortcuts",
241
+ exit: "Exit",
242
+ language: "Language",
243
+ selectLanguage: "Select Language"
244
+ },
245
+ overlay: {
246
+ play: "PLAY",
247
+ systemFirmware: "SYSTEM FIRMWARE",
248
+ loading: "Loading {{system}}",
249
+ initializing: "Initializing emulator",
250
+ loadingSave: "Loading Save",
251
+ preparingSlot: "Preparing slot {{num}}",
252
+ systemError: "System Error",
253
+ failedInit: "Failed to initialize emulator",
254
+ retry: "Retry",
255
+ slotReady: "Slot {{num}} ready",
256
+ paused: "Paused"
257
+ },
258
+ notifications: {
259
+ saved: "State saved",
260
+ loaded: "State loaded",
261
+ error: "Error",
262
+ recordingStarted: "Recording started",
263
+ recordingSaved: "Recording saved",
264
+ downloaded: "State downloaded",
265
+ loadedFile: "State loaded from file",
266
+ savedSlot: "Saved to slot {{num}}",
267
+ loadedSlot: "Loaded from slot {{num}}",
268
+ deletedSlot: "Deleted slot {{num}}",
269
+ emptySlot: "Empty slot",
270
+ noSaveFound: "No save found",
271
+ failedSave: "Failed to save",
272
+ failedLoad: "Failed to load",
273
+ failedDelete: "Failed to delete",
274
+ failedFetch: "Failed to load save slots",
275
+ controllerConnected: "Controller Connected",
276
+ controllerDisconnected: "Controller Lost",
277
+ controllerReady: "Controller ready to use",
278
+ insertCoin: "Press SHIFT to insert coin",
279
+ insertCoinTitle: "\u{1FA99} Insert Coin",
280
+ controlsSaved: "Controls saved",
281
+ controlsReset: "Controls reset to defaults"
282
+ },
283
+ modals: {
284
+ shortcuts: {
285
+ title: "Keyboard Shortcuts",
286
+ playerShortcuts: "Player Shortcuts",
287
+ overlays: "Overlays",
288
+ recording: "Recording",
289
+ showHelp: "Show Help",
290
+ perfOverlay: "Performance Overlay",
291
+ inputDisplay: "Input Display",
292
+ toggleRec: "Toggle Recording",
293
+ toggleMute: "Toggle Mute",
294
+ pressEsc: "Press ESC to close"
295
+ },
296
+ controls: {
297
+ title: "Controls",
298
+ keyboard: "Keyboard Mapping",
299
+ description: "Click a button and press a key to remap",
300
+ pressKey: "Press...",
301
+ reset: "Reset to Default",
302
+ save: "Save Controls"
303
+ },
304
+ gamepad: {
305
+ title: "Gamepad Settings",
306
+ noGamepad: "No gamepad detected",
307
+ connected: "{{count}} controller{{s}} connected",
308
+ none: "No controllers detected",
309
+ player: "Player:",
310
+ noController: "No controller detected",
311
+ pressAny: "Press any button on your gamepad to connect",
312
+ waiting: "Waiting for input...",
313
+ pressButton: "Press a button on your controller for {{button}}",
314
+ pressEsc: "Press Escape to cancel",
315
+ reset: "Reset to Default",
316
+ save: "Save Settings"
317
+ },
318
+ cheats: {
319
+ title: "Cheats",
320
+ addCheat: "Add Cheat",
321
+ available: "{{count}} cheat{{s}} available",
322
+ emptyTitle: "No cheats available",
323
+ emptyDesc: "No cheat codes found for this game",
324
+ copy: "Copy code",
325
+ active: "{{count}} cheat{{s}} active",
326
+ toggleHint: "Click a cheat to toggle it on/off"
327
+ },
328
+ saveSlots: {
329
+ title: "Save States",
330
+ saveTitle: "Save Game",
331
+ loadTitle: "Load Game",
332
+ emptySlot: "Empty Slot",
333
+ subtitleSave: "Choose a slot to save your progress",
334
+ subtitleLoad: "Select a save to restore",
335
+ loading: "Loading saves...",
336
+ locked: "Slot {{num}} Locked",
337
+ upgrade: "Upgrade to unlock more save slots",
338
+ autoSave: "Auto-Save Slot",
339
+ autoSaveDesc: "Reserved for automatic saves",
340
+ noData: "No save data",
341
+ slot: "Slot {{num}}",
342
+ footerSave: "Saves are stored in the cloud and sync across devices",
343
+ footerLoad: "Your progress will be restored to the selected save point"
344
+ },
345
+ bios: {
346
+ title: "BIOS Selection",
347
+ description: "Select a BIOS file to use for this game",
348
+ warningTitle: "Note:",
349
+ warning: "Changing BIOS requires the emulator to restart. Your unsaved progress will be lost.",
350
+ systemDefault: "System Default",
351
+ active: "Active",
352
+ defaultDesc: "Use the emulator's built-in or default BIOS",
353
+ emptyTitle: "No BIOS files found for this console.",
354
+ emptyDesc: "Upload BIOS files in your Dashboard Library.",
355
+ footer: "System Firmware Settings"
356
+ }
357
+ },
358
+ retroAchievements: {
359
+ title: "RetroAchievements",
360
+ login: "Login",
361
+ logout: "Logout",
362
+ username: "Username",
363
+ password: "Password",
364
+ hardcore: "Hardcore Mode",
365
+ achievements: "Achievements",
366
+ locked: "Locked",
367
+ unlocked: "Unlocked",
368
+ mastered: "Mastered",
369
+ identifying: "Identifying game...",
370
+ achievementsAvailable: "{{count}} achievements available",
371
+ gameNotSupported: "Connected - Game not in RA database",
372
+ connect: "Connect RetroAchievements",
373
+ connected: "RetroAchievements (connected)",
374
+ createAccount: "Create an account",
375
+ privacy: "Privacy:",
376
+ privacyText: "Your password is only used to authenticate with RetroAchievements. It is never stored.",
377
+ connecting: "Connecting...",
378
+ connectAccount: "Connect your account to track achievements",
379
+ poweredBy: "Powered by",
380
+ connectedStatus: "Connected",
381
+ yourUsername: "Your RA username",
382
+ yourPassword: "Your RA password",
383
+ usernameRequired: "Username and Password are required",
384
+ noGame: "No game loaded",
385
+ loadGame: "Load a game to see achievements",
386
+ noAchievements: "No achievements found",
387
+ notSupported: "This game may not be supported",
388
+ ptsRemaining: "{{count}} pts remaining",
389
+ filters: {
390
+ all: "All",
391
+ locked: "Locked",
392
+ unlocked: "Unlocked"
393
+ }
394
+ },
395
+ overlays: {
396
+ performance: {
397
+ title: "Stats",
398
+ fps: "FPS",
399
+ frameTime: "FT",
400
+ memory: "MEM",
401
+ core: "Core",
402
+ input: "INPUT",
403
+ active: "ACTIVE"
404
+ },
405
+ toast: {
406
+ saved: "Game Saved",
407
+ loaded: "Game Loaded",
408
+ error: "Error"
409
+ },
410
+ recording: {
411
+ started: "Recording Started",
412
+ stopped: "Recording Stopped",
413
+ saved: "Recording Saved",
414
+ paused: "PAUSED",
415
+ recording: "RECORDING",
416
+ resume: "Resume recording",
417
+ pause: "Pause recording",
418
+ stop: "Stop and save recording",
419
+ hover: "Hover for controls"
420
+ }
421
+ }
422
+ };
423
+ var KoinI18nContext = createContext(en);
424
+ function deepMerge(target, source) {
425
+ const result = { ...target };
426
+ for (const key in source) {
427
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
428
+ const sourceValue = source[key];
429
+ const targetValue = result[key];
430
+ if (sourceValue && typeof sourceValue === "object" && targetValue && typeof targetValue === "object" && !Array.isArray(sourceValue)) {
431
+ result[key] = deepMerge(targetValue, sourceValue);
432
+ } else if (sourceValue !== void 0) {
433
+ result[key] = sourceValue;
434
+ }
435
+ }
436
+ }
437
+ return result;
438
+ }
439
+ var KoinI18nProvider = ({ children, translations }) => {
440
+ const value = useMemo(() => {
441
+ if (!translations) return en;
442
+ return deepMerge(en, translations);
443
+ }, [translations]);
444
+ return /* @__PURE__ */ jsx(KoinI18nContext.Provider, { value, children });
445
+ };
446
+ function useKoinTranslation() {
447
+ return useContext(KoinI18nContext);
448
+ }
174
449
  var PlaybackControls = memo(function PlaybackControls2({
175
450
  isPaused,
176
451
  isRunning,
@@ -190,20 +465,30 @@ var PlaybackControls = memo(function PlaybackControls2({
190
465
  systemColor = "#00FF41",
191
466
  hardcoreRestrictions
192
467
  }) {
468
+ const t = useKoinTranslation();
193
469
  const hasRewindHistory = rewindBufferSize > 0;
194
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 flex-shrink-0", children: [
470
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-center gap-4 w-full sm:w-auto sm:flex-nowrap sm:gap-3 flex-shrink-0", children: [
195
471
  /* @__PURE__ */ jsx(
196
472
  ControlButton,
197
473
  {
198
474
  onClick: onPauseToggle,
199
475
  icon: !isRunning || isPaused ? Play : Pause,
200
- label: !isRunning || isPaused ? "Play" : "Pause",
476
+ label: !isRunning || isPaused ? t.controls.play : t.controls.pause,
201
477
  active: isPaused,
202
478
  disabled,
203
479
  systemColor
204
480
  }
205
481
  ),
206
- /* @__PURE__ */ jsx(ControlButton, { onClick: onRestart, icon: RotateCcw, label: "Reset", disabled, systemColor }),
482
+ /* @__PURE__ */ jsx(
483
+ ControlButton,
484
+ {
485
+ onClick: onRestart,
486
+ icon: RotateCcw,
487
+ label: t.controls.reset,
488
+ disabled,
489
+ systemColor
490
+ }
491
+ ),
207
492
  /* @__PURE__ */ jsx(SpeedMenu, { speed, onSpeedChange, disabled }),
208
493
  /* @__PURE__ */ jsxs("div", { className: "relative group", children: [
209
494
  /* @__PURE__ */ jsx(
@@ -215,7 +500,7 @@ var PlaybackControls = memo(function PlaybackControls2({
215
500
  onTouchStart: hasRewindHistory && hardcoreRestrictions?.canUseRewind !== false ? onRewindStart : void 0,
216
501
  onTouchEnd: hasRewindHistory && hardcoreRestrictions?.canUseRewind !== false ? onRewindStop : void 0,
217
502
  icon: Rewind,
218
- label: "Rewind",
503
+ label: t.controls.rewind,
219
504
  active: isRewinding,
220
505
  disabled: disabled || !hasRewindHistory || hardcoreRestrictions?.canUseRewind === false,
221
506
  systemColor
@@ -226,10 +511,10 @@ var PlaybackControls = memo(function PlaybackControls2({
226
511
  HardcoreTooltip,
227
512
  {
228
513
  show: hardcoreRestrictions?.canUseRewind === false,
229
- message: hardcoreRestrictions?.isHardcore ? "Disabled in Hardcore mode" : "Not supported on this console"
514
+ message: hardcoreRestrictions?.isHardcore ? t.common.disabledInHardcore : t.common.notSupported
230
515
  }
231
516
  ),
232
- 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: "Play for a few seconds to enable rewind" })
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 })
233
518
  ] }),
234
519
  /* @__PURE__ */ jsx("div", { className: "w-px h-8 bg-white/10 mx-1" }),
235
520
  /* @__PURE__ */ jsx(
@@ -432,7 +717,8 @@ var SaveLoadControls = memo(function SaveLoadControls2({
432
717
  autoSavePaused = false,
433
718
  onAutoSaveToggle
434
719
  }) {
435
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-3 sm:px-4 border-x border-white/10 flex-shrink-0", children: [
720
+ const t = useKoinTranslation();
721
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-center gap-4 w-full sm:w-auto sm:flex-nowrap sm:gap-3 px-3 sm:px-4 sm:border-x sm:border-white/10 flex-shrink-0", children: [
436
722
  autoSaveEnabled && /* @__PURE__ */ jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsx(
437
723
  AutoSaveIndicator,
438
724
  {
@@ -449,12 +735,18 @@ var SaveLoadControls = memo(function SaveLoadControls2({
449
735
  {
450
736
  onClick: hardcoreRestrictions?.canUseSaveStates === false ? void 0 : onSave,
451
737
  icon: Save,
452
- label: "Save",
738
+ label: t.controls.save,
453
739
  disabled: disabled || saveDisabled || hardcoreRestrictions?.canUseSaveStates === false,
454
740
  systemColor
455
741
  }
456
742
  ),
457
- /* @__PURE__ */ jsx(HardcoreTooltip, { show: hardcoreRestrictions?.canUseSaveStates === false })
743
+ /* @__PURE__ */ jsx(
744
+ HardcoreTooltip,
745
+ {
746
+ show: hardcoreRestrictions?.canUseSaveStates === false,
747
+ message: hardcoreRestrictions?.isHardcore ? t.common.disabledInHardcore : t.common.notSupported
748
+ }
749
+ )
458
750
  ] }),
459
751
  /* @__PURE__ */ jsxs("div", { className: "relative group flex-shrink-0", children: [
460
752
  /* @__PURE__ */ jsx(
@@ -462,19 +754,25 @@ var SaveLoadControls = memo(function SaveLoadControls2({
462
754
  {
463
755
  onClick: hardcoreRestrictions?.canUseSaveStates === false ? void 0 : onLoad,
464
756
  icon: Download,
465
- label: "Load",
757
+ label: t.controls.load,
466
758
  disabled: disabled || loadDisabled || hardcoreRestrictions?.canUseSaveStates === false,
467
759
  systemColor
468
760
  }
469
761
  ),
470
- /* @__PURE__ */ jsx(HardcoreTooltip, { show: hardcoreRestrictions?.canUseSaveStates === false })
762
+ /* @__PURE__ */ jsx(
763
+ HardcoreTooltip,
764
+ {
765
+ show: hardcoreRestrictions?.canUseSaveStates === false,
766
+ message: hardcoreRestrictions?.isHardcore ? t.common.disabledInHardcore : t.common.notSupported
767
+ }
768
+ )
471
769
  ] }),
472
770
  /* @__PURE__ */ jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsx(
473
771
  ControlButton,
474
772
  {
475
773
  onClick: onScreenshot,
476
774
  icon: Camera,
477
- label: "Snap",
775
+ label: t.controls.snap,
478
776
  disabled: disabled || saveDisabled,
479
777
  systemColor
480
778
  }
@@ -485,7 +783,7 @@ var SaveLoadControls = memo(function SaveLoadControls2({
485
783
  onClick: onRecordToggle,
486
784
  disabled,
487
785
  className: `flex flex-col items-center gap-1 px-2 sm:px-3 py-2 rounded-lg transition-all duration-200 hover:bg-white/10 ${disabled ? "opacity-50 cursor-not-allowed" : ""}`,
488
- title: isRecording ? "Stop recording" : "Start recording",
786
+ title: isRecording ? t.controls.stopRecord : t.controls.startRecord,
489
787
  children: [
490
788
  /* @__PURE__ */ jsx("div", { className: "relative", children: isRecording ? /* @__PURE__ */ jsxs(Fragment, { children: [
491
789
  /* @__PURE__ */ jsx(Video, { size: 20, className: "text-red-500" }),
@@ -496,7 +794,7 @@ var SaveLoadControls = memo(function SaveLoadControls2({
496
794
  {
497
795
  className: "text-[10px] font-bold uppercase tracking-wider",
498
796
  style: { color: isRecording ? "#FF3333" : void 0 },
499
- children: isRecording ? "REC" : "Rec"
797
+ children: t.controls.rec
500
798
  }
501
799
  )
502
800
  ]
@@ -629,6 +927,9 @@ function RAButton({
629
927
  achievementCount,
630
928
  className = ""
631
929
  }) {
930
+ const t = useKoinTranslation();
931
+ const title = isGameFound ? `${t.retroAchievements.title} (${achievementCount} achievements)` : isConnected ? t.retroAchievements.connected : t.retroAchievements.title;
932
+ const tooltip = isIdentifying ? t.retroAchievements.identifying : isGameFound ? t.retroAchievements.achievementsAvailable.replace("{{count}}", achievementCount.toString()) : isConnected ? t.retroAchievements.gameNotSupported : t.retroAchievements.connect;
632
933
  return /* @__PURE__ */ jsxs("div", { className: `relative group ${className}`, children: [
633
934
  /* @__PURE__ */ jsxs(
634
935
  "button",
@@ -641,7 +942,7 @@ function RAButton({
641
942
  select-none
642
943
  ${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"}
643
944
  `,
644
- title: isGameFound ? `RetroAchievements (${achievementCount} achievements)` : isConnected ? "RetroAchievements (connected)" : "RetroAchievements",
945
+ title,
645
946
  children: [
646
947
  isIdentifying ? /* @__PURE__ */ jsx(Loader2, { size: 20, className: "animate-spin text-yellow-400" }) : /* @__PURE__ */ jsx(
647
948
  Trophy,
@@ -662,13 +963,14 @@ function RAButton({
662
963
  absolute -top-1 -right-1 w-3 h-3 rounded-full border-2 border-black
663
964
  ${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)]"}
664
965
  `, children: isGameFound && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-green-400 rounded-full animate-ping opacity-50" }) }),
665
- /* @__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: isIdentifying ? "Identifying game..." : isGameFound ? `${achievementCount} achievements available` : isConnected ? "Connected - Game not in RA database" : "Connect RetroAchievements" })
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 })
666
967
  ] });
667
968
  }
668
969
  var SettingsControls = memo(function SettingsControls2({
669
970
  onFullscreen,
670
971
  onControls,
671
972
  onGamepadSettings,
973
+ onSettings,
672
974
  onCheats,
673
975
  onRetroAchievements,
674
976
  onShowShortcuts,
@@ -685,8 +987,10 @@ var SettingsControls = memo(function SettingsControls2({
685
987
  raAchievementCount = 0,
686
988
  raIsIdentifying = false
687
989
  }) {
990
+ const t = useKoinTranslation();
688
991
  const gamepadIndicatorText = gamepadCount > 0 ? Array.from({ length: gamepadCount }, (_, i) => `P${i + 1}`).join(" ") : "";
689
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 flex-shrink-0", children: [
992
+ const gamepadConnectedTitle = t.controls.gamepadConnected.replace("{{count}}", gamepadCount.toString()).replace("{{plural}}", gamepadCount > 1 ? "s" : "");
993
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-center gap-4 w-full sm:w-auto sm:flex-nowrap sm:gap-3 flex-shrink-0", children: [
690
994
  /* @__PURE__ */ jsx(
691
995
  ShaderDropdown_default,
692
996
  {
@@ -697,15 +1001,15 @@ var SettingsControls = memo(function SettingsControls2({
697
1001
  disabled
698
1002
  }
699
1003
  ),
700
- onShowShortcuts && /* @__PURE__ */ jsx(ControlButton, { onClick: onShowShortcuts, icon: HelpCircle, label: "Help", disabled, className: "hidden sm:flex", systemColor }),
701
- /* @__PURE__ */ jsx(ControlButton, { onClick: onFullscreen, icon: Maximize, label: "Full", disabled, className: "hidden sm:flex", systemColor }),
702
- /* @__PURE__ */ jsx(ControlButton, { onClick: onControls, icon: Gamepad2, label: "Keys", disabled, className: "hidden sm:flex", systemColor }),
1004
+ onShowShortcuts && /* @__PURE__ */ jsx(ControlButton, { onClick: onShowShortcuts, icon: HelpCircle, label: t.controls.help, disabled, className: "hidden sm:flex", systemColor }),
1005
+ /* @__PURE__ */ jsx(ControlButton, { onClick: onFullscreen, icon: Maximize, label: t.controls.full, disabled, className: "hidden sm:flex", systemColor }),
1006
+ /* @__PURE__ */ jsx(ControlButton, { onClick: onControls, icon: Gamepad2, label: t.controls.keys, disabled, className: "hidden sm:flex", systemColor }),
703
1007
  gamepadCount > 0 ? /* @__PURE__ */ jsxs(
704
1008
  "button",
705
1009
  {
706
1010
  onClick: onGamepadSettings,
707
1011
  className: "relative group flex flex-col items-center gap-1 px-2 sm:px-3 py-2 rounded-lg transition-all duration-200 hover:bg-white/10 flex-shrink-0 hidden sm:flex",
708
- title: `${gamepadCount} controller${gamepadCount > 1 ? "s" : ""} connected - click to configure`,
1012
+ title: gamepadConnectedTitle,
709
1013
  children: [
710
1014
  /* @__PURE__ */ jsxs("div", { className: "relative", children: [
711
1015
  /* @__PURE__ */ jsx(Joystick, { size: 20, style: { color: systemColor }, className: "transition-transform group-hover:scale-110" }),
@@ -726,15 +1030,15 @@ var SettingsControls = memo(function SettingsControls2({
726
1030
  "button",
727
1031
  {
728
1032
  onClick: onGamepadSettings,
729
- className: "relative group flex-col items-center gap-1 px-3 py-2 transition-all duration-200 flex-shrink-0 hidden sm:flex",
730
- title: "No controller detected - press any button on your gamepad to connect",
1033
+ className: "relative group flex-col items-center gap-1 px-3 py-2 transition-all duration-200 flex-shrink-0",
1034
+ title: t.controls.noGamepad,
731
1035
  style: {
732
1036
  border: "2px dashed #6b7280",
733
1037
  backgroundColor: "transparent"
734
1038
  },
735
1039
  children: /* @__PURE__ */ jsxs("div", { className: "relative flex items-center gap-2", children: [
736
1040
  /* @__PURE__ */ jsx(Gamepad2, { size: 18, className: "text-gray-400 transition-transform group-hover:scale-110 group-hover:text-white" }),
737
- /* @__PURE__ */ jsx("span", { className: "text-[10px] font-bold uppercase tracking-wider text-gray-400 group-hover:text-white whitespace-nowrap", children: "Press" }),
1041
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] font-bold uppercase tracking-wider text-gray-400 group-hover:text-white whitespace-nowrap", children: t.controls.press }),
738
1042
  /* @__PURE__ */ jsx("div", { className: "w-5 h-5 rounded-full border-2 border-gray-400 group-hover:border-white flex items-center justify-center animate-pulse", children: /* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full bg-gray-400 group-hover:bg-white" }) })
739
1043
  ] })
740
1044
  }
@@ -746,12 +1050,18 @@ var SettingsControls = memo(function SettingsControls2({
746
1050
  {
747
1051
  onClick: hardcoreRestrictions?.canUseCheats === false ? void 0 : onCheats,
748
1052
  icon: Code,
749
- label: "Cheats",
1053
+ label: t.settings.cheats,
750
1054
  disabled: disabled || hardcoreRestrictions?.canUseCheats === false,
751
1055
  systemColor
752
1056
  }
753
1057
  ),
754
- /* @__PURE__ */ jsx(HardcoreTooltip, { show: hardcoreRestrictions?.canUseCheats === false })
1058
+ /* @__PURE__ */ jsx(
1059
+ HardcoreTooltip,
1060
+ {
1061
+ show: hardcoreRestrictions?.canUseCheats === false,
1062
+ message: hardcoreRestrictions?.isHardcore ? t.common.disabledInHardcore : t.common.notSupported
1063
+ }
1064
+ )
755
1065
  ] }),
756
1066
  /* @__PURE__ */ jsx(
757
1067
  RAButton,
@@ -765,14 +1075,26 @@ var SettingsControls = memo(function SettingsControls2({
765
1075
  className: "hidden sm:flex"
766
1076
  }
767
1077
  ),
1078
+ onSettings && /* @__PURE__ */ jsx(
1079
+ ControlButton,
1080
+ {
1081
+ onClick: onSettings,
1082
+ icon: Settings,
1083
+ label: t.settings.title,
1084
+ disabled,
1085
+ systemColor,
1086
+ className: "hidden sm:flex"
1087
+ }
1088
+ ),
768
1089
  /* @__PURE__ */ jsx("div", { className: "w-px h-8 bg-white/10 mx-2 hidden sm:block" }),
769
- /* @__PURE__ */ jsx(ControlButton, { onClick: onExit, icon: Power, label: "Exit", danger: true, disabled, systemColor })
1090
+ /* @__PURE__ */ jsx(ControlButton, { onClick: onExit, icon: Power, label: t.settings.exit, danger: true, disabled, systemColor })
770
1091
  ] });
771
1092
  });
772
1093
  var MobileControlDrawer = memo(function MobileControlDrawer2({
773
1094
  children,
774
1095
  systemColor = "#00FF41"
775
1096
  }) {
1097
+ const t = useKoinTranslation();
776
1098
  const [isExpanded, setIsExpanded] = useState(false);
777
1099
  useEffect(() => {
778
1100
  if (isExpanded) {
@@ -807,7 +1129,7 @@ var MobileControlDrawer = memo(function MobileControlDrawer2({
807
1129
  boxShadow: isExpanded ? `0 0 20px ${systemColor}60, 0 4px 20px rgba(0,0,0,0.5)` : "0 4px 20px rgba(0,0,0,0.5)",
808
1130
  borderColor: isExpanded ? systemColor : void 0
809
1131
  },
810
- "aria-label": isExpanded ? "Close menu" : "Open menu",
1132
+ "aria-label": isExpanded ? t.controls.menuClose : t.controls.menuOpen,
811
1133
  children: /* @__PURE__ */ jsxs(
812
1134
  "svg",
813
1135
  {
@@ -848,7 +1170,7 @@ var MobileControlDrawer = memo(function MobileControlDrawer2({
848
1170
  /* @__PURE__ */ jsx(
849
1171
  "div",
850
1172
  {
851
- className: "relative flex flex-wrap items-center justify-center gap-3 px-4 py-6 pb-20",
1173
+ className: "relative flex flex-col items-center px-4 py-6 pb-20",
852
1174
  style: { paddingBottom: "calc(env(safe-area-inset-bottom, 20px) + 80px)" },
853
1175
  children
854
1176
  }
@@ -896,6 +1218,7 @@ var PlayerControls = memo(function PlayerControls2({
896
1218
  systemColor = "#00FF41",
897
1219
  gamepadCount = 0,
898
1220
  onGamepadSettings,
1221
+ onSettings,
899
1222
  volume = 100,
900
1223
  isMuted = false,
901
1224
  onVolumeChange,
@@ -928,6 +1251,7 @@ var PlayerControls = memo(function PlayerControls2({
928
1251
  hardcoreRestrictions
929
1252
  }
930
1253
  ),
1254
+ /* @__PURE__ */ jsx("div", { className: "w-full h-px bg-white/10 sm:hidden my-4" }),
931
1255
  /* @__PURE__ */ jsx(
932
1256
  SaveLoadControls,
933
1257
  {
@@ -948,12 +1272,14 @@ var PlayerControls = memo(function PlayerControls2({
948
1272
  onAutoSaveToggle
949
1273
  }
950
1274
  ),
1275
+ /* @__PURE__ */ jsx("div", { className: "w-full h-px bg-white/10 sm:hidden my-4" }),
951
1276
  /* @__PURE__ */ jsx(
952
1277
  SettingsControls,
953
1278
  {
954
1279
  onFullscreen,
955
1280
  onControls,
956
1281
  onGamepadSettings,
1282
+ onSettings,
957
1283
  onCheats,
958
1284
  onRetroAchievements,
959
1285
  onShowShortcuts,
@@ -974,7 +1300,7 @@ var PlayerControls = memo(function PlayerControls2({
974
1300
  ] });
975
1301
  return /* @__PURE__ */ jsxs(Fragment, { children: [
976
1302
  /* @__PURE__ */ jsx(MobileControlDrawer_default, { systemColor, children: controlsContent }),
977
- /* @__PURE__ */ jsx("div", { className: "hidden sm:flex w-full items-center justify-between gap-2 px-4 py-2 bg-black backdrop-blur-sm border-t border-white/10 shrink-0 overflow-x-auto", children: controlsContent })
1303
+ /* @__PURE__ */ jsx("div", { className: "hidden sm:flex w-full bg-black/90 backdrop-blur-md border-t border-white/10 shrink-0 z-50 overflow-x-auto no-scrollbar", children: /* @__PURE__ */ jsx("div", { className: "flex items-center min-w-max mx-auto gap-4 px-8 py-4", children: controlsContent }) })
978
1304
  ] });
979
1305
  });
980
1306
  var PlayerControls_default = PlayerControls;
@@ -1137,6 +1463,7 @@ var PerformanceOverlay = memo(function PerformanceOverlay2({
1137
1463
  coreName = "Unknown",
1138
1464
  systemColor = "#00FF41"
1139
1465
  }) {
1466
+ const t = useKoinTranslation();
1140
1467
  const [fps, setFps] = useState(0);
1141
1468
  const [frameTime, setFrameTime] = useState(0);
1142
1469
  const frameTimesRef = useRef([]);
@@ -1188,12 +1515,12 @@ var PerformanceOverlay = memo(function PerformanceOverlay2({
1188
1515
  },
1189
1516
  children: [
1190
1517
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
1191
- /* @__PURE__ */ jsx("span", { className: "opacity-60", children: "FPS" }),
1518
+ /* @__PURE__ */ jsx("span", { className: "opacity-60 text-[10px] uppercase", children: t.overlays.performance.fps }),
1192
1519
  /* @__PURE__ */ jsx("span", { className: "font-bold", children: fps })
1193
1520
  ] }),
1194
1521
  /* @__PURE__ */ jsx("div", { className: "w-px h-3 bg-white/20" }),
1195
1522
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
1196
- /* @__PURE__ */ jsx("span", { className: "opacity-60", children: "Frame" }),
1523
+ /* @__PURE__ */ jsx("span", { className: "opacity-60 text-[10px] uppercase", children: t.overlays.performance.frameTime }),
1197
1524
  /* @__PURE__ */ jsxs("span", { className: "font-bold", children: [
1198
1525
  frameTime,
1199
1526
  "ms"
@@ -1201,7 +1528,7 @@ var PerformanceOverlay = memo(function PerformanceOverlay2({
1201
1528
  ] }),
1202
1529
  /* @__PURE__ */ jsx("div", { className: "w-px h-3 bg-white/20" }),
1203
1530
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
1204
- /* @__PURE__ */ jsx("span", { className: "opacity-60", children: "Core" }),
1531
+ /* @__PURE__ */ jsx("span", { className: "opacity-60 text-[10px] uppercase", children: t.overlays.performance.core }),
1205
1532
  /* @__PURE__ */ jsx("span", { className: "font-bold text-white/80", children: coreName })
1206
1533
  ] })
1207
1534
  ]
@@ -1375,6 +1702,7 @@ var RecordingIndicator = memo(function RecordingIndicator2({
1375
1702
  onStop,
1376
1703
  systemColor = "#FF3333"
1377
1704
  }) {
1705
+ const t = useKoinTranslation();
1378
1706
  const [isHovered, setIsHovered] = useState(false);
1379
1707
  if (!isRecording) return null;
1380
1708
  const minutes = Math.floor(duration / 60);
@@ -1422,7 +1750,7 @@ var RecordingIndicator = memo(function RecordingIndicator2({
1422
1750
  {
1423
1751
  className: "text-xs font-bold",
1424
1752
  style: { color: isPaused ? "#FFA500" : "#FF3333" },
1425
- children: isPaused ? "PAUSED" : "RECORDING"
1753
+ children: isPaused ? t.overlays.recording.paused : t.overlays.recording.recording
1426
1754
  }
1427
1755
  ),
1428
1756
  /* @__PURE__ */ jsx("span", { className: "text-[10px] text-white/60", children: timeString })
@@ -1433,7 +1761,7 @@ var RecordingIndicator = memo(function RecordingIndicator2({
1433
1761
  {
1434
1762
  onClick: onResume,
1435
1763
  className: "p-1.5 rounded hover:bg-white/20 transition-colors",
1436
- title: "Resume recording",
1764
+ title: t.overlays.recording.resume,
1437
1765
  children: /* @__PURE__ */ jsx(Play, { size: 14, className: "text-orange-400", fill: "#FFA500" })
1438
1766
  }
1439
1767
  ) : /* @__PURE__ */ jsx(
@@ -1441,7 +1769,7 @@ var RecordingIndicator = memo(function RecordingIndicator2({
1441
1769
  {
1442
1770
  onClick: onPause,
1443
1771
  className: "p-1.5 rounded hover:bg-white/20 transition-colors",
1444
- title: "Pause recording",
1772
+ title: t.overlays.recording.pause,
1445
1773
  children: /* @__PURE__ */ jsx(Pause, { size: 14, className: "text-white/80" })
1446
1774
  }
1447
1775
  ),
@@ -1450,7 +1778,7 @@ var RecordingIndicator = memo(function RecordingIndicator2({
1450
1778
  {
1451
1779
  onClick: onStop,
1452
1780
  className: "p-1.5 rounded hover:bg-red-500/30 transition-colors flex items-center gap-1",
1453
- title: "Stop and save recording",
1781
+ title: t.overlays.recording.stop,
1454
1782
  children: [
1455
1783
  /* @__PURE__ */ jsx(Square, { size: 12, fill: "#FF3333", className: "text-red-500" }),
1456
1784
  /* @__PURE__ */ jsx(Download, { size: 12, className: "text-white/60" })
@@ -1461,39 +1789,40 @@ var RecordingIndicator = memo(function RecordingIndicator2({
1461
1789
  ]
1462
1790
  }
1463
1791
  ),
1464
- !isHovered && /* @__PURE__ */ jsx("div", { className: "text-[9px] text-white/40 text-center mt-1", children: "Hover for controls" })
1792
+ !isHovered && /* @__PURE__ */ jsx("div", { className: "text-[9px] text-white/40 text-center mt-1", children: t.overlays.recording.hover })
1465
1793
  ]
1466
1794
  }
1467
1795
  );
1468
1796
  });
1469
1797
  var RecordingIndicator_default = RecordingIndicator;
1470
- var SHORTCUTS = [
1471
- {
1472
- section: "Overlays",
1473
- items: [
1474
- { key: "F1", description: "Show this help" },
1475
- { key: "F3", description: "Performance Overlay (FPS)" },
1476
- { key: "F4", description: "Input Display" }
1477
- ]
1478
- },
1479
- {
1480
- section: "Recording",
1481
- items: [
1482
- { key: "F5", description: "Start/Stop Recording" }
1483
- ]
1484
- },
1485
- {
1486
- section: "Audio",
1487
- items: [
1488
- { key: "F9", description: "Toggle Mute" }
1489
- ]
1490
- }
1491
- ];
1492
1798
  var ShortcutsModal = memo(function ShortcutsModal2({
1493
1799
  isOpen,
1494
1800
  onClose,
1495
1801
  systemColor = "#00FF41"
1496
1802
  }) {
1803
+ const t = useKoinTranslation();
1804
+ const shortcuts = useMemo(() => [
1805
+ {
1806
+ section: t.modals.shortcuts.overlays,
1807
+ items: [
1808
+ { key: "F1", description: t.modals.shortcuts.showHelp },
1809
+ { key: "F3", description: t.modals.shortcuts.perfOverlay },
1810
+ { key: "F4", description: t.modals.shortcuts.inputDisplay }
1811
+ ]
1812
+ },
1813
+ {
1814
+ section: t.modals.shortcuts.recording,
1815
+ items: [
1816
+ { key: "F5", description: t.modals.shortcuts.toggleRec }
1817
+ ]
1818
+ },
1819
+ {
1820
+ section: t.settings.audio,
1821
+ items: [
1822
+ { key: "F9", description: t.modals.shortcuts.toggleMute }
1823
+ ]
1824
+ }
1825
+ ], [t]);
1497
1826
  if (!isOpen) return null;
1498
1827
  return /* @__PURE__ */ jsx(
1499
1828
  "div",
@@ -1515,7 +1844,7 @@ var ShortcutsModal = memo(function ShortcutsModal2({
1515
1844
  children: [
1516
1845
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1517
1846
  /* @__PURE__ */ jsx(Keyboard, { size: 18, style: { color: systemColor } }),
1518
- /* @__PURE__ */ jsx("span", { className: "font-bold text-white", children: "Player Shortcuts" })
1847
+ /* @__PURE__ */ jsx("span", { className: "font-bold text-white", children: t.modals.shortcuts.playerShortcuts })
1519
1848
  ] }),
1520
1849
  /* @__PURE__ */ jsx(
1521
1850
  "button",
@@ -1529,7 +1858,7 @@ var ShortcutsModal = memo(function ShortcutsModal2({
1529
1858
  }
1530
1859
  ),
1531
1860
  /* @__PURE__ */ jsxs("div", { className: "p-4 space-y-3", children: [
1532
- SHORTCUTS.map(({ section, items }) => /* @__PURE__ */ jsxs("div", { children: [
1861
+ shortcuts.map(({ section, items }) => /* @__PURE__ */ jsxs("div", { children: [
1533
1862
  /* @__PURE__ */ jsx(
1534
1863
  "h3",
1535
1864
  {
@@ -1563,20 +1892,16 @@ var ShortcutsModal = memo(function ShortcutsModal2({
1563
1892
  ] }, section)),
1564
1893
  /* @__PURE__ */ jsxs("div", { className: "pt-2 border-t border-white/10 text-xs text-white/40", children: [
1565
1894
  "Game controls can be configured in ",
1566
- /* @__PURE__ */ jsx("strong", { className: "text-white/60", children: "Keys" }),
1895
+ /* @__PURE__ */ jsx("strong", { className: "text-white/60", children: t.controls.keys }),
1567
1896
  " settings."
1568
1897
  ] })
1569
1898
  ] }),
1570
- /* @__PURE__ */ jsxs(
1899
+ /* @__PURE__ */ jsx(
1571
1900
  "div",
1572
1901
  {
1573
1902
  className: "px-4 py-2 text-center text-xs text-white/40 border-t",
1574
1903
  style: { borderColor: `${systemColor}20` },
1575
- children: [
1576
- "Press ",
1577
- /* @__PURE__ */ jsx("kbd", { className: "px-1 bg-white/10 rounded font-mono", children: "ESC" }),
1578
- " to close"
1579
- ]
1904
+ children: t.modals.shortcuts.pressEsc
1580
1905
  }
1581
1906
  )
1582
1907
  ]
@@ -1753,92 +2078,175 @@ function useMobile() {
1753
2078
 
1754
2079
  // src/components/VirtualController/layouts.ts
1755
2080
  var BUTTON_SMALL = 44;
1756
- var BUTTON_MEDIUM = 52;
1757
- var BUTTON_LARGE = 60;
1758
- var NES_LAYOUT = {
1759
- console: "NES",
2081
+ var BUTTON_MEDIUM = 56;
2082
+ var BUTTON_LARGE = 68;
2083
+ var START_SELECT_Y = 8;
2084
+ var SELECT_X = 38;
2085
+ var START_X = 62;
2086
+ var DPAD_BUTTONS = [
2087
+ { type: "up", label: "\u2191", x: 14, y: 55, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2088
+ { type: "down", label: "\u2193", x: 14, y: 70, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2089
+ { type: "left", label: "\u2190", x: 6, y: 62.5, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2090
+ { type: "right", label: "\u2192", x: 22, y: 62.5, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true }
2091
+ ];
2092
+ var TWO_BUTTON_LAYOUT = {
2093
+ console: "2BUTTON",
1760
2094
  buttons: [
1761
- // D-pad - y:55-70 to stay above control bar
1762
- { type: "up", label: "\u2191", x: 12, y: 55, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
1763
- { type: "down", label: "\u2193", x: 12, y: 70, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
1764
- { type: "left", label: "\u2190", x: 5, y: 62, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
1765
- { type: "right", label: "\u2192", x: 19, y: 62, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
1766
- // Action buttons
1767
- { type: "b", label: "B", x: 80, y: 64, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
1768
- { type: "a", label: "A", x: 92, y: 56, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
1769
- // System buttons - top center
1770
- { type: "select", label: "SEL", x: 38, y: 8, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true },
1771
- { type: "start", label: "START", x: 62, y: 8, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true }
2095
+ ...DPAD_BUTTONS,
2096
+ { type: "b", label: "B", x: 78, y: 66, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
2097
+ { type: "a", label: "A", x: 90, y: 56, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
2098
+ { type: "select", label: "SELECT", x: SELECT_X, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" },
2099
+ { type: "start", label: "START", x: START_X, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" }
1772
2100
  ]
1773
2101
  };
1774
2102
  var SNES_LAYOUT = {
1775
2103
  console: "SNES",
1776
2104
  buttons: [
1777
- { type: "up", label: "\u2191", x: 12, y: 45, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
1778
- { type: "down", label: "\u2193", x: 12, y: 60, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
1779
- { type: "left", label: "\u2190", x: 5, y: 52, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
1780
- { type: "right", label: "\u2192", x: 19, y: 52, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
1781
- { type: "y", label: "Y", x: 76, y: 45, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
1782
- { type: "x", label: "X", x: 88, y: 37, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
1783
- { type: "b", label: "B", x: 88, y: 53, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
1784
- { type: "a", label: "A", x: 96, y: 45, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
1785
- { type: "l", label: "L", x: 8, y: 18, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true },
1786
- { type: "r", label: "R", x: 92, y: 18, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true },
1787
- { type: "select", label: "SEL", x: 38, y: 8, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true },
1788
- { type: "start", label: "START", x: 62, y: 8, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true }
2105
+ ...DPAD_BUTTONS,
2106
+ // Diamond: Y-X-B-A (left-top-bottom-right)
2107
+ { type: "y", label: "Y", x: 74, y: 58, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2108
+ { type: "x", label: "X", x: 84, y: 46, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2109
+ { type: "b", label: "B", x: 84, y: 70, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2110
+ { type: "a", label: "A", x: 94, y: 58, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2111
+ // Shoulders
2112
+ { type: "l", label: "L", x: 8, y: 20, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
2113
+ { type: "r", label: "R", x: 92, y: 20, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
2114
+ { type: "select", label: "SELECT", x: SELECT_X, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" },
2115
+ { type: "start", label: "START", x: START_X, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" }
1789
2116
  ]
1790
2117
  };
1791
- var GB_LAYOUT = {
1792
- console: "GB",
2118
+ var GBA_LAYOUT = {
2119
+ console: "GBA",
1793
2120
  buttons: [
1794
- { type: "up", label: "\u2191", x: 12, y: 55, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
1795
- { type: "down", label: "\u2193", x: 12, y: 70, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
1796
- { type: "left", label: "\u2190", x: 5, y: 62, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
1797
- { type: "right", label: "\u2192", x: 19, y: 62, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
1798
- { type: "b", label: "B", x: 80, y: 64, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
1799
- { type: "a", label: "A", x: 92, y: 56, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
1800
- { type: "select", label: "SEL", x: 38, y: 8, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true },
1801
- { type: "start", label: "START", x: 62, y: 8, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true }
2121
+ ...DPAD_BUTTONS,
2122
+ { type: "b", label: "B", x: 80, y: 62, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
2123
+ { type: "a", label: "A", x: 92, y: 52, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
2124
+ { type: "l", label: "L", x: 8, y: 20, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
2125
+ { type: "r", label: "R", x: 92, y: 20, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
2126
+ { type: "select", label: "SELECT", x: SELECT_X, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" },
2127
+ { type: "start", label: "START", x: START_X, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" }
1802
2128
  ]
1803
2129
  };
1804
- var GBA_LAYOUT = {
1805
- console: "GBA",
2130
+ var SIX_BUTTON_LAYOUT = {
2131
+ console: "6BUTTON",
2132
+ buttons: [
2133
+ ...DPAD_BUTTONS,
2134
+ // Top row (X, Y, Z) → mapped to L, X, R
2135
+ { type: "l", label: "X", x: 74, y: 48, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2136
+ { type: "x", label: "Y", x: 84, y: 44, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2137
+ { type: "r", label: "Z", x: 94, y: 40, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2138
+ // Bottom row (A, B, C) → mapped to Y, B, A
2139
+ { type: "y", label: "A", x: 72, y: 64, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2140
+ { type: "b", label: "B", x: 82, y: 60, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2141
+ { type: "a", label: "C", x: 92, y: 56, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2142
+ { type: "start", label: "START", x: 50, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" }
2143
+ ]
2144
+ };
2145
+ var SATURN_LAYOUT = {
2146
+ console: "SATURN",
2147
+ buttons: [
2148
+ ...DPAD_BUTTONS,
2149
+ // Face buttons (same as 6-button but uses standard buttons)
2150
+ // X/Y/Z on top
2151
+ { type: "x", label: "X", x: 74, y: 48, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2152
+ { type: "y", label: "Y", x: 84, y: 44, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2153
+ { type: "l", label: "Z", x: 94, y: 40, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2154
+ // A/B/C on bottom
2155
+ { type: "b", label: "A", x: 72, y: 64, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2156
+ { type: "a", label: "B", x: 82, y: 60, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2157
+ { type: "r", label: "C", x: 92, y: 56, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2158
+ // Triggers (L2/R2 for Saturn L/R)
2159
+ { type: "l2", label: "L", x: 8, y: 20, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
2160
+ { type: "r2", label: "R", x: 92, y: 20, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
2161
+ { type: "start", label: "START", x: 50, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" }
2162
+ ]
2163
+ };
2164
+ var NEOGEO_LAYOUT = {
2165
+ console: "NEOGEO",
2166
+ buttons: [
2167
+ ...DPAD_BUTTONS,
2168
+ // Curved A-B-C-D layout (maps to b-a-y-x for RetroPad)
2169
+ { type: "b", label: "A", x: 68, y: 68, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
2170
+ { type: "a", label: "B", x: 78, y: 62, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
2171
+ { type: "y", label: "C", x: 88, y: 56, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
2172
+ { type: "x", label: "D", x: 96, y: 48, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
2173
+ { type: "select", label: "COIN", x: SELECT_X, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" },
2174
+ { type: "start", label: "START", x: START_X, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" }
2175
+ ]
2176
+ };
2177
+ var PSX_LAYOUT = {
2178
+ console: "PSX",
2179
+ buttons: [
2180
+ ...DPAD_BUTTONS,
2181
+ // PS symbols: △◯✕□ → y, b, a, x (triangle-circle-cross-square)
2182
+ { type: "y", label: "\u25B3", x: 84, y: 46, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2183
+ { type: "b", label: "\u25CB", x: 94, y: 58, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2184
+ { type: "a", label: "\u2715", x: 84, y: 70, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2185
+ { type: "x", label: "\u25A1", x: 74, y: 58, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2186
+ // Shoulders
2187
+ { type: "l", label: "L1", x: 8, y: 25, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
2188
+ { type: "r", label: "R1", x: 92, y: 25, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
2189
+ { type: "l2", label: "L2", x: 8, y: 15, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "rect" },
2190
+ { type: "r2", label: "R2", x: 92, y: 15, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "rect" },
2191
+ { type: "select", label: "SELECT", x: SELECT_X, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" },
2192
+ { type: "start", label: "START", x: START_X, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" }
2193
+ ]
2194
+ };
2195
+ var N64_LAYOUT = {
2196
+ console: "N64",
1806
2197
  buttons: [
1807
- { type: "up", label: "\u2191", x: 12, y: 45, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
1808
- { type: "down", label: "\u2193", x: 12, y: 60, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
1809
- { type: "left", label: "\u2190", x: 5, y: 52, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
1810
- { type: "right", label: "\u2192", x: 19, y: 52, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
1811
- { type: "b", label: "B", x: 82, y: 55, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
1812
- { type: "a", label: "A", x: 94, y: 45, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
1813
- { type: "l", label: "L", x: 8, y: 18, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true },
1814
- { type: "r", label: "R", x: 92, y: 18, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true },
1815
- { type: "select", label: "SEL", x: 38, y: 8, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true },
1816
- { type: "start", label: "START", x: 62, y: 8, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true }
2198
+ ...DPAD_BUTTONS,
2199
+ // A/B
2200
+ { type: "a", label: "A", x: 80, y: 68, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
2201
+ { type: "b", label: "B", x: 72, y: 76, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2202
+ // C-buttons (use x/y as C-left/C-up, l2/r2 as C-down/C-right)
2203
+ { type: "y", label: "C\u2191", x: 92, y: 50, size: 36, showInPortrait: true, showInLandscape: true },
2204
+ { type: "x", label: "C\u2190", x: 86, y: 56, size: 36, showInPortrait: true, showInLandscape: true },
2205
+ { type: "l2", label: "C\u2193", x: 92, y: 62, size: 36, showInPortrait: true, showInLandscape: true },
2206
+ { type: "r2", label: "C\u2192", x: 98, y: 56, size: 36, showInPortrait: true, showInLandscape: true },
2207
+ // Shoulders
2208
+ { type: "l", label: "L", x: 8, y: 20, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
2209
+ { type: "r", label: "R", x: 92, y: 20, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
2210
+ // Z trigger (use select as workaround since it's a unique button)
2211
+ { type: "select", label: "Z", x: 8, y: 35, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
2212
+ { type: "start", label: "START", x: 50, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" }
1817
2213
  ]
1818
2214
  };
1819
- var GENESIS_LAYOUT = {
1820
- console: "GENESIS",
2215
+ var DREAMCAST_LAYOUT = {
2216
+ console: "DREAMCAST",
1821
2217
  buttons: [
1822
- { type: "up", label: "\u2191", x: 12, y: 45, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
1823
- { type: "down", label: "\u2193", x: 12, y: 60, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
1824
- { type: "left", label: "\u2190", x: 5, y: 52, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
1825
- { type: "right", label: "\u2192", x: 19, y: 52, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
1826
- { type: "a", label: "A", x: 74, y: 56, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
1827
- { type: "b", label: "B", x: 85, y: 48, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
1828
- { type: "c", label: "C", x: 96, y: 40, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
1829
- { type: "start", label: "START", x: 50, y: 8, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true }
2218
+ ...DPAD_BUTTONS,
2219
+ // Standard diamond
2220
+ { type: "y", label: "Y", x: 74, y: 58, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2221
+ { type: "x", label: "X", x: 84, y: 46, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2222
+ { type: "b", label: "B", x: 84, y: 70, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2223
+ { type: "a", label: "A", x: 94, y: 58, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2224
+ // Triggers
2225
+ { type: "l", label: "L", x: 8, y: 20, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
2226
+ { type: "r", label: "R", x: 92, y: 20, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
2227
+ { type: "start", label: "START", x: 50, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" }
1830
2228
  ]
1831
2229
  };
1832
2230
  function getLayoutForSystem(system) {
1833
- const n = system.toUpperCase();
1834
- if (n.includes("NES") || n.includes("FAMICOM")) return NES_LAYOUT;
1835
- if (n.includes("SNES") || n.includes("SUPER")) return SNES_LAYOUT;
1836
- if (n.includes("GBA") || n.includes("ADVANCE")) return GBA_LAYOUT;
1837
- if (n.includes("GB") || n.includes("GAME BOY")) return GB_LAYOUT;
1838
- if (n.includes("GENESIS") || n.includes("MEGA")) return GENESIS_LAYOUT;
1839
- return NES_LAYOUT;
2231
+ const s = system.toUpperCase();
2232
+ if (s === "PS1" || s === "PSX" || s === "PSP") return PSX_LAYOUT;
2233
+ if (s === "N64") return N64_LAYOUT;
2234
+ if (s === "SNES" || s === "NDS") return SNES_LAYOUT;
2235
+ if (s === "GBA") return GBA_LAYOUT;
2236
+ if (s === "NES" || s === "GB" || s === "GBC" || s === "VIRTUAL_BOY") return TWO_BUTTON_LAYOUT;
2237
+ if (s === "DREAMCAST") return DREAMCAST_LAYOUT;
2238
+ if (s === "SATURN") return SATURN_LAYOUT;
2239
+ if (s === "GENESIS") return SIX_BUTTON_LAYOUT;
2240
+ if (s === "MASTER_SYSTEM" || s === "GAME_GEAR") return TWO_BUTTON_LAYOUT;
2241
+ if (s === "NEOGEO") return NEOGEO_LAYOUT;
2242
+ if (s.includes("NEOGEO_POCKET")) return TWO_BUTTON_LAYOUT;
2243
+ if (s === "ARCADE") return SIX_BUTTON_LAYOUT;
2244
+ if (s === "PC_ENGINE" || s === "TURBOGRAFX") return TWO_BUTTON_LAYOUT;
2245
+ if (s.includes("WONDERSWAN")) return TWO_BUTTON_LAYOUT;
2246
+ if (s.includes("ATARI")) return TWO_BUTTON_LAYOUT;
2247
+ return TWO_BUTTON_LAYOUT;
1840
2248
  }
1841
- var DRAG_HOLD_DELAY = 300;
2249
+ var DRAG_HOLD_DELAY = 350;
1842
2250
  var DRAG_MOVE_THRESHOLD = 10;
1843
2251
  var DRAG_CENTER_THRESHOLD = 0.4;
1844
2252
  function useTouchHandlers({
@@ -1871,6 +2279,9 @@ function useTouchHandlers({
1871
2279
  x: touchX - displayX / 100 * containerWidth,
1872
2280
  y: touchY - displayY / 100 * containerHeight
1873
2281
  };
2282
+ if (navigator.vibrate) {
2283
+ navigator.vibrate([10, 30, 10]);
2284
+ }
1874
2285
  if (!isSystemButton) {
1875
2286
  onRelease(buttonType);
1876
2287
  }
@@ -1986,58 +2397,122 @@ function useTouchHandlers({
1986
2397
  }
1987
2398
 
1988
2399
  // src/components/VirtualController/utils/buttonStyles.ts
1989
- function getButtonStyles(buttonType, isPressed) {
2400
+ var DEFAULT_FACE = {
2401
+ bg: "bg-white/10 backdrop-blur-md",
2402
+ text: "text-white",
2403
+ border: "border-white/30",
2404
+ shadow: "shadow-lg",
2405
+ transform: ""
2406
+ };
2407
+ var DEFAULT_SHOULDER = {
2408
+ bg: "bg-white/20 backdrop-blur-md",
2409
+ text: "text-white",
2410
+ border: "border-white/30",
2411
+ shadow: "shadow-sm",
2412
+ transform: ""
2413
+ };
2414
+ var DEFAULT_SYSTEM = {
2415
+ bg: "bg-black/60 backdrop-blur-md",
2416
+ text: "text-white/80",
2417
+ border: "border-white/20",
2418
+ shadow: "shadow-sm",
2419
+ transform: ""
2420
+ };
2421
+ var PRESSED_STYLE = {
2422
+ bg: "bg-white/90",
2423
+ text: "text-black",
2424
+ border: "border-white",
2425
+ shadow: "shadow-none",
2426
+ transform: "scale(0.95)"
2427
+ };
2428
+ function coloredButton(bgColor, textColor = "text-white") {
2429
+ return {
2430
+ bg: bgColor,
2431
+ text: textColor,
2432
+ border: "border-white/20",
2433
+ shadow: "shadow-lg",
2434
+ transform: ""
2435
+ };
2436
+ }
2437
+ function psButton(symbolColor) {
2438
+ return {
2439
+ bg: "bg-black/80 backdrop-blur-md",
2440
+ text: symbolColor,
2441
+ border: "border-white/30",
2442
+ shadow: "shadow-lg",
2443
+ transform: ""
2444
+ };
2445
+ }
2446
+ function getButtonStyles(buttonType, isPressed, consoleName = "") {
1990
2447
  if (isPressed) {
1991
- return {
1992
- bg: "bg-retro-primary",
1993
- text: "text-black",
1994
- border: "border-white",
1995
- shadow: "shadow-none",
1996
- transform: "translate-x-[3px] translate-y-[3px]"
1997
- };
2448
+ return PRESSED_STYLE;
2449
+ }
2450
+ const c = consoleName.toUpperCase();
2451
+ if (c === "NEOGEO") {
2452
+ switch (buttonType) {
2453
+ case "b":
2454
+ return coloredButton("bg-[#E60012]");
2455
+ // A = Red
2456
+ case "a":
2457
+ return coloredButton("bg-[#FFD600]", "text-black");
2458
+ // B = Yellow
2459
+ case "y":
2460
+ return coloredButton("bg-[#009944]");
2461
+ // C = Green
2462
+ case "x":
2463
+ return coloredButton("bg-[#0068B7]");
2464
+ }
2465
+ }
2466
+ if (c === "PSX" || c === "PS1" || c === "PSP") {
2467
+ switch (buttonType) {
2468
+ case "y":
2469
+ return psButton("text-[#25D998]");
2470
+ // △ = Green
2471
+ case "b":
2472
+ return psButton("text-[#FF5555]");
2473
+ // ○ = Red
2474
+ case "a":
2475
+ return psButton("text-[#5599FF]");
2476
+ // ✕ = Blue
2477
+ case "x":
2478
+ return psButton("text-[#E889DD]");
2479
+ }
2480
+ }
2481
+ if (c === "SNES" || c === "NDS") {
2482
+ switch (buttonType) {
2483
+ case "a":
2484
+ return coloredButton("bg-[#CC0000]");
2485
+ // A = Red
2486
+ case "b":
2487
+ return coloredButton("bg-[#CCCC00]", "text-black");
2488
+ // B = Yellow
2489
+ case "x":
2490
+ return coloredButton("bg-[#0033CC]");
2491
+ // X = Blue
2492
+ case "y":
2493
+ return coloredButton("bg-[#006600]");
2494
+ }
1998
2495
  }
1999
2496
  switch (buttonType) {
2000
2497
  case "a":
2001
2498
  case "b":
2002
- case "c":
2003
- return {
2004
- bg: "bg-red-600",
2005
- text: "text-white",
2006
- border: "border-white",
2007
- shadow: "shadow-hard",
2008
- transform: ""
2009
- };
2010
2499
  case "x":
2011
2500
  case "y":
2012
- return {
2013
- bg: "bg-blue-600",
2014
- text: "text-white",
2015
- border: "border-white",
2016
- shadow: "shadow-hard",
2017
- transform: ""
2018
- };
2501
+ return DEFAULT_FACE;
2019
2502
  case "l":
2020
2503
  case "r":
2504
+ case "l2":
2505
+ case "r2":
2506
+ return DEFAULT_SHOULDER;
2021
2507
  case "start":
2022
2508
  case "select":
2023
- return {
2024
- bg: "bg-retro-surface",
2025
- text: "text-white",
2026
- border: "border-white",
2027
- shadow: "shadow-hard",
2028
- transform: ""
2029
- };
2509
+ case "menu":
2510
+ return DEFAULT_SYSTEM;
2030
2511
  default:
2031
- return {
2032
- bg: "bg-black",
2033
- text: "text-white",
2034
- border: "border-white",
2035
- shadow: "shadow-hard",
2036
- transform: ""
2037
- };
2512
+ return DEFAULT_FACE;
2038
2513
  }
2039
2514
  }
2040
- var VirtualButton = React5.memo(function VirtualButton2({
2515
+ var VirtualButton = React2.memo(function VirtualButton2({
2041
2516
  config,
2042
2517
  isPressed,
2043
2518
  onPress,
@@ -2048,13 +2523,16 @@ var VirtualButton = React5.memo(function VirtualButton2({
2048
2523
  customPosition,
2049
2524
  onPositionChange,
2050
2525
  isLandscape = false,
2051
- systemColor = "#00FF41"
2052
- // Default retro green
2526
+ console: console2 = ""
2053
2527
  }) {
2528
+ const t = useKoinTranslation();
2054
2529
  const buttonRef = useRef(null);
2055
- const isSystemButton = config.type === "start" || config.type === "select";
2530
+ const isSystemButton = config.type === "start" || config.type === "select" || config.type === "menu";
2056
2531
  const displayX = customPosition ? customPosition.x : config.x;
2057
2532
  const displayY = customPosition ? customPosition.y : config.y;
2533
+ let label = config.label;
2534
+ if (config.type === "start") label = t.controls.startBtn;
2535
+ if (config.type === "select") label = t.controls.selectBtn;
2058
2536
  const {
2059
2537
  handleTouchStart,
2060
2538
  handleTouchMove,
@@ -2092,63 +2570,78 @@ var VirtualButton = React5.memo(function VirtualButton2({
2092
2570
  const leftPercent = displayX / 100 * containerWidth - config.size / 2;
2093
2571
  const topPercent = displayY / 100 * containerHeight - config.size / 2;
2094
2572
  const transform = `translate3d(${leftPercent.toFixed(1)}px, ${topPercent.toFixed(1)}px, 0)`;
2095
- const styles = getButtonStyles(config.type, isPressed);
2096
- const isActionButton = config.type === "a" || config.type === "b";
2097
- const borderRadius = isActionButton ? "50%" : "0";
2098
- const pressedStyle = isPressed ? {
2099
- backgroundColor: systemColor,
2100
- color: "#000000",
2101
- borderColor: "#FFFFFF"
2102
- } : {};
2573
+ const styles = getButtonStyles(config.type, isPressed, console2);
2574
+ let borderRadius = "50%";
2575
+ let width = `${config.size}px`;
2576
+ if (config.shape === "pill") {
2577
+ borderRadius = "20px";
2578
+ width = `${config.size * 1.8}px`;
2579
+ } else if (config.shape === "rect") {
2580
+ borderRadius = "8px";
2581
+ width = `${config.size * 2.5}px`;
2582
+ } else if (config.shape === "square") {
2583
+ borderRadius = "12px";
2584
+ } else {
2585
+ if (["l", "r", "l2", "r2"].includes(config.type)) {
2586
+ borderRadius = "10px";
2587
+ width = `${config.size * 2}px`;
2588
+ }
2589
+ }
2103
2590
  return /* @__PURE__ */ jsx(
2104
2591
  "button",
2105
2592
  {
2106
2593
  ref: buttonRef,
2107
2594
  className: `
2108
- absolute border-4 font-heading font-bold uppercase tracking-wider
2109
- transition-all duration-100 select-none
2595
+ absolute flex items-center justify-center
2596
+ font-heading font-bold uppercase tracking-wider
2597
+ transition-all duration-75 select-none
2110
2598
  pointer-events-auto touch-manipulation
2111
- ${isPressed ? "" : `${styles.bg} ${styles.text} ${styles.border} ${styles.shadow}`} ${styles.transform}
2112
- active:translate-x-[3px] active:translate-y-[3px] active:shadow-none
2599
+ backdrop-blur-sm
2600
+ ${isPressed ? "" : `${styles.bg} ${styles.border} ${styles.shadow}`}
2601
+ ${styles.text}
2602
+ active:shadow-none
2113
2603
  `,
2114
2604
  style: {
2115
- // Remove left/top and use transform instead for high performance (compositor only)
2116
2605
  top: 0,
2117
2606
  left: 0,
2118
- transform,
2607
+ transform: transform + (isPressed ? " scale(0.95)" : ""),
2119
2608
  willChange: "transform",
2120
- width: `${config.size}px`,
2609
+ width,
2121
2610
  height: `${config.size}px`,
2611
+ // Height stays consistent
2122
2612
  minWidth: `${config.size}px`,
2123
- minHeight: `${config.size}px`,
2124
- fontSize: config.size < 50 ? "10px" : "12px",
2613
+ fontSize: config.size < 50 ? "11px" : "16px",
2125
2614
  borderRadius,
2615
+ borderWidth: isPressed ? "0px" : "1.5px",
2616
+ // slightly thicker border
2126
2617
  lineHeight: "1",
2127
2618
  // Semi-transparent in landscape mode
2128
2619
  opacity: isLandscape ? 0.85 : 1,
2129
- // Prevent context menu on long-press
2130
2620
  WebkitTouchCallout: "none",
2131
2621
  WebkitUserSelect: "none",
2132
2622
  userSelect: "none",
2133
- ...pressedStyle
2623
+ // Direct style overrides if needed from getButtonStyles (for exact hex colors)
2624
+ backgroundColor: isPressed ? styles.bg.startsWith("bg-") ? void 0 : styles.bg : styles.bg.startsWith("bg-") ? void 0 : styles.bg,
2625
+ borderColor: isPressed ? void 0 : styles.border.startsWith("border-") ? void 0 : styles.border,
2626
+ color: styles.text.startsWith("text-") ? void 0 : styles.text
2134
2627
  },
2135
- "aria-label": config.label,
2628
+ "aria-label": label,
2136
2629
  onContextMenu: (e) => e.preventDefault(),
2137
- children: config.label
2630
+ children: /* @__PURE__ */ jsx("span", { className: "drop-shadow-md", children: label })
2138
2631
  }
2139
2632
  );
2140
2633
  });
2141
2634
  var VirtualButton_default = VirtualButton;
2142
2635
 
2143
2636
  // src/lib/controls/types.ts
2144
- var DPAD_BUTTONS = ["up", "down", "left", "right"];
2637
+ var DPAD_BUTTONS2 = ["up", "down", "left", "right"];
2145
2638
  var FACE_BUTTONS = ["a", "b", "x", "y"];
2146
2639
  var SHOULDER_BUTTONS = ["l", "r"];
2147
2640
  var TRIGGER_BUTTONS = ["l2", "r2"];
2148
2641
  var STICK_BUTTONS = ["l3", "r3"];
2149
2642
  var SYSTEM_BUTTONS = ["start", "select"];
2150
2643
  var ALL_BUTTONS = [
2151
- ...DPAD_BUTTONS,
2644
+ ...DPAD_BUTTONS2,
2152
2645
  ...FACE_BUTTONS,
2153
2646
  ...SHOULDER_BUTTONS,
2154
2647
  ...TRIGGER_BUTTONS,
@@ -2306,91 +2799,91 @@ var CONSOLE_CAPABILITIES = {
2306
2799
  // ============ Nintendo ============
2307
2800
  NES: {
2308
2801
  console: "NES",
2309
- buttons: [...DPAD_BUTTONS, "a", "b", "start", "select"]
2802
+ buttons: [...DPAD_BUTTONS2, "a", "b", "start", "select"]
2310
2803
  },
2311
2804
  SNES: {
2312
2805
  console: "SNES",
2313
- buttons: [...DPAD_BUTTONS, "a", "b", "x", "y", "l", "r", "start", "select"]
2806
+ buttons: [...DPAD_BUTTONS2, "a", "b", "x", "y", "l", "r", "start", "select"]
2314
2807
  },
2315
2808
  N64: {
2316
2809
  console: "N64",
2317
- buttons: [...DPAD_BUTTONS, "a", "b", "l", "r", "start"]
2810
+ buttons: [...DPAD_BUTTONS2, "a", "b", "l", "r", "start"]
2318
2811
  // No select, has Z trigger (mapped to l2)
2319
2812
  },
2320
2813
  GB: {
2321
2814
  console: "GB",
2322
- buttons: [...DPAD_BUTTONS, "a", "b", "start", "select"]
2815
+ buttons: [...DPAD_BUTTONS2, "a", "b", "start", "select"]
2323
2816
  },
2324
2817
  GBC: {
2325
2818
  console: "GBC",
2326
- buttons: [...DPAD_BUTTONS, "a", "b", "start", "select"]
2819
+ buttons: [...DPAD_BUTTONS2, "a", "b", "start", "select"]
2327
2820
  },
2328
2821
  GBA: {
2329
2822
  console: "GBA",
2330
- buttons: [...DPAD_BUTTONS, "a", "b", "l", "r", "start", "select"]
2823
+ buttons: [...DPAD_BUTTONS2, "a", "b", "l", "r", "start", "select"]
2331
2824
  },
2332
2825
  // ============ Sega ============
2333
2826
  SMS: {
2334
2827
  console: "SMS",
2335
- buttons: [...DPAD_BUTTONS, "a", "b", "start"]
2828
+ buttons: [...DPAD_BUTTONS2, "a", "b", "start"]
2336
2829
  // 1, 2, Pause
2337
2830
  },
2338
2831
  GENESIS: {
2339
2832
  console: "GENESIS",
2340
- buttons: [...DPAD_BUTTONS, "a", "b", "x", "y", "l", "r", "start"]
2833
+ buttons: [...DPAD_BUTTONS2, "a", "b", "x", "y", "l", "r", "start"]
2341
2834
  // 6-button: A/B/C + X/Y/Z
2342
2835
  },
2343
2836
  GG: {
2344
2837
  console: "GG",
2345
- buttons: [...DPAD_BUTTONS, "a", "b", "start"]
2838
+ buttons: [...DPAD_BUTTONS2, "a", "b", "start"]
2346
2839
  },
2347
2840
  SATURN: {
2348
2841
  console: "SATURN",
2349
- buttons: [...DPAD_BUTTONS, "a", "b", "x", "y", "l", "r", "l2", "r2", "start"]
2842
+ buttons: [...DPAD_BUTTONS2, "a", "b", "x", "y", "l", "r", "l2", "r2", "start"]
2350
2843
  // Full 8-button
2351
2844
  },
2352
2845
  // ============ Sony ============
2353
2846
  PS1: {
2354
2847
  console: "PS1",
2355
- buttons: [...DPAD_BUTTONS, "a", "b", "x", "y", "l", "r", "l2", "r2", "l3", "r3", "start", "select"]
2848
+ buttons: [...DPAD_BUTTONS2, "a", "b", "x", "y", "l", "r", "l2", "r2", "l3", "r3", "start", "select"]
2356
2849
  },
2357
2850
  // ============ NEC ============
2358
2851
  PCE: {
2359
2852
  console: "PCE",
2360
2853
  // TurboGrafx-16
2361
- buttons: [...DPAD_BUTTONS, "a", "b", "start", "select"]
2854
+ buttons: [...DPAD_BUTTONS2, "a", "b", "start", "select"]
2362
2855
  // I, II, Run, Select
2363
2856
  },
2364
2857
  // ============ Atari ============
2365
2858
  ATARI2600: {
2366
2859
  console: "ATARI2600",
2367
- buttons: [...DPAD_BUTTONS, "a", "select"]
2860
+ buttons: [...DPAD_BUTTONS2, "a", "select"]
2368
2861
  // Fire, Select/Reset
2369
2862
  },
2370
2863
  ATARI7800: {
2371
2864
  console: "ATARI7800",
2372
- buttons: [...DPAD_BUTTONS, "a", "b", "select"]
2865
+ buttons: [...DPAD_BUTTONS2, "a", "b", "select"]
2373
2866
  },
2374
2867
  LYNX: {
2375
2868
  console: "LYNX",
2376
- buttons: [...DPAD_BUTTONS, "a", "b", "start", "select"]
2869
+ buttons: [...DPAD_BUTTONS2, "a", "b", "start", "select"]
2377
2870
  // A, B, Option 1, Option 2
2378
2871
  },
2379
2872
  // ============ SNK ============
2380
2873
  NGPC: {
2381
2874
  console: "NGPC",
2382
2875
  // Neo Geo Pocket
2383
- buttons: [...DPAD_BUTTONS, "a", "b", "start", "select"]
2876
+ buttons: [...DPAD_BUTTONS2, "a", "b", "start", "select"]
2384
2877
  },
2385
2878
  // ============ Other ============
2386
2879
  WSC: {
2387
2880
  console: "WSC",
2388
2881
  // WonderSwan
2389
- buttons: [...DPAD_BUTTONS, "a", "b", "x", "y", "start"]
2882
+ buttons: [...DPAD_BUTTONS2, "a", "b", "x", "y", "start"]
2390
2883
  },
2391
2884
  ARCADE: {
2392
2885
  console: "ARCADE",
2393
- buttons: [...DPAD_BUTTONS, "a", "b", "x", "y", "l", "r", "start", "select"]
2886
+ buttons: [...DPAD_BUTTONS2, "a", "b", "x", "y", "l", "r", "start", "select"]
2394
2887
  // Generic 6-button
2395
2888
  }
2396
2889
  };
@@ -2431,7 +2924,7 @@ function getConsoleCapabilities(system) {
2431
2924
  const normalized = system.toUpperCase();
2432
2925
  return CONSOLE_CAPABILITIES[normalized] ?? {
2433
2926
  console: normalized,
2434
- buttons: [...DPAD_BUTTONS, "a", "b", "x", "y", "l", "r", "start", "select"]
2927
+ buttons: [...DPAD_BUTTONS2, "a", "b", "x", "y", "l", "r", "start", "select"]
2435
2928
  };
2436
2929
  }
2437
2930
  function getConsoleButtons(system) {
@@ -2719,13 +3212,11 @@ var DEFAULT_CONTROLS = DEFAULT_KEYBOARD;
2719
3212
 
2720
3213
  // src/components/VirtualController/utils/keyboardEvents.ts
2721
3214
  function getKeyboardCode(buttonType, controls) {
2722
- const controlMapping = controls || DEFAULT_CONTROLS;
2723
- const mappingKey = buttonType === "c" ? "x" : buttonType;
2724
- const key = mappingKey;
2725
- if (key in controlMapping) {
2726
- return controlMapping[key] ?? null;
3215
+ const key = buttonType;
3216
+ if (controls && key in controls && controls[key]) {
3217
+ return controls[key];
2727
3218
  }
2728
- return null;
3219
+ return DEFAULT_CONTROLS[key] ?? null;
2729
3220
  }
2730
3221
  function getKeyName(code) {
2731
3222
  if (code.startsWith("Key")) return code.slice(3).toLowerCase();
@@ -2751,23 +3242,34 @@ function dispatchKeyboardEvent(type, code) {
2751
3242
  canvas.dispatchEvent(event);
2752
3243
  return true;
2753
3244
  }
2754
- var Dpad = React5.memo(function Dpad2({
2755
- size,
3245
+ var DRAG_HOLD_DELAY2 = 350;
3246
+ var CENTER_TOUCH_RADIUS = 0.25;
3247
+ var Dpad = React2.memo(function Dpad2({
3248
+ size = 180,
2756
3249
  x,
2757
3250
  y,
2758
3251
  containerWidth,
2759
3252
  containerHeight,
2760
3253
  controls,
2761
3254
  systemColor = "#00FF41",
2762
- isLandscape = false
3255
+ isLandscape = false,
3256
+ customPosition,
3257
+ onPositionChange
2763
3258
  }) {
2764
3259
  const dpadRef = useRef(null);
2765
3260
  const activeTouchRef = useRef(null);
2766
3261
  const activeDirectionsRef = useRef(/* @__PURE__ */ new Set());
2767
- const upRef = useRef(null);
2768
- const downRef = useRef(null);
2769
- const leftRef = useRef(null);
2770
- const rightRef = useRef(null);
3262
+ const [isDragging, setIsDragging] = useState(false);
3263
+ const dragTimerRef = useRef(null);
3264
+ const dragStartRef = useRef({ x: 0, y: 0, touchX: 0, touchY: 0 });
3265
+ const touchStartPosRef = useRef({ x: 0, y: 0, time: 0 });
3266
+ const upPathRef = useRef(null);
3267
+ const downPathRef = useRef(null);
3268
+ const leftPathRef = useRef(null);
3269
+ const rightPathRef = useRef(null);
3270
+ const centerCircleRef = useRef(null);
3271
+ const displayX = customPosition ? customPosition.x : x;
3272
+ const displayY = customPosition ? customPosition.y : y;
2771
3273
  const getKeyCode = useCallback((direction) => {
2772
3274
  if (!controls) {
2773
3275
  const defaults = {
@@ -2785,24 +3287,41 @@ var Dpad = React5.memo(function Dpad2({
2785
3287
  const centerY = rect.top + rect.height / 2;
2786
3288
  const dx = touchX - centerX;
2787
3289
  const dy = touchY - centerY;
2788
- const deadZone = rect.width * 0.12;
2789
3290
  const distance = Math.sqrt(dx * dx + dy * dy);
3291
+ const deadZone = rect.width / 2 * 0.15;
2790
3292
  if (distance < deadZone) return /* @__PURE__ */ new Set();
2791
3293
  const directions = /* @__PURE__ */ new Set();
2792
3294
  const angle = Math.atan2(dy, dx) * (180 / Math.PI);
2793
- if (angle >= -157.5 && angle <= -22.5) directions.add("up");
2794
- if (angle >= 22.5 && angle <= 157.5) directions.add("down");
2795
- if (angle >= 112.5 || angle <= -112.5) directions.add("left");
2796
- if (angle >= -67.5 && angle <= 67.5) directions.add("right");
3295
+ if (angle >= -150 && angle <= -30) directions.add("up");
3296
+ if (angle >= 30 && angle <= 150) directions.add("down");
3297
+ if (angle >= 120 || angle <= -120) directions.add("left");
3298
+ if (angle >= -60 && angle <= 60) directions.add("right");
2797
3299
  return directions;
2798
3300
  }, []);
2799
3301
  const updateVisuals = useCallback((directions) => {
2800
- const activeColor = systemColor;
2801
- const inactiveColor = "#1a1a1a";
2802
- if (upRef.current) upRef.current.style.fill = directions.has("up") ? activeColor : inactiveColor;
2803
- if (downRef.current) downRef.current.style.fill = directions.has("down") ? activeColor : inactiveColor;
2804
- if (leftRef.current) leftRef.current.style.fill = directions.has("left") ? activeColor : inactiveColor;
2805
- if (rightRef.current) rightRef.current.style.fill = directions.has("right") ? activeColor : inactiveColor;
3302
+ const activeFill = `${systemColor}80`;
3303
+ const inactiveFill = "rgba(255, 255, 255, 0.05)";
3304
+ const activeStroke = systemColor;
3305
+ const inactiveStroke = "rgba(255, 255, 255, 0.2)";
3306
+ const glow = `0 0 15px ${systemColor}`;
3307
+ const updatePart = (ref, isActive) => {
3308
+ if (ref.current) {
3309
+ ref.current.style.fill = isActive ? activeFill : inactiveFill;
3310
+ ref.current.style.stroke = isActive ? activeStroke : inactiveStroke;
3311
+ ref.current.style.filter = isActive ? `drop-shadow(${glow})` : "none";
3312
+ ref.current.style.transform = isActive ? "scale(0.98)" : "scale(1)";
3313
+ ref.current.style.transformOrigin = "center";
3314
+ }
3315
+ };
3316
+ updatePart(upPathRef, directions.has("up"));
3317
+ updatePart(downPathRef, directions.has("down"));
3318
+ updatePart(leftPathRef, directions.has("left"));
3319
+ updatePart(rightPathRef, directions.has("right"));
3320
+ if (centerCircleRef.current) {
3321
+ const isAny = directions.size > 0;
3322
+ centerCircleRef.current.style.fill = isAny ? systemColor : "rgba(0,0,0,0.5)";
3323
+ centerCircleRef.current.style.stroke = isAny ? "#fff" : "rgba(255,255,255,0.3)";
3324
+ }
2806
3325
  }, [systemColor]);
2807
3326
  const updateDirections = useCallback((newDirections) => {
2808
3327
  const prev = activeDirectionsRef.current;
@@ -2824,48 +3343,107 @@ var Dpad = React5.memo(function Dpad2({
2824
3343
  activeDirectionsRef.current = newDirections;
2825
3344
  updateVisuals(newDirections);
2826
3345
  }, [getKeyCode, updateVisuals]);
3346
+ const clearDragTimer = useCallback(() => {
3347
+ if (dragTimerRef.current) {
3348
+ clearTimeout(dragTimerRef.current);
3349
+ dragTimerRef.current = null;
3350
+ }
3351
+ }, []);
3352
+ const startDragging = useCallback((touchX, touchY) => {
3353
+ setIsDragging(true);
3354
+ dragStartRef.current = {
3355
+ x: displayX,
3356
+ y: displayY,
3357
+ touchX,
3358
+ touchY
3359
+ };
3360
+ activeDirectionsRef.current.forEach((dir) => {
3361
+ const keyCode = getKeyCode(dir);
3362
+ if (keyCode) dispatchKeyboardEvent("keyup", keyCode);
3363
+ });
3364
+ activeDirectionsRef.current = /* @__PURE__ */ new Set();
3365
+ updateVisuals(/* @__PURE__ */ new Set());
3366
+ if (navigator.vibrate) navigator.vibrate([10, 30, 10]);
3367
+ }, [displayX, displayY, getKeyCode, updateVisuals]);
2827
3368
  const handleTouchStart = useCallback((e) => {
2828
3369
  e.preventDefault();
2829
- e.stopPropagation();
2830
- const touch = e.touches[0];
3370
+ if (activeTouchRef.current !== null) return;
3371
+ const touch = e.changedTouches[0];
2831
3372
  activeTouchRef.current = touch.identifier;
3373
+ touchStartPosRef.current = { x: touch.clientX, y: touch.clientY, time: Date.now() };
2832
3374
  const rect = dpadRef.current?.getBoundingClientRect();
2833
3375
  if (!rect) return;
2834
- updateDirections(getDirectionsFromTouch(touch.clientX, touch.clientY, rect));
2835
- }, [getDirectionsFromTouch, updateDirections]);
3376
+ const centerX = rect.left + rect.width / 2;
3377
+ const centerY = rect.top + rect.height / 2;
3378
+ const distFromCenter = Math.sqrt(
3379
+ Math.pow(touch.clientX - centerX, 2) + Math.pow(touch.clientY - centerY, 2)
3380
+ );
3381
+ const centerRadius = size * CENTER_TOUCH_RADIUS;
3382
+ if (distFromCenter < centerRadius && onPositionChange) {
3383
+ dragTimerRef.current = setTimeout(() => {
3384
+ startDragging(touch.clientX, touch.clientY);
3385
+ }, DRAG_HOLD_DELAY2);
3386
+ }
3387
+ if (!isDragging) {
3388
+ updateDirections(getDirectionsFromTouch(touch.clientX, touch.clientY, rect));
3389
+ }
3390
+ }, [getDirectionsFromTouch, updateDirections, isDragging, size, onPositionChange, startDragging]);
2836
3391
  const handleTouchMove = useCallback((e) => {
2837
3392
  e.preventDefault();
2838
3393
  let touch = null;
2839
- for (let i = 0; i < e.touches.length; i++) {
2840
- if (e.touches[i].identifier === activeTouchRef.current) {
2841
- touch = e.touches[i];
3394
+ for (let i = 0; i < e.changedTouches.length; i++) {
3395
+ if (e.changedTouches[i].identifier === activeTouchRef.current) {
3396
+ touch = e.changedTouches[i];
2842
3397
  break;
2843
3398
  }
2844
3399
  }
2845
3400
  if (!touch) return;
2846
- const rect = dpadRef.current?.getBoundingClientRect();
2847
- if (!rect) return;
2848
- updateDirections(getDirectionsFromTouch(touch.clientX, touch.clientY, rect));
2849
- }, [getDirectionsFromTouch, updateDirections]);
3401
+ if (isDragging && onPositionChange) {
3402
+ const deltaX = touch.clientX - dragStartRef.current.touchX;
3403
+ const deltaY = touch.clientY - dragStartRef.current.touchY;
3404
+ const newXPercent = dragStartRef.current.x + deltaX / containerWidth * 100;
3405
+ const newYPercent = dragStartRef.current.y + deltaY / containerHeight * 100;
3406
+ const margin = size / 2 / Math.min(containerWidth, containerHeight) * 100;
3407
+ const constrainedX = Math.max(margin, Math.min(100 - margin, newXPercent));
3408
+ const constrainedY = Math.max(margin, Math.min(100 - margin, newYPercent));
3409
+ onPositionChange(constrainedX, constrainedY);
3410
+ } else {
3411
+ const moveDistance = Math.sqrt(
3412
+ Math.pow(touch.clientX - touchStartPosRef.current.x, 2) + Math.pow(touch.clientY - touchStartPosRef.current.y, 2)
3413
+ );
3414
+ if (moveDistance > 15) {
3415
+ clearDragTimer();
3416
+ }
3417
+ const rect = dpadRef.current?.getBoundingClientRect();
3418
+ if (rect) {
3419
+ updateDirections(getDirectionsFromTouch(touch.clientX, touch.clientY, rect));
3420
+ }
3421
+ }
3422
+ }, [isDragging, onPositionChange, containerWidth, containerHeight, size, getDirectionsFromTouch, updateDirections, clearDragTimer]);
2850
3423
  const handleTouchEnd = useCallback((e) => {
2851
3424
  e.preventDefault();
2852
- let touchEnded = true;
2853
- for (let i = 0; i < e.touches.length; i++) {
2854
- if (e.touches[i].identifier === activeTouchRef.current) {
2855
- touchEnded = false;
3425
+ clearDragTimer();
3426
+ let touchEnded = false;
3427
+ for (let i = 0; i < e.changedTouches.length; i++) {
3428
+ if (e.changedTouches[i].identifier === activeTouchRef.current) {
3429
+ touchEnded = true;
2856
3430
  break;
2857
3431
  }
2858
3432
  }
2859
3433
  if (touchEnded) {
2860
3434
  activeTouchRef.current = null;
2861
- activeDirectionsRef.current.forEach((dir) => {
2862
- const keyCode = getKeyCode(dir);
2863
- if (keyCode) dispatchKeyboardEvent("keyup", keyCode);
2864
- });
2865
- activeDirectionsRef.current = /* @__PURE__ */ new Set();
2866
- updateVisuals(/* @__PURE__ */ new Set());
3435
+ if (isDragging) {
3436
+ setIsDragging(false);
3437
+ } else {
3438
+ activeDirectionsRef.current.forEach((dir) => {
3439
+ const keyCode = getKeyCode(dir);
3440
+ if (keyCode) dispatchKeyboardEvent("keyup", keyCode);
3441
+ });
3442
+ activeDirectionsRef.current = /* @__PURE__ */ new Set();
3443
+ updateVisuals(/* @__PURE__ */ new Set());
3444
+ }
2867
3445
  }
2868
- }, [getKeyCode, updateVisuals]);
3446
+ }, [getKeyCode, updateVisuals, isDragging, clearDragTimer]);
2869
3447
  useEffect(() => {
2870
3448
  const dpad = dpadRef.current;
2871
3449
  if (!dpad) return;
@@ -2878,37 +3456,57 @@ var Dpad = React5.memo(function Dpad2({
2878
3456
  dpad.removeEventListener("touchmove", handleTouchMove);
2879
3457
  dpad.removeEventListener("touchend", handleTouchEnd);
2880
3458
  dpad.removeEventListener("touchcancel", handleTouchEnd);
3459
+ clearDragTimer();
2881
3460
  };
2882
- }, [handleTouchStart, handleTouchMove, handleTouchEnd]);
2883
- const leftPx = x / 100 * containerWidth - size / 2;
2884
- const topPx = y / 100 * containerHeight - size / 2;
2885
- return /* @__PURE__ */ jsx(
3461
+ }, [handleTouchStart, handleTouchMove, handleTouchEnd, clearDragTimer]);
3462
+ const leftPx = displayX / 100 * containerWidth - size / 2;
3463
+ const topPx = displayY / 100 * containerHeight - size / 2;
3464
+ const dUp = "M 35,5 L 65,5 L 65,35 L 50,50 L 35,35 Z";
3465
+ const dRight = "M 65,35 L 95,35 L 95,65 L 65,65 L 50,50 Z";
3466
+ const dDown = "M 65,65 L 65,95 L 35,95 L 35,65 L 50,50 Z";
3467
+ const dLeft = "M 35,65 L 5,65 L 5,35 L 35,35 L 50,50 Z";
3468
+ return /* @__PURE__ */ jsxs(
2886
3469
  "div",
2887
3470
  {
2888
3471
  ref: dpadRef,
2889
- className: "absolute pointer-events-auto touch-manipulation",
3472
+ className: `absolute pointer-events-auto touch-manipulation select-none ${isDragging ? "opacity-60" : ""}`,
2890
3473
  style: {
2891
3474
  top: 0,
2892
3475
  left: 0,
2893
- transform: `translate3d(${leftPx}px, ${topPx}px, 0)`,
3476
+ transform: `translate3d(${leftPx}px, ${topPx}px, 0)${isDragging ? " scale(1.05)" : ""}`,
2894
3477
  width: size,
2895
3478
  height: size,
2896
- opacity: isLandscape ? 0.85 : 1,
3479
+ opacity: isLandscape ? 0.75 : 0.9,
2897
3480
  WebkitTouchCallout: "none",
2898
- userSelect: "none"
3481
+ WebkitUserSelect: "none",
3482
+ touchAction: "none",
3483
+ transition: isDragging ? "none" : "transform 0.1s ease-out"
2899
3484
  },
2900
- onContextMenu: (e) => e.preventDefault(),
2901
- children: /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 100 100", children: [
2902
- /* @__PURE__ */ jsx("rect", { ref: upRef, x: "35", y: "5", width: "30", height: "30", rx: "4", fill: "#1a1a1a", stroke: "white", strokeWidth: "2" }),
2903
- /* @__PURE__ */ jsx("rect", { ref: downRef, x: "35", y: "65", width: "30", height: "30", rx: "4", fill: "#1a1a1a", stroke: "white", strokeWidth: "2" }),
2904
- /* @__PURE__ */ jsx("rect", { ref: leftRef, x: "5", y: "35", width: "30", height: "30", rx: "4", fill: "#1a1a1a", stroke: "white", strokeWidth: "2" }),
2905
- /* @__PURE__ */ jsx("rect", { ref: rightRef, x: "65", y: "35", width: "30", height: "30", rx: "4", fill: "#1a1a1a", stroke: "white", strokeWidth: "2" }),
2906
- /* @__PURE__ */ jsx("rect", { x: "35", y: "35", width: "30", height: "30", fill: "#000", stroke: "white", strokeWidth: "2" }),
2907
- /* @__PURE__ */ jsx("text", { x: "50", y: "25", textAnchor: "middle", fill: "#fff", fontSize: "14", fontWeight: "bold", children: "\u2191" }),
2908
- /* @__PURE__ */ jsx("text", { x: "50", y: "85", textAnchor: "middle", fill: "#fff", fontSize: "14", fontWeight: "bold", children: "\u2193" }),
2909
- /* @__PURE__ */ jsx("text", { x: "20", y: "55", textAnchor: "middle", fill: "#fff", fontSize: "14", fontWeight: "bold", children: "\u2190" }),
2910
- /* @__PURE__ */ jsx("text", { x: "80", y: "55", textAnchor: "middle", fill: "#fff", fontSize: "14", fontWeight: "bold", children: "\u2192" })
2911
- ] })
3485
+ children: [
3486
+ /* @__PURE__ */ jsx("div", { className: `absolute inset-0 rounded-full bg-black/40 backdrop-blur-md border shadow-lg ${isDragging ? "border-white/50 ring-2 ring-white/30" : "border-white/10"}` }),
3487
+ /* @__PURE__ */ jsxs("svg", { width: "100%", height: "100%", viewBox: "0 0 100 100", className: "drop-shadow-xl relative z-10", children: [
3488
+ /* @__PURE__ */ jsx("path", { ref: upPathRef, d: dUp, fill: "rgba(255,255,255,0.05)", stroke: "rgba(255,255,255,0.2)", strokeWidth: "1", className: "transition-all duration-75" }),
3489
+ /* @__PURE__ */ jsx("path", { ref: rightPathRef, d: dRight, fill: "rgba(255,255,255,0.05)", stroke: "rgba(255,255,255,0.2)", strokeWidth: "1", className: "transition-all duration-75" }),
3490
+ /* @__PURE__ */ jsx("path", { ref: downPathRef, d: dDown, fill: "rgba(255,255,255,0.05)", stroke: "rgba(255,255,255,0.2)", strokeWidth: "1", className: "transition-all duration-75" }),
3491
+ /* @__PURE__ */ jsx("path", { ref: leftPathRef, d: dLeft, fill: "rgba(255,255,255,0.05)", stroke: "rgba(255,255,255,0.2)", strokeWidth: "1", className: "transition-all duration-75" }),
3492
+ /* @__PURE__ */ jsx(
3493
+ "circle",
3494
+ {
3495
+ ref: centerCircleRef,
3496
+ cx: "50",
3497
+ cy: "50",
3498
+ r: "12",
3499
+ fill: isDragging ? systemColor : "rgba(0,0,0,0.5)",
3500
+ stroke: isDragging ? "#fff" : "rgba(255,255,255,0.3)",
3501
+ strokeWidth: isDragging ? 2 : 1
3502
+ }
3503
+ ),
3504
+ /* @__PURE__ */ jsx("path", { d: "M 50,15 L 50,25 M 45,20 L 50,15 L 55,20", stroke: "white", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", fill: "none", opacity: "0.8", pointerEvents: "none" }),
3505
+ /* @__PURE__ */ jsx("path", { d: "M 50,85 L 50,75 M 45,80 L 50,85 L 55,80", stroke: "white", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", fill: "none", opacity: "0.8", pointerEvents: "none" }),
3506
+ /* @__PURE__ */ jsx("path", { d: "M 15,50 L 25,50 M 20,45 L 15,50 L 20,55", stroke: "white", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", fill: "none", opacity: "0.8", pointerEvents: "none" }),
3507
+ /* @__PURE__ */ jsx("path", { d: "M 85,50 L 75,50 M 80,45 L 85,50 L 80,55", stroke: "white", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", fill: "none", opacity: "0.8", pointerEvents: "none" })
3508
+ ] })
3509
+ ]
2912
3510
  }
2913
3511
  );
2914
3512
  });
@@ -2994,6 +3592,72 @@ function useButtonPositions() {
2994
3592
  resetPositions
2995
3593
  };
2996
3594
  }
3595
+ var STORAGE_KEY2 = "koin-controls-hint-shown";
3596
+ function ControlsHint({ isVisible }) {
3597
+ const [show, setShow] = useState(false);
3598
+ useEffect(() => {
3599
+ if (!isVisible) return;
3600
+ try {
3601
+ const wasShown = sessionStorage.getItem(STORAGE_KEY2);
3602
+ if (!wasShown) {
3603
+ const timer = setTimeout(() => setShow(true), 1500);
3604
+ return () => clearTimeout(timer);
3605
+ }
3606
+ } catch {
3607
+ }
3608
+ }, [isVisible]);
3609
+ const handleDismiss = () => {
3610
+ setShow(false);
3611
+ try {
3612
+ sessionStorage.setItem(STORAGE_KEY2, "true");
3613
+ } catch {
3614
+ }
3615
+ };
3616
+ if (!show) return null;
3617
+ return /* @__PURE__ */ jsx(
3618
+ "div",
3619
+ {
3620
+ className: "fixed inset-0 z-[100] flex items-center justify-center bg-black/60 backdrop-blur-sm",
3621
+ onClick: handleDismiss,
3622
+ onTouchEnd: handleDismiss,
3623
+ children: /* @__PURE__ */ jsxs(
3624
+ "div",
3625
+ {
3626
+ className: "bg-black/90 border border-white/30 rounded-2xl p-6 mx-4 max-w-sm text-center shadow-2xl pointer-events-auto",
3627
+ onClick: (e) => e.stopPropagation(),
3628
+ onTouchEnd: (e) => e.stopPropagation(),
3629
+ children: [
3630
+ /* @__PURE__ */ jsx("div", { className: "flex justify-center mb-4", children: /* @__PURE__ */ jsx("div", { className: "w-16 h-16 rounded-full bg-green-500/20 border-2 border-green-400 flex items-center justify-center", children: /* @__PURE__ */ jsx(Move, { size: 32, className: "text-green-400" }) }) }),
3631
+ /* @__PURE__ */ jsx("h3", { className: "text-white text-lg font-bold mb-2", children: "Customize Your Controls" }),
3632
+ /* @__PURE__ */ jsxs("p", { className: "text-white/70 text-sm mb-4", children: [
3633
+ /* @__PURE__ */ jsx("strong", { className: "text-white", children: "Long-press" }),
3634
+ " any button or the ",
3635
+ /* @__PURE__ */ jsx("strong", { className: "text-white", children: "D-pad center" }),
3636
+ " to drag and reposition it."
3637
+ ] }),
3638
+ /* @__PURE__ */ jsx("p", { className: "text-white/50 text-xs mb-4", children: "Your layout is saved separately for portrait and landscape modes." }),
3639
+ /* @__PURE__ */ jsx(
3640
+ "button",
3641
+ {
3642
+ type: "button",
3643
+ onClick: (e) => {
3644
+ e.stopPropagation();
3645
+ handleDismiss();
3646
+ },
3647
+ onTouchEnd: (e) => {
3648
+ e.stopPropagation();
3649
+ handleDismiss();
3650
+ },
3651
+ className: "w-full py-3 px-4 bg-green-500 hover:bg-green-400 active:bg-green-600 text-black font-bold rounded-xl transition-colors flex items-center justify-center gap-2 cursor-pointer touch-manipulation",
3652
+ children: "Got it!"
3653
+ }
3654
+ )
3655
+ ]
3656
+ }
3657
+ )
3658
+ }
3659
+ );
3660
+ }
2997
3661
  function VirtualController({
2998
3662
  system,
2999
3663
  isRunning,
@@ -3014,7 +3678,6 @@ function VirtualController({
3014
3678
  return btn.showInLandscape;
3015
3679
  });
3016
3680
  const DPAD_TYPES = ["up", "down", "left", "right"];
3017
- const dpadButtons = visibleButtons.filter((btn) => DPAD_TYPES.includes(btn.type));
3018
3681
  useEffect(() => {
3019
3682
  const updateSize = () => {
3020
3683
  const { width, height } = getViewportSize();
@@ -3064,7 +3727,7 @@ function VirtualController({
3064
3727
  },
3065
3728
  [controls]
3066
3729
  );
3067
- const SYSTEM_BUTTONS2 = ["start", "select"];
3730
+ const SYSTEM_BUTTONS2 = ["start", "select", "menu"];
3068
3731
  const handlePress = useCallback(
3069
3732
  (buttonType) => {
3070
3733
  const isSystemButton = SYSTEM_BUTTONS2.includes(buttonType);
@@ -3152,26 +3815,28 @@ function VirtualController({
3152
3815
  if (!isMobile) {
3153
3816
  return null;
3154
3817
  }
3155
- if (visibleButtons.length === 0) {
3156
- return null;
3157
- }
3818
+ const dpadSize = containerSize.width > containerSize.height ? 160 : 180;
3819
+ const dpadY = containerSize.width > containerSize.height ? 55 : 62;
3820
+ const finalDpadY = system.toUpperCase() === "NEOGEO" ? dpadY - 5 : dpadY;
3158
3821
  return /* @__PURE__ */ jsxs(
3159
3822
  "div",
3160
3823
  {
3161
3824
  className: "fixed inset-0 z-30 pointer-events-none",
3162
3825
  style: { touchAction: "none" },
3163
3826
  children: [
3164
- dpadButtons.length > 0 && /* @__PURE__ */ jsx(
3827
+ /* @__PURE__ */ jsx(
3165
3828
  Dpad_default,
3166
3829
  {
3167
- size: containerSize.width > containerSize.height ? 120 : 130,
3168
- x: 12,
3169
- y: containerSize.width > containerSize.height ? 52 : 62,
3830
+ size: dpadSize,
3831
+ x: 14,
3832
+ y: finalDpadY,
3170
3833
  containerWidth: containerSize.width || window.innerWidth,
3171
3834
  containerHeight: containerSize.height || window.innerHeight,
3172
3835
  controls,
3173
3836
  systemColor,
3174
- isLandscape
3837
+ isLandscape,
3838
+ customPosition: getPosition("up", isLandscape),
3839
+ onPositionChange: (x, y) => savePosition("up", x, y, isLandscape)
3175
3840
  }
3176
3841
  ),
3177
3842
  memoizedButtonElements.filter(({ buttonConfig }) => !DPAD_TYPES.includes(buttonConfig.type)).map(({ buttonConfig, adjustedConfig, customPosition, width, height }) => /* @__PURE__ */ jsx(
@@ -3187,64 +3852,77 @@ function VirtualController({
3187
3852
  customPosition,
3188
3853
  onPositionChange: (x, y) => savePosition(buttonConfig.type, x, y, isLandscape),
3189
3854
  isLandscape,
3190
- systemColor
3855
+ console: layout.console
3191
3856
  },
3192
3857
  buttonConfig.type
3193
- ))
3858
+ )),
3859
+ /* @__PURE__ */ jsx(ControlsHint, { isVisible: isRunning })
3194
3860
  ]
3195
3861
  }
3196
3862
  );
3197
3863
  }
3198
3864
  function FloatingExitButton({ onClick, disabled = false }) {
3199
- return /* @__PURE__ */ jsx(
3865
+ return /* @__PURE__ */ jsxs(
3200
3866
  "button",
3201
3867
  {
3202
3868
  onClick,
3203
3869
  disabled,
3204
3870
  className: `
3205
- fixed top-3 left-1/2 -translate-x-1/2 z-50
3206
- w-11 h-11 rounded-lg
3207
- bg-black/90 backdrop-blur-sm
3208
- border-2 border-white/80
3209
- shadow-lg
3210
- flex items-center justify-center
3211
- transition-all duration-200
3871
+ fixed top-3 right-3 z-50
3872
+ px-3 py-2 rounded-xl
3873
+ bg-black/80 backdrop-blur-md
3874
+ border-2 border-red-400/60
3875
+ shadow-xl
3876
+ flex items-center gap-2
3877
+ transition-all duration-300
3212
3878
  hover:bg-red-600/30 hover:border-red-400 hover:scale-105
3213
3879
  active:scale-95
3214
3880
  disabled:opacity-40 disabled:cursor-not-allowed
3215
3881
  touch-manipulation
3216
3882
  `,
3217
3883
  style: {
3218
- paddingTop: "env(safe-area-inset-top, 0px)"
3884
+ paddingTop: "max(env(safe-area-inset-top, 0px), 8px)"
3219
3885
  },
3220
3886
  "aria-label": "Exit fullscreen",
3221
- title: "Exit fullscreen",
3222
- children: /* @__PURE__ */ jsx(X, { size: 22, className: "text-white" })
3887
+ children: [
3888
+ /* @__PURE__ */ jsx(X, { size: 16, className: "text-red-400" }),
3889
+ /* @__PURE__ */ jsx("span", { className: "text-white text-xs font-bold uppercase tracking-wider", children: "Exit" }),
3890
+ /* @__PURE__ */ jsx(Minimize2, { size: 14, className: "text-white/60" })
3891
+ ]
3223
3892
  }
3224
3893
  );
3225
3894
  }
3226
3895
  function FloatingFullscreenButton({ onClick, disabled = false }) {
3227
- return /* @__PURE__ */ jsx(
3896
+ const [pulse, setPulse] = useState(true);
3897
+ useEffect(() => {
3898
+ const timer = setTimeout(() => setPulse(false), 5e3);
3899
+ return () => clearTimeout(timer);
3900
+ }, []);
3901
+ return /* @__PURE__ */ jsxs(
3228
3902
  "button",
3229
3903
  {
3230
3904
  onClick,
3231
3905
  disabled,
3232
3906
  className: `
3233
3907
  absolute top-3 left-3 z-50
3234
- w-9 h-9 rounded-lg
3235
- bg-black/70 backdrop-blur-sm
3236
- border-2 border-white/50
3237
- shadow-md
3238
- flex items-center justify-center
3239
- transition-all duration-200
3240
- hover:bg-white/20 hover:border-white
3908
+ px-3 py-2 rounded-xl
3909
+ bg-black/80 backdrop-blur-md
3910
+ border-2 border-white/60
3911
+ shadow-xl
3912
+ flex items-center gap-2
3913
+ transition-all duration-300
3914
+ hover:bg-white/20 hover:border-white hover:scale-105
3241
3915
  active:scale-95
3242
3916
  disabled:opacity-40 disabled:cursor-not-allowed
3243
3917
  touch-manipulation
3918
+ ${pulse ? "animate-pulse border-green-400/80" : ""}
3244
3919
  `,
3245
- "aria-label": "Enter fullscreen",
3246
- title: "Enter fullscreen",
3247
- children: /* @__PURE__ */ jsx(Maximize, { size: 16, className: "text-white/80" })
3920
+ "aria-label": "Tap for fullscreen controls",
3921
+ children: [
3922
+ /* @__PURE__ */ jsx(Gamepad2, { size: 18, className: "text-green-400" }),
3923
+ /* @__PURE__ */ jsx("span", { className: "text-white text-xs font-bold uppercase tracking-wider whitespace-nowrap", children: "Tap for Controls" }),
3924
+ /* @__PURE__ */ jsx(Maximize, { size: 14, className: "text-white/60" })
3925
+ ]
3248
3926
  }
3249
3927
  );
3250
3928
  }
@@ -3263,9 +3941,10 @@ function GameOverlay({
3263
3941
  isLoadingSave,
3264
3942
  onSelectBios
3265
3943
  }) {
3944
+ const t = useKoinTranslation();
3266
3945
  const isLoading = status === "loading" || status === "ready" && isLoadingSave;
3267
3946
  if (isLoading) {
3268
- const message = status === "loading" ? { title: `Loading ${system}`, subtitle: "Initializing emulator" } : { title: "Loading Save", subtitle: `Preparing slot ${pendingSlot}` };
3947
+ const message = status === "loading" ? { title: t.overlay.loading.replace("{{system}}", system || ""), subtitle: t.overlay.initializing } : { title: t.overlay.loadingSave, subtitle: t.overlay.preparingSlot.replace("{{num}}", String(pendingSlot || "")) };
3269
3948
  return /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex flex-col items-center justify-center bg-black z-20", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-4", children: [
3270
3949
  /* @__PURE__ */ jsx(LoadingSpinner, { color: systemColor, size: "lg" }),
3271
3950
  /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
@@ -3303,12 +3982,11 @@ function GameOverlay({
3303
3982
  children: /* @__PURE__ */ jsx(Play, { className: "w-8 h-8 ml-1", style: { color: systemColor }, fill: systemColor })
3304
3983
  }
3305
3984
  ),
3306
- /* @__PURE__ */ jsx("span", { className: "font-mono text-lg uppercase tracking-wider", style: { color: systemColor }, children: hasPendingSave ? "Continue" : "Play" }),
3985
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-lg uppercase tracking-wider", style: { color: systemColor }, children: t.overlay.play }),
3307
3986
  hasPendingSave && /* @__PURE__ */ jsxs("span", { className: "text-gray-400 text-xs flex items-center gap-1", children: [
3308
3987
  /* @__PURE__ */ jsx(Save, { size: 12 }),
3309
- " Slot ",
3310
- pendingSlot,
3311
- " ready"
3988
+ " ",
3989
+ t.overlay.slotReady.replace("{{num}}", String(pendingSlot))
3312
3990
  ] })
3313
3991
  ]
3314
3992
  }
@@ -3320,7 +3998,7 @@ function GameOverlay({
3320
3998
  className: "mt-6 flex items-center gap-2 px-4 py-2 rounded-lg text-sm text-gray-400 hover:text-white hover:bg-white/10 transition-colors",
3321
3999
  children: [
3322
4000
  /* @__PURE__ */ jsx("div", { className: "w-2 h-2 rounded-full", style: { backgroundColor: systemColor } }),
3323
- /* @__PURE__ */ jsx("span", { className: "font-mono uppercase tracking-wider text-xs", children: "System Firmware" })
4001
+ /* @__PURE__ */ jsx("span", { className: "font-mono uppercase tracking-wider text-xs", children: t.overlay.systemFirmware })
3324
4002
  ]
3325
4003
  }
3326
4004
  )
@@ -3330,8 +4008,8 @@ function GameOverlay({
3330
4008
  return /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex flex-col items-center justify-center bg-black/90 z-20", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-4", children: [
3331
4009
  /* @__PURE__ */ jsx(AlertTriangle, { className: "w-12 h-12 text-red-500" }),
3332
4010
  /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
3333
- /* @__PURE__ */ jsx("p", { className: "text-red-400 font-mono uppercase tracking-widest text-sm", children: "System Error" }),
3334
- /* @__PURE__ */ jsx("p", { className: "text-gray-500 text-xs mt-1 max-w-xs", children: error || "Failed to initialize emulator" })
4011
+ /* @__PURE__ */ jsx("p", { className: "text-red-400 font-mono uppercase tracking-widest text-sm", children: t.overlay.systemError }),
4012
+ /* @__PURE__ */ jsx("p", { className: "text-gray-500 text-xs mt-1 max-w-xs", children: error || t.overlay.failedInit })
3335
4013
  ] }),
3336
4014
  /* @__PURE__ */ jsx(
3337
4015
  "button",
@@ -3339,7 +4017,7 @@ function GameOverlay({
3339
4017
  onClick: onStart,
3340
4018
  className: "mt-2 px-4 py-2 text-sm font-bold rounded-lg transition-colors",
3341
4019
  style: { backgroundColor: systemColor, color: "#000" },
3342
- children: "Retry"
4020
+ children: t.overlay.retry
3343
4021
  }
3344
4022
  )
3345
4023
  ] }) });
@@ -3357,7 +4035,7 @@ function GameOverlay({
3357
4035
  ] })
3358
4036
  }
3359
4037
  ),
3360
- /* @__PURE__ */ jsx("p", { className: "font-mono uppercase tracking-wider text-sm", style: { color: systemColor }, children: "Paused" })
4038
+ /* @__PURE__ */ jsx("p", { className: "font-mono uppercase tracking-wider text-sm", style: { color: systemColor }, children: t.overlay.paused })
3361
4039
  ] }) });
3362
4040
  }
3363
4041
  return null;
@@ -3466,6 +4144,7 @@ function ControlMapper({
3466
4144
  onClose,
3467
4145
  system
3468
4146
  }) {
4147
+ const t = useKoinTranslation();
3469
4148
  const [localControls, setLocalControls] = useState(controls);
3470
4149
  const [listeningFor, setListeningFor] = useState(null);
3471
4150
  const activeButtons = useMemo(() => {
@@ -3525,8 +4204,8 @@ function ControlMapper({
3525
4204
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
3526
4205
  /* @__PURE__ */ jsx(Gamepad2, { className: "text-retro-primary", size: 24 }),
3527
4206
  /* @__PURE__ */ jsxs("div", { children: [
3528
- /* @__PURE__ */ jsx("h2", { className: "text-lg font-bold text-white", children: "Control Mapping" }),
3529
- /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400", children: "Click a button and press a key to remap" })
4207
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-bold text-white", children: t.modals.controls.title }),
4208
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400", children: t.modals.controls.description })
3530
4209
  ] })
3531
4210
  ] }),
3532
4211
  /* @__PURE__ */ jsx(
@@ -3557,7 +4236,7 @@ function ControlMapper({
3557
4236
  px-2 py-1 rounded text-xs font-mono
3558
4237
  ${listeningFor === btn ? "bg-retro-primary/30 text-retro-primary animate-pulse" : "bg-black/50 text-white"}
3559
4238
  `,
3560
- children: listeningFor === btn ? "Press..." : formatKeyCode(localControls[btn] || "")
4239
+ children: listeningFor === btn ? t.modals.controls.pressKey : formatKeyCode(localControls[btn] || "")
3561
4240
  }
3562
4241
  )
3563
4242
  ]
@@ -3573,7 +4252,7 @@ function ControlMapper({
3573
4252
  className: "flex items-center gap-2 px-4 py-2 rounded-lg text-sm text-gray-400 hover:text-white hover:bg-white/10 transition-colors",
3574
4253
  children: [
3575
4254
  /* @__PURE__ */ jsx(RotateCcw, { size: 16 }),
3576
- "Reset to Default"
4255
+ t.modals.controls.reset
3577
4256
  ]
3578
4257
  }
3579
4258
  ),
@@ -3584,7 +4263,7 @@ function ControlMapper({
3584
4263
  className: "flex items-center gap-2 px-6 py-2 rounded-lg bg-retro-primary text-black font-bold text-sm hover:bg-retro-primary/90 transition-colors",
3585
4264
  children: [
3586
4265
  /* @__PURE__ */ jsx(Check, { size: 16 }),
3587
- "Save Controls"
4266
+ t.modals.controls.save
3588
4267
  ]
3589
4268
  }
3590
4269
  )
@@ -3764,6 +4443,7 @@ function GamepadMapper({
3764
4443
  onSave,
3765
4444
  systemColor = "#00FF41"
3766
4445
  }) {
4446
+ const t = useKoinTranslation();
3767
4447
  const [selectedPlayer, setSelectedPlayer] = useState(1);
3768
4448
  const [bindings, setBindings] = useState({});
3769
4449
  const [listeningFor, setListeningFor] = useState(null);
@@ -3860,8 +4540,8 @@ function GamepadMapper({
3860
4540
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
3861
4541
  /* @__PURE__ */ jsx(Joystick, { className: "text-retro-primary", size: 24, style: { color: systemColor } }),
3862
4542
  /* @__PURE__ */ jsxs("div", { children: [
3863
- /* @__PURE__ */ jsx("h2", { className: "text-lg font-bold text-white", children: "Gamepad Settings" }),
3864
- /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400", children: gamepads.length > 0 ? `${gamepads.length} controller${gamepads.length > 1 ? "s" : ""} connected` : "No controllers detected" })
4543
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-bold text-white", children: t.modals.gamepad.title }),
4544
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400", children: gamepads.length > 0 ? t.modals.gamepad.connected.replace("{{count}}", gamepads.length.toString()) : t.modals.gamepad.none })
3865
4545
  ] })
3866
4546
  ] }),
3867
4547
  /* @__PURE__ */ jsx(
@@ -3875,7 +4555,7 @@ function GamepadMapper({
3875
4555
  ] }),
3876
4556
  gamepads.length > 1 && /* @__PURE__ */ jsxs("div", { className: "px-6 py-3 border-b border-white/10 bg-black/30", children: [
3877
4557
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
3878
- /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-400 font-medium", children: "Player:" }),
4558
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-400 font-medium", children: t.modals.gamepad.player }),
3879
4559
  /* @__PURE__ */ jsx("div", { className: "flex gap-1", children: gamepads.map((gp) => /* @__PURE__ */ jsxs(
3880
4560
  "button",
3881
4561
  {
@@ -3908,10 +4588,10 @@ function GamepadMapper({
3908
4588
  /* @__PURE__ */ jsx(Joystick, { size: 56, className: "text-gray-600 animate-pulse" }),
3909
4589
  /* @__PURE__ */ jsx("div", { className: "absolute inset-0 -m-2 rounded-full border-2 border-dashed border-gray-600 animate-spin", style: { animationDuration: "8s" } })
3910
4590
  ] }),
3911
- /* @__PURE__ */ jsx("p", { className: "text-gray-300 font-medium mb-2", children: "No controller detected" }),
3912
- /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500 mb-4", children: "Press any button on your gamepad to connect" }),
4591
+ /* @__PURE__ */ jsx("p", { className: "text-gray-300 font-medium mb-2", children: t.modals.gamepad.noController }),
4592
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500 mb-4", children: t.modals.gamepad.pressAny }),
3913
4593
  /* @__PURE__ */ jsxs("div", { className: "inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-white/5 border border-white/10", children: [
3914
- /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-400", children: "Waiting for input..." }),
4594
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-400", children: t.modals.gamepad.waiting }),
3915
4595
  /* @__PURE__ */ jsxs("div", { className: "flex gap-1", children: [
3916
4596
  /* @__PURE__ */ jsx("div", { className: "w-1.5 h-1.5 rounded-full bg-gray-500 animate-bounce", style: { animationDelay: "0ms" } }),
3917
4597
  /* @__PURE__ */ jsx("div", { className: "w-1.5 h-1.5 rounded-full bg-gray-500 animate-bounce", style: { animationDelay: "150ms" } }),
@@ -3921,11 +4601,8 @@ function GamepadMapper({
3921
4601
  ] }),
3922
4602
  gamepads.length > 0 && /* @__PURE__ */ jsxs("div", { className: "p-4 space-y-6 max-h-[400px] overflow-y-auto", children: [
3923
4603
  listeningFor && /* @__PURE__ */ jsxs("div", { className: "p-4 rounded-lg bg-black/50 border border-retro-primary/50 text-center animate-pulse", style: { borderColor: `${systemColor}50` }, children: [
3924
- /* @__PURE__ */ jsxs("p", { className: "text-sm text-white mb-1", children: [
3925
- "Press a button on your controller for ",
3926
- /* @__PURE__ */ jsx("strong", { children: BUTTON_LABELS[listeningFor] })
3927
- ] }),
3928
- /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400", children: "Press Escape to cancel" })
4604
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-white mb-1", children: t.modals.gamepad.pressButton.replace("{{button}}", BUTTON_LABELS[listeningFor]) }),
4605
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400", children: t.modals.gamepad.pressEsc })
3929
4606
  ] }),
3930
4607
  BUTTON_GROUPS.map((group) => /* @__PURE__ */ jsxs("div", { children: [
3931
4608
  /* @__PURE__ */ jsx("h3", { className: "text-xs font-bold text-gray-500 uppercase tracking-wider mb-2", children: group.label }),
@@ -3957,7 +4634,7 @@ function GamepadMapper({
3957
4634
  backgroundColor: `${systemColor}30`,
3958
4635
  color: systemColor
3959
4636
  } : {},
3960
- children: listeningFor === btn ? "Press..." : formatGamepadButton(currentBindings[btn])
4637
+ children: listeningFor === btn ? t.controls.press : formatGamepadButton(currentBindings[btn])
3961
4638
  }
3962
4639
  )
3963
4640
  ]
@@ -3975,7 +4652,7 @@ function GamepadMapper({
3975
4652
  className: "flex items-center gap-2 px-4 py-2 rounded-lg text-sm text-gray-400 hover:text-white hover:bg-white/10 transition-colors disabled:opacity-50",
3976
4653
  children: [
3977
4654
  /* @__PURE__ */ jsx(RotateCcw, { size: 16 }),
3978
- "Reset to Default"
4655
+ t.modals.gamepad.reset
3979
4656
  ]
3980
4657
  }
3981
4658
  ),
@@ -3988,7 +4665,7 @@ function GamepadMapper({
3988
4665
  style: { backgroundColor: systemColor },
3989
4666
  children: [
3990
4667
  /* @__PURE__ */ jsx(Check, { size: 16 }),
3991
- "Save Settings"
4668
+ t.modals.gamepad.save
3992
4669
  ]
3993
4670
  }
3994
4671
  )
@@ -4003,7 +4680,8 @@ function CheatModal({
4003
4680
  onToggle,
4004
4681
  onClose
4005
4682
  }) {
4006
- const [copiedId, setCopiedId] = React5.useState(null);
4683
+ const t = useKoinTranslation();
4684
+ const [copiedId, setCopiedId] = React2.useState(null);
4007
4685
  if (!isOpen) return null;
4008
4686
  const handleCopy = async (code, id) => {
4009
4687
  await navigator.clipboard.writeText(code);
@@ -4023,13 +4701,8 @@ function CheatModal({
4023
4701
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
4024
4702
  /* @__PURE__ */ jsx(Code, { className: "text-purple-400", size: 24 }),
4025
4703
  /* @__PURE__ */ jsxs("div", { children: [
4026
- /* @__PURE__ */ jsx("h2", { className: "text-lg font-bold text-white", children: "Cheat Codes" }),
4027
- /* @__PURE__ */ jsxs("p", { className: "text-xs text-gray-400", children: [
4028
- cheats.length,
4029
- " cheat",
4030
- cheats.length !== 1 ? "s" : "",
4031
- " available"
4032
- ] })
4704
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-bold text-white", children: t.modals.cheats.title }),
4705
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400", children: t.modals.cheats.available.replace("{{count}}", cheats.length.toString()) })
4033
4706
  ] })
4034
4707
  ] }),
4035
4708
  /* @__PURE__ */ jsx(
@@ -4043,8 +4716,8 @@ function CheatModal({
4043
4716
  ] }),
4044
4717
  /* @__PURE__ */ jsx("div", { className: "p-4 space-y-2 max-h-[400px] overflow-y-auto", children: cheats.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "text-center py-12 text-gray-500", children: [
4045
4718
  /* @__PURE__ */ jsx(Code, { size: 48, className: "mx-auto mb-3 opacity-50" }),
4046
- /* @__PURE__ */ jsx("p", { className: "font-medium", children: "No cheats available" }),
4047
- /* @__PURE__ */ jsx("p", { className: "text-sm mt-1", children: "No cheat codes found for this game" })
4719
+ /* @__PURE__ */ jsx("p", { className: "font-medium", children: t.modals.cheats.emptyTitle }),
4720
+ /* @__PURE__ */ jsx("p", { className: "text-sm mt-1", children: t.modals.cheats.emptyDesc })
4048
4721
  ] }) : cheats.map((cheat) => {
4049
4722
  const isActive = activeCheats.has(cheat.id);
4050
4723
  return /* @__PURE__ */ jsxs(
@@ -4078,7 +4751,7 @@ function CheatModal({
4078
4751
  handleCopy(cheat.code, cheat.id);
4079
4752
  },
4080
4753
  className: "p-1.5 rounded hover:bg-white/10 text-gray-500 hover:text-white transition-colors",
4081
- title: "Copy code",
4754
+ title: t.modals.cheats.copy,
4082
4755
  children: copiedId === cheat.id ? /* @__PURE__ */ jsx(Check, { size: 14, className: "text-green-400" }) : /* @__PURE__ */ jsx(Copy, { size: 14 })
4083
4756
  }
4084
4757
  )
@@ -4089,7 +4762,7 @@ function CheatModal({
4089
4762
  cheat.id
4090
4763
  );
4091
4764
  }) }),
4092
- /* @__PURE__ */ jsx("div", { className: "px-6 py-3 bg-black/30 border-t border-white/10", children: /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 text-center", children: activeCheats.size > 0 ? `${activeCheats.size} cheat${activeCheats.size !== 1 ? "s" : ""} active` : "Click a cheat to toggle it on/off" }) })
4765
+ /* @__PURE__ */ jsx("div", { className: "px-6 py-3 bg-black/30 border-t border-white/10", children: /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 text-center", children: activeCheats.size > 0 ? t.modals.cheats.active.replace("{{count}}", activeCheats.size.toString()) : t.modals.cheats.toggleHint }) })
4093
4766
  ] })
4094
4767
  ] });
4095
4768
  }
@@ -4102,18 +4775,22 @@ function formatBytes(bytes) {
4102
4775
  return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
4103
4776
  }
4104
4777
  function formatTimestamp(timestamp) {
4105
- const date = new Date(timestamp);
4106
- const now = /* @__PURE__ */ new Date();
4107
- const diff = now.getTime() - date.getTime();
4108
- if (diff < 6e4) return "Just now";
4109
- if (diff < 36e5) return `${Math.floor(diff / 6e4)}m ago`;
4110
- if (diff < 864e5) return `${Math.floor(diff / 36e5)}h ago`;
4111
- return date.toLocaleDateString("en-US", {
4112
- month: "short",
4113
- day: "numeric",
4114
- hour: "2-digit",
4115
- minute: "2-digit"
4116
- });
4778
+ try {
4779
+ const date = new Date(timestamp);
4780
+ const now = /* @__PURE__ */ new Date();
4781
+ const diff = now.getTime() - date.getTime();
4782
+ if (diff < 6e4) return "Just now";
4783
+ if (diff < 36e5) return `${Math.floor(diff / 6e4)}m ago`;
4784
+ if (diff < 864e5) return `${Math.floor(diff / 36e5)}h ago`;
4785
+ return date.toLocaleDateString("en-US", {
4786
+ month: "short",
4787
+ day: "numeric",
4788
+ hour: "2-digit",
4789
+ minute: "2-digit"
4790
+ });
4791
+ } catch {
4792
+ return "Unknown";
4793
+ }
4117
4794
  }
4118
4795
  function SaveSlotModal({
4119
4796
  isOpen,
@@ -4127,6 +4804,7 @@ function SaveSlotModal({
4127
4804
  maxSlots = 5,
4128
4805
  onUpgrade
4129
4806
  }) {
4807
+ const t = useKoinTranslation();
4130
4808
  if (!isOpen) return null;
4131
4809
  const isSaveMode = mode === "save";
4132
4810
  const allSlots = [1, 2, 3, 4, 5];
@@ -4155,8 +4833,8 @@ function SaveSlotModal({
4155
4833
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
4156
4834
  isSaveMode ? /* @__PURE__ */ jsx(Save, { className: "text-retro-primary", size: 24 }) : /* @__PURE__ */ jsx(Download, { className: "text-retro-primary", size: 24 }),
4157
4835
  /* @__PURE__ */ jsxs("div", { children: [
4158
- /* @__PURE__ */ jsx("h2", { className: "text-lg font-bold text-white", children: isSaveMode ? "Save Game" : "Load Game" }),
4159
- /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400", children: isSaveMode ? "Choose a slot to save your progress" : "Select a save to restore" })
4836
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-bold text-white", children: isSaveMode ? t.modals.saveSlots.saveTitle : t.modals.saveSlots.loadTitle }),
4837
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400", children: isSaveMode ? t.modals.saveSlots.subtitleSave : t.modals.saveSlots.subtitleLoad })
4160
4838
  ] })
4161
4839
  ] }),
4162
4840
  /* @__PURE__ */ jsx(
@@ -4170,7 +4848,7 @@ function SaveSlotModal({
4170
4848
  ] }),
4171
4849
  /* @__PURE__ */ jsx("div", { className: "p-4 space-y-2 max-h-[400px] overflow-y-auto", children: isLoading ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-gray-400", children: [
4172
4850
  /* @__PURE__ */ jsx(Loader2, { className: "w-8 h-8 animate-spin mb-3" }),
4173
- /* @__PURE__ */ jsx("span", { className: "text-sm", children: "Loading saves..." })
4851
+ /* @__PURE__ */ jsx("span", { className: "text-sm", children: t.modals.saveSlots.loading })
4174
4852
  ] }) : displaySlots.map((slotNum) => {
4175
4853
  const slotData = getSlotData(slotNum);
4176
4854
  const isEmpty = !slotData;
@@ -4187,14 +4865,10 @@ function SaveSlotModal({
4187
4865
  children: [
4188
4866
  /* @__PURE__ */ jsx("div", { className: "flex-shrink-0 w-12 h-12 rounded-lg flex items-center justify-center font-bold text-xl bg-gray-700 text-gray-500", children: /* @__PURE__ */ jsx(Lock, { size: 20 }) }),
4189
4867
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
4190
- /* @__PURE__ */ jsxs("p", { className: "font-medium text-gray-400 group-hover:text-white transition-colors", children: [
4191
- "Slot ",
4192
- slotNum,
4193
- " Locked"
4194
- ] }),
4868
+ /* @__PURE__ */ jsx("p", { className: "font-medium text-gray-400 group-hover:text-white transition-colors", children: t.modals.saveSlots.locked.replace("{{num}}", slotNum.toString()) }),
4195
4869
  /* @__PURE__ */ jsxs("p", { className: "text-xs text-gray-500 group-hover:text-retro-primary transition-colors flex items-center gap-1", children: [
4196
4870
  /* @__PURE__ */ jsx(Zap, { size: 10 }),
4197
- "Upgrade to unlock more save slots"
4871
+ t.modals.saveSlots.upgrade
4198
4872
  ] })
4199
4873
  ] })
4200
4874
  ]
@@ -4218,8 +4892,8 @@ function SaveSlotModal({
4218
4892
  /* @__PURE__ */ jsx("div", { className: "flex-1 min-w-0", children: isAutoSaveSlot ? (
4219
4893
  // Auto-save slot - special display
4220
4894
  isEmpty ? /* @__PURE__ */ jsxs("div", { className: "text-retro-primary/70", children: [
4221
- /* @__PURE__ */ jsx("p", { className: "font-medium", children: "Auto-Save Slot" }),
4222
- /* @__PURE__ */ jsx("p", { className: "text-xs text-retro-primary/50", children: "Reserved for automatic saves" })
4895
+ /* @__PURE__ */ jsx("p", { className: "font-medium", children: t.modals.saveSlots.autoSave }),
4896
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-retro-primary/50", children: t.modals.saveSlots.autoSaveDesc })
4223
4897
  ] }) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
4224
4898
  slotData.screenshot && /* @__PURE__ */ jsx("div", { className: "flex-shrink-0 w-16 h-12 rounded border border-retro-primary/30 overflow-hidden bg-black", children: /* @__PURE__ */ jsx(
4225
4899
  "img",
@@ -4233,7 +4907,7 @@ function SaveSlotModal({
4233
4907
  }
4234
4908
  ) }),
4235
4909
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
4236
- /* @__PURE__ */ jsx("p", { className: "font-medium text-retro-primary truncate", children: "Auto-Save" }),
4910
+ /* @__PURE__ */ jsx("p", { className: "font-medium text-retro-primary truncate", children: t.modals.saveSlots.autoSave }),
4237
4911
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-xs text-retro-primary/60 mt-1", children: [
4238
4912
  /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
4239
4913
  /* @__PURE__ */ jsx(Clock, { size: 12 }),
@@ -4247,8 +4921,8 @@ function SaveSlotModal({
4247
4921
  ] })
4248
4922
  ] })
4249
4923
  ) : isEmpty ? /* @__PURE__ */ jsxs("div", { className: "text-gray-500", children: [
4250
- /* @__PURE__ */ jsx("p", { className: "font-medium", children: "Empty Slot" }),
4251
- /* @__PURE__ */ jsx("p", { className: "text-xs", children: "No save data" })
4924
+ /* @__PURE__ */ jsx("p", { className: "font-medium", children: t.modals.saveSlots.emptySlot }),
4925
+ /* @__PURE__ */ jsx("p", { className: "text-xs", children: t.modals.saveSlots.noData })
4252
4926
  ] }) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
4253
4927
  slotData.screenshot && /* @__PURE__ */ jsx("div", { className: "flex-shrink-0 w-16 h-12 rounded border border-white/10 overflow-hidden bg-black", children: /* @__PURE__ */ jsx(
4254
4928
  "img",
@@ -4262,10 +4936,7 @@ function SaveSlotModal({
4262
4936
  }
4263
4937
  ) }),
4264
4938
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
4265
- /* @__PURE__ */ jsxs("p", { className: "font-medium text-white truncate", children: [
4266
- "Slot ",
4267
- slotNum
4268
- ] }),
4939
+ /* @__PURE__ */ jsx("p", { className: "font-medium text-white truncate", children: t.modals.saveSlots.slot.replace("{{num}}", slotNum.toString()) }),
4269
4940
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 text-xs text-gray-400 mt-1", children: [
4270
4941
  /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
4271
4942
  /* @__PURE__ */ jsx(Clock, { size: 12 }),
@@ -4295,7 +4966,7 @@ function SaveSlotModal({
4295
4966
  slotNum
4296
4967
  );
4297
4968
  }) }),
4298
- /* @__PURE__ */ jsx("div", { className: "px-6 py-3 bg-black/30 border-t border-white/10", children: /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 text-center", children: isSaveMode ? "Saves are stored in the cloud and sync across devices" : "Your progress will be restored to the selected save point" }) })
4969
+ /* @__PURE__ */ jsx("div", { className: "px-6 py-3 bg-black/30 border-t border-white/10", children: /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 text-center", children: isSaveMode ? t.modals.saveSlots.footerSave : t.modals.saveSlots.footerLoad }) })
4299
4970
  ] })
4300
4971
  ] });
4301
4972
  }
@@ -4307,6 +4978,7 @@ function BiosSelectionModal({
4307
4978
  onSelectBios,
4308
4979
  systemColor = "#00FF41"
4309
4980
  }) {
4981
+ const t = useKoinTranslation();
4310
4982
  if (!isOpen) return null;
4311
4983
  return /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
4312
4984
  /* @__PURE__ */ jsx(
@@ -4326,8 +4998,8 @@ function BiosSelectionModal({
4326
4998
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
4327
4999
  /* @__PURE__ */ jsx(Cpu, { size: 24, style: { color: systemColor } }),
4328
5000
  /* @__PURE__ */ jsxs("div", { children: [
4329
- /* @__PURE__ */ jsx("h2", { className: "text-lg font-bold text-white uppercase tracking-wide", children: "BIOS Selection" }),
4330
- /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400", children: "Select a BIOS file to use for this game" })
5001
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-bold text-white uppercase tracking-wide", children: t.modals.bios.title }),
5002
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-400", children: t.modals.bios.description })
4331
5003
  ] })
4332
5004
  ] }),
4333
5005
  /* @__PURE__ */ jsx(
@@ -4343,8 +5015,9 @@ function BiosSelectionModal({
4343
5015
  /* @__PURE__ */ jsxs("div", { className: "mb-4 p-3 rounded bg-yellow-500/10 border border-yellow-500/20 flex items-start gap-3", children: [
4344
5016
  /* @__PURE__ */ jsx(AlertCircle, { className: "shrink-0 text-yellow-500 mt-0.5", size: 16 }),
4345
5017
  /* @__PURE__ */ jsxs("div", { className: "text-xs text-yellow-200/80", children: [
4346
- /* @__PURE__ */ jsx("strong", { children: "Note:" }),
4347
- " Changing BIOS requires the emulator to restart. Your unsaved progress will be lost."
5018
+ /* @__PURE__ */ jsx("strong", { children: t.modals.bios.warningTitle }),
5019
+ " ",
5020
+ t.modals.bios.warning
4348
5021
  ] })
4349
5022
  ] }),
4350
5023
  /* @__PURE__ */ jsxs(
@@ -4374,10 +5047,10 @@ function BiosSelectionModal({
4374
5047
  ),
4375
5048
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
4376
5049
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
4377
- /* @__PURE__ */ jsx("span", { className: `font-mono font-bold truncate ${!currentBiosId ? "text-white" : "text-gray-300"}`, children: "System Default" }),
4378
- !currentBiosId && /* @__PURE__ */ jsx("span", { className: "px-1.5 py-0.5 rounded text-[10px] font-bold bg-white/20 text-white uppercase tracking-wider", children: "Active" })
5050
+ /* @__PURE__ */ jsx("span", { className: `font-mono font-bold truncate ${!currentBiosId ? "text-white" : "text-gray-300"}`, children: t.modals.bios.systemDefault }),
5051
+ !currentBiosId && /* @__PURE__ */ jsx("span", { className: "px-1.5 py-0.5 rounded text-[10px] font-bold bg-white/20 text-white uppercase tracking-wider", children: t.modals.bios.active })
4379
5052
  ] }),
4380
- /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 truncate mt-0.5", children: "Use the emulator's built-in or default BIOS" })
5053
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 truncate mt-0.5", children: t.modals.bios.defaultDesc })
4381
5054
  ] }),
4382
5055
  !currentBiosId && /* @__PURE__ */ jsx(Check, { size: 20, style: { color: systemColor } })
4383
5056
  ]
@@ -4385,8 +5058,8 @@ function BiosSelectionModal({
4385
5058
  ),
4386
5059
  biosOptions.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "text-center py-8 text-gray-500", children: [
4387
5060
  /* @__PURE__ */ jsx(FileCode, { size: 48, className: "mx-auto mb-3 opacity-30" }),
4388
- /* @__PURE__ */ jsx("p", { className: "text-sm", children: "No BIOS files found for this console." }),
4389
- /* @__PURE__ */ jsx("p", { className: "text-[10px] mt-1 text-gray-600", children: "Upload BIOS files in your Dashboard Library." })
5061
+ /* @__PURE__ */ jsx("p", { className: "text-sm", children: t.modals.bios.emptyTitle }),
5062
+ /* @__PURE__ */ jsx("p", { className: "text-[10px] mt-1 text-gray-600", children: t.modals.bios.emptyDesc })
4390
5063
  ] }) : biosOptions.map((bios) => {
4391
5064
  const isSelected = bios.id === currentBiosId;
4392
5065
  return /* @__PURE__ */ jsxs(
@@ -4417,7 +5090,7 @@ function BiosSelectionModal({
4417
5090
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
4418
5091
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
4419
5092
  /* @__PURE__ */ jsx("span", { className: `font-mono font-bold truncate ${isSelected ? "text-white" : "text-gray-300"}`, children: bios.name }),
4420
- isSelected && /* @__PURE__ */ jsx("span", { className: "px-1.5 py-0.5 rounded text-[10px] font-bold bg-white/20 text-white uppercase tracking-wider", children: "Active" })
5093
+ isSelected && /* @__PURE__ */ jsx("span", { className: "px-1.5 py-0.5 rounded text-[10px] font-bold bg-white/20 text-white uppercase tracking-wider", children: t.modals.bios.active })
4421
5094
  ] }),
4422
5095
  bios.description && /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 truncate mt-0.5", children: bios.description })
4423
5096
  ] }),
@@ -4428,12 +5101,84 @@ function BiosSelectionModal({
4428
5101
  );
4429
5102
  })
4430
5103
  ] }),
4431
- /* @__PURE__ */ jsx("div", { className: "px-6 py-3 bg-black/30 border-t border-white/10 text-center", children: /* @__PURE__ */ jsx("p", { className: "text-[10px] text-gray-500 uppercase tracking-widest", children: "System Firmware Settings" }) })
5104
+ /* @__PURE__ */ jsx("div", { className: "px-6 py-3 bg-black/30 border-t border-white/10 text-center", children: /* @__PURE__ */ jsx("p", { className: "text-[10px] text-gray-500 uppercase tracking-widest", children: t.modals.bios.footer }) })
4432
5105
  ]
4433
5106
  }
4434
5107
  )
4435
5108
  ] });
4436
5109
  }
5110
+ function SettingsModal({
5111
+ isOpen,
5112
+ onClose,
5113
+ currentLanguage,
5114
+ onLanguageChange,
5115
+ systemColor = "#00FF41"
5116
+ }) {
5117
+ const t = useKoinTranslation();
5118
+ if (!isOpen) return null;
5119
+ const languages = [
5120
+ { code: "en", name: "English" },
5121
+ { code: "es", name: "Espa\xF1ol" },
5122
+ { code: "fr", name: "Fran\xE7ais" }
5123
+ ];
5124
+ return /* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
5125
+ /* @__PURE__ */ jsx(
5126
+ "div",
5127
+ {
5128
+ className: "absolute inset-0 bg-black/80 backdrop-blur-sm",
5129
+ onClick: onClose
5130
+ }
5131
+ ),
5132
+ /* @__PURE__ */ jsxs("div", { className: "relative bg-gray-900 border border-white/10 rounded-xl shadow-2xl w-full max-w-sm mx-4 overflow-hidden animate-in fade-in zoom-in-95 duration-200", children: [
5133
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-6 py-4 border-b border-white/10 bg-black/50", children: [
5134
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
5135
+ /* @__PURE__ */ jsx(Settings, { className: "text-white", size: 20 }),
5136
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-bold text-white", children: t.settings.title })
5137
+ ] }),
5138
+ /* @__PURE__ */ jsx(
5139
+ "button",
5140
+ {
5141
+ onClick: onClose,
5142
+ className: "p-2 rounded-lg hover:bg-white/10 text-gray-400 hover:text-white transition-colors",
5143
+ children: /* @__PURE__ */ jsx(X, { size: 20 })
5144
+ }
5145
+ )
5146
+ ] }),
5147
+ /* @__PURE__ */ jsx("div", { className: "p-6 space-y-6", children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
5148
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm font-medium text-gray-400", children: [
5149
+ /* @__PURE__ */ jsx(Globe, { size: 16 }),
5150
+ /* @__PURE__ */ jsx("span", { children: t.settings.language })
5151
+ ] }),
5152
+ /* @__PURE__ */ jsx("div", { className: "grid gap-2", children: languages.map((lang) => {
5153
+ const isActive = currentLanguage === lang.code;
5154
+ return /* @__PURE__ */ jsxs(
5155
+ "button",
5156
+ {
5157
+ onClick: () => onLanguageChange(lang.code),
5158
+ className: `
5159
+ flex items-center justify-between px-4 py-3 rounded-lg border transition-all
5160
+ ${isActive ? "bg-white/10 border-white/20 text-white" : "bg-black/20 border-transparent text-gray-400 hover:bg-white/5 hover:text-white"}
5161
+ `,
5162
+ children: [
5163
+ /* @__PURE__ */ jsx("span", { children: lang.name }),
5164
+ isActive && /* @__PURE__ */ jsx(Check, { size: 16, style: { color: systemColor } })
5165
+ ]
5166
+ },
5167
+ lang.code
5168
+ );
5169
+ }) })
5170
+ ] }) }),
5171
+ /* @__PURE__ */ jsx("div", { className: "px-6 py-4 bg-black/30 border-t border-white/10 text-center", children: /* @__PURE__ */ jsx(
5172
+ "button",
5173
+ {
5174
+ onClick: onClose,
5175
+ className: "text-sm text-gray-500 hover:text-white transition-colors",
5176
+ children: t.modals.shortcuts.pressEsc
5177
+ }
5178
+ ) })
5179
+ ] })
5180
+ ] });
5181
+ }
4437
5182
  function GameModals({
4438
5183
  controlsModalOpen,
4439
5184
  setControlsModalOpen,
@@ -4465,7 +5210,11 @@ function GameModals({
4465
5210
  setBiosModalOpen,
4466
5211
  availableBios,
4467
5212
  currentBiosId,
4468
- onSelectBios
5213
+ onSelectBios,
5214
+ settingsModalOpen,
5215
+ setSettingsModalOpen,
5216
+ currentLanguage,
5217
+ onLanguageChange
4469
5218
  }) {
4470
5219
  return /* @__PURE__ */ jsxs(Fragment, { children: [
4471
5220
  /* @__PURE__ */ jsx(
@@ -4541,6 +5290,19 @@ function GameModals({
4541
5290
  },
4542
5291
  systemColor
4543
5292
  }
5293
+ ),
5294
+ /* @__PURE__ */ jsx(
5295
+ SettingsModal,
5296
+ {
5297
+ isOpen: settingsModalOpen,
5298
+ onClose: () => {
5299
+ setSettingsModalOpen(false);
5300
+ onResume();
5301
+ },
5302
+ currentLanguage,
5303
+ onLanguageChange,
5304
+ systemColor
5305
+ }
4544
5306
  )
4545
5307
  ] });
4546
5308
  }
@@ -4555,10 +5317,11 @@ function LoginForm({
4555
5317
  error,
4556
5318
  onSubmit
4557
5319
  }) {
5320
+ const t = useKoinTranslation();
4558
5321
  return /* @__PURE__ */ jsxs("form", { onSubmit, className: "p-4 space-y-3", children: [
4559
5322
  /* @__PURE__ */ jsxs("div", { className: "text-center mb-4", children: [
4560
5323
  /* @__PURE__ */ jsx("div", { className: "w-16 h-16 mx-auto mb-3 rounded-full bg-gradient-to-br from-yellow-500/20 to-orange-500/20 border border-yellow-500/30 flex items-center justify-center", children: /* @__PURE__ */ jsx(Trophy, { className: "text-yellow-400", size: 32 }) }),
4561
- /* @__PURE__ */ jsx("p", { className: "text-gray-400 text-xs", children: "Connect your account to track achievements" }),
5324
+ /* @__PURE__ */ jsx("p", { className: "text-gray-400 text-xs", children: t.retroAchievements.connectAccount }),
4562
5325
  /* @__PURE__ */ jsxs(
4563
5326
  "a",
4564
5327
  {
@@ -4567,14 +5330,14 @@ function LoginForm({
4567
5330
  rel: "noopener noreferrer",
4568
5331
  className: "text-yellow-400 hover:text-yellow-300 text-xs inline-flex items-center gap-1 mt-1",
4569
5332
  children: [
4570
- "Create an account",
5333
+ t.retroAchievements.createAccount,
4571
5334
  /* @__PURE__ */ jsx(ExternalLink, { size: 10 })
4572
5335
  ]
4573
5336
  }
4574
5337
  )
4575
5338
  ] }),
4576
5339
  /* @__PURE__ */ jsxs("div", { children: [
4577
- /* @__PURE__ */ jsx("label", { className: "block text-[10px] text-gray-500 mb-1 uppercase tracking-wider", children: "Username" }),
5340
+ /* @__PURE__ */ jsx("label", { className: "block text-[10px] text-gray-500 mb-1 uppercase tracking-wider", children: t.retroAchievements.username }),
4578
5341
  /* @__PURE__ */ jsxs("div", { className: "relative", children: [
4579
5342
  /* @__PURE__ */ jsx(User, { className: "absolute left-2.5 top-1/2 -translate-y-1/2 text-gray-500", size: 14 }),
4580
5343
  /* @__PURE__ */ jsx(
@@ -4583,7 +5346,7 @@ function LoginForm({
4583
5346
  type: "text",
4584
5347
  value: username,
4585
5348
  onChange: (e) => setUsername(e.target.value),
4586
- placeholder: "Your RA username",
5349
+ placeholder: t.retroAchievements.yourUsername,
4587
5350
  className: "w-full pl-8 pr-3 py-2 text-sm bg-black/50 border border-white/20 rounded-lg text-white placeholder-gray-500 focus:border-yellow-500/50 focus:outline-none transition-colors",
4588
5351
  disabled: isLoading
4589
5352
  }
@@ -4591,7 +5354,7 @@ function LoginForm({
4591
5354
  ] })
4592
5355
  ] }),
4593
5356
  /* @__PURE__ */ jsxs("div", { children: [
4594
- /* @__PURE__ */ jsx("label", { className: "block text-[10px] text-gray-500 mb-1 uppercase tracking-wider", children: "Password" }),
5357
+ /* @__PURE__ */ jsx("label", { className: "block text-[10px] text-gray-500 mb-1 uppercase tracking-wider", children: t.retroAchievements.password }),
4595
5358
  /* @__PURE__ */ jsxs("div", { className: "relative", children: [
4596
5359
  /* @__PURE__ */ jsx(Lock, { className: "absolute left-2.5 top-1/2 -translate-y-1/2 text-gray-500", size: 14 }),
4597
5360
  /* @__PURE__ */ jsx(
@@ -4600,7 +5363,7 @@ function LoginForm({
4600
5363
  type: showPassword ? "text" : "password",
4601
5364
  value: password,
4602
5365
  onChange: (e) => setPassword(e.target.value),
4603
- placeholder: "Your RA password",
5366
+ placeholder: t.retroAchievements.yourPassword,
4604
5367
  className: "w-full pl-8 pr-9 py-2 text-sm bg-black/50 border border-white/20 rounded-lg text-white placeholder-gray-500 focus:border-yellow-500/50 focus:outline-none transition-colors",
4605
5368
  disabled: isLoading
4606
5369
  }
@@ -4628,16 +5391,20 @@ function LoginForm({
4628
5391
  className: "w-full flex items-center justify-center gap-2 px-3 py-2.5 bg-gradient-to-r from-yellow-500 to-orange-500 hover:from-yellow-400 hover:to-orange-400 text-black text-sm font-bold rounded-lg transition-all disabled:opacity-50 disabled:cursor-not-allowed",
4629
5392
  children: isLoading ? /* @__PURE__ */ jsxs(Fragment, { children: [
4630
5393
  /* @__PURE__ */ jsx(Loader2, { className: "animate-spin", size: 14 }),
4631
- /* @__PURE__ */ jsx("span", { children: "Connecting..." })
5394
+ /* @__PURE__ */ jsx("span", { children: t.retroAchievements.connecting })
4632
5395
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
4633
5396
  /* @__PURE__ */ jsx(Trophy, { size: 14 }),
4634
- /* @__PURE__ */ jsx("span", { children: "Connect" })
5397
+ /* @__PURE__ */ jsx("span", { children: t.retroAchievements.login })
4635
5398
  ] })
4636
5399
  }
4637
5400
  ),
4638
5401
  /* @__PURE__ */ jsx("div", { className: "p-2 bg-blue-500/10 border border-blue-500/20 rounded-lg", children: /* @__PURE__ */ jsxs("p", { className: "text-[10px] text-blue-300 leading-relaxed", children: [
4639
- /* @__PURE__ */ jsx("strong", { children: "\u{1F512} Privacy:" }),
4640
- " Your password is only used to authenticate with RetroAchievements. It is never stored."
5402
+ /* @__PURE__ */ jsxs("strong", { children: [
5403
+ "\u{1F512} ",
5404
+ t.retroAchievements.privacy
5405
+ ] }),
5406
+ " ",
5407
+ t.retroAchievements.privacyText
4641
5408
  ] }) })
4642
5409
  ] });
4643
5410
  }
@@ -4661,6 +5428,7 @@ function SettingsTab({
4661
5428
  progress,
4662
5429
  onLogout
4663
5430
  }) {
5431
+ const t = useKoinTranslation();
4664
5432
  return /* @__PURE__ */ jsxs("div", { className: "p-3 space-y-3", children: [
4665
5433
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 p-3 bg-black/30 rounded-lg border border-yellow-500/20", children: [
4666
5434
  /* @__PURE__ */ jsx("div", { className: "w-12 h-12 rounded-lg bg-gray-800 overflow-hidden border-2 border-yellow-500/50 flex-shrink-0", children: credentials.avatarUrl ? /* @__PURE__ */ jsx(
@@ -4730,7 +5498,7 @@ function SettingsTab({
4730
5498
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
4731
5499
  /* @__PURE__ */ jsx(Shield, { className: hardcoreEnabled ? "text-red-500" : "text-gray-500", size: 16 }),
4732
5500
  /* @__PURE__ */ jsxs("div", { children: [
4733
- /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-white", children: "Hardcore Mode" }),
5501
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-white", children: t.retroAchievements.hardcore }),
4734
5502
  /* @__PURE__ */ jsx("p", { className: "text-[10px] text-gray-500", children: "2x points, no savestates" })
4735
5503
  ] })
4736
5504
  ] }),
@@ -4748,11 +5516,11 @@ function SettingsTab({
4748
5516
  }
4749
5517
  )
4750
5518
  ] }),
4751
- hardcoreEnabled && /* @__PURE__ */ jsx("div", { className: "mt-2 p-2 bg-red-500/10 border border-red-500/20 rounded text-[10px] text-red-300", children: "Savestates, rewind, cheats & slow-mo disabled" })
5519
+ hardcoreEnabled && /* @__PURE__ */ jsx("div", { className: "mt-2 p-2 bg-red-500/10 border border-red-500/20 rounded text-[10px] text-red-300", children: t.common.disabledInHardcore })
4752
5520
  ] }),
4753
5521
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-xs px-1", children: [
4754
5522
  /* @__PURE__ */ jsx(CheckCircle, { className: "text-green-500", size: 12 }),
4755
- /* @__PURE__ */ jsx("span", { className: "text-green-400", children: "Connected" })
5523
+ /* @__PURE__ */ jsx("span", { className: "text-green-400", children: t.retroAchievements.connectedStatus })
4756
5524
  ] }),
4757
5525
  /* @__PURE__ */ jsxs(
4758
5526
  "button",
@@ -4761,7 +5529,7 @@ function SettingsTab({
4761
5529
  className: "w-full flex items-center justify-center gap-2 px-3 py-2 bg-gray-800 hover:bg-gray-700 text-white text-xs rounded-lg transition-colors border border-white/10",
4762
5530
  children: [
4763
5531
  /* @__PURE__ */ jsx(LogOut, { size: 14 }),
4764
- /* @__PURE__ */ jsx("span", { children: "Sign Out" })
5532
+ /* @__PURE__ */ jsx("span", { children: t.retroAchievements.logout })
4765
5533
  ]
4766
5534
  }
4767
5535
  )
@@ -4778,18 +5546,19 @@ function AchievementsTab({
4778
5546
  earnedPoints,
4779
5547
  totalPoints
4780
5548
  }) {
5549
+ const t = useKoinTranslation();
4781
5550
  if (!currentGame) {
4782
5551
  return /* @__PURE__ */ jsxs("div", { className: "p-6 text-center", children: [
4783
5552
  /* @__PURE__ */ jsx("div", { className: "w-12 h-12 mx-auto mb-3 rounded-full bg-gray-800 flex items-center justify-center", children: /* @__PURE__ */ jsx(Trophy, { className: "text-gray-600", size: 24 }) }),
4784
- /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-400", children: "No game loaded" }),
4785
- /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 mt-1", children: "Load a game to see achievements" })
5553
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-400", children: t.retroAchievements.noGame }),
5554
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 mt-1", children: t.retroAchievements.loadGame })
4786
5555
  ] });
4787
5556
  }
4788
5557
  if (achievements.length === 0) {
4789
5558
  return /* @__PURE__ */ jsxs("div", { className: "p-6 text-center", children: [
4790
5559
  /* @__PURE__ */ jsx("div", { className: "w-12 h-12 mx-auto mb-3 rounded-full bg-gray-800 flex items-center justify-center", children: /* @__PURE__ */ jsx(Trophy, { className: "text-gray-600", size: 24 }) }),
4791
- /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-400", children: "No achievements found" }),
4792
- /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 mt-1", children: "This game may not be supported" })
5560
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-400", children: t.retroAchievements.noAchievements }),
5561
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 mt-1", children: t.retroAchievements.notSupported })
4793
5562
  ] });
4794
5563
  }
4795
5564
  return /* @__PURE__ */ jsxs("div", { children: [
@@ -4842,7 +5611,7 @@ function AchievementsTab({
4842
5611
  {
4843
5612
  onClick: () => setFilter(f),
4844
5613
  className: `px-2 py-0.5 text-[10px] rounded-full border transition-colors ${filter === f ? "bg-yellow-500/20 border-yellow-500/50 text-yellow-400" : "border-gray-700 text-gray-500 hover:text-gray-300"}`,
4845
- children: f.charAt(0).toUpperCase() + f.slice(1)
5614
+ children: t.retroAchievements.filters[f]
4846
5615
  },
4847
5616
  f
4848
5617
  )) })
@@ -4881,8 +5650,7 @@ function AchievementsTab({
4881
5650
  /* @__PURE__ */ jsxs("div", { className: "p-2 text-[10px] text-gray-500 text-center border-t border-gray-800/50", children: [
4882
5651
  progress,
4883
5652
  "% complete \u2022 ",
4884
- totalPoints - earnedPoints,
4885
- " pts remaining"
5653
+ t.retroAchievements.ptsRemaining.replace("{{count}}", (totalPoints - earnedPoints).toString())
4886
5654
  ] })
4887
5655
  ] });
4888
5656
  }
@@ -4901,6 +5669,7 @@ function RASidebar({
4901
5669
  achievements,
4902
5670
  unlockedIds
4903
5671
  }) {
5672
+ const t = useKoinTranslation();
4904
5673
  const defaultTab = isLoggedIn && currentGame && achievements.length > 0 ? "achievements" : "settings";
4905
5674
  const [activeTab, setActiveTab] = useState(defaultTab);
4906
5675
  const [filter, setFilter] = useState("all");
@@ -4926,7 +5695,7 @@ function RASidebar({
4926
5695
  e.preventDefault();
4927
5696
  setLocalError(null);
4928
5697
  if (!username.trim() || !password.trim()) {
4929
- setLocalError("Username and Password are required");
5698
+ setLocalError(t.retroAchievements.usernameRequired);
4930
5699
  return;
4931
5700
  }
4932
5701
  const success = await onLogin(username.trim(), password.trim());
@@ -4969,7 +5738,7 @@ function RASidebar({
4969
5738
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
4970
5739
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
4971
5740
  /* @__PURE__ */ jsx(Trophy, { className: "text-yellow-400", size: 18 }),
4972
- /* @__PURE__ */ jsx("span", { className: "font-heading text-sm text-white", children: "RetroAchievements" })
5741
+ /* @__PURE__ */ jsx("span", { className: "font-heading text-sm text-white", children: t.retroAchievements.title })
4973
5742
  ] }),
4974
5743
  /* @__PURE__ */ jsx(
4975
5744
  "button",
@@ -4988,7 +5757,7 @@ function RASidebar({
4988
5757
  className: `flex-1 flex items-center justify-center gap-1.5 px-2 py-1.5 text-xs rounded-md transition-colors ${activeTab === "achievements" ? "bg-yellow-500/20 text-yellow-400" : "text-gray-400 hover:text-white"}`,
4989
5758
  children: [
4990
5759
  /* @__PURE__ */ jsx(List, { size: 12 }),
4991
- "Achievements"
5760
+ t.retroAchievements.achievements
4992
5761
  ]
4993
5762
  }
4994
5763
  ),
@@ -4999,7 +5768,7 @@ function RASidebar({
4999
5768
  className: `flex-1 flex items-center justify-center gap-1.5 px-2 py-1.5 text-xs rounded-md transition-colors ${activeTab === "settings" ? "bg-yellow-500/20 text-yellow-400" : "text-gray-400 hover:text-white"}`,
5000
5769
  children: [
5001
5770
  /* @__PURE__ */ jsx(Settings, { size: 12 }),
5002
- "Settings"
5771
+ t.settings.title
5003
5772
  ]
5004
5773
  }
5005
5774
  )
@@ -5046,7 +5815,7 @@ function RASidebar({
5046
5815
  }
5047
5816
  ) }),
5048
5817
  /* @__PURE__ */ jsxs("div", { className: "p-2 border-t border-gray-800 bg-gray-900/80 text-[10px] text-gray-500 text-center flex-shrink-0", children: [
5049
- "Powered by",
5818
+ t.retroAchievements.poweredBy,
5050
5819
  " ",
5051
5820
  /* @__PURE__ */ jsx(
5052
5821
  "a",
@@ -6650,6 +7419,7 @@ function useVolume({
6650
7419
  };
6651
7420
  }
6652
7421
  function useControls(system, onNotify) {
7422
+ const t = useKoinTranslation();
6653
7423
  const defaultControls = getConsoleKeyboardDefaults(system || "SNES");
6654
7424
  const [controls, setControls] = useState(() => {
6655
7425
  if (typeof window !== "undefined") {
@@ -6664,13 +7434,13 @@ function useControls(system, onNotify) {
6664
7434
  const saveControls = useCallback((newControls) => {
6665
7435
  setControls(newControls);
6666
7436
  saveKeyboardMapping(newControls, system);
6667
- onNotify?.("Controls saved", "success");
6668
- }, [system, onNotify]);
7437
+ onNotify?.(t.notifications.controlsSaved, "success");
7438
+ }, [system, onNotify, t]);
6669
7439
  const resetToDefaults = useCallback(() => {
6670
7440
  setControls(defaultControls);
6671
7441
  saveKeyboardMapping(defaultControls, system);
6672
- onNotify?.("Controls reset to defaults", "info");
6673
- }, [defaultControls, system, onNotify]);
7442
+ onNotify?.(t.notifications.controlsReset, "info");
7443
+ }, [defaultControls, system, onNotify, t]);
6674
7444
  return {
6675
7445
  controls,
6676
7446
  saveControls,
@@ -6696,16 +7466,17 @@ function useGameSession(props) {
6696
7466
  canvasRef,
6697
7467
  showToast
6698
7468
  } = props;
7469
+ const t = useKoinTranslation();
6699
7470
  const { controls, saveControls } = useControls(system, showToast);
6700
7471
  const [gamepadModalOpen, setGamepadModalOpen] = useState(false);
6701
7472
  const [controlsModalOpen, setControlsModalOpen] = useState(false);
6702
7473
  const { gamepads, connectedCount } = useGamepad({
6703
7474
  onConnect: (gamepad) => {
6704
7475
  showToast(
6705
- gamepad.name || "Controller ready to use",
7476
+ gamepad.name || t.notifications.controllerReady,
6706
7477
  "gamepad",
6707
7478
  {
6708
- title: "Controller Connected",
7479
+ title: t.notifications.controllerConnected,
6709
7480
  duration: 4e3,
6710
7481
  action: {
6711
7482
  label: "Configure",
@@ -6716,10 +7487,11 @@ function useGameSession(props) {
6716
7487
  },
6717
7488
  onDisconnect: () => {
6718
7489
  showToast(
6719
- "Controller was disconnected",
7490
+ t.notifications.controllerDisconnected,
6720
7491
  "warning",
6721
7492
  {
6722
- title: "Controller Lost",
7493
+ title: t.notifications.controllerDisconnected,
7494
+ // Title repeats or generic? Using same for now
6723
7495
  duration: 3e3
6724
7496
  }
6725
7497
  );
@@ -6749,10 +7521,10 @@ function useGameSession(props) {
6749
7521
  if (arcadeSystems.includes(system.toLowerCase())) {
6750
7522
  setTimeout(() => {
6751
7523
  showToast(
6752
- "Press SHIFT to insert coin",
7524
+ t.notifications.insertCoin,
6753
7525
  "info",
6754
7526
  {
6755
- title: "\u{1FA99} Insert Coin",
7527
+ title: t.notifications.insertCoinTitle,
6756
7528
  duration: 5e3
6757
7529
  }
6758
7530
  );
@@ -6990,6 +7762,7 @@ function useGameSaves({
6990
7762
  onDeleteSaveState,
6991
7763
  autoSaveInterval
6992
7764
  }) {
7765
+ const t = useKoinTranslation();
6993
7766
  const [saveModalOpen, setSaveModalOpen] = useState(false);
6994
7767
  const [saveModalMode, setSaveModalMode] = useState("save");
6995
7768
  const [saveSlots, setSaveSlots] = useState([]);
@@ -7016,7 +7789,7 @@ function useGameSaves({
7016
7789
  setSaveSlots(slots);
7017
7790
  } catch (err) {
7018
7791
  console.error("Failed to fetch save slots:", err);
7019
- showToast("Failed to load save slots", "error");
7792
+ showToast(t.notifications.failedFetch, "error", { title: t.overlays.toast.error });
7020
7793
  } finally {
7021
7794
  setIsSlotLoading(false);
7022
7795
  }
@@ -7033,7 +7806,7 @@ function useGameSaves({
7033
7806
  const result = await nostalgist.saveStateWithBlob();
7034
7807
  if (result) {
7035
7808
  await onSaveState(0, result.blob, void 0);
7036
- showToast("State saved successfully", "success");
7809
+ showToast(t.notifications.saved, "success", { title: t.overlays.toast.saved });
7037
7810
  }
7038
7811
  });
7039
7812
  } else {
@@ -7047,7 +7820,7 @@ function useGameSaves({
7047
7820
  a.download = `${fileName}.state`;
7048
7821
  a.click();
7049
7822
  URL.revokeObjectURL(url);
7050
- showToast("State downloaded", "success");
7823
+ showToast(t.notifications.downloaded, "success", { title: t.overlays.toast.saved });
7051
7824
  }
7052
7825
  });
7053
7826
  }
@@ -7066,9 +7839,9 @@ function useGameSaves({
7066
7839
  await queueRef.current.add(async () => {
7067
7840
  await nostalgist.loadState(new Uint8Array(buffer));
7068
7841
  });
7069
- showToast("State loaded successfully", "success");
7842
+ showToast(t.notifications.loaded, "success", { title: t.overlays.toast.loaded });
7070
7843
  } else {
7071
- showToast("No save found", "error");
7844
+ showToast(t.notifications.noSaveFound, "error", { title: t.overlays.toast.error });
7072
7845
  }
7073
7846
  } else {
7074
7847
  const input = document.createElement("input");
@@ -7081,7 +7854,7 @@ function useGameSaves({
7081
7854
  await queueRef.current.add(async () => {
7082
7855
  await nostalgist.loadState(new Uint8Array(buffer));
7083
7856
  });
7084
- showToast("State loaded from file", "success");
7857
+ showToast(t.notifications.loadedFile, "success", { title: t.overlays.toast.loaded });
7085
7858
  }
7086
7859
  };
7087
7860
  input.click();
@@ -7106,14 +7879,14 @@ function useGameSaves({
7106
7879
  console.warn("Screenshot failed", e);
7107
7880
  }
7108
7881
  await onSaveState(slot, result.blob, screen);
7109
- showToast(`Saved to slot ${slot}`, "success");
7882
+ showToast(t.notifications.savedSlot.replace("{{num}}", slot.toString()), "success", { title: t.overlays.toast.saved });
7110
7883
  setSaveModalOpen(false);
7111
7884
  resume();
7112
7885
  }
7113
7886
  });
7114
7887
  } catch (err) {
7115
7888
  console.error("Save failed:", err);
7116
- showToast("Failed to save", "error");
7889
+ showToast(t.notifications.failedSave, "error", { title: t.overlays.toast.error });
7117
7890
  } finally {
7118
7891
  setActioningSlot(null);
7119
7892
  }
@@ -7127,15 +7900,15 @@ function useGameSaves({
7127
7900
  await queueRef.current.add(async () => {
7128
7901
  await nostalgist.loadState(new Uint8Array(buffer));
7129
7902
  });
7130
- showToast(`Loaded from slot ${slot}`, "success");
7903
+ showToast(t.notifications.loadedSlot.replace("{{num}}", slot.toString()), "success", { title: t.overlays.toast.loaded });
7131
7904
  setSaveModalOpen(false);
7132
7905
  resume();
7133
7906
  } else {
7134
- showToast("Empty slot", "error");
7907
+ showToast(t.notifications.emptySlot, "error", { title: t.overlays.toast.error });
7135
7908
  }
7136
7909
  } catch (err) {
7137
7910
  console.error("Load failed:", err);
7138
- showToast("Failed to load", "error");
7911
+ showToast(t.notifications.failedLoad, "error", { title: t.overlays.toast.error });
7139
7912
  } finally {
7140
7913
  setActioningSlot(null);
7141
7914
  }
@@ -7147,11 +7920,11 @@ function useGameSaves({
7147
7920
  setActioningSlot(slot);
7148
7921
  try {
7149
7922
  await onDeleteSaveState(slot);
7150
- showToast(`Deleted slot ${slot}`, "success");
7923
+ showToast(t.notifications.deletedSlot.replace("{{num}}", slot.toString()), "success", { title: t.overlays.toast.saved });
7151
7924
  refreshSlots();
7152
7925
  } catch (err) {
7153
7926
  console.error("Delete failed:", err);
7154
- showToast("Failed to delete", "error");
7927
+ showToast(t.notifications.failedDelete, "error", { title: t.overlays.toast.error });
7155
7928
  } finally {
7156
7929
  setActioningSlot(null);
7157
7930
  }
@@ -7465,7 +8238,7 @@ function useGamePlayer(props) {
7465
8238
  recordingSupported
7466
8239
  };
7467
8240
  }
7468
- var STORAGE_KEY2 = "koin-player-settings";
8241
+ var STORAGE_KEY3 = "koin-player-settings";
7469
8242
  var DEFAULT_SETTINGS = {
7470
8243
  volume: 1,
7471
8244
  muted: false,
@@ -7478,7 +8251,7 @@ function usePlayerPersistence(onSettingsChange) {
7478
8251
  const [isLoaded, setIsLoaded] = useState(false);
7479
8252
  useEffect(() => {
7480
8253
  try {
7481
- const stored = localStorage.getItem(STORAGE_KEY2);
8254
+ const stored = localStorage.getItem(STORAGE_KEY3);
7482
8255
  if (stored) {
7483
8256
  const parsed = JSON.parse(stored);
7484
8257
  setSettings((prev) => ({ ...prev, ...parsed }));
@@ -7492,7 +8265,7 @@ function usePlayerPersistence(onSettingsChange) {
7492
8265
  setSettings((prev) => {
7493
8266
  const next = { ...prev, ...updates };
7494
8267
  try {
7495
- localStorage.setItem(STORAGE_KEY2, JSON.stringify(next));
8268
+ localStorage.setItem(STORAGE_KEY3, JSON.stringify(next));
7496
8269
  } catch (e) {
7497
8270
  console.error("Failed to save player settings", e);
7498
8271
  }
@@ -7529,7 +8302,485 @@ var sendTelemetry = (eventName, params = {}) => {
7529
8302
  } catch (e) {
7530
8303
  }
7531
8304
  };
7532
- var GamePlayer = memo(function GamePlayer2(props) {
8305
+
8306
+ // src/locales/es.ts
8307
+ var es = {
8308
+ controls: {
8309
+ play: "Jugar",
8310
+ pause: "Pausar",
8311
+ reset: "Reiniciar",
8312
+ rewind: "Rebobinar",
8313
+ save: "Guardar",
8314
+ load: "Cargar",
8315
+ snap: "Capturar",
8316
+ rec: "Grabar",
8317
+ stopRec: "Detener",
8318
+ startRecord: "Comenzar grabaci\xF3n",
8319
+ stopRecord: "Detener grabaci\xF3n",
8320
+ mute: "Silenciar",
8321
+ unmute: "Activar sonido",
8322
+ help: "Ayuda",
8323
+ full: "Pantalla comp.",
8324
+ // Abbreviated
8325
+ keys: "Teclas",
8326
+ menuOpen: "Abrir men\xFA",
8327
+ menuClose: "Cerrar men\xFA",
8328
+ gamepadConnected: "{{count}} mando{{plural}} conectado(s) - haz clic para configurar",
8329
+ noGamepad: "No hay mando detectado - presiona cualquier bot\xF3n para conectar",
8330
+ press: "Pulsa",
8331
+ startBtn: "START",
8332
+ selectBtn: "SELECT"
8333
+ },
8334
+ common: {
8335
+ disabledInHardcore: "Desactivado en modo Hardcore",
8336
+ notSupported: "No compatible con esta consola",
8337
+ playToEnableRewind: "Juega unos segundos para activar el rebobinado"
8338
+ },
8339
+ settings: {
8340
+ title: "Ajustes",
8341
+ general: "General",
8342
+ audio: "Sonido",
8343
+ video: "V\xEDdeo",
8344
+ input: "Entrada",
8345
+ advanced: "Avanzado",
8346
+ fullscreen: "Pantalla completa",
8347
+ controls: "Controles",
8348
+ gamepad: "Mando",
8349
+ cheats: "Trucos",
8350
+ retroAchievements: "RetroAchievements",
8351
+ shortcuts: "Atajos",
8352
+ exit: "Salir",
8353
+ language: "Idioma",
8354
+ selectLanguage: "Seleccionar idioma"
8355
+ },
8356
+ overlay: {
8357
+ play: "JUGAR",
8358
+ systemFirmware: "FIRMWARE DEL SISTEMA",
8359
+ loading: "Cargando {{system}}",
8360
+ initializing: "Inicializando emulador",
8361
+ loadingSave: "Cargando partida",
8362
+ preparingSlot: "Preparando ranura {{num}}",
8363
+ systemError: "Error del sistema",
8364
+ failedInit: "Error al inicializar el emulador",
8365
+ retry: "Reintentar",
8366
+ slotReady: "Ranura {{num}} lista",
8367
+ paused: "Pausado"
8368
+ },
8369
+ notifications: {
8370
+ saved: "Estado guardado",
8371
+ loaded: "Estado cargado",
8372
+ error: "Error",
8373
+ recordingStarted: "Grabaci\xF3n iniciada",
8374
+ recordingSaved: "Grabaci\xF3n guardada",
8375
+ downloaded: "Estado descargado",
8376
+ loadedFile: "Estado cargado desde archivo",
8377
+ savedSlot: "Guardado en ranura {{num}}",
8378
+ loadedSlot: "Cargado desde ranura {{num}}",
8379
+ deletedSlot: "Ranura {{num}} eliminada",
8380
+ emptySlot: "Ranura vac\xEDa",
8381
+ noSaveFound: "No se encontr\xF3 partida guardada",
8382
+ failedSave: "Error al guardar",
8383
+ failedLoad: "Error al cargar",
8384
+ failedDelete: "Error al eliminar",
8385
+ failedFetch: "Error al cargar ranuras",
8386
+ controllerConnected: "Mando conectado",
8387
+ controllerDisconnected: "Mando desconectado",
8388
+ controllerReady: "Mando listo para usar",
8389
+ insertCoin: "Pulsa SHIFT para insertar moneda",
8390
+ insertCoinTitle: "\u{1FA99} Insertar Moneda",
8391
+ controlsSaved: "Controles guardados",
8392
+ controlsReset: "Controles restablecidos"
8393
+ },
8394
+ modals: {
8395
+ shortcuts: {
8396
+ title: "Atajos de teclado",
8397
+ playerShortcuts: "Atajos del reproductor",
8398
+ overlays: "Superposiciones",
8399
+ recording: "Grabaci\xF3n",
8400
+ showHelp: "Mostrar ayuda",
8401
+ perfOverlay: "Superposici\xF3n de rendimiento",
8402
+ inputDisplay: "Mostrar entrada",
8403
+ toggleRec: "Alternar grabaci\xF3n",
8404
+ toggleMute: "Alternar silencio",
8405
+ pressEsc: "Pulsa ESC para cerrar"
8406
+ },
8407
+ controls: {
8408
+ title: "Controles",
8409
+ keyboard: "Asignaci\xF3n de teclado",
8410
+ description: "Haz clic en un bot\xF3n y pulsa una tecla",
8411
+ pressKey: "Pulsa una tecla...",
8412
+ reset: "Restablecer",
8413
+ save: "Guardar controles"
8414
+ },
8415
+ gamepad: {
8416
+ title: "Ajustes de mando",
8417
+ noGamepad: "No se detecta mando",
8418
+ connected: "{{count}} mando{{s}} conectado(s)",
8419
+ none: "Ning\xFAn mando detectado",
8420
+ player: "Jugador:",
8421
+ noController: "Sin mando",
8422
+ pressAny: "Pulsa cualquier bot\xF3n para conectar",
8423
+ waiting: "Esperando entrada...",
8424
+ pressButton: "Pulsa bot\xF3n para {{button}}",
8425
+ pressEsc: "Pulsa Escape para cancelar",
8426
+ reset: "Restablecer",
8427
+ save: "Guardar ajustes"
8428
+ },
8429
+ cheats: {
8430
+ title: "Trucos",
8431
+ addCheat: "A\xF1adir truco",
8432
+ available: "{{count}} truco{{s}} disponible(s)",
8433
+ emptyTitle: "No hay trucos disponibles",
8434
+ emptyDesc: "No se encontraron c\xF3digos para este juego",
8435
+ copy: "Copiar c\xF3digo",
8436
+ active: "{{count}} truco{{s}} activo(s)",
8437
+ toggleHint: "Haz clic para activar/desactivar"
8438
+ },
8439
+ saveSlots: {
8440
+ title: "Guardar partida",
8441
+ saveTitle: "Guardar",
8442
+ loadTitle: "Cargar",
8443
+ emptySlot: "Ranura vac\xEDa",
8444
+ subtitleSave: "Elige una ranura para guardar",
8445
+ subtitleLoad: "Elige una ranura para cargar",
8446
+ loading: "Cargando partidas...",
8447
+ locked: "Ranura {{num}} bloqueada",
8448
+ upgrade: "Mejora para desbloquear m\xE1s ranuras",
8449
+ autoSave: "Autoguardado",
8450
+ autoSaveDesc: "Reservado para guardado autom\xE1tico",
8451
+ noData: "Sin datos",
8452
+ slot: "Ranura {{num}}",
8453
+ footerSave: "Las partidas se guardan en la nube y se sincronizan",
8454
+ footerLoad: "Tu progreso se restaurar\xE1 al punto seleccionado"
8455
+ },
8456
+ bios: {
8457
+ title: "Selecci\xF3n de BIOS",
8458
+ description: "Selecciona una BIOS para este juego",
8459
+ warningTitle: "Nota:",
8460
+ warning: "Cambiar la BIOS reiniciar\xE1 el emulador. Se perder\xE1 el progreso no guardado.",
8461
+ systemDefault: "Por defecto",
8462
+ active: "Activo",
8463
+ defaultDesc: "Usar BIOS interna del emulador",
8464
+ emptyTitle: "No se encontraron archivos de BIOS.",
8465
+ emptyDesc: "Sube archivos BIOS en tu biblioteca.",
8466
+ footer: "Firmware del sistema"
8467
+ }
8468
+ },
8469
+ retroAchievements: {
8470
+ title: "RetroAchievements",
8471
+ login: "Iniciar sesi\xF3n",
8472
+ logout: "Cerrar sesi\xF3n",
8473
+ username: "Usuario",
8474
+ password: "Password",
8475
+ hardcore: "Modo Hardcore",
8476
+ achievements: "Logros",
8477
+ locked: "Bloqueado",
8478
+ unlocked: "Desbloqueado",
8479
+ mastered: "Dominado",
8480
+ identifying: "Identificando juego...",
8481
+ achievementsAvailable: "{{count}} logros disponibles",
8482
+ gameNotSupported: "Conectado - Juego no en base de datos",
8483
+ connect: "Conectar RetroAchievements",
8484
+ connected: "RetroAchievements (conectado)",
8485
+ createAccount: "Crear cuenta",
8486
+ privacy: "Privacidad:",
8487
+ privacyText: "Tu contrase\xF1a solo se usa para autenticaci\xF3n con RA. No se almacena.",
8488
+ connecting: "Conectando...",
8489
+ connectAccount: "Conecta tu cuenta para rastrear logros",
8490
+ poweredBy: "Con la tecnolog\xEDa de",
8491
+ connectedStatus: "Conectado",
8492
+ yourUsername: "Tu usuario RA",
8493
+ yourPassword: "Tu contrase\xF1a RA",
8494
+ usernameRequired: "Usuario y contrase\xF1a requeridos",
8495
+ noGame: "Sin juego cargado",
8496
+ loadGame: "Carga un juego para ver los logros",
8497
+ noAchievements: "No se encontraron logros",
8498
+ notSupported: "Este juego puede no ser compatible",
8499
+ ptsRemaining: "{{count}} pts restantes",
8500
+ filters: {
8501
+ all: "Todos",
8502
+ locked: "Bloqueado",
8503
+ unlocked: "Desbloqueado"
8504
+ }
8505
+ },
8506
+ overlays: {
8507
+ performance: {
8508
+ title: "Estad\xEDsticas",
8509
+ fps: "FPS",
8510
+ frameTime: "FT",
8511
+ memory: "MEM",
8512
+ core: "Core",
8513
+ input: "ENTRADA",
8514
+ active: "ACTIVO"
8515
+ },
8516
+ toast: {
8517
+ saved: "Juego guardado",
8518
+ loaded: "Juego cargado",
8519
+ error: "Error"
8520
+ },
8521
+ recording: {
8522
+ started: "Grabaci\xF3n iniciada",
8523
+ stopped: "Grabaci\xF3n detenida",
8524
+ saved: "Grabaci\xF3n guardada",
8525
+ paused: "PAUSA",
8526
+ recording: "GRABANDO",
8527
+ resume: "Reanudar grabaci\xF3n",
8528
+ pause: "Pausar grabaci\xF3n",
8529
+ stop: "Detener y guardar",
8530
+ hover: "Controles"
8531
+ }
8532
+ }
8533
+ };
8534
+
8535
+ // src/locales/fr.ts
8536
+ var fr = {
8537
+ controls: {
8538
+ play: "Jouer",
8539
+ pause: "Pause",
8540
+ reset: "R\xE9initialiser",
8541
+ rewind: "Rembobiner",
8542
+ save: "Sauver",
8543
+ load: "Charger",
8544
+ snap: "Photo",
8545
+ rec: "Enr.",
8546
+ stopRec: "Arr\xEAter",
8547
+ startRecord: "D\xE9marrer l'enregistrement",
8548
+ stopRecord: "Arr\xEAter l'enregistrement",
8549
+ mute: "Muet",
8550
+ unmute: "Son",
8551
+ help: "Aide",
8552
+ full: "Plein \xE9cran",
8553
+ keys: "Touches",
8554
+ menuOpen: "Ouvrir menu",
8555
+ menuClose: "Fermer menu",
8556
+ gamepadConnected: "{{count}} manette{{plural}} connect\xE9e(s)",
8557
+ noGamepad: "Aucune manette d\xE9tect\xE9e",
8558
+ press: "Appuyez",
8559
+ startBtn: "START",
8560
+ selectBtn: "SELECT"
8561
+ },
8562
+ common: {
8563
+ disabledInHardcore: "D\xE9sactiv\xE9 en mode Hardcore",
8564
+ notSupported: "Non support\xE9 sur cette console",
8565
+ playToEnableRewind: "Jouez quelques secondes pour activer le rembobinage"
8566
+ },
8567
+ settings: {
8568
+ title: "Param\xE8tres",
8569
+ general: "G\xE9n\xE9ral",
8570
+ audio: "Audio",
8571
+ video: "Vid\xE9o",
8572
+ input: "Entr\xE9e",
8573
+ advanced: "Avanc\xE9",
8574
+ fullscreen: "Plein \xE9cran",
8575
+ controls: "Contr\xF4les",
8576
+ gamepad: "Manette",
8577
+ cheats: "Codes",
8578
+ retroAchievements: "Succ\xE8s",
8579
+ shortcuts: "Raccourcis",
8580
+ exit: "Quitter",
8581
+ language: "Langue",
8582
+ selectLanguage: "Choisir la langue"
8583
+ },
8584
+ overlay: {
8585
+ play: "JOUER",
8586
+ systemFirmware: "MICROLOGICIEL SYST\xC8ME",
8587
+ loading: "Chargement {{system}}",
8588
+ initializing: "Initialisation de l'\xE9mulateur",
8589
+ loadingSave: "Chargement de la sauvegarde",
8590
+ preparingSlot: "Pr\xE9paration de l'emplacement {{num}}",
8591
+ systemError: "Erreur syst\xE8me",
8592
+ failedInit: "\xC9chec de l'initialisation",
8593
+ retry: "R\xE9essayer",
8594
+ slotReady: "Emplacement {{num}} pr\xEAt",
8595
+ paused: "Pause"
8596
+ },
8597
+ notifications: {
8598
+ saved: "\xC9tat sauvegard\xE9",
8599
+ loaded: "\xC9tat charg\xE9",
8600
+ error: "Erreur",
8601
+ recordingStarted: "Enregistrement d\xE9marr\xE9",
8602
+ recordingSaved: "Enregistrement sauvegard\xE9",
8603
+ downloaded: "\xC9tat t\xE9l\xE9charg\xE9",
8604
+ loadedFile: "\xC9tat charg\xE9 depuis fichier",
8605
+ savedSlot: "Sauvegard\xE9 sur l'emplacement {{num}}",
8606
+ loadedSlot: "Charg\xE9 depuis l'emplacement {{num}}",
8607
+ deletedSlot: "Emplacement {{num}} supprim\xE9",
8608
+ emptySlot: "Emplacement vide",
8609
+ noSaveFound: "Aucune sauvegarde trouv\xE9e",
8610
+ failedSave: "\xC9chec de la sauvegarde",
8611
+ failedLoad: "\xC9chec du chargement",
8612
+ failedDelete: "\xC9chec de la suppression",
8613
+ failedFetch: "\xC9chec du chargement des emplacements",
8614
+ controllerConnected: "Manette connect\xE9e",
8615
+ controllerDisconnected: "Manette d\xE9connect\xE9e",
8616
+ controllerReady: "Manette pr\xEAte",
8617
+ insertCoin: "Appuyez sur SHIFT pour ins\xE9rer une pi\xE8ce",
8618
+ insertCoinTitle: "\u{1FA99} Ins\xE9rer Pi\xE8ce",
8619
+ controlsSaved: "Contr\xF4les sauvegard\xE9s",
8620
+ controlsReset: "Contr\xF4les r\xE9initialis\xE9s"
8621
+ },
8622
+ modals: {
8623
+ shortcuts: {
8624
+ title: "Raccourcis clavier",
8625
+ playerShortcuts: "Raccourcis du lecteur",
8626
+ overlays: "Superpositions",
8627
+ recording: "Enregistrement",
8628
+ showHelp: "Afficher l'aide",
8629
+ perfOverlay: "Overlay de performance",
8630
+ inputDisplay: "Afficher les entr\xE9es",
8631
+ toggleRec: "Basculer l'enregistrement",
8632
+ toggleMute: "Basculer le son",
8633
+ pressEsc: "Appuyez sur ESC pour fermer"
8634
+ },
8635
+ controls: {
8636
+ title: "Contr\xF4les",
8637
+ keyboard: "Configuration clavier",
8638
+ description: "Cliquez sur un bouton et appuyez sur une touche",
8639
+ pressKey: "Appuyez...",
8640
+ reset: "R\xE9initialiser",
8641
+ save: "Sauvegarder"
8642
+ },
8643
+ gamepad: {
8644
+ title: "Param\xE8tres manette",
8645
+ noGamepad: "Aucune manette",
8646
+ connected: "{{count}} manette{{s}} connect\xE9e(s)",
8647
+ none: "Aucune manette d\xE9tect\xE9e",
8648
+ player: "Joueur:",
8649
+ noController: "Aucune manette",
8650
+ pressAny: "Appuyez sur un bouton pour connecter",
8651
+ waiting: "En attente...",
8652
+ pressButton: "Appuyez pour {{button}}",
8653
+ pressEsc: "Echap pour annuler",
8654
+ reset: "R\xE9initialiser",
8655
+ save: "Sauvegarder"
8656
+ },
8657
+ cheats: {
8658
+ title: "Codes de triche",
8659
+ addCheat: "Ajouter un code",
8660
+ available: "{{count}} code{{s}} disponible(s)",
8661
+ emptyTitle: "Aucun code disponible",
8662
+ emptyDesc: "Aucun code trouv\xE9 pour ce jeu",
8663
+ copy: "Copier",
8664
+ active: "{{count}} actif(s)",
8665
+ toggleHint: "Cliquez pour activer/d\xE9sactiver"
8666
+ },
8667
+ saveSlots: {
8668
+ title: "Sauvegardes",
8669
+ saveTitle: "Sauvegarder",
8670
+ loadTitle: "Charger",
8671
+ emptySlot: "Vide",
8672
+ subtitleSave: "Choisir un emplacement",
8673
+ subtitleLoad: "Choisir une sauvegarde",
8674
+ loading: "Chargement...",
8675
+ locked: "Emplacement {{num}} verrouill\xE9",
8676
+ upgrade: "Am\xE9liorer pour plus d'emplacements",
8677
+ autoSave: "Auto-Sauvegarde",
8678
+ autoSaveDesc: "R\xE9serv\xE9 \xE0 la sauvegarde automatique",
8679
+ noData: "Aucune donn\xE9e",
8680
+ slot: "Emplacement {{num}}",
8681
+ footerSave: "Les sauvegardes sont synchronis\xE9es dans le cloud",
8682
+ footerLoad: "Votre progression sera restaur\xE9e"
8683
+ },
8684
+ bios: {
8685
+ title: "S\xE9lection BIOS",
8686
+ description: "S\xE9lectionnez un BIOS",
8687
+ warningTitle: "Note:",
8688
+ warning: "Changer le BIOS red\xE9marre l'\xE9mulateur. Progression non sauvegard\xE9e sera perdue.",
8689
+ systemDefault: "Par d\xE9faut",
8690
+ active: "Actif",
8691
+ defaultDesc: "Utiliser le BIOS par d\xE9faut",
8692
+ emptyTitle: "Aucun BIOS trouv\xE9.",
8693
+ emptyDesc: "T\xE9l\xE9versez des fichiers BIOS dans votre biblioth\xE8que.",
8694
+ footer: "Firmware syst\xE8me"
8695
+ }
8696
+ },
8697
+ retroAchievements: {
8698
+ title: "RetroAchievements",
8699
+ login: "Connexion",
8700
+ logout: "D\xE9connexion",
8701
+ username: "Utilisateur",
8702
+ password: "Mot de passe",
8703
+ hardcore: "Mode Hardcore",
8704
+ achievements: "Succ\xE8s",
8705
+ locked: "Verrouill\xE9",
8706
+ unlocked: "D\xE9verrouill\xE9",
8707
+ mastered: "Ma\xEEtris\xE9",
8708
+ identifying: "Identification...",
8709
+ achievementsAvailable: "{{count}} succ\xE8s disponibles",
8710
+ gameNotSupported: "Jeu non support\xE9 par RA",
8711
+ connect: "Connecter RetroAchievements",
8712
+ connected: "RetroAchievements (connect\xE9)",
8713
+ createAccount: "Cr\xE9er un compte",
8714
+ privacy: "Confidentialit\xE9:",
8715
+ privacyText: "Votre mot de passe n'est pas stock\xE9.",
8716
+ connecting: "Connexion...",
8717
+ connectAccount: "Connectez votre compte",
8718
+ poweredBy: "Propuls\xE9 par",
8719
+ connectedStatus: "Connect\xE9",
8720
+ yourUsername: "Votre utilisateur RA",
8721
+ yourPassword: "Votre mot de passe RA",
8722
+ usernameRequired: "Utilisateur et mot de passe requis",
8723
+ noGame: "Aucun jeu",
8724
+ loadGame: "Chargez un jeu pour voir les succ\xE8s",
8725
+ noAchievements: "Aucun succ\xE8s trouv\xE9",
8726
+ notSupported: "Ce jeu n'est peut-\xEAtre pas support\xE9",
8727
+ ptsRemaining: "{{count}} pts restants",
8728
+ filters: {
8729
+ all: "Tous",
8730
+ locked: "Verrouill\xE9s",
8731
+ unlocked: "D\xE9verrouill\xE9s"
8732
+ }
8733
+ },
8734
+ overlays: {
8735
+ performance: {
8736
+ title: "Stats",
8737
+ fps: "FPS",
8738
+ frameTime: "FT",
8739
+ memory: "MEM",
8740
+ core: "Core",
8741
+ input: "ENTR\xC9E",
8742
+ active: "ACTIF"
8743
+ },
8744
+ toast: {
8745
+ saved: "Sauvegard\xE9",
8746
+ loaded: "Charg\xE9",
8747
+ error: "Erreur"
8748
+ },
8749
+ recording: {
8750
+ started: "Enregistrement d\xE9marr\xE9",
8751
+ stopped: "Enregistrement arr\xEAt\xE9",
8752
+ saved: "Enregistrement sauvegard\xE9",
8753
+ paused: "PAUSE",
8754
+ recording: "ENR.",
8755
+ resume: "Reprendre",
8756
+ pause: "Pause",
8757
+ stop: "Arr\xEAter et sauver",
8758
+ hover: "Contr\xF4les"
8759
+ }
8760
+ }
8761
+ };
8762
+
8763
+ // src/lib/common-utils.ts
8764
+ function deepMerge2(target, source) {
8765
+ const isObject = (obj) => obj && typeof obj === "object";
8766
+ if (!isObject(target) || !isObject(source)) {
8767
+ return source;
8768
+ }
8769
+ const output = { ...target };
8770
+ Object.keys(source).forEach((key) => {
8771
+ const targetValue = output[key];
8772
+ const sourceValue = source[key];
8773
+ if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
8774
+ output[key] = sourceValue;
8775
+ } else if (isObject(targetValue) && isObject(sourceValue)) {
8776
+ output[key] = deepMerge2(targetValue, sourceValue);
8777
+ } else {
8778
+ output[key] = sourceValue;
8779
+ }
8780
+ });
8781
+ return output;
8782
+ }
8783
+ var GamePlayerInner = memo(function GamePlayerInner2(props) {
7533
8784
  const { settings, updateSettings, isLoaded: settingsLoaded } = usePlayerPersistence();
7534
8785
  useEffect(() => {
7535
8786
  sendTelemetry("game_start", {
@@ -7540,6 +8791,7 @@ var GamePlayer = memo(function GamePlayer2(props) {
7540
8791
  }, [props.system, props.core, props.title]);
7541
8792
  const [biosModalOpen, setBiosModalOpen] = useState(false);
7542
8793
  const [showShortcutsModal, setShowShortcutsModal] = useState(false);
8794
+ const [settingsModalOpen, setSettingsModalOpen] = useState(false);
7543
8795
  const effectiveShader = props.shader !== void 0 ? props.shader : settings.shader;
7544
8796
  const {
7545
8797
  // Refs
@@ -7674,6 +8926,10 @@ var GamePlayer = memo(function GamePlayer2(props) {
7674
8926
  pause();
7675
8927
  setGamepadModalOpen(true);
7676
8928
  }, [pause, setGamepadModalOpen]);
8929
+ const handleShowSettings = useCallback(() => {
8930
+ pause();
8931
+ setSettingsModalOpen(true);
8932
+ }, [pause, setSettingsModalOpen]);
7677
8933
  const handleExitClick = useCallback(() => {
7678
8934
  onExit?.();
7679
8935
  }, [onExit]);
@@ -7885,6 +9141,7 @@ var GamePlayer = memo(function GamePlayer2(props) {
7885
9141
  systemColor,
7886
9142
  gamepadCount: connectedCount,
7887
9143
  onGamepadSettings: handleShowGamepadSettings,
9144
+ onSettings: handleShowSettings,
7888
9145
  volume,
7889
9146
  isMuted: muted,
7890
9147
  onVolumeChange: handleVolumeChange,
@@ -7950,7 +9207,11 @@ var GamePlayer = memo(function GamePlayer2(props) {
7950
9207
  setBiosModalOpen,
7951
9208
  availableBios: props.availableBios,
7952
9209
  currentBiosId: props.currentBiosId,
7953
- onSelectBios: props.onSelectBios
9210
+ onSelectBios: props.onSelectBios,
9211
+ settingsModalOpen,
9212
+ setSettingsModalOpen,
9213
+ currentLanguage: props.currentLanguage,
9214
+ onLanguageChange: props.onLanguageChange
7954
9215
  }
7955
9216
  ),
7956
9217
  !isMobile && /* @__PURE__ */ jsx(
@@ -7978,6 +9239,27 @@ var GamePlayer = memo(function GamePlayer2(props) {
7978
9239
  }
7979
9240
  ) });
7980
9241
  });
9242
+ var GamePlayer = memo(function GamePlayer2(props) {
9243
+ const [currentLanguage, setCurrentLanguage] = useState(props.initialLanguage || "en");
9244
+ const effectiveTranslations = useMemo(() => {
9245
+ const base = currentLanguage === "es" ? es : currentLanguage === "fr" ? fr : en;
9246
+ if (props.translations) {
9247
+ return deepMerge2(base, props.translations);
9248
+ }
9249
+ return base;
9250
+ }, [currentLanguage, props.translations]);
9251
+ const handleLanguageChange = useCallback((lang) => {
9252
+ setCurrentLanguage(lang);
9253
+ }, []);
9254
+ return /* @__PURE__ */ jsx(KoinI18nProvider, { translations: effectiveTranslations, children: /* @__PURE__ */ jsx(
9255
+ GamePlayerInner,
9256
+ {
9257
+ ...props,
9258
+ currentLanguage,
9259
+ onLanguageChange: handleLanguageChange
9260
+ }
9261
+ ) });
9262
+ });
7981
9263
  var GamePlayer_default = GamePlayer;
7982
9264
  function AchievementPopup({
7983
9265
  achievement,
@@ -8139,7 +9421,7 @@ var ShaderSelector = memo(function ShaderSelector2({
8139
9421
  ] });
8140
9422
  });
8141
9423
  var ShaderSelector_default = ShaderSelector;
8142
- var SHORTCUTS2 = [
9424
+ var SHORTCUTS = [
8143
9425
  { key: "F1", description: "Help" },
8144
9426
  { key: "F3", description: "FPS Overlay" },
8145
9427
  { key: "F4", description: "Input Display" },
@@ -8174,7 +9456,7 @@ var ShortcutsReference = memo(function ShortcutsReference2({
8174
9456
  ]
8175
9457
  }
8176
9458
  ),
8177
- isExpanded && /* @__PURE__ */ jsx("div", { className: "px-3 pb-3 pt-1 space-y-1.5 border-t border-white/10", children: SHORTCUTS2.map(({ key, description }) => /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-xs", children: [
9459
+ isExpanded && /* @__PURE__ */ jsx("div", { className: "px-3 pb-3 pt-1 space-y-1.5 border-t border-white/10", children: SHORTCUTS.map(({ key, description }) => /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-xs", children: [
8178
9460
  /* @__PURE__ */ jsx("span", { className: "text-white/60", children: description }),
8179
9461
  /* @__PURE__ */ jsx(
8180
9462
  "kbd",
@@ -8195,6 +9477,6 @@ var ShortcutsReference = memo(function ShortcutsReference2({
8195
9477
  });
8196
9478
  var ShortcutsReference_default = ShortcutsReference;
8197
9479
 
8198
- export { ALL_BUTTONS, AchievementPopup, BUTTON_GROUPS, BUTTON_LABELS, CONSOLE_CAPABILITIES, CONSOLE_KEYBOARD_OVERRIDES, DEFAULT_CONTROLS, DEFAULT_GAMEPAD, DEFAULT_KEYBOARD, DPAD_BUTTONS, FACE_BUTTONS, GamePlayer_default as GamePlayer, 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, fetchAndCacheRom, formatGamepadButton, formatKeyCode, 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, useNostalgist, useToast };
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 };
8199
9481
  //# sourceMappingURL=index.mjs.map
8200
9482
  //# sourceMappingURL=index.mjs.map