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.js CHANGED
@@ -2393,26 +2393,49 @@ function getLayoutForSystem(system) {
2393
2393
  if (s.includes("ATARI")) return TWO_BUTTON_LAYOUT;
2394
2394
  return TWO_BUTTON_LAYOUT;
2395
2395
  }
2396
- var DRAG_HOLD_DELAY = 350;
2396
+
2397
+ // src/components/VirtualController/utils/dragConstraints.ts
2398
+ function constrainToViewport({
2399
+ newXPercent,
2400
+ newYPercent,
2401
+ elementSize,
2402
+ containerWidth,
2403
+ containerHeight
2404
+ }) {
2405
+ const xMargin = elementSize / 2 / containerWidth * 100;
2406
+ const yMargin = elementSize / 2 / containerHeight * 100;
2407
+ return {
2408
+ x: Math.max(xMargin, Math.min(100 - xMargin, newXPercent)),
2409
+ y: Math.max(yMargin, Math.min(100 - yMargin, newYPercent))
2410
+ };
2411
+ }
2412
+
2413
+ // src/components/VirtualController/hooks/useDrag.ts
2414
+ var DEFAULT_HOLD_DELAY = 350;
2415
+ var DEFAULT_CENTER_THRESHOLD = 0.4;
2397
2416
  var DRAG_MOVE_THRESHOLD = 10;
2398
- var DRAG_CENTER_THRESHOLD = 0.4;
2399
- function useTouchHandlers({
2400
- buttonType,
2401
- isSystemButton,
2402
- buttonSize,
2417
+ function useDrag({
2418
+ elementSize,
2403
2419
  displayX,
2404
2420
  displayY,
2405
2421
  containerWidth,
2406
2422
  containerHeight,
2407
- onPress,
2408
- onPressDown,
2409
- onRelease,
2410
- onPositionChange
2423
+ onPositionChange,
2424
+ holdDelay = DEFAULT_HOLD_DELAY,
2425
+ centerThreshold = DEFAULT_CENTER_THRESHOLD,
2426
+ onDragStart,
2427
+ onDragEnd
2411
2428
  }) {
2429
+ const [isDragging, setIsDragging] = React2.useState(false);
2412
2430
  const isDraggingRef = React2.useRef(false);
2413
- const dragStartRef = React2.useRef({ x: 0, y: 0 });
2414
2431
  const dragTimerRef = React2.useRef(null);
2415
2432
  const touchStartPosRef = React2.useRef({ x: 0, y: 0 });
2433
+ const dragStartRef = React2.useRef({
2434
+ elementX: 0,
2435
+ elementY: 0,
2436
+ touchX: 0,
2437
+ touchY: 0
2438
+ });
2416
2439
  const clearDragTimer = React2.useCallback(() => {
2417
2440
  if (dragTimerRef.current) {
2418
2441
  clearTimeout(dragTimerRef.current);
@@ -2422,23 +2445,129 @@ function useTouchHandlers({
2422
2445
  const startDragging = React2.useCallback(
2423
2446
  (touchX, touchY) => {
2424
2447
  isDraggingRef.current = true;
2448
+ setIsDragging(true);
2425
2449
  dragStartRef.current = {
2426
- x: touchX - displayX / 100 * containerWidth,
2427
- y: touchY - displayY / 100 * containerHeight
2450
+ elementX: displayX,
2451
+ elementY: displayY,
2452
+ touchX,
2453
+ touchY
2428
2454
  };
2429
2455
  if (navigator.vibrate) {
2430
2456
  navigator.vibrate([10, 30, 10]);
2431
2457
  }
2458
+ onDragStart?.();
2459
+ },
2460
+ [displayX, displayY, onDragStart]
2461
+ );
2462
+ const checkDragStart = React2.useCallback(
2463
+ (touchX, touchY, elementRect) => {
2464
+ if (!onPositionChange) return false;
2465
+ touchStartPosRef.current = { x: touchX, y: touchY };
2466
+ const centerX = elementRect.left + elementRect.width / 2;
2467
+ const centerY = elementRect.top + elementRect.height / 2;
2468
+ const distFromCenter = Math.sqrt(
2469
+ Math.pow(touchX - centerX, 2) + Math.pow(touchY - centerY, 2)
2470
+ );
2471
+ const centerRadius = elementSize * centerThreshold;
2472
+ if (distFromCenter < centerRadius) {
2473
+ dragTimerRef.current = setTimeout(() => {
2474
+ if (!isDraggingRef.current) {
2475
+ startDragging(touchX, touchY);
2476
+ }
2477
+ }, holdDelay);
2478
+ return true;
2479
+ }
2480
+ return false;
2481
+ },
2482
+ [onPositionChange, elementSize, centerThreshold, holdDelay, startDragging]
2483
+ );
2484
+ const checkMoveThreshold = React2.useCallback(
2485
+ (touchX, touchY) => {
2486
+ if (!onPositionChange || isDraggingRef.current) return false;
2487
+ const moveDistance = Math.sqrt(
2488
+ Math.pow(touchX - touchStartPosRef.current.x, 2) + Math.pow(touchY - touchStartPosRef.current.y, 2)
2489
+ );
2490
+ if (moveDistance > DRAG_MOVE_THRESHOLD) {
2491
+ clearDragTimer();
2492
+ startDragging(touchX, touchY);
2493
+ return true;
2494
+ }
2495
+ return false;
2496
+ },
2497
+ [onPositionChange, clearDragTimer, startDragging]
2498
+ );
2499
+ const handleDragMove = React2.useCallback(
2500
+ (touchX, touchY) => {
2501
+ if (!isDraggingRef.current || !onPositionChange) return;
2502
+ const deltaX = touchX - dragStartRef.current.touchX;
2503
+ const deltaY = touchY - dragStartRef.current.touchY;
2504
+ const newXPercent = dragStartRef.current.elementX + deltaX / containerWidth * 100;
2505
+ const newYPercent = dragStartRef.current.elementY + deltaY / containerHeight * 100;
2506
+ const constrained = constrainToViewport({
2507
+ newXPercent,
2508
+ newYPercent,
2509
+ elementSize,
2510
+ containerWidth,
2511
+ containerHeight
2512
+ });
2513
+ onPositionChange(constrained.x, constrained.y);
2514
+ },
2515
+ [onPositionChange, containerWidth, containerHeight, elementSize]
2516
+ );
2517
+ const handleDragEnd = React2.useCallback(() => {
2518
+ clearDragTimer();
2519
+ if (isDraggingRef.current) {
2520
+ isDraggingRef.current = false;
2521
+ setIsDragging(false);
2522
+ onDragEnd?.();
2523
+ }
2524
+ }, [clearDragTimer, onDragEnd]);
2525
+ return {
2526
+ isDragging,
2527
+ checkDragStart,
2528
+ handleDragMove,
2529
+ handleDragEnd,
2530
+ clearDragTimer,
2531
+ checkMoveThreshold
2532
+ };
2533
+ }
2534
+
2535
+ // src/components/VirtualController/hooks/useTouchHandlers.ts
2536
+ function useTouchHandlers({
2537
+ buttonType,
2538
+ isSystemButton,
2539
+ buttonSize,
2540
+ displayX,
2541
+ displayY,
2542
+ containerWidth,
2543
+ containerHeight,
2544
+ onPress,
2545
+ onPressDown,
2546
+ onRelease,
2547
+ onPositionChange
2548
+ }) {
2549
+ const isDraggingRef = React2.useRef(false);
2550
+ const drag = useDrag({
2551
+ elementSize: buttonSize,
2552
+ displayX,
2553
+ displayY,
2554
+ containerWidth,
2555
+ containerHeight,
2556
+ onPositionChange,
2557
+ centerThreshold: 0.4,
2558
+ onDragStart: () => {
2559
+ isDraggingRef.current = true;
2432
2560
  if (!isSystemButton) {
2433
2561
  onRelease(buttonType);
2434
2562
  }
2435
2563
  },
2436
- [displayX, displayY, containerWidth, containerHeight, isSystemButton, buttonType, onRelease]
2437
- );
2564
+ onDragEnd: () => {
2565
+ isDraggingRef.current = false;
2566
+ }
2567
+ });
2438
2568
  const handleTouchStart = React2.useCallback(
2439
2569
  (e) => {
2440
2570
  const touch = e.touches[0];
2441
- touchStartPosRef.current = { x: touch.clientX, y: touch.clientY };
2442
2571
  e.preventDefault();
2443
2572
  e.stopPropagation();
2444
2573
  if (navigator.vibrate) {
@@ -2453,57 +2582,32 @@ function useTouchHandlers({
2453
2582
  const target = e.currentTarget;
2454
2583
  if (!target) return;
2455
2584
  const rect = target.getBoundingClientRect();
2456
- const centerX = rect.left + rect.width / 2;
2457
- const centerY = rect.top + rect.height / 2;
2458
- const distance = Math.sqrt(
2459
- Math.pow(touch.clientX - centerX, 2) + Math.pow(touch.clientY - centerY, 2)
2460
- );
2461
- const dragThreshold = buttonSize * DRAG_CENTER_THRESHOLD;
2462
- if (distance < dragThreshold) {
2463
- dragTimerRef.current = setTimeout(() => {
2464
- if (!isDraggingRef.current) {
2465
- startDragging(touch.clientX, touch.clientY);
2466
- }
2467
- }, DRAG_HOLD_DELAY);
2468
- }
2585
+ drag.checkDragStart(touch.clientX, touch.clientY, rect);
2469
2586
  }
2470
2587
  },
2471
- [isSystemButton, buttonType, onPress, onPressDown, onPositionChange, buttonSize, startDragging]
2588
+ [isSystemButton, buttonType, onPress, onPressDown, onPositionChange, drag]
2472
2589
  );
2473
2590
  const handleTouchMove = React2.useCallback(
2474
2591
  (e) => {
2475
2592
  const touch = e.touches[0];
2476
2593
  if (onPositionChange && !isDraggingRef.current) {
2477
- const moveDistance = Math.sqrt(
2478
- Math.pow(touch.clientX - touchStartPosRef.current.x, 2) + Math.pow(touch.clientY - touchStartPosRef.current.y, 2)
2479
- );
2480
- if (moveDistance > DRAG_MOVE_THRESHOLD) {
2481
- clearDragTimer();
2482
- startDragging(touch.clientX, touch.clientY);
2483
- }
2594
+ drag.checkMoveThreshold(touch.clientX, touch.clientY);
2484
2595
  }
2485
- if (isDraggingRef.current && onPositionChange) {
2596
+ if (isDraggingRef.current) {
2486
2597
  e.preventDefault();
2487
2598
  e.stopPropagation();
2488
- const newX = touch.clientX - dragStartRef.current.x;
2489
- const newY = touch.clientY - dragStartRef.current.y;
2490
- const newXPercent = newX / containerWidth * 100;
2491
- const newYPercent = newY / containerHeight * 100;
2492
- const margin = buttonSize / 2 / Math.min(containerWidth, containerHeight) * 100;
2493
- const constrainedX = Math.max(margin, Math.min(100 - margin, newXPercent));
2494
- const constrainedY = Math.max(margin, Math.min(100 - margin, newYPercent));
2495
- onPositionChange(constrainedX, constrainedY);
2599
+ drag.handleDragMove(touch.clientX, touch.clientY);
2496
2600
  }
2497
2601
  },
2498
- [onPositionChange, clearDragTimer, startDragging, containerWidth, containerHeight, buttonSize]
2602
+ [onPositionChange, drag]
2499
2603
  );
2500
2604
  const handleTouchEnd = React2.useCallback(
2501
2605
  (e) => {
2502
- clearDragTimer();
2606
+ drag.clearDragTimer();
2503
2607
  if (isDraggingRef.current) {
2504
2608
  e.preventDefault();
2505
2609
  e.stopPropagation();
2506
- isDraggingRef.current = false;
2610
+ drag.handleDragEnd();
2507
2611
  return;
2508
2612
  }
2509
2613
  e.preventDefault();
@@ -2512,15 +2616,15 @@ function useTouchHandlers({
2512
2616
  onRelease(buttonType);
2513
2617
  }
2514
2618
  },
2515
- [clearDragTimer, isSystemButton, buttonType, onRelease]
2619
+ [drag, isSystemButton, buttonType, onRelease]
2516
2620
  );
2517
2621
  const handleTouchCancel = React2.useCallback(
2518
2622
  (e) => {
2519
- clearDragTimer();
2623
+ drag.clearDragTimer();
2520
2624
  if (isDraggingRef.current) {
2521
2625
  e.preventDefault();
2522
2626
  e.stopPropagation();
2523
- isDraggingRef.current = false;
2627
+ drag.handleDragEnd();
2524
2628
  return;
2525
2629
  }
2526
2630
  e.preventDefault();
@@ -2529,11 +2633,11 @@ function useTouchHandlers({
2529
2633
  onRelease(buttonType);
2530
2634
  }
2531
2635
  },
2532
- [clearDragTimer, isSystemButton, buttonType, onRelease]
2636
+ [drag, isSystemButton, buttonType, onRelease]
2533
2637
  );
2534
2638
  const cleanup = React2.useCallback(() => {
2535
- clearDragTimer();
2536
- }, [clearDragTimer]);
2639
+ drag.clearDragTimer();
2640
+ }, [drag]);
2537
2641
  return {
2538
2642
  handleTouchStart,
2539
2643
  handleTouchMove,
@@ -3389,7 +3493,6 @@ function dispatchKeyboardEvent(type, code) {
3389
3493
  canvas.dispatchEvent(event);
3390
3494
  return true;
3391
3495
  }
3392
- var DRAG_HOLD_DELAY2 = 350;
3393
3496
  var CENTER_TOUCH_RADIUS = 0.25;
3394
3497
  var Dpad = React2__default.default.memo(function Dpad2({
3395
3498
  size = 180,
@@ -3406,10 +3509,6 @@ var Dpad = React2__default.default.memo(function Dpad2({
3406
3509
  const dpadRef = React2.useRef(null);
3407
3510
  const activeTouchRef = React2.useRef(null);
3408
3511
  const activeDirectionsRef = React2.useRef(/* @__PURE__ */ new Set());
3409
- const [isDragging, setIsDragging] = React2.useState(false);
3410
- const dragTimerRef = React2.useRef(null);
3411
- const dragStartRef = React2.useRef({ x: 0, y: 0, touchX: 0, touchY: 0 });
3412
- const touchStartPosRef = React2.useRef({ x: 0, y: 0, time: 0 });
3413
3512
  const upPathRef = React2.useRef(null);
3414
3513
  const downPathRef = React2.useRef(null);
3415
3514
  const leftPathRef = React2.useRef(null);
@@ -3417,6 +3516,13 @@ var Dpad = React2__default.default.memo(function Dpad2({
3417
3516
  const centerCircleRef = React2.useRef(null);
3418
3517
  const displayX = customPosition ? customPosition.x : x;
3419
3518
  const displayY = customPosition ? customPosition.y : y;
3519
+ const releaseAllDirections = React2.useCallback((getKeyCode2) => {
3520
+ activeDirectionsRef.current.forEach((dir) => {
3521
+ const keyCode = getKeyCode2(dir);
3522
+ if (keyCode) dispatchKeyboardEvent("keyup", keyCode);
3523
+ });
3524
+ activeDirectionsRef.current = /* @__PURE__ */ new Set();
3525
+ }, []);
3420
3526
  const getKeyCode = React2.useCallback((direction) => {
3421
3527
  if (!controls) {
3422
3528
  const defaults = {
@@ -3429,6 +3535,19 @@ var Dpad = React2__default.default.memo(function Dpad2({
3429
3535
  }
3430
3536
  return controls[direction] || "";
3431
3537
  }, [controls]);
3538
+ const drag = useDrag({
3539
+ elementSize: size,
3540
+ displayX,
3541
+ displayY,
3542
+ containerWidth,
3543
+ containerHeight,
3544
+ onPositionChange,
3545
+ centerThreshold: CENTER_TOUCH_RADIUS,
3546
+ onDragStart: () => {
3547
+ releaseAllDirections(getKeyCode);
3548
+ updateVisuals(/* @__PURE__ */ new Set());
3549
+ }
3550
+ });
3432
3551
  const getDirectionsFromTouch = React2.useCallback((touchX, touchY, rect) => {
3433
3552
  const centerX = rect.left + rect.width / 2;
3434
3553
  const centerY = rect.top + rect.height / 2;
@@ -3490,51 +3609,20 @@ var Dpad = React2__default.default.memo(function Dpad2({
3490
3609
  activeDirectionsRef.current = newDirections;
3491
3610
  updateVisuals(newDirections);
3492
3611
  }, [getKeyCode, updateVisuals]);
3493
- const clearDragTimer = React2.useCallback(() => {
3494
- if (dragTimerRef.current) {
3495
- clearTimeout(dragTimerRef.current);
3496
- dragTimerRef.current = null;
3497
- }
3498
- }, []);
3499
- const startDragging = React2.useCallback((touchX, touchY) => {
3500
- setIsDragging(true);
3501
- dragStartRef.current = {
3502
- x: displayX,
3503
- y: displayY,
3504
- touchX,
3505
- touchY
3506
- };
3507
- activeDirectionsRef.current.forEach((dir) => {
3508
- const keyCode = getKeyCode(dir);
3509
- if (keyCode) dispatchKeyboardEvent("keyup", keyCode);
3510
- });
3511
- activeDirectionsRef.current = /* @__PURE__ */ new Set();
3512
- updateVisuals(/* @__PURE__ */ new Set());
3513
- if (navigator.vibrate) navigator.vibrate([10, 30, 10]);
3514
- }, [displayX, displayY, getKeyCode, updateVisuals]);
3515
3612
  const handleTouchStart = React2.useCallback((e) => {
3516
3613
  e.preventDefault();
3517
3614
  if (activeTouchRef.current !== null) return;
3518
3615
  const touch = e.changedTouches[0];
3519
3616
  activeTouchRef.current = touch.identifier;
3520
- touchStartPosRef.current = { x: touch.clientX, y: touch.clientY, time: Date.now() };
3521
3617
  const rect = dpadRef.current?.getBoundingClientRect();
3522
3618
  if (!rect) return;
3523
- const centerX = rect.left + rect.width / 2;
3524
- const centerY = rect.top + rect.height / 2;
3525
- const distFromCenter = Math.sqrt(
3526
- Math.pow(touch.clientX - centerX, 2) + Math.pow(touch.clientY - centerY, 2)
3527
- );
3528
- const centerRadius = size * CENTER_TOUCH_RADIUS;
3529
- if (distFromCenter < centerRadius && onPositionChange) {
3530
- dragTimerRef.current = setTimeout(() => {
3531
- startDragging(touch.clientX, touch.clientY);
3532
- }, DRAG_HOLD_DELAY2);
3619
+ if (onPositionChange) {
3620
+ drag.checkDragStart(touch.clientX, touch.clientY, rect);
3533
3621
  }
3534
- if (!isDragging) {
3622
+ if (!drag.isDragging) {
3535
3623
  updateDirections(getDirectionsFromTouch(touch.clientX, touch.clientY, rect));
3536
3624
  }
3537
- }, [getDirectionsFromTouch, updateDirections, isDragging, size, onPositionChange, startDragging]);
3625
+ }, [getDirectionsFromTouch, updateDirections, onPositionChange, drag]);
3538
3626
  const handleTouchMove = React2.useCallback((e) => {
3539
3627
  e.preventDefault();
3540
3628
  let touch = null;
@@ -3545,31 +3633,19 @@ var Dpad = React2__default.default.memo(function Dpad2({
3545
3633
  }
3546
3634
  }
3547
3635
  if (!touch) return;
3548
- if (isDragging && onPositionChange) {
3549
- const deltaX = touch.clientX - dragStartRef.current.touchX;
3550
- const deltaY = touch.clientY - dragStartRef.current.touchY;
3551
- const newXPercent = dragStartRef.current.x + deltaX / containerWidth * 100;
3552
- const newYPercent = dragStartRef.current.y + deltaY / containerHeight * 100;
3553
- const margin = size / 2 / Math.min(containerWidth, containerHeight) * 100;
3554
- const constrainedX = Math.max(margin, Math.min(100 - margin, newXPercent));
3555
- const constrainedY = Math.max(margin, Math.min(100 - margin, newYPercent));
3556
- onPositionChange(constrainedX, constrainedY);
3636
+ if (drag.isDragging) {
3637
+ drag.handleDragMove(touch.clientX, touch.clientY);
3557
3638
  } else {
3558
- const moveDistance = Math.sqrt(
3559
- Math.pow(touch.clientX - touchStartPosRef.current.x, 2) + Math.pow(touch.clientY - touchStartPosRef.current.y, 2)
3560
- );
3561
- if (moveDistance > 15) {
3562
- clearDragTimer();
3563
- }
3564
3639
  const rect = dpadRef.current?.getBoundingClientRect();
3565
3640
  if (rect) {
3641
+ drag.clearDragTimer();
3566
3642
  updateDirections(getDirectionsFromTouch(touch.clientX, touch.clientY, rect));
3567
3643
  }
3568
3644
  }
3569
- }, [isDragging, onPositionChange, containerWidth, containerHeight, size, getDirectionsFromTouch, updateDirections, clearDragTimer]);
3645
+ }, [drag, getDirectionsFromTouch, updateDirections]);
3570
3646
  const handleTouchEnd = React2.useCallback((e) => {
3571
3647
  e.preventDefault();
3572
- clearDragTimer();
3648
+ drag.clearDragTimer();
3573
3649
  let touchEnded = false;
3574
3650
  for (let i = 0; i < e.changedTouches.length; i++) {
3575
3651
  if (e.changedTouches[i].identifier === activeTouchRef.current) {
@@ -3579,8 +3655,8 @@ var Dpad = React2__default.default.memo(function Dpad2({
3579
3655
  }
3580
3656
  if (touchEnded) {
3581
3657
  activeTouchRef.current = null;
3582
- if (isDragging) {
3583
- setIsDragging(false);
3658
+ if (drag.isDragging) {
3659
+ drag.handleDragEnd();
3584
3660
  } else {
3585
3661
  activeDirectionsRef.current.forEach((dir) => {
3586
3662
  const keyCode = getKeyCode(dir);
@@ -3590,7 +3666,7 @@ var Dpad = React2__default.default.memo(function Dpad2({
3590
3666
  updateVisuals(/* @__PURE__ */ new Set());
3591
3667
  }
3592
3668
  }
3593
- }, [getKeyCode, updateVisuals, isDragging, clearDragTimer]);
3669
+ }, [getKeyCode, updateVisuals, drag]);
3594
3670
  React2.useEffect(() => {
3595
3671
  const dpad = dpadRef.current;
3596
3672
  if (!dpad) return;
@@ -3603,9 +3679,9 @@ var Dpad = React2__default.default.memo(function Dpad2({
3603
3679
  dpad.removeEventListener("touchmove", handleTouchMove);
3604
3680
  dpad.removeEventListener("touchend", handleTouchEnd);
3605
3681
  dpad.removeEventListener("touchcancel", handleTouchEnd);
3606
- clearDragTimer();
3682
+ drag.clearDragTimer();
3607
3683
  };
3608
- }, [handleTouchStart, handleTouchMove, handleTouchEnd, clearDragTimer]);
3684
+ }, [handleTouchStart, handleTouchMove, handleTouchEnd, drag]);
3609
3685
  const leftPx = displayX / 100 * containerWidth - size / 2;
3610
3686
  const topPx = displayY / 100 * containerHeight - size / 2;
3611
3687
  const dUp = "M 35,5 L 65,5 L 65,35 L 50,50 L 35,35 Z";
@@ -3616,21 +3692,21 @@ var Dpad = React2__default.default.memo(function Dpad2({
3616
3692
  "div",
3617
3693
  {
3618
3694
  ref: dpadRef,
3619
- className: `absolute pointer-events-auto touch-manipulation select-none ${isDragging ? "opacity-60" : ""}`,
3695
+ className: `absolute pointer-events-auto touch-manipulation select-none ${drag.isDragging ? "opacity-60" : ""}`,
3620
3696
  style: {
3621
3697
  top: 0,
3622
3698
  left: 0,
3623
- transform: `translate3d(${leftPx}px, ${topPx}px, 0)${isDragging ? " scale(1.05)" : ""}`,
3699
+ transform: `translate3d(${leftPx}px, ${topPx}px, 0)${drag.isDragging ? " scale(1.05)" : ""}`,
3624
3700
  width: size,
3625
3701
  height: size,
3626
3702
  opacity: isLandscape ? 0.75 : 0.9,
3627
3703
  WebkitTouchCallout: "none",
3628
3704
  WebkitUserSelect: "none",
3629
3705
  touchAction: "none",
3630
- transition: isDragging ? "none" : "transform 0.1s ease-out"
3706
+ transition: drag.isDragging ? "none" : "transform 0.1s ease-out"
3631
3707
  },
3632
3708
  children: [
3633
- /* @__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"}` }),
3709
+ /* @__PURE__ */ jsxRuntime.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"}` }),
3634
3710
  /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "100%", height: "100%", viewBox: "0 0 100 100", className: "drop-shadow-xl relative z-10", children: [
3635
3711
  /* @__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" }),
3636
3712
  /* @__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" }),
@@ -3643,9 +3719,9 @@ var Dpad = React2__default.default.memo(function Dpad2({
3643
3719
  cx: "50",
3644
3720
  cy: "50",
3645
3721
  r: "12",
3646
- fill: isDragging ? systemColor : "rgba(0,0,0,0.5)",
3647
- stroke: isDragging ? "#fff" : "rgba(255,255,255,0.3)",
3648
- strokeWidth: isDragging ? 2 : 1
3722
+ fill: drag.isDragging ? systemColor : "rgba(0,0,0,0.5)",
3723
+ stroke: drag.isDragging ? "#fff" : "rgba(255,255,255,0.3)",
3724
+ strokeWidth: drag.isDragging ? 2 : 1
3649
3725
  }
3650
3726
  ),
3651
3727
  /* @__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" }),
@@ -4073,6 +4149,45 @@ function FloatingFullscreenButton({ onClick, disabled = false }) {
4073
4149
  }
4074
4150
  );
4075
4151
  }
4152
+ function FloatingPauseButton({
4153
+ isPaused,
4154
+ onClick,
4155
+ disabled = false,
4156
+ systemColor = "#00FF41"
4157
+ }) {
4158
+ return /* @__PURE__ */ jsxRuntime.jsx(
4159
+ "button",
4160
+ {
4161
+ onClick,
4162
+ disabled,
4163
+ className: `
4164
+ fixed top-3 left-3 z-50
4165
+ px-3 py-2 rounded-xl
4166
+ bg-black/80 backdrop-blur-md
4167
+ border-2
4168
+ shadow-xl
4169
+ flex items-center gap-2
4170
+ transition-all duration-300
4171
+ hover:scale-105
4172
+ active:scale-95
4173
+ disabled:opacity-40 disabled:cursor-not-allowed
4174
+ touch-manipulation
4175
+ `,
4176
+ style: {
4177
+ paddingTop: "max(env(safe-area-inset-top, 0px), 8px)",
4178
+ borderColor: isPaused ? systemColor : "rgba(255,255,255,0.3)"
4179
+ },
4180
+ "aria-label": isPaused ? "Resume game" : "Pause game",
4181
+ children: isPaused ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4182
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Play, { size: 16, style: { color: systemColor }, fill: systemColor }),
4183
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-white text-xs font-bold uppercase tracking-wider", children: "Play" })
4184
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4185
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Pause, { size: 16, className: "text-white/80" }),
4186
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-white text-xs font-bold uppercase tracking-wider", children: "Pause" })
4187
+ ] })
4188
+ }
4189
+ );
4190
+ }
4076
4191
  function LoadingSpinner({ color, size = "lg" }) {
4077
4192
  const sizeClass = size === "lg" ? "w-12 h-12" : "w-8 h-8";
4078
4193
  return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: `${sizeClass} animate-spin`, style: { color } });
@@ -9206,6 +9321,14 @@ var GamePlayerInner = React2.memo(function GamePlayerInner2(props) {
9206
9321
  disabled: status === "loading" || status === "error"
9207
9322
  }
9208
9323
  ),
9324
+ isFullscreen2 && isMobile && (status === "running" || status === "paused") && /* @__PURE__ */ jsxRuntime.jsx(
9325
+ FloatingPauseButton,
9326
+ {
9327
+ isPaused,
9328
+ onClick: handlePauseToggle,
9329
+ systemColor
9330
+ }
9331
+ ),
9209
9332
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute top-2 right-2 z-40 flex flex-col items-end gap-2 pointer-events-auto", children: [
9210
9333
  /* @__PURE__ */ jsxRuntime.jsx(
9211
9334
  RecordingIndicator_default,