koin.js 1.0.8 → 1.0.10
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 +1 -1
- package/dist/index.js +605 -256
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +606 -257
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +138 -58
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2084,92 +2084,175 @@ function useMobile() {
|
|
|
2084
2084
|
|
|
2085
2085
|
// src/components/VirtualController/layouts.ts
|
|
2086
2086
|
var BUTTON_SMALL = 44;
|
|
2087
|
-
var BUTTON_MEDIUM =
|
|
2088
|
-
var BUTTON_LARGE =
|
|
2089
|
-
var
|
|
2090
|
-
|
|
2087
|
+
var BUTTON_MEDIUM = 56;
|
|
2088
|
+
var BUTTON_LARGE = 68;
|
|
2089
|
+
var START_SELECT_Y = 8;
|
|
2090
|
+
var SELECT_X = 38;
|
|
2091
|
+
var START_X = 62;
|
|
2092
|
+
var DPAD_BUTTONS = [
|
|
2093
|
+
{ type: "up", label: "\u2191", x: 14, y: 55, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
|
|
2094
|
+
{ type: "down", label: "\u2193", x: 14, y: 70, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
|
|
2095
|
+
{ type: "left", label: "\u2190", x: 6, y: 62.5, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
|
|
2096
|
+
{ type: "right", label: "\u2192", x: 22, y: 62.5, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true }
|
|
2097
|
+
];
|
|
2098
|
+
var TWO_BUTTON_LAYOUT = {
|
|
2099
|
+
console: "2BUTTON",
|
|
2091
2100
|
buttons: [
|
|
2092
|
-
|
|
2093
|
-
{ type: "
|
|
2094
|
-
{ type: "
|
|
2095
|
-
{ type: "
|
|
2096
|
-
{ type: "
|
|
2097
|
-
// Action buttons
|
|
2098
|
-
{ type: "b", label: "B", x: 80, y: 64, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
|
|
2099
|
-
{ type: "a", label: "A", x: 92, y: 56, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
|
|
2100
|
-
// System buttons - top center
|
|
2101
|
-
{ type: "select", label: "SEL", x: 38, y: 8, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true },
|
|
2102
|
-
{ type: "start", label: "START", x: 62, y: 8, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true }
|
|
2101
|
+
...DPAD_BUTTONS,
|
|
2102
|
+
{ type: "b", label: "B", x: 78, y: 66, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
|
|
2103
|
+
{ type: "a", label: "A", x: 90, y: 56, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
|
|
2104
|
+
{ type: "select", label: "SELECT", x: SELECT_X, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" },
|
|
2105
|
+
{ type: "start", label: "START", x: START_X, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" }
|
|
2103
2106
|
]
|
|
2104
2107
|
};
|
|
2105
2108
|
var SNES_LAYOUT = {
|
|
2106
2109
|
console: "SNES",
|
|
2107
2110
|
buttons: [
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
{ type: "
|
|
2111
|
-
{ type: "
|
|
2112
|
-
{ type: "
|
|
2113
|
-
{ type: "
|
|
2114
|
-
|
|
2115
|
-
{ type: "
|
|
2116
|
-
{ type: "
|
|
2117
|
-
{ type: "
|
|
2118
|
-
{ type: "
|
|
2119
|
-
{ type: "start", label: "START", x: 62, y: 8, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true }
|
|
2111
|
+
...DPAD_BUTTONS,
|
|
2112
|
+
// Diamond: Y-X-B-A (left-top-bottom-right)
|
|
2113
|
+
{ type: "y", label: "Y", x: 74, y: 58, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
|
|
2114
|
+
{ type: "x", label: "X", x: 84, y: 46, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
|
|
2115
|
+
{ type: "b", label: "B", x: 84, y: 70, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
|
|
2116
|
+
{ type: "a", label: "A", x: 94, y: 58, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
|
|
2117
|
+
// Shoulders
|
|
2118
|
+
{ type: "l", label: "L", x: 8, y: 20, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
|
|
2119
|
+
{ type: "r", label: "R", x: 92, y: 20, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
|
|
2120
|
+
{ type: "select", label: "SELECT", x: SELECT_X, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" },
|
|
2121
|
+
{ type: "start", label: "START", x: START_X, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" }
|
|
2120
2122
|
]
|
|
2121
2123
|
};
|
|
2122
|
-
var
|
|
2123
|
-
console: "
|
|
2124
|
+
var GBA_LAYOUT = {
|
|
2125
|
+
console: "GBA",
|
|
2124
2126
|
buttons: [
|
|
2125
|
-
|
|
2126
|
-
{ type: "
|
|
2127
|
-
{ type: "
|
|
2128
|
-
{ type: "
|
|
2129
|
-
{ type: "
|
|
2130
|
-
{ type: "
|
|
2131
|
-
{ type: "
|
|
2132
|
-
{ type: "start", label: "START", x: 62, y: 8, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true }
|
|
2127
|
+
...DPAD_BUTTONS,
|
|
2128
|
+
{ type: "b", label: "B", x: 80, y: 62, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
|
|
2129
|
+
{ type: "a", label: "A", x: 92, y: 52, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
|
|
2130
|
+
{ type: "l", label: "L", x: 8, y: 20, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
|
|
2131
|
+
{ type: "r", label: "R", x: 92, y: 20, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
|
|
2132
|
+
{ type: "select", label: "SELECT", x: SELECT_X, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" },
|
|
2133
|
+
{ type: "start", label: "START", x: START_X, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" }
|
|
2133
2134
|
]
|
|
2134
2135
|
};
|
|
2135
|
-
var
|
|
2136
|
-
console: "
|
|
2136
|
+
var SIX_BUTTON_LAYOUT = {
|
|
2137
|
+
console: "6BUTTON",
|
|
2137
2138
|
buttons: [
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
{ type: "
|
|
2141
|
-
{ type: "
|
|
2142
|
-
{ type: "
|
|
2143
|
-
|
|
2144
|
-
{ type: "
|
|
2145
|
-
{ type: "
|
|
2146
|
-
{ type: "
|
|
2147
|
-
{ type: "start", label: "START", x:
|
|
2139
|
+
...DPAD_BUTTONS,
|
|
2140
|
+
// Top row (X, Y, Z) → mapped to L, X, R
|
|
2141
|
+
{ type: "l", label: "X", x: 74, y: 48, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
|
|
2142
|
+
{ type: "x", label: "Y", x: 84, y: 44, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
|
|
2143
|
+
{ type: "r", label: "Z", x: 94, y: 40, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
|
|
2144
|
+
// Bottom row (A, B, C) → mapped to Y, B, A
|
|
2145
|
+
{ type: "y", label: "A", x: 72, y: 64, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
|
|
2146
|
+
{ type: "b", label: "B", x: 82, y: 60, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
|
|
2147
|
+
{ type: "a", label: "C", x: 92, y: 56, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
|
|
2148
|
+
{ type: "start", label: "START", x: 50, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" }
|
|
2148
2149
|
]
|
|
2149
2150
|
};
|
|
2150
|
-
var
|
|
2151
|
-
console: "
|
|
2151
|
+
var SATURN_LAYOUT = {
|
|
2152
|
+
console: "SATURN",
|
|
2152
2153
|
buttons: [
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
{ type: "
|
|
2157
|
-
{ type: "
|
|
2158
|
-
{ type: "
|
|
2159
|
-
|
|
2160
|
-
{ type: "
|
|
2154
|
+
...DPAD_BUTTONS,
|
|
2155
|
+
// Face buttons (same as 6-button but uses standard buttons)
|
|
2156
|
+
// X/Y/Z on top
|
|
2157
|
+
{ type: "x", label: "X", x: 74, y: 48, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
|
|
2158
|
+
{ type: "y", label: "Y", x: 84, y: 44, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
|
|
2159
|
+
{ type: "l", label: "Z", x: 94, y: 40, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
|
|
2160
|
+
// A/B/C on bottom
|
|
2161
|
+
{ type: "b", label: "A", x: 72, y: 64, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
|
|
2162
|
+
{ type: "a", label: "B", x: 82, y: 60, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
|
|
2163
|
+
{ type: "r", label: "C", x: 92, y: 56, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
|
|
2164
|
+
// Triggers (L2/R2 for Saturn L/R)
|
|
2165
|
+
{ type: "l2", label: "L", x: 8, y: 20, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
|
|
2166
|
+
{ type: "r2", label: "R", x: 92, y: 20, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
|
|
2167
|
+
{ type: "start", label: "START", x: 50, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" }
|
|
2168
|
+
]
|
|
2169
|
+
};
|
|
2170
|
+
var NEOGEO_LAYOUT = {
|
|
2171
|
+
console: "NEOGEO",
|
|
2172
|
+
buttons: [
|
|
2173
|
+
...DPAD_BUTTONS,
|
|
2174
|
+
// Curved A-B-C-D layout (maps to b-a-y-x for RetroPad)
|
|
2175
|
+
{ type: "b", label: "A", x: 68, y: 68, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
|
|
2176
|
+
{ type: "a", label: "B", x: 78, y: 62, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
|
|
2177
|
+
{ type: "y", label: "C", x: 88, y: 56, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
|
|
2178
|
+
{ type: "x", label: "D", x: 96, y: 48, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
|
|
2179
|
+
{ type: "select", label: "COIN", x: SELECT_X, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" },
|
|
2180
|
+
{ type: "start", label: "START", x: START_X, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" }
|
|
2181
|
+
]
|
|
2182
|
+
};
|
|
2183
|
+
var PSX_LAYOUT = {
|
|
2184
|
+
console: "PSX",
|
|
2185
|
+
buttons: [
|
|
2186
|
+
...DPAD_BUTTONS,
|
|
2187
|
+
// PS symbols: △◯✕□ → y, b, a, x (triangle-circle-cross-square)
|
|
2188
|
+
{ type: "y", label: "\u25B3", x: 84, y: 46, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
|
|
2189
|
+
{ type: "b", label: "\u25CB", x: 94, y: 58, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
|
|
2190
|
+
{ type: "a", label: "\u2715", x: 84, y: 70, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
|
|
2191
|
+
{ type: "x", label: "\u25A1", x: 74, y: 58, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
|
|
2192
|
+
// Shoulders
|
|
2193
|
+
{ type: "l", label: "L1", x: 8, y: 25, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
|
|
2194
|
+
{ type: "r", label: "R1", x: 92, y: 25, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
|
|
2195
|
+
{ type: "l2", label: "L2", x: 8, y: 15, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "rect" },
|
|
2196
|
+
{ type: "r2", label: "R2", x: 92, y: 15, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "rect" },
|
|
2197
|
+
{ type: "select", label: "SELECT", x: SELECT_X, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" },
|
|
2198
|
+
{ type: "start", label: "START", x: START_X, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" }
|
|
2199
|
+
]
|
|
2200
|
+
};
|
|
2201
|
+
var N64_LAYOUT = {
|
|
2202
|
+
console: "N64",
|
|
2203
|
+
buttons: [
|
|
2204
|
+
...DPAD_BUTTONS,
|
|
2205
|
+
// A/B
|
|
2206
|
+
{ type: "a", label: "A", x: 80, y: 68, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
|
|
2207
|
+
{ type: "b", label: "B", x: 72, y: 76, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
|
|
2208
|
+
// C-buttons (use x/y as C-left/C-up, l2/r2 as C-down/C-right)
|
|
2209
|
+
{ type: "y", label: "C\u2191", x: 92, y: 50, size: 36, showInPortrait: true, showInLandscape: true },
|
|
2210
|
+
{ type: "x", label: "C\u2190", x: 86, y: 56, size: 36, showInPortrait: true, showInLandscape: true },
|
|
2211
|
+
{ type: "l2", label: "C\u2193", x: 92, y: 62, size: 36, showInPortrait: true, showInLandscape: true },
|
|
2212
|
+
{ type: "r2", label: "C\u2192", x: 98, y: 56, size: 36, showInPortrait: true, showInLandscape: true },
|
|
2213
|
+
// Shoulders
|
|
2214
|
+
{ type: "l", label: "L", x: 8, y: 20, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
|
|
2215
|
+
{ type: "r", label: "R", x: 92, y: 20, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
|
|
2216
|
+
// Z trigger (use select as workaround since it's a unique button)
|
|
2217
|
+
{ type: "select", label: "Z", x: 8, y: 35, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
|
|
2218
|
+
{ type: "start", label: "START", x: 50, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" }
|
|
2219
|
+
]
|
|
2220
|
+
};
|
|
2221
|
+
var DREAMCAST_LAYOUT = {
|
|
2222
|
+
console: "DREAMCAST",
|
|
2223
|
+
buttons: [
|
|
2224
|
+
...DPAD_BUTTONS,
|
|
2225
|
+
// Standard diamond
|
|
2226
|
+
{ type: "y", label: "Y", x: 74, y: 58, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
|
|
2227
|
+
{ type: "x", label: "X", x: 84, y: 46, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
|
|
2228
|
+
{ type: "b", label: "B", x: 84, y: 70, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
|
|
2229
|
+
{ type: "a", label: "A", x: 94, y: 58, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
|
|
2230
|
+
// Triggers
|
|
2231
|
+
{ type: "l", label: "L", x: 8, y: 20, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
|
|
2232
|
+
{ type: "r", label: "R", x: 92, y: 20, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
|
|
2233
|
+
{ type: "start", label: "START", x: 50, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" }
|
|
2161
2234
|
]
|
|
2162
2235
|
};
|
|
2163
2236
|
function getLayoutForSystem(system) {
|
|
2164
|
-
const
|
|
2165
|
-
if (
|
|
2166
|
-
if (
|
|
2167
|
-
if (
|
|
2168
|
-
if (
|
|
2169
|
-
if (
|
|
2170
|
-
return
|
|
2237
|
+
const s = system.toUpperCase();
|
|
2238
|
+
if (s === "PS1" || s === "PSX" || s === "PSP") return PSX_LAYOUT;
|
|
2239
|
+
if (s === "N64") return N64_LAYOUT;
|
|
2240
|
+
if (s === "SNES" || s === "NDS") return SNES_LAYOUT;
|
|
2241
|
+
if (s === "GBA") return GBA_LAYOUT;
|
|
2242
|
+
if (s === "NES" || s === "GB" || s === "GBC" || s === "VIRTUAL_BOY") return TWO_BUTTON_LAYOUT;
|
|
2243
|
+
if (s === "DREAMCAST") return DREAMCAST_LAYOUT;
|
|
2244
|
+
if (s === "SATURN") return SATURN_LAYOUT;
|
|
2245
|
+
if (s === "GENESIS") return SIX_BUTTON_LAYOUT;
|
|
2246
|
+
if (s === "MASTER_SYSTEM" || s === "GAME_GEAR") return TWO_BUTTON_LAYOUT;
|
|
2247
|
+
if (s === "NEOGEO") return NEOGEO_LAYOUT;
|
|
2248
|
+
if (s.includes("NEOGEO_POCKET")) return TWO_BUTTON_LAYOUT;
|
|
2249
|
+
if (s === "ARCADE") return SIX_BUTTON_LAYOUT;
|
|
2250
|
+
if (s === "PC_ENGINE" || s === "TURBOGRAFX") return TWO_BUTTON_LAYOUT;
|
|
2251
|
+
if (s.includes("WONDERSWAN")) return TWO_BUTTON_LAYOUT;
|
|
2252
|
+
if (s.includes("ATARI")) return TWO_BUTTON_LAYOUT;
|
|
2253
|
+
return TWO_BUTTON_LAYOUT;
|
|
2171
2254
|
}
|
|
2172
|
-
var DRAG_HOLD_DELAY =
|
|
2255
|
+
var DRAG_HOLD_DELAY = 350;
|
|
2173
2256
|
var DRAG_MOVE_THRESHOLD = 10;
|
|
2174
2257
|
var DRAG_CENTER_THRESHOLD = 0.4;
|
|
2175
2258
|
function useTouchHandlers({
|
|
@@ -2202,6 +2285,9 @@ function useTouchHandlers({
|
|
|
2202
2285
|
x: touchX - displayX / 100 * containerWidth,
|
|
2203
2286
|
y: touchY - displayY / 100 * containerHeight
|
|
2204
2287
|
};
|
|
2288
|
+
if (navigator.vibrate) {
|
|
2289
|
+
navigator.vibrate([10, 30, 10]);
|
|
2290
|
+
}
|
|
2205
2291
|
if (!isSystemButton) {
|
|
2206
2292
|
onRelease(buttonType);
|
|
2207
2293
|
}
|
|
@@ -2317,55 +2403,119 @@ function useTouchHandlers({
|
|
|
2317
2403
|
}
|
|
2318
2404
|
|
|
2319
2405
|
// src/components/VirtualController/utils/buttonStyles.ts
|
|
2320
|
-
|
|
2406
|
+
var DEFAULT_FACE = {
|
|
2407
|
+
bg: "bg-white/10 backdrop-blur-md",
|
|
2408
|
+
text: "text-white",
|
|
2409
|
+
border: "border-white/30",
|
|
2410
|
+
shadow: "shadow-lg",
|
|
2411
|
+
transform: ""
|
|
2412
|
+
};
|
|
2413
|
+
var DEFAULT_SHOULDER = {
|
|
2414
|
+
bg: "bg-white/20 backdrop-blur-md",
|
|
2415
|
+
text: "text-white",
|
|
2416
|
+
border: "border-white/30",
|
|
2417
|
+
shadow: "shadow-sm",
|
|
2418
|
+
transform: ""
|
|
2419
|
+
};
|
|
2420
|
+
var DEFAULT_SYSTEM = {
|
|
2421
|
+
bg: "bg-black/60 backdrop-blur-md",
|
|
2422
|
+
text: "text-white/80",
|
|
2423
|
+
border: "border-white/20",
|
|
2424
|
+
shadow: "shadow-sm",
|
|
2425
|
+
transform: ""
|
|
2426
|
+
};
|
|
2427
|
+
var PRESSED_STYLE = {
|
|
2428
|
+
bg: "bg-white/90",
|
|
2429
|
+
text: "text-black",
|
|
2430
|
+
border: "border-white",
|
|
2431
|
+
shadow: "shadow-none",
|
|
2432
|
+
transform: "scale(0.95)"
|
|
2433
|
+
};
|
|
2434
|
+
function coloredButton(bgColor, textColor = "text-white") {
|
|
2435
|
+
return {
|
|
2436
|
+
bg: bgColor,
|
|
2437
|
+
text: textColor,
|
|
2438
|
+
border: "border-white/20",
|
|
2439
|
+
shadow: "shadow-lg",
|
|
2440
|
+
transform: ""
|
|
2441
|
+
};
|
|
2442
|
+
}
|
|
2443
|
+
function psButton(symbolColor) {
|
|
2444
|
+
return {
|
|
2445
|
+
bg: "bg-black/80 backdrop-blur-md",
|
|
2446
|
+
text: symbolColor,
|
|
2447
|
+
border: "border-white/30",
|
|
2448
|
+
shadow: "shadow-lg",
|
|
2449
|
+
transform: ""
|
|
2450
|
+
};
|
|
2451
|
+
}
|
|
2452
|
+
function getButtonStyles(buttonType, isPressed, consoleName = "") {
|
|
2321
2453
|
if (isPressed) {
|
|
2322
|
-
return
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2454
|
+
return PRESSED_STYLE;
|
|
2455
|
+
}
|
|
2456
|
+
const c = consoleName.toUpperCase();
|
|
2457
|
+
if (c === "NEOGEO") {
|
|
2458
|
+
switch (buttonType) {
|
|
2459
|
+
case "b":
|
|
2460
|
+
return coloredButton("bg-[#E60012]");
|
|
2461
|
+
// A = Red
|
|
2462
|
+
case "a":
|
|
2463
|
+
return coloredButton("bg-[#FFD600]", "text-black");
|
|
2464
|
+
// B = Yellow
|
|
2465
|
+
case "y":
|
|
2466
|
+
return coloredButton("bg-[#009944]");
|
|
2467
|
+
// C = Green
|
|
2468
|
+
case "x":
|
|
2469
|
+
return coloredButton("bg-[#0068B7]");
|
|
2470
|
+
}
|
|
2471
|
+
}
|
|
2472
|
+
if (c === "PSX" || c === "PS1" || c === "PSP") {
|
|
2473
|
+
switch (buttonType) {
|
|
2474
|
+
case "y":
|
|
2475
|
+
return psButton("text-[#25D998]");
|
|
2476
|
+
// △ = Green
|
|
2477
|
+
case "b":
|
|
2478
|
+
return psButton("text-[#FF5555]");
|
|
2479
|
+
// ○ = Red
|
|
2480
|
+
case "a":
|
|
2481
|
+
return psButton("text-[#5599FF]");
|
|
2482
|
+
// ✕ = Blue
|
|
2483
|
+
case "x":
|
|
2484
|
+
return psButton("text-[#E889DD]");
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
if (c === "SNES" || c === "NDS") {
|
|
2488
|
+
switch (buttonType) {
|
|
2489
|
+
case "a":
|
|
2490
|
+
return coloredButton("bg-[#CC0000]");
|
|
2491
|
+
// A = Red
|
|
2492
|
+
case "b":
|
|
2493
|
+
return coloredButton("bg-[#CCCC00]", "text-black");
|
|
2494
|
+
// B = Yellow
|
|
2495
|
+
case "x":
|
|
2496
|
+
return coloredButton("bg-[#0033CC]");
|
|
2497
|
+
// X = Blue
|
|
2498
|
+
case "y":
|
|
2499
|
+
return coloredButton("bg-[#006600]");
|
|
2500
|
+
}
|
|
2329
2501
|
}
|
|
2330
2502
|
switch (buttonType) {
|
|
2331
2503
|
case "a":
|
|
2332
2504
|
case "b":
|
|
2333
|
-
case "c":
|
|
2334
|
-
return {
|
|
2335
|
-
bg: "bg-red-600",
|
|
2336
|
-
text: "text-white",
|
|
2337
|
-
border: "border-white",
|
|
2338
|
-
shadow: "shadow-hard",
|
|
2339
|
-
transform: ""
|
|
2340
|
-
};
|
|
2341
2505
|
case "x":
|
|
2342
2506
|
case "y":
|
|
2343
|
-
return
|
|
2344
|
-
bg: "bg-blue-600",
|
|
2345
|
-
text: "text-white",
|
|
2346
|
-
border: "border-white",
|
|
2347
|
-
shadow: "shadow-hard",
|
|
2348
|
-
transform: ""
|
|
2349
|
-
};
|
|
2507
|
+
return DEFAULT_FACE;
|
|
2350
2508
|
case "l":
|
|
2351
2509
|
case "r":
|
|
2510
|
+
case "l2":
|
|
2511
|
+
case "r2":
|
|
2512
|
+
return DEFAULT_SHOULDER;
|
|
2352
2513
|
case "start":
|
|
2353
2514
|
case "select":
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
text: "text-white",
|
|
2357
|
-
border: "border-white",
|
|
2358
|
-
shadow: "shadow-hard",
|
|
2359
|
-
transform: ""
|
|
2360
|
-
};
|
|
2515
|
+
case "menu":
|
|
2516
|
+
return DEFAULT_SYSTEM;
|
|
2361
2517
|
default:
|
|
2362
|
-
return
|
|
2363
|
-
bg: "bg-black",
|
|
2364
|
-
text: "text-white",
|
|
2365
|
-
border: "border-white",
|
|
2366
|
-
shadow: "shadow-hard",
|
|
2367
|
-
transform: ""
|
|
2368
|
-
};
|
|
2518
|
+
return DEFAULT_FACE;
|
|
2369
2519
|
}
|
|
2370
2520
|
}
|
|
2371
2521
|
var VirtualButton = React2__default.default.memo(function VirtualButton2({
|
|
@@ -2379,12 +2529,11 @@ var VirtualButton = React2__default.default.memo(function VirtualButton2({
|
|
|
2379
2529
|
customPosition,
|
|
2380
2530
|
onPositionChange,
|
|
2381
2531
|
isLandscape = false,
|
|
2382
|
-
|
|
2383
|
-
// Default retro green
|
|
2532
|
+
console: console2 = ""
|
|
2384
2533
|
}) {
|
|
2385
2534
|
const t = useKoinTranslation();
|
|
2386
2535
|
const buttonRef = React2.useRef(null);
|
|
2387
|
-
const isSystemButton = config.type === "start" || config.type === "select";
|
|
2536
|
+
const isSystemButton = config.type === "start" || config.type === "select" || config.type === "menu";
|
|
2388
2537
|
const displayX = customPosition ? customPosition.x : config.x;
|
|
2389
2538
|
const displayY = customPosition ? customPosition.y : config.y;
|
|
2390
2539
|
let label = config.label;
|
|
@@ -2427,63 +2576,78 @@ var VirtualButton = React2__default.default.memo(function VirtualButton2({
|
|
|
2427
2576
|
const leftPercent = displayX / 100 * containerWidth - config.size / 2;
|
|
2428
2577
|
const topPercent = displayY / 100 * containerHeight - config.size / 2;
|
|
2429
2578
|
const transform = `translate3d(${leftPercent.toFixed(1)}px, ${topPercent.toFixed(1)}px, 0)`;
|
|
2430
|
-
const styles = getButtonStyles(config.type, isPressed);
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2579
|
+
const styles = getButtonStyles(config.type, isPressed, console2);
|
|
2580
|
+
let borderRadius = "50%";
|
|
2581
|
+
let width = `${config.size}px`;
|
|
2582
|
+
if (config.shape === "pill") {
|
|
2583
|
+
borderRadius = "20px";
|
|
2584
|
+
width = `${config.size * 1.8}px`;
|
|
2585
|
+
} else if (config.shape === "rect") {
|
|
2586
|
+
borderRadius = "8px";
|
|
2587
|
+
width = `${config.size * 2.5}px`;
|
|
2588
|
+
} else if (config.shape === "square") {
|
|
2589
|
+
borderRadius = "12px";
|
|
2590
|
+
} else {
|
|
2591
|
+
if (["l", "r", "l2", "r2"].includes(config.type)) {
|
|
2592
|
+
borderRadius = "10px";
|
|
2593
|
+
width = `${config.size * 2}px`;
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2438
2596
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2439
2597
|
"button",
|
|
2440
2598
|
{
|
|
2441
2599
|
ref: buttonRef,
|
|
2442
2600
|
className: `
|
|
2443
|
-
absolute
|
|
2444
|
-
|
|
2601
|
+
absolute flex items-center justify-center
|
|
2602
|
+
font-heading font-bold uppercase tracking-wider
|
|
2603
|
+
transition-all duration-75 select-none
|
|
2445
2604
|
pointer-events-auto touch-manipulation
|
|
2446
|
-
|
|
2447
|
-
|
|
2605
|
+
backdrop-blur-sm
|
|
2606
|
+
${isPressed ? "" : `${styles.bg} ${styles.border} ${styles.shadow}`}
|
|
2607
|
+
${styles.text}
|
|
2608
|
+
active:shadow-none
|
|
2448
2609
|
`,
|
|
2449
2610
|
style: {
|
|
2450
|
-
// Remove left/top and use transform instead for high performance (compositor only)
|
|
2451
2611
|
top: 0,
|
|
2452
2612
|
left: 0,
|
|
2453
|
-
transform,
|
|
2613
|
+
transform: transform + (isPressed ? " scale(0.95)" : ""),
|
|
2454
2614
|
willChange: "transform",
|
|
2455
|
-
width
|
|
2615
|
+
width,
|
|
2456
2616
|
height: `${config.size}px`,
|
|
2617
|
+
// Height stays consistent
|
|
2457
2618
|
minWidth: `${config.size}px`,
|
|
2458
|
-
|
|
2459
|
-
fontSize: config.size < 50 ? "10px" : "12px",
|
|
2619
|
+
fontSize: config.size < 50 ? "11px" : "16px",
|
|
2460
2620
|
borderRadius,
|
|
2621
|
+
borderWidth: isPressed ? "0px" : "1.5px",
|
|
2622
|
+
// slightly thicker border
|
|
2461
2623
|
lineHeight: "1",
|
|
2462
2624
|
// Semi-transparent in landscape mode
|
|
2463
2625
|
opacity: isLandscape ? 0.85 : 1,
|
|
2464
|
-
// Prevent context menu on long-press
|
|
2465
2626
|
WebkitTouchCallout: "none",
|
|
2466
2627
|
WebkitUserSelect: "none",
|
|
2467
2628
|
userSelect: "none",
|
|
2468
|
-
|
|
2629
|
+
// Direct style overrides if needed from getButtonStyles (for exact hex colors)
|
|
2630
|
+
backgroundColor: isPressed ? styles.bg.startsWith("bg-") ? void 0 : styles.bg : styles.bg.startsWith("bg-") ? void 0 : styles.bg,
|
|
2631
|
+
borderColor: isPressed ? void 0 : styles.border.startsWith("border-") ? void 0 : styles.border,
|
|
2632
|
+
color: styles.text.startsWith("text-") ? void 0 : styles.text
|
|
2469
2633
|
},
|
|
2470
2634
|
"aria-label": label,
|
|
2471
2635
|
onContextMenu: (e) => e.preventDefault(),
|
|
2472
|
-
children: label
|
|
2636
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "drop-shadow-md", children: label })
|
|
2473
2637
|
}
|
|
2474
2638
|
);
|
|
2475
2639
|
});
|
|
2476
2640
|
var VirtualButton_default = VirtualButton;
|
|
2477
2641
|
|
|
2478
2642
|
// src/lib/controls/types.ts
|
|
2479
|
-
var
|
|
2643
|
+
var DPAD_BUTTONS2 = ["up", "down", "left", "right"];
|
|
2480
2644
|
var FACE_BUTTONS = ["a", "b", "x", "y"];
|
|
2481
2645
|
var SHOULDER_BUTTONS = ["l", "r"];
|
|
2482
2646
|
var TRIGGER_BUTTONS = ["l2", "r2"];
|
|
2483
2647
|
var STICK_BUTTONS = ["l3", "r3"];
|
|
2484
2648
|
var SYSTEM_BUTTONS = ["start", "select"];
|
|
2485
2649
|
var ALL_BUTTONS = [
|
|
2486
|
-
...
|
|
2650
|
+
...DPAD_BUTTONS2,
|
|
2487
2651
|
...FACE_BUTTONS,
|
|
2488
2652
|
...SHOULDER_BUTTONS,
|
|
2489
2653
|
...TRIGGER_BUTTONS,
|
|
@@ -2641,91 +2805,91 @@ var CONSOLE_CAPABILITIES = {
|
|
|
2641
2805
|
// ============ Nintendo ============
|
|
2642
2806
|
NES: {
|
|
2643
2807
|
console: "NES",
|
|
2644
|
-
buttons: [...
|
|
2808
|
+
buttons: [...DPAD_BUTTONS2, "a", "b", "start", "select"]
|
|
2645
2809
|
},
|
|
2646
2810
|
SNES: {
|
|
2647
2811
|
console: "SNES",
|
|
2648
|
-
buttons: [...
|
|
2812
|
+
buttons: [...DPAD_BUTTONS2, "a", "b", "x", "y", "l", "r", "start", "select"]
|
|
2649
2813
|
},
|
|
2650
2814
|
N64: {
|
|
2651
2815
|
console: "N64",
|
|
2652
|
-
buttons: [...
|
|
2816
|
+
buttons: [...DPAD_BUTTONS2, "a", "b", "l", "r", "start"]
|
|
2653
2817
|
// No select, has Z trigger (mapped to l2)
|
|
2654
2818
|
},
|
|
2655
2819
|
GB: {
|
|
2656
2820
|
console: "GB",
|
|
2657
|
-
buttons: [...
|
|
2821
|
+
buttons: [...DPAD_BUTTONS2, "a", "b", "start", "select"]
|
|
2658
2822
|
},
|
|
2659
2823
|
GBC: {
|
|
2660
2824
|
console: "GBC",
|
|
2661
|
-
buttons: [...
|
|
2825
|
+
buttons: [...DPAD_BUTTONS2, "a", "b", "start", "select"]
|
|
2662
2826
|
},
|
|
2663
2827
|
GBA: {
|
|
2664
2828
|
console: "GBA",
|
|
2665
|
-
buttons: [...
|
|
2829
|
+
buttons: [...DPAD_BUTTONS2, "a", "b", "l", "r", "start", "select"]
|
|
2666
2830
|
},
|
|
2667
2831
|
// ============ Sega ============
|
|
2668
2832
|
SMS: {
|
|
2669
2833
|
console: "SMS",
|
|
2670
|
-
buttons: [...
|
|
2834
|
+
buttons: [...DPAD_BUTTONS2, "a", "b", "start"]
|
|
2671
2835
|
// 1, 2, Pause
|
|
2672
2836
|
},
|
|
2673
2837
|
GENESIS: {
|
|
2674
2838
|
console: "GENESIS",
|
|
2675
|
-
buttons: [...
|
|
2839
|
+
buttons: [...DPAD_BUTTONS2, "a", "b", "x", "y", "l", "r", "start"]
|
|
2676
2840
|
// 6-button: A/B/C + X/Y/Z
|
|
2677
2841
|
},
|
|
2678
2842
|
GG: {
|
|
2679
2843
|
console: "GG",
|
|
2680
|
-
buttons: [...
|
|
2844
|
+
buttons: [...DPAD_BUTTONS2, "a", "b", "start"]
|
|
2681
2845
|
},
|
|
2682
2846
|
SATURN: {
|
|
2683
2847
|
console: "SATURN",
|
|
2684
|
-
buttons: [...
|
|
2848
|
+
buttons: [...DPAD_BUTTONS2, "a", "b", "x", "y", "l", "r", "l2", "r2", "start"]
|
|
2685
2849
|
// Full 8-button
|
|
2686
2850
|
},
|
|
2687
2851
|
// ============ Sony ============
|
|
2688
2852
|
PS1: {
|
|
2689
2853
|
console: "PS1",
|
|
2690
|
-
buttons: [...
|
|
2854
|
+
buttons: [...DPAD_BUTTONS2, "a", "b", "x", "y", "l", "r", "l2", "r2", "l3", "r3", "start", "select"]
|
|
2691
2855
|
},
|
|
2692
2856
|
// ============ NEC ============
|
|
2693
2857
|
PCE: {
|
|
2694
2858
|
console: "PCE",
|
|
2695
2859
|
// TurboGrafx-16
|
|
2696
|
-
buttons: [...
|
|
2860
|
+
buttons: [...DPAD_BUTTONS2, "a", "b", "start", "select"]
|
|
2697
2861
|
// I, II, Run, Select
|
|
2698
2862
|
},
|
|
2699
2863
|
// ============ Atari ============
|
|
2700
2864
|
ATARI2600: {
|
|
2701
2865
|
console: "ATARI2600",
|
|
2702
|
-
buttons: [...
|
|
2866
|
+
buttons: [...DPAD_BUTTONS2, "a", "select"]
|
|
2703
2867
|
// Fire, Select/Reset
|
|
2704
2868
|
},
|
|
2705
2869
|
ATARI7800: {
|
|
2706
2870
|
console: "ATARI7800",
|
|
2707
|
-
buttons: [...
|
|
2871
|
+
buttons: [...DPAD_BUTTONS2, "a", "b", "select"]
|
|
2708
2872
|
},
|
|
2709
2873
|
LYNX: {
|
|
2710
2874
|
console: "LYNX",
|
|
2711
|
-
buttons: [...
|
|
2875
|
+
buttons: [...DPAD_BUTTONS2, "a", "b", "start", "select"]
|
|
2712
2876
|
// A, B, Option 1, Option 2
|
|
2713
2877
|
},
|
|
2714
2878
|
// ============ SNK ============
|
|
2715
2879
|
NGPC: {
|
|
2716
2880
|
console: "NGPC",
|
|
2717
2881
|
// Neo Geo Pocket
|
|
2718
|
-
buttons: [...
|
|
2882
|
+
buttons: [...DPAD_BUTTONS2, "a", "b", "start", "select"]
|
|
2719
2883
|
},
|
|
2720
2884
|
// ============ Other ============
|
|
2721
2885
|
WSC: {
|
|
2722
2886
|
console: "WSC",
|
|
2723
2887
|
// WonderSwan
|
|
2724
|
-
buttons: [...
|
|
2888
|
+
buttons: [...DPAD_BUTTONS2, "a", "b", "x", "y", "start"]
|
|
2725
2889
|
},
|
|
2726
2890
|
ARCADE: {
|
|
2727
2891
|
console: "ARCADE",
|
|
2728
|
-
buttons: [...
|
|
2892
|
+
buttons: [...DPAD_BUTTONS2, "a", "b", "x", "y", "l", "r", "start", "select"]
|
|
2729
2893
|
// Generic 6-button
|
|
2730
2894
|
}
|
|
2731
2895
|
};
|
|
@@ -2766,7 +2930,7 @@ function getConsoleCapabilities(system) {
|
|
|
2766
2930
|
const normalized = system.toUpperCase();
|
|
2767
2931
|
return CONSOLE_CAPABILITIES[normalized] ?? {
|
|
2768
2932
|
console: normalized,
|
|
2769
|
-
buttons: [...
|
|
2933
|
+
buttons: [...DPAD_BUTTONS2, "a", "b", "x", "y", "l", "r", "start", "select"]
|
|
2770
2934
|
};
|
|
2771
2935
|
}
|
|
2772
2936
|
function getConsoleButtons(system) {
|
|
@@ -3054,13 +3218,11 @@ var DEFAULT_CONTROLS = DEFAULT_KEYBOARD;
|
|
|
3054
3218
|
|
|
3055
3219
|
// src/components/VirtualController/utils/keyboardEvents.ts
|
|
3056
3220
|
function getKeyboardCode(buttonType, controls) {
|
|
3057
|
-
const
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
if (key in controlMapping) {
|
|
3061
|
-
return controlMapping[key] ?? null;
|
|
3221
|
+
const key = buttonType;
|
|
3222
|
+
if (controls && key in controls && controls[key]) {
|
|
3223
|
+
return controls[key];
|
|
3062
3224
|
}
|
|
3063
|
-
return null;
|
|
3225
|
+
return DEFAULT_CONTROLS[key] ?? null;
|
|
3064
3226
|
}
|
|
3065
3227
|
function getKeyName(code) {
|
|
3066
3228
|
if (code.startsWith("Key")) return code.slice(3).toLowerCase();
|
|
@@ -3086,23 +3248,34 @@ function dispatchKeyboardEvent(type, code) {
|
|
|
3086
3248
|
canvas.dispatchEvent(event);
|
|
3087
3249
|
return true;
|
|
3088
3250
|
}
|
|
3251
|
+
var DRAG_HOLD_DELAY2 = 350;
|
|
3252
|
+
var CENTER_TOUCH_RADIUS = 0.25;
|
|
3089
3253
|
var Dpad = React2__default.default.memo(function Dpad2({
|
|
3090
|
-
size,
|
|
3254
|
+
size = 180,
|
|
3091
3255
|
x,
|
|
3092
3256
|
y,
|
|
3093
3257
|
containerWidth,
|
|
3094
3258
|
containerHeight,
|
|
3095
3259
|
controls,
|
|
3096
3260
|
systemColor = "#00FF41",
|
|
3097
|
-
isLandscape = false
|
|
3261
|
+
isLandscape = false,
|
|
3262
|
+
customPosition,
|
|
3263
|
+
onPositionChange
|
|
3098
3264
|
}) {
|
|
3099
3265
|
const dpadRef = React2.useRef(null);
|
|
3100
3266
|
const activeTouchRef = React2.useRef(null);
|
|
3101
3267
|
const activeDirectionsRef = React2.useRef(/* @__PURE__ */ new Set());
|
|
3102
|
-
const
|
|
3103
|
-
const
|
|
3104
|
-
const
|
|
3105
|
-
const
|
|
3268
|
+
const [isDragging, setIsDragging] = React2.useState(false);
|
|
3269
|
+
const dragTimerRef = React2.useRef(null);
|
|
3270
|
+
const dragStartRef = React2.useRef({ x: 0, y: 0, touchX: 0, touchY: 0 });
|
|
3271
|
+
const touchStartPosRef = React2.useRef({ x: 0, y: 0, time: 0 });
|
|
3272
|
+
const upPathRef = React2.useRef(null);
|
|
3273
|
+
const downPathRef = React2.useRef(null);
|
|
3274
|
+
const leftPathRef = React2.useRef(null);
|
|
3275
|
+
const rightPathRef = React2.useRef(null);
|
|
3276
|
+
const centerCircleRef = React2.useRef(null);
|
|
3277
|
+
const displayX = customPosition ? customPosition.x : x;
|
|
3278
|
+
const displayY = customPosition ? customPosition.y : y;
|
|
3106
3279
|
const getKeyCode = React2.useCallback((direction) => {
|
|
3107
3280
|
if (!controls) {
|
|
3108
3281
|
const defaults = {
|
|
@@ -3120,24 +3293,41 @@ var Dpad = React2__default.default.memo(function Dpad2({
|
|
|
3120
3293
|
const centerY = rect.top + rect.height / 2;
|
|
3121
3294
|
const dx = touchX - centerX;
|
|
3122
3295
|
const dy = touchY - centerY;
|
|
3123
|
-
const deadZone = rect.width * 0.12;
|
|
3124
3296
|
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
3297
|
+
const deadZone = rect.width / 2 * 0.15;
|
|
3125
3298
|
if (distance < deadZone) return /* @__PURE__ */ new Set();
|
|
3126
3299
|
const directions = /* @__PURE__ */ new Set();
|
|
3127
3300
|
const angle = Math.atan2(dy, dx) * (180 / Math.PI);
|
|
3128
|
-
if (angle >= -
|
|
3129
|
-
if (angle >=
|
|
3130
|
-
if (angle >=
|
|
3131
|
-
if (angle >= -
|
|
3301
|
+
if (angle >= -150 && angle <= -30) directions.add("up");
|
|
3302
|
+
if (angle >= 30 && angle <= 150) directions.add("down");
|
|
3303
|
+
if (angle >= 120 || angle <= -120) directions.add("left");
|
|
3304
|
+
if (angle >= -60 && angle <= 60) directions.add("right");
|
|
3132
3305
|
return directions;
|
|
3133
3306
|
}, []);
|
|
3134
3307
|
const updateVisuals = React2.useCallback((directions) => {
|
|
3135
|
-
const
|
|
3136
|
-
const
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3308
|
+
const activeFill = `${systemColor}80`;
|
|
3309
|
+
const inactiveFill = "rgba(255, 255, 255, 0.05)";
|
|
3310
|
+
const activeStroke = systemColor;
|
|
3311
|
+
const inactiveStroke = "rgba(255, 255, 255, 0.2)";
|
|
3312
|
+
const glow = `0 0 15px ${systemColor}`;
|
|
3313
|
+
const updatePart = (ref, isActive) => {
|
|
3314
|
+
if (ref.current) {
|
|
3315
|
+
ref.current.style.fill = isActive ? activeFill : inactiveFill;
|
|
3316
|
+
ref.current.style.stroke = isActive ? activeStroke : inactiveStroke;
|
|
3317
|
+
ref.current.style.filter = isActive ? `drop-shadow(${glow})` : "none";
|
|
3318
|
+
ref.current.style.transform = isActive ? "scale(0.98)" : "scale(1)";
|
|
3319
|
+
ref.current.style.transformOrigin = "center";
|
|
3320
|
+
}
|
|
3321
|
+
};
|
|
3322
|
+
updatePart(upPathRef, directions.has("up"));
|
|
3323
|
+
updatePart(downPathRef, directions.has("down"));
|
|
3324
|
+
updatePart(leftPathRef, directions.has("left"));
|
|
3325
|
+
updatePart(rightPathRef, directions.has("right"));
|
|
3326
|
+
if (centerCircleRef.current) {
|
|
3327
|
+
const isAny = directions.size > 0;
|
|
3328
|
+
centerCircleRef.current.style.fill = isAny ? systemColor : "rgba(0,0,0,0.5)";
|
|
3329
|
+
centerCircleRef.current.style.stroke = isAny ? "#fff" : "rgba(255,255,255,0.3)";
|
|
3330
|
+
}
|
|
3141
3331
|
}, [systemColor]);
|
|
3142
3332
|
const updateDirections = React2.useCallback((newDirections) => {
|
|
3143
3333
|
const prev = activeDirectionsRef.current;
|
|
@@ -3159,48 +3349,107 @@ var Dpad = React2__default.default.memo(function Dpad2({
|
|
|
3159
3349
|
activeDirectionsRef.current = newDirections;
|
|
3160
3350
|
updateVisuals(newDirections);
|
|
3161
3351
|
}, [getKeyCode, updateVisuals]);
|
|
3352
|
+
const clearDragTimer = React2.useCallback(() => {
|
|
3353
|
+
if (dragTimerRef.current) {
|
|
3354
|
+
clearTimeout(dragTimerRef.current);
|
|
3355
|
+
dragTimerRef.current = null;
|
|
3356
|
+
}
|
|
3357
|
+
}, []);
|
|
3358
|
+
const startDragging = React2.useCallback((touchX, touchY) => {
|
|
3359
|
+
setIsDragging(true);
|
|
3360
|
+
dragStartRef.current = {
|
|
3361
|
+
x: displayX,
|
|
3362
|
+
y: displayY,
|
|
3363
|
+
touchX,
|
|
3364
|
+
touchY
|
|
3365
|
+
};
|
|
3366
|
+
activeDirectionsRef.current.forEach((dir) => {
|
|
3367
|
+
const keyCode = getKeyCode(dir);
|
|
3368
|
+
if (keyCode) dispatchKeyboardEvent("keyup", keyCode);
|
|
3369
|
+
});
|
|
3370
|
+
activeDirectionsRef.current = /* @__PURE__ */ new Set();
|
|
3371
|
+
updateVisuals(/* @__PURE__ */ new Set());
|
|
3372
|
+
if (navigator.vibrate) navigator.vibrate([10, 30, 10]);
|
|
3373
|
+
}, [displayX, displayY, getKeyCode, updateVisuals]);
|
|
3162
3374
|
const handleTouchStart = React2.useCallback((e) => {
|
|
3163
3375
|
e.preventDefault();
|
|
3164
|
-
|
|
3165
|
-
const touch = e.
|
|
3376
|
+
if (activeTouchRef.current !== null) return;
|
|
3377
|
+
const touch = e.changedTouches[0];
|
|
3166
3378
|
activeTouchRef.current = touch.identifier;
|
|
3379
|
+
touchStartPosRef.current = { x: touch.clientX, y: touch.clientY, time: Date.now() };
|
|
3167
3380
|
const rect = dpadRef.current?.getBoundingClientRect();
|
|
3168
3381
|
if (!rect) return;
|
|
3169
|
-
|
|
3170
|
-
|
|
3382
|
+
const centerX = rect.left + rect.width / 2;
|
|
3383
|
+
const centerY = rect.top + rect.height / 2;
|
|
3384
|
+
const distFromCenter = Math.sqrt(
|
|
3385
|
+
Math.pow(touch.clientX - centerX, 2) + Math.pow(touch.clientY - centerY, 2)
|
|
3386
|
+
);
|
|
3387
|
+
const centerRadius = size * CENTER_TOUCH_RADIUS;
|
|
3388
|
+
if (distFromCenter < centerRadius && onPositionChange) {
|
|
3389
|
+
dragTimerRef.current = setTimeout(() => {
|
|
3390
|
+
startDragging(touch.clientX, touch.clientY);
|
|
3391
|
+
}, DRAG_HOLD_DELAY2);
|
|
3392
|
+
}
|
|
3393
|
+
if (!isDragging) {
|
|
3394
|
+
updateDirections(getDirectionsFromTouch(touch.clientX, touch.clientY, rect));
|
|
3395
|
+
}
|
|
3396
|
+
}, [getDirectionsFromTouch, updateDirections, isDragging, size, onPositionChange, startDragging]);
|
|
3171
3397
|
const handleTouchMove = React2.useCallback((e) => {
|
|
3172
3398
|
e.preventDefault();
|
|
3173
3399
|
let touch = null;
|
|
3174
|
-
for (let i = 0; i < e.
|
|
3175
|
-
if (e.
|
|
3176
|
-
touch = e.
|
|
3400
|
+
for (let i = 0; i < e.changedTouches.length; i++) {
|
|
3401
|
+
if (e.changedTouches[i].identifier === activeTouchRef.current) {
|
|
3402
|
+
touch = e.changedTouches[i];
|
|
3177
3403
|
break;
|
|
3178
3404
|
}
|
|
3179
3405
|
}
|
|
3180
3406
|
if (!touch) return;
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3407
|
+
if (isDragging && onPositionChange) {
|
|
3408
|
+
const deltaX = touch.clientX - dragStartRef.current.touchX;
|
|
3409
|
+
const deltaY = touch.clientY - dragStartRef.current.touchY;
|
|
3410
|
+
const newXPercent = dragStartRef.current.x + deltaX / containerWidth * 100;
|
|
3411
|
+
const newYPercent = dragStartRef.current.y + deltaY / containerHeight * 100;
|
|
3412
|
+
const margin = size / 2 / Math.min(containerWidth, containerHeight) * 100;
|
|
3413
|
+
const constrainedX = Math.max(margin, Math.min(100 - margin, newXPercent));
|
|
3414
|
+
const constrainedY = Math.max(margin, Math.min(100 - margin, newYPercent));
|
|
3415
|
+
onPositionChange(constrainedX, constrainedY);
|
|
3416
|
+
} else {
|
|
3417
|
+
const moveDistance = Math.sqrt(
|
|
3418
|
+
Math.pow(touch.clientX - touchStartPosRef.current.x, 2) + Math.pow(touch.clientY - touchStartPosRef.current.y, 2)
|
|
3419
|
+
);
|
|
3420
|
+
if (moveDistance > 15) {
|
|
3421
|
+
clearDragTimer();
|
|
3422
|
+
}
|
|
3423
|
+
const rect = dpadRef.current?.getBoundingClientRect();
|
|
3424
|
+
if (rect) {
|
|
3425
|
+
updateDirections(getDirectionsFromTouch(touch.clientX, touch.clientY, rect));
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
3428
|
+
}, [isDragging, onPositionChange, containerWidth, containerHeight, size, getDirectionsFromTouch, updateDirections, clearDragTimer]);
|
|
3185
3429
|
const handleTouchEnd = React2.useCallback((e) => {
|
|
3186
3430
|
e.preventDefault();
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3431
|
+
clearDragTimer();
|
|
3432
|
+
let touchEnded = false;
|
|
3433
|
+
for (let i = 0; i < e.changedTouches.length; i++) {
|
|
3434
|
+
if (e.changedTouches[i].identifier === activeTouchRef.current) {
|
|
3435
|
+
touchEnded = true;
|
|
3191
3436
|
break;
|
|
3192
3437
|
}
|
|
3193
3438
|
}
|
|
3194
3439
|
if (touchEnded) {
|
|
3195
3440
|
activeTouchRef.current = null;
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3441
|
+
if (isDragging) {
|
|
3442
|
+
setIsDragging(false);
|
|
3443
|
+
} else {
|
|
3444
|
+
activeDirectionsRef.current.forEach((dir) => {
|
|
3445
|
+
const keyCode = getKeyCode(dir);
|
|
3446
|
+
if (keyCode) dispatchKeyboardEvent("keyup", keyCode);
|
|
3447
|
+
});
|
|
3448
|
+
activeDirectionsRef.current = /* @__PURE__ */ new Set();
|
|
3449
|
+
updateVisuals(/* @__PURE__ */ new Set());
|
|
3450
|
+
}
|
|
3202
3451
|
}
|
|
3203
|
-
}, [getKeyCode, updateVisuals]);
|
|
3452
|
+
}, [getKeyCode, updateVisuals, isDragging, clearDragTimer]);
|
|
3204
3453
|
React2.useEffect(() => {
|
|
3205
3454
|
const dpad = dpadRef.current;
|
|
3206
3455
|
if (!dpad) return;
|
|
@@ -3213,37 +3462,57 @@ var Dpad = React2__default.default.memo(function Dpad2({
|
|
|
3213
3462
|
dpad.removeEventListener("touchmove", handleTouchMove);
|
|
3214
3463
|
dpad.removeEventListener("touchend", handleTouchEnd);
|
|
3215
3464
|
dpad.removeEventListener("touchcancel", handleTouchEnd);
|
|
3465
|
+
clearDragTimer();
|
|
3216
3466
|
};
|
|
3217
|
-
}, [handleTouchStart, handleTouchMove, handleTouchEnd]);
|
|
3218
|
-
const leftPx =
|
|
3219
|
-
const topPx =
|
|
3220
|
-
|
|
3467
|
+
}, [handleTouchStart, handleTouchMove, handleTouchEnd, clearDragTimer]);
|
|
3468
|
+
const leftPx = displayX / 100 * containerWidth - size / 2;
|
|
3469
|
+
const topPx = displayY / 100 * containerHeight - size / 2;
|
|
3470
|
+
const dUp = "M 35,5 L 65,5 L 65,35 L 50,50 L 35,35 Z";
|
|
3471
|
+
const dRight = "M 65,35 L 95,35 L 95,65 L 65,65 L 50,50 Z";
|
|
3472
|
+
const dDown = "M 65,65 L 65,95 L 35,95 L 35,65 L 50,50 Z";
|
|
3473
|
+
const dLeft = "M 35,65 L 5,65 L 5,35 L 35,35 L 50,50 Z";
|
|
3474
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3221
3475
|
"div",
|
|
3222
3476
|
{
|
|
3223
3477
|
ref: dpadRef,
|
|
3224
|
-
className:
|
|
3478
|
+
className: `absolute pointer-events-auto touch-manipulation select-none ${isDragging ? "opacity-60" : ""}`,
|
|
3225
3479
|
style: {
|
|
3226
3480
|
top: 0,
|
|
3227
3481
|
left: 0,
|
|
3228
|
-
transform: `translate3d(${leftPx}px, ${topPx}px, 0)`,
|
|
3482
|
+
transform: `translate3d(${leftPx}px, ${topPx}px, 0)${isDragging ? " scale(1.05)" : ""}`,
|
|
3229
3483
|
width: size,
|
|
3230
3484
|
height: size,
|
|
3231
|
-
opacity: isLandscape ? 0.
|
|
3485
|
+
opacity: isLandscape ? 0.75 : 0.9,
|
|
3232
3486
|
WebkitTouchCallout: "none",
|
|
3233
|
-
|
|
3487
|
+
WebkitUserSelect: "none",
|
|
3488
|
+
touchAction: "none",
|
|
3489
|
+
transition: isDragging ? "none" : "transform 0.1s ease-out"
|
|
3234
3490
|
},
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
/* @__PURE__ */ jsxRuntime.
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3491
|
+
children: [
|
|
3492
|
+
/* @__PURE__ */ jsxRuntime.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"}` }),
|
|
3493
|
+
/* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "100%", height: "100%", viewBox: "0 0 100 100", className: "drop-shadow-xl relative z-10", children: [
|
|
3494
|
+
/* @__PURE__ */ jsxRuntime.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" }),
|
|
3495
|
+
/* @__PURE__ */ jsxRuntime.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" }),
|
|
3496
|
+
/* @__PURE__ */ jsxRuntime.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" }),
|
|
3497
|
+
/* @__PURE__ */ jsxRuntime.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" }),
|
|
3498
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3499
|
+
"circle",
|
|
3500
|
+
{
|
|
3501
|
+
ref: centerCircleRef,
|
|
3502
|
+
cx: "50",
|
|
3503
|
+
cy: "50",
|
|
3504
|
+
r: "12",
|
|
3505
|
+
fill: isDragging ? systemColor : "rgba(0,0,0,0.5)",
|
|
3506
|
+
stroke: isDragging ? "#fff" : "rgba(255,255,255,0.3)",
|
|
3507
|
+
strokeWidth: isDragging ? 2 : 1
|
|
3508
|
+
}
|
|
3509
|
+
),
|
|
3510
|
+
/* @__PURE__ */ jsxRuntime.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" }),
|
|
3511
|
+
/* @__PURE__ */ jsxRuntime.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" }),
|
|
3512
|
+
/* @__PURE__ */ jsxRuntime.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" }),
|
|
3513
|
+
/* @__PURE__ */ jsxRuntime.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" })
|
|
3514
|
+
] })
|
|
3515
|
+
]
|
|
3247
3516
|
}
|
|
3248
3517
|
);
|
|
3249
3518
|
});
|
|
@@ -3329,6 +3598,72 @@ function useButtonPositions() {
|
|
|
3329
3598
|
resetPositions
|
|
3330
3599
|
};
|
|
3331
3600
|
}
|
|
3601
|
+
var STORAGE_KEY2 = "koin-controls-hint-shown";
|
|
3602
|
+
function ControlsHint({ isVisible }) {
|
|
3603
|
+
const [show, setShow] = React2.useState(false);
|
|
3604
|
+
React2.useEffect(() => {
|
|
3605
|
+
if (!isVisible) return;
|
|
3606
|
+
try {
|
|
3607
|
+
const wasShown = sessionStorage.getItem(STORAGE_KEY2);
|
|
3608
|
+
if (!wasShown) {
|
|
3609
|
+
const timer = setTimeout(() => setShow(true), 1500);
|
|
3610
|
+
return () => clearTimeout(timer);
|
|
3611
|
+
}
|
|
3612
|
+
} catch {
|
|
3613
|
+
}
|
|
3614
|
+
}, [isVisible]);
|
|
3615
|
+
const handleDismiss = () => {
|
|
3616
|
+
setShow(false);
|
|
3617
|
+
try {
|
|
3618
|
+
sessionStorage.setItem(STORAGE_KEY2, "true");
|
|
3619
|
+
} catch {
|
|
3620
|
+
}
|
|
3621
|
+
};
|
|
3622
|
+
if (!show) return null;
|
|
3623
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3624
|
+
"div",
|
|
3625
|
+
{
|
|
3626
|
+
className: "fixed inset-0 z-[100] flex items-center justify-center bg-black/60 backdrop-blur-sm",
|
|
3627
|
+
onClick: handleDismiss,
|
|
3628
|
+
onTouchEnd: handleDismiss,
|
|
3629
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3630
|
+
"div",
|
|
3631
|
+
{
|
|
3632
|
+
className: "bg-black/90 border border-white/30 rounded-2xl p-6 mx-4 max-w-sm text-center shadow-2xl pointer-events-auto",
|
|
3633
|
+
onClick: (e) => e.stopPropagation(),
|
|
3634
|
+
onTouchEnd: (e) => e.stopPropagation(),
|
|
3635
|
+
children: [
|
|
3636
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-center mb-4", children: /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx(lucideReact.Move, { size: 32, className: "text-green-400" }) }) }),
|
|
3637
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-white text-lg font-bold mb-2", children: "Customize Your Controls" }),
|
|
3638
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-white/70 text-sm mb-4", children: [
|
|
3639
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { className: "text-white", children: "Long-press" }),
|
|
3640
|
+
" any button or the ",
|
|
3641
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { className: "text-white", children: "D-pad center" }),
|
|
3642
|
+
" to drag and reposition it."
|
|
3643
|
+
] }),
|
|
3644
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-white/50 text-xs mb-4", children: "Your layout is saved separately for portrait and landscape modes." }),
|
|
3645
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3646
|
+
"button",
|
|
3647
|
+
{
|
|
3648
|
+
type: "button",
|
|
3649
|
+
onClick: (e) => {
|
|
3650
|
+
e.stopPropagation();
|
|
3651
|
+
handleDismiss();
|
|
3652
|
+
},
|
|
3653
|
+
onTouchEnd: (e) => {
|
|
3654
|
+
e.stopPropagation();
|
|
3655
|
+
handleDismiss();
|
|
3656
|
+
},
|
|
3657
|
+
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",
|
|
3658
|
+
children: "Got it!"
|
|
3659
|
+
}
|
|
3660
|
+
)
|
|
3661
|
+
]
|
|
3662
|
+
}
|
|
3663
|
+
)
|
|
3664
|
+
}
|
|
3665
|
+
);
|
|
3666
|
+
}
|
|
3332
3667
|
function VirtualController({
|
|
3333
3668
|
system,
|
|
3334
3669
|
isRunning,
|
|
@@ -3349,7 +3684,6 @@ function VirtualController({
|
|
|
3349
3684
|
return btn.showInLandscape;
|
|
3350
3685
|
});
|
|
3351
3686
|
const DPAD_TYPES = ["up", "down", "left", "right"];
|
|
3352
|
-
const dpadButtons = visibleButtons.filter((btn) => DPAD_TYPES.includes(btn.type));
|
|
3353
3687
|
React2.useEffect(() => {
|
|
3354
3688
|
const updateSize = () => {
|
|
3355
3689
|
const { width, height } = getViewportSize();
|
|
@@ -3399,7 +3733,7 @@ function VirtualController({
|
|
|
3399
3733
|
},
|
|
3400
3734
|
[controls]
|
|
3401
3735
|
);
|
|
3402
|
-
const SYSTEM_BUTTONS2 = ["start", "select"];
|
|
3736
|
+
const SYSTEM_BUTTONS2 = ["start", "select", "menu"];
|
|
3403
3737
|
const handlePress = React2.useCallback(
|
|
3404
3738
|
(buttonType) => {
|
|
3405
3739
|
const isSystemButton = SYSTEM_BUTTONS2.includes(buttonType);
|
|
@@ -3487,26 +3821,28 @@ function VirtualController({
|
|
|
3487
3821
|
if (!isMobile) {
|
|
3488
3822
|
return null;
|
|
3489
3823
|
}
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3824
|
+
const dpadSize = containerSize.width > containerSize.height ? 160 : 180;
|
|
3825
|
+
const dpadY = containerSize.width > containerSize.height ? 55 : 62;
|
|
3826
|
+
const finalDpadY = system.toUpperCase() === "NEOGEO" ? dpadY - 5 : dpadY;
|
|
3493
3827
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3494
3828
|
"div",
|
|
3495
3829
|
{
|
|
3496
3830
|
className: "fixed inset-0 z-30 pointer-events-none",
|
|
3497
3831
|
style: { touchAction: "none" },
|
|
3498
3832
|
children: [
|
|
3499
|
-
|
|
3833
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3500
3834
|
Dpad_default,
|
|
3501
3835
|
{
|
|
3502
|
-
size:
|
|
3503
|
-
x:
|
|
3504
|
-
y:
|
|
3836
|
+
size: dpadSize,
|
|
3837
|
+
x: 14,
|
|
3838
|
+
y: finalDpadY,
|
|
3505
3839
|
containerWidth: containerSize.width || window.innerWidth,
|
|
3506
3840
|
containerHeight: containerSize.height || window.innerHeight,
|
|
3507
3841
|
controls,
|
|
3508
3842
|
systemColor,
|
|
3509
|
-
isLandscape
|
|
3843
|
+
isLandscape,
|
|
3844
|
+
customPosition: getPosition("up", isLandscape),
|
|
3845
|
+
onPositionChange: (x, y) => savePosition("up", x, y, isLandscape)
|
|
3510
3846
|
}
|
|
3511
3847
|
),
|
|
3512
3848
|
memoizedButtonElements.filter(({ buttonConfig }) => !DPAD_TYPES.includes(buttonConfig.type)).map(({ buttonConfig, adjustedConfig, customPosition, width, height }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -3522,64 +3858,77 @@ function VirtualController({
|
|
|
3522
3858
|
customPosition,
|
|
3523
3859
|
onPositionChange: (x, y) => savePosition(buttonConfig.type, x, y, isLandscape),
|
|
3524
3860
|
isLandscape,
|
|
3525
|
-
|
|
3861
|
+
console: layout.console
|
|
3526
3862
|
},
|
|
3527
3863
|
buttonConfig.type
|
|
3528
|
-
))
|
|
3864
|
+
)),
|
|
3865
|
+
/* @__PURE__ */ jsxRuntime.jsx(ControlsHint, { isVisible: isRunning })
|
|
3529
3866
|
]
|
|
3530
3867
|
}
|
|
3531
3868
|
);
|
|
3532
3869
|
}
|
|
3533
3870
|
function FloatingExitButton({ onClick, disabled = false }) {
|
|
3534
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
3871
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3535
3872
|
"button",
|
|
3536
3873
|
{
|
|
3537
3874
|
onClick,
|
|
3538
3875
|
disabled,
|
|
3539
3876
|
className: `
|
|
3540
|
-
fixed top-3
|
|
3541
|
-
|
|
3542
|
-
bg-black/
|
|
3543
|
-
border-2 border-
|
|
3544
|
-
shadow-
|
|
3545
|
-
flex items-center
|
|
3546
|
-
transition-all duration-
|
|
3877
|
+
fixed top-3 right-3 z-50
|
|
3878
|
+
px-3 py-2 rounded-xl
|
|
3879
|
+
bg-black/80 backdrop-blur-md
|
|
3880
|
+
border-2 border-red-400/60
|
|
3881
|
+
shadow-xl
|
|
3882
|
+
flex items-center gap-2
|
|
3883
|
+
transition-all duration-300
|
|
3547
3884
|
hover:bg-red-600/30 hover:border-red-400 hover:scale-105
|
|
3548
3885
|
active:scale-95
|
|
3549
3886
|
disabled:opacity-40 disabled:cursor-not-allowed
|
|
3550
3887
|
touch-manipulation
|
|
3551
3888
|
`,
|
|
3552
3889
|
style: {
|
|
3553
|
-
paddingTop: "env(safe-area-inset-top, 0px)"
|
|
3890
|
+
paddingTop: "max(env(safe-area-inset-top, 0px), 8px)"
|
|
3554
3891
|
},
|
|
3555
3892
|
"aria-label": "Exit fullscreen",
|
|
3556
|
-
|
|
3557
|
-
|
|
3893
|
+
children: [
|
|
3894
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { size: 16, className: "text-red-400" }),
|
|
3895
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-white text-xs font-bold uppercase tracking-wider", children: "Exit" }),
|
|
3896
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Minimize2, { size: 14, className: "text-white/60" })
|
|
3897
|
+
]
|
|
3558
3898
|
}
|
|
3559
3899
|
);
|
|
3560
3900
|
}
|
|
3561
3901
|
function FloatingFullscreenButton({ onClick, disabled = false }) {
|
|
3562
|
-
|
|
3902
|
+
const [pulse, setPulse] = React2.useState(true);
|
|
3903
|
+
React2.useEffect(() => {
|
|
3904
|
+
const timer = setTimeout(() => setPulse(false), 5e3);
|
|
3905
|
+
return () => clearTimeout(timer);
|
|
3906
|
+
}, []);
|
|
3907
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3563
3908
|
"button",
|
|
3564
3909
|
{
|
|
3565
3910
|
onClick,
|
|
3566
3911
|
disabled,
|
|
3567
3912
|
className: `
|
|
3568
3913
|
absolute top-3 left-3 z-50
|
|
3569
|
-
|
|
3570
|
-
bg-black/
|
|
3571
|
-
border-2 border-white/
|
|
3572
|
-
shadow-
|
|
3573
|
-
flex items-center
|
|
3574
|
-
transition-all duration-
|
|
3575
|
-
hover:bg-white/20 hover:border-white
|
|
3914
|
+
px-3 py-2 rounded-xl
|
|
3915
|
+
bg-black/80 backdrop-blur-md
|
|
3916
|
+
border-2 border-white/60
|
|
3917
|
+
shadow-xl
|
|
3918
|
+
flex items-center gap-2
|
|
3919
|
+
transition-all duration-300
|
|
3920
|
+
hover:bg-white/20 hover:border-white hover:scale-105
|
|
3576
3921
|
active:scale-95
|
|
3577
3922
|
disabled:opacity-40 disabled:cursor-not-allowed
|
|
3578
3923
|
touch-manipulation
|
|
3924
|
+
${pulse ? "animate-pulse border-green-400/80" : ""}
|
|
3579
3925
|
`,
|
|
3580
|
-
"aria-label": "
|
|
3581
|
-
|
|
3582
|
-
|
|
3926
|
+
"aria-label": "Tap for fullscreen controls",
|
|
3927
|
+
children: [
|
|
3928
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Gamepad2, { size: 18, className: "text-green-400" }),
|
|
3929
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-white text-xs font-bold uppercase tracking-wider whitespace-nowrap", children: "Tap for Controls" }),
|
|
3930
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Maximize, { size: 14, className: "text-white/60" })
|
|
3931
|
+
]
|
|
3583
3932
|
}
|
|
3584
3933
|
);
|
|
3585
3934
|
}
|
|
@@ -5906,16 +6255,16 @@ var SYSTEMS = [
|
|
|
5906
6255
|
},
|
|
5907
6256
|
{
|
|
5908
6257
|
key: "ARCADE",
|
|
5909
|
-
label: "Arcade (
|
|
6258
|
+
label: "Arcade (FBNeo)",
|
|
5910
6259
|
fullName: "Arcade",
|
|
5911
6260
|
slug: "arcade",
|
|
5912
|
-
extensions: [],
|
|
5913
|
-
core: "
|
|
5914
|
-
dbNames: ["Arcade", "MAME"],
|
|
6261
|
+
extensions: [".zip"],
|
|
6262
|
+
core: "fbneo",
|
|
6263
|
+
dbNames: ["Arcade", "MAME", "FBNeo", "FinalBurn Neo"],
|
|
5915
6264
|
iconName: "ARCADE",
|
|
5916
6265
|
color: "group-hover:text-[#D500F9]",
|
|
5917
6266
|
accentHex: "#D500F9",
|
|
5918
|
-
aliases: ["MAME", "CPS1", "CPS2", "CPS3"],
|
|
6267
|
+
aliases: ["MAME", "CPS1", "CPS2", "CPS3", "FBNEO", "FINALBURN", "SYSTEM16", "SEGA SYSTEM 16"],
|
|
5919
6268
|
biosNeeded: true
|
|
5920
6269
|
},
|
|
5921
6270
|
{
|
|
@@ -7895,7 +8244,7 @@ function useGamePlayer(props) {
|
|
|
7895
8244
|
recordingSupported
|
|
7896
8245
|
};
|
|
7897
8246
|
}
|
|
7898
|
-
var
|
|
8247
|
+
var STORAGE_KEY3 = "koin-player-settings";
|
|
7899
8248
|
var DEFAULT_SETTINGS = {
|
|
7900
8249
|
volume: 1,
|
|
7901
8250
|
muted: false,
|
|
@@ -7908,7 +8257,7 @@ function usePlayerPersistence(onSettingsChange) {
|
|
|
7908
8257
|
const [isLoaded, setIsLoaded] = React2.useState(false);
|
|
7909
8258
|
React2.useEffect(() => {
|
|
7910
8259
|
try {
|
|
7911
|
-
const stored = localStorage.getItem(
|
|
8260
|
+
const stored = localStorage.getItem(STORAGE_KEY3);
|
|
7912
8261
|
if (stored) {
|
|
7913
8262
|
const parsed = JSON.parse(stored);
|
|
7914
8263
|
setSettings((prev) => ({ ...prev, ...parsed }));
|
|
@@ -7922,7 +8271,7 @@ function usePlayerPersistence(onSettingsChange) {
|
|
|
7922
8271
|
setSettings((prev) => {
|
|
7923
8272
|
const next = { ...prev, ...updates };
|
|
7924
8273
|
try {
|
|
7925
|
-
localStorage.setItem(
|
|
8274
|
+
localStorage.setItem(STORAGE_KEY3, JSON.stringify(next));
|
|
7926
8275
|
} catch (e) {
|
|
7927
8276
|
console.error("Failed to save player settings", e);
|
|
7928
8277
|
}
|
|
@@ -9143,7 +9492,7 @@ exports.CONSOLE_KEYBOARD_OVERRIDES = CONSOLE_KEYBOARD_OVERRIDES;
|
|
|
9143
9492
|
exports.DEFAULT_CONTROLS = DEFAULT_CONTROLS;
|
|
9144
9493
|
exports.DEFAULT_GAMEPAD = DEFAULT_GAMEPAD;
|
|
9145
9494
|
exports.DEFAULT_KEYBOARD = DEFAULT_KEYBOARD;
|
|
9146
|
-
exports.DPAD_BUTTONS =
|
|
9495
|
+
exports.DPAD_BUTTONS = DPAD_BUTTONS2;
|
|
9147
9496
|
exports.FACE_BUTTONS = FACE_BUTTONS;
|
|
9148
9497
|
exports.GamePlayer = GamePlayer_default;
|
|
9149
9498
|
exports.KoinI18nProvider = KoinI18nProvider;
|