@almadar/ui 2.16.0 → 2.16.1

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.
@@ -9,6 +9,8 @@ var GLTFLoader = require('three/examples/jsm/loaders/GLTFLoader');
9
9
  var OrbitControls_js = require('three/examples/jsm/controls/OrbitControls.js');
10
10
  var GLTFLoader_js = require('three/examples/jsm/loaders/GLTFLoader.js');
11
11
  var OBJLoader_js = require('three/examples/jsm/loaders/OBJLoader.js');
12
+ var clsx = require('clsx');
13
+ var tailwindMerge = require('tailwind-merge');
12
14
 
13
15
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
14
16
 
@@ -2200,6 +2202,1067 @@ function preloadFeatures(urls) {
2200
2202
  }
2201
2203
  });
2202
2204
  }
2205
+ var DEFAULT_GRID_CONFIG = {
2206
+ cellSize: 1,
2207
+ offsetX: 0,
2208
+ offsetZ: 0
2209
+ };
2210
+ function CameraController({
2211
+ onCameraChange
2212
+ }) {
2213
+ const { camera } = fiber.useThree();
2214
+ React8.useEffect(() => {
2215
+ if (onCameraChange) {
2216
+ onCameraChange({
2217
+ x: camera.position.x,
2218
+ y: camera.position.y,
2219
+ z: camera.position.z
2220
+ });
2221
+ }
2222
+ }, [camera.position, onCameraChange]);
2223
+ return null;
2224
+ }
2225
+ var GameCanvas3D = React8.forwardRef(
2226
+ ({
2227
+ tiles = [],
2228
+ units = [],
2229
+ features = [],
2230
+ events = [],
2231
+ orientation = "standard",
2232
+ cameraMode = "isometric",
2233
+ showGrid = true,
2234
+ showCoordinates = false,
2235
+ showTileInfo = false,
2236
+ overlay = "default",
2237
+ shadows = true,
2238
+ backgroundColor = "#1a1a2e",
2239
+ onTileClick,
2240
+ onUnitClick,
2241
+ onFeatureClick,
2242
+ onCanvasClick,
2243
+ onTileHover,
2244
+ onUnitAnimation,
2245
+ assetLoader: customAssetLoader,
2246
+ tileRenderer: CustomTileRenderer,
2247
+ unitRenderer: CustomUnitRenderer,
2248
+ featureRenderer: CustomFeatureRenderer,
2249
+ className,
2250
+ isLoading: externalLoading,
2251
+ error: externalError,
2252
+ entity,
2253
+ preloadAssets = [],
2254
+ tileClickEvent,
2255
+ unitClickEvent,
2256
+ featureClickEvent,
2257
+ canvasClickEvent,
2258
+ tileHoverEvent,
2259
+ tileLeaveEvent,
2260
+ unitAnimationEvent,
2261
+ cameraChangeEvent,
2262
+ loadingMessage = "Loading 3D Scene...",
2263
+ useInstancing = true,
2264
+ validMoves = [],
2265
+ attackTargets = [],
2266
+ selectedTileIds = [],
2267
+ selectedUnitId = null,
2268
+ children
2269
+ }, ref) => {
2270
+ const containerRef = React8.useRef(null);
2271
+ const controlsRef = React8.useRef(null);
2272
+ const [hoveredTile, setHoveredTile] = React8.useState(null);
2273
+ const [internalError, setInternalError] = React8.useState(null);
2274
+ const { isLoading: assetsLoading, progress, loaded, total } = useAssetLoader({
2275
+ preloadUrls: preloadAssets,
2276
+ loader: customAssetLoader
2277
+ });
2278
+ const eventHandlers = useGameCanvas3DEvents({
2279
+ tileClickEvent,
2280
+ unitClickEvent,
2281
+ featureClickEvent,
2282
+ canvasClickEvent,
2283
+ tileHoverEvent,
2284
+ tileLeaveEvent,
2285
+ unitAnimationEvent,
2286
+ cameraChangeEvent,
2287
+ onTileClick,
2288
+ onUnitClick,
2289
+ onFeatureClick,
2290
+ onCanvasClick,
2291
+ onTileHover,
2292
+ onUnitAnimation
2293
+ });
2294
+ const gridBounds = React8.useMemo(() => {
2295
+ if (tiles.length === 0) {
2296
+ return { minX: 0, maxX: 10, minZ: 0, maxZ: 10 };
2297
+ }
2298
+ const xs = tiles.map((t) => t.x);
2299
+ const zs = tiles.map((t) => t.z || t.y || 0);
2300
+ return {
2301
+ minX: Math.min(...xs),
2302
+ maxX: Math.max(...xs),
2303
+ minZ: Math.min(...zs),
2304
+ maxZ: Math.max(...zs)
2305
+ };
2306
+ }, [tiles]);
2307
+ const cameraTarget = React8.useMemo(() => {
2308
+ return [
2309
+ (gridBounds.minX + gridBounds.maxX) / 2,
2310
+ 0,
2311
+ (gridBounds.minZ + gridBounds.maxZ) / 2
2312
+ ];
2313
+ }, [gridBounds]);
2314
+ const gridConfig = React8.useMemo(
2315
+ () => ({
2316
+ ...DEFAULT_GRID_CONFIG,
2317
+ offsetX: -(gridBounds.maxX - gridBounds.minX) / 2,
2318
+ offsetZ: -(gridBounds.maxZ - gridBounds.minZ) / 2
2319
+ }),
2320
+ [gridBounds]
2321
+ );
2322
+ const gridToWorld2 = React8.useCallback(
2323
+ (x, z, y = 0) => {
2324
+ const worldX = (x - gridBounds.minX) * gridConfig.cellSize;
2325
+ const worldZ = (z - gridBounds.minZ) * gridConfig.cellSize;
2326
+ return [worldX, y * gridConfig.cellSize, worldZ];
2327
+ },
2328
+ [gridBounds, gridConfig]
2329
+ );
2330
+ React8.useImperativeHandle(ref, () => ({
2331
+ getCameraPosition: () => {
2332
+ if (controlsRef.current) {
2333
+ const pos = controlsRef.current.object.position;
2334
+ return new THREE6__namespace.Vector3(pos.x, pos.y, pos.z);
2335
+ }
2336
+ return null;
2337
+ },
2338
+ setCameraPosition: (x, y, z) => {
2339
+ if (controlsRef.current) {
2340
+ controlsRef.current.object.position.set(x, y, z);
2341
+ controlsRef.current.update();
2342
+ }
2343
+ },
2344
+ lookAt: (x, y, z) => {
2345
+ if (controlsRef.current) {
2346
+ controlsRef.current.target.set(x, y, z);
2347
+ controlsRef.current.update();
2348
+ }
2349
+ },
2350
+ resetCamera: () => {
2351
+ if (controlsRef.current) {
2352
+ controlsRef.current.reset();
2353
+ }
2354
+ },
2355
+ screenshot: () => {
2356
+ const canvas = containerRef.current?.querySelector("canvas");
2357
+ if (canvas) {
2358
+ return canvas.toDataURL("image/png");
2359
+ }
2360
+ return null;
2361
+ },
2362
+ export: () => ({
2363
+ tiles,
2364
+ units,
2365
+ features
2366
+ })
2367
+ }));
2368
+ const handleTileClick = React8.useCallback(
2369
+ (tile, event) => {
2370
+ eventHandlers.handleTileClick(tile, event);
2371
+ },
2372
+ [eventHandlers]
2373
+ );
2374
+ const handleUnitClick = React8.useCallback(
2375
+ (unit, event) => {
2376
+ eventHandlers.handleUnitClick(unit, event);
2377
+ },
2378
+ [eventHandlers]
2379
+ );
2380
+ const handleFeatureClick = React8.useCallback(
2381
+ (feature, event) => {
2382
+ if (event) {
2383
+ eventHandlers.handleFeatureClick(feature, event);
2384
+ }
2385
+ },
2386
+ [eventHandlers]
2387
+ );
2388
+ const handleTileHover = React8.useCallback(
2389
+ (tile, event) => {
2390
+ setHoveredTile(tile);
2391
+ if (event) {
2392
+ eventHandlers.handleTileHover(tile, event);
2393
+ }
2394
+ },
2395
+ [eventHandlers]
2396
+ );
2397
+ const cameraConfig = React8.useMemo(() => {
2398
+ const size = Math.max(
2399
+ gridBounds.maxX - gridBounds.minX,
2400
+ gridBounds.maxZ - gridBounds.minZ
2401
+ );
2402
+ const distance = size * 1.5;
2403
+ switch (cameraMode) {
2404
+ case "isometric":
2405
+ return {
2406
+ position: [distance, distance * 0.8, distance],
2407
+ fov: 45
2408
+ };
2409
+ case "top-down":
2410
+ return {
2411
+ position: [0, distance * 2, 0],
2412
+ fov: 45
2413
+ };
2414
+ case "perspective":
2415
+ default:
2416
+ return {
2417
+ position: [distance, distance, distance],
2418
+ fov: 45
2419
+ };
2420
+ }
2421
+ }, [cameraMode, gridBounds]);
2422
+ const DefaultTileRenderer = React8.useCallback(
2423
+ ({ tile, position }) => {
2424
+ const isSelected = tile.id ? selectedTileIds.includes(tile.id) : false;
2425
+ const isHovered = hoveredTile?.id === tile.id;
2426
+ const isValidMove = validMoves.some(
2427
+ (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
2428
+ );
2429
+ const isAttackTarget = attackTargets.some(
2430
+ (m) => m.x === tile.x && m.z === (tile.z ?? tile.y ?? 0)
2431
+ );
2432
+ let color = 8421504;
2433
+ if (tile.type === "water") color = 4491468;
2434
+ else if (tile.type === "grass") color = 4500036;
2435
+ else if (tile.type === "sand") color = 14535816;
2436
+ else if (tile.type === "rock") color = 8947848;
2437
+ else if (tile.type === "snow") color = 15658734;
2438
+ let emissive = 0;
2439
+ if (isSelected) emissive = 4473924;
2440
+ else if (isAttackTarget) emissive = 4456448;
2441
+ else if (isValidMove) emissive = 17408;
2442
+ else if (isHovered) emissive = 2236962;
2443
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2444
+ "mesh",
2445
+ {
2446
+ position,
2447
+ onClick: (e) => handleTileClick(tile, e),
2448
+ onPointerEnter: (e) => handleTileHover(tile, e),
2449
+ onPointerLeave: (e) => handleTileHover(null, e),
2450
+ userData: { type: "tile", tileId: tile.id, gridX: tile.x, gridZ: tile.z ?? tile.y },
2451
+ children: [
2452
+ /* @__PURE__ */ jsxRuntime.jsx("boxGeometry", { args: [0.95, 0.2, 0.95] }),
2453
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color, emissive })
2454
+ ]
2455
+ }
2456
+ );
2457
+ },
2458
+ [selectedTileIds, hoveredTile, validMoves, attackTargets, handleTileClick, handleTileHover]
2459
+ );
2460
+ const DefaultUnitRenderer = React8.useCallback(
2461
+ ({ unit, position }) => {
2462
+ const isSelected = selectedUnitId === unit.id;
2463
+ const color = unit.faction === "player" ? 4491519 : unit.faction === "enemy" ? 16729156 : 16777028;
2464
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2465
+ "group",
2466
+ {
2467
+ position,
2468
+ onClick: (e) => handleUnitClick(unit, e),
2469
+ userData: { type: "unit", unitId: unit.id },
2470
+ children: [
2471
+ isSelected && /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.05, 0], rotation: [-Math.PI / 2, 0, 0], children: [
2472
+ /* @__PURE__ */ jsxRuntime.jsx("ringGeometry", { args: [0.4, 0.5, 32] }),
2473
+ /* @__PURE__ */ jsxRuntime.jsx("meshBasicMaterial", { color: "#ffff00", transparent: true, opacity: 0.8 })
2474
+ ] }),
2475
+ /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.3, 0], children: [
2476
+ /* @__PURE__ */ jsxRuntime.jsx("cylinderGeometry", { args: [0.3, 0.3, 0.1, 8] }),
2477
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color })
2478
+ ] }),
2479
+ /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.6, 0], children: [
2480
+ /* @__PURE__ */ jsxRuntime.jsx("capsuleGeometry", { args: [0.2, 0.4, 4, 8] }),
2481
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color })
2482
+ ] }),
2483
+ /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.9, 0], children: [
2484
+ /* @__PURE__ */ jsxRuntime.jsx("sphereGeometry", { args: [0.12, 8, 8] }),
2485
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color })
2486
+ ] }),
2487
+ unit.health !== void 0 && unit.maxHealth !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("group", { position: [0, 1.2, 0], children: [
2488
+ /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [-0.25, 0, 0], children: [
2489
+ /* @__PURE__ */ jsxRuntime.jsx("planeGeometry", { args: [0.5, 0.05] }),
2490
+ /* @__PURE__ */ jsxRuntime.jsx("meshBasicMaterial", { color: 3355443 })
2491
+ ] }),
2492
+ /* @__PURE__ */ jsxRuntime.jsxs(
2493
+ "mesh",
2494
+ {
2495
+ position: [
2496
+ -0.25 + 0.5 * (unit.health / unit.maxHealth) / 2,
2497
+ 0,
2498
+ 0.01
2499
+ ],
2500
+ children: [
2501
+ /* @__PURE__ */ jsxRuntime.jsx("planeGeometry", { args: [0.5 * (unit.health / unit.maxHealth), 0.05] }),
2502
+ /* @__PURE__ */ jsxRuntime.jsx(
2503
+ "meshBasicMaterial",
2504
+ {
2505
+ color: unit.health / unit.maxHealth > 0.5 ? 4500036 : unit.health / unit.maxHealth > 0.25 ? 11184708 : 16729156
2506
+ }
2507
+ )
2508
+ ]
2509
+ }
2510
+ )
2511
+ ] })
2512
+ ]
2513
+ }
2514
+ );
2515
+ },
2516
+ [selectedUnitId, handleUnitClick]
2517
+ );
2518
+ const DefaultFeatureRenderer = React8.useCallback(
2519
+ ({
2520
+ feature,
2521
+ position
2522
+ }) => {
2523
+ if (feature.assetUrl) {
2524
+ return /* @__PURE__ */ jsxRuntime.jsx(
2525
+ ModelLoader,
2526
+ {
2527
+ url: feature.assetUrl,
2528
+ position,
2529
+ scale: 0.5,
2530
+ rotation: [0, feature.rotation ?? 0, 0],
2531
+ onClick: () => handleFeatureClick(feature, null),
2532
+ fallbackGeometry: "box"
2533
+ },
2534
+ feature.id
2535
+ );
2536
+ }
2537
+ if (feature.type === "tree") {
2538
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2539
+ "group",
2540
+ {
2541
+ position,
2542
+ onClick: (e) => handleFeatureClick(feature, e),
2543
+ userData: { type: "feature", featureId: feature.id },
2544
+ children: [
2545
+ /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.4, 0], children: [
2546
+ /* @__PURE__ */ jsxRuntime.jsx("cylinderGeometry", { args: [0.1, 0.15, 0.8, 6] }),
2547
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: 9127187 })
2548
+ ] }),
2549
+ /* @__PURE__ */ jsxRuntime.jsxs("mesh", { position: [0, 0.9, 0], children: [
2550
+ /* @__PURE__ */ jsxRuntime.jsx("coneGeometry", { args: [0.5, 0.8, 8] }),
2551
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: 2263842 })
2552
+ ] })
2553
+ ]
2554
+ }
2555
+ );
2556
+ }
2557
+ if (feature.type === "rock") {
2558
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2559
+ "mesh",
2560
+ {
2561
+ position: [position[0], position[1] + 0.3, position[2]],
2562
+ onClick: (e) => handleFeatureClick(feature, e),
2563
+ userData: { type: "feature", featureId: feature.id },
2564
+ children: [
2565
+ /* @__PURE__ */ jsxRuntime.jsx("dodecahedronGeometry", { args: [0.3, 0] }),
2566
+ /* @__PURE__ */ jsxRuntime.jsx("meshStandardMaterial", { color: 8421504 })
2567
+ ]
2568
+ }
2569
+ );
2570
+ }
2571
+ return null;
2572
+ },
2573
+ [handleFeatureClick]
2574
+ );
2575
+ if (externalLoading || assetsLoading && preloadAssets.length > 0) {
2576
+ return /* @__PURE__ */ jsxRuntime.jsx(
2577
+ Canvas3DLoadingState,
2578
+ {
2579
+ progress,
2580
+ loaded,
2581
+ total,
2582
+ message: loadingMessage,
2583
+ className
2584
+ }
2585
+ );
2586
+ }
2587
+ const displayError = externalError || internalError;
2588
+ if (displayError) {
2589
+ return /* @__PURE__ */ jsxRuntime.jsx(Canvas3DErrorBoundary, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "game-canvas-3d game-canvas-3d--error", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "game-canvas-3d__error", children: [
2590
+ "Error: ",
2591
+ displayError
2592
+ ] }) }) });
2593
+ }
2594
+ return /* @__PURE__ */ jsxRuntime.jsx(
2595
+ Canvas3DErrorBoundary,
2596
+ {
2597
+ onError: (err) => setInternalError(err.message),
2598
+ onReset: () => setInternalError(null),
2599
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
2600
+ "div",
2601
+ {
2602
+ ref: containerRef,
2603
+ className: `game-canvas-3d ${className || ""}`,
2604
+ "data-orientation": orientation,
2605
+ "data-camera-mode": cameraMode,
2606
+ "data-overlay": overlay,
2607
+ children: [
2608
+ /* @__PURE__ */ jsxRuntime.jsxs(
2609
+ fiber.Canvas,
2610
+ {
2611
+ shadows,
2612
+ camera: {
2613
+ position: cameraConfig.position,
2614
+ fov: cameraConfig.fov,
2615
+ near: 0.1,
2616
+ far: 1e3
2617
+ },
2618
+ style: { background: backgroundColor },
2619
+ onClick: (e) => {
2620
+ if (e.target === e.currentTarget) {
2621
+ eventHandlers.handleCanvasClick(e);
2622
+ }
2623
+ },
2624
+ children: [
2625
+ /* @__PURE__ */ jsxRuntime.jsx(CameraController, { onCameraChange: eventHandlers.handleCameraChange }),
2626
+ /* @__PURE__ */ jsxRuntime.jsx("ambientLight", { intensity: 0.6 }),
2627
+ /* @__PURE__ */ jsxRuntime.jsx(
2628
+ "directionalLight",
2629
+ {
2630
+ position: [10, 20, 10],
2631
+ intensity: 0.8,
2632
+ castShadow: shadows,
2633
+ "shadow-mapSize": [2048, 2048]
2634
+ }
2635
+ ),
2636
+ /* @__PURE__ */ jsxRuntime.jsx("hemisphereLight", { intensity: 0.3, color: "#87ceeb", groundColor: "#362d1d" }),
2637
+ showGrid && /* @__PURE__ */ jsxRuntime.jsx(
2638
+ drei.Grid,
2639
+ {
2640
+ args: [
2641
+ Math.max(gridBounds.maxX - gridBounds.minX + 2, 10),
2642
+ Math.max(gridBounds.maxZ - gridBounds.minZ + 2, 10)
2643
+ ],
2644
+ position: [
2645
+ (gridBounds.maxX - gridBounds.minX) / 2 - 0.5,
2646
+ 0,
2647
+ (gridBounds.maxZ - gridBounds.minZ) / 2 - 0.5
2648
+ ],
2649
+ cellSize: 1,
2650
+ cellThickness: 1,
2651
+ cellColor: "#444444",
2652
+ sectionSize: 5,
2653
+ sectionThickness: 1.5,
2654
+ sectionColor: "#666666",
2655
+ fadeDistance: 50,
2656
+ fadeStrength: 1
2657
+ }
2658
+ ),
2659
+ tiles.map((tile, index) => {
2660
+ const position = gridToWorld2(
2661
+ tile.x,
2662
+ tile.z ?? tile.y ?? 0,
2663
+ tile.elevation ?? 0
2664
+ );
2665
+ const Renderer = CustomTileRenderer || DefaultTileRenderer;
2666
+ return /* @__PURE__ */ jsxRuntime.jsx(Renderer, { tile, position }, tile.id ?? `tile-${index}`);
2667
+ }),
2668
+ features.map((feature, index) => {
2669
+ const position = gridToWorld2(
2670
+ feature.x,
2671
+ feature.z ?? feature.y ?? 0,
2672
+ (feature.elevation ?? 0) + 0.5
2673
+ );
2674
+ const Renderer = CustomFeatureRenderer || DefaultFeatureRenderer;
2675
+ return /* @__PURE__ */ jsxRuntime.jsx(Renderer, { feature, position }, feature.id ?? `feature-${index}`);
2676
+ }),
2677
+ units.map((unit) => {
2678
+ const position = gridToWorld2(
2679
+ unit.x ?? 0,
2680
+ unit.z ?? unit.y ?? 0,
2681
+ (unit.elevation ?? 0) + 0.5
2682
+ );
2683
+ const Renderer = CustomUnitRenderer || DefaultUnitRenderer;
2684
+ return /* @__PURE__ */ jsxRuntime.jsx(Renderer, { unit, position }, unit.id);
2685
+ }),
2686
+ children,
2687
+ /* @__PURE__ */ jsxRuntime.jsx(
2688
+ drei.OrbitControls,
2689
+ {
2690
+ ref: controlsRef,
2691
+ target: cameraTarget,
2692
+ enableDamping: true,
2693
+ dampingFactor: 0.05,
2694
+ minDistance: 2,
2695
+ maxDistance: 100,
2696
+ maxPolarAngle: Math.PI / 2 - 0.1
2697
+ }
2698
+ )
2699
+ ]
2700
+ }
2701
+ ),
2702
+ showCoordinates && hoveredTile && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "game-canvas-3d__coordinates", children: [
2703
+ "X: ",
2704
+ hoveredTile.x,
2705
+ ", Z: ",
2706
+ hoveredTile.z ?? hoveredTile.y ?? 0
2707
+ ] }),
2708
+ showTileInfo && hoveredTile && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "game-canvas-3d__tile-info", children: [
2709
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "tile-info__type", children: hoveredTile.type }),
2710
+ hoveredTile.terrain && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "tile-info__terrain", children: hoveredTile.terrain })
2711
+ ] })
2712
+ ]
2713
+ }
2714
+ )
2715
+ }
2716
+ );
2717
+ }
2718
+ );
2719
+ GameCanvas3D.displayName = "GameCanvas3D";
2720
+ function cn(...inputs) {
2721
+ return tailwindMerge.twMerge(clsx.clsx(inputs));
2722
+ }
2723
+ var paddingStyles = {
2724
+ none: "p-0",
2725
+ xs: "p-1",
2726
+ sm: "p-2",
2727
+ md: "p-4",
2728
+ lg: "p-6",
2729
+ xl: "p-8",
2730
+ "2xl": "p-12"
2731
+ };
2732
+ var paddingXStyles = {
2733
+ none: "px-0",
2734
+ xs: "px-1",
2735
+ sm: "px-2",
2736
+ md: "px-4",
2737
+ lg: "px-6",
2738
+ xl: "px-8",
2739
+ "2xl": "px-12"
2740
+ };
2741
+ var paddingYStyles = {
2742
+ none: "py-0",
2743
+ xs: "py-1",
2744
+ sm: "py-2",
2745
+ md: "py-4",
2746
+ lg: "py-6",
2747
+ xl: "py-8",
2748
+ "2xl": "py-12"
2749
+ };
2750
+ var marginStyles = {
2751
+ none: "m-0",
2752
+ xs: "m-1",
2753
+ sm: "m-2",
2754
+ md: "m-4",
2755
+ lg: "m-6",
2756
+ xl: "m-8",
2757
+ "2xl": "m-12",
2758
+ auto: "m-auto"
2759
+ };
2760
+ var marginXStyles = {
2761
+ none: "mx-0",
2762
+ xs: "mx-1",
2763
+ sm: "mx-2",
2764
+ md: "mx-4",
2765
+ lg: "mx-6",
2766
+ xl: "mx-8",
2767
+ "2xl": "mx-12",
2768
+ auto: "mx-auto"
2769
+ };
2770
+ var marginYStyles = {
2771
+ none: "my-0",
2772
+ xs: "my-1",
2773
+ sm: "my-2",
2774
+ md: "my-4",
2775
+ lg: "my-6",
2776
+ xl: "my-8",
2777
+ "2xl": "my-12",
2778
+ auto: "my-auto"
2779
+ };
2780
+ var bgStyles = {
2781
+ transparent: "bg-transparent",
2782
+ primary: "bg-[var(--color-primary)] text-[var(--color-primary-foreground)]",
2783
+ secondary: "bg-[var(--color-secondary)] text-[var(--color-secondary-foreground)]",
2784
+ muted: "bg-[var(--color-muted)] text-[var(--color-foreground)]",
2785
+ accent: "bg-[var(--color-accent)] text-[var(--color-accent-foreground)]",
2786
+ surface: "bg-[var(--color-card)]",
2787
+ overlay: "bg-[var(--color-card)]/80 backdrop-blur-sm"
2788
+ };
2789
+ var roundedStyles = {
2790
+ none: "rounded-none",
2791
+ sm: "rounded-[var(--radius-sm)]",
2792
+ md: "rounded-[var(--radius-md)]",
2793
+ lg: "rounded-[var(--radius-lg)]",
2794
+ xl: "rounded-[var(--radius-xl)]",
2795
+ "2xl": "rounded-[var(--radius-xl)]",
2796
+ full: "rounded-[var(--radius-full)]"
2797
+ };
2798
+ var shadowStyles = {
2799
+ none: "shadow-none",
2800
+ sm: "shadow-[var(--shadow-sm)]",
2801
+ md: "shadow-[var(--shadow-main)]",
2802
+ lg: "shadow-[var(--shadow-lg)]",
2803
+ xl: "shadow-[var(--shadow-lg)]"
2804
+ };
2805
+ var displayStyles = {
2806
+ block: "block",
2807
+ inline: "inline",
2808
+ "inline-block": "inline-block",
2809
+ flex: "flex",
2810
+ "inline-flex": "inline-flex",
2811
+ grid: "grid"
2812
+ };
2813
+ var overflowStyles = {
2814
+ auto: "overflow-auto",
2815
+ hidden: "overflow-hidden",
2816
+ visible: "overflow-visible",
2817
+ scroll: "overflow-scroll"
2818
+ };
2819
+ var positionStyles = {
2820
+ relative: "relative",
2821
+ absolute: "absolute",
2822
+ fixed: "fixed",
2823
+ sticky: "sticky"
2824
+ };
2825
+ var Box = React8__default.default.forwardRef(
2826
+ ({
2827
+ padding,
2828
+ paddingX,
2829
+ paddingY,
2830
+ margin,
2831
+ marginX,
2832
+ marginY,
2833
+ bg = "transparent",
2834
+ border = false,
2835
+ rounded = "none",
2836
+ shadow = "none",
2837
+ display,
2838
+ fullWidth = false,
2839
+ fullHeight = false,
2840
+ overflow,
2841
+ position,
2842
+ className,
2843
+ children,
2844
+ as: Component2 = "div",
2845
+ action,
2846
+ actionPayload,
2847
+ hoverEvent,
2848
+ onClick,
2849
+ onMouseEnter,
2850
+ onMouseLeave,
2851
+ ...rest
2852
+ }, ref) => {
2853
+ const eventBus = useEventBus();
2854
+ const handleClick = React8.useCallback((e) => {
2855
+ if (action) {
2856
+ e.stopPropagation();
2857
+ eventBus.emit(`UI:${action}`, actionPayload ?? {});
2858
+ }
2859
+ onClick?.(e);
2860
+ }, [action, actionPayload, eventBus, onClick]);
2861
+ const handleMouseEnter = React8.useCallback((e) => {
2862
+ if (hoverEvent) {
2863
+ eventBus.emit(`UI:${hoverEvent}`, { hovered: true });
2864
+ }
2865
+ onMouseEnter?.(e);
2866
+ }, [hoverEvent, eventBus, onMouseEnter]);
2867
+ const handleMouseLeave = React8.useCallback((e) => {
2868
+ if (hoverEvent) {
2869
+ eventBus.emit(`UI:${hoverEvent}`, { hovered: false });
2870
+ }
2871
+ onMouseLeave?.(e);
2872
+ }, [hoverEvent, eventBus, onMouseLeave]);
2873
+ const isClickable = action || onClick;
2874
+ const Comp = Component2;
2875
+ return /* @__PURE__ */ jsxRuntime.jsx(
2876
+ Comp,
2877
+ {
2878
+ ref,
2879
+ className: cn(
2880
+ // Padding
2881
+ padding && paddingStyles[padding],
2882
+ paddingX && paddingXStyles[paddingX],
2883
+ paddingY && paddingYStyles[paddingY],
2884
+ // Margin
2885
+ margin && marginStyles[margin],
2886
+ marginX && marginXStyles[marginX],
2887
+ marginY && marginYStyles[marginY],
2888
+ // Background
2889
+ bgStyles[bg],
2890
+ // Border - uses theme variables
2891
+ border && "border-[length:var(--border-width)] border-[var(--color-border)]",
2892
+ // Rounded
2893
+ roundedStyles[rounded],
2894
+ // Shadow
2895
+ shadowStyles[shadow],
2896
+ // Display
2897
+ display && displayStyles[display],
2898
+ // Dimensions
2899
+ fullWidth && "w-full",
2900
+ fullHeight && "h-full",
2901
+ // Overflow
2902
+ overflow && overflowStyles[overflow],
2903
+ // Position
2904
+ position && positionStyles[position],
2905
+ // Cursor for clickable
2906
+ isClickable && "cursor-pointer",
2907
+ className
2908
+ ),
2909
+ onClick: isClickable ? handleClick : void 0,
2910
+ onMouseEnter: hoverEvent || onMouseEnter ? handleMouseEnter : void 0,
2911
+ onMouseLeave: hoverEvent || onMouseLeave ? handleMouseLeave : void 0,
2912
+ ...rest,
2913
+ children
2914
+ }
2915
+ );
2916
+ }
2917
+ );
2918
+ Box.displayName = "Box";
2919
+ var gapStyles = {
2920
+ none: "gap-0",
2921
+ xs: "gap-1",
2922
+ sm: "gap-2",
2923
+ md: "gap-4",
2924
+ lg: "gap-6",
2925
+ xl: "gap-8",
2926
+ "2xl": "gap-12"
2927
+ };
2928
+ var alignStyles = {
2929
+ start: "items-start",
2930
+ center: "items-center",
2931
+ end: "items-end",
2932
+ stretch: "items-stretch",
2933
+ baseline: "items-baseline"
2934
+ };
2935
+ var justifyStyles = {
2936
+ start: "justify-start",
2937
+ center: "justify-center",
2938
+ end: "justify-end",
2939
+ between: "justify-between",
2940
+ around: "justify-around",
2941
+ evenly: "justify-evenly"
2942
+ };
2943
+ var Stack = ({
2944
+ direction = "vertical",
2945
+ gap = "md",
2946
+ align = "stretch",
2947
+ justify = "start",
2948
+ wrap = false,
2949
+ reverse = false,
2950
+ flex = false,
2951
+ className,
2952
+ style,
2953
+ children,
2954
+ as: Component2 = "div",
2955
+ onClick,
2956
+ onKeyDown,
2957
+ role,
2958
+ tabIndex,
2959
+ action,
2960
+ actionPayload,
2961
+ responsive = false
2962
+ }) => {
2963
+ const eventBus = useEventBus();
2964
+ const handleClick = (e) => {
2965
+ if (action) {
2966
+ eventBus.emit(`UI:${action}`, actionPayload ?? {});
2967
+ }
2968
+ onClick?.(e);
2969
+ };
2970
+ const isHorizontal = direction === "horizontal";
2971
+ const directionClass = responsive && isHorizontal ? reverse ? "flex-col-reverse md:flex-row-reverse" : "flex-col md:flex-row" : isHorizontal ? reverse ? "flex-row-reverse" : "flex-row" : reverse ? "flex-col-reverse" : "flex-col";
2972
+ const Comp = Component2;
2973
+ return /* @__PURE__ */ jsxRuntime.jsx(
2974
+ Comp,
2975
+ {
2976
+ className: cn(
2977
+ "flex",
2978
+ directionClass,
2979
+ gapStyles[gap],
2980
+ alignStyles[align],
2981
+ justifyStyles[justify],
2982
+ wrap && "flex-wrap",
2983
+ flex && "flex-1",
2984
+ className
2985
+ ),
2986
+ style,
2987
+ onClick: action || onClick ? handleClick : void 0,
2988
+ onKeyDown,
2989
+ role,
2990
+ tabIndex,
2991
+ children
2992
+ }
2993
+ );
2994
+ };
2995
+ var VStack = (props) => /* @__PURE__ */ jsxRuntime.jsx(Stack, { direction: "vertical", ...props });
2996
+ var HStack = (props) => /* @__PURE__ */ jsxRuntime.jsx(Stack, { direction: "horizontal", ...props });
2997
+ var variantStyles = {
2998
+ h1: "text-4xl font-bold tracking-tight text-[var(--color-foreground)]",
2999
+ h2: "text-3xl font-bold tracking-tight text-[var(--color-foreground)]",
3000
+ h3: "text-2xl font-bold text-[var(--color-foreground)]",
3001
+ h4: "text-xl font-bold text-[var(--color-foreground)]",
3002
+ h5: "text-lg font-bold text-[var(--color-foreground)]",
3003
+ h6: "text-base font-bold text-[var(--color-foreground)]",
3004
+ heading: "text-2xl font-bold text-[var(--color-foreground)]",
3005
+ subheading: "text-lg font-semibold text-[var(--color-foreground)]",
3006
+ body1: "text-base font-normal text-[var(--color-foreground)]",
3007
+ body2: "text-sm font-normal text-[var(--color-foreground)]",
3008
+ body: "text-base font-normal text-[var(--color-foreground)]",
3009
+ caption: "text-xs font-normal text-[var(--color-muted-foreground)]",
3010
+ overline: "text-xs uppercase tracking-wide font-bold text-[var(--color-muted-foreground)]",
3011
+ small: "text-sm font-normal text-[var(--color-foreground)]",
3012
+ large: "text-lg font-medium text-[var(--color-foreground)]",
3013
+ label: "text-sm font-medium text-[var(--color-foreground)]"
3014
+ };
3015
+ var colorStyles = {
3016
+ primary: "text-[var(--color-foreground)]",
3017
+ secondary: "text-[var(--color-muted-foreground)]",
3018
+ muted: "text-[var(--color-muted-foreground)]",
3019
+ error: "text-[var(--color-error)]",
3020
+ success: "text-[var(--color-success)]",
3021
+ warning: "text-[var(--color-warning)]",
3022
+ inherit: "text-inherit"
3023
+ };
3024
+ var weightStyles = {
3025
+ light: "font-light",
3026
+ normal: "font-normal",
3027
+ medium: "font-medium",
3028
+ semibold: "font-semibold",
3029
+ bold: "font-bold"
3030
+ };
3031
+ var defaultElements = {
3032
+ h1: "h1",
3033
+ h2: "h2",
3034
+ h3: "h3",
3035
+ h4: "h4",
3036
+ h5: "h5",
3037
+ h6: "h6",
3038
+ heading: "h2",
3039
+ subheading: "h3",
3040
+ body1: "p",
3041
+ body2: "p",
3042
+ body: "p",
3043
+ caption: "span",
3044
+ overline: "span",
3045
+ small: "span",
3046
+ large: "p",
3047
+ label: "span"
3048
+ };
3049
+ var typographySizeStyles = {
3050
+ xs: "text-xs",
3051
+ sm: "text-sm",
3052
+ md: "text-base",
3053
+ lg: "text-lg",
3054
+ xl: "text-xl",
3055
+ "2xl": "text-2xl",
3056
+ "3xl": "text-3xl"
3057
+ };
3058
+ var overflowStyles2 = {
3059
+ visible: "overflow-visible",
3060
+ hidden: "overflow-hidden",
3061
+ wrap: "break-words overflow-hidden",
3062
+ "clamp-2": "overflow-hidden line-clamp-2",
3063
+ "clamp-3": "overflow-hidden line-clamp-3"
3064
+ };
3065
+ var Typography = ({
3066
+ variant: variantProp,
3067
+ level,
3068
+ color = "primary",
3069
+ align,
3070
+ weight,
3071
+ size,
3072
+ truncate = false,
3073
+ overflow,
3074
+ as,
3075
+ id,
3076
+ className,
3077
+ style,
3078
+ content,
3079
+ children
3080
+ }) => {
3081
+ const variant = variantProp ?? (level ? `h${level}` : "body1");
3082
+ const Component2 = as || defaultElements[variant];
3083
+ const Comp = Component2;
3084
+ return /* @__PURE__ */ jsxRuntime.jsx(
3085
+ Comp,
3086
+ {
3087
+ id,
3088
+ className: cn(
3089
+ variantStyles[variant],
3090
+ colorStyles[color],
3091
+ weight && weightStyles[weight],
3092
+ size && typographySizeStyles[size],
3093
+ align && `text-${align}`,
3094
+ truncate && "truncate overflow-hidden text-ellipsis",
3095
+ overflow && overflowStyles2[overflow],
3096
+ className
3097
+ ),
3098
+ style,
3099
+ children: children ?? content
3100
+ }
3101
+ );
3102
+ };
3103
+ Typography.displayName = "Typography";
3104
+ function GameCanvas3DBattleTemplate({
3105
+ entity,
3106
+ cameraMode = "perspective",
3107
+ showGrid = true,
3108
+ shadows = true,
3109
+ backgroundColor = "#2a1a1a",
3110
+ tileClickEvent,
3111
+ unitClickEvent,
3112
+ unitAttackEvent,
3113
+ unitMoveEvent,
3114
+ endTurnEvent,
3115
+ exitEvent,
3116
+ selectedUnitId,
3117
+ validMoves,
3118
+ attackTargets,
3119
+ className
3120
+ }) {
3121
+ const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
3122
+ if (!resolved) return null;
3123
+ return /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: cn("game-canvas-3d-battle-template", className), children: [
3124
+ /* @__PURE__ */ jsxRuntime.jsx(
3125
+ GameCanvas3D,
3126
+ {
3127
+ tiles: resolved.tiles,
3128
+ units: resolved.units,
3129
+ features: resolved.features,
3130
+ cameraMode,
3131
+ showGrid,
3132
+ showCoordinates: false,
3133
+ showTileInfo: false,
3134
+ shadows,
3135
+ backgroundColor,
3136
+ tileClickEvent,
3137
+ unitClickEvent,
3138
+ selectedUnitId,
3139
+ validMoves,
3140
+ attackTargets,
3141
+ className: "game-canvas-3d-battle-template__canvas"
3142
+ }
3143
+ ),
3144
+ resolved.currentTurn && /* @__PURE__ */ jsxRuntime.jsxs(
3145
+ HStack,
3146
+ {
3147
+ gap: "sm",
3148
+ align: "center",
3149
+ className: cn("battle-template__turn-indicator", `battle-template__turn-indicator--${resolved.currentTurn}`),
3150
+ children: [
3151
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "body", className: "turn-indicator__label", children: resolved.currentTurn === "player" ? "Your Turn" : "Enemy's Turn" }),
3152
+ resolved.round && /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", className: "turn-indicator__round", children: [
3153
+ "Round ",
3154
+ resolved.round
3155
+ ] })
3156
+ ]
3157
+ }
3158
+ )
3159
+ ] });
3160
+ }
3161
+ GameCanvas3DBattleTemplate.displayName = "GameCanvas3DBattleTemplate";
3162
+ function GameCanvas3DCastleTemplate({
3163
+ entity,
3164
+ cameraMode = "isometric",
3165
+ showGrid = true,
3166
+ shadows = true,
3167
+ backgroundColor = "#1e1e2e",
3168
+ buildingClickEvent,
3169
+ unitClickEvent,
3170
+ buildEvent,
3171
+ recruitEvent,
3172
+ exitEvent,
3173
+ selectedBuildingId,
3174
+ selectedTileIds = [],
3175
+ availableBuildSites,
3176
+ showHeader = true,
3177
+ className
3178
+ }) {
3179
+ const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
3180
+ if (!resolved) return null;
3181
+ return /* @__PURE__ */ jsxRuntime.jsxs(VStack, { className: cn("game-canvas-3d-castle-template", className), children: [
3182
+ showHeader && resolved.name && /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "md", align: "center", className: "castle-template__header", children: [
3183
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h2", className: "header__name", children: resolved.name }),
3184
+ resolved.level && /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", className: "header__level", children: [
3185
+ "Level ",
3186
+ resolved.level
3187
+ ] }),
3188
+ resolved.owner && /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", color: "muted", className: "header__owner", children: resolved.owner })
3189
+ ] }),
3190
+ /* @__PURE__ */ jsxRuntime.jsx(
3191
+ GameCanvas3D,
3192
+ {
3193
+ tiles: resolved.tiles,
3194
+ units: resolved.units,
3195
+ features: resolved.features,
3196
+ cameraMode,
3197
+ showGrid,
3198
+ showCoordinates: false,
3199
+ showTileInfo: false,
3200
+ shadows,
3201
+ backgroundColor,
3202
+ featureClickEvent: buildingClickEvent,
3203
+ unitClickEvent,
3204
+ selectedTileIds,
3205
+ validMoves: availableBuildSites,
3206
+ className: "game-canvas-3d-castle-template__canvas"
3207
+ }
3208
+ ),
3209
+ resolved.units.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "sm", align: "center", className: "castle-template__garrison-info", children: [
3210
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", className: "garrison-info__label", children: "Garrison:" }),
3211
+ /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", weight: "bold", className: "garrison-info__count", children: [
3212
+ resolved.units.length,
3213
+ " units"
3214
+ ] })
3215
+ ] })
3216
+ ] });
3217
+ }
3218
+ GameCanvas3DCastleTemplate.displayName = "GameCanvas3DCastleTemplate";
3219
+ function GameCanvas3DWorldMapTemplate({
3220
+ entity,
3221
+ cameraMode = "isometric",
3222
+ showGrid = true,
3223
+ showCoordinates = true,
3224
+ showTileInfo = true,
3225
+ shadows = true,
3226
+ backgroundColor = "#1a1a2e",
3227
+ tileClickEvent,
3228
+ unitClickEvent,
3229
+ featureClickEvent,
3230
+ tileHoverEvent,
3231
+ tileLeaveEvent,
3232
+ cameraChangeEvent,
3233
+ selectedUnitId,
3234
+ validMoves,
3235
+ attackTargets,
3236
+ className
3237
+ }) {
3238
+ const resolved = entity && typeof entity === "object" && !Array.isArray(entity) ? entity : void 0;
3239
+ if (!resolved) return null;
3240
+ return /* @__PURE__ */ jsxRuntime.jsx(
3241
+ GameCanvas3D,
3242
+ {
3243
+ tiles: resolved.tiles,
3244
+ units: resolved.units,
3245
+ features: resolved.features,
3246
+ cameraMode,
3247
+ showGrid,
3248
+ showCoordinates,
3249
+ showTileInfo,
3250
+ shadows,
3251
+ backgroundColor,
3252
+ tileClickEvent,
3253
+ unitClickEvent,
3254
+ featureClickEvent,
3255
+ tileHoverEvent,
3256
+ tileLeaveEvent,
3257
+ cameraChangeEvent,
3258
+ selectedUnitId,
3259
+ validMoves,
3260
+ attackTargets,
3261
+ className
3262
+ }
3263
+ );
3264
+ }
3265
+ GameCanvas3DWorldMapTemplate.displayName = "GameCanvas3DWorldMapTemplate";
2203
3266
  var DEFAULT_CONFIG = {
2204
3267
  cellSize: 1,
2205
3268
  offsetX: 0,
@@ -2491,6 +3554,10 @@ exports.Canvas3DErrorBoundary = Canvas3DErrorBoundary;
2491
3554
  exports.Canvas3DLoadingState = Canvas3DLoadingState;
2492
3555
  exports.FeatureRenderer = FeatureRenderer;
2493
3556
  exports.FeatureRenderer3D = FeatureRenderer3D;
3557
+ exports.GameCanvas3D = GameCanvas3D;
3558
+ exports.GameCanvas3DBattleTemplate = GameCanvas3DBattleTemplate;
3559
+ exports.GameCanvas3DCastleTemplate = GameCanvas3DCastleTemplate;
3560
+ exports.GameCanvas3DWorldMapTemplate = GameCanvas3DWorldMapTemplate;
2494
3561
  exports.Lighting3D = Lighting3D;
2495
3562
  exports.ModelLoader = ModelLoader;
2496
3563
  exports.PhysicsObject3D = PhysicsObject3D;