@almadar/ui 6.5.0 → 6.7.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 (48) hide show
  1. package/dist/{EntityBindingContext-0Evn_LcT.d.cts → EntityBindingContext-Bn3ePJQC.d.cts} +1 -1
  2. package/dist/{EntityBindingContext-0Evn_LcT.d.ts → EntityBindingContext-Bn3ePJQC.d.ts} +1 -1
  3. package/dist/{GameAudioProvider-B48iXwz3.d.ts → GameAudioProvider-Ctceau1s.d.ts} +6 -18
  4. package/dist/{GameAudioProvider-CQAdPreB.d.cts → GameAudioProvider-Dk_Y3LN3.d.cts} +6 -18
  5. package/dist/avl/index.cjs +1268 -220
  6. package/dist/avl/index.js +1269 -221
  7. package/dist/{avl-schema-parser-dvJKXn-o.d.ts → avl-schema-parser-DncJLEn9.d.ts} +18 -0
  8. package/dist/{avl-schema-parser-Bt4NzAam.d.cts → avl-schema-parser-FQ1474v8.d.cts} +18 -0
  9. package/dist/{cn-DJSBBm5W.d.cts → cn-CCjFspAo.d.cts} +25 -1
  10. package/dist/{cn-BbhJq_nr.d.ts → cn-sgpqpN0U.d.ts} +25 -1
  11. package/dist/components/index.cjs +1092 -249
  12. package/dist/components/index.d.cts +193 -23
  13. package/dist/components/index.d.ts +193 -23
  14. package/dist/components/index.js +1089 -250
  15. package/dist/context/index.cjs +10 -13
  16. package/dist/context/index.js +10 -13
  17. package/dist/hooks/index.cjs +63 -13
  18. package/dist/hooks/index.d.cts +2 -2
  19. package/dist/hooks/index.d.ts +2 -2
  20. package/dist/hooks/index.js +64 -15
  21. package/dist/lib/drawable/three/index.cjs +126 -13
  22. package/dist/lib/drawable/three/index.d.cts +2 -2
  23. package/dist/lib/drawable/three/index.d.ts +2 -2
  24. package/dist/lib/drawable/three/index.js +127 -14
  25. package/dist/lib/index.cjs +180 -0
  26. package/dist/lib/index.d.cts +1 -1
  27. package/dist/lib/index.d.ts +1 -1
  28. package/dist/lib/index.js +178 -1
  29. package/dist/locales/index.cjs +6 -3
  30. package/dist/locales/index.js +6 -3
  31. package/dist/providers/index.cjs +974 -202
  32. package/dist/providers/index.d.cts +2 -2
  33. package/dist/providers/index.d.ts +2 -2
  34. package/dist/providers/index.js +974 -202
  35. package/dist/runtime/index.cjs +1594 -196
  36. package/dist/runtime/index.d.cts +79 -6
  37. package/dist/runtime/index.d.ts +79 -6
  38. package/dist/runtime/index.js +1592 -198
  39. package/dist/slot-host-Czc2JAxo.d.cts +47 -0
  40. package/dist/slot-host-Czc2JAxo.d.ts +47 -0
  41. package/dist/{useEventBus-CQWyAWpK.d.ts → useKeyboardRouter-D84cNWmA.d.ts} +31 -1
  42. package/dist/{useEventBus-Ckr4wqW3.d.cts → useKeyboardRouter-DQEE_9mq.d.cts} +31 -1
  43. package/locales/ar.json +2 -1
  44. package/locales/en.json +2 -1
  45. package/locales/sl.json +2 -1
  46. package/package.json +4 -4
  47. package/themes/comic.css +437 -0
  48. package/themes/index.css +1 -0
@@ -13787,7 +13787,7 @@ function collectDrawnItems(nodes) {
13787
13787
  for (const n of nodes) {
13788
13788
  switch (n.type) {
13789
13789
  case "draw-sprite":
13790
- if (isValidScenePos(n.position)) out.push({ pos: n.position, id: n.id, anchor: n.anchor, width: n.width, height: n.height });
13790
+ if (isValidScenePos(n.position)) out.push({ pos: n.position, id: n.id, anchor: n.anchor, width: n.width, height: n.height, rotation: n.rotation });
13791
13791
  break;
13792
13792
  case "draw-shape":
13793
13793
  case "draw-text":
@@ -13797,7 +13797,7 @@ function collectDrawnItems(nodes) {
13797
13797
  break;
13798
13798
  case "draw-sprite-layer":
13799
13799
  for (const it of n.items) {
13800
- if (isValidScenePos(it.position)) out.push({ pos: it.position, id: it.id, anchor: it.anchor, width: it.width, height: it.height });
13800
+ if (isValidScenePos(it.position)) out.push({ pos: it.position, id: it.id, anchor: it.anchor, width: it.width, height: it.height, rotation: it.rotation });
13801
13801
  }
13802
13802
  break;
13803
13803
  case "draw-shape-layer":
@@ -13817,15 +13817,39 @@ function buildHitIndex(items) {
13817
13817
  }
13818
13818
  return m;
13819
13819
  }
13820
+ function withPreviewPosition(nodes, id, pos) {
13821
+ return nodes.map((n) => {
13822
+ if (n.type === "draw-sprite-layer" || n.type === "draw-shape-layer" || n.type === "draw-text-layer") {
13823
+ if (!n.items.some((it) => it.id === id)) return n;
13824
+ return { ...n, items: n.items.map((it) => it.id === id ? { ...it, position: pos } : it) };
13825
+ }
13826
+ if (n.type === "draw-group") {
13827
+ if (!Array.isArray(n.items)) return n;
13828
+ return { ...n, items: withPreviewPosition(n.items, id, pos) };
13829
+ }
13830
+ if (n.id === id && "position" in n) {
13831
+ return { ...n, position: pos };
13832
+ }
13833
+ return n;
13834
+ });
13835
+ }
13820
13836
  function hitTestSprites(items, projector, point) {
13821
13837
  for (let i = items.length - 1; i >= 0; i--) {
13822
13838
  const it = items[i];
13823
13839
  if (it.id === void 0) continue;
13824
13840
  const r2 = spriteRect(projector, { position: it.pos, anchor: it.anchor, width: it.width, height: it.height });
13825
- if (point.x >= r2.x && point.x <= r2.x + r2.w && point.y >= r2.y && point.y <= r2.y + r2.h) return it.id;
13841
+ const test = it.rotation ? rotatePoint(point, { x: r2.x + r2.w / 2, y: r2.y + r2.h / 2 }, -it.rotation) : point;
13842
+ if (test.x >= r2.x && test.x <= r2.x + r2.w && test.y >= r2.y && test.y <= r2.y + r2.h) return it.id;
13826
13843
  }
13827
13844
  return void 0;
13828
13845
  }
13846
+ function rotatePoint(p, center, radians) {
13847
+ const cos = Math.cos(radians);
13848
+ const sin = Math.sin(radians);
13849
+ const dx = p.x - center.x;
13850
+ const dy = p.y - center.y;
13851
+ return { x: center.x + dx * cos - dy * sin, y: center.y + dx * sin + dy * cos };
13852
+ }
13829
13853
  var init_hitTest = __esm({
13830
13854
  "lib/drawable/hitTest.ts"() {
13831
13855
  init_contract();
@@ -13834,6 +13858,41 @@ var init_hitTest = __esm({
13834
13858
  function normalizeBackdrop(bg) {
13835
13859
  return typeof bg === "string" ? { url: bg, role: "decoration", category: "background" } : bg;
13836
13860
  }
13861
+ function selectionOverlayNodes(projector, item) {
13862
+ const r2 = spriteRect(projector, { position: item.pos, anchor: item.anchor, width: item.width, height: item.height });
13863
+ const tw = projector.tileWidth;
13864
+ const cellTopLeft = projector.anchorPoint(item.pos, "top-left");
13865
+ const offsetX = (r2.x - cellTopLeft.x) / tw;
13866
+ const offsetY = (r2.y - cellTopLeft.y) / tw;
13867
+ const width = r2.w / tw;
13868
+ const height = r2.h / tw;
13869
+ const handle = EDIT_HANDLE_SIZE_PX / tw;
13870
+ const ring = {
13871
+ type: "draw-shape",
13872
+ shape: "rect",
13873
+ position: item.pos,
13874
+ anchor: "top-left",
13875
+ offsetX,
13876
+ offsetY,
13877
+ width,
13878
+ height,
13879
+ stroke: EDIT_SELECTION_COLOR,
13880
+ strokeWidth: 2,
13881
+ fill: "none"
13882
+ };
13883
+ const handles = EDIT_SELECTION_CORNERS.map(([cx, cy]) => ({
13884
+ type: "draw-shape",
13885
+ shape: "rect",
13886
+ position: item.pos,
13887
+ anchor: "top-left",
13888
+ offsetX: offsetX + cx * width - handle / 2,
13889
+ offsetY: offsetY + cy * height - handle / 2,
13890
+ width: handle,
13891
+ height: handle,
13892
+ fill: EDIT_SELECTION_COLOR
13893
+ }));
13894
+ return [ring, ...handles];
13895
+ }
13837
13896
  function Canvas2D({
13838
13897
  className,
13839
13898
  isLoading = false,
@@ -13847,6 +13906,12 @@ function Canvas2D({
13847
13906
  tileLeaveEvent,
13848
13907
  keyMap,
13849
13908
  keyUpMap,
13909
+ editable = false,
13910
+ selectedId = null,
13911
+ onSelect,
13912
+ onMove,
13913
+ selectEvent,
13914
+ moveEvent,
13850
13915
  camera = "pan-zoom",
13851
13916
  scale = 0.4,
13852
13917
  tileWidth,
@@ -14104,10 +14169,22 @@ function Canvas2D({
14104
14169
  painter.scale(cam.zoom, cam.zoom);
14105
14170
  painter.translate(-viewportSize.width / 2 - cam.x, -viewportSize.height / 2 - cam.y);
14106
14171
  const dctx = { projector, time: timeMs, invalidate: bumpAtlas };
14107
- for (const node of drawables) paintDrawable(painter, node, dctx);
14172
+ let paintNodes = drawables;
14173
+ if (editable) {
14174
+ const drag = editDragRef.current;
14175
+ if (drag && drag.moved) {
14176
+ paintNodes = withPreviewPosition(paintNodes, drag.id, { x: drag.previewX, y: drag.previewY });
14177
+ }
14178
+ const selectedItem = selectedId != null ? drawnItems.find((it) => it.id === selectedId) : void 0;
14179
+ if (selectedItem) {
14180
+ const overlaySource = drag && drag.moved && drag.id === selectedId ? { ...selectedItem, pos: { x: drag.previewX, y: drag.previewY } } : selectedItem;
14181
+ paintNodes = [...paintNodes, ...selectionOverlayNodes(projector, overlaySource)];
14182
+ }
14183
+ }
14184
+ for (const node of paintNodes) paintDrawable(painter, node, dctx);
14108
14185
  painter.restore();
14109
- scheduleAnimation(drawables);
14110
- }, [viewportSize, backgroundImage, bgColor, drawables, projector, cameraRef, bumpAtlas, getImage, cameraPos, defaultGridFocus, camera, dragDistance]);
14186
+ scheduleAnimation(paintNodes);
14187
+ }, [viewportSize, backgroundImage, bgColor, drawables, projector, cameraRef, bumpAtlas, getImage, cameraPos, defaultGridFocus, camera, dragDistance, editable, selectedId, drawnItems]);
14111
14188
  React96.useEffect(() => {
14112
14189
  drawTimeRef.current = draw;
14113
14190
  }, [draw]);
@@ -14152,23 +14229,83 @@ function Canvas2D({
14152
14229
  };
14153
14230
  }, [camera, followTarget, lerpToTarget, draw]);
14154
14231
  const singlePointerActiveRef = React96.useRef(false);
14232
+ const editDragRef = React96.useRef(null);
14233
+ const pointerToScene = React96.useCallback((clientX, clientY) => {
14234
+ const canvas = canvasRef.current;
14235
+ if (!canvas) return { x: 0, y: 0 };
14236
+ const world = screenToWorld(clientX, clientY, canvas, viewportSize);
14237
+ const adjustedX = world.x - scaledTileWidth / 2;
14238
+ const adjustedY = squareGrid ? world.y - scaledTileWidth / 2 : world.y - scaledDiamondTopY - scaledFloorHeight / 2;
14239
+ return unproject(adjustedX, adjustedY);
14240
+ }, [screenToWorld, viewportSize, scaledTileWidth, squareGrid, scaledDiamondTopY, scaledFloorHeight, unproject]);
14155
14241
  const handleCanvasPointerDown = React96.useCallback((e) => {
14156
14242
  singlePointerActiveRef.current = true;
14243
+ if (editable) {
14244
+ if (!canvasRef.current) return;
14245
+ const world = screenToWorld(e.clientX, e.clientY, canvasRef.current, viewportSize);
14246
+ const hitId = hitTestSprites(drawnItems, projector, world);
14247
+ if (hitId === void 0) return;
14248
+ const item = [...drawnItems].reverse().find((it) => it.id === hitId);
14249
+ if (!item) return;
14250
+ editDragRef.current = {
14251
+ id: hitId,
14252
+ pointerId: e.pointerId,
14253
+ startClientX: e.clientX,
14254
+ startClientY: e.clientY,
14255
+ startSceneX: item.pos.x,
14256
+ startSceneY: item.pos.y,
14257
+ moved: false,
14258
+ previewX: item.pos.x,
14259
+ previewY: item.pos.y
14260
+ };
14261
+ return;
14262
+ }
14157
14263
  if (enableCamera) handlePointerDown(e);
14158
- }, [enableCamera, handlePointerDown]);
14264
+ }, [editable, screenToWorld, viewportSize, drawnItems, projector, enableCamera, handlePointerDown]);
14159
14265
  const handleCanvasPointerMove = React96.useCallback((e) => {
14266
+ if (editable) {
14267
+ const drag = editDragRef.current;
14268
+ if (!drag || drag.pointerId !== e.pointerId) return;
14269
+ const dxPx = e.clientX - drag.startClientX;
14270
+ const dyPx = e.clientY - drag.startClientY;
14271
+ if (!drag.moved && Math.abs(dxPx) + Math.abs(dyPx) <= 5) return;
14272
+ drag.moved = true;
14273
+ const nowScene = pointerToScene(e.clientX, e.clientY);
14274
+ const startScene = pointerToScene(drag.startClientX, drag.startClientY);
14275
+ drag.previewX = drag.startSceneX + (nowScene.x - startScene.x);
14276
+ drag.previewY = drag.startSceneY + (nowScene.y - startScene.y);
14277
+ draw();
14278
+ return;
14279
+ }
14160
14280
  if (enableCamera) handlePointerMove(e, () => draw());
14161
- }, [enableCamera, handlePointerMove, draw]);
14281
+ }, [editable, pointerToScene, draw, enableCamera, handlePointerMove]);
14162
14282
  const handleCanvasHover = React96.useCallback((e) => {
14163
14283
  if (singlePointerActiveRef.current) return;
14164
14284
  if (!tileHoverEvent || !canvasRef.current) return;
14165
- const world = screenToWorld(e.clientX, e.clientY, canvasRef.current, viewportSize);
14166
- const adjustedX = world.x - scaledTileWidth / 2;
14167
- const adjustedY = squareGrid ? world.y - scaledTileWidth / 2 : world.y - scaledDiamondTopY - scaledFloorHeight / 2;
14168
- const isoPos = unproject(adjustedX, adjustedY);
14285
+ const isoPos = pointerToScene(e.clientX, e.clientY);
14169
14286
  eventBus.emit(`UI:${tileHoverEvent}`, { x: isoPos.x, y: isoPos.y });
14170
- }, [screenToWorld, viewportSize, scaledTileWidth, squareGrid, scaledDiamondTopY, scaledFloorHeight, unproject, tileHoverEvent, eventBus]);
14287
+ }, [pointerToScene, tileHoverEvent, eventBus]);
14171
14288
  const handleCanvasPointerUp = React96.useCallback((e) => {
14289
+ if (editable) {
14290
+ singlePointerActiveRef.current = false;
14291
+ const drag = editDragRef.current;
14292
+ if (drag && drag.pointerId === e.pointerId) {
14293
+ editDragRef.current = null;
14294
+ if (drag.moved) {
14295
+ onMove?.(drag.id, drag.previewX, drag.previewY);
14296
+ if (moveEvent) eventBus.emit(`UI:${moveEvent}`, { id: drag.id, x: drag.previewX, y: drag.previewY });
14297
+ draw();
14298
+ return;
14299
+ }
14300
+ const next = selectedId === drag.id ? null : drag.id;
14301
+ onSelect?.(next);
14302
+ if (selectEvent) eventBus.emit(`UI:${selectEvent}`, { id: next });
14303
+ return;
14304
+ }
14305
+ onSelect?.(null);
14306
+ if (selectEvent) eventBus.emit(`UI:${selectEvent}`, { id: null });
14307
+ return;
14308
+ }
14172
14309
  singlePointerActiveRef.current = false;
14173
14310
  if (enableCamera) handlePointerUp();
14174
14311
  if (dragDistance() > 5) return;
@@ -14179,16 +14316,14 @@ function Canvas2D({
14179
14316
  eventBus.emit(`UI:${unitClickEvent}`, { unitId: spriteHit });
14180
14317
  return;
14181
14318
  }
14182
- const adjustedX = world.x - scaledTileWidth / 2;
14183
- const adjustedY = squareGrid ? world.y - scaledTileWidth / 2 : world.y - scaledDiamondTopY - scaledFloorHeight / 2;
14184
- const isoPos = unproject(adjustedX, adjustedY);
14319
+ const isoPos = pointerToScene(e.clientX, e.clientY);
14185
14320
  const hitId = hitIndex.get(`${isoPos.x},${isoPos.y}`);
14186
14321
  if (hitId !== void 0 && unitClickEvent) {
14187
14322
  eventBus.emit(`UI:${unitClickEvent}`, { unitId: hitId });
14188
14323
  } else if (tileClickEvent) {
14189
14324
  eventBus.emit(`UI:${tileClickEvent}`, { x: isoPos.x, y: isoPos.y });
14190
14325
  }
14191
- }, [enableCamera, handlePointerUp, dragDistance, screenToWorld, viewportSize, scaledTileWidth, squareGrid, scaledDiamondTopY, scaledFloorHeight, unproject, hitIndex, drawnItems, projector, tileClickEvent, unitClickEvent, eventBus]);
14326
+ }, [editable, selectedId, onMove, moveEvent, onSelect, selectEvent, eventBus, draw, enableCamera, handlePointerUp, dragDistance, screenToWorld, viewportSize, drawnItems, projector, tileClickEvent, unitClickEvent, hitIndex, pointerToScene]);
14192
14327
  const handleCanvasPointerLeave = React96.useCallback(() => {
14193
14328
  handleMouseLeave();
14194
14329
  if (tileLeaveEvent) eventBus.emit(`UI:${tileLeaveEvent}`, {});
@@ -14207,7 +14342,7 @@ function Canvas2D({
14207
14342
  }, [enableCamera, handlePointerUp]);
14208
14343
  const gestureHandlers = useCanvasGestures({
14209
14344
  canvasRef,
14210
- enabled: enableCamera || !!tileHoverEvent || !!tileClickEvent || !!unitClickEvent,
14345
+ enabled: enableCamera || !!tileHoverEvent || !!tileClickEvent || !!unitClickEvent || editable,
14211
14346
  onPointerDown: handleCanvasPointerDown,
14212
14347
  onPointerMove: handleCanvasPointerMove,
14213
14348
  onPointerUp: handleCanvasPointerUp,
@@ -14339,7 +14474,7 @@ function Canvas2D({
14339
14474
  }
14340
14475
  ) });
14341
14476
  }
14342
- var canvas2DLog;
14477
+ var canvas2DLog, EDIT_SELECTION_COLOR, EDIT_HANDLE_SIZE_PX, EDIT_SELECTION_CORNERS;
14343
14478
  var init_Canvas2D = __esm({
14344
14479
  "components/game/molecules/Canvas2D.tsx"() {
14345
14480
  "use client";
@@ -14364,8 +14499,12 @@ var init_Canvas2D = __esm({
14364
14499
  init_DrawGroup();
14365
14500
  init_registry();
14366
14501
  init_hitTest();
14502
+ init_contract();
14367
14503
  init_isometric();
14368
14504
  canvas2DLog = logger.createLogger("almadar:ui:game-canvas");
14505
+ EDIT_SELECTION_COLOR = "#3b82f6";
14506
+ EDIT_HANDLE_SIZE_PX = 8;
14507
+ EDIT_SELECTION_CORNERS = [[0, 0], [1, 0], [0, 1], [1, 1]];
14369
14508
  Canvas2D.displayName = "Canvas2D";
14370
14509
  }
14371
14510
  });
@@ -14407,6 +14546,12 @@ function Canvas({
14407
14546
  featureClickEvent,
14408
14547
  keyMap,
14409
14548
  keyUpMap,
14549
+ editable,
14550
+ selectedId,
14551
+ onSelect,
14552
+ onMove,
14553
+ selectEvent,
14554
+ moveEvent,
14410
14555
  children
14411
14556
  }) {
14412
14557
  canvasLog.debug("Canvas render", { mode, drawablesCount: drawables?.length, projection, camera: camera ? JSON.stringify(camera) : void 0 });
@@ -14484,6 +14629,12 @@ function Canvas({
14484
14629
  tileLeaveEvent,
14485
14630
  keyMap,
14486
14631
  keyUpMap,
14632
+ editable,
14633
+ selectedId,
14634
+ onSelect,
14635
+ onMove,
14636
+ selectEvent,
14637
+ moveEvent,
14487
14638
  ...children !== void 0 ? { children } : {}
14488
14639
  }
14489
14640
  );
@@ -14505,15 +14656,19 @@ function GameAudioToggle({
14505
14656
  size = "sm",
14506
14657
  className,
14507
14658
  onAsset,
14508
- offAsset
14659
+ offAsset,
14660
+ toggleEvent
14509
14661
  }) {
14510
14662
  const ctx = providers.useGameAudioContextOptional();
14511
14663
  const [localMuted, setLocalMuted] = React96.useState(false);
14512
14664
  const muted = ctx ? ctx.muted : localMuted;
14513
14665
  const setMuted = ctx ? ctx.setMuted : setLocalMuted;
14666
+ const eventBus = useEventBus();
14514
14667
  const handleToggle = React96.useCallback(() => {
14515
- setMuted(!muted);
14516
- }, [muted, setMuted]);
14668
+ const next = !muted;
14669
+ setMuted(next);
14670
+ if (toggleEvent) eventBus.emit(`UI:${toggleEvent}`, { muted: next });
14671
+ }, [muted, setMuted, toggleEvent, eventBus]);
14517
14672
  const activeAsset = muted ? offAsset : onAsset;
14518
14673
  return /* @__PURE__ */ jsxRuntime.jsx(
14519
14674
  Button,
@@ -14532,10 +14687,319 @@ var init_GameAudioToggle = __esm({
14532
14687
  "use client";
14533
14688
  init_atoms();
14534
14689
  init_cn();
14690
+ init_useEventBus();
14535
14691
  init_GameIcon();
14536
14692
  GameAudioToggle.displayName = "GameAudioToggle";
14537
14693
  }
14538
14694
  });
14695
+ function pickPath(entry) {
14696
+ if (Array.isArray(entry.path)) {
14697
+ return entry.path[Math.floor(Math.random() * entry.path.length)];
14698
+ }
14699
+ return entry.path;
14700
+ }
14701
+ function useGameAudio({
14702
+ manifest,
14703
+ baseUrl = "",
14704
+ initialMuted = false,
14705
+ initialVolume = 1
14706
+ }) {
14707
+ const [muted, setMutedState] = React96.useState(initialMuted);
14708
+ const [masterVolume, setMasterVolumeState] = React96.useState(initialVolume);
14709
+ const mutedRef = React96.useRef(muted);
14710
+ const volumeRef = React96.useRef(masterVolume);
14711
+ const manifestRef = React96.useRef(manifest);
14712
+ mutedRef.current = muted;
14713
+ volumeRef.current = masterVolume;
14714
+ manifestRef.current = manifest;
14715
+ const poolsRef = React96.useRef(/* @__PURE__ */ new Map());
14716
+ const getOrCreateElement = React96.useCallback((key) => {
14717
+ const entry = manifestRef.current[key];
14718
+ if (!entry) return null;
14719
+ let pool = poolsRef.current.get(key);
14720
+ if (!pool) {
14721
+ pool = [];
14722
+ poolsRef.current.set(key, pool);
14723
+ }
14724
+ const maxSize = entry.poolSize ?? 1;
14725
+ for (const audio of pool) {
14726
+ if (audio.paused && (audio.ended || audio.currentTime === 0)) {
14727
+ return audio;
14728
+ }
14729
+ }
14730
+ if (pool.length < maxSize) {
14731
+ const src = baseUrl + pickPath(entry);
14732
+ const audio = new Audio(src);
14733
+ audio.loop = entry.loop ?? false;
14734
+ pool.push(audio);
14735
+ return audio;
14736
+ }
14737
+ if (!entry.loop) {
14738
+ let oldest = pool[0];
14739
+ for (const audio of pool) {
14740
+ if (audio.currentTime > oldest.currentTime) {
14741
+ oldest = audio;
14742
+ }
14743
+ }
14744
+ oldest.pause();
14745
+ oldest.currentTime = 0;
14746
+ return oldest;
14747
+ }
14748
+ return null;
14749
+ }, [baseUrl]);
14750
+ const play = React96.useCallback((key) => {
14751
+ if (mutedRef.current) return;
14752
+ const entry = manifestRef.current[key];
14753
+ if (!entry) return;
14754
+ const audio = getOrCreateElement(key);
14755
+ if (!audio) return;
14756
+ audio.volume = Math.min(1, (entry.volume ?? 1) * volumeRef.current);
14757
+ if (!entry.loop) {
14758
+ audio.currentTime = 0;
14759
+ }
14760
+ const promise = audio.play();
14761
+ if (promise) {
14762
+ promise.catch(() => {
14763
+ });
14764
+ }
14765
+ }, [getOrCreateElement]);
14766
+ const stop = React96.useCallback((key) => {
14767
+ const pool = poolsRef.current.get(key);
14768
+ if (!pool) return;
14769
+ for (const audio of pool) {
14770
+ audio.pause();
14771
+ audio.currentTime = 0;
14772
+ }
14773
+ }, []);
14774
+ const currentMusicKeyRef = React96.useRef(null);
14775
+ const currentMusicElRef = React96.useRef(null);
14776
+ const musicFadeRef = React96.useRef(null);
14777
+ const pendingMusicKeyRef = React96.useRef(null);
14778
+ const clearMusicFade = React96.useCallback(() => {
14779
+ if (musicFadeRef.current) {
14780
+ clearInterval(musicFadeRef.current);
14781
+ musicFadeRef.current = null;
14782
+ }
14783
+ }, []);
14784
+ const playMusic = React96.useCallback((key) => {
14785
+ if (key === currentMusicKeyRef.current) return;
14786
+ pendingMusicKeyRef.current = key;
14787
+ const entry = manifestRef.current[key];
14788
+ if (!entry) return;
14789
+ const fadeDurationMs = entry.crossfadeDurationMs ?? 1500;
14790
+ const stepMs = 50;
14791
+ const totalSteps = Math.max(1, fadeDurationMs / stepMs);
14792
+ const targetVolume = Math.min(1, (entry.volume ?? 1) * volumeRef.current);
14793
+ clearMusicFade();
14794
+ const src = baseUrl + (Array.isArray(entry.path) ? entry.path[0] : entry.path);
14795
+ const incoming = new Audio(src);
14796
+ incoming.loop = true;
14797
+ incoming.volume = 0;
14798
+ const outgoing = currentMusicElRef.current;
14799
+ const outgoingStartVol = outgoing?.volume ?? 0;
14800
+ currentMusicKeyRef.current = key;
14801
+ currentMusicElRef.current = incoming;
14802
+ if (!mutedRef.current) {
14803
+ incoming.play().catch(() => {
14804
+ currentMusicKeyRef.current = null;
14805
+ currentMusicElRef.current = outgoing;
14806
+ });
14807
+ }
14808
+ let step = 0;
14809
+ musicFadeRef.current = setInterval(() => {
14810
+ step++;
14811
+ const progress = Math.min(step / totalSteps, 1);
14812
+ incoming.volume = Math.min(1, targetVolume * progress);
14813
+ if (outgoing) {
14814
+ outgoing.volume = Math.max(0, outgoingStartVol * (1 - progress));
14815
+ }
14816
+ if (progress >= 1) {
14817
+ clearMusicFade();
14818
+ if (outgoing) {
14819
+ outgoing.pause();
14820
+ outgoing.src = "";
14821
+ }
14822
+ }
14823
+ }, stepMs);
14824
+ }, [baseUrl, clearMusicFade]);
14825
+ const stopMusic = React96.useCallback((fadeDurationMs = 1e3) => {
14826
+ const outgoing = currentMusicElRef.current;
14827
+ if (!outgoing) return;
14828
+ currentMusicKeyRef.current = null;
14829
+ currentMusicElRef.current = null;
14830
+ pendingMusicKeyRef.current = null;
14831
+ clearMusicFade();
14832
+ const startVolume = outgoing.volume;
14833
+ const stepMs = 50;
14834
+ const totalSteps = Math.max(1, fadeDurationMs / stepMs);
14835
+ let step = 0;
14836
+ musicFadeRef.current = setInterval(() => {
14837
+ step++;
14838
+ const progress = step / totalSteps;
14839
+ outgoing.volume = Math.max(0, startVolume * (1 - progress));
14840
+ if (progress >= 1) {
14841
+ clearMusicFade();
14842
+ outgoing.pause();
14843
+ outgoing.src = "";
14844
+ }
14845
+ }, stepMs);
14846
+ }, [clearMusicFade]);
14847
+ const stopAll = React96.useCallback(() => {
14848
+ for (const pool of poolsRef.current.values()) {
14849
+ for (const audio of pool) {
14850
+ audio.pause();
14851
+ audio.currentTime = 0;
14852
+ }
14853
+ }
14854
+ stopMusic(0);
14855
+ }, [stopMusic]);
14856
+ const setMuted = React96.useCallback((value) => {
14857
+ setMutedState(value);
14858
+ if (value) {
14859
+ for (const [key, pool] of poolsRef.current.entries()) {
14860
+ if (manifestRef.current[key]?.loop) {
14861
+ for (const audio of pool) {
14862
+ if (!audio.paused) audio.pause();
14863
+ }
14864
+ }
14865
+ }
14866
+ currentMusicElRef.current?.pause();
14867
+ } else {
14868
+ for (const [key, pool] of poolsRef.current.entries()) {
14869
+ const entry = manifestRef.current[key];
14870
+ if (entry?.loop && entry?.autostart) {
14871
+ for (const audio of pool) {
14872
+ if (audio.paused) audio.play().catch(() => {
14873
+ });
14874
+ }
14875
+ }
14876
+ }
14877
+ const musicEl = currentMusicElRef.current;
14878
+ if (musicEl) {
14879
+ musicEl.play().catch(() => {
14880
+ });
14881
+ }
14882
+ }
14883
+ }, []);
14884
+ const setMasterVolume = React96.useCallback((volume) => {
14885
+ const clamped = Math.max(0, Math.min(1, volume));
14886
+ setMasterVolumeState(clamped);
14887
+ for (const [key, pool] of poolsRef.current.entries()) {
14888
+ const entryVol = manifestRef.current[key]?.volume ?? 1;
14889
+ for (const audio of pool) {
14890
+ audio.volume = Math.min(1, entryVol * clamped);
14891
+ }
14892
+ }
14893
+ if (!musicFadeRef.current && currentMusicElRef.current) {
14894
+ const key = currentMusicKeyRef.current;
14895
+ const entryVol = key ? manifestRef.current[key]?.volume ?? 1 : 1;
14896
+ currentMusicElRef.current.volume = Math.min(1, entryVol * clamped);
14897
+ }
14898
+ }, []);
14899
+ const unlockedRef = React96.useRef(false);
14900
+ React96.useEffect(() => {
14901
+ const autoKeys = Object.keys(manifest).filter((k) => manifest[k].autostart);
14902
+ const hasPendingMusic = () => pendingMusicKeyRef.current !== null;
14903
+ const hasAutoStart = autoKeys.length > 0;
14904
+ if (!hasAutoStart && !hasPendingMusic()) return;
14905
+ const unlock = () => {
14906
+ if (unlockedRef.current) return;
14907
+ unlockedRef.current = true;
14908
+ if (!mutedRef.current) {
14909
+ for (const key of autoKeys) {
14910
+ play(key);
14911
+ }
14912
+ const pending = pendingMusicKeyRef.current;
14913
+ if (pending && pending !== currentMusicKeyRef.current) {
14914
+ playMusic(pending);
14915
+ }
14916
+ }
14917
+ };
14918
+ document.addEventListener("click", unlock, { once: true });
14919
+ document.addEventListener("keydown", unlock, { once: true });
14920
+ document.addEventListener("touchstart", unlock, { once: true });
14921
+ return () => {
14922
+ document.removeEventListener("click", unlock);
14923
+ document.removeEventListener("keydown", unlock);
14924
+ document.removeEventListener("touchstart", unlock);
14925
+ };
14926
+ }, [manifest, play, playMusic]);
14927
+ React96.useEffect(() => {
14928
+ return () => {
14929
+ clearMusicFade();
14930
+ for (const pool of poolsRef.current.values()) {
14931
+ for (const audio of pool) {
14932
+ audio.pause();
14933
+ audio.src = "";
14934
+ }
14935
+ }
14936
+ poolsRef.current.clear();
14937
+ if (currentMusicElRef.current) {
14938
+ currentMusicElRef.current.pause();
14939
+ currentMusicElRef.current.src = "";
14940
+ currentMusicElRef.current = null;
14941
+ }
14942
+ };
14943
+ }, [clearMusicFade]);
14944
+ return {
14945
+ play,
14946
+ stop,
14947
+ stopAll,
14948
+ playMusic,
14949
+ stopMusic,
14950
+ muted,
14951
+ setMuted,
14952
+ masterVolume,
14953
+ setMasterVolume
14954
+ };
14955
+ }
14956
+ var init_useGameAudio = __esm({
14957
+ "hooks/useGameAudio.ts"() {
14958
+ "use client";
14959
+ useGameAudio.displayName = "useGameAudio";
14960
+ }
14961
+ });
14962
+ function GameAudioCue({
14963
+ cue,
14964
+ cueSeq,
14965
+ music,
14966
+ muted,
14967
+ volume,
14968
+ manifest,
14969
+ baseUrl
14970
+ }) {
14971
+ const { play, playMusic, stopMusic, setMuted, setMasterVolume } = useGameAudio({
14972
+ manifest,
14973
+ baseUrl,
14974
+ initialMuted: muted,
14975
+ initialVolume: volume
14976
+ });
14977
+ const prevCueSeqRef = React96.useRef(cueSeq);
14978
+ React96.useEffect(() => {
14979
+ if (cue && cueSeq !== void 0 && cueSeq !== prevCueSeqRef.current) {
14980
+ play(cue);
14981
+ }
14982
+ prevCueSeqRef.current = cueSeq;
14983
+ }, [cue, cueSeq, play]);
14984
+ React96.useEffect(() => {
14985
+ if (music) playMusic(music);
14986
+ else stopMusic();
14987
+ }, [music, playMusic, stopMusic]);
14988
+ React96.useEffect(() => {
14989
+ if (muted !== void 0) setMuted(muted);
14990
+ }, [muted, setMuted]);
14991
+ React96.useEffect(() => {
14992
+ if (volume !== void 0) setMasterVolume(volume);
14993
+ }, [volume, setMasterVolume]);
14994
+ return null;
14995
+ }
14996
+ var init_GameAudioCue = __esm({
14997
+ "components/game/atoms/GameAudioCue.tsx"() {
14998
+ "use client";
14999
+ init_useGameAudio();
15000
+ GameAudioCue.displayName = "GameAudioCue";
15001
+ }
15002
+ });
14539
15003
  function isKnownState(s) {
14540
15004
  return s in DEFAULT_STATE_STYLES;
14541
15005
  }
@@ -15234,15 +15698,18 @@ var init_StateJsonView = __esm({
15234
15698
  StateJsonView.displayName = "StateJsonView";
15235
15699
  }
15236
15700
  });
15237
- var GAME_FONTS, GameShell;
15238
- var init_GameShell = __esm({
15239
- "components/game/templates/GameShell.tsx"() {
15240
- init_cn();
15241
- init_Box();
15242
- init_Card();
15243
- init_Typography();
15244
- init_AtlasImage();
15245
- GAME_FONTS = {
15701
+
15702
+ // lib/gameFonts.ts
15703
+ function resolveGameFontFamily(input) {
15704
+ if (!input) return void 0;
15705
+ const resolved = GAME_FONT_KEYS[input];
15706
+ if (resolved) return `'${resolved}', ui-sans-serif, system-ui, sans-serif`;
15707
+ return input;
15708
+ }
15709
+ var GAME_FONT_KEYS;
15710
+ var init_gameFonts = __esm({
15711
+ "lib/gameFonts.ts"() {
15712
+ GAME_FONT_KEYS = {
15246
15713
  fredoka: "Fredoka",
15247
15714
  future: "Kenney Future",
15248
15715
  "future-narrow": "Kenney Future Narrow",
@@ -15250,6 +15717,17 @@ var init_GameShell = __esm({
15250
15717
  blocks: "Kenney Blocks",
15251
15718
  mini: "Kenney Mini"
15252
15719
  };
15720
+ }
15721
+ });
15722
+ var GameShell;
15723
+ var init_GameShell = __esm({
15724
+ "components/game/templates/GameShell.tsx"() {
15725
+ init_cn();
15726
+ init_gameFonts();
15727
+ init_Box();
15728
+ init_Card();
15729
+ init_Typography();
15730
+ init_AtlasImage();
15253
15731
  GameShell = ({
15254
15732
  appName = "Game",
15255
15733
  hud,
@@ -15263,7 +15741,7 @@ var init_GameShell = __esm({
15263
15741
  fontFamily,
15264
15742
  "data-theme": dataTheme
15265
15743
  }) => {
15266
- const font = fontFamily ? GAME_FONTS[fontFamily] ?? fontFamily : void 0;
15744
+ const displayFont = resolveGameFontFamily(fontFamily);
15267
15745
  return /* @__PURE__ */ jsxRuntime.jsxs(
15268
15746
  Box,
15269
15747
  {
@@ -15282,7 +15760,7 @@ var init_GameShell = __esm({
15282
15760
  // passed — an always-on inline stamp would shadow the orbital's
15283
15761
  // inline-theme font-family-display token (inline style beats the
15284
15762
  // theme provider's vars for the whole shell subtree).
15285
- ...font ? { "--font-family-display": `'${font}', ui-sans-serif, system-ui, sans-serif` } : {}
15763
+ ...displayFont ? { "--font-family-display": displayFont } : {}
15286
15764
  },
15287
15765
  children: [
15288
15766
  backgroundAsset && /* @__PURE__ */ jsxRuntime.jsx(
@@ -15493,14 +15971,15 @@ function drawArrowHead(ctx, x1, y1, x2, y2, size) {
15493
15971
  ctx.closePath();
15494
15972
  ctx.fill();
15495
15973
  }
15496
- function drawShape(ctx, shape, width, height, allShapes) {
15974
+ function drawShape(ctx, shape, width, height, allShapes, fontFamily) {
15497
15975
  ctx.save();
15498
15976
  const opacity = shape.opacity ?? 1;
15499
15977
  ctx.globalAlpha = opacity;
15500
15978
  const stroke = resolveColor2(shape.color, ctx, "#333333");
15501
15979
  const fill = shape.fill ? resolveColor2(shape.fill, ctx, "#cccccc") : void 0;
15502
15980
  ctx.lineWidth = shape.lineWidth ?? 2;
15503
- if (shape.dash) ctx.setLineDash([...DASH_PATTERNS[shape.dash]]);
15981
+ const dashPattern = shape.dash ? DASH_PATTERNS[shape.dash] : void 0;
15982
+ if (dashPattern) ctx.setLineDash([...dashPattern]);
15504
15983
  switch (shape.type) {
15505
15984
  case "grid": {
15506
15985
  const step = shape.step ?? 40;
@@ -15622,7 +16101,7 @@ function drawShape(ctx, shape, width, height, allShapes) {
15622
16101
  case "text": {
15623
16102
  if (shape.x == null || shape.y == null || !shape.text) break;
15624
16103
  ctx.fillStyle = stroke;
15625
- ctx.font = `${shape.fontSize ?? 14}px ${themeBodyFont(ctx.canvas)}`;
16104
+ ctx.font = `${shape.fontSize ?? 14}px ${shape.fontFamily ?? fontFamily ?? themeBodyFont(ctx.canvas)}`;
15626
16105
  ctx.textAlign = shape.align ?? "left";
15627
16106
  ctx.textBaseline = "middle";
15628
16107
  ctx.fillText(shape.text, shape.x, shape.y);
@@ -15664,7 +16143,7 @@ function drawShape(ctx, shape, width, height, allShapes) {
15664
16143
  }
15665
16144
  ctx.restore();
15666
16145
  }
15667
- function readoutShapes(readouts, width) {
16146
+ function readoutShapes(readouts, width, fontFamily) {
15668
16147
  const out = [];
15669
16148
  const chipH = 18;
15670
16149
  const gap = 6;
@@ -15688,13 +16167,14 @@ function readoutShapes(readouts, width) {
15688
16167
  text,
15689
16168
  color: "#ffffff",
15690
16169
  fontSize: 10,
15691
- align: "center"
16170
+ align: "center",
16171
+ fontFamily
15692
16172
  });
15693
16173
  rightEdge = chipX - gap;
15694
16174
  }
15695
16175
  return out;
15696
16176
  }
15697
- function traceShapes(panel, k, width, height) {
16177
+ function traceShapes(panel, k, width, height, fontFamily) {
15698
16178
  const w = panel.width ?? Math.round(width * 0.32);
15699
16179
  const h = panel.height ?? Math.round(height * 0.28);
15700
16180
  const x = panel.x ?? width - w - 8;
@@ -15748,14 +16228,14 @@ function traceShapes(panel, k, width, height) {
15748
16228
  out.push({ type: "circle", x: last.x, y: last.y, radius: 2, color, fill: color });
15749
16229
  }
15750
16230
  if (series.label) {
15751
- out.push({ type: "text", x: x + 6, y: y + 10 + 11 * j, text: series.label, color, fontSize: 9 });
16231
+ out.push({ type: "text", x: x + 6, y: y + 10 + 11 * j, text: series.label, color, fontSize: 9, fontFamily });
15752
16232
  }
15753
16233
  });
15754
16234
  if (panel.yLabel) {
15755
- out.push({ type: "text", x: x + w - 6, y: y + 10, text: panel.yLabel, color: "#6b7280", fontSize: 9, align: "right" });
16235
+ out.push({ type: "text", x: x + w - 6, y: y + 10, text: panel.yLabel, color: "#6b7280", fontSize: 9, align: "right", fontFamily });
15756
16236
  }
15757
16237
  if (panel.xLabel) {
15758
- out.push({ type: "text", x: x + w - 6, y: y + h - 6, text: panel.xLabel, color: "#6b7280", fontSize: 9, align: "right" });
16238
+ out.push({ type: "text", x: x + w - 6, y: y + h - 6, text: panel.xLabel, color: "#6b7280", fontSize: 9, align: "right", fontFamily });
15759
16239
  }
15760
16240
  return out;
15761
16241
  }
@@ -15778,13 +16258,14 @@ var init_LearningCanvas = __esm({
15778
16258
  init_useEventBus();
15779
16259
  init_webPainter2d();
15780
16260
  init_paintDispatch();
15781
- DASH_PATTERNS = { dashed: [6, 4], dotted: [2, 3] };
16261
+ DASH_PATTERNS = { solid: [], dashed: [6, 4], dotted: [2, 3] };
15782
16262
  TRACE_SERIES_COLORS = ["#2563eb", "#dc2626", "#16a34a", "#f59e0b"];
15783
16263
  LearningCanvas = ({
15784
16264
  className,
15785
16265
  width = 600,
15786
16266
  height = 400,
15787
16267
  backgroundColor,
16268
+ fontFamily,
15788
16269
  shapes = [],
15789
16270
  drawables,
15790
16271
  projector,
@@ -15820,10 +16301,10 @@ var init_LearningCanvas = __esm({
15820
16301
  }, [shapes]);
15821
16302
  const derivedShapes = React96.useMemo(() => {
15822
16303
  if (!traces?.length && !readouts?.length) return shapes;
15823
- const traceOut = (traces ?? []).flatMap((panel, k) => traceShapes(panel, k, width, height));
15824
- const readoutOut = readouts?.length ? readoutShapes(readouts, width) : [];
16304
+ const traceOut = (traces ?? []).flatMap((panel, k) => traceShapes(panel, k, width, height, fontFamily));
16305
+ const readoutOut = readouts?.length ? readoutShapes(readouts, width, fontFamily) : [];
15825
16306
  return [...shapes, ...traceOut, ...readoutOut];
15826
- }, [shapes, traces, readouts, width, height]);
16307
+ }, [shapes, traces, readouts, width, height, fontFamily]);
15827
16308
  const draw = React96.useCallback(() => {
15828
16309
  const _perfT = ui.perfStart("learningcanvas:paint");
15829
16310
  const canvas = canvasRef.current;
@@ -15842,15 +16323,15 @@ var init_LearningCanvas = __esm({
15842
16323
  ctx.fillRect(0, 0, width, height);
15843
16324
  }
15844
16325
  for (const shape of derivedShapes) {
15845
- if (shape.type !== "text") drawShape(ctx, shape, width, height, derivedShapes);
16326
+ if (shape.type !== "text") drawShape(ctx, shape, width, height, derivedShapes, fontFamily);
15846
16327
  }
15847
16328
  for (const shape of derivedShapes) {
15848
- if (shape.type === "text") drawShape(ctx, shape, width, height, derivedShapes);
16329
+ if (shape.type === "text") drawShape(ctx, shape, width, height, derivedShapes, fontFamily);
15849
16330
  }
15850
16331
  if (drawables?.length && projector) {
15851
16332
  const painter = createWebPainter(ctx, invalidateRef.current);
15852
16333
  const timeMs = needsAnim && typeof performance !== "undefined" ? performance.now() : 0;
15853
- const dctx = { projector, time: timeMs, invalidate: invalidateRef.current, fontFamily: themeBodyFont(canvas) };
16334
+ const dctx = { projector, time: timeMs, invalidate: invalidateRef.current, fontFamily: fontFamily || themeBodyFont(canvas) };
15854
16335
  for (const node of drawables) {
15855
16336
  paintDrawable(painter, node, dctx);
15856
16337
  }
@@ -19622,6 +20103,250 @@ var init_EmptyState = __esm({
19622
20103
  EmptyState.displayName = "EmptyState";
19623
20104
  }
19624
20105
  });
20106
+
20107
+ // lib/editorMotions.ts
20108
+ function clamp(value, min, max) {
20109
+ return Math.max(min, Math.min(max, value));
20110
+ }
20111
+ function computeLines(text) {
20112
+ const lines = [];
20113
+ let start = 0;
20114
+ for (let i = 0; i <= text.length; i++) {
20115
+ if (i === text.length || text[i] === "\n") {
20116
+ lines.push({ start, end: i });
20117
+ start = i + 1;
20118
+ }
20119
+ }
20120
+ return lines;
20121
+ }
20122
+ function lineIndexAt(lines, pos) {
20123
+ for (let i = 0; i < lines.length; i++) {
20124
+ if (pos <= lines[i].end) return i;
20125
+ }
20126
+ return lines.length - 1;
20127
+ }
20128
+ function findWords(text) {
20129
+ const words = [];
20130
+ const re = /\w+|\S+/g;
20131
+ let m;
20132
+ while ((m = re.exec(text)) !== null) {
20133
+ words.push({ start: m.index, end: m.index + m[0].length });
20134
+ }
20135
+ return words;
20136
+ }
20137
+ function nextWordStart(text, pos) {
20138
+ for (const w of findWords(text)) {
20139
+ if (w.start > pos) return w.start;
20140
+ }
20141
+ return text.length;
20142
+ }
20143
+ function prevWordStart(text, pos) {
20144
+ let result = 0;
20145
+ for (const w of findWords(text)) {
20146
+ if (w.start < pos) result = w.start;
20147
+ else break;
20148
+ }
20149
+ return result;
20150
+ }
20151
+ function nextWordEnd(text, pos) {
20152
+ for (const w of findWords(text)) {
20153
+ const lastChar = w.end - 1;
20154
+ if (lastChar > pos) return lastChar;
20155
+ }
20156
+ return text.length > 0 ? text.length - 1 : 0;
20157
+ }
20158
+ function firstNonBlank(text, line) {
20159
+ let i = line.start;
20160
+ while (i < line.end && (text[i] === " " || text[i] === " ")) i++;
20161
+ return i;
20162
+ }
20163
+ function nextParagraphBoundary(lines, fromLineIdx, textLength) {
20164
+ for (let i = fromLineIdx + 1; i < lines.length; i++) {
20165
+ if (lines[i].start === lines[i].end) return lines[i].start;
20166
+ }
20167
+ return textLength;
20168
+ }
20169
+ function prevParagraphBoundary(lines, fromLineIdx) {
20170
+ for (let i = fromLineIdx - 1; i >= 0; i--) {
20171
+ if (lines[i].start === lines[i].end) return lines[i].start;
20172
+ }
20173
+ return 0;
20174
+ }
20175
+ function applyMotion(text, caret, motion, count) {
20176
+ const n = Math.max(1, count);
20177
+ const lines = computeLines(text);
20178
+ const lineIdx = lineIndexAt(lines, caret);
20179
+ const line = lines[lineIdx];
20180
+ switch (motion) {
20181
+ case "left":
20182
+ return clamp(caret - n, line.start, line.end);
20183
+ case "right":
20184
+ return clamp(caret + n, line.start, line.end);
20185
+ case "up": {
20186
+ const col = caret - line.start;
20187
+ const targetIdx = clamp(lineIdx - n, 0, lines.length - 1);
20188
+ const target = lines[targetIdx];
20189
+ return clamp(target.start + col, target.start, target.end);
20190
+ }
20191
+ case "down": {
20192
+ const col = caret - line.start;
20193
+ const targetIdx = clamp(lineIdx + n, 0, lines.length - 1);
20194
+ const target = lines[targetIdx];
20195
+ return clamp(target.start + col, target.start, target.end);
20196
+ }
20197
+ case "word-forward": {
20198
+ let pos = caret;
20199
+ for (let i = 0; i < n; i++) pos = nextWordStart(text, pos);
20200
+ return pos;
20201
+ }
20202
+ case "word-back": {
20203
+ let pos = caret;
20204
+ for (let i = 0; i < n; i++) pos = prevWordStart(text, pos);
20205
+ return pos;
20206
+ }
20207
+ case "word-end": {
20208
+ let pos = caret;
20209
+ for (let i = 0; i < n; i++) pos = nextWordEnd(text, pos);
20210
+ return pos;
20211
+ }
20212
+ case "line-start":
20213
+ return line.start;
20214
+ case "line-end":
20215
+ return line.end > line.start ? line.end - 1 : line.start;
20216
+ case "first-nonblank":
20217
+ return firstNonBlank(text, line);
20218
+ case "doc-start":
20219
+ return 0;
20220
+ case "doc-end":
20221
+ return text.length;
20222
+ case "paragraph-forward": {
20223
+ let pos = caret;
20224
+ for (let i = 0; i < n; i++) {
20225
+ pos = nextParagraphBoundary(lines, lineIndexAt(lines, pos), text.length);
20226
+ }
20227
+ return pos;
20228
+ }
20229
+ case "paragraph-back": {
20230
+ let pos = caret;
20231
+ for (let i = 0; i < n; i++) {
20232
+ pos = prevParagraphBoundary(lines, lineIndexAt(lines, pos));
20233
+ }
20234
+ return pos;
20235
+ }
20236
+ case "line":
20237
+ case "selection":
20238
+ return caret;
20239
+ default: {
20240
+ const _exhaustive = motion;
20241
+ return _exhaustive;
20242
+ }
20243
+ }
20244
+ }
20245
+ function motionRange(text, caret, motion, count, selection) {
20246
+ if (motion === "selection") {
20247
+ if (!selection) return [caret, caret];
20248
+ return [Math.min(selection[0], selection[1]), Math.max(selection[0], selection[1])];
20249
+ }
20250
+ const lines = computeLines(text);
20251
+ if (motion === "line") {
20252
+ const n = Math.max(1, count);
20253
+ const startIdx = lineIndexAt(lines, caret);
20254
+ const endIdx = clamp(startIdx + n - 1, 0, lines.length - 1);
20255
+ const start2 = lines[startIdx].start;
20256
+ const rawEnd = lines[endIdx].end;
20257
+ const end2 = rawEnd < text.length ? rawEnd + 1 : rawEnd;
20258
+ return [start2, end2];
20259
+ }
20260
+ const newCaret = applyMotion(text, caret, motion, count);
20261
+ let start = Math.min(caret, newCaret);
20262
+ let end = Math.max(caret, newCaret);
20263
+ if (motion === "word-end" || motion === "line-end") {
20264
+ end = Math.min(Math.max(start, newCaret) + 1, text.length);
20265
+ } else if (motion === "word-forward" && newCaret > caret) {
20266
+ const startLine = lineIndexAt(lines, caret);
20267
+ const endLine = lineIndexAt(lines, newCaret);
20268
+ if (endLine !== startLine) {
20269
+ end = lines[startLine].end;
20270
+ }
20271
+ }
20272
+ return [start, end];
20273
+ }
20274
+ function applyOperator(text, range, operator, register) {
20275
+ const start = clamp(range[0], 0, text.length);
20276
+ const end = clamp(range[1], start, text.length);
20277
+ const removed = text.slice(start, end);
20278
+ if (operator === "yank") {
20279
+ return { text, caret: start, register: removed };
20280
+ }
20281
+ return { text: text.slice(0, start) + text.slice(end), caret: start, register: removed };
20282
+ }
20283
+ var init_editorMotions = __esm({
20284
+ "lib/editorMotions.ts"() {
20285
+ }
20286
+ });
20287
+ function isMotionPayload(payload) {
20288
+ return !!payload && typeof payload.editorId === "string" && typeof payload.motion === "string" && typeof payload.count === "number";
20289
+ }
20290
+ function isOperatePayload(payload) {
20291
+ return !!payload && typeof payload.editorId === "string" && typeof payload.operator === "string" && typeof payload.motion === "string" && typeof payload.count === "number";
20292
+ }
20293
+ function isInsertTextPayload(payload) {
20294
+ return !!payload && typeof payload.editorId === "string" && typeof payload.text === "string";
20295
+ }
20296
+ function isSetModePayload(payload) {
20297
+ return !!payload && typeof payload.editorId === "string" && typeof payload.mode === "string" && typeof payload.caret === "string";
20298
+ }
20299
+ function useEditorCapabilities(args) {
20300
+ const [caretMode, setCaretMode] = React96.useState("bar");
20301
+ const registerRef = React96.useRef("");
20302
+ useEventListener(`UI:${args.events.onMotion}`, (evt) => {
20303
+ if (!args.editorId || !isMotionPayload(evt.payload) || evt.payload.editorId !== args.editorId) return;
20304
+ const ta = args.textareaRef.current;
20305
+ if (!ta) return;
20306
+ const { motion, count } = evt.payload;
20307
+ const newCaret = applyMotion(ta.value, ta.selectionStart, motion, count);
20308
+ if (ta.selectionStart !== ta.selectionEnd) {
20309
+ ta.setSelectionRange(ta.selectionStart, newCaret);
20310
+ } else {
20311
+ ta.setSelectionRange(newCaret, newCaret);
20312
+ }
20313
+ });
20314
+ useEventListener(`UI:${args.events.onOperate}`, (evt) => {
20315
+ if (!args.editorId || !isOperatePayload(evt.payload) || evt.payload.editorId !== args.editorId) return;
20316
+ const ta = args.textareaRef.current;
20317
+ if (!ta) return;
20318
+ const { operator, motion, count } = evt.payload;
20319
+ const selection = motion === "selection" ? [ta.selectionStart, ta.selectionEnd] : void 0;
20320
+ const range = motionRange(ta.value, ta.selectionStart, motion, count, selection);
20321
+ const result = applyOperator(ta.value, range, operator, registerRef.current);
20322
+ registerRef.current = result.register;
20323
+ if (operator === "yank") {
20324
+ ta.setSelectionRange(range[0], range[0]);
20325
+ } else {
20326
+ ta.setRangeText("", range[0], range[1], "end");
20327
+ }
20328
+ args.applyChange(ta.value);
20329
+ });
20330
+ useEventListener(`UI:${args.events.onInsertText}`, (evt) => {
20331
+ if (!args.editorId || !isInsertTextPayload(evt.payload) || evt.payload.editorId !== args.editorId) return;
20332
+ const ta = args.textareaRef.current;
20333
+ if (!ta) return;
20334
+ ta.setRangeText(evt.payload.text, ta.selectionStart, ta.selectionEnd, "end");
20335
+ args.applyChange(ta.value);
20336
+ });
20337
+ useEventListener(`UI:${args.events.onSetMode}`, (evt) => {
20338
+ if (!args.editorId || !isSetModePayload(evt.payload) || evt.payload.editorId !== args.editorId) return;
20339
+ setCaretMode(evt.payload.caret);
20340
+ });
20341
+ return { caretMode };
20342
+ }
20343
+ var init_useEditorCapabilities = __esm({
20344
+ "components/core/molecules/markdown/useEditorCapabilities.ts"() {
20345
+ "use client";
20346
+ init_useEventBus();
20347
+ init_editorMotions();
20348
+ }
20349
+ });
19625
20350
  function isLanguageRegistered(lang) {
19626
20351
  return CODE_LANGUAGE_SET.has(lang) || dynamicallyLoaded.has(lang);
19627
20352
  }
@@ -19707,7 +20432,36 @@ function useLanguageReady(language) {
19707
20432
  }, [language]);
19708
20433
  return ready;
19709
20434
  }
19710
- var dynamicallyLoaded, codeLanguageLoader, orbStyleOverrides, orbStyle, loloStyleOverrides, loloStyle, log4, CODE_LANGUAGES, CODE_LANGUAGE_SET, DIFF_STYLES, DIFF_STYLE_FALLBACK, LINE_PROPS_FN, HIDDEN_LINE_NUMBERS, HIGHLIGHT_CAPACITY_BYTES, CodeBlock;
20435
+ function resolveHighlightStyle(lang) {
20436
+ if (lang === "orb") return orbStyle;
20437
+ if (lang === "lolo") return loloStyle;
20438
+ return dark__default.default;
20439
+ }
20440
+ function plainCodeColorOf(style) {
20441
+ return style['code[class*="language-"]']?.color ?? "#d4d4d4";
20442
+ }
20443
+ function buildLineProps(errorLines, extraClassName) {
20444
+ return (lineNumber) => {
20445
+ const base = {
20446
+ "data-line": String(lineNumber - 1),
20447
+ ...extraClassName ? { className: extraClassName } : {}
20448
+ };
20449
+ const severity = errorLines?.get(lineNumber);
20450
+ if (!severity) return base;
20451
+ return {
20452
+ ...base,
20453
+ style: {
20454
+ display: "block",
20455
+ backgroundColor: severity === "error" ? "rgba(248, 113, 113, 0.18)" : "rgba(251, 191, 36, 0.18)",
20456
+ // amber-400 @ 18%
20457
+ borderLeft: `3px solid ${severity === "error" ? "#ef4444" : "#f59e0b"}`,
20458
+ paddingLeft: "0.5rem",
20459
+ marginLeft: "-0.5rem"
20460
+ }
20461
+ };
20462
+ };
20463
+ }
20464
+ var dynamicallyLoaded, codeLanguageLoader, orbStyleOverrides, orbStyle, loloStyleOverrides, loloStyle, log4, CODE_LANGUAGES, CODE_LANGUAGE_SET, DIFF_STYLES, DIFF_STYLE_FALLBACK, LINE_PROPS_FN, HIDDEN_LINE_NUMBERS, HIGHLIGHT_CAPACITY_BYTES, MONO_FONT_FAMILY, VIEWER_LINE_NUMBER_STYLE, CodeBlock;
19711
20465
  var init_CodeBlock = __esm({
19712
20466
  "components/core/molecules/markdown/CodeBlock.tsx"() {
19713
20467
  init_cn();
@@ -19723,6 +20477,7 @@ var init_CodeBlock = __esm({
19723
20477
  init_Textarea();
19724
20478
  init_Icon();
19725
20479
  init_useEventBus();
20480
+ init_useEditorCapabilities();
19726
20481
  SyntaxHighlighter__default.default.registerLanguage("json", langJson__default.default);
19727
20482
  SyntaxHighlighter__default.default.registerLanguage("javascript", langJavascript__default.default);
19728
20483
  SyntaxHighlighter__default.default.registerLanguage("js", langJavascript__default.default);
@@ -19941,6 +20696,15 @@ var init_CodeBlock = __esm({
19941
20696
  LINE_PROPS_FN = (n) => ({ "data-line": String(n - 1) });
19942
20697
  HIDDEN_LINE_NUMBERS = { display: "none" };
19943
20698
  HIGHLIGHT_CAPACITY_BYTES = 512 * 1024;
20699
+ MONO_FONT_FAMILY = 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace';
20700
+ VIEWER_LINE_NUMBER_STYLE = {
20701
+ minWidth: "2.5em",
20702
+ paddingRight: "1rem",
20703
+ textAlign: "right",
20704
+ userSelect: "none",
20705
+ opacity: 0.5,
20706
+ fontVariantNumeric: "tabular-nums"
20707
+ };
19944
20708
  CodeBlock = React96__namespace.default.memo(
19945
20709
  ({
19946
20710
  code: rawCode,
@@ -19965,15 +20729,39 @@ var init_CodeBlock = __esm({
19965
20729
  actions,
19966
20730
  isLoading = false,
19967
20731
  error,
19968
- showCopy
20732
+ showCopy,
20733
+ // editor capability surface — P1 wires these
20734
+ editorId,
20735
+ onEditorFocus = "EDITOR_FOCUS",
20736
+ onEditorBlur = "EDITOR_BLUR",
20737
+ onMotion = "MOTION",
20738
+ onOperate = "OPERATE",
20739
+ onInsertText = "INSERT_TEXT",
20740
+ onSetMode = "SET_MODE",
20741
+ motions = [
20742
+ "left",
20743
+ "right",
20744
+ "up",
20745
+ "down",
20746
+ "word-forward",
20747
+ "word-back",
20748
+ "word-end",
20749
+ "line-start",
20750
+ "line-end",
20751
+ "first-nonblank",
20752
+ "doc-start",
20753
+ "doc-end",
20754
+ "paragraph-forward",
20755
+ "paragraph-back",
20756
+ "line",
20757
+ "selection"
20758
+ ],
20759
+ operators = ["delete", "yank", "change"]
19969
20760
  }) => {
19970
20761
  const code = typeof rawCode === "string" ? rawCode : String(rawCode ?? "");
19971
- const isOrb = language === "orb";
19972
- const isLolo = language === "lolo";
19973
- const activeStyle = isOrb ? orbStyle : isLolo ? loloStyle : dark__default.default;
20762
+ const activeStyle = resolveHighlightStyle(language);
19974
20763
  const overCapacity = code.length > HIGHLIGHT_CAPACITY_BYTES;
19975
- const plainCodeColor = activeStyle['code[class*="language-"]']?.color ?? "#d4d4d4";
19976
- const languageReady = useLanguageReady(language);
20764
+ const plainCodeColor = plainCodeColorOf(activeStyle);
19977
20765
  const eventBus = useEventBus();
19978
20766
  const { t } = hooks.useTranslate();
19979
20767
  const scrollRef = React96.useRef(null);
@@ -19985,6 +20773,9 @@ var init_CodeBlock = __esm({
19985
20773
  const activeFile = files?.[activeFileIndex];
19986
20774
  const activeCode = activeFile?.code ?? code;
19987
20775
  const activeLanguage = activeFile?.language ?? language;
20776
+ const languageReady = useLanguageReady(activeLanguage);
20777
+ const viewerStyle = resolveHighlightStyle(activeLanguage);
20778
+ const viewerPlainCodeColor = plainCodeColorOf(viewerStyle);
19988
20779
  const diffLines = React96.useMemo(() => {
19989
20780
  if (propDiff) return propDiff;
19990
20781
  if (mode === "diff" && oldValue !== void 0 && newValue !== void 0) {
@@ -20014,28 +20805,28 @@ var init_CodeBlock = __esm({
20014
20805
  ov.scrollLeft = ta.scrollLeft;
20015
20806
  }
20016
20807
  }, []);
20017
- const errorLineProps = React96.useMemo(() => {
20018
- if (!errorLines || errorLines.size === 0) {
20019
- return LINE_PROPS_FN;
20020
- }
20021
- return (lineNumber) => {
20022
- const severity = errorLines.get(lineNumber);
20023
- if (!severity) {
20024
- return { "data-line": String(lineNumber - 1) };
20025
- }
20026
- return {
20027
- "data-line": String(lineNumber - 1),
20028
- style: {
20029
- display: "block",
20030
- backgroundColor: severity === "error" ? "rgba(248, 113, 113, 0.18)" : "rgba(251, 191, 36, 0.18)",
20031
- // amber-400 @ 18%
20032
- borderLeft: `3px solid ${severity === "error" ? "#ef4444" : "#f59e0b"}`,
20033
- paddingLeft: "0.5rem",
20034
- marginLeft: "-0.5rem"
20035
- }
20036
- };
20037
- };
20038
- }, [errorLines]);
20808
+ const handleEditableChange = React96.useCallback((v) => {
20809
+ lastPropCodeRef.current = v;
20810
+ setEditableValue(v);
20811
+ onChange?.(v);
20812
+ }, [onChange]);
20813
+ const { caretMode } = useEditorCapabilities({
20814
+ editorId: editable ? editorId : void 0,
20815
+ textareaRef: editableTextareaRef,
20816
+ events: { onMotion, onOperate, onInsertText, onSetMode },
20817
+ applyChange: handleEditableChange
20818
+ });
20819
+ const [caretIndex, setCaretIndex] = React96.useState(0);
20820
+ const caretRowCol = React96.useMemo(() => {
20821
+ const before = editableValue.slice(0, Math.min(caretIndex, editableValue.length));
20822
+ const lines = before.split("\n");
20823
+ return { row: lines.length - 1, col: lines[lines.length - 1].length };
20824
+ }, [editableValue, caretIndex]);
20825
+ const errorLineProps = React96.useMemo(() => buildLineProps(errorLines), [errorLines]);
20826
+ const viewerLineProps = React96.useMemo(
20827
+ () => buildLineProps(errorLines, "px-4 py-0.5 hover:bg-muted/50"),
20828
+ [errorLines]
20829
+ );
20039
20830
  const isFoldable = foldableProp ?? true;
20040
20831
  const [collapsed, setCollapsed] = React96.useState(() => /* @__PURE__ */ new Set());
20041
20832
  const foldRegions = React96.useMemo(
@@ -20158,6 +20949,110 @@ var init_CodeBlock = __esm({
20158
20949
  ),
20159
20950
  [code, overCapacity, plainCodeColor, language, activeStyle, languageReady]
20160
20951
  );
20952
+ const viewerOverCapacity = activeCode.length > HIGHLIGHT_CAPACITY_BYTES;
20953
+ const viewerHighlightedElement = React96.useMemo(
20954
+ () => viewerOverCapacity ? /* @__PURE__ */ jsxRuntime.jsx(
20955
+ "div",
20956
+ {
20957
+ className: "px-4 py-0.5",
20958
+ style: {
20959
+ margin: 0,
20960
+ whiteSpace: wrap ? "pre-wrap" : "pre",
20961
+ wordBreak: wrap ? "break-all" : "normal",
20962
+ color: viewerPlainCodeColor,
20963
+ fontFamily: MONO_FONT_FAMILY,
20964
+ fontSize: "12px",
20965
+ lineHeight: "1.6"
20966
+ },
20967
+ children: activeCode
20968
+ }
20969
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
20970
+ SyntaxHighlighter__default.default,
20971
+ {
20972
+ PreTag: "div",
20973
+ language: activeLanguage,
20974
+ style: viewerStyle,
20975
+ wrapLines: true,
20976
+ wrapLongLines: wrap,
20977
+ showLineNumbers,
20978
+ lineNumberStyle: VIEWER_LINE_NUMBER_STYLE,
20979
+ lineProps: viewerLineProps,
20980
+ customStyle: {
20981
+ backgroundColor: "transparent",
20982
+ borderRadius: 0,
20983
+ padding: "0.25rem 0",
20984
+ margin: 0,
20985
+ whiteSpace: wrap ? "pre-wrap" : "pre",
20986
+ wordBreak: wrap ? "break-all" : "normal",
20987
+ fontFamily: MONO_FONT_FAMILY,
20988
+ fontSize: "12px",
20989
+ lineHeight: "1.6"
20990
+ },
20991
+ codeTagProps: { style: { fontFamily: MONO_FONT_FAMILY, fontSize: "12px", lineHeight: "1.6" } },
20992
+ children: activeCode
20993
+ }
20994
+ ),
20995
+ [activeCode, viewerOverCapacity, viewerPlainCodeColor, activeLanguage, viewerStyle, wrap, showLineNumbers, viewerLineProps, languageReady]
20996
+ );
20997
+ const diffOverCapacity = React96.useMemo(
20998
+ () => !!diffLines && diffLines.reduce((n, l) => n + l.content.length + 1, 0) > HIGHLIGHT_CAPACITY_BYTES,
20999
+ [diffLines]
21000
+ );
21001
+ const diffRowElements = React96.useMemo(() => {
21002
+ if (!diffLines) return null;
21003
+ return diffLines.map((line, idx) => {
21004
+ const style = DIFF_STYLES[line.type] ?? DIFF_STYLE_FALLBACK;
21005
+ return /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "none", align: "start", className: cn(style.bg, "px-4 py-0.5"), children: [
21006
+ showLineNumbers && /* @__PURE__ */ jsxRuntime.jsx(
21007
+ Typography,
21008
+ {
21009
+ variant: "caption",
21010
+ color: "secondary",
21011
+ className: "w-8 text-right mr-3 select-none tabular-nums flex-shrink-0",
21012
+ children: line.lineNumber ?? ""
21013
+ }
21014
+ ),
21015
+ /* @__PURE__ */ jsxRuntime.jsxs(
21016
+ Typography,
21017
+ {
21018
+ variant: "caption",
21019
+ className: cn("font-mono flex-1 min-w-0", style.text, wrap ? "whitespace-pre-wrap break-all" : "whitespace-pre"),
21020
+ children: [
21021
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { as: "span", className: "select-none opacity-50 mr-2", children: style.prefix }),
21022
+ diffOverCapacity ? line.content || " " : /* @__PURE__ */ jsxRuntime.jsx(
21023
+ SyntaxHighlighter__default.default,
21024
+ {
21025
+ PreTag: "span",
21026
+ CodeTag: "span",
21027
+ language: activeLanguage,
21028
+ style: viewerStyle,
21029
+ customStyle: {
21030
+ display: "inline",
21031
+ background: "transparent",
21032
+ padding: 0,
21033
+ margin: 0,
21034
+ whiteSpace: wrap ? "pre-wrap" : "pre",
21035
+ wordBreak: wrap ? "break-all" : "normal",
21036
+ fontFamily: "inherit",
21037
+ fontSize: "inherit",
21038
+ lineHeight: "inherit"
21039
+ },
21040
+ codeTagProps: {
21041
+ style: {
21042
+ whiteSpace: wrap ? "pre-wrap" : "pre",
21043
+ fontFamily: "inherit",
21044
+ fontSize: "inherit"
21045
+ }
21046
+ },
21047
+ children: line.content || " "
21048
+ }
21049
+ )
21050
+ ]
21051
+ }
21052
+ )
21053
+ ] }, idx);
21054
+ });
21055
+ }, [diffLines, showLineNumbers, wrap, diffOverCapacity, activeLanguage, viewerStyle, languageReady]);
20161
21056
  React96.useLayoutEffect(() => {
20162
21057
  const container = codeRef.current;
20163
21058
  if (!container) return;
@@ -20301,7 +21196,6 @@ var init_CodeBlock = __esm({
20301
21196
  label: file.label,
20302
21197
  content: null
20303
21198
  }));
20304
- const lines = activeCode.split("\n");
20305
21199
  return /* @__PURE__ */ jsxRuntime.jsx(Card, { className: cn("overflow-hidden", className), children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column" }, children: [
20306
21200
  tabItems && tabItems.length > 1 && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "border-b border-border", children: /* @__PURE__ */ jsxRuntime.jsx(
20307
21201
  Tabs,
@@ -20364,49 +21258,7 @@ var init_CodeBlock = __esm({
20364
21258
  ]
20365
21259
  }
20366
21260
  ),
20367
- /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "overflow-auto bg-muted/20", style: { maxHeight }, children: diffLines ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column" }, className: "font-mono text-xs", children: diffLines.map((line, idx) => {
20368
- const style = DIFF_STYLES[line.type] ?? DIFF_STYLE_FALLBACK;
20369
- return /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "none", align: "start", className: cn(style.bg, "px-4 py-0.5"), children: [
20370
- showLineNumbers && /* @__PURE__ */ jsxRuntime.jsx(
20371
- Typography,
20372
- {
20373
- variant: "caption",
20374
- color: "secondary",
20375
- className: "w-8 text-right mr-3 select-none tabular-nums flex-shrink-0",
20376
- children: line.lineNumber ?? ""
20377
- }
20378
- ),
20379
- /* @__PURE__ */ jsxRuntime.jsxs(
20380
- Typography,
20381
- {
20382
- variant: "caption",
20383
- className: cn("font-mono flex-1 min-w-0", style.text, wrap ? "whitespace-pre-wrap break-all" : "whitespace-pre"),
20384
- children: [
20385
- /* @__PURE__ */ jsxRuntime.jsx(Box, { as: "span", className: "select-none opacity-50 mr-2", children: style.prefix }),
20386
- line.content
20387
- ]
20388
- }
20389
- )
20390
- ] }, idx);
20391
- }) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column" }, className: "font-mono text-xs", children: lines.map((line, idx) => /* @__PURE__ */ jsxRuntime.jsxs(HStack, { gap: "none", align: "start", className: "px-4 py-0.5 hover:bg-muted/50", children: [
20392
- showLineNumbers && /* @__PURE__ */ jsxRuntime.jsx(
20393
- Typography,
20394
- {
20395
- variant: "caption",
20396
- color: "secondary",
20397
- className: "w-8 text-right mr-4 select-none tabular-nums flex-shrink-0",
20398
- children: idx + 1
20399
- }
20400
- ),
20401
- /* @__PURE__ */ jsxRuntime.jsx(
20402
- Typography,
20403
- {
20404
- variant: "caption",
20405
- className: cn("font-mono flex-1 min-w-0", wrap ? "whitespace-pre-wrap break-all" : "whitespace-pre"),
20406
- children: line || " "
20407
- }
20408
- )
20409
- ] }, idx)) }) })
21261
+ /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "overflow-auto bg-muted/20", style: { maxHeight }, children: diffLines ? /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column" }, className: "font-mono text-xs", children: diffRowElements }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-mono text-xs", children: viewerHighlightedElement }) })
20410
21262
  ] }) });
20411
21263
  }
20412
21264
  const hasHeader = showLanguageBadge || effectiveCopy;
@@ -20493,13 +21345,11 @@ var init_CodeBlock = __esm({
20493
21345
  {
20494
21346
  ref: editableTextareaRef,
20495
21347
  defaultValue: code,
20496
- onChange: (e) => {
20497
- const v = e.target.value;
20498
- lastPropCodeRef.current = v;
20499
- setEditableValue(v);
20500
- onChange?.(v);
20501
- },
21348
+ onChange: (e) => handleEditableChange(e.target.value),
20502
21349
  onScroll: handleEditableScroll,
21350
+ onSelect: (e) => setCaretIndex(e.currentTarget.selectionStart),
21351
+ onFocus: editorId ? () => eventBus.emit(`UI:${onEditorFocus}`, { editorId }) : void 0,
21352
+ onBlur: editorId ? () => eventBus.emit(`UI:${onEditorBlur}`, { editorId }) : void 0,
20503
21353
  spellCheck: false,
20504
21354
  style: {
20505
21355
  position: "absolute",
@@ -20514,7 +21364,7 @@ var init_CodeBlock = __esm({
20514
21364
  resize: "none",
20515
21365
  backgroundColor: "transparent",
20516
21366
  color: "transparent",
20517
- caretColor: "#e6e6e6",
21367
+ caretColor: caretMode === "block" ? "transparent" : "#e6e6e6",
20518
21368
  WebkitTextFillColor: "transparent",
20519
21369
  fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, "Cascadia Mono", "Courier New", monospace',
20520
21370
  fontSize: "13px",
@@ -20525,6 +21375,22 @@ var init_CodeBlock = __esm({
20525
21375
  }
20526
21376
  },
20527
21377
  editableTextareaKey
21378
+ ),
21379
+ caretMode !== "bar" && /* @__PURE__ */ jsxRuntime.jsx(
21380
+ "span",
21381
+ {
21382
+ "aria-hidden": true,
21383
+ style: {
21384
+ position: "absolute",
21385
+ top: `calc(1rem + ${caretRowCol.row * 19.5}px)`,
21386
+ left: `calc(1rem + ${caretRowCol.col}ch)`,
21387
+ width: "1ch",
21388
+ height: caretMode === "block" ? "19.5px" : "2px",
21389
+ backgroundColor: caretMode === "block" ? "rgba(230, 230, 230, 0.5)" : void 0,
21390
+ borderBottom: caretMode === "underline" ? "2px solid #e6e6e6" : void 0,
21391
+ pointerEvents: "none"
21392
+ }
21393
+ }
20528
21394
  )
20529
21395
  ]
20530
21396
  }
@@ -20552,55 +21418,175 @@ var init_CodeBlock = __esm({
20552
21418
  )
20553
21419
  ] });
20554
21420
  },
20555
- (prev, next) => prev.language === next.language && prev.code === next.code && prev.showCopyButton === next.showCopyButton && prev.showCopy === next.showCopy && prev.maxHeight === next.maxHeight && prev.foldable === next.foldable && prev.editable === next.editable && prev.onChange === next.onChange && prev.errorLines === next.errorLines && prev.mode === next.mode && prev.title === next.title && prev.diff === next.diff && prev.files === next.files && prev.actions === next.actions && prev.isLoading === next.isLoading && prev.error === next.error
21421
+ (prev, next) => prev.language === next.language && prev.code === next.code && prev.showCopyButton === next.showCopyButton && prev.showCopy === next.showCopy && prev.maxHeight === next.maxHeight && prev.foldable === next.foldable && prev.editable === next.editable && prev.onChange === next.onChange && prev.errorLines === next.errorLines && prev.mode === next.mode && prev.title === next.title && prev.diff === next.diff && prev.files === next.files && prev.actions === next.actions && prev.isLoading === next.isLoading && prev.error === next.error && prev.editorId === next.editorId && prev.onEditorFocus === next.onEditorFocus && prev.onEditorBlur === next.onEditorBlur && prev.onMotion === next.onMotion && prev.onOperate === next.onOperate && prev.onInsertText === next.onInsertText && prev.onSetMode === next.onSetMode && prev.motions === next.motions && prev.operators === next.operators
20556
21422
  );
20557
21423
  CodeBlock.displayName = "CodeBlock";
20558
21424
  }
20559
21425
  });
21426
+
21427
+ // components/core/molecules/markdown/mermaidSource.ts
21428
+ function isQuoted(label) {
21429
+ const trimmed = label.trim();
21430
+ return trimmed.length === 0 || trimmed.startsWith('"') && trimmed.endsWith('"') && trimmed.length > 1;
21431
+ }
21432
+ function quote(label) {
21433
+ return `"${label.replace(/"/g, "#quot;")}"`;
21434
+ }
21435
+ function isIdentifierChar(ch) {
21436
+ return /[A-Za-z0-9_\-.]/.test(ch);
21437
+ }
21438
+ function declaredType(code) {
21439
+ for (const line of code.split("\n")) {
21440
+ const trimmed = line.trim();
21441
+ if (trimmed.length === 0 || trimmed.startsWith("%%")) continue;
21442
+ return trimmed;
21443
+ }
21444
+ return "";
21445
+ }
21446
+ function isFlowchart(code) {
21447
+ return FLOWCHART_DIRECTIVE.test(declaredType(code));
21448
+ }
21449
+ function quoteNodeLabels(code) {
21450
+ let out = "";
21451
+ let i = 0;
21452
+ while (i < code.length) {
21453
+ const shape = NODE_SHAPES.find(([open2]) => code.startsWith(open2, i));
21454
+ const precededByIdentifier = i > 0 && isIdentifierChar(code[i - 1] ?? "");
21455
+ if (shape === void 0 || !precededByIdentifier) {
21456
+ out += code[i];
21457
+ i += 1;
21458
+ continue;
21459
+ }
21460
+ const [open, close] = shape;
21461
+ const contentStart = i + open.length;
21462
+ const closeAt = code.indexOf(close, contentStart);
21463
+ const newlineAt = code.indexOf("\n", contentStart);
21464
+ if (closeAt === -1 || newlineAt !== -1 && newlineAt < closeAt) {
21465
+ out += code[i];
21466
+ i += 1;
21467
+ continue;
21468
+ }
21469
+ const label = code.slice(contentStart, closeAt);
21470
+ out += open + (isQuoted(label) ? label : quote(label)) + close;
21471
+ i = closeAt + close.length;
21472
+ }
21473
+ return out;
21474
+ }
21475
+ function quoteEdgeLabels(code) {
21476
+ return code.split("\n").map((line) => {
21477
+ let out = "";
21478
+ let rest = line;
21479
+ for (; ; ) {
21480
+ const open = rest.indexOf("|");
21481
+ if (open === -1) break;
21482
+ const close = rest.indexOf("|", open + 1);
21483
+ if (close === -1) break;
21484
+ const label = rest.slice(open + 1, close);
21485
+ out += rest.slice(0, open + 1) + (isQuoted(label) ? label : quote(label)) + "|";
21486
+ rest = rest.slice(close + 1);
21487
+ }
21488
+ return out + rest;
21489
+ }).join("\n");
21490
+ }
21491
+ function quoteSubgraphTitles(code) {
21492
+ return code.split("\n").map((line) => {
21493
+ const match = /^(\s*subgraph\s+)(.+?)(\s*)$/.exec(line);
21494
+ if (match === null) return line;
21495
+ const [, prefix, title, trailing] = match;
21496
+ if (title === void 0 || prefix === void 0) return line;
21497
+ if (isQuoted(title) || title.includes("[")) return line;
21498
+ return prefix + quote(title) + (trailing ?? "");
21499
+ }).join("\n");
21500
+ }
21501
+ function mermaidRepairCandidates(code) {
21502
+ if (!isFlowchart(code)) return [];
21503
+ const nodes = quoteNodeLabels(code);
21504
+ const nodesAndEdges = quoteEdgeLabels(nodes);
21505
+ const all = quoteSubgraphTitles(nodesAndEdges);
21506
+ const ordered2 = [nodes, nodesAndEdges, all];
21507
+ const seen = /* @__PURE__ */ new Set([code]);
21508
+ const candidates = [];
21509
+ for (const candidate of ordered2) {
21510
+ if (seen.has(candidate)) continue;
21511
+ seen.add(candidate);
21512
+ candidates.push(candidate);
21513
+ }
21514
+ return candidates;
21515
+ }
21516
+ var NODE_SHAPES, FLOWCHART_DIRECTIVE;
21517
+ var init_mermaidSource = __esm({
21518
+ "components/core/molecules/markdown/mermaidSource.ts"() {
21519
+ NODE_SHAPES = [
21520
+ ["[[", "]]"],
21521
+ ["[(", ")]"],
21522
+ ["([", "])"],
21523
+ ["((", "))"],
21524
+ ["{{", "}}"],
21525
+ ["[", "]"],
21526
+ ["(", ")"],
21527
+ ["{", "}"]
21528
+ ];
21529
+ FLOWCHART_DIRECTIVE = /^(?:graph|flowchart)\b/;
21530
+ }
21531
+ });
20560
21532
  function loadMermaid() {
20561
21533
  mermaidModule ?? (mermaidModule = import('mermaid').then((m) => m.default));
20562
21534
  return mermaidModule;
20563
21535
  }
20564
- var mermaidModule, MermaidDiagram;
21536
+ var log5, mermaidModule, MermaidDiagram;
20565
21537
  var init_MermaidDiagram = __esm({
20566
21538
  "components/core/molecules/markdown/MermaidDiagram.tsx"() {
20567
21539
  init_Box();
20568
21540
  init_Typography();
20569
21541
  init_CodeBlock();
21542
+ init_mermaidSource();
20570
21543
  init_cn();
21544
+ log5 = logger.createLogger("almadar:ui:mermaid-diagram");
20571
21545
  mermaidModule = null;
20572
21546
  MermaidDiagram = React96__namespace.default.memo(
20573
21547
  ({ code, className }) => {
20574
21548
  const { resolvedMode } = context.useTheme();
21549
+ const { t } = hooks.useTranslate();
20575
21550
  const containerRef = React96.useRef(null);
20576
- const [error, setError] = React96.useState(null);
21551
+ const [unrenderable, setUnrenderable] = React96.useState(false);
20577
21552
  const reactId = React96.useId();
20578
21553
  React96.useEffect(() => {
20579
21554
  let active = true;
20580
21555
  void (async () => {
20581
- try {
20582
- const mermaid = await loadMermaid();
20583
- mermaid.initialize({
20584
- startOnLoad: false,
20585
- securityLevel: "strict",
20586
- theme: resolvedMode === "dark" ? "dark" : "default"
20587
- });
20588
- const domId = `mermaid-${reactId.replace(/[^a-zA-Z0-9]/g, "")}`;
20589
- const { svg } = await mermaid.render(domId, code);
20590
- if (!active || !containerRef.current) return;
20591
- containerRef.current.innerHTML = svg;
20592
- setError(null);
20593
- } catch (err) {
20594
- if (active) setError(err instanceof Error ? err.message : String(err));
21556
+ const mermaid = await loadMermaid();
21557
+ mermaid.initialize({
21558
+ startOnLoad: false,
21559
+ securityLevel: "strict",
21560
+ theme: resolvedMode === "dark" ? "dark" : "default"
21561
+ });
21562
+ const domId = `mermaid-${reactId.replace(/[^a-zA-Z0-9]/g, "")}`;
21563
+ let firstError = null;
21564
+ for (const [index, source] of [code, ...mermaidRepairCandidates(code)].entries()) {
21565
+ try {
21566
+ const { svg } = await mermaid.render(domId, source);
21567
+ if (!active) return;
21568
+ const container = containerRef.current;
21569
+ if (container === null) return;
21570
+ container.innerHTML = svg;
21571
+ container.dataset.mermaidRepaired = String(index > 0);
21572
+ setUnrenderable(false);
21573
+ if (index > 0) log5.debug("mermaid:repaired", { candidate: index });
21574
+ return;
21575
+ } catch (err) {
21576
+ firstError ?? (firstError = err instanceof Error ? err : new Error(String(err)));
21577
+ }
20595
21578
  }
21579
+ if (!active) return;
21580
+ log5.warn("mermaid:unrenderable", { error: firstError?.message ?? "", code });
21581
+ setUnrenderable(true);
20596
21582
  })();
20597
21583
  return () => {
20598
21584
  active = false;
20599
21585
  };
20600
21586
  }, [code, resolvedMode, reactId]);
20601
21587
  return /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: cn("not-prose my-4", className), children: [
20602
- error !== null && /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "space-y-2 mb-2", children: [
20603
- /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-error whitespace-pre-wrap", children: error }),
21588
+ unrenderable && /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "space-y-2 mb-2", "data-testid": "mermaid-unrenderable", children: [
21589
+ /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "caption", className: "text-muted-foreground", children: t("mermaid.unrenderable") }),
20604
21590
  /* @__PURE__ */ jsxRuntime.jsx(CodeBlock, { code, language: "mermaid" })
20605
21591
  ] }),
20606
21592
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -20609,7 +21595,7 @@ var init_MermaidDiagram = __esm({
20609
21595
  ref: containerRef,
20610
21596
  "data-testid": "mermaid-diagram",
20611
21597
  className: "overflow-x-auto",
20612
- style: error !== null ? { display: "none" } : void 0
21598
+ style: unrenderable ? { display: "none" } : void 0
20613
21599
  }
20614
21600
  )
20615
21601
  ] });
@@ -24014,14 +25000,14 @@ function useSafeEventBus2() {
24014
25000
  } };
24015
25001
  }
24016
25002
  }
24017
- var log5, lookStyles4, ButtonGroup;
25003
+ var log6, lookStyles4, ButtonGroup;
24018
25004
  var init_ButtonGroup = __esm({
24019
25005
  "components/core/molecules/ButtonGroup.tsx"() {
24020
25006
  "use client";
24021
25007
  init_cn();
24022
25008
  init_atoms();
24023
25009
  init_useEventBus();
24024
- log5 = logger.createLogger("almadar:ui:button-group");
25010
+ log6 = logger.createLogger("almadar:ui:button-group");
24025
25011
  lookStyles4 = {
24026
25012
  "right-aligned-buttons": "",
24027
25013
  "floating-bar": "fixed bottom-section left-1/2 -translate-x-1/2 shadow-elevation-toast bg-card p-card-sm rounded-container",
@@ -24102,7 +25088,7 @@ var init_ButtonGroup = __esm({
24102
25088
  {
24103
25089
  variant: "ghost",
24104
25090
  onClick: () => {
24105
- log5.debug("Filter clicked", { field: filter.field });
25091
+ log6.debug("Filter clicked", { field: filter.field });
24106
25092
  },
24107
25093
  children: filter.label
24108
25094
  },
@@ -26886,6 +27872,12 @@ function commandMatches(command, query) {
26886
27872
  if (matchesQuery(query, command.label)) return true;
26887
27873
  return (command.keywords ?? []).some((keyword) => matchesQuery(query, keyword));
26888
27874
  }
27875
+ function dispatchCommandPaletteCommand(command, deps) {
27876
+ if (command.disabled) return;
27877
+ if (command.event) deps.emit(`UI:${command.event}`, { commandId: command.id });
27878
+ if (command.action) deps.emit(`UI:${command.action}`, command.actionPayload ?? {});
27879
+ deps.onSelect?.(command);
27880
+ }
26889
27881
  var UNGROUPED, CommandPalette;
26890
27882
  var init_CommandPalette = __esm({
26891
27883
  "components/core/molecules/CommandPalette.tsx"() {
@@ -26940,9 +27932,7 @@ var init_CommandPalette = __esm({
26940
27932
  const handleSelect = React96.useCallback(
26941
27933
  (command) => {
26942
27934
  if (command.disabled) return;
26943
- if (command.event) eventBus.emit(`UI:${command.event}`, { commandId: command.id });
26944
- if (command.action) eventBus.emit(`UI:${command.action}`, command.actionPayload ?? {});
26945
- onSelect?.(command);
27935
+ dispatchCommandPaletteCommand(command, { emit: eventBus.emit, onSelect });
26946
27936
  handleClose();
26947
27937
  },
26948
27938
  [eventBus, onSelect, handleClose]
@@ -31921,9 +32911,9 @@ function debug(...args) {
31921
32911
  const [first, ...rest] = args;
31922
32912
  const message = typeof first === "string" ? first : "<debug>";
31923
32913
  if (rest.length === 0 && typeof first === "string") {
31924
- log6.debug(message);
32914
+ log7.debug(message);
31925
32915
  } else {
31926
- log6.debug(message, { args: rest.length > 0 ? formatArgs(rest) : formatArgs([first]) });
32916
+ log7.debug(message, { args: rest.length > 0 ? formatArgs(rest) : formatArgs([first]) });
31927
32917
  }
31928
32918
  }
31929
32919
  function debugGroup(label) {
@@ -31951,11 +32941,11 @@ function toLogMetaValue(v) {
31951
32941
  }
31952
32942
  return String(v);
31953
32943
  }
31954
- var NAMESPACE, log6;
32944
+ var NAMESPACE, log7;
31955
32945
  var init_debug = __esm({
31956
32946
  "lib/debug.ts"() {
31957
32947
  NAMESPACE = "almadar:ui:debug";
31958
- log6 = logger.createLogger(NAMESPACE);
32948
+ log7 = logger.createLogger(NAMESPACE);
31959
32949
  logger.createLogger("almadar:ui:debug:input");
31960
32950
  logger.createLogger("almadar:ui:debug:collision");
31961
32951
  logger.createLogger("almadar:ui:debug:physics");
@@ -33608,6 +34598,7 @@ var init_MathCanvas = __esm({
33608
34598
  init_perf();
33609
34599
  init_atoms();
33610
34600
  init_Stack();
34601
+ init_gameFonts();
33611
34602
  init_LearningCanvas();
33612
34603
  MathCanvas = ({
33613
34604
  className,
@@ -33627,6 +34618,7 @@ var init_MathCanvas = __esm({
33627
34618
  showTickLabels = false,
33628
34619
  tickLabelFontSize = 10,
33629
34620
  labelFontSize = 12,
34621
+ fontFamily: fontFamilyProp,
33630
34622
  showCurveLabels = false,
33631
34623
  curves = [],
33632
34624
  points = [],
@@ -33649,6 +34641,7 @@ var init_MathCanvas = __esm({
33649
34641
  error
33650
34642
  }) => {
33651
34643
  const eventBus = useEventBus();
34644
+ const fontFamily = resolveGameFontFamily(fontFamilyProp);
33652
34645
  const keyMapKey = keyMap ? JSON.stringify(keyMap) : null;
33653
34646
  const keyUpMapKey = keyUpMap ? JSON.stringify(keyUpMap) : null;
33654
34647
  const stableKeyMap = React96.useMemo(() => keyMap, [keyMapKey]);
@@ -33698,18 +34691,18 @@ var init_MathCanvas = __esm({
33698
34691
  let kx = 0;
33699
34692
  for (let x = Math.ceil(xMin / gridStep) * gridStep; x <= xMax; x += gridStep, kx++) {
33700
34693
  if (kx % labelEveryX === 0 && x !== 0) {
33701
- out.push({ type: "text", x: mapX(x), y: xAxisY + 12, text: formatTick(x), color: "#6b7280", fontSize: tickLabelFontSize, align: "center" });
34694
+ out.push({ type: "text", fontFamily, x: mapX(x), y: xAxisY + 12, text: formatTick(x), color: "#6b7280", fontSize: tickLabelFontSize, align: "center" });
33702
34695
  }
33703
34696
  }
33704
34697
  const labelEveryY = Math.max(1, Math.ceil((yMax - yMin) / gridStep / Math.floor(plotH / 28)));
33705
34698
  let ky = 0;
33706
34699
  for (let y = Math.ceil(yMin / gridStep) * gridStep; y <= yMax; y += gridStep, ky++) {
33707
34700
  if (ky % labelEveryY === 0 && y !== 0) {
33708
- out.push({ type: "text", x: yAxisX - 6, y: mapY(y), text: formatTick(y), color: "#6b7280", fontSize: tickLabelFontSize, align: "right" });
34701
+ out.push({ type: "text", fontFamily, x: yAxisX - 6, y: mapY(y), text: formatTick(y), color: "#6b7280", fontSize: tickLabelFontSize, align: "right" });
33709
34702
  }
33710
34703
  }
33711
34704
  if (xMin <= 0 && xMax >= 0 && yMin <= 0 && yMax >= 0) {
33712
- out.push({ type: "text", x: yAxisX - 6, y: xAxisY + 12, text: "0", color: "#6b7280", fontSize: tickLabelFontSize, align: "right" });
34705
+ out.push({ type: "text", fontFamily, x: yAxisX - 6, y: xAxisY + 12, text: "0", color: "#6b7280", fontSize: tickLabelFontSize, align: "right" });
33713
34706
  }
33714
34707
  }
33715
34708
  for (const region of regions) {
@@ -33736,6 +34729,7 @@ var init_MathCanvas = __esm({
33736
34729
  const mid = Math.floor(region.samples.length / 2);
33737
34730
  out.push({
33738
34731
  type: "text",
34732
+ fontFamily,
33739
34733
  x: mapX((first.x + last.x) / 2),
33740
34734
  y: (mapY(region.samples[mid].y) + mapY(baseline)) / 2,
33741
34735
  text: region.label,
@@ -33772,14 +34766,14 @@ var init_MathCanvas = __esm({
33772
34766
  const px = mapX(guide.at);
33773
34767
  out.push({ type: "line", x1: px, y1: margin, x2: px, y2: height - margin, color, dash });
33774
34768
  if (guide.label) {
33775
- out.push({ type: "text", x: px + 4, y: margin + 10, text: guide.label, color, fontSize: 11 });
34769
+ out.push({ type: "text", fontFamily, x: px + 4, y: margin + 10, text: guide.label, color, fontSize: 11 });
33776
34770
  }
33777
34771
  } else {
33778
34772
  if (guide.at < yMin || guide.at > yMax) continue;
33779
34773
  const py = mapY(guide.at);
33780
34774
  out.push({ type: "line", x1: margin, y1: py, x2: width - margin, y2: py, color, dash });
33781
34775
  if (guide.label) {
33782
- out.push({ type: "text", x: width - margin - 4, y: py - 8, text: guide.label, color, fontSize: labelFontSize, align: "right" });
34776
+ out.push({ type: "text", fontFamily, x: width - margin - 4, y: py - 8, text: guide.label, color, fontSize: labelFontSize, align: "right" });
33783
34777
  }
33784
34778
  }
33785
34779
  }
@@ -33818,6 +34812,7 @@ var init_MathCanvas = __esm({
33818
34812
  if (showCurveLabels && curve.label && lastInRange) {
33819
34813
  out.push({
33820
34814
  type: "text",
34815
+ fontFamily,
33821
34816
  x: mapX(lastInRange.x) + 6,
33822
34817
  y: mapY(lastInRange.y) - 6,
33823
34818
  text: curve.label,
@@ -33855,6 +34850,7 @@ var init_MathCanvas = __esm({
33855
34850
  if (hop.label) {
33856
34851
  out.push({
33857
34852
  type: "text",
34853
+ fontFamily,
33858
34854
  x: (x1 + x2) / 2,
33859
34855
  y: xAxisY - peak - 8,
33860
34856
  text: hop.label,
@@ -33882,6 +34878,7 @@ var init_MathCanvas = __esm({
33882
34878
  const rad = mid * Math.PI / 180;
33883
34879
  out.push({
33884
34880
  type: "text",
34881
+ fontFamily,
33885
34882
  x: mapX(angle.x + 1.35 * radius * Math.cos(rad)),
33886
34883
  y: mapY(angle.y + 1.35 * radius * Math.sin(rad)),
33887
34884
  text: angle.label,
@@ -33903,7 +34900,7 @@ var init_MathCanvas = __esm({
33903
34900
  fill: isOpen ? "#ffffff" : p.color ?? "#dc2626"
33904
34901
  });
33905
34902
  if (p.label) {
33906
- out.push({ type: "text", x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: p.color ?? "#111827", fontSize: labelFontSize });
34903
+ out.push({ type: "text", fontFamily, x: mapX(p.x) + 8, y: mapY(p.y) - 8, text: p.label, color: p.color ?? "#111827", fontSize: labelFontSize });
33907
34904
  }
33908
34905
  }
33909
34906
  for (const v of vectors) {
@@ -33914,7 +34911,7 @@ var init_MathCanvas = __esm({
33914
34911
  const y2 = mapY(v.y + v.vy);
33915
34912
  out.push({ type: "arrow", x1, y1, x2, y2, color: v.color ?? "#7c3aed", lineWidth: 2 });
33916
34913
  if (v.label) {
33917
- out.push({ type: "text", x: x2 + 6, y: y2 - 6, text: v.label, color: v.color ?? "#7c3aed", fontSize: labelFontSize });
34914
+ out.push({ type: "text", fontFamily, x: x2 + 6, y: y2 - 6, text: v.label, color: v.color ?? "#7c3aed", fontSize: labelFontSize });
33918
34915
  }
33919
34916
  }
33920
34917
  out.push(...shapes);
@@ -33935,6 +34932,7 @@ var init_MathCanvas = __esm({
33935
34932
  showTickLabels,
33936
34933
  tickLabelFontSize,
33937
34934
  labelFontSize,
34935
+ fontFamily,
33938
34936
  showCurveLabels,
33939
34937
  curves,
33940
34938
  points,
@@ -33990,6 +34988,7 @@ var init_MathCanvas = __esm({
33990
34988
  width,
33991
34989
  height,
33992
34990
  backgroundColor,
34991
+ fontFamily,
33993
34992
  shapes: derivedShapes,
33994
34993
  drawables,
33995
34994
  projector,
@@ -35222,13 +36221,13 @@ var init_MapView = __esm({
35222
36221
  shadowSize: [41, 41]
35223
36222
  });
35224
36223
  L.Marker.prototype.options.icon = defaultIcon;
35225
- const { useEffect: useEffect68, useRef: useRef67, useCallback: useCallback99, useState: useState107 } = React96__namespace.default;
36224
+ const { useEffect: useEffect70, useRef: useRef70, useCallback: useCallback100, useState: useState109 } = React96__namespace.default;
35226
36225
  const { Typography: Typography2 } = await Promise.resolve().then(() => (init_Typography(), Typography_exports));
35227
36226
  const { useEventBus: useEventBus2 } = await Promise.resolve().then(() => (init_useEventBus(), useEventBus_exports));
35228
36227
  function MapUpdater({ centerLat, centerLng, zoom }) {
35229
36228
  const map = useMap();
35230
- const prevRef = useRef67({ centerLat, centerLng, zoom });
35231
- useEffect68(() => {
36229
+ const prevRef = useRef70({ centerLat, centerLng, zoom });
36230
+ useEffect70(() => {
35232
36231
  const prev = prevRef.current;
35233
36232
  if (prev.centerLat !== centerLat || prev.centerLng !== centerLng || prev.zoom !== zoom) {
35234
36233
  map.setView([centerLat, centerLng], zoom);
@@ -35239,7 +36238,7 @@ var init_MapView = __esm({
35239
36238
  }
35240
36239
  function MapClickHandler({ onMapClick }) {
35241
36240
  const map = useMap();
35242
- useEffect68(() => {
36241
+ useEffect70(() => {
35243
36242
  if (!onMapClick) return;
35244
36243
  const handler = (e) => {
35245
36244
  onMapClick(e.latlng.lat, e.latlng.lng);
@@ -35267,8 +36266,8 @@ var init_MapView = __esm({
35267
36266
  showAttribution = true
35268
36267
  }) {
35269
36268
  const eventBus = useEventBus2();
35270
- const [clickedPosition, setClickedPosition] = useState107(null);
35271
- const handleMapClick = useCallback99((lat, lng) => {
36269
+ const [clickedPosition, setClickedPosition] = useState109(null);
36270
+ const handleMapClick = useCallback100((lat, lng) => {
35272
36271
  if (showClickedPin) {
35273
36272
  setClickedPosition({ lat, lng });
35274
36273
  }
@@ -35277,7 +36276,7 @@ var init_MapView = __esm({
35277
36276
  eventBus.emit(`UI:${mapClickEvent}`, { latitude: lat, longitude: lng });
35278
36277
  }
35279
36278
  }, [onMapClick, mapClickEvent, eventBus, showClickedPin]);
35280
- const handleMarkerClick = useCallback99((marker) => {
36279
+ const handleMarkerClick = useCallback100((marker) => {
35281
36280
  onMarkerClick?.(marker);
35282
36281
  if (markerClickEvent) {
35283
36282
  eventBus.emit(`UI:${markerClickEvent}`, { ...marker });
@@ -49586,7 +50585,7 @@ function getAllEvents(traits2) {
49586
50585
  function EventDispatcherTab({ traits: traits2, schema }) {
49587
50586
  const eventBus = useEventBus();
49588
50587
  const { t } = hooks.useTranslate();
49589
- const [log11, setLog] = React96__namespace.useState([]);
50588
+ const [log12, setLog] = React96__namespace.useState([]);
49590
50589
  const prevStatesRef = React96__namespace.useRef(/* @__PURE__ */ new Map());
49591
50590
  React96__namespace.useEffect(() => {
49592
50591
  for (const trait of traits2) {
@@ -49650,9 +50649,9 @@ function EventDispatcherTab({ traits: traits2, schema }) {
49650
50649
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", weight: "medium", className: "text-muted-foreground mb-1", children: t("debug.otherEvents") }),
49651
50650
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1", children: unavailableEvents.map((event) => /* @__PURE__ */ jsxRuntime.jsx(Badge, { variant: "default", size: "sm", className: "opacity-50", children: event }, event)) })
49652
50651
  ] }),
49653
- log11.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
50652
+ log12.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
49654
50653
  /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "small", weight: "medium", className: "text-muted-foreground mb-1", children: t("debug.recentTransitions") }),
49655
- /* @__PURE__ */ jsxRuntime.jsx(Stack, { gap: "xs", children: log11.map((entry, i) => /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", className: "font-mono text-xs", children: [
50654
+ /* @__PURE__ */ jsxRuntime.jsx(Stack, { gap: "xs", children: log12.map((entry, i) => /* @__PURE__ */ jsxRuntime.jsxs(Typography, { variant: "small", className: "font-mono text-xs", children: [
49656
50655
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-primary", children: entry.traitName }),
49657
50656
  " ",
49658
50657
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: entry.from }),
@@ -51843,6 +52842,7 @@ var init_component_registry_generated = __esm({
51843
52842
  init_FormSection();
51844
52843
  init_FormSectionHeader();
51845
52844
  init_FxOverlay();
52845
+ init_GameAudioCue();
51846
52846
  init_GameAudioToggle();
51847
52847
  init_GameHud();
51848
52848
  init_GameIcon();
@@ -52120,6 +53120,7 @@ var init_component_registry_generated = __esm({
52120
53120
  "FormLayout": FormLayout,
52121
53121
  "FormSectionHeader": FormSectionHeader,
52122
53122
  "FxOverlay": FxOverlay,
53123
+ "GameAudioCue": GameAudioCue,
52123
53124
  "GameAudioToggle": GameAudioToggle,
52124
53125
  "GameHud": GameHud,
52125
53126
  "GameIcon": GameIcon,
@@ -52568,7 +53569,9 @@ function UISlotComponentInner({
52568
53569
  className,
52569
53570
  children,
52570
53571
  pattern,
52571
- sourceTrait
53572
+ sourceTrait,
53573
+ fallback,
53574
+ mode = "replace"
52572
53575
  }) {
52573
53576
  const { slots, clear } = context.useUISlots();
52574
53577
  const eventBus = useEventBus();
@@ -52620,12 +53623,26 @@ function UISlotComponentInner({
52620
53623
  );
52621
53624
  }
52622
53625
  if (!content) {
53626
+ if (fallback !== void 0) {
53627
+ return /* @__PURE__ */ jsxRuntime.jsx(
53628
+ Box,
53629
+ {
53630
+ id: `slot-${slot}`,
53631
+ className: cn("ui-slot", `ui-slot-${slot}`, className),
53632
+ "data-testid": `ui-slot-${slot}`,
53633
+ "data-slot-mode": "fallback",
53634
+ children: fallback
53635
+ }
53636
+ );
53637
+ }
52623
53638
  if (!portal) {
52624
53639
  return /* @__PURE__ */ jsxRuntime.jsx(
52625
53640
  Box,
52626
53641
  {
52627
53642
  id: `slot-${slot}`,
52628
- className: cn("ui-slot", `ui-slot-${slot}`, className)
53643
+ className: cn("ui-slot", `ui-slot-${slot}`, className),
53644
+ "data-testid": `ui-slot-${slot}`,
53645
+ "data-slot-mode": "empty"
52629
53646
  }
52630
53647
  );
52631
53648
  }
@@ -52642,29 +53659,51 @@ function UISlotComponentInner({
52642
53659
  clear(slot);
52643
53660
  };
52644
53661
  if (portal) {
52645
- if (contained) {
52646
- return renderContainedPortal(t, slot, content, handleDismiss);
52647
- }
52648
- return /* @__PURE__ */ jsxRuntime.jsx(
52649
- SlotPortal,
53662
+ const inlineFallback = mode === "append" && fallback !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx(
53663
+ Box,
52650
53664
  {
52651
- slot,
52652
- content,
52653
- position,
52654
- onDismiss: handleDismiss
53665
+ id: `slot-${slot}-fallback`,
53666
+ className: cn("ui-slot", `ui-slot-${slot}-fallback`, className),
53667
+ "data-testid": `ui-slot-${slot}-fallback`,
53668
+ "data-slot-mode": "append",
53669
+ children: fallback
52655
53670
  }
52656
- );
53671
+ ) : null;
53672
+ if (contained) {
53673
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
53674
+ inlineFallback,
53675
+ renderContainedPortal(t, slot, content, handleDismiss)
53676
+ ] });
53677
+ }
53678
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
53679
+ inlineFallback,
53680
+ /* @__PURE__ */ jsxRuntime.jsx(
53681
+ SlotPortal,
53682
+ {
53683
+ slot,
53684
+ content,
53685
+ position,
53686
+ onDismiss: handleDismiss
53687
+ }
53688
+ )
53689
+ ] });
52657
53690
  }
52658
53691
  const slotContent = /* @__PURE__ */ jsxRuntime.jsx(SlotContentRenderer, { content, onDismiss: handleDismiss });
52659
53692
  const wrappedContent = suspenseConfig.enabled ? /* @__PURE__ */ jsxRuntime.jsx(ErrorBoundary, { children: /* @__PURE__ */ jsxRuntime.jsx(React96.Suspense, { fallback: getSlotFallback(slot, suspenseConfig), children: slotContent }) }) : /* @__PURE__ */ jsxRuntime.jsx(ErrorBoundary, { children: slotContent });
52660
- return /* @__PURE__ */ jsxRuntime.jsx(
53693
+ const showFallback = mode === "append" && fallback !== void 0;
53694
+ return /* @__PURE__ */ jsxRuntime.jsxs(
52661
53695
  Box,
52662
53696
  {
52663
53697
  id: `slot-${slot}`,
52664
53698
  className: cn("ui-slot", `ui-slot-${slot}`, className),
52665
53699
  "data-pattern": content.pattern,
52666
53700
  "data-source-trait": content.sourceTrait,
52667
- children: /* @__PURE__ */ jsxRuntime.jsx(MaybeTraitScope, { sourceTrait: content.sourceTrait, children: wrappedContent })
53701
+ "data-testid": `ui-slot-${slot}`,
53702
+ "data-slot-mode": showFallback ? "append" : "content",
53703
+ children: [
53704
+ showFallback ? fallback : null,
53705
+ /* @__PURE__ */ jsxRuntime.jsx(MaybeTraitScope, { sourceTrait: content.sourceTrait, children: wrappedContent })
53706
+ ]
52668
53707
  }
52669
53708
  );
52670
53709
  }
@@ -56916,7 +57955,7 @@ function convertFnFormLambdasInProps(props) {
56916
57955
 
56917
57956
  // hooks/index.ts
56918
57957
  init_useEventBus();
56919
- var log7 = logger.createLogger("almadar:ui:shared-entity-store");
57958
+ var log8 = logger.createLogger("almadar:ui:shared-entity-store");
56920
57959
  var EMPTY_ENTITY_STATE = {};
56921
57960
  function createSharedEntityStore() {
56922
57961
  const states = /* @__PURE__ */ new Map();
@@ -56946,7 +57985,7 @@ function createSharedEntityStore() {
56946
57985
  try {
56947
57986
  callback();
56948
57987
  } catch (error) {
56949
- log7.error("Shared entity subscriber error", {
57988
+ log8.error("Shared entity subscriber error", {
56950
57989
  entityId,
56951
57990
  error: error instanceof Error ? error : String(error)
56952
57991
  });
@@ -56977,7 +58016,7 @@ function runTickFrame(entityId, orderedWriters, store) {
56977
58016
  store.commit(entityId, scratch);
56978
58017
  return scratch;
56979
58018
  }
56980
- var log8 = logger.createLogger("almadar:ui:effects:client-handlers");
58019
+ var log9 = logger.createLogger("almadar:ui:effects:client-handlers");
56981
58020
  function createClientEffectHandlers(options) {
56982
58021
  const { eventBus, slotSetter, navigate, navigateBack, notify, callService, liveEntity } = options;
56983
58022
  return {
@@ -56986,12 +58025,12 @@ function createClientEffectHandlers(options) {
56986
58025
  eventBus.emit(prefixedEvent, payload, source);
56987
58026
  },
56988
58027
  persist: async () => {
56989
- log8.warn("persist is server-side only, ignored on client");
58028
+ log9.warn("persist is server-side only, ignored on client");
56990
58029
  },
56991
58030
  // @almadar/runtime EffectHandlers.set types value:unknown — should be FieldValue (upstream fix queued)
56992
58031
  set: ((_entityId, field, value) => {
56993
58032
  if (!liveEntity) {
56994
- log8.warn("set is server-side only, ignored on client (no live entity)");
58033
+ log9.warn("set is server-side only, ignored on client (no live entity)");
56995
58034
  return;
56996
58035
  }
56997
58036
  liveEntity[field] = value;
@@ -57027,13 +58066,13 @@ function createClientEffectHandlers(options) {
57027
58066
  window.location.href = path;
57028
58067
  return;
57029
58068
  }
57030
- log8.warn("No navigate handler, ignoring", { path });
58069
+ log9.warn("No navigate handler, ignoring", { path });
57031
58070
  }),
57032
58071
  navigateBack: navigateBack ?? (() => {
57033
- log8.warn("No navigate-back handler, ignoring");
58072
+ log9.warn("No navigate-back handler, ignoring");
57034
58073
  }),
57035
58074
  notify: notify ?? ((msg, type) => {
57036
- log8.debug("notify", { type, message: msg });
58075
+ log9.debug("notify", { type, message: msg });
57037
58076
  })
57038
58077
  };
57039
58078
  }
@@ -57468,7 +58507,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
57468
58507
  };
57469
58508
  }, [traitBindings]);
57470
58509
  const executeTransitionEffects = React96.useCallback(async (params) => {
57471
- const { binding, previousState, newState, payload, flushEvent, syncOnly, log: log11 } = params;
58510
+ const { binding, previousState, newState, payload, flushEvent, syncOnly, log: log12 } = params;
57472
58511
  const traitName = binding.trait.name;
57473
58512
  const linkedEntity = binding.linkedEntity || "";
57474
58513
  const entityId = payload?.entityId;
@@ -57597,7 +58636,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
57597
58636
  if (sharedKey !== void 0) {
57598
58637
  sharedWrites.push({ field, value });
57599
58638
  }
57600
- log11.debug("set:write", {
58639
+ log12.debug("set:write", {
57601
58640
  traitName,
57602
58641
  field,
57603
58642
  value: JSON.stringify(value),
@@ -57665,7 +58704,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
57665
58704
  core.mergeEntityFrame(sharedEntityStore.getSnapshot(sharedKey), sharedWrites)
57666
58705
  );
57667
58706
  }
57668
- log11.debug("effects:executed", () => ({
58707
+ log12.debug("effects:executed", () => ({
57669
58708
  traitName,
57670
58709
  transition: `${previousState}->${newState}`,
57671
58710
  event: flushEvent,
@@ -57675,7 +58714,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
57675
58714
  slotsTouched: Array.from(pendingSlots.keys()).join(",")
57676
58715
  }));
57677
58716
  for (const [slot, patterns] of pendingSlots) {
57678
- log11.debug("flush:slot", {
58717
+ log12.debug("flush:slot", {
57679
58718
  traitName,
57680
58719
  slot,
57681
58720
  patternCount: patterns.length,
@@ -57693,7 +58732,7 @@ function useTraitStateMachine(traitBindings, uiSlots, options) {
57693
58732
  publishBindingSnapshot(traitName, liveEntity);
57694
58733
  }
57695
58734
  } catch (error) {
57696
- log11.error("effects:error", {
58735
+ log12.error("effects:error", {
57697
58736
  traitName,
57698
58737
  transition: `${previousState}->${newState}`,
57699
58738
  event: flushEvent,
@@ -58772,11 +59811,20 @@ function BrowserPlayground({
58772
59811
  mode = "mock",
58773
59812
  initialPagePath,
58774
59813
  height,
58775
- className
59814
+ className,
59815
+ paused
58776
59816
  }) {
58777
59817
  const [runtime] = React96.useState(
58778
59818
  () => new OrbitalServerRuntime.OrbitalServerRuntime({ mode, debug: false })
58779
59819
  );
59820
+ React96.useEffect(() => {
59821
+ if (paused === void 0) return;
59822
+ if (paused) {
59823
+ runtime.pauseTicks();
59824
+ } else {
59825
+ runtime.resumeTicks();
59826
+ }
59827
+ }, [runtime, paused]);
58780
59828
  const registrationReady = React96.useMemo(() => {
58781
59829
  const orbitalNames = schema.orbitals.map((o) => o.name);
58782
59830
  playgroundLog.debug("register:start", { schema: schema.name, orbitalNames });
@@ -58837,7 +59885,7 @@ init_useEventBus();
58837
59885
  // components/avl/hooks/useCanvasDnd.tsx
58838
59886
  init_useEventBus();
58839
59887
  init_useAlmadarDndCollision();
58840
- var log9 = logger.createLogger("almadar:ui:canvas-dnd");
59888
+ var log10 = logger.createLogger("almadar:ui:canvas-dnd");
58841
59889
  function useCanvasDraggable({
58842
59890
  id,
58843
59891
  payload,
@@ -58875,7 +59923,7 @@ function defaultEmit(eventBus, drop) {
58875
59923
  if (payload.kind === "pattern") {
58876
59924
  const patternType = payload.data["type"];
58877
59925
  if (typeof patternType !== "string") {
58878
- log9.warn("default-emit:pattern:missing-type");
59926
+ log10.warn("default-emit:pattern:missing-type");
58879
59927
  return;
58880
59928
  }
58881
59929
  const out = { patternType, containerNode: target.containerNode };
@@ -58884,26 +59932,26 @@ function defaultEmit(eventBus, drop) {
58884
59932
  out.index = resolved.index;
58885
59933
  }
58886
59934
  eventBus.emit("UI:PATTERN_DROP", out);
58887
- log9.info("default-emit:pattern", { patternType, level: target.level });
59935
+ log10.info("default-emit:pattern", { patternType, level: target.level });
58888
59936
  return;
58889
59937
  }
58890
59938
  if (payload.kind === "behavior") {
58891
59939
  const behaviorName = payload.data["name"];
58892
59940
  if (typeof behaviorName !== "string") {
58893
- log9.warn("default-emit:behavior:missing-name");
59941
+ log10.warn("default-emit:behavior:missing-name");
58894
59942
  return;
58895
59943
  }
58896
59944
  eventBus.emit("UI:BEHAVIOR_DROP", {
58897
59945
  behaviorName,
58898
59946
  containerNode: target.containerNode
58899
59947
  });
58900
- log9.info("default-emit:behavior", { behaviorName, level: target.level });
59948
+ log10.info("default-emit:behavior", { behaviorName, level: target.level });
58901
59949
  return;
58902
59950
  }
58903
59951
  if (payload.kind === "pattern-instance") {
58904
59952
  const fromPath = payload.data["fromPath"];
58905
59953
  if (typeof fromPath !== "string") {
58906
- log9.warn("default-emit:pattern-instance:missing-fromPath");
59954
+ log10.warn("default-emit:pattern-instance:missing-fromPath");
58907
59955
  return;
58908
59956
  }
58909
59957
  const out = { fromPath, loc: payload.data["loc"] };
@@ -58912,10 +59960,10 @@ function defaultEmit(eventBus, drop) {
58912
59960
  out.toIndex = resolved.index;
58913
59961
  }
58914
59962
  eventBus.emit("UI:PATTERN_MOVE", out);
58915
- log9.info("default-emit:pattern-instance", { fromPath, level: target.level });
59963
+ log10.info("default-emit:pattern-instance", { fromPath, level: target.level });
58916
59964
  return;
58917
59965
  }
58918
- log9.debug("default-emit:unhandled-kind", { kind: payload.kind });
59966
+ log10.debug("default-emit:unhandled-kind", { kind: payload.kind });
58919
59967
  }
58920
59968
  function CanvasDndProvider({
58921
59969
  children,
@@ -58938,9 +59986,9 @@ function CanvasDndProvider({
58938
59986
  if (payload) {
58939
59987
  setActivePayload(payload);
58940
59988
  eventBus.emit("UI:DRAG_START", { kind: payload.kind, data: payload.data });
58941
- log9.info("dragStart", { id: e.active.id, kind: payload.kind });
59989
+ log10.info("dragStart", { id: e.active.id, kind: payload.kind });
58942
59990
  } else {
58943
- log9.warn("dragStart:missing-payload", { id: e.active.id });
59991
+ log10.warn("dragStart:missing-payload", { id: e.active.id });
58944
59992
  }
58945
59993
  }, [eventBus, trackPointer]);
58946
59994
  const handleDragEnd = React96__namespace.default.useCallback((e) => {
@@ -58951,7 +59999,7 @@ function CanvasDndProvider({
58951
59999
  const overData = e.over?.data.current;
58952
60000
  const target = overData?.target;
58953
60001
  const accepts = overData?.accepts;
58954
- log9.info("dragEnd", {
60002
+ log10.info("dragEnd", {
58955
60003
  activeId: e.active.id,
58956
60004
  overId: e.over?.id,
58957
60005
  hasPayload: !!payload,
@@ -58963,12 +60011,12 @@ function CanvasDndProvider({
58963
60011
  }
58964
60012
  if (!payload || !target) return;
58965
60013
  if (accepts && !accepts.includes(payload.kind)) {
58966
- log9.debug("dragEnd:rejected:kind", { kind: payload.kind, accepts: [...accepts] });
60014
+ log10.debug("dragEnd:rejected:kind", { kind: payload.kind, accepts: [...accepts] });
58967
60015
  return;
58968
60016
  }
58969
60017
  const cursor = lastPointerRef.current;
58970
60018
  const resolved = target.resolvePath && cursor ? target.resolvePath(cursor) : null;
58971
- log9.debug("dragEnd:resolve", {
60019
+ log10.debug("dragEnd:resolve", {
58972
60020
  hasResolver: !!target.resolvePath,
58973
60021
  cursorX: cursor?.x,
58974
60022
  cursorY: cursor?.y,
@@ -58982,7 +60030,7 @@ function CanvasDndProvider({
58982
60030
  const handleDragCancel = React96__namespace.default.useCallback(() => {
58983
60031
  setActivePayload(null);
58984
60032
  document.removeEventListener("pointermove", trackPointer);
58985
- log9.info("dragCancel");
60033
+ log10.info("dragCancel");
58986
60034
  }, [trackPointer]);
58987
60035
  return /* @__PURE__ */ jsxRuntime.jsxs(
58988
60036
  core$1.DndContext,
@@ -61042,7 +62090,7 @@ init_AvlTransitionLane();
61042
62090
  init_AvlSwimLane();
61043
62091
  init_avl_atom_types();
61044
62092
  init_avl_elk_layout();
61045
- var log10 = logger.createLogger("almadar:ui:avl:trait-scene");
62093
+ var log11 = logger.createLogger("almadar:ui:avl:trait-scene");
61046
62094
  var SWIM_GUTTER2 = 120;
61047
62095
  var CENTER_W2 = 360;
61048
62096
  var AvlTraitScene = ({
@@ -61055,7 +62103,7 @@ var AvlTraitScene = ({
61055
62103
  const dataKey = React96.useMemo(() => JSON.stringify(data), [data]);
61056
62104
  React96.useEffect(() => {
61057
62105
  computeTraitLayout(data).then(setLayout).catch((error) => {
61058
- log10.error("computeTraitLayout failed", { error: error instanceof Error ? error : String(error) });
62106
+ log11.error("computeTraitLayout failed", { error: error instanceof Error ? error : String(error) });
61059
62107
  });
61060
62108
  }, [dataKey]);
61061
62109
  if (!layout) {