@almadar/ui 5.134.0 → 5.136.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.
Files changed (31) hide show
  1. package/dist/{TraitProvider-Ch79cUcb.d.cts → EntityBindingContext-CD9ZoXb4.d.cts} +30 -2
  2. package/dist/{TraitProvider-Ch79cUcb.d.ts → EntityBindingContext-CD9ZoXb4.d.ts} +30 -2
  3. package/dist/avl/index.cjs +819 -429
  4. package/dist/avl/index.js +820 -430
  5. package/dist/{cn-Dm0VrLRG.d.ts → cn-CCaph5o9.d.ts} +1 -1
  6. package/dist/{cn-D3H9UzCW.d.cts → cn-CL0kdshO.d.cts} +1 -1
  7. package/dist/components/index.cjs +1649 -1482
  8. package/dist/components/index.d.cts +105 -202
  9. package/dist/components/index.d.ts +105 -202
  10. package/dist/components/index.js +705 -533
  11. package/dist/lib/drawable/three/index.cjs +16 -0
  12. package/dist/lib/drawable/three/index.d.cts +1 -1
  13. package/dist/lib/drawable/three/index.d.ts +1 -1
  14. package/dist/lib/drawable/three/index.js +16 -0
  15. package/dist/lib/index.cjs +28 -0
  16. package/dist/lib/index.d.cts +21 -3
  17. package/dist/lib/index.d.ts +21 -3
  18. package/dist/lib/index.js +27 -1
  19. package/dist/marketing/index.cjs +1 -0
  20. package/dist/marketing/index.js +1 -0
  21. package/dist/{paintDispatch-BXJgISot.d.cts → paintDispatch-DXygiK7M.d.cts} +39 -10
  22. package/dist/{paintDispatch-BXJgISot.d.ts → paintDispatch-DXygiK7M.d.ts} +39 -10
  23. package/dist/providers/index.cjs +701 -323
  24. package/dist/providers/index.d.cts +3 -3
  25. package/dist/providers/index.d.ts +3 -3
  26. package/dist/providers/index.js +698 -322
  27. package/dist/runtime/index.cjs +819 -429
  28. package/dist/runtime/index.d.cts +5 -2
  29. package/dist/runtime/index.d.ts +5 -2
  30. package/dist/runtime/index.js +820 -430
  31. package/package.json +6 -5
@@ -1,7 +1,10 @@
1
1
  import * as React85 from 'react';
2
- import React85__default, { createContext, useContext, useMemo, useRef, useEffect, useCallback, Suspense, useState, useLayoutEffect, lazy, useId, useSyncExternalStore } from 'react';
3
- import { EventBusContext, useTraitScopeChain, useEntitySchemaOptional, TraitScopeProvider, useCurrentPagePath, useGameAudioContextOptional } from '@almadar/ui/providers';
2
+ import React85__default, { createContext, useContext, useMemo, useRef, useEffect, useCallback, Suspense, useState, useSyncExternalStore, useLayoutEffect, lazy, useId } from 'react';
3
+ import { EventBusContext, useTraitScopeChain, useEntitySchemaOptional, useEntityBindingSnapshot, useTraitScope, TraitScopeProvider, useCurrentPagePath, useGameAudioContextOptional } from '@almadar/ui/providers';
4
4
  import { createLogger, isLogLevelEnabled } from '@almadar/logger';
5
+ import { createContextFromBindings, interpolateValue } from '@almadar/runtime';
6
+ import { ANONYMOUS_USER, isRenderBindingMarker, isInlineTrait } from '@almadar/core';
7
+ export { ANONYMOUS_USER } from '@almadar/core';
5
8
  import { clsx } from 'clsx';
6
9
  import { twMerge } from 'tailwind-merge';
7
10
  import * as LucideIcons2 from 'lucide-react';
@@ -68,11 +71,10 @@ import ReactMarkdown from 'react-markdown';
68
71
  import remarkGfm from 'remark-gfm';
69
72
  import remarkMath from 'remark-math';
70
73
  import rehypeKatex from 'rehype-katex';
71
- import { ANONYMOUS_USER, isInlineTrait } from '@almadar/core';
72
- export { ANONYMOUS_USER } from '@almadar/core';
73
74
  import { DndContext, pointerWithin, rectIntersection, closestCorners, useSensors, useSensor, PointerSensor, KeyboardSensor, useDroppable } from '@dnd-kit/core';
74
75
  import { useSortable, arrayMove, sortableKeyboardCoordinates, SortableContext, rectSortingStrategy, verticalListSortingStrategy } from '@dnd-kit/sortable';
75
76
  import { CSS } from '@dnd-kit/utilities';
77
+ import { ordered, lib } from 'emojilib';
76
78
  import { useNodeId, ReactFlowProvider, Handle, Position } from '@xyflow/react';
77
79
  import { forceSimulation, forceLink, forceManyBody, forceCollide, forceX, forceY } from 'd3-force';
78
80
  import { isDrawHostPattern, getPatternDefinition, getComponentForPattern as getComponentForPattern$1 } from '@almadar/core/patterns';
@@ -267,6 +269,75 @@ var init_useEventBus = __esm({
267
269
  useEventBus_default = useEventBus;
268
270
  }
269
271
  });
272
+ function resolveMarkerExpression(expression, entity, config, state) {
273
+ const ctx = createContextFromBindings({
274
+ entity,
275
+ payload: {},
276
+ state,
277
+ ...config !== void 0 ? { config } : {}
278
+ });
279
+ return interpolateValue(expression, ctx);
280
+ }
281
+ function isPlainObject(value) {
282
+ if (value === null || value === void 0 || typeof value !== "object") return false;
283
+ if (Array.isArray(value)) return false;
284
+ if (React85__default.isValidElement(value)) return false;
285
+ if (value instanceof Date) return false;
286
+ if (typeof value === "function") return false;
287
+ return true;
288
+ }
289
+ function walkValue(value, scopeTrait, entity, config, state) {
290
+ if (isRenderBindingMarker(value)) {
291
+ return { resolved: resolveMarkerExpression(value.expression, entity, config, state), changed: true };
292
+ }
293
+ if (Array.isArray(value)) {
294
+ const out = [];
295
+ let changed = false;
296
+ for (const item of value) {
297
+ const element = item;
298
+ const wasMarker = isRenderBindingMarker(element);
299
+ const { resolved, changed: itemChanged } = walkValue(element, scopeTrait, entity, config, state);
300
+ if (wasMarker && Array.isArray(resolved)) {
301
+ out.push(...resolved);
302
+ changed = true;
303
+ continue;
304
+ }
305
+ out.push(resolved);
306
+ if (itemChanged) changed = true;
307
+ }
308
+ return changed ? { resolved: out, changed: true } : { resolved: value, changed: false };
309
+ }
310
+ if (isPlainObject(value)) {
311
+ const sourceTrait = value._sourceTrait;
312
+ if (typeof sourceTrait === "string" && sourceTrait !== scopeTrait) {
313
+ return { resolved: value, changed: false };
314
+ }
315
+ const out = {};
316
+ let changed = false;
317
+ for (const [key, item] of Object.entries(value)) {
318
+ const { resolved, changed: itemChanged } = walkValue(item, scopeTrait, entity, config, state);
319
+ out[key] = resolved;
320
+ if (itemChanged) changed = true;
321
+ }
322
+ return changed ? { resolved: out, changed: true } : { resolved: value, changed: false };
323
+ }
324
+ return { resolved: value, changed: false };
325
+ }
326
+ function resolveRenderBindingMarkers(props, scopeTrait, entity, config, state) {
327
+ const out = {};
328
+ let changed = false;
329
+ for (const [key, value] of Object.entries(props)) {
330
+ const { resolved, changed: propChanged } = walkValue(value, scopeTrait, entity, config, state);
331
+ out[key] = resolved;
332
+ if (propChanged) changed = true;
333
+ }
334
+ return changed ? out : props;
335
+ }
336
+ var init_resolve_render_bindings = __esm({
337
+ "lib/resolve-render-bindings.ts"() {
338
+ "use client";
339
+ }
340
+ });
270
341
  function cn(...inputs) {
271
342
  return twMerge(clsx(inputs));
272
343
  }
@@ -774,9 +845,13 @@ function getAtlas(url, onReady) {
774
845
  onReady();
775
846
  }).catch(() => {
776
847
  atlasCache.set(url, null);
848
+ onReady();
777
849
  });
778
850
  return void 0;
779
851
  }
852
+ function atlasFailed(url) {
853
+ return atlasCache.get(url) === null;
854
+ }
780
855
  function subRectFor(atlas, sprite) {
781
856
  if (isTilesheet(atlas)) {
782
857
  let col;
@@ -1197,6 +1272,32 @@ function formatValue(value, format) {
1197
1272
  return String(value);
1198
1273
  }
1199
1274
  }
1275
+ function compareCellValues(a, b) {
1276
+ const aEmpty = a === null || a === void 0 || a === "";
1277
+ const bEmpty = b === null || b === void 0 || b === "";
1278
+ if (aEmpty || bEmpty) return aEmpty && bEmpty ? 0 : aEmpty ? 1 : -1;
1279
+ if (typeof a === "number" && typeof b === "number") return a - b;
1280
+ if (typeof a === "boolean" && typeof b === "boolean") return Number(a) - Number(b);
1281
+ const aNum = Number(a);
1282
+ const bNum = Number(b);
1283
+ if (Number.isFinite(aNum) && Number.isFinite(bNum)) return aNum - bNum;
1284
+ const aTime = dateLikeTime(a);
1285
+ const bTime = dateLikeTime(b);
1286
+ if (aTime !== null && bTime !== null) return aTime - bTime;
1287
+ return String(a).localeCompare(String(b));
1288
+ }
1289
+ function dateLikeTime(value) {
1290
+ if (value instanceof Date) return value.getTime();
1291
+ const text = String(value);
1292
+ if (!/\d/.test(text) || !/[-/:T]/.test(text)) return null;
1293
+ const time = Date.parse(text);
1294
+ return Number.isNaN(time) ? null : time;
1295
+ }
1296
+ function sortRows(rows, field, direction = "asc") {
1297
+ if (!field) return rows;
1298
+ const dir = direction === "desc" ? -1 : 1;
1299
+ return [...rows].sort((a, b) => dir * compareCellValues(a?.[field], b?.[field]));
1300
+ }
1200
1301
  var init_format = __esm({
1201
1302
  "lib/format.ts"() {
1202
1303
  }
@@ -3019,6 +3120,20 @@ var init_Input = __esm({
3019
3120
  const { t } = useTranslate();
3020
3121
  const eventBus = useEventBus();
3021
3122
  const type = inputType || htmlType || "text";
3123
+ const isDeclarative = typeof onChange === "string";
3124
+ const [localValue, setLocalValue] = React85__default.useState(value);
3125
+ const pendingEchoRef = React85__default.useRef(/* @__PURE__ */ new Set());
3126
+ React85__default.useEffect(() => {
3127
+ if (!isDeclarative) return;
3128
+ const incoming = value == null ? "" : String(value);
3129
+ if (pendingEchoRef.current.has(incoming)) {
3130
+ pendingEchoRef.current.delete(incoming);
3131
+ return;
3132
+ }
3133
+ pendingEchoRef.current.clear();
3134
+ setLocalValue(value);
3135
+ }, [value, isDeclarative]);
3136
+ const displayValue = isDeclarative ? localValue : value;
3022
3137
  const resolveIconNode = (i, cls) => {
3023
3138
  if (!i) return null;
3024
3139
  if (typeof i === "string") return /* @__PURE__ */ jsx(Icon, { name: i, className: cls });
@@ -3028,7 +3143,7 @@ var init_Input = __esm({
3028
3143
  const iconCls = "h-icon-default w-icon-default";
3029
3144
  const IconComponent = typeof iconProp === "string" ? resolveIcon(iconProp) : iconProp;
3030
3145
  const resolvedLeftIcon = (leftIcon ? resolveIconNode(leftIcon, iconCls) : null) || IconComponent && /* @__PURE__ */ jsx(IconComponent, { className: iconCls });
3031
- const showClearButton = clearable && value && String(value).length > 0;
3146
+ const showClearButton = clearable && displayValue && String(displayValue).length > 0;
3032
3147
  const isMultiline = type === "textarea";
3033
3148
  const baseClassName = cn(
3034
3149
  "block w-full rounded-sm transition-all duration-fast",
@@ -3047,6 +3162,10 @@ var init_Input = __esm({
3047
3162
  if (typeof onChange === "string") {
3048
3163
  const target = e.target;
3049
3164
  const payload = type === "checkbox" ? { checked: target.checked } : { value: target.value };
3165
+ if (type !== "checkbox") {
3166
+ pendingEchoRef.current.add(target.value);
3167
+ setLocalValue(target.value);
3168
+ }
3050
3169
  eventBus.emit(`UI:${onChange}`, payload);
3051
3170
  } else {
3052
3171
  onChange?.(e);
@@ -3077,7 +3196,7 @@ var init_Input = __esm({
3077
3196
  "select",
3078
3197
  {
3079
3198
  ref,
3080
- value,
3199
+ value: displayValue,
3081
3200
  onChange: handleChange,
3082
3201
  className: cn(baseClassName, "appearance-none pr-10", className),
3083
3202
  ...props,
@@ -3097,7 +3216,7 @@ var init_Input = __esm({
3097
3216
  "textarea",
3098
3217
  {
3099
3218
  ref,
3100
- value,
3219
+ value: displayValue,
3101
3220
  onChange: handleChange,
3102
3221
  rows,
3103
3222
  className: baseClassName,
@@ -3136,7 +3255,7 @@ var init_Input = __esm({
3136
3255
  {
3137
3256
  ref,
3138
3257
  type,
3139
- value,
3258
+ value: displayValue,
3140
3259
  onChange: handleChange,
3141
3260
  onKeyDown: handleKeyDown,
3142
3261
  className: baseClassName,
@@ -7059,46 +7178,45 @@ var init_useImageCache = __esm({
7059
7178
  });
7060
7179
 
7061
7180
  // lib/isometric.ts
7062
- function isoToScreen(tileX, tileY, scale, baseOffsetX, layout = "isometric") {
7063
- const scaledTileWidth = TILE_WIDTH * scale;
7064
- const scaledFloorHeight = FLOOR_HEIGHT * scale;
7181
+ function isoToScreen(tileX, tileY, cellWidth, baseOffsetX, layout = "isometric") {
7182
+ const w = cellWidth;
7183
+ const fh = cellWidth / 2;
7065
7184
  if (layout === "hex") {
7066
- const screenX2 = tileX * scaledTileWidth + (tileY & 1) * (scaledTileWidth / 2) + baseOffsetX;
7067
- const screenY2 = tileY * (scaledFloorHeight * 0.75);
7185
+ const screenX2 = tileX * w + (tileY & 1) * (w / 2) + baseOffsetX;
7186
+ const screenY2 = tileY * (fh * 0.75);
7068
7187
  return { x: screenX2, y: screenY2 };
7069
7188
  }
7070
7189
  if (layout === "flat") {
7071
- const screenX2 = tileX * scaledTileWidth + baseOffsetX;
7072
- const screenY2 = tileY * scaledTileWidth;
7190
+ const screenX2 = tileX * w + baseOffsetX;
7191
+ const screenY2 = tileY * w;
7073
7192
  return { x: screenX2, y: screenY2 };
7074
7193
  }
7075
- const screenX = (tileX - tileY) * (scaledTileWidth / 2) + baseOffsetX;
7076
- const screenY = (tileX + tileY) * (scaledFloorHeight / 2);
7194
+ const screenX = (tileX - tileY) * (w / 2) + baseOffsetX;
7195
+ const screenY = (tileX + tileY) * (fh / 2);
7077
7196
  return { x: screenX, y: screenY };
7078
7197
  }
7079
- function screenToIso(screenX, screenY, scale, baseOffsetX, layout = "isometric") {
7080
- const scaledTileWidth = TILE_WIDTH * scale;
7081
- const scaledFloorHeight = FLOOR_HEIGHT * scale;
7198
+ function screenToIso(screenX, screenY, cellWidth, baseOffsetX, layout = "isometric") {
7199
+ const w = cellWidth;
7200
+ const fh = cellWidth / 2;
7082
7201
  if (layout === "hex") {
7083
- const row = Math.round(screenY / (scaledFloorHeight * 0.75));
7084
- const col = Math.round((screenX - (row & 1) * (scaledTileWidth / 2) - baseOffsetX) / scaledTileWidth);
7202
+ const row = Math.round(screenY / (fh * 0.75));
7203
+ const col = Math.round((screenX - (row & 1) * (w / 2) - baseOffsetX) / w);
7085
7204
  return { x: col, y: row };
7086
7205
  }
7087
7206
  if (layout === "flat") {
7088
- const col = Math.round((screenX - baseOffsetX) / scaledTileWidth);
7089
- const row = Math.round(screenY / scaledTileWidth);
7207
+ const col = Math.round((screenX - baseOffsetX) / w);
7208
+ const row = Math.round(screenY / w);
7090
7209
  return { x: col, y: row };
7091
7210
  }
7092
7211
  const adjustedX = screenX - baseOffsetX;
7093
- const tileX = (adjustedX / (scaledTileWidth / 2) + screenY / (scaledFloorHeight / 2)) / 2;
7094
- const tileY = (screenY / (scaledFloorHeight / 2) - adjustedX / (scaledTileWidth / 2)) / 2;
7212
+ const tileX = (adjustedX / (w / 2) + screenY / (fh / 2)) / 2;
7213
+ const tileY = (screenY / (fh / 2) - adjustedX / (w / 2)) / 2;
7095
7214
  return { x: Math.round(tileX), y: Math.round(tileY) };
7096
7215
  }
7097
- var TILE_WIDTH, FLOOR_HEIGHT, DIAMOND_TOP_Y, BACKGROUND_FALLBACK_COLOR, MINIMAP_TERRAIN_COLORS;
7216
+ var TILE_WIDTH, DIAMOND_TOP_Y, BACKGROUND_FALLBACK_COLOR, MINIMAP_TERRAIN_COLORS;
7098
7217
  var init_isometric = __esm({
7099
7218
  "lib/isometric.ts"() {
7100
7219
  TILE_WIDTH = 256;
7101
- FLOOR_HEIGHT = 128;
7102
7220
  DIAMOND_TOP_Y = 374;
7103
7221
  BACKGROUND_FALLBACK_COLOR = "#1a1a2e";
7104
7222
  MINIMAP_TERRAIN_COLORS = {
@@ -7583,210 +7701,6 @@ var init_ChoiceButton = __esm({
7583
7701
  ChoiceButton.displayName = "ChoiceButton";
7584
7702
  }
7585
7703
  });
7586
- function SvgStage({
7587
- cols,
7588
- rows,
7589
- tileSize = 32,
7590
- background = "var(--color-background)",
7591
- tileClickEvent,
7592
- tileHoverEvent,
7593
- tileLeaveEvent,
7594
- keyMap,
7595
- keyUpMap,
7596
- className,
7597
- children
7598
- }) {
7599
- const eventBus = useEventBus();
7600
- const svgRef = useRef(null);
7601
- const pointerDownRef = useRef(null);
7602
- const cellFromClient = useCallback((clientX, clientY) => {
7603
- const svg = svgRef.current;
7604
- if (!svg) return null;
7605
- const rect = svg.getBoundingClientRect();
7606
- if (rect.width === 0 || rect.height === 0) return null;
7607
- const vbW = cols * tileSize;
7608
- const vbH = rows * tileSize;
7609
- const meet = Math.min(rect.width / vbW, rect.height / vbH);
7610
- const offsetX = (rect.width - vbW * meet) / 2;
7611
- const offsetY = (rect.height - vbH * meet) / 2;
7612
- const svgX = (clientX - rect.left - offsetX) / meet;
7613
- const svgY = (clientY - rect.top - offsetY) / meet;
7614
- return {
7615
- x: Math.min(Math.max(Math.floor(svgX / tileSize), 0), cols - 1),
7616
- y: Math.min(Math.max(Math.floor(svgY / tileSize), 0), rows - 1)
7617
- };
7618
- }, [cols, rows, tileSize]);
7619
- const handlePointerDown = useCallback((e) => {
7620
- pointerDownRef.current = { clientX: e.clientX, clientY: e.clientY };
7621
- }, []);
7622
- const handlePointerUp = useCallback((e) => {
7623
- const down = pointerDownRef.current;
7624
- pointerDownRef.current = null;
7625
- if (!tileClickEvent) return;
7626
- if (down && Math.hypot(e.clientX - down.clientX, e.clientY - down.clientY) > 5) return;
7627
- const cell = cellFromClient(e.clientX, e.clientY);
7628
- if (cell) eventBus.emit(`UI:${tileClickEvent}`, cell);
7629
- }, [cellFromClient, tileClickEvent, eventBus]);
7630
- const handlePointerMove = useCallback((e) => {
7631
- if (!tileHoverEvent) return;
7632
- const cell = cellFromClient(e.clientX, e.clientY);
7633
- if (cell) eventBus.emit(`UI:${tileHoverEvent}`, cell);
7634
- }, [cellFromClient, tileHoverEvent, eventBus]);
7635
- const handlePointerLeave = useCallback(() => {
7636
- pointerDownRef.current = null;
7637
- if (tileLeaveEvent) eventBus.emit(`UI:${tileLeaveEvent}`, {});
7638
- }, [tileLeaveEvent, eventBus]);
7639
- useEffect(() => {
7640
- if (!keyMap && !keyUpMap) return;
7641
- const onDown = (e) => {
7642
- const ev = keyMap?.[e.code];
7643
- if (ev) {
7644
- eventBus.emit(`UI:${ev}`, {});
7645
- e.preventDefault();
7646
- }
7647
- };
7648
- const onUp = (e) => {
7649
- const ev = keyUpMap?.[e.code];
7650
- if (ev) eventBus.emit(`UI:${ev}`, {});
7651
- };
7652
- window.addEventListener("keydown", onDown);
7653
- window.addEventListener("keyup", onUp);
7654
- return () => {
7655
- window.removeEventListener("keydown", onDown);
7656
- window.removeEventListener("keyup", onUp);
7657
- };
7658
- }, [keyMap, keyUpMap, eventBus]);
7659
- useEffect(() => {
7660
- if (!keyMap && !keyUpMap) return;
7661
- svgRef.current?.focus();
7662
- }, [keyMap, keyUpMap]);
7663
- const stageContext = useMemo(() => ({ tileSize }), [tileSize]);
7664
- return /* @__PURE__ */ jsxs(
7665
- "svg",
7666
- {
7667
- ref: svgRef,
7668
- "data-testid": "svg-stage",
7669
- viewBox: `0 0 ${cols * tileSize} ${rows * tileSize}`,
7670
- preserveAspectRatio: "xMidYMid meet",
7671
- className: cn("block h-full w-full", className),
7672
- tabIndex: keyMap || keyUpMap ? 0 : void 0,
7673
- onPointerDown: handlePointerDown,
7674
- onPointerMove: handlePointerMove,
7675
- onPointerUp: handlePointerUp,
7676
- onPointerLeave: handlePointerLeave,
7677
- children: [
7678
- /* @__PURE__ */ jsx("rect", { width: cols * tileSize, height: rows * tileSize, fill: background }),
7679
- /* @__PURE__ */ jsx(SvgStageContext.Provider, { value: stageContext, children })
7680
- ]
7681
- }
7682
- );
7683
- }
7684
- var SvgStageContext;
7685
- var init_SvgStage = __esm({
7686
- "components/game/molecules/SvgStage.tsx"() {
7687
- "use client";
7688
- init_cn();
7689
- init_useEventBus();
7690
- SvgStageContext = React85.createContext({ tileSize: 1 });
7691
- SvgStage.displayName = "SvgStage";
7692
- }
7693
- });
7694
- function SvgDrawShape({
7695
- shape,
7696
- x,
7697
- y,
7698
- width,
7699
- height,
7700
- radius,
7701
- radiusY,
7702
- points,
7703
- d,
7704
- x2,
7705
- y2,
7706
- fill,
7707
- stroke,
7708
- strokeWidth,
7709
- opacity,
7710
- className
7711
- }) {
7712
- const { tileSize } = useContext(SvgStageContext);
7713
- const cell = (v) => v === void 0 ? void 0 : v * tileSize;
7714
- const paint = {
7715
- fill: fill ?? (stroke === void 0 ? "var(--color-primary)" : "none"),
7716
- stroke,
7717
- strokeWidth,
7718
- opacity,
7719
- className
7720
- };
7721
- return /* @__PURE__ */ jsxs("g", { transform: `translate(${x * tileSize} ${y * tileSize})`, children: [
7722
- shape === "rect" && /* @__PURE__ */ jsx("rect", { width: cell(width), height: cell(height), ...paint }),
7723
- shape === "circle" && /* @__PURE__ */ jsx("circle", { r: cell(radius), ...paint }),
7724
- shape === "ellipse" && /* @__PURE__ */ jsx("ellipse", { rx: cell(radius), ry: cell(radiusY ?? radius), ...paint }),
7725
- shape === "polygon" && /* @__PURE__ */ jsx("polygon", { points, ...paint }),
7726
- shape === "polyline" && /* @__PURE__ */ jsx("polyline", { points, ...paint }),
7727
- shape === "path" && /* @__PURE__ */ jsx("path", { d, ...paint }),
7728
- shape === "line" && /* @__PURE__ */ jsx("line", { x2: cell(x2), y2: cell(y2), ...paint })
7729
- ] });
7730
- }
7731
- var init_SvgDrawShape = __esm({
7732
- "components/game/atoms/SvgDrawShape.tsx"() {
7733
- "use client";
7734
- init_SvgStage();
7735
- SvgDrawShape.displayName = "SvgDrawShape";
7736
- }
7737
- });
7738
- function SvgDrawGroup({
7739
- x = 0,
7740
- y = 0,
7741
- scale,
7742
- rotate,
7743
- opacity,
7744
- className,
7745
- children
7746
- }) {
7747
- const { tileSize } = useContext(SvgStageContext);
7748
- const transforms = [`translate(${x * tileSize} ${y * tileSize})`];
7749
- if (rotate !== void 0) transforms.push(`rotate(${rotate})`);
7750
- if (scale !== void 0) transforms.push(`scale(${scale})`);
7751
- return /* @__PURE__ */ jsx("g", { transform: transforms.join(" "), opacity, className, children });
7752
- }
7753
- var init_SvgDrawGroup = __esm({
7754
- "components/game/atoms/SvgDrawGroup.tsx"() {
7755
- "use client";
7756
- init_SvgStage();
7757
- SvgDrawGroup.displayName = "SvgDrawGroup";
7758
- }
7759
- });
7760
- function SvgDrawText({
7761
- x,
7762
- y,
7763
- text,
7764
- size = 12,
7765
- fill = "var(--color-foreground)",
7766
- anchor = "middle",
7767
- className
7768
- }) {
7769
- const { tileSize } = useContext(SvgStageContext);
7770
- return /* @__PURE__ */ jsx(
7771
- "text",
7772
- {
7773
- x: x * tileSize,
7774
- y: y * tileSize,
7775
- fontSize: size,
7776
- fill,
7777
- textAnchor: anchor,
7778
- className,
7779
- children: text
7780
- }
7781
- );
7782
- }
7783
- var init_SvgDrawText = __esm({
7784
- "components/game/atoms/SvgDrawText.tsx"() {
7785
- "use client";
7786
- init_SvgStage();
7787
- SvgDrawText.displayName = "SvgDrawText";
7788
- }
7789
- });
7790
7704
  function ControlGrid({
7791
7705
  kind,
7792
7706
  buttons = DEFAULT_BUTTONS,
@@ -8490,6 +8404,7 @@ function MiniMap({
8490
8404
  const cached = imgCacheRef.current.get(url);
8491
8405
  if (cached) return cached.complete ? cached : null;
8492
8406
  const img = new Image();
8407
+ img.crossOrigin = "anonymous";
8493
8408
  img.src = url;
8494
8409
  img.onload = () => {
8495
8410
  const canvas = canvasRef.current;
@@ -8657,7 +8572,7 @@ function useCamera(initial) {
8657
8572
  const handleWheel = useCallback((e, drawFn) => {
8658
8573
  e.preventDefault();
8659
8574
  const zoomDelta = e.deltaY > 0 ? 0.9 : 1.1;
8660
- cameraRef.current.zoom = Math.max(0.5, Math.min(3, cameraRef.current.zoom * zoomDelta));
8575
+ cameraRef.current.zoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, cameraRef.current.zoom * zoomDelta));
8661
8576
  drawFn?.();
8662
8577
  }, []);
8663
8578
  const handlePointerDown = useCallback((e) => {
@@ -8678,7 +8593,7 @@ function useCamera(initial) {
8678
8593
  const zoomAtPoint = useCallback((factor, centerX, centerY, viewportSize, drawFn) => {
8679
8594
  const cam = cameraRef.current;
8680
8595
  const oldZoom = cam.zoom;
8681
- const newZoom = Math.max(0.5, Math.min(3, oldZoom * factor));
8596
+ const newZoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, oldZoom * factor));
8682
8597
  if (newZoom === oldZoom) {
8683
8598
  drawFn?.();
8684
8599
  return;
@@ -8730,9 +8645,12 @@ function useCamera(initial) {
8730
8645
  lerpToTarget
8731
8646
  };
8732
8647
  }
8648
+ var MIN_ZOOM, MAX_ZOOM;
8733
8649
  var init_useCamera = __esm({
8734
8650
  "hooks/useCamera.ts"() {
8735
8651
  "use client";
8652
+ MIN_ZOOM = 0.05;
8653
+ MAX_ZOOM = 10;
8736
8654
  }
8737
8655
  });
8738
8656
  function localPoint(canvas, clientX, clientY) {
@@ -8853,6 +8771,7 @@ function getOrLoadImage(url, onReady) {
8853
8771
  return null;
8854
8772
  }
8855
8773
  const img = new Image();
8774
+ img.crossOrigin = "anonymous";
8856
8775
  const entry = { img, status: "pending", onReady };
8857
8776
  cache.set(url, entry);
8858
8777
  updateAssetStatus(url, "pending");
@@ -8864,10 +8783,14 @@ function getOrLoadImage(url, onReady) {
8864
8783
  img.onerror = () => {
8865
8784
  entry.status = "failed";
8866
8785
  updateAssetStatus(url, "failed");
8786
+ entry.onReady?.();
8867
8787
  };
8868
8788
  img.src = url;
8869
8789
  return null;
8870
8790
  }
8791
+ function getImageStatus(url) {
8792
+ return cache.get(url)?.status;
8793
+ }
8871
8794
  var cache;
8872
8795
  var init_imageCache = __esm({
8873
8796
  "lib/imageCache.ts"() {
@@ -8976,6 +8899,15 @@ function createWebPainter(ctx, onAssetLoad) {
8976
8899
  ctx.lineWidth = lineWidth;
8977
8900
  ctx.stroke();
8978
8901
  },
8902
+ fillPath(d, color) {
8903
+ ctx.fillStyle = color;
8904
+ ctx.fill(new Path2D(d));
8905
+ },
8906
+ strokePath(d, color, lineWidth = 1) {
8907
+ ctx.strokeStyle = color;
8908
+ ctx.lineWidth = lineWidth;
8909
+ ctx.stroke(new Path2D(d));
8910
+ },
8979
8911
  text(str, x, y, style) {
8980
8912
  if (style.font) ctx.font = style.font;
8981
8913
  ctx.fillStyle = style.color;
@@ -8997,12 +8929,13 @@ var init_webPainter2d = __esm({
8997
8929
 
8998
8930
  // lib/drawable/projector.ts
8999
8931
  function create2DProjector(opts) {
9000
- const { scale, baseOffsetX, layout } = opts;
9001
- const tileWidth = layout === "free" ? 1 : TILE_WIDTH * scale;
9002
- const floorHeight = layout === "free" ? 1 : FLOOR_HEIGHT * scale;
9003
- const diamondTopY = layout === "free" ? 0 : (opts.diamondTopY ?? DIAMOND_TOP_Y) * scale;
8932
+ const { baseOffsetX, layout } = opts;
8933
+ const tw = opts.tileWidth ?? TILE_WIDTH;
8934
+ const tileWidth = layout === "free" ? 1 : tw;
8935
+ const floorHeight = layout === "free" ? 1 : tw / 2;
8936
+ const diamondTopY = layout === "free" ? 0 : opts.diamondTopY ?? tw * (DIAMOND_TOP_Y / TILE_WIDTH);
9004
8937
  const squareGrid = layout === "flat" || layout === "free";
9005
- const project = (pos) => layout === "free" ? { x: pos.x, y: pos.y } : isoToScreen(pos.x, pos.y, scale, baseOffsetX, layout);
8938
+ const project = (pos) => layout === "free" ? { x: pos.x, y: pos.y } : isoToScreen(pos.x, pos.y, tw, baseOffsetX, layout);
9006
8939
  const anchorPoint = (pos, anchor) => {
9007
8940
  const base = project(pos);
9008
8941
  if (anchor === "top-left") return base;
@@ -9033,7 +8966,7 @@ function create2DProjector(opts) {
9033
8966
  { x: base.x, y: topY + floorHeight / 2 }
9034
8967
  ];
9035
8968
  };
9036
- return { project, anchorPoint, cellPath, tileWidth, floorHeight, diamondTopY, scale, squareGrid };
8969
+ return { project, anchorPoint, cellPath, tileWidth, floorHeight, diamondTopY, squareGrid, worldPixelDirect: layout === "free" };
9037
8970
  }
9038
8971
  var init_projector = __esm({
9039
8972
  "lib/drawable/projector.ts"() {
@@ -9049,32 +8982,67 @@ var init_contract = __esm({
9049
8982
  "lib/drawable/contract.ts"() {
9050
8983
  }
9051
8984
  });
9052
-
9053
- // components/game/atoms/DrawSprite.tsx
8985
+ function warnMissingOnce(reason, node) {
8986
+ const key = `${reason}:${node.asset.url}:${String(node.asset.atlas)}:${String(node.asset.sprite)}`;
8987
+ if (loggedMissing.has(key)) return;
8988
+ loggedMissing.add(key);
8989
+ spriteLog.warn("draw-sprite asset unresolvable \u2014 painting fallback square", { reason, url: node.asset.url, atlas: node.asset.atlas, sprite: node.asset.sprite });
8990
+ }
8991
+ function paintFallbackSquare(painter, node, dctx, reason) {
8992
+ warnMissingOnce(reason, node);
8993
+ const tw = dctx.projector.tileWidth;
8994
+ const natural = dctx.projector.worldPixelDirect ? FALLBACK_WORLD_PX : tw;
8995
+ const w = node.width !== void 0 ? node.width * tw : natural;
8996
+ const h = node.height !== void 0 ? node.height * tw : natural;
8997
+ const anchor = node.anchor ?? "top-left";
8998
+ const p = dctx.projector.anchorPoint(node.position, anchor);
8999
+ const dx = anchor === "top-left" ? p.x : p.x - w / 2;
9000
+ const dy = anchor === "ground" ? p.y - h : anchor === "center" ? p.y - h / 2 : p.y;
9001
+ painter.save();
9002
+ if (node.opacity !== void 0 && node.opacity !== 1) painter.setAlpha(node.opacity);
9003
+ painter.fillRect(dx, dy, w, h, "#9b8f7f");
9004
+ painter.strokeRect(dx, dy, w, h, "#5e564b", Math.max(1, tw / 32));
9005
+ painter.restore();
9006
+ }
9054
9007
  function DrawSprite(_props) {
9055
9008
  return null;
9056
9009
  }
9057
- var paintSprite;
9010
+ var spriteLog, loggedMissing, FALLBACK_WORLD_PX, paintSprite;
9058
9011
  var init_DrawSprite = __esm({
9059
9012
  "components/game/atoms/DrawSprite.tsx"() {
9060
9013
  "use client";
9061
9014
  init_atlasSlice();
9015
+ init_imageCache();
9062
9016
  init_contract();
9017
+ spriteLog = createLogger("almadar:ui:draw-sprite");
9018
+ loggedMissing = /* @__PURE__ */ new Set();
9019
+ FALLBACK_WORLD_PX = 32;
9063
9020
  paintSprite = (painter, node, dctx) => {
9064
9021
  if (!node.asset?.url || !isValidScenePos(node.position)) return;
9065
9022
  const tex = painter.resolveTexture(node.asset.url);
9066
- if (!tex) return;
9023
+ if (!tex) {
9024
+ if (getImageStatus(node.asset.url) === "failed") paintFallbackSquare(painter, node, dctx, "texture-failed");
9025
+ return;
9026
+ }
9067
9027
  let src = typeof node.frame === "object" ? node.frame : void 0;
9068
9028
  if (!src && isAtlasAsset(node.asset)) {
9069
9029
  const atlas = getAtlas(node.asset.atlas, dctx.invalidate);
9070
- if (!atlas) return;
9030
+ if (!atlas) {
9031
+ if (atlasFailed(node.asset.atlas)) paintFallbackSquare(painter, node, dctx, "atlas-failed");
9032
+ return;
9033
+ }
9071
9034
  const r = subRectFor(atlas, node.asset.sprite);
9072
- if (!r) return;
9035
+ if (!r) {
9036
+ paintFallbackSquare(painter, node, dctx, "sprite-missing");
9037
+ return;
9038
+ }
9073
9039
  src = { x: r.sx, y: r.sy, w: r.sw, h: r.sh };
9074
9040
  }
9075
9041
  const tw = dctx.projector.tileWidth;
9076
- const w = node.width !== void 0 ? node.width * tw : src ? src.w : tex.width;
9077
- const h = node.height !== void 0 ? node.height * tw : src ? src.h : tex.height;
9042
+ const fallbackW = dctx.projector.worldPixelDirect ? src ? src.w : tex.width : tw;
9043
+ const fallbackH = dctx.projector.worldPixelDirect ? src ? src.h : tex.height : tw;
9044
+ const w = node.width !== void 0 ? node.width * tw : fallbackW;
9045
+ const h = node.height !== void 0 ? node.height * tw : fallbackH;
9078
9046
  const anchor = node.anchor ?? "top-left";
9079
9047
  const p = dctx.projector.anchorPoint(node.position, anchor);
9080
9048
  const dx = anchor === "top-left" ? p.x : p.x - w / 2;
@@ -9151,6 +9119,16 @@ var init_DrawShape = __esm({
9151
9119
  if (node.stroke) painter.strokePoly(pts, node.stroke, node.strokeWidth ?? 1, true);
9152
9120
  break;
9153
9121
  }
9122
+ case "path": {
9123
+ if (!node.d) break;
9124
+ const base = dctx.projector.project(node.position);
9125
+ const tw = dctx.projector.tileWidth;
9126
+ painter.translate(base.x, base.y);
9127
+ painter.scale(tw, tw);
9128
+ if (node.fill) painter.fillPath(node.d, node.fill);
9129
+ if (node.stroke) painter.strokePath(node.d, node.stroke, (node.strokeWidth ?? 1) / tw);
9130
+ break;
9131
+ }
9154
9132
  }
9155
9133
  painter.restore();
9156
9134
  };
@@ -9242,6 +9220,19 @@ function paintDrawable(painter, node, dctx) {
9242
9220
  case "draw-text":
9243
9221
  paintText(painter, node, dctx);
9244
9222
  break;
9223
+ case "draw-group": {
9224
+ if (!isValidScenePos(node.position)) break;
9225
+ if (!Array.isArray(node.items)) break;
9226
+ const p = dctx.projector.project(node.position);
9227
+ painter.save();
9228
+ painter.translate(p.x, p.y);
9229
+ if (node.scale !== void 0) painter.scale(node.scale, node.scale);
9230
+ if (node.rotate !== void 0) painter.rotate(node.rotate);
9231
+ if (node.opacity !== void 0 && node.opacity !== 1) painter.setAlpha(node.opacity);
9232
+ for (const item of node.items) paintDrawable(painter, item, dctx);
9233
+ painter.restore();
9234
+ break;
9235
+ }
9245
9236
  case "draw-sprite-layer":
9246
9237
  paintSpriteLayer(painter, node, dctx);
9247
9238
  break;
@@ -9255,6 +9246,7 @@ function paintDrawable(painter, node, dctx) {
9255
9246
  }
9256
9247
  var init_paintDispatch = __esm({
9257
9248
  "lib/drawable/paintDispatch.ts"() {
9249
+ init_contract();
9258
9250
  init_DrawSprite();
9259
9251
  init_DrawShape();
9260
9252
  init_DrawText();
@@ -9272,6 +9264,7 @@ function collectDrawnItems(nodes) {
9272
9264
  case "draw-sprite":
9273
9265
  case "draw-shape":
9274
9266
  case "draw-text":
9267
+ case "draw-group":
9275
9268
  if (isValidScenePos(n.position)) out.push({ pos: n.position, id: n.id });
9276
9269
  break;
9277
9270
  case "draw-sprite-layer":
@@ -9315,6 +9308,8 @@ function Canvas2D({
9315
9308
  keyUpMap,
9316
9309
  camera = "pan-zoom",
9317
9310
  scale = 0.4,
9311
+ tileWidth,
9312
+ fit = false,
9318
9313
  showMinimap = true,
9319
9314
  followTarget,
9320
9315
  cameraPos,
@@ -9357,9 +9352,32 @@ function Canvas2D({
9357
9352
  observer2.observe(el);
9358
9353
  return () => observer2.disconnect();
9359
9354
  }, []);
9360
- const scaledTileWidth = TILE_WIDTH * scale;
9361
- const scaledFloorHeight = FLOOR_HEIGHT * scale;
9362
- const scaledDiamondTopY = DIAMOND_TOP_Y * scale;
9355
+ const [atlasVersion, setAtlasVersion] = useState(0);
9356
+ const bumpAtlas = useCallback(() => setAtlasVersion((v) => v + 1), []);
9357
+ const detectedTileWidth = useMemo(() => {
9358
+ for (const n of drawables ?? []) {
9359
+ const refs = [];
9360
+ if (n.type === "draw-sprite") refs.push(n.asset);
9361
+ else if (n.type === "draw-sprite-layer") for (const it of n.items) refs.push(it.asset);
9362
+ for (const a of refs) {
9363
+ if (a && isAtlasAsset(a) && a.atlas) {
9364
+ const atlas = getAtlas(a.atlas, bumpAtlas);
9365
+ if (atlas) {
9366
+ if ("tileWidth" in atlas) return atlas.tileWidth;
9367
+ if ("subTextures" in atlas) {
9368
+ const first = Object.values(atlas.subTextures)[0];
9369
+ if (first && typeof first.width === "number") return first.width;
9370
+ }
9371
+ }
9372
+ }
9373
+ }
9374
+ }
9375
+ return void 0;
9376
+ }, [drawables, atlasVersion]);
9377
+ const nativeTileW = tileWidth ?? detectedTileWidth ?? TILE_WIDTH;
9378
+ const scaledTileWidth = nativeTileW;
9379
+ const scaledFloorHeight = nativeTileW / 2;
9380
+ const scaledDiamondTopY = nativeTileW * (DIAMOND_TOP_Y / TILE_WIDTH);
9363
9381
  const drawnItems = useMemo(() => collectDrawnItems(drawables ?? []), [drawables]);
9364
9382
  const scenePositions = useMemo(() => drawnItems.map((i) => i.pos), [drawnItems]);
9365
9383
  const hitIndex = useMemo(() => buildHitIndex(drawnItems), [drawnItems]);
@@ -9377,14 +9395,36 @@ function Canvas2D({
9377
9395
  if (isFree || projection === "flat" || projection === "side") return 0;
9378
9396
  return (gridExtent.height - 1) * (scaledTileWidth / 2);
9379
9397
  }, [isFree, projection, gridExtent.height, scaledTileWidth]);
9398
+ const effectiveZoom = useMemo(() => {
9399
+ if (isFree || projection === "side") return scale;
9400
+ if (!fit) {
9401
+ const z2 = TILE_WIDTH * scale * scale / nativeTileW;
9402
+ return Number.isFinite(z2) && z2 > 0 ? z2 : scale;
9403
+ }
9404
+ if (!viewportSize.width || gridExtent.width < 2 || gridExtent.height < 2) return scale;
9405
+ let boardW;
9406
+ let boardH;
9407
+ if (projection === "flat") {
9408
+ boardW = gridExtent.width * nativeTileW;
9409
+ boardH = gridExtent.height * nativeTileW;
9410
+ } else if (projection === "hex") {
9411
+ boardW = (gridExtent.width + 0.5) * nativeTileW;
9412
+ boardH = gridExtent.height * (nativeTileW / 2) * 0.75 + nativeTileW / 2;
9413
+ } else {
9414
+ boardW = (gridExtent.width + gridExtent.height) * (nativeTileW / 2);
9415
+ boardH = (gridExtent.width + gridExtent.height) * (nativeTileW / 4);
9416
+ }
9417
+ const z = Math.min(viewportSize.width * 0.85 / boardW, viewportSize.height * 0.85 / boardH);
9418
+ return Number.isFinite(z) && z > 0 ? z : scale;
9419
+ }, [isFree, projection, fit, viewportSize, gridExtent, nativeTileW, scale]);
9380
9420
  const projector = useMemo(
9381
- () => create2DProjector({ scale, baseOffsetX, layout }),
9382
- [scale, baseOffsetX, layout]
9421
+ () => create2DProjector({ tileWidth: nativeTileW, baseOffsetX, layout }),
9422
+ [nativeTileW, baseOffsetX, layout]
9383
9423
  );
9384
9424
  const unproject = useCallback((screenX, screenY) => {
9385
9425
  if (projection === "free" || projection === "side") return { x: Math.round(screenX), y: Math.round(screenY) };
9386
- return screenToIso(screenX, screenY, scale, baseOffsetX, projection);
9387
- }, [projection, scale, baseOffsetX]);
9426
+ return screenToIso(screenX, screenY, nativeTileW, baseOffsetX, projection);
9427
+ }, [projection, nativeTileW, baseOffsetX]);
9388
9428
  const bgUrls = useMemo(() => backgroundImage ? [backgroundImage.url] : [], [backgroundImage]);
9389
9429
  const { getImage, pendingCount: _imagePendingCount } = useImageCache(bgUrls);
9390
9430
  useEffect(() => {
@@ -9411,9 +9451,7 @@ function Canvas2D({
9411
9451
  zoomAtPoint,
9412
9452
  screenToWorld,
9413
9453
  lerpToTarget
9414
- } = useCamera({ zoom: scale });
9415
- const [atlasVersion, setAtlasVersion] = useState(0);
9416
- const bumpAtlas = useCallback(() => setAtlasVersion((v) => v + 1), []);
9454
+ } = useCamera({ zoom: effectiveZoom });
9417
9455
  const miniMapTiles = useMemo(() => {
9418
9456
  if (!showMinimap) return [];
9419
9457
  const color = MINIMAP_TERRAIN_COLORS.default;
@@ -9488,6 +9526,13 @@ function Canvas2D({
9488
9526
  useEffect(() => {
9489
9527
  draw();
9490
9528
  }, [_imagePendingCount, draw]);
9529
+ const userZoomedRef = useRef(false);
9530
+ useEffect(() => {
9531
+ if (userZoomedRef.current) return;
9532
+ if (cameraRef.current.zoom === effectiveZoom) return;
9533
+ cameraRef.current.zoom = effectiveZoom;
9534
+ draw();
9535
+ }, [effectiveZoom, cameraRef, draw]);
9491
9536
  useEffect(() => {
9492
9537
  draw();
9493
9538
  }, [atlasVersion, draw]);
@@ -9544,7 +9589,9 @@ function Canvas2D({
9544
9589
  if (tileLeaveEvent) eventBus.emit(`UI:${tileLeaveEvent}`, {});
9545
9590
  }, [handleMouseLeave, tileLeaveEvent, eventBus]);
9546
9591
  const applyZoom = useCallback((factor, centerX, centerY) => {
9547
- if (enableCamera) zoomAtPoint(factor, centerX, centerY, viewportSize, () => draw());
9592
+ if (!enableCamera) return;
9593
+ userZoomedRef.current = true;
9594
+ zoomAtPoint(factor, centerX, centerY, viewportSize, () => draw());
9548
9595
  }, [enableCamera, zoomAtPoint, viewportSize, draw]);
9549
9596
  const applyPanDelta = useCallback((dx, dy) => {
9550
9597
  if (enableCamera) panBy(dx, dy, () => draw());
@@ -9729,6 +9776,8 @@ function Canvas({
9729
9776
  isLoading,
9730
9777
  unitScale,
9731
9778
  showMinimap,
9779
+ fit,
9780
+ tileWidth,
9732
9781
  backgroundImage,
9733
9782
  backgroundColor,
9734
9783
  worldWidth,
@@ -9786,6 +9835,8 @@ function Canvas({
9786
9835
  projection,
9787
9836
  camera: to2DCamera(camera?.mode),
9788
9837
  ...zoom !== void 0 ? { scale: zoom } : {},
9838
+ ...fit !== void 0 ? { fit } : {},
9839
+ ...tileWidth !== void 0 ? { tileWidth } : {},
9789
9840
  ...camera?.target !== void 0 ? { followTarget: camera.target } : {},
9790
9841
  ...camera?.pos !== void 0 ? { cameraPos: camera.pos } : {},
9791
9842
  showMinimap,
@@ -9812,32 +9863,6 @@ var init_Canvas = __esm({
9812
9863
  Canvas.displayName = "Canvas";
9813
9864
  }
9814
9865
  });
9815
- function SvgDrawShapeLayer({
9816
- items,
9817
- fill,
9818
- stroke,
9819
- strokeWidth,
9820
- opacity
9821
- }) {
9822
- return /* @__PURE__ */ jsx("g", { children: items.map(({ id, ...shape }) => /* @__PURE__ */ jsx(
9823
- SvgDrawShape,
9824
- {
9825
- ...shape,
9826
- fill: shape.fill ?? fill,
9827
- stroke: shape.stroke ?? stroke,
9828
- strokeWidth: shape.strokeWidth ?? strokeWidth,
9829
- opacity: shape.opacity ?? opacity
9830
- },
9831
- id
9832
- )) });
9833
- }
9834
- var init_SvgDrawShapeLayer = __esm({
9835
- "components/game/molecules/SvgDrawShapeLayer.tsx"() {
9836
- "use client";
9837
- init_SvgDrawShape();
9838
- SvgDrawShapeLayer.displayName = "SvgDrawShapeLayer";
9839
- }
9840
- });
9841
9866
  function GameAudioToggle({
9842
9867
  size = "sm",
9843
9868
  className,
@@ -23804,6 +23829,8 @@ function DataList({
23804
23829
  hasMore,
23805
23830
  children,
23806
23831
  pageSize = 5,
23832
+ sortBy,
23833
+ sortDirection,
23807
23834
  renderItem: schemaRenderItem,
23808
23835
  dragGroup,
23809
23836
  accepts,
@@ -23832,7 +23859,11 @@ function DataList({
23832
23859
  dndItemIdField,
23833
23860
  dndRoot
23834
23861
  });
23835
- const allData = dnd.orderedItems;
23862
+ const orderedData = dnd.orderedItems;
23863
+ const allData = React85__default.useMemo(
23864
+ () => sortRows(orderedData, sortBy, sortDirection),
23865
+ [orderedData, sortBy, sortDirection]
23866
+ );
23836
23867
  const data = pageSize > 0 ? allData.slice(0, visibleCount) : allData;
23837
23868
  const hasMoreLocal = pageSize > 0 && visibleCount < allData.length;
23838
23869
  const hasRenderProp = typeof children === "function";
@@ -23869,7 +23900,7 @@ function DataList({
23869
23900
  };
23870
23901
  eventBus.emit(`UI:${action.event}`, payload);
23871
23902
  };
23872
- const renderItemActions = (itemData) => {
23903
+ const renderItemActions = (itemData, onPrimary = false) => {
23873
23904
  if (!itemActions || itemActions.length === 0) return null;
23874
23905
  const inline = maxInlineActions != null ? itemActions.slice(0, maxInlineActions) : itemActions;
23875
23906
  const overflow = maxInlineActions != null ? itemActions.slice(maxInlineActions) : [];
@@ -23882,7 +23913,12 @@ function DataList({
23882
23913
  onClick: handleActionClick(action, itemData),
23883
23914
  "data-testid": `action-${action.event}`,
23884
23915
  "data-row-id": String(itemData.id),
23885
- className: cn(action.variant === "danger" && "text-error hover:bg-error/10"),
23916
+ className: cn(
23917
+ action.variant === "danger" && "text-error hover:bg-error/10",
23918
+ // Must sit on the Button itself: the variant's own text colour
23919
+ // beats an inherited one from the row wrapper.
23920
+ onPrimary && "!text-primary-foreground hover:bg-primary-foreground/15"
23921
+ ),
23886
23922
  children: [
23887
23923
  action.icon && renderIconInput2(action.icon, { size: "xs", className: "mr-1" }),
23888
23924
  action.label
@@ -23982,7 +24018,11 @@ function DataList({
23982
24018
  metaFields.length > 0 && /* @__PURE__ */ jsx(HStack, { gap: "xs", className: "mt-1 flex-wrap", children: metaFields.map((f3) => {
23983
24019
  const v = getNestedValue(itemData, f3.name);
23984
24020
  if (v === void 0 || v === null || v === "") return null;
23985
- return f3.variant === "badge" ? /* @__PURE__ */ jsx(Badge, { variant: statusVariant3(String(v)), children: String(v) }, f3.name) : /* @__PURE__ */ jsx(
24021
+ return f3.variant === "badge" ? (
24022
+ // `format` applies here too — a boolean field badged
24023
+ // without it renders the raw "false" instead of "No".
24024
+ /* @__PURE__ */ jsx(Badge, { variant: statusVariant3(String(v)), children: formatValue2(v, f3.format) }, f3.name)
24025
+ ) : /* @__PURE__ */ jsx(
23986
24026
  Typography,
23987
24027
  {
23988
24028
  variant: "caption",
@@ -24001,7 +24041,7 @@ function DataList({
24001
24041
  children: formatDate(timestamp)
24002
24042
  }
24003
24043
  ) : /* @__PURE__ */ jsx("span", {}),
24004
- renderItemActions(itemData)
24044
+ renderItemActions(itemData, isSent)
24005
24045
  ] })
24006
24046
  ]
24007
24047
  }
@@ -24094,7 +24134,7 @@ function DataList({
24094
24134
  if (val === void 0 || val === null) return null;
24095
24135
  return /* @__PURE__ */ jsxs(HStack, { gap: "xs", className: "items-center flex-shrink-0", children: [
24096
24136
  field.icon && renderIconInput2(field.icon, { size: "xs" }),
24097
- /* @__PURE__ */ jsx(Badge, { variant: statusVariant3(String(val)), children: String(val) })
24137
+ /* @__PURE__ */ jsx(Badge, { variant: statusVariant3(String(val)), children: formatValue2(val, field.format) })
24098
24138
  ] }, field.name);
24099
24139
  })
24100
24140
  ] }),
@@ -24333,6 +24373,182 @@ var init_FormSection = __esm({
24333
24373
  FormActions.displayName = "FormActions";
24334
24374
  }
24335
24375
  });
24376
+ var ALL_CATEGORY, MAX_RENDERED, GridPicker;
24377
+ var init_GridPicker = __esm({
24378
+ "components/core/molecules/GridPicker.tsx"() {
24379
+ "use client";
24380
+ init_cn();
24381
+ init_Input();
24382
+ init_Badge();
24383
+ init_Stack();
24384
+ ALL_CATEGORY = "__all__";
24385
+ MAX_RENDERED = 300;
24386
+ GridPicker = ({
24387
+ items,
24388
+ value,
24389
+ onChange,
24390
+ categories,
24391
+ searchPlaceholder,
24392
+ renderThumbnail,
24393
+ cellSize = 32,
24394
+ className
24395
+ }) => {
24396
+ const [search, setSearch] = useState("");
24397
+ const [activeCategory, setActiveCategory] = useState(ALL_CATEGORY);
24398
+ const gridRef = useRef(null);
24399
+ const categoryChips = useMemo(() => {
24400
+ if (categories !== void 0) return categories;
24401
+ const seen = [];
24402
+ for (const item of items) {
24403
+ if (!seen.includes(item.category)) seen.push(item.category);
24404
+ }
24405
+ return seen;
24406
+ }, [categories, items]);
24407
+ const filtered = useMemo(() => {
24408
+ const needle = search.trim().toLowerCase();
24409
+ return items.filter((item) => {
24410
+ const matchesCategory = activeCategory === ALL_CATEGORY || item.category === activeCategory;
24411
+ const matchesSearch = needle === "" || item.label.toLowerCase().includes(needle) || item.keywords !== void 0 && item.keywords.some((k) => k.toLowerCase().includes(needle));
24412
+ return matchesCategory && matchesSearch;
24413
+ });
24414
+ }, [items, search, activeCategory]);
24415
+ const visible = useMemo(() => filtered.slice(0, MAX_RENDERED), [filtered]);
24416
+ const truncated = filtered.length - visible.length;
24417
+ const select = useCallback(
24418
+ (item) => {
24419
+ onChange(item.id);
24420
+ },
24421
+ [onChange]
24422
+ );
24423
+ const handleKeyDown = useCallback(
24424
+ (e, index) => {
24425
+ const cells = gridRef.current?.querySelectorAll(
24426
+ "[data-gridpicker-cell]"
24427
+ );
24428
+ if (cells === void 0 || cells.length === 0) return;
24429
+ const columns = (() => {
24430
+ const grid = gridRef.current;
24431
+ if (grid === null) return 1;
24432
+ const style = window.getComputedStyle(grid);
24433
+ const cols = style.gridTemplateColumns.split(" ").filter(Boolean).length;
24434
+ return cols > 0 ? cols : 1;
24435
+ })();
24436
+ let next = -1;
24437
+ if (e.key === "ArrowRight") next = index + 1;
24438
+ else if (e.key === "ArrowLeft") next = index - 1;
24439
+ else if (e.key === "ArrowDown") next = index + columns;
24440
+ else if (e.key === "ArrowUp") next = index - columns;
24441
+ else if (e.key === "Enter" || e.key === " ") {
24442
+ e.preventDefault();
24443
+ select(filtered[index]);
24444
+ return;
24445
+ } else {
24446
+ return;
24447
+ }
24448
+ e.preventDefault();
24449
+ if (next >= 0 && next < cells.length) {
24450
+ cells[next].focus();
24451
+ }
24452
+ },
24453
+ [filtered, select]
24454
+ );
24455
+ return /* @__PURE__ */ jsxs(VStack, { gap: "sm", className: cn("w-full", className), children: [
24456
+ /* @__PURE__ */ jsx(
24457
+ Input,
24458
+ {
24459
+ type: "search",
24460
+ icon: "search",
24461
+ value: search,
24462
+ placeholder: searchPlaceholder,
24463
+ clearable: true,
24464
+ onClear: () => setSearch(""),
24465
+ onChange: (e) => setSearch(e.target.value)
24466
+ }
24467
+ ),
24468
+ categoryChips.length > 0 && /* @__PURE__ */ jsxs(HStack, { gap: "xs", wrap: true, children: [
24469
+ /* @__PURE__ */ jsx(
24470
+ Badge,
24471
+ {
24472
+ variant: activeCategory === ALL_CATEGORY ? "primary" : "neutral",
24473
+ size: "sm",
24474
+ role: "button",
24475
+ tabIndex: 0,
24476
+ "aria-pressed": activeCategory === ALL_CATEGORY,
24477
+ className: "cursor-pointer",
24478
+ onClick: () => setActiveCategory(ALL_CATEGORY),
24479
+ onKeyDown: (e) => {
24480
+ if (e.key === "Enter" || e.key === " ") {
24481
+ e.preventDefault();
24482
+ setActiveCategory(ALL_CATEGORY);
24483
+ }
24484
+ },
24485
+ children: "All"
24486
+ }
24487
+ ),
24488
+ categoryChips.map((category) => /* @__PURE__ */ jsx(
24489
+ Badge,
24490
+ {
24491
+ variant: activeCategory === category ? "primary" : "neutral",
24492
+ size: "sm",
24493
+ role: "button",
24494
+ tabIndex: 0,
24495
+ "aria-pressed": activeCategory === category,
24496
+ className: "cursor-pointer",
24497
+ onClick: () => setActiveCategory(category),
24498
+ onKeyDown: (e) => {
24499
+ if (e.key === "Enter" || e.key === " ") {
24500
+ e.preventDefault();
24501
+ setActiveCategory(category);
24502
+ }
24503
+ },
24504
+ children: category
24505
+ },
24506
+ category
24507
+ ))
24508
+ ] }),
24509
+ /* @__PURE__ */ jsx(
24510
+ "div",
24511
+ {
24512
+ ref: gridRef,
24513
+ role: "listbox",
24514
+ className: "grid gap-1 overflow-y-auto max-h-64 p-1",
24515
+ style: {
24516
+ gridTemplateColumns: `repeat(auto-fill, minmax(${cellSize}px, 1fr))`
24517
+ },
24518
+ children: visible.map((item, index) => {
24519
+ const selected = item.id === value;
24520
+ return /* @__PURE__ */ jsx(
24521
+ "button",
24522
+ {
24523
+ type: "button",
24524
+ role: "option",
24525
+ "aria-selected": selected,
24526
+ "aria-label": item.label,
24527
+ title: item.label,
24528
+ "data-gridpicker-cell": true,
24529
+ tabIndex: selected || value === void 0 && index === 0 ? 0 : -1,
24530
+ onClick: () => select(item),
24531
+ onKeyDown: (e) => handleKeyDown(e, index),
24532
+ className: cn(
24533
+ "flex items-center justify-center rounded-sm",
24534
+ "transition-colors hover:bg-muted",
24535
+ "focus:outline-none focus:ring-1 focus:ring-ring",
24536
+ selected && "bg-primary/10 ring-1 ring-primary"
24537
+ ),
24538
+ style: { width: cellSize, height: cellSize },
24539
+ children: renderThumbnail(item)
24540
+ },
24541
+ item.id
24542
+ );
24543
+ })
24544
+ }
24545
+ ),
24546
+ truncated > 0 && /* @__PURE__ */ jsx("div", { className: "px-1 text-xs text-muted-foreground", children: `+${truncated} more \u2014 refine your search` })
24547
+ ] });
24548
+ };
24549
+ GridPicker.displayName = "GridPicker";
24550
+ }
24551
+ });
24336
24552
  function fileIcon(name) {
24337
24553
  const ext = name.split(".").pop()?.toLowerCase() ?? "";
24338
24554
  switch (ext) {
@@ -25361,9 +25577,16 @@ var init_Popover = __esm({
25361
25577
  position = "bottom",
25362
25578
  trigger = "click",
25363
25579
  showArrow = true,
25580
+ open,
25581
+ onOpenChange,
25364
25582
  className
25365
25583
  }) => {
25366
- const [isOpen, setIsOpen] = useState(false);
25584
+ const [uncontrolledOpen, setUncontrolledOpen] = useState(false);
25585
+ const isOpen = open !== void 0 ? open : uncontrolledOpen;
25586
+ const setIsOpen = (next) => {
25587
+ if (open === void 0) setUncontrolledOpen(next);
25588
+ onOpenChange?.(next);
25589
+ };
25367
25590
  const [triggerRect, setTriggerRect] = useState(null);
25368
25591
  const [popoverWidth, setPopoverWidth] = useState(0);
25369
25592
  const triggerRef = useRef(null);
@@ -25396,6 +25619,23 @@ var init_Popover = __esm({
25396
25619
  updatePosition();
25397
25620
  }
25398
25621
  }, [isOpen]);
25622
+ useEffect(() => {
25623
+ if (!isOpen) return;
25624
+ let raf = 0;
25625
+ let lastTop = Number.NaN;
25626
+ let lastLeft = Number.NaN;
25627
+ const track = () => {
25628
+ const rect = triggerRef.current?.getBoundingClientRect();
25629
+ if (rect && (rect.top !== lastTop || rect.left !== lastLeft)) {
25630
+ lastTop = rect.top;
25631
+ lastLeft = rect.left;
25632
+ updatePosition();
25633
+ }
25634
+ raf = requestAnimationFrame(track);
25635
+ };
25636
+ raf = requestAnimationFrame(track);
25637
+ return () => cancelAnimationFrame(raf);
25638
+ }, [isOpen]);
25399
25639
  useEffect(() => {
25400
25640
  if (!mounted) setPopoverWidth(0);
25401
25641
  }, [mounted]);
@@ -26744,6 +26984,80 @@ var init_FlipCard = __esm({
26744
26984
  FlipCard.displayName = "FlipCard";
26745
26985
  }
26746
26986
  });
26987
+ var EMOJI_ITEMS, EmojiPicker;
26988
+ var init_EmojiPicker = __esm({
26989
+ "components/core/molecules/EmojiPicker.tsx"() {
26990
+ "use client";
26991
+ init_useEventBus();
26992
+ init_Button();
26993
+ init_GridPicker();
26994
+ init_Popover();
26995
+ EMOJI_ITEMS = (() => {
26996
+ const items = [];
26997
+ for (const name of ordered) {
26998
+ const entry = lib[name];
26999
+ if (entry === void 0 || entry.char === null || entry.char === "") continue;
27000
+ items.push({
27001
+ id: entry.char,
27002
+ label: name.replace(/_/g, " "),
27003
+ category: entry.category.replace(/_/g, " "),
27004
+ keywords: entry.keywords
27005
+ });
27006
+ }
27007
+ return items;
27008
+ })();
27009
+ EmojiPicker = ({
27010
+ pickEvent,
27011
+ position = "top",
27012
+ triggerIcon = "smile",
27013
+ triggerLabel = "Add emoji",
27014
+ className
27015
+ }) => {
27016
+ const eventBus = useEventBus();
27017
+ const [open, setOpen] = useState(false);
27018
+ const handlePick = (glyph) => {
27019
+ if (pickEvent !== void 0) {
27020
+ const payload = { emoji: glyph };
27021
+ eventBus.emit(`UI:${pickEvent}`, payload);
27022
+ }
27023
+ setOpen(false);
27024
+ };
27025
+ return /* @__PURE__ */ jsx(
27026
+ Popover,
27027
+ {
27028
+ position,
27029
+ trigger: "click",
27030
+ showArrow: false,
27031
+ open,
27032
+ onOpenChange: setOpen,
27033
+ content: /* @__PURE__ */ jsx(
27034
+ GridPicker,
27035
+ {
27036
+ items: EMOJI_ITEMS,
27037
+ onChange: handlePick,
27038
+ searchPlaceholder: "Search emoji\u2026",
27039
+ renderThumbnail: (item) => /* @__PURE__ */ jsx("span", { className: "text-xl leading-none", "aria-hidden": "true", children: item.id }),
27040
+ cellSize: 32,
27041
+ className: "w-80"
27042
+ }
27043
+ ),
27044
+ children: /* @__PURE__ */ jsx(
27045
+ Button,
27046
+ {
27047
+ variant: "ghost",
27048
+ icon: triggerIcon,
27049
+ "aria-label": triggerLabel,
27050
+ title: triggerLabel,
27051
+ className,
27052
+ "data-testid": "emoji-picker-trigger"
27053
+ }
27054
+ )
27055
+ }
27056
+ );
27057
+ };
27058
+ EmojiPicker.displayName = "EmojiPicker";
27059
+ }
27060
+ });
26747
27061
  function toISODate(d) {
26748
27062
  return d.toISOString().slice(0, 10);
26749
27063
  }
@@ -27696,13 +28010,13 @@ var init_MapView = __esm({
27696
28010
  shadowSize: [41, 41]
27697
28011
  });
27698
28012
  L.Marker.prototype.options.icon = defaultIcon;
27699
- const { useEffect: useEffect63, useRef: useRef61, useCallback: useCallback95, useState: useState91 } = React85__default;
28013
+ const { useEffect: useEffect62, useRef: useRef61, useCallback: useCallback95, useState: useState93 } = React85__default;
27700
28014
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
27701
28015
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
27702
28016
  function MapUpdater({ centerLat, centerLng, zoom }) {
27703
28017
  const map = useMap();
27704
28018
  const prevRef = useRef61({ centerLat, centerLng, zoom });
27705
- useEffect63(() => {
28019
+ useEffect62(() => {
27706
28020
  const prev = prevRef.current;
27707
28021
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
27708
28022
  map.setView([centerLat, centerLng], zoom);
@@ -27713,7 +28027,7 @@ var init_MapView = __esm({
27713
28027
  }
27714
28028
  function MapClickHandler({ onMapClick }) {
27715
28029
  const map = useMap();
27716
- useEffect63(() => {
28030
+ useEffect62(() => {
27717
28031
  if (!onMapClick) return;
27718
28032
  const handler = (e) => {
27719
28033
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -27741,7 +28055,7 @@ var init_MapView = __esm({
27741
28055
  showAttribution = true
27742
28056
  }) {
27743
28057
  const eventBus = useEventBus2();
27744
- const [clickedPosition, setClickedPosition] = useState91(null);
28058
+ const [clickedPosition, setClickedPosition] = useState93(null);
27745
28059
  const handleMapClick = useCallback95((lat, lng) => {
27746
28060
  if (showClickedPin) {
27747
28061
  setClickedPosition({ lat, lng });
@@ -28586,6 +28900,7 @@ function TableView({
28586
28900
  fields,
28587
28901
  itemActions,
28588
28902
  maxInlineActions,
28903
+ itemClickEvent,
28589
28904
  selectable = false,
28590
28905
  selectEvent,
28591
28906
  selectedIds,
@@ -28630,9 +28945,9 @@ function TableView({
28630
28945
  dndItemIdField,
28631
28946
  dndRoot
28632
28947
  });
28633
- const ordered = dnd.orderedItems;
28634
- const data = pageSize > 0 ? ordered.slice(0, visibleCount) : ordered;
28635
- const hasMore = pageSize > 0 && visibleCount < ordered.length;
28948
+ const ordered2 = dnd.orderedItems;
28949
+ const data = pageSize > 0 ? ordered2.slice(0, visibleCount) : ordered2;
28950
+ const hasMore = pageSize > 0 && visibleCount < ordered2.length;
28636
28951
  const hasRenderProp = typeof children === "function";
28637
28952
  const idField = dndItemIdField ?? "id";
28638
28953
  const isCoarsePointer = useMediaQuery("(pointer: coarse)");
@@ -28685,6 +29000,14 @@ function TableView({
28685
29000
  };
28686
29001
  eventBus.emit(`UI:${action.event}`, payload);
28687
29002
  };
29003
+ const handleRowClick = (row) => () => {
29004
+ if (!itemClickEvent) return;
29005
+ const payload = {
29006
+ id: row.id,
29007
+ row
29008
+ };
29009
+ eventBus.emit(`UI:${itemClickEvent}`, payload);
29010
+ };
28688
29011
  const colFloors = React85__default.useMemo(
28689
29012
  () => colDefs.map((col) => {
28690
29013
  const longest = data.reduce((widest, row) => {
@@ -28761,10 +29084,12 @@ function TableView({
28761
29084
  role: "row",
28762
29085
  "data-entity-row": true,
28763
29086
  "data-entity-id": id,
29087
+ onClick: itemClickEvent ? handleRowClick(row) : void 0,
28764
29088
  style: !hasRenderProp ? { gridTemplateColumns } : void 0,
28765
29089
  className: cn(
28766
29090
  "group items-center gap-3 transition-colors duration-fast",
28767
29091
  hasRenderProp ? "flex" : "grid",
29092
+ itemClickEvent && "cursor-pointer",
28768
29093
  lk.rowPad,
28769
29094
  lk.divider && "border-b border-[var(--color-border)]",
28770
29095
  lk.striped && index % 2 === 1 && "bg-[var(--color-surface-subtle)]",
@@ -28772,7 +29097,7 @@ function TableView({
28772
29097
  look === "bordered" && "[&>*]:border-r [&>*]:border-[var(--color-border)] [&>*:last-child]:border-r-0"
28773
29098
  ),
28774
29099
  children: [
28775
- selectable && /* @__PURE__ */ jsx(Box, { className: "flex items-center", children: /* @__PURE__ */ jsx(
29100
+ selectable && /* @__PURE__ */ jsx(Box, { className: "flex items-center", onClick: itemClickEvent ? (e) => e.stopPropagation() : void 0, children: /* @__PURE__ */ jsx(
28776
29101
  Checkbox,
28777
29102
  {
28778
29103
  checked: selected.has(id),
@@ -28797,6 +29122,7 @@ function TableView({
28797
29122
  HStack,
28798
29123
  {
28799
29124
  gap: "xs",
29125
+ onClick: itemClickEvent ? (e) => e.stopPropagation() : void 0,
28800
29126
  className: cn(
28801
29127
  // Pinned: the fixed column tracks routinely overflow the caller's
28802
29128
  // scroll container, which used to leave the actions off-screen.
@@ -28866,7 +29192,7 @@ function TableView({
28866
29192
  /* @__PURE__ */ jsx(Icon, { name: "chevron-down", size: "xs", className: "mr-1" }),
28867
29193
  t("common.showMore"),
28868
29194
  " (",
28869
- t("common.remaining", { count: ordered.length - visibleCount }),
29195
+ t("common.remaining", { count: ordered2.length - visibleCount }),
28870
29196
  ")"
28871
29197
  ] }) })
28872
29198
  ]
@@ -38305,6 +38631,16 @@ var init_DetailPanel = __esm({
38305
38631
  DetailPanel.displayName = "DetailPanel";
38306
38632
  }
38307
38633
  });
38634
+
38635
+ // components/game/atoms/DrawGroup.tsx
38636
+ function DrawGroup(_props) {
38637
+ return null;
38638
+ }
38639
+ var init_DrawGroup = __esm({
38640
+ "components/game/atoms/DrawGroup.tsx"() {
38641
+ "use client";
38642
+ }
38643
+ });
38308
38644
  function extractTitle(children) {
38309
38645
  if (!React85__default.isValidElement(children)) return void 0;
38310
38646
  const props = children.props;
@@ -44008,6 +44344,7 @@ var init_component_registry_generated = __esm({
44008
44344
  init_DocSidebar();
44009
44345
  init_DocTOC();
44010
44346
  init_DocumentViewer();
44347
+ init_DrawGroup();
44011
44348
  init_DrawShape();
44012
44349
  init_DrawShapeLayer();
44013
44350
  init_DrawSprite();
@@ -44017,6 +44354,7 @@ var init_component_registry_generated = __esm({
44017
44354
  init_Drawer();
44018
44355
  init_DrawerSlot();
44019
44356
  init_EdgeDecoration();
44357
+ init_EmojiPicker();
44020
44358
  init_EmptyState();
44021
44359
  init_ErrorBoundary();
44022
44360
  init_ErrorState();
@@ -44151,10 +44489,6 @@ var init_component_registry_generated = __esm({
44151
44489
  init_SubagentTracePanel();
44152
44490
  init_SvgBranch();
44153
44491
  init_SvgConnection();
44154
- init_SvgDrawGroup();
44155
- init_SvgDrawShape();
44156
- init_SvgDrawShapeLayer();
44157
- init_SvgDrawText();
44158
44492
  init_SvgFlow();
44159
44493
  init_SvgGrid();
44160
44494
  init_SvgLobe();
@@ -44165,7 +44499,6 @@ var init_component_registry_generated = __esm({
44165
44499
  init_SvgRing();
44166
44500
  init_SvgShield();
44167
44501
  init_SvgStack();
44168
- init_SvgStage();
44169
44502
  init_SwipeableRow();
44170
44503
  init_Switch();
44171
44504
  init_TabbedContainer();
@@ -44279,6 +44612,7 @@ var init_component_registry_generated = __esm({
44279
44612
  "DocSidebar": DocSidebar,
44280
44613
  "DocTOC": DocTOC,
44281
44614
  "DocumentViewer": DocumentViewer,
44615
+ "DrawGroup": DrawGroup,
44282
44616
  "DrawShape": DrawShape,
44283
44617
  "DrawShapeLayer": DrawShapeLayer,
44284
44618
  "DrawSprite": DrawSprite,
@@ -44288,6 +44622,7 @@ var init_component_registry_generated = __esm({
44288
44622
  "Drawer": Drawer,
44289
44623
  "DrawerSlot": DrawerSlot,
44290
44624
  "EdgeDecoration": EdgeDecoration,
44625
+ "EmojiPicker": EmojiPicker,
44291
44626
  "EmptyState": EmptyState,
44292
44627
  "ErrorBoundary": ErrorBoundary,
44293
44628
  "ErrorState": ErrorState,
@@ -44427,10 +44762,6 @@ var init_component_registry_generated = __esm({
44427
44762
  "SubagentTracePanel": SubagentTracePanel,
44428
44763
  "SvgBranch": SvgBranch,
44429
44764
  "SvgConnection": SvgConnection,
44430
- "SvgDrawGroup": SvgDrawGroup,
44431
- "SvgDrawShape": SvgDrawShape,
44432
- "SvgDrawShapeLayer": SvgDrawShapeLayer,
44433
- "SvgDrawText": SvgDrawText,
44434
44765
  "SvgFlow": SvgFlow,
44435
44766
  "SvgGrid": SvgGrid,
44436
44767
  "SvgLobe": SvgLobe,
@@ -44441,7 +44772,6 @@ var init_component_registry_generated = __esm({
44441
44772
  "SvgRing": SvgRing,
44442
44773
  "SvgShield": SvgShield,
44443
44774
  "SvgStack": SvgStack,
44444
- "SvgStage": SvgStage,
44445
44775
  "SwipeableRow": SwipeableRow,
44446
44776
  "Switch": Switch,
44447
44777
  "TabbedContainer": TabbedContainer,
@@ -44720,7 +45050,19 @@ function UISlotComponent({
44720
45050
  const suspenseConfig = useContext(SuspenseConfigContext);
44721
45051
  const contained = useContext(SlotContainedContext);
44722
45052
  const schemaCtx = useEntitySchemaOptional();
44723
- const content = slots[slot];
45053
+ const rawContent = slots[slot];
45054
+ const binding = useEntityBindingSnapshot(rawContent?.sourceTrait);
45055
+ const content = useMemo(() => {
45056
+ if (!rawContent) return rawContent;
45057
+ const resolvedProps = resolveRenderBindingMarkers(
45058
+ rawContent.props,
45059
+ rawContent.sourceTrait,
45060
+ binding.entity,
45061
+ binding.config,
45062
+ binding.state
45063
+ );
45064
+ return resolvedProps === rawContent.props ? rawContent : { ...rawContent, props: resolvedProps };
45065
+ }, [rawContent, binding.entity, binding.config, binding.state]);
44724
45066
  if (children !== void 0) {
44725
45067
  if (pattern === "clear") {
44726
45068
  return null;
@@ -45064,6 +45406,7 @@ function isPlainConfigObject(value) {
45064
45406
  return proto === Object.prototype || proto === null;
45065
45407
  }
45066
45408
  function substituteTraitRefsDeep(value, pathKey) {
45409
+ if (isRenderBindingMarker(value)) return value;
45067
45410
  if (typeof value === "string") {
45068
45411
  const match = TRAIT_BINDING_RE.exec(value);
45069
45412
  if (match) {
@@ -45136,7 +45479,14 @@ function SlotContentRenderer({
45136
45479
  onDismiss,
45137
45480
  patternPath
45138
45481
  }) {
45139
- const entityProp = content.props.entity;
45482
+ const ambientScope = useTraitScope();
45483
+ const bindingTrait = content.sourceTrait ?? ambientScope?.trait;
45484
+ const binding = useEntityBindingSnapshot(bindingTrait);
45485
+ const liveProps = useMemo(
45486
+ () => resolveRenderBindingMarkers(content.props, bindingTrait, binding.entity, binding.config, binding.state),
45487
+ [content.props, bindingTrait, binding.entity, binding.config, binding.state]
45488
+ );
45489
+ const entityProp = liveProps.entity;
45140
45490
  if (content.pattern === "form-section") {
45141
45491
  slotLog.debug("SlotContentRenderer:form-section-render", {
45142
45492
  contentId: content.id,
@@ -45167,7 +45517,7 @@ function SlotContentRenderer({
45167
45517
  const orbitalName = schemaCtx && content.sourceTrait !== void 0 ? schemaCtx.orbitalsByTrait.get(content.sourceTrait) : void 0;
45168
45518
  const PatternComponent = getComponentForPattern(content.pattern);
45169
45519
  if (PatternComponent) {
45170
- const childrenConfig = content.props.children;
45520
+ const childrenConfig = liveProps.children;
45171
45521
  const isSingleChild = typeof childrenConfig === "string" || typeof childrenConfig === "object" && childrenConfig !== null && !Array.isArray(childrenConfig) && "type" in childrenConfig;
45172
45522
  const hasChildren = PATTERNS_WITH_CHILDREN.has(content.pattern) || Array.isArray(childrenConfig) && childrenConfig.length > 0 || isSingleChild;
45173
45523
  const isDrawHost = isDrawHostPattern(content.pattern);
@@ -45178,9 +45528,9 @@ function SlotContentRenderer({
45178
45528
  fromState: content.fromState,
45179
45529
  entity: content.entity
45180
45530
  }) : void 0;
45181
- const incomingChildren = content.props.children;
45531
+ const incomingChildren = liveProps.children;
45182
45532
  const childrenIsRenderFn = typeof incomingChildren === "function";
45183
- const { children: _childrenConfig, ...restPropsNoChildren } = content.props;
45533
+ const { children: _childrenConfig, ...restPropsNoChildren } = liveProps;
45184
45534
  const restProps = childrenIsRenderFn ? { ...restPropsNoChildren, children: incomingChildren } : restPropsNoChildren;
45185
45535
  const nodeSlotOverrides = {};
45186
45536
  for (const slotKey of CONTENT_NODE_SLOTS) {
@@ -45289,7 +45639,7 @@ function SlotContentRenderer({
45289
45639
  "data-orb-path": patternPath ?? "root",
45290
45640
  "data-orb-pattern": content.pattern,
45291
45641
  "data-orb-orbital": orbitalName,
45292
- children: content.props.children ?? /* @__PURE__ */ jsxs(Box, { className: "p-4 text-sm text-muted-foreground border border-dashed border-border rounded", children: [
45642
+ children: liveProps.children ?? /* @__PURE__ */ jsxs(Box, { className: "p-4 text-sm text-muted-foreground border border-dashed border-border rounded", children: [
45293
45643
  "Unknown pattern: ",
45294
45644
  content.pattern,
45295
45645
  content.sourceTrait && /* @__PURE__ */ jsxs(Typography, { variant: "small", className: "ml-2", children: [
@@ -45361,6 +45711,7 @@ var scopeWrapLog, TRAIT_BINDING_RE, SuspenseConfigContext, SlotContainedContext,
45361
45711
  var init_UISlotRenderer = __esm({
45362
45712
  "components/core/organisms/UISlotRenderer.tsx"() {
45363
45713
  "use client";
45714
+ init_resolve_render_bindings();
45364
45715
  init_Modal();
45365
45716
  init_Drawer();
45366
45717
  init_Toast();
@@ -45755,9 +46106,10 @@ function VerificationProvider({
45755
46106
  }));
45756
46107
  const effectResults = Array.isArray(payload["effectResults"]) ? payload["effectResults"] : [];
45757
46108
  for (const er of effectResults) {
46109
+ const target = er["entity"] ?? er["service"];
45758
46110
  effects.push({
45759
46111
  type: String(er["type"] ?? er["effect"] ?? "server-effect"),
45760
- args: [er["entity"] ?? er["service"] ?? ""].filter(Boolean),
46112
+ args: typeof target === "string" && target !== "" ? [target] : [],
45761
46113
  status: er["error"] ? "failed" : "executed",
45762
46114
  error: er["error"]
45763
46115
  });
@@ -46546,7 +46898,7 @@ function reEmitServerEvent(eventBus, emitted, origin) {
46546
46898
  sourceTrait: evTrait,
46547
46899
  origin
46548
46900
  });
46549
- eventBus.emit(key, emitted.payload);
46901
+ eventBus.emit(key, emitted.payload, emitted.source);
46550
46902
  }
46551
46903
  function isBusPushEnvelope(value) {
46552
46904
  return value.type === "bus" && typeof value.event === "string";
@@ -46714,7 +47066,12 @@ function ServerBridgeProvider({
46714
47066
  }
46715
47067
  if (result.emittedEvents) {
46716
47068
  for (const emitted of result.emittedEvents) {
46717
- reEmitServerEvent(eventBus, emitted, orbitalName);
47069
+ if (emitted.event === event) continue;
47070
+ reEmitServerEvent(
47071
+ eventBus,
47072
+ { ...emitted, source: { ...emitted.source, dispatched: true } },
47073
+ orbitalName
47074
+ );
46718
47075
  }
46719
47076
  }
46720
47077
  } else if (result.error) {
@@ -46852,10 +47209,29 @@ function useTraitScopeChain2() {
46852
47209
  const chain = useContext(TraitScopeContext);
46853
47210
  return chain ?? EMPTY_CHAIN;
46854
47211
  }
46855
- function useTraitScope() {
47212
+ function useTraitScope2() {
46856
47213
  const chain = useContext(TraitScopeContext);
46857
47214
  return chain && chain.length > 0 ? chain[0] : null;
46858
47215
  }
47216
+ var EntityBindingContext = createContext(null);
47217
+ var EMPTY_ENTITY = {};
47218
+ var NOOP_SUBSCRIBE = () => () => void 0;
47219
+ function useEntityBindingSnapshot2(traitName) {
47220
+ const source = useContext(EntityBindingContext);
47221
+ const entity = useSyncExternalStore(
47222
+ source !== null && traitName !== void 0 ? (onStoreChange) => source.subscribe(traitName, onStoreChange) : NOOP_SUBSCRIBE,
47223
+ () => source !== null && traitName !== void 0 ? source.getEntitySnapshot(traitName) : EMPTY_ENTITY
47224
+ );
47225
+ const config = useMemo(
47226
+ () => source !== null && traitName !== void 0 ? source.getConfig(traitName) : void 0,
47227
+ [source, traitName]
47228
+ );
47229
+ return {
47230
+ entity,
47231
+ config,
47232
+ state: source !== null && traitName !== void 0 ? source.getState(traitName) : ""
47233
+ };
47234
+ }
46859
47235
 
46860
47236
  // providers/OfflineModeProvider.tsx
46861
47237
  init_offline_executor();
@@ -46948,4 +47324,4 @@ function GameAudioProvider2({
46948
47324
  }
46949
47325
  GameAudioProvider2.displayName = "GameAudioProvider";
46950
47326
 
46951
- export { CurrentPagePathContext, CurrentPagePathProvider, EntitySchemaProvider, EventBusContext2 as EventBusContext, EventBusProvider, GameAudioContext2 as GameAudioContext, GameAudioProvider2 as GameAudioProvider, NavigationProvider2 as NavigationProvider, OfflineModeProvider, OrbitalProvider, OrbitalThemeProvider, SelectionContext, SelectionProvider, ServerBridgeProvider, TraitContext, TraitProvider, TraitScopeProvider3 as TraitScopeProvider, UserContext, UserProvider, VerificationProvider, comparePathSpecificity2 as comparePathSpecificity, extractRouteParams2 as extractRouteParams, findPageByName2 as findPageByName, findPageByPath2 as findPageByPath, getAllPages2 as getAllPages, getDefaultPage2 as getDefaultPage, matchPath2 as matchPath, matchPathAmong2 as matchPathAmong, pathMatches2 as pathMatches, useActivePage2 as useActivePage, useCurrentPagePath2 as useCurrentPagePath, useEntitySchema, useEntitySchemaOptional6 as useEntitySchemaOptional, useGameAudioContext2 as useGameAudioContext, useGameAudioContextOptional2 as useGameAudioContextOptional, useHasPermission, useHasRole, useInitPayload2 as useInitPayload, useNavigateTo2 as useNavigateTo, useNavigation2 as useNavigation, useNavigationId2 as useNavigationId, useNavigationState2 as useNavigationState, useOfflineMode, useOptionalOfflineMode, useSelection, useSelectionOptional, useServerBridge, useTrait, useTraitContext, useTraitScope, useTraitScopeChain2 as useTraitScopeChain, useUser, useUserForEvaluation };
47327
+ export { CurrentPagePathContext, CurrentPagePathProvider, EntityBindingContext, EntitySchemaProvider, EventBusContext2 as EventBusContext, EventBusProvider, GameAudioContext2 as GameAudioContext, GameAudioProvider2 as GameAudioProvider, NavigationProvider2 as NavigationProvider, OfflineModeProvider, OrbitalProvider, OrbitalThemeProvider, SelectionContext, SelectionProvider, ServerBridgeProvider, TraitContext, TraitProvider, TraitScopeProvider3 as TraitScopeProvider, UserContext, UserProvider, VerificationProvider, comparePathSpecificity2 as comparePathSpecificity, extractRouteParams2 as extractRouteParams, findPageByName2 as findPageByName, findPageByPath2 as findPageByPath, getAllPages2 as getAllPages, getDefaultPage2 as getDefaultPage, matchPath2 as matchPath, matchPathAmong2 as matchPathAmong, pathMatches2 as pathMatches, useActivePage2 as useActivePage, useCurrentPagePath2 as useCurrentPagePath, useEntityBindingSnapshot2 as useEntityBindingSnapshot, useEntitySchema, useEntitySchemaOptional6 as useEntitySchemaOptional, useGameAudioContext2 as useGameAudioContext, useGameAudioContextOptional2 as useGameAudioContextOptional, useHasPermission, useHasRole, useInitPayload2 as useInitPayload, useNavigateTo2 as useNavigateTo, useNavigation2 as useNavigation, useNavigationId2 as useNavigationId, useNavigationState2 as useNavigationState, useOfflineMode, useOptionalOfflineMode, useSelection, useSelectionOptional, useServerBridge, useTrait, useTraitContext, useTraitScope2 as useTraitScope, useTraitScopeChain2 as useTraitScopeChain, useUser, useUserForEvaluation };