koin.js 1.0.7 → 1.0.8
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/README.md +113 -20
- package/dist/index.d.mts +248 -1
- package/dist/index.d.ts +248 -1
- package/dist/index.js +1444 -506
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1169 -236
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +56 -10
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var React2 = require('react');
|
|
4
4
|
var lucideReact = require('lucide-react');
|
|
5
5
|
var jsxRuntime = require('react/jsx-runtime');
|
|
6
6
|
var nostalgist = require('nostalgist');
|
|
7
7
|
|
|
8
8
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
9
|
|
|
10
|
-
var
|
|
10
|
+
var React2__default = /*#__PURE__*/_interopDefault(React2);
|
|
11
11
|
|
|
12
12
|
var __defProp = Object.defineProperty;
|
|
13
13
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
14
14
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
15
|
-
var ControlButton =
|
|
15
|
+
var ControlButton = React2.memo(function ControlButton2({
|
|
16
16
|
onClick,
|
|
17
17
|
onMouseDown,
|
|
18
18
|
onMouseUp,
|
|
@@ -39,31 +39,45 @@ var ControlButton = React5.memo(function ControlButton2({
|
|
|
39
39
|
onTouchEnd,
|
|
40
40
|
disabled,
|
|
41
41
|
className: `
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
42
|
+
flex flex-col items-center justify-center gap-1.5
|
|
43
|
+
px-3 py-2 rounded-lg
|
|
44
|
+
transition-all duration-200
|
|
45
|
+
disabled:opacity-50 disabled:cursor-not-allowed
|
|
46
|
+
hover:bg-white/10 active:bg-white/20
|
|
47
|
+
${active ? "bg-white/10 ring-1 ring-inset" : ""}
|
|
48
|
+
${danger ? "hover:bg-red-500/20 text-red-400" : "text-gray-400 hover:text-white"}
|
|
49
|
+
${className}
|
|
50
|
+
`,
|
|
48
51
|
style: active ? {
|
|
49
52
|
backgroundColor: `${systemColor}20`,
|
|
50
53
|
color: systemColor
|
|
51
54
|
} : {},
|
|
52
55
|
title: label,
|
|
53
56
|
children: [
|
|
54
|
-
/* @__PURE__ */ jsxRuntime.jsx(Icon, { size: iconSize, className:
|
|
55
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[9px] font-bold uppercase tracking-wider
|
|
57
|
+
/* @__PURE__ */ jsxRuntime.jsx(Icon, { size: iconSize, className: `transition-transform duration-200 ${active ? "scale-110" : "group-hover:scale-110"}`, style: active ? { color: systemColor } : void 0 }),
|
|
58
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[9px] font-bold uppercase tracking-wider whitespace-nowrap", style: { color: active ? systemColor : void 0 }, children: label })
|
|
56
59
|
]
|
|
57
60
|
}
|
|
58
61
|
);
|
|
59
62
|
});
|
|
60
63
|
var SPEED_OPTIONS = [1, 2];
|
|
61
64
|
function SpeedMenu({ speed, onSpeedChange, disabled = false }) {
|
|
62
|
-
const [showMenu, setShowMenu] =
|
|
65
|
+
const [showMenu, setShowMenu] = React2.useState(false);
|
|
66
|
+
const buttonRef = React2__default.default.useRef(null);
|
|
67
|
+
const getMenuPosition = () => {
|
|
68
|
+
if (!buttonRef.current) return {};
|
|
69
|
+
const rect = buttonRef.current.getBoundingClientRect();
|
|
70
|
+
return {
|
|
71
|
+
bottom: `${window.innerHeight - rect.top + 8}px`,
|
|
72
|
+
left: `${rect.left + rect.width / 2}px`,
|
|
73
|
+
transform: "translateX(-50%)"
|
|
74
|
+
};
|
|
75
|
+
};
|
|
63
76
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
|
|
64
77
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
65
78
|
"button",
|
|
66
79
|
{
|
|
80
|
+
ref: buttonRef,
|
|
67
81
|
onClick: () => setShowMenu(!showMenu),
|
|
68
82
|
disabled,
|
|
69
83
|
className: `
|
|
@@ -90,25 +104,32 @@ function SpeedMenu({ speed, onSpeedChange, disabled = false }) {
|
|
|
90
104
|
}
|
|
91
105
|
),
|
|
92
106
|
showMenu && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
93
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 z-
|
|
94
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
95
|
-
"
|
|
107
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 z-[9998]", onClick: () => setShowMenu(false) }),
|
|
108
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
109
|
+
"div",
|
|
96
110
|
{
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
111
|
+
className: "fixed z-[9999] bg-black/90 backdrop-blur-md border border-white/20 rounded-lg p-1.5 shadow-xl flex flex-col gap-1 min-w-[80px]",
|
|
112
|
+
style: getMenuPosition(),
|
|
113
|
+
children: SPEED_OPTIONS.map((s) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
114
|
+
"button",
|
|
115
|
+
{
|
|
116
|
+
onClick: () => {
|
|
117
|
+
onSpeedChange(s);
|
|
118
|
+
setShowMenu(false);
|
|
119
|
+
},
|
|
120
|
+
className: `
|
|
102
121
|
px-3 py-2 rounded text-xs font-mono font-bold text-center transition-colors
|
|
103
122
|
${speed === s ? "bg-white/20 text-white" : "hover:bg-white/10 text-gray-400 hover:text-white"}
|
|
104
123
|
`,
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
124
|
+
children: [
|
|
125
|
+
s,
|
|
126
|
+
"x"
|
|
127
|
+
]
|
|
128
|
+
},
|
|
129
|
+
s
|
|
130
|
+
))
|
|
131
|
+
}
|
|
132
|
+
)
|
|
112
133
|
] })
|
|
113
134
|
] });
|
|
114
135
|
}
|
|
@@ -177,7 +198,261 @@ function HardcoreTooltip({ show, message = "Disabled in Hardcore mode", children
|
|
|
177
198
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 bg-amber-500/90 text-black text-xs rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-50", children: message })
|
|
178
199
|
] });
|
|
179
200
|
}
|
|
180
|
-
|
|
201
|
+
|
|
202
|
+
// src/locales/en.ts
|
|
203
|
+
var en = {
|
|
204
|
+
controls: {
|
|
205
|
+
play: "Play",
|
|
206
|
+
pause: "Pause",
|
|
207
|
+
reset: "Reset",
|
|
208
|
+
rewind: "Rewind",
|
|
209
|
+
save: "Save",
|
|
210
|
+
load: "Load",
|
|
211
|
+
snap: "Snap",
|
|
212
|
+
rec: "Rec",
|
|
213
|
+
stopRec: "Stop",
|
|
214
|
+
startRecord: "Start recording",
|
|
215
|
+
stopRecord: "Stop recording",
|
|
216
|
+
mute: "Mute",
|
|
217
|
+
unmute: "Unmute",
|
|
218
|
+
help: "Help",
|
|
219
|
+
full: "Full",
|
|
220
|
+
keys: "Keys",
|
|
221
|
+
menuOpen: "Open menu",
|
|
222
|
+
menuClose: "Close menu",
|
|
223
|
+
gamepadConnected: "{{count}} controller{{plural}} connected - click to configure",
|
|
224
|
+
noGamepad: "No controller detected - press any button on your gamepad to connect",
|
|
225
|
+
press: "Press",
|
|
226
|
+
startBtn: "START",
|
|
227
|
+
selectBtn: "SEL"
|
|
228
|
+
},
|
|
229
|
+
common: {
|
|
230
|
+
disabledInHardcore: "Disabled in Hardcore mode",
|
|
231
|
+
notSupported: "Not supported on this console",
|
|
232
|
+
playToEnableRewind: "Play for a few seconds to enable rewind"
|
|
233
|
+
},
|
|
234
|
+
settings: {
|
|
235
|
+
title: "Settings",
|
|
236
|
+
general: "General",
|
|
237
|
+
audio: "Audio",
|
|
238
|
+
video: "Video",
|
|
239
|
+
input: "Input",
|
|
240
|
+
advanced: "Advanced",
|
|
241
|
+
fullscreen: "Fullscreen",
|
|
242
|
+
controls: "Controls",
|
|
243
|
+
gamepad: "Gamepad",
|
|
244
|
+
cheats: "Cheats",
|
|
245
|
+
retroAchievements: "RetroAchievements",
|
|
246
|
+
shortcuts: "Shortcuts",
|
|
247
|
+
exit: "Exit",
|
|
248
|
+
language: "Language",
|
|
249
|
+
selectLanguage: "Select Language"
|
|
250
|
+
},
|
|
251
|
+
overlay: {
|
|
252
|
+
play: "PLAY",
|
|
253
|
+
systemFirmware: "SYSTEM FIRMWARE",
|
|
254
|
+
loading: "Loading {{system}}",
|
|
255
|
+
initializing: "Initializing emulator",
|
|
256
|
+
loadingSave: "Loading Save",
|
|
257
|
+
preparingSlot: "Preparing slot {{num}}",
|
|
258
|
+
systemError: "System Error",
|
|
259
|
+
failedInit: "Failed to initialize emulator",
|
|
260
|
+
retry: "Retry",
|
|
261
|
+
slotReady: "Slot {{num}} ready",
|
|
262
|
+
paused: "Paused"
|
|
263
|
+
},
|
|
264
|
+
notifications: {
|
|
265
|
+
saved: "State saved",
|
|
266
|
+
loaded: "State loaded",
|
|
267
|
+
error: "Error",
|
|
268
|
+
recordingStarted: "Recording started",
|
|
269
|
+
recordingSaved: "Recording saved",
|
|
270
|
+
downloaded: "State downloaded",
|
|
271
|
+
loadedFile: "State loaded from file",
|
|
272
|
+
savedSlot: "Saved to slot {{num}}",
|
|
273
|
+
loadedSlot: "Loaded from slot {{num}}",
|
|
274
|
+
deletedSlot: "Deleted slot {{num}}",
|
|
275
|
+
emptySlot: "Empty slot",
|
|
276
|
+
noSaveFound: "No save found",
|
|
277
|
+
failedSave: "Failed to save",
|
|
278
|
+
failedLoad: "Failed to load",
|
|
279
|
+
failedDelete: "Failed to delete",
|
|
280
|
+
failedFetch: "Failed to load save slots",
|
|
281
|
+
controllerConnected: "Controller Connected",
|
|
282
|
+
controllerDisconnected: "Controller Lost",
|
|
283
|
+
controllerReady: "Controller ready to use",
|
|
284
|
+
insertCoin: "Press SHIFT to insert coin",
|
|
285
|
+
insertCoinTitle: "\u{1FA99} Insert Coin",
|
|
286
|
+
controlsSaved: "Controls saved",
|
|
287
|
+
controlsReset: "Controls reset to defaults"
|
|
288
|
+
},
|
|
289
|
+
modals: {
|
|
290
|
+
shortcuts: {
|
|
291
|
+
title: "Keyboard Shortcuts",
|
|
292
|
+
playerShortcuts: "Player Shortcuts",
|
|
293
|
+
overlays: "Overlays",
|
|
294
|
+
recording: "Recording",
|
|
295
|
+
showHelp: "Show Help",
|
|
296
|
+
perfOverlay: "Performance Overlay",
|
|
297
|
+
inputDisplay: "Input Display",
|
|
298
|
+
toggleRec: "Toggle Recording",
|
|
299
|
+
toggleMute: "Toggle Mute",
|
|
300
|
+
pressEsc: "Press ESC to close"
|
|
301
|
+
},
|
|
302
|
+
controls: {
|
|
303
|
+
title: "Controls",
|
|
304
|
+
keyboard: "Keyboard Mapping",
|
|
305
|
+
description: "Click a button and press a key to remap",
|
|
306
|
+
pressKey: "Press...",
|
|
307
|
+
reset: "Reset to Default",
|
|
308
|
+
save: "Save Controls"
|
|
309
|
+
},
|
|
310
|
+
gamepad: {
|
|
311
|
+
title: "Gamepad Settings",
|
|
312
|
+
noGamepad: "No gamepad detected",
|
|
313
|
+
connected: "{{count}} controller{{s}} connected",
|
|
314
|
+
none: "No controllers detected",
|
|
315
|
+
player: "Player:",
|
|
316
|
+
noController: "No controller detected",
|
|
317
|
+
pressAny: "Press any button on your gamepad to connect",
|
|
318
|
+
waiting: "Waiting for input...",
|
|
319
|
+
pressButton: "Press a button on your controller for {{button}}",
|
|
320
|
+
pressEsc: "Press Escape to cancel",
|
|
321
|
+
reset: "Reset to Default",
|
|
322
|
+
save: "Save Settings"
|
|
323
|
+
},
|
|
324
|
+
cheats: {
|
|
325
|
+
title: "Cheats",
|
|
326
|
+
addCheat: "Add Cheat",
|
|
327
|
+
available: "{{count}} cheat{{s}} available",
|
|
328
|
+
emptyTitle: "No cheats available",
|
|
329
|
+
emptyDesc: "No cheat codes found for this game",
|
|
330
|
+
copy: "Copy code",
|
|
331
|
+
active: "{{count}} cheat{{s}} active",
|
|
332
|
+
toggleHint: "Click a cheat to toggle it on/off"
|
|
333
|
+
},
|
|
334
|
+
saveSlots: {
|
|
335
|
+
title: "Save States",
|
|
336
|
+
saveTitle: "Save Game",
|
|
337
|
+
loadTitle: "Load Game",
|
|
338
|
+
emptySlot: "Empty Slot",
|
|
339
|
+
subtitleSave: "Choose a slot to save your progress",
|
|
340
|
+
subtitleLoad: "Select a save to restore",
|
|
341
|
+
loading: "Loading saves...",
|
|
342
|
+
locked: "Slot {{num}} Locked",
|
|
343
|
+
upgrade: "Upgrade to unlock more save slots",
|
|
344
|
+
autoSave: "Auto-Save Slot",
|
|
345
|
+
autoSaveDesc: "Reserved for automatic saves",
|
|
346
|
+
noData: "No save data",
|
|
347
|
+
slot: "Slot {{num}}",
|
|
348
|
+
footerSave: "Saves are stored in the cloud and sync across devices",
|
|
349
|
+
footerLoad: "Your progress will be restored to the selected save point"
|
|
350
|
+
},
|
|
351
|
+
bios: {
|
|
352
|
+
title: "BIOS Selection",
|
|
353
|
+
description: "Select a BIOS file to use for this game",
|
|
354
|
+
warningTitle: "Note:",
|
|
355
|
+
warning: "Changing BIOS requires the emulator to restart. Your unsaved progress will be lost.",
|
|
356
|
+
systemDefault: "System Default",
|
|
357
|
+
active: "Active",
|
|
358
|
+
defaultDesc: "Use the emulator's built-in or default BIOS",
|
|
359
|
+
emptyTitle: "No BIOS files found for this console.",
|
|
360
|
+
emptyDesc: "Upload BIOS files in your Dashboard Library.",
|
|
361
|
+
footer: "System Firmware Settings"
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
retroAchievements: {
|
|
365
|
+
title: "RetroAchievements",
|
|
366
|
+
login: "Login",
|
|
367
|
+
logout: "Logout",
|
|
368
|
+
username: "Username",
|
|
369
|
+
password: "Password",
|
|
370
|
+
hardcore: "Hardcore Mode",
|
|
371
|
+
achievements: "Achievements",
|
|
372
|
+
locked: "Locked",
|
|
373
|
+
unlocked: "Unlocked",
|
|
374
|
+
mastered: "Mastered",
|
|
375
|
+
identifying: "Identifying game...",
|
|
376
|
+
achievementsAvailable: "{{count}} achievements available",
|
|
377
|
+
gameNotSupported: "Connected - Game not in RA database",
|
|
378
|
+
connect: "Connect RetroAchievements",
|
|
379
|
+
connected: "RetroAchievements (connected)",
|
|
380
|
+
createAccount: "Create an account",
|
|
381
|
+
privacy: "Privacy:",
|
|
382
|
+
privacyText: "Your password is only used to authenticate with RetroAchievements. It is never stored.",
|
|
383
|
+
connecting: "Connecting...",
|
|
384
|
+
connectAccount: "Connect your account to track achievements",
|
|
385
|
+
poweredBy: "Powered by",
|
|
386
|
+
connectedStatus: "Connected",
|
|
387
|
+
yourUsername: "Your RA username",
|
|
388
|
+
yourPassword: "Your RA password",
|
|
389
|
+
usernameRequired: "Username and Password are required",
|
|
390
|
+
noGame: "No game loaded",
|
|
391
|
+
loadGame: "Load a game to see achievements",
|
|
392
|
+
noAchievements: "No achievements found",
|
|
393
|
+
notSupported: "This game may not be supported",
|
|
394
|
+
ptsRemaining: "{{count}} pts remaining",
|
|
395
|
+
filters: {
|
|
396
|
+
all: "All",
|
|
397
|
+
locked: "Locked",
|
|
398
|
+
unlocked: "Unlocked"
|
|
399
|
+
}
|
|
400
|
+
},
|
|
401
|
+
overlays: {
|
|
402
|
+
performance: {
|
|
403
|
+
title: "Stats",
|
|
404
|
+
fps: "FPS",
|
|
405
|
+
frameTime: "FT",
|
|
406
|
+
memory: "MEM",
|
|
407
|
+
core: "Core",
|
|
408
|
+
input: "INPUT",
|
|
409
|
+
active: "ACTIVE"
|
|
410
|
+
},
|
|
411
|
+
toast: {
|
|
412
|
+
saved: "Game Saved",
|
|
413
|
+
loaded: "Game Loaded",
|
|
414
|
+
error: "Error"
|
|
415
|
+
},
|
|
416
|
+
recording: {
|
|
417
|
+
started: "Recording Started",
|
|
418
|
+
stopped: "Recording Stopped",
|
|
419
|
+
saved: "Recording Saved",
|
|
420
|
+
paused: "PAUSED",
|
|
421
|
+
recording: "RECORDING",
|
|
422
|
+
resume: "Resume recording",
|
|
423
|
+
pause: "Pause recording",
|
|
424
|
+
stop: "Stop and save recording",
|
|
425
|
+
hover: "Hover for controls"
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
var KoinI18nContext = React2.createContext(en);
|
|
430
|
+
function deepMerge(target, source) {
|
|
431
|
+
const result = { ...target };
|
|
432
|
+
for (const key in source) {
|
|
433
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
434
|
+
const sourceValue = source[key];
|
|
435
|
+
const targetValue = result[key];
|
|
436
|
+
if (sourceValue && typeof sourceValue === "object" && targetValue && typeof targetValue === "object" && !Array.isArray(sourceValue)) {
|
|
437
|
+
result[key] = deepMerge(targetValue, sourceValue);
|
|
438
|
+
} else if (sourceValue !== void 0) {
|
|
439
|
+
result[key] = sourceValue;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return result;
|
|
444
|
+
}
|
|
445
|
+
var KoinI18nProvider = ({ children, translations }) => {
|
|
446
|
+
const value = React2.useMemo(() => {
|
|
447
|
+
if (!translations) return en;
|
|
448
|
+
return deepMerge(en, translations);
|
|
449
|
+
}, [translations]);
|
|
450
|
+
return /* @__PURE__ */ jsxRuntime.jsx(KoinI18nContext.Provider, { value, children });
|
|
451
|
+
};
|
|
452
|
+
function useKoinTranslation() {
|
|
453
|
+
return React2.useContext(KoinI18nContext);
|
|
454
|
+
}
|
|
455
|
+
var PlaybackControls = React2.memo(function PlaybackControls2({
|
|
181
456
|
isPaused,
|
|
182
457
|
isRunning,
|
|
183
458
|
speed,
|
|
@@ -196,20 +471,30 @@ var PlaybackControls = React5.memo(function PlaybackControls2({
|
|
|
196
471
|
systemColor = "#00FF41",
|
|
197
472
|
hardcoreRestrictions
|
|
198
473
|
}) {
|
|
474
|
+
const t = useKoinTranslation();
|
|
199
475
|
const hasRewindHistory = rewindBufferSize > 0;
|
|
200
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 flex-shrink-0", children: [
|
|
476
|
+
return /* @__PURE__ */ jsxRuntime.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: [
|
|
201
477
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
202
478
|
ControlButton,
|
|
203
479
|
{
|
|
204
480
|
onClick: onPauseToggle,
|
|
205
481
|
icon: !isRunning || isPaused ? lucideReact.Play : lucideReact.Pause,
|
|
206
|
-
label: !isRunning || isPaused ?
|
|
482
|
+
label: !isRunning || isPaused ? t.controls.play : t.controls.pause,
|
|
207
483
|
active: isPaused,
|
|
208
484
|
disabled,
|
|
209
485
|
systemColor
|
|
210
486
|
}
|
|
211
487
|
),
|
|
212
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
488
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
489
|
+
ControlButton,
|
|
490
|
+
{
|
|
491
|
+
onClick: onRestart,
|
|
492
|
+
icon: lucideReact.RotateCcw,
|
|
493
|
+
label: t.controls.reset,
|
|
494
|
+
disabled,
|
|
495
|
+
systemColor
|
|
496
|
+
}
|
|
497
|
+
),
|
|
213
498
|
/* @__PURE__ */ jsxRuntime.jsx(SpeedMenu, { speed, onSpeedChange, disabled }),
|
|
214
499
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative group", children: [
|
|
215
500
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -221,7 +506,7 @@ var PlaybackControls = React5.memo(function PlaybackControls2({
|
|
|
221
506
|
onTouchStart: hasRewindHistory && hardcoreRestrictions?.canUseRewind !== false ? onRewindStart : void 0,
|
|
222
507
|
onTouchEnd: hasRewindHistory && hardcoreRestrictions?.canUseRewind !== false ? onRewindStop : void 0,
|
|
223
508
|
icon: lucideReact.Rewind,
|
|
224
|
-
label:
|
|
509
|
+
label: t.controls.rewind,
|
|
225
510
|
active: isRewinding,
|
|
226
511
|
disabled: disabled || !hasRewindHistory || hardcoreRestrictions?.canUseRewind === false,
|
|
227
512
|
systemColor
|
|
@@ -232,10 +517,10 @@ var PlaybackControls = React5.memo(function PlaybackControls2({
|
|
|
232
517
|
HardcoreTooltip,
|
|
233
518
|
{
|
|
234
519
|
show: hardcoreRestrictions?.canUseRewind === false,
|
|
235
|
-
message: hardcoreRestrictions?.isHardcore ?
|
|
520
|
+
message: hardcoreRestrictions?.isHardcore ? t.common.disabledInHardcore : t.common.notSupported
|
|
236
521
|
}
|
|
237
522
|
),
|
|
238
|
-
hardcoreRestrictions?.canUseRewind !== false && !hasRewindHistory && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 bg-black/90 text-white text-xs rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-50", children:
|
|
523
|
+
hardcoreRestrictions?.canUseRewind !== false && !hasRewindHistory && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 bg-black/90 text-white text-xs rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-50", children: t.common.playToEnableRewind })
|
|
239
524
|
] }),
|
|
240
525
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-px h-8 bg-white/10 mx-1" }),
|
|
241
526
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -260,10 +545,10 @@ function AutoSaveIndicator({
|
|
|
260
545
|
isPaused = false,
|
|
261
546
|
onClick
|
|
262
547
|
}) {
|
|
263
|
-
const prevStateRef =
|
|
264
|
-
const [displayProgress, setDisplayProgress] =
|
|
265
|
-
const [iconOpacity, setIconOpacity] =
|
|
266
|
-
|
|
548
|
+
const prevStateRef = React2.useRef(state);
|
|
549
|
+
const [displayProgress, setDisplayProgress] = React2.useState(progress);
|
|
550
|
+
const [iconOpacity, setIconOpacity] = React2.useState(1);
|
|
551
|
+
React2.useEffect(() => {
|
|
267
552
|
const prevState = prevStateRef.current;
|
|
268
553
|
if (state === "done" && prevState === "saving") {
|
|
269
554
|
const startProgress = displayProgress;
|
|
@@ -306,7 +591,7 @@ function AutoSaveIndicator({
|
|
|
306
591
|
}
|
|
307
592
|
prevStateRef.current = state;
|
|
308
593
|
}, [state, progress]);
|
|
309
|
-
|
|
594
|
+
React2.useEffect(() => {
|
|
310
595
|
if (state !== prevStateRef.current) {
|
|
311
596
|
setIconOpacity(0);
|
|
312
597
|
const timer = setTimeout(() => {
|
|
@@ -421,7 +706,7 @@ function AutoSaveIndicator({
|
|
|
421
706
|
}
|
|
422
707
|
);
|
|
423
708
|
}
|
|
424
|
-
var SaveLoadControls =
|
|
709
|
+
var SaveLoadControls = React2.memo(function SaveLoadControls2({
|
|
425
710
|
onSave,
|
|
426
711
|
onLoad,
|
|
427
712
|
onScreenshot,
|
|
@@ -438,7 +723,8 @@ var SaveLoadControls = React5.memo(function SaveLoadControls2({
|
|
|
438
723
|
autoSavePaused = false,
|
|
439
724
|
onAutoSaveToggle
|
|
440
725
|
}) {
|
|
441
|
-
|
|
726
|
+
const t = useKoinTranslation();
|
|
727
|
+
return /* @__PURE__ */ jsxRuntime.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: [
|
|
442
728
|
autoSaveEnabled && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
443
729
|
AutoSaveIndicator,
|
|
444
730
|
{
|
|
@@ -455,12 +741,18 @@ var SaveLoadControls = React5.memo(function SaveLoadControls2({
|
|
|
455
741
|
{
|
|
456
742
|
onClick: hardcoreRestrictions?.canUseSaveStates === false ? void 0 : onSave,
|
|
457
743
|
icon: lucideReact.Save,
|
|
458
|
-
label:
|
|
744
|
+
label: t.controls.save,
|
|
459
745
|
disabled: disabled || saveDisabled || hardcoreRestrictions?.canUseSaveStates === false,
|
|
460
746
|
systemColor
|
|
461
747
|
}
|
|
462
748
|
),
|
|
463
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
749
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
750
|
+
HardcoreTooltip,
|
|
751
|
+
{
|
|
752
|
+
show: hardcoreRestrictions?.canUseSaveStates === false,
|
|
753
|
+
message: hardcoreRestrictions?.isHardcore ? t.common.disabledInHardcore : t.common.notSupported
|
|
754
|
+
}
|
|
755
|
+
)
|
|
464
756
|
] }),
|
|
465
757
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative group flex-shrink-0", children: [
|
|
466
758
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -468,19 +760,25 @@ var SaveLoadControls = React5.memo(function SaveLoadControls2({
|
|
|
468
760
|
{
|
|
469
761
|
onClick: hardcoreRestrictions?.canUseSaveStates === false ? void 0 : onLoad,
|
|
470
762
|
icon: lucideReact.Download,
|
|
471
|
-
label:
|
|
763
|
+
label: t.controls.load,
|
|
472
764
|
disabled: disabled || loadDisabled || hardcoreRestrictions?.canUseSaveStates === false,
|
|
473
765
|
systemColor
|
|
474
766
|
}
|
|
475
767
|
),
|
|
476
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
768
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
769
|
+
HardcoreTooltip,
|
|
770
|
+
{
|
|
771
|
+
show: hardcoreRestrictions?.canUseSaveStates === false,
|
|
772
|
+
message: hardcoreRestrictions?.isHardcore ? t.common.disabledInHardcore : t.common.notSupported
|
|
773
|
+
}
|
|
774
|
+
)
|
|
477
775
|
] }),
|
|
478
776
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
479
777
|
ControlButton,
|
|
480
778
|
{
|
|
481
779
|
onClick: onScreenshot,
|
|
482
780
|
icon: lucideReact.Camera,
|
|
483
|
-
label:
|
|
781
|
+
label: t.controls.snap,
|
|
484
782
|
disabled: disabled || saveDisabled,
|
|
485
783
|
systemColor
|
|
486
784
|
}
|
|
@@ -491,7 +789,7 @@ var SaveLoadControls = React5.memo(function SaveLoadControls2({
|
|
|
491
789
|
onClick: onRecordToggle,
|
|
492
790
|
disabled,
|
|
493
791
|
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" : ""}`,
|
|
494
|
-
title: isRecording ?
|
|
792
|
+
title: isRecording ? t.controls.stopRecord : t.controls.startRecord,
|
|
495
793
|
children: [
|
|
496
794
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative", children: isRecording ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
497
795
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Video, { size: 20, className: "text-red-500" }),
|
|
@@ -502,7 +800,7 @@ var SaveLoadControls = React5.memo(function SaveLoadControls2({
|
|
|
502
800
|
{
|
|
503
801
|
className: "text-[10px] font-bold uppercase tracking-wider",
|
|
504
802
|
style: { color: isRecording ? "#FF3333" : void 0 },
|
|
505
|
-
children:
|
|
803
|
+
children: t.controls.rec
|
|
506
804
|
}
|
|
507
805
|
)
|
|
508
806
|
]
|
|
@@ -517,17 +815,17 @@ var QUICK_PRESETS = [
|
|
|
517
815
|
{ id: "crt/crt-geom", label: "CRT Geom" },
|
|
518
816
|
{ id: "handheld/lcd-grid-v2", label: "LCD Grid" }
|
|
519
817
|
];
|
|
520
|
-
var ShaderDropdown =
|
|
818
|
+
var ShaderDropdown = React2.memo(function ShaderDropdown2({
|
|
521
819
|
currentShader = "",
|
|
522
820
|
onShaderChange,
|
|
523
821
|
isRunning = false,
|
|
524
822
|
systemColor = "#00FF41",
|
|
525
823
|
disabled = false
|
|
526
824
|
}) {
|
|
527
|
-
const [isOpen, setIsOpen] =
|
|
528
|
-
const [pendingShader, setPendingShader] =
|
|
529
|
-
const dropdownRef =
|
|
530
|
-
|
|
825
|
+
const [isOpen, setIsOpen] = React2.useState(false);
|
|
826
|
+
const [pendingShader, setPendingShader] = React2.useState(null);
|
|
827
|
+
const dropdownRef = React2.useRef(null);
|
|
828
|
+
React2.useEffect(() => {
|
|
531
829
|
const handleClickOutside = (e) => {
|
|
532
830
|
if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
|
|
533
831
|
setIsOpen(false);
|
|
@@ -635,6 +933,9 @@ function RAButton({
|
|
|
635
933
|
achievementCount,
|
|
636
934
|
className = ""
|
|
637
935
|
}) {
|
|
936
|
+
const t = useKoinTranslation();
|
|
937
|
+
const title = isGameFound ? `${t.retroAchievements.title} (${achievementCount} achievements)` : isConnected ? t.retroAchievements.connected : t.retroAchievements.title;
|
|
938
|
+
const tooltip = isIdentifying ? t.retroAchievements.identifying : isGameFound ? t.retroAchievements.achievementsAvailable.replace("{{count}}", achievementCount.toString()) : isConnected ? t.retroAchievements.gameNotSupported : t.retroAchievements.connect;
|
|
638
939
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `relative group ${className}`, children: [
|
|
639
940
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
640
941
|
"button",
|
|
@@ -647,7 +948,7 @@ function RAButton({
|
|
|
647
948
|
select-none
|
|
648
949
|
${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"}
|
|
649
950
|
`,
|
|
650
|
-
title
|
|
951
|
+
title,
|
|
651
952
|
children: [
|
|
652
953
|
isIdentifying ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { size: 20, className: "animate-spin text-yellow-400" }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
653
954
|
lucideReact.Trophy,
|
|
@@ -668,13 +969,14 @@ function RAButton({
|
|
|
668
969
|
absolute -top-1 -right-1 w-3 h-3 rounded-full border-2 border-black
|
|
669
970
|
${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)]"}
|
|
670
971
|
`, children: isGameFound && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 bg-green-400 rounded-full animate-ping opacity-50" }) }),
|
|
671
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 bg-gray-900/95 text-white text-xs rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-50 border border-white/10", children:
|
|
972
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute bottom-full left-1/2 -translate-x-1/2 mb-2 px-2 py-1 bg-gray-900/95 text-white text-xs rounded whitespace-nowrap opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none z-50 border border-white/10", children: tooltip })
|
|
672
973
|
] });
|
|
673
974
|
}
|
|
674
|
-
var SettingsControls =
|
|
975
|
+
var SettingsControls = React2.memo(function SettingsControls2({
|
|
675
976
|
onFullscreen,
|
|
676
977
|
onControls,
|
|
677
978
|
onGamepadSettings,
|
|
979
|
+
onSettings,
|
|
678
980
|
onCheats,
|
|
679
981
|
onRetroAchievements,
|
|
680
982
|
onShowShortcuts,
|
|
@@ -691,8 +993,10 @@ var SettingsControls = React5.memo(function SettingsControls2({
|
|
|
691
993
|
raAchievementCount = 0,
|
|
692
994
|
raIsIdentifying = false
|
|
693
995
|
}) {
|
|
996
|
+
const t = useKoinTranslation();
|
|
694
997
|
const gamepadIndicatorText = gamepadCount > 0 ? Array.from({ length: gamepadCount }, (_, i) => `P${i + 1}`).join(" ") : "";
|
|
695
|
-
|
|
998
|
+
const gamepadConnectedTitle = t.controls.gamepadConnected.replace("{{count}}", gamepadCount.toString()).replace("{{plural}}", gamepadCount > 1 ? "s" : "");
|
|
999
|
+
return /* @__PURE__ */ jsxRuntime.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: [
|
|
696
1000
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
697
1001
|
ShaderDropdown_default,
|
|
698
1002
|
{
|
|
@@ -703,15 +1007,15 @@ var SettingsControls = React5.memo(function SettingsControls2({
|
|
|
703
1007
|
disabled
|
|
704
1008
|
}
|
|
705
1009
|
),
|
|
706
|
-
onShowShortcuts && /* @__PURE__ */ jsxRuntime.jsx(ControlButton, { onClick: onShowShortcuts, icon: lucideReact.HelpCircle, label:
|
|
707
|
-
/* @__PURE__ */ jsxRuntime.jsx(ControlButton, { onClick: onFullscreen, icon: lucideReact.Maximize, label:
|
|
708
|
-
/* @__PURE__ */ jsxRuntime.jsx(ControlButton, { onClick: onControls, icon: lucideReact.Gamepad2, label:
|
|
1010
|
+
onShowShortcuts && /* @__PURE__ */ jsxRuntime.jsx(ControlButton, { onClick: onShowShortcuts, icon: lucideReact.HelpCircle, label: t.controls.help, disabled, className: "hidden sm:flex", systemColor }),
|
|
1011
|
+
/* @__PURE__ */ jsxRuntime.jsx(ControlButton, { onClick: onFullscreen, icon: lucideReact.Maximize, label: t.controls.full, disabled, className: "hidden sm:flex", systemColor }),
|
|
1012
|
+
/* @__PURE__ */ jsxRuntime.jsx(ControlButton, { onClick: onControls, icon: lucideReact.Gamepad2, label: t.controls.keys, disabled, className: "hidden sm:flex", systemColor }),
|
|
709
1013
|
gamepadCount > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
710
1014
|
"button",
|
|
711
1015
|
{
|
|
712
1016
|
onClick: onGamepadSettings,
|
|
713
1017
|
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",
|
|
714
|
-
title:
|
|
1018
|
+
title: gamepadConnectedTitle,
|
|
715
1019
|
children: [
|
|
716
1020
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
|
|
717
1021
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Joystick, { size: 20, style: { color: systemColor }, className: "transition-transform group-hover:scale-110" }),
|
|
@@ -732,15 +1036,15 @@ var SettingsControls = React5.memo(function SettingsControls2({
|
|
|
732
1036
|
"button",
|
|
733
1037
|
{
|
|
734
1038
|
onClick: onGamepadSettings,
|
|
735
|
-
className: "relative group flex-col items-center gap-1 px-3 py-2 transition-all duration-200 flex-shrink-0
|
|
736
|
-
title:
|
|
1039
|
+
className: "relative group flex-col items-center gap-1 px-3 py-2 transition-all duration-200 flex-shrink-0",
|
|
1040
|
+
title: t.controls.noGamepad,
|
|
737
1041
|
style: {
|
|
738
1042
|
border: "2px dashed #6b7280",
|
|
739
1043
|
backgroundColor: "transparent"
|
|
740
1044
|
},
|
|
741
1045
|
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex items-center gap-2", children: [
|
|
742
1046
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Gamepad2, { size: 18, className: "text-gray-400 transition-transform group-hover:scale-110 group-hover:text-white" }),
|
|
743
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-bold uppercase tracking-wider text-gray-400 group-hover:text-white whitespace-nowrap", children:
|
|
1047
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-bold uppercase tracking-wider text-gray-400 group-hover:text-white whitespace-nowrap", children: t.controls.press }),
|
|
744
1048
|
/* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx("div", { className: "w-2 h-2 rounded-full bg-gray-400 group-hover:bg-white" }) })
|
|
745
1049
|
] })
|
|
746
1050
|
}
|
|
@@ -752,12 +1056,18 @@ var SettingsControls = React5.memo(function SettingsControls2({
|
|
|
752
1056
|
{
|
|
753
1057
|
onClick: hardcoreRestrictions?.canUseCheats === false ? void 0 : onCheats,
|
|
754
1058
|
icon: lucideReact.Code,
|
|
755
|
-
label:
|
|
1059
|
+
label: t.settings.cheats,
|
|
756
1060
|
disabled: disabled || hardcoreRestrictions?.canUseCheats === false,
|
|
757
1061
|
systemColor
|
|
758
1062
|
}
|
|
759
1063
|
),
|
|
760
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1064
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1065
|
+
HardcoreTooltip,
|
|
1066
|
+
{
|
|
1067
|
+
show: hardcoreRestrictions?.canUseCheats === false,
|
|
1068
|
+
message: hardcoreRestrictions?.isHardcore ? t.common.disabledInHardcore : t.common.notSupported
|
|
1069
|
+
}
|
|
1070
|
+
)
|
|
761
1071
|
] }),
|
|
762
1072
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
763
1073
|
RAButton,
|
|
@@ -771,16 +1081,28 @@ var SettingsControls = React5.memo(function SettingsControls2({
|
|
|
771
1081
|
className: "hidden sm:flex"
|
|
772
1082
|
}
|
|
773
1083
|
),
|
|
1084
|
+
onSettings && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1085
|
+
ControlButton,
|
|
1086
|
+
{
|
|
1087
|
+
onClick: onSettings,
|
|
1088
|
+
icon: lucideReact.Settings,
|
|
1089
|
+
label: t.settings.title,
|
|
1090
|
+
disabled,
|
|
1091
|
+
systemColor,
|
|
1092
|
+
className: "hidden sm:flex"
|
|
1093
|
+
}
|
|
1094
|
+
),
|
|
774
1095
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-px h-8 bg-white/10 mx-2 hidden sm:block" }),
|
|
775
|
-
/* @__PURE__ */ jsxRuntime.jsx(ControlButton, { onClick: onExit, icon: lucideReact.Power, label:
|
|
1096
|
+
/* @__PURE__ */ jsxRuntime.jsx(ControlButton, { onClick: onExit, icon: lucideReact.Power, label: t.settings.exit, danger: true, disabled, systemColor })
|
|
776
1097
|
] });
|
|
777
1098
|
});
|
|
778
|
-
var MobileControlDrawer =
|
|
1099
|
+
var MobileControlDrawer = React2.memo(function MobileControlDrawer2({
|
|
779
1100
|
children,
|
|
780
1101
|
systemColor = "#00FF41"
|
|
781
1102
|
}) {
|
|
782
|
-
const
|
|
783
|
-
|
|
1103
|
+
const t = useKoinTranslation();
|
|
1104
|
+
const [isExpanded, setIsExpanded] = React2.useState(false);
|
|
1105
|
+
React2.useEffect(() => {
|
|
784
1106
|
if (isExpanded) {
|
|
785
1107
|
const handleInteract = (e) => {
|
|
786
1108
|
const target = e.target;
|
|
@@ -813,7 +1135,7 @@ var MobileControlDrawer = React5.memo(function MobileControlDrawer2({
|
|
|
813
1135
|
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)",
|
|
814
1136
|
borderColor: isExpanded ? systemColor : void 0
|
|
815
1137
|
},
|
|
816
|
-
"aria-label": isExpanded ?
|
|
1138
|
+
"aria-label": isExpanded ? t.controls.menuClose : t.controls.menuOpen,
|
|
817
1139
|
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
818
1140
|
"svg",
|
|
819
1141
|
{
|
|
@@ -854,7 +1176,7 @@ var MobileControlDrawer = React5.memo(function MobileControlDrawer2({
|
|
|
854
1176
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
855
1177
|
"div",
|
|
856
1178
|
{
|
|
857
|
-
className: "relative flex flex-
|
|
1179
|
+
className: "relative flex flex-col items-center px-4 py-6 pb-20",
|
|
858
1180
|
style: { paddingBottom: "calc(env(safe-area-inset-bottom, 20px) + 80px)" },
|
|
859
1181
|
children
|
|
860
1182
|
}
|
|
@@ -866,7 +1188,7 @@ var MobileControlDrawer = React5.memo(function MobileControlDrawer2({
|
|
|
866
1188
|
] });
|
|
867
1189
|
});
|
|
868
1190
|
var MobileControlDrawer_default = MobileControlDrawer;
|
|
869
|
-
var PlayerControls =
|
|
1191
|
+
var PlayerControls = React2.memo(function PlayerControls2({
|
|
870
1192
|
isPaused,
|
|
871
1193
|
isRunning,
|
|
872
1194
|
speed,
|
|
@@ -902,6 +1224,7 @@ var PlayerControls = React5.memo(function PlayerControls2({
|
|
|
902
1224
|
systemColor = "#00FF41",
|
|
903
1225
|
gamepadCount = 0,
|
|
904
1226
|
onGamepadSettings,
|
|
1227
|
+
onSettings,
|
|
905
1228
|
volume = 100,
|
|
906
1229
|
isMuted = false,
|
|
907
1230
|
onVolumeChange,
|
|
@@ -934,6 +1257,7 @@ var PlayerControls = React5.memo(function PlayerControls2({
|
|
|
934
1257
|
hardcoreRestrictions
|
|
935
1258
|
}
|
|
936
1259
|
),
|
|
1260
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-px bg-white/10 sm:hidden my-4" }),
|
|
937
1261
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
938
1262
|
SaveLoadControls,
|
|
939
1263
|
{
|
|
@@ -954,12 +1278,14 @@ var PlayerControls = React5.memo(function PlayerControls2({
|
|
|
954
1278
|
onAutoSaveToggle
|
|
955
1279
|
}
|
|
956
1280
|
),
|
|
1281
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-px bg-white/10 sm:hidden my-4" }),
|
|
957
1282
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
958
1283
|
SettingsControls,
|
|
959
1284
|
{
|
|
960
1285
|
onFullscreen,
|
|
961
1286
|
onControls,
|
|
962
1287
|
onGamepadSettings,
|
|
1288
|
+
onSettings,
|
|
963
1289
|
onCheats,
|
|
964
1290
|
onRetroAchievements,
|
|
965
1291
|
onShowShortcuts,
|
|
@@ -980,7 +1306,7 @@ var PlayerControls = React5.memo(function PlayerControls2({
|
|
|
980
1306
|
] });
|
|
981
1307
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
982
1308
|
/* @__PURE__ */ jsxRuntime.jsx(MobileControlDrawer_default, { systemColor, children: controlsContent }),
|
|
983
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden sm:flex w-full
|
|
1309
|
+
/* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx("div", { className: "flex items-center min-w-max mx-auto gap-4 px-8 py-4", children: controlsContent }) })
|
|
984
1310
|
] });
|
|
985
1311
|
});
|
|
986
1312
|
var PlayerControls_default = PlayerControls;
|
|
@@ -1028,11 +1354,11 @@ var TOAST_CONFIGS = {
|
|
|
1028
1354
|
}
|
|
1029
1355
|
};
|
|
1030
1356
|
function ToastItem({ toast, onDismiss }) {
|
|
1031
|
-
const [isVisible, setIsVisible] =
|
|
1032
|
-
const [isExiting, setIsExiting] =
|
|
1357
|
+
const [isVisible, setIsVisible] = React2.useState(false);
|
|
1358
|
+
const [isExiting, setIsExiting] = React2.useState(false);
|
|
1033
1359
|
const config = TOAST_CONFIGS[toast.type];
|
|
1034
1360
|
const IconComponent = config.icon;
|
|
1035
|
-
|
|
1361
|
+
React2.useEffect(() => {
|
|
1036
1362
|
requestAnimationFrame(() => {
|
|
1037
1363
|
setIsVisible(true);
|
|
1038
1364
|
});
|
|
@@ -1138,17 +1464,18 @@ function ToastContainer({ toasts, onDismiss }) {
|
|
|
1138
1464
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed top-4 right-4 z-[9999] flex flex-col gap-2 pointer-events-none", children: toasts.map((toast) => /* @__PURE__ */ jsxRuntime.jsx(ToastItem, { toast, onDismiss }, toast.id)) })
|
|
1139
1465
|
] });
|
|
1140
1466
|
}
|
|
1141
|
-
var PerformanceOverlay =
|
|
1467
|
+
var PerformanceOverlay = React2.memo(function PerformanceOverlay2({
|
|
1142
1468
|
isVisible,
|
|
1143
1469
|
coreName = "Unknown",
|
|
1144
1470
|
systemColor = "#00FF41"
|
|
1145
1471
|
}) {
|
|
1146
|
-
const
|
|
1147
|
-
const [
|
|
1148
|
-
const
|
|
1149
|
-
const
|
|
1150
|
-
const
|
|
1151
|
-
|
|
1472
|
+
const t = useKoinTranslation();
|
|
1473
|
+
const [fps, setFps] = React2.useState(0);
|
|
1474
|
+
const [frameTime, setFrameTime] = React2.useState(0);
|
|
1475
|
+
const frameTimesRef = React2.useRef([]);
|
|
1476
|
+
const lastTimeRef = React2.useRef(performance.now());
|
|
1477
|
+
const rafIdRef = React2.useRef(null);
|
|
1478
|
+
React2.useEffect(() => {
|
|
1152
1479
|
if (!isVisible) {
|
|
1153
1480
|
if (rafIdRef.current) {
|
|
1154
1481
|
cancelAnimationFrame(rafIdRef.current);
|
|
@@ -1194,12 +1521,12 @@ var PerformanceOverlay = React5.memo(function PerformanceOverlay2({
|
|
|
1194
1521
|
},
|
|
1195
1522
|
children: [
|
|
1196
1523
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
|
|
1197
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "opacity-60", children:
|
|
1524
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "opacity-60 text-[10px] uppercase", children: t.overlays.performance.fps }),
|
|
1198
1525
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-bold", children: fps })
|
|
1199
1526
|
] }),
|
|
1200
1527
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-px h-3 bg-white/20" }),
|
|
1201
1528
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
|
|
1202
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "opacity-60", children:
|
|
1529
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "opacity-60 text-[10px] uppercase", children: t.overlays.performance.frameTime }),
|
|
1203
1530
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-bold", children: [
|
|
1204
1531
|
frameTime,
|
|
1205
1532
|
"ms"
|
|
@@ -1207,7 +1534,7 @@ var PerformanceOverlay = React5.memo(function PerformanceOverlay2({
|
|
|
1207
1534
|
] }),
|
|
1208
1535
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-px h-3 bg-white/20" }),
|
|
1209
1536
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
|
|
1210
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "opacity-60", children:
|
|
1537
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "opacity-60 text-[10px] uppercase", children: t.overlays.performance.core }),
|
|
1211
1538
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-bold text-white/80", children: coreName })
|
|
1212
1539
|
] })
|
|
1213
1540
|
]
|
|
@@ -1254,14 +1581,14 @@ var KEY_TO_BUTTON = {
|
|
|
1254
1581
|
"ShiftRight": "SELECT",
|
|
1255
1582
|
"Space": "COIN"
|
|
1256
1583
|
};
|
|
1257
|
-
var InputDisplay =
|
|
1584
|
+
var InputDisplay = React2.memo(function InputDisplay2({
|
|
1258
1585
|
isVisible,
|
|
1259
1586
|
system,
|
|
1260
1587
|
systemColor = "#00FF41",
|
|
1261
1588
|
position = "bottom-right"
|
|
1262
1589
|
}) {
|
|
1263
|
-
const [activeKeys, setActiveKeys] =
|
|
1264
|
-
|
|
1590
|
+
const [activeKeys, setActiveKeys] = React2.useState(/* @__PURE__ */ new Set());
|
|
1591
|
+
React2.useEffect(() => {
|
|
1265
1592
|
if (!isVisible) return;
|
|
1266
1593
|
const handleKeyDown = (e) => {
|
|
1267
1594
|
const button = KEY_TO_BUTTON[e.code];
|
|
@@ -1335,7 +1662,7 @@ var InputDisplay = React5.memo(function InputDisplay2({
|
|
|
1335
1662
|
}
|
|
1336
1663
|
);
|
|
1337
1664
|
});
|
|
1338
|
-
var DpadButton =
|
|
1665
|
+
var DpadButton = React2.memo(function DpadButton2({
|
|
1339
1666
|
active,
|
|
1340
1667
|
color,
|
|
1341
1668
|
children
|
|
@@ -1353,7 +1680,7 @@ var DpadButton = React5.memo(function DpadButton2({
|
|
|
1353
1680
|
}
|
|
1354
1681
|
);
|
|
1355
1682
|
});
|
|
1356
|
-
var ActionButton =
|
|
1683
|
+
var ActionButton = React2.memo(function ActionButton2({
|
|
1357
1684
|
active,
|
|
1358
1685
|
color,
|
|
1359
1686
|
children
|
|
@@ -1372,7 +1699,7 @@ var ActionButton = React5.memo(function ActionButton2({
|
|
|
1372
1699
|
);
|
|
1373
1700
|
});
|
|
1374
1701
|
var InputDisplay_default = InputDisplay;
|
|
1375
|
-
var RecordingIndicator =
|
|
1702
|
+
var RecordingIndicator = React2.memo(function RecordingIndicator2({
|
|
1376
1703
|
isRecording,
|
|
1377
1704
|
isPaused,
|
|
1378
1705
|
duration,
|
|
@@ -1381,7 +1708,8 @@ var RecordingIndicator = React5.memo(function RecordingIndicator2({
|
|
|
1381
1708
|
onStop,
|
|
1382
1709
|
systemColor = "#FF3333"
|
|
1383
1710
|
}) {
|
|
1384
|
-
const
|
|
1711
|
+
const t = useKoinTranslation();
|
|
1712
|
+
const [isHovered, setIsHovered] = React2.useState(false);
|
|
1385
1713
|
if (!isRecording) return null;
|
|
1386
1714
|
const minutes = Math.floor(duration / 60);
|
|
1387
1715
|
const seconds = duration % 60;
|
|
@@ -1428,7 +1756,7 @@ var RecordingIndicator = React5.memo(function RecordingIndicator2({
|
|
|
1428
1756
|
{
|
|
1429
1757
|
className: "text-xs font-bold",
|
|
1430
1758
|
style: { color: isPaused ? "#FFA500" : "#FF3333" },
|
|
1431
|
-
children: isPaused ?
|
|
1759
|
+
children: isPaused ? t.overlays.recording.paused : t.overlays.recording.recording
|
|
1432
1760
|
}
|
|
1433
1761
|
),
|
|
1434
1762
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-white/60", children: timeString })
|
|
@@ -1439,7 +1767,7 @@ var RecordingIndicator = React5.memo(function RecordingIndicator2({
|
|
|
1439
1767
|
{
|
|
1440
1768
|
onClick: onResume,
|
|
1441
1769
|
className: "p-1.5 rounded hover:bg-white/20 transition-colors",
|
|
1442
|
-
title:
|
|
1770
|
+
title: t.overlays.recording.resume,
|
|
1443
1771
|
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Play, { size: 14, className: "text-orange-400", fill: "#FFA500" })
|
|
1444
1772
|
}
|
|
1445
1773
|
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -1447,7 +1775,7 @@ var RecordingIndicator = React5.memo(function RecordingIndicator2({
|
|
|
1447
1775
|
{
|
|
1448
1776
|
onClick: onPause,
|
|
1449
1777
|
className: "p-1.5 rounded hover:bg-white/20 transition-colors",
|
|
1450
|
-
title:
|
|
1778
|
+
title: t.overlays.recording.pause,
|
|
1451
1779
|
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Pause, { size: 14, className: "text-white/80" })
|
|
1452
1780
|
}
|
|
1453
1781
|
),
|
|
@@ -1456,7 +1784,7 @@ var RecordingIndicator = React5.memo(function RecordingIndicator2({
|
|
|
1456
1784
|
{
|
|
1457
1785
|
onClick: onStop,
|
|
1458
1786
|
className: "p-1.5 rounded hover:bg-red-500/30 transition-colors flex items-center gap-1",
|
|
1459
|
-
title:
|
|
1787
|
+
title: t.overlays.recording.stop,
|
|
1460
1788
|
children: [
|
|
1461
1789
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Square, { size: 12, fill: "#FF3333", className: "text-red-500" }),
|
|
1462
1790
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { size: 12, className: "text-white/60" })
|
|
@@ -1467,39 +1795,40 @@ var RecordingIndicator = React5.memo(function RecordingIndicator2({
|
|
|
1467
1795
|
]
|
|
1468
1796
|
}
|
|
1469
1797
|
),
|
|
1470
|
-
!isHovered && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-[9px] text-white/40 text-center mt-1", children:
|
|
1798
|
+
!isHovered && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-[9px] text-white/40 text-center mt-1", children: t.overlays.recording.hover })
|
|
1471
1799
|
]
|
|
1472
1800
|
}
|
|
1473
1801
|
);
|
|
1474
1802
|
});
|
|
1475
1803
|
var RecordingIndicator_default = RecordingIndicator;
|
|
1476
|
-
var
|
|
1477
|
-
{
|
|
1478
|
-
section: "Overlays",
|
|
1479
|
-
items: [
|
|
1480
|
-
{ key: "F1", description: "Show this help" },
|
|
1481
|
-
{ key: "F3", description: "Performance Overlay (FPS)" },
|
|
1482
|
-
{ key: "F4", description: "Input Display" }
|
|
1483
|
-
]
|
|
1484
|
-
},
|
|
1485
|
-
{
|
|
1486
|
-
section: "Recording",
|
|
1487
|
-
items: [
|
|
1488
|
-
{ key: "F5", description: "Start/Stop Recording" }
|
|
1489
|
-
]
|
|
1490
|
-
},
|
|
1491
|
-
{
|
|
1492
|
-
section: "Audio",
|
|
1493
|
-
items: [
|
|
1494
|
-
{ key: "F9", description: "Toggle Mute" }
|
|
1495
|
-
]
|
|
1496
|
-
}
|
|
1497
|
-
];
|
|
1498
|
-
var ShortcutsModal = React5.memo(function ShortcutsModal2({
|
|
1804
|
+
var ShortcutsModal = React2.memo(function ShortcutsModal2({
|
|
1499
1805
|
isOpen,
|
|
1500
1806
|
onClose,
|
|
1501
1807
|
systemColor = "#00FF41"
|
|
1502
1808
|
}) {
|
|
1809
|
+
const t = useKoinTranslation();
|
|
1810
|
+
const shortcuts = React2.useMemo(() => [
|
|
1811
|
+
{
|
|
1812
|
+
section: t.modals.shortcuts.overlays,
|
|
1813
|
+
items: [
|
|
1814
|
+
{ key: "F1", description: t.modals.shortcuts.showHelp },
|
|
1815
|
+
{ key: "F3", description: t.modals.shortcuts.perfOverlay },
|
|
1816
|
+
{ key: "F4", description: t.modals.shortcuts.inputDisplay }
|
|
1817
|
+
]
|
|
1818
|
+
},
|
|
1819
|
+
{
|
|
1820
|
+
section: t.modals.shortcuts.recording,
|
|
1821
|
+
items: [
|
|
1822
|
+
{ key: "F5", description: t.modals.shortcuts.toggleRec }
|
|
1823
|
+
]
|
|
1824
|
+
},
|
|
1825
|
+
{
|
|
1826
|
+
section: t.settings.audio,
|
|
1827
|
+
items: [
|
|
1828
|
+
{ key: "F9", description: t.modals.shortcuts.toggleMute }
|
|
1829
|
+
]
|
|
1830
|
+
}
|
|
1831
|
+
], [t]);
|
|
1503
1832
|
if (!isOpen) return null;
|
|
1504
1833
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1505
1834
|
"div",
|
|
@@ -1521,7 +1850,7 @@ var ShortcutsModal = React5.memo(function ShortcutsModal2({
|
|
|
1521
1850
|
children: [
|
|
1522
1851
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1523
1852
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Keyboard, { size: 18, style: { color: systemColor } }),
|
|
1524
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-bold text-white", children:
|
|
1853
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-bold text-white", children: t.modals.shortcuts.playerShortcuts })
|
|
1525
1854
|
] }),
|
|
1526
1855
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1527
1856
|
"button",
|
|
@@ -1535,7 +1864,7 @@ var ShortcutsModal = React5.memo(function ShortcutsModal2({
|
|
|
1535
1864
|
}
|
|
1536
1865
|
),
|
|
1537
1866
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 space-y-3", children: [
|
|
1538
|
-
|
|
1867
|
+
shortcuts.map(({ section, items }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1539
1868
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1540
1869
|
"h3",
|
|
1541
1870
|
{
|
|
@@ -1569,20 +1898,16 @@ var ShortcutsModal = React5.memo(function ShortcutsModal2({
|
|
|
1569
1898
|
] }, section)),
|
|
1570
1899
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pt-2 border-t border-white/10 text-xs text-white/40", children: [
|
|
1571
1900
|
"Game controls can be configured in ",
|
|
1572
|
-
/* @__PURE__ */ jsxRuntime.jsx("strong", { className: "text-white/60", children:
|
|
1901
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { className: "text-white/60", children: t.controls.keys }),
|
|
1573
1902
|
" settings."
|
|
1574
1903
|
] })
|
|
1575
1904
|
] }),
|
|
1576
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1905
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1577
1906
|
"div",
|
|
1578
1907
|
{
|
|
1579
1908
|
className: "px-4 py-2 text-center text-xs text-white/40 border-t",
|
|
1580
1909
|
style: { borderColor: `${systemColor}20` },
|
|
1581
|
-
children:
|
|
1582
|
-
"Press ",
|
|
1583
|
-
/* @__PURE__ */ jsxRuntime.jsx("kbd", { className: "px-1 bg-white/10 rounded font-mono", children: "ESC" }),
|
|
1584
|
-
" to close"
|
|
1585
|
-
]
|
|
1910
|
+
children: t.modals.shortcuts.pressEsc
|
|
1586
1911
|
}
|
|
1587
1912
|
)
|
|
1588
1913
|
]
|
|
@@ -1705,10 +2030,10 @@ function createOrientationChangeHandler(callback, checkReady, maxRafs = 3) {
|
|
|
1705
2030
|
|
|
1706
2031
|
// src/hooks/useMobile.ts
|
|
1707
2032
|
function useMobile() {
|
|
1708
|
-
const [isMobile, setIsMobile] =
|
|
1709
|
-
const [isLandscape, setIsLandscape] =
|
|
1710
|
-
const [isPortrait, setIsPortrait] =
|
|
1711
|
-
|
|
2033
|
+
const [isMobile, setIsMobile] = React2.useState(false);
|
|
2034
|
+
const [isLandscape, setIsLandscape] = React2.useState(false);
|
|
2035
|
+
const [isPortrait, setIsPortrait] = React2.useState(true);
|
|
2036
|
+
React2.useEffect(() => {
|
|
1712
2037
|
let lastOrientation = null;
|
|
1713
2038
|
let orientationTimeout = null;
|
|
1714
2039
|
const checkOrientation = () => {
|
|
@@ -1860,17 +2185,17 @@ function useTouchHandlers({
|
|
|
1860
2185
|
onRelease,
|
|
1861
2186
|
onPositionChange
|
|
1862
2187
|
}) {
|
|
1863
|
-
const isDraggingRef =
|
|
1864
|
-
const dragStartRef =
|
|
1865
|
-
const dragTimerRef =
|
|
1866
|
-
const touchStartPosRef =
|
|
1867
|
-
const clearDragTimer =
|
|
2188
|
+
const isDraggingRef = React2.useRef(false);
|
|
2189
|
+
const dragStartRef = React2.useRef({ x: 0, y: 0 });
|
|
2190
|
+
const dragTimerRef = React2.useRef(null);
|
|
2191
|
+
const touchStartPosRef = React2.useRef({ x: 0, y: 0 });
|
|
2192
|
+
const clearDragTimer = React2.useCallback(() => {
|
|
1868
2193
|
if (dragTimerRef.current) {
|
|
1869
2194
|
clearTimeout(dragTimerRef.current);
|
|
1870
2195
|
dragTimerRef.current = null;
|
|
1871
2196
|
}
|
|
1872
2197
|
}, []);
|
|
1873
|
-
const startDragging =
|
|
2198
|
+
const startDragging = React2.useCallback(
|
|
1874
2199
|
(touchX, touchY) => {
|
|
1875
2200
|
isDraggingRef.current = true;
|
|
1876
2201
|
dragStartRef.current = {
|
|
@@ -1883,7 +2208,7 @@ function useTouchHandlers({
|
|
|
1883
2208
|
},
|
|
1884
2209
|
[displayX, displayY, containerWidth, containerHeight, isSystemButton, buttonType, onRelease]
|
|
1885
2210
|
);
|
|
1886
|
-
const handleTouchStart =
|
|
2211
|
+
const handleTouchStart = React2.useCallback(
|
|
1887
2212
|
(e) => {
|
|
1888
2213
|
const touch = e.touches[0];
|
|
1889
2214
|
touchStartPosRef.current = { x: touch.clientX, y: touch.clientY };
|
|
@@ -1918,7 +2243,7 @@ function useTouchHandlers({
|
|
|
1918
2243
|
},
|
|
1919
2244
|
[isSystemButton, buttonType, onPress, onPressDown, onPositionChange, buttonSize, startDragging]
|
|
1920
2245
|
);
|
|
1921
|
-
const handleTouchMove =
|
|
2246
|
+
const handleTouchMove = React2.useCallback(
|
|
1922
2247
|
(e) => {
|
|
1923
2248
|
const touch = e.touches[0];
|
|
1924
2249
|
if (onPositionChange && !isDraggingRef.current) {
|
|
@@ -1945,7 +2270,7 @@ function useTouchHandlers({
|
|
|
1945
2270
|
},
|
|
1946
2271
|
[onPositionChange, clearDragTimer, startDragging, containerWidth, containerHeight, buttonSize]
|
|
1947
2272
|
);
|
|
1948
|
-
const handleTouchEnd =
|
|
2273
|
+
const handleTouchEnd = React2.useCallback(
|
|
1949
2274
|
(e) => {
|
|
1950
2275
|
clearDragTimer();
|
|
1951
2276
|
if (isDraggingRef.current) {
|
|
@@ -1962,7 +2287,7 @@ function useTouchHandlers({
|
|
|
1962
2287
|
},
|
|
1963
2288
|
[clearDragTimer, isSystemButton, buttonType, onRelease]
|
|
1964
2289
|
);
|
|
1965
|
-
const handleTouchCancel =
|
|
2290
|
+
const handleTouchCancel = React2.useCallback(
|
|
1966
2291
|
(e) => {
|
|
1967
2292
|
clearDragTimer();
|
|
1968
2293
|
if (isDraggingRef.current) {
|
|
@@ -1979,7 +2304,7 @@ function useTouchHandlers({
|
|
|
1979
2304
|
},
|
|
1980
2305
|
[clearDragTimer, isSystemButton, buttonType, onRelease]
|
|
1981
2306
|
);
|
|
1982
|
-
const cleanup =
|
|
2307
|
+
const cleanup = React2.useCallback(() => {
|
|
1983
2308
|
clearDragTimer();
|
|
1984
2309
|
}, [clearDragTimer]);
|
|
1985
2310
|
return {
|
|
@@ -2043,7 +2368,7 @@ function getButtonStyles(buttonType, isPressed) {
|
|
|
2043
2368
|
};
|
|
2044
2369
|
}
|
|
2045
2370
|
}
|
|
2046
|
-
var VirtualButton =
|
|
2371
|
+
var VirtualButton = React2__default.default.memo(function VirtualButton2({
|
|
2047
2372
|
config,
|
|
2048
2373
|
isPressed,
|
|
2049
2374
|
onPress,
|
|
@@ -2057,10 +2382,14 @@ var VirtualButton = React5__default.default.memo(function VirtualButton2({
|
|
|
2057
2382
|
systemColor = "#00FF41"
|
|
2058
2383
|
// Default retro green
|
|
2059
2384
|
}) {
|
|
2060
|
-
const
|
|
2385
|
+
const t = useKoinTranslation();
|
|
2386
|
+
const buttonRef = React2.useRef(null);
|
|
2061
2387
|
const isSystemButton = config.type === "start" || config.type === "select";
|
|
2062
2388
|
const displayX = customPosition ? customPosition.x : config.x;
|
|
2063
2389
|
const displayY = customPosition ? customPosition.y : config.y;
|
|
2390
|
+
let label = config.label;
|
|
2391
|
+
if (config.type === "start") label = t.controls.startBtn;
|
|
2392
|
+
if (config.type === "select") label = t.controls.selectBtn;
|
|
2064
2393
|
const {
|
|
2065
2394
|
handleTouchStart,
|
|
2066
2395
|
handleTouchMove,
|
|
@@ -2080,7 +2409,7 @@ var VirtualButton = React5__default.default.memo(function VirtualButton2({
|
|
|
2080
2409
|
onRelease,
|
|
2081
2410
|
onPositionChange
|
|
2082
2411
|
});
|
|
2083
|
-
|
|
2412
|
+
React2.useEffect(() => {
|
|
2084
2413
|
const button = buttonRef.current;
|
|
2085
2414
|
if (!button) return;
|
|
2086
2415
|
button.addEventListener("touchstart", handleTouchStart, { passive: false });
|
|
@@ -2138,9 +2467,9 @@ var VirtualButton = React5__default.default.memo(function VirtualButton2({
|
|
|
2138
2467
|
userSelect: "none",
|
|
2139
2468
|
...pressedStyle
|
|
2140
2469
|
},
|
|
2141
|
-
"aria-label":
|
|
2470
|
+
"aria-label": label,
|
|
2142
2471
|
onContextMenu: (e) => e.preventDefault(),
|
|
2143
|
-
children:
|
|
2472
|
+
children: label
|
|
2144
2473
|
}
|
|
2145
2474
|
);
|
|
2146
2475
|
});
|
|
@@ -2757,7 +3086,7 @@ function dispatchKeyboardEvent(type, code) {
|
|
|
2757
3086
|
canvas.dispatchEvent(event);
|
|
2758
3087
|
return true;
|
|
2759
3088
|
}
|
|
2760
|
-
var Dpad =
|
|
3089
|
+
var Dpad = React2__default.default.memo(function Dpad2({
|
|
2761
3090
|
size,
|
|
2762
3091
|
x,
|
|
2763
3092
|
y,
|
|
@@ -2767,14 +3096,14 @@ var Dpad = React5__default.default.memo(function Dpad2({
|
|
|
2767
3096
|
systemColor = "#00FF41",
|
|
2768
3097
|
isLandscape = false
|
|
2769
3098
|
}) {
|
|
2770
|
-
const dpadRef =
|
|
2771
|
-
const activeTouchRef =
|
|
2772
|
-
const activeDirectionsRef =
|
|
2773
|
-
const upRef =
|
|
2774
|
-
const downRef =
|
|
2775
|
-
const leftRef =
|
|
2776
|
-
const rightRef =
|
|
2777
|
-
const getKeyCode =
|
|
3099
|
+
const dpadRef = React2.useRef(null);
|
|
3100
|
+
const activeTouchRef = React2.useRef(null);
|
|
3101
|
+
const activeDirectionsRef = React2.useRef(/* @__PURE__ */ new Set());
|
|
3102
|
+
const upRef = React2.useRef(null);
|
|
3103
|
+
const downRef = React2.useRef(null);
|
|
3104
|
+
const leftRef = React2.useRef(null);
|
|
3105
|
+
const rightRef = React2.useRef(null);
|
|
3106
|
+
const getKeyCode = React2.useCallback((direction) => {
|
|
2778
3107
|
if (!controls) {
|
|
2779
3108
|
const defaults = {
|
|
2780
3109
|
up: "ArrowUp",
|
|
@@ -2786,7 +3115,7 @@ var Dpad = React5__default.default.memo(function Dpad2({
|
|
|
2786
3115
|
}
|
|
2787
3116
|
return controls[direction] || "";
|
|
2788
3117
|
}, [controls]);
|
|
2789
|
-
const getDirectionsFromTouch =
|
|
3118
|
+
const getDirectionsFromTouch = React2.useCallback((touchX, touchY, rect) => {
|
|
2790
3119
|
const centerX = rect.left + rect.width / 2;
|
|
2791
3120
|
const centerY = rect.top + rect.height / 2;
|
|
2792
3121
|
const dx = touchX - centerX;
|
|
@@ -2802,7 +3131,7 @@ var Dpad = React5__default.default.memo(function Dpad2({
|
|
|
2802
3131
|
if (angle >= -67.5 && angle <= 67.5) directions.add("right");
|
|
2803
3132
|
return directions;
|
|
2804
3133
|
}, []);
|
|
2805
|
-
const updateVisuals =
|
|
3134
|
+
const updateVisuals = React2.useCallback((directions) => {
|
|
2806
3135
|
const activeColor = systemColor;
|
|
2807
3136
|
const inactiveColor = "#1a1a1a";
|
|
2808
3137
|
if (upRef.current) upRef.current.style.fill = directions.has("up") ? activeColor : inactiveColor;
|
|
@@ -2810,7 +3139,7 @@ var Dpad = React5__default.default.memo(function Dpad2({
|
|
|
2810
3139
|
if (leftRef.current) leftRef.current.style.fill = directions.has("left") ? activeColor : inactiveColor;
|
|
2811
3140
|
if (rightRef.current) rightRef.current.style.fill = directions.has("right") ? activeColor : inactiveColor;
|
|
2812
3141
|
}, [systemColor]);
|
|
2813
|
-
const updateDirections =
|
|
3142
|
+
const updateDirections = React2.useCallback((newDirections) => {
|
|
2814
3143
|
const prev = activeDirectionsRef.current;
|
|
2815
3144
|
prev.forEach((dir) => {
|
|
2816
3145
|
if (!newDirections.has(dir)) {
|
|
@@ -2830,7 +3159,7 @@ var Dpad = React5__default.default.memo(function Dpad2({
|
|
|
2830
3159
|
activeDirectionsRef.current = newDirections;
|
|
2831
3160
|
updateVisuals(newDirections);
|
|
2832
3161
|
}, [getKeyCode, updateVisuals]);
|
|
2833
|
-
const handleTouchStart =
|
|
3162
|
+
const handleTouchStart = React2.useCallback((e) => {
|
|
2834
3163
|
e.preventDefault();
|
|
2835
3164
|
e.stopPropagation();
|
|
2836
3165
|
const touch = e.touches[0];
|
|
@@ -2839,7 +3168,7 @@ var Dpad = React5__default.default.memo(function Dpad2({
|
|
|
2839
3168
|
if (!rect) return;
|
|
2840
3169
|
updateDirections(getDirectionsFromTouch(touch.clientX, touch.clientY, rect));
|
|
2841
3170
|
}, [getDirectionsFromTouch, updateDirections]);
|
|
2842
|
-
const handleTouchMove =
|
|
3171
|
+
const handleTouchMove = React2.useCallback((e) => {
|
|
2843
3172
|
e.preventDefault();
|
|
2844
3173
|
let touch = null;
|
|
2845
3174
|
for (let i = 0; i < e.touches.length; i++) {
|
|
@@ -2853,7 +3182,7 @@ var Dpad = React5__default.default.memo(function Dpad2({
|
|
|
2853
3182
|
if (!rect) return;
|
|
2854
3183
|
updateDirections(getDirectionsFromTouch(touch.clientX, touch.clientY, rect));
|
|
2855
3184
|
}, [getDirectionsFromTouch, updateDirections]);
|
|
2856
|
-
const handleTouchEnd =
|
|
3185
|
+
const handleTouchEnd = React2.useCallback((e) => {
|
|
2857
3186
|
e.preventDefault();
|
|
2858
3187
|
let touchEnded = true;
|
|
2859
3188
|
for (let i = 0; i < e.touches.length; i++) {
|
|
@@ -2872,7 +3201,7 @@ var Dpad = React5__default.default.memo(function Dpad2({
|
|
|
2872
3201
|
updateVisuals(/* @__PURE__ */ new Set());
|
|
2873
3202
|
}
|
|
2874
3203
|
}, [getKeyCode, updateVisuals]);
|
|
2875
|
-
|
|
3204
|
+
React2.useEffect(() => {
|
|
2876
3205
|
const dpad = dpadRef.current;
|
|
2877
3206
|
if (!dpad) return;
|
|
2878
3207
|
dpad.addEventListener("touchstart", handleTouchStart, { passive: false });
|
|
@@ -2930,9 +3259,9 @@ function adjustButtonPosition(config, context) {
|
|
|
2930
3259
|
}
|
|
2931
3260
|
var STORAGE_KEY = "virtual-button-positions";
|
|
2932
3261
|
function useButtonPositions() {
|
|
2933
|
-
const [landscapePositions, setLandscapePositions] =
|
|
2934
|
-
const [portraitPositions, setPortraitPositions] =
|
|
2935
|
-
|
|
3262
|
+
const [landscapePositions, setLandscapePositions] = React2.useState({});
|
|
3263
|
+
const [portraitPositions, setPortraitPositions] = React2.useState({});
|
|
3264
|
+
React2.useEffect(() => {
|
|
2936
3265
|
try {
|
|
2937
3266
|
const stored = localStorage.getItem(STORAGE_KEY);
|
|
2938
3267
|
if (stored) {
|
|
@@ -2948,7 +3277,7 @@ function useButtonPositions() {
|
|
|
2948
3277
|
console.error("Failed to load button positions:", e);
|
|
2949
3278
|
}
|
|
2950
3279
|
}, []);
|
|
2951
|
-
const savePosition =
|
|
3280
|
+
const savePosition = React2.useCallback((buttonType, x, y, isLandscape) => {
|
|
2952
3281
|
if (isLandscape) {
|
|
2953
3282
|
setLandscapePositions((prev) => {
|
|
2954
3283
|
const updated = { ...prev, [buttonType]: { x, y } };
|
|
@@ -2979,11 +3308,11 @@ function useButtonPositions() {
|
|
|
2979
3308
|
});
|
|
2980
3309
|
}
|
|
2981
3310
|
}, [landscapePositions, portraitPositions]);
|
|
2982
|
-
const getPosition =
|
|
3311
|
+
const getPosition = React2.useCallback((buttonType, isLandscape) => {
|
|
2983
3312
|
const positions = isLandscape ? landscapePositions : portraitPositions;
|
|
2984
3313
|
return positions[buttonType] || null;
|
|
2985
3314
|
}, [landscapePositions, portraitPositions]);
|
|
2986
|
-
const resetPositions =
|
|
3315
|
+
const resetPositions = React2.useCallback(() => {
|
|
2987
3316
|
setLandscapePositions({});
|
|
2988
3317
|
setPortraitPositions({});
|
|
2989
3318
|
try {
|
|
@@ -3008,9 +3337,9 @@ function VirtualController({
|
|
|
3008
3337
|
// Default retro green
|
|
3009
3338
|
}) {
|
|
3010
3339
|
const { isMobile, isLandscape, isPortrait } = useMobile();
|
|
3011
|
-
const [pressedButtons, setPressedButtons] =
|
|
3012
|
-
const [containerSize, setContainerSize] =
|
|
3013
|
-
const [isFullscreenState, setIsFullscreenState] =
|
|
3340
|
+
const [pressedButtons, setPressedButtons] = React2.useState(/* @__PURE__ */ new Set());
|
|
3341
|
+
const [containerSize, setContainerSize] = React2.useState({ width: 0, height: 0 });
|
|
3342
|
+
const [isFullscreenState, setIsFullscreenState] = React2.useState(false);
|
|
3014
3343
|
const { getPosition, savePosition } = useButtonPositions();
|
|
3015
3344
|
const layout = getLayoutForSystem(system);
|
|
3016
3345
|
const visibleButtons = layout.buttons.filter((btn) => {
|
|
@@ -3021,7 +3350,7 @@ function VirtualController({
|
|
|
3021
3350
|
});
|
|
3022
3351
|
const DPAD_TYPES = ["up", "down", "left", "right"];
|
|
3023
3352
|
const dpadButtons = visibleButtons.filter((btn) => DPAD_TYPES.includes(btn.type));
|
|
3024
|
-
|
|
3353
|
+
React2.useEffect(() => {
|
|
3025
3354
|
const updateSize = () => {
|
|
3026
3355
|
const { width, height } = getViewportSize();
|
|
3027
3356
|
setContainerSize({ width, height });
|
|
@@ -3064,14 +3393,14 @@ function VirtualController({
|
|
|
3064
3393
|
cleanupFullscreen();
|
|
3065
3394
|
};
|
|
3066
3395
|
}, [containerSize.height]);
|
|
3067
|
-
const getButtonKeyboardCode =
|
|
3396
|
+
const getButtonKeyboardCode = React2.useCallback(
|
|
3068
3397
|
(buttonType) => {
|
|
3069
3398
|
return getKeyboardCode(buttonType, controls);
|
|
3070
3399
|
},
|
|
3071
3400
|
[controls]
|
|
3072
3401
|
);
|
|
3073
3402
|
const SYSTEM_BUTTONS2 = ["start", "select"];
|
|
3074
|
-
const handlePress =
|
|
3403
|
+
const handlePress = React2.useCallback(
|
|
3075
3404
|
(buttonType) => {
|
|
3076
3405
|
const isSystemButton = SYSTEM_BUTTONS2.includes(buttonType);
|
|
3077
3406
|
if (!isSystemButton && !isRunning) {
|
|
@@ -3094,7 +3423,7 @@ function VirtualController({
|
|
|
3094
3423
|
},
|
|
3095
3424
|
[isRunning, getButtonKeyboardCode]
|
|
3096
3425
|
);
|
|
3097
|
-
const handlePressDown =
|
|
3426
|
+
const handlePressDown = React2.useCallback(
|
|
3098
3427
|
(buttonType) => {
|
|
3099
3428
|
if (!isRunning) return;
|
|
3100
3429
|
const isSystemButton = SYSTEM_BUTTONS2.includes(buttonType);
|
|
@@ -3111,7 +3440,7 @@ function VirtualController({
|
|
|
3111
3440
|
},
|
|
3112
3441
|
[isRunning, getButtonKeyboardCode]
|
|
3113
3442
|
);
|
|
3114
|
-
const handleRelease =
|
|
3443
|
+
const handleRelease = React2.useCallback(
|
|
3115
3444
|
(buttonType) => {
|
|
3116
3445
|
const isSystemButton = SYSTEM_BUTTONS2.includes(buttonType);
|
|
3117
3446
|
if (isSystemButton) return;
|
|
@@ -3127,7 +3456,7 @@ function VirtualController({
|
|
|
3127
3456
|
},
|
|
3128
3457
|
[getButtonKeyboardCode]
|
|
3129
3458
|
);
|
|
3130
|
-
|
|
3459
|
+
React2.useEffect(() => {
|
|
3131
3460
|
if (!isRunning && pressedButtons.size > 0) {
|
|
3132
3461
|
pressedButtons.forEach((buttonType) => {
|
|
3133
3462
|
if (!SYSTEM_BUTTONS2.includes(buttonType)) {
|
|
@@ -3137,7 +3466,7 @@ function VirtualController({
|
|
|
3137
3466
|
setPressedButtons(/* @__PURE__ */ new Set());
|
|
3138
3467
|
}
|
|
3139
3468
|
}, [isRunning, pressedButtons, handleRelease]);
|
|
3140
|
-
const memoizedButtonElements =
|
|
3469
|
+
const memoizedButtonElements = React2.useMemo(() => {
|
|
3141
3470
|
const width = containerSize.width || (typeof window !== "undefined" ? window.innerWidth : 0);
|
|
3142
3471
|
const height = containerSize.height || (typeof window !== "undefined" ? window.innerHeight : 0);
|
|
3143
3472
|
const context = {
|
|
@@ -3269,9 +3598,10 @@ function GameOverlay({
|
|
|
3269
3598
|
isLoadingSave,
|
|
3270
3599
|
onSelectBios
|
|
3271
3600
|
}) {
|
|
3601
|
+
const t = useKoinTranslation();
|
|
3272
3602
|
const isLoading = status === "loading" || status === "ready" && isLoadingSave;
|
|
3273
3603
|
if (isLoading) {
|
|
3274
|
-
const message = status === "loading" ? { title:
|
|
3604
|
+
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 || "")) };
|
|
3275
3605
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex flex-col items-center justify-center bg-black z-20", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-4", children: [
|
|
3276
3606
|
/* @__PURE__ */ jsxRuntime.jsx(LoadingSpinner, { color: systemColor, size: "lg" }),
|
|
3277
3607
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
|
|
@@ -3309,12 +3639,11 @@ function GameOverlay({
|
|
|
3309
3639
|
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Play, { className: "w-8 h-8 ml-1", style: { color: systemColor }, fill: systemColor })
|
|
3310
3640
|
}
|
|
3311
3641
|
),
|
|
3312
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono text-lg uppercase tracking-wider", style: { color: systemColor }, children:
|
|
3642
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono text-lg uppercase tracking-wider", style: { color: systemColor }, children: t.overlay.play }),
|
|
3313
3643
|
hasPendingSave && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-gray-400 text-xs flex items-center gap-1", children: [
|
|
3314
3644
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Save, { size: 12 }),
|
|
3315
|
-
"
|
|
3316
|
-
pendingSlot
|
|
3317
|
-
" ready"
|
|
3645
|
+
" ",
|
|
3646
|
+
t.overlay.slotReady.replace("{{num}}", String(pendingSlot))
|
|
3318
3647
|
] })
|
|
3319
3648
|
]
|
|
3320
3649
|
}
|
|
@@ -3326,7 +3655,7 @@ function GameOverlay({
|
|
|
3326
3655
|
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",
|
|
3327
3656
|
children: [
|
|
3328
3657
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-2 h-2 rounded-full", style: { backgroundColor: systemColor } }),
|
|
3329
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono uppercase tracking-wider text-xs", children:
|
|
3658
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono uppercase tracking-wider text-xs", children: t.overlay.systemFirmware })
|
|
3330
3659
|
]
|
|
3331
3660
|
}
|
|
3332
3661
|
)
|
|
@@ -3336,8 +3665,8 @@ function GameOverlay({
|
|
|
3336
3665
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex flex-col items-center justify-center bg-black/90 z-20", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-4", children: [
|
|
3337
3666
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { className: "w-12 h-12 text-red-500" }),
|
|
3338
3667
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
|
|
3339
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-400 font-mono uppercase tracking-widest text-sm", children:
|
|
3340
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-500 text-xs mt-1 max-w-xs", children: error ||
|
|
3668
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-400 font-mono uppercase tracking-widest text-sm", children: t.overlay.systemError }),
|
|
3669
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-500 text-xs mt-1 max-w-xs", children: error || t.overlay.failedInit })
|
|
3341
3670
|
] }),
|
|
3342
3671
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3343
3672
|
"button",
|
|
@@ -3345,7 +3674,7 @@ function GameOverlay({
|
|
|
3345
3674
|
onClick: onStart,
|
|
3346
3675
|
className: "mt-2 px-4 py-2 text-sm font-bold rounded-lg transition-colors",
|
|
3347
3676
|
style: { backgroundColor: systemColor, color: "#000" },
|
|
3348
|
-
children:
|
|
3677
|
+
children: t.overlay.retry
|
|
3349
3678
|
}
|
|
3350
3679
|
)
|
|
3351
3680
|
] }) });
|
|
@@ -3363,7 +3692,7 @@ function GameOverlay({
|
|
|
3363
3692
|
] })
|
|
3364
3693
|
}
|
|
3365
3694
|
),
|
|
3366
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-mono uppercase tracking-wider text-sm", style: { color: systemColor }, children:
|
|
3695
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-mono uppercase tracking-wider text-sm", style: { color: systemColor }, children: t.overlay.paused })
|
|
3367
3696
|
] }) });
|
|
3368
3697
|
}
|
|
3369
3698
|
return null;
|
|
@@ -3408,7 +3737,7 @@ function saveMuteState(muted) {
|
|
|
3408
3737
|
if (typeof window === "undefined") return;
|
|
3409
3738
|
localStorage.setItem(MUTE_KEY, muted.toString());
|
|
3410
3739
|
}
|
|
3411
|
-
var GameCanvas =
|
|
3740
|
+
var GameCanvas = React2.memo(function GameCanvas2({
|
|
3412
3741
|
status,
|
|
3413
3742
|
system,
|
|
3414
3743
|
error,
|
|
@@ -3419,8 +3748,8 @@ var GameCanvas = React5.memo(function GameCanvas2({
|
|
|
3419
3748
|
canvasRef,
|
|
3420
3749
|
onSelectBios
|
|
3421
3750
|
}) {
|
|
3422
|
-
const canvasContainerRef =
|
|
3423
|
-
|
|
3751
|
+
const canvasContainerRef = React2.useRef(null);
|
|
3752
|
+
React2.useEffect(() => {
|
|
3424
3753
|
const container = canvasContainerRef.current;
|
|
3425
3754
|
if (!container || canvasRef.current) return;
|
|
3426
3755
|
const canvas = document.createElement("canvas");
|
|
@@ -3438,7 +3767,7 @@ var GameCanvas = React5.memo(function GameCanvas2({
|
|
|
3438
3767
|
canvasRef.current = null;
|
|
3439
3768
|
};
|
|
3440
3769
|
}, [canvasRef]);
|
|
3441
|
-
|
|
3770
|
+
React2.useEffect(() => {
|
|
3442
3771
|
if (status !== "ready" && status !== "running" && status !== "paused") return;
|
|
3443
3772
|
return setupCanvasResize(canvasContainerRef);
|
|
3444
3773
|
}, [status]);
|
|
@@ -3472,23 +3801,24 @@ function ControlMapper({
|
|
|
3472
3801
|
onClose,
|
|
3473
3802
|
system
|
|
3474
3803
|
}) {
|
|
3475
|
-
const
|
|
3476
|
-
const [
|
|
3477
|
-
const
|
|
3804
|
+
const t = useKoinTranslation();
|
|
3805
|
+
const [localControls, setLocalControls] = React2.useState(controls);
|
|
3806
|
+
const [listeningFor, setListeningFor] = React2.useState(null);
|
|
3807
|
+
const activeButtons = React2.useMemo(() => {
|
|
3478
3808
|
return getConsoleButtons(system || "SNES");
|
|
3479
3809
|
}, [system]);
|
|
3480
|
-
const controlGroups =
|
|
3810
|
+
const controlGroups = React2.useMemo(() => {
|
|
3481
3811
|
return getFilteredGroups(activeButtons);
|
|
3482
3812
|
}, [activeButtons]);
|
|
3483
|
-
const defaultControls =
|
|
3813
|
+
const defaultControls = React2.useMemo(() => {
|
|
3484
3814
|
return getConsoleKeyboardDefaults(system || "SNES");
|
|
3485
3815
|
}, [system]);
|
|
3486
|
-
|
|
3816
|
+
React2.useEffect(() => {
|
|
3487
3817
|
if (isOpen) {
|
|
3488
3818
|
setLocalControls(controls);
|
|
3489
3819
|
}
|
|
3490
3820
|
}, [isOpen, controls]);
|
|
3491
|
-
|
|
3821
|
+
React2.useEffect(() => {
|
|
3492
3822
|
if (!isOpen) {
|
|
3493
3823
|
setListeningFor(null);
|
|
3494
3824
|
return;
|
|
@@ -3531,8 +3861,8 @@ function ControlMapper({
|
|
|
3531
3861
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
3532
3862
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Gamepad2, { className: "text-retro-primary", size: 24 }),
|
|
3533
3863
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
3534
|
-
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-bold text-white", children:
|
|
3535
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-400", children:
|
|
3864
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-bold text-white", children: t.modals.controls.title }),
|
|
3865
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-400", children: t.modals.controls.description })
|
|
3536
3866
|
] })
|
|
3537
3867
|
] }),
|
|
3538
3868
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -3563,7 +3893,7 @@ function ControlMapper({
|
|
|
3563
3893
|
px-2 py-1 rounded text-xs font-mono
|
|
3564
3894
|
${listeningFor === btn ? "bg-retro-primary/30 text-retro-primary animate-pulse" : "bg-black/50 text-white"}
|
|
3565
3895
|
`,
|
|
3566
|
-
children: listeningFor === btn ?
|
|
3896
|
+
children: listeningFor === btn ? t.modals.controls.pressKey : formatKeyCode(localControls[btn] || "")
|
|
3567
3897
|
}
|
|
3568
3898
|
)
|
|
3569
3899
|
]
|
|
@@ -3579,7 +3909,7 @@ function ControlMapper({
|
|
|
3579
3909
|
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",
|
|
3580
3910
|
children: [
|
|
3581
3911
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.RotateCcw, { size: 16 }),
|
|
3582
|
-
|
|
3912
|
+
t.modals.controls.reset
|
|
3583
3913
|
]
|
|
3584
3914
|
}
|
|
3585
3915
|
),
|
|
@@ -3590,7 +3920,7 @@ function ControlMapper({
|
|
|
3590
3920
|
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",
|
|
3591
3921
|
children: [
|
|
3592
3922
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { size: 16 }),
|
|
3593
|
-
|
|
3923
|
+
t.modals.controls.save
|
|
3594
3924
|
]
|
|
3595
3925
|
}
|
|
3596
3926
|
)
|
|
@@ -3638,17 +3968,17 @@ function toGamepadInfo(gamepad) {
|
|
|
3638
3968
|
}
|
|
3639
3969
|
function useGamepad(options) {
|
|
3640
3970
|
const { onConnect, onDisconnect } = options || {};
|
|
3641
|
-
const [gamepads, setGamepads] =
|
|
3642
|
-
const rafRef =
|
|
3643
|
-
const lastStateRef =
|
|
3644
|
-
const prevCountRef =
|
|
3645
|
-
const onConnectRef =
|
|
3646
|
-
const onDisconnectRef =
|
|
3647
|
-
|
|
3971
|
+
const [gamepads, setGamepads] = React2.useState([]);
|
|
3972
|
+
const rafRef = React2.useRef(null);
|
|
3973
|
+
const lastStateRef = React2.useRef("");
|
|
3974
|
+
const prevCountRef = React2.useRef(0);
|
|
3975
|
+
const onConnectRef = React2.useRef(onConnect);
|
|
3976
|
+
const onDisconnectRef = React2.useRef(onDisconnect);
|
|
3977
|
+
React2.useEffect(() => {
|
|
3648
3978
|
onConnectRef.current = onConnect;
|
|
3649
3979
|
onDisconnectRef.current = onDisconnect;
|
|
3650
3980
|
}, [onConnect, onDisconnect]);
|
|
3651
|
-
const getGamepads =
|
|
3981
|
+
const getGamepads = React2.useCallback(() => {
|
|
3652
3982
|
if (typeof navigator === "undefined" || typeof navigator.getGamepads !== "function") {
|
|
3653
3983
|
return [];
|
|
3654
3984
|
}
|
|
@@ -3662,14 +3992,14 @@ function useGamepad(options) {
|
|
|
3662
3992
|
}
|
|
3663
3993
|
return connected;
|
|
3664
3994
|
}, []);
|
|
3665
|
-
const getRawGamepad =
|
|
3995
|
+
const getRawGamepad = React2.useCallback((index) => {
|
|
3666
3996
|
const rawGamepads = navigator.getGamepads?.() ?? [];
|
|
3667
3997
|
return rawGamepads[index] ?? null;
|
|
3668
3998
|
}, []);
|
|
3669
|
-
const refresh =
|
|
3999
|
+
const refresh = React2.useCallback(() => {
|
|
3670
4000
|
setGamepads(getGamepads());
|
|
3671
4001
|
}, [getGamepads]);
|
|
3672
|
-
|
|
4002
|
+
React2.useEffect(() => {
|
|
3673
4003
|
if (typeof window === "undefined" || typeof navigator === "undefined") {
|
|
3674
4004
|
return;
|
|
3675
4005
|
}
|
|
@@ -3770,11 +4100,12 @@ function GamepadMapper({
|
|
|
3770
4100
|
onSave,
|
|
3771
4101
|
systemColor = "#00FF41"
|
|
3772
4102
|
}) {
|
|
3773
|
-
const
|
|
3774
|
-
const [
|
|
3775
|
-
const [
|
|
3776
|
-
const
|
|
3777
|
-
|
|
4103
|
+
const t = useKoinTranslation();
|
|
4104
|
+
const [selectedPlayer, setSelectedPlayer] = React2.useState(1);
|
|
4105
|
+
const [bindings, setBindings] = React2.useState({});
|
|
4106
|
+
const [listeningFor, setListeningFor] = React2.useState(null);
|
|
4107
|
+
const rafRef = React2.useRef(null);
|
|
4108
|
+
React2.useEffect(() => {
|
|
3778
4109
|
if (isOpen) {
|
|
3779
4110
|
const loadedBindings = {};
|
|
3780
4111
|
for (let i = 1; i <= 4; i++) {
|
|
@@ -3786,7 +4117,7 @@ function GamepadMapper({
|
|
|
3786
4117
|
}
|
|
3787
4118
|
}
|
|
3788
4119
|
}, [isOpen, gamepads]);
|
|
3789
|
-
|
|
4120
|
+
React2.useEffect(() => {
|
|
3790
4121
|
if (!isOpen || !listeningFor) {
|
|
3791
4122
|
if (rafRef.current) {
|
|
3792
4123
|
cancelAnimationFrame(rafRef.current);
|
|
@@ -3821,7 +4152,7 @@ function GamepadMapper({
|
|
|
3821
4152
|
}
|
|
3822
4153
|
};
|
|
3823
4154
|
}, [isOpen, listeningFor, selectedPlayer]);
|
|
3824
|
-
|
|
4155
|
+
React2.useEffect(() => {
|
|
3825
4156
|
if (!isOpen) return;
|
|
3826
4157
|
const handleKeyDown = (e) => {
|
|
3827
4158
|
if (e.code === "Escape") {
|
|
@@ -3866,8 +4197,8 @@ function GamepadMapper({
|
|
|
3866
4197
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
3867
4198
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Joystick, { className: "text-retro-primary", size: 24, style: { color: systemColor } }),
|
|
3868
4199
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
3869
|
-
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-bold text-white", children:
|
|
3870
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-400", children: gamepads.length > 0 ?
|
|
4200
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-bold text-white", children: t.modals.gamepad.title }),
|
|
4201
|
+
/* @__PURE__ */ jsxRuntime.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 })
|
|
3871
4202
|
] })
|
|
3872
4203
|
] }),
|
|
3873
4204
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -3881,7 +4212,7 @@ function GamepadMapper({
|
|
|
3881
4212
|
] }),
|
|
3882
4213
|
gamepads.length > 1 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-3 border-b border-white/10 bg-black/30", children: [
|
|
3883
4214
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
3884
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-gray-400 font-medium", children:
|
|
4215
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-gray-400 font-medium", children: t.modals.gamepad.player }),
|
|
3885
4216
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-1", children: gamepads.map((gp) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3886
4217
|
"button",
|
|
3887
4218
|
{
|
|
@@ -3914,10 +4245,10 @@ function GamepadMapper({
|
|
|
3914
4245
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Joystick, { size: 56, className: "text-gray-600 animate-pulse" }),
|
|
3915
4246
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 -m-2 rounded-full border-2 border-dashed border-gray-600 animate-spin", style: { animationDuration: "8s" } })
|
|
3916
4247
|
] }),
|
|
3917
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-300 font-medium mb-2", children:
|
|
3918
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 mb-4", children:
|
|
4248
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-300 font-medium mb-2", children: t.modals.gamepad.noController }),
|
|
4249
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 mb-4", children: t.modals.gamepad.pressAny }),
|
|
3919
4250
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-white/5 border border-white/10", children: [
|
|
3920
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-gray-400", children:
|
|
4251
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-gray-400", children: t.modals.gamepad.waiting }),
|
|
3921
4252
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-1", children: [
|
|
3922
4253
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-1.5 h-1.5 rounded-full bg-gray-500 animate-bounce", style: { animationDelay: "0ms" } }),
|
|
3923
4254
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-1.5 h-1.5 rounded-full bg-gray-500 animate-bounce", style: { animationDelay: "150ms" } }),
|
|
@@ -3927,11 +4258,8 @@ function GamepadMapper({
|
|
|
3927
4258
|
] }),
|
|
3928
4259
|
gamepads.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 space-y-6 max-h-[400px] overflow-y-auto", children: [
|
|
3929
4260
|
listeningFor && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 rounded-lg bg-black/50 border border-retro-primary/50 text-center animate-pulse", style: { borderColor: `${systemColor}50` }, children: [
|
|
3930
|
-
/* @__PURE__ */ jsxRuntime.
|
|
3931
|
-
|
|
3932
|
-
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: BUTTON_LABELS[listeningFor] })
|
|
3933
|
-
] }),
|
|
3934
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-400", children: "Press Escape to cancel" })
|
|
4261
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-white mb-1", children: t.modals.gamepad.pressButton.replace("{{button}}", BUTTON_LABELS[listeningFor]) }),
|
|
4262
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-400", children: t.modals.gamepad.pressEsc })
|
|
3935
4263
|
] }),
|
|
3936
4264
|
BUTTON_GROUPS.map((group) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
3937
4265
|
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-xs font-bold text-gray-500 uppercase tracking-wider mb-2", children: group.label }),
|
|
@@ -3963,7 +4291,7 @@ function GamepadMapper({
|
|
|
3963
4291
|
backgroundColor: `${systemColor}30`,
|
|
3964
4292
|
color: systemColor
|
|
3965
4293
|
} : {},
|
|
3966
|
-
children: listeningFor === btn ?
|
|
4294
|
+
children: listeningFor === btn ? t.controls.press : formatGamepadButton(currentBindings[btn])
|
|
3967
4295
|
}
|
|
3968
4296
|
)
|
|
3969
4297
|
]
|
|
@@ -3981,7 +4309,7 @@ function GamepadMapper({
|
|
|
3981
4309
|
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",
|
|
3982
4310
|
children: [
|
|
3983
4311
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.RotateCcw, { size: 16 }),
|
|
3984
|
-
|
|
4312
|
+
t.modals.gamepad.reset
|
|
3985
4313
|
]
|
|
3986
4314
|
}
|
|
3987
4315
|
),
|
|
@@ -3994,7 +4322,7 @@ function GamepadMapper({
|
|
|
3994
4322
|
style: { backgroundColor: systemColor },
|
|
3995
4323
|
children: [
|
|
3996
4324
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { size: 16 }),
|
|
3997
|
-
|
|
4325
|
+
t.modals.gamepad.save
|
|
3998
4326
|
]
|
|
3999
4327
|
}
|
|
4000
4328
|
)
|
|
@@ -4009,7 +4337,8 @@ function CheatModal({
|
|
|
4009
4337
|
onToggle,
|
|
4010
4338
|
onClose
|
|
4011
4339
|
}) {
|
|
4012
|
-
const
|
|
4340
|
+
const t = useKoinTranslation();
|
|
4341
|
+
const [copiedId, setCopiedId] = React2__default.default.useState(null);
|
|
4013
4342
|
if (!isOpen) return null;
|
|
4014
4343
|
const handleCopy = async (code, id) => {
|
|
4015
4344
|
await navigator.clipboard.writeText(code);
|
|
@@ -4029,13 +4358,8 @@ function CheatModal({
|
|
|
4029
4358
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
4030
4359
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Code, { className: "text-purple-400", size: 24 }),
|
|
4031
4360
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
4032
|
-
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-bold text-white", children:
|
|
4033
|
-
/* @__PURE__ */ jsxRuntime.
|
|
4034
|
-
cheats.length,
|
|
4035
|
-
" cheat",
|
|
4036
|
-
cheats.length !== 1 ? "s" : "",
|
|
4037
|
-
" available"
|
|
4038
|
-
] })
|
|
4361
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-bold text-white", children: t.modals.cheats.title }),
|
|
4362
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-400", children: t.modals.cheats.available.replace("{{count}}", cheats.length.toString()) })
|
|
4039
4363
|
] })
|
|
4040
4364
|
] }),
|
|
4041
4365
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -4049,8 +4373,8 @@ function CheatModal({
|
|
|
4049
4373
|
] }),
|
|
4050
4374
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 space-y-2 max-h-[400px] overflow-y-auto", children: cheats.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center py-12 text-gray-500", children: [
|
|
4051
4375
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Code, { size: 48, className: "mx-auto mb-3 opacity-50" }),
|
|
4052
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium", children:
|
|
4053
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm mt-1", children:
|
|
4376
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium", children: t.modals.cheats.emptyTitle }),
|
|
4377
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm mt-1", children: t.modals.cheats.emptyDesc })
|
|
4054
4378
|
] }) : cheats.map((cheat) => {
|
|
4055
4379
|
const isActive = activeCheats.has(cheat.id);
|
|
4056
4380
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -4084,7 +4408,7 @@ function CheatModal({
|
|
|
4084
4408
|
handleCopy(cheat.code, cheat.id);
|
|
4085
4409
|
},
|
|
4086
4410
|
className: "p-1.5 rounded hover:bg-white/10 text-gray-500 hover:text-white transition-colors",
|
|
4087
|
-
title:
|
|
4411
|
+
title: t.modals.cheats.copy,
|
|
4088
4412
|
children: copiedId === cheat.id ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { size: 14, className: "text-green-400" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Copy, { size: 14 })
|
|
4089
4413
|
}
|
|
4090
4414
|
)
|
|
@@ -4095,7 +4419,7 @@ function CheatModal({
|
|
|
4095
4419
|
cheat.id
|
|
4096
4420
|
);
|
|
4097
4421
|
}) }),
|
|
4098
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-3 bg-black/30 border-t border-white/10", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 text-center", children: activeCheats.size > 0 ?
|
|
4422
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-3 bg-black/30 border-t border-white/10", children: /* @__PURE__ */ jsxRuntime.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 }) })
|
|
4099
4423
|
] })
|
|
4100
4424
|
] });
|
|
4101
4425
|
}
|
|
@@ -4108,18 +4432,22 @@ function formatBytes(bytes) {
|
|
|
4108
4432
|
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
|
|
4109
4433
|
}
|
|
4110
4434
|
function formatTimestamp(timestamp) {
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4435
|
+
try {
|
|
4436
|
+
const date = new Date(timestamp);
|
|
4437
|
+
const now = /* @__PURE__ */ new Date();
|
|
4438
|
+
const diff = now.getTime() - date.getTime();
|
|
4439
|
+
if (diff < 6e4) return "Just now";
|
|
4440
|
+
if (diff < 36e5) return `${Math.floor(diff / 6e4)}m ago`;
|
|
4441
|
+
if (diff < 864e5) return `${Math.floor(diff / 36e5)}h ago`;
|
|
4442
|
+
return date.toLocaleDateString("en-US", {
|
|
4443
|
+
month: "short",
|
|
4444
|
+
day: "numeric",
|
|
4445
|
+
hour: "2-digit",
|
|
4446
|
+
minute: "2-digit"
|
|
4447
|
+
});
|
|
4448
|
+
} catch {
|
|
4449
|
+
return "Unknown";
|
|
4450
|
+
}
|
|
4123
4451
|
}
|
|
4124
4452
|
function SaveSlotModal({
|
|
4125
4453
|
isOpen,
|
|
@@ -4133,6 +4461,7 @@ function SaveSlotModal({
|
|
|
4133
4461
|
maxSlots = 5,
|
|
4134
4462
|
onUpgrade
|
|
4135
4463
|
}) {
|
|
4464
|
+
const t = useKoinTranslation();
|
|
4136
4465
|
if (!isOpen) return null;
|
|
4137
4466
|
const isSaveMode = mode === "save";
|
|
4138
4467
|
const allSlots = [1, 2, 3, 4, 5];
|
|
@@ -4161,8 +4490,8 @@ function SaveSlotModal({
|
|
|
4161
4490
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
4162
4491
|
isSaveMode ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Save, { className: "text-retro-primary", size: 24 }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "text-retro-primary", size: 24 }),
|
|
4163
4492
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
4164
|
-
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-bold text-white", children: isSaveMode ?
|
|
4165
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-400", children: isSaveMode ?
|
|
4493
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-bold text-white", children: isSaveMode ? t.modals.saveSlots.saveTitle : t.modals.saveSlots.loadTitle }),
|
|
4494
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-400", children: isSaveMode ? t.modals.saveSlots.subtitleSave : t.modals.saveSlots.subtitleLoad })
|
|
4166
4495
|
] })
|
|
4167
4496
|
] }),
|
|
4168
4497
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -4176,7 +4505,7 @@ function SaveSlotModal({
|
|
|
4176
4505
|
] }),
|
|
4177
4506
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 space-y-2 max-h-[400px] overflow-y-auto", children: isLoading ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-gray-400", children: [
|
|
4178
4507
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "w-8 h-8 animate-spin mb-3" }),
|
|
4179
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm", children:
|
|
4508
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm", children: t.modals.saveSlots.loading })
|
|
4180
4509
|
] }) : displaySlots.map((slotNum) => {
|
|
4181
4510
|
const slotData = getSlotData(slotNum);
|
|
4182
4511
|
const isEmpty = !slotData;
|
|
@@ -4193,14 +4522,10 @@ function SaveSlotModal({
|
|
|
4193
4522
|
children: [
|
|
4194
4523
|
/* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx(lucideReact.Lock, { size: 20 }) }),
|
|
4195
4524
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
4196
|
-
/* @__PURE__ */ jsxRuntime.
|
|
4197
|
-
"Slot ",
|
|
4198
|
-
slotNum,
|
|
4199
|
-
" Locked"
|
|
4200
|
-
] }),
|
|
4525
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium text-gray-400 group-hover:text-white transition-colors", children: t.modals.saveSlots.locked.replace("{{num}}", slotNum.toString()) }),
|
|
4201
4526
|
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-gray-500 group-hover:text-retro-primary transition-colors flex items-center gap-1", children: [
|
|
4202
4527
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Zap, { size: 10 }),
|
|
4203
|
-
|
|
4528
|
+
t.modals.saveSlots.upgrade
|
|
4204
4529
|
] })
|
|
4205
4530
|
] })
|
|
4206
4531
|
]
|
|
@@ -4224,8 +4549,8 @@ function SaveSlotModal({
|
|
|
4224
4549
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-w-0", children: isAutoSaveSlot ? (
|
|
4225
4550
|
// Auto-save slot - special display
|
|
4226
4551
|
isEmpty ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-retro-primary/70", children: [
|
|
4227
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium", children:
|
|
4228
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-retro-primary/50", children:
|
|
4552
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium", children: t.modals.saveSlots.autoSave }),
|
|
4553
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-retro-primary/50", children: t.modals.saveSlots.autoSaveDesc })
|
|
4229
4554
|
] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
4230
4555
|
slotData.screenshot && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0 w-16 h-12 rounded border border-retro-primary/30 overflow-hidden bg-black", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4231
4556
|
"img",
|
|
@@ -4239,7 +4564,7 @@ function SaveSlotModal({
|
|
|
4239
4564
|
}
|
|
4240
4565
|
) }),
|
|
4241
4566
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
4242
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium text-retro-primary truncate", children:
|
|
4567
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium text-retro-primary truncate", children: t.modals.saveSlots.autoSave }),
|
|
4243
4568
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 text-xs text-retro-primary/60 mt-1", children: [
|
|
4244
4569
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1", children: [
|
|
4245
4570
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { size: 12 }),
|
|
@@ -4253,8 +4578,8 @@ function SaveSlotModal({
|
|
|
4253
4578
|
] })
|
|
4254
4579
|
] })
|
|
4255
4580
|
) : isEmpty ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-gray-500", children: [
|
|
4256
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium", children:
|
|
4257
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs", children:
|
|
4581
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium", children: t.modals.saveSlots.emptySlot }),
|
|
4582
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs", children: t.modals.saveSlots.noData })
|
|
4258
4583
|
] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
4259
4584
|
slotData.screenshot && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0 w-16 h-12 rounded border border-white/10 overflow-hidden bg-black", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4260
4585
|
"img",
|
|
@@ -4268,10 +4593,7 @@ function SaveSlotModal({
|
|
|
4268
4593
|
}
|
|
4269
4594
|
) }),
|
|
4270
4595
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
4271
|
-
/* @__PURE__ */ jsxRuntime.
|
|
4272
|
-
"Slot ",
|
|
4273
|
-
slotNum
|
|
4274
|
-
] }),
|
|
4596
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium text-white truncate", children: t.modals.saveSlots.slot.replace("{{num}}", slotNum.toString()) }),
|
|
4275
4597
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 text-xs text-gray-400 mt-1", children: [
|
|
4276
4598
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1", children: [
|
|
4277
4599
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { size: 12 }),
|
|
@@ -4301,7 +4623,7 @@ function SaveSlotModal({
|
|
|
4301
4623
|
slotNum
|
|
4302
4624
|
);
|
|
4303
4625
|
}) }),
|
|
4304
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-3 bg-black/30 border-t border-white/10", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 text-center", children: isSaveMode ?
|
|
4626
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-3 bg-black/30 border-t border-white/10", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 text-center", children: isSaveMode ? t.modals.saveSlots.footerSave : t.modals.saveSlots.footerLoad }) })
|
|
4305
4627
|
] })
|
|
4306
4628
|
] });
|
|
4307
4629
|
}
|
|
@@ -4313,6 +4635,7 @@ function BiosSelectionModal({
|
|
|
4313
4635
|
onSelectBios,
|
|
4314
4636
|
systemColor = "#00FF41"
|
|
4315
4637
|
}) {
|
|
4638
|
+
const t = useKoinTranslation();
|
|
4316
4639
|
if (!isOpen) return null;
|
|
4317
4640
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
|
|
4318
4641
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -4332,8 +4655,8 @@ function BiosSelectionModal({
|
|
|
4332
4655
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
4333
4656
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Cpu, { size: 24, style: { color: systemColor } }),
|
|
4334
4657
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
4335
|
-
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-bold text-white uppercase tracking-wide", children:
|
|
4336
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-400", children:
|
|
4658
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-bold text-white uppercase tracking-wide", children: t.modals.bios.title }),
|
|
4659
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-400", children: t.modals.bios.description })
|
|
4337
4660
|
] })
|
|
4338
4661
|
] }),
|
|
4339
4662
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -4349,8 +4672,9 @@ function BiosSelectionModal({
|
|
|
4349
4672
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-4 p-3 rounded bg-yellow-500/10 border border-yellow-500/20 flex items-start gap-3", children: [
|
|
4350
4673
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircle, { className: "shrink-0 text-yellow-500 mt-0.5", size: 16 }),
|
|
4351
4674
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-xs text-yellow-200/80", children: [
|
|
4352
|
-
/* @__PURE__ */ jsxRuntime.jsx("strong", { children:
|
|
4353
|
-
"
|
|
4675
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: t.modals.bios.warningTitle }),
|
|
4676
|
+
" ",
|
|
4677
|
+
t.modals.bios.warning
|
|
4354
4678
|
] })
|
|
4355
4679
|
] }),
|
|
4356
4680
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -4380,10 +4704,10 @@ function BiosSelectionModal({
|
|
|
4380
4704
|
),
|
|
4381
4705
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
4382
4706
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
4383
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: `font-mono font-bold truncate ${!currentBiosId ? "text-white" : "text-gray-300"}`, children:
|
|
4384
|
-
!currentBiosId && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-1.5 py-0.5 rounded text-[10px] font-bold bg-white/20 text-white uppercase tracking-wider", children:
|
|
4707
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: `font-mono font-bold truncate ${!currentBiosId ? "text-white" : "text-gray-300"}`, children: t.modals.bios.systemDefault }),
|
|
4708
|
+
!currentBiosId && /* @__PURE__ */ jsxRuntime.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 })
|
|
4385
4709
|
] }),
|
|
4386
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 truncate mt-0.5", children:
|
|
4710
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 truncate mt-0.5", children: t.modals.bios.defaultDesc })
|
|
4387
4711
|
] }),
|
|
4388
4712
|
!currentBiosId && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { size: 20, style: { color: systemColor } })
|
|
4389
4713
|
]
|
|
@@ -4391,8 +4715,8 @@ function BiosSelectionModal({
|
|
|
4391
4715
|
),
|
|
4392
4716
|
biosOptions.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center py-8 text-gray-500", children: [
|
|
4393
4717
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileCode, { size: 48, className: "mx-auto mb-3 opacity-30" }),
|
|
4394
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm", children:
|
|
4395
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] mt-1 text-gray-600", children:
|
|
4718
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm", children: t.modals.bios.emptyTitle }),
|
|
4719
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] mt-1 text-gray-600", children: t.modals.bios.emptyDesc })
|
|
4396
4720
|
] }) : biosOptions.map((bios) => {
|
|
4397
4721
|
const isSelected = bios.id === currentBiosId;
|
|
4398
4722
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -4423,7 +4747,7 @@ function BiosSelectionModal({
|
|
|
4423
4747
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
4424
4748
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
4425
4749
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: `font-mono font-bold truncate ${isSelected ? "text-white" : "text-gray-300"}`, children: bios.name }),
|
|
4426
|
-
isSelected && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-1.5 py-0.5 rounded text-[10px] font-bold bg-white/20 text-white uppercase tracking-wider", children:
|
|
4750
|
+
isSelected && /* @__PURE__ */ jsxRuntime.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 })
|
|
4427
4751
|
] }),
|
|
4428
4752
|
bios.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 truncate mt-0.5", children: bios.description })
|
|
4429
4753
|
] }),
|
|
@@ -4434,12 +4758,84 @@ function BiosSelectionModal({
|
|
|
4434
4758
|
);
|
|
4435
4759
|
})
|
|
4436
4760
|
] }),
|
|
4437
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-3 bg-black/30 border-t border-white/10 text-center", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] text-gray-500 uppercase tracking-widest", children:
|
|
4761
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-3 bg-black/30 border-t border-white/10 text-center", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] text-gray-500 uppercase tracking-widest", children: t.modals.bios.footer }) })
|
|
4438
4762
|
]
|
|
4439
4763
|
}
|
|
4440
4764
|
)
|
|
4441
4765
|
] });
|
|
4442
4766
|
}
|
|
4767
|
+
function SettingsModal({
|
|
4768
|
+
isOpen,
|
|
4769
|
+
onClose,
|
|
4770
|
+
currentLanguage,
|
|
4771
|
+
onLanguageChange,
|
|
4772
|
+
systemColor = "#00FF41"
|
|
4773
|
+
}) {
|
|
4774
|
+
const t = useKoinTranslation();
|
|
4775
|
+
if (!isOpen) return null;
|
|
4776
|
+
const languages = [
|
|
4777
|
+
{ code: "en", name: "English" },
|
|
4778
|
+
{ code: "es", name: "Espa\xF1ol" },
|
|
4779
|
+
{ code: "fr", name: "Fran\xE7ais" }
|
|
4780
|
+
];
|
|
4781
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
|
|
4782
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4783
|
+
"div",
|
|
4784
|
+
{
|
|
4785
|
+
className: "absolute inset-0 bg-black/80 backdrop-blur-sm",
|
|
4786
|
+
onClick: onClose
|
|
4787
|
+
}
|
|
4788
|
+
),
|
|
4789
|
+
/* @__PURE__ */ jsxRuntime.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: [
|
|
4790
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4 border-b border-white/10 bg-black/50", children: [
|
|
4791
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
4792
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Settings, { className: "text-white", size: 20 }),
|
|
4793
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-bold text-white", children: t.settings.title })
|
|
4794
|
+
] }),
|
|
4795
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4796
|
+
"button",
|
|
4797
|
+
{
|
|
4798
|
+
onClick: onClose,
|
|
4799
|
+
className: "p-2 rounded-lg hover:bg-white/10 text-gray-400 hover:text-white transition-colors",
|
|
4800
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 20 })
|
|
4801
|
+
}
|
|
4802
|
+
)
|
|
4803
|
+
] }),
|
|
4804
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-6 space-y-6", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
|
|
4805
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-sm font-medium text-gray-400", children: [
|
|
4806
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Globe, { size: 16 }),
|
|
4807
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: t.settings.language })
|
|
4808
|
+
] }),
|
|
4809
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid gap-2", children: languages.map((lang) => {
|
|
4810
|
+
const isActive = currentLanguage === lang.code;
|
|
4811
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
4812
|
+
"button",
|
|
4813
|
+
{
|
|
4814
|
+
onClick: () => onLanguageChange(lang.code),
|
|
4815
|
+
className: `
|
|
4816
|
+
flex items-center justify-between px-4 py-3 rounded-lg border transition-all
|
|
4817
|
+
${isActive ? "bg-white/10 border-white/20 text-white" : "bg-black/20 border-transparent text-gray-400 hover:bg-white/5 hover:text-white"}
|
|
4818
|
+
`,
|
|
4819
|
+
children: [
|
|
4820
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: lang.name }),
|
|
4821
|
+
isActive && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { size: 16, style: { color: systemColor } })
|
|
4822
|
+
]
|
|
4823
|
+
},
|
|
4824
|
+
lang.code
|
|
4825
|
+
);
|
|
4826
|
+
}) })
|
|
4827
|
+
] }) }),
|
|
4828
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4 bg-black/30 border-t border-white/10 text-center", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4829
|
+
"button",
|
|
4830
|
+
{
|
|
4831
|
+
onClick: onClose,
|
|
4832
|
+
className: "text-sm text-gray-500 hover:text-white transition-colors",
|
|
4833
|
+
children: t.modals.shortcuts.pressEsc
|
|
4834
|
+
}
|
|
4835
|
+
) })
|
|
4836
|
+
] })
|
|
4837
|
+
] });
|
|
4838
|
+
}
|
|
4443
4839
|
function GameModals({
|
|
4444
4840
|
controlsModalOpen,
|
|
4445
4841
|
setControlsModalOpen,
|
|
@@ -4471,7 +4867,11 @@ function GameModals({
|
|
|
4471
4867
|
setBiosModalOpen,
|
|
4472
4868
|
availableBios,
|
|
4473
4869
|
currentBiosId,
|
|
4474
|
-
onSelectBios
|
|
4870
|
+
onSelectBios,
|
|
4871
|
+
settingsModalOpen,
|
|
4872
|
+
setSettingsModalOpen,
|
|
4873
|
+
currentLanguage,
|
|
4874
|
+
onLanguageChange
|
|
4475
4875
|
}) {
|
|
4476
4876
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
4477
4877
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -4547,6 +4947,19 @@ function GameModals({
|
|
|
4547
4947
|
},
|
|
4548
4948
|
systemColor
|
|
4549
4949
|
}
|
|
4950
|
+
),
|
|
4951
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4952
|
+
SettingsModal,
|
|
4953
|
+
{
|
|
4954
|
+
isOpen: settingsModalOpen,
|
|
4955
|
+
onClose: () => {
|
|
4956
|
+
setSettingsModalOpen(false);
|
|
4957
|
+
onResume();
|
|
4958
|
+
},
|
|
4959
|
+
currentLanguage,
|
|
4960
|
+
onLanguageChange,
|
|
4961
|
+
systemColor
|
|
4962
|
+
}
|
|
4550
4963
|
)
|
|
4551
4964
|
] });
|
|
4552
4965
|
}
|
|
@@ -4561,10 +4974,11 @@ function LoginForm({
|
|
|
4561
4974
|
error,
|
|
4562
4975
|
onSubmit
|
|
4563
4976
|
}) {
|
|
4977
|
+
const t = useKoinTranslation();
|
|
4564
4978
|
return /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit, className: "p-4 space-y-3", children: [
|
|
4565
4979
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center mb-4", children: [
|
|
4566
4980
|
/* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx(lucideReact.Trophy, { className: "text-yellow-400", size: 32 }) }),
|
|
4567
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-400 text-xs", children:
|
|
4981
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-400 text-xs", children: t.retroAchievements.connectAccount }),
|
|
4568
4982
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
4569
4983
|
"a",
|
|
4570
4984
|
{
|
|
@@ -4573,14 +4987,14 @@ function LoginForm({
|
|
|
4573
4987
|
rel: "noopener noreferrer",
|
|
4574
4988
|
className: "text-yellow-400 hover:text-yellow-300 text-xs inline-flex items-center gap-1 mt-1",
|
|
4575
4989
|
children: [
|
|
4576
|
-
|
|
4990
|
+
t.retroAchievements.createAccount,
|
|
4577
4991
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ExternalLink, { size: 10 })
|
|
4578
4992
|
]
|
|
4579
4993
|
}
|
|
4580
4994
|
)
|
|
4581
4995
|
] }),
|
|
4582
4996
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
4583
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-[10px] text-gray-500 mb-1 uppercase tracking-wider", children:
|
|
4997
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-[10px] text-gray-500 mb-1 uppercase tracking-wider", children: t.retroAchievements.username }),
|
|
4584
4998
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
|
|
4585
4999
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.User, { className: "absolute left-2.5 top-1/2 -translate-y-1/2 text-gray-500", size: 14 }),
|
|
4586
5000
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -4589,7 +5003,7 @@ function LoginForm({
|
|
|
4589
5003
|
type: "text",
|
|
4590
5004
|
value: username,
|
|
4591
5005
|
onChange: (e) => setUsername(e.target.value),
|
|
4592
|
-
placeholder:
|
|
5006
|
+
placeholder: t.retroAchievements.yourUsername,
|
|
4593
5007
|
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",
|
|
4594
5008
|
disabled: isLoading
|
|
4595
5009
|
}
|
|
@@ -4597,7 +5011,7 @@ function LoginForm({
|
|
|
4597
5011
|
] })
|
|
4598
5012
|
] }),
|
|
4599
5013
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
4600
|
-
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-[10px] text-gray-500 mb-1 uppercase tracking-wider", children:
|
|
5014
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-[10px] text-gray-500 mb-1 uppercase tracking-wider", children: t.retroAchievements.password }),
|
|
4601
5015
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
|
|
4602
5016
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Lock, { className: "absolute left-2.5 top-1/2 -translate-y-1/2 text-gray-500", size: 14 }),
|
|
4603
5017
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -4606,7 +5020,7 @@ function LoginForm({
|
|
|
4606
5020
|
type: showPassword ? "text" : "password",
|
|
4607
5021
|
value: password,
|
|
4608
5022
|
onChange: (e) => setPassword(e.target.value),
|
|
4609
|
-
placeholder:
|
|
5023
|
+
placeholder: t.retroAchievements.yourPassword,
|
|
4610
5024
|
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",
|
|
4611
5025
|
disabled: isLoading
|
|
4612
5026
|
}
|
|
@@ -4634,16 +5048,20 @@ function LoginForm({
|
|
|
4634
5048
|
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",
|
|
4635
5049
|
children: isLoading ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
4636
5050
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "animate-spin", size: 14 }),
|
|
4637
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children:
|
|
5051
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: t.retroAchievements.connecting })
|
|
4638
5052
|
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
4639
5053
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trophy, { size: 14 }),
|
|
4640
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children:
|
|
5054
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: t.retroAchievements.login })
|
|
4641
5055
|
] })
|
|
4642
5056
|
}
|
|
4643
5057
|
),
|
|
4644
5058
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-2 bg-blue-500/10 border border-blue-500/20 rounded-lg", children: /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-[10px] text-blue-300 leading-relaxed", children: [
|
|
4645
|
-
/* @__PURE__ */ jsxRuntime.
|
|
4646
|
-
|
|
5059
|
+
/* @__PURE__ */ jsxRuntime.jsxs("strong", { children: [
|
|
5060
|
+
"\u{1F512} ",
|
|
5061
|
+
t.retroAchievements.privacy
|
|
5062
|
+
] }),
|
|
5063
|
+
" ",
|
|
5064
|
+
t.retroAchievements.privacyText
|
|
4647
5065
|
] }) })
|
|
4648
5066
|
] });
|
|
4649
5067
|
}
|
|
@@ -4667,6 +5085,7 @@ function SettingsTab({
|
|
|
4667
5085
|
progress,
|
|
4668
5086
|
onLogout
|
|
4669
5087
|
}) {
|
|
5088
|
+
const t = useKoinTranslation();
|
|
4670
5089
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-3 space-y-3", children: [
|
|
4671
5090
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 p-3 bg-black/30 rounded-lg border border-yellow-500/20", children: [
|
|
4672
5091
|
/* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx(
|
|
@@ -4736,7 +5155,7 @@ function SettingsTab({
|
|
|
4736
5155
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
4737
5156
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Shield, { className: hardcoreEnabled ? "text-red-500" : "text-gray-500", size: 16 }),
|
|
4738
5157
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
4739
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-medium text-white", children:
|
|
5158
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-medium text-white", children: t.retroAchievements.hardcore }),
|
|
4740
5159
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] text-gray-500", children: "2x points, no savestates" })
|
|
4741
5160
|
] })
|
|
4742
5161
|
] }),
|
|
@@ -4754,11 +5173,11 @@ function SettingsTab({
|
|
|
4754
5173
|
}
|
|
4755
5174
|
)
|
|
4756
5175
|
] }),
|
|
4757
|
-
hardcoreEnabled && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 p-2 bg-red-500/10 border border-red-500/20 rounded text-[10px] text-red-300", children:
|
|
5176
|
+
hardcoreEnabled && /* @__PURE__ */ jsxRuntime.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 })
|
|
4758
5177
|
] }),
|
|
4759
5178
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-xs px-1", children: [
|
|
4760
5179
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckCircle, { className: "text-green-500", size: 12 }),
|
|
4761
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-green-400", children:
|
|
5180
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-green-400", children: t.retroAchievements.connectedStatus })
|
|
4762
5181
|
] }),
|
|
4763
5182
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
4764
5183
|
"button",
|
|
@@ -4767,7 +5186,7 @@ function SettingsTab({
|
|
|
4767
5186
|
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",
|
|
4768
5187
|
children: [
|
|
4769
5188
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.LogOut, { size: 14 }),
|
|
4770
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { children:
|
|
5189
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: t.retroAchievements.logout })
|
|
4771
5190
|
]
|
|
4772
5191
|
}
|
|
4773
5192
|
)
|
|
@@ -4784,18 +5203,19 @@ function AchievementsTab({
|
|
|
4784
5203
|
earnedPoints,
|
|
4785
5204
|
totalPoints
|
|
4786
5205
|
}) {
|
|
5206
|
+
const t = useKoinTranslation();
|
|
4787
5207
|
if (!currentGame) {
|
|
4788
5208
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-6 text-center", children: [
|
|
4789
5209
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-12 h-12 mx-auto mb-3 rounded-full bg-gray-800 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trophy, { className: "text-gray-600", size: 24 }) }),
|
|
4790
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-400", children:
|
|
4791
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 mt-1", children:
|
|
5210
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-400", children: t.retroAchievements.noGame }),
|
|
5211
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 mt-1", children: t.retroAchievements.loadGame })
|
|
4792
5212
|
] });
|
|
4793
5213
|
}
|
|
4794
5214
|
if (achievements.length === 0) {
|
|
4795
5215
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-6 text-center", children: [
|
|
4796
5216
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-12 h-12 mx-auto mb-3 rounded-full bg-gray-800 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trophy, { className: "text-gray-600", size: 24 }) }),
|
|
4797
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-400", children:
|
|
4798
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 mt-1", children:
|
|
5217
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-400", children: t.retroAchievements.noAchievements }),
|
|
5218
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 mt-1", children: t.retroAchievements.notSupported })
|
|
4799
5219
|
] });
|
|
4800
5220
|
}
|
|
4801
5221
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
@@ -4848,7 +5268,7 @@ function AchievementsTab({
|
|
|
4848
5268
|
{
|
|
4849
5269
|
onClick: () => setFilter(f),
|
|
4850
5270
|
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"}`,
|
|
4851
|
-
children:
|
|
5271
|
+
children: t.retroAchievements.filters[f]
|
|
4852
5272
|
},
|
|
4853
5273
|
f
|
|
4854
5274
|
)) })
|
|
@@ -4887,8 +5307,7 @@ function AchievementsTab({
|
|
|
4887
5307
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-2 text-[10px] text-gray-500 text-center border-t border-gray-800/50", children: [
|
|
4888
5308
|
progress,
|
|
4889
5309
|
"% complete \u2022 ",
|
|
4890
|
-
totalPoints - earnedPoints
|
|
4891
|
-
" pts remaining"
|
|
5310
|
+
t.retroAchievements.ptsRemaining.replace("{{count}}", (totalPoints - earnedPoints).toString())
|
|
4892
5311
|
] })
|
|
4893
5312
|
] });
|
|
4894
5313
|
}
|
|
@@ -4907,14 +5326,15 @@ function RASidebar({
|
|
|
4907
5326
|
achievements,
|
|
4908
5327
|
unlockedIds
|
|
4909
5328
|
}) {
|
|
5329
|
+
const t = useKoinTranslation();
|
|
4910
5330
|
const defaultTab = isLoggedIn && currentGame && achievements.length > 0 ? "achievements" : "settings";
|
|
4911
|
-
const [activeTab, setActiveTab] =
|
|
4912
|
-
const [filter, setFilter] =
|
|
4913
|
-
const [username, setUsername] =
|
|
4914
|
-
const [password, setPassword] =
|
|
4915
|
-
const [showPassword, setShowPassword] =
|
|
4916
|
-
const [localError, setLocalError] =
|
|
4917
|
-
|
|
5331
|
+
const [activeTab, setActiveTab] = React2.useState(defaultTab);
|
|
5332
|
+
const [filter, setFilter] = React2.useState("all");
|
|
5333
|
+
const [username, setUsername] = React2.useState("");
|
|
5334
|
+
const [password, setPassword] = React2.useState("");
|
|
5335
|
+
const [showPassword, setShowPassword] = React2.useState(false);
|
|
5336
|
+
const [localError, setLocalError] = React2.useState(null);
|
|
5337
|
+
React2.useEffect(() => {
|
|
4918
5338
|
if (!isOpen) {
|
|
4919
5339
|
setUsername("");
|
|
4920
5340
|
setPassword("");
|
|
@@ -4932,7 +5352,7 @@ function RASidebar({
|
|
|
4932
5352
|
e.preventDefault();
|
|
4933
5353
|
setLocalError(null);
|
|
4934
5354
|
if (!username.trim() || !password.trim()) {
|
|
4935
|
-
setLocalError(
|
|
5355
|
+
setLocalError(t.retroAchievements.usernameRequired);
|
|
4936
5356
|
return;
|
|
4937
5357
|
}
|
|
4938
5358
|
const success = await onLogin(username.trim(), password.trim());
|
|
@@ -4975,7 +5395,7 @@ function RASidebar({
|
|
|
4975
5395
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
4976
5396
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
4977
5397
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trophy, { className: "text-yellow-400", size: 18 }),
|
|
4978
|
-
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-heading text-sm text-white", children:
|
|
5398
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-heading text-sm text-white", children: t.retroAchievements.title })
|
|
4979
5399
|
] }),
|
|
4980
5400
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4981
5401
|
"button",
|
|
@@ -4994,7 +5414,7 @@ function RASidebar({
|
|
|
4994
5414
|
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"}`,
|
|
4995
5415
|
children: [
|
|
4996
5416
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.List, { size: 12 }),
|
|
4997
|
-
|
|
5417
|
+
t.retroAchievements.achievements
|
|
4998
5418
|
]
|
|
4999
5419
|
}
|
|
5000
5420
|
),
|
|
@@ -5005,7 +5425,7 @@ function RASidebar({
|
|
|
5005
5425
|
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"}`,
|
|
5006
5426
|
children: [
|
|
5007
5427
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Settings, { size: 12 }),
|
|
5008
|
-
|
|
5428
|
+
t.settings.title
|
|
5009
5429
|
]
|
|
5010
5430
|
}
|
|
5011
5431
|
)
|
|
@@ -5052,7 +5472,7 @@ function RASidebar({
|
|
|
5052
5472
|
}
|
|
5053
5473
|
) }),
|
|
5054
5474
|
/* @__PURE__ */ jsxRuntime.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: [
|
|
5055
|
-
|
|
5475
|
+
t.retroAchievements.poweredBy,
|
|
5056
5476
|
" ",
|
|
5057
5477
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5058
5478
|
"a",
|
|
@@ -5069,8 +5489,8 @@ function RASidebar({
|
|
|
5069
5489
|
] });
|
|
5070
5490
|
}
|
|
5071
5491
|
function useToast(defaultDuration = 3e3) {
|
|
5072
|
-
const [toasts, setToasts] =
|
|
5073
|
-
const showToast =
|
|
5492
|
+
const [toasts, setToasts] = React2.useState([]);
|
|
5493
|
+
const showToast = React2.useCallback((message, type = "info", options) => {
|
|
5074
5494
|
const id = crypto.randomUUID();
|
|
5075
5495
|
const duration = options?.duration ?? defaultDuration;
|
|
5076
5496
|
const newToast = {
|
|
@@ -5089,10 +5509,10 @@ function useToast(defaultDuration = 3e3) {
|
|
|
5089
5509
|
}, duration);
|
|
5090
5510
|
}
|
|
5091
5511
|
}, [defaultDuration]);
|
|
5092
|
-
const dismissToast =
|
|
5512
|
+
const dismissToast = React2.useCallback((id) => {
|
|
5093
5513
|
setToasts((prev) => prev.filter((t) => t.id !== id));
|
|
5094
5514
|
}, []);
|
|
5095
|
-
const clearToasts =
|
|
5515
|
+
const clearToasts = React2.useCallback(() => {
|
|
5096
5516
|
setToasts([]);
|
|
5097
5517
|
}, []);
|
|
5098
5518
|
return {
|
|
@@ -5106,21 +5526,21 @@ function useToast(defaultDuration = 3e3) {
|
|
|
5106
5526
|
// src/hooks/useGameUI.ts
|
|
5107
5527
|
function useGameUI() {
|
|
5108
5528
|
const { isMobile } = useMobile();
|
|
5109
|
-
const containerRef =
|
|
5110
|
-
const canvasRef =
|
|
5529
|
+
const containerRef = React2.useRef(null);
|
|
5530
|
+
const canvasRef = React2.useRef(null);
|
|
5111
5531
|
const { toasts, showToast, dismissToast } = useToast(3500);
|
|
5112
|
-
const [isFullscreen2, setIsFullscreen] =
|
|
5113
|
-
const [raSidebarOpen, setRaSidebarOpen] =
|
|
5114
|
-
const checkFullscreen =
|
|
5532
|
+
const [isFullscreen2, setIsFullscreen] = React2.useState(false);
|
|
5533
|
+
const [raSidebarOpen, setRaSidebarOpen] = React2.useState(false);
|
|
5534
|
+
const checkFullscreen = React2.useCallback(() => {
|
|
5115
5535
|
const fullscreen = isFullscreen();
|
|
5116
5536
|
setIsFullscreen(fullscreen);
|
|
5117
5537
|
return fullscreen;
|
|
5118
5538
|
}, []);
|
|
5119
|
-
|
|
5539
|
+
React2.useEffect(() => {
|
|
5120
5540
|
checkFullscreen();
|
|
5121
5541
|
return setupFullscreenListener(checkFullscreen);
|
|
5122
5542
|
}, [checkFullscreen]);
|
|
5123
|
-
const handleFullscreen =
|
|
5543
|
+
const handleFullscreen = React2.useCallback(async () => {
|
|
5124
5544
|
if (!containerRef.current) return;
|
|
5125
5545
|
try {
|
|
5126
5546
|
const nativeWorked = await toggleFullscreen(containerRef.current);
|
|
@@ -5712,15 +6132,15 @@ function useEmulatorCore({
|
|
|
5712
6132
|
onReady,
|
|
5713
6133
|
onError
|
|
5714
6134
|
}) {
|
|
5715
|
-
const [status, setStatus] =
|
|
5716
|
-
const [error, setError] =
|
|
5717
|
-
const [isPaused, setIsPaused] =
|
|
5718
|
-
const [speed, setSpeedState] =
|
|
5719
|
-
const [isFastForwardOn, setIsFastForwardOn] =
|
|
5720
|
-
const [isPerformanceMode, setIsPerformanceMode] =
|
|
5721
|
-
const nostalgistRef =
|
|
5722
|
-
const isStartingRef =
|
|
5723
|
-
const prepare =
|
|
6135
|
+
const [status, setStatus] = React2.useState("idle");
|
|
6136
|
+
const [error, setError] = React2.useState(null);
|
|
6137
|
+
const [isPaused, setIsPaused] = React2.useState(false);
|
|
6138
|
+
const [speed, setSpeedState] = React2.useState(1);
|
|
6139
|
+
const [isFastForwardOn, setIsFastForwardOn] = React2.useState(false);
|
|
6140
|
+
const [isPerformanceMode, setIsPerformanceMode] = React2.useState(false);
|
|
6141
|
+
const nostalgistRef = React2.useRef(null);
|
|
6142
|
+
const isStartingRef = React2.useRef(false);
|
|
6143
|
+
const prepare = React2.useCallback(async () => {
|
|
5724
6144
|
if (!romUrl || !system) {
|
|
5725
6145
|
console.warn("[Nostalgist] Missing romUrl or system");
|
|
5726
6146
|
return;
|
|
@@ -5869,7 +6289,7 @@ function useEmulatorCore({
|
|
|
5869
6289
|
onError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
5870
6290
|
}
|
|
5871
6291
|
}, [system, romUrl, coreOverride, biosUrl, initialState, getCanvasElement, keyboardControls, gamepadBindings, initialVolume, onError, retroAchievements]);
|
|
5872
|
-
const start =
|
|
6292
|
+
const start = React2.useCallback(async () => {
|
|
5873
6293
|
if (isStartingRef.current) {
|
|
5874
6294
|
console.log("[Nostalgist] Already starting");
|
|
5875
6295
|
return;
|
|
@@ -5902,7 +6322,7 @@ function useEmulatorCore({
|
|
|
5902
6322
|
isStartingRef.current = false;
|
|
5903
6323
|
}
|
|
5904
6324
|
}, [prepare, onReady, onError]);
|
|
5905
|
-
const stop =
|
|
6325
|
+
const stop = React2.useCallback(() => {
|
|
5906
6326
|
if (nostalgistRef.current) {
|
|
5907
6327
|
try {
|
|
5908
6328
|
nostalgistRef.current.exit();
|
|
@@ -5915,7 +6335,7 @@ function useEmulatorCore({
|
|
|
5915
6335
|
setIsPaused(false);
|
|
5916
6336
|
}
|
|
5917
6337
|
}, []);
|
|
5918
|
-
|
|
6338
|
+
React2.useEffect(() => {
|
|
5919
6339
|
return () => {
|
|
5920
6340
|
if (nostalgistRef.current) {
|
|
5921
6341
|
console.log("[Nostalgist] Cleaning up emulator on unmount");
|
|
@@ -5923,7 +6343,7 @@ function useEmulatorCore({
|
|
|
5923
6343
|
}
|
|
5924
6344
|
};
|
|
5925
6345
|
}, [stop]);
|
|
5926
|
-
const restart =
|
|
6346
|
+
const restart = React2.useCallback(async () => {
|
|
5927
6347
|
if (nostalgistRef.current) {
|
|
5928
6348
|
try {
|
|
5929
6349
|
nostalgistRef.current.restart();
|
|
@@ -5938,7 +6358,7 @@ function useEmulatorCore({
|
|
|
5938
6358
|
}
|
|
5939
6359
|
}
|
|
5940
6360
|
}, [stop, start]);
|
|
5941
|
-
const pause =
|
|
6361
|
+
const pause = React2.useCallback(() => {
|
|
5942
6362
|
if (nostalgistRef.current && !isPaused && status === "running") {
|
|
5943
6363
|
try {
|
|
5944
6364
|
nostalgistRef.current.pause();
|
|
@@ -5949,7 +6369,7 @@ function useEmulatorCore({
|
|
|
5949
6369
|
}
|
|
5950
6370
|
}
|
|
5951
6371
|
}, [isPaused, status]);
|
|
5952
|
-
const resume =
|
|
6372
|
+
const resume = React2.useCallback(() => {
|
|
5953
6373
|
if (nostalgistRef.current) {
|
|
5954
6374
|
try {
|
|
5955
6375
|
nostalgistRef.current.resume();
|
|
@@ -5962,14 +6382,14 @@ function useEmulatorCore({
|
|
|
5962
6382
|
}
|
|
5963
6383
|
}
|
|
5964
6384
|
}, [status]);
|
|
5965
|
-
const togglePause =
|
|
6385
|
+
const togglePause = React2.useCallback(() => {
|
|
5966
6386
|
if (isPaused) {
|
|
5967
6387
|
resume();
|
|
5968
6388
|
} else {
|
|
5969
6389
|
pause();
|
|
5970
6390
|
}
|
|
5971
6391
|
}, [isPaused, pause, resume]);
|
|
5972
|
-
const setSpeed =
|
|
6392
|
+
const setSpeed = React2.useCallback((multiplier) => {
|
|
5973
6393
|
if (!nostalgistRef.current) return;
|
|
5974
6394
|
try {
|
|
5975
6395
|
const nostalgist = nostalgistRef.current;
|
|
@@ -5989,7 +6409,7 @@ function useEmulatorCore({
|
|
|
5989
6409
|
console.error("[Nostalgist] Set speed error:", err);
|
|
5990
6410
|
}
|
|
5991
6411
|
}, [isFastForwardOn]);
|
|
5992
|
-
const screenshot =
|
|
6412
|
+
const screenshot = React2.useCallback(async () => {
|
|
5993
6413
|
if (!nostalgistRef.current) {
|
|
5994
6414
|
return null;
|
|
5995
6415
|
}
|
|
@@ -6019,7 +6439,7 @@ function useEmulatorCore({
|
|
|
6019
6439
|
return null;
|
|
6020
6440
|
}
|
|
6021
6441
|
}, []);
|
|
6022
|
-
const resize =
|
|
6442
|
+
const resize = React2.useCallback((size) => {
|
|
6023
6443
|
if (!nostalgistRef.current) {
|
|
6024
6444
|
console.warn("[Nostalgist] Cannot resize: emulator not ready");
|
|
6025
6445
|
return;
|
|
@@ -6030,7 +6450,7 @@ function useEmulatorCore({
|
|
|
6030
6450
|
console.error("[Nostalgist] Resize error:", err);
|
|
6031
6451
|
}
|
|
6032
6452
|
}, []);
|
|
6033
|
-
|
|
6453
|
+
React2.useCallback(() => {
|
|
6034
6454
|
return nostalgistRef.current;
|
|
6035
6455
|
}, []);
|
|
6036
6456
|
return {
|
|
@@ -6056,11 +6476,11 @@ function useEmulatorCore({
|
|
|
6056
6476
|
};
|
|
6057
6477
|
}
|
|
6058
6478
|
function useEmulatorAudio({ nostalgistRef, initialVolume = 100 }) {
|
|
6059
|
-
const [volume, setVolume] =
|
|
6060
|
-
const [isMuted, setIsMuted] =
|
|
6061
|
-
const gainNodeRef =
|
|
6062
|
-
const lastVolumeRef =
|
|
6063
|
-
|
|
6479
|
+
const [volume, setVolume] = React2.useState(initialVolume);
|
|
6480
|
+
const [isMuted, setIsMuted] = React2.useState(false);
|
|
6481
|
+
const gainNodeRef = React2.useRef(null);
|
|
6482
|
+
const lastVolumeRef = React2.useRef(initialVolume);
|
|
6483
|
+
React2.useEffect(() => {
|
|
6064
6484
|
const originalConnect = AudioNode.prototype.connect;
|
|
6065
6485
|
const contextGainMap = /* @__PURE__ */ new WeakMap();
|
|
6066
6486
|
AudioNode.prototype.connect = function(destination, output, input) {
|
|
@@ -6088,7 +6508,7 @@ function useEmulatorAudio({ nostalgistRef, initialVolume = 100 }) {
|
|
|
6088
6508
|
AudioNode.prototype.connect = originalConnect;
|
|
6089
6509
|
};
|
|
6090
6510
|
}, []);
|
|
6091
|
-
const setVolumeLevel =
|
|
6511
|
+
const setVolumeLevel = React2.useCallback((newVolume) => {
|
|
6092
6512
|
const clampedVolume = Math.max(0, Math.min(100, newVolume));
|
|
6093
6513
|
const volumeValue = clampedVolume / 100;
|
|
6094
6514
|
try {
|
|
@@ -6109,7 +6529,7 @@ function useEmulatorAudio({ nostalgistRef, initialVolume = 100 }) {
|
|
|
6109
6529
|
console.error("[Nostalgist] Volume change error:", err);
|
|
6110
6530
|
}
|
|
6111
6531
|
}, []);
|
|
6112
|
-
const toggleMute =
|
|
6532
|
+
const toggleMute = React2.useCallback(() => {
|
|
6113
6533
|
if (!nostalgistRef.current) return;
|
|
6114
6534
|
try {
|
|
6115
6535
|
const emscripten = nostalgistRef.current.getEmscripten();
|
|
@@ -6151,7 +6571,7 @@ function useEmulatorAudio({ nostalgistRef, initialVolume = 100 }) {
|
|
|
6151
6571
|
};
|
|
6152
6572
|
}
|
|
6153
6573
|
function useEmulatorInput({ nostalgistRef }) {
|
|
6154
|
-
const pressKey =
|
|
6574
|
+
const pressKey = React2.useCallback((key) => {
|
|
6155
6575
|
if (!nostalgistRef.current) return;
|
|
6156
6576
|
try {
|
|
6157
6577
|
nostalgistRef.current.press(key);
|
|
@@ -6166,10 +6586,10 @@ function useEmulatorInput({ nostalgistRef }) {
|
|
|
6166
6586
|
var MIN_SAVE_INTERVAL = 100;
|
|
6167
6587
|
var SAVE_TIMEOUT = 5e3;
|
|
6168
6588
|
function useSaveScheduler(nostalgistRef) {
|
|
6169
|
-
const queueRef =
|
|
6170
|
-
const savingRef =
|
|
6171
|
-
const lastSaveTimeRef =
|
|
6172
|
-
const processQueue =
|
|
6589
|
+
const queueRef = React2.useRef([]);
|
|
6590
|
+
const savingRef = React2.useRef(false);
|
|
6591
|
+
const lastSaveTimeRef = React2.useRef(0);
|
|
6592
|
+
const processQueue = React2.useCallback(async () => {
|
|
6173
6593
|
if (savingRef.current || queueRef.current.length === 0) {
|
|
6174
6594
|
return;
|
|
6175
6595
|
}
|
|
@@ -6223,7 +6643,7 @@ function useSaveScheduler(nostalgistRef) {
|
|
|
6223
6643
|
setTimeout(() => processQueue(), MIN_SAVE_INTERVAL);
|
|
6224
6644
|
}
|
|
6225
6645
|
}, [nostalgistRef]);
|
|
6226
|
-
const save =
|
|
6646
|
+
const save = React2.useCallback(() => {
|
|
6227
6647
|
return new Promise((resolve) => {
|
|
6228
6648
|
queueRef.current.push({
|
|
6229
6649
|
priority: "high",
|
|
@@ -6233,7 +6653,7 @@ function useSaveScheduler(nostalgistRef) {
|
|
|
6233
6653
|
processQueue();
|
|
6234
6654
|
});
|
|
6235
6655
|
}, [processQueue]);
|
|
6236
|
-
const queueRewindCapture =
|
|
6656
|
+
const queueRewindCapture = React2.useCallback(() => {
|
|
6237
6657
|
const lowPriorityCount = queueRef.current.filter((q) => q.priority === "low").length;
|
|
6238
6658
|
if (lowPriorityCount >= 3) {
|
|
6239
6659
|
return Promise.resolve(null);
|
|
@@ -6247,13 +6667,13 @@ function useSaveScheduler(nostalgistRef) {
|
|
|
6247
6667
|
processQueue();
|
|
6248
6668
|
});
|
|
6249
6669
|
}, [processQueue]);
|
|
6250
|
-
const isSaving =
|
|
6251
|
-
const getQueueLength =
|
|
6252
|
-
const clearQueue =
|
|
6670
|
+
const isSaving = React2.useCallback(() => savingRef.current, []);
|
|
6671
|
+
const getQueueLength = React2.useCallback(() => queueRef.current.length, []);
|
|
6672
|
+
const clearQueue = React2.useCallback(() => {
|
|
6253
6673
|
queueRef.current.forEach((item) => item.resolve(null));
|
|
6254
6674
|
queueRef.current = [];
|
|
6255
6675
|
}, []);
|
|
6256
|
-
const resultRef =
|
|
6676
|
+
const resultRef = React2.useRef({
|
|
6257
6677
|
save,
|
|
6258
6678
|
queueRewindCapture,
|
|
6259
6679
|
isSaving,
|
|
@@ -6270,22 +6690,22 @@ function useSaveScheduler(nostalgistRef) {
|
|
|
6270
6690
|
|
|
6271
6691
|
// src/hooks/emulator/useEmulatorSaves.ts
|
|
6272
6692
|
function useEmulatorSaves({ nostalgistRef, isPaused, setIsPaused, setStatus, rewindEnabled = true }) {
|
|
6273
|
-
const [isRewinding, setIsRewinding] =
|
|
6274
|
-
const [rewindBufferSize, setRewindBufferSize] =
|
|
6275
|
-
const rewindIntervalRef =
|
|
6276
|
-
const rewindBufferRef =
|
|
6277
|
-
const rewindCaptureIntervalRef =
|
|
6693
|
+
const [isRewinding, setIsRewinding] = React2.useState(false);
|
|
6694
|
+
const [rewindBufferSize, setRewindBufferSize] = React2.useState(0);
|
|
6695
|
+
const rewindIntervalRef = React2.useRef(null);
|
|
6696
|
+
const rewindBufferRef = React2.useRef([]);
|
|
6697
|
+
const rewindCaptureIntervalRef = React2.useRef(null);
|
|
6278
6698
|
const saveScheduler = useSaveScheduler(nostalgistRef);
|
|
6279
|
-
const saveState =
|
|
6699
|
+
const saveState = React2.useCallback(async () => {
|
|
6280
6700
|
if (!nostalgistRef.current) return null;
|
|
6281
6701
|
const result = await saveScheduler.queueRewindCapture();
|
|
6282
6702
|
return result?.data ?? null;
|
|
6283
6703
|
}, [saveScheduler, nostalgistRef]);
|
|
6284
|
-
const saveStateWithBlob =
|
|
6704
|
+
const saveStateWithBlob = React2.useCallback(async () => {
|
|
6285
6705
|
if (!nostalgistRef.current) return null;
|
|
6286
6706
|
return saveScheduler.save();
|
|
6287
6707
|
}, [saveScheduler, nostalgistRef]);
|
|
6288
|
-
const loadState =
|
|
6708
|
+
const loadState = React2.useCallback(async (state) => {
|
|
6289
6709
|
if (!nostalgistRef.current) {
|
|
6290
6710
|
console.warn("[Nostalgist] Cannot load state: emulator not running");
|
|
6291
6711
|
return false;
|
|
@@ -6306,7 +6726,7 @@ function useEmulatorSaves({ nostalgistRef, isPaused, setIsPaused, setStatus, rew
|
|
|
6306
6726
|
return false;
|
|
6307
6727
|
}
|
|
6308
6728
|
}, [nostalgistRef, setIsPaused, setStatus]);
|
|
6309
|
-
const startRewindCapture =
|
|
6729
|
+
const startRewindCapture = React2.useCallback(() => {
|
|
6310
6730
|
if (!rewindEnabled) {
|
|
6311
6731
|
return;
|
|
6312
6732
|
}
|
|
@@ -6334,13 +6754,13 @@ function useEmulatorSaves({ nostalgistRef, isPaused, setIsPaused, setStatus, rew
|
|
|
6334
6754
|
}
|
|
6335
6755
|
}, 500);
|
|
6336
6756
|
}, [isPaused, saveState, nostalgistRef]);
|
|
6337
|
-
const stopRewindCapture =
|
|
6757
|
+
const stopRewindCapture = React2.useCallback(() => {
|
|
6338
6758
|
if (rewindCaptureIntervalRef.current) {
|
|
6339
6759
|
clearInterval(rewindCaptureIntervalRef.current);
|
|
6340
6760
|
rewindCaptureIntervalRef.current = null;
|
|
6341
6761
|
}
|
|
6342
6762
|
}, []);
|
|
6343
|
-
const stopRewind =
|
|
6763
|
+
const stopRewind = React2.useCallback(() => {
|
|
6344
6764
|
if (!isRewinding) {
|
|
6345
6765
|
return;
|
|
6346
6766
|
}
|
|
@@ -6361,7 +6781,7 @@ function useEmulatorSaves({ nostalgistRef, isPaused, setIsPaused, setStatus, rew
|
|
|
6361
6781
|
console.error("[Nostalgist] Stop rewind error:", err);
|
|
6362
6782
|
}
|
|
6363
6783
|
}, [isRewinding, startRewindCapture, nostalgistRef]);
|
|
6364
|
-
const startRewind =
|
|
6784
|
+
const startRewind = React2.useCallback(() => {
|
|
6365
6785
|
if (!nostalgistRef.current || isRewinding) {
|
|
6366
6786
|
return;
|
|
6367
6787
|
}
|
|
@@ -6400,7 +6820,7 @@ function useEmulatorSaves({ nostalgistRef, isPaused, setIsPaused, setStatus, rew
|
|
|
6400
6820
|
startRewindCapture();
|
|
6401
6821
|
}
|
|
6402
6822
|
}, [isRewinding, loadState, stopRewindCapture, startRewindCapture, stopRewind, nostalgistRef]);
|
|
6403
|
-
|
|
6823
|
+
React2.useEffect(() => {
|
|
6404
6824
|
return () => {
|
|
6405
6825
|
saveScheduler.clearQueue();
|
|
6406
6826
|
if (rewindIntervalRef.current) {
|
|
@@ -6428,7 +6848,7 @@ function useEmulatorSaves({ nostalgistRef, isPaused, setIsPaused, setStatus, rew
|
|
|
6428
6848
|
};
|
|
6429
6849
|
}
|
|
6430
6850
|
function useEmulatorCheats({ nostalgistRef }) {
|
|
6431
|
-
const applyCheat =
|
|
6851
|
+
const applyCheat = React2.useCallback((code) => {
|
|
6432
6852
|
if (!nostalgistRef.current) return;
|
|
6433
6853
|
try {
|
|
6434
6854
|
nostalgistRef.current.addCheat(code);
|
|
@@ -6436,7 +6856,7 @@ function useEmulatorCheats({ nostalgistRef }) {
|
|
|
6436
6856
|
console.error("[Nostalgist] Apply cheat error:", err);
|
|
6437
6857
|
}
|
|
6438
6858
|
}, [nostalgistRef]);
|
|
6439
|
-
const resetCheats =
|
|
6859
|
+
const resetCheats = React2.useCallback(() => {
|
|
6440
6860
|
if (!nostalgistRef.current) return;
|
|
6441
6861
|
try {
|
|
6442
6862
|
nostalgistRef.current.resetCheats();
|
|
@@ -6468,7 +6888,7 @@ var useNostalgist = ({
|
|
|
6468
6888
|
shader,
|
|
6469
6889
|
romId
|
|
6470
6890
|
}) => {
|
|
6471
|
-
const isHeavySystem =
|
|
6891
|
+
const isHeavySystem = React2.useMemo(() => {
|
|
6472
6892
|
return PERFORMANCE_TIER_2_SYSTEMS.has(system.toUpperCase());
|
|
6473
6893
|
}, [system]);
|
|
6474
6894
|
const {
|
|
@@ -6546,7 +6966,7 @@ var useNostalgist = ({
|
|
|
6546
6966
|
} = useEmulatorCheats({
|
|
6547
6967
|
nostalgistRef
|
|
6548
6968
|
});
|
|
6549
|
-
const start =
|
|
6969
|
+
const start = React2.useCallback(async () => {
|
|
6550
6970
|
await coreStart();
|
|
6551
6971
|
setTimeout(() => {
|
|
6552
6972
|
if (nostalgistRef.current) {
|
|
@@ -6554,11 +6974,11 @@ var useNostalgist = ({
|
|
|
6554
6974
|
}
|
|
6555
6975
|
}, 2e3);
|
|
6556
6976
|
}, [coreStart, startRewindCapture, nostalgistRef]);
|
|
6557
|
-
const stop =
|
|
6977
|
+
const stop = React2.useCallback(() => {
|
|
6558
6978
|
stopRewindCapture();
|
|
6559
6979
|
coreStop();
|
|
6560
6980
|
}, [stopRewindCapture, coreStop]);
|
|
6561
|
-
const hookReturn =
|
|
6981
|
+
const hookReturn = React2.useMemo(() => ({
|
|
6562
6982
|
status,
|
|
6563
6983
|
error,
|
|
6564
6984
|
isPaused,
|
|
@@ -6629,18 +7049,18 @@ function useVolume({
|
|
|
6629
7049
|
setVolume: setVolumeInHook,
|
|
6630
7050
|
toggleMute: toggleMuteInHook
|
|
6631
7051
|
}) {
|
|
6632
|
-
const [volume, setVolumeState] =
|
|
6633
|
-
const [isMuted, setIsMutedState] =
|
|
6634
|
-
|
|
7052
|
+
const [volume, setVolumeState] = React2.useState(() => loadVolume());
|
|
7053
|
+
const [isMuted, setIsMutedState] = React2.useState(() => loadMuteState());
|
|
7054
|
+
React2.useEffect(() => {
|
|
6635
7055
|
setVolumeInHook(volume);
|
|
6636
7056
|
}, [setVolumeInHook]);
|
|
6637
|
-
const setVolume =
|
|
7057
|
+
const setVolume = React2.useCallback((newVolume) => {
|
|
6638
7058
|
const clampedVolume = Math.max(0, Math.min(100, newVolume));
|
|
6639
7059
|
setVolumeState(clampedVolume);
|
|
6640
7060
|
saveVolume(clampedVolume);
|
|
6641
7061
|
setVolumeInHook(clampedVolume);
|
|
6642
7062
|
}, [setVolumeInHook]);
|
|
6643
|
-
const toggleMute =
|
|
7063
|
+
const toggleMute = React2.useCallback(() => {
|
|
6644
7064
|
setIsMutedState((prev) => {
|
|
6645
7065
|
const newMuted = !prev;
|
|
6646
7066
|
saveMuteState(newMuted);
|
|
@@ -6656,27 +7076,28 @@ function useVolume({
|
|
|
6656
7076
|
};
|
|
6657
7077
|
}
|
|
6658
7078
|
function useControls(system, onNotify) {
|
|
7079
|
+
const t = useKoinTranslation();
|
|
6659
7080
|
const defaultControls = getConsoleKeyboardDefaults(system || "SNES");
|
|
6660
|
-
const [controls, setControls] =
|
|
7081
|
+
const [controls, setControls] = React2.useState(() => {
|
|
6661
7082
|
if (typeof window !== "undefined") {
|
|
6662
7083
|
return loadKeyboardMapping(system);
|
|
6663
7084
|
}
|
|
6664
7085
|
return defaultControls;
|
|
6665
7086
|
});
|
|
6666
|
-
|
|
7087
|
+
React2.useEffect(() => {
|
|
6667
7088
|
const loaded = loadKeyboardMapping(system);
|
|
6668
7089
|
setControls(loaded);
|
|
6669
7090
|
}, [system]);
|
|
6670
|
-
const saveControls =
|
|
7091
|
+
const saveControls = React2.useCallback((newControls) => {
|
|
6671
7092
|
setControls(newControls);
|
|
6672
7093
|
saveKeyboardMapping(newControls, system);
|
|
6673
|
-
onNotify?.(
|
|
6674
|
-
}, [system, onNotify]);
|
|
6675
|
-
const resetToDefaults =
|
|
7094
|
+
onNotify?.(t.notifications.controlsSaved, "success");
|
|
7095
|
+
}, [system, onNotify, t]);
|
|
7096
|
+
const resetToDefaults = React2.useCallback(() => {
|
|
6676
7097
|
setControls(defaultControls);
|
|
6677
7098
|
saveKeyboardMapping(defaultControls, system);
|
|
6678
|
-
onNotify?.(
|
|
6679
|
-
}, [defaultControls, system, onNotify]);
|
|
7099
|
+
onNotify?.(t.notifications.controlsReset, "info");
|
|
7100
|
+
}, [defaultControls, system, onNotify, t]);
|
|
6680
7101
|
return {
|
|
6681
7102
|
controls,
|
|
6682
7103
|
saveControls,
|
|
@@ -6702,16 +7123,17 @@ function useGameSession(props) {
|
|
|
6702
7123
|
canvasRef,
|
|
6703
7124
|
showToast
|
|
6704
7125
|
} = props;
|
|
7126
|
+
const t = useKoinTranslation();
|
|
6705
7127
|
const { controls, saveControls } = useControls(system, showToast);
|
|
6706
|
-
const [gamepadModalOpen, setGamepadModalOpen] =
|
|
6707
|
-
const [controlsModalOpen, setControlsModalOpen] =
|
|
7128
|
+
const [gamepadModalOpen, setGamepadModalOpen] = React2.useState(false);
|
|
7129
|
+
const [controlsModalOpen, setControlsModalOpen] = React2.useState(false);
|
|
6708
7130
|
const { gamepads, connectedCount } = useGamepad({
|
|
6709
7131
|
onConnect: (gamepad) => {
|
|
6710
7132
|
showToast(
|
|
6711
|
-
gamepad.name ||
|
|
7133
|
+
gamepad.name || t.notifications.controllerReady,
|
|
6712
7134
|
"gamepad",
|
|
6713
7135
|
{
|
|
6714
|
-
title:
|
|
7136
|
+
title: t.notifications.controllerConnected,
|
|
6715
7137
|
duration: 4e3,
|
|
6716
7138
|
action: {
|
|
6717
7139
|
label: "Configure",
|
|
@@ -6722,16 +7144,17 @@ function useGameSession(props) {
|
|
|
6722
7144
|
},
|
|
6723
7145
|
onDisconnect: () => {
|
|
6724
7146
|
showToast(
|
|
6725
|
-
|
|
7147
|
+
t.notifications.controllerDisconnected,
|
|
6726
7148
|
"warning",
|
|
6727
7149
|
{
|
|
6728
|
-
title:
|
|
7150
|
+
title: t.notifications.controllerDisconnected,
|
|
7151
|
+
// Title repeats or generic? Using same for now
|
|
6729
7152
|
duration: 3e3
|
|
6730
7153
|
}
|
|
6731
7154
|
);
|
|
6732
7155
|
}
|
|
6733
7156
|
});
|
|
6734
|
-
const gamepadBindings =
|
|
7157
|
+
const gamepadBindings = React2.useMemo(() => {
|
|
6735
7158
|
return [];
|
|
6736
7159
|
}, [gamepads.length]);
|
|
6737
7160
|
const nostalgist = useNostalgist({
|
|
@@ -6755,10 +7178,10 @@ function useGameSession(props) {
|
|
|
6755
7178
|
if (arcadeSystems.includes(system.toLowerCase())) {
|
|
6756
7179
|
setTimeout(() => {
|
|
6757
7180
|
showToast(
|
|
6758
|
-
|
|
7181
|
+
t.notifications.insertCoin,
|
|
6759
7182
|
"info",
|
|
6760
7183
|
{
|
|
6761
|
-
title:
|
|
7184
|
+
title: t.notifications.insertCoinTitle,
|
|
6762
7185
|
duration: 5e3
|
|
6763
7186
|
}
|
|
6764
7187
|
);
|
|
@@ -6776,7 +7199,7 @@ function useGameSession(props) {
|
|
|
6776
7199
|
toggleMute: toggleMuteInHook,
|
|
6777
7200
|
prepare
|
|
6778
7201
|
} = nostalgist;
|
|
6779
|
-
|
|
7202
|
+
React2.useEffect(() => {
|
|
6780
7203
|
return () => {
|
|
6781
7204
|
if (status === "running" || status === "paused") {
|
|
6782
7205
|
onSessionEnd?.();
|
|
@@ -6787,8 +7210,8 @@ function useGameSession(props) {
|
|
|
6787
7210
|
setVolume: setVolumeInHook,
|
|
6788
7211
|
toggleMute: toggleMuteInHook
|
|
6789
7212
|
});
|
|
6790
|
-
|
|
6791
|
-
|
|
7213
|
+
React2.useEffect(() => suppressEmulatorWarnings(), []);
|
|
7214
|
+
React2.useEffect(() => {
|
|
6792
7215
|
if (!romUrl || !system || status !== "idle") return;
|
|
6793
7216
|
const checkAndPrepare = async () => {
|
|
6794
7217
|
if (canvasRef.current && canvasRef.current.isConnected) {
|
|
@@ -6800,7 +7223,7 @@ function useGameSession(props) {
|
|
|
6800
7223
|
const rafId = requestAnimationFrame(checkAndPrepare);
|
|
6801
7224
|
return () => cancelAnimationFrame(rafId);
|
|
6802
7225
|
}, [romUrl, system, status, prepare]);
|
|
6803
|
-
const hardcoreRestrictions =
|
|
7226
|
+
const hardcoreRestrictions = React2.useMemo(() => {
|
|
6804
7227
|
const isHardcore = !!retroAchievementsConfig?.hardcore;
|
|
6805
7228
|
return {
|
|
6806
7229
|
isHardcore,
|
|
@@ -6871,17 +7294,17 @@ function useAutoSave({
|
|
|
6871
7294
|
queueRef,
|
|
6872
7295
|
autoSaveInterval = 6e4
|
|
6873
7296
|
}) {
|
|
6874
|
-
const [autoSavePaused, setAutoSavePaused] =
|
|
6875
|
-
const [autoSaveState, setAutoSaveState] =
|
|
6876
|
-
const [autoSaveProgress, setAutoSaveProgress] =
|
|
6877
|
-
const onAutoSaveRef =
|
|
6878
|
-
const nostalgistRef =
|
|
6879
|
-
|
|
7297
|
+
const [autoSavePaused, setAutoSavePaused] = React2.useState(false);
|
|
7298
|
+
const [autoSaveState, setAutoSaveState] = React2.useState("idle");
|
|
7299
|
+
const [autoSaveProgress, setAutoSaveProgress] = React2.useState(0);
|
|
7300
|
+
const onAutoSaveRef = React2.useRef(onAutoSave);
|
|
7301
|
+
const nostalgistRef = React2.useRef(nostalgist);
|
|
7302
|
+
React2.useEffect(() => {
|
|
6880
7303
|
onAutoSaveRef.current = onAutoSave;
|
|
6881
7304
|
nostalgistRef.current = nostalgist;
|
|
6882
7305
|
}, [onAutoSave, nostalgist]);
|
|
6883
|
-
const [loopTrigger, setLoopTrigger] =
|
|
6884
|
-
|
|
7306
|
+
const [loopTrigger, setLoopTrigger] = React2.useState(0);
|
|
7307
|
+
React2.useEffect(() => {
|
|
6885
7308
|
const currentNostalgist = nostalgist;
|
|
6886
7309
|
if (!onAutoSave || !currentNostalgist || currentNostalgist.status !== "running" || autoSavePaused) {
|
|
6887
7310
|
setAutoSaveState("idle");
|
|
@@ -6931,10 +7354,10 @@ function useAutoSave({
|
|
|
6931
7354
|
clearTimeout(saveTimeoutId);
|
|
6932
7355
|
};
|
|
6933
7356
|
}, [nostalgist?.status, autoSavePaused, !!onAutoSave, loopTrigger, autoSaveInterval]);
|
|
6934
|
-
const handleAutoSaveToggle =
|
|
7357
|
+
const handleAutoSaveToggle = React2.useCallback(() => {
|
|
6935
7358
|
setAutoSavePaused((prev) => !prev);
|
|
6936
7359
|
}, []);
|
|
6937
|
-
|
|
7360
|
+
React2.useEffect(() => {
|
|
6938
7361
|
if (!onAutoSave || !nostalgist || nostalgist.status !== "running") return;
|
|
6939
7362
|
const performEmergencySave = async () => {
|
|
6940
7363
|
try {
|
|
@@ -6996,12 +7419,13 @@ function useGameSaves({
|
|
|
6996
7419
|
onDeleteSaveState,
|
|
6997
7420
|
autoSaveInterval
|
|
6998
7421
|
}) {
|
|
6999
|
-
const
|
|
7000
|
-
const [
|
|
7001
|
-
const [
|
|
7002
|
-
const [
|
|
7003
|
-
const [
|
|
7004
|
-
const
|
|
7422
|
+
const t = useKoinTranslation();
|
|
7423
|
+
const [saveModalOpen, setSaveModalOpen] = React2.useState(false);
|
|
7424
|
+
const [saveModalMode, setSaveModalMode] = React2.useState("save");
|
|
7425
|
+
const [saveSlots, setSaveSlots] = React2.useState([]);
|
|
7426
|
+
const [isSlotLoading, setIsSlotLoading] = React2.useState(false);
|
|
7427
|
+
const [actioningSlot, setActioningSlot] = React2.useState(null);
|
|
7428
|
+
const queueRef = React2.useRef(new SaveQueue());
|
|
7005
7429
|
const {
|
|
7006
7430
|
autoSaveEnabled,
|
|
7007
7431
|
autoSavePaused,
|
|
@@ -7014,7 +7438,7 @@ function useGameSaves({
|
|
|
7014
7438
|
queueRef,
|
|
7015
7439
|
autoSaveInterval
|
|
7016
7440
|
});
|
|
7017
|
-
const refreshSlots =
|
|
7441
|
+
const refreshSlots = React2.useCallback(async () => {
|
|
7018
7442
|
if (!onGetSaveSlots) return;
|
|
7019
7443
|
setIsSlotLoading(true);
|
|
7020
7444
|
try {
|
|
@@ -7022,7 +7446,7 @@ function useGameSaves({
|
|
|
7022
7446
|
setSaveSlots(slots);
|
|
7023
7447
|
} catch (err) {
|
|
7024
7448
|
console.error("Failed to fetch save slots:", err);
|
|
7025
|
-
showToast("
|
|
7449
|
+
showToast(t.notifications.failedFetch, "error", { title: t.overlays.toast.error });
|
|
7026
7450
|
} finally {
|
|
7027
7451
|
setIsSlotLoading(false);
|
|
7028
7452
|
}
|
|
@@ -7039,7 +7463,7 @@ function useGameSaves({
|
|
|
7039
7463
|
const result = await nostalgist.saveStateWithBlob();
|
|
7040
7464
|
if (result) {
|
|
7041
7465
|
await onSaveState(0, result.blob, void 0);
|
|
7042
|
-
showToast(
|
|
7466
|
+
showToast(t.notifications.saved, "success", { title: t.overlays.toast.saved });
|
|
7043
7467
|
}
|
|
7044
7468
|
});
|
|
7045
7469
|
} else {
|
|
@@ -7053,7 +7477,7 @@ function useGameSaves({
|
|
|
7053
7477
|
a.download = `${fileName}.state`;
|
|
7054
7478
|
a.click();
|
|
7055
7479
|
URL.revokeObjectURL(url);
|
|
7056
|
-
showToast(
|
|
7480
|
+
showToast(t.notifications.downloaded, "success", { title: t.overlays.toast.saved });
|
|
7057
7481
|
}
|
|
7058
7482
|
});
|
|
7059
7483
|
}
|
|
@@ -7072,9 +7496,9 @@ function useGameSaves({
|
|
|
7072
7496
|
await queueRef.current.add(async () => {
|
|
7073
7497
|
await nostalgist.loadState(new Uint8Array(buffer));
|
|
7074
7498
|
});
|
|
7075
|
-
showToast(
|
|
7499
|
+
showToast(t.notifications.loaded, "success", { title: t.overlays.toast.loaded });
|
|
7076
7500
|
} else {
|
|
7077
|
-
showToast(
|
|
7501
|
+
showToast(t.notifications.noSaveFound, "error", { title: t.overlays.toast.error });
|
|
7078
7502
|
}
|
|
7079
7503
|
} else {
|
|
7080
7504
|
const input = document.createElement("input");
|
|
@@ -7087,7 +7511,7 @@ function useGameSaves({
|
|
|
7087
7511
|
await queueRef.current.add(async () => {
|
|
7088
7512
|
await nostalgist.loadState(new Uint8Array(buffer));
|
|
7089
7513
|
});
|
|
7090
|
-
showToast("
|
|
7514
|
+
showToast(t.notifications.loadedFile, "success", { title: t.overlays.toast.loaded });
|
|
7091
7515
|
}
|
|
7092
7516
|
};
|
|
7093
7517
|
input.click();
|
|
@@ -7112,14 +7536,14 @@ function useGameSaves({
|
|
|
7112
7536
|
console.warn("Screenshot failed", e);
|
|
7113
7537
|
}
|
|
7114
7538
|
await onSaveState(slot, result.blob, screen);
|
|
7115
|
-
showToast(
|
|
7539
|
+
showToast(t.notifications.savedSlot.replace("{{num}}", slot.toString()), "success", { title: t.overlays.toast.saved });
|
|
7116
7540
|
setSaveModalOpen(false);
|
|
7117
7541
|
resume();
|
|
7118
7542
|
}
|
|
7119
7543
|
});
|
|
7120
7544
|
} catch (err) {
|
|
7121
7545
|
console.error("Save failed:", err);
|
|
7122
|
-
showToast(
|
|
7546
|
+
showToast(t.notifications.failedSave, "error", { title: t.overlays.toast.error });
|
|
7123
7547
|
} finally {
|
|
7124
7548
|
setActioningSlot(null);
|
|
7125
7549
|
}
|
|
@@ -7133,15 +7557,15 @@ function useGameSaves({
|
|
|
7133
7557
|
await queueRef.current.add(async () => {
|
|
7134
7558
|
await nostalgist.loadState(new Uint8Array(buffer));
|
|
7135
7559
|
});
|
|
7136
|
-
showToast(
|
|
7560
|
+
showToast(t.notifications.loadedSlot.replace("{{num}}", slot.toString()), "success", { title: t.overlays.toast.loaded });
|
|
7137
7561
|
setSaveModalOpen(false);
|
|
7138
7562
|
resume();
|
|
7139
7563
|
} else {
|
|
7140
|
-
showToast(
|
|
7564
|
+
showToast(t.notifications.emptySlot, "error", { title: t.overlays.toast.error });
|
|
7141
7565
|
}
|
|
7142
7566
|
} catch (err) {
|
|
7143
7567
|
console.error("Load failed:", err);
|
|
7144
|
-
showToast(
|
|
7568
|
+
showToast(t.notifications.failedLoad, "error", { title: t.overlays.toast.error });
|
|
7145
7569
|
} finally {
|
|
7146
7570
|
setActioningSlot(null);
|
|
7147
7571
|
}
|
|
@@ -7153,11 +7577,11 @@ function useGameSaves({
|
|
|
7153
7577
|
setActioningSlot(slot);
|
|
7154
7578
|
try {
|
|
7155
7579
|
await onDeleteSaveState(slot);
|
|
7156
|
-
showToast(
|
|
7580
|
+
showToast(t.notifications.deletedSlot.replace("{{num}}", slot.toString()), "success", { title: t.overlays.toast.saved });
|
|
7157
7581
|
refreshSlots();
|
|
7158
7582
|
} catch (err) {
|
|
7159
7583
|
console.error("Delete failed:", err);
|
|
7160
|
-
showToast(
|
|
7584
|
+
showToast(t.notifications.failedDelete, "error", { title: t.overlays.toast.error });
|
|
7161
7585
|
} finally {
|
|
7162
7586
|
setActioningSlot(null);
|
|
7163
7587
|
}
|
|
@@ -7186,8 +7610,8 @@ function useGameCheats({
|
|
|
7186
7610
|
cheats = [],
|
|
7187
7611
|
onToggleCheat
|
|
7188
7612
|
}) {
|
|
7189
|
-
const [cheatsModalOpen, setCheatsModalOpen] =
|
|
7190
|
-
const [activeCheats, setActiveCheats] =
|
|
7613
|
+
const [cheatsModalOpen, setCheatsModalOpen] = React2.useState(false);
|
|
7614
|
+
const [activeCheats, setActiveCheats] = React2.useState(/* @__PURE__ */ new Set());
|
|
7191
7615
|
const handleToggleCheat = (cheatId) => {
|
|
7192
7616
|
if (!nostalgist) return;
|
|
7193
7617
|
const newActiveCheats = new Set(activeCheats);
|
|
@@ -7219,16 +7643,16 @@ function useGameCheats({
|
|
|
7219
7643
|
function useGameRecording({
|
|
7220
7644
|
getCanvasElement
|
|
7221
7645
|
}) {
|
|
7222
|
-
const [isRecording, setIsRecording] =
|
|
7223
|
-
const [isPaused, setIsPaused] =
|
|
7224
|
-
const [recordingDuration, setRecordingDuration] =
|
|
7225
|
-
const mediaRecorderRef =
|
|
7226
|
-
const chunksRef =
|
|
7227
|
-
const startTimeRef =
|
|
7228
|
-
const pausedTimeRef =
|
|
7229
|
-
const durationIntervalRef =
|
|
7646
|
+
const [isRecording, setIsRecording] = React2.useState(false);
|
|
7647
|
+
const [isPaused, setIsPaused] = React2.useState(false);
|
|
7648
|
+
const [recordingDuration, setRecordingDuration] = React2.useState(0);
|
|
7649
|
+
const mediaRecorderRef = React2.useRef(null);
|
|
7650
|
+
const chunksRef = React2.useRef([]);
|
|
7651
|
+
const startTimeRef = React2.useRef(0);
|
|
7652
|
+
const pausedTimeRef = React2.useRef(0);
|
|
7653
|
+
const durationIntervalRef = React2.useRef(null);
|
|
7230
7654
|
const isSupported = typeof MediaRecorder !== "undefined" && MediaRecorder.isTypeSupported("video/webm");
|
|
7231
|
-
const startRecording =
|
|
7655
|
+
const startRecording = React2.useCallback(() => {
|
|
7232
7656
|
const canvas = getCanvasElement();
|
|
7233
7657
|
if (!canvas || !isSupported) {
|
|
7234
7658
|
console.warn("[Recording] Canvas not found or MediaRecorder not supported");
|
|
@@ -7266,7 +7690,7 @@ function useGameRecording({
|
|
|
7266
7690
|
console.error("[Recording] Failed to start:", err);
|
|
7267
7691
|
}
|
|
7268
7692
|
}, [getCanvasElement, isSupported, isPaused]);
|
|
7269
|
-
const stopRecording =
|
|
7693
|
+
const stopRecording = React2.useCallback(async () => {
|
|
7270
7694
|
return new Promise((resolve) => {
|
|
7271
7695
|
const mediaRecorder = mediaRecorderRef.current;
|
|
7272
7696
|
if (!mediaRecorder || mediaRecorder.state === "inactive") {
|
|
@@ -7288,7 +7712,7 @@ function useGameRecording({
|
|
|
7288
7712
|
mediaRecorder.stop();
|
|
7289
7713
|
});
|
|
7290
7714
|
}, []);
|
|
7291
|
-
const pauseRecording =
|
|
7715
|
+
const pauseRecording = React2.useCallback(() => {
|
|
7292
7716
|
const mediaRecorder = mediaRecorderRef.current;
|
|
7293
7717
|
if (mediaRecorder && mediaRecorder.state === "recording") {
|
|
7294
7718
|
mediaRecorder.pause();
|
|
@@ -7297,7 +7721,7 @@ function useGameRecording({
|
|
|
7297
7721
|
console.log("[Recording] Paused");
|
|
7298
7722
|
}
|
|
7299
7723
|
}, []);
|
|
7300
|
-
const resumeRecording =
|
|
7724
|
+
const resumeRecording = React2.useCallback(() => {
|
|
7301
7725
|
const mediaRecorder = mediaRecorderRef.current;
|
|
7302
7726
|
if (mediaRecorder && mediaRecorder.state === "paused") {
|
|
7303
7727
|
pausedTimeRef.current = Date.now() - pausedTimeRef.current;
|
|
@@ -7306,7 +7730,7 @@ function useGameRecording({
|
|
|
7306
7730
|
console.log("[Recording] Resumed");
|
|
7307
7731
|
}
|
|
7308
7732
|
}, []);
|
|
7309
|
-
|
|
7733
|
+
React2.useEffect(() => {
|
|
7310
7734
|
return () => {
|
|
7311
7735
|
if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") {
|
|
7312
7736
|
mediaRecorderRef.current.stop();
|
|
@@ -7480,9 +7904,9 @@ var DEFAULT_SETTINGS = {
|
|
|
7480
7904
|
showInputDisplay: false
|
|
7481
7905
|
};
|
|
7482
7906
|
function usePlayerPersistence(onSettingsChange) {
|
|
7483
|
-
const [settings, setSettings] =
|
|
7484
|
-
const [isLoaded, setIsLoaded] =
|
|
7485
|
-
|
|
7907
|
+
const [settings, setSettings] = React2.useState(DEFAULT_SETTINGS);
|
|
7908
|
+
const [isLoaded, setIsLoaded] = React2.useState(false);
|
|
7909
|
+
React2.useEffect(() => {
|
|
7486
7910
|
try {
|
|
7487
7911
|
const stored = localStorage.getItem(STORAGE_KEY2);
|
|
7488
7912
|
if (stored) {
|
|
@@ -7494,7 +7918,7 @@ function usePlayerPersistence(onSettingsChange) {
|
|
|
7494
7918
|
}
|
|
7495
7919
|
setIsLoaded(true);
|
|
7496
7920
|
}, []);
|
|
7497
|
-
const updateSettings =
|
|
7921
|
+
const updateSettings = React2.useCallback((updates) => {
|
|
7498
7922
|
setSettings((prev) => {
|
|
7499
7923
|
const next = { ...prev, ...updates };
|
|
7500
7924
|
try {
|
|
@@ -7535,17 +7959,496 @@ var sendTelemetry = (eventName, params = {}) => {
|
|
|
7535
7959
|
} catch (e) {
|
|
7536
7960
|
}
|
|
7537
7961
|
};
|
|
7538
|
-
|
|
7962
|
+
|
|
7963
|
+
// src/locales/es.ts
|
|
7964
|
+
var es = {
|
|
7965
|
+
controls: {
|
|
7966
|
+
play: "Jugar",
|
|
7967
|
+
pause: "Pausar",
|
|
7968
|
+
reset: "Reiniciar",
|
|
7969
|
+
rewind: "Rebobinar",
|
|
7970
|
+
save: "Guardar",
|
|
7971
|
+
load: "Cargar",
|
|
7972
|
+
snap: "Capturar",
|
|
7973
|
+
rec: "Grabar",
|
|
7974
|
+
stopRec: "Detener",
|
|
7975
|
+
startRecord: "Comenzar grabaci\xF3n",
|
|
7976
|
+
stopRecord: "Detener grabaci\xF3n",
|
|
7977
|
+
mute: "Silenciar",
|
|
7978
|
+
unmute: "Activar sonido",
|
|
7979
|
+
help: "Ayuda",
|
|
7980
|
+
full: "Pantalla comp.",
|
|
7981
|
+
// Abbreviated
|
|
7982
|
+
keys: "Teclas",
|
|
7983
|
+
menuOpen: "Abrir men\xFA",
|
|
7984
|
+
menuClose: "Cerrar men\xFA",
|
|
7985
|
+
gamepadConnected: "{{count}} mando{{plural}} conectado(s) - haz clic para configurar",
|
|
7986
|
+
noGamepad: "No hay mando detectado - presiona cualquier bot\xF3n para conectar",
|
|
7987
|
+
press: "Pulsa",
|
|
7988
|
+
startBtn: "START",
|
|
7989
|
+
selectBtn: "SELECT"
|
|
7990
|
+
},
|
|
7991
|
+
common: {
|
|
7992
|
+
disabledInHardcore: "Desactivado en modo Hardcore",
|
|
7993
|
+
notSupported: "No compatible con esta consola",
|
|
7994
|
+
playToEnableRewind: "Juega unos segundos para activar el rebobinado"
|
|
7995
|
+
},
|
|
7996
|
+
settings: {
|
|
7997
|
+
title: "Ajustes",
|
|
7998
|
+
general: "General",
|
|
7999
|
+
audio: "Sonido",
|
|
8000
|
+
video: "V\xEDdeo",
|
|
8001
|
+
input: "Entrada",
|
|
8002
|
+
advanced: "Avanzado",
|
|
8003
|
+
fullscreen: "Pantalla completa",
|
|
8004
|
+
controls: "Controles",
|
|
8005
|
+
gamepad: "Mando",
|
|
8006
|
+
cheats: "Trucos",
|
|
8007
|
+
retroAchievements: "RetroAchievements",
|
|
8008
|
+
shortcuts: "Atajos",
|
|
8009
|
+
exit: "Salir",
|
|
8010
|
+
language: "Idioma",
|
|
8011
|
+
selectLanguage: "Seleccionar idioma"
|
|
8012
|
+
},
|
|
8013
|
+
overlay: {
|
|
8014
|
+
play: "JUGAR",
|
|
8015
|
+
systemFirmware: "FIRMWARE DEL SISTEMA",
|
|
8016
|
+
loading: "Cargando {{system}}",
|
|
8017
|
+
initializing: "Inicializando emulador",
|
|
8018
|
+
loadingSave: "Cargando partida",
|
|
8019
|
+
preparingSlot: "Preparando ranura {{num}}",
|
|
8020
|
+
systemError: "Error del sistema",
|
|
8021
|
+
failedInit: "Error al inicializar el emulador",
|
|
8022
|
+
retry: "Reintentar",
|
|
8023
|
+
slotReady: "Ranura {{num}} lista",
|
|
8024
|
+
paused: "Pausado"
|
|
8025
|
+
},
|
|
8026
|
+
notifications: {
|
|
8027
|
+
saved: "Estado guardado",
|
|
8028
|
+
loaded: "Estado cargado",
|
|
8029
|
+
error: "Error",
|
|
8030
|
+
recordingStarted: "Grabaci\xF3n iniciada",
|
|
8031
|
+
recordingSaved: "Grabaci\xF3n guardada",
|
|
8032
|
+
downloaded: "Estado descargado",
|
|
8033
|
+
loadedFile: "Estado cargado desde archivo",
|
|
8034
|
+
savedSlot: "Guardado en ranura {{num}}",
|
|
8035
|
+
loadedSlot: "Cargado desde ranura {{num}}",
|
|
8036
|
+
deletedSlot: "Ranura {{num}} eliminada",
|
|
8037
|
+
emptySlot: "Ranura vac\xEDa",
|
|
8038
|
+
noSaveFound: "No se encontr\xF3 partida guardada",
|
|
8039
|
+
failedSave: "Error al guardar",
|
|
8040
|
+
failedLoad: "Error al cargar",
|
|
8041
|
+
failedDelete: "Error al eliminar",
|
|
8042
|
+
failedFetch: "Error al cargar ranuras",
|
|
8043
|
+
controllerConnected: "Mando conectado",
|
|
8044
|
+
controllerDisconnected: "Mando desconectado",
|
|
8045
|
+
controllerReady: "Mando listo para usar",
|
|
8046
|
+
insertCoin: "Pulsa SHIFT para insertar moneda",
|
|
8047
|
+
insertCoinTitle: "\u{1FA99} Insertar Moneda",
|
|
8048
|
+
controlsSaved: "Controles guardados",
|
|
8049
|
+
controlsReset: "Controles restablecidos"
|
|
8050
|
+
},
|
|
8051
|
+
modals: {
|
|
8052
|
+
shortcuts: {
|
|
8053
|
+
title: "Atajos de teclado",
|
|
8054
|
+
playerShortcuts: "Atajos del reproductor",
|
|
8055
|
+
overlays: "Superposiciones",
|
|
8056
|
+
recording: "Grabaci\xF3n",
|
|
8057
|
+
showHelp: "Mostrar ayuda",
|
|
8058
|
+
perfOverlay: "Superposici\xF3n de rendimiento",
|
|
8059
|
+
inputDisplay: "Mostrar entrada",
|
|
8060
|
+
toggleRec: "Alternar grabaci\xF3n",
|
|
8061
|
+
toggleMute: "Alternar silencio",
|
|
8062
|
+
pressEsc: "Pulsa ESC para cerrar"
|
|
8063
|
+
},
|
|
8064
|
+
controls: {
|
|
8065
|
+
title: "Controles",
|
|
8066
|
+
keyboard: "Asignaci\xF3n de teclado",
|
|
8067
|
+
description: "Haz clic en un bot\xF3n y pulsa una tecla",
|
|
8068
|
+
pressKey: "Pulsa una tecla...",
|
|
8069
|
+
reset: "Restablecer",
|
|
8070
|
+
save: "Guardar controles"
|
|
8071
|
+
},
|
|
8072
|
+
gamepad: {
|
|
8073
|
+
title: "Ajustes de mando",
|
|
8074
|
+
noGamepad: "No se detecta mando",
|
|
8075
|
+
connected: "{{count}} mando{{s}} conectado(s)",
|
|
8076
|
+
none: "Ning\xFAn mando detectado",
|
|
8077
|
+
player: "Jugador:",
|
|
8078
|
+
noController: "Sin mando",
|
|
8079
|
+
pressAny: "Pulsa cualquier bot\xF3n para conectar",
|
|
8080
|
+
waiting: "Esperando entrada...",
|
|
8081
|
+
pressButton: "Pulsa bot\xF3n para {{button}}",
|
|
8082
|
+
pressEsc: "Pulsa Escape para cancelar",
|
|
8083
|
+
reset: "Restablecer",
|
|
8084
|
+
save: "Guardar ajustes"
|
|
8085
|
+
},
|
|
8086
|
+
cheats: {
|
|
8087
|
+
title: "Trucos",
|
|
8088
|
+
addCheat: "A\xF1adir truco",
|
|
8089
|
+
available: "{{count}} truco{{s}} disponible(s)",
|
|
8090
|
+
emptyTitle: "No hay trucos disponibles",
|
|
8091
|
+
emptyDesc: "No se encontraron c\xF3digos para este juego",
|
|
8092
|
+
copy: "Copiar c\xF3digo",
|
|
8093
|
+
active: "{{count}} truco{{s}} activo(s)",
|
|
8094
|
+
toggleHint: "Haz clic para activar/desactivar"
|
|
8095
|
+
},
|
|
8096
|
+
saveSlots: {
|
|
8097
|
+
title: "Guardar partida",
|
|
8098
|
+
saveTitle: "Guardar",
|
|
8099
|
+
loadTitle: "Cargar",
|
|
8100
|
+
emptySlot: "Ranura vac\xEDa",
|
|
8101
|
+
subtitleSave: "Elige una ranura para guardar",
|
|
8102
|
+
subtitleLoad: "Elige una ranura para cargar",
|
|
8103
|
+
loading: "Cargando partidas...",
|
|
8104
|
+
locked: "Ranura {{num}} bloqueada",
|
|
8105
|
+
upgrade: "Mejora para desbloquear m\xE1s ranuras",
|
|
8106
|
+
autoSave: "Autoguardado",
|
|
8107
|
+
autoSaveDesc: "Reservado para guardado autom\xE1tico",
|
|
8108
|
+
noData: "Sin datos",
|
|
8109
|
+
slot: "Ranura {{num}}",
|
|
8110
|
+
footerSave: "Las partidas se guardan en la nube y se sincronizan",
|
|
8111
|
+
footerLoad: "Tu progreso se restaurar\xE1 al punto seleccionado"
|
|
8112
|
+
},
|
|
8113
|
+
bios: {
|
|
8114
|
+
title: "Selecci\xF3n de BIOS",
|
|
8115
|
+
description: "Selecciona una BIOS para este juego",
|
|
8116
|
+
warningTitle: "Nota:",
|
|
8117
|
+
warning: "Cambiar la BIOS reiniciar\xE1 el emulador. Se perder\xE1 el progreso no guardado.",
|
|
8118
|
+
systemDefault: "Por defecto",
|
|
8119
|
+
active: "Activo",
|
|
8120
|
+
defaultDesc: "Usar BIOS interna del emulador",
|
|
8121
|
+
emptyTitle: "No se encontraron archivos de BIOS.",
|
|
8122
|
+
emptyDesc: "Sube archivos BIOS en tu biblioteca.",
|
|
8123
|
+
footer: "Firmware del sistema"
|
|
8124
|
+
}
|
|
8125
|
+
},
|
|
8126
|
+
retroAchievements: {
|
|
8127
|
+
title: "RetroAchievements",
|
|
8128
|
+
login: "Iniciar sesi\xF3n",
|
|
8129
|
+
logout: "Cerrar sesi\xF3n",
|
|
8130
|
+
username: "Usuario",
|
|
8131
|
+
password: "Password",
|
|
8132
|
+
hardcore: "Modo Hardcore",
|
|
8133
|
+
achievements: "Logros",
|
|
8134
|
+
locked: "Bloqueado",
|
|
8135
|
+
unlocked: "Desbloqueado",
|
|
8136
|
+
mastered: "Dominado",
|
|
8137
|
+
identifying: "Identificando juego...",
|
|
8138
|
+
achievementsAvailable: "{{count}} logros disponibles",
|
|
8139
|
+
gameNotSupported: "Conectado - Juego no en base de datos",
|
|
8140
|
+
connect: "Conectar RetroAchievements",
|
|
8141
|
+
connected: "RetroAchievements (conectado)",
|
|
8142
|
+
createAccount: "Crear cuenta",
|
|
8143
|
+
privacy: "Privacidad:",
|
|
8144
|
+
privacyText: "Tu contrase\xF1a solo se usa para autenticaci\xF3n con RA. No se almacena.",
|
|
8145
|
+
connecting: "Conectando...",
|
|
8146
|
+
connectAccount: "Conecta tu cuenta para rastrear logros",
|
|
8147
|
+
poweredBy: "Con la tecnolog\xEDa de",
|
|
8148
|
+
connectedStatus: "Conectado",
|
|
8149
|
+
yourUsername: "Tu usuario RA",
|
|
8150
|
+
yourPassword: "Tu contrase\xF1a RA",
|
|
8151
|
+
usernameRequired: "Usuario y contrase\xF1a requeridos",
|
|
8152
|
+
noGame: "Sin juego cargado",
|
|
8153
|
+
loadGame: "Carga un juego para ver los logros",
|
|
8154
|
+
noAchievements: "No se encontraron logros",
|
|
8155
|
+
notSupported: "Este juego puede no ser compatible",
|
|
8156
|
+
ptsRemaining: "{{count}} pts restantes",
|
|
8157
|
+
filters: {
|
|
8158
|
+
all: "Todos",
|
|
8159
|
+
locked: "Bloqueado",
|
|
8160
|
+
unlocked: "Desbloqueado"
|
|
8161
|
+
}
|
|
8162
|
+
},
|
|
8163
|
+
overlays: {
|
|
8164
|
+
performance: {
|
|
8165
|
+
title: "Estad\xEDsticas",
|
|
8166
|
+
fps: "FPS",
|
|
8167
|
+
frameTime: "FT",
|
|
8168
|
+
memory: "MEM",
|
|
8169
|
+
core: "Core",
|
|
8170
|
+
input: "ENTRADA",
|
|
8171
|
+
active: "ACTIVO"
|
|
8172
|
+
},
|
|
8173
|
+
toast: {
|
|
8174
|
+
saved: "Juego guardado",
|
|
8175
|
+
loaded: "Juego cargado",
|
|
8176
|
+
error: "Error"
|
|
8177
|
+
},
|
|
8178
|
+
recording: {
|
|
8179
|
+
started: "Grabaci\xF3n iniciada",
|
|
8180
|
+
stopped: "Grabaci\xF3n detenida",
|
|
8181
|
+
saved: "Grabaci\xF3n guardada",
|
|
8182
|
+
paused: "PAUSA",
|
|
8183
|
+
recording: "GRABANDO",
|
|
8184
|
+
resume: "Reanudar grabaci\xF3n",
|
|
8185
|
+
pause: "Pausar grabaci\xF3n",
|
|
8186
|
+
stop: "Detener y guardar",
|
|
8187
|
+
hover: "Controles"
|
|
8188
|
+
}
|
|
8189
|
+
}
|
|
8190
|
+
};
|
|
8191
|
+
|
|
8192
|
+
// src/locales/fr.ts
|
|
8193
|
+
var fr = {
|
|
8194
|
+
controls: {
|
|
8195
|
+
play: "Jouer",
|
|
8196
|
+
pause: "Pause",
|
|
8197
|
+
reset: "R\xE9initialiser",
|
|
8198
|
+
rewind: "Rembobiner",
|
|
8199
|
+
save: "Sauver",
|
|
8200
|
+
load: "Charger",
|
|
8201
|
+
snap: "Photo",
|
|
8202
|
+
rec: "Enr.",
|
|
8203
|
+
stopRec: "Arr\xEAter",
|
|
8204
|
+
startRecord: "D\xE9marrer l'enregistrement",
|
|
8205
|
+
stopRecord: "Arr\xEAter l'enregistrement",
|
|
8206
|
+
mute: "Muet",
|
|
8207
|
+
unmute: "Son",
|
|
8208
|
+
help: "Aide",
|
|
8209
|
+
full: "Plein \xE9cran",
|
|
8210
|
+
keys: "Touches",
|
|
8211
|
+
menuOpen: "Ouvrir menu",
|
|
8212
|
+
menuClose: "Fermer menu",
|
|
8213
|
+
gamepadConnected: "{{count}} manette{{plural}} connect\xE9e(s)",
|
|
8214
|
+
noGamepad: "Aucune manette d\xE9tect\xE9e",
|
|
8215
|
+
press: "Appuyez",
|
|
8216
|
+
startBtn: "START",
|
|
8217
|
+
selectBtn: "SELECT"
|
|
8218
|
+
},
|
|
8219
|
+
common: {
|
|
8220
|
+
disabledInHardcore: "D\xE9sactiv\xE9 en mode Hardcore",
|
|
8221
|
+
notSupported: "Non support\xE9 sur cette console",
|
|
8222
|
+
playToEnableRewind: "Jouez quelques secondes pour activer le rembobinage"
|
|
8223
|
+
},
|
|
8224
|
+
settings: {
|
|
8225
|
+
title: "Param\xE8tres",
|
|
8226
|
+
general: "G\xE9n\xE9ral",
|
|
8227
|
+
audio: "Audio",
|
|
8228
|
+
video: "Vid\xE9o",
|
|
8229
|
+
input: "Entr\xE9e",
|
|
8230
|
+
advanced: "Avanc\xE9",
|
|
8231
|
+
fullscreen: "Plein \xE9cran",
|
|
8232
|
+
controls: "Contr\xF4les",
|
|
8233
|
+
gamepad: "Manette",
|
|
8234
|
+
cheats: "Codes",
|
|
8235
|
+
retroAchievements: "Succ\xE8s",
|
|
8236
|
+
shortcuts: "Raccourcis",
|
|
8237
|
+
exit: "Quitter",
|
|
8238
|
+
language: "Langue",
|
|
8239
|
+
selectLanguage: "Choisir la langue"
|
|
8240
|
+
},
|
|
8241
|
+
overlay: {
|
|
8242
|
+
play: "JOUER",
|
|
8243
|
+
systemFirmware: "MICROLOGICIEL SYST\xC8ME",
|
|
8244
|
+
loading: "Chargement {{system}}",
|
|
8245
|
+
initializing: "Initialisation de l'\xE9mulateur",
|
|
8246
|
+
loadingSave: "Chargement de la sauvegarde",
|
|
8247
|
+
preparingSlot: "Pr\xE9paration de l'emplacement {{num}}",
|
|
8248
|
+
systemError: "Erreur syst\xE8me",
|
|
8249
|
+
failedInit: "\xC9chec de l'initialisation",
|
|
8250
|
+
retry: "R\xE9essayer",
|
|
8251
|
+
slotReady: "Emplacement {{num}} pr\xEAt",
|
|
8252
|
+
paused: "Pause"
|
|
8253
|
+
},
|
|
8254
|
+
notifications: {
|
|
8255
|
+
saved: "\xC9tat sauvegard\xE9",
|
|
8256
|
+
loaded: "\xC9tat charg\xE9",
|
|
8257
|
+
error: "Erreur",
|
|
8258
|
+
recordingStarted: "Enregistrement d\xE9marr\xE9",
|
|
8259
|
+
recordingSaved: "Enregistrement sauvegard\xE9",
|
|
8260
|
+
downloaded: "\xC9tat t\xE9l\xE9charg\xE9",
|
|
8261
|
+
loadedFile: "\xC9tat charg\xE9 depuis fichier",
|
|
8262
|
+
savedSlot: "Sauvegard\xE9 sur l'emplacement {{num}}",
|
|
8263
|
+
loadedSlot: "Charg\xE9 depuis l'emplacement {{num}}",
|
|
8264
|
+
deletedSlot: "Emplacement {{num}} supprim\xE9",
|
|
8265
|
+
emptySlot: "Emplacement vide",
|
|
8266
|
+
noSaveFound: "Aucune sauvegarde trouv\xE9e",
|
|
8267
|
+
failedSave: "\xC9chec de la sauvegarde",
|
|
8268
|
+
failedLoad: "\xC9chec du chargement",
|
|
8269
|
+
failedDelete: "\xC9chec de la suppression",
|
|
8270
|
+
failedFetch: "\xC9chec du chargement des emplacements",
|
|
8271
|
+
controllerConnected: "Manette connect\xE9e",
|
|
8272
|
+
controllerDisconnected: "Manette d\xE9connect\xE9e",
|
|
8273
|
+
controllerReady: "Manette pr\xEAte",
|
|
8274
|
+
insertCoin: "Appuyez sur SHIFT pour ins\xE9rer une pi\xE8ce",
|
|
8275
|
+
insertCoinTitle: "\u{1FA99} Ins\xE9rer Pi\xE8ce",
|
|
8276
|
+
controlsSaved: "Contr\xF4les sauvegard\xE9s",
|
|
8277
|
+
controlsReset: "Contr\xF4les r\xE9initialis\xE9s"
|
|
8278
|
+
},
|
|
8279
|
+
modals: {
|
|
8280
|
+
shortcuts: {
|
|
8281
|
+
title: "Raccourcis clavier",
|
|
8282
|
+
playerShortcuts: "Raccourcis du lecteur",
|
|
8283
|
+
overlays: "Superpositions",
|
|
8284
|
+
recording: "Enregistrement",
|
|
8285
|
+
showHelp: "Afficher l'aide",
|
|
8286
|
+
perfOverlay: "Overlay de performance",
|
|
8287
|
+
inputDisplay: "Afficher les entr\xE9es",
|
|
8288
|
+
toggleRec: "Basculer l'enregistrement",
|
|
8289
|
+
toggleMute: "Basculer le son",
|
|
8290
|
+
pressEsc: "Appuyez sur ESC pour fermer"
|
|
8291
|
+
},
|
|
8292
|
+
controls: {
|
|
8293
|
+
title: "Contr\xF4les",
|
|
8294
|
+
keyboard: "Configuration clavier",
|
|
8295
|
+
description: "Cliquez sur un bouton et appuyez sur une touche",
|
|
8296
|
+
pressKey: "Appuyez...",
|
|
8297
|
+
reset: "R\xE9initialiser",
|
|
8298
|
+
save: "Sauvegarder"
|
|
8299
|
+
},
|
|
8300
|
+
gamepad: {
|
|
8301
|
+
title: "Param\xE8tres manette",
|
|
8302
|
+
noGamepad: "Aucune manette",
|
|
8303
|
+
connected: "{{count}} manette{{s}} connect\xE9e(s)",
|
|
8304
|
+
none: "Aucune manette d\xE9tect\xE9e",
|
|
8305
|
+
player: "Joueur:",
|
|
8306
|
+
noController: "Aucune manette",
|
|
8307
|
+
pressAny: "Appuyez sur un bouton pour connecter",
|
|
8308
|
+
waiting: "En attente...",
|
|
8309
|
+
pressButton: "Appuyez pour {{button}}",
|
|
8310
|
+
pressEsc: "Echap pour annuler",
|
|
8311
|
+
reset: "R\xE9initialiser",
|
|
8312
|
+
save: "Sauvegarder"
|
|
8313
|
+
},
|
|
8314
|
+
cheats: {
|
|
8315
|
+
title: "Codes de triche",
|
|
8316
|
+
addCheat: "Ajouter un code",
|
|
8317
|
+
available: "{{count}} code{{s}} disponible(s)",
|
|
8318
|
+
emptyTitle: "Aucun code disponible",
|
|
8319
|
+
emptyDesc: "Aucun code trouv\xE9 pour ce jeu",
|
|
8320
|
+
copy: "Copier",
|
|
8321
|
+
active: "{{count}} actif(s)",
|
|
8322
|
+
toggleHint: "Cliquez pour activer/d\xE9sactiver"
|
|
8323
|
+
},
|
|
8324
|
+
saveSlots: {
|
|
8325
|
+
title: "Sauvegardes",
|
|
8326
|
+
saveTitle: "Sauvegarder",
|
|
8327
|
+
loadTitle: "Charger",
|
|
8328
|
+
emptySlot: "Vide",
|
|
8329
|
+
subtitleSave: "Choisir un emplacement",
|
|
8330
|
+
subtitleLoad: "Choisir une sauvegarde",
|
|
8331
|
+
loading: "Chargement...",
|
|
8332
|
+
locked: "Emplacement {{num}} verrouill\xE9",
|
|
8333
|
+
upgrade: "Am\xE9liorer pour plus d'emplacements",
|
|
8334
|
+
autoSave: "Auto-Sauvegarde",
|
|
8335
|
+
autoSaveDesc: "R\xE9serv\xE9 \xE0 la sauvegarde automatique",
|
|
8336
|
+
noData: "Aucune donn\xE9e",
|
|
8337
|
+
slot: "Emplacement {{num}}",
|
|
8338
|
+
footerSave: "Les sauvegardes sont synchronis\xE9es dans le cloud",
|
|
8339
|
+
footerLoad: "Votre progression sera restaur\xE9e"
|
|
8340
|
+
},
|
|
8341
|
+
bios: {
|
|
8342
|
+
title: "S\xE9lection BIOS",
|
|
8343
|
+
description: "S\xE9lectionnez un BIOS",
|
|
8344
|
+
warningTitle: "Note:",
|
|
8345
|
+
warning: "Changer le BIOS red\xE9marre l'\xE9mulateur. Progression non sauvegard\xE9e sera perdue.",
|
|
8346
|
+
systemDefault: "Par d\xE9faut",
|
|
8347
|
+
active: "Actif",
|
|
8348
|
+
defaultDesc: "Utiliser le BIOS par d\xE9faut",
|
|
8349
|
+
emptyTitle: "Aucun BIOS trouv\xE9.",
|
|
8350
|
+
emptyDesc: "T\xE9l\xE9versez des fichiers BIOS dans votre biblioth\xE8que.",
|
|
8351
|
+
footer: "Firmware syst\xE8me"
|
|
8352
|
+
}
|
|
8353
|
+
},
|
|
8354
|
+
retroAchievements: {
|
|
8355
|
+
title: "RetroAchievements",
|
|
8356
|
+
login: "Connexion",
|
|
8357
|
+
logout: "D\xE9connexion",
|
|
8358
|
+
username: "Utilisateur",
|
|
8359
|
+
password: "Mot de passe",
|
|
8360
|
+
hardcore: "Mode Hardcore",
|
|
8361
|
+
achievements: "Succ\xE8s",
|
|
8362
|
+
locked: "Verrouill\xE9",
|
|
8363
|
+
unlocked: "D\xE9verrouill\xE9",
|
|
8364
|
+
mastered: "Ma\xEEtris\xE9",
|
|
8365
|
+
identifying: "Identification...",
|
|
8366
|
+
achievementsAvailable: "{{count}} succ\xE8s disponibles",
|
|
8367
|
+
gameNotSupported: "Jeu non support\xE9 par RA",
|
|
8368
|
+
connect: "Connecter RetroAchievements",
|
|
8369
|
+
connected: "RetroAchievements (connect\xE9)",
|
|
8370
|
+
createAccount: "Cr\xE9er un compte",
|
|
8371
|
+
privacy: "Confidentialit\xE9:",
|
|
8372
|
+
privacyText: "Votre mot de passe n'est pas stock\xE9.",
|
|
8373
|
+
connecting: "Connexion...",
|
|
8374
|
+
connectAccount: "Connectez votre compte",
|
|
8375
|
+
poweredBy: "Propuls\xE9 par",
|
|
8376
|
+
connectedStatus: "Connect\xE9",
|
|
8377
|
+
yourUsername: "Votre utilisateur RA",
|
|
8378
|
+
yourPassword: "Votre mot de passe RA",
|
|
8379
|
+
usernameRequired: "Utilisateur et mot de passe requis",
|
|
8380
|
+
noGame: "Aucun jeu",
|
|
8381
|
+
loadGame: "Chargez un jeu pour voir les succ\xE8s",
|
|
8382
|
+
noAchievements: "Aucun succ\xE8s trouv\xE9",
|
|
8383
|
+
notSupported: "Ce jeu n'est peut-\xEAtre pas support\xE9",
|
|
8384
|
+
ptsRemaining: "{{count}} pts restants",
|
|
8385
|
+
filters: {
|
|
8386
|
+
all: "Tous",
|
|
8387
|
+
locked: "Verrouill\xE9s",
|
|
8388
|
+
unlocked: "D\xE9verrouill\xE9s"
|
|
8389
|
+
}
|
|
8390
|
+
},
|
|
8391
|
+
overlays: {
|
|
8392
|
+
performance: {
|
|
8393
|
+
title: "Stats",
|
|
8394
|
+
fps: "FPS",
|
|
8395
|
+
frameTime: "FT",
|
|
8396
|
+
memory: "MEM",
|
|
8397
|
+
core: "Core",
|
|
8398
|
+
input: "ENTR\xC9E",
|
|
8399
|
+
active: "ACTIF"
|
|
8400
|
+
},
|
|
8401
|
+
toast: {
|
|
8402
|
+
saved: "Sauvegard\xE9",
|
|
8403
|
+
loaded: "Charg\xE9",
|
|
8404
|
+
error: "Erreur"
|
|
8405
|
+
},
|
|
8406
|
+
recording: {
|
|
8407
|
+
started: "Enregistrement d\xE9marr\xE9",
|
|
8408
|
+
stopped: "Enregistrement arr\xEAt\xE9",
|
|
8409
|
+
saved: "Enregistrement sauvegard\xE9",
|
|
8410
|
+
paused: "PAUSE",
|
|
8411
|
+
recording: "ENR.",
|
|
8412
|
+
resume: "Reprendre",
|
|
8413
|
+
pause: "Pause",
|
|
8414
|
+
stop: "Arr\xEAter et sauver",
|
|
8415
|
+
hover: "Contr\xF4les"
|
|
8416
|
+
}
|
|
8417
|
+
}
|
|
8418
|
+
};
|
|
8419
|
+
|
|
8420
|
+
// src/lib/common-utils.ts
|
|
8421
|
+
function deepMerge2(target, source) {
|
|
8422
|
+
const isObject = (obj) => obj && typeof obj === "object";
|
|
8423
|
+
if (!isObject(target) || !isObject(source)) {
|
|
8424
|
+
return source;
|
|
8425
|
+
}
|
|
8426
|
+
const output = { ...target };
|
|
8427
|
+
Object.keys(source).forEach((key) => {
|
|
8428
|
+
const targetValue = output[key];
|
|
8429
|
+
const sourceValue = source[key];
|
|
8430
|
+
if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
|
|
8431
|
+
output[key] = sourceValue;
|
|
8432
|
+
} else if (isObject(targetValue) && isObject(sourceValue)) {
|
|
8433
|
+
output[key] = deepMerge2(targetValue, sourceValue);
|
|
8434
|
+
} else {
|
|
8435
|
+
output[key] = sourceValue;
|
|
8436
|
+
}
|
|
8437
|
+
});
|
|
8438
|
+
return output;
|
|
8439
|
+
}
|
|
8440
|
+
var GamePlayerInner = React2.memo(function GamePlayerInner2(props) {
|
|
7539
8441
|
const { settings, updateSettings, isLoaded: settingsLoaded } = usePlayerPersistence();
|
|
7540
|
-
|
|
8442
|
+
React2.useEffect(() => {
|
|
7541
8443
|
sendTelemetry("game_start", {
|
|
7542
8444
|
system: props.system,
|
|
7543
8445
|
core: props.core,
|
|
7544
8446
|
game: props.title || "unknown"
|
|
7545
8447
|
});
|
|
7546
8448
|
}, [props.system, props.core, props.title]);
|
|
7547
|
-
const [biosModalOpen, setBiosModalOpen] =
|
|
7548
|
-
const [showShortcutsModal, setShowShortcutsModal] =
|
|
8449
|
+
const [biosModalOpen, setBiosModalOpen] = React2.useState(false);
|
|
8450
|
+
const [showShortcutsModal, setShowShortcutsModal] = React2.useState(false);
|
|
8451
|
+
const [settingsModalOpen, setSettingsModalOpen] = React2.useState(false);
|
|
7549
8452
|
const effectiveShader = props.shader !== void 0 ? props.shader : settings.shader;
|
|
7550
8453
|
const {
|
|
7551
8454
|
// Refs
|
|
@@ -7641,72 +8544,76 @@ var GamePlayer = React5.memo(function GamePlayer2(props) {
|
|
|
7641
8544
|
status,
|
|
7642
8545
|
isPerformanceMode
|
|
7643
8546
|
} = nostalgist;
|
|
7644
|
-
|
|
8547
|
+
React2.useEffect(() => {
|
|
7645
8548
|
if (status === "running") {
|
|
7646
8549
|
console.log("[Koin Debug] Status:", status);
|
|
7647
8550
|
console.log("[Koin Debug] isPerformanceMode:", isPerformanceMode);
|
|
7648
8551
|
console.log("[Koin Debug] crossOriginIsolated:", typeof window !== "undefined" ? window.crossOriginIsolated : "N/A");
|
|
7649
8552
|
}
|
|
7650
8553
|
}, [status, isPerformanceMode]);
|
|
7651
|
-
|
|
8554
|
+
React2.useEffect(() => {
|
|
7652
8555
|
if (settingsLoaded) {
|
|
7653
8556
|
setVolume(settings.volume);
|
|
7654
8557
|
if (muted !== settings.muted) toggleMute();
|
|
7655
8558
|
}
|
|
7656
8559
|
}, [settingsLoaded]);
|
|
7657
8560
|
const { system, systemColor = "#00FF41", cheats = [], onExit } = props;
|
|
7658
|
-
const handlePauseToggle =
|
|
8561
|
+
const handlePauseToggle = React2.useCallback(() => {
|
|
7659
8562
|
status === "ready" ? start() : togglePause();
|
|
7660
8563
|
}, [status, start, togglePause]);
|
|
7661
|
-
const handleScreenshot =
|
|
8564
|
+
const handleScreenshot = React2.useCallback(async () => {
|
|
7662
8565
|
const result = await screenshot();
|
|
7663
8566
|
if (result && props.onScreenshotCaptured) {
|
|
7664
8567
|
props.onScreenshotCaptured(result);
|
|
7665
8568
|
}
|
|
7666
8569
|
}, [screenshot, props.onScreenshotCaptured]);
|
|
7667
|
-
const handleShowControls =
|
|
8570
|
+
const handleShowControls = React2.useCallback(() => {
|
|
7668
8571
|
pause();
|
|
7669
8572
|
setControlsModalOpen(true);
|
|
7670
8573
|
}, [pause, setControlsModalOpen]);
|
|
7671
|
-
const handleShowCheats =
|
|
8574
|
+
const handleShowCheats = React2.useCallback(() => {
|
|
7672
8575
|
pause();
|
|
7673
8576
|
setCheatsModalOpen(true);
|
|
7674
8577
|
}, [pause, setCheatsModalOpen]);
|
|
7675
|
-
const handleShowRA =
|
|
8578
|
+
const handleShowRA = React2.useCallback(() => {
|
|
7676
8579
|
pause();
|
|
7677
8580
|
setRaSidebarOpen(true);
|
|
7678
8581
|
}, [pause, setRaSidebarOpen]);
|
|
7679
|
-
const handleShowGamepadSettings =
|
|
8582
|
+
const handleShowGamepadSettings = React2.useCallback(() => {
|
|
7680
8583
|
pause();
|
|
7681
8584
|
setGamepadModalOpen(true);
|
|
7682
8585
|
}, [pause, setGamepadModalOpen]);
|
|
7683
|
-
const
|
|
8586
|
+
const handleShowSettings = React2.useCallback(() => {
|
|
8587
|
+
pause();
|
|
8588
|
+
setSettingsModalOpen(true);
|
|
8589
|
+
}, [pause, setSettingsModalOpen]);
|
|
8590
|
+
const handleExitClick = React2.useCallback(() => {
|
|
7684
8591
|
onExit?.();
|
|
7685
8592
|
}, [onExit]);
|
|
7686
|
-
const handleBiosSelection =
|
|
8593
|
+
const handleBiosSelection = React2.useCallback(() => {
|
|
7687
8594
|
setBiosModalOpen(true);
|
|
7688
8595
|
}, [setBiosModalOpen]);
|
|
7689
|
-
const handleVolumeChange =
|
|
8596
|
+
const handleVolumeChange = React2.useCallback((val) => {
|
|
7690
8597
|
setVolume(val);
|
|
7691
8598
|
updateSettings({ volume: val });
|
|
7692
8599
|
}, [setVolume, updateSettings]);
|
|
7693
|
-
const handleToggleMute =
|
|
8600
|
+
const handleToggleMute = React2.useCallback(() => {
|
|
7694
8601
|
toggleMute();
|
|
7695
8602
|
updateSettings({ muted: !muted });
|
|
7696
8603
|
}, [toggleMute, updateSettings, muted]);
|
|
7697
|
-
const handleShaderChange =
|
|
8604
|
+
const handleShaderChange = React2.useCallback((newShader, requiresRestart) => {
|
|
7698
8605
|
updateSettings({ shader: newShader });
|
|
7699
8606
|
if (props.onShaderChange) {
|
|
7700
8607
|
props.onShaderChange(newShader, requiresRestart);
|
|
7701
8608
|
}
|
|
7702
8609
|
}, [updateSettings, props.onShaderChange]);
|
|
7703
|
-
const handleTogglePerformanceOverlay =
|
|
8610
|
+
const handleTogglePerformanceOverlay = React2.useCallback(() => {
|
|
7704
8611
|
updateSettings({ showPerformanceOverlay: !settings.showPerformanceOverlay });
|
|
7705
8612
|
}, [updateSettings, settings.showPerformanceOverlay]);
|
|
7706
|
-
const handleToggleInputDisplay =
|
|
8613
|
+
const handleToggleInputDisplay = React2.useCallback(() => {
|
|
7707
8614
|
updateSettings({ showInputDisplay: !settings.showInputDisplay });
|
|
7708
8615
|
}, [updateSettings, settings.showInputDisplay]);
|
|
7709
|
-
const handleToggleRecording =
|
|
8616
|
+
const handleToggleRecording = React2.useCallback(async () => {
|
|
7710
8617
|
if (!recordingSupported) {
|
|
7711
8618
|
console.warn("[Recording] Not supported in this browser");
|
|
7712
8619
|
return;
|
|
@@ -7727,14 +8634,14 @@ var GamePlayer = React5.memo(function GamePlayer2(props) {
|
|
|
7727
8634
|
startRecording();
|
|
7728
8635
|
}
|
|
7729
8636
|
}, [isRecording, recordingSupported, startRecording, stopRecording]);
|
|
7730
|
-
const handleToggleShortcuts =
|
|
8637
|
+
const handleToggleShortcuts = React2.useCallback(() => {
|
|
7731
8638
|
setShowShortcutsModal((prev) => {
|
|
7732
8639
|
if (!prev) pause();
|
|
7733
8640
|
else resume();
|
|
7734
8641
|
return !prev;
|
|
7735
8642
|
});
|
|
7736
8643
|
}, [pause, resume]);
|
|
7737
|
-
|
|
8644
|
+
React2.useEffect(() => {
|
|
7738
8645
|
const handleKeyDown = (e) => {
|
|
7739
8646
|
if (e.key === "F1") {
|
|
7740
8647
|
e.preventDefault();
|
|
@@ -7891,6 +8798,7 @@ var GamePlayer = React5.memo(function GamePlayer2(props) {
|
|
|
7891
8798
|
systemColor,
|
|
7892
8799
|
gamepadCount: connectedCount,
|
|
7893
8800
|
onGamepadSettings: handleShowGamepadSettings,
|
|
8801
|
+
onSettings: handleShowSettings,
|
|
7894
8802
|
volume,
|
|
7895
8803
|
isMuted: muted,
|
|
7896
8804
|
onVolumeChange: handleVolumeChange,
|
|
@@ -7956,7 +8864,11 @@ var GamePlayer = React5.memo(function GamePlayer2(props) {
|
|
|
7956
8864
|
setBiosModalOpen,
|
|
7957
8865
|
availableBios: props.availableBios,
|
|
7958
8866
|
currentBiosId: props.currentBiosId,
|
|
7959
|
-
onSelectBios: props.onSelectBios
|
|
8867
|
+
onSelectBios: props.onSelectBios,
|
|
8868
|
+
settingsModalOpen,
|
|
8869
|
+
setSettingsModalOpen,
|
|
8870
|
+
currentLanguage: props.currentLanguage,
|
|
8871
|
+
onLanguageChange: props.onLanguageChange
|
|
7960
8872
|
}
|
|
7961
8873
|
),
|
|
7962
8874
|
!isMobile && /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -7984,6 +8896,27 @@ var GamePlayer = React5.memo(function GamePlayer2(props) {
|
|
|
7984
8896
|
}
|
|
7985
8897
|
) });
|
|
7986
8898
|
});
|
|
8899
|
+
var GamePlayer = React2.memo(function GamePlayer2(props) {
|
|
8900
|
+
const [currentLanguage, setCurrentLanguage] = React2.useState(props.initialLanguage || "en");
|
|
8901
|
+
const effectiveTranslations = React2.useMemo(() => {
|
|
8902
|
+
const base = currentLanguage === "es" ? es : currentLanguage === "fr" ? fr : en;
|
|
8903
|
+
if (props.translations) {
|
|
8904
|
+
return deepMerge2(base, props.translations);
|
|
8905
|
+
}
|
|
8906
|
+
return base;
|
|
8907
|
+
}, [currentLanguage, props.translations]);
|
|
8908
|
+
const handleLanguageChange = React2.useCallback((lang) => {
|
|
8909
|
+
setCurrentLanguage(lang);
|
|
8910
|
+
}, []);
|
|
8911
|
+
return /* @__PURE__ */ jsxRuntime.jsx(KoinI18nProvider, { translations: effectiveTranslations, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
8912
|
+
GamePlayerInner,
|
|
8913
|
+
{
|
|
8914
|
+
...props,
|
|
8915
|
+
currentLanguage,
|
|
8916
|
+
onLanguageChange: handleLanguageChange
|
|
8917
|
+
}
|
|
8918
|
+
) });
|
|
8919
|
+
});
|
|
7987
8920
|
var GamePlayer_default = GamePlayer;
|
|
7988
8921
|
function AchievementPopup({
|
|
7989
8922
|
achievement,
|
|
@@ -7991,9 +8924,9 @@ function AchievementPopup({
|
|
|
7991
8924
|
onDismiss,
|
|
7992
8925
|
autoDismissMs = 5e3
|
|
7993
8926
|
}) {
|
|
7994
|
-
const [isVisible, setIsVisible] =
|
|
7995
|
-
const [isExiting, setIsExiting] =
|
|
7996
|
-
|
|
8927
|
+
const [isVisible, setIsVisible] = React2.useState(false);
|
|
8928
|
+
const [isExiting, setIsExiting] = React2.useState(false);
|
|
8929
|
+
React2.useEffect(() => {
|
|
7997
8930
|
requestAnimationFrame(() => {
|
|
7998
8931
|
setIsVisible(true);
|
|
7999
8932
|
});
|
|
@@ -8070,15 +9003,15 @@ var SHADER_PRESETS = [
|
|
|
8070
9003
|
{ id: "handheld/lcd-grid-v2", name: "LCD Grid", description: "Game Boy style LCD effect" },
|
|
8071
9004
|
{ id: "scanlines", name: "Scanlines", description: "Simple horizontal scanlines" }
|
|
8072
9005
|
];
|
|
8073
|
-
var ShaderSelector =
|
|
9006
|
+
var ShaderSelector = React2.memo(function ShaderSelector2({
|
|
8074
9007
|
currentShader,
|
|
8075
9008
|
onShaderChange,
|
|
8076
9009
|
disabled = false,
|
|
8077
9010
|
systemColor = "#00FF41"
|
|
8078
9011
|
}) {
|
|
8079
|
-
const [isOpen, setIsOpen] =
|
|
8080
|
-
const dropdownRef =
|
|
8081
|
-
|
|
9012
|
+
const [isOpen, setIsOpen] = React2.useState(false);
|
|
9013
|
+
const dropdownRef = React2.useRef(null);
|
|
9014
|
+
React2.useEffect(() => {
|
|
8082
9015
|
const handleClickOutside = (e) => {
|
|
8083
9016
|
if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
|
|
8084
9017
|
setIsOpen(false);
|
|
@@ -8145,18 +9078,18 @@ var ShaderSelector = React5.memo(function ShaderSelector2({
|
|
|
8145
9078
|
] });
|
|
8146
9079
|
});
|
|
8147
9080
|
var ShaderSelector_default = ShaderSelector;
|
|
8148
|
-
var
|
|
9081
|
+
var SHORTCUTS = [
|
|
8149
9082
|
{ key: "F1", description: "Help" },
|
|
8150
9083
|
{ key: "F3", description: "FPS Overlay" },
|
|
8151
9084
|
{ key: "F4", description: "Input Display" },
|
|
8152
9085
|
{ key: "F5", description: "Record" },
|
|
8153
9086
|
{ key: "F9", description: "Mute" }
|
|
8154
9087
|
];
|
|
8155
|
-
var ShortcutsReference =
|
|
9088
|
+
var ShortcutsReference = React2.memo(function ShortcutsReference2({
|
|
8156
9089
|
systemColor = "#00FF41",
|
|
8157
9090
|
isExpanded: initialExpanded = false
|
|
8158
9091
|
}) {
|
|
8159
|
-
const [isExpanded, setIsExpanded] =
|
|
9092
|
+
const [isExpanded, setIsExpanded] = React2.useState(initialExpanded);
|
|
8160
9093
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
8161
9094
|
"div",
|
|
8162
9095
|
{
|
|
@@ -8180,7 +9113,7 @@ var ShortcutsReference = React5.memo(function ShortcutsReference2({
|
|
|
8180
9113
|
]
|
|
8181
9114
|
}
|
|
8182
9115
|
),
|
|
8183
|
-
isExpanded && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 pb-3 pt-1 space-y-1.5 border-t border-white/10", children:
|
|
9116
|
+
isExpanded && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 pb-3 pt-1 space-y-1.5 border-t border-white/10", children: SHORTCUTS.map(({ key, description }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-xs", children: [
|
|
8184
9117
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-white/60", children: description }),
|
|
8185
9118
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
8186
9119
|
"kbd",
|
|
@@ -8213,6 +9146,7 @@ exports.DEFAULT_KEYBOARD = DEFAULT_KEYBOARD;
|
|
|
8213
9146
|
exports.DPAD_BUTTONS = DPAD_BUTTONS;
|
|
8214
9147
|
exports.FACE_BUTTONS = FACE_BUTTONS;
|
|
8215
9148
|
exports.GamePlayer = GamePlayer_default;
|
|
9149
|
+
exports.KoinI18nProvider = KoinI18nProvider;
|
|
8216
9150
|
exports.PERFORMANCE_TIER_1_SYSTEMS = PERFORMANCE_TIER_1_SYSTEMS;
|
|
8217
9151
|
exports.PERFORMANCE_TIER_2_SYSTEMS = PERFORMANCE_TIER_2_SYSTEMS;
|
|
8218
9152
|
exports.RASidebar = RASidebar;
|
|
@@ -8234,9 +9168,12 @@ exports.clearAllControls = clearAllControls;
|
|
|
8234
9168
|
exports.consoleHasButton = consoleHasButton;
|
|
8235
9169
|
exports.detectControllerBrand = detectControllerBrand;
|
|
8236
9170
|
exports.detectSystem = detectSystem;
|
|
9171
|
+
exports.en = en;
|
|
9172
|
+
exports.es = es;
|
|
8237
9173
|
exports.fetchAndCacheRom = fetchAndCacheRom;
|
|
8238
9174
|
exports.formatGamepadButton = formatGamepadButton;
|
|
8239
9175
|
exports.formatKeyCode = formatKeyCode;
|
|
9176
|
+
exports.fr = fr;
|
|
8240
9177
|
exports.gamepadToRetroArchConfig = gamepadToRetroArchConfig;
|
|
8241
9178
|
exports.getAchievementBadgeUrl = getAchievementBadgeUrl;
|
|
8242
9179
|
exports.getCachedRom = getCachedRom;
|
|
@@ -8265,6 +9202,7 @@ exports.saveKeyboardMapping = saveKeyboardMapping;
|
|
|
8265
9202
|
exports.systemsMatch = systemsMatch;
|
|
8266
9203
|
exports.useGameRecording = useGameRecording;
|
|
8267
9204
|
exports.useGamepad = useGamepad;
|
|
9205
|
+
exports.useKoinTranslation = useKoinTranslation;
|
|
8268
9206
|
exports.useNostalgist = useNostalgist;
|
|
8269
9207
|
exports.useToast = useToast;
|
|
8270
9208
|
//# sourceMappingURL=index.js.map
|