cubeforge 0.5.2 → 0.6.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.
package/dist/index.d.ts CHANGED
@@ -503,7 +503,48 @@ interface SquashStretchProps {
503
503
  }
504
504
  declare function SquashStretch({ intensity, recovery }: SquashStretchProps): null;
505
505
 
506
- type ParticlePreset = 'explosion' | 'spark' | 'smoke' | 'coinPickup' | 'jumpDust';
506
+ type ParticlePreset = 'explosion' | 'spark' | 'smoke' | 'coinPickup' | 'jumpDust' | 'fire' | 'magic' | 'rain' | 'snow' | 'confetti' | 'sparkle' | 'heal' | 'damage' | 'pickup' | 'fountain' | 'trail';
507
+ interface ParticleEmitterConfig {
508
+ rate?: number;
509
+ speed?: number;
510
+ spread?: number;
511
+ angle?: number;
512
+ particleLife?: number;
513
+ particleSize?: number;
514
+ color?: string;
515
+ gravity?: number;
516
+ maxParticles?: number;
517
+ /** Colors to interpolate through over particle lifetime. */
518
+ colorOverLife?: string[];
519
+ /** Start/end size ratios for per-particle size curve (end is multiplier of particleSize). */
520
+ sizeOverLife?: {
521
+ start: number;
522
+ end: number;
523
+ };
524
+ /** Blend mode for the emitter (additive for glows, normal for solid). */
525
+ blendMode?: 'normal' | 'additive';
526
+ /** Particle shape: 'soft' gradient, 'circle' hard disc, 'square' quad. */
527
+ particleShape?: 'soft' | 'circle' | 'square';
528
+ }
529
+ /**
530
+ * Ready-made particle configurations. Pass via the `preset` prop on
531
+ * `<ParticleEmitter>`:
532
+ *
533
+ * ```tsx
534
+ * <ParticleEmitter preset="fire" />
535
+ * <ParticleEmitter preset="explosion" color="#8bc34a" /> // overrides
536
+ * ```
537
+ *
538
+ * Individual props always override preset values — presets are defaults.
539
+ *
540
+ * ## Catalog
541
+ *
542
+ * Action / combat: `explosion`, `spark`, `damage`, `heal`, `magic`
543
+ * Environment: `fire`, `smoke`, `rain`, `snow`, `fountain`
544
+ * UI / reward: `coinPickup`, `pickup`, `sparkle`, `confetti`
545
+ * Character: `jumpDust`, `trail`
546
+ */
547
+ declare const PARTICLE_PRESETS: Record<ParticlePreset, ParticleEmitterConfig>;
507
548
 
508
549
  interface ParticleEmitterProps {
509
550
  active?: boolean;
@@ -3100,6 +3141,143 @@ declare function deleteSavedScene(key: string): boolean;
3100
3141
  */
3101
3142
  declare function listSavedScenes(prefix?: string): string[];
3102
3143
 
3144
+ type HUDPosition = 'topLeft' | 'topCenter' | 'topRight' | 'centerLeft' | 'center' | 'centerRight' | 'bottomLeft' | 'bottomCenter' | 'bottomRight';
3145
+ interface HUDProps {
3146
+ /**
3147
+ * Whether the HUD fades out during scene transitions. Default true. When a
3148
+ * `SceneTransitionOverlay` is active or `usePause` is paused, the HUD will
3149
+ * dim to `dimmedOpacity` to get out of the way.
3150
+ */
3151
+ dimDuringTransitions?: boolean;
3152
+ /** Opacity when dimmed. Default 0.25. */
3153
+ dimmedOpacity?: number;
3154
+ /** Whether the HUD is currently visible. Default true. */
3155
+ visible?: boolean;
3156
+ /** Whether to apply CSS `env(safe-area-inset-*)` padding. Default true on touch. */
3157
+ safeArea?: boolean;
3158
+ /** Extra padding around all zones in CSS pixels. Default 12. */
3159
+ padding?: number;
3160
+ /** Additional style for the HUD root. */
3161
+ style?: CSSProperties;
3162
+ /** Additional CSS class. */
3163
+ className?: string;
3164
+ children?: ReactNode;
3165
+ }
3166
+ /**
3167
+ * Heads-up-display root. Positions a full-screen overlay over the game canvas
3168
+ * and provides layout zones via {@link HUDZone}. Integrates automatically with
3169
+ * CubeForge primitives:
3170
+ *
3171
+ * - Fades out during scene transitions (via `dimDuringTransitions`)
3172
+ * - Respects `prefers-reduced-motion` for fade timing
3173
+ * - Honors mobile safe-area insets when `safeArea` is on
3174
+ *
3175
+ * @example
3176
+ * ```tsx
3177
+ * <Stage>
3178
+ * <HUD>
3179
+ * <HUDZone position="topLeft">
3180
+ * <HUDBar value={hp} max={100} label="HP" color="#ef5350" />
3181
+ * </HUDZone>
3182
+ * <HUDZone position="topRight">
3183
+ * <span>Score: {score}</span>
3184
+ * </HUDZone>
3185
+ * <HUDZone position="bottomCenter">
3186
+ * <button onClick={onJump}>Jump</button>
3187
+ * </HUDZone>
3188
+ * </HUD>
3189
+ * </Stage>
3190
+ * ```
3191
+ */
3192
+ declare function HUD({ dimDuringTransitions, dimmedOpacity, visible, safeArea, padding, style, className, children, }: HUDProps): react_jsx_runtime.JSX.Element;
3193
+ interface HUDZoneProps {
3194
+ /** Where to anchor this zone. Default 'topLeft'. */
3195
+ position?: HUDPosition;
3196
+ /** Direction children lay out. Default 'row' for top/bottom, 'column' for left/right/center. */
3197
+ direction?: 'row' | 'column';
3198
+ /** Gap between children in CSS pixels. Default 8. */
3199
+ gap?: number;
3200
+ /** Allow pointer events on zone contents (buttons, sliders). Default true for this zone. */
3201
+ interactive?: boolean;
3202
+ /** Override style for the zone. */
3203
+ style?: CSSProperties;
3204
+ /** Additional CSS class. */
3205
+ className?: string;
3206
+ children?: ReactNode;
3207
+ }
3208
+ /**
3209
+ * Positioned content zone inside a {@link HUD}. Nine anchors available
3210
+ * (topLeft, topCenter, topRight, centerLeft, center, centerRight, bottomLeft,
3211
+ * bottomCenter, bottomRight). Honors the parent HUD's padding and safe-area.
3212
+ */
3213
+ declare function HUDZone({ position, direction, gap, interactive, style, className, children, }: HUDZoneProps): react_jsx_runtime.JSX.Element;
3214
+ interface HUDBarProps {
3215
+ /** Current value. */
3216
+ value: number;
3217
+ /** Maximum value. */
3218
+ max: number;
3219
+ /** Optional label shown on top of the bar. */
3220
+ label?: string;
3221
+ /** Bar color. Default '#4fc3f7'. */
3222
+ color?: string;
3223
+ /** Track (empty) color. Default 'rgba(255,255,255,0.08)'. */
3224
+ trackColor?: string;
3225
+ /** Bar width in CSS pixels. Default 180. */
3226
+ width?: number;
3227
+ /** Bar height in CSS pixels. Default 14. */
3228
+ height?: number;
3229
+ /** Show numeric value as "value / max". Default true. */
3230
+ showValue?: boolean;
3231
+ /** Reverse direction: fills right→left. Default false. */
3232
+ rtl?: boolean;
3233
+ /** Round the bar corners. Default true. */
3234
+ rounded?: boolean;
3235
+ /** Smooth transition when value changes. Default true. */
3236
+ animated?: boolean;
3237
+ /** Additional style. */
3238
+ style?: CSSProperties;
3239
+ }
3240
+ /**
3241
+ * A simple value bar for HP, stamina, XP, progress, etc. Meant to be dropped
3242
+ * inside a {@link HUDZone} without any extra styling. Animates smoothly by
3243
+ * default; respects `prefers-reduced-motion` transparently via CSS.
3244
+ *
3245
+ * @example
3246
+ * ```tsx
3247
+ * <HUDZone position="topLeft">
3248
+ * <HUDBar value={hp} max={100} label="HP" color="#ef5350" />
3249
+ * <HUDBar value={mana} max={50} label="MP" color="#4fc3f7" />
3250
+ * </HUDZone>
3251
+ * ```
3252
+ */
3253
+ declare function HUDBar({ value, max, label, color, trackColor, width, height, showValue, rtl, rounded, animated, style, }: HUDBarProps): react_jsx_runtime.JSX.Element;
3254
+ interface HUDCounterProps {
3255
+ /** Numeric value to display. */
3256
+ value: number;
3257
+ /** Optional icon or emoji shown before the value. */
3258
+ icon?: ReactNode;
3259
+ /** Optional label shown after the value. */
3260
+ label?: string;
3261
+ /** Flash briefly when the value changes. Default true. */
3262
+ pulse?: boolean;
3263
+ /** Value color. Default '#fff'. */
3264
+ color?: string;
3265
+ /** Font size in CSS pixels. Default 18. */
3266
+ fontSize?: number;
3267
+ style?: CSSProperties;
3268
+ }
3269
+ /**
3270
+ * A numeric counter widget for score, coins, ammo, etc.
3271
+ *
3272
+ * @example
3273
+ * ```tsx
3274
+ * <HUDZone position="topRight">
3275
+ * <HUDCounter icon="🪙" value={coins} />
3276
+ * </HUDZone>
3277
+ * ```
3278
+ */
3279
+ declare function HUDCounter({ value, icon, label, pulse, color, fontSize, style, }: HUDCounterProps): react_jsx_runtime.JSX.Element;
3280
+
3103
3281
  declare function playClip(world: ECSWorld, entityId: EntityId, clipName: string): void;
3104
3282
  declare function setAnimationState(world: ECSWorld, entityId: EntityId, stateName: string): void;
3105
3283
  declare function setAnimatorParam(world: ECSWorld, entityId: EntityId, name: string, value: AnimatorParamValue): void;
@@ -3206,4 +3384,4 @@ declare function useRemotePlayer(config: {
3206
3384
  opts?: RemotePlayerOptions;
3207
3385
  }): RemotePlayerControls;
3208
3386
 
3209
- export { A11yNode, type A11yNodeProps, type AccessibilityControls, AnimatedSprite, type AnimatedSpriteProps, Animation, type AnimationSet, Animator, AssetLoader, type BoundInputMap, BoxCollider, Camera2D, type CameraControls, type CameraLookaheadOptions, CameraZone, CapsuleCollider, Checkpoint, Circle, CircleCollider, type ComboDetectorResult, CompoundCollider, ConvexCollider, type CoordinateHelpers, type CoroutineControls, type CoroutineFactory, type CoroutineYield, type DraggableControls, type DraggableOptions, type DroppableControls, type DroppableOptions, EditableText, type EditableTextProps, Entity, type ExportOptions, FocusRing, type FocusRingProps, type FocusableOptions, Game, type GameControls, type GamepadState, type GestureHandlers, type GestureOptions, Gradient, type GridCell, type GridControls, type GridOptions, type HMRControls, HalfSpaceCollider, HeightFieldCollider, type HistoryControls, type HistoryOptions, type HoverableControls, type HoverableOptions, type InputContextControls, Joint, type KeyboardFocusControls, Line, Mask, MovingPlatform, type NetworkSyncOptions, NineSlice, ParallaxLayer, ParticleEmitter, type ParticlePreset, type PauseControls, type PinchEvent, Polygon, type PreloadState, type ProfilerData, type RemotePlayerControls, type RemotePlayerOptions, RigidBody, type SceneManagerControls, type SceneSaveOptions, type SceneTransitionControls, SceneTransitionOverlay, ScreenFlash, type ScreenFlashHandle, Script, SegmentCollider, type SelectOptions, Selection, type SelectionControls, type SelectionProps, type SnapControls, type SnapOptions, type SnapResult, type SnapshotControls, Sprite, type SpriteAtlas, SquashStretch, type SquashStretchControls, Stage, type SwipeEvent, Text, type TiledLayer, type TiledObject, Tilemap, type TimerControls, type TouchControls, Trail, Transform, TransformHandles, type TransformHandlesProps, type TransitionEffect, TriMeshCollider, TriangleCollider, type TurnSystemControls, type TurnSystemOptions, VectorPath, type VectorPathProps, type VirtualInputState, VirtualJoystick, type VirtualJoystickProps, type Waypoint, World, createAtlas, defineAnimations, definePrefab, deleteSavedScene, downloadCanvas, exportToBlob, exportToDataURL, listSavedScenes, loadScene, loadSceneFromLocalStorage, playClip, saveScene, saveSceneToLocalStorage, setAnimationState, setAnimatorParam, useAccessibility, useAudioListener, useCamera, useCameraLookahead, useComboDetector, useCoordinates, useCoroutine, useDestroyEntity, useDraggable, useDroppable, useEntity, useEvent, useEvents, useFocusable, useGame, useGamepad, useGamepadHaptics, useGestures, useGrid, useHMR, useHistory, useHitstop, useHoverable, useInput, useInputBuffer, useInputContext, useInputMap, useInputRecorder, useKeyboardFocus, useLocalMultiplayer, useNetworkSync, useParent, usePause, usePlayerInput, usePostProcess, usePreload, useProfiler, useRemotePlayer, useSceneManager, useSceneTransition, useSelection, useSnap, useSnapshot, useSquashStretch, useTimer, useTouch, useTurnSystem, useVirtualInput, useWebGLPostProcess, useWorldQuery, wait, waitFrames, waitUntil };
3387
+ export { A11yNode, type A11yNodeProps, type AccessibilityControls, AnimatedSprite, type AnimatedSpriteProps, Animation, type AnimationSet, Animator, AssetLoader, type BoundInputMap, BoxCollider, Camera2D, type CameraControls, type CameraLookaheadOptions, CameraZone, CapsuleCollider, Checkpoint, Circle, CircleCollider, type ComboDetectorResult, CompoundCollider, ConvexCollider, type CoordinateHelpers, type CoroutineControls, type CoroutineFactory, type CoroutineYield, type DraggableControls, type DraggableOptions, type DroppableControls, type DroppableOptions, EditableText, type EditableTextProps, Entity, type ExportOptions, FocusRing, type FocusRingProps, type FocusableOptions, Game, type GameControls, type GamepadState, type GestureHandlers, type GestureOptions, Gradient, type GridCell, type GridControls, type GridOptions, type HMRControls, HUD, HUDBar, type HUDBarProps, HUDCounter, type HUDCounterProps, type HUDPosition, type HUDProps, HUDZone, type HUDZoneProps, HalfSpaceCollider, HeightFieldCollider, type HistoryControls, type HistoryOptions, type HoverableControls, type HoverableOptions, type InputContextControls, Joint, type KeyboardFocusControls, Line, Mask, MovingPlatform, type NetworkSyncOptions, NineSlice, PARTICLE_PRESETS, ParallaxLayer, ParticleEmitter, type ParticleEmitterConfig, type ParticlePreset, type PauseControls, type PinchEvent, Polygon, type PreloadState, type ProfilerData, type RemotePlayerControls, type RemotePlayerOptions, RigidBody, type SceneManagerControls, type SceneSaveOptions, type SceneTransitionControls, SceneTransitionOverlay, ScreenFlash, type ScreenFlashHandle, Script, SegmentCollider, type SelectOptions, Selection, type SelectionControls, type SelectionProps, type SnapControls, type SnapOptions, type SnapResult, type SnapshotControls, Sprite, type SpriteAtlas, SquashStretch, type SquashStretchControls, Stage, type SwipeEvent, Text, type TiledLayer, type TiledObject, Tilemap, type TimerControls, type TouchControls, Trail, Transform, TransformHandles, type TransformHandlesProps, type TransitionEffect, TriMeshCollider, TriangleCollider, type TurnSystemControls, type TurnSystemOptions, VectorPath, type VectorPathProps, type VirtualInputState, VirtualJoystick, type VirtualJoystickProps, type Waypoint, World, createAtlas, defineAnimations, definePrefab, deleteSavedScene, downloadCanvas, exportToBlob, exportToDataURL, listSavedScenes, loadScene, loadSceneFromLocalStorage, playClip, saveScene, saveSceneToLocalStorage, setAnimationState, setAnimatorParam, useAccessibility, useAudioListener, useCamera, useCameraLookahead, useComboDetector, useCoordinates, useCoroutine, useDestroyEntity, useDraggable, useDroppable, useEntity, useEvent, useEvents, useFocusable, useGame, useGamepad, useGamepadHaptics, useGestures, useGrid, useHMR, useHistory, useHitstop, useHoverable, useInput, useInputBuffer, useInputContext, useInputMap, useInputRecorder, useKeyboardFocus, useLocalMultiplayer, useNetworkSync, useParent, usePause, usePlayerInput, usePostProcess, usePreload, useProfiler, useRemotePlayer, useSceneManager, useSceneTransition, useSelection, useSnap, useSnapshot, useSquashStretch, useTimer, useTouch, useTurnSystem, useVirtualInput, useWebGLPostProcess, useWorldQuery, wait, waitFrames, waitUntil };
package/dist/index.js CHANGED
@@ -7829,6 +7829,7 @@ import { useEffect as useEffect28, useContext as useContext19 } from "react";
7829
7829
 
7830
7830
  // src/components/particlePresets.ts
7831
7831
  var PARTICLE_PRESETS = {
7832
+ // ── Action / combat ──
7832
7833
  explosion: {
7833
7834
  rate: 60,
7834
7835
  speed: 200,
@@ -7837,8 +7838,12 @@ var PARTICLE_PRESETS = {
7837
7838
  particleLife: 0.5,
7838
7839
  particleSize: 6,
7839
7840
  color: "#ff6b35",
7841
+ colorOverLife: ["#fff3b0", "#ff6b35", "#6d4c41"],
7842
+ sizeOverLife: { start: 1.4, end: 0 },
7840
7843
  gravity: 300,
7841
- maxParticles: 80
7844
+ maxParticles: 80,
7845
+ blendMode: "additive",
7846
+ particleShape: "soft"
7842
7847
  },
7843
7848
  spark: {
7844
7849
  rate: 40,
@@ -7849,7 +7854,70 @@ var PARTICLE_PRESETS = {
7849
7854
  particleSize: 3,
7850
7855
  color: "#ffd54f",
7851
7856
  gravity: 400,
7852
- maxParticles: 50
7857
+ maxParticles: 50,
7858
+ blendMode: "additive",
7859
+ particleShape: "soft"
7860
+ },
7861
+ damage: {
7862
+ rate: 45,
7863
+ speed: 120,
7864
+ spread: Math.PI * 2,
7865
+ angle: 0,
7866
+ particleLife: 0.35,
7867
+ particleSize: 4,
7868
+ color: "#ef5350",
7869
+ colorOverLife: ["#ffab91", "#ef5350", "#4a148c"],
7870
+ sizeOverLife: { start: 1, end: 0.2 },
7871
+ gravity: 150,
7872
+ maxParticles: 40,
7873
+ blendMode: "normal",
7874
+ particleShape: "circle"
7875
+ },
7876
+ heal: {
7877
+ rate: 22,
7878
+ speed: 50,
7879
+ spread: Math.PI / 2,
7880
+ angle: -Math.PI / 2,
7881
+ particleLife: 0.9,
7882
+ particleSize: 5,
7883
+ color: "#8bc34a",
7884
+ colorOverLife: ["#e8f5e9", "#8bc34a"],
7885
+ sizeOverLife: { start: 0.5, end: 1.2 },
7886
+ gravity: -60,
7887
+ maxParticles: 30,
7888
+ blendMode: "additive",
7889
+ particleShape: "soft"
7890
+ },
7891
+ magic: {
7892
+ rate: 32,
7893
+ speed: 80,
7894
+ spread: Math.PI * 2,
7895
+ angle: 0,
7896
+ particleLife: 1.1,
7897
+ particleSize: 5,
7898
+ color: "#ba68c8",
7899
+ colorOverLife: ["#e1bee7", "#ba68c8", "#4a148c"],
7900
+ sizeOverLife: { start: 1, end: 0 },
7901
+ gravity: -20,
7902
+ maxParticles: 60,
7903
+ blendMode: "additive",
7904
+ particleShape: "soft"
7905
+ },
7906
+ // ── Environment ──
7907
+ fire: {
7908
+ rate: 40,
7909
+ speed: 60,
7910
+ spread: 0.6,
7911
+ angle: -Math.PI / 2,
7912
+ particleLife: 0.7,
7913
+ particleSize: 7,
7914
+ color: "#ff9800",
7915
+ colorOverLife: ["#ffe082", "#ff9800", "#6d4c41", "#37474f"],
7916
+ sizeOverLife: { start: 1.2, end: 0.3 },
7917
+ gravity: -120,
7918
+ maxParticles: 70,
7919
+ blendMode: "additive",
7920
+ particleShape: "soft"
7853
7921
  },
7854
7922
  smoke: {
7855
7923
  rate: 15,
@@ -7859,9 +7927,54 @@ var PARTICLE_PRESETS = {
7859
7927
  particleLife: 1.2,
7860
7928
  particleSize: 10,
7861
7929
  color: "#90a4ae",
7930
+ colorOverLife: ["#90a4ae", "rgba(55,71,79,0)"],
7931
+ sizeOverLife: { start: 0.5, end: 1.8 },
7862
7932
  gravity: -20,
7863
- maxParticles: 40
7933
+ maxParticles: 40,
7934
+ blendMode: "normal",
7935
+ particleShape: "soft"
7936
+ },
7937
+ rain: {
7938
+ rate: 80,
7939
+ speed: 350,
7940
+ spread: 0.05,
7941
+ angle: Math.PI / 2,
7942
+ particleLife: 0.8,
7943
+ particleSize: 2,
7944
+ color: "#81d4fa",
7945
+ gravity: 900,
7946
+ maxParticles: 300,
7947
+ blendMode: "normal",
7948
+ particleShape: "square"
7949
+ },
7950
+ snow: {
7951
+ rate: 20,
7952
+ speed: 40,
7953
+ spread: 0.4,
7954
+ angle: Math.PI / 2,
7955
+ particleLife: 4,
7956
+ particleSize: 3,
7957
+ color: "#ffffff",
7958
+ gravity: 20,
7959
+ maxParticles: 150,
7960
+ blendMode: "normal",
7961
+ particleShape: "circle"
7962
+ },
7963
+ fountain: {
7964
+ rate: 50,
7965
+ speed: 300,
7966
+ spread: 0.3,
7967
+ angle: -Math.PI / 2,
7968
+ particleLife: 1.5,
7969
+ particleSize: 4,
7970
+ color: "#4fc3f7",
7971
+ colorOverLife: ["#b3e5fc", "#4fc3f7", "#0277bd"],
7972
+ gravity: 500,
7973
+ maxParticles: 200,
7974
+ blendMode: "additive",
7975
+ particleShape: "soft"
7864
7976
  },
7977
+ // ── UI / reward ──
7865
7978
  coinPickup: {
7866
7979
  rate: 30,
7867
7980
  speed: 80,
@@ -7871,8 +7984,54 @@ var PARTICLE_PRESETS = {
7871
7984
  particleSize: 4,
7872
7985
  color: "#ffd700",
7873
7986
  gravity: 200,
7874
- maxParticles: 20
7987
+ maxParticles: 20,
7988
+ blendMode: "additive",
7989
+ particleShape: "soft"
7990
+ },
7991
+ pickup: {
7992
+ rate: 35,
7993
+ speed: 70,
7994
+ spread: Math.PI * 2,
7995
+ angle: 0,
7996
+ particleLife: 0.5,
7997
+ particleSize: 5,
7998
+ color: "#4fc3f7",
7999
+ colorOverLife: ["#ffffff", "#4fc3f7"],
8000
+ sizeOverLife: { start: 1, end: 0 },
8001
+ gravity: -80,
8002
+ maxParticles: 24,
8003
+ blendMode: "additive",
8004
+ particleShape: "soft"
8005
+ },
8006
+ sparkle: {
8007
+ rate: 14,
8008
+ speed: 40,
8009
+ spread: Math.PI * 2,
8010
+ angle: 0,
8011
+ particleLife: 0.8,
8012
+ particleSize: 3,
8013
+ color: "#fffde7",
8014
+ sizeOverLife: { start: 0, end: 1 },
8015
+ gravity: 0,
8016
+ maxParticles: 30,
8017
+ blendMode: "additive",
8018
+ particleShape: "soft"
7875
8019
  },
8020
+ confetti: {
8021
+ rate: 70,
8022
+ speed: 280,
8023
+ spread: Math.PI / 3,
8024
+ angle: -Math.PI / 2,
8025
+ particleLife: 2.2,
8026
+ particleSize: 5,
8027
+ color: "#ffc107",
8028
+ colorOverLife: ["#f44336", "#ffc107", "#4caf50", "#2196f3", "#9c27b0"],
8029
+ gravity: 400,
8030
+ maxParticles: 150,
8031
+ blendMode: "normal",
8032
+ particleShape: "square"
8033
+ },
8034
+ // ── Character ──
7876
8035
  jumpDust: {
7877
8036
  rate: 25,
7878
8037
  speed: 60,
@@ -7882,7 +8041,23 @@ var PARTICLE_PRESETS = {
7882
8041
  particleSize: 5,
7883
8042
  color: "#b0bec5",
7884
8043
  gravity: 80,
7885
- maxParticles: 20
8044
+ maxParticles: 20,
8045
+ blendMode: "normal",
8046
+ particleShape: "soft"
8047
+ },
8048
+ trail: {
8049
+ rate: 30,
8050
+ speed: 8,
8051
+ spread: 0.2,
8052
+ angle: Math.PI / 2,
8053
+ particleLife: 0.5,
8054
+ particleSize: 4,
8055
+ color: "#4fc3f7",
8056
+ sizeOverLife: { start: 1, end: 0 },
8057
+ gravity: 0,
8058
+ maxParticles: 40,
8059
+ blendMode: "additive",
8060
+ particleShape: "soft"
7886
8061
  }
7887
8062
  };
7888
8063
 
@@ -7928,6 +8103,10 @@ function ParticleEmitter({
7928
8103
  const resolvedColor = color ?? presetConfig.color ?? "#ffffff";
7929
8104
  const resolvedGravity = gravity ?? presetConfig.gravity ?? 200;
7930
8105
  const resolvedMaxParticles = maxParticles ?? presetConfig.maxParticles ?? 100;
8106
+ const resolvedBlendMode = blendMode ?? presetConfig.blendMode;
8107
+ const resolvedParticleShape = particleShape ?? presetConfig.particleShape;
8108
+ const resolvedColorOverLife = colorOverLife ?? presetConfig.colorOverLife;
8109
+ const resolvedSizeOverLife = sizeOverLife ?? presetConfig.sizeOverLife;
7931
8110
  const engine = useContext19(EngineContext);
7932
8111
  const entityId = useContext19(EntityContext);
7933
8112
  useEffect28(() => {
@@ -7953,15 +8132,15 @@ function ParticleEmitter({
7953
8132
  textureSrc,
7954
8133
  enableRotation,
7955
8134
  rotationSpeedRange,
7956
- sizeOverLife,
8135
+ sizeOverLife: resolvedSizeOverLife,
7957
8136
  attractors,
7958
- colorOverLife,
7959
- blendMode,
8137
+ colorOverLife: resolvedColorOverLife,
8138
+ blendMode: resolvedBlendMode,
7960
8139
  mode,
7961
8140
  formationPoints,
7962
8141
  seekStrength,
7963
8142
  colorTransitionDuration,
7964
- particleShape
8143
+ particleShape: resolvedParticleShape
7965
8144
  });
7966
8145
  return () => engine.ecs.removeComponent(entityId, "ParticlePool");
7967
8146
  }, []);
@@ -7973,9 +8152,9 @@ function ParticleEmitter({
7973
8152
  useEffect28(() => {
7974
8153
  const pool = engine.ecs.getComponent(entityId, "ParticlePool");
7975
8154
  if (!pool) return;
7976
- pool.blendMode = blendMode;
7977
- pool.particleShape = particleShape;
7978
- }, [blendMode, particleShape, engine, entityId]);
8155
+ pool.blendMode = resolvedBlendMode;
8156
+ pool.particleShape = resolvedParticleShape;
8157
+ }, [resolvedBlendMode, resolvedParticleShape, engine, entityId]);
7979
8158
  useEffect28(() => {
7980
8159
  const pool = engine.ecs.getComponent(entityId, "ParticlePool");
7981
8160
  if (!pool) return;
@@ -13358,6 +13537,164 @@ function createAtlas(names, _columns) {
13358
13537
  return atlas;
13359
13538
  }
13360
13539
 
13540
+ // src/components/HUD.tsx
13541
+ import { createContext as createContext3, useContext as useContext78, useMemo as useMemo19 } from "react";
13542
+ import { jsx as jsx22, jsxs as jsxs11 } from "react/jsx-runtime";
13543
+ var HUDContextRef = createContext3(null);
13544
+ function HUD({
13545
+ dimDuringTransitions = true,
13546
+ dimmedOpacity = 0.25,
13547
+ visible = true,
13548
+ safeArea = true,
13549
+ padding = 12,
13550
+ style,
13551
+ className,
13552
+ children
13553
+ }) {
13554
+ const ctx = useMemo19(() => ({ padding, safeArea }), [padding, safeArea]);
13555
+ const rootStyle = {
13556
+ position: "absolute",
13557
+ inset: 0,
13558
+ pointerEvents: "none",
13559
+ zIndex: 100,
13560
+ opacity: visible ? 1 : 0,
13561
+ transition: "opacity 180ms ease-out",
13562
+ userSelect: "none",
13563
+ ...style
13564
+ };
13565
+ void dimDuringTransitions;
13566
+ void dimmedOpacity;
13567
+ return /* @__PURE__ */ jsx22(HUDContextRef.Provider, { value: ctx, children: /* @__PURE__ */ jsx22("div", { className, style: rootStyle, "data-cubeforge-hud": "true", children }) });
13568
+ }
13569
+ function useHUD() {
13570
+ return useContext78(HUDContextRef) ?? { padding: 12, safeArea: false };
13571
+ }
13572
+ function HUDZone({
13573
+ position = "topLeft",
13574
+ direction,
13575
+ gap = 8,
13576
+ interactive = true,
13577
+ style,
13578
+ className,
13579
+ children
13580
+ }) {
13581
+ const { padding, safeArea } = useHUD();
13582
+ const isTop = position.startsWith("top");
13583
+ const isBottom = position.startsWith("bottom");
13584
+ const isCenter = position.startsWith("center");
13585
+ const isLeft = position.endsWith("Left") || position === "centerLeft";
13586
+ const isRight = position.endsWith("Right") || position === "centerRight";
13587
+ const isHCenter = position.endsWith("Center") || position === "center";
13588
+ const flexDirection = direction ?? (isTop || isBottom ? "row" : "column");
13589
+ const sa = (side) => safeArea ? `max(${padding}px, env(safe-area-inset-${side}))` : `${padding}px`;
13590
+ const zoneStyle = {
13591
+ position: "absolute",
13592
+ display: "flex",
13593
+ flexDirection,
13594
+ gap,
13595
+ pointerEvents: interactive ? "auto" : "none",
13596
+ ...isTop && { top: sa("top") },
13597
+ ...isBottom && { bottom: sa("bottom") },
13598
+ ...isLeft && { left: sa("left") },
13599
+ ...isRight && { right: sa("right") },
13600
+ ...isCenter && !isHCenter && { top: "50%", transform: "translateY(-50%)" },
13601
+ ...isHCenter && !isCenter && { left: "50%", transform: "translateX(-50%)" },
13602
+ ...position === "center" && { top: "50%", left: "50%", transform: "translate(-50%, -50%)" },
13603
+ ...isHCenter && !isCenter && isTop && { top: sa("top") },
13604
+ ...isHCenter && !isCenter && isBottom && { bottom: sa("bottom") },
13605
+ alignItems: isHCenter ? "center" : isRight ? "flex-end" : "flex-start",
13606
+ ...style
13607
+ };
13608
+ return /* @__PURE__ */ jsx22("div", { className, style: zoneStyle, children });
13609
+ }
13610
+ function HUDBar({
13611
+ value,
13612
+ max,
13613
+ label,
13614
+ color = "#4fc3f7",
13615
+ trackColor = "rgba(255,255,255,0.08)",
13616
+ width = 180,
13617
+ height = 14,
13618
+ showValue = true,
13619
+ rtl = false,
13620
+ rounded = true,
13621
+ animated = true,
13622
+ style
13623
+ }) {
13624
+ const pct = max > 0 ? Math.max(0, Math.min(1, value / max)) : 0;
13625
+ const fillStyle = {
13626
+ width: `${pct * 100}%`,
13627
+ height: "100%",
13628
+ background: color,
13629
+ borderRadius: rounded ? height / 2 : 0,
13630
+ transition: animated ? "width 160ms ease-out" : void 0,
13631
+ ...rtl && { marginLeft: "auto" }
13632
+ };
13633
+ const trackStyle = {
13634
+ width,
13635
+ height,
13636
+ background: trackColor,
13637
+ borderRadius: rounded ? height / 2 : 0,
13638
+ overflow: "hidden",
13639
+ position: "relative",
13640
+ display: "flex",
13641
+ flexDirection: rtl ? "row-reverse" : "row"
13642
+ };
13643
+ const labelStyle = {
13644
+ fontFamily: "system-ui, sans-serif",
13645
+ fontSize: 11,
13646
+ letterSpacing: 0.5,
13647
+ textTransform: "uppercase",
13648
+ color: "#e0e7f1",
13649
+ opacity: 0.85,
13650
+ display: "flex",
13651
+ justifyContent: "space-between",
13652
+ marginBottom: 4
13653
+ };
13654
+ return /* @__PURE__ */ jsxs11("div", { style: { display: "flex", flexDirection: "column", ...style }, "aria-label": label, children: [
13655
+ (label || showValue) && /* @__PURE__ */ jsxs11("div", { style: labelStyle, children: [
13656
+ label && /* @__PURE__ */ jsx22("span", { children: label }),
13657
+ showValue && /* @__PURE__ */ jsxs11("span", { style: { fontVariantNumeric: "tabular-nums" }, children: [
13658
+ Math.round(value),
13659
+ " / ",
13660
+ max
13661
+ ] })
13662
+ ] }),
13663
+ /* @__PURE__ */ jsx22("div", { style: trackStyle, role: "progressbar", "aria-valuenow": value, "aria-valuemin": 0, "aria-valuemax": max, children: /* @__PURE__ */ jsx22("div", { style: fillStyle }) })
13664
+ ] });
13665
+ }
13666
+ function HUDCounter({
13667
+ value,
13668
+ icon,
13669
+ label,
13670
+ pulse = true,
13671
+ color = "#fff",
13672
+ fontSize = 18,
13673
+ style
13674
+ }) {
13675
+ void pulse;
13676
+ return /* @__PURE__ */ jsxs11(
13677
+ "div",
13678
+ {
13679
+ style: {
13680
+ display: "flex",
13681
+ alignItems: "center",
13682
+ gap: 6,
13683
+ fontFamily: "system-ui, sans-serif",
13684
+ fontSize,
13685
+ color,
13686
+ fontVariantNumeric: "tabular-nums",
13687
+ ...style
13688
+ },
13689
+ children: [
13690
+ icon && /* @__PURE__ */ jsx22("span", { style: { fontSize: fontSize * 1.1 }, children: icon }),
13691
+ /* @__PURE__ */ jsx22("span", { children: value }),
13692
+ label && /* @__PURE__ */ jsx22("span", { style: { opacity: 0.7, fontSize: fontSize * 0.75 }, children: label })
13693
+ ]
13694
+ }
13695
+ );
13696
+ }
13697
+
13361
13698
  // src/utils/animationHelpers.ts
13362
13699
  function playClip(world, entityId, clipName) {
13363
13700
  const anim = world.getComponent(entityId, "AnimationState");
@@ -14056,6 +14393,10 @@ export {
14056
14393
  FocusRing,
14057
14394
  Game,
14058
14395
  Gradient,
14396
+ HUD,
14397
+ HUDBar,
14398
+ HUDCounter,
14399
+ HUDZone,
14059
14400
  HalfSpaceCollider,
14060
14401
  HeightFieldCollider,
14061
14402
  HierarchySystem,
@@ -14066,6 +14407,7 @@ export {
14066
14407
  Mask,
14067
14408
  MovingPlatform,
14068
14409
  NineSlice,
14410
+ PARTICLE_PRESETS,
14069
14411
  ParallaxLayer,
14070
14412
  ParticleEmitter,
14071
14413
  Polygon,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cubeforge",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "description": "React-first 2D browser game engine",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",