@almadar/ui 6.0.0 → 6.2.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.
@@ -727,6 +727,7 @@ function resolveFxView(item, presets) {
727
727
  vy: item.vy ?? row.vy,
728
728
  vz: item.vz ?? row.vz,
729
729
  particleCount: item.particleCount ?? row.particleCount,
730
+ animation: item.animation ?? row.animation,
730
731
  shape: row.shape,
731
732
  count: row.count,
732
733
  glow: row.glow,
@@ -880,7 +881,10 @@ function expandFxItem(view, node, epochNowMs, dim) {
880
881
  anchor: "center",
881
882
  width: spriteSize,
882
883
  height: spriteSize,
883
- opacity: fade
884
+ opacity: fade,
885
+ animation: view.animation ?? sprite.animations?.[0],
886
+ // One-shot fx rows (burst) play from the item's birth, not wall-time.
887
+ clockMs: ageMs
884
888
  });
885
889
  } else {
886
890
  out.push(...proceduralShapes(view, pos, fade, progress));
@@ -1,12 +1,12 @@
1
- import { C as CanvasPointLightConfig, I as IsometricTile, a as IsometricUnit, b as IsometricFeature, A as ApplicationLevelData, O as OrbitalLevelData, T as TraitLevelData, c as TransitionLevelData } from '../../../avl-schema-parser-Do_LPV1o.cjs';
2
- export { d as Canvas3DHost, e as Canvas3DHostHandle, f as Canvas3DHostProps, d as GameCanvas3D, U as UnitAnimationState } from '../../../avl-schema-parser-Do_LPV1o.cjs';
1
+ import { C as CanvasPointLightConfig, I as IsometricTile, a as IsometricUnit, b as IsometricFeature, A as ApplicationLevelData, O as OrbitalLevelData, T as TraitLevelData, c as TransitionLevelData } from '../../../avl-schema-parser-Bt4NzAam.cjs';
2
+ export { d as Canvas3DHost, e as Canvas3DHostHandle, f as Canvas3DHostProps, d as GameCanvas3D, U as UnitAnimationState } from '../../../avl-schema-parser-Bt4NzAam.cjs';
3
3
  import * as React$1 from 'react';
4
4
  import React__default, { Component, ReactNode, ErrorInfo } from 'react';
5
5
  import * as THREE from 'three';
6
6
  import { QuadraticBezierCurve3 } from 'three';
7
7
  import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
8
8
  import { JsonObject, EventEmit, OrbitalSchema } from '@almadar/core';
9
- import '../../../paintDispatch-DgBctxIq.cjs';
9
+ import '../../../paintDispatch-D3R8NNmV.cjs';
10
10
  import '../../../types-B3nHgnMG.cjs';
11
11
 
12
12
  /**
@@ -1,12 +1,12 @@
1
- import { C as CanvasPointLightConfig, I as IsometricTile, a as IsometricUnit, b as IsometricFeature, A as ApplicationLevelData, O as OrbitalLevelData, T as TraitLevelData, c as TransitionLevelData } from '../../../avl-schema-parser-CLIm28Wa.js';
2
- export { d as Canvas3DHost, e as Canvas3DHostHandle, f as Canvas3DHostProps, d as GameCanvas3D, U as UnitAnimationState } from '../../../avl-schema-parser-CLIm28Wa.js';
1
+ import { C as CanvasPointLightConfig, I as IsometricTile, a as IsometricUnit, b as IsometricFeature, A as ApplicationLevelData, O as OrbitalLevelData, T as TraitLevelData, c as TransitionLevelData } from '../../../avl-schema-parser-dvJKXn-o.js';
2
+ export { d as Canvas3DHost, e as Canvas3DHostHandle, f as Canvas3DHostProps, d as GameCanvas3D, U as UnitAnimationState } from '../../../avl-schema-parser-dvJKXn-o.js';
3
3
  import * as React$1 from 'react';
4
4
  import React__default, { Component, ReactNode, ErrorInfo } from 'react';
5
5
  import * as THREE from 'three';
6
6
  import { QuadraticBezierCurve3 } from 'three';
7
7
  import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
8
8
  import { JsonObject, EventEmit, OrbitalSchema } from '@almadar/core';
9
- import '../../../paintDispatch-B5n2DTB-.js';
9
+ import '../../../paintDispatch-B5ObLnUv.js';
10
10
  import '../../../types-B3nHgnMG.js';
11
11
 
12
12
  /**
@@ -703,6 +703,7 @@ function resolveFxView(item, presets) {
703
703
  vy: item.vy ?? row.vy,
704
704
  vz: item.vz ?? row.vz,
705
705
  particleCount: item.particleCount ?? row.particleCount,
706
+ animation: item.animation ?? row.animation,
706
707
  shape: row.shape,
707
708
  count: row.count,
708
709
  glow: row.glow,
@@ -856,7 +857,10 @@ function expandFxItem(view, node, epochNowMs, dim) {
856
857
  anchor: "center",
857
858
  width: spriteSize,
858
859
  height: spriteSize,
859
- opacity: fade
860
+ opacity: fade,
861
+ animation: view.animation ?? sprite.animations?.[0],
862
+ // One-shot fx rows (burst) play from the item's birth, not wall-time.
863
+ clockMs: ageMs
860
864
  });
861
865
  } else {
862
866
  out.push(...proceduralShapes(view, pos, fade, progress));
@@ -1,10 +1,10 @@
1
- export { C as ContentSegment, D as DEFAULT_CONFIG, a as DomEntityBox, b as DomLayoutData, c as DomOutputsBox, d as DomStateNode, e as DomTransitionLabel, f as DomTransitionPath, E as EntityDefinition, L as LessonSegment, R as RenderOptions, S as StateDefinition, g as StateMachineDefinition, T as TraitSnapshotGetter, h as TransitionDefinition, V as VisualizerConfig, i as bindCanvasCapture, j as bindEventBus, k as bindLastDrawables, l as bindTraitStateGetter, m as clearVerification, n as cn, o as extractOutputsFromTransitions, p as extractStateMachine, q as formatGuard, r as getAllChecks, s as getBridgeHealth, t as getEffectSummary, u as getSnapshot, v as getSummary, w as getTraitSnapshots, x as getTransitions, y as getTransitionsForTrait, z as parseContentSegments, A as parseLessonSegments, B as parseMarkdownWithCodeBlocks, F as recordServerResponse, G as recordTransition, H as registerCheck, I as registerTraitSnapshot, J as renderStateMachineToDomData, K as renderStateMachineToSvg, M as subscribeToVerification, N as updateAssetStatus, O as updateBridgeHealth, P as updateCheck, Q as waitForTransition } from '../cn-TH-RHihd.cjs';
1
+ export { C as ContentSegment, D as DEFAULT_CONFIG, a as DomEntityBox, b as DomLayoutData, c as DomOutputsBox, d as DomStateNode, e as DomTransitionLabel, f as DomTransitionPath, E as EntityDefinition, L as LessonSegment, R as RenderOptions, S as StateDefinition, g as StateMachineDefinition, T as TraitSnapshotGetter, h as TransitionDefinition, V as VisualizerConfig, i as bindCanvasCapture, j as bindEventBus, k as bindLastDrawables, l as bindTraitStateGetter, m as clearVerification, n as cn, o as extractOutputsFromTransitions, p as extractStateMachine, q as formatGuard, r as getAllChecks, s as getBridgeHealth, t as getEffectSummary, u as getSnapshot, v as getSummary, w as getTraitSnapshots, x as getTransitions, y as getTransitionsForTrait, z as parseContentSegments, A as parseLessonSegments, B as parseMarkdownWithCodeBlocks, F as recordServerResponse, G as recordTransition, H as registerCheck, I as registerTraitSnapshot, J as renderStateMachineToDomData, K as renderStateMachineToSvg, M as subscribeToVerification, N as updateAssetStatus, O as updateBridgeHealth, P as updateCheck, Q as waitForTransition } from '../cn-DJSBBm5W.cjs';
2
2
  import { FieldValue, EntityRow, EventPayload } from '@almadar/core';
3
3
  export { AssetLoadStatus, BridgeHealth, CheckStatus, EffectTrace, EventLogEntry, OrbitalVerificationAPI, ServerResponseTrace, TraitStateSnapshot, TransitionTrace, VerificationCheck, VerificationSnapshot, VerificationSummary } from '@almadar/core';
4
4
  export { p as profilerOnRender, u as usePerfBuffer } from '../perf-DaVdsbU0.cjs';
5
5
  export { PERF_NAMESPACE, PerfEntry, clearPerf, perfEnd, perfGauge, perfStart, perfTime, perfTimeAsync } from '@almadar/runtime/ui';
6
6
  import 'react';
7
- import '../paintDispatch-DgBctxIq.cjs';
7
+ import '../paintDispatch-D3R8NNmV.cjs';
8
8
  import '../types-B3nHgnMG.cjs';
9
9
  import 'clsx';
10
10
 
@@ -1,10 +1,10 @@
1
- export { C as ContentSegment, D as DEFAULT_CONFIG, a as DomEntityBox, b as DomLayoutData, c as DomOutputsBox, d as DomStateNode, e as DomTransitionLabel, f as DomTransitionPath, E as EntityDefinition, L as LessonSegment, R as RenderOptions, S as StateDefinition, g as StateMachineDefinition, T as TraitSnapshotGetter, h as TransitionDefinition, V as VisualizerConfig, i as bindCanvasCapture, j as bindEventBus, k as bindLastDrawables, l as bindTraitStateGetter, m as clearVerification, n as cn, o as extractOutputsFromTransitions, p as extractStateMachine, q as formatGuard, r as getAllChecks, s as getBridgeHealth, t as getEffectSummary, u as getSnapshot, v as getSummary, w as getTraitSnapshots, x as getTransitions, y as getTransitionsForTrait, z as parseContentSegments, A as parseLessonSegments, B as parseMarkdownWithCodeBlocks, F as recordServerResponse, G as recordTransition, H as registerCheck, I as registerTraitSnapshot, J as renderStateMachineToDomData, K as renderStateMachineToSvg, M as subscribeToVerification, N as updateAssetStatus, O as updateBridgeHealth, P as updateCheck, Q as waitForTransition } from '../cn-CFurBb2q.js';
1
+ export { C as ContentSegment, D as DEFAULT_CONFIG, a as DomEntityBox, b as DomLayoutData, c as DomOutputsBox, d as DomStateNode, e as DomTransitionLabel, f as DomTransitionPath, E as EntityDefinition, L as LessonSegment, R as RenderOptions, S as StateDefinition, g as StateMachineDefinition, T as TraitSnapshotGetter, h as TransitionDefinition, V as VisualizerConfig, i as bindCanvasCapture, j as bindEventBus, k as bindLastDrawables, l as bindTraitStateGetter, m as clearVerification, n as cn, o as extractOutputsFromTransitions, p as extractStateMachine, q as formatGuard, r as getAllChecks, s as getBridgeHealth, t as getEffectSummary, u as getSnapshot, v as getSummary, w as getTraitSnapshots, x as getTransitions, y as getTransitionsForTrait, z as parseContentSegments, A as parseLessonSegments, B as parseMarkdownWithCodeBlocks, F as recordServerResponse, G as recordTransition, H as registerCheck, I as registerTraitSnapshot, J as renderStateMachineToDomData, K as renderStateMachineToSvg, M as subscribeToVerification, N as updateAssetStatus, O as updateBridgeHealth, P as updateCheck, Q as waitForTransition } from '../cn-BbhJq_nr.js';
2
2
  import { FieldValue, EntityRow, EventPayload } from '@almadar/core';
3
3
  export { AssetLoadStatus, BridgeHealth, CheckStatus, EffectTrace, EventLogEntry, OrbitalVerificationAPI, ServerResponseTrace, TraitStateSnapshot, TransitionTrace, VerificationCheck, VerificationSnapshot, VerificationSummary } from '@almadar/core';
4
4
  export { p as profilerOnRender, u as usePerfBuffer } from '../perf-DaVdsbU0.js';
5
5
  export { PERF_NAMESPACE, PerfEntry, clearPerf, perfEnd, perfGauge, perfStart, perfTime, perfTimeAsync } from '@almadar/runtime/ui';
6
6
  import 'react';
7
- import '../paintDispatch-B5n2DTB-.js';
7
+ import '../paintDispatch-B5ObLnUv.js';
8
8
  import '../types-B3nHgnMG.js';
9
9
  import 'clsx';
10
10
 
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { EventKey, EventPayload, AssetUrl, Asset } from '@almadar/core';
2
+ import { EventKey, EventPayload, EventEmit, AssetUrl, Asset } from '@almadar/core';
3
3
  import { LucideIcon } from 'lucide-react';
4
4
 
5
5
  /**
@@ -53,10 +53,14 @@ interface BoxProps extends React.HTMLAttributes<HTMLDivElement> {
53
53
  as?: React.ElementType;
54
54
  /** Declarative event name — emits UI:{action} via eventBus on click */
55
55
  action?: EventKey;
56
- /** Payload to include with the action event */
56
+ /** Payload to include with the action event
57
+ * @payloadFor action
58
+ */
57
59
  actionPayload?: EventPayload;
58
60
  /** Declarative hover event — emits UI:{hoverEvent} with { hovered: true/false } on mouseEnter/mouseLeave */
59
- hoverEvent?: EventKey;
61
+ hoverEvent?: EventEmit<{
62
+ hovered: boolean;
63
+ }>;
60
64
  /** When true (default), a touch/pen tap also fires `hoverEvent` (toggling hovered) so hover-only reveals work on touch. */
61
65
  tapReveal?: boolean;
62
66
  /** Maximum width (CSS value, e.g., "550px", "80rem") */
@@ -113,7 +117,9 @@ interface StackProps {
113
117
  tabIndex?: number;
114
118
  /** Declarative event name — emits UI:{action} via eventBus on click */
115
119
  action?: EventKey;
116
- /** Payload to include with the action event */
120
+ /** Payload to include with the action event
121
+ * @payloadFor action
122
+ */
117
123
  actionPayload?: EventPayload;
118
124
  /** When true, horizontal stacks flip to vertical below the md breakpoint (768px) */
119
125
  responsive?: boolean;
@@ -272,7 +278,9 @@ interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
272
278
  iconAsset?: Asset;
273
279
  /** Declarative event name — emits UI:{action} via eventBus on click */
274
280
  action?: EventKey;
275
- /** Payload to include with the action event */
281
+ /** Payload to include with the action event
282
+ * @payloadFor action
283
+ */
276
284
  actionPayload?: EventPayload;
277
285
  /** Button label text (alternative to children for schema-driven rendering) */
278
286
  label?: string;
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { EventKey, EventPayload, AssetUrl, Asset } from '@almadar/core';
2
+ import { EventKey, EventPayload, EventEmit, AssetUrl, Asset } from '@almadar/core';
3
3
  import { LucideIcon } from 'lucide-react';
4
4
 
5
5
  /**
@@ -53,10 +53,14 @@ interface BoxProps extends React.HTMLAttributes<HTMLDivElement> {
53
53
  as?: React.ElementType;
54
54
  /** Declarative event name — emits UI:{action} via eventBus on click */
55
55
  action?: EventKey;
56
- /** Payload to include with the action event */
56
+ /** Payload to include with the action event
57
+ * @payloadFor action
58
+ */
57
59
  actionPayload?: EventPayload;
58
60
  /** Declarative hover event — emits UI:{hoverEvent} with { hovered: true/false } on mouseEnter/mouseLeave */
59
- hoverEvent?: EventKey;
61
+ hoverEvent?: EventEmit<{
62
+ hovered: boolean;
63
+ }>;
60
64
  /** When true (default), a touch/pen tap also fires `hoverEvent` (toggling hovered) so hover-only reveals work on touch. */
61
65
  tapReveal?: boolean;
62
66
  /** Maximum width (CSS value, e.g., "550px", "80rem") */
@@ -113,7 +117,9 @@ interface StackProps {
113
117
  tabIndex?: number;
114
118
  /** Declarative event name — emits UI:{action} via eventBus on click */
115
119
  action?: EventKey;
116
- /** Payload to include with the action event */
120
+ /** Payload to include with the action event
121
+ * @payloadFor action
122
+ */
117
123
  actionPayload?: EventPayload;
118
124
  /** When true, horizontal stacks flip to vertical below the md breakpoint (768px) */
119
125
  responsive?: boolean;
@@ -272,7 +278,9 @@ interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
272
278
  iconAsset?: Asset;
273
279
  /** Declarative event name — emits UI:{action} via eventBus on click */
274
280
  action?: EventKey;
275
- /** Payload to include with the action event */
281
+ /** Payload to include with the action event
282
+ * @payloadFor action
283
+ */
276
284
  actionPayload?: EventPayload;
277
285
  /** Button label text (alternative to children for schema-driven rendering) */
278
286
  label?: string;
@@ -1,6 +1,11 @@
1
1
  import { ScenePos, Asset } from '@almadar/core';
2
2
  import { P as Point, R as Rect } from './types-B3nHgnMG.js';
3
3
 
4
+ /** A polyline/polygon vertex in pixel space. */
5
+ interface PainterPoint {
6
+ x: number;
7
+ y: number;
8
+ }
4
9
  /** One color stop of a {@link PainterGradient}; `offset` is 0..1 along the gradient. */
5
10
  interface PainterGradientStop {
6
11
  offset: number;
@@ -28,6 +33,31 @@ interface PainterGradientStop {
28
33
  * - `center` — the cell's visual center (effects).
29
34
  */
30
35
  type DrawableAnchor = 'top-left' | 'ground' | 'center';
36
+ /**
37
+ * Maps logical scene coordinates to pixel space for one projection (iso/hex/flat/free).
38
+ * Owns ALL projection geometry so the primitives stay projection-agnostic. The host
39
+ * applies the camera transform on the painter around the walk; the projector is
40
+ * pre-camera (logical→cell-pixel).
41
+ */
42
+ interface Projector {
43
+ /** Cell top-left in pixels. */
44
+ project(pos: ScenePos): PainterPoint;
45
+ /** The pixel reference point for an anchor (see {@link DrawableAnchor}). */
46
+ anchorPoint(pos: ScenePos, anchor: DrawableAnchor): PainterPoint;
47
+ /** The cell footprint polygon — a rect (square grids) or a diamond (iso/hex). */
48
+ cellPath(pos: ScenePos): PainterPoint[];
49
+ /** Cell width in source-px tile-space — the board's native tile width for grid layouts (`flat`/`iso`/`hex`), 1 for `free`/`side` (world-pixel-direct). The host camera applies the on-screen zoom once. */
50
+ readonly tileWidth: number;
51
+ /** Cell floor height in source-px tile-space (`tileWidth / 2` for grid layouts, 1 for `free`/`side`). */
52
+ readonly floorHeight: number;
53
+ /** Diamond-top offset in source-px tile-space (where the iso diamond starts in the sprite). */
54
+ readonly diamondTopY: number;
55
+ /** Render scale factor. */
56
+ /** True for square-pitch grids (`flat`/`free`); false for diamond (iso/hex). */
57
+ readonly squareGrid: boolean;
58
+ /** True for world-pixel-direct layouts (`free`/`side`); false for tile grids (`flat`/`iso`/`hex`). */
59
+ readonly worldPixelDirect: boolean;
60
+ }
31
61
  /** Common shape of every drawable descriptor. `type` keys the host's paint dispatch. */
32
62
  interface DrawableBase {
33
63
  type: string;
@@ -222,6 +252,8 @@ interface DrawSpriteProps extends DrawableBase {
222
252
  animation?: string;
223
253
  /** Override the sheet row's loop flag. The paint clock is wall-time, so one-shot rows hold their final frame — preview pages set `true` to cycle attack/hit/death continuously. */
224
254
  loop?: boolean;
255
+ /** Animation clock override in ms (e.g. an fx entry's age since spawn). Omitted → the host paint clock; one-shot rows anchored to wall-time would otherwise sit on their final frame. */
256
+ clockMs?: number;
225
257
  /** Mirror horizontally (facing). */
226
258
  flipX?: boolean;
227
259
  /** Rotation in radians about the sprite's center. */
@@ -573,6 +605,8 @@ interface FxItem {
573
605
  vz?: number;
574
606
  /** Sub-particle count override (procedural burst / confetti). */
575
607
  particleCount?: number;
608
+ /** Sprite-sheet animation name (e.g. "burst") — used when the resolved look is a sprite atlas. */
609
+ animation?: string;
576
610
  }
577
611
  /**
578
612
  * One row of a consumer-declared effect vocabulary: the look + recipe for one
@@ -605,6 +639,8 @@ interface FxPreset {
605
639
  effect?: FxOverlayKind;
606
640
  /** Sub-particle count for screen-space kinds (confetti/sparkle). */
607
641
  particleCount?: number;
642
+ /** Sprite-sheet animation name to play when this preset resolves to a sprite atlas. */
643
+ animation?: string;
608
644
  }
609
645
  /**
610
646
  * A screen-space fx entry as `fx-overlay` consumes it — the same mechanic rows,
@@ -695,4 +731,4 @@ interface DrawFxLayerProps extends DrawableBase {
695
731
  /** Every drawable descriptor. The host's `children` are a `DrawableNode[]`. */
696
732
  type DrawableNode = DrawSpriteProps | DrawShapeProps | DrawTextProps | DrawGroupProps | DrawMeshProps | DrawSpriteLayerProps | DrawShapeLayerProps | DrawTextLayerProps | DrawFxLayerProps;
697
733
 
698
- export type { DrawableNode as D, FxOverlayItem as F, MeshShapeKind as M, MeshMaterial as a, DrawGroupProps as b, DrawTextProps as c, DrawMeshProps as d, FxOverlayKind as e };
734
+ export type { DrawableNode as D, FxOverlayItem as F, MeshShapeKind as M, Projector as P, MeshMaterial as a, DrawGroupProps as b, DrawTextProps as c, DrawMeshProps as d, FxOverlayKind as e };
@@ -1,6 +1,11 @@
1
1
  import { ScenePos, Asset } from '@almadar/core';
2
2
  import { P as Point, R as Rect } from './types-B3nHgnMG.cjs';
3
3
 
4
+ /** A polyline/polygon vertex in pixel space. */
5
+ interface PainterPoint {
6
+ x: number;
7
+ y: number;
8
+ }
4
9
  /** One color stop of a {@link PainterGradient}; `offset` is 0..1 along the gradient. */
5
10
  interface PainterGradientStop {
6
11
  offset: number;
@@ -28,6 +33,31 @@ interface PainterGradientStop {
28
33
  * - `center` — the cell's visual center (effects).
29
34
  */
30
35
  type DrawableAnchor = 'top-left' | 'ground' | 'center';
36
+ /**
37
+ * Maps logical scene coordinates to pixel space for one projection (iso/hex/flat/free).
38
+ * Owns ALL projection geometry so the primitives stay projection-agnostic. The host
39
+ * applies the camera transform on the painter around the walk; the projector is
40
+ * pre-camera (logical→cell-pixel).
41
+ */
42
+ interface Projector {
43
+ /** Cell top-left in pixels. */
44
+ project(pos: ScenePos): PainterPoint;
45
+ /** The pixel reference point for an anchor (see {@link DrawableAnchor}). */
46
+ anchorPoint(pos: ScenePos, anchor: DrawableAnchor): PainterPoint;
47
+ /** The cell footprint polygon — a rect (square grids) or a diamond (iso/hex). */
48
+ cellPath(pos: ScenePos): PainterPoint[];
49
+ /** Cell width in source-px tile-space — the board's native tile width for grid layouts (`flat`/`iso`/`hex`), 1 for `free`/`side` (world-pixel-direct). The host camera applies the on-screen zoom once. */
50
+ readonly tileWidth: number;
51
+ /** Cell floor height in source-px tile-space (`tileWidth / 2` for grid layouts, 1 for `free`/`side`). */
52
+ readonly floorHeight: number;
53
+ /** Diamond-top offset in source-px tile-space (where the iso diamond starts in the sprite). */
54
+ readonly diamondTopY: number;
55
+ /** Render scale factor. */
56
+ /** True for square-pitch grids (`flat`/`free`); false for diamond (iso/hex). */
57
+ readonly squareGrid: boolean;
58
+ /** True for world-pixel-direct layouts (`free`/`side`); false for tile grids (`flat`/`iso`/`hex`). */
59
+ readonly worldPixelDirect: boolean;
60
+ }
31
61
  /** Common shape of every drawable descriptor. `type` keys the host's paint dispatch. */
32
62
  interface DrawableBase {
33
63
  type: string;
@@ -222,6 +252,8 @@ interface DrawSpriteProps extends DrawableBase {
222
252
  animation?: string;
223
253
  /** Override the sheet row's loop flag. The paint clock is wall-time, so one-shot rows hold their final frame — preview pages set `true` to cycle attack/hit/death continuously. */
224
254
  loop?: boolean;
255
+ /** Animation clock override in ms (e.g. an fx entry's age since spawn). Omitted → the host paint clock; one-shot rows anchored to wall-time would otherwise sit on their final frame. */
256
+ clockMs?: number;
225
257
  /** Mirror horizontally (facing). */
226
258
  flipX?: boolean;
227
259
  /** Rotation in radians about the sprite's center. */
@@ -573,6 +605,8 @@ interface FxItem {
573
605
  vz?: number;
574
606
  /** Sub-particle count override (procedural burst / confetti). */
575
607
  particleCount?: number;
608
+ /** Sprite-sheet animation name (e.g. "burst") — used when the resolved look is a sprite atlas. */
609
+ animation?: string;
576
610
  }
577
611
  /**
578
612
  * One row of a consumer-declared effect vocabulary: the look + recipe for one
@@ -605,6 +639,8 @@ interface FxPreset {
605
639
  effect?: FxOverlayKind;
606
640
  /** Sub-particle count for screen-space kinds (confetti/sparkle). */
607
641
  particleCount?: number;
642
+ /** Sprite-sheet animation name to play when this preset resolves to a sprite atlas. */
643
+ animation?: string;
608
644
  }
609
645
  /**
610
646
  * A screen-space fx entry as `fx-overlay` consumes it — the same mechanic rows,
@@ -695,4 +731,4 @@ interface DrawFxLayerProps extends DrawableBase {
695
731
  /** Every drawable descriptor. The host's `children` are a `DrawableNode[]`. */
696
732
  type DrawableNode = DrawSpriteProps | DrawShapeProps | DrawTextProps | DrawGroupProps | DrawMeshProps | DrawSpriteLayerProps | DrawShapeLayerProps | DrawTextLayerProps | DrawFxLayerProps;
697
733
 
698
- export type { DrawableNode as D, FxOverlayItem as F, MeshShapeKind as M, MeshMaterial as a, DrawGroupProps as b, DrawTextProps as c, DrawMeshProps as d, FxOverlayKind as e };
734
+ export type { DrawableNode as D, FxOverlayItem as F, MeshShapeKind as M, Projector as P, MeshMaterial as a, DrawGroupProps as b, DrawTextProps as c, DrawMeshProps as d, FxOverlayKind as e };
@@ -3314,9 +3314,16 @@ var init_Input = __esm({
3314
3314
  }
3315
3315
  };
3316
3316
  const handleKeyDown = (e) => {
3317
- if (action && e.key === "Enter") {
3318
- eventBus.emit(`UI:${action}`, { value: e.currentTarget.value });
3317
+ if (!action || e.key !== "Enter") return;
3318
+ const raw = e.currentTarget.value;
3319
+ if (type === "number") {
3320
+ if (raw.trim() === "") return;
3321
+ const numeric = Number(raw);
3322
+ if (Number.isNaN(numeric)) return;
3323
+ eventBus.emit(`UI:${action}`, { value: numeric });
3324
+ return;
3319
3325
  }
3326
+ eventBus.emit(`UI:${action}`, { value: raw });
3320
3327
  };
3321
3328
  const wrapField = (field, fullWidth = true) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: fullWidth ? "w-full" : "w-fit", children: [
3322
3329
  label && /* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-sm font-medium text-foreground mb-1", children: label }),
@@ -5874,6 +5881,7 @@ function resolveFxView(item, presets) {
5874
5881
  vy: item.vy ?? row.vy,
5875
5882
  vz: item.vz ?? row.vz,
5876
5883
  particleCount: item.particleCount ?? row.particleCount,
5884
+ animation: item.animation ?? row.animation,
5877
5885
  shape: row.shape,
5878
5886
  count: row.count,
5879
5887
  glow: row.glow,
@@ -7406,9 +7414,6 @@ var init_isometric = __esm({
7406
7414
  };
7407
7415
  }
7408
7416
  });
7409
- function isAnimationName(name) {
7410
- return core.ANIMATION_NAMES.includes(name);
7411
- }
7412
7417
  function frameRect(frame, row, columns, frameWidth, frameHeight) {
7413
7418
  return {
7414
7419
  sx: frame % columns * frameWidth,
@@ -9342,13 +9347,17 @@ var init_DrawSprite = __esm({
9342
9347
  return;
9343
9348
  }
9344
9349
  if (isSpriteSheetAtlas(atlas)) {
9345
- const def = isAnimationName(node.animation) ? atlas.animations[node.animation] : void 0;
9350
+ const def = atlas.animations[node.animation];
9346
9351
  if (!def) {
9347
9352
  paintFallbackSquare(painter, node, dctx, "sprite-missing");
9348
9353
  return;
9349
9354
  }
9350
- const { frame } = getCurrentFrameFromDef(node.loop === void 0 ? def : { ...def, loop: node.loop }, dctx.time);
9355
+ const { frame } = getCurrentFrameFromDef(node.loop === void 0 ? def : { ...def, loop: node.loop }, node.clockMs ?? dctx.time);
9351
9356
  const r = frameRect(frame, def.row, atlas.columns, atlas.frameWidth, atlas.frameHeight);
9357
+ if (![r.sx, r.sy, r.sw, r.sh].every(Number.isFinite)) {
9358
+ paintFallbackSquare(painter, node, dctx, "sprite-missing");
9359
+ return;
9360
+ }
9352
9361
  src = { x: r.sx, y: r.sy, w: r.sw, h: r.sh };
9353
9362
  }
9354
9363
  }
@@ -9896,7 +9905,10 @@ function expandFxItem(view, node, epochNowMs, dim) {
9896
9905
  anchor: "center",
9897
9906
  width: spriteSize,
9898
9907
  height: spriteSize,
9899
- opacity: fade
9908
+ opacity: fade,
9909
+ animation: view.animation ?? sprite.animations?.[0],
9910
+ // One-shot fx rows (burst) play from the item's birth, not wall-time.
9911
+ clockMs: ageMs
9900
9912
  });
9901
9913
  } else {
9902
9914
  out.push(...proceduralShapes(view, pos, fade, progress));
@@ -11743,6 +11755,7 @@ var init_GameShell = __esm({
11743
11755
  init_Typography();
11744
11756
  init_AtlasImage();
11745
11757
  GAME_FONTS = {
11758
+ fredoka: "Fredoka",
11746
11759
  future: "Kenney Future",
11747
11760
  "future-narrow": "Kenney Future Narrow",
11748
11761
  pixel: "Kenney Pixel",
@@ -11759,10 +11772,10 @@ var init_GameShell = __esm({
11759
11772
  showTopBar = true,
11760
11773
  children,
11761
11774
  backgroundAsset,
11762
- fontFamily = "future",
11775
+ fontFamily,
11763
11776
  "data-theme": dataTheme
11764
11777
  }) => {
11765
- const font = GAME_FONTS[fontFamily] ?? fontFamily;
11778
+ const font = fontFamily ? GAME_FONTS[fontFamily] ?? fontFamily : void 0;
11766
11779
  return /* @__PURE__ */ jsxRuntime.jsxs(
11767
11780
  Box,
11768
11781
  {
@@ -11777,8 +11790,11 @@ var init_GameShell = __esm({
11777
11790
  color: "var(--color-foreground, #e0e0e0)",
11778
11791
  // The fontFamily knob is a scoped override of the theme contract's
11779
11792
  // display slot: titles/numerics take the game face, body text keeps
11780
- // the active theme's --font-family-body.
11781
- "--font-family-display": `'${font}', ui-sans-serif, system-ui, sans-serif`
11793
+ // the active theme's --font-family-body. Set ONLY when explicitly
11794
+ // passed an always-on inline stamp would shadow the orbital's
11795
+ // inline-theme font-family-display token (inline style beats the
11796
+ // theme provider's vars for the whole shell subtree).
11797
+ ...font ? { "--font-family-display": `'${font}', ui-sans-serif, system-ui, sans-serif` } : {}
11782
11798
  },
11783
11799
  children: [
11784
11800
  backgroundAsset && /* @__PURE__ */ jsxRuntime.jsx(
@@ -12246,6 +12262,16 @@ function traceShapes(panel, k, width, height) {
12246
12262
  }
12247
12263
  return out;
12248
12264
  }
12265
+ function drawableNeedsAnimation(nodes) {
12266
+ if (!nodes) return false;
12267
+ return nodes.some((node) => {
12268
+ if (node.type === "draw-sprite") return node.animation !== void 0;
12269
+ if (node.type === "draw-fx-layer") return Array.isArray(node.items) && node.items.length > 0;
12270
+ if (node.type === "draw-sprite-layer") return Array.isArray(node.items) && node.items.some((it) => it.animation !== void 0);
12271
+ if (node.type === "draw-group") return Array.isArray(node.items) && drawableNeedsAnimation(node.items);
12272
+ return false;
12273
+ });
12274
+ }
12249
12275
  var DASH_PATTERNS, TRACE_SERIES_COLORS, LearningCanvas;
12250
12276
  var init_LearningCanvas = __esm({
12251
12277
  "components/learning/atoms/LearningCanvas.tsx"() {
@@ -12253,6 +12279,8 @@ var init_LearningCanvas = __esm({
12253
12279
  init_cn();
12254
12280
  init_perf();
12255
12281
  init_useEventBus();
12282
+ init_webPainter2d();
12283
+ init_paintDispatch();
12256
12284
  DASH_PATTERNS = { dashed: [6, 4], dotted: [2, 3] };
12257
12285
  TRACE_SERIES_COLORS = ["#2563eb", "#dc2626", "#16a34a", "#f59e0b"];
12258
12286
  LearningCanvas = ({
@@ -12261,6 +12289,8 @@ var init_LearningCanvas = __esm({
12261
12289
  height = 400,
12262
12290
  backgroundColor,
12263
12291
  shapes = [],
12292
+ drawables,
12293
+ projector,
12264
12294
  readouts,
12265
12295
  traces,
12266
12296
  interactive = false,
@@ -12274,6 +12304,9 @@ var init_LearningCanvas = __esm({
12274
12304
  const eventBus = useEventBus();
12275
12305
  const animRef = React87.useRef(0);
12276
12306
  const hoverIndexRef = React87.useRef(-1);
12307
+ const [drawVersion, setDrawVersion] = React87.useState(0);
12308
+ const invalidateRef = React87.useRef(() => setDrawVersion((v) => v + 1));
12309
+ const needsAnim = React87.useMemo(() => drawableNeedsAnimation(drawables), [drawables]);
12277
12310
  const findShapeAt = React87.useCallback((clientX, clientY) => {
12278
12311
  const canvas = canvasRef.current;
12279
12312
  if (!canvas) return -1;
@@ -12317,20 +12350,29 @@ var init_LearningCanvas = __esm({
12317
12350
  for (const shape of derivedShapes) {
12318
12351
  if (shape.type === "text") drawShape(ctx, shape, width, height, derivedShapes);
12319
12352
  }
12353
+ if (drawables?.length && projector) {
12354
+ const painter = createWebPainter(ctx, invalidateRef.current);
12355
+ const timeMs = needsAnim && typeof performance !== "undefined" ? performance.now() : 0;
12356
+ const dctx = { projector, time: timeMs, invalidate: invalidateRef.current };
12357
+ for (const node of drawables) {
12358
+ paintDrawable(painter, node, dctx);
12359
+ }
12360
+ }
12320
12361
  ui.perfEnd("learningcanvas:paint", _perfT);
12321
- }, [width, height, backgroundColor, derivedShapes]);
12362
+ }, [width, height, backgroundColor, derivedShapes, drawables, projector, needsAnim]);
12322
12363
  React87.useEffect(() => {
12323
12364
  draw();
12324
- }, [draw]);
12365
+ }, [draw, drawVersion]);
12325
12366
  React87.useEffect(() => {
12326
- if (!animate) return;
12367
+ const shouldAnimate = animate || needsAnim;
12368
+ if (!shouldAnimate) return;
12327
12369
  const loop = () => {
12328
12370
  draw();
12329
12371
  animRef.current = requestAnimationFrame(loop);
12330
12372
  };
12331
12373
  animRef.current = requestAnimationFrame(loop);
12332
12374
  return () => cancelAnimationFrame(animRef.current);
12333
- }, [animate, draw]);
12375
+ }, [animate, needsAnim, draw]);
12334
12376
  const handlePointerMove = React87.useCallback(
12335
12377
  (e) => {
12336
12378
  if (!interactive) return;
@@ -24972,8 +25014,9 @@ var init_DashboardLayout = __esm({
24972
25014
  if (notificationClickEvent) eventBus.emit(`UI:${notificationClickEvent}`, {});
24973
25015
  if (onNotificationClick) onNotificationClick();
24974
25016
  };
24975
- const handleTopBarActionClick = (event) => {
24976
- eventBus.emit(`UI:${event}`, {});
25017
+ const handleTopBarActionClick = (action) => {
25018
+ if (action.event) eventBus.emit(`UI:${action.event}`, {});
25019
+ if (action.navigatesTo) eventBus.emit("UI:NAVIGATE", { url: action.navigatesTo });
24977
25020
  };
24978
25021
  const [sidebarOpen, setSidebarOpen] = React87.useState(false);
24979
25022
  const [userMenuOpen, setUserMenuOpen] = React87.useState(false);
@@ -25188,7 +25231,7 @@ var init_DashboardLayout = __esm({
25188
25231
  {
25189
25232
  variant: "ghost",
25190
25233
  className: "relative p-2 rounded-full hover:bg-muted dark:hover:bg-muted",
25191
- onClick: () => handleTopBarActionClick(action.event),
25234
+ onClick: () => handleTopBarActionClick(action),
25192
25235
  "aria-label": action.label ?? (typeof action.icon === "string" ? action.icon : void 0),
25193
25236
  children: [
25194
25237
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: action.icon, className: "h-5 w-5 text-muted-foreground dark:text-muted-foreground" }),
@@ -25205,7 +25248,7 @@ var init_DashboardLayout = __esm({
25205
25248
  )
25206
25249
  ]
25207
25250
  },
25208
- `${action.event}-${idx}`
25251
+ `${action.event ?? action.navigatesTo ?? "action"}-${idx}`
25209
25252
  )),
25210
25253
  notificationsEnabled && /* @__PURE__ */ jsxRuntime.jsxs(
25211
25254
  Button,
@@ -30867,6 +30910,7 @@ var init_MathCanvas = __esm({
30867
30910
  angles = [],
30868
30911
  hops = [],
30869
30912
  shapes = [],
30913
+ drawables,
30870
30914
  readouts,
30871
30915
  traces,
30872
30916
  interactive = false,
@@ -31173,6 +31217,42 @@ var init_MathCanvas = __esm({
31173
31217
  hops,
31174
31218
  shapes
31175
31219
  ]);
31220
+ const projector = React87.useMemo(() => {
31221
+ if (!drawables?.length) return void 0;
31222
+ const margin = 24;
31223
+ const plotW = width - margin * 2;
31224
+ const plotH = height - margin * 2;
31225
+ const xScale = plotW / (xMax - xMin);
31226
+ const mapX = (x) => margin + (x - xMin) / (xMax - xMin) * plotW;
31227
+ const mapY = (y) => height - (margin + (y - yMin) / (yMax - yMin) * plotH);
31228
+ const project = (pos) => ({ x: mapX(pos.x), y: mapY(pos.y) });
31229
+ const anchorPoint = (pos, anchor) => {
31230
+ const base = project(pos);
31231
+ if (anchor === "top-left") return base;
31232
+ const cx = base.x + xScale / 2;
31233
+ if (anchor === "ground") return { x: cx, y: base.y + xScale * 0.92 };
31234
+ return { x: cx, y: base.y + xScale / 2 };
31235
+ };
31236
+ const cellPath = (pos) => {
31237
+ const base = project(pos);
31238
+ return [
31239
+ { x: base.x, y: base.y },
31240
+ { x: base.x + xScale, y: base.y },
31241
+ { x: base.x + xScale, y: base.y + xScale },
31242
+ { x: base.x, y: base.y + xScale }
31243
+ ];
31244
+ };
31245
+ return {
31246
+ project,
31247
+ anchorPoint,
31248
+ cellPath,
31249
+ tileWidth: xScale,
31250
+ floorHeight: xScale / 2,
31251
+ diamondTopY: 0,
31252
+ squareGrid: true,
31253
+ worldPixelDirect: false
31254
+ };
31255
+ }, [drawables?.length, width, height, xMin, xMax, yMin, yMax]);
31176
31256
  return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
31177
31257
  title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
31178
31258
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -31182,6 +31262,8 @@ var init_MathCanvas = __esm({
31182
31262
  height,
31183
31263
  backgroundColor,
31184
31264
  shapes: derivedShapes,
31265
+ drawables,
31266
+ projector,
31185
31267
  readouts,
31186
31268
  traces,
31187
31269
  interactive,
@@ -32411,7 +32493,7 @@ var init_MapView = __esm({
32411
32493
  shadowSize: [41, 41]
32412
32494
  });
32413
32495
  L.Marker.prototype.options.icon = defaultIcon;
32414
- const { useEffect: useEffect67, useRef: useRef65, useCallback: useCallback97, useState: useState97 } = React87__namespace.default;
32496
+ const { useEffect: useEffect67, useRef: useRef65, useCallback: useCallback97, useState: useState98 } = React87__namespace.default;
32415
32497
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
32416
32498
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
32417
32499
  function MapUpdater({ centerLat, centerLng, zoom }) {
@@ -32456,7 +32538,7 @@ var init_MapView = __esm({
32456
32538
  showAttribution = true
32457
32539
  }) {
32458
32540
  const eventBus = useEventBus2();
32459
- const [clickedPosition, setClickedPosition] = useState97(null);
32541
+ const [clickedPosition, setClickedPosition] = useState98(null);
32460
32542
  const handleMapClick = useCallback97((lat, lng) => {
32461
32543
  if (showClickedPin) {
32462
32544
  setClickedPosition({ lat, lng });