Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | 77x 77x 77x 77x 6x 71x 47x 24x 3x 21x 5x 16x 77x 77x 77x 77x 77x 77x 77x 77x | /**
* Mobile Layout Helpers
*
* Shared utilities for calculating mobile area bounds with consistent aspect ratios
* and device-specific sizing. Used by both combat and training layout hooks.
*
* @module utils/mobileLayoutHelpers
* @category Layout
* @korean 모바일레이아웃도우미
*/
import { calculateArenaWorldDimensions } from "./arenaWorldDimensions";
/**
* Mobile area bounds with world dimensions.
*
* @public
*/
export interface MobileAreaBounds {
readonly x: number;
readonly y: number;
readonly width: number;
readonly height: number;
readonly scale: number;
readonly worldWidthMeters: number;
readonly worldDepthMeters: number;
}
/**
* Calculate mobile area bounds with 4:3 aspect ratio
*
* Implements consistent mobile area sizing logic shared between combat and training screens.
* Adapts to different device resolutions while maintaining a 4:3 aspect ratio.
* Enhanced with extra-small device support (<380px) for low-end mobile devices.
*
* @param width - Screen width in pixels
* @param height - Screen height in pixels
* @param topClearance - Minimum space to reserve at top (for HUD/header)
* @param bottomClearance - Minimum space to reserve at bottom (for controls)
* @param yOffset - Y position offset (typically header height + padding)
* @returns Mobile area bounds with position, dimensions, scale, and world dimensions
*
* @example
* ```typescript
* const bounds = calculateMobileAreaBounds(375, 667, 80, 120, 100);
* // Returns: { x: ~37, y: 100, width: 300, height: 225, scale: 0.3125, worldWidthMeters: 6, worldDepthMeters: 6 }
*
* const boundsSmall = calculateMobileAreaBounds(320, 568, 75, 110, 90);
* // Returns: { x: ~25, y: 90, width: 270, height: 202, scale: 0.28125, worldWidthMeters: 6, worldDepthMeters: 6 }
* ```
*
* @public
* @korean 모바일영역경계계산
*/
export function calculateMobileAreaBounds(
width: number,
height: number,
topClearance: number,
bottomClearance: number,
yOffset: number,
): MobileAreaBounds {
// Calculate available space for the area
// Extra-small devices (<380px) use tighter margins for more screen real estate
const horizontalMargin = width < 380 ? 30 : 40; // 15px vs 20px per side
const availableHeight = height - topClearance - bottomClearance;
const availableWidth = width - horizontalMargin;
// Determine max width based on device resolution
// Device-specific sizing with extra-small support:
// - 4K/QHD+ (≥1440px): up to 800px
// - 2K (1200-1439px): up to 600px
// - Large phones (768-1199px): up to 500px
// - Standard phones (380-767px): up to 400px
// - Extra-small phones (<380px): up to 320px
let maxMobileWidth: number;
if (width >= 1440) {
maxMobileWidth = Math.min(availableWidth, 800);
} else if (width >= 1200) {
maxMobileWidth = Math.min(availableWidth, 600);
} else if (width >= 768) {
maxMobileWidth = Math.min(availableWidth, 500);
} else if (width >= 380) {
maxMobileWidth = Math.min(availableWidth, 400);
} else {
// Extra-small devices (iPhone SE, old Android, budget phones)
maxMobileWidth = Math.min(availableWidth, 320);
}
// Extra-small devices also get reduced max height for better fit
const maxMobileHeight = Math.min(availableHeight, width < 380 ? 240 : 800);
// Use 4:3 aspect ratio for arena (width > height)
// Calculate area width first, constrained by available space
const areaWidth = Math.max(
Math.min(maxMobileWidth, maxMobileHeight * (4 / 3)),
280,
);
const areaHeight = areaWidth * (3 / 4); // 4:3 aspect ratio
// Calculate world dimensions based on RENDERED arena width (not screen width)
// This ensures correct pixels-per-meter ratio for the actual visible arena
const worldDimensions = calculateArenaWorldDimensions(areaWidth);
// Calculate 3D scale factor based on reference arena
// Reference: 10m arena at 1000px = 100 px/m
const pixelsPerMeter = areaWidth / worldDimensions.widthMeters;
const referencePixelsPerMeter = 100;
const scale = pixelsPerMeter / referencePixelsPerMeter;
return {
x: (width - areaWidth) / 2, // Centered horizontally
y: yOffset,
width: areaWidth,
height: areaHeight, // 4:3 aspect ratio
scale,
worldWidthMeters: worldDimensions.widthMeters,
worldDepthMeters: worldDimensions.depthMeters,
};
}
|