@almadar/ui 2.16.0 → 2.17.0

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