koin.js 1.0.15 → 1.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +22 -6
- package/dist/index.d.ts +22 -6
- package/dist/index.js +1059 -545
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1060 -546
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +64 -0
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React2, { memo, useState, useEffect, useRef, createContext, useMemo, useCallback, useContext } from 'react';
|
|
2
|
-
import { Gauge, Play, Pause, RotateCcw, Rewind, Save, Download, Camera, Video, Circle, Monitor, ChevronDown, RefreshCw, HelpCircle, Maximize, Gamepad2, Joystick, Code, Settings, Power, Square, Keyboard, ChevronUp, VolumeX, Volume1, Volume2, Loader2, Trophy, X, AlertTriangle, Minimize2, List, PauseCircle, Check, Clock, Lock, Unlock, Move, User, Copy,
|
|
2
|
+
import { Gauge, Play, Pause, RotateCcw, Rewind, Save, Download, Camera, Video, Circle, Monitor, ChevronDown, RefreshCw, HelpCircle, Maximize, Gamepad2, Joystick, Code, Settings, Power, Square, Keyboard, Hand, Zap, ChevronUp, VolumeX, Volume1, Volume2, Loader2, Trophy, X, AlertTriangle, Minimize2, List, PauseCircle, Check, Clock, Lock, Unlock, StopCircle, ZapOff, Move, User, Copy, 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 { createPortal } from 'react-dom';
|
|
5
5
|
import { Nostalgist } from 'nostalgist';
|
|
@@ -328,9 +328,11 @@ var en = {
|
|
|
328
328
|
cheats: "Cheats",
|
|
329
329
|
retroAchievements: "RetroAchievements",
|
|
330
330
|
shortcuts: "Shortcuts",
|
|
331
|
-
exit: "Exit",
|
|
331
|
+
exit: "Exit Game",
|
|
332
332
|
language: "Language",
|
|
333
|
-
selectLanguage: "Select Language"
|
|
333
|
+
selectLanguage: "Select Language",
|
|
334
|
+
haptics: "Haptic Feedback",
|
|
335
|
+
enableHaptics: "Vibration"
|
|
334
336
|
},
|
|
335
337
|
overlay: {
|
|
336
338
|
play: "PLAY",
|
|
@@ -407,13 +409,16 @@ var en = {
|
|
|
407
409
|
},
|
|
408
410
|
cheats: {
|
|
409
411
|
title: "Cheats",
|
|
410
|
-
addCheat: "Add Cheat",
|
|
411
|
-
available: "
|
|
412
|
-
emptyTitle: "No
|
|
413
|
-
emptyDesc: "
|
|
414
|
-
copy: "Copy
|
|
415
|
-
active: "
|
|
416
|
-
toggleHint: "Click
|
|
412
|
+
addCheat: "Add Manual Cheat",
|
|
413
|
+
available: "Available Cheats",
|
|
414
|
+
emptyTitle: "No Cheats Available",
|
|
415
|
+
emptyDesc: "This game does not have any known cheats.",
|
|
416
|
+
copy: "Copy",
|
|
417
|
+
active: "Active",
|
|
418
|
+
toggleHint: "Click to toggle",
|
|
419
|
+
codePlaceholder: "Enter cheat code (e.g. 00C-048-E6E)",
|
|
420
|
+
descPlaceholder: "Cheat description (optional)",
|
|
421
|
+
add: "Add Cheat"
|
|
417
422
|
},
|
|
418
423
|
saveSlots: {
|
|
419
424
|
title: "Save States",
|
|
@@ -821,7 +826,7 @@ var SaveLoadControls = memo(function SaveLoadControls2({
|
|
|
821
826
|
{
|
|
822
827
|
progress: autoSaveProgress,
|
|
823
828
|
state: autoSavePaused ? "idle" : autoSaveState,
|
|
824
|
-
intervalSeconds:
|
|
829
|
+
intervalSeconds: 60,
|
|
825
830
|
isPaused: autoSavePaused,
|
|
826
831
|
onClick: onAutoSaveToggle
|
|
827
832
|
}
|
|
@@ -2586,7 +2591,8 @@ function useTouchHandlers({
|
|
|
2586
2591
|
onPress,
|
|
2587
2592
|
onPressDown,
|
|
2588
2593
|
onRelease,
|
|
2589
|
-
onPositionChange
|
|
2594
|
+
onPositionChange,
|
|
2595
|
+
hapticsEnabled = true
|
|
2590
2596
|
}) {
|
|
2591
2597
|
const isDraggingRef = useRef(false);
|
|
2592
2598
|
const drag = useDrag({
|
|
@@ -2612,7 +2618,7 @@ function useTouchHandlers({
|
|
|
2612
2618
|
const touch = e.touches[0];
|
|
2613
2619
|
e.preventDefault();
|
|
2614
2620
|
e.stopPropagation();
|
|
2615
|
-
if (navigator.vibrate) {
|
|
2621
|
+
if (hapticsEnabled && navigator.vibrate) {
|
|
2616
2622
|
navigator.vibrate(8);
|
|
2617
2623
|
}
|
|
2618
2624
|
if (isSystemButton) {
|
|
@@ -2849,7 +2855,11 @@ var VirtualButton = React2.memo(function VirtualButton2({
|
|
|
2849
2855
|
customPosition,
|
|
2850
2856
|
onPositionChange,
|
|
2851
2857
|
isLandscape = false,
|
|
2852
|
-
console: console2 = ""
|
|
2858
|
+
console: console2 = "",
|
|
2859
|
+
hapticsEnabled = true,
|
|
2860
|
+
mode = "normal",
|
|
2861
|
+
isHeld = false,
|
|
2862
|
+
isInTurbo = false
|
|
2853
2863
|
}) {
|
|
2854
2864
|
const t = useKoinTranslation();
|
|
2855
2865
|
const buttonRef = useRef(null);
|
|
@@ -2876,7 +2886,9 @@ var VirtualButton = React2.memo(function VirtualButton2({
|
|
|
2876
2886
|
onPress,
|
|
2877
2887
|
onPressDown,
|
|
2878
2888
|
onRelease,
|
|
2879
|
-
onPositionChange
|
|
2889
|
+
onPositionChange,
|
|
2890
|
+
hapticsEnabled
|
|
2891
|
+
// Pass haptics setting
|
|
2880
2892
|
});
|
|
2881
2893
|
useTouchEvents(buttonRef, {
|
|
2882
2894
|
onTouchStart: handleTouchStart,
|
|
@@ -2904,7 +2916,7 @@ var VirtualButton = React2.memo(function VirtualButton2({
|
|
|
2904
2916
|
width = `${config.size * 2}px`;
|
|
2905
2917
|
}
|
|
2906
2918
|
}
|
|
2907
|
-
return /* @__PURE__ */
|
|
2919
|
+
return /* @__PURE__ */ jsxs(
|
|
2908
2920
|
"button",
|
|
2909
2921
|
{
|
|
2910
2922
|
ref: buttonRef,
|
|
@@ -2944,55 +2956,371 @@ var VirtualButton = React2.memo(function VirtualButton2({
|
|
|
2944
2956
|
},
|
|
2945
2957
|
"aria-label": label,
|
|
2946
2958
|
onContextMenu: (e) => e.preventDefault(),
|
|
2947
|
-
children:
|
|
2959
|
+
children: [
|
|
2960
|
+
(mode !== "normal" || isHeld || isInTurbo) && /* @__PURE__ */ jsx(
|
|
2961
|
+
"span",
|
|
2962
|
+
{
|
|
2963
|
+
className: "absolute -top-1 -right-1 rounded-full flex items-center justify-center",
|
|
2964
|
+
style: {
|
|
2965
|
+
width: "16px",
|
|
2966
|
+
height: "16px",
|
|
2967
|
+
backgroundColor: isHeld ? "#22c55e" : isInTurbo ? "#fbbf24" : "rgba(255,255,255,0.2)",
|
|
2968
|
+
animation: isInTurbo ? "pulse 0.5s infinite" : "none"
|
|
2969
|
+
},
|
|
2970
|
+
children: isHeld ? /* @__PURE__ */ jsx(Hand, { size: 10, color: "white", strokeWidth: 3 }) : isInTurbo ? /* @__PURE__ */ jsx(Zap, { size: 10, color: "white", fill: "white" }) : mode === "hold" ? /* @__PURE__ */ jsx(Hand, { size: 10, color: "white" }) : mode === "turbo" ? /* @__PURE__ */ jsx(Zap, { size: 10, color: "white" }) : null
|
|
2971
|
+
}
|
|
2972
|
+
),
|
|
2973
|
+
/* @__PURE__ */ jsx("span", { className: "drop-shadow-md", children: label })
|
|
2974
|
+
]
|
|
2948
2975
|
}
|
|
2949
2976
|
);
|
|
2950
2977
|
});
|
|
2951
2978
|
var VirtualButton_default = VirtualButton;
|
|
2979
|
+
var CENTER_TOUCH_RADIUS = 0.35;
|
|
2980
|
+
var Dpad = React2.memo(function Dpad2({
|
|
2981
|
+
size = 180,
|
|
2982
|
+
x,
|
|
2983
|
+
y,
|
|
2984
|
+
containerWidth,
|
|
2985
|
+
containerHeight,
|
|
2986
|
+
systemColor = "#00FF41",
|
|
2987
|
+
isLandscape = false,
|
|
2988
|
+
customPosition,
|
|
2989
|
+
onPositionChange,
|
|
2990
|
+
hapticsEnabled = true,
|
|
2991
|
+
onButtonDown,
|
|
2992
|
+
onButtonUp
|
|
2993
|
+
}) {
|
|
2994
|
+
const dpadRef = useRef(null);
|
|
2995
|
+
const activeTouchRef = useRef(null);
|
|
2996
|
+
const activeDirectionsRef = useRef(/* @__PURE__ */ new Set());
|
|
2997
|
+
const upPathRef = useRef(null);
|
|
2998
|
+
const downPathRef = useRef(null);
|
|
2999
|
+
const leftPathRef = useRef(null);
|
|
3000
|
+
const rightPathRef = useRef(null);
|
|
3001
|
+
const centerCircleRef = useRef(null);
|
|
3002
|
+
const displayX = customPosition ? customPosition.x : x;
|
|
3003
|
+
const displayY = customPosition ? customPosition.y : y;
|
|
3004
|
+
const releaseAllDirections = useCallback(() => {
|
|
3005
|
+
activeDirectionsRef.current.forEach((dir) => onButtonUp(dir));
|
|
3006
|
+
activeDirectionsRef.current = /* @__PURE__ */ new Set();
|
|
3007
|
+
}, [onButtonUp]);
|
|
3008
|
+
const drag = useDrag({
|
|
3009
|
+
elementSize: size,
|
|
3010
|
+
displayX,
|
|
3011
|
+
displayY,
|
|
3012
|
+
containerWidth,
|
|
3013
|
+
containerHeight,
|
|
3014
|
+
onPositionChange,
|
|
3015
|
+
centerThreshold: CENTER_TOUCH_RADIUS,
|
|
3016
|
+
onDragStart: () => {
|
|
3017
|
+
releaseAllDirections();
|
|
3018
|
+
updateVisuals(/* @__PURE__ */ new Set());
|
|
3019
|
+
}
|
|
3020
|
+
});
|
|
3021
|
+
const getDirectionsFromTouch = useCallback((touchX, touchY, rect) => {
|
|
3022
|
+
const centerX = rect.left + rect.width / 2;
|
|
3023
|
+
const centerY = rect.top + rect.height / 2;
|
|
3024
|
+
const dx = touchX - centerX;
|
|
3025
|
+
const dy = touchY - centerY;
|
|
3026
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
3027
|
+
const deadZone = rect.width / 2 * 0.15;
|
|
3028
|
+
if (distance < deadZone) return /* @__PURE__ */ new Set();
|
|
3029
|
+
const directions = /* @__PURE__ */ new Set();
|
|
3030
|
+
const angle = Math.atan2(dy, dx) * (180 / Math.PI);
|
|
3031
|
+
if (angle >= -150 && angle <= -30) directions.add("up");
|
|
3032
|
+
if (angle >= 30 && angle <= 150) directions.add("down");
|
|
3033
|
+
if (angle >= 120 || angle <= -120) directions.add("left");
|
|
3034
|
+
if (angle >= -60 && angle <= 60) directions.add("right");
|
|
3035
|
+
return directions;
|
|
3036
|
+
}, []);
|
|
3037
|
+
const updateVisuals = useCallback((directions) => {
|
|
3038
|
+
const activeFill = `${systemColor}80`;
|
|
3039
|
+
const inactiveFill = "rgba(255, 255, 255, 0.05)";
|
|
3040
|
+
const activeStroke = systemColor;
|
|
3041
|
+
const inactiveStroke = "rgba(255, 255, 255, 0.2)";
|
|
3042
|
+
const glow = `0 0 15px ${systemColor}`;
|
|
3043
|
+
const updatePart = (ref, isActive) => {
|
|
3044
|
+
if (ref.current) {
|
|
3045
|
+
ref.current.style.fill = isActive ? activeFill : inactiveFill;
|
|
3046
|
+
ref.current.style.stroke = isActive ? activeStroke : inactiveStroke;
|
|
3047
|
+
ref.current.style.filter = isActive ? `drop-shadow(${glow})` : "none";
|
|
3048
|
+
ref.current.style.transform = isActive ? "scale(0.98)" : "scale(1)";
|
|
3049
|
+
ref.current.style.transformOrigin = "center";
|
|
3050
|
+
}
|
|
3051
|
+
};
|
|
3052
|
+
updatePart(upPathRef, directions.has("up"));
|
|
3053
|
+
updatePart(downPathRef, directions.has("down"));
|
|
3054
|
+
updatePart(leftPathRef, directions.has("left"));
|
|
3055
|
+
updatePart(rightPathRef, directions.has("right"));
|
|
3056
|
+
if (centerCircleRef.current) {
|
|
3057
|
+
const isAny = directions.size > 0;
|
|
3058
|
+
centerCircleRef.current.style.fill = isAny ? systemColor : "rgba(0,0,0,0.5)";
|
|
3059
|
+
centerCircleRef.current.style.stroke = isAny ? "#fff" : "rgba(255,255,255,0.3)";
|
|
3060
|
+
}
|
|
3061
|
+
}, [systemColor]);
|
|
3062
|
+
const updateDirections = useCallback((newDirections) => {
|
|
3063
|
+
const prev = activeDirectionsRef.current;
|
|
3064
|
+
prev.forEach((dir) => {
|
|
3065
|
+
if (!newDirections.has(dir)) {
|
|
3066
|
+
onButtonUp(dir);
|
|
3067
|
+
}
|
|
3068
|
+
});
|
|
3069
|
+
newDirections.forEach((dir) => {
|
|
3070
|
+
if (!prev.has(dir)) {
|
|
3071
|
+
if (hapticsEnabled && navigator.vibrate) navigator.vibrate(5);
|
|
3072
|
+
onButtonDown(dir);
|
|
3073
|
+
}
|
|
3074
|
+
});
|
|
3075
|
+
activeDirectionsRef.current = newDirections;
|
|
3076
|
+
updateVisuals(newDirections);
|
|
3077
|
+
}, [updateVisuals, onButtonDown, onButtonUp, hapticsEnabled]);
|
|
3078
|
+
const handleTouchStart = useCallback((e) => {
|
|
3079
|
+
e.preventDefault();
|
|
3080
|
+
if (activeTouchRef.current !== null) return;
|
|
3081
|
+
const touch = e.changedTouches[0];
|
|
3082
|
+
activeTouchRef.current = touch.identifier;
|
|
3083
|
+
const rect = dpadRef.current?.getBoundingClientRect();
|
|
3084
|
+
if (!rect) return;
|
|
3085
|
+
if (onPositionChange) {
|
|
3086
|
+
drag.checkDragStart(touch.clientX, touch.clientY, rect);
|
|
3087
|
+
}
|
|
3088
|
+
if (!drag.isDragging) {
|
|
3089
|
+
updateDirections(getDirectionsFromTouch(touch.clientX, touch.clientY, rect));
|
|
3090
|
+
}
|
|
3091
|
+
}, [getDirectionsFromTouch, updateDirections, onPositionChange, drag]);
|
|
3092
|
+
const handleTouchMove = useCallback((e) => {
|
|
3093
|
+
e.preventDefault();
|
|
3094
|
+
let touch = null;
|
|
3095
|
+
for (let i = 0; i < e.changedTouches.length; i++) {
|
|
3096
|
+
if (e.changedTouches[i].identifier === activeTouchRef.current) {
|
|
3097
|
+
touch = e.changedTouches[i];
|
|
3098
|
+
break;
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
if (!touch) return;
|
|
3102
|
+
if (drag.isDragging) {
|
|
3103
|
+
drag.handleDragMove(touch.clientX, touch.clientY);
|
|
3104
|
+
} else if (onPositionChange) {
|
|
3105
|
+
const startedDrag = drag.checkMoveThreshold(touch.clientX, touch.clientY);
|
|
3106
|
+
if (!startedDrag) {
|
|
3107
|
+
drag.clearDragTimer();
|
|
3108
|
+
const rect = dpadRef.current?.getBoundingClientRect();
|
|
3109
|
+
if (rect) {
|
|
3110
|
+
updateDirections(getDirectionsFromTouch(touch.clientX, touch.clientY, rect));
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
3113
|
+
} else {
|
|
3114
|
+
const rect = dpadRef.current?.getBoundingClientRect();
|
|
3115
|
+
if (rect) {
|
|
3116
|
+
updateDirections(getDirectionsFromTouch(touch.clientX, touch.clientY, rect));
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
}, [drag, getDirectionsFromTouch, updateDirections, onPositionChange]);
|
|
3120
|
+
const handleTouchEnd = useCallback((e) => {
|
|
3121
|
+
e.preventDefault();
|
|
3122
|
+
drag.clearDragTimer();
|
|
3123
|
+
let touchEnded = false;
|
|
3124
|
+
for (let i = 0; i < e.changedTouches.length; i++) {
|
|
3125
|
+
if (e.changedTouches[i].identifier === activeTouchRef.current) {
|
|
3126
|
+
touchEnded = true;
|
|
3127
|
+
break;
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
if (touchEnded) {
|
|
3131
|
+
activeTouchRef.current = null;
|
|
3132
|
+
if (drag.isDragging) {
|
|
3133
|
+
drag.handleDragEnd();
|
|
3134
|
+
} else {
|
|
3135
|
+
activeDirectionsRef.current.forEach((dir) => onButtonUp(dir));
|
|
3136
|
+
activeDirectionsRef.current = /* @__PURE__ */ new Set();
|
|
3137
|
+
updateVisuals(/* @__PURE__ */ new Set());
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
}, [updateVisuals, drag, onButtonUp]);
|
|
3141
|
+
useTouchEvents(dpadRef, {
|
|
3142
|
+
onTouchStart: handleTouchStart,
|
|
3143
|
+
onTouchMove: handleTouchMove,
|
|
3144
|
+
onTouchEnd: handleTouchEnd,
|
|
3145
|
+
onTouchCancel: handleTouchEnd
|
|
3146
|
+
}, { cleanup: drag.clearDragTimer });
|
|
3147
|
+
const leftPx = displayX / 100 * containerWidth - size / 2;
|
|
3148
|
+
const topPx = displayY / 100 * containerHeight - size / 2;
|
|
3149
|
+
const dUp = "M 35,5 L 65,5 L 65,35 L 50,50 L 35,35 Z";
|
|
3150
|
+
const dRight = "M 65,35 L 95,35 L 95,65 L 65,65 L 50,50 Z";
|
|
3151
|
+
const dDown = "M 65,65 L 65,95 L 35,95 L 35,65 L 50,50 Z";
|
|
3152
|
+
const dLeft = "M 35,65 L 5,65 L 5,35 L 35,35 L 50,50 Z";
|
|
3153
|
+
return /* @__PURE__ */ jsxs(
|
|
3154
|
+
"div",
|
|
3155
|
+
{
|
|
3156
|
+
ref: dpadRef,
|
|
3157
|
+
className: `absolute pointer-events-auto touch-manipulation select-none ${drag.isDragging ? "opacity-60" : ""}`,
|
|
3158
|
+
style: {
|
|
3159
|
+
top: 0,
|
|
3160
|
+
left: 0,
|
|
3161
|
+
transform: `translate3d(${leftPx}px, ${topPx}px, 0)${drag.isDragging ? " scale(1.05)" : ""}`,
|
|
3162
|
+
width: size,
|
|
3163
|
+
height: size,
|
|
3164
|
+
opacity: isLandscape ? 0.75 : 0.9,
|
|
3165
|
+
WebkitTouchCallout: "none",
|
|
3166
|
+
WebkitUserSelect: "none",
|
|
3167
|
+
touchAction: "none",
|
|
3168
|
+
transition: drag.isDragging ? "none" : "transform 0.1s ease-out"
|
|
3169
|
+
},
|
|
3170
|
+
children: [
|
|
3171
|
+
/* @__PURE__ */ jsx("div", { className: `absolute inset-0 rounded-full bg-black/40 backdrop-blur-md border shadow-lg ${drag.isDragging ? "border-white/50 ring-2 ring-white/30" : "border-white/10"}` }),
|
|
3172
|
+
/* @__PURE__ */ jsxs("svg", { width: "100%", height: "100%", viewBox: "0 0 100 100", className: "drop-shadow-xl relative z-10", children: [
|
|
3173
|
+
/* @__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" }),
|
|
3174
|
+
/* @__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" }),
|
|
3175
|
+
/* @__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" }),
|
|
3176
|
+
/* @__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" }),
|
|
3177
|
+
/* @__PURE__ */ jsx(
|
|
3178
|
+
"circle",
|
|
3179
|
+
{
|
|
3180
|
+
ref: centerCircleRef,
|
|
3181
|
+
cx: "50",
|
|
3182
|
+
cy: "50",
|
|
3183
|
+
r: "12",
|
|
3184
|
+
fill: drag.isDragging ? systemColor : "rgba(0,0,0,0.5)",
|
|
3185
|
+
stroke: drag.isDragging ? "#fff" : "rgba(255,255,255,0.3)",
|
|
3186
|
+
strokeWidth: drag.isDragging ? 2 : 1
|
|
3187
|
+
}
|
|
3188
|
+
),
|
|
3189
|
+
/* @__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" }),
|
|
3190
|
+
/* @__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" }),
|
|
3191
|
+
/* @__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" }),
|
|
3192
|
+
/* @__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" })
|
|
3193
|
+
] })
|
|
3194
|
+
]
|
|
3195
|
+
}
|
|
3196
|
+
);
|
|
3197
|
+
});
|
|
3198
|
+
var Dpad_default = Dpad;
|
|
2952
3199
|
|
|
2953
|
-
// src/
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
3200
|
+
// src/components/VirtualController/positioning.ts
|
|
3201
|
+
function adjustButtonPosition(config, context) {
|
|
3202
|
+
const sizeMultiplier = context.isFullscreen ? 1.1 : 1;
|
|
3203
|
+
return {
|
|
3204
|
+
...config,
|
|
3205
|
+
size: Math.floor(config.size * sizeMultiplier)
|
|
3206
|
+
};
|
|
3207
|
+
}
|
|
3208
|
+
var STORAGE_KEY = "virtual-button-positions";
|
|
3209
|
+
function useButtonPositions() {
|
|
3210
|
+
const [landscapePositions, setLandscapePositions] = useState({});
|
|
3211
|
+
const [portraitPositions, setPortraitPositions] = useState({});
|
|
3212
|
+
useEffect(() => {
|
|
3213
|
+
try {
|
|
3214
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
3215
|
+
if (stored) {
|
|
3216
|
+
const parsed = JSON.parse(stored);
|
|
3217
|
+
if (parsed.landscape && parsed.portrait) {
|
|
3218
|
+
setLandscapePositions(parsed.landscape);
|
|
3219
|
+
setPortraitPositions(parsed.portrait);
|
|
3220
|
+
} else {
|
|
3221
|
+
setLandscapePositions(parsed);
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
} catch (e) {
|
|
3225
|
+
console.error("Failed to load button positions:", e);
|
|
3226
|
+
}
|
|
3227
|
+
}, []);
|
|
3228
|
+
const savePosition = useCallback((buttonType, x, y, isLandscape) => {
|
|
3229
|
+
if (isLandscape) {
|
|
3230
|
+
setLandscapePositions((prev) => {
|
|
3231
|
+
const updated = { ...prev, [buttonType]: { x, y } };
|
|
3232
|
+
try {
|
|
3233
|
+
const stored = {
|
|
3234
|
+
landscape: updated,
|
|
3235
|
+
portrait: portraitPositions
|
|
3236
|
+
};
|
|
3237
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(stored));
|
|
3238
|
+
} catch (e) {
|
|
3239
|
+
console.error("Failed to save button position:", e);
|
|
3240
|
+
}
|
|
3241
|
+
return updated;
|
|
3242
|
+
});
|
|
3243
|
+
} else {
|
|
3244
|
+
setPortraitPositions((prev) => {
|
|
3245
|
+
const updated = { ...prev, [buttonType]: { x, y } };
|
|
3246
|
+
try {
|
|
3247
|
+
const stored = {
|
|
3248
|
+
landscape: landscapePositions,
|
|
3249
|
+
portrait: updated
|
|
3250
|
+
};
|
|
3251
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(stored));
|
|
3252
|
+
} catch (e) {
|
|
3253
|
+
console.error("Failed to save button position:", e);
|
|
3254
|
+
}
|
|
3255
|
+
return updated;
|
|
3256
|
+
});
|
|
3257
|
+
}
|
|
3258
|
+
}, [landscapePositions, portraitPositions]);
|
|
3259
|
+
const getPosition = useCallback((buttonType, isLandscape) => {
|
|
3260
|
+
const positions = isLandscape ? landscapePositions : portraitPositions;
|
|
3261
|
+
return positions[buttonType] || null;
|
|
3262
|
+
}, [landscapePositions, portraitPositions]);
|
|
3263
|
+
const resetPositions = useCallback(() => {
|
|
3264
|
+
setLandscapePositions({});
|
|
3265
|
+
setPortraitPositions({});
|
|
3266
|
+
try {
|
|
3267
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
3268
|
+
} catch (e) {
|
|
3269
|
+
console.error("Failed to reset button positions:", e);
|
|
3270
|
+
}
|
|
3271
|
+
}, []);
|
|
3272
|
+
return {
|
|
3273
|
+
landscapePositions,
|
|
3274
|
+
portraitPositions,
|
|
3275
|
+
savePosition,
|
|
3276
|
+
getPosition,
|
|
3277
|
+
resetPositions
|
|
3278
|
+
};
|
|
3279
|
+
}
|
|
3280
|
+
|
|
3281
|
+
// src/lib/controls/types.ts
|
|
3282
|
+
var DPAD_BUTTONS2 = ["up", "down", "left", "right"];
|
|
3283
|
+
var FACE_BUTTONS = ["a", "b", "x", "y"];
|
|
3284
|
+
var SHOULDER_BUTTONS = ["l", "r"];
|
|
3285
|
+
var TRIGGER_BUTTONS = ["l2", "r2"];
|
|
3286
|
+
var STICK_BUTTONS = ["l3", "r3"];
|
|
3287
|
+
var SYSTEM_BUTTONS = ["start", "select"];
|
|
3288
|
+
var ALL_BUTTONS = [
|
|
3289
|
+
...DPAD_BUTTONS2,
|
|
3290
|
+
...FACE_BUTTONS,
|
|
3291
|
+
...SHOULDER_BUTTONS,
|
|
3292
|
+
...TRIGGER_BUTTONS,
|
|
3293
|
+
...STICK_BUTTONS,
|
|
3294
|
+
...SYSTEM_BUTTONS
|
|
3295
|
+
];
|
|
3296
|
+
|
|
3297
|
+
// src/lib/controls/defaults.ts
|
|
3298
|
+
var STANDARD_GAMEPAD_BUTTONS = {
|
|
3299
|
+
// Face buttons (Xbox/PlayStation layout)
|
|
3300
|
+
a: 0,
|
|
3301
|
+
// A / Cross (bottom)
|
|
3302
|
+
b: 1,
|
|
3303
|
+
// B / Circle (right)
|
|
3304
|
+
x: 2,
|
|
3305
|
+
// X / Square (left)
|
|
3306
|
+
y: 3,
|
|
3307
|
+
// Y / Triangle (top)
|
|
3308
|
+
// Shoulders
|
|
3309
|
+
l: 4,
|
|
3310
|
+
// LB / L1
|
|
3311
|
+
r: 5,
|
|
3312
|
+
// RB / R1
|
|
3313
|
+
// Triggers
|
|
3314
|
+
l2: 6,
|
|
3315
|
+
// LT / L2
|
|
3316
|
+
r2: 7,
|
|
3317
|
+
// RT / R2
|
|
3318
|
+
// System
|
|
3319
|
+
select: 8,
|
|
3320
|
+
// Back / Select / Share
|
|
3321
|
+
start: 9,
|
|
3322
|
+
// Start / Options
|
|
3323
|
+
// Stick clicks
|
|
2996
3324
|
l3: 10,
|
|
2997
3325
|
// Left stick click
|
|
2998
3326
|
r3: 11,
|
|
@@ -3535,343 +3863,6 @@ function getKeyboardCode(buttonType, controls) {
|
|
|
3535
3863
|
}
|
|
3536
3864
|
return DEFAULT_CONTROLS[key] ?? null;
|
|
3537
3865
|
}
|
|
3538
|
-
function getKeyName(code) {
|
|
3539
|
-
if (code.startsWith("Key")) return code.slice(3).toLowerCase();
|
|
3540
|
-
if (code.startsWith("Arrow")) return code.slice(5);
|
|
3541
|
-
if (code === "Enter") return "Enter";
|
|
3542
|
-
if (code === "ShiftRight" || code === "ShiftLeft") return "Shift";
|
|
3543
|
-
return code;
|
|
3544
|
-
}
|
|
3545
|
-
function getCanvas() {
|
|
3546
|
-
return document.querySelector(".game-canvas-container canvas") || document.querySelector("canvas");
|
|
3547
|
-
}
|
|
3548
|
-
function dispatchKeyboardEvent(type, code) {
|
|
3549
|
-
const canvas = getCanvas();
|
|
3550
|
-
if (!canvas) {
|
|
3551
|
-
return false;
|
|
3552
|
-
}
|
|
3553
|
-
const event = new KeyboardEvent(type, {
|
|
3554
|
-
code,
|
|
3555
|
-
key: getKeyName(code),
|
|
3556
|
-
bubbles: true,
|
|
3557
|
-
cancelable: true
|
|
3558
|
-
});
|
|
3559
|
-
canvas.dispatchEvent(event);
|
|
3560
|
-
return true;
|
|
3561
|
-
}
|
|
3562
|
-
var CENTER_TOUCH_RADIUS = 0.35;
|
|
3563
|
-
var Dpad = React2.memo(function Dpad2({
|
|
3564
|
-
size = 180,
|
|
3565
|
-
x,
|
|
3566
|
-
y,
|
|
3567
|
-
containerWidth,
|
|
3568
|
-
containerHeight,
|
|
3569
|
-
controls,
|
|
3570
|
-
systemColor = "#00FF41",
|
|
3571
|
-
isLandscape = false,
|
|
3572
|
-
customPosition,
|
|
3573
|
-
onPositionChange
|
|
3574
|
-
}) {
|
|
3575
|
-
const dpadRef = useRef(null);
|
|
3576
|
-
const activeTouchRef = useRef(null);
|
|
3577
|
-
const activeDirectionsRef = useRef(/* @__PURE__ */ new Set());
|
|
3578
|
-
const upPathRef = useRef(null);
|
|
3579
|
-
const downPathRef = useRef(null);
|
|
3580
|
-
const leftPathRef = useRef(null);
|
|
3581
|
-
const rightPathRef = useRef(null);
|
|
3582
|
-
const centerCircleRef = useRef(null);
|
|
3583
|
-
const displayX = customPosition ? customPosition.x : x;
|
|
3584
|
-
const displayY = customPosition ? customPosition.y : y;
|
|
3585
|
-
const releaseAllDirections = useCallback((getKeyCode2) => {
|
|
3586
|
-
activeDirectionsRef.current.forEach((dir) => {
|
|
3587
|
-
const keyCode = getKeyCode2(dir);
|
|
3588
|
-
if (keyCode) dispatchKeyboardEvent("keyup", keyCode);
|
|
3589
|
-
});
|
|
3590
|
-
activeDirectionsRef.current = /* @__PURE__ */ new Set();
|
|
3591
|
-
}, []);
|
|
3592
|
-
const getKeyCode = useCallback((direction) => {
|
|
3593
|
-
if (!controls) {
|
|
3594
|
-
const defaults = {
|
|
3595
|
-
up: "ArrowUp",
|
|
3596
|
-
down: "ArrowDown",
|
|
3597
|
-
left: "ArrowLeft",
|
|
3598
|
-
right: "ArrowRight"
|
|
3599
|
-
};
|
|
3600
|
-
return defaults[direction];
|
|
3601
|
-
}
|
|
3602
|
-
return controls[direction] || "";
|
|
3603
|
-
}, [controls]);
|
|
3604
|
-
const drag = useDrag({
|
|
3605
|
-
elementSize: size,
|
|
3606
|
-
displayX,
|
|
3607
|
-
displayY,
|
|
3608
|
-
containerWidth,
|
|
3609
|
-
containerHeight,
|
|
3610
|
-
onPositionChange,
|
|
3611
|
-
centerThreshold: CENTER_TOUCH_RADIUS,
|
|
3612
|
-
onDragStart: () => {
|
|
3613
|
-
releaseAllDirections(getKeyCode);
|
|
3614
|
-
updateVisuals(/* @__PURE__ */ new Set());
|
|
3615
|
-
}
|
|
3616
|
-
});
|
|
3617
|
-
const getDirectionsFromTouch = useCallback((touchX, touchY, rect) => {
|
|
3618
|
-
const centerX = rect.left + rect.width / 2;
|
|
3619
|
-
const centerY = rect.top + rect.height / 2;
|
|
3620
|
-
const dx = touchX - centerX;
|
|
3621
|
-
const dy = touchY - centerY;
|
|
3622
|
-
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
3623
|
-
const deadZone = rect.width / 2 * 0.15;
|
|
3624
|
-
if (distance < deadZone) return /* @__PURE__ */ new Set();
|
|
3625
|
-
const directions = /* @__PURE__ */ new Set();
|
|
3626
|
-
const angle = Math.atan2(dy, dx) * (180 / Math.PI);
|
|
3627
|
-
if (angle >= -150 && angle <= -30) directions.add("up");
|
|
3628
|
-
if (angle >= 30 && angle <= 150) directions.add("down");
|
|
3629
|
-
if (angle >= 120 || angle <= -120) directions.add("left");
|
|
3630
|
-
if (angle >= -60 && angle <= 60) directions.add("right");
|
|
3631
|
-
return directions;
|
|
3632
|
-
}, []);
|
|
3633
|
-
const updateVisuals = useCallback((directions) => {
|
|
3634
|
-
const activeFill = `${systemColor}80`;
|
|
3635
|
-
const inactiveFill = "rgba(255, 255, 255, 0.05)";
|
|
3636
|
-
const activeStroke = systemColor;
|
|
3637
|
-
const inactiveStroke = "rgba(255, 255, 255, 0.2)";
|
|
3638
|
-
const glow = `0 0 15px ${systemColor}`;
|
|
3639
|
-
const updatePart = (ref, isActive) => {
|
|
3640
|
-
if (ref.current) {
|
|
3641
|
-
ref.current.style.fill = isActive ? activeFill : inactiveFill;
|
|
3642
|
-
ref.current.style.stroke = isActive ? activeStroke : inactiveStroke;
|
|
3643
|
-
ref.current.style.filter = isActive ? `drop-shadow(${glow})` : "none";
|
|
3644
|
-
ref.current.style.transform = isActive ? "scale(0.98)" : "scale(1)";
|
|
3645
|
-
ref.current.style.transformOrigin = "center";
|
|
3646
|
-
}
|
|
3647
|
-
};
|
|
3648
|
-
updatePart(upPathRef, directions.has("up"));
|
|
3649
|
-
updatePart(downPathRef, directions.has("down"));
|
|
3650
|
-
updatePart(leftPathRef, directions.has("left"));
|
|
3651
|
-
updatePart(rightPathRef, directions.has("right"));
|
|
3652
|
-
if (centerCircleRef.current) {
|
|
3653
|
-
const isAny = directions.size > 0;
|
|
3654
|
-
centerCircleRef.current.style.fill = isAny ? systemColor : "rgba(0,0,0,0.5)";
|
|
3655
|
-
centerCircleRef.current.style.stroke = isAny ? "#fff" : "rgba(255,255,255,0.3)";
|
|
3656
|
-
}
|
|
3657
|
-
}, [systemColor]);
|
|
3658
|
-
const updateDirections = useCallback((newDirections) => {
|
|
3659
|
-
const prev = activeDirectionsRef.current;
|
|
3660
|
-
prev.forEach((dir) => {
|
|
3661
|
-
if (!newDirections.has(dir)) {
|
|
3662
|
-
const keyCode = getKeyCode(dir);
|
|
3663
|
-
if (keyCode) dispatchKeyboardEvent("keyup", keyCode);
|
|
3664
|
-
}
|
|
3665
|
-
});
|
|
3666
|
-
newDirections.forEach((dir) => {
|
|
3667
|
-
if (!prev.has(dir)) {
|
|
3668
|
-
const keyCode = getKeyCode(dir);
|
|
3669
|
-
if (keyCode) {
|
|
3670
|
-
if (navigator.vibrate) navigator.vibrate(5);
|
|
3671
|
-
dispatchKeyboardEvent("keydown", keyCode);
|
|
3672
|
-
}
|
|
3673
|
-
}
|
|
3674
|
-
});
|
|
3675
|
-
activeDirectionsRef.current = newDirections;
|
|
3676
|
-
updateVisuals(newDirections);
|
|
3677
|
-
}, [getKeyCode, updateVisuals]);
|
|
3678
|
-
const handleTouchStart = useCallback((e) => {
|
|
3679
|
-
e.preventDefault();
|
|
3680
|
-
if (activeTouchRef.current !== null) return;
|
|
3681
|
-
const touch = e.changedTouches[0];
|
|
3682
|
-
activeTouchRef.current = touch.identifier;
|
|
3683
|
-
const rect = dpadRef.current?.getBoundingClientRect();
|
|
3684
|
-
if (!rect) return;
|
|
3685
|
-
if (onPositionChange) {
|
|
3686
|
-
drag.checkDragStart(touch.clientX, touch.clientY, rect);
|
|
3687
|
-
}
|
|
3688
|
-
if (!drag.isDragging) {
|
|
3689
|
-
updateDirections(getDirectionsFromTouch(touch.clientX, touch.clientY, rect));
|
|
3690
|
-
}
|
|
3691
|
-
}, [getDirectionsFromTouch, updateDirections, onPositionChange, drag]);
|
|
3692
|
-
const handleTouchMove = useCallback((e) => {
|
|
3693
|
-
e.preventDefault();
|
|
3694
|
-
let touch = null;
|
|
3695
|
-
for (let i = 0; i < e.changedTouches.length; i++) {
|
|
3696
|
-
if (e.changedTouches[i].identifier === activeTouchRef.current) {
|
|
3697
|
-
touch = e.changedTouches[i];
|
|
3698
|
-
break;
|
|
3699
|
-
}
|
|
3700
|
-
}
|
|
3701
|
-
if (!touch) return;
|
|
3702
|
-
if (drag.isDragging) {
|
|
3703
|
-
drag.handleDragMove(touch.clientX, touch.clientY);
|
|
3704
|
-
} else {
|
|
3705
|
-
const rect = dpadRef.current?.getBoundingClientRect();
|
|
3706
|
-
if (rect) {
|
|
3707
|
-
drag.clearDragTimer();
|
|
3708
|
-
updateDirections(getDirectionsFromTouch(touch.clientX, touch.clientY, rect));
|
|
3709
|
-
}
|
|
3710
|
-
}
|
|
3711
|
-
}, [drag, getDirectionsFromTouch, updateDirections]);
|
|
3712
|
-
const handleTouchEnd = useCallback((e) => {
|
|
3713
|
-
e.preventDefault();
|
|
3714
|
-
drag.clearDragTimer();
|
|
3715
|
-
let touchEnded = false;
|
|
3716
|
-
for (let i = 0; i < e.changedTouches.length; i++) {
|
|
3717
|
-
if (e.changedTouches[i].identifier === activeTouchRef.current) {
|
|
3718
|
-
touchEnded = true;
|
|
3719
|
-
break;
|
|
3720
|
-
}
|
|
3721
|
-
}
|
|
3722
|
-
if (touchEnded) {
|
|
3723
|
-
activeTouchRef.current = null;
|
|
3724
|
-
if (drag.isDragging) {
|
|
3725
|
-
drag.handleDragEnd();
|
|
3726
|
-
} else {
|
|
3727
|
-
activeDirectionsRef.current.forEach((dir) => {
|
|
3728
|
-
const keyCode = getKeyCode(dir);
|
|
3729
|
-
if (keyCode) dispatchKeyboardEvent("keyup", keyCode);
|
|
3730
|
-
});
|
|
3731
|
-
activeDirectionsRef.current = /* @__PURE__ */ new Set();
|
|
3732
|
-
updateVisuals(/* @__PURE__ */ new Set());
|
|
3733
|
-
}
|
|
3734
|
-
}
|
|
3735
|
-
}, [getKeyCode, updateVisuals, drag]);
|
|
3736
|
-
useTouchEvents(dpadRef, {
|
|
3737
|
-
onTouchStart: handleTouchStart,
|
|
3738
|
-
onTouchMove: handleTouchMove,
|
|
3739
|
-
onTouchEnd: handleTouchEnd,
|
|
3740
|
-
onTouchCancel: handleTouchEnd
|
|
3741
|
-
}, { cleanup: drag.clearDragTimer });
|
|
3742
|
-
const leftPx = displayX / 100 * containerWidth - size / 2;
|
|
3743
|
-
const topPx = displayY / 100 * containerHeight - size / 2;
|
|
3744
|
-
const dUp = "M 35,5 L 65,5 L 65,35 L 50,50 L 35,35 Z";
|
|
3745
|
-
const dRight = "M 65,35 L 95,35 L 95,65 L 65,65 L 50,50 Z";
|
|
3746
|
-
const dDown = "M 65,65 L 65,95 L 35,95 L 35,65 L 50,50 Z";
|
|
3747
|
-
const dLeft = "M 35,65 L 5,65 L 5,35 L 35,35 L 50,50 Z";
|
|
3748
|
-
return /* @__PURE__ */ jsxs(
|
|
3749
|
-
"div",
|
|
3750
|
-
{
|
|
3751
|
-
ref: dpadRef,
|
|
3752
|
-
className: `absolute pointer-events-auto touch-manipulation select-none ${drag.isDragging ? "opacity-60" : ""}`,
|
|
3753
|
-
style: {
|
|
3754
|
-
top: 0,
|
|
3755
|
-
left: 0,
|
|
3756
|
-
transform: `translate3d(${leftPx}px, ${topPx}px, 0)${drag.isDragging ? " scale(1.05)" : ""}`,
|
|
3757
|
-
width: size,
|
|
3758
|
-
height: size,
|
|
3759
|
-
opacity: isLandscape ? 0.75 : 0.9,
|
|
3760
|
-
WebkitTouchCallout: "none",
|
|
3761
|
-
WebkitUserSelect: "none",
|
|
3762
|
-
touchAction: "none",
|
|
3763
|
-
transition: drag.isDragging ? "none" : "transform 0.1s ease-out"
|
|
3764
|
-
},
|
|
3765
|
-
children: [
|
|
3766
|
-
/* @__PURE__ */ jsx("div", { className: `absolute inset-0 rounded-full bg-black/40 backdrop-blur-md border shadow-lg ${drag.isDragging ? "border-white/50 ring-2 ring-white/30" : "border-white/10"}` }),
|
|
3767
|
-
/* @__PURE__ */ jsxs("svg", { width: "100%", height: "100%", viewBox: "0 0 100 100", className: "drop-shadow-xl relative z-10", children: [
|
|
3768
|
-
/* @__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" }),
|
|
3769
|
-
/* @__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" }),
|
|
3770
|
-
/* @__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" }),
|
|
3771
|
-
/* @__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" }),
|
|
3772
|
-
/* @__PURE__ */ jsx(
|
|
3773
|
-
"circle",
|
|
3774
|
-
{
|
|
3775
|
-
ref: centerCircleRef,
|
|
3776
|
-
cx: "50",
|
|
3777
|
-
cy: "50",
|
|
3778
|
-
r: "12",
|
|
3779
|
-
fill: drag.isDragging ? systemColor : "rgba(0,0,0,0.5)",
|
|
3780
|
-
stroke: drag.isDragging ? "#fff" : "rgba(255,255,255,0.3)",
|
|
3781
|
-
strokeWidth: drag.isDragging ? 2 : 1
|
|
3782
|
-
}
|
|
3783
|
-
),
|
|
3784
|
-
/* @__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" }),
|
|
3785
|
-
/* @__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" }),
|
|
3786
|
-
/* @__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" }),
|
|
3787
|
-
/* @__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" })
|
|
3788
|
-
] })
|
|
3789
|
-
]
|
|
3790
|
-
}
|
|
3791
|
-
);
|
|
3792
|
-
});
|
|
3793
|
-
var Dpad_default = Dpad;
|
|
3794
|
-
|
|
3795
|
-
// src/components/VirtualController/positioning.ts
|
|
3796
|
-
function adjustButtonPosition(config, context) {
|
|
3797
|
-
const sizeMultiplier = context.isFullscreen ? 1.1 : 1;
|
|
3798
|
-
return {
|
|
3799
|
-
...config,
|
|
3800
|
-
size: Math.floor(config.size * sizeMultiplier)
|
|
3801
|
-
};
|
|
3802
|
-
}
|
|
3803
|
-
var STORAGE_KEY = "virtual-button-positions";
|
|
3804
|
-
function useButtonPositions() {
|
|
3805
|
-
const [landscapePositions, setLandscapePositions] = useState({});
|
|
3806
|
-
const [portraitPositions, setPortraitPositions] = useState({});
|
|
3807
|
-
useEffect(() => {
|
|
3808
|
-
try {
|
|
3809
|
-
const stored = localStorage.getItem(STORAGE_KEY);
|
|
3810
|
-
if (stored) {
|
|
3811
|
-
const parsed = JSON.parse(stored);
|
|
3812
|
-
if (parsed.landscape && parsed.portrait) {
|
|
3813
|
-
setLandscapePositions(parsed.landscape);
|
|
3814
|
-
setPortraitPositions(parsed.portrait);
|
|
3815
|
-
} else {
|
|
3816
|
-
setLandscapePositions(parsed);
|
|
3817
|
-
}
|
|
3818
|
-
}
|
|
3819
|
-
} catch (e) {
|
|
3820
|
-
console.error("Failed to load button positions:", e);
|
|
3821
|
-
}
|
|
3822
|
-
}, []);
|
|
3823
|
-
const savePosition = useCallback((buttonType, x, y, isLandscape) => {
|
|
3824
|
-
if (isLandscape) {
|
|
3825
|
-
setLandscapePositions((prev) => {
|
|
3826
|
-
const updated = { ...prev, [buttonType]: { x, y } };
|
|
3827
|
-
try {
|
|
3828
|
-
const stored = {
|
|
3829
|
-
landscape: updated,
|
|
3830
|
-
portrait: portraitPositions
|
|
3831
|
-
};
|
|
3832
|
-
localStorage.setItem(STORAGE_KEY, JSON.stringify(stored));
|
|
3833
|
-
} catch (e) {
|
|
3834
|
-
console.error("Failed to save button position:", e);
|
|
3835
|
-
}
|
|
3836
|
-
return updated;
|
|
3837
|
-
});
|
|
3838
|
-
} else {
|
|
3839
|
-
setPortraitPositions((prev) => {
|
|
3840
|
-
const updated = { ...prev, [buttonType]: { x, y } };
|
|
3841
|
-
try {
|
|
3842
|
-
const stored = {
|
|
3843
|
-
landscape: landscapePositions,
|
|
3844
|
-
portrait: updated
|
|
3845
|
-
};
|
|
3846
|
-
localStorage.setItem(STORAGE_KEY, JSON.stringify(stored));
|
|
3847
|
-
} catch (e) {
|
|
3848
|
-
console.error("Failed to save button position:", e);
|
|
3849
|
-
}
|
|
3850
|
-
return updated;
|
|
3851
|
-
});
|
|
3852
|
-
}
|
|
3853
|
-
}, [landscapePositions, portraitPositions]);
|
|
3854
|
-
const getPosition = useCallback((buttonType, isLandscape) => {
|
|
3855
|
-
const positions = isLandscape ? landscapePositions : portraitPositions;
|
|
3856
|
-
return positions[buttonType] || null;
|
|
3857
|
-
}, [landscapePositions, portraitPositions]);
|
|
3858
|
-
const resetPositions = useCallback(() => {
|
|
3859
|
-
setLandscapePositions({});
|
|
3860
|
-
setPortraitPositions({});
|
|
3861
|
-
try {
|
|
3862
|
-
localStorage.removeItem(STORAGE_KEY);
|
|
3863
|
-
} catch (e) {
|
|
3864
|
-
console.error("Failed to reset button positions:", e);
|
|
3865
|
-
}
|
|
3866
|
-
}, []);
|
|
3867
|
-
return {
|
|
3868
|
-
landscapePositions,
|
|
3869
|
-
portraitPositions,
|
|
3870
|
-
savePosition,
|
|
3871
|
-
getPosition,
|
|
3872
|
-
resetPositions
|
|
3873
|
-
};
|
|
3874
|
-
}
|
|
3875
3866
|
var STORAGE_KEY2 = "koin-controls-hint-shown";
|
|
3876
3867
|
function ControlsHint({ isVisible }) {
|
|
3877
3868
|
const [show, setShow] = useState(false);
|
|
@@ -3909,8 +3900,16 @@ function ControlsHint({ isVisible }) {
|
|
|
3909
3900
|
children: [
|
|
3910
3901
|
/* @__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" }) }) }),
|
|
3911
3902
|
/* @__PURE__ */ jsx("h3", { className: "text-white text-lg font-bold mb-2", children: "Customize Your Controls" }),
|
|
3912
|
-
/* @__PURE__ */ jsxs("p", { className: "text-white/70 text-sm mb-
|
|
3913
|
-
|
|
3903
|
+
/* @__PURE__ */ jsxs("p", { className: "text-white/70 text-sm mb-3", children: [
|
|
3904
|
+
"Use the ",
|
|
3905
|
+
/* @__PURE__ */ jsx(Lock, { size: 12, className: "inline mx-1 text-white" }),
|
|
3906
|
+
" ",
|
|
3907
|
+
/* @__PURE__ */ jsx("strong", { className: "text-white", children: "lock icon" }),
|
|
3908
|
+
" at the top to unlock controls for repositioning."
|
|
3909
|
+
] }),
|
|
3910
|
+
/* @__PURE__ */ jsxs("p", { className: "text-white/70 text-sm mb-3", children: [
|
|
3911
|
+
"When unlocked, ",
|
|
3912
|
+
/* @__PURE__ */ jsx("strong", { className: "text-white", children: "long-press" }),
|
|
3914
3913
|
" any button or the ",
|
|
3915
3914
|
/* @__PURE__ */ jsx("strong", { className: "text-white", children: "D-pad center" }),
|
|
3916
3915
|
" to drag and reposition it."
|
|
@@ -3948,7 +3947,7 @@ function LockButton({
|
|
|
3948
3947
|
"button",
|
|
3949
3948
|
{
|
|
3950
3949
|
onClick: onToggle,
|
|
3951
|
-
className: "
|
|
3950
|
+
className: "pointer-events-auto p-2 rounded-full backdrop-blur-sm transition-all active:scale-95",
|
|
3952
3951
|
style: {
|
|
3953
3952
|
backgroundColor: isLocked ? "rgba(0,0,0,0.6)" : `${systemColor}20`,
|
|
3954
3953
|
border: `1px solid ${isLocked ? "rgba(255,255,255,0.2)" : systemColor}`
|
|
@@ -3962,18 +3961,164 @@ function LockButton({
|
|
|
3962
3961
|
}
|
|
3963
3962
|
)
|
|
3964
3963
|
}
|
|
3965
|
-
);
|
|
3964
|
+
);
|
|
3965
|
+
}
|
|
3966
|
+
function HoldButton({
|
|
3967
|
+
isActive,
|
|
3968
|
+
onToggle,
|
|
3969
|
+
systemColor = "#00FF41"
|
|
3970
|
+
}) {
|
|
3971
|
+
const Icon = isActive ? StopCircle : Hand;
|
|
3972
|
+
return /* @__PURE__ */ jsx(
|
|
3973
|
+
"button",
|
|
3974
|
+
{
|
|
3975
|
+
onClick: onToggle,
|
|
3976
|
+
className: "pointer-events-auto p-2 rounded-full backdrop-blur-sm transition-all active:scale-95",
|
|
3977
|
+
style: {
|
|
3978
|
+
backgroundColor: isActive ? "rgba(0,0,0,0.6)" : `${systemColor}20`,
|
|
3979
|
+
border: `1px solid ${isActive ? "rgba(255,255,255,0.4)" : systemColor}`
|
|
3980
|
+
},
|
|
3981
|
+
"aria-label": isActive ? "Disable Hold Mode" : "Enable Button Hold Mode",
|
|
3982
|
+
children: /* @__PURE__ */ jsx(
|
|
3983
|
+
Icon,
|
|
3984
|
+
{
|
|
3985
|
+
size: 18,
|
|
3986
|
+
style: { color: isActive ? "#fff" : systemColor }
|
|
3987
|
+
}
|
|
3988
|
+
)
|
|
3989
|
+
}
|
|
3990
|
+
);
|
|
3991
|
+
}
|
|
3992
|
+
function TurboButton({
|
|
3993
|
+
isActive,
|
|
3994
|
+
onToggle,
|
|
3995
|
+
systemColor = "#00FF41"
|
|
3996
|
+
}) {
|
|
3997
|
+
const Icon = isActive ? ZapOff : Zap;
|
|
3998
|
+
return /* @__PURE__ */ jsx(
|
|
3999
|
+
"button",
|
|
4000
|
+
{
|
|
4001
|
+
onClick: onToggle,
|
|
4002
|
+
className: "pointer-events-auto p-2 rounded-full backdrop-blur-sm transition-all active:scale-95",
|
|
4003
|
+
style: {
|
|
4004
|
+
backgroundColor: isActive ? "rgba(255,200,0,0.3)" : `${systemColor}20`,
|
|
4005
|
+
border: `1px solid ${isActive ? "rgba(255,200,0,0.6)" : systemColor}`
|
|
4006
|
+
},
|
|
4007
|
+
"aria-label": isActive ? "Disable Turbo Mode" : "Enable Turbo Fire Mode",
|
|
4008
|
+
children: /* @__PURE__ */ jsx(
|
|
4009
|
+
Icon,
|
|
4010
|
+
{
|
|
4011
|
+
size: 18,
|
|
4012
|
+
style: { color: isActive ? "#FFC800" : systemColor }
|
|
4013
|
+
}
|
|
4014
|
+
)
|
|
4015
|
+
}
|
|
4016
|
+
);
|
|
4017
|
+
}
|
|
4018
|
+
var MODE_CONFIG = {
|
|
4019
|
+
hold: {
|
|
4020
|
+
Icon: Hand,
|
|
4021
|
+
title: "Hold Mode",
|
|
4022
|
+
instruction: "Tap a button to hold it",
|
|
4023
|
+
buttonIcon: Hand,
|
|
4024
|
+
buttonColor: "#22c55e"
|
|
4025
|
+
// green
|
|
4026
|
+
},
|
|
4027
|
+
turbo: {
|
|
4028
|
+
Icon: Zap,
|
|
4029
|
+
title: "Turbo Mode",
|
|
4030
|
+
instruction: "Tap a button for rapid fire",
|
|
4031
|
+
buttonIcon: Zap,
|
|
4032
|
+
buttonColor: "#fbbf24"
|
|
4033
|
+
// yellow
|
|
4034
|
+
}
|
|
4035
|
+
};
|
|
4036
|
+
function ModeOverlay({
|
|
4037
|
+
mode,
|
|
4038
|
+
heldButtons,
|
|
4039
|
+
turboButtons,
|
|
4040
|
+
systemColor = "#00FF41",
|
|
4041
|
+
onExit
|
|
4042
|
+
}) {
|
|
4043
|
+
if (!mode) return null;
|
|
4044
|
+
const config = MODE_CONFIG[mode];
|
|
4045
|
+
const buttons = mode === "hold" ? heldButtons : turboButtons;
|
|
4046
|
+
const buttonArray = Array.from(buttons);
|
|
4047
|
+
const { Icon, buttonIcon: ButtonIcon } = config;
|
|
4048
|
+
return /* @__PURE__ */ jsx("div", { className: "fixed top-0 left-0 right-0 z-40 flex justify-center pt-4 pointer-events-none", children: /* @__PURE__ */ jsxs(
|
|
4049
|
+
"div",
|
|
4050
|
+
{
|
|
4051
|
+
className: "relative px-5 py-3 rounded-2xl backdrop-blur-md border pointer-events-auto",
|
|
4052
|
+
style: {
|
|
4053
|
+
backgroundColor: "rgba(0,0,0,0.85)",
|
|
4054
|
+
borderColor: `${systemColor}60`,
|
|
4055
|
+
boxShadow: `0 4px 20px ${systemColor}30`
|
|
4056
|
+
},
|
|
4057
|
+
children: [
|
|
4058
|
+
/* @__PURE__ */ jsx(
|
|
4059
|
+
"button",
|
|
4060
|
+
{
|
|
4061
|
+
onClick: onExit,
|
|
4062
|
+
className: "absolute -top-2 -right-2 w-7 h-7 rounded-full flex items-center justify-center transition-transform active:scale-90",
|
|
4063
|
+
style: {
|
|
4064
|
+
backgroundColor: "#ef4444",
|
|
4065
|
+
border: "2px solid rgba(255,255,255,0.3)"
|
|
4066
|
+
},
|
|
4067
|
+
"aria-label": "Exit mode",
|
|
4068
|
+
children: /* @__PURE__ */ jsx(X, { size: 14, color: "white", strokeWidth: 3 })
|
|
4069
|
+
}
|
|
4070
|
+
),
|
|
4071
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
4072
|
+
/* @__PURE__ */ jsx(
|
|
4073
|
+
"div",
|
|
4074
|
+
{
|
|
4075
|
+
className: "w-10 h-10 rounded-full flex items-center justify-center flex-shrink-0",
|
|
4076
|
+
style: { backgroundColor: `${systemColor}30` },
|
|
4077
|
+
children: /* @__PURE__ */ jsx(Icon, { size: 20, style: { color: systemColor } })
|
|
4078
|
+
}
|
|
4079
|
+
),
|
|
4080
|
+
/* @__PURE__ */ jsxs("div", { className: "text-left", children: [
|
|
4081
|
+
/* @__PURE__ */ jsx("h3", { className: "text-white font-bold text-sm", children: config.title }),
|
|
4082
|
+
/* @__PURE__ */ jsx("p", { className: "text-white/60 text-xs", children: config.instruction })
|
|
4083
|
+
] })
|
|
4084
|
+
] }),
|
|
4085
|
+
buttonArray.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5 justify-center mt-2 pt-2 border-t border-white/10", children: buttonArray.map((button) => /* @__PURE__ */ jsxs(
|
|
4086
|
+
"span",
|
|
4087
|
+
{
|
|
4088
|
+
className: "px-2 py-0.5 rounded-full text-[10px] font-bold uppercase flex items-center gap-1",
|
|
4089
|
+
style: {
|
|
4090
|
+
backgroundColor: `${config.buttonColor}25`,
|
|
4091
|
+
color: config.buttonColor
|
|
4092
|
+
},
|
|
4093
|
+
children: [
|
|
4094
|
+
/* @__PURE__ */ jsx(ButtonIcon, { size: 10 }),
|
|
4095
|
+
button
|
|
4096
|
+
]
|
|
4097
|
+
},
|
|
4098
|
+
button
|
|
4099
|
+
)) })
|
|
4100
|
+
]
|
|
4101
|
+
}
|
|
4102
|
+
) });
|
|
3966
4103
|
}
|
|
3967
4104
|
var LOCK_KEY = "koin-controls-locked";
|
|
3968
4105
|
function VirtualController({
|
|
3969
4106
|
system,
|
|
3970
4107
|
isRunning,
|
|
3971
4108
|
controls,
|
|
3972
|
-
systemColor = "#00FF41"
|
|
4109
|
+
systemColor = "#00FF41",
|
|
3973
4110
|
// Default retro green
|
|
4111
|
+
hapticsEnabled = true,
|
|
4112
|
+
onButtonDown,
|
|
4113
|
+
onButtonUp,
|
|
4114
|
+
onPause,
|
|
4115
|
+
onResume
|
|
3974
4116
|
}) {
|
|
3975
4117
|
const { isMobile, isLandscape, isPortrait } = useMobile();
|
|
3976
4118
|
const [pressedButtons, setPressedButtons] = useState(/* @__PURE__ */ new Set());
|
|
4119
|
+
const pressedButtonsRef = useRef(/* @__PURE__ */ new Set());
|
|
4120
|
+
const [heldButtons, setHeldButtons] = useState(/* @__PURE__ */ new Set());
|
|
4121
|
+
const [isHoldMode, setIsHoldMode] = useState(false);
|
|
3977
4122
|
const [containerSize, setContainerSize] = useState({ width: 0, height: 0 });
|
|
3978
4123
|
const [isFullscreenState, setIsFullscreenState] = useState(false);
|
|
3979
4124
|
const [isLocked, setIsLocked] = useState(true);
|
|
@@ -3991,6 +4136,28 @@ function VirtualController({
|
|
|
3991
4136
|
return newValue;
|
|
3992
4137
|
});
|
|
3993
4138
|
}, []);
|
|
4139
|
+
const toggleHoldMode = useCallback(() => {
|
|
4140
|
+
setIsHoldMode((prev) => {
|
|
4141
|
+
if (prev) {
|
|
4142
|
+
onResume();
|
|
4143
|
+
} else {
|
|
4144
|
+
onPause();
|
|
4145
|
+
}
|
|
4146
|
+
return !prev;
|
|
4147
|
+
});
|
|
4148
|
+
}, [onPause, onResume]);
|
|
4149
|
+
const [isTurboMode, setIsTurboMode] = useState(false);
|
|
4150
|
+
const [turboButtons, setTurboButtons] = useState(/* @__PURE__ */ new Set());
|
|
4151
|
+
const toggleTurboMode = useCallback(() => {
|
|
4152
|
+
setIsTurboMode((prev) => {
|
|
4153
|
+
if (prev) {
|
|
4154
|
+
onResume();
|
|
4155
|
+
} else {
|
|
4156
|
+
onPause();
|
|
4157
|
+
}
|
|
4158
|
+
return !prev;
|
|
4159
|
+
});
|
|
4160
|
+
}, [onPause, onResume]);
|
|
3994
4161
|
const layout = getLayoutForSystem(system);
|
|
3995
4162
|
const visibleButtons = layout.buttons.filter((btn) => {
|
|
3996
4163
|
if (isPortrait) {
|
|
@@ -4060,9 +4227,9 @@ function VirtualController({
|
|
|
4060
4227
|
return;
|
|
4061
4228
|
}
|
|
4062
4229
|
setPressedButtons((prev) => new Set(prev).add(buttonType));
|
|
4063
|
-
|
|
4230
|
+
onButtonDown(buttonType);
|
|
4064
4231
|
setTimeout(() => {
|
|
4065
|
-
|
|
4232
|
+
onButtonUp(buttonType);
|
|
4066
4233
|
setPressedButtons((prev) => {
|
|
4067
4234
|
const next = new Set(prev);
|
|
4068
4235
|
next.delete(buttonType);
|
|
@@ -4070,7 +4237,7 @@ function VirtualController({
|
|
|
4070
4237
|
});
|
|
4071
4238
|
}, 100);
|
|
4072
4239
|
},
|
|
4073
|
-
[isRunning, getButtonKeyboardCode]
|
|
4240
|
+
[isRunning, getButtonKeyboardCode, onButtonDown, onButtonUp]
|
|
4074
4241
|
);
|
|
4075
4242
|
const handlePressDown = useCallback(
|
|
4076
4243
|
(buttonType) => {
|
|
@@ -4079,15 +4246,70 @@ function VirtualController({
|
|
|
4079
4246
|
if (isSystemButton) return;
|
|
4080
4247
|
const keyboardCode = getButtonKeyboardCode(buttonType);
|
|
4081
4248
|
if (!keyboardCode) return;
|
|
4249
|
+
if (isHoldMode) {
|
|
4250
|
+
const isHeld = heldButtons.has(buttonType);
|
|
4251
|
+
if (isHeld) {
|
|
4252
|
+
setHeldButtons((prev) => {
|
|
4253
|
+
const next = new Set(prev);
|
|
4254
|
+
next.delete(buttonType);
|
|
4255
|
+
return next;
|
|
4256
|
+
});
|
|
4257
|
+
onButtonUp(buttonType);
|
|
4258
|
+
if (hapticsEnabled && navigator.vibrate) navigator.vibrate(10);
|
|
4259
|
+
} else {
|
|
4260
|
+
setHeldButtons((prev) => {
|
|
4261
|
+
setTurboButtons((turboPrev) => {
|
|
4262
|
+
if (turboPrev.has(buttonType)) {
|
|
4263
|
+
const nextTurbo = new Set(turboPrev);
|
|
4264
|
+
nextTurbo.delete(buttonType);
|
|
4265
|
+
return nextTurbo;
|
|
4266
|
+
}
|
|
4267
|
+
return turboPrev;
|
|
4268
|
+
});
|
|
4269
|
+
return new Set(prev).add(buttonType);
|
|
4270
|
+
});
|
|
4271
|
+
onButtonDown(buttonType);
|
|
4272
|
+
if (hapticsEnabled && navigator.vibrate) navigator.vibrate([10, 30, 10]);
|
|
4273
|
+
}
|
|
4274
|
+
return;
|
|
4275
|
+
}
|
|
4276
|
+
if (isTurboMode) {
|
|
4277
|
+
const isTurbo = turboButtons.has(buttonType);
|
|
4278
|
+
if (isTurbo) {
|
|
4279
|
+
setTurboButtons((prev) => {
|
|
4280
|
+
const next = new Set(prev);
|
|
4281
|
+
next.delete(buttonType);
|
|
4282
|
+
return next;
|
|
4283
|
+
});
|
|
4284
|
+
if (hapticsEnabled && navigator.vibrate) navigator.vibrate(10);
|
|
4285
|
+
} else {
|
|
4286
|
+
setTurboButtons((prev) => {
|
|
4287
|
+
setHeldButtons((holdPrev) => {
|
|
4288
|
+
if (holdPrev.has(buttonType)) {
|
|
4289
|
+
const nextHold = new Set(holdPrev);
|
|
4290
|
+
nextHold.delete(buttonType);
|
|
4291
|
+
onButtonUp(buttonType);
|
|
4292
|
+
return nextHold;
|
|
4293
|
+
}
|
|
4294
|
+
return holdPrev;
|
|
4295
|
+
});
|
|
4296
|
+
return new Set(prev).add(buttonType);
|
|
4297
|
+
});
|
|
4298
|
+
if (hapticsEnabled && navigator.vibrate) navigator.vibrate([5, 10, 5]);
|
|
4299
|
+
}
|
|
4300
|
+
return;
|
|
4301
|
+
}
|
|
4082
4302
|
setPressedButtons((prev) => {
|
|
4083
4303
|
if (prev.has(buttonType)) return prev;
|
|
4084
4304
|
const next = new Set(prev);
|
|
4085
4305
|
next.add(buttonType);
|
|
4086
4306
|
return next;
|
|
4087
4307
|
});
|
|
4088
|
-
|
|
4308
|
+
if (!heldButtons.has(buttonType)) {
|
|
4309
|
+
onButtonDown(buttonType);
|
|
4310
|
+
}
|
|
4089
4311
|
},
|
|
4090
|
-
[isRunning, getButtonKeyboardCode]
|
|
4312
|
+
[isRunning, getButtonKeyboardCode, isHoldMode, isTurboMode, heldButtons, hapticsEnabled, onButtonDown, onButtonUp]
|
|
4091
4313
|
);
|
|
4092
4314
|
const handleRelease = useCallback(
|
|
4093
4315
|
(buttonType) => {
|
|
@@ -4095,15 +4317,29 @@ function VirtualController({
|
|
|
4095
4317
|
if (isSystemButton) return;
|
|
4096
4318
|
const keyboardCode = getButtonKeyboardCode(buttonType);
|
|
4097
4319
|
if (!keyboardCode) return;
|
|
4320
|
+
if (isHoldMode) return;
|
|
4321
|
+
if (heldButtons.has(buttonType)) {
|
|
4322
|
+
if (!isHoldMode) {
|
|
4323
|
+
setHeldButtons((prev) => {
|
|
4324
|
+
const next = new Set(prev);
|
|
4325
|
+
next.delete(buttonType);
|
|
4326
|
+
return next;
|
|
4327
|
+
});
|
|
4328
|
+
onButtonUp(buttonType);
|
|
4329
|
+
if (hapticsEnabled && navigator.vibrate) navigator.vibrate(10);
|
|
4330
|
+
}
|
|
4331
|
+
return;
|
|
4332
|
+
}
|
|
4333
|
+
if (isTurboMode) return;
|
|
4098
4334
|
setPressedButtons((prev) => {
|
|
4099
4335
|
if (!prev.has(buttonType)) return prev;
|
|
4100
4336
|
const next = new Set(prev);
|
|
4101
4337
|
next.delete(buttonType);
|
|
4102
4338
|
return next;
|
|
4103
4339
|
});
|
|
4104
|
-
|
|
4340
|
+
onButtonUp(buttonType);
|
|
4105
4341
|
},
|
|
4106
|
-
[getButtonKeyboardCode]
|
|
4342
|
+
[getButtonKeyboardCode, isHoldMode, isTurboMode, heldButtons, hapticsEnabled, onButtonUp]
|
|
4107
4343
|
);
|
|
4108
4344
|
useEffect(() => {
|
|
4109
4345
|
if (!isRunning && pressedButtons.size > 0) {
|
|
@@ -4115,6 +4351,22 @@ function VirtualController({
|
|
|
4115
4351
|
setPressedButtons(/* @__PURE__ */ new Set());
|
|
4116
4352
|
}
|
|
4117
4353
|
}, [isRunning, pressedButtons, handleRelease]);
|
|
4354
|
+
const TURBO_RATE = 15;
|
|
4355
|
+
useEffect(() => {
|
|
4356
|
+
pressedButtonsRef.current = pressedButtons;
|
|
4357
|
+
}, [pressedButtons]);
|
|
4358
|
+
useEffect(() => {
|
|
4359
|
+
if (isTurboMode || turboButtons.size === 0) return;
|
|
4360
|
+
const interval = setInterval(() => {
|
|
4361
|
+
turboButtons.forEach((buttonType) => {
|
|
4362
|
+
if (pressedButtonsRef.current.has(buttonType)) {
|
|
4363
|
+
onButtonDown(buttonType);
|
|
4364
|
+
setTimeout(() => onButtonUp(buttonType), 25);
|
|
4365
|
+
}
|
|
4366
|
+
});
|
|
4367
|
+
}, 1e3 / TURBO_RATE);
|
|
4368
|
+
return () => clearInterval(interval);
|
|
4369
|
+
}, [isTurboMode, turboButtons, onButtonDown, onButtonUp]);
|
|
4118
4370
|
const memoizedButtonElements = useMemo(() => {
|
|
4119
4371
|
const width = containerSize.width || (typeof window !== "undefined" ? window.innerWidth : 0);
|
|
4120
4372
|
const height = containerSize.height || (typeof window !== "undefined" ? window.innerHeight : 0);
|
|
@@ -4145,14 +4397,32 @@ function VirtualController({
|
|
|
4145
4397
|
className: "fixed inset-0 z-30 pointer-events-none",
|
|
4146
4398
|
style: { touchAction: "none" },
|
|
4147
4399
|
children: [
|
|
4148
|
-
/* @__PURE__ */
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4400
|
+
/* @__PURE__ */ jsxs("div", { className: "fixed top-4 left-1/2 -translate-x-1/2 z-40 flex gap-4", children: [
|
|
4401
|
+
/* @__PURE__ */ jsx(
|
|
4402
|
+
LockButton,
|
|
4403
|
+
{
|
|
4404
|
+
isLocked,
|
|
4405
|
+
onToggle: toggleLock,
|
|
4406
|
+
systemColor
|
|
4407
|
+
}
|
|
4408
|
+
),
|
|
4409
|
+
/* @__PURE__ */ jsx(
|
|
4410
|
+
HoldButton,
|
|
4411
|
+
{
|
|
4412
|
+
isActive: isHoldMode,
|
|
4413
|
+
onToggle: toggleHoldMode,
|
|
4414
|
+
systemColor
|
|
4415
|
+
}
|
|
4416
|
+
),
|
|
4417
|
+
/* @__PURE__ */ jsx(
|
|
4418
|
+
TurboButton,
|
|
4419
|
+
{
|
|
4420
|
+
isActive: isTurboMode,
|
|
4421
|
+
onToggle: toggleTurboMode,
|
|
4422
|
+
systemColor
|
|
4423
|
+
}
|
|
4424
|
+
)
|
|
4425
|
+
] }),
|
|
4156
4426
|
/* @__PURE__ */ jsx(
|
|
4157
4427
|
Dpad_default,
|
|
4158
4428
|
{
|
|
@@ -4161,18 +4431,20 @@ function VirtualController({
|
|
|
4161
4431
|
y: finalDpadY,
|
|
4162
4432
|
containerWidth: containerSize.width || window.innerWidth,
|
|
4163
4433
|
containerHeight: containerSize.height || window.innerHeight,
|
|
4164
|
-
controls,
|
|
4165
4434
|
systemColor,
|
|
4166
4435
|
isLandscape,
|
|
4167
4436
|
customPosition: getPosition("up", isLandscape),
|
|
4168
|
-
onPositionChange: isLocked ? void 0 : (x, y) => savePosition("up", x, y, isLandscape)
|
|
4437
|
+
onPositionChange: isLocked ? void 0 : (x, y) => savePosition("up", x, y, isLandscape),
|
|
4438
|
+
hapticsEnabled,
|
|
4439
|
+
onButtonDown,
|
|
4440
|
+
onButtonUp
|
|
4169
4441
|
}
|
|
4170
4442
|
),
|
|
4171
4443
|
memoizedButtonElements.filter(({ buttonConfig }) => !DPAD_TYPES.includes(buttonConfig.type)).map(({ buttonConfig, adjustedConfig, customPosition, width, height }) => /* @__PURE__ */ jsx(
|
|
4172
4444
|
VirtualButton_default,
|
|
4173
4445
|
{
|
|
4174
4446
|
config: adjustedConfig,
|
|
4175
|
-
isPressed: pressedButtons.has(buttonConfig.type),
|
|
4447
|
+
isPressed: pressedButtons.has(buttonConfig.type) || heldButtons.has(buttonConfig.type),
|
|
4176
4448
|
onPress: handlePress,
|
|
4177
4449
|
onPressDown: handlePressDown,
|
|
4178
4450
|
onRelease: handleRelease,
|
|
@@ -4181,10 +4453,24 @@ function VirtualController({
|
|
|
4181
4453
|
customPosition,
|
|
4182
4454
|
onPositionChange: isLocked ? void 0 : (x, y) => savePosition(buttonConfig.type, x, y, isLandscape),
|
|
4183
4455
|
isLandscape,
|
|
4184
|
-
console: layout.console
|
|
4456
|
+
console: layout.console,
|
|
4457
|
+
hapticsEnabled,
|
|
4458
|
+
mode: isHoldMode ? "hold" : isTurboMode ? "turbo" : "normal",
|
|
4459
|
+
isHeld: heldButtons.has(buttonConfig.type),
|
|
4460
|
+
isInTurbo: turboButtons.has(buttonConfig.type)
|
|
4185
4461
|
},
|
|
4186
4462
|
buttonConfig.type
|
|
4187
4463
|
)),
|
|
4464
|
+
(isHoldMode || isTurboMode) && /* @__PURE__ */ jsx(
|
|
4465
|
+
ModeOverlay,
|
|
4466
|
+
{
|
|
4467
|
+
mode: isHoldMode ? "hold" : "turbo",
|
|
4468
|
+
heldButtons,
|
|
4469
|
+
turboButtons,
|
|
4470
|
+
systemColor,
|
|
4471
|
+
onExit: isHoldMode ? toggleHoldMode : toggleTurboMode
|
|
4472
|
+
}
|
|
4473
|
+
),
|
|
4188
4474
|
/* @__PURE__ */ jsx(ControlsHint, { isVisible: isRunning })
|
|
4189
4475
|
]
|
|
4190
4476
|
}
|
|
@@ -4871,7 +5157,8 @@ function CheatModal({
|
|
|
4871
5157
|
cheats,
|
|
4872
5158
|
activeCheats,
|
|
4873
5159
|
onToggle,
|
|
4874
|
-
onClose
|
|
5160
|
+
onClose,
|
|
5161
|
+
onAddManualCheat
|
|
4875
5162
|
}) {
|
|
4876
5163
|
const t = useKoinTranslation();
|
|
4877
5164
|
const [copiedId, setCopiedId] = React2.useState(null);
|
|
@@ -4889,54 +5176,111 @@ function CheatModal({
|
|
|
4889
5176
|
subtitle: t.modals.cheats.available.replace("{{count}}", cheats.length.toString()),
|
|
4890
5177
|
icon: /* @__PURE__ */ jsx(Code, { size: 24, className: "text-purple-400" }),
|
|
4891
5178
|
footer: /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 text-center w-full", children: activeCheats.size > 0 ? t.modals.cheats.active.replace("{{count}}", activeCheats.size.toString()) : t.modals.cheats.toggleHint }),
|
|
4892
|
-
children: /* @__PURE__ */
|
|
4893
|
-
/* @__PURE__ */
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
flex-shrink-0 w-6 h-6 rounded border-2 flex items-center justify-center transition-all
|
|
4912
|
-
${isActive ? "border-purple-500 bg-purple-500" : "border-gray-600 group-hover:border-gray-400"}
|
|
4913
|
-
`,
|
|
4914
|
-
children: isActive && /* @__PURE__ */ jsx(Check, { size: 14, className: "text-white" })
|
|
5179
|
+
children: /* @__PURE__ */ jsxs("div", { className: "p-4 space-y-4 max-h-[400px] overflow-y-auto", children: [
|
|
5180
|
+
onAddManualCheat && /* @__PURE__ */ jsxs("div", { className: "p-3 bg-white/5 rounded-lg border border-white/10 space-y-3", children: [
|
|
5181
|
+
/* @__PURE__ */ jsx("div", { className: "flex gap-2", children: /* @__PURE__ */ jsx(
|
|
5182
|
+
"input",
|
|
5183
|
+
{
|
|
5184
|
+
type: "text",
|
|
5185
|
+
placeholder: t.modals.cheats.codePlaceholder || "Enter cheat code",
|
|
5186
|
+
className: "flex-1 bg-black/50 border border-white/20 rounded px-3 py-2 text-sm text-white placeholder-gray-500 focus:outline-none focus:border-purple-500",
|
|
5187
|
+
onKeyDown: (e) => {
|
|
5188
|
+
if (e.key === "Enter") {
|
|
5189
|
+
const input = e.currentTarget;
|
|
5190
|
+
const code = input.value.trim();
|
|
5191
|
+
if (code) {
|
|
5192
|
+
const descInput = input.parentElement?.nextElementSibling?.querySelector("input");
|
|
5193
|
+
const desc = descInput?.value.trim() || "Custom Cheat";
|
|
5194
|
+
onAddManualCheat(code, desc);
|
|
5195
|
+
input.value = "";
|
|
5196
|
+
if (descInput) descInput.value = "";
|
|
5197
|
+
}
|
|
4915
5198
|
}
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
5199
|
+
}
|
|
5200
|
+
}
|
|
5201
|
+
) }),
|
|
5202
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
5203
|
+
/* @__PURE__ */ jsx(
|
|
5204
|
+
"input",
|
|
5205
|
+
{
|
|
5206
|
+
type: "text",
|
|
5207
|
+
placeholder: t.modals.cheats.descPlaceholder || "Description (optional)",
|
|
5208
|
+
className: "flex-1 bg-black/50 border border-white/20 rounded px-3 py-2 text-sm text-white placeholder-gray-500 focus:outline-none focus:border-purple-500"
|
|
5209
|
+
}
|
|
5210
|
+
),
|
|
5211
|
+
/* @__PURE__ */ jsx(
|
|
5212
|
+
"button",
|
|
5213
|
+
{
|
|
5214
|
+
onClick: (e) => {
|
|
5215
|
+
const descInput = e.currentTarget.previousElementSibling;
|
|
5216
|
+
const codeInput = e.currentTarget.parentElement?.previousElementSibling?.querySelector("input");
|
|
5217
|
+
const code = codeInput?.value.trim();
|
|
5218
|
+
const desc = descInput?.value.trim() || "Custom Cheat";
|
|
5219
|
+
if (code) {
|
|
5220
|
+
onAddManualCheat(code, desc);
|
|
5221
|
+
codeInput.value = "";
|
|
5222
|
+
descInput.value = "";
|
|
5223
|
+
}
|
|
5224
|
+
},
|
|
5225
|
+
className: "px-3 py-2 bg-purple-600 hover:bg-purple-500 text-white text-sm rounded transition-colors",
|
|
5226
|
+
children: t.modals.cheats.add || "Add"
|
|
5227
|
+
}
|
|
5228
|
+
)
|
|
5229
|
+
] })
|
|
5230
|
+
] }),
|
|
5231
|
+
cheats.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "text-center py-12 text-gray-500", children: [
|
|
5232
|
+
/* @__PURE__ */ jsx(Code, { size: 48, className: "mx-auto mb-3 opacity-50" }),
|
|
5233
|
+
/* @__PURE__ */ jsx("p", { className: "font-medium", children: t.modals.cheats.emptyTitle }),
|
|
5234
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm mt-1", children: t.modals.cheats.emptyDesc })
|
|
5235
|
+
] }) : /* @__PURE__ */ jsx("div", { className: "space-y-2", children: cheats.map((cheat) => {
|
|
5236
|
+
const isActive = activeCheats.has(cheat.id);
|
|
5237
|
+
const isManual = cheat.source === "manual";
|
|
5238
|
+
return /* @__PURE__ */ jsxs(
|
|
5239
|
+
"div",
|
|
5240
|
+
{
|
|
5241
|
+
className: `
|
|
5242
|
+
group flex items-start gap-4 p-4 rounded-lg border transition-all cursor-pointer
|
|
5243
|
+
${isActive ? "border-purple-500/50 bg-purple-500/10" : "border-white/10 bg-white/5 hover:border-white/20 hover:bg-white/10"}
|
|
5244
|
+
`,
|
|
5245
|
+
onClick: () => onToggle(cheat.id),
|
|
5246
|
+
children: [
|
|
5247
|
+
/* @__PURE__ */ jsx(
|
|
5248
|
+
"div",
|
|
5249
|
+
{
|
|
5250
|
+
className: `
|
|
5251
|
+
flex-shrink-0 w-6 h-6 rounded border-2 flex items-center justify-center transition-all
|
|
5252
|
+
${isActive ? "border-purple-500 bg-purple-500" : "border-gray-600 group-hover:border-gray-400"}
|
|
5253
|
+
`,
|
|
5254
|
+
children: isActive && /* @__PURE__ */ jsx(Check, { size: 14, className: "text-white" })
|
|
5255
|
+
}
|
|
5256
|
+
),
|
|
5257
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
5258
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
5259
|
+
isManual && /* @__PURE__ */ jsx("span", { className: "text-xs bg-purple-500/20 text-purple-300 px-1.5 py-0.5 rounded border border-purple-500/30", children: "USER" }),
|
|
5260
|
+
/* @__PURE__ */ jsx("p", { className: `font-medium ${isActive ? "text-purple-300" : "text-white"}`, children: cheat.description })
|
|
5261
|
+
] }),
|
|
5262
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-2", children: [
|
|
5263
|
+
/* @__PURE__ */ jsx("code", { className: "px-2 py-1 bg-black/50 rounded text-xs font-mono text-gray-400 truncate max-w-[200px]", children: cheat.code }),
|
|
5264
|
+
/* @__PURE__ */ jsx(
|
|
5265
|
+
"button",
|
|
5266
|
+
{
|
|
5267
|
+
onClick: (e) => {
|
|
5268
|
+
e.stopPropagation();
|
|
5269
|
+
handleCopy(cheat.code, cheat.id);
|
|
5270
|
+
},
|
|
5271
|
+
className: "p-1.5 rounded hover:bg-white/10 text-gray-500 hover:text-white transition-colors",
|
|
5272
|
+
title: t.modals.cheats.copy,
|
|
5273
|
+
children: copiedId === cheat.id ? /* @__PURE__ */ jsx(Check, { size: 14, className: "text-green-400" }) : /* @__PURE__ */ jsx(Copy, { size: 14 })
|
|
5274
|
+
}
|
|
5275
|
+
)
|
|
5276
|
+
] })
|
|
4933
5277
|
] })
|
|
4934
|
-
]
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
)
|
|
4939
|
-
|
|
5278
|
+
]
|
|
5279
|
+
},
|
|
5280
|
+
cheat.id
|
|
5281
|
+
);
|
|
5282
|
+
}) })
|
|
5283
|
+
] })
|
|
4940
5284
|
}
|
|
4941
5285
|
);
|
|
4942
5286
|
}
|
|
@@ -5268,7 +5612,9 @@ function SettingsModal({
|
|
|
5268
5612
|
onClose,
|
|
5269
5613
|
currentLanguage,
|
|
5270
5614
|
onLanguageChange,
|
|
5271
|
-
systemColor = "#00FF41"
|
|
5615
|
+
systemColor = "#00FF41",
|
|
5616
|
+
hapticsEnabled,
|
|
5617
|
+
onToggleHaptics
|
|
5272
5618
|
}) {
|
|
5273
5619
|
const t = useKoinTranslation();
|
|
5274
5620
|
const languages = [
|
|
@@ -5292,30 +5638,57 @@ function SettingsModal({
|
|
|
5292
5638
|
children: t.modals.shortcuts.pressEsc
|
|
5293
5639
|
}
|
|
5294
5640
|
),
|
|
5295
|
-
children: /* @__PURE__ */
|
|
5296
|
-
/* @__PURE__ */ jsxs("div", { className: "
|
|
5297
|
-
/* @__PURE__ */
|
|
5298
|
-
|
|
5641
|
+
children: /* @__PURE__ */ jsxs("div", { className: "p-6 space-y-6", children: [
|
|
5642
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
5643
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm font-medium text-gray-400", children: [
|
|
5644
|
+
/* @__PURE__ */ jsx(Globe, { size: 16 }),
|
|
5645
|
+
/* @__PURE__ */ jsx("span", { children: t.settings.language })
|
|
5646
|
+
] }),
|
|
5647
|
+
/* @__PURE__ */ jsx("div", { className: "grid gap-2", children: languages.map((lang) => {
|
|
5648
|
+
const isActive = currentLanguage === lang.code;
|
|
5649
|
+
return /* @__PURE__ */ jsxs(
|
|
5650
|
+
"button",
|
|
5651
|
+
{
|
|
5652
|
+
onClick: () => onLanguageChange(lang.code),
|
|
5653
|
+
className: `
|
|
5654
|
+
flex items-center justify-between px-4 py-3 rounded-lg border transition-all
|
|
5655
|
+
${isActive ? "bg-white/10 border-white/20 text-white" : "bg-black/20 border-transparent text-gray-400 hover:bg-white/5 hover:text-white"}
|
|
5656
|
+
`,
|
|
5657
|
+
children: [
|
|
5658
|
+
/* @__PURE__ */ jsx("span", { children: lang.name }),
|
|
5659
|
+
isActive && /* @__PURE__ */ jsx(Check, { size: 16, style: { color: systemColor } })
|
|
5660
|
+
]
|
|
5661
|
+
},
|
|
5662
|
+
lang.code
|
|
5663
|
+
);
|
|
5664
|
+
}) })
|
|
5299
5665
|
] }),
|
|
5300
|
-
/* @__PURE__ */
|
|
5301
|
-
|
|
5302
|
-
|
|
5666
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
5667
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm font-medium text-gray-400", children: [
|
|
5668
|
+
/* @__PURE__ */ jsx(Zap, { size: 16 }),
|
|
5669
|
+
/* @__PURE__ */ jsx("span", { children: t.settings.haptics })
|
|
5670
|
+
] }),
|
|
5671
|
+
/* @__PURE__ */ jsxs(
|
|
5303
5672
|
"button",
|
|
5304
5673
|
{
|
|
5305
|
-
onClick:
|
|
5674
|
+
onClick: onToggleHaptics,
|
|
5306
5675
|
className: `
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5676
|
+
w-full flex items-center justify-between px-4 py-3 rounded-lg border transition-all
|
|
5677
|
+
${hapticsEnabled ? "bg-white/10 border-white/20 text-white" : "bg-black/20 border-transparent text-gray-400 hover:bg-white/5 hover:text-white"}
|
|
5678
|
+
`,
|
|
5310
5679
|
children: [
|
|
5311
|
-
/* @__PURE__ */ jsx("span", { children:
|
|
5312
|
-
|
|
5680
|
+
/* @__PURE__ */ jsx("span", { children: t.settings.enableHaptics }),
|
|
5681
|
+
/* @__PURE__ */ jsx("div", { className: `w-10 h-6 rounded-full p-1 transition-colors ${hapticsEnabled ? "bg-[#00FF41]" : "bg-gray-700"}`, children: /* @__PURE__ */ jsx(
|
|
5682
|
+
"div",
|
|
5683
|
+
{
|
|
5684
|
+
className: `w-4 h-4 rounded-full bg-white transition-transform ${hapticsEnabled ? "translate-x-4" : "translate-x-0"}`
|
|
5685
|
+
}
|
|
5686
|
+
) })
|
|
5313
5687
|
]
|
|
5314
|
-
}
|
|
5315
|
-
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
] }) })
|
|
5688
|
+
}
|
|
5689
|
+
)
|
|
5690
|
+
] })
|
|
5691
|
+
] })
|
|
5319
5692
|
}
|
|
5320
5693
|
);
|
|
5321
5694
|
}
|
|
@@ -5335,6 +5708,7 @@ function GameModals({
|
|
|
5335
5708
|
cheats,
|
|
5336
5709
|
activeCheats,
|
|
5337
5710
|
onToggleCheat,
|
|
5711
|
+
onAddManualCheat,
|
|
5338
5712
|
saveModalOpen,
|
|
5339
5713
|
setSaveModalOpen,
|
|
5340
5714
|
saveModalMode,
|
|
@@ -5354,7 +5728,9 @@ function GameModals({
|
|
|
5354
5728
|
settingsModalOpen,
|
|
5355
5729
|
setSettingsModalOpen,
|
|
5356
5730
|
currentLanguage,
|
|
5357
|
-
onLanguageChange
|
|
5731
|
+
onLanguageChange,
|
|
5732
|
+
hapticsEnabled,
|
|
5733
|
+
onToggleHaptics
|
|
5358
5734
|
}) {
|
|
5359
5735
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5360
5736
|
/* @__PURE__ */ jsx(
|
|
@@ -5389,6 +5765,7 @@ function GameModals({
|
|
|
5389
5765
|
cheats,
|
|
5390
5766
|
activeCheats,
|
|
5391
5767
|
onToggle: onToggleCheat,
|
|
5768
|
+
onAddManualCheat,
|
|
5392
5769
|
onClose: () => {
|
|
5393
5770
|
setCheatsModalOpen(false);
|
|
5394
5771
|
onResume();
|
|
@@ -5441,7 +5818,9 @@ function GameModals({
|
|
|
5441
5818
|
},
|
|
5442
5819
|
currentLanguage,
|
|
5443
5820
|
onLanguageChange,
|
|
5444
|
-
systemColor
|
|
5821
|
+
systemColor,
|
|
5822
|
+
hapticsEnabled,
|
|
5823
|
+
onToggleHaptics
|
|
5445
5824
|
}
|
|
5446
5825
|
)
|
|
5447
5826
|
] });
|
|
@@ -6736,6 +7115,11 @@ function useEmulatorCore({
|
|
|
6736
7115
|
input_volume_up: "add",
|
|
6737
7116
|
input_volume_down: "subtract",
|
|
6738
7117
|
input_audio_mute: "f9",
|
|
7118
|
+
// Cheat hotkeys
|
|
7119
|
+
quick_menu_show_cheats: true,
|
|
7120
|
+
input_cheat_index_plus: "y",
|
|
7121
|
+
input_cheat_index_minus: "t",
|
|
7122
|
+
input_cheat_toggle: "u",
|
|
6739
7123
|
...inputConfig,
|
|
6740
7124
|
...specificConfig,
|
|
6741
7125
|
// Apply system-specific optimizations
|
|
@@ -6829,18 +7213,15 @@ function useEmulatorCore({
|
|
|
6829
7213
|
const restart = useCallback(async () => {
|
|
6830
7214
|
if (nostalgistRef.current) {
|
|
6831
7215
|
try {
|
|
6832
|
-
|
|
6833
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
6834
|
-
nostalgistRef.current.resume();
|
|
6835
|
-
setIsPaused(false);
|
|
6836
|
-
setStatus("running");
|
|
6837
|
-
} catch (err) {
|
|
6838
|
-
console.error("[Nostalgist] Restart error:", err);
|
|
7216
|
+
console.log("[Nostalgist] Full restart - stopping, re-preparing with fresh config, starting");
|
|
6839
7217
|
stop();
|
|
7218
|
+
await prepare();
|
|
6840
7219
|
await start();
|
|
7220
|
+
} catch (err) {
|
|
7221
|
+
console.error("[Nostalgist] Restart error:", err);
|
|
6841
7222
|
}
|
|
6842
7223
|
}
|
|
6843
|
-
}, [stop, start]);
|
|
7224
|
+
}, [stop, prepare, start]);
|
|
6844
7225
|
const pause = useCallback(() => {
|
|
6845
7226
|
if (nostalgistRef.current && !isPaused && status === "running") {
|
|
6846
7227
|
try {
|
|
@@ -6933,9 +7314,6 @@ function useEmulatorCore({
|
|
|
6933
7314
|
console.error("[Nostalgist] Resize error:", err);
|
|
6934
7315
|
}
|
|
6935
7316
|
}, []);
|
|
6936
|
-
useCallback(() => {
|
|
6937
|
-
return nostalgistRef.current;
|
|
6938
|
-
}, []);
|
|
6939
7317
|
return {
|
|
6940
7318
|
status,
|
|
6941
7319
|
setStatus,
|
|
@@ -7062,8 +7440,26 @@ function useEmulatorInput({ nostalgistRef }) {
|
|
|
7062
7440
|
console.error("[Nostalgist] Press key error:", err);
|
|
7063
7441
|
}
|
|
7064
7442
|
}, [nostalgistRef]);
|
|
7443
|
+
const pressDown = useCallback((button) => {
|
|
7444
|
+
if (!nostalgistRef.current) return;
|
|
7445
|
+
try {
|
|
7446
|
+
nostalgistRef.current.pressDown(button);
|
|
7447
|
+
} catch (err) {
|
|
7448
|
+
console.error("[Nostalgist] Press down error:", err);
|
|
7449
|
+
}
|
|
7450
|
+
}, [nostalgistRef]);
|
|
7451
|
+
const pressUp = useCallback((button) => {
|
|
7452
|
+
if (!nostalgistRef.current) return;
|
|
7453
|
+
try {
|
|
7454
|
+
nostalgistRef.current.pressUp(button);
|
|
7455
|
+
} catch (err) {
|
|
7456
|
+
console.error("[Nostalgist] Press up error:", err);
|
|
7457
|
+
}
|
|
7458
|
+
}, [nostalgistRef]);
|
|
7065
7459
|
return {
|
|
7066
|
-
pressKey
|
|
7460
|
+
pressKey,
|
|
7461
|
+
pressDown,
|
|
7462
|
+
pressUp
|
|
7067
7463
|
};
|
|
7068
7464
|
}
|
|
7069
7465
|
var MIN_SAVE_INTERVAL = 100;
|
|
@@ -7330,26 +7726,53 @@ function useEmulatorSaves({ nostalgistRef, isPaused, setIsPaused, setStatus, rew
|
|
|
7330
7726
|
stopRewindCapture
|
|
7331
7727
|
};
|
|
7332
7728
|
}
|
|
7333
|
-
function useEmulatorCheats({
|
|
7334
|
-
|
|
7729
|
+
function useEmulatorCheats({
|
|
7730
|
+
nostalgistRef
|
|
7731
|
+
}) {
|
|
7732
|
+
const allocatedPointersRef = useRef([]);
|
|
7733
|
+
const injectCheats = useCallback((cheats) => {
|
|
7335
7734
|
if (!nostalgistRef.current) return;
|
|
7336
|
-
|
|
7337
|
-
|
|
7338
|
-
|
|
7339
|
-
|
|
7735
|
+
const module = nostalgistRef.current.getEmscriptenModule();
|
|
7736
|
+
if (!module) return;
|
|
7737
|
+
if (module._free && allocatedPointersRef.current.length > 0) {
|
|
7738
|
+
allocatedPointersRef.current.forEach((ptr) => {
|
|
7739
|
+
try {
|
|
7740
|
+
module._free(ptr);
|
|
7741
|
+
} catch (e) {
|
|
7742
|
+
}
|
|
7743
|
+
});
|
|
7744
|
+
allocatedPointersRef.current = [];
|
|
7340
7745
|
}
|
|
7341
|
-
|
|
7342
|
-
|
|
7343
|
-
|
|
7344
|
-
|
|
7345
|
-
|
|
7346
|
-
}
|
|
7347
|
-
|
|
7746
|
+
if (module._cmd_cheat_realloc) {
|
|
7747
|
+
module._cmd_cheat_realloc(0);
|
|
7748
|
+
if (cheats.length > 0) {
|
|
7749
|
+
module._cmd_cheat_realloc(cheats.length);
|
|
7750
|
+
}
|
|
7751
|
+
}
|
|
7752
|
+
if (cheats.length > 0 && module.stringToNewUTF8 && module._cmd_cheat_set_code) {
|
|
7753
|
+
cheats.forEach((cheat, index) => {
|
|
7754
|
+
try {
|
|
7755
|
+
const ptr = module.stringToNewUTF8(cheat.code);
|
|
7756
|
+
allocatedPointersRef.current.push(ptr);
|
|
7757
|
+
module._cmd_cheat_set_code(index, ptr);
|
|
7758
|
+
if (module._cmd_cheat_toggle_index) {
|
|
7759
|
+
module._cmd_cheat_toggle_index(index, true);
|
|
7760
|
+
}
|
|
7761
|
+
} catch (err) {
|
|
7762
|
+
console.error("[Cheats] Failed to inject cheat:", index, err);
|
|
7763
|
+
}
|
|
7764
|
+
});
|
|
7765
|
+
}
|
|
7766
|
+
if (module._cmd_cheat_apply_cheats) {
|
|
7767
|
+
module._cmd_cheat_apply_cheats();
|
|
7348
7768
|
}
|
|
7349
7769
|
}, [nostalgistRef]);
|
|
7770
|
+
const clearCheats = useCallback(() => {
|
|
7771
|
+
injectCheats([]);
|
|
7772
|
+
}, [injectCheats]);
|
|
7350
7773
|
return {
|
|
7351
|
-
|
|
7352
|
-
|
|
7774
|
+
injectCheats,
|
|
7775
|
+
clearCheats
|
|
7353
7776
|
};
|
|
7354
7777
|
}
|
|
7355
7778
|
|
|
@@ -7421,7 +7844,9 @@ var useNostalgist = ({
|
|
|
7421
7844
|
initialVolume
|
|
7422
7845
|
});
|
|
7423
7846
|
const {
|
|
7424
|
-
pressKey
|
|
7847
|
+
pressKey,
|
|
7848
|
+
pressDown,
|
|
7849
|
+
pressUp
|
|
7425
7850
|
} = useEmulatorInput({
|
|
7426
7851
|
nostalgistRef
|
|
7427
7852
|
});
|
|
@@ -7444,8 +7869,8 @@ var useNostalgist = ({
|
|
|
7444
7869
|
// Disable manual rewind loop for heavy systems
|
|
7445
7870
|
});
|
|
7446
7871
|
const {
|
|
7447
|
-
|
|
7448
|
-
|
|
7872
|
+
injectCheats,
|
|
7873
|
+
clearCheats
|
|
7449
7874
|
} = useEmulatorCheats({
|
|
7450
7875
|
nostalgistRef
|
|
7451
7876
|
});
|
|
@@ -7488,9 +7913,11 @@ var useNostalgist = ({
|
|
|
7488
7913
|
toggleMute,
|
|
7489
7914
|
screenshot,
|
|
7490
7915
|
pressKey,
|
|
7916
|
+
pressDown,
|
|
7917
|
+
pressUp,
|
|
7491
7918
|
resize,
|
|
7492
|
-
|
|
7493
|
-
|
|
7919
|
+
injectCheats,
|
|
7920
|
+
clearCheats,
|
|
7494
7921
|
getNostalgistInstance,
|
|
7495
7922
|
isPerformanceMode
|
|
7496
7923
|
}), [
|
|
@@ -7520,9 +7947,11 @@ var useNostalgist = ({
|
|
|
7520
7947
|
toggleMute,
|
|
7521
7948
|
screenshot,
|
|
7522
7949
|
pressKey,
|
|
7950
|
+
pressDown,
|
|
7951
|
+
pressUp,
|
|
7523
7952
|
resize,
|
|
7524
|
-
|
|
7525
|
-
|
|
7953
|
+
injectCheats,
|
|
7954
|
+
clearCheats,
|
|
7526
7955
|
getNostalgistInstance,
|
|
7527
7956
|
isPerformanceMode
|
|
7528
7957
|
]);
|
|
@@ -8253,39 +8682,95 @@ function useGameSaves({
|
|
|
8253
8682
|
handleAutoSaveToggle
|
|
8254
8683
|
};
|
|
8255
8684
|
}
|
|
8685
|
+
var generateManualId = () => `manual-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
8256
8686
|
function useGameCheats({
|
|
8257
8687
|
nostalgist,
|
|
8258
8688
|
cheats = [],
|
|
8259
|
-
onToggleCheat
|
|
8689
|
+
onToggleCheat,
|
|
8690
|
+
showToast,
|
|
8691
|
+
romId
|
|
8260
8692
|
}) {
|
|
8261
8693
|
const [cheatsModalOpen, setCheatsModalOpen] = useState(false);
|
|
8262
8694
|
const [activeCheats, setActiveCheats] = useState(/* @__PURE__ */ new Set());
|
|
8695
|
+
const [manualCheatsInternal, setManualCheatsInternal] = useState([]);
|
|
8696
|
+
const [isLoaded, setIsLoaded] = useState(false);
|
|
8697
|
+
const cheatStorageKey = romId ? `koin_cheats_${romId}` : null;
|
|
8698
|
+
useEffect(() => {
|
|
8699
|
+
if (!cheatStorageKey) return;
|
|
8700
|
+
setIsLoaded(false);
|
|
8701
|
+
try {
|
|
8702
|
+
const stored = localStorage.getItem(cheatStorageKey);
|
|
8703
|
+
if (stored) {
|
|
8704
|
+
const parsed = JSON.parse(stored);
|
|
8705
|
+
if (Array.isArray(parsed)) {
|
|
8706
|
+
setManualCheatsInternal(parsed);
|
|
8707
|
+
}
|
|
8708
|
+
}
|
|
8709
|
+
} catch (e) {
|
|
8710
|
+
console.error("[Cheats] Failed to load cheats:", e);
|
|
8711
|
+
} finally {
|
|
8712
|
+
setIsLoaded(true);
|
|
8713
|
+
}
|
|
8714
|
+
}, [cheatStorageKey]);
|
|
8715
|
+
useEffect(() => {
|
|
8716
|
+
if (!cheatStorageKey || !isLoaded) return;
|
|
8717
|
+
localStorage.setItem(cheatStorageKey, JSON.stringify(manualCheatsInternal));
|
|
8718
|
+
}, [manualCheatsInternal, cheatStorageKey, isLoaded]);
|
|
8719
|
+
const allCheats = useMemo(() => {
|
|
8720
|
+
const normalizedExternal = cheats.map((c) => ({
|
|
8721
|
+
id: typeof c.id === "number" ? `db-${c.id}` : c.id,
|
|
8722
|
+
code: c.code,
|
|
8723
|
+
description: c.description,
|
|
8724
|
+
source: "database"
|
|
8725
|
+
}));
|
|
8726
|
+
return [...normalizedExternal, ...manualCheatsInternal];
|
|
8727
|
+
}, [cheats, manualCheatsInternal]);
|
|
8728
|
+
const handleAddManualCheat = (code, description) => {
|
|
8729
|
+
if (!nostalgist) return;
|
|
8730
|
+
const newCheat = {
|
|
8731
|
+
id: generateManualId(),
|
|
8732
|
+
code,
|
|
8733
|
+
description,
|
|
8734
|
+
source: "manual"
|
|
8735
|
+
};
|
|
8736
|
+
setManualCheatsInternal((prev) => [...prev, newCheat]);
|
|
8737
|
+
setActiveCheats((prev) => new Set(prev).add(newCheat.id));
|
|
8738
|
+
setCheatsModalOpen(false);
|
|
8739
|
+
const currentActiveCheats = [...activeCheats, newCheat.id];
|
|
8740
|
+
const cheatsToInject = allCheats.filter((c) => currentActiveCheats.includes(c.id) || c.id === newCheat.id).concat([newCheat]);
|
|
8741
|
+
nostalgist.injectCheats(cheatsToInject.map((c) => ({ code: c.code })));
|
|
8742
|
+
nostalgist.resume();
|
|
8743
|
+
showToast?.("Cheat added!", "success");
|
|
8744
|
+
};
|
|
8263
8745
|
const handleToggleCheat = (cheatId) => {
|
|
8264
8746
|
if (!nostalgist) return;
|
|
8265
8747
|
const newActiveCheats = new Set(activeCheats);
|
|
8266
8748
|
const isActive = newActiveCheats.has(cheatId);
|
|
8267
8749
|
if (isActive) {
|
|
8268
8750
|
newActiveCheats.delete(cheatId);
|
|
8751
|
+
showToast?.("Cheat Disabled");
|
|
8269
8752
|
} else {
|
|
8270
8753
|
newActiveCheats.add(cheatId);
|
|
8754
|
+
showToast?.("Cheat Enabled", "success");
|
|
8271
8755
|
}
|
|
8272
8756
|
setActiveCheats(newActiveCheats);
|
|
8273
|
-
onToggleCheat
|
|
8274
|
-
|
|
8275
|
-
|
|
8276
|
-
|
|
8277
|
-
|
|
8278
|
-
|
|
8279
|
-
|
|
8280
|
-
|
|
8281
|
-
});
|
|
8282
|
-
}, 50);
|
|
8757
|
+
if (onToggleCheat) {
|
|
8758
|
+
const numericId = cheatId.startsWith("db-") ? parseInt(cheatId.slice(3), 10) : void 0;
|
|
8759
|
+
if (numericId !== void 0) {
|
|
8760
|
+
onToggleCheat(numericId, !isActive);
|
|
8761
|
+
}
|
|
8762
|
+
}
|
|
8763
|
+
const cheatsToInject = allCheats.filter((c) => newActiveCheats.has(c.id)).map((c) => ({ code: c.code }));
|
|
8764
|
+
nostalgist.injectCheats(cheatsToInject);
|
|
8283
8765
|
};
|
|
8284
8766
|
return {
|
|
8285
8767
|
cheatsModalOpen,
|
|
8286
8768
|
setCheatsModalOpen,
|
|
8287
8769
|
activeCheats,
|
|
8288
|
-
|
|
8770
|
+
allCheats,
|
|
8771
|
+
// Unified list - replaces separate cheats + manualCheats
|
|
8772
|
+
handleToggleCheat,
|
|
8773
|
+
handleAddManualCheat
|
|
8289
8774
|
};
|
|
8290
8775
|
}
|
|
8291
8776
|
function useGameRecording({
|
|
@@ -8462,10 +8947,13 @@ function useGamePlayer(props) {
|
|
|
8462
8947
|
cheatsModalOpen,
|
|
8463
8948
|
setCheatsModalOpen,
|
|
8464
8949
|
activeCheats,
|
|
8465
|
-
|
|
8950
|
+
allCheats,
|
|
8951
|
+
handleToggleCheat,
|
|
8952
|
+
handleAddManualCheat
|
|
8466
8953
|
} = useGameCheats({
|
|
8467
8954
|
...props,
|
|
8468
|
-
nostalgist
|
|
8955
|
+
nostalgist,
|
|
8956
|
+
showToast
|
|
8469
8957
|
});
|
|
8470
8958
|
const {
|
|
8471
8959
|
isRecording,
|
|
@@ -8521,7 +9009,9 @@ function useGamePlayer(props) {
|
|
|
8521
9009
|
hardcoreRestrictions,
|
|
8522
9010
|
// Cheats
|
|
8523
9011
|
activeCheats,
|
|
9012
|
+
allCheats,
|
|
8524
9013
|
handleToggleCheat,
|
|
9014
|
+
handleAddManualCheat,
|
|
8525
9015
|
// Emulator
|
|
8526
9016
|
nostalgist,
|
|
8527
9017
|
volumeState,
|
|
@@ -8549,7 +9039,8 @@ var DEFAULT_SETTINGS = {
|
|
|
8549
9039
|
muted: false,
|
|
8550
9040
|
shader: "",
|
|
8551
9041
|
showPerformanceOverlay: false,
|
|
8552
|
-
showInputDisplay: false
|
|
9042
|
+
showInputDisplay: false,
|
|
9043
|
+
hapticsEnabled: true
|
|
8553
9044
|
};
|
|
8554
9045
|
function usePlayerPersistence(onSettingsChange) {
|
|
8555
9046
|
const [settings, setSettings] = useState(DEFAULT_SETTINGS);
|
|
@@ -8656,7 +9147,9 @@ var es = {
|
|
|
8656
9147
|
shortcuts: "Atajos",
|
|
8657
9148
|
exit: "Salir",
|
|
8658
9149
|
language: "Idioma",
|
|
8659
|
-
selectLanguage: "Seleccionar idioma"
|
|
9150
|
+
selectLanguage: "Seleccionar idioma",
|
|
9151
|
+
haptics: "Vibraci\xF3n",
|
|
9152
|
+
enableHaptics: "Habilitar vibraci\xF3n"
|
|
8660
9153
|
},
|
|
8661
9154
|
overlay: {
|
|
8662
9155
|
play: "JUGAR",
|
|
@@ -8739,7 +9232,10 @@ var es = {
|
|
|
8739
9232
|
emptyDesc: "No se encontraron c\xF3digos para este juego",
|
|
8740
9233
|
copy: "Copiar c\xF3digo",
|
|
8741
9234
|
active: "{{count}} truco{{s}} activo(s)",
|
|
8742
|
-
toggleHint: "Haz clic para activar/desactivar"
|
|
9235
|
+
toggleHint: "Haz clic para activar/desactivar",
|
|
9236
|
+
codePlaceholder: "Introduce c\xF3digo (ej: 00C-048-E6E)",
|
|
9237
|
+
descPlaceholder: "Descripci\xF3n (opcional)",
|
|
9238
|
+
add: "A\xF1adir truco"
|
|
8743
9239
|
},
|
|
8744
9240
|
saveSlots: {
|
|
8745
9241
|
title: "Guardar partida",
|
|
@@ -8884,7 +9380,9 @@ var fr = {
|
|
|
8884
9380
|
shortcuts: "Raccourcis",
|
|
8885
9381
|
exit: "Quitter",
|
|
8886
9382
|
language: "Langue",
|
|
8887
|
-
selectLanguage: "Choisir la langue"
|
|
9383
|
+
selectLanguage: "Choisir la langue",
|
|
9384
|
+
haptics: "Vibration",
|
|
9385
|
+
enableHaptics: "Activer la vibration"
|
|
8888
9386
|
},
|
|
8889
9387
|
overlay: {
|
|
8890
9388
|
play: "JOUER",
|
|
@@ -8967,7 +9465,10 @@ var fr = {
|
|
|
8967
9465
|
emptyDesc: "Aucun code trouv\xE9 pour ce jeu",
|
|
8968
9466
|
copy: "Copier",
|
|
8969
9467
|
active: "{{count}} actif(s)",
|
|
8970
|
-
toggleHint: "Cliquez pour activer/d\xE9sactiver"
|
|
9468
|
+
toggleHint: "Cliquez pour activer/d\xE9sactiver",
|
|
9469
|
+
codePlaceholder: "Entrez le code (ex: 00C-048-E6E)",
|
|
9470
|
+
descPlaceholder: "Description (optionnel)",
|
|
9471
|
+
add: "Ajouter"
|
|
8971
9472
|
},
|
|
8972
9473
|
saveSlots: {
|
|
8973
9474
|
title: "Sauvegardes",
|
|
@@ -9140,7 +9641,9 @@ var GamePlayerInner = memo(function GamePlayerInner2(props) {
|
|
|
9140
9641
|
hardcoreRestrictions,
|
|
9141
9642
|
// Cheats
|
|
9142
9643
|
activeCheats,
|
|
9644
|
+
allCheats,
|
|
9143
9645
|
handleToggleCheat,
|
|
9646
|
+
handleAddManualCheat,
|
|
9144
9647
|
// Emulator Instance & State
|
|
9145
9648
|
nostalgist,
|
|
9146
9649
|
// Contains start, restart, etc.
|
|
@@ -9205,7 +9708,7 @@ var GamePlayerInner = memo(function GamePlayerInner2(props) {
|
|
|
9205
9708
|
if (muted !== settings.muted) toggleMute();
|
|
9206
9709
|
}
|
|
9207
9710
|
}, [settingsLoaded]);
|
|
9208
|
-
const { system, systemColor = "#00FF41",
|
|
9711
|
+
const { system, systemColor = "#00FF41", onExit } = props;
|
|
9209
9712
|
const handlePauseToggle = useCallback(() => {
|
|
9210
9713
|
status === "ready" ? start() : togglePause();
|
|
9211
9714
|
}, [status, start, togglePause]);
|
|
@@ -9261,6 +9764,9 @@ var GamePlayerInner = memo(function GamePlayerInner2(props) {
|
|
|
9261
9764
|
const handleToggleInputDisplay = useCallback(() => {
|
|
9262
9765
|
updateSettings({ showInputDisplay: !settings.showInputDisplay });
|
|
9263
9766
|
}, [updateSettings, settings.showInputDisplay]);
|
|
9767
|
+
const handleToggleHaptics = useCallback(() => {
|
|
9768
|
+
updateSettings({ hapticsEnabled: !settings.hapticsEnabled });
|
|
9769
|
+
}, [updateSettings, settings.hapticsEnabled]);
|
|
9264
9770
|
const handleToggleRecording = useCallback(async () => {
|
|
9265
9771
|
if (!recordingSupported) {
|
|
9266
9772
|
console.warn("[Recording] Not supported in this browser");
|
|
@@ -9347,7 +9853,12 @@ var GamePlayerInner = memo(function GamePlayerInner2(props) {
|
|
|
9347
9853
|
system,
|
|
9348
9854
|
isRunning: status === "running" || status === "paused",
|
|
9349
9855
|
controls,
|
|
9350
|
-
systemColor
|
|
9856
|
+
systemColor,
|
|
9857
|
+
hapticsEnabled: settings.hapticsEnabled,
|
|
9858
|
+
onButtonDown: nostalgist.pressDown,
|
|
9859
|
+
onButtonUp: nostalgist.pressUp,
|
|
9860
|
+
onPause: nostalgist.pause,
|
|
9861
|
+
onResume: nostalgist.resume
|
|
9351
9862
|
}
|
|
9352
9863
|
),
|
|
9353
9864
|
!isFullscreen2 && isMobile && /* @__PURE__ */ jsx(
|
|
@@ -9502,9 +10013,10 @@ var GamePlayerInner = memo(function GamePlayerInner2(props) {
|
|
|
9502
10013
|
systemColor,
|
|
9503
10014
|
cheatsModalOpen,
|
|
9504
10015
|
setCheatsModalOpen,
|
|
9505
|
-
cheats,
|
|
10016
|
+
cheats: allCheats,
|
|
9506
10017
|
activeCheats,
|
|
9507
10018
|
onToggleCheat: handleToggleCheat,
|
|
10019
|
+
onAddManualCheat: handleAddManualCheat,
|
|
9508
10020
|
saveModalOpen,
|
|
9509
10021
|
setSaveModalOpen,
|
|
9510
10022
|
saveModalMode,
|
|
@@ -9524,7 +10036,9 @@ var GamePlayerInner = memo(function GamePlayerInner2(props) {
|
|
|
9524
10036
|
settingsModalOpen,
|
|
9525
10037
|
setSettingsModalOpen,
|
|
9526
10038
|
currentLanguage: props.currentLanguage,
|
|
9527
|
-
onLanguageChange: props.onLanguageChange
|
|
10039
|
+
onLanguageChange: props.onLanguageChange,
|
|
10040
|
+
hapticsEnabled: settings.hapticsEnabled,
|
|
10041
|
+
onToggleHaptics: handleToggleHaptics
|
|
9528
10042
|
}
|
|
9529
10043
|
),
|
|
9530
10044
|
!isMobile && /* @__PURE__ */ jsx(
|