@almadar/ui 5.166.0 → 6.1.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-C6Yz2MuW.cjs';
2
+ export { d as Canvas3DHost, e as Canvas3DHostHandle, f as Canvas3DHostProps, d as GameCanvas3D, U as UnitAnimationState } from '../../../avl-schema-parser-C6Yz2MuW.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-BKbH_SFt.js';
2
+ export { d as Canvas3DHost, e as Canvas3DHostHandle, f as Canvas3DHostProps, d as GameCanvas3D, U as UnitAnimationState } from '../../../avl-schema-parser-BKbH_SFt.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 };
@@ -5874,6 +5874,7 @@ function resolveFxView(item, presets) {
5874
5874
  vy: item.vy ?? row.vy,
5875
5875
  vz: item.vz ?? row.vz,
5876
5876
  particleCount: item.particleCount ?? row.particleCount,
5877
+ animation: item.animation ?? row.animation,
5877
5878
  shape: row.shape,
5878
5879
  count: row.count,
5879
5880
  glow: row.glow,
@@ -7406,9 +7407,6 @@ var init_isometric = __esm({
7406
7407
  };
7407
7408
  }
7408
7409
  });
7409
- function isAnimationName(name) {
7410
- return core.ANIMATION_NAMES.includes(name);
7411
- }
7412
7410
  function frameRect(frame, row, columns, frameWidth, frameHeight) {
7413
7411
  return {
7414
7412
  sx: frame % columns * frameWidth,
@@ -9342,13 +9340,17 @@ var init_DrawSprite = __esm({
9342
9340
  return;
9343
9341
  }
9344
9342
  if (isSpriteSheetAtlas(atlas)) {
9345
- const def = isAnimationName(node.animation) ? atlas.animations[node.animation] : void 0;
9343
+ const def = atlas.animations[node.animation];
9346
9344
  if (!def) {
9347
9345
  paintFallbackSquare(painter, node, dctx, "sprite-missing");
9348
9346
  return;
9349
9347
  }
9350
- const { frame } = getCurrentFrameFromDef(node.loop === void 0 ? def : { ...def, loop: node.loop }, dctx.time);
9348
+ const { frame } = getCurrentFrameFromDef(node.loop === void 0 ? def : { ...def, loop: node.loop }, node.clockMs ?? dctx.time);
9351
9349
  const r = frameRect(frame, def.row, atlas.columns, atlas.frameWidth, atlas.frameHeight);
9350
+ if (![r.sx, r.sy, r.sw, r.sh].every(Number.isFinite)) {
9351
+ paintFallbackSquare(painter, node, dctx, "sprite-missing");
9352
+ return;
9353
+ }
9352
9354
  src = { x: r.sx, y: r.sy, w: r.sw, h: r.sh };
9353
9355
  }
9354
9356
  }
@@ -9896,7 +9898,10 @@ function expandFxItem(view, node, epochNowMs, dim) {
9896
9898
  anchor: "center",
9897
9899
  width: spriteSize,
9898
9900
  height: spriteSize,
9899
- opacity: fade
9901
+ opacity: fade,
9902
+ animation: view.animation ?? sprite.animations?.[0],
9903
+ // One-shot fx rows (burst) play from the item's birth, not wall-time.
9904
+ clockMs: ageMs
9900
9905
  });
9901
9906
  } else {
9902
9907
  out.push(...proceduralShapes(view, pos, fade, progress));
@@ -11743,6 +11748,7 @@ var init_GameShell = __esm({
11743
11748
  init_Typography();
11744
11749
  init_AtlasImage();
11745
11750
  GAME_FONTS = {
11751
+ fredoka: "Fredoka",
11746
11752
  future: "Kenney Future",
11747
11753
  "future-narrow": "Kenney Future Narrow",
11748
11754
  pixel: "Kenney Pixel",
@@ -11759,10 +11765,10 @@ var init_GameShell = __esm({
11759
11765
  showTopBar = true,
11760
11766
  children,
11761
11767
  backgroundAsset,
11762
- fontFamily = "future",
11768
+ fontFamily,
11763
11769
  "data-theme": dataTheme
11764
11770
  }) => {
11765
- const font = GAME_FONTS[fontFamily] ?? fontFamily;
11771
+ const font = fontFamily ? GAME_FONTS[fontFamily] ?? fontFamily : void 0;
11766
11772
  return /* @__PURE__ */ jsxRuntime.jsxs(
11767
11773
  Box,
11768
11774
  {
@@ -11777,8 +11783,11 @@ var init_GameShell = __esm({
11777
11783
  color: "var(--color-foreground, #e0e0e0)",
11778
11784
  // The fontFamily knob is a scoped override of the theme contract's
11779
11785
  // 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`
11786
+ // the active theme's --font-family-body. Set ONLY when explicitly
11787
+ // passed an always-on inline stamp would shadow the orbital's
11788
+ // inline-theme font-family-display token (inline style beats the
11789
+ // theme provider's vars for the whole shell subtree).
11790
+ ...font ? { "--font-family-display": `'${font}', ui-sans-serif, system-ui, sans-serif` } : {}
11782
11791
  },
11783
11792
  children: [
11784
11793
  backgroundAsset && /* @__PURE__ */ jsxRuntime.jsx(
@@ -12246,6 +12255,16 @@ function traceShapes(panel, k, width, height) {
12246
12255
  }
12247
12256
  return out;
12248
12257
  }
12258
+ function drawableNeedsAnimation(nodes) {
12259
+ if (!nodes) return false;
12260
+ return nodes.some((node) => {
12261
+ if (node.type === "draw-sprite") return node.animation !== void 0;
12262
+ if (node.type === "draw-fx-layer") return Array.isArray(node.items) && node.items.length > 0;
12263
+ if (node.type === "draw-sprite-layer") return Array.isArray(node.items) && node.items.some((it) => it.animation !== void 0);
12264
+ if (node.type === "draw-group") return Array.isArray(node.items) && drawableNeedsAnimation(node.items);
12265
+ return false;
12266
+ });
12267
+ }
12249
12268
  var DASH_PATTERNS, TRACE_SERIES_COLORS, LearningCanvas;
12250
12269
  var init_LearningCanvas = __esm({
12251
12270
  "components/learning/atoms/LearningCanvas.tsx"() {
@@ -12253,6 +12272,8 @@ var init_LearningCanvas = __esm({
12253
12272
  init_cn();
12254
12273
  init_perf();
12255
12274
  init_useEventBus();
12275
+ init_webPainter2d();
12276
+ init_paintDispatch();
12256
12277
  DASH_PATTERNS = { dashed: [6, 4], dotted: [2, 3] };
12257
12278
  TRACE_SERIES_COLORS = ["#2563eb", "#dc2626", "#16a34a", "#f59e0b"];
12258
12279
  LearningCanvas = ({
@@ -12261,6 +12282,8 @@ var init_LearningCanvas = __esm({
12261
12282
  height = 400,
12262
12283
  backgroundColor,
12263
12284
  shapes = [],
12285
+ drawables,
12286
+ projector,
12264
12287
  readouts,
12265
12288
  traces,
12266
12289
  interactive = false,
@@ -12274,6 +12297,9 @@ var init_LearningCanvas = __esm({
12274
12297
  const eventBus = useEventBus();
12275
12298
  const animRef = React87.useRef(0);
12276
12299
  const hoverIndexRef = React87.useRef(-1);
12300
+ const [drawVersion, setDrawVersion] = React87.useState(0);
12301
+ const invalidateRef = React87.useRef(() => setDrawVersion((v) => v + 1));
12302
+ const needsAnim = React87.useMemo(() => drawableNeedsAnimation(drawables), [drawables]);
12277
12303
  const findShapeAt = React87.useCallback((clientX, clientY) => {
12278
12304
  const canvas = canvasRef.current;
12279
12305
  if (!canvas) return -1;
@@ -12317,20 +12343,29 @@ var init_LearningCanvas = __esm({
12317
12343
  for (const shape of derivedShapes) {
12318
12344
  if (shape.type === "text") drawShape(ctx, shape, width, height, derivedShapes);
12319
12345
  }
12346
+ if (drawables?.length && projector) {
12347
+ const painter = createWebPainter(ctx, invalidateRef.current);
12348
+ const timeMs = needsAnim && typeof performance !== "undefined" ? performance.now() : 0;
12349
+ const dctx = { projector, time: timeMs, invalidate: invalidateRef.current };
12350
+ for (const node of drawables) {
12351
+ paintDrawable(painter, node, dctx);
12352
+ }
12353
+ }
12320
12354
  ui.perfEnd("learningcanvas:paint", _perfT);
12321
- }, [width, height, backgroundColor, derivedShapes]);
12355
+ }, [width, height, backgroundColor, derivedShapes, drawables, projector, needsAnim]);
12322
12356
  React87.useEffect(() => {
12323
12357
  draw();
12324
- }, [draw]);
12358
+ }, [draw, drawVersion]);
12325
12359
  React87.useEffect(() => {
12326
- if (!animate) return;
12360
+ const shouldAnimate = animate || needsAnim;
12361
+ if (!shouldAnimate) return;
12327
12362
  const loop = () => {
12328
12363
  draw();
12329
12364
  animRef.current = requestAnimationFrame(loop);
12330
12365
  };
12331
12366
  animRef.current = requestAnimationFrame(loop);
12332
12367
  return () => cancelAnimationFrame(animRef.current);
12333
- }, [animate, draw]);
12368
+ }, [animate, needsAnim, draw]);
12334
12369
  const handlePointerMove = React87.useCallback(
12335
12370
  (e) => {
12336
12371
  if (!interactive) return;
@@ -24972,8 +25007,9 @@ var init_DashboardLayout = __esm({
24972
25007
  if (notificationClickEvent) eventBus.emit(`UI:${notificationClickEvent}`, {});
24973
25008
  if (onNotificationClick) onNotificationClick();
24974
25009
  };
24975
- const handleTopBarActionClick = (event) => {
24976
- eventBus.emit(`UI:${event}`, {});
25010
+ const handleTopBarActionClick = (action) => {
25011
+ if (action.event) eventBus.emit(`UI:${action.event}`, {});
25012
+ if (action.navigatesTo) eventBus.emit("UI:NAVIGATE", { url: action.navigatesTo });
24977
25013
  };
24978
25014
  const [sidebarOpen, setSidebarOpen] = React87.useState(false);
24979
25015
  const [userMenuOpen, setUserMenuOpen] = React87.useState(false);
@@ -25188,7 +25224,7 @@ var init_DashboardLayout = __esm({
25188
25224
  {
25189
25225
  variant: "ghost",
25190
25226
  className: "relative p-2 rounded-full hover:bg-muted dark:hover:bg-muted",
25191
- onClick: () => handleTopBarActionClick(action.event),
25227
+ onClick: () => handleTopBarActionClick(action),
25192
25228
  "aria-label": action.label ?? (typeof action.icon === "string" ? action.icon : void 0),
25193
25229
  children: [
25194
25230
  /* @__PURE__ */ jsxRuntime.jsx(Icon, { icon: action.icon, className: "h-5 w-5 text-muted-foreground dark:text-muted-foreground" }),
@@ -25205,7 +25241,7 @@ var init_DashboardLayout = __esm({
25205
25241
  )
25206
25242
  ]
25207
25243
  },
25208
- `${action.event}-${idx}`
25244
+ `${action.event ?? action.navigatesTo ?? "action"}-${idx}`
25209
25245
  )),
25210
25246
  notificationsEnabled && /* @__PURE__ */ jsxRuntime.jsxs(
25211
25247
  Button,
@@ -30867,6 +30903,7 @@ var init_MathCanvas = __esm({
30867
30903
  angles = [],
30868
30904
  hops = [],
30869
30905
  shapes = [],
30906
+ drawables,
30870
30907
  readouts,
30871
30908
  traces,
30872
30909
  interactive = false,
@@ -31173,6 +31210,42 @@ var init_MathCanvas = __esm({
31173
31210
  hops,
31174
31211
  shapes
31175
31212
  ]);
31213
+ const projector = React87.useMemo(() => {
31214
+ if (!drawables?.length) return void 0;
31215
+ const margin = 24;
31216
+ const plotW = width - margin * 2;
31217
+ const plotH = height - margin * 2;
31218
+ const xScale = plotW / (xMax - xMin);
31219
+ const mapX = (x) => margin + (x - xMin) / (xMax - xMin) * plotW;
31220
+ const mapY = (y) => height - (margin + (y - yMin) / (yMax - yMin) * plotH);
31221
+ const project = (pos) => ({ x: mapX(pos.x), y: mapY(pos.y) });
31222
+ const anchorPoint = (pos, anchor) => {
31223
+ const base = project(pos);
31224
+ if (anchor === "top-left") return base;
31225
+ const cx = base.x + xScale / 2;
31226
+ if (anchor === "ground") return { x: cx, y: base.y + xScale * 0.92 };
31227
+ return { x: cx, y: base.y + xScale / 2 };
31228
+ };
31229
+ const cellPath = (pos) => {
31230
+ const base = project(pos);
31231
+ return [
31232
+ { x: base.x, y: base.y },
31233
+ { x: base.x + xScale, y: base.y },
31234
+ { x: base.x + xScale, y: base.y + xScale },
31235
+ { x: base.x, y: base.y + xScale }
31236
+ ];
31237
+ };
31238
+ return {
31239
+ project,
31240
+ anchorPoint,
31241
+ cellPath,
31242
+ tileWidth: xScale,
31243
+ floorHeight: xScale / 2,
31244
+ diamondTopY: 0,
31245
+ squareGrid: true,
31246
+ worldPixelDirect: false
31247
+ };
31248
+ }, [drawables?.length, width, height, xMin, xMax, yMin, yMax]);
31176
31249
  return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
31177
31250
  title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
31178
31251
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -31182,6 +31255,8 @@ var init_MathCanvas = __esm({
31182
31255
  height,
31183
31256
  backgroundColor,
31184
31257
  shapes: derivedShapes,
31258
+ drawables,
31259
+ projector,
31185
31260
  readouts,
31186
31261
  traces,
31187
31262
  interactive,
@@ -32411,7 +32486,7 @@ var init_MapView = __esm({
32411
32486
  shadowSize: [41, 41]
32412
32487
  });
32413
32488
  L.Marker.prototype.options.icon = defaultIcon;
32414
- const { useEffect: useEffect67, useRef: useRef65, useCallback: useCallback97, useState: useState97 } = React87__namespace.default;
32489
+ const { useEffect: useEffect67, useRef: useRef65, useCallback: useCallback97, useState: useState98 } = React87__namespace.default;
32415
32490
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
32416
32491
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
32417
32492
  function MapUpdater({ centerLat, centerLng, zoom }) {
@@ -32456,7 +32531,7 @@ var init_MapView = __esm({
32456
32531
  showAttribution = true
32457
32532
  }) {
32458
32533
  const eventBus = useEventBus2();
32459
- const [clickedPosition, setClickedPosition] = useState97(null);
32534
+ const [clickedPosition, setClickedPosition] = useState98(null);
32460
32535
  const handleMapClick = useCallback97((lat, lng) => {
32461
32536
  if (showClickedPin) {
32462
32537
  setClickedPosition({ lat, lng });