koin.js 1.0.6 → 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 +254 -1
- package/dist/index.d.ts +254 -1
- package/dist/index.js +1472 -518
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1195 -248
- 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);
|
|
@@ -5662,22 +6082,36 @@ async function getCachedRom(romId) {
|
|
|
5662
6082
|
}
|
|
5663
6083
|
return null;
|
|
5664
6084
|
}
|
|
6085
|
+
var pendingFetches = /* @__PURE__ */ new Map();
|
|
5665
6086
|
async function fetchAndCacheRom(romId, url) {
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
6087
|
+
if (pendingFetches.has(romId)) {
|
|
6088
|
+
console.log(`[Cache] Joining in-flight fetch for ${romId}`);
|
|
6089
|
+
return pendingFetches.get(romId);
|
|
5669
6090
|
}
|
|
5670
|
-
const
|
|
5671
|
-
if (typeof caches !== "undefined") {
|
|
6091
|
+
const fetchPromise = (async () => {
|
|
5672
6092
|
try {
|
|
5673
|
-
const
|
|
5674
|
-
|
|
5675
|
-
console.log(`[Cache]
|
|
5676
|
-
|
|
5677
|
-
|
|
6093
|
+
const cached = await getCachedRom(romId);
|
|
6094
|
+
if (cached) return cached;
|
|
6095
|
+
console.log(`[Cache] Fetching ROM ${romId}...`);
|
|
6096
|
+
const response = await fetch(url);
|
|
6097
|
+
if (!response.ok) throw new Error(`Failed to fetch ROM: ${response.statusText}`);
|
|
6098
|
+
const blob = await response.blob();
|
|
6099
|
+
if (typeof caches !== "undefined") {
|
|
6100
|
+
try {
|
|
6101
|
+
const cache = await caches.open(CACHE_NAME);
|
|
6102
|
+
await cache.put(romId, new Response(blob));
|
|
6103
|
+
console.log(`[Cache] Cached ROM ${romId}`);
|
|
6104
|
+
} catch (e) {
|
|
6105
|
+
console.warn("[Cache] Write failed:", e);
|
|
6106
|
+
}
|
|
6107
|
+
}
|
|
6108
|
+
return blob;
|
|
6109
|
+
} finally {
|
|
6110
|
+
pendingFetches.delete(romId);
|
|
5678
6111
|
}
|
|
5679
|
-
}
|
|
5680
|
-
|
|
6112
|
+
})();
|
|
6113
|
+
pendingFetches.set(romId, fetchPromise);
|
|
6114
|
+
return fetchPromise;
|
|
5681
6115
|
}
|
|
5682
6116
|
|
|
5683
6117
|
// src/hooks/emulator/useEmulatorCore.ts
|
|
@@ -5698,15 +6132,15 @@ function useEmulatorCore({
|
|
|
5698
6132
|
onReady,
|
|
5699
6133
|
onError
|
|
5700
6134
|
}) {
|
|
5701
|
-
const [status, setStatus] =
|
|
5702
|
-
const [error, setError] =
|
|
5703
|
-
const [isPaused, setIsPaused] =
|
|
5704
|
-
const [speed, setSpeedState] =
|
|
5705
|
-
const [isFastForwardOn, setIsFastForwardOn] =
|
|
5706
|
-
const [isPerformanceMode, setIsPerformanceMode] =
|
|
5707
|
-
const nostalgistRef =
|
|
5708
|
-
const isStartingRef =
|
|
5709
|
-
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 () => {
|
|
5710
6144
|
if (!romUrl || !system) {
|
|
5711
6145
|
console.warn("[Nostalgist] Missing romUrl or system");
|
|
5712
6146
|
return;
|
|
@@ -5855,7 +6289,7 @@ function useEmulatorCore({
|
|
|
5855
6289
|
onError?.(err instanceof Error ? err : new Error(errorMessage));
|
|
5856
6290
|
}
|
|
5857
6291
|
}, [system, romUrl, coreOverride, biosUrl, initialState, getCanvasElement, keyboardControls, gamepadBindings, initialVolume, onError, retroAchievements]);
|
|
5858
|
-
const start =
|
|
6292
|
+
const start = React2.useCallback(async () => {
|
|
5859
6293
|
if (isStartingRef.current) {
|
|
5860
6294
|
console.log("[Nostalgist] Already starting");
|
|
5861
6295
|
return;
|
|
@@ -5888,7 +6322,7 @@ function useEmulatorCore({
|
|
|
5888
6322
|
isStartingRef.current = false;
|
|
5889
6323
|
}
|
|
5890
6324
|
}, [prepare, onReady, onError]);
|
|
5891
|
-
const stop =
|
|
6325
|
+
const stop = React2.useCallback(() => {
|
|
5892
6326
|
if (nostalgistRef.current) {
|
|
5893
6327
|
try {
|
|
5894
6328
|
nostalgistRef.current.exit();
|
|
@@ -5901,7 +6335,7 @@ function useEmulatorCore({
|
|
|
5901
6335
|
setIsPaused(false);
|
|
5902
6336
|
}
|
|
5903
6337
|
}, []);
|
|
5904
|
-
|
|
6338
|
+
React2.useEffect(() => {
|
|
5905
6339
|
return () => {
|
|
5906
6340
|
if (nostalgistRef.current) {
|
|
5907
6341
|
console.log("[Nostalgist] Cleaning up emulator on unmount");
|
|
@@ -5909,7 +6343,7 @@ function useEmulatorCore({
|
|
|
5909
6343
|
}
|
|
5910
6344
|
};
|
|
5911
6345
|
}, [stop]);
|
|
5912
|
-
const restart =
|
|
6346
|
+
const restart = React2.useCallback(async () => {
|
|
5913
6347
|
if (nostalgistRef.current) {
|
|
5914
6348
|
try {
|
|
5915
6349
|
nostalgistRef.current.restart();
|
|
@@ -5924,7 +6358,7 @@ function useEmulatorCore({
|
|
|
5924
6358
|
}
|
|
5925
6359
|
}
|
|
5926
6360
|
}, [stop, start]);
|
|
5927
|
-
const pause =
|
|
6361
|
+
const pause = React2.useCallback(() => {
|
|
5928
6362
|
if (nostalgistRef.current && !isPaused && status === "running") {
|
|
5929
6363
|
try {
|
|
5930
6364
|
nostalgistRef.current.pause();
|
|
@@ -5935,7 +6369,7 @@ function useEmulatorCore({
|
|
|
5935
6369
|
}
|
|
5936
6370
|
}
|
|
5937
6371
|
}, [isPaused, status]);
|
|
5938
|
-
const resume =
|
|
6372
|
+
const resume = React2.useCallback(() => {
|
|
5939
6373
|
if (nostalgistRef.current) {
|
|
5940
6374
|
try {
|
|
5941
6375
|
nostalgistRef.current.resume();
|
|
@@ -5948,14 +6382,14 @@ function useEmulatorCore({
|
|
|
5948
6382
|
}
|
|
5949
6383
|
}
|
|
5950
6384
|
}, [status]);
|
|
5951
|
-
const togglePause =
|
|
6385
|
+
const togglePause = React2.useCallback(() => {
|
|
5952
6386
|
if (isPaused) {
|
|
5953
6387
|
resume();
|
|
5954
6388
|
} else {
|
|
5955
6389
|
pause();
|
|
5956
6390
|
}
|
|
5957
6391
|
}, [isPaused, pause, resume]);
|
|
5958
|
-
const setSpeed =
|
|
6392
|
+
const setSpeed = React2.useCallback((multiplier) => {
|
|
5959
6393
|
if (!nostalgistRef.current) return;
|
|
5960
6394
|
try {
|
|
5961
6395
|
const nostalgist = nostalgistRef.current;
|
|
@@ -5975,7 +6409,7 @@ function useEmulatorCore({
|
|
|
5975
6409
|
console.error("[Nostalgist] Set speed error:", err);
|
|
5976
6410
|
}
|
|
5977
6411
|
}, [isFastForwardOn]);
|
|
5978
|
-
const screenshot =
|
|
6412
|
+
const screenshot = React2.useCallback(async () => {
|
|
5979
6413
|
if (!nostalgistRef.current) {
|
|
5980
6414
|
return null;
|
|
5981
6415
|
}
|
|
@@ -6005,7 +6439,7 @@ function useEmulatorCore({
|
|
|
6005
6439
|
return null;
|
|
6006
6440
|
}
|
|
6007
6441
|
}, []);
|
|
6008
|
-
const resize =
|
|
6442
|
+
const resize = React2.useCallback((size) => {
|
|
6009
6443
|
if (!nostalgistRef.current) {
|
|
6010
6444
|
console.warn("[Nostalgist] Cannot resize: emulator not ready");
|
|
6011
6445
|
return;
|
|
@@ -6016,7 +6450,7 @@ function useEmulatorCore({
|
|
|
6016
6450
|
console.error("[Nostalgist] Resize error:", err);
|
|
6017
6451
|
}
|
|
6018
6452
|
}, []);
|
|
6019
|
-
|
|
6453
|
+
React2.useCallback(() => {
|
|
6020
6454
|
return nostalgistRef.current;
|
|
6021
6455
|
}, []);
|
|
6022
6456
|
return {
|
|
@@ -6042,11 +6476,11 @@ function useEmulatorCore({
|
|
|
6042
6476
|
};
|
|
6043
6477
|
}
|
|
6044
6478
|
function useEmulatorAudio({ nostalgistRef, initialVolume = 100 }) {
|
|
6045
|
-
const [volume, setVolume] =
|
|
6046
|
-
const [isMuted, setIsMuted] =
|
|
6047
|
-
const gainNodeRef =
|
|
6048
|
-
const lastVolumeRef =
|
|
6049
|
-
|
|
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(() => {
|
|
6050
6484
|
const originalConnect = AudioNode.prototype.connect;
|
|
6051
6485
|
const contextGainMap = /* @__PURE__ */ new WeakMap();
|
|
6052
6486
|
AudioNode.prototype.connect = function(destination, output, input) {
|
|
@@ -6074,7 +6508,7 @@ function useEmulatorAudio({ nostalgistRef, initialVolume = 100 }) {
|
|
|
6074
6508
|
AudioNode.prototype.connect = originalConnect;
|
|
6075
6509
|
};
|
|
6076
6510
|
}, []);
|
|
6077
|
-
const setVolumeLevel =
|
|
6511
|
+
const setVolumeLevel = React2.useCallback((newVolume) => {
|
|
6078
6512
|
const clampedVolume = Math.max(0, Math.min(100, newVolume));
|
|
6079
6513
|
const volumeValue = clampedVolume / 100;
|
|
6080
6514
|
try {
|
|
@@ -6095,7 +6529,7 @@ function useEmulatorAudio({ nostalgistRef, initialVolume = 100 }) {
|
|
|
6095
6529
|
console.error("[Nostalgist] Volume change error:", err);
|
|
6096
6530
|
}
|
|
6097
6531
|
}, []);
|
|
6098
|
-
const toggleMute =
|
|
6532
|
+
const toggleMute = React2.useCallback(() => {
|
|
6099
6533
|
if (!nostalgistRef.current) return;
|
|
6100
6534
|
try {
|
|
6101
6535
|
const emscripten = nostalgistRef.current.getEmscripten();
|
|
@@ -6137,7 +6571,7 @@ function useEmulatorAudio({ nostalgistRef, initialVolume = 100 }) {
|
|
|
6137
6571
|
};
|
|
6138
6572
|
}
|
|
6139
6573
|
function useEmulatorInput({ nostalgistRef }) {
|
|
6140
|
-
const pressKey =
|
|
6574
|
+
const pressKey = React2.useCallback((key) => {
|
|
6141
6575
|
if (!nostalgistRef.current) return;
|
|
6142
6576
|
try {
|
|
6143
6577
|
nostalgistRef.current.press(key);
|
|
@@ -6152,10 +6586,10 @@ function useEmulatorInput({ nostalgistRef }) {
|
|
|
6152
6586
|
var MIN_SAVE_INTERVAL = 100;
|
|
6153
6587
|
var SAVE_TIMEOUT = 5e3;
|
|
6154
6588
|
function useSaveScheduler(nostalgistRef) {
|
|
6155
|
-
const queueRef =
|
|
6156
|
-
const savingRef =
|
|
6157
|
-
const lastSaveTimeRef =
|
|
6158
|
-
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 () => {
|
|
6159
6593
|
if (savingRef.current || queueRef.current.length === 0) {
|
|
6160
6594
|
return;
|
|
6161
6595
|
}
|
|
@@ -6209,7 +6643,7 @@ function useSaveScheduler(nostalgistRef) {
|
|
|
6209
6643
|
setTimeout(() => processQueue(), MIN_SAVE_INTERVAL);
|
|
6210
6644
|
}
|
|
6211
6645
|
}, [nostalgistRef]);
|
|
6212
|
-
const save =
|
|
6646
|
+
const save = React2.useCallback(() => {
|
|
6213
6647
|
return new Promise((resolve) => {
|
|
6214
6648
|
queueRef.current.push({
|
|
6215
6649
|
priority: "high",
|
|
@@ -6219,7 +6653,7 @@ function useSaveScheduler(nostalgistRef) {
|
|
|
6219
6653
|
processQueue();
|
|
6220
6654
|
});
|
|
6221
6655
|
}, [processQueue]);
|
|
6222
|
-
const queueRewindCapture =
|
|
6656
|
+
const queueRewindCapture = React2.useCallback(() => {
|
|
6223
6657
|
const lowPriorityCount = queueRef.current.filter((q) => q.priority === "low").length;
|
|
6224
6658
|
if (lowPriorityCount >= 3) {
|
|
6225
6659
|
return Promise.resolve(null);
|
|
@@ -6233,13 +6667,13 @@ function useSaveScheduler(nostalgistRef) {
|
|
|
6233
6667
|
processQueue();
|
|
6234
6668
|
});
|
|
6235
6669
|
}, [processQueue]);
|
|
6236
|
-
const isSaving =
|
|
6237
|
-
const getQueueLength =
|
|
6238
|
-
const clearQueue =
|
|
6670
|
+
const isSaving = React2.useCallback(() => savingRef.current, []);
|
|
6671
|
+
const getQueueLength = React2.useCallback(() => queueRef.current.length, []);
|
|
6672
|
+
const clearQueue = React2.useCallback(() => {
|
|
6239
6673
|
queueRef.current.forEach((item) => item.resolve(null));
|
|
6240
6674
|
queueRef.current = [];
|
|
6241
6675
|
}, []);
|
|
6242
|
-
const resultRef =
|
|
6676
|
+
const resultRef = React2.useRef({
|
|
6243
6677
|
save,
|
|
6244
6678
|
queueRewindCapture,
|
|
6245
6679
|
isSaving,
|
|
@@ -6256,22 +6690,22 @@ function useSaveScheduler(nostalgistRef) {
|
|
|
6256
6690
|
|
|
6257
6691
|
// src/hooks/emulator/useEmulatorSaves.ts
|
|
6258
6692
|
function useEmulatorSaves({ nostalgistRef, isPaused, setIsPaused, setStatus, rewindEnabled = true }) {
|
|
6259
|
-
const [isRewinding, setIsRewinding] =
|
|
6260
|
-
const [rewindBufferSize, setRewindBufferSize] =
|
|
6261
|
-
const rewindIntervalRef =
|
|
6262
|
-
const rewindBufferRef =
|
|
6263
|
-
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);
|
|
6264
6698
|
const saveScheduler = useSaveScheduler(nostalgistRef);
|
|
6265
|
-
const saveState =
|
|
6699
|
+
const saveState = React2.useCallback(async () => {
|
|
6266
6700
|
if (!nostalgistRef.current) return null;
|
|
6267
6701
|
const result = await saveScheduler.queueRewindCapture();
|
|
6268
6702
|
return result?.data ?? null;
|
|
6269
6703
|
}, [saveScheduler, nostalgistRef]);
|
|
6270
|
-
const saveStateWithBlob =
|
|
6704
|
+
const saveStateWithBlob = React2.useCallback(async () => {
|
|
6271
6705
|
if (!nostalgistRef.current) return null;
|
|
6272
6706
|
return saveScheduler.save();
|
|
6273
6707
|
}, [saveScheduler, nostalgistRef]);
|
|
6274
|
-
const loadState =
|
|
6708
|
+
const loadState = React2.useCallback(async (state) => {
|
|
6275
6709
|
if (!nostalgistRef.current) {
|
|
6276
6710
|
console.warn("[Nostalgist] Cannot load state: emulator not running");
|
|
6277
6711
|
return false;
|
|
@@ -6292,7 +6726,7 @@ function useEmulatorSaves({ nostalgistRef, isPaused, setIsPaused, setStatus, rew
|
|
|
6292
6726
|
return false;
|
|
6293
6727
|
}
|
|
6294
6728
|
}, [nostalgistRef, setIsPaused, setStatus]);
|
|
6295
|
-
const startRewindCapture =
|
|
6729
|
+
const startRewindCapture = React2.useCallback(() => {
|
|
6296
6730
|
if (!rewindEnabled) {
|
|
6297
6731
|
return;
|
|
6298
6732
|
}
|
|
@@ -6320,13 +6754,13 @@ function useEmulatorSaves({ nostalgistRef, isPaused, setIsPaused, setStatus, rew
|
|
|
6320
6754
|
}
|
|
6321
6755
|
}, 500);
|
|
6322
6756
|
}, [isPaused, saveState, nostalgistRef]);
|
|
6323
|
-
const stopRewindCapture =
|
|
6757
|
+
const stopRewindCapture = React2.useCallback(() => {
|
|
6324
6758
|
if (rewindCaptureIntervalRef.current) {
|
|
6325
6759
|
clearInterval(rewindCaptureIntervalRef.current);
|
|
6326
6760
|
rewindCaptureIntervalRef.current = null;
|
|
6327
6761
|
}
|
|
6328
6762
|
}, []);
|
|
6329
|
-
const stopRewind =
|
|
6763
|
+
const stopRewind = React2.useCallback(() => {
|
|
6330
6764
|
if (!isRewinding) {
|
|
6331
6765
|
return;
|
|
6332
6766
|
}
|
|
@@ -6347,7 +6781,7 @@ function useEmulatorSaves({ nostalgistRef, isPaused, setIsPaused, setStatus, rew
|
|
|
6347
6781
|
console.error("[Nostalgist] Stop rewind error:", err);
|
|
6348
6782
|
}
|
|
6349
6783
|
}, [isRewinding, startRewindCapture, nostalgistRef]);
|
|
6350
|
-
const startRewind =
|
|
6784
|
+
const startRewind = React2.useCallback(() => {
|
|
6351
6785
|
if (!nostalgistRef.current || isRewinding) {
|
|
6352
6786
|
return;
|
|
6353
6787
|
}
|
|
@@ -6386,7 +6820,7 @@ function useEmulatorSaves({ nostalgistRef, isPaused, setIsPaused, setStatus, rew
|
|
|
6386
6820
|
startRewindCapture();
|
|
6387
6821
|
}
|
|
6388
6822
|
}, [isRewinding, loadState, stopRewindCapture, startRewindCapture, stopRewind, nostalgistRef]);
|
|
6389
|
-
|
|
6823
|
+
React2.useEffect(() => {
|
|
6390
6824
|
return () => {
|
|
6391
6825
|
saveScheduler.clearQueue();
|
|
6392
6826
|
if (rewindIntervalRef.current) {
|
|
@@ -6414,7 +6848,7 @@ function useEmulatorSaves({ nostalgistRef, isPaused, setIsPaused, setStatus, rew
|
|
|
6414
6848
|
};
|
|
6415
6849
|
}
|
|
6416
6850
|
function useEmulatorCheats({ nostalgistRef }) {
|
|
6417
|
-
const applyCheat =
|
|
6851
|
+
const applyCheat = React2.useCallback((code) => {
|
|
6418
6852
|
if (!nostalgistRef.current) return;
|
|
6419
6853
|
try {
|
|
6420
6854
|
nostalgistRef.current.addCheat(code);
|
|
@@ -6422,7 +6856,7 @@ function useEmulatorCheats({ nostalgistRef }) {
|
|
|
6422
6856
|
console.error("[Nostalgist] Apply cheat error:", err);
|
|
6423
6857
|
}
|
|
6424
6858
|
}, [nostalgistRef]);
|
|
6425
|
-
const resetCheats =
|
|
6859
|
+
const resetCheats = React2.useCallback(() => {
|
|
6426
6860
|
if (!nostalgistRef.current) return;
|
|
6427
6861
|
try {
|
|
6428
6862
|
nostalgistRef.current.resetCheats();
|
|
@@ -6454,7 +6888,7 @@ var useNostalgist = ({
|
|
|
6454
6888
|
shader,
|
|
6455
6889
|
romId
|
|
6456
6890
|
}) => {
|
|
6457
|
-
const isHeavySystem =
|
|
6891
|
+
const isHeavySystem = React2.useMemo(() => {
|
|
6458
6892
|
return PERFORMANCE_TIER_2_SYSTEMS.has(system.toUpperCase());
|
|
6459
6893
|
}, [system]);
|
|
6460
6894
|
const {
|
|
@@ -6532,7 +6966,7 @@ var useNostalgist = ({
|
|
|
6532
6966
|
} = useEmulatorCheats({
|
|
6533
6967
|
nostalgistRef
|
|
6534
6968
|
});
|
|
6535
|
-
const start =
|
|
6969
|
+
const start = React2.useCallback(async () => {
|
|
6536
6970
|
await coreStart();
|
|
6537
6971
|
setTimeout(() => {
|
|
6538
6972
|
if (nostalgistRef.current) {
|
|
@@ -6540,11 +6974,11 @@ var useNostalgist = ({
|
|
|
6540
6974
|
}
|
|
6541
6975
|
}, 2e3);
|
|
6542
6976
|
}, [coreStart, startRewindCapture, nostalgistRef]);
|
|
6543
|
-
const stop =
|
|
6977
|
+
const stop = React2.useCallback(() => {
|
|
6544
6978
|
stopRewindCapture();
|
|
6545
6979
|
coreStop();
|
|
6546
6980
|
}, [stopRewindCapture, coreStop]);
|
|
6547
|
-
const hookReturn =
|
|
6981
|
+
const hookReturn = React2.useMemo(() => ({
|
|
6548
6982
|
status,
|
|
6549
6983
|
error,
|
|
6550
6984
|
isPaused,
|
|
@@ -6615,18 +7049,18 @@ function useVolume({
|
|
|
6615
7049
|
setVolume: setVolumeInHook,
|
|
6616
7050
|
toggleMute: toggleMuteInHook
|
|
6617
7051
|
}) {
|
|
6618
|
-
const [volume, setVolumeState] =
|
|
6619
|
-
const [isMuted, setIsMutedState] =
|
|
6620
|
-
|
|
7052
|
+
const [volume, setVolumeState] = React2.useState(() => loadVolume());
|
|
7053
|
+
const [isMuted, setIsMutedState] = React2.useState(() => loadMuteState());
|
|
7054
|
+
React2.useEffect(() => {
|
|
6621
7055
|
setVolumeInHook(volume);
|
|
6622
7056
|
}, [setVolumeInHook]);
|
|
6623
|
-
const setVolume =
|
|
7057
|
+
const setVolume = React2.useCallback((newVolume) => {
|
|
6624
7058
|
const clampedVolume = Math.max(0, Math.min(100, newVolume));
|
|
6625
7059
|
setVolumeState(clampedVolume);
|
|
6626
7060
|
saveVolume(clampedVolume);
|
|
6627
7061
|
setVolumeInHook(clampedVolume);
|
|
6628
7062
|
}, [setVolumeInHook]);
|
|
6629
|
-
const toggleMute =
|
|
7063
|
+
const toggleMute = React2.useCallback(() => {
|
|
6630
7064
|
setIsMutedState((prev) => {
|
|
6631
7065
|
const newMuted = !prev;
|
|
6632
7066
|
saveMuteState(newMuted);
|
|
@@ -6642,27 +7076,28 @@ function useVolume({
|
|
|
6642
7076
|
};
|
|
6643
7077
|
}
|
|
6644
7078
|
function useControls(system, onNotify) {
|
|
7079
|
+
const t = useKoinTranslation();
|
|
6645
7080
|
const defaultControls = getConsoleKeyboardDefaults(system || "SNES");
|
|
6646
|
-
const [controls, setControls] =
|
|
7081
|
+
const [controls, setControls] = React2.useState(() => {
|
|
6647
7082
|
if (typeof window !== "undefined") {
|
|
6648
7083
|
return loadKeyboardMapping(system);
|
|
6649
7084
|
}
|
|
6650
7085
|
return defaultControls;
|
|
6651
7086
|
});
|
|
6652
|
-
|
|
7087
|
+
React2.useEffect(() => {
|
|
6653
7088
|
const loaded = loadKeyboardMapping(system);
|
|
6654
7089
|
setControls(loaded);
|
|
6655
7090
|
}, [system]);
|
|
6656
|
-
const saveControls =
|
|
7091
|
+
const saveControls = React2.useCallback((newControls) => {
|
|
6657
7092
|
setControls(newControls);
|
|
6658
7093
|
saveKeyboardMapping(newControls, system);
|
|
6659
|
-
onNotify?.(
|
|
6660
|
-
}, [system, onNotify]);
|
|
6661
|
-
const resetToDefaults =
|
|
7094
|
+
onNotify?.(t.notifications.controlsSaved, "success");
|
|
7095
|
+
}, [system, onNotify, t]);
|
|
7096
|
+
const resetToDefaults = React2.useCallback(() => {
|
|
6662
7097
|
setControls(defaultControls);
|
|
6663
7098
|
saveKeyboardMapping(defaultControls, system);
|
|
6664
|
-
onNotify?.(
|
|
6665
|
-
}, [defaultControls, system, onNotify]);
|
|
7099
|
+
onNotify?.(t.notifications.controlsReset, "info");
|
|
7100
|
+
}, [defaultControls, system, onNotify, t]);
|
|
6666
7101
|
return {
|
|
6667
7102
|
controls,
|
|
6668
7103
|
saveControls,
|
|
@@ -6688,16 +7123,17 @@ function useGameSession(props) {
|
|
|
6688
7123
|
canvasRef,
|
|
6689
7124
|
showToast
|
|
6690
7125
|
} = props;
|
|
7126
|
+
const t = useKoinTranslation();
|
|
6691
7127
|
const { controls, saveControls } = useControls(system, showToast);
|
|
6692
|
-
const [gamepadModalOpen, setGamepadModalOpen] =
|
|
6693
|
-
const [controlsModalOpen, setControlsModalOpen] =
|
|
7128
|
+
const [gamepadModalOpen, setGamepadModalOpen] = React2.useState(false);
|
|
7129
|
+
const [controlsModalOpen, setControlsModalOpen] = React2.useState(false);
|
|
6694
7130
|
const { gamepads, connectedCount } = useGamepad({
|
|
6695
7131
|
onConnect: (gamepad) => {
|
|
6696
7132
|
showToast(
|
|
6697
|
-
gamepad.name ||
|
|
7133
|
+
gamepad.name || t.notifications.controllerReady,
|
|
6698
7134
|
"gamepad",
|
|
6699
7135
|
{
|
|
6700
|
-
title:
|
|
7136
|
+
title: t.notifications.controllerConnected,
|
|
6701
7137
|
duration: 4e3,
|
|
6702
7138
|
action: {
|
|
6703
7139
|
label: "Configure",
|
|
@@ -6708,16 +7144,17 @@ function useGameSession(props) {
|
|
|
6708
7144
|
},
|
|
6709
7145
|
onDisconnect: () => {
|
|
6710
7146
|
showToast(
|
|
6711
|
-
|
|
7147
|
+
t.notifications.controllerDisconnected,
|
|
6712
7148
|
"warning",
|
|
6713
7149
|
{
|
|
6714
|
-
title:
|
|
7150
|
+
title: t.notifications.controllerDisconnected,
|
|
7151
|
+
// Title repeats or generic? Using same for now
|
|
6715
7152
|
duration: 3e3
|
|
6716
7153
|
}
|
|
6717
7154
|
);
|
|
6718
7155
|
}
|
|
6719
7156
|
});
|
|
6720
|
-
const gamepadBindings =
|
|
7157
|
+
const gamepadBindings = React2.useMemo(() => {
|
|
6721
7158
|
return [];
|
|
6722
7159
|
}, [gamepads.length]);
|
|
6723
7160
|
const nostalgist = useNostalgist({
|
|
@@ -6741,10 +7178,10 @@ function useGameSession(props) {
|
|
|
6741
7178
|
if (arcadeSystems.includes(system.toLowerCase())) {
|
|
6742
7179
|
setTimeout(() => {
|
|
6743
7180
|
showToast(
|
|
6744
|
-
|
|
7181
|
+
t.notifications.insertCoin,
|
|
6745
7182
|
"info",
|
|
6746
7183
|
{
|
|
6747
|
-
title:
|
|
7184
|
+
title: t.notifications.insertCoinTitle,
|
|
6748
7185
|
duration: 5e3
|
|
6749
7186
|
}
|
|
6750
7187
|
);
|
|
@@ -6762,7 +7199,7 @@ function useGameSession(props) {
|
|
|
6762
7199
|
toggleMute: toggleMuteInHook,
|
|
6763
7200
|
prepare
|
|
6764
7201
|
} = nostalgist;
|
|
6765
|
-
|
|
7202
|
+
React2.useEffect(() => {
|
|
6766
7203
|
return () => {
|
|
6767
7204
|
if (status === "running" || status === "paused") {
|
|
6768
7205
|
onSessionEnd?.();
|
|
@@ -6773,8 +7210,8 @@ function useGameSession(props) {
|
|
|
6773
7210
|
setVolume: setVolumeInHook,
|
|
6774
7211
|
toggleMute: toggleMuteInHook
|
|
6775
7212
|
});
|
|
6776
|
-
|
|
6777
|
-
|
|
7213
|
+
React2.useEffect(() => suppressEmulatorWarnings(), []);
|
|
7214
|
+
React2.useEffect(() => {
|
|
6778
7215
|
if (!romUrl || !system || status !== "idle") return;
|
|
6779
7216
|
const checkAndPrepare = async () => {
|
|
6780
7217
|
if (canvasRef.current && canvasRef.current.isConnected) {
|
|
@@ -6786,7 +7223,7 @@ function useGameSession(props) {
|
|
|
6786
7223
|
const rafId = requestAnimationFrame(checkAndPrepare);
|
|
6787
7224
|
return () => cancelAnimationFrame(rafId);
|
|
6788
7225
|
}, [romUrl, system, status, prepare]);
|
|
6789
|
-
const hardcoreRestrictions =
|
|
7226
|
+
const hardcoreRestrictions = React2.useMemo(() => {
|
|
6790
7227
|
const isHardcore = !!retroAchievementsConfig?.hardcore;
|
|
6791
7228
|
return {
|
|
6792
7229
|
isHardcore,
|
|
@@ -6857,17 +7294,17 @@ function useAutoSave({
|
|
|
6857
7294
|
queueRef,
|
|
6858
7295
|
autoSaveInterval = 6e4
|
|
6859
7296
|
}) {
|
|
6860
|
-
const [autoSavePaused, setAutoSavePaused] =
|
|
6861
|
-
const [autoSaveState, setAutoSaveState] =
|
|
6862
|
-
const [autoSaveProgress, setAutoSaveProgress] =
|
|
6863
|
-
const onAutoSaveRef =
|
|
6864
|
-
const nostalgistRef =
|
|
6865
|
-
|
|
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(() => {
|
|
6866
7303
|
onAutoSaveRef.current = onAutoSave;
|
|
6867
7304
|
nostalgistRef.current = nostalgist;
|
|
6868
7305
|
}, [onAutoSave, nostalgist]);
|
|
6869
|
-
const [loopTrigger, setLoopTrigger] =
|
|
6870
|
-
|
|
7306
|
+
const [loopTrigger, setLoopTrigger] = React2.useState(0);
|
|
7307
|
+
React2.useEffect(() => {
|
|
6871
7308
|
const currentNostalgist = nostalgist;
|
|
6872
7309
|
if (!onAutoSave || !currentNostalgist || currentNostalgist.status !== "running" || autoSavePaused) {
|
|
6873
7310
|
setAutoSaveState("idle");
|
|
@@ -6917,10 +7354,10 @@ function useAutoSave({
|
|
|
6917
7354
|
clearTimeout(saveTimeoutId);
|
|
6918
7355
|
};
|
|
6919
7356
|
}, [nostalgist?.status, autoSavePaused, !!onAutoSave, loopTrigger, autoSaveInterval]);
|
|
6920
|
-
const handleAutoSaveToggle =
|
|
7357
|
+
const handleAutoSaveToggle = React2.useCallback(() => {
|
|
6921
7358
|
setAutoSavePaused((prev) => !prev);
|
|
6922
7359
|
}, []);
|
|
6923
|
-
|
|
7360
|
+
React2.useEffect(() => {
|
|
6924
7361
|
if (!onAutoSave || !nostalgist || nostalgist.status !== "running") return;
|
|
6925
7362
|
const performEmergencySave = async () => {
|
|
6926
7363
|
try {
|
|
@@ -6982,12 +7419,13 @@ function useGameSaves({
|
|
|
6982
7419
|
onDeleteSaveState,
|
|
6983
7420
|
autoSaveInterval
|
|
6984
7421
|
}) {
|
|
6985
|
-
const
|
|
6986
|
-
const [
|
|
6987
|
-
const [
|
|
6988
|
-
const [
|
|
6989
|
-
const [
|
|
6990
|
-
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());
|
|
6991
7429
|
const {
|
|
6992
7430
|
autoSaveEnabled,
|
|
6993
7431
|
autoSavePaused,
|
|
@@ -7000,7 +7438,7 @@ function useGameSaves({
|
|
|
7000
7438
|
queueRef,
|
|
7001
7439
|
autoSaveInterval
|
|
7002
7440
|
});
|
|
7003
|
-
const refreshSlots =
|
|
7441
|
+
const refreshSlots = React2.useCallback(async () => {
|
|
7004
7442
|
if (!onGetSaveSlots) return;
|
|
7005
7443
|
setIsSlotLoading(true);
|
|
7006
7444
|
try {
|
|
@@ -7008,7 +7446,7 @@ function useGameSaves({
|
|
|
7008
7446
|
setSaveSlots(slots);
|
|
7009
7447
|
} catch (err) {
|
|
7010
7448
|
console.error("Failed to fetch save slots:", err);
|
|
7011
|
-
showToast("
|
|
7449
|
+
showToast(t.notifications.failedFetch, "error", { title: t.overlays.toast.error });
|
|
7012
7450
|
} finally {
|
|
7013
7451
|
setIsSlotLoading(false);
|
|
7014
7452
|
}
|
|
@@ -7025,7 +7463,7 @@ function useGameSaves({
|
|
|
7025
7463
|
const result = await nostalgist.saveStateWithBlob();
|
|
7026
7464
|
if (result) {
|
|
7027
7465
|
await onSaveState(0, result.blob, void 0);
|
|
7028
|
-
showToast(
|
|
7466
|
+
showToast(t.notifications.saved, "success", { title: t.overlays.toast.saved });
|
|
7029
7467
|
}
|
|
7030
7468
|
});
|
|
7031
7469
|
} else {
|
|
@@ -7039,7 +7477,7 @@ function useGameSaves({
|
|
|
7039
7477
|
a.download = `${fileName}.state`;
|
|
7040
7478
|
a.click();
|
|
7041
7479
|
URL.revokeObjectURL(url);
|
|
7042
|
-
showToast(
|
|
7480
|
+
showToast(t.notifications.downloaded, "success", { title: t.overlays.toast.saved });
|
|
7043
7481
|
}
|
|
7044
7482
|
});
|
|
7045
7483
|
}
|
|
@@ -7058,9 +7496,9 @@ function useGameSaves({
|
|
|
7058
7496
|
await queueRef.current.add(async () => {
|
|
7059
7497
|
await nostalgist.loadState(new Uint8Array(buffer));
|
|
7060
7498
|
});
|
|
7061
|
-
showToast(
|
|
7499
|
+
showToast(t.notifications.loaded, "success", { title: t.overlays.toast.loaded });
|
|
7062
7500
|
} else {
|
|
7063
|
-
showToast(
|
|
7501
|
+
showToast(t.notifications.noSaveFound, "error", { title: t.overlays.toast.error });
|
|
7064
7502
|
}
|
|
7065
7503
|
} else {
|
|
7066
7504
|
const input = document.createElement("input");
|
|
@@ -7073,7 +7511,7 @@ function useGameSaves({
|
|
|
7073
7511
|
await queueRef.current.add(async () => {
|
|
7074
7512
|
await nostalgist.loadState(new Uint8Array(buffer));
|
|
7075
7513
|
});
|
|
7076
|
-
showToast("
|
|
7514
|
+
showToast(t.notifications.loadedFile, "success", { title: t.overlays.toast.loaded });
|
|
7077
7515
|
}
|
|
7078
7516
|
};
|
|
7079
7517
|
input.click();
|
|
@@ -7098,14 +7536,14 @@ function useGameSaves({
|
|
|
7098
7536
|
console.warn("Screenshot failed", e);
|
|
7099
7537
|
}
|
|
7100
7538
|
await onSaveState(slot, result.blob, screen);
|
|
7101
|
-
showToast(
|
|
7539
|
+
showToast(t.notifications.savedSlot.replace("{{num}}", slot.toString()), "success", { title: t.overlays.toast.saved });
|
|
7102
7540
|
setSaveModalOpen(false);
|
|
7103
7541
|
resume();
|
|
7104
7542
|
}
|
|
7105
7543
|
});
|
|
7106
7544
|
} catch (err) {
|
|
7107
7545
|
console.error("Save failed:", err);
|
|
7108
|
-
showToast(
|
|
7546
|
+
showToast(t.notifications.failedSave, "error", { title: t.overlays.toast.error });
|
|
7109
7547
|
} finally {
|
|
7110
7548
|
setActioningSlot(null);
|
|
7111
7549
|
}
|
|
@@ -7119,15 +7557,15 @@ function useGameSaves({
|
|
|
7119
7557
|
await queueRef.current.add(async () => {
|
|
7120
7558
|
await nostalgist.loadState(new Uint8Array(buffer));
|
|
7121
7559
|
});
|
|
7122
|
-
showToast(
|
|
7560
|
+
showToast(t.notifications.loadedSlot.replace("{{num}}", slot.toString()), "success", { title: t.overlays.toast.loaded });
|
|
7123
7561
|
setSaveModalOpen(false);
|
|
7124
7562
|
resume();
|
|
7125
7563
|
} else {
|
|
7126
|
-
showToast(
|
|
7564
|
+
showToast(t.notifications.emptySlot, "error", { title: t.overlays.toast.error });
|
|
7127
7565
|
}
|
|
7128
7566
|
} catch (err) {
|
|
7129
7567
|
console.error("Load failed:", err);
|
|
7130
|
-
showToast(
|
|
7568
|
+
showToast(t.notifications.failedLoad, "error", { title: t.overlays.toast.error });
|
|
7131
7569
|
} finally {
|
|
7132
7570
|
setActioningSlot(null);
|
|
7133
7571
|
}
|
|
@@ -7139,11 +7577,11 @@ function useGameSaves({
|
|
|
7139
7577
|
setActioningSlot(slot);
|
|
7140
7578
|
try {
|
|
7141
7579
|
await onDeleteSaveState(slot);
|
|
7142
|
-
showToast(
|
|
7580
|
+
showToast(t.notifications.deletedSlot.replace("{{num}}", slot.toString()), "success", { title: t.overlays.toast.saved });
|
|
7143
7581
|
refreshSlots();
|
|
7144
7582
|
} catch (err) {
|
|
7145
7583
|
console.error("Delete failed:", err);
|
|
7146
|
-
showToast(
|
|
7584
|
+
showToast(t.notifications.failedDelete, "error", { title: t.overlays.toast.error });
|
|
7147
7585
|
} finally {
|
|
7148
7586
|
setActioningSlot(null);
|
|
7149
7587
|
}
|
|
@@ -7172,8 +7610,8 @@ function useGameCheats({
|
|
|
7172
7610
|
cheats = [],
|
|
7173
7611
|
onToggleCheat
|
|
7174
7612
|
}) {
|
|
7175
|
-
const [cheatsModalOpen, setCheatsModalOpen] =
|
|
7176
|
-
const [activeCheats, setActiveCheats] =
|
|
7613
|
+
const [cheatsModalOpen, setCheatsModalOpen] = React2.useState(false);
|
|
7614
|
+
const [activeCheats, setActiveCheats] = React2.useState(/* @__PURE__ */ new Set());
|
|
7177
7615
|
const handleToggleCheat = (cheatId) => {
|
|
7178
7616
|
if (!nostalgist) return;
|
|
7179
7617
|
const newActiveCheats = new Set(activeCheats);
|
|
@@ -7205,16 +7643,16 @@ function useGameCheats({
|
|
|
7205
7643
|
function useGameRecording({
|
|
7206
7644
|
getCanvasElement
|
|
7207
7645
|
}) {
|
|
7208
|
-
const [isRecording, setIsRecording] =
|
|
7209
|
-
const [isPaused, setIsPaused] =
|
|
7210
|
-
const [recordingDuration, setRecordingDuration] =
|
|
7211
|
-
const mediaRecorderRef =
|
|
7212
|
-
const chunksRef =
|
|
7213
|
-
const startTimeRef =
|
|
7214
|
-
const pausedTimeRef =
|
|
7215
|
-
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);
|
|
7216
7654
|
const isSupported = typeof MediaRecorder !== "undefined" && MediaRecorder.isTypeSupported("video/webm");
|
|
7217
|
-
const startRecording =
|
|
7655
|
+
const startRecording = React2.useCallback(() => {
|
|
7218
7656
|
const canvas = getCanvasElement();
|
|
7219
7657
|
if (!canvas || !isSupported) {
|
|
7220
7658
|
console.warn("[Recording] Canvas not found or MediaRecorder not supported");
|
|
@@ -7252,7 +7690,7 @@ function useGameRecording({
|
|
|
7252
7690
|
console.error("[Recording] Failed to start:", err);
|
|
7253
7691
|
}
|
|
7254
7692
|
}, [getCanvasElement, isSupported, isPaused]);
|
|
7255
|
-
const stopRecording =
|
|
7693
|
+
const stopRecording = React2.useCallback(async () => {
|
|
7256
7694
|
return new Promise((resolve) => {
|
|
7257
7695
|
const mediaRecorder = mediaRecorderRef.current;
|
|
7258
7696
|
if (!mediaRecorder || mediaRecorder.state === "inactive") {
|
|
@@ -7274,7 +7712,7 @@ function useGameRecording({
|
|
|
7274
7712
|
mediaRecorder.stop();
|
|
7275
7713
|
});
|
|
7276
7714
|
}, []);
|
|
7277
|
-
const pauseRecording =
|
|
7715
|
+
const pauseRecording = React2.useCallback(() => {
|
|
7278
7716
|
const mediaRecorder = mediaRecorderRef.current;
|
|
7279
7717
|
if (mediaRecorder && mediaRecorder.state === "recording") {
|
|
7280
7718
|
mediaRecorder.pause();
|
|
@@ -7283,7 +7721,7 @@ function useGameRecording({
|
|
|
7283
7721
|
console.log("[Recording] Paused");
|
|
7284
7722
|
}
|
|
7285
7723
|
}, []);
|
|
7286
|
-
const resumeRecording =
|
|
7724
|
+
const resumeRecording = React2.useCallback(() => {
|
|
7287
7725
|
const mediaRecorder = mediaRecorderRef.current;
|
|
7288
7726
|
if (mediaRecorder && mediaRecorder.state === "paused") {
|
|
7289
7727
|
pausedTimeRef.current = Date.now() - pausedTimeRef.current;
|
|
@@ -7292,7 +7730,7 @@ function useGameRecording({
|
|
|
7292
7730
|
console.log("[Recording] Resumed");
|
|
7293
7731
|
}
|
|
7294
7732
|
}, []);
|
|
7295
|
-
|
|
7733
|
+
React2.useEffect(() => {
|
|
7296
7734
|
return () => {
|
|
7297
7735
|
if (mediaRecorderRef.current && mediaRecorderRef.current.state !== "inactive") {
|
|
7298
7736
|
mediaRecorderRef.current.stop();
|
|
@@ -7466,9 +7904,9 @@ var DEFAULT_SETTINGS = {
|
|
|
7466
7904
|
showInputDisplay: false
|
|
7467
7905
|
};
|
|
7468
7906
|
function usePlayerPersistence(onSettingsChange) {
|
|
7469
|
-
const [settings, setSettings] =
|
|
7470
|
-
const [isLoaded, setIsLoaded] =
|
|
7471
|
-
|
|
7907
|
+
const [settings, setSettings] = React2.useState(DEFAULT_SETTINGS);
|
|
7908
|
+
const [isLoaded, setIsLoaded] = React2.useState(false);
|
|
7909
|
+
React2.useEffect(() => {
|
|
7472
7910
|
try {
|
|
7473
7911
|
const stored = localStorage.getItem(STORAGE_KEY2);
|
|
7474
7912
|
if (stored) {
|
|
@@ -7480,7 +7918,7 @@ function usePlayerPersistence(onSettingsChange) {
|
|
|
7480
7918
|
}
|
|
7481
7919
|
setIsLoaded(true);
|
|
7482
7920
|
}, []);
|
|
7483
|
-
const updateSettings =
|
|
7921
|
+
const updateSettings = React2.useCallback((updates) => {
|
|
7484
7922
|
setSettings((prev) => {
|
|
7485
7923
|
const next = { ...prev, ...updates };
|
|
7486
7924
|
try {
|
|
@@ -7521,17 +7959,496 @@ var sendTelemetry = (eventName, params = {}) => {
|
|
|
7521
7959
|
} catch (e) {
|
|
7522
7960
|
}
|
|
7523
7961
|
};
|
|
7524
|
-
|
|
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) {
|
|
7525
8441
|
const { settings, updateSettings, isLoaded: settingsLoaded } = usePlayerPersistence();
|
|
7526
|
-
|
|
8442
|
+
React2.useEffect(() => {
|
|
7527
8443
|
sendTelemetry("game_start", {
|
|
7528
8444
|
system: props.system,
|
|
7529
8445
|
core: props.core,
|
|
7530
8446
|
game: props.title || "unknown"
|
|
7531
8447
|
});
|
|
7532
8448
|
}, [props.system, props.core, props.title]);
|
|
7533
|
-
const [biosModalOpen, setBiosModalOpen] =
|
|
7534
|
-
const [showShortcutsModal, setShowShortcutsModal] =
|
|
8449
|
+
const [biosModalOpen, setBiosModalOpen] = React2.useState(false);
|
|
8450
|
+
const [showShortcutsModal, setShowShortcutsModal] = React2.useState(false);
|
|
8451
|
+
const [settingsModalOpen, setSettingsModalOpen] = React2.useState(false);
|
|
7535
8452
|
const effectiveShader = props.shader !== void 0 ? props.shader : settings.shader;
|
|
7536
8453
|
const {
|
|
7537
8454
|
// Refs
|
|
@@ -7627,72 +8544,76 @@ var GamePlayer = React5.memo(function GamePlayer2(props) {
|
|
|
7627
8544
|
status,
|
|
7628
8545
|
isPerformanceMode
|
|
7629
8546
|
} = nostalgist;
|
|
7630
|
-
|
|
8547
|
+
React2.useEffect(() => {
|
|
7631
8548
|
if (status === "running") {
|
|
7632
8549
|
console.log("[Koin Debug] Status:", status);
|
|
7633
8550
|
console.log("[Koin Debug] isPerformanceMode:", isPerformanceMode);
|
|
7634
8551
|
console.log("[Koin Debug] crossOriginIsolated:", typeof window !== "undefined" ? window.crossOriginIsolated : "N/A");
|
|
7635
8552
|
}
|
|
7636
8553
|
}, [status, isPerformanceMode]);
|
|
7637
|
-
|
|
8554
|
+
React2.useEffect(() => {
|
|
7638
8555
|
if (settingsLoaded) {
|
|
7639
8556
|
setVolume(settings.volume);
|
|
7640
8557
|
if (muted !== settings.muted) toggleMute();
|
|
7641
8558
|
}
|
|
7642
8559
|
}, [settingsLoaded]);
|
|
7643
8560
|
const { system, systemColor = "#00FF41", cheats = [], onExit } = props;
|
|
7644
|
-
const handlePauseToggle =
|
|
8561
|
+
const handlePauseToggle = React2.useCallback(() => {
|
|
7645
8562
|
status === "ready" ? start() : togglePause();
|
|
7646
8563
|
}, [status, start, togglePause]);
|
|
7647
|
-
const handleScreenshot =
|
|
8564
|
+
const handleScreenshot = React2.useCallback(async () => {
|
|
7648
8565
|
const result = await screenshot();
|
|
7649
8566
|
if (result && props.onScreenshotCaptured) {
|
|
7650
8567
|
props.onScreenshotCaptured(result);
|
|
7651
8568
|
}
|
|
7652
8569
|
}, [screenshot, props.onScreenshotCaptured]);
|
|
7653
|
-
const handleShowControls =
|
|
8570
|
+
const handleShowControls = React2.useCallback(() => {
|
|
7654
8571
|
pause();
|
|
7655
8572
|
setControlsModalOpen(true);
|
|
7656
8573
|
}, [pause, setControlsModalOpen]);
|
|
7657
|
-
const handleShowCheats =
|
|
8574
|
+
const handleShowCheats = React2.useCallback(() => {
|
|
7658
8575
|
pause();
|
|
7659
8576
|
setCheatsModalOpen(true);
|
|
7660
8577
|
}, [pause, setCheatsModalOpen]);
|
|
7661
|
-
const handleShowRA =
|
|
8578
|
+
const handleShowRA = React2.useCallback(() => {
|
|
7662
8579
|
pause();
|
|
7663
8580
|
setRaSidebarOpen(true);
|
|
7664
8581
|
}, [pause, setRaSidebarOpen]);
|
|
7665
|
-
const handleShowGamepadSettings =
|
|
8582
|
+
const handleShowGamepadSettings = React2.useCallback(() => {
|
|
7666
8583
|
pause();
|
|
7667
8584
|
setGamepadModalOpen(true);
|
|
7668
8585
|
}, [pause, setGamepadModalOpen]);
|
|
7669
|
-
const
|
|
8586
|
+
const handleShowSettings = React2.useCallback(() => {
|
|
8587
|
+
pause();
|
|
8588
|
+
setSettingsModalOpen(true);
|
|
8589
|
+
}, [pause, setSettingsModalOpen]);
|
|
8590
|
+
const handleExitClick = React2.useCallback(() => {
|
|
7670
8591
|
onExit?.();
|
|
7671
8592
|
}, [onExit]);
|
|
7672
|
-
const handleBiosSelection =
|
|
8593
|
+
const handleBiosSelection = React2.useCallback(() => {
|
|
7673
8594
|
setBiosModalOpen(true);
|
|
7674
8595
|
}, [setBiosModalOpen]);
|
|
7675
|
-
const handleVolumeChange =
|
|
8596
|
+
const handleVolumeChange = React2.useCallback((val) => {
|
|
7676
8597
|
setVolume(val);
|
|
7677
8598
|
updateSettings({ volume: val });
|
|
7678
8599
|
}, [setVolume, updateSettings]);
|
|
7679
|
-
const handleToggleMute =
|
|
8600
|
+
const handleToggleMute = React2.useCallback(() => {
|
|
7680
8601
|
toggleMute();
|
|
7681
8602
|
updateSettings({ muted: !muted });
|
|
7682
8603
|
}, [toggleMute, updateSettings, muted]);
|
|
7683
|
-
const handleShaderChange =
|
|
8604
|
+
const handleShaderChange = React2.useCallback((newShader, requiresRestart) => {
|
|
7684
8605
|
updateSettings({ shader: newShader });
|
|
7685
8606
|
if (props.onShaderChange) {
|
|
7686
8607
|
props.onShaderChange(newShader, requiresRestart);
|
|
7687
8608
|
}
|
|
7688
8609
|
}, [updateSettings, props.onShaderChange]);
|
|
7689
|
-
const handleTogglePerformanceOverlay =
|
|
8610
|
+
const handleTogglePerformanceOverlay = React2.useCallback(() => {
|
|
7690
8611
|
updateSettings({ showPerformanceOverlay: !settings.showPerformanceOverlay });
|
|
7691
8612
|
}, [updateSettings, settings.showPerformanceOverlay]);
|
|
7692
|
-
const handleToggleInputDisplay =
|
|
8613
|
+
const handleToggleInputDisplay = React2.useCallback(() => {
|
|
7693
8614
|
updateSettings({ showInputDisplay: !settings.showInputDisplay });
|
|
7694
8615
|
}, [updateSettings, settings.showInputDisplay]);
|
|
7695
|
-
const handleToggleRecording =
|
|
8616
|
+
const handleToggleRecording = React2.useCallback(async () => {
|
|
7696
8617
|
if (!recordingSupported) {
|
|
7697
8618
|
console.warn("[Recording] Not supported in this browser");
|
|
7698
8619
|
return;
|
|
@@ -7713,14 +8634,14 @@ var GamePlayer = React5.memo(function GamePlayer2(props) {
|
|
|
7713
8634
|
startRecording();
|
|
7714
8635
|
}
|
|
7715
8636
|
}, [isRecording, recordingSupported, startRecording, stopRecording]);
|
|
7716
|
-
const handleToggleShortcuts =
|
|
8637
|
+
const handleToggleShortcuts = React2.useCallback(() => {
|
|
7717
8638
|
setShowShortcutsModal((prev) => {
|
|
7718
8639
|
if (!prev) pause();
|
|
7719
8640
|
else resume();
|
|
7720
8641
|
return !prev;
|
|
7721
8642
|
});
|
|
7722
8643
|
}, [pause, resume]);
|
|
7723
|
-
|
|
8644
|
+
React2.useEffect(() => {
|
|
7724
8645
|
const handleKeyDown = (e) => {
|
|
7725
8646
|
if (e.key === "F1") {
|
|
7726
8647
|
e.preventDefault();
|
|
@@ -7877,6 +8798,7 @@ var GamePlayer = React5.memo(function GamePlayer2(props) {
|
|
|
7877
8798
|
systemColor,
|
|
7878
8799
|
gamepadCount: connectedCount,
|
|
7879
8800
|
onGamepadSettings: handleShowGamepadSettings,
|
|
8801
|
+
onSettings: handleShowSettings,
|
|
7880
8802
|
volume,
|
|
7881
8803
|
isMuted: muted,
|
|
7882
8804
|
onVolumeChange: handleVolumeChange,
|
|
@@ -7942,7 +8864,11 @@ var GamePlayer = React5.memo(function GamePlayer2(props) {
|
|
|
7942
8864
|
setBiosModalOpen,
|
|
7943
8865
|
availableBios: props.availableBios,
|
|
7944
8866
|
currentBiosId: props.currentBiosId,
|
|
7945
|
-
onSelectBios: props.onSelectBios
|
|
8867
|
+
onSelectBios: props.onSelectBios,
|
|
8868
|
+
settingsModalOpen,
|
|
8869
|
+
setSettingsModalOpen,
|
|
8870
|
+
currentLanguage: props.currentLanguage,
|
|
8871
|
+
onLanguageChange: props.onLanguageChange
|
|
7946
8872
|
}
|
|
7947
8873
|
),
|
|
7948
8874
|
!isMobile && /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -7970,6 +8896,27 @@ var GamePlayer = React5.memo(function GamePlayer2(props) {
|
|
|
7970
8896
|
}
|
|
7971
8897
|
) });
|
|
7972
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
|
+
});
|
|
7973
8920
|
var GamePlayer_default = GamePlayer;
|
|
7974
8921
|
function AchievementPopup({
|
|
7975
8922
|
achievement,
|
|
@@ -7977,9 +8924,9 @@ function AchievementPopup({
|
|
|
7977
8924
|
onDismiss,
|
|
7978
8925
|
autoDismissMs = 5e3
|
|
7979
8926
|
}) {
|
|
7980
|
-
const [isVisible, setIsVisible] =
|
|
7981
|
-
const [isExiting, setIsExiting] =
|
|
7982
|
-
|
|
8927
|
+
const [isVisible, setIsVisible] = React2.useState(false);
|
|
8928
|
+
const [isExiting, setIsExiting] = React2.useState(false);
|
|
8929
|
+
React2.useEffect(() => {
|
|
7983
8930
|
requestAnimationFrame(() => {
|
|
7984
8931
|
setIsVisible(true);
|
|
7985
8932
|
});
|
|
@@ -8056,15 +9003,15 @@ var SHADER_PRESETS = [
|
|
|
8056
9003
|
{ id: "handheld/lcd-grid-v2", name: "LCD Grid", description: "Game Boy style LCD effect" },
|
|
8057
9004
|
{ id: "scanlines", name: "Scanlines", description: "Simple horizontal scanlines" }
|
|
8058
9005
|
];
|
|
8059
|
-
var ShaderSelector =
|
|
9006
|
+
var ShaderSelector = React2.memo(function ShaderSelector2({
|
|
8060
9007
|
currentShader,
|
|
8061
9008
|
onShaderChange,
|
|
8062
9009
|
disabled = false,
|
|
8063
9010
|
systemColor = "#00FF41"
|
|
8064
9011
|
}) {
|
|
8065
|
-
const [isOpen, setIsOpen] =
|
|
8066
|
-
const dropdownRef =
|
|
8067
|
-
|
|
9012
|
+
const [isOpen, setIsOpen] = React2.useState(false);
|
|
9013
|
+
const dropdownRef = React2.useRef(null);
|
|
9014
|
+
React2.useEffect(() => {
|
|
8068
9015
|
const handleClickOutside = (e) => {
|
|
8069
9016
|
if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
|
|
8070
9017
|
setIsOpen(false);
|
|
@@ -8131,18 +9078,18 @@ var ShaderSelector = React5.memo(function ShaderSelector2({
|
|
|
8131
9078
|
] });
|
|
8132
9079
|
});
|
|
8133
9080
|
var ShaderSelector_default = ShaderSelector;
|
|
8134
|
-
var
|
|
9081
|
+
var SHORTCUTS = [
|
|
8135
9082
|
{ key: "F1", description: "Help" },
|
|
8136
9083
|
{ key: "F3", description: "FPS Overlay" },
|
|
8137
9084
|
{ key: "F4", description: "Input Display" },
|
|
8138
9085
|
{ key: "F5", description: "Record" },
|
|
8139
9086
|
{ key: "F9", description: "Mute" }
|
|
8140
9087
|
];
|
|
8141
|
-
var ShortcutsReference =
|
|
9088
|
+
var ShortcutsReference = React2.memo(function ShortcutsReference2({
|
|
8142
9089
|
systemColor = "#00FF41",
|
|
8143
9090
|
isExpanded: initialExpanded = false
|
|
8144
9091
|
}) {
|
|
8145
|
-
const [isExpanded, setIsExpanded] =
|
|
9092
|
+
const [isExpanded, setIsExpanded] = React2.useState(initialExpanded);
|
|
8146
9093
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
8147
9094
|
"div",
|
|
8148
9095
|
{
|
|
@@ -8166,7 +9113,7 @@ var ShortcutsReference = React5.memo(function ShortcutsReference2({
|
|
|
8166
9113
|
]
|
|
8167
9114
|
}
|
|
8168
9115
|
),
|
|
8169
|
-
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: [
|
|
8170
9117
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-white/60", children: description }),
|
|
8171
9118
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
8172
9119
|
"kbd",
|
|
@@ -8199,6 +9146,7 @@ exports.DEFAULT_KEYBOARD = DEFAULT_KEYBOARD;
|
|
|
8199
9146
|
exports.DPAD_BUTTONS = DPAD_BUTTONS;
|
|
8200
9147
|
exports.FACE_BUTTONS = FACE_BUTTONS;
|
|
8201
9148
|
exports.GamePlayer = GamePlayer_default;
|
|
9149
|
+
exports.KoinI18nProvider = KoinI18nProvider;
|
|
8202
9150
|
exports.PERFORMANCE_TIER_1_SYSTEMS = PERFORMANCE_TIER_1_SYSTEMS;
|
|
8203
9151
|
exports.PERFORMANCE_TIER_2_SYSTEMS = PERFORMANCE_TIER_2_SYSTEMS;
|
|
8204
9152
|
exports.RASidebar = RASidebar;
|
|
@@ -8220,10 +9168,15 @@ exports.clearAllControls = clearAllControls;
|
|
|
8220
9168
|
exports.consoleHasButton = consoleHasButton;
|
|
8221
9169
|
exports.detectControllerBrand = detectControllerBrand;
|
|
8222
9170
|
exports.detectSystem = detectSystem;
|
|
9171
|
+
exports.en = en;
|
|
9172
|
+
exports.es = es;
|
|
9173
|
+
exports.fetchAndCacheRom = fetchAndCacheRom;
|
|
8223
9174
|
exports.formatGamepadButton = formatGamepadButton;
|
|
8224
9175
|
exports.formatKeyCode = formatKeyCode;
|
|
9176
|
+
exports.fr = fr;
|
|
8225
9177
|
exports.gamepadToRetroArchConfig = gamepadToRetroArchConfig;
|
|
8226
9178
|
exports.getAchievementBadgeUrl = getAchievementBadgeUrl;
|
|
9179
|
+
exports.getCachedRom = getCachedRom;
|
|
8227
9180
|
exports.getConsoleButtons = getConsoleButtons;
|
|
8228
9181
|
exports.getConsoleCapabilities = getConsoleCapabilities;
|
|
8229
9182
|
exports.getConsoleKeyboardDefaults = getConsoleKeyboardDefaults;
|
|
@@ -8249,6 +9202,7 @@ exports.saveKeyboardMapping = saveKeyboardMapping;
|
|
|
8249
9202
|
exports.systemsMatch = systemsMatch;
|
|
8250
9203
|
exports.useGameRecording = useGameRecording;
|
|
8251
9204
|
exports.useGamepad = useGamepad;
|
|
9205
|
+
exports.useKoinTranslation = useKoinTranslation;
|
|
8252
9206
|
exports.useNostalgist = useNostalgist;
|
|
8253
9207
|
exports.useToast = useToast;
|
|
8254
9208
|
//# sourceMappingURL=index.js.map
|