@neta-art/cohub 11.1.0 → 11.3.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 (78) hide show
  1. package/dist/board/editor/context.d.ts +1 -0
  2. package/dist/board/editor/context.js +1 -0
  3. package/dist/board/editor/editor.d.ts +1 -0
  4. package/dist/board/editor/editor.js +7 -0
  5. package/dist/board/editor/interaction.d.ts +4 -0
  6. package/dist/board/editor/interaction.js +16 -7
  7. package/dist/board/editor/selection-transform.d.ts +1 -1
  8. package/dist/board/editor/selection-transform.js +13 -2
  9. package/dist/board/export/index.d.ts +2 -0
  10. package/dist/board/export/index.js +1 -0
  11. package/dist/board/export/scene.d.ts +2 -0
  12. package/dist/board/export/scene.js +4 -0
  13. package/dist/board/model/file-preview.d.ts +0 -1
  14. package/dist/board/model/file-preview.js +10 -1
  15. package/dist/board/model/index.d.ts +2 -1
  16. package/dist/board/model/index.js +2 -1
  17. package/dist/board/model/script-api.d.ts +69 -0
  18. package/dist/board/model/script-api.js +77 -0
  19. package/dist/board/render/audio-waveform.d.ts +1 -1
  20. package/dist/board/render/audio-waveform.js +8 -5
  21. package/dist/board/render/card-tag.d.ts +24 -0
  22. package/dist/board/render/card-tag.js +205 -0
  23. package/dist/board/render/index.d.ts +7 -4
  24. package/dist/board/render/index.js +7 -4
  25. package/dist/board/render/media-interaction.d.ts +2 -1
  26. package/dist/board/render/media-interaction.js +14 -6
  27. package/dist/board/render/palette.js +2 -2
  28. package/dist/board/render/renderers/arrow-card-renderer.js +20 -16
  29. package/dist/board/render/renderers/audio-card-renderer.d.ts +8 -1
  30. package/dist/board/render/renderers/audio-card-renderer.js +64 -60
  31. package/dist/board/render/renderers/base-card-renderer.d.ts +1 -26
  32. package/dist/board/render/renderers/base-card-renderer.js +6 -92
  33. package/dist/board/render/renderers/board-renderer-registry.d.ts +8 -0
  34. package/dist/board/render/renderers/board-renderer-registry.js +3 -3
  35. package/dist/board/render/renderers/custom-card-renderer.js +4 -7
  36. package/dist/board/render/renderers/effect-card-renderer.js +0 -5
  37. package/dist/board/render/renderers/file-card-renderer.d.ts +1 -11
  38. package/dist/board/render/renderers/file-card-renderer.js +43 -105
  39. package/dist/board/render/renderers/frame-card-renderer.js +14 -15
  40. package/dist/board/render/renderers/image-card-renderer.js +62 -67
  41. package/dist/board/render/renderers/media-key.d.ts +6 -0
  42. package/dist/board/render/renderers/media-key.js +39 -0
  43. package/dist/board/render/renderers/shape-card-renderer.js +6 -12
  44. package/dist/board/render/renderers/task-card-renderer.d.ts +1 -9
  45. package/dist/board/render/renderers/task-card-renderer.js +82 -56
  46. package/dist/board/render/renderers/unknown-card-renderer.js +17 -16
  47. package/dist/board/render/renderers/video-card-renderer.js +73 -80
  48. package/dist/board/render/stroke.d.ts +1 -1
  49. package/dist/board/render/stroke.js +5 -4
  50. package/dist/board/render/style.d.ts +29 -0
  51. package/dist/board/render/style.js +54 -0
  52. package/dist/board/render/surface.d.ts +34 -0
  53. package/dist/board/render/surface.js +132 -0
  54. package/dist/board/render/text-fit.d.ts +8 -0
  55. package/dist/board/render/text-fit.js +60 -0
  56. package/dist/board/render/text-resolution.d.ts +2 -1
  57. package/dist/board/render/text-resolution.js +4 -1
  58. package/dist/board/stage/index.d.ts +3 -2
  59. package/dist/board/stage/index.js +2 -1
  60. package/dist/board/stage/overlay.d.ts +41 -0
  61. package/dist/board/stage/overlay.js +182 -0
  62. package/dist/board/stage/scene.d.ts +2 -43
  63. package/dist/board/stage/scene.js +23 -131
  64. package/dist/board/stage/stage.d.ts +2 -0
  65. package/dist/board/stage/stage.js +99 -32
  66. package/dist/chunks/app-realtime.js +1697 -0
  67. package/dist/chunks/environment.d.ts +997 -3
  68. package/dist/chunks/http.d.ts +393 -3
  69. package/dist/chunks/http.js +40 -1
  70. package/dist/chunks/websocket.d.ts +4 -0
  71. package/dist/chunks/websocket.js +11 -2
  72. package/dist/http.d.ts +2 -2
  73. package/dist/index.d.ts +4 -323
  74. package/dist/index.js +58 -1660
  75. package/dist/protocol/dist/board-model.js +51 -51
  76. package/dist/protocol/dist/board-schema.d.ts +1 -2
  77. package/dist/protocol/dist/board-schema.js +7 -22
  78. package/package.json +2 -1
@@ -1,20 +1,9 @@
1
+ import { cardElevation, containRect, drawCardSurface, traceCardOutline } from "../surface.js";
1
2
  import { positionShell } from "./base-card-renderer.js";
2
3
  import { drawFarPlate } from "./far-plate.js";
3
4
  import { Container, Graphics, Rectangle, Sprite, Texture } from "pixi.js";
4
5
  //#region src/board/render/renderers/image-card-renderer.ts
5
6
  const partsByContainer = /* @__PURE__ */ new WeakMap();
6
- const RADIUS = 2;
7
- function layoutContain(sprite, width, height) {
8
- const texture = sprite.texture;
9
- const tw = texture.width;
10
- const th = texture.height;
11
- if (!tw || !th) return;
12
- const scale = Math.min(width / tw, height / th);
13
- sprite.width = tw * scale;
14
- sprite.height = th * scale;
15
- sprite.x = (width - sprite.width) / 2;
16
- sprite.y = (height - sprite.height) / 2;
17
- }
18
7
  function cropRect(crop) {
19
8
  if (!crop) return null;
20
9
  const w = Math.min(crop.w, 1 - crop.x);
@@ -48,70 +37,85 @@ function showTexture(parts, texture, crop) {
48
37
  if (parts.sprite.texture !== display) parts.sprite.texture = display;
49
38
  if (previous && previous !== parts.cropped) previous.destroy(false);
50
39
  }
40
+ const PRINT_MARGIN = 6;
51
41
  function sync(container, item, context) {
52
42
  const parts = partsByContainer.get(container);
53
43
  if (!parts) return;
54
44
  positionShell(parts.root, item);
55
45
  const { width, height } = item.frame;
56
- const selected = context.selectedIds.has(item.id);
57
- const hovered = context.hoveredId === item.id;
58
46
  const key = context.assetKey(item);
59
47
  const texture = key ? context.getTexture(key) : null;
60
48
  const failed = Boolean(key && !texture && context.hasError(key));
61
- const texId = texture ? `${texture.width}x${texture.height}` : "none";
49
+ const elevation = cardElevation(context, item.id);
62
50
  const sig = [
63
51
  key ?? "",
64
52
  width,
65
53
  height,
66
- selected,
67
- hovered,
68
- texId,
54
+ texture ? `${texture.width}x${texture.height}` : "none",
69
55
  JSON.stringify(item.props.crop ?? null),
70
56
  failed,
71
- context.colorScheme
57
+ elevation,
58
+ context.themeKey
72
59
  ].join("|");
73
60
  if (sig === parts.sig) return;
74
61
  parts.sig = sig;
75
62
  showTexture(parts, texture, item.props.crop);
63
+ const display = parts.sprite.texture;
64
+ const framed = context.style.media === "framed";
65
+ const margin = framed ? Math.min(PRINT_MARGIN, width / 8, height / 8) : 0;
66
+ const area = {
67
+ x: margin,
68
+ y: margin,
69
+ width: width - margin * 2,
70
+ height: height - margin * 2
71
+ };
72
+ const fitted = texture ? containRect(area.width, area.height, display.width, display.height) : {
73
+ ...area,
74
+ x: 0,
75
+ y: 0
76
+ };
77
+ const picture = {
78
+ x: area.x + fitted.x,
79
+ y: area.y + fitted.y,
80
+ width: fitted.width,
81
+ height: fitted.height
82
+ };
83
+ const card = framed ? {
84
+ x: picture.x - margin,
85
+ y: picture.y - margin,
86
+ width: picture.width + margin * 2,
87
+ height: picture.height + margin * 2
88
+ } : picture;
76
89
  parts.sprite.visible = Boolean(texture);
77
- if (texture) layoutContain(parts.sprite, width, height);
78
- parts.placeholder.clear();
79
- if (!texture) {
80
- parts.placeholder.roundRect(0, 0, width, height, RADIUS).fill({
81
- color: context.palette.surface,
82
- alpha: .85
83
- }).roundRect(0, 0, width, height, RADIUS).stroke({
84
- color: failed ? context.palette.border : context.palette.hover,
85
- width: 1,
86
- alpha: .9
87
- });
88
- const cx = width / 2;
89
- const cy = height / 2;
90
- if (failed) parts.placeholder.moveTo(cx - 10, cy - 10).lineTo(cx + 10, cy + 10).moveTo(cx + 10, cy - 10).lineTo(cx - 10, cy + 10).stroke({
91
- color: context.palette.muted,
92
- width: 1.5,
93
- alpha: .8
94
- });
95
- else parts.placeholder.circle(cx, cy, 6).stroke({
96
- color: context.palette.muted,
97
- width: 1.25,
98
- alpha: .55
99
- });
100
- }
101
- parts.placeholder.visible = !texture;
102
- if (selected || hovered) {
103
- const hasPixels = Boolean(texture) && parts.sprite.width > 0;
104
- const ox = hasPixels ? parts.sprite.x : 0;
105
- const oy = hasPixels ? parts.sprite.y : 0;
106
- const ow = hasPixels ? parts.sprite.width : width;
107
- const oh = hasPixels ? parts.sprite.height : height;
108
- parts.placeholder.visible = true;
109
- parts.placeholder.roundRect(ox, oy, ow, oh, RADIUS).stroke({
110
- color: selected ? context.palette.brand : context.palette.muted,
111
- width: selected ? 2 : 1,
112
- alpha: selected ? .95 : .7
113
- });
90
+ if (texture) {
91
+ parts.sprite.position.set(picture.x, picture.y);
92
+ parts.sprite.width = picture.width;
93
+ parts.sprite.height = picture.height;
114
94
  }
95
+ traceCardOutline(parts.mask.clear(), picture, framed ? {
96
+ corner: "round",
97
+ radius: 0
98
+ } : context.style).fill({ color: 16777215 });
99
+ parts.placeholder.clear();
100
+ if (texture && !framed) return;
101
+ drawCardSurface(parts.placeholder, card, context.style, {
102
+ fill: context.palette.surface,
103
+ border: texture ? null : failed ? context.palette.border : context.palette.hover,
104
+ elevation
105
+ });
106
+ if (texture) return;
107
+ const cx = width / 2;
108
+ const cy = height / 2;
109
+ if (failed) parts.placeholder.moveTo(cx - 10, cy - 10).lineTo(cx + 10, cy + 10).moveTo(cx + 10, cy - 10).lineTo(cx - 10, cy + 10).stroke({
110
+ color: context.palette.muted,
111
+ width: 1.5,
112
+ alpha: .8
113
+ });
114
+ else parts.placeholder.circle(cx, cy, 6).stroke({
115
+ color: context.palette.muted,
116
+ width: 1.25,
117
+ alpha: .55
118
+ });
115
119
  }
116
120
  const imageCardRenderer = {
117
121
  id: "image-card",
@@ -121,17 +125,13 @@ const imageCardRenderer = {
121
125
  const sprite = new Sprite(Texture.EMPTY);
122
126
  const placeholder = new Graphics();
123
127
  const mask = new Graphics();
124
- const maskBox = () => {
125
- mask.clear();
126
- if (item.type === "image") mask.roundRect(0, 0, item.frame.width, item.frame.height, RADIUS).fill({ color: 16777215 });
127
- };
128
- maskBox();
129
128
  root.addChild(placeholder, sprite, mask);
130
129
  sprite.mask = mask;
131
130
  partsByContainer.set(root, {
132
131
  root,
133
132
  sprite,
134
133
  placeholder,
134
+ mask,
135
135
  sig: "",
136
136
  cropped: null,
137
137
  cropSource: null,
@@ -141,12 +141,7 @@ const imageCardRenderer = {
141
141
  return root;
142
142
  },
143
143
  update: (container, item, context) => {
144
- if (item.type !== "image") return;
145
- const parts = partsByContainer.get(container);
146
- if (!parts) return;
147
- const mask = parts.root.children.find((child) => child !== parts.sprite && child !== parts.placeholder);
148
- if (mask) mask.clear().roundRect(0, 0, item.frame.width, item.frame.height, RADIUS).fill({ color: 16777215 });
149
- sync(container, item, context);
144
+ if (item.type === "image") sync(container, item, context);
150
145
  },
151
146
  renderFar: (graphics, item, context) => {
152
147
  drawFarPlate(graphics, item.frame, {
@@ -0,0 +1,6 @@
1
+ import { BoardRenderContext } from "./board-renderer-registry.js";
2
+ import { Graphics } from "pixi.js";
3
+ //#region src/board/render/renderers/media-key.d.ts
4
+ declare function drawMediaPlayKey(graphics: Graphics, cx: number, cy: number, radius: number, context: BoardRenderContext, solid?: boolean): void;
5
+ //#endregion
6
+ export { drawMediaPlayKey };
@@ -0,0 +1,39 @@
1
+ import { tracePixelRect } from "../surface.js";
2
+ //#region src/board/render/renderers/media-key.ts
3
+ const MEDIA_SCRIM = 0;
4
+ const MEDIA_INK = 16777215;
5
+ function drawMediaPlayKey(graphics, cx, cy, radius, context, solid = false) {
6
+ const face = solid ? context.palette.text : MEDIA_SCRIM;
7
+ const ink = solid ? context.palette.surface : MEDIA_INK;
8
+ if (context.style.corner === "pixel") {
9
+ const size = Math.round(radius * 1.6);
10
+ tracePixelRect(graphics, Math.round(cx - size / 2), Math.round(cy - size / 2), size, size, 2).fill({
11
+ color: face,
12
+ alpha: solid ? 1 : .62
13
+ });
14
+ const cell = Math.max(1, Math.round(size / 14));
15
+ const x = Math.round(cx - cell * 1.5);
16
+ const y = Math.round(cy - cell * 3.5);
17
+ for (let row = 0; row < 7; row += 1) graphics.rect(x, y + row * cell, (4 - Math.abs(row - 3)) * cell, cell);
18
+ graphics.fill({ color: ink });
19
+ return;
20
+ }
21
+ graphics.circle(cx, cy, radius).fill({
22
+ color: face,
23
+ alpha: solid ? 1 : .45
24
+ });
25
+ const size = radius * .5;
26
+ graphics.poly([
27
+ cx - size * .45,
28
+ cy - size * .75,
29
+ cx + size * .8,
30
+ cy,
31
+ cx - size * .45,
32
+ cy + size * .75
33
+ ]).fill({
34
+ color: ink,
35
+ alpha: .96
36
+ });
37
+ }
38
+ //#endregion
39
+ export { drawMediaPlayKey };
@@ -2,9 +2,9 @@ import { BOARD_FONT_STACK } from "../../../protocol/dist/board-constants.js";
2
2
  import { worldPoint } from "../../model/geometry.js";
3
3
  import { itemAlpha, itemColor } from "../palette.js";
4
4
  import { syncTextResolution, syncTextWrapWidth, textResolutionForZoom } from "../text-resolution.js";
5
+ import { dashPattern, roundRectPoints, tracePolyline, trimPolyline } from "../stroke.js";
5
6
  import { positionShell } from "./base-card-renderer.js";
6
7
  import { drawFarPlate } from "./far-plate.js";
7
- import { dashPattern, roundRectPoints, tracePolyline, trimPolyline } from "../stroke.js";
8
8
  import { Container, Graphics, GraphicsPath, Matrix, Text } from "pixi.js";
9
9
  //#region src/board/render/renderers/shape-card-renderer.ts
10
10
  const DEFAULT_ROUNDED_RADIUS = 8;
@@ -95,8 +95,6 @@ function sync(container, item, context) {
95
95
  positionShell(parts.root, item);
96
96
  const { width, height } = item.frame;
97
97
  const { props, style } = item;
98
- const selected = context.selectedIds.has(item.id);
99
- const hovered = context.hoveredId === item.id;
100
98
  const stroke = itemColor(context, style.stroke ?? style.fill, "brand");
101
99
  const fill = itemColor(context, style.fill ?? style.stroke, "brand", "fill");
102
100
  const fillOpacity = (style.fillOpacity ?? (style.fill ? 1 : 0)) * itemAlpha(context, style.fill ?? style.stroke);
@@ -106,8 +104,6 @@ function sync(container, item, context) {
106
104
  const visualSig = [
107
105
  width,
108
106
  height,
109
- selected,
110
- hovered,
111
107
  props.geometry,
112
108
  props.path,
113
109
  fill,
@@ -117,8 +113,7 @@ function sync(container, item, context) {
117
113
  strokeWidth,
118
114
  style.dash,
119
115
  style.radius,
120
- style.trim,
121
- context.palette.muted
116
+ style.trim
122
117
  ].join("|");
123
118
  if (visualSig !== parts.visualSig) {
124
119
  parts.visualSig = visualSig;
@@ -130,8 +125,7 @@ function sync(container, item, context) {
130
125
  alpha: fillOpacity
131
126
  });
132
127
  }
133
- const lineWidth = selected ? Math.max(2.5, strokeWidth) : strokeWidth;
134
- const pattern = dashPattern(style.dash, lineWidth);
128
+ const pattern = dashPattern(style.dash, strokeWidth);
135
129
  const partial = style.trim !== void 0 && style.trim < 1;
136
130
  if (strokeWidth > 0 && (!partial || style.trim)) {
137
131
  if ((pattern || partial) && props.geometry !== "path") {
@@ -139,9 +133,9 @@ function sync(container, item, context) {
139
133
  tracePolyline(parts.shape, trimPolyline(outlinePoints(props.geometry, width, height, radius), style.trim), pattern);
140
134
  } else traceShapeOutline(parts.shape, props, width, height, style.radius);
141
135
  parts.shape.stroke({
142
- color: hovered && !selected ? context.palette.muted : stroke,
143
- width: lineWidth,
144
- alpha: (selected ? 1 : hovered ? .95 : .85) * strokeAlpha,
136
+ color: stroke,
137
+ width: strokeWidth,
138
+ alpha: .85 * strokeAlpha,
145
139
  cap: partial || pattern ? "round" : "butt",
146
140
  join: "round"
147
141
  });
@@ -1,13 +1,5 @@
1
1
  import { BoardCardRenderer } from "./board-renderer-registry.js";
2
2
  //#region src/board/render/renderers/task-card-renderer.d.ts
3
- declare const TASK_CARD_FULL_DETAIL_ZOOM = 0.55;
4
- declare function formatTaskElapsed(ms: number): string;
5
- declare function containTaskPreviewRect(width: number, height: number, imageWidth: number, imageHeight: number): {
6
- x: number;
7
- y: number;
8
- width: number;
9
- height: number;
10
- };
11
3
  declare const taskCardRenderer: BoardCardRenderer;
12
4
  //#endregion
13
- export { TASK_CARD_FULL_DETAIL_ZOOM, containTaskPreviewRect, formatTaskElapsed, taskCardRenderer };
5
+ export { taskCardRenderer };
@@ -1,16 +1,16 @@
1
1
  import { BOARD_FONT_STACK, BOARD_MONO_FONT_STACK } from "../../../protocol/dist/board-constants.js";
2
2
  import { featuredTaskArtifact } from "../../../protocol/dist/board-task.js";
3
+ import { fitTextToLines } from "../text-fit.js";
4
+ import { boardCardDetailed, syncTextResolution, textResolutionForZoom } from "../text-resolution.js";
5
+ import { createCardTag, formatBoardDuration, syncCardTag, zoomCardTag } from "../card-tag.js";
3
6
  import { drawAudioWaveform } from "../audio-waveform.js";
4
- import { syncTextResolution, textResolutionForZoom } from "../text-resolution.js";
7
+ import { cardElevation, containRect, drawCardSurface, traceCardOutline } from "../surface.js";
5
8
  import { positionShell } from "./base-card-renderer.js";
6
9
  import { drawFarPlate } from "./far-plate.js";
7
- import { fitTextToLines } from "./file-card-renderer.js";
8
10
  import { Container, FillGradient, Graphics, Sprite, Text } from "pixi.js";
9
11
  //#region src/board/render/renderers/task-card-renderer.ts
10
- const RADIUS = 4;
11
12
  const PADDING = 12;
12
13
  const META_HEIGHT = 28;
13
- const TASK_CARD_FULL_DETAIL_ZOOM = .55;
14
14
  const PLAY_BADGE_RADIUS = 15;
15
15
  const SHIMMER_PERIOD_MS = 1600;
16
16
  const SHIMMER_FRAME_MS = 33;
@@ -62,30 +62,6 @@ function statusColor(status, context) {
62
62
  if (status === "running") return context.colors.brand.stroke;
63
63
  return context.colors.neutral.stroke;
64
64
  }
65
- function formatTaskElapsed(ms) {
66
- const total = Math.max(0, Math.floor(ms / SECOND));
67
- const hours = Math.floor(total / 3600);
68
- const minutes = Math.floor(total % 3600 / 60);
69
- const seconds = String(total % 60).padStart(2, "0");
70
- return hours > 0 ? `${hours}:${String(minutes).padStart(2, "0")}:${seconds}` : `${minutes}:${seconds}`;
71
- }
72
- function containTaskPreviewRect(width, height, imageWidth, imageHeight) {
73
- if (width <= 0 || height <= 0 || imageWidth <= 0 || imageHeight <= 0) return {
74
- x: 0,
75
- y: 0,
76
- width: 0,
77
- height: 0
78
- };
79
- const scale = Math.min(width / imageWidth, height / imageHeight);
80
- const renderedWidth = imageWidth * scale;
81
- const renderedHeight = imageHeight * scale;
82
- return {
83
- x: (width - renderedWidth) / 2,
84
- y: (height - renderedHeight) / 2,
85
- width: renderedWidth,
86
- height: renderedHeight
87
- };
88
- }
89
65
  function drawPlayBadge(graphics, rect, context) {
90
66
  const radius = Math.min(PLAY_BADGE_RADIUS, Math.max(8, Math.min(rect.width, rect.height) * .16));
91
67
  const cx = rect.x + rect.width / 2;
@@ -208,17 +184,46 @@ function drawShimmerBand(graphics, width, height, color) {
208
184
  const band = Math.max(48, width * .45);
209
185
  graphics.clear().rect(-band, 0, band, height).fill(shimmerGradient(color));
210
186
  }
187
+ const MOSAIC_FRAME_MS = 160;
188
+ const MOSAIC_CELL = 8;
189
+ function drawMosaic(graphics, width, height, color, frame) {
190
+ const columns = Math.max(1, Math.floor(width / MOSAIC_CELL));
191
+ const rows = Math.max(1, Math.floor(height / MOSAIC_CELL));
192
+ const left = (width - columns * MOSAIC_CELL) / 2;
193
+ const top = (height - rows * MOSAIC_CELL) / 2;
194
+ graphics.clear();
195
+ for (const [threshold, alpha] of [[.08, .16], [.02, .3]]) {
196
+ for (let row = 0; row < rows; row += 1) for (let column = 0; column < columns; column += 1) {
197
+ let hash = Math.imul(column + 1, 2654435761) ^ Math.imul(row + 1, 2246822507) ^ Math.imul(frame + 1, 3266489909);
198
+ hash ^= hash >>> 15;
199
+ hash = Math.imul(hash, 739982445);
200
+ hash ^= hash >>> 12;
201
+ if ((hash >>> 0) / 4294967295 < threshold) graphics.rect(left + column * MOSAIC_CELL + 1, top + row * MOSAIC_CELL + 1, 6, 6);
202
+ }
203
+ graphics.fill({
204
+ color,
205
+ alpha
206
+ });
207
+ }
208
+ }
211
209
  function advance(parts, clock) {
212
210
  const { motion } = parts;
213
211
  let next = null;
214
- if (motion.shimmer) {
212
+ if (motion.mosaic) {
213
+ const frame = Math.floor(clock / MOSAIC_FRAME_MS);
214
+ if (frame !== motion.mosaic.step) {
215
+ motion.mosaic.step = frame;
216
+ drawMosaic(parts.shimmer, motion.width, motion.mosaic.height, motion.mosaic.color, frame);
217
+ }
218
+ next = MOSAIC_FRAME_MS - clock % MOSAIC_FRAME_MS;
219
+ } else if (motion.shimmer) {
215
220
  const travel = motion.width + parts.shimmer.width;
216
221
  parts.shimmer.x = clock % SHIMMER_PERIOD_MS / SHIMMER_PERIOD_MS * travel;
217
222
  next = SHIMMER_FRAME_MS;
218
223
  }
219
224
  if (motion.startedAt !== null) {
220
225
  const elapsedMs = Date.now() - motion.startedAt;
221
- const elapsed = formatTaskElapsed(elapsedMs);
226
+ const elapsed = formatBoardDuration(elapsedMs);
222
227
  if (elapsed !== motion.elapsed) {
223
228
  motion.elapsed = elapsed;
224
229
  parts.body.text = `${motion.label} · ${elapsed}`;
@@ -241,9 +246,13 @@ function sync(container, item, context) {
241
246
  const { width, height } = item.frame;
242
247
  const { snapshot } = item.props;
243
248
  const { status, startedAt } = liveStatus(item, context);
244
- const selected = context.selectedIds.has(item.id);
245
- const hovered = context.hoveredId === item.id;
246
- const full = context.zoom >= TASK_CARD_FULL_DETAIL_ZOOM;
249
+ const full = boardCardDetailed(context.zoom);
250
+ const elevation = cardElevation(context, item.id);
251
+ syncCardTag(parts.tag, {
252
+ glyph: "task",
253
+ text: snapshot.title,
254
+ width
255
+ }, context);
247
256
  const working = status === "pending" || status === "running";
248
257
  const color = statusColor(status, context);
249
258
  const key = context.assetKey(item);
@@ -270,7 +279,7 @@ function sync(container, item, context) {
270
279
  const showBadge = full && Boolean(count);
271
280
  const label = stateLabel(surface, context);
272
281
  const counting = full && working && startedAt !== null && surface !== null;
273
- const elapsed = counting ? formatTaskElapsed(Date.now() - startedAt) : "";
282
+ const elapsed = counting ? formatBoardDuration(Date.now() - startedAt) : "";
274
283
  const detailText = !full || !surface ? "" : status === "failed" ? snapshot.errorExcerpt ?? "" : working ? snapshot.promptExcerpt ?? "" : "";
275
284
  const textArtifact = kind === "text" && artifact?.type === "text";
276
285
  const bodyText = textArtifact ? artifact.textExcerpt : counting ? `${label} · ${elapsed}` : label;
@@ -290,10 +299,13 @@ function sync(container, item, context) {
290
299
  height,
291
300
  color,
292
301
  context.palette.text,
293
- context.palette.muted
302
+ context.palette.muted,
303
+ context.style.font
294
304
  ].join("|");
295
305
  if (textSig !== parts.textSig) {
296
306
  parts.textSig = textSig;
307
+ parts.body.style.fontFamily = context.style.font;
308
+ parts.detail.style.fontFamily = context.style.font;
297
309
  const wrap = Math.max(24, width - 24);
298
310
  parts.body.style.fill = surface === "failed" ? color : context.palette.text;
299
311
  parts.body.anchor.set(textArtifact ? 0 : .5, 0);
@@ -309,9 +321,8 @@ function sync(container, item, context) {
309
321
  const visualSig = [
310
322
  width,
311
323
  height,
312
- selected,
313
- hovered,
314
324
  full,
325
+ elevation,
315
326
  color,
316
327
  kind,
317
328
  surface,
@@ -322,30 +333,30 @@ function sync(container, item, context) {
322
333
  artifact?.id ?? "none",
323
334
  item.props.taskRunId,
324
335
  texture ? `${texture.width}x${texture.height}` : "none",
325
- context.palette.surface,
326
- context.palette.hover,
327
- context.palette.border,
328
- context.palette.bg,
329
- context.palette.text
336
+ context.themeKey
330
337
  ].join("|");
331
338
  if (visualSig !== parts.visualSig) {
332
339
  parts.visualSig = visualSig;
333
- parts.plate.clear().roundRect(0, 0, width, height, RADIUS).fill({
334
- color: context.palette.surface,
335
- alpha: .98
336
- }).roundRect(0, 0, width, height, RADIUS).stroke({
337
- color: selected ? context.palette.brand : hovered ? context.palette.muted : context.palette.border,
338
- width: selected ? 2 : 1,
339
- alpha: selected ? .96 : .82
340
+ const rect = {
341
+ x: 0,
342
+ y: 0,
343
+ width,
344
+ height
345
+ };
346
+ drawCardSurface(parts.plate.clear(), rect, context.style, {
347
+ fill: context.palette.surface,
348
+ border: context.palette.border,
349
+ borderAlpha: .82,
350
+ elevation
340
351
  });
341
- parts.clip.clear().roundRect(1, 1, frame.width, frame.height, 3).fill({ color: 16777215 });
352
+ traceCardOutline(parts.clip.clear(), rect, context.style, -1).fill({ color: 16777215 });
342
353
  parts.previewBg.clear().rect(frame.x, frame.y, frame.width, frame.height).fill({
343
354
  color: context.palette.hover,
344
355
  alpha: .5
345
356
  });
346
357
  parts.previewMask.clear().rect(frame.x, frame.y, frame.width, frame.height).fill({ color: 16777215 });
347
- drawShimmerBand(parts.shimmer, frame.width, frame.height, context.palette.text);
348
- parts.shimmer.y = frame.y;
358
+ if (context.style.progress === "shimmer") drawShimmerBand(parts.shimmer, frame.width, frame.height, context.palette.text);
359
+ parts.shimmer.position.set(context.style.progress === "shimmer" ? 0 : frame.x, frame.y);
349
360
  parts.previewArt.clear();
350
361
  if (kind === "waveform") {
351
362
  const bottomInset = showMeta ? META_HEIGHT : 0;
@@ -384,7 +395,7 @@ function sync(container, item, context) {
384
395
  parts.preview.visible = showPreview;
385
396
  if (showPreview && texture) {
386
397
  if (parts.preview.texture !== texture) parts.preview.texture = texture;
387
- const fitted = containTaskPreviewRect(frame.width, frame.height, texture.width, texture.height);
398
+ const fitted = containRect(frame.width, frame.height, texture.width, texture.height);
388
399
  parts.preview.position.set(frame.x + fitted.x, frame.y + fitted.y);
389
400
  parts.preview.width = fitted.width;
390
401
  parts.preview.height = fitted.height;
@@ -400,6 +411,12 @@ function sync(container, item, context) {
400
411
  parts.badge.position.set(width - 8 - badgeWidth / 2, 18);
401
412
  parts.motion.width = frame.width;
402
413
  parts.motion.shimmer = full && working && kind !== "texture" && !context.reducedMotion;
414
+ const mosaic = parts.motion.shimmer && context.style.progress === "mosaic";
415
+ parts.motion.mosaic = mosaic ? {
416
+ color: context.palette.brand,
417
+ height: frame.height,
418
+ step: -1
419
+ } : null;
403
420
  parts.shimmer.visible = parts.motion.shimmer;
404
421
  parts.motion.startedAt = counting ? startedAt : null;
405
422
  parts.motion.label = label;
@@ -439,10 +456,14 @@ const taskCardRenderer = {
439
456
  detail.anchor.set(.5, 0);
440
457
  detail.style.align = "center";
441
458
  preview.mask = previewMask;
442
- root.mask = clip;
443
- root.addChild(plate, clip, previewBg, shimmer, preview, previewMask, previewArt, body, detail, meta, badge);
459
+ const content = new Container();
460
+ content.mask = clip;
461
+ content.addChild(previewBg, shimmer, preview, previewMask, previewArt, body, detail, meta, badge);
462
+ const tag = createCardTag();
463
+ root.addChild(plate, content, clip, tag.root);
444
464
  partsByContainer.set(root, {
445
465
  root,
466
+ tag,
446
467
  plate,
447
468
  clip,
448
469
  previewBg,
@@ -462,6 +483,7 @@ const taskCardRenderer = {
462
483
  itemId: "",
463
484
  width: 0,
464
485
  shimmer: false,
486
+ mosaic: null,
465
487
  startedAt: null,
466
488
  label: "",
467
489
  elapsed: "",
@@ -479,6 +501,10 @@ const taskCardRenderer = {
479
501
  const parts = partsByContainer.get(container);
480
502
  return parts ? advance(parts, clock) : null;
481
503
  },
504
+ zoom: (container, zoom) => {
505
+ const tag = partsByContainer.get(container)?.tag;
506
+ if (tag) zoomCardTag(tag, zoom);
507
+ },
482
508
  renderFar: (graphics, item, context) => {
483
509
  if (item.type !== "task") return;
484
510
  const { status } = liveStatus(item, context);
@@ -500,4 +526,4 @@ const taskCardRenderer = {
500
526
  }
501
527
  };
502
528
  //#endregion
503
- export { TASK_CARD_FULL_DETAIL_ZOOM, containTaskPreviewRect, formatTaskElapsed, taskCardRenderer };
529
+ export { taskCardRenderer };
@@ -1,40 +1,41 @@
1
1
  import { BOARD_MONO_FONT_STACK } from "../../../protocol/dist/board-constants.js";
2
2
  import { syncTextResolution, textResolutionForZoom } from "../text-resolution.js";
3
+ import { cardElevation, drawCardSurface, traceCardOutline } from "../surface.js";
3
4
  import { positionShell } from "./base-card-renderer.js";
4
5
  import { drawFarPlate } from "./far-plate.js";
5
6
  import { Container, Graphics, Text } from "pixi.js";
6
7
  //#region src/board/render/renderers/unknown-card-renderer.ts
7
- const RADIUS = 10;
8
8
  const partsByContainer = /* @__PURE__ */ new WeakMap();
9
9
  function sync(container, item, context) {
10
10
  const parts = partsByContainer.get(container);
11
11
  if (!parts) return;
12
12
  positionShell(parts.root, item);
13
13
  const { width, height } = item.frame;
14
- const selected = context.selectedIds.has(item.id);
15
14
  const realType = item.type;
16
15
  syncTextResolution(parts.label, parts, context.zoom);
16
+ const elevation = cardElevation(context, item.id);
17
17
  const visualSig = [
18
18
  width,
19
19
  height,
20
- selected,
21
- context.palette.surface,
22
- context.palette.brand,
23
- context.palette.border,
24
- context.palette.muted
20
+ elevation,
21
+ context.themeKey
25
22
  ].join("|");
26
23
  if (visualSig !== parts.visualSig) {
27
24
  parts.visualSig = visualSig;
28
- parts.box.clear();
29
- parts.box.roundRect(0, 0, width, height, RADIUS).fill({
30
- color: context.palette.surface,
31
- alpha: .7
32
- }).roundRect(0, 0, width, height, RADIUS).stroke({
33
- color: selected ? context.palette.brand : context.palette.border,
34
- width: selected ? 2 : 1,
35
- alpha: .8
25
+ const rect = {
26
+ x: 0,
27
+ y: 0,
28
+ width,
29
+ height
30
+ };
31
+ drawCardSurface(parts.box.clear(), rect, context.style, {
32
+ fill: context.palette.surface,
33
+ fillAlpha: .7,
34
+ border: context.palette.border,
35
+ borderAlpha: .8,
36
+ elevation
36
37
  });
37
- parts.box.roundRect(6, 6, width - 12, height - 12, 6).stroke({
38
+ traceCardOutline(parts.box, rect, context.style, -6).stroke({
38
39
  color: context.palette.muted,
39
40
  width: 1,
40
41
  alpha: .4