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