@neta-art/cohub 11.1.0 → 11.2.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 (67) 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/render/audio-waveform.d.ts +1 -1
  16. package/dist/board/render/audio-waveform.js +8 -5
  17. package/dist/board/render/card-tag.d.ts +24 -0
  18. package/dist/board/render/card-tag.js +205 -0
  19. package/dist/board/render/index.d.ts +7 -4
  20. package/dist/board/render/index.js +7 -4
  21. package/dist/board/render/media-interaction.d.ts +2 -1
  22. package/dist/board/render/media-interaction.js +14 -6
  23. package/dist/board/render/palette.js +2 -2
  24. package/dist/board/render/renderers/arrow-card-renderer.js +20 -16
  25. package/dist/board/render/renderers/audio-card-renderer.d.ts +8 -1
  26. package/dist/board/render/renderers/audio-card-renderer.js +64 -60
  27. package/dist/board/render/renderers/base-card-renderer.d.ts +1 -26
  28. package/dist/board/render/renderers/base-card-renderer.js +6 -92
  29. package/dist/board/render/renderers/board-renderer-registry.d.ts +8 -0
  30. package/dist/board/render/renderers/board-renderer-registry.js +3 -3
  31. package/dist/board/render/renderers/custom-card-renderer.js +4 -7
  32. package/dist/board/render/renderers/effect-card-renderer.js +0 -5
  33. package/dist/board/render/renderers/file-card-renderer.d.ts +1 -11
  34. package/dist/board/render/renderers/file-card-renderer.js +43 -105
  35. package/dist/board/render/renderers/frame-card-renderer.js +14 -15
  36. package/dist/board/render/renderers/image-card-renderer.js +62 -67
  37. package/dist/board/render/renderers/media-key.d.ts +6 -0
  38. package/dist/board/render/renderers/media-key.js +39 -0
  39. package/dist/board/render/renderers/shape-card-renderer.js +6 -12
  40. package/dist/board/render/renderers/task-card-renderer.d.ts +1 -9
  41. package/dist/board/render/renderers/task-card-renderer.js +82 -56
  42. package/dist/board/render/renderers/unknown-card-renderer.js +17 -16
  43. package/dist/board/render/renderers/video-card-renderer.js +73 -80
  44. package/dist/board/render/stroke.d.ts +1 -1
  45. package/dist/board/render/stroke.js +5 -4
  46. package/dist/board/render/style.d.ts +29 -0
  47. package/dist/board/render/style.js +54 -0
  48. package/dist/board/render/surface.d.ts +34 -0
  49. package/dist/board/render/surface.js +132 -0
  50. package/dist/board/render/text-fit.d.ts +8 -0
  51. package/dist/board/render/text-fit.js +60 -0
  52. package/dist/board/render/text-resolution.d.ts +2 -1
  53. package/dist/board/render/text-resolution.js +4 -1
  54. package/dist/board/stage/index.d.ts +3 -2
  55. package/dist/board/stage/index.js +2 -1
  56. package/dist/board/stage/overlay.d.ts +41 -0
  57. package/dist/board/stage/overlay.js +182 -0
  58. package/dist/board/stage/scene.d.ts +2 -43
  59. package/dist/board/stage/scene.js +23 -131
  60. package/dist/board/stage/stage.d.ts +2 -0
  61. package/dist/board/stage/stage.js +100 -33
  62. package/dist/chunks/environment.d.ts +997 -3
  63. package/dist/chunks/http.d.ts +48 -1
  64. package/dist/chunks/http.js +8 -0
  65. package/dist/http.d.ts +2 -2
  66. package/dist/index.d.ts +2 -2
  67. package/package.json +2 -1
@@ -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
@@ -1,91 +1,94 @@
1
- import { BOARD_FONT_STACK } from "../../../protocol/dist/board-constants.js";
2
1
  import { getMediaResourceTitle } from "../../model/media.js";
3
- import { syncTextResolution, syncTextWrapWidth, textResolutionForZoom } from "../text-resolution.js";
2
+ import { boardCardDetailed } from "../text-resolution.js";
3
+ import { createCardTag, formatBoardDuration, syncCardTag, zoomCardTag } from "../card-tag.js";
4
+ import { cardElevation, containRect, drawCardSurface, traceCardOutline } from "../surface.js";
4
5
  import { positionShell } from "./base-card-renderer.js";
5
6
  import { drawFarPlate } from "./far-plate.js";
6
- import { Container, Graphics, Sprite, Text, Texture } from "pixi.js";
7
+ import { drawMediaPlayKey } from "./media-key.js";
8
+ import { Container, Graphics, Sprite, Texture } from "pixi.js";
7
9
  //#region src/board/render/renderers/video-card-renderer.ts
8
10
  const partsByContainer = /* @__PURE__ */ new WeakMap();
9
- const RADIUS = 4;
10
- function layoutContain(sprite, width, height) {
11
- const texture = sprite.texture;
12
- if (!texture.width || !texture.height) return;
13
- const scale = Math.min(width / texture.width, height / texture.height);
14
- sprite.width = texture.width * scale;
15
- sprite.height = texture.height * scale;
16
- sprite.x = (width - sprite.width) / 2;
17
- sprite.y = (height - sprite.height) / 2;
11
+ const FILM_RAIL = 10;
12
+ const FILM_HOLE = 4;
13
+ function videoTitle(item) {
14
+ return item.props.snapshot?.title ?? getMediaResourceTitle(item.props.src);
18
15
  }
19
16
  function sync(container, item, context) {
20
17
  const parts = partsByContainer.get(container);
21
18
  if (!parts) return;
22
19
  positionShell(parts.root, item);
23
20
  const { width, height } = item.frame;
24
- const selected = context.selectedIds.has(item.id);
25
- const hovered = context.hoveredId === item.id;
26
- const resizing = context.resizingIds.has(item.id);
27
- const title = item.props.snapshot?.title ?? getMediaResourceTitle(item.props.src);
21
+ const title = videoTitle(item);
22
+ const durationMs = item.props.snapshot?.durationMs;
23
+ const duration = durationMs ? formatBoardDuration(durationMs) : "";
24
+ syncCardTag(parts.tag, {
25
+ glyph: "video",
26
+ text: duration ? `${title} · ${duration}` : title,
27
+ width
28
+ }, context);
28
29
  const key = context.assetKey(item);
29
30
  const texture = key ? context.getTexture(key) : null;
30
- const failed = Boolean(key && !texture && context.hasError(key));
31
- syncTextResolution(parts.label, parts, context.zoom);
32
- if (parts.label.text !== title) parts.label.text = title;
33
- syncTextWrapWidth(parts.label, parts, Math.max(1, width - 16), resizing);
34
- parts.label.position.set(8, Math.max(8, height - parts.label.height - 8));
31
+ const elevation = cardElevation(context, item.id);
35
32
  const sig = [
36
33
  key ?? "",
37
34
  texture ? `${texture.width}x${texture.height}` : "none",
38
35
  width,
39
36
  height,
40
- selected,
41
- hovered,
42
- failed,
43
- title,
44
- item.props.time ?? 0,
45
- parts.label.height,
46
- context.palette.surface,
47
- context.palette.brand,
48
- context.palette.muted,
49
- context.palette.border,
50
- context.palette.hover,
51
- context.palette.text
37
+ elevation,
38
+ boardCardDetailed(context.zoom),
39
+ context.themeKey
52
40
  ].join("|");
53
41
  if (sig === parts.sig) return;
54
42
  parts.sig = sig;
43
+ const framed = context.style.media === "framed";
44
+ const rect = {
45
+ x: 0,
46
+ y: 0,
47
+ width,
48
+ height
49
+ };
50
+ const rail = framed ? Math.min(FILM_RAIL, height / 6) : 0;
51
+ const screen = {
52
+ x: framed ? rail * .6 : 0,
53
+ y: rail,
54
+ width: width - (framed ? rail * 1.2 : 0),
55
+ height: height - rail * 2
56
+ };
57
+ drawCardSurface(parts.plate.clear(), rect, context.style, {
58
+ fill: framed ? context.palette.text : context.palette.surface,
59
+ border: framed ? null : context.palette.border,
60
+ borderAlpha: .85,
61
+ elevation
62
+ });
63
+ if (framed) drawFilmRails(parts.plate, width, height, rail, context.palette.bg);
64
+ traceCardOutline(parts.mask.clear(), framed ? screen : rect, framed ? {
65
+ corner: "round",
66
+ radius: 1
67
+ } : context.style).fill({ color: 16777215 });
55
68
  if (texture && parts.sprite.texture !== texture) parts.sprite.texture = texture;
56
69
  parts.sprite.visible = Boolean(texture);
57
- if (texture) layoutContain(parts.sprite, width, height);
58
- parts.mask.clear().roundRect(0, 0, width, height, RADIUS).fill({ color: 16777215 });
59
- parts.plate.clear().roundRect(0, 0, width, height, RADIUS).fill({
60
- color: context.palette.surface,
61
- alpha: .96
62
- });
63
- parts.chrome.clear();
64
70
  if (texture) {
65
- const bandHeight = Math.min(height, parts.label.height + 16);
66
- parts.chrome.rect(0, height - bandHeight, width, bandHeight).fill({
67
- color: context.palette.surface,
68
- alpha: .78
69
- });
71
+ const fitted = containRect(screen.width, screen.height, texture.width, texture.height);
72
+ parts.sprite.position.set(screen.x + fitted.x, screen.y + fitted.y);
73
+ parts.sprite.width = fitted.width;
74
+ parts.sprite.height = fitted.height;
70
75
  }
71
- const cx = width / 2;
72
- const cy = height / 2;
73
- const radius = Math.min(22, Math.min(width, height) * .18);
74
- parts.chrome.circle(cx, cy, radius).fill({
75
- color: selected ? context.palette.brand : context.palette.surface,
76
- alpha: texture ? .88 : .96
77
- });
78
- const triangle = radius * .55;
79
- parts.chrome.moveTo(cx - triangle * .35, cy - triangle * .6).lineTo(cx - triangle * .35, cy + triangle * .6).lineTo(cx + triangle * .7, cy).closePath().fill({
80
- color: context.palette.text,
81
- alpha: .94
82
- });
83
- parts.chrome.roundRect(0, 0, width, height, RADIUS).stroke({
84
- color: selected ? context.palette.brand : hovered ? context.palette.muted : context.palette.border,
85
- width: selected ? 2 : 1,
86
- alpha: selected ? .95 : .85
76
+ parts.chrome.clear();
77
+ if (boardCardDetailed(context.zoom)) drawMediaPlayKey(parts.chrome, screen.x + screen.width / 2, screen.y + screen.height / 2, Math.min(18, Math.min(screen.width, screen.height) * .16), context);
78
+ }
79
+ function drawFilmRails(graphics, width, height, rail, hole) {
80
+ const size = Math.min(FILM_HOLE, rail * .45);
81
+ const step = size * 2.5;
82
+ const count = Math.max(1, Math.floor((width - step) / step));
83
+ const start = (width - (count - 1) * step - size) / 2;
84
+ for (let index = 0; index < count; index += 1) {
85
+ const x = start + index * step;
86
+ graphics.rect(x, (rail - size) / 2, size, size).rect(x, height - rail + (rail - size) / 2, size, size);
87
+ }
88
+ graphics.fill({
89
+ color: hole,
90
+ alpha: .9
87
91
  });
88
- parts.label.style.fill = texture ? context.palette.text : context.palette.muted;
89
92
  }
90
93
  const videoCardRenderer = {
91
94
  id: "video-card",
@@ -96,20 +99,8 @@ const videoCardRenderer = {
96
99
  const sprite = new Sprite(Texture.EMPTY);
97
100
  const mask = new Graphics();
98
101
  const chrome = new Graphics();
99
- const resolution = textResolutionForZoom(context.zoom);
100
- const label = new Text({
101
- text: "",
102
- style: {
103
- fill: context.palette.muted,
104
- fontFamily: BOARD_FONT_STACK,
105
- fontSize: 12,
106
- fontWeight: "500",
107
- wordWrap: true
108
- },
109
- resolution,
110
- roundPixels: true
111
- });
112
- root.addChild(plate, sprite, mask, chrome, label);
102
+ const tag = createCardTag();
103
+ root.addChild(plate, sprite, mask, chrome, tag.root);
113
104
  sprite.mask = mask;
114
105
  partsByContainer.set(root, {
115
106
  root,
@@ -117,10 +108,8 @@ const videoCardRenderer = {
117
108
  sprite,
118
109
  mask,
119
110
  chrome,
120
- label,
121
- sig: "",
122
- wrapWidth: 0,
123
- resolution
111
+ tag,
112
+ sig: ""
124
113
  });
125
114
  if (item.type === "video") sync(root, item, context);
126
115
  return root;
@@ -128,6 +117,10 @@ const videoCardRenderer = {
128
117
  update: (container, item, context) => {
129
118
  if (item.type === "video") sync(container, item, context);
130
119
  },
120
+ zoom: (container, zoom) => {
121
+ const tag = partsByContainer.get(container)?.tag;
122
+ if (tag) zoomCardTag(tag, zoom);
123
+ },
131
124
  renderFar: (graphics, item, context) => {
132
125
  drawFarPlate(graphics, item.frame, {
133
126
  fill: context.palette.surface,
@@ -5,7 +5,7 @@ import { Graphics } from "pixi.js";
5
5
  //#region src/board/render/stroke.d.ts
6
6
  declare function dashPattern(dash: BoardDash | undefined, width: number): [number, number] | null;
7
7
  declare function trimPolyline(path: readonly WorldPoint[], trim: number | undefined): readonly WorldPoint[];
8
- declare function tracePolyline(graphics: Graphics, path: readonly WorldPoint[], pattern: [number, number] | null, closed?: boolean): void;
8
+ declare function tracePolyline(graphics: Graphics, path: readonly WorldPoint[], pattern: [number, number] | null, closed?: boolean, offset?: number): void;
9
9
  declare function traceArrowhead(graphics: Graphics, tip: WorldPoint, angle: number, size: number): void;
10
10
  declare function endAngle(path: readonly WorldPoint[], atStart: boolean): number | null;
11
11
  declare function roundRectPoints(width: number, height: number, radius: number): WorldPoint[];
@@ -31,7 +31,7 @@ function trimPolyline(path, trim) {
31
31
  }
32
32
  return out;
33
33
  }
34
- function tracePolyline(graphics, path, pattern, closed = false) {
34
+ function tracePolyline(graphics, path, pattern, closed = false, offset = 0) {
35
35
  const points = closed && path.length > 2 ? [...path, path[0]] : path;
36
36
  if (points.length < 2) return;
37
37
  if (!pattern) {
@@ -45,9 +45,10 @@ function tracePolyline(graphics, path, pattern, closed = false) {
45
45
  return;
46
46
  }
47
47
  const [dash, gap] = pattern;
48
- let penDown = true;
49
- let fresh = true;
50
- let remaining = dash;
48
+ const phase = Math.max(0, offset) % (dash + gap);
49
+ let penDown = phase < dash;
50
+ let fresh = penDown;
51
+ let remaining = penDown ? dash - phase : dash + gap - phase;
51
52
  for (let index = 1; index < points.length; index += 1) {
52
53
  const a = points[index - 1];
53
54
  const b = points[index];
@@ -0,0 +1,29 @@
1
+ //#region src/board/render/style.d.ts
2
+ type BoardRenderStyle = {
3
+ corner: "round" | "pixel";
4
+ radius: number;
5
+ shadow: BoardShadowStyle | null;
6
+ selection: BoardSelectionStyle;
7
+ media: "plain" | "framed";
8
+ progress: "shimmer" | "mosaic";
9
+ icons: "line" | "pixel";
10
+ motion: "smooth" | "stepped";
11
+ font: string;
12
+ };
13
+ type BoardShadowStyle = {
14
+ color: number;
15
+ alpha: number;
16
+ blur: number;
17
+ y: number;
18
+ };
19
+ type BoardSelectionStyle = {
20
+ width: number;
21
+ outset: number;
22
+ dash: number;
23
+ handle: "circle" | "square";
24
+ handleSize: number;
25
+ };
26
+ declare function defaultBoardRenderStyle(mode: "dark" | "light"): BoardRenderStyle;
27
+ declare function resolveBoardRenderStyle(mode: "dark" | "light", style?: Partial<BoardRenderStyle>): BoardRenderStyle;
28
+ //#endregion
29
+ export { BoardRenderStyle, BoardSelectionStyle, BoardShadowStyle, defaultBoardRenderStyle, resolveBoardRenderStyle };
@@ -0,0 +1,54 @@
1
+ import { BOARD_FONT_STACK } from "../../protocol/dist/board-constants.js";
2
+ //#region src/board/render/style.ts
3
+ const BASE = {
4
+ corner: "round",
5
+ radius: 6,
6
+ selection: {
7
+ width: 1.5,
8
+ outset: 3,
9
+ dash: 0,
10
+ handle: "square",
11
+ handleSize: 8
12
+ },
13
+ media: "plain",
14
+ progress: "shimmer",
15
+ icons: "line",
16
+ motion: "smooth",
17
+ font: BOARD_FONT_STACK
18
+ };
19
+ const SHADOW = {
20
+ light: {
21
+ color: 1447704,
22
+ alpha: .06,
23
+ blur: 12,
24
+ y: 2
25
+ },
26
+ dark: {
27
+ color: 0,
28
+ alpha: .24,
29
+ blur: 12,
30
+ y: 2
31
+ }
32
+ };
33
+ function defaultBoardRenderStyle(mode) {
34
+ return {
35
+ ...BASE,
36
+ selection: { ...BASE.selection },
37
+ shadow: { ...SHADOW[mode] }
38
+ };
39
+ }
40
+ function resolveBoardRenderStyle(mode, style) {
41
+ const base = defaultBoardRenderStyle(mode);
42
+ if (!style) return base;
43
+ return {
44
+ ...base,
45
+ ...style,
46
+ selection: {
47
+ ...base.selection,
48
+ ...style.selection
49
+ },
50
+ shadow: style.shadow === void 0 ? base.shadow : style.shadow
51
+ };
52
+ }
53
+ //#endregion
54
+ export { defaultBoardRenderStyle, resolveBoardRenderStyle };
@@ -0,0 +1,34 @@
1
+ import { WorldPoint } from "../model/geometry.js";
2
+ import { BoardRenderStyle, BoardShadowStyle } from "./style.js";
3
+ import { Graphics } from "pixi.js";
4
+ //#region src/board/render/surface.d.ts
5
+ declare const PIXEL_STEP = 2;
6
+ type CardRect = {
7
+ x: number;
8
+ y: number;
9
+ width: number;
10
+ height: number;
11
+ };
12
+ declare function traceCardOutline(graphics: Graphics, rect: CardRect, style: Pick<BoardRenderStyle, "corner" | "radius">, grow?: number): Graphics;
13
+ declare function tracePixelRect(graphics: Graphics, x: number, y: number, width: number, height: number, step: number): Graphics;
14
+ declare function cardOutlinePoints(rect: CardRect, style: Pick<BoardRenderStyle, "corner" | "radius">, grow?: number): WorldPoint[];
15
+ declare function drawCardShadow(graphics: Graphics, rect: CardRect, style: Pick<BoardRenderStyle, "corner" | "radius">, shadow: BoardShadowStyle | null): void;
16
+ type CardSurface = {
17
+ fill: number;
18
+ fillAlpha?: number;
19
+ border?: number | null;
20
+ borderAlpha?: number;
21
+ elevation?: CardElevation;
22
+ };
23
+ type CardElevation = 0 | 1 | 2;
24
+ declare function cardShadow(style: BoardRenderStyle, elevation: CardElevation): BoardShadowStyle | null;
25
+ declare function drawCardSurface(graphics: Graphics, rect: CardRect, style: BoardRenderStyle, surface: CardSurface): Graphics;
26
+ declare function containRect(width: number, height: number, imageWidth: number, imageHeight: number): CardRect;
27
+ declare function cardElevation(context: {
28
+ liftedIds: ReadonlySet<string>;
29
+ }, id: string): CardElevation;
30
+ declare function isBoardCard(item: {
31
+ type: string;
32
+ }): boolean;
33
+ //#endregion
34
+ export { CardElevation, CardRect, CardSurface, PIXEL_STEP, cardElevation, cardOutlinePoints, cardShadow, containRect, drawCardShadow, drawCardSurface, isBoardCard, traceCardOutline, tracePixelRect };