koin.js 1.0.13 → 1.0.14

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
@@ -2387,26 +2387,49 @@ function getLayoutForSystem(system) {
2387
2387
  if (s.includes("ATARI")) return TWO_BUTTON_LAYOUT;
2388
2388
  return TWO_BUTTON_LAYOUT;
2389
2389
  }
2390
- var DRAG_HOLD_DELAY = 350;
2390
+
2391
+ // src/components/VirtualController/utils/dragConstraints.ts
2392
+ function constrainToViewport({
2393
+ newXPercent,
2394
+ newYPercent,
2395
+ elementSize,
2396
+ containerWidth,
2397
+ containerHeight
2398
+ }) {
2399
+ const xMargin = elementSize / 2 / containerWidth * 100;
2400
+ const yMargin = elementSize / 2 / containerHeight * 100;
2401
+ return {
2402
+ x: Math.max(xMargin, Math.min(100 - xMargin, newXPercent)),
2403
+ y: Math.max(yMargin, Math.min(100 - yMargin, newYPercent))
2404
+ };
2405
+ }
2406
+
2407
+ // src/components/VirtualController/hooks/useDrag.ts
2408
+ var DEFAULT_HOLD_DELAY = 350;
2409
+ var DEFAULT_CENTER_THRESHOLD = 0.4;
2391
2410
  var DRAG_MOVE_THRESHOLD = 10;
2392
- var DRAG_CENTER_THRESHOLD = 0.4;
2393
- function useTouchHandlers({
2394
- buttonType,
2395
- isSystemButton,
2396
- buttonSize,
2411
+ function useDrag({
2412
+ elementSize,
2397
2413
  displayX,
2398
2414
  displayY,
2399
2415
  containerWidth,
2400
2416
  containerHeight,
2401
- onPress,
2402
- onPressDown,
2403
- onRelease,
2404
- onPositionChange
2417
+ onPositionChange,
2418
+ holdDelay = DEFAULT_HOLD_DELAY,
2419
+ centerThreshold = DEFAULT_CENTER_THRESHOLD,
2420
+ onDragStart,
2421
+ onDragEnd
2405
2422
  }) {
2423
+ const [isDragging, setIsDragging] = useState(false);
2406
2424
  const isDraggingRef = useRef(false);
2407
- const dragStartRef = useRef({ x: 0, y: 0 });
2408
2425
  const dragTimerRef = useRef(null);
2409
2426
  const touchStartPosRef = useRef({ x: 0, y: 0 });
2427
+ const dragStartRef = useRef({
2428
+ elementX: 0,
2429
+ elementY: 0,
2430
+ touchX: 0,
2431
+ touchY: 0
2432
+ });
2410
2433
  const clearDragTimer = useCallback(() => {
2411
2434
  if (dragTimerRef.current) {
2412
2435
  clearTimeout(dragTimerRef.current);
@@ -2416,23 +2439,129 @@ function useTouchHandlers({
2416
2439
  const startDragging = useCallback(
2417
2440
  (touchX, touchY) => {
2418
2441
  isDraggingRef.current = true;
2442
+ setIsDragging(true);
2419
2443
  dragStartRef.current = {
2420
- x: touchX - displayX / 100 * containerWidth,
2421
- y: touchY - displayY / 100 * containerHeight
2444
+ elementX: displayX,
2445
+ elementY: displayY,
2446
+ touchX,
2447
+ touchY
2422
2448
  };
2423
2449
  if (navigator.vibrate) {
2424
2450
  navigator.vibrate([10, 30, 10]);
2425
2451
  }
2452
+ onDragStart?.();
2453
+ },
2454
+ [displayX, displayY, onDragStart]
2455
+ );
2456
+ const checkDragStart = useCallback(
2457
+ (touchX, touchY, elementRect) => {
2458
+ if (!onPositionChange) return false;
2459
+ touchStartPosRef.current = { x: touchX, y: touchY };
2460
+ const centerX = elementRect.left + elementRect.width / 2;
2461
+ const centerY = elementRect.top + elementRect.height / 2;
2462
+ const distFromCenter = Math.sqrt(
2463
+ Math.pow(touchX - centerX, 2) + Math.pow(touchY - centerY, 2)
2464
+ );
2465
+ const centerRadius = elementSize * centerThreshold;
2466
+ if (distFromCenter < centerRadius) {
2467
+ dragTimerRef.current = setTimeout(() => {
2468
+ if (!isDraggingRef.current) {
2469
+ startDragging(touchX, touchY);
2470
+ }
2471
+ }, holdDelay);
2472
+ return true;
2473
+ }
2474
+ return false;
2475
+ },
2476
+ [onPositionChange, elementSize, centerThreshold, holdDelay, startDragging]
2477
+ );
2478
+ const checkMoveThreshold = useCallback(
2479
+ (touchX, touchY) => {
2480
+ if (!onPositionChange || isDraggingRef.current) return false;
2481
+ const moveDistance = Math.sqrt(
2482
+ Math.pow(touchX - touchStartPosRef.current.x, 2) + Math.pow(touchY - touchStartPosRef.current.y, 2)
2483
+ );
2484
+ if (moveDistance > DRAG_MOVE_THRESHOLD) {
2485
+ clearDragTimer();
2486
+ startDragging(touchX, touchY);
2487
+ return true;
2488
+ }
2489
+ return false;
2490
+ },
2491
+ [onPositionChange, clearDragTimer, startDragging]
2492
+ );
2493
+ const handleDragMove = useCallback(
2494
+ (touchX, touchY) => {
2495
+ if (!isDraggingRef.current || !onPositionChange) return;
2496
+ const deltaX = touchX - dragStartRef.current.touchX;
2497
+ const deltaY = touchY - dragStartRef.current.touchY;
2498
+ const newXPercent = dragStartRef.current.elementX + deltaX / containerWidth * 100;
2499
+ const newYPercent = dragStartRef.current.elementY + deltaY / containerHeight * 100;
2500
+ const constrained = constrainToViewport({
2501
+ newXPercent,
2502
+ newYPercent,
2503
+ elementSize,
2504
+ containerWidth,
2505
+ containerHeight
2506
+ });
2507
+ onPositionChange(constrained.x, constrained.y);
2508
+ },
2509
+ [onPositionChange, containerWidth, containerHeight, elementSize]
2510
+ );
2511
+ const handleDragEnd = useCallback(() => {
2512
+ clearDragTimer();
2513
+ if (isDraggingRef.current) {
2514
+ isDraggingRef.current = false;
2515
+ setIsDragging(false);
2516
+ onDragEnd?.();
2517
+ }
2518
+ }, [clearDragTimer, onDragEnd]);
2519
+ return {
2520
+ isDragging,
2521
+ checkDragStart,
2522
+ handleDragMove,
2523
+ handleDragEnd,
2524
+ clearDragTimer,
2525
+ checkMoveThreshold
2526
+ };
2527
+ }
2528
+
2529
+ // src/components/VirtualController/hooks/useTouchHandlers.ts
2530
+ function useTouchHandlers({
2531
+ buttonType,
2532
+ isSystemButton,
2533
+ buttonSize,
2534
+ displayX,
2535
+ displayY,
2536
+ containerWidth,
2537
+ containerHeight,
2538
+ onPress,
2539
+ onPressDown,
2540
+ onRelease,
2541
+ onPositionChange
2542
+ }) {
2543
+ const isDraggingRef = useRef(false);
2544
+ const drag = useDrag({
2545
+ elementSize: buttonSize,
2546
+ displayX,
2547
+ displayY,
2548
+ containerWidth,
2549
+ containerHeight,
2550
+ onPositionChange,
2551
+ centerThreshold: 0.4,
2552
+ onDragStart: () => {
2553
+ isDraggingRef.current = true;
2426
2554
  if (!isSystemButton) {
2427
2555
  onRelease(buttonType);
2428
2556
  }
2429
2557
  },
2430
- [displayX, displayY, containerWidth, containerHeight, isSystemButton, buttonType, onRelease]
2431
- );
2558
+ onDragEnd: () => {
2559
+ isDraggingRef.current = false;
2560
+ }
2561
+ });
2432
2562
  const handleTouchStart = useCallback(
2433
2563
  (e) => {
2434
2564
  const touch = e.touches[0];
2435
- touchStartPosRef.current = { x: touch.clientX, y: touch.clientY };
2436
2565
  e.preventDefault();
2437
2566
  e.stopPropagation();
2438
2567
  if (navigator.vibrate) {
@@ -2447,57 +2576,32 @@ function useTouchHandlers({
2447
2576
  const target = e.currentTarget;
2448
2577
  if (!target) return;
2449
2578
  const rect = target.getBoundingClientRect();
2450
- const centerX = rect.left + rect.width / 2;
2451
- const centerY = rect.top + rect.height / 2;
2452
- const distance = Math.sqrt(
2453
- Math.pow(touch.clientX - centerX, 2) + Math.pow(touch.clientY - centerY, 2)
2454
- );
2455
- const dragThreshold = buttonSize * DRAG_CENTER_THRESHOLD;
2456
- if (distance < dragThreshold) {
2457
- dragTimerRef.current = setTimeout(() => {
2458
- if (!isDraggingRef.current) {
2459
- startDragging(touch.clientX, touch.clientY);
2460
- }
2461
- }, DRAG_HOLD_DELAY);
2462
- }
2579
+ drag.checkDragStart(touch.clientX, touch.clientY, rect);
2463
2580
  }
2464
2581
  },
2465
- [isSystemButton, buttonType, onPress, onPressDown, onPositionChange, buttonSize, startDragging]
2582
+ [isSystemButton, buttonType, onPress, onPressDown, onPositionChange, drag]
2466
2583
  );
2467
2584
  const handleTouchMove = useCallback(
2468
2585
  (e) => {
2469
2586
  const touch = e.touches[0];
2470
2587
  if (onPositionChange && !isDraggingRef.current) {
2471
- const moveDistance = Math.sqrt(
2472
- Math.pow(touch.clientX - touchStartPosRef.current.x, 2) + Math.pow(touch.clientY - touchStartPosRef.current.y, 2)
2473
- );
2474
- if (moveDistance > DRAG_MOVE_THRESHOLD) {
2475
- clearDragTimer();
2476
- startDragging(touch.clientX, touch.clientY);
2477
- }
2588
+ drag.checkMoveThreshold(touch.clientX, touch.clientY);
2478
2589
  }
2479
- if (isDraggingRef.current && onPositionChange) {
2590
+ if (isDraggingRef.current) {
2480
2591
  e.preventDefault();
2481
2592
  e.stopPropagation();
2482
- const newX = touch.clientX - dragStartRef.current.x;
2483
- const newY = touch.clientY - dragStartRef.current.y;
2484
- const newXPercent = newX / containerWidth * 100;
2485
- const newYPercent = newY / containerHeight * 100;
2486
- const margin = buttonSize / 2 / Math.min(containerWidth, containerHeight) * 100;
2487
- const constrainedX = Math.max(margin, Math.min(100 - margin, newXPercent));
2488
- const constrainedY = Math.max(margin, Math.min(100 - margin, newYPercent));
2489
- onPositionChange(constrainedX, constrainedY);
2593
+ drag.handleDragMove(touch.clientX, touch.clientY);
2490
2594
  }
2491
2595
  },
2492
- [onPositionChange, clearDragTimer, startDragging, containerWidth, containerHeight, buttonSize]
2596
+ [onPositionChange, drag]
2493
2597
  );
2494
2598
  const handleTouchEnd = useCallback(
2495
2599
  (e) => {
2496
- clearDragTimer();
2600
+ drag.clearDragTimer();
2497
2601
  if (isDraggingRef.current) {
2498
2602
  e.preventDefault();
2499
2603
  e.stopPropagation();
2500
- isDraggingRef.current = false;
2604
+ drag.handleDragEnd();
2501
2605
  return;
2502
2606
  }
2503
2607
  e.preventDefault();
@@ -2506,15 +2610,15 @@ function useTouchHandlers({
2506
2610
  onRelease(buttonType);
2507
2611
  }
2508
2612
  },
2509
- [clearDragTimer, isSystemButton, buttonType, onRelease]
2613
+ [drag, isSystemButton, buttonType, onRelease]
2510
2614
  );
2511
2615
  const handleTouchCancel = useCallback(
2512
2616
  (e) => {
2513
- clearDragTimer();
2617
+ drag.clearDragTimer();
2514
2618
  if (isDraggingRef.current) {
2515
2619
  e.preventDefault();
2516
2620
  e.stopPropagation();
2517
- isDraggingRef.current = false;
2621
+ drag.handleDragEnd();
2518
2622
  return;
2519
2623
  }
2520
2624
  e.preventDefault();
@@ -2523,11 +2627,11 @@ function useTouchHandlers({
2523
2627
  onRelease(buttonType);
2524
2628
  }
2525
2629
  },
2526
- [clearDragTimer, isSystemButton, buttonType, onRelease]
2630
+ [drag, isSystemButton, buttonType, onRelease]
2527
2631
  );
2528
2632
  const cleanup = useCallback(() => {
2529
- clearDragTimer();
2530
- }, [clearDragTimer]);
2633
+ drag.clearDragTimer();
2634
+ }, [drag]);
2531
2635
  return {
2532
2636
  handleTouchStart,
2533
2637
  handleTouchMove,
@@ -3383,7 +3487,6 @@ function dispatchKeyboardEvent(type, code) {
3383
3487
  canvas.dispatchEvent(event);
3384
3488
  return true;
3385
3489
  }
3386
- var DRAG_HOLD_DELAY2 = 350;
3387
3490
  var CENTER_TOUCH_RADIUS = 0.25;
3388
3491
  var Dpad = React2.memo(function Dpad2({
3389
3492
  size = 180,
@@ -3400,10 +3503,6 @@ var Dpad = React2.memo(function Dpad2({
3400
3503
  const dpadRef = useRef(null);
3401
3504
  const activeTouchRef = useRef(null);
3402
3505
  const activeDirectionsRef = useRef(/* @__PURE__ */ new Set());
3403
- const [isDragging, setIsDragging] = useState(false);
3404
- const dragTimerRef = useRef(null);
3405
- const dragStartRef = useRef({ x: 0, y: 0, touchX: 0, touchY: 0 });
3406
- const touchStartPosRef = useRef({ x: 0, y: 0, time: 0 });
3407
3506
  const upPathRef = useRef(null);
3408
3507
  const downPathRef = useRef(null);
3409
3508
  const leftPathRef = useRef(null);
@@ -3411,6 +3510,13 @@ var Dpad = React2.memo(function Dpad2({
3411
3510
  const centerCircleRef = useRef(null);
3412
3511
  const displayX = customPosition ? customPosition.x : x;
3413
3512
  const displayY = customPosition ? customPosition.y : y;
3513
+ const releaseAllDirections = useCallback((getKeyCode2) => {
3514
+ activeDirectionsRef.current.forEach((dir) => {
3515
+ const keyCode = getKeyCode2(dir);
3516
+ if (keyCode) dispatchKeyboardEvent("keyup", keyCode);
3517
+ });
3518
+ activeDirectionsRef.current = /* @__PURE__ */ new Set();
3519
+ }, []);
3414
3520
  const getKeyCode = useCallback((direction) => {
3415
3521
  if (!controls) {
3416
3522
  const defaults = {
@@ -3423,6 +3529,19 @@ var Dpad = React2.memo(function Dpad2({
3423
3529
  }
3424
3530
  return controls[direction] || "";
3425
3531
  }, [controls]);
3532
+ const drag = useDrag({
3533
+ elementSize: size,
3534
+ displayX,
3535
+ displayY,
3536
+ containerWidth,
3537
+ containerHeight,
3538
+ onPositionChange,
3539
+ centerThreshold: CENTER_TOUCH_RADIUS,
3540
+ onDragStart: () => {
3541
+ releaseAllDirections(getKeyCode);
3542
+ updateVisuals(/* @__PURE__ */ new Set());
3543
+ }
3544
+ });
3426
3545
  const getDirectionsFromTouch = useCallback((touchX, touchY, rect) => {
3427
3546
  const centerX = rect.left + rect.width / 2;
3428
3547
  const centerY = rect.top + rect.height / 2;
@@ -3484,51 +3603,20 @@ var Dpad = React2.memo(function Dpad2({
3484
3603
  activeDirectionsRef.current = newDirections;
3485
3604
  updateVisuals(newDirections);
3486
3605
  }, [getKeyCode, updateVisuals]);
3487
- const clearDragTimer = useCallback(() => {
3488
- if (dragTimerRef.current) {
3489
- clearTimeout(dragTimerRef.current);
3490
- dragTimerRef.current = null;
3491
- }
3492
- }, []);
3493
- const startDragging = useCallback((touchX, touchY) => {
3494
- setIsDragging(true);
3495
- dragStartRef.current = {
3496
- x: displayX,
3497
- y: displayY,
3498
- touchX,
3499
- touchY
3500
- };
3501
- activeDirectionsRef.current.forEach((dir) => {
3502
- const keyCode = getKeyCode(dir);
3503
- if (keyCode) dispatchKeyboardEvent("keyup", keyCode);
3504
- });
3505
- activeDirectionsRef.current = /* @__PURE__ */ new Set();
3506
- updateVisuals(/* @__PURE__ */ new Set());
3507
- if (navigator.vibrate) navigator.vibrate([10, 30, 10]);
3508
- }, [displayX, displayY, getKeyCode, updateVisuals]);
3509
3606
  const handleTouchStart = useCallback((e) => {
3510
3607
  e.preventDefault();
3511
3608
  if (activeTouchRef.current !== null) return;
3512
3609
  const touch = e.changedTouches[0];
3513
3610
  activeTouchRef.current = touch.identifier;
3514
- touchStartPosRef.current = { x: touch.clientX, y: touch.clientY, time: Date.now() };
3515
3611
  const rect = dpadRef.current?.getBoundingClientRect();
3516
3612
  if (!rect) return;
3517
- const centerX = rect.left + rect.width / 2;
3518
- const centerY = rect.top + rect.height / 2;
3519
- const distFromCenter = Math.sqrt(
3520
- Math.pow(touch.clientX - centerX, 2) + Math.pow(touch.clientY - centerY, 2)
3521
- );
3522
- const centerRadius = size * CENTER_TOUCH_RADIUS;
3523
- if (distFromCenter < centerRadius && onPositionChange) {
3524
- dragTimerRef.current = setTimeout(() => {
3525
- startDragging(touch.clientX, touch.clientY);
3526
- }, DRAG_HOLD_DELAY2);
3613
+ if (onPositionChange) {
3614
+ drag.checkDragStart(touch.clientX, touch.clientY, rect);
3527
3615
  }
3528
- if (!isDragging) {
3616
+ if (!drag.isDragging) {
3529
3617
  updateDirections(getDirectionsFromTouch(touch.clientX, touch.clientY, rect));
3530
3618
  }
3531
- }, [getDirectionsFromTouch, updateDirections, isDragging, size, onPositionChange, startDragging]);
3619
+ }, [getDirectionsFromTouch, updateDirections, onPositionChange, drag]);
3532
3620
  const handleTouchMove = useCallback((e) => {
3533
3621
  e.preventDefault();
3534
3622
  let touch = null;
@@ -3539,31 +3627,19 @@ var Dpad = React2.memo(function Dpad2({
3539
3627
  }
3540
3628
  }
3541
3629
  if (!touch) return;
3542
- if (isDragging && onPositionChange) {
3543
- const deltaX = touch.clientX - dragStartRef.current.touchX;
3544
- const deltaY = touch.clientY - dragStartRef.current.touchY;
3545
- const newXPercent = dragStartRef.current.x + deltaX / containerWidth * 100;
3546
- const newYPercent = dragStartRef.current.y + deltaY / containerHeight * 100;
3547
- const margin = size / 2 / Math.min(containerWidth, containerHeight) * 100;
3548
- const constrainedX = Math.max(margin, Math.min(100 - margin, newXPercent));
3549
- const constrainedY = Math.max(margin, Math.min(100 - margin, newYPercent));
3550
- onPositionChange(constrainedX, constrainedY);
3630
+ if (drag.isDragging) {
3631
+ drag.handleDragMove(touch.clientX, touch.clientY);
3551
3632
  } else {
3552
- const moveDistance = Math.sqrt(
3553
- Math.pow(touch.clientX - touchStartPosRef.current.x, 2) + Math.pow(touch.clientY - touchStartPosRef.current.y, 2)
3554
- );
3555
- if (moveDistance > 15) {
3556
- clearDragTimer();
3557
- }
3558
3633
  const rect = dpadRef.current?.getBoundingClientRect();
3559
3634
  if (rect) {
3635
+ drag.clearDragTimer();
3560
3636
  updateDirections(getDirectionsFromTouch(touch.clientX, touch.clientY, rect));
3561
3637
  }
3562
3638
  }
3563
- }, [isDragging, onPositionChange, containerWidth, containerHeight, size, getDirectionsFromTouch, updateDirections, clearDragTimer]);
3639
+ }, [drag, getDirectionsFromTouch, updateDirections]);
3564
3640
  const handleTouchEnd = useCallback((e) => {
3565
3641
  e.preventDefault();
3566
- clearDragTimer();
3642
+ drag.clearDragTimer();
3567
3643
  let touchEnded = false;
3568
3644
  for (let i = 0; i < e.changedTouches.length; i++) {
3569
3645
  if (e.changedTouches[i].identifier === activeTouchRef.current) {
@@ -3573,8 +3649,8 @@ var Dpad = React2.memo(function Dpad2({
3573
3649
  }
3574
3650
  if (touchEnded) {
3575
3651
  activeTouchRef.current = null;
3576
- if (isDragging) {
3577
- setIsDragging(false);
3652
+ if (drag.isDragging) {
3653
+ drag.handleDragEnd();
3578
3654
  } else {
3579
3655
  activeDirectionsRef.current.forEach((dir) => {
3580
3656
  const keyCode = getKeyCode(dir);
@@ -3584,7 +3660,7 @@ var Dpad = React2.memo(function Dpad2({
3584
3660
  updateVisuals(/* @__PURE__ */ new Set());
3585
3661
  }
3586
3662
  }
3587
- }, [getKeyCode, updateVisuals, isDragging, clearDragTimer]);
3663
+ }, [getKeyCode, updateVisuals, drag]);
3588
3664
  useEffect(() => {
3589
3665
  const dpad = dpadRef.current;
3590
3666
  if (!dpad) return;
@@ -3597,9 +3673,9 @@ var Dpad = React2.memo(function Dpad2({
3597
3673
  dpad.removeEventListener("touchmove", handleTouchMove);
3598
3674
  dpad.removeEventListener("touchend", handleTouchEnd);
3599
3675
  dpad.removeEventListener("touchcancel", handleTouchEnd);
3600
- clearDragTimer();
3676
+ drag.clearDragTimer();
3601
3677
  };
3602
- }, [handleTouchStart, handleTouchMove, handleTouchEnd, clearDragTimer]);
3678
+ }, [handleTouchStart, handleTouchMove, handleTouchEnd, drag]);
3603
3679
  const leftPx = displayX / 100 * containerWidth - size / 2;
3604
3680
  const topPx = displayY / 100 * containerHeight - size / 2;
3605
3681
  const dUp = "M 35,5 L 65,5 L 65,35 L 50,50 L 35,35 Z";
@@ -3610,21 +3686,21 @@ var Dpad = React2.memo(function Dpad2({
3610
3686
  "div",
3611
3687
  {
3612
3688
  ref: dpadRef,
3613
- className: `absolute pointer-events-auto touch-manipulation select-none ${isDragging ? "opacity-60" : ""}`,
3689
+ className: `absolute pointer-events-auto touch-manipulation select-none ${drag.isDragging ? "opacity-60" : ""}`,
3614
3690
  style: {
3615
3691
  top: 0,
3616
3692
  left: 0,
3617
- transform: `translate3d(${leftPx}px, ${topPx}px, 0)${isDragging ? " scale(1.05)" : ""}`,
3693
+ transform: `translate3d(${leftPx}px, ${topPx}px, 0)${drag.isDragging ? " scale(1.05)" : ""}`,
3618
3694
  width: size,
3619
3695
  height: size,
3620
3696
  opacity: isLandscape ? 0.75 : 0.9,
3621
3697
  WebkitTouchCallout: "none",
3622
3698
  WebkitUserSelect: "none",
3623
3699
  touchAction: "none",
3624
- transition: isDragging ? "none" : "transform 0.1s ease-out"
3700
+ transition: drag.isDragging ? "none" : "transform 0.1s ease-out"
3625
3701
  },
3626
3702
  children: [
3627
- /* @__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"}` }),
3703
+ /* @__PURE__ */ jsx("div", { className: `absolute inset-0 rounded-full bg-black/40 backdrop-blur-md border shadow-lg ${drag.isDragging ? "border-white/50 ring-2 ring-white/30" : "border-white/10"}` }),
3628
3704
  /* @__PURE__ */ jsxs("svg", { width: "100%", height: "100%", viewBox: "0 0 100 100", className: "drop-shadow-xl relative z-10", children: [
3629
3705
  /* @__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" }),
3630
3706
  /* @__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" }),
@@ -3637,9 +3713,9 @@ var Dpad = React2.memo(function Dpad2({
3637
3713
  cx: "50",
3638
3714
  cy: "50",
3639
3715
  r: "12",
3640
- fill: isDragging ? systemColor : "rgba(0,0,0,0.5)",
3641
- stroke: isDragging ? "#fff" : "rgba(255,255,255,0.3)",
3642
- strokeWidth: isDragging ? 2 : 1
3716
+ fill: drag.isDragging ? systemColor : "rgba(0,0,0,0.5)",
3717
+ stroke: drag.isDragging ? "#fff" : "rgba(255,255,255,0.3)",
3718
+ strokeWidth: drag.isDragging ? 2 : 1
3643
3719
  }
3644
3720
  ),
3645
3721
  /* @__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" }),
@@ -4067,6 +4143,45 @@ function FloatingFullscreenButton({ onClick, disabled = false }) {
4067
4143
  }
4068
4144
  );
4069
4145
  }
4146
+ function FloatingPauseButton({
4147
+ isPaused,
4148
+ onClick,
4149
+ disabled = false,
4150
+ systemColor = "#00FF41"
4151
+ }) {
4152
+ return /* @__PURE__ */ jsx(
4153
+ "button",
4154
+ {
4155
+ onClick,
4156
+ disabled,
4157
+ className: `
4158
+ fixed top-3 left-3 z-50
4159
+ px-3 py-2 rounded-xl
4160
+ bg-black/80 backdrop-blur-md
4161
+ border-2
4162
+ shadow-xl
4163
+ flex items-center gap-2
4164
+ transition-all duration-300
4165
+ hover:scale-105
4166
+ active:scale-95
4167
+ disabled:opacity-40 disabled:cursor-not-allowed
4168
+ touch-manipulation
4169
+ `,
4170
+ style: {
4171
+ paddingTop: "max(env(safe-area-inset-top, 0px), 8px)",
4172
+ borderColor: isPaused ? systemColor : "rgba(255,255,255,0.3)"
4173
+ },
4174
+ "aria-label": isPaused ? "Resume game" : "Pause game",
4175
+ children: isPaused ? /* @__PURE__ */ jsxs(Fragment, { children: [
4176
+ /* @__PURE__ */ jsx(Play, { size: 16, style: { color: systemColor }, fill: systemColor }),
4177
+ /* @__PURE__ */ jsx("span", { className: "text-white text-xs font-bold uppercase tracking-wider", children: "Play" })
4178
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
4179
+ /* @__PURE__ */ jsx(Pause, { size: 16, className: "text-white/80" }),
4180
+ /* @__PURE__ */ jsx("span", { className: "text-white text-xs font-bold uppercase tracking-wider", children: "Pause" })
4181
+ ] })
4182
+ }
4183
+ );
4184
+ }
4070
4185
  function LoadingSpinner({ color, size = "lg" }) {
4071
4186
  const sizeClass = size === "lg" ? "w-12 h-12" : "w-8 h-8";
4072
4187
  return /* @__PURE__ */ jsx(Loader2, { className: `${sizeClass} animate-spin`, style: { color } });
@@ -9200,6 +9315,14 @@ var GamePlayerInner = memo(function GamePlayerInner2(props) {
9200
9315
  disabled: status === "loading" || status === "error"
9201
9316
  }
9202
9317
  ),
9318
+ isFullscreen2 && isMobile && (status === "running" || status === "paused") && /* @__PURE__ */ jsx(
9319
+ FloatingPauseButton,
9320
+ {
9321
+ isPaused,
9322
+ onClick: handlePauseToggle,
9323
+ systemColor
9324
+ }
9325
+ ),
9203
9326
  /* @__PURE__ */ jsxs("div", { className: "absolute top-2 right-2 z-40 flex flex-col items-end gap-2 pointer-events-auto", children: [
9204
9327
  /* @__PURE__ */ jsx(
9205
9328
  RecordingIndicator_default,