@almadar/ui 5.94.1 → 5.95.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.
@@ -0,0 +1,58 @@
1
+ /**
2
+ * AlgorithmCanvas
3
+ *
4
+ * A field-scoped learning molecule for computer-science algorithm visualizations.
5
+ * Projects semantic `bars` (sorting/histograms), `cells` (grids/arrays/DP tables),
6
+ * and `pointers` (index cursors) into pixel-space shapes on the declarative
7
+ * `LearningCanvas` atom, so a `.lolo` behavior never computes pixel coordinates —
8
+ * it just sets `bars`/`cells`/`pointers` and lets this molecule lay them out.
9
+ * Graph/tree algorithms reuse `BiologyCanvas` (nodes + edges); this canvas owns
10
+ * only the bar/cell/pointer vocabulary.
11
+ *
12
+ * @packageDocumentation
13
+ */
14
+ import * as React from 'react';
15
+ import type { LearningShape } from '../atoms/LearningCanvas';
16
+ import type { UiError } from '../../core/atoms/types';
17
+ export interface AlgorithmBar {
18
+ value: number;
19
+ color?: string;
20
+ label?: string;
21
+ }
22
+ export interface AlgorithmCell {
23
+ row: number;
24
+ col: number;
25
+ value?: number;
26
+ color?: string;
27
+ label?: string;
28
+ }
29
+ export interface AlgorithmPointer {
30
+ index: number;
31
+ label?: string;
32
+ color?: string;
33
+ }
34
+ export interface AlgorithmCanvasProps {
35
+ className?: string;
36
+ width?: number;
37
+ height?: number;
38
+ title?: string;
39
+ backgroundColor?: string;
40
+ /** Sorting/histogram bars; laid out left-to-right, height proportional to value. */
41
+ bars?: AlgorithmBar[];
42
+ /** Grid/array/DP cells; laid out on a row/col lattice sized to the extent. */
43
+ cells?: AlgorithmCell[];
44
+ /** Index cursors (i/j/lo/hi/mid); drawn as markers beneath the referenced bar/cell column. */
45
+ pointers?: AlgorithmPointer[];
46
+ /** Extra declarative shapes in canvas pixel coordinates. */
47
+ shapes?: LearningShape[];
48
+ interactive?: boolean;
49
+ animate?: boolean;
50
+ onShapeClick?: (payload: {
51
+ id?: string;
52
+ type?: string;
53
+ index: number;
54
+ }) => void;
55
+ isLoading?: boolean;
56
+ error?: UiError | null;
57
+ }
58
+ export declare const AlgorithmCanvas: React.FC<AlgorithmCanvasProps>;
@@ -9661,6 +9661,76 @@ var init_StateGraph = __esm({
9661
9661
  init_TransitionArrow();
9662
9662
  }
9663
9663
  });
9664
+
9665
+ // components/game/shared/atlasSlice.ts
9666
+ function isTilesheet(a) {
9667
+ return typeof a.tileWidth === "number";
9668
+ }
9669
+ function getAtlas(url, onReady) {
9670
+ if (atlasCache.has(url)) return atlasCache.get(url) ?? void 0;
9671
+ atlasCache.set(url, void 0);
9672
+ fetch(url).then((r) => r.json()).then((json) => {
9673
+ atlasCache.set(url, json);
9674
+ onReady();
9675
+ }).catch(() => {
9676
+ atlasCache.set(url, null);
9677
+ });
9678
+ return void 0;
9679
+ }
9680
+ function subRectFor(atlas, sprite) {
9681
+ if (isTilesheet(atlas)) {
9682
+ let col;
9683
+ let row;
9684
+ if (sprite.includes(",")) {
9685
+ const [c, r] = sprite.split(",").map((n) => Number(n.trim()));
9686
+ col = c;
9687
+ row = r;
9688
+ } else {
9689
+ const i = Number(sprite);
9690
+ if (!Number.isFinite(i)) return null;
9691
+ col = i % atlas.columns;
9692
+ row = Math.floor(i / atlas.columns);
9693
+ }
9694
+ if (!Number.isFinite(col) || !Number.isFinite(row) || col < 0 || row < 0 || col >= atlas.columns || row >= atlas.rows) return null;
9695
+ const margin = atlas.margin ?? 0;
9696
+ const spacing = atlas.spacing ?? 0;
9697
+ return {
9698
+ sx: margin + col * (atlas.tileWidth + spacing),
9699
+ sy: margin + row * (atlas.tileHeight + spacing),
9700
+ sw: atlas.tileWidth,
9701
+ sh: atlas.tileHeight
9702
+ };
9703
+ }
9704
+ const st = atlas.subTextures[sprite];
9705
+ if (!st) return null;
9706
+ return { sx: st.x, sy: st.y, sw: st.width, sh: st.height };
9707
+ }
9708
+ function isAtlasAsset(asset) {
9709
+ return !!asset && typeof asset.atlas === "string" && typeof asset.sprite === "string";
9710
+ }
9711
+ function resolveAssetSource(img, asset, onReady) {
9712
+ if (isAtlasAsset(asset)) {
9713
+ const atlas = getAtlas(asset.atlas, onReady);
9714
+ if (!atlas) return null;
9715
+ const rect = subRectFor(atlas, asset.sprite);
9716
+ if (!rect) return null;
9717
+ return { img, rect, aspect: rect.sw / rect.sh };
9718
+ }
9719
+ const natW = img.naturalWidth || 1;
9720
+ const natH = img.naturalHeight || 1;
9721
+ return { img, rect: null, aspect: natW / natH };
9722
+ }
9723
+ function blit(ctx, src, dx, dy, dw, dh) {
9724
+ if (src.rect) ctx.drawImage(src.img, src.rect.sx, src.rect.sy, src.rect.sw, src.rect.sh, dx, dy, dw, dh);
9725
+ else ctx.drawImage(src.img, dx, dy, dw, dh);
9726
+ }
9727
+ var atlasCache;
9728
+ var init_atlasSlice = __esm({
9729
+ "components/game/shared/atlasSlice.ts"() {
9730
+ "use client";
9731
+ atlasCache = /* @__PURE__ */ new Map();
9732
+ }
9733
+ });
9664
9734
  function useCamera() {
9665
9735
  const cameraRef = React97.useRef({ x: 0, y: 0, zoom: 1 });
9666
9736
  const targetCameraRef = React97.useRef(null);
@@ -10232,15 +10302,18 @@ function SideView({
10232
10302
  const platType = plat.type ?? "ground";
10233
10303
  const spriteAsset = tSprites?.[platType];
10234
10304
  const tileImg = spriteAsset ? loadImage(spriteAsset.url) : null;
10235
- if (tileImg) {
10236
- const tileW = tileImg.naturalWidth;
10237
- const tileH = tileImg.naturalHeight;
10305
+ const tileSrc = tileImg ? resolveAssetSource(tileImg, spriteAsset, NOOP) : null;
10306
+ if (tileSrc) {
10307
+ const originX = tileSrc.rect ? tileSrc.rect.sx : 0;
10308
+ const originY = tileSrc.rect ? tileSrc.rect.sy : 0;
10309
+ const tileW = tileSrc.rect ? tileSrc.rect.sw : tileImg.naturalWidth;
10310
+ const tileH = tileSrc.rect ? tileSrc.rect.sh : tileImg.naturalHeight;
10238
10311
  const scaleH = plat.height / tileH;
10239
10312
  const scaledW = tileW * scaleH;
10240
10313
  for (let tx = 0; tx < plat.width; tx += scaledW) {
10241
10314
  const drawW = Math.min(scaledW, plat.width - tx);
10242
10315
  const srcW = drawW / scaleH;
10243
- ctx.drawImage(tileImg, 0, 0, srcW, tileH, platX + tx, platY, drawW, plat.height);
10316
+ ctx.drawImage(tileImg, originX, originY, srcW, tileH, platX + tx, platY, drawW, plat.height);
10244
10317
  }
10245
10318
  } else {
10246
10319
  const color = PLATFORM_COLORS[platType] ?? PLATFORM_COLORS.ground;
@@ -10274,14 +10347,15 @@ function SideView({
10274
10347
  const ppy = py - camY;
10275
10348
  const facingRight = auth.facingRight ?? true;
10276
10349
  const playerImg = pSprite ? loadImage(pSprite.url) : null;
10277
- if (playerImg) {
10350
+ const playerSrc = playerImg ? resolveAssetSource(playerImg, pSprite, NOOP) : null;
10351
+ if (playerSrc) {
10278
10352
  ctx.save();
10279
10353
  if (!facingRight) {
10280
10354
  ctx.translate(ppx + pw, ppy);
10281
10355
  ctx.scale(-1, 1);
10282
- ctx.drawImage(playerImg, 0, 0, pw, ph);
10356
+ blit(ctx, playerSrc, 0, 0, pw, ph);
10283
10357
  } else {
10284
- ctx.drawImage(playerImg, ppx, ppy, pw, ph);
10358
+ blit(ctx, playerSrc, ppx, ppy, pw, ph);
10285
10359
  }
10286
10360
  ctx.restore();
10287
10361
  } else {
@@ -10482,10 +10556,11 @@ function Canvas2D({
10482
10556
  const attackTargetSet = React97.useMemo(() => new Set(attackTargets.map((p) => `${p.x},${p.y}`)), [attackTargets]);
10483
10557
  const spriteUrls = React97.useMemo(() => {
10484
10558
  const urls = [];
10559
+ const toUrl = (x) => typeof x === "string" ? x : x?.url;
10485
10560
  for (const tile of sortedTiles) {
10486
10561
  if (tile.terrainSprite) urls.push(tile.terrainSprite.url);
10487
10562
  else if (getTerrainSprite) {
10488
- const url = getTerrainSprite(tile.terrain ?? "");
10563
+ const url = toUrl(getTerrainSprite(tile.terrain ?? ""));
10489
10564
  if (url) urls.push(url);
10490
10565
  } else {
10491
10566
  const url = assetManifest?.terrains?.[tile.terrain ?? ""]?.url;
@@ -10495,7 +10570,7 @@ function Canvas2D({
10495
10570
  for (const feature of features) {
10496
10571
  if (feature.sprite) urls.push(feature.sprite.url);
10497
10572
  else if (getFeatureSprite) {
10498
- const url = getFeatureSprite(feature.type);
10573
+ const url = toUrl(getFeatureSprite(feature.type));
10499
10574
  if (url) urls.push(url);
10500
10575
  } else {
10501
10576
  const url = assetManifest?.features?.[feature.type]?.url;
@@ -10505,7 +10580,7 @@ function Canvas2D({
10505
10580
  for (const unit of units) {
10506
10581
  if (unit.sprite) urls.push(unit.sprite.url);
10507
10582
  else if (getUnitSprite) {
10508
- const url = getUnitSprite(unit);
10583
+ const url = toUrl(getUnitSprite(unit));
10509
10584
  if (url) urls.push(url);
10510
10585
  } else if (unit.unitType) {
10511
10586
  const url = assetManifest?.units?.[unit.unitType]?.url;
@@ -10546,14 +10621,25 @@ function Canvas2D({
10546
10621
  screenToWorld,
10547
10622
  lerpToTarget
10548
10623
  } = useCamera();
10549
- const resolveTerrainSpriteUrl = React97.useCallback((tile) => {
10550
- return tile.terrainSprite?.url || getTerrainSprite?.(tile.terrain ?? "") || assetManifest?.terrains?.[tile.terrain ?? ""]?.url;
10624
+ const [, setAtlasVersion] = React97.useState(0);
10625
+ const bumpAtlas = React97.useCallback(() => setAtlasVersion((v) => v + 1), []);
10626
+ const resolveTerrainAsset = React97.useCallback((tile) => {
10627
+ if (tile.terrainSprite) return tile.terrainSprite;
10628
+ const s = getTerrainSprite?.(tile.terrain ?? "");
10629
+ if (s) return typeof s === "string" ? { url: s } : s;
10630
+ return assetManifest?.terrains?.[tile.terrain ?? ""];
10551
10631
  }, [getTerrainSprite, assetManifest]);
10552
- const resolveFeatureSpriteUrl = React97.useCallback((featureType) => {
10553
- return getFeatureSprite?.(featureType) || assetManifest?.features?.[featureType]?.url;
10632
+ const resolveFeatureAsset = React97.useCallback((feature) => {
10633
+ if (feature.sprite) return feature.sprite;
10634
+ const s = getFeatureSprite?.(feature.type);
10635
+ if (s) return typeof s === "string" ? { url: s } : s;
10636
+ return assetManifest?.features?.[feature.type];
10554
10637
  }, [getFeatureSprite, assetManifest]);
10555
- const resolveUnitSpriteUrl = React97.useCallback((unit) => {
10556
- return unit.sprite?.url || getUnitSprite?.(unit) || (unit.unitType ? assetManifest?.units?.[unit.unitType]?.url : void 0);
10638
+ const resolveUnitAsset = React97.useCallback((unit) => {
10639
+ if (unit.sprite) return unit.sprite;
10640
+ const s = getUnitSprite?.(unit);
10641
+ if (s) return typeof s === "string" ? { url: s } : s;
10642
+ return unit.unitType ? assetManifest?.units?.[unit.unitType] : void 0;
10557
10643
  }, [getUnitSprite, assetManifest]);
10558
10644
  const miniMapTiles = React97.useMemo(() => {
10559
10645
  if (!showMinimap) return [];
@@ -10617,18 +10703,17 @@ function Canvas2D({
10617
10703
  if (pos.x + scaledTileWidth < visLeft || pos.x > visRight || pos.y + scaledTileHeight < visTop || pos.y > visBottom) {
10618
10704
  continue;
10619
10705
  }
10620
- const spriteUrl = resolveTerrainSpriteUrl(tile);
10621
- const img = spriteUrl ? getImage(spriteUrl) : null;
10622
- if (img) {
10623
- if (img.naturalWidth === 0) {
10624
- ctx.drawImage(img, pos.x, pos.y, scaledTileWidth, scaledTileHeight);
10625
- } else {
10626
- const drawW = scaledTileWidth;
10627
- const drawH = scaledTileWidth * (img.naturalHeight / img.naturalWidth);
10628
- const drawX = pos.x;
10629
- const drawY = flatLike ? pos.y : pos.y + scaledTileHeight - drawH;
10630
- ctx.drawImage(img, drawX, drawY, drawW, drawH);
10631
- }
10706
+ const terrainAsset = resolveTerrainAsset(tile);
10707
+ const img = terrainAsset?.url ? getImage(terrainAsset.url) : null;
10708
+ const src = img ? resolveAssetSource(img, terrainAsset, bumpAtlas) : null;
10709
+ if (src) {
10710
+ const drawW = scaledTileWidth;
10711
+ const drawH = scaledTileWidth / src.aspect;
10712
+ const drawX = pos.x;
10713
+ const drawY = flatLike ? pos.y : pos.y + scaledTileHeight - drawH;
10714
+ blit(ctx, src, drawX, drawY, drawW, drawH);
10715
+ } else if (img && img.naturalWidth === 0) {
10716
+ ctx.drawImage(img, pos.x, pos.y, scaledTileWidth, scaledTileHeight);
10632
10717
  } else {
10633
10718
  const centerX = pos.x + scaledTileWidth / 2;
10634
10719
  const topY = pos.y + scaledDiamondTopY;
@@ -10682,15 +10767,16 @@ function Canvas2D({
10682
10767
  if (pos.x + scaledTileWidth < visLeft || pos.x > visRight || pos.y + scaledTileHeight < visTop || pos.y > visBottom) {
10683
10768
  continue;
10684
10769
  }
10685
- const spriteUrl = feature.sprite?.url || resolveFeatureSpriteUrl(feature.type);
10686
- const img = spriteUrl ? getImage(spriteUrl) : null;
10770
+ const featureAsset = resolveFeatureAsset(feature);
10771
+ const img = featureAsset?.url ? getImage(featureAsset.url) : null;
10772
+ const src = img ? resolveAssetSource(img, featureAsset, bumpAtlas) : null;
10687
10773
  const centerX = pos.x + scaledTileWidth / 2;
10688
10774
  const featureGroundY = pos.y + scaledDiamondTopY + scaledFloorHeight * 0.5;
10689
10775
  const isCastle = feature.type === "castle";
10690
10776
  const featureDrawH = isCastle ? scaledFloorHeight * 3.5 : scaledFloorHeight * 1.6;
10691
10777
  const maxFeatureW = isCastle ? scaledTileWidth * 1.8 : scaledTileWidth * 0.7;
10692
- if (img) {
10693
- const ar = img.naturalWidth / img.naturalHeight;
10778
+ if (src) {
10779
+ const ar = src.aspect;
10694
10780
  let drawH = featureDrawH;
10695
10781
  let drawW = featureDrawH * ar;
10696
10782
  if (drawW > maxFeatureW) {
@@ -10699,7 +10785,7 @@ function Canvas2D({
10699
10785
  }
10700
10786
  const drawX = centerX - drawW / 2;
10701
10787
  const drawY = featureGroundY - drawH;
10702
- ctx.drawImage(img, drawX, drawY, drawW, drawH);
10788
+ blit(ctx, src, drawX, drawY, drawW, drawH);
10703
10789
  } else {
10704
10790
  const color = FEATURE_COLORS[feature.type] || FEATURE_COLORS.default;
10705
10791
  ctx.beginPath();
@@ -10728,17 +10814,18 @@ function Canvas2D({
10728
10814
  const centerX = pos.x + scaledTileWidth / 2;
10729
10815
  const groundY = pos.y + scaledDiamondTopY + scaledFloorHeight * 0.5;
10730
10816
  const breatheOffset = 0;
10731
- const unitSpriteUrl = resolveUnitSpriteUrl(unit);
10732
- const img = unitSpriteUrl ? getImage(unitSpriteUrl) : null;
10817
+ const unitAsset = resolveUnitAsset(unit);
10818
+ const img = unitAsset?.url ? getImage(unitAsset.url) : null;
10733
10819
  const unitDrawH = scaledFloorHeight * spriteHeightRatio * unitScale;
10734
10820
  const maxUnitW = scaledTileWidth * spriteMaxWidthRatio * unitScale;
10735
10821
  const unitIsSheet = unit.spriteSheet?.url !== void 0;
10822
+ const unitSrc = img && !unitIsSheet ? resolveAssetSource(img, unitAsset, bumpAtlas) : null;
10736
10823
  const SHEET_ROWS = 5;
10737
10824
  const sheetFrameW = img ? img.naturalWidth / SHEET_COLUMNS : 0;
10738
10825
  const sheetFrameH = img ? img.naturalHeight / SHEET_ROWS : 0;
10739
10826
  const frameW = unitIsSheet ? sheetFrameW : img?.naturalWidth ?? 1;
10740
10827
  const frameH = unitIsSheet ? sheetFrameH : img?.naturalHeight ?? 1;
10741
- const ar = frameW / (frameH || 1);
10828
+ const ar = unitIsSheet ? sheetFrameW / (sheetFrameH || 1) : unitSrc ? unitSrc.aspect : frameW / (frameH || 1);
10742
10829
  let drawH = unitDrawH;
10743
10830
  let drawW = unitDrawH * ar;
10744
10831
  if (drawW > maxUnitW) {
@@ -10754,6 +10841,8 @@ function Canvas2D({
10754
10841
  if (img) {
10755
10842
  if (unitIsSheet) {
10756
10843
  ctx.drawImage(img, 0, 0, sheetFrameW, sheetFrameH, ghostCenterX - drawW / 2, ghostGroundY - drawH, drawW, drawH);
10844
+ } else if (unitSrc) {
10845
+ blit(ctx, unitSrc, ghostCenterX - drawW / 2, ghostGroundY - drawH, drawW, drawH);
10757
10846
  } else {
10758
10847
  ctx.drawImage(img, ghostCenterX - drawW / 2, ghostGroundY - drawH, drawW, drawH);
10759
10848
  }
@@ -10802,6 +10891,8 @@ function Canvas2D({
10802
10891
  const drawUnit = (x) => {
10803
10892
  if (unitIsSheet) {
10804
10893
  ctx.drawImage(img, 0, 0, sheetFrameW, sheetFrameH, x, spriteY, drawW, drawH);
10894
+ } else if (unitSrc) {
10895
+ blit(ctx, unitSrc, x, spriteY, drawW, drawH);
10805
10896
  } else {
10806
10897
  ctx.drawImage(img, x, spriteY, drawW, drawH);
10807
10898
  }
@@ -10852,11 +10943,12 @@ function Canvas2D({
10852
10943
  flatLike,
10853
10944
  scale,
10854
10945
  debug2,
10855
- resolveTerrainSpriteUrl,
10856
- resolveFeatureSpriteUrl,
10857
- resolveUnitSpriteUrl,
10946
+ resolveTerrainAsset,
10947
+ resolveFeatureAsset,
10948
+ resolveUnitAsset,
10858
10949
  resolveFrameForUnit,
10859
10950
  getImage,
10951
+ bumpAtlas,
10860
10952
  baseOffsetX,
10861
10953
  scaledTileWidth,
10862
10954
  scaledTileHeight,
@@ -11170,7 +11262,7 @@ function Canvas2D({
11170
11262
  }
11171
11263
  );
11172
11264
  }
11173
- var PLATFORM_COLORS, PLAYER_COLOR, PLAYER_EYE_COLOR, SKY_GRADIENT_TOP, SKY_GRADIENT_BOTTOM, GRID_COLOR, DEFAULT_PLATFORMS;
11265
+ var PLATFORM_COLORS, NOOP, PLAYER_COLOR, PLAYER_EYE_COLOR, SKY_GRADIENT_TOP, SKY_GRADIENT_BOTTOM, GRID_COLOR, DEFAULT_PLATFORMS;
11174
11266
  var init_Canvas2D = __esm({
11175
11267
  "components/game/2d/molecules/Canvas2D.tsx"() {
11176
11268
  "use client";
@@ -11184,6 +11276,7 @@ var init_Canvas2D = __esm({
11184
11276
  init_MiniMap();
11185
11277
  init_HealthBar();
11186
11278
  init_useImageCache();
11279
+ init_atlasSlice();
11187
11280
  init_useCamera();
11188
11281
  init_useCanvasGestures();
11189
11282
  init_useRenderInterpolation();
@@ -11197,6 +11290,8 @@ var init_Canvas2D = __esm({
11197
11290
  hazard: "#c0392b",
11198
11291
  goal: "#f1c40f"
11199
11292
  };
11293
+ NOOP = () => {
11294
+ };
11200
11295
  PLAYER_COLOR = "#3498db";
11201
11296
  PLAYER_EYE_COLOR = "#ffffff";
11202
11297
  SKY_GRADIENT_TOP = "#1a1a2e";
@@ -16238,6 +16333,153 @@ var init_ComponentPatterns = __esm({
16238
16333
  AlertPattern.displayName = "AlertPattern";
16239
16334
  }
16240
16335
  });
16336
+ var DEFAULT_BAR_COLOR, DEFAULT_CELL_COLOR, DEFAULT_POINTER_COLOR, POINTER_BAND, TOP_PAD, AlgorithmCanvas;
16337
+ var init_AlgorithmCanvas = __esm({
16338
+ "components/learning/molecules/AlgorithmCanvas.tsx"() {
16339
+ "use client";
16340
+ init_atoms();
16341
+ init_Stack();
16342
+ init_LearningCanvas();
16343
+ DEFAULT_BAR_COLOR = "#3b82f6";
16344
+ DEFAULT_CELL_COLOR = "#e5e7eb";
16345
+ DEFAULT_POINTER_COLOR = "#dc2626";
16346
+ POINTER_BAND = 34;
16347
+ TOP_PAD = 12;
16348
+ AlgorithmCanvas = ({
16349
+ className,
16350
+ width = 600,
16351
+ height = 400,
16352
+ title,
16353
+ backgroundColor,
16354
+ bars = [],
16355
+ cells = [],
16356
+ pointers = [],
16357
+ shapes = [],
16358
+ interactive = false,
16359
+ animate = false,
16360
+ onShapeClick,
16361
+ isLoading,
16362
+ error
16363
+ }) => {
16364
+ const derivedShapes = React97.useMemo(() => {
16365
+ const out = [];
16366
+ if (bars.length > 0) {
16367
+ const slot = width / bars.length;
16368
+ const barW = slot * 0.8;
16369
+ const gap = slot * 0.1;
16370
+ const baseline = height - POINTER_BAND;
16371
+ const usableH = baseline - TOP_PAD;
16372
+ const maxV = Math.max(1, ...bars.map((b) => Number.isFinite(b.value) ? b.value : 0));
16373
+ bars.forEach((bar, i) => {
16374
+ const v = Number.isFinite(bar.value) ? bar.value : 0;
16375
+ const bh = Math.max(0, v / maxV * usableH);
16376
+ const x = i * slot + gap;
16377
+ const color = bar.color ?? DEFAULT_BAR_COLOR;
16378
+ out.push({
16379
+ type: "rect",
16380
+ id: `bar-${i}`,
16381
+ x,
16382
+ y: baseline - bh,
16383
+ width: barW,
16384
+ height: bh,
16385
+ color,
16386
+ fill: color
16387
+ });
16388
+ const label = bar.label ?? (bars.length <= 24 ? String(v) : void 0);
16389
+ if (label) {
16390
+ out.push({
16391
+ type: "text",
16392
+ x: x + barW / 2,
16393
+ y: baseline - bh - 8,
16394
+ text: label,
16395
+ color: "#374151",
16396
+ fontSize: 11,
16397
+ align: "center"
16398
+ });
16399
+ }
16400
+ });
16401
+ pointers.forEach((p) => {
16402
+ if (p.index < 0 || p.index >= bars.length) return;
16403
+ const cx = p.index * slot + slot / 2;
16404
+ const color = p.color ?? DEFAULT_POINTER_COLOR;
16405
+ out.push({
16406
+ type: "arrow",
16407
+ x1: cx,
16408
+ y1: height - 6,
16409
+ x2: cx,
16410
+ y2: baseline + 4,
16411
+ color,
16412
+ lineWidth: 2
16413
+ });
16414
+ if (p.label) {
16415
+ out.push({
16416
+ type: "text",
16417
+ x: cx,
16418
+ y: height - 22,
16419
+ text: p.label,
16420
+ color,
16421
+ fontSize: 11,
16422
+ align: "center"
16423
+ });
16424
+ }
16425
+ });
16426
+ }
16427
+ if (cells.length > 0) {
16428
+ const maxCol = Math.max(0, ...cells.map((c) => c.col)) + 1;
16429
+ const maxRow = Math.max(0, ...cells.map((c) => c.row)) + 1;
16430
+ const cw = width / maxCol;
16431
+ const ch = height / maxRow;
16432
+ cells.forEach((c, i) => {
16433
+ const x = c.col * cw;
16434
+ const y = c.row * ch;
16435
+ const color = c.color ?? DEFAULT_CELL_COLOR;
16436
+ out.push({
16437
+ type: "rect",
16438
+ id: `cell-${i}`,
16439
+ x: x + 1,
16440
+ y: y + 1,
16441
+ width: cw - 2,
16442
+ height: ch - 2,
16443
+ color: "#9ca3af",
16444
+ fill: color
16445
+ });
16446
+ const label = c.label ?? (c.value != null ? String(c.value) : void 0);
16447
+ if (label && cw >= 18 && ch >= 14) {
16448
+ out.push({
16449
+ type: "text",
16450
+ x: x + cw / 2,
16451
+ y: y + ch / 2,
16452
+ text: label,
16453
+ color: "#111827",
16454
+ fontSize: 12,
16455
+ align: "center"
16456
+ });
16457
+ }
16458
+ });
16459
+ }
16460
+ out.push(...shapes);
16461
+ return out;
16462
+ }, [bars, cells, pointers, shapes, width, height]);
16463
+ return /* @__PURE__ */ jsxRuntime.jsx(Card, { className, children: /* @__PURE__ */ jsxRuntime.jsxs(VStack, { gap: "sm", children: [
16464
+ title ? /* @__PURE__ */ jsxRuntime.jsx(Typography, { variant: "h4", children: title }) : null,
16465
+ /* @__PURE__ */ jsxRuntime.jsx(
16466
+ LearningCanvas,
16467
+ {
16468
+ width,
16469
+ height,
16470
+ backgroundColor,
16471
+ shapes: derivedShapes,
16472
+ interactive,
16473
+ animate,
16474
+ onShapeClick,
16475
+ isLoading,
16476
+ error
16477
+ }
16478
+ )
16479
+ ] }) });
16480
+ };
16481
+ }
16482
+ });
16241
16483
  var AuthLayout;
16242
16484
  var init_AuthLayout = __esm({
16243
16485
  "components/marketing/templates/AuthLayout.tsx"() {
@@ -45910,6 +46152,7 @@ var init_component_registry_generated = __esm({
45910
46152
  init_ActionTile();
45911
46153
  init_ActivationBlock();
45912
46154
  init_ComponentPatterns();
46155
+ init_AlgorithmCanvas();
45913
46156
  init_AnimatedCounter();
45914
46157
  init_AnimatedGraphic();
45915
46158
  init_AnimatedReveal();
@@ -46208,6 +46451,7 @@ var init_component_registry_generated = __esm({
46208
46451
  "ActivationBlock": ActivationBlock,
46209
46452
  "Alert": AlertPattern,
46210
46453
  "AlertPattern": AlertPattern,
46454
+ "AlgorithmCanvas": AlgorithmCanvas,
46211
46455
  "AnimatedCounter": AnimatedCounter,
46212
46456
  "AnimatedGraphic": AnimatedGraphic,
46213
46457
  "AnimatedReveal": AnimatedReveal,