koin.js 1.0.8 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import React2, { memo, createContext, useState, useRef, useEffect, useMemo, useCallback, useContext } from 'react';
2
- import { Play, Pause, RotateCcw, Rewind, Save, Download, Camera, Video, Circle, Monitor, ChevronDown, RefreshCw, HelpCircle, Maximize, Gamepad2, Joystick, Code, Settings, Power, Square, Keyboard, X, Palette, ChevronUp, Gauge, VolumeX, Volume1, Volume2, Loader2, Trophy, AlertTriangle, List, PauseCircle, Check, Clock, User, Copy, Lock, Zap, HardDrive, Trash2, Cpu, AlertCircle, FileCode, Globe, ExternalLink, EyeOff, Eye, Shield, CheckCircle, LogOut, Info, XCircle } from 'lucide-react';
2
+ import { Play, Pause, RotateCcw, Rewind, Save, Download, Camera, Video, Circle, Monitor, ChevronDown, RefreshCw, HelpCircle, Maximize, Gamepad2, Joystick, Code, Settings, Power, Square, Keyboard, X, Palette, ChevronUp, Gauge, VolumeX, Volume1, Volume2, Loader2, Trophy, AlertTriangle, Minimize2, List, PauseCircle, Check, Clock, Move, User, Copy, Lock, Zap, HardDrive, Trash2, Cpu, AlertCircle, FileCode, Globe, ExternalLink, EyeOff, Eye, Shield, CheckCircle, LogOut, Info, XCircle } from 'lucide-react';
3
3
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
4
  import { Nostalgist } from 'nostalgist';
5
5
 
@@ -2078,92 +2078,175 @@ function useMobile() {
2078
2078
 
2079
2079
  // src/components/VirtualController/layouts.ts
2080
2080
  var BUTTON_SMALL = 44;
2081
- var BUTTON_MEDIUM = 52;
2082
- var BUTTON_LARGE = 60;
2083
- var NES_LAYOUT = {
2084
- console: "NES",
2081
+ var BUTTON_MEDIUM = 56;
2082
+ var BUTTON_LARGE = 68;
2083
+ var START_SELECT_Y = 8;
2084
+ var SELECT_X = 38;
2085
+ var START_X = 62;
2086
+ var DPAD_BUTTONS = [
2087
+ { type: "up", label: "\u2191", x: 14, y: 55, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2088
+ { type: "down", label: "\u2193", x: 14, y: 70, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2089
+ { type: "left", label: "\u2190", x: 6, y: 62.5, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2090
+ { type: "right", label: "\u2192", x: 22, y: 62.5, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true }
2091
+ ];
2092
+ var TWO_BUTTON_LAYOUT = {
2093
+ console: "2BUTTON",
2085
2094
  buttons: [
2086
- // D-pad - y:55-70 to stay above control bar
2087
- { type: "up", label: "\u2191", x: 12, y: 55, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2088
- { type: "down", label: "\u2193", x: 12, y: 70, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2089
- { type: "left", label: "\u2190", x: 5, y: 62, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2090
- { type: "right", label: "\u2192", x: 19, y: 62, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2091
- // Action buttons
2092
- { type: "b", label: "B", x: 80, y: 64, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
2093
- { type: "a", label: "A", x: 92, y: 56, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
2094
- // System buttons - top center
2095
- { type: "select", label: "SEL", x: 38, y: 8, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true },
2096
- { type: "start", label: "START", x: 62, y: 8, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true }
2095
+ ...DPAD_BUTTONS,
2096
+ { type: "b", label: "B", x: 78, y: 66, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
2097
+ { type: "a", label: "A", x: 90, y: 56, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
2098
+ { type: "select", label: "SELECT", x: SELECT_X, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" },
2099
+ { type: "start", label: "START", x: START_X, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" }
2097
2100
  ]
2098
2101
  };
2099
2102
  var SNES_LAYOUT = {
2100
2103
  console: "SNES",
2101
2104
  buttons: [
2102
- { type: "up", label: "\u2191", x: 12, y: 45, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2103
- { type: "down", label: "\u2193", x: 12, y: 60, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2104
- { type: "left", label: "\u2190", x: 5, y: 52, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2105
- { type: "right", label: "\u2192", x: 19, y: 52, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2106
- { type: "y", label: "Y", x: 76, y: 45, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2107
- { type: "x", label: "X", x: 88, y: 37, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2108
- { type: "b", label: "B", x: 88, y: 53, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2109
- { type: "a", label: "A", x: 96, y: 45, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2110
- { type: "l", label: "L", x: 8, y: 18, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true },
2111
- { type: "r", label: "R", x: 92, y: 18, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true },
2112
- { type: "select", label: "SEL", x: 38, y: 8, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true },
2113
- { type: "start", label: "START", x: 62, y: 8, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true }
2105
+ ...DPAD_BUTTONS,
2106
+ // Diamond: Y-X-B-A (left-top-bottom-right)
2107
+ { type: "y", label: "Y", x: 74, y: 58, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2108
+ { type: "x", label: "X", x: 84, y: 46, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2109
+ { type: "b", label: "B", x: 84, y: 70, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2110
+ { type: "a", label: "A", x: 94, y: 58, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2111
+ // Shoulders
2112
+ { type: "l", label: "L", x: 8, y: 20, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
2113
+ { type: "r", label: "R", x: 92, y: 20, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
2114
+ { type: "select", label: "SELECT", x: SELECT_X, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" },
2115
+ { type: "start", label: "START", x: START_X, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" }
2114
2116
  ]
2115
2117
  };
2116
- var GB_LAYOUT = {
2117
- console: "GB",
2118
+ var GBA_LAYOUT = {
2119
+ console: "GBA",
2118
2120
  buttons: [
2119
- { type: "up", label: "\u2191", x: 12, y: 55, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2120
- { type: "down", label: "\u2193", x: 12, y: 70, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2121
- { type: "left", label: "\u2190", x: 5, y: 62, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2122
- { type: "right", label: "\u2192", x: 19, y: 62, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2123
- { type: "b", label: "B", x: 80, y: 64, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
2124
- { type: "a", label: "A", x: 92, y: 56, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
2125
- { type: "select", label: "SEL", x: 38, y: 8, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true },
2126
- { type: "start", label: "START", x: 62, y: 8, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true }
2121
+ ...DPAD_BUTTONS,
2122
+ { type: "b", label: "B", x: 80, y: 62, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
2123
+ { type: "a", label: "A", x: 92, y: 52, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
2124
+ { type: "l", label: "L", x: 8, y: 20, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
2125
+ { type: "r", label: "R", x: 92, y: 20, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
2126
+ { type: "select", label: "SELECT", x: SELECT_X, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" },
2127
+ { type: "start", label: "START", x: START_X, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" }
2127
2128
  ]
2128
2129
  };
2129
- var GBA_LAYOUT = {
2130
- console: "GBA",
2130
+ var SIX_BUTTON_LAYOUT = {
2131
+ console: "6BUTTON",
2131
2132
  buttons: [
2132
- { type: "up", label: "\u2191", x: 12, y: 45, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2133
- { type: "down", label: "\u2193", x: 12, y: 60, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2134
- { type: "left", label: "\u2190", x: 5, y: 52, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2135
- { type: "right", label: "\u2192", x: 19, y: 52, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2136
- { type: "b", label: "B", x: 82, y: 55, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
2137
- { type: "a", label: "A", x: 94, y: 45, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
2138
- { type: "l", label: "L", x: 8, y: 18, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true },
2139
- { type: "r", label: "R", x: 92, y: 18, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true },
2140
- { type: "select", label: "SEL", x: 38, y: 8, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true },
2141
- { type: "start", label: "START", x: 62, y: 8, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true }
2133
+ ...DPAD_BUTTONS,
2134
+ // Top row (X, Y, Z) mapped to L, X, R
2135
+ { type: "l", label: "X", x: 74, y: 48, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2136
+ { type: "x", label: "Y", x: 84, y: 44, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2137
+ { type: "r", label: "Z", x: 94, y: 40, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2138
+ // Bottom row (A, B, C) mapped to Y, B, A
2139
+ { type: "y", label: "A", x: 72, y: 64, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2140
+ { type: "b", label: "B", x: 82, y: 60, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2141
+ { type: "a", label: "C", x: 92, y: 56, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2142
+ { type: "start", label: "START", x: 50, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" }
2142
2143
  ]
2143
2144
  };
2144
- var GENESIS_LAYOUT = {
2145
- console: "GENESIS",
2145
+ var SATURN_LAYOUT = {
2146
+ console: "SATURN",
2146
2147
  buttons: [
2147
- { type: "up", label: "\u2191", x: 12, y: 45, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2148
- { type: "down", label: "\u2193", x: 12, y: 60, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2149
- { type: "left", label: "\u2190", x: 5, y: 52, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2150
- { type: "right", label: "\u2192", x: 19, y: 52, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2151
- { type: "a", label: "A", x: 74, y: 56, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2152
- { type: "b", label: "B", x: 85, y: 48, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2153
- { type: "c", label: "C", x: 96, y: 40, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2154
- { type: "start", label: "START", x: 50, y: 8, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true }
2148
+ ...DPAD_BUTTONS,
2149
+ // Face buttons (same as 6-button but uses standard buttons)
2150
+ // X/Y/Z on top
2151
+ { type: "x", label: "X", x: 74, y: 48, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2152
+ { type: "y", label: "Y", x: 84, y: 44, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2153
+ { type: "l", label: "Z", x: 94, y: 40, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2154
+ // A/B/C on bottom
2155
+ { type: "b", label: "A", x: 72, y: 64, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2156
+ { type: "a", label: "B", x: 82, y: 60, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2157
+ { type: "r", label: "C", x: 92, y: 56, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2158
+ // Triggers (L2/R2 for Saturn L/R)
2159
+ { type: "l2", label: "L", x: 8, y: 20, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
2160
+ { type: "r2", label: "R", x: 92, y: 20, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
2161
+ { type: "start", label: "START", x: 50, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" }
2162
+ ]
2163
+ };
2164
+ var NEOGEO_LAYOUT = {
2165
+ console: "NEOGEO",
2166
+ buttons: [
2167
+ ...DPAD_BUTTONS,
2168
+ // Curved A-B-C-D layout (maps to b-a-y-x for RetroPad)
2169
+ { type: "b", label: "A", x: 68, y: 68, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
2170
+ { type: "a", label: "B", x: 78, y: 62, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
2171
+ { type: "y", label: "C", x: 88, y: 56, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
2172
+ { type: "x", label: "D", x: 96, y: 48, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
2173
+ { type: "select", label: "COIN", x: SELECT_X, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" },
2174
+ { type: "start", label: "START", x: START_X, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" }
2175
+ ]
2176
+ };
2177
+ var PSX_LAYOUT = {
2178
+ console: "PSX",
2179
+ buttons: [
2180
+ ...DPAD_BUTTONS,
2181
+ // PS symbols: △◯✕□ → y, b, a, x (triangle-circle-cross-square)
2182
+ { type: "y", label: "\u25B3", x: 84, y: 46, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2183
+ { type: "b", label: "\u25CB", x: 94, y: 58, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2184
+ { type: "a", label: "\u2715", x: 84, y: 70, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2185
+ { type: "x", label: "\u25A1", x: 74, y: 58, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2186
+ // Shoulders
2187
+ { type: "l", label: "L1", x: 8, y: 25, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
2188
+ { type: "r", label: "R1", x: 92, y: 25, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
2189
+ { type: "l2", label: "L2", x: 8, y: 15, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "rect" },
2190
+ { type: "r2", label: "R2", x: 92, y: 15, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "rect" },
2191
+ { type: "select", label: "SELECT", x: SELECT_X, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" },
2192
+ { type: "start", label: "START", x: START_X, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" }
2193
+ ]
2194
+ };
2195
+ var N64_LAYOUT = {
2196
+ console: "N64",
2197
+ buttons: [
2198
+ ...DPAD_BUTTONS,
2199
+ // A/B
2200
+ { type: "a", label: "A", x: 80, y: 68, size: BUTTON_LARGE, showInPortrait: true, showInLandscape: true },
2201
+ { type: "b", label: "B", x: 72, y: 76, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2202
+ // C-buttons (use x/y as C-left/C-up, l2/r2 as C-down/C-right)
2203
+ { type: "y", label: "C\u2191", x: 92, y: 50, size: 36, showInPortrait: true, showInLandscape: true },
2204
+ { type: "x", label: "C\u2190", x: 86, y: 56, size: 36, showInPortrait: true, showInLandscape: true },
2205
+ { type: "l2", label: "C\u2193", x: 92, y: 62, size: 36, showInPortrait: true, showInLandscape: true },
2206
+ { type: "r2", label: "C\u2192", x: 98, y: 56, size: 36, showInPortrait: true, showInLandscape: true },
2207
+ // Shoulders
2208
+ { type: "l", label: "L", x: 8, y: 20, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
2209
+ { type: "r", label: "R", x: 92, y: 20, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
2210
+ // Z trigger (use select as workaround since it's a unique button)
2211
+ { type: "select", label: "Z", x: 8, y: 35, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
2212
+ { type: "start", label: "START", x: 50, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" }
2213
+ ]
2214
+ };
2215
+ var DREAMCAST_LAYOUT = {
2216
+ console: "DREAMCAST",
2217
+ buttons: [
2218
+ ...DPAD_BUTTONS,
2219
+ // Standard diamond
2220
+ { type: "y", label: "Y", x: 74, y: 58, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2221
+ { type: "x", label: "X", x: 84, y: 46, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2222
+ { type: "b", label: "B", x: 84, y: 70, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2223
+ { type: "a", label: "A", x: 94, y: 58, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true },
2224
+ // Triggers
2225
+ { type: "l", label: "L", x: 8, y: 20, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
2226
+ { type: "r", label: "R", x: 92, y: 20, size: BUTTON_MEDIUM, showInPortrait: true, showInLandscape: true, shape: "rect" },
2227
+ { type: "start", label: "START", x: 50, y: START_SELECT_Y, size: BUTTON_SMALL, showInPortrait: true, showInLandscape: true, shape: "pill" }
2155
2228
  ]
2156
2229
  };
2157
2230
  function getLayoutForSystem(system) {
2158
- const n = system.toUpperCase();
2159
- if (n.includes("NES") || n.includes("FAMICOM")) return NES_LAYOUT;
2160
- if (n.includes("SNES") || n.includes("SUPER")) return SNES_LAYOUT;
2161
- if (n.includes("GBA") || n.includes("ADVANCE")) return GBA_LAYOUT;
2162
- if (n.includes("GB") || n.includes("GAME BOY")) return GB_LAYOUT;
2163
- if (n.includes("GENESIS") || n.includes("MEGA")) return GENESIS_LAYOUT;
2164
- return NES_LAYOUT;
2231
+ const s = system.toUpperCase();
2232
+ if (s === "PS1" || s === "PSX" || s === "PSP") return PSX_LAYOUT;
2233
+ if (s === "N64") return N64_LAYOUT;
2234
+ if (s === "SNES" || s === "NDS") return SNES_LAYOUT;
2235
+ if (s === "GBA") return GBA_LAYOUT;
2236
+ if (s === "NES" || s === "GB" || s === "GBC" || s === "VIRTUAL_BOY") return TWO_BUTTON_LAYOUT;
2237
+ if (s === "DREAMCAST") return DREAMCAST_LAYOUT;
2238
+ if (s === "SATURN") return SATURN_LAYOUT;
2239
+ if (s === "GENESIS") return SIX_BUTTON_LAYOUT;
2240
+ if (s === "MASTER_SYSTEM" || s === "GAME_GEAR") return TWO_BUTTON_LAYOUT;
2241
+ if (s === "NEOGEO") return NEOGEO_LAYOUT;
2242
+ if (s.includes("NEOGEO_POCKET")) return TWO_BUTTON_LAYOUT;
2243
+ if (s === "ARCADE") return SIX_BUTTON_LAYOUT;
2244
+ if (s === "PC_ENGINE" || s === "TURBOGRAFX") return TWO_BUTTON_LAYOUT;
2245
+ if (s.includes("WONDERSWAN")) return TWO_BUTTON_LAYOUT;
2246
+ if (s.includes("ATARI")) return TWO_BUTTON_LAYOUT;
2247
+ return TWO_BUTTON_LAYOUT;
2165
2248
  }
2166
- var DRAG_HOLD_DELAY = 300;
2249
+ var DRAG_HOLD_DELAY = 350;
2167
2250
  var DRAG_MOVE_THRESHOLD = 10;
2168
2251
  var DRAG_CENTER_THRESHOLD = 0.4;
2169
2252
  function useTouchHandlers({
@@ -2196,6 +2279,9 @@ function useTouchHandlers({
2196
2279
  x: touchX - displayX / 100 * containerWidth,
2197
2280
  y: touchY - displayY / 100 * containerHeight
2198
2281
  };
2282
+ if (navigator.vibrate) {
2283
+ navigator.vibrate([10, 30, 10]);
2284
+ }
2199
2285
  if (!isSystemButton) {
2200
2286
  onRelease(buttonType);
2201
2287
  }
@@ -2311,55 +2397,119 @@ function useTouchHandlers({
2311
2397
  }
2312
2398
 
2313
2399
  // src/components/VirtualController/utils/buttonStyles.ts
2314
- function getButtonStyles(buttonType, isPressed) {
2400
+ var DEFAULT_FACE = {
2401
+ bg: "bg-white/10 backdrop-blur-md",
2402
+ text: "text-white",
2403
+ border: "border-white/30",
2404
+ shadow: "shadow-lg",
2405
+ transform: ""
2406
+ };
2407
+ var DEFAULT_SHOULDER = {
2408
+ bg: "bg-white/20 backdrop-blur-md",
2409
+ text: "text-white",
2410
+ border: "border-white/30",
2411
+ shadow: "shadow-sm",
2412
+ transform: ""
2413
+ };
2414
+ var DEFAULT_SYSTEM = {
2415
+ bg: "bg-black/60 backdrop-blur-md",
2416
+ text: "text-white/80",
2417
+ border: "border-white/20",
2418
+ shadow: "shadow-sm",
2419
+ transform: ""
2420
+ };
2421
+ var PRESSED_STYLE = {
2422
+ bg: "bg-white/90",
2423
+ text: "text-black",
2424
+ border: "border-white",
2425
+ shadow: "shadow-none",
2426
+ transform: "scale(0.95)"
2427
+ };
2428
+ function coloredButton(bgColor, textColor = "text-white") {
2429
+ return {
2430
+ bg: bgColor,
2431
+ text: textColor,
2432
+ border: "border-white/20",
2433
+ shadow: "shadow-lg",
2434
+ transform: ""
2435
+ };
2436
+ }
2437
+ function psButton(symbolColor) {
2438
+ return {
2439
+ bg: "bg-black/80 backdrop-blur-md",
2440
+ text: symbolColor,
2441
+ border: "border-white/30",
2442
+ shadow: "shadow-lg",
2443
+ transform: ""
2444
+ };
2445
+ }
2446
+ function getButtonStyles(buttonType, isPressed, consoleName = "") {
2315
2447
  if (isPressed) {
2316
- return {
2317
- bg: "bg-retro-primary",
2318
- text: "text-black",
2319
- border: "border-white",
2320
- shadow: "shadow-none",
2321
- transform: "translate-x-[3px] translate-y-[3px]"
2322
- };
2448
+ return PRESSED_STYLE;
2449
+ }
2450
+ const c = consoleName.toUpperCase();
2451
+ if (c === "NEOGEO") {
2452
+ switch (buttonType) {
2453
+ case "b":
2454
+ return coloredButton("bg-[#E60012]");
2455
+ // A = Red
2456
+ case "a":
2457
+ return coloredButton("bg-[#FFD600]", "text-black");
2458
+ // B = Yellow
2459
+ case "y":
2460
+ return coloredButton("bg-[#009944]");
2461
+ // C = Green
2462
+ case "x":
2463
+ return coloredButton("bg-[#0068B7]");
2464
+ }
2465
+ }
2466
+ if (c === "PSX" || c === "PS1" || c === "PSP") {
2467
+ switch (buttonType) {
2468
+ case "y":
2469
+ return psButton("text-[#25D998]");
2470
+ // △ = Green
2471
+ case "b":
2472
+ return psButton("text-[#FF5555]");
2473
+ // ○ = Red
2474
+ case "a":
2475
+ return psButton("text-[#5599FF]");
2476
+ // ✕ = Blue
2477
+ case "x":
2478
+ return psButton("text-[#E889DD]");
2479
+ }
2480
+ }
2481
+ if (c === "SNES" || c === "NDS") {
2482
+ switch (buttonType) {
2483
+ case "a":
2484
+ return coloredButton("bg-[#CC0000]");
2485
+ // A = Red
2486
+ case "b":
2487
+ return coloredButton("bg-[#CCCC00]", "text-black");
2488
+ // B = Yellow
2489
+ case "x":
2490
+ return coloredButton("bg-[#0033CC]");
2491
+ // X = Blue
2492
+ case "y":
2493
+ return coloredButton("bg-[#006600]");
2494
+ }
2323
2495
  }
2324
2496
  switch (buttonType) {
2325
2497
  case "a":
2326
2498
  case "b":
2327
- case "c":
2328
- return {
2329
- bg: "bg-red-600",
2330
- text: "text-white",
2331
- border: "border-white",
2332
- shadow: "shadow-hard",
2333
- transform: ""
2334
- };
2335
2499
  case "x":
2336
2500
  case "y":
2337
- return {
2338
- bg: "bg-blue-600",
2339
- text: "text-white",
2340
- border: "border-white",
2341
- shadow: "shadow-hard",
2342
- transform: ""
2343
- };
2501
+ return DEFAULT_FACE;
2344
2502
  case "l":
2345
2503
  case "r":
2504
+ case "l2":
2505
+ case "r2":
2506
+ return DEFAULT_SHOULDER;
2346
2507
  case "start":
2347
2508
  case "select":
2348
- return {
2349
- bg: "bg-retro-surface",
2350
- text: "text-white",
2351
- border: "border-white",
2352
- shadow: "shadow-hard",
2353
- transform: ""
2354
- };
2509
+ case "menu":
2510
+ return DEFAULT_SYSTEM;
2355
2511
  default:
2356
- return {
2357
- bg: "bg-black",
2358
- text: "text-white",
2359
- border: "border-white",
2360
- shadow: "shadow-hard",
2361
- transform: ""
2362
- };
2512
+ return DEFAULT_FACE;
2363
2513
  }
2364
2514
  }
2365
2515
  var VirtualButton = React2.memo(function VirtualButton2({
@@ -2373,12 +2523,11 @@ var VirtualButton = React2.memo(function VirtualButton2({
2373
2523
  customPosition,
2374
2524
  onPositionChange,
2375
2525
  isLandscape = false,
2376
- systemColor = "#00FF41"
2377
- // Default retro green
2526
+ console: console2 = ""
2378
2527
  }) {
2379
2528
  const t = useKoinTranslation();
2380
2529
  const buttonRef = useRef(null);
2381
- const isSystemButton = config.type === "start" || config.type === "select";
2530
+ const isSystemButton = config.type === "start" || config.type === "select" || config.type === "menu";
2382
2531
  const displayX = customPosition ? customPosition.x : config.x;
2383
2532
  const displayY = customPosition ? customPosition.y : config.y;
2384
2533
  let label = config.label;
@@ -2421,63 +2570,78 @@ var VirtualButton = React2.memo(function VirtualButton2({
2421
2570
  const leftPercent = displayX / 100 * containerWidth - config.size / 2;
2422
2571
  const topPercent = displayY / 100 * containerHeight - config.size / 2;
2423
2572
  const transform = `translate3d(${leftPercent.toFixed(1)}px, ${topPercent.toFixed(1)}px, 0)`;
2424
- const styles = getButtonStyles(config.type, isPressed);
2425
- const isActionButton = config.type === "a" || config.type === "b";
2426
- const borderRadius = isActionButton ? "50%" : "0";
2427
- const pressedStyle = isPressed ? {
2428
- backgroundColor: systemColor,
2429
- color: "#000000",
2430
- borderColor: "#FFFFFF"
2431
- } : {};
2573
+ const styles = getButtonStyles(config.type, isPressed, console2);
2574
+ let borderRadius = "50%";
2575
+ let width = `${config.size}px`;
2576
+ if (config.shape === "pill") {
2577
+ borderRadius = "20px";
2578
+ width = `${config.size * 1.8}px`;
2579
+ } else if (config.shape === "rect") {
2580
+ borderRadius = "8px";
2581
+ width = `${config.size * 2.5}px`;
2582
+ } else if (config.shape === "square") {
2583
+ borderRadius = "12px";
2584
+ } else {
2585
+ if (["l", "r", "l2", "r2"].includes(config.type)) {
2586
+ borderRadius = "10px";
2587
+ width = `${config.size * 2}px`;
2588
+ }
2589
+ }
2432
2590
  return /* @__PURE__ */ jsx(
2433
2591
  "button",
2434
2592
  {
2435
2593
  ref: buttonRef,
2436
2594
  className: `
2437
- absolute border-4 font-heading font-bold uppercase tracking-wider
2438
- transition-all duration-100 select-none
2595
+ absolute flex items-center justify-center
2596
+ font-heading font-bold uppercase tracking-wider
2597
+ transition-all duration-75 select-none
2439
2598
  pointer-events-auto touch-manipulation
2440
- ${isPressed ? "" : `${styles.bg} ${styles.text} ${styles.border} ${styles.shadow}`} ${styles.transform}
2441
- active:translate-x-[3px] active:translate-y-[3px] active:shadow-none
2599
+ backdrop-blur-sm
2600
+ ${isPressed ? "" : `${styles.bg} ${styles.border} ${styles.shadow}`}
2601
+ ${styles.text}
2602
+ active:shadow-none
2442
2603
  `,
2443
2604
  style: {
2444
- // Remove left/top and use transform instead for high performance (compositor only)
2445
2605
  top: 0,
2446
2606
  left: 0,
2447
- transform,
2607
+ transform: transform + (isPressed ? " scale(0.95)" : ""),
2448
2608
  willChange: "transform",
2449
- width: `${config.size}px`,
2609
+ width,
2450
2610
  height: `${config.size}px`,
2611
+ // Height stays consistent
2451
2612
  minWidth: `${config.size}px`,
2452
- minHeight: `${config.size}px`,
2453
- fontSize: config.size < 50 ? "10px" : "12px",
2613
+ fontSize: config.size < 50 ? "11px" : "16px",
2454
2614
  borderRadius,
2615
+ borderWidth: isPressed ? "0px" : "1.5px",
2616
+ // slightly thicker border
2455
2617
  lineHeight: "1",
2456
2618
  // Semi-transparent in landscape mode
2457
2619
  opacity: isLandscape ? 0.85 : 1,
2458
- // Prevent context menu on long-press
2459
2620
  WebkitTouchCallout: "none",
2460
2621
  WebkitUserSelect: "none",
2461
2622
  userSelect: "none",
2462
- ...pressedStyle
2623
+ // Direct style overrides if needed from getButtonStyles (for exact hex colors)
2624
+ backgroundColor: isPressed ? styles.bg.startsWith("bg-") ? void 0 : styles.bg : styles.bg.startsWith("bg-") ? void 0 : styles.bg,
2625
+ borderColor: isPressed ? void 0 : styles.border.startsWith("border-") ? void 0 : styles.border,
2626
+ color: styles.text.startsWith("text-") ? void 0 : styles.text
2463
2627
  },
2464
2628
  "aria-label": label,
2465
2629
  onContextMenu: (e) => e.preventDefault(),
2466
- children: label
2630
+ children: /* @__PURE__ */ jsx("span", { className: "drop-shadow-md", children: label })
2467
2631
  }
2468
2632
  );
2469
2633
  });
2470
2634
  var VirtualButton_default = VirtualButton;
2471
2635
 
2472
2636
  // src/lib/controls/types.ts
2473
- var DPAD_BUTTONS = ["up", "down", "left", "right"];
2637
+ var DPAD_BUTTONS2 = ["up", "down", "left", "right"];
2474
2638
  var FACE_BUTTONS = ["a", "b", "x", "y"];
2475
2639
  var SHOULDER_BUTTONS = ["l", "r"];
2476
2640
  var TRIGGER_BUTTONS = ["l2", "r2"];
2477
2641
  var STICK_BUTTONS = ["l3", "r3"];
2478
2642
  var SYSTEM_BUTTONS = ["start", "select"];
2479
2643
  var ALL_BUTTONS = [
2480
- ...DPAD_BUTTONS,
2644
+ ...DPAD_BUTTONS2,
2481
2645
  ...FACE_BUTTONS,
2482
2646
  ...SHOULDER_BUTTONS,
2483
2647
  ...TRIGGER_BUTTONS,
@@ -2635,91 +2799,91 @@ var CONSOLE_CAPABILITIES = {
2635
2799
  // ============ Nintendo ============
2636
2800
  NES: {
2637
2801
  console: "NES",
2638
- buttons: [...DPAD_BUTTONS, "a", "b", "start", "select"]
2802
+ buttons: [...DPAD_BUTTONS2, "a", "b", "start", "select"]
2639
2803
  },
2640
2804
  SNES: {
2641
2805
  console: "SNES",
2642
- buttons: [...DPAD_BUTTONS, "a", "b", "x", "y", "l", "r", "start", "select"]
2806
+ buttons: [...DPAD_BUTTONS2, "a", "b", "x", "y", "l", "r", "start", "select"]
2643
2807
  },
2644
2808
  N64: {
2645
2809
  console: "N64",
2646
- buttons: [...DPAD_BUTTONS, "a", "b", "l", "r", "start"]
2810
+ buttons: [...DPAD_BUTTONS2, "a", "b", "l", "r", "start"]
2647
2811
  // No select, has Z trigger (mapped to l2)
2648
2812
  },
2649
2813
  GB: {
2650
2814
  console: "GB",
2651
- buttons: [...DPAD_BUTTONS, "a", "b", "start", "select"]
2815
+ buttons: [...DPAD_BUTTONS2, "a", "b", "start", "select"]
2652
2816
  },
2653
2817
  GBC: {
2654
2818
  console: "GBC",
2655
- buttons: [...DPAD_BUTTONS, "a", "b", "start", "select"]
2819
+ buttons: [...DPAD_BUTTONS2, "a", "b", "start", "select"]
2656
2820
  },
2657
2821
  GBA: {
2658
2822
  console: "GBA",
2659
- buttons: [...DPAD_BUTTONS, "a", "b", "l", "r", "start", "select"]
2823
+ buttons: [...DPAD_BUTTONS2, "a", "b", "l", "r", "start", "select"]
2660
2824
  },
2661
2825
  // ============ Sega ============
2662
2826
  SMS: {
2663
2827
  console: "SMS",
2664
- buttons: [...DPAD_BUTTONS, "a", "b", "start"]
2828
+ buttons: [...DPAD_BUTTONS2, "a", "b", "start"]
2665
2829
  // 1, 2, Pause
2666
2830
  },
2667
2831
  GENESIS: {
2668
2832
  console: "GENESIS",
2669
- buttons: [...DPAD_BUTTONS, "a", "b", "x", "y", "l", "r", "start"]
2833
+ buttons: [...DPAD_BUTTONS2, "a", "b", "x", "y", "l", "r", "start"]
2670
2834
  // 6-button: A/B/C + X/Y/Z
2671
2835
  },
2672
2836
  GG: {
2673
2837
  console: "GG",
2674
- buttons: [...DPAD_BUTTONS, "a", "b", "start"]
2838
+ buttons: [...DPAD_BUTTONS2, "a", "b", "start"]
2675
2839
  },
2676
2840
  SATURN: {
2677
2841
  console: "SATURN",
2678
- buttons: [...DPAD_BUTTONS, "a", "b", "x", "y", "l", "r", "l2", "r2", "start"]
2842
+ buttons: [...DPAD_BUTTONS2, "a", "b", "x", "y", "l", "r", "l2", "r2", "start"]
2679
2843
  // Full 8-button
2680
2844
  },
2681
2845
  // ============ Sony ============
2682
2846
  PS1: {
2683
2847
  console: "PS1",
2684
- buttons: [...DPAD_BUTTONS, "a", "b", "x", "y", "l", "r", "l2", "r2", "l3", "r3", "start", "select"]
2848
+ buttons: [...DPAD_BUTTONS2, "a", "b", "x", "y", "l", "r", "l2", "r2", "l3", "r3", "start", "select"]
2685
2849
  },
2686
2850
  // ============ NEC ============
2687
2851
  PCE: {
2688
2852
  console: "PCE",
2689
2853
  // TurboGrafx-16
2690
- buttons: [...DPAD_BUTTONS, "a", "b", "start", "select"]
2854
+ buttons: [...DPAD_BUTTONS2, "a", "b", "start", "select"]
2691
2855
  // I, II, Run, Select
2692
2856
  },
2693
2857
  // ============ Atari ============
2694
2858
  ATARI2600: {
2695
2859
  console: "ATARI2600",
2696
- buttons: [...DPAD_BUTTONS, "a", "select"]
2860
+ buttons: [...DPAD_BUTTONS2, "a", "select"]
2697
2861
  // Fire, Select/Reset
2698
2862
  },
2699
2863
  ATARI7800: {
2700
2864
  console: "ATARI7800",
2701
- buttons: [...DPAD_BUTTONS, "a", "b", "select"]
2865
+ buttons: [...DPAD_BUTTONS2, "a", "b", "select"]
2702
2866
  },
2703
2867
  LYNX: {
2704
2868
  console: "LYNX",
2705
- buttons: [...DPAD_BUTTONS, "a", "b", "start", "select"]
2869
+ buttons: [...DPAD_BUTTONS2, "a", "b", "start", "select"]
2706
2870
  // A, B, Option 1, Option 2
2707
2871
  },
2708
2872
  // ============ SNK ============
2709
2873
  NGPC: {
2710
2874
  console: "NGPC",
2711
2875
  // Neo Geo Pocket
2712
- buttons: [...DPAD_BUTTONS, "a", "b", "start", "select"]
2876
+ buttons: [...DPAD_BUTTONS2, "a", "b", "start", "select"]
2713
2877
  },
2714
2878
  // ============ Other ============
2715
2879
  WSC: {
2716
2880
  console: "WSC",
2717
2881
  // WonderSwan
2718
- buttons: [...DPAD_BUTTONS, "a", "b", "x", "y", "start"]
2882
+ buttons: [...DPAD_BUTTONS2, "a", "b", "x", "y", "start"]
2719
2883
  },
2720
2884
  ARCADE: {
2721
2885
  console: "ARCADE",
2722
- buttons: [...DPAD_BUTTONS, "a", "b", "x", "y", "l", "r", "start", "select"]
2886
+ buttons: [...DPAD_BUTTONS2, "a", "b", "x", "y", "l", "r", "start", "select"]
2723
2887
  // Generic 6-button
2724
2888
  }
2725
2889
  };
@@ -2760,7 +2924,7 @@ function getConsoleCapabilities(system) {
2760
2924
  const normalized = system.toUpperCase();
2761
2925
  return CONSOLE_CAPABILITIES[normalized] ?? {
2762
2926
  console: normalized,
2763
- buttons: [...DPAD_BUTTONS, "a", "b", "x", "y", "l", "r", "start", "select"]
2927
+ buttons: [...DPAD_BUTTONS2, "a", "b", "x", "y", "l", "r", "start", "select"]
2764
2928
  };
2765
2929
  }
2766
2930
  function getConsoleButtons(system) {
@@ -3048,13 +3212,11 @@ var DEFAULT_CONTROLS = DEFAULT_KEYBOARD;
3048
3212
 
3049
3213
  // src/components/VirtualController/utils/keyboardEvents.ts
3050
3214
  function getKeyboardCode(buttonType, controls) {
3051
- const controlMapping = controls || DEFAULT_CONTROLS;
3052
- const mappingKey = buttonType === "c" ? "x" : buttonType;
3053
- const key = mappingKey;
3054
- if (key in controlMapping) {
3055
- return controlMapping[key] ?? null;
3215
+ const key = buttonType;
3216
+ if (controls && key in controls && controls[key]) {
3217
+ return controls[key];
3056
3218
  }
3057
- return null;
3219
+ return DEFAULT_CONTROLS[key] ?? null;
3058
3220
  }
3059
3221
  function getKeyName(code) {
3060
3222
  if (code.startsWith("Key")) return code.slice(3).toLowerCase();
@@ -3080,23 +3242,34 @@ function dispatchKeyboardEvent(type, code) {
3080
3242
  canvas.dispatchEvent(event);
3081
3243
  return true;
3082
3244
  }
3245
+ var DRAG_HOLD_DELAY2 = 350;
3246
+ var CENTER_TOUCH_RADIUS = 0.25;
3083
3247
  var Dpad = React2.memo(function Dpad2({
3084
- size,
3248
+ size = 180,
3085
3249
  x,
3086
3250
  y,
3087
3251
  containerWidth,
3088
3252
  containerHeight,
3089
3253
  controls,
3090
3254
  systemColor = "#00FF41",
3091
- isLandscape = false
3255
+ isLandscape = false,
3256
+ customPosition,
3257
+ onPositionChange
3092
3258
  }) {
3093
3259
  const dpadRef = useRef(null);
3094
3260
  const activeTouchRef = useRef(null);
3095
3261
  const activeDirectionsRef = useRef(/* @__PURE__ */ new Set());
3096
- const upRef = useRef(null);
3097
- const downRef = useRef(null);
3098
- const leftRef = useRef(null);
3099
- const rightRef = useRef(null);
3262
+ const [isDragging, setIsDragging] = useState(false);
3263
+ const dragTimerRef = useRef(null);
3264
+ const dragStartRef = useRef({ x: 0, y: 0, touchX: 0, touchY: 0 });
3265
+ const touchStartPosRef = useRef({ x: 0, y: 0, time: 0 });
3266
+ const upPathRef = useRef(null);
3267
+ const downPathRef = useRef(null);
3268
+ const leftPathRef = useRef(null);
3269
+ const rightPathRef = useRef(null);
3270
+ const centerCircleRef = useRef(null);
3271
+ const displayX = customPosition ? customPosition.x : x;
3272
+ const displayY = customPosition ? customPosition.y : y;
3100
3273
  const getKeyCode = useCallback((direction) => {
3101
3274
  if (!controls) {
3102
3275
  const defaults = {
@@ -3114,24 +3287,41 @@ var Dpad = React2.memo(function Dpad2({
3114
3287
  const centerY = rect.top + rect.height / 2;
3115
3288
  const dx = touchX - centerX;
3116
3289
  const dy = touchY - centerY;
3117
- const deadZone = rect.width * 0.12;
3118
3290
  const distance = Math.sqrt(dx * dx + dy * dy);
3291
+ const deadZone = rect.width / 2 * 0.15;
3119
3292
  if (distance < deadZone) return /* @__PURE__ */ new Set();
3120
3293
  const directions = /* @__PURE__ */ new Set();
3121
3294
  const angle = Math.atan2(dy, dx) * (180 / Math.PI);
3122
- if (angle >= -157.5 && angle <= -22.5) directions.add("up");
3123
- if (angle >= 22.5 && angle <= 157.5) directions.add("down");
3124
- if (angle >= 112.5 || angle <= -112.5) directions.add("left");
3125
- if (angle >= -67.5 && angle <= 67.5) directions.add("right");
3295
+ if (angle >= -150 && angle <= -30) directions.add("up");
3296
+ if (angle >= 30 && angle <= 150) directions.add("down");
3297
+ if (angle >= 120 || angle <= -120) directions.add("left");
3298
+ if (angle >= -60 && angle <= 60) directions.add("right");
3126
3299
  return directions;
3127
3300
  }, []);
3128
3301
  const updateVisuals = useCallback((directions) => {
3129
- const activeColor = systemColor;
3130
- const inactiveColor = "#1a1a1a";
3131
- if (upRef.current) upRef.current.style.fill = directions.has("up") ? activeColor : inactiveColor;
3132
- if (downRef.current) downRef.current.style.fill = directions.has("down") ? activeColor : inactiveColor;
3133
- if (leftRef.current) leftRef.current.style.fill = directions.has("left") ? activeColor : inactiveColor;
3134
- if (rightRef.current) rightRef.current.style.fill = directions.has("right") ? activeColor : inactiveColor;
3302
+ const activeFill = `${systemColor}80`;
3303
+ const inactiveFill = "rgba(255, 255, 255, 0.05)";
3304
+ const activeStroke = systemColor;
3305
+ const inactiveStroke = "rgba(255, 255, 255, 0.2)";
3306
+ const glow = `0 0 15px ${systemColor}`;
3307
+ const updatePart = (ref, isActive) => {
3308
+ if (ref.current) {
3309
+ ref.current.style.fill = isActive ? activeFill : inactiveFill;
3310
+ ref.current.style.stroke = isActive ? activeStroke : inactiveStroke;
3311
+ ref.current.style.filter = isActive ? `drop-shadow(${glow})` : "none";
3312
+ ref.current.style.transform = isActive ? "scale(0.98)" : "scale(1)";
3313
+ ref.current.style.transformOrigin = "center";
3314
+ }
3315
+ };
3316
+ updatePart(upPathRef, directions.has("up"));
3317
+ updatePart(downPathRef, directions.has("down"));
3318
+ updatePart(leftPathRef, directions.has("left"));
3319
+ updatePart(rightPathRef, directions.has("right"));
3320
+ if (centerCircleRef.current) {
3321
+ const isAny = directions.size > 0;
3322
+ centerCircleRef.current.style.fill = isAny ? systemColor : "rgba(0,0,0,0.5)";
3323
+ centerCircleRef.current.style.stroke = isAny ? "#fff" : "rgba(255,255,255,0.3)";
3324
+ }
3135
3325
  }, [systemColor]);
3136
3326
  const updateDirections = useCallback((newDirections) => {
3137
3327
  const prev = activeDirectionsRef.current;
@@ -3153,48 +3343,107 @@ var Dpad = React2.memo(function Dpad2({
3153
3343
  activeDirectionsRef.current = newDirections;
3154
3344
  updateVisuals(newDirections);
3155
3345
  }, [getKeyCode, updateVisuals]);
3346
+ const clearDragTimer = useCallback(() => {
3347
+ if (dragTimerRef.current) {
3348
+ clearTimeout(dragTimerRef.current);
3349
+ dragTimerRef.current = null;
3350
+ }
3351
+ }, []);
3352
+ const startDragging = useCallback((touchX, touchY) => {
3353
+ setIsDragging(true);
3354
+ dragStartRef.current = {
3355
+ x: displayX,
3356
+ y: displayY,
3357
+ touchX,
3358
+ touchY
3359
+ };
3360
+ activeDirectionsRef.current.forEach((dir) => {
3361
+ const keyCode = getKeyCode(dir);
3362
+ if (keyCode) dispatchKeyboardEvent("keyup", keyCode);
3363
+ });
3364
+ activeDirectionsRef.current = /* @__PURE__ */ new Set();
3365
+ updateVisuals(/* @__PURE__ */ new Set());
3366
+ if (navigator.vibrate) navigator.vibrate([10, 30, 10]);
3367
+ }, [displayX, displayY, getKeyCode, updateVisuals]);
3156
3368
  const handleTouchStart = useCallback((e) => {
3157
3369
  e.preventDefault();
3158
- e.stopPropagation();
3159
- const touch = e.touches[0];
3370
+ if (activeTouchRef.current !== null) return;
3371
+ const touch = e.changedTouches[0];
3160
3372
  activeTouchRef.current = touch.identifier;
3373
+ touchStartPosRef.current = { x: touch.clientX, y: touch.clientY, time: Date.now() };
3161
3374
  const rect = dpadRef.current?.getBoundingClientRect();
3162
3375
  if (!rect) return;
3163
- updateDirections(getDirectionsFromTouch(touch.clientX, touch.clientY, rect));
3164
- }, [getDirectionsFromTouch, updateDirections]);
3376
+ const centerX = rect.left + rect.width / 2;
3377
+ const centerY = rect.top + rect.height / 2;
3378
+ const distFromCenter = Math.sqrt(
3379
+ Math.pow(touch.clientX - centerX, 2) + Math.pow(touch.clientY - centerY, 2)
3380
+ );
3381
+ const centerRadius = size * CENTER_TOUCH_RADIUS;
3382
+ if (distFromCenter < centerRadius && onPositionChange) {
3383
+ dragTimerRef.current = setTimeout(() => {
3384
+ startDragging(touch.clientX, touch.clientY);
3385
+ }, DRAG_HOLD_DELAY2);
3386
+ }
3387
+ if (!isDragging) {
3388
+ updateDirections(getDirectionsFromTouch(touch.clientX, touch.clientY, rect));
3389
+ }
3390
+ }, [getDirectionsFromTouch, updateDirections, isDragging, size, onPositionChange, startDragging]);
3165
3391
  const handleTouchMove = useCallback((e) => {
3166
3392
  e.preventDefault();
3167
3393
  let touch = null;
3168
- for (let i = 0; i < e.touches.length; i++) {
3169
- if (e.touches[i].identifier === activeTouchRef.current) {
3170
- touch = e.touches[i];
3394
+ for (let i = 0; i < e.changedTouches.length; i++) {
3395
+ if (e.changedTouches[i].identifier === activeTouchRef.current) {
3396
+ touch = e.changedTouches[i];
3171
3397
  break;
3172
3398
  }
3173
3399
  }
3174
3400
  if (!touch) return;
3175
- const rect = dpadRef.current?.getBoundingClientRect();
3176
- if (!rect) return;
3177
- updateDirections(getDirectionsFromTouch(touch.clientX, touch.clientY, rect));
3178
- }, [getDirectionsFromTouch, updateDirections]);
3401
+ if (isDragging && onPositionChange) {
3402
+ const deltaX = touch.clientX - dragStartRef.current.touchX;
3403
+ const deltaY = touch.clientY - dragStartRef.current.touchY;
3404
+ const newXPercent = dragStartRef.current.x + deltaX / containerWidth * 100;
3405
+ const newYPercent = dragStartRef.current.y + deltaY / containerHeight * 100;
3406
+ const margin = size / 2 / Math.min(containerWidth, containerHeight) * 100;
3407
+ const constrainedX = Math.max(margin, Math.min(100 - margin, newXPercent));
3408
+ const constrainedY = Math.max(margin, Math.min(100 - margin, newYPercent));
3409
+ onPositionChange(constrainedX, constrainedY);
3410
+ } else {
3411
+ const moveDistance = Math.sqrt(
3412
+ Math.pow(touch.clientX - touchStartPosRef.current.x, 2) + Math.pow(touch.clientY - touchStartPosRef.current.y, 2)
3413
+ );
3414
+ if (moveDistance > 15) {
3415
+ clearDragTimer();
3416
+ }
3417
+ const rect = dpadRef.current?.getBoundingClientRect();
3418
+ if (rect) {
3419
+ updateDirections(getDirectionsFromTouch(touch.clientX, touch.clientY, rect));
3420
+ }
3421
+ }
3422
+ }, [isDragging, onPositionChange, containerWidth, containerHeight, size, getDirectionsFromTouch, updateDirections, clearDragTimer]);
3179
3423
  const handleTouchEnd = useCallback((e) => {
3180
3424
  e.preventDefault();
3181
- let touchEnded = true;
3182
- for (let i = 0; i < e.touches.length; i++) {
3183
- if (e.touches[i].identifier === activeTouchRef.current) {
3184
- touchEnded = false;
3425
+ clearDragTimer();
3426
+ let touchEnded = false;
3427
+ for (let i = 0; i < e.changedTouches.length; i++) {
3428
+ if (e.changedTouches[i].identifier === activeTouchRef.current) {
3429
+ touchEnded = true;
3185
3430
  break;
3186
3431
  }
3187
3432
  }
3188
3433
  if (touchEnded) {
3189
3434
  activeTouchRef.current = null;
3190
- activeDirectionsRef.current.forEach((dir) => {
3191
- const keyCode = getKeyCode(dir);
3192
- if (keyCode) dispatchKeyboardEvent("keyup", keyCode);
3193
- });
3194
- activeDirectionsRef.current = /* @__PURE__ */ new Set();
3195
- updateVisuals(/* @__PURE__ */ new Set());
3435
+ if (isDragging) {
3436
+ setIsDragging(false);
3437
+ } else {
3438
+ activeDirectionsRef.current.forEach((dir) => {
3439
+ const keyCode = getKeyCode(dir);
3440
+ if (keyCode) dispatchKeyboardEvent("keyup", keyCode);
3441
+ });
3442
+ activeDirectionsRef.current = /* @__PURE__ */ new Set();
3443
+ updateVisuals(/* @__PURE__ */ new Set());
3444
+ }
3196
3445
  }
3197
- }, [getKeyCode, updateVisuals]);
3446
+ }, [getKeyCode, updateVisuals, isDragging, clearDragTimer]);
3198
3447
  useEffect(() => {
3199
3448
  const dpad = dpadRef.current;
3200
3449
  if (!dpad) return;
@@ -3207,37 +3456,57 @@ var Dpad = React2.memo(function Dpad2({
3207
3456
  dpad.removeEventListener("touchmove", handleTouchMove);
3208
3457
  dpad.removeEventListener("touchend", handleTouchEnd);
3209
3458
  dpad.removeEventListener("touchcancel", handleTouchEnd);
3459
+ clearDragTimer();
3210
3460
  };
3211
- }, [handleTouchStart, handleTouchMove, handleTouchEnd]);
3212
- const leftPx = x / 100 * containerWidth - size / 2;
3213
- const topPx = y / 100 * containerHeight - size / 2;
3214
- return /* @__PURE__ */ jsx(
3461
+ }, [handleTouchStart, handleTouchMove, handleTouchEnd, clearDragTimer]);
3462
+ const leftPx = displayX / 100 * containerWidth - size / 2;
3463
+ const topPx = displayY / 100 * containerHeight - size / 2;
3464
+ const dUp = "M 35,5 L 65,5 L 65,35 L 50,50 L 35,35 Z";
3465
+ const dRight = "M 65,35 L 95,35 L 95,65 L 65,65 L 50,50 Z";
3466
+ const dDown = "M 65,65 L 65,95 L 35,95 L 35,65 L 50,50 Z";
3467
+ const dLeft = "M 35,65 L 5,65 L 5,35 L 35,35 L 50,50 Z";
3468
+ return /* @__PURE__ */ jsxs(
3215
3469
  "div",
3216
3470
  {
3217
3471
  ref: dpadRef,
3218
- className: "absolute pointer-events-auto touch-manipulation",
3472
+ className: `absolute pointer-events-auto touch-manipulation select-none ${isDragging ? "opacity-60" : ""}`,
3219
3473
  style: {
3220
3474
  top: 0,
3221
3475
  left: 0,
3222
- transform: `translate3d(${leftPx}px, ${topPx}px, 0)`,
3476
+ transform: `translate3d(${leftPx}px, ${topPx}px, 0)${isDragging ? " scale(1.05)" : ""}`,
3223
3477
  width: size,
3224
3478
  height: size,
3225
- opacity: isLandscape ? 0.85 : 1,
3479
+ opacity: isLandscape ? 0.75 : 0.9,
3226
3480
  WebkitTouchCallout: "none",
3227
- userSelect: "none"
3481
+ WebkitUserSelect: "none",
3482
+ touchAction: "none",
3483
+ transition: isDragging ? "none" : "transform 0.1s ease-out"
3228
3484
  },
3229
- onContextMenu: (e) => e.preventDefault(),
3230
- children: /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 100 100", children: [
3231
- /* @__PURE__ */ jsx("rect", { ref: upRef, x: "35", y: "5", width: "30", height: "30", rx: "4", fill: "#1a1a1a", stroke: "white", strokeWidth: "2" }),
3232
- /* @__PURE__ */ jsx("rect", { ref: downRef, x: "35", y: "65", width: "30", height: "30", rx: "4", fill: "#1a1a1a", stroke: "white", strokeWidth: "2" }),
3233
- /* @__PURE__ */ jsx("rect", { ref: leftRef, x: "5", y: "35", width: "30", height: "30", rx: "4", fill: "#1a1a1a", stroke: "white", strokeWidth: "2" }),
3234
- /* @__PURE__ */ jsx("rect", { ref: rightRef, x: "65", y: "35", width: "30", height: "30", rx: "4", fill: "#1a1a1a", stroke: "white", strokeWidth: "2" }),
3235
- /* @__PURE__ */ jsx("rect", { x: "35", y: "35", width: "30", height: "30", fill: "#000", stroke: "white", strokeWidth: "2" }),
3236
- /* @__PURE__ */ jsx("text", { x: "50", y: "25", textAnchor: "middle", fill: "#fff", fontSize: "14", fontWeight: "bold", children: "\u2191" }),
3237
- /* @__PURE__ */ jsx("text", { x: "50", y: "85", textAnchor: "middle", fill: "#fff", fontSize: "14", fontWeight: "bold", children: "\u2193" }),
3238
- /* @__PURE__ */ jsx("text", { x: "20", y: "55", textAnchor: "middle", fill: "#fff", fontSize: "14", fontWeight: "bold", children: "\u2190" }),
3239
- /* @__PURE__ */ jsx("text", { x: "80", y: "55", textAnchor: "middle", fill: "#fff", fontSize: "14", fontWeight: "bold", children: "\u2192" })
3240
- ] })
3485
+ children: [
3486
+ /* @__PURE__ */ jsx("div", { className: `absolute inset-0 rounded-full bg-black/40 backdrop-blur-md border shadow-lg ${isDragging ? "border-white/50 ring-2 ring-white/30" : "border-white/10"}` }),
3487
+ /* @__PURE__ */ jsxs("svg", { width: "100%", height: "100%", viewBox: "0 0 100 100", className: "drop-shadow-xl relative z-10", children: [
3488
+ /* @__PURE__ */ jsx("path", { ref: upPathRef, d: dUp, fill: "rgba(255,255,255,0.05)", stroke: "rgba(255,255,255,0.2)", strokeWidth: "1", className: "transition-all duration-75" }),
3489
+ /* @__PURE__ */ jsx("path", { ref: rightPathRef, d: dRight, fill: "rgba(255,255,255,0.05)", stroke: "rgba(255,255,255,0.2)", strokeWidth: "1", className: "transition-all duration-75" }),
3490
+ /* @__PURE__ */ jsx("path", { ref: downPathRef, d: dDown, fill: "rgba(255,255,255,0.05)", stroke: "rgba(255,255,255,0.2)", strokeWidth: "1", className: "transition-all duration-75" }),
3491
+ /* @__PURE__ */ jsx("path", { ref: leftPathRef, d: dLeft, fill: "rgba(255,255,255,0.05)", stroke: "rgba(255,255,255,0.2)", strokeWidth: "1", className: "transition-all duration-75" }),
3492
+ /* @__PURE__ */ jsx(
3493
+ "circle",
3494
+ {
3495
+ ref: centerCircleRef,
3496
+ cx: "50",
3497
+ cy: "50",
3498
+ r: "12",
3499
+ fill: isDragging ? systemColor : "rgba(0,0,0,0.5)",
3500
+ stroke: isDragging ? "#fff" : "rgba(255,255,255,0.3)",
3501
+ strokeWidth: isDragging ? 2 : 1
3502
+ }
3503
+ ),
3504
+ /* @__PURE__ */ jsx("path", { d: "M 50,15 L 50,25 M 45,20 L 50,15 L 55,20", stroke: "white", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", fill: "none", opacity: "0.8", pointerEvents: "none" }),
3505
+ /* @__PURE__ */ jsx("path", { d: "M 50,85 L 50,75 M 45,80 L 50,85 L 55,80", stroke: "white", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", fill: "none", opacity: "0.8", pointerEvents: "none" }),
3506
+ /* @__PURE__ */ jsx("path", { d: "M 15,50 L 25,50 M 20,45 L 15,50 L 20,55", stroke: "white", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", fill: "none", opacity: "0.8", pointerEvents: "none" }),
3507
+ /* @__PURE__ */ jsx("path", { d: "M 85,50 L 75,50 M 80,45 L 85,50 L 80,55", stroke: "white", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", fill: "none", opacity: "0.8", pointerEvents: "none" })
3508
+ ] })
3509
+ ]
3241
3510
  }
3242
3511
  );
3243
3512
  });
@@ -3323,6 +3592,72 @@ function useButtonPositions() {
3323
3592
  resetPositions
3324
3593
  };
3325
3594
  }
3595
+ var STORAGE_KEY2 = "koin-controls-hint-shown";
3596
+ function ControlsHint({ isVisible }) {
3597
+ const [show, setShow] = useState(false);
3598
+ useEffect(() => {
3599
+ if (!isVisible) return;
3600
+ try {
3601
+ const wasShown = sessionStorage.getItem(STORAGE_KEY2);
3602
+ if (!wasShown) {
3603
+ const timer = setTimeout(() => setShow(true), 1500);
3604
+ return () => clearTimeout(timer);
3605
+ }
3606
+ } catch {
3607
+ }
3608
+ }, [isVisible]);
3609
+ const handleDismiss = () => {
3610
+ setShow(false);
3611
+ try {
3612
+ sessionStorage.setItem(STORAGE_KEY2, "true");
3613
+ } catch {
3614
+ }
3615
+ };
3616
+ if (!show) return null;
3617
+ return /* @__PURE__ */ jsx(
3618
+ "div",
3619
+ {
3620
+ className: "fixed inset-0 z-[100] flex items-center justify-center bg-black/60 backdrop-blur-sm",
3621
+ onClick: handleDismiss,
3622
+ onTouchEnd: handleDismiss,
3623
+ children: /* @__PURE__ */ jsxs(
3624
+ "div",
3625
+ {
3626
+ className: "bg-black/90 border border-white/30 rounded-2xl p-6 mx-4 max-w-sm text-center shadow-2xl pointer-events-auto",
3627
+ onClick: (e) => e.stopPropagation(),
3628
+ onTouchEnd: (e) => e.stopPropagation(),
3629
+ children: [
3630
+ /* @__PURE__ */ jsx("div", { className: "flex justify-center mb-4", children: /* @__PURE__ */ jsx("div", { className: "w-16 h-16 rounded-full bg-green-500/20 border-2 border-green-400 flex items-center justify-center", children: /* @__PURE__ */ jsx(Move, { size: 32, className: "text-green-400" }) }) }),
3631
+ /* @__PURE__ */ jsx("h3", { className: "text-white text-lg font-bold mb-2", children: "Customize Your Controls" }),
3632
+ /* @__PURE__ */ jsxs("p", { className: "text-white/70 text-sm mb-4", children: [
3633
+ /* @__PURE__ */ jsx("strong", { className: "text-white", children: "Long-press" }),
3634
+ " any button or the ",
3635
+ /* @__PURE__ */ jsx("strong", { className: "text-white", children: "D-pad center" }),
3636
+ " to drag and reposition it."
3637
+ ] }),
3638
+ /* @__PURE__ */ jsx("p", { className: "text-white/50 text-xs mb-4", children: "Your layout is saved separately for portrait and landscape modes." }),
3639
+ /* @__PURE__ */ jsx(
3640
+ "button",
3641
+ {
3642
+ type: "button",
3643
+ onClick: (e) => {
3644
+ e.stopPropagation();
3645
+ handleDismiss();
3646
+ },
3647
+ onTouchEnd: (e) => {
3648
+ e.stopPropagation();
3649
+ handleDismiss();
3650
+ },
3651
+ className: "w-full py-3 px-4 bg-green-500 hover:bg-green-400 active:bg-green-600 text-black font-bold rounded-xl transition-colors flex items-center justify-center gap-2 cursor-pointer touch-manipulation",
3652
+ children: "Got it!"
3653
+ }
3654
+ )
3655
+ ]
3656
+ }
3657
+ )
3658
+ }
3659
+ );
3660
+ }
3326
3661
  function VirtualController({
3327
3662
  system,
3328
3663
  isRunning,
@@ -3343,7 +3678,6 @@ function VirtualController({
3343
3678
  return btn.showInLandscape;
3344
3679
  });
3345
3680
  const DPAD_TYPES = ["up", "down", "left", "right"];
3346
- const dpadButtons = visibleButtons.filter((btn) => DPAD_TYPES.includes(btn.type));
3347
3681
  useEffect(() => {
3348
3682
  const updateSize = () => {
3349
3683
  const { width, height } = getViewportSize();
@@ -3393,7 +3727,7 @@ function VirtualController({
3393
3727
  },
3394
3728
  [controls]
3395
3729
  );
3396
- const SYSTEM_BUTTONS2 = ["start", "select"];
3730
+ const SYSTEM_BUTTONS2 = ["start", "select", "menu"];
3397
3731
  const handlePress = useCallback(
3398
3732
  (buttonType) => {
3399
3733
  const isSystemButton = SYSTEM_BUTTONS2.includes(buttonType);
@@ -3481,26 +3815,28 @@ function VirtualController({
3481
3815
  if (!isMobile) {
3482
3816
  return null;
3483
3817
  }
3484
- if (visibleButtons.length === 0) {
3485
- return null;
3486
- }
3818
+ const dpadSize = containerSize.width > containerSize.height ? 160 : 180;
3819
+ const dpadY = containerSize.width > containerSize.height ? 55 : 62;
3820
+ const finalDpadY = system.toUpperCase() === "NEOGEO" ? dpadY - 5 : dpadY;
3487
3821
  return /* @__PURE__ */ jsxs(
3488
3822
  "div",
3489
3823
  {
3490
3824
  className: "fixed inset-0 z-30 pointer-events-none",
3491
3825
  style: { touchAction: "none" },
3492
3826
  children: [
3493
- dpadButtons.length > 0 && /* @__PURE__ */ jsx(
3827
+ /* @__PURE__ */ jsx(
3494
3828
  Dpad_default,
3495
3829
  {
3496
- size: containerSize.width > containerSize.height ? 120 : 130,
3497
- x: 12,
3498
- y: containerSize.width > containerSize.height ? 52 : 62,
3830
+ size: dpadSize,
3831
+ x: 14,
3832
+ y: finalDpadY,
3499
3833
  containerWidth: containerSize.width || window.innerWidth,
3500
3834
  containerHeight: containerSize.height || window.innerHeight,
3501
3835
  controls,
3502
3836
  systemColor,
3503
- isLandscape
3837
+ isLandscape,
3838
+ customPosition: getPosition("up", isLandscape),
3839
+ onPositionChange: (x, y) => savePosition("up", x, y, isLandscape)
3504
3840
  }
3505
3841
  ),
3506
3842
  memoizedButtonElements.filter(({ buttonConfig }) => !DPAD_TYPES.includes(buttonConfig.type)).map(({ buttonConfig, adjustedConfig, customPosition, width, height }) => /* @__PURE__ */ jsx(
@@ -3516,64 +3852,77 @@ function VirtualController({
3516
3852
  customPosition,
3517
3853
  onPositionChange: (x, y) => savePosition(buttonConfig.type, x, y, isLandscape),
3518
3854
  isLandscape,
3519
- systemColor
3855
+ console: layout.console
3520
3856
  },
3521
3857
  buttonConfig.type
3522
- ))
3858
+ )),
3859
+ /* @__PURE__ */ jsx(ControlsHint, { isVisible: isRunning })
3523
3860
  ]
3524
3861
  }
3525
3862
  );
3526
3863
  }
3527
3864
  function FloatingExitButton({ onClick, disabled = false }) {
3528
- return /* @__PURE__ */ jsx(
3865
+ return /* @__PURE__ */ jsxs(
3529
3866
  "button",
3530
3867
  {
3531
3868
  onClick,
3532
3869
  disabled,
3533
3870
  className: `
3534
- fixed top-3 left-1/2 -translate-x-1/2 z-50
3535
- w-11 h-11 rounded-lg
3536
- bg-black/90 backdrop-blur-sm
3537
- border-2 border-white/80
3538
- shadow-lg
3539
- flex items-center justify-center
3540
- transition-all duration-200
3871
+ fixed top-3 right-3 z-50
3872
+ px-3 py-2 rounded-xl
3873
+ bg-black/80 backdrop-blur-md
3874
+ border-2 border-red-400/60
3875
+ shadow-xl
3876
+ flex items-center gap-2
3877
+ transition-all duration-300
3541
3878
  hover:bg-red-600/30 hover:border-red-400 hover:scale-105
3542
3879
  active:scale-95
3543
3880
  disabled:opacity-40 disabled:cursor-not-allowed
3544
3881
  touch-manipulation
3545
3882
  `,
3546
3883
  style: {
3547
- paddingTop: "env(safe-area-inset-top, 0px)"
3884
+ paddingTop: "max(env(safe-area-inset-top, 0px), 8px)"
3548
3885
  },
3549
3886
  "aria-label": "Exit fullscreen",
3550
- title: "Exit fullscreen",
3551
- children: /* @__PURE__ */ jsx(X, { size: 22, className: "text-white" })
3887
+ children: [
3888
+ /* @__PURE__ */ jsx(X, { size: 16, className: "text-red-400" }),
3889
+ /* @__PURE__ */ jsx("span", { className: "text-white text-xs font-bold uppercase tracking-wider", children: "Exit" }),
3890
+ /* @__PURE__ */ jsx(Minimize2, { size: 14, className: "text-white/60" })
3891
+ ]
3552
3892
  }
3553
3893
  );
3554
3894
  }
3555
3895
  function FloatingFullscreenButton({ onClick, disabled = false }) {
3556
- return /* @__PURE__ */ jsx(
3896
+ const [pulse, setPulse] = useState(true);
3897
+ useEffect(() => {
3898
+ const timer = setTimeout(() => setPulse(false), 5e3);
3899
+ return () => clearTimeout(timer);
3900
+ }, []);
3901
+ return /* @__PURE__ */ jsxs(
3557
3902
  "button",
3558
3903
  {
3559
3904
  onClick,
3560
3905
  disabled,
3561
3906
  className: `
3562
3907
  absolute top-3 left-3 z-50
3563
- w-9 h-9 rounded-lg
3564
- bg-black/70 backdrop-blur-sm
3565
- border-2 border-white/50
3566
- shadow-md
3567
- flex items-center justify-center
3568
- transition-all duration-200
3569
- hover:bg-white/20 hover:border-white
3908
+ px-3 py-2 rounded-xl
3909
+ bg-black/80 backdrop-blur-md
3910
+ border-2 border-white/60
3911
+ shadow-xl
3912
+ flex items-center gap-2
3913
+ transition-all duration-300
3914
+ hover:bg-white/20 hover:border-white hover:scale-105
3570
3915
  active:scale-95
3571
3916
  disabled:opacity-40 disabled:cursor-not-allowed
3572
3917
  touch-manipulation
3918
+ ${pulse ? "animate-pulse border-green-400/80" : ""}
3573
3919
  `,
3574
- "aria-label": "Enter fullscreen",
3575
- title: "Enter fullscreen",
3576
- children: /* @__PURE__ */ jsx(Maximize, { size: 16, className: "text-white/80" })
3920
+ "aria-label": "Tap for fullscreen controls",
3921
+ children: [
3922
+ /* @__PURE__ */ jsx(Gamepad2, { size: 18, className: "text-green-400" }),
3923
+ /* @__PURE__ */ jsx("span", { className: "text-white text-xs font-bold uppercase tracking-wider whitespace-nowrap", children: "Tap for Controls" }),
3924
+ /* @__PURE__ */ jsx(Maximize, { size: 14, className: "text-white/60" })
3925
+ ]
3577
3926
  }
3578
3927
  );
3579
3928
  }
@@ -7889,7 +8238,7 @@ function useGamePlayer(props) {
7889
8238
  recordingSupported
7890
8239
  };
7891
8240
  }
7892
- var STORAGE_KEY2 = "koin-player-settings";
8241
+ var STORAGE_KEY3 = "koin-player-settings";
7893
8242
  var DEFAULT_SETTINGS = {
7894
8243
  volume: 1,
7895
8244
  muted: false,
@@ -7902,7 +8251,7 @@ function usePlayerPersistence(onSettingsChange) {
7902
8251
  const [isLoaded, setIsLoaded] = useState(false);
7903
8252
  useEffect(() => {
7904
8253
  try {
7905
- const stored = localStorage.getItem(STORAGE_KEY2);
8254
+ const stored = localStorage.getItem(STORAGE_KEY3);
7906
8255
  if (stored) {
7907
8256
  const parsed = JSON.parse(stored);
7908
8257
  setSettings((prev) => ({ ...prev, ...parsed }));
@@ -7916,7 +8265,7 @@ function usePlayerPersistence(onSettingsChange) {
7916
8265
  setSettings((prev) => {
7917
8266
  const next = { ...prev, ...updates };
7918
8267
  try {
7919
- localStorage.setItem(STORAGE_KEY2, JSON.stringify(next));
8268
+ localStorage.setItem(STORAGE_KEY3, JSON.stringify(next));
7920
8269
  } catch (e) {
7921
8270
  console.error("Failed to save player settings", e);
7922
8271
  }
@@ -9128,6 +9477,6 @@ var ShortcutsReference = memo(function ShortcutsReference2({
9128
9477
  });
9129
9478
  var ShortcutsReference_default = ShortcutsReference;
9130
9479
 
9131
- export { ALL_BUTTONS, AchievementPopup, BUTTON_GROUPS, BUTTON_LABELS, CONSOLE_CAPABILITIES, CONSOLE_KEYBOARD_OVERRIDES, DEFAULT_CONTROLS, DEFAULT_GAMEPAD, DEFAULT_KEYBOARD, DPAD_BUTTONS, FACE_BUTTONS, GamePlayer_default as GamePlayer, KoinI18nProvider, PERFORMANCE_TIER_1_SYSTEMS, PERFORMANCE_TIER_2_SYSTEMS, RASidebar, RA_MEDIA_BASE, SHADER_PRESETS, SHOULDER_BUTTONS, STANDARD_AXIS_MAP, STANDARD_GAMEPAD_BUTTONS, STICK_BUTTONS, SUPPORTED_EXTENSIONS, SYSTEMS, SYSTEM_BUTTONS, ShaderSelector_default as ShaderSelector, ShortcutsReference_default as ShortcutsReference, TRIGGER_BUTTONS, ToastContainer, buildRetroArchConfig, clearAllControls, consoleHasButton, detectControllerBrand, detectSystem, en, es, fetchAndCacheRom, formatGamepadButton, formatKeyCode, fr, gamepadToRetroArchConfig, getAchievementBadgeUrl, getCachedRom, getConsoleButtons, getConsoleCapabilities, getConsoleKeyboardDefaults, getCore, getDBSystemNames, getFullGamepadMapping, getFullKeyboardMapping, getSupportedExtensions, getSystem, getSystemByDbName, getSystemByKey, getSystemFromExtension, getSystemsList, getUserAvatarUrl, isSystemSupported, keyboardToRetroArchConfig, loadAllGamepadMappings, loadGamepadMapping, loadKeyboardMapping, normalizeSystemKey, saveGamepadMapping, saveKeyboardMapping, systemsMatch, useGameRecording, useGamepad, useKoinTranslation, useNostalgist, useToast };
9480
+ export { ALL_BUTTONS, AchievementPopup, BUTTON_GROUPS, BUTTON_LABELS, CONSOLE_CAPABILITIES, CONSOLE_KEYBOARD_OVERRIDES, DEFAULT_CONTROLS, DEFAULT_GAMEPAD, DEFAULT_KEYBOARD, DPAD_BUTTONS2 as DPAD_BUTTONS, FACE_BUTTONS, GamePlayer_default as GamePlayer, KoinI18nProvider, PERFORMANCE_TIER_1_SYSTEMS, PERFORMANCE_TIER_2_SYSTEMS, RASidebar, RA_MEDIA_BASE, SHADER_PRESETS, SHOULDER_BUTTONS, STANDARD_AXIS_MAP, STANDARD_GAMEPAD_BUTTONS, STICK_BUTTONS, SUPPORTED_EXTENSIONS, SYSTEMS, SYSTEM_BUTTONS, ShaderSelector_default as ShaderSelector, ShortcutsReference_default as ShortcutsReference, TRIGGER_BUTTONS, ToastContainer, buildRetroArchConfig, clearAllControls, consoleHasButton, detectControllerBrand, detectSystem, en, es, fetchAndCacheRom, formatGamepadButton, formatKeyCode, fr, gamepadToRetroArchConfig, getAchievementBadgeUrl, getCachedRom, getConsoleButtons, getConsoleCapabilities, getConsoleKeyboardDefaults, getCore, getDBSystemNames, getFullGamepadMapping, getFullKeyboardMapping, getSupportedExtensions, getSystem, getSystemByDbName, getSystemByKey, getSystemFromExtension, getSystemsList, getUserAvatarUrl, isSystemSupported, keyboardToRetroArchConfig, loadAllGamepadMappings, loadGamepadMapping, loadKeyboardMapping, normalizeSystemKey, saveGamepadMapping, saveKeyboardMapping, systemsMatch, useGameRecording, useGamepad, useKoinTranslation, useNostalgist, useToast };
9132
9481
  //# sourceMappingURL=index.mjs.map
9133
9482
  //# sourceMappingURL=index.mjs.map