@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,10 +1,7 @@
1
1
  import { BOARD_FONT_STACK } from "../../../protocol/dist/board-constants.js";
2
2
  import { getBoardResolution } from "../text-resolution.js";
3
- import { Container, Graphics, Text } from "pixi.js";
3
+ import { Text } from "pixi.js";
4
4
  //#region src/board/render/renderers/base-card-renderer.ts
5
- const CARD_RADIUS = 10;
6
- const CARD_PADDING = 12;
7
- const FOOTER_HEIGHT = 34;
8
5
  const TEXT_OPTIONS = {
9
6
  resolution: getBoardResolution(),
10
7
  roundPixels: true
@@ -13,94 +10,11 @@ function createLabel(text, style) {
13
10
  return new Text({
14
11
  ...TEXT_OPTIONS,
15
12
  text,
16
- style
17
- });
18
- }
19
- function createCardShell() {
20
- const root = new Container();
21
- const background = new Graphics();
22
- const content = new Container();
23
- const mask = new Graphics();
24
- const footerBg = new Graphics();
25
- const title = createLabel("", {
26
- fill: 16777215,
27
- fontFamily: BOARD_FONT_STACK,
28
- fontSize: 12,
29
- fontWeight: "500",
30
- wordWrap: true
31
- });
32
- content.mask = mask;
33
- root.addChild(background, mask, content, footerBg, title);
34
- let last = null;
35
- function paletteKeyOf(palette) {
36
- return `${palette.surface}|${palette.border}|${palette.hover}|${palette.text}|${palette.muted}`;
37
- }
38
- function contentRectFor(width, height, footer) {
39
- const bottom = footer ? 34 : 12;
40
- return {
41
- x: 12,
42
- y: 12,
43
- width: Math.max(1, width - 24),
44
- height: Math.max(1, height - 12 - bottom)
45
- };
46
- }
47
- function draw(state, palette, footer) {
48
- const { width, height, selected, hovered, accent } = state;
49
- background.clear();
50
- background.roundRect(0, 0, width, height, 10).fill({
51
- color: palette.surface,
52
- alpha: .98
53
- });
54
- const borderColor = selected ? accent : hovered ? palette.muted : palette.border;
55
- background.roundRect(0, 0, width, height, 10).stroke({
56
- color: borderColor,
57
- width: selected ? 2 : 1,
58
- alpha: selected ? .95 : hovered ? .9 : .8
59
- });
60
- mask.clear();
61
- const rect = contentRectFor(width, height, footer);
62
- mask.roundRect(rect.x, rect.y, rect.width, rect.height, 4).fill({ color: 16777215 });
63
- footerBg.clear();
64
- footerBg.visible = footer;
65
- title.visible = footer;
66
- if (footer) {
67
- const fx = 1;
68
- const fy = height - 34;
69
- const fw = width - 2;
70
- const fh = 33;
71
- const r = 9;
72
- footerBg.moveTo(fx, fy).lineTo(fx + fw, fy).lineTo(fx + fw, fy + fh - r).arcTo(fx + fw, fy + fh, fx + fw - r, fy + fh, r).lineTo(10, fy + fh).arcTo(fx, fy + fh, fx, fy + fh - r, r).lineTo(fx, fy).fill({
73
- color: palette.hover,
74
- alpha: .55
75
- });
76
- title.text = state.title;
77
- title.style.fill = palette.text;
78
- title.style.wordWrapWidth = width - 24;
79
- title.x = 12;
80
- title.y = height - 34 + (34 - title.height) / 2;
13
+ style: {
14
+ fontFamily: BOARD_FONT_STACK,
15
+ ...style
81
16
  }
82
- }
83
- function update(state, palette, footer) {
84
- const paletteKey = paletteKeyOf(palette);
85
- if (!last || last.width !== state.width || last.height !== state.height || last.selected !== state.selected || last.hovered !== state.hovered || last.accent !== state.accent || last.title !== state.title || last.footer !== footer || last.paletteKey !== paletteKey) {
86
- draw(state, palette, footer);
87
- last = {
88
- ...state,
89
- footer,
90
- paletteKey
91
- };
92
- }
93
- }
94
- function destroy() {
95
- root.destroy({ children: true });
96
- }
97
- return {
98
- root,
99
- content,
100
- contentRect: () => contentRectFor(last?.width ?? 0, last?.height ?? 0, last?.footer ?? true),
101
- update,
102
- destroy
103
- };
17
+ });
104
18
  }
105
19
  function positionShell(root, item) {
106
20
  const { x, y, width, height, rotation } = item.frame;
@@ -110,4 +24,4 @@ function positionShell(root, item) {
110
24
  root.rotation = (rotation || 0) * Math.PI / 180;
111
25
  }
112
26
  //#endregion
113
- export { CARD_PADDING, CARD_RADIUS, FOOTER_HEIGHT, createCardShell, createLabel, positionShell };
27
+ export { createLabel, positionShell };
@@ -3,6 +3,7 @@ import { BoardFrame } from "../../../protocol/dist/board-layout.js";
3
3
  import "../../../protocol/dist/index.js";
4
4
  import { BoardScene, BoardSceneItem, SceneItem } from "../../model/scene.js";
5
5
  import { BoardShapeColors } from "../../model/palette.js";
6
+ import { BoardRenderStyle } from "../style.js";
6
7
  import { Container, Graphics, Texture } from "pixi.js";
7
8
  //#region src/board/render/renderers/board-renderer-registry.d.ts
8
9
  type BoardRenderPalette = {
@@ -39,9 +40,12 @@ type BoardRenderContext = {
39
40
  selectedIds: Set<string>;
40
41
  hoveredId: string | null;
41
42
  resizingIds: Set<string>;
43
+ liftedIds: ReadonlySet<string>;
42
44
  palette: BoardRenderPalette;
43
45
  colors: BoardShapeColors;
44
46
  colorScheme: "dark" | "light";
47
+ style: BoardRenderStyle;
48
+ themeKey: string;
45
49
  rendererType: "gpu" | "canvas";
46
50
  zoom: number;
47
51
  time: number;
@@ -64,9 +68,13 @@ type BoardCardRenderer = {
64
68
  id: string;
65
69
  canRender: (item: BoardSceneItem, context: BoardRenderContext) => boolean;
66
70
  animated?: boolean;
71
+ /** Opt in to updates on selection and hover changes. */
72
+ observesSelection?: boolean;
67
73
  tick?: (container: Container, clock: number) => number | null;
68
74
  create: (item: BoardSceneItem, context: BoardRenderContext) => Container;
69
75
  update: (container: Container, item: BoardSceneItem, context: BoardRenderContext) => void;
76
+ /** Runs on every zoom step; only update transforms and visibility. */
77
+ zoom?: (container: Container, zoom: number) => void;
70
78
  renderFar?: (graphics: Graphics, item: BoardSceneItem, context: BoardRenderContext) => void;
71
79
  destroy?: (container: Container, context: BoardRenderContext) => void;
72
80
  };
@@ -1,14 +1,14 @@
1
1
  import { ensureBoardTextMeasurement } from "../text-measurement.js";
2
- import { fileCardRenderer } from "./file-card-renderer.js";
3
- import { taskCardRenderer } from "./task-card-renderer.js";
4
- import { arrowCardRenderer } from "./arrow-card-renderer.js";
5
2
  import { audioCardRenderer } from "./audio-card-renderer.js";
3
+ import { arrowCardRenderer } from "./arrow-card-renderer.js";
6
4
  import { drawCardRenderer } from "./draw-card-renderer.js";
7
5
  import { effectCardRenderer } from "./effect-card-renderer.js";
6
+ import { fileCardRenderer } from "./file-card-renderer.js";
8
7
  import { frameCardRenderer } from "./frame-card-renderer.js";
9
8
  import { shapeCardRenderer } from "./shape-card-renderer.js";
10
9
  import { customCardRenderer } from "./custom-card-renderer.js";
11
10
  import { imageCardRenderer } from "./image-card-renderer.js";
11
+ import { taskCardRenderer } from "./task-card-renderer.js";
12
12
  import { textCardRenderer } from "./text-card-renderer.js";
13
13
  import { unknownCardRenderer } from "./unknown-card-renderer.js";
14
14
  import { videoCardRenderer } from "./video-card-renderer.js";
@@ -22,24 +22,21 @@ function sync(container, item, context) {
22
22
  if (parts.sprite.texture !== display) parts.sprite.texture = display;
23
23
  parts.sprite.width = width;
24
24
  parts.sprite.height = height;
25
- const selected = context.selectedIds.has(item.id);
26
25
  const ready = Boolean(texture) || context.scripts?.ready?.(item.id) === true;
27
26
  const message = error ? `${item.script.src}\n${error}` : ready ? "" : item.script.src;
28
27
  const sig = [
29
28
  width,
30
29
  height,
31
- selected,
32
30
  Boolean(texture),
33
31
  message,
34
- context.palette.border,
35
- context.palette.muted
32
+ context.themeKey
36
33
  ].join("|");
37
34
  if (sig === parts.sig) return;
38
35
  parts.sig = sig;
39
36
  parts.placeholder.clear();
40
- if (!ready || selected || error) parts.placeholder.rect(0, 0, width, height).stroke({
41
- color: selected ? context.palette.brand : context.palette.border,
42
- width: selected ? 2 : 1,
37
+ if (!ready || error) parts.placeholder.rect(0, 0, width, height).stroke({
38
+ color: context.palette.border,
39
+ width: 1,
43
40
  alpha: .8
44
41
  });
45
42
  parts.label.visible = !ready || Boolean(error);
@@ -173,11 +173,6 @@ function sync(container, item, context) {
173
173
  break;
174
174
  case "trail": drawTrail(graphics, parts, item, context, color);
175
175
  }
176
- if (context.selectedIds.has(item.id) || context.hoveredId === item.id) graphics.rect(0, 0, width, height).stroke({
177
- color: context.palette.muted,
178
- width: 1,
179
- alpha: .6
180
- });
181
176
  }
182
177
  const effectCardRenderer = {
183
178
  id: "effect-card",
@@ -2,18 +2,8 @@ import { BoardFileItem } from "../../../protocol/dist/board-model.js";
2
2
  import "../../../protocol/dist/index.js";
3
3
  import { BoardSceneItem, SceneItem } from "../../model/scene.js";
4
4
  import { BoardCardRenderer } from "./board-renderer-registry.js";
5
- import { Text } from "pixi.js";
6
5
  //#region src/board/render/renderers/file-card-renderer.d.ts
7
- declare function ellipsizeWrappedLines(lines: string[], maxLines: number): string;
8
- declare function fitLineWithEllipsis(line: string, wrapWidth: number, measureWidth: (value: string) => number): string;
9
- declare function fitTextToLines(text: Text, value: string, maxLines: number, wrapWidth: number): void;
10
- declare function containCoverRect(width: number, height: number, imageWidth: number, imageHeight: number): {
11
- x: number;
12
- y: number;
13
- width: number;
14
- height: number;
15
- };
16
6
  declare const fileCardRenderer: BoardCardRenderer;
17
7
  declare function isFileItem(item: BoardSceneItem): item is SceneItem<BoardFileItem>;
18
8
  //#endregion
19
- export { containCoverRect, ellipsizeWrappedLines, fileCardRenderer, fitLineWithEllipsis, fitTextToLines, isFileItem };
9
+ export { fileCardRenderer, isFileItem };
@@ -1,12 +1,14 @@
1
1
  import { BOARD_FONT_STACK, BOARD_MONO_FONT_STACK } from "../../../protocol/dist/board-constants.js";
2
2
  import { fileCategory, fileStem } from "../../model/file-snapshot.js";
3
- import { fileCategoryAccent, filePreviewKind, fileTypeLabel } from "../../model/file-preview.js";
4
- import { syncTextResolution } from "../text-resolution.js";
3
+ import { fileCategoryAccent, fileMetaLine, filePreviewKind, fileTypeLabel } from "../../model/file-preview.js";
4
+ import { fitTextToLines } from "../text-fit.js";
5
+ import { boardCardDetailed, syncTextResolution } from "../text-resolution.js";
6
+ import { createCardTag, syncCardTag, zoomCardTag } from "../card-tag.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 { CanvasTextMetrics, Container, Graphics, Sprite, Text, Texture } from "pixi.js";
10
+ import { Container, Graphics, Sprite, Text, Texture } from "pixi.js";
8
11
  //#region src/board/render/renderers/file-card-renderer.ts
9
- const RADIUS = 4;
10
12
  const PADDING = 10;
11
13
  const COVER_RATIO = .56;
12
14
  const MIN_BODY_FOR_COVER = 38;
@@ -19,108 +21,41 @@ const EXCERPT_MAX_LINES = 3;
19
21
  const TYPE_MARK_SIZE = 28;
20
22
  const GAP = 4;
21
23
  const STRIPE = 2;
22
- const LOD_TITLE_ZOOM = .35;
23
- const LOD_BODY_ZOOM = .6;
24
24
  const MAX_DASHES = 40;
25
25
  const partsByContainer = /* @__PURE__ */ new WeakMap();
26
26
  function detailFor(zoom) {
27
- if (zoom < LOD_TITLE_ZOOM) return "plate";
28
- if (zoom < LOD_BODY_ZOOM) return "title";
29
- return "full";
27
+ return boardCardDetailed(zoom) ? "full" : "title";
30
28
  }
31
29
  function coverHeight(item, height) {
32
30
  if (filePreviewKind(item.props.snapshot) !== "cover") return 0;
33
31
  const ideal = Math.round(height * COVER_RATIO);
34
32
  return Math.max(0, Math.min(ideal, height - MIN_BODY_FOR_COVER));
35
33
  }
36
- function ellipsizeWrappedLines(lines, maxLines) {
37
- if (maxLines <= 0 || lines.length === 0) return "";
38
- if (lines.length <= maxLines) return lines.join("\n");
39
- const kept = lines.slice(0, maxLines);
40
- const lastIndex = kept.length - 1;
41
- const last = (kept[lastIndex] ?? "").replace(/\s+$/u, "");
42
- kept[lastIndex] = last ? `${last}…` : "…";
43
- return kept.join("\n");
44
- }
45
- function fitLineWithEllipsis(line, wrapWidth, measureWidth) {
46
- const trimmed = line.replace(/\s+$/u, "");
47
- if (!trimmed) return "…";
48
- const full = `${trimmed}…`;
49
- if (measureWidth(full) <= wrapWidth) return full;
50
- let lo = 0;
51
- let hi = trimmed.length;
52
- let best = "…";
53
- while (lo <= hi) {
54
- const mid = lo + hi >> 1;
55
- const candidate = mid > 0 ? `${trimmed.slice(0, mid)}…` : "…";
56
- if (measureWidth(candidate) <= wrapWidth) {
57
- best = candidate;
58
- lo = mid + 1;
59
- } else hi = mid - 1;
60
- }
61
- return best;
62
- }
63
- function fitTextToLines(text, value, maxLines, wrapWidth) {
64
- const width = Math.max(1, wrapWidth);
65
- text.style.wordWrapWidth = width;
66
- if (!value || maxLines <= 0) {
67
- if (text.text !== "") text.text = "";
68
- return;
69
- }
70
- const charCap = Math.max(maxLines * Math.ceil(width), maxLines * 4);
71
- const sample = value.length > charCap ? value.slice(0, charCap) : value;
72
- const metrics = CanvasTextMetrics.measureText(sample, text.style);
73
- if (!(metrics.lines.length > maxLines || sample !== value)) {
74
- if (text.text !== value) text.text = value;
75
- return;
76
- }
77
- const keptCount = Math.min(maxLines, metrics.lines.length);
78
- const kept = metrics.lines.slice(0, keptCount);
79
- const lastIndex = kept.length - 1;
80
- const probe = text.style.clone();
81
- probe.wordWrap = false;
82
- kept[lastIndex] = fitLineWithEllipsis(kept[lastIndex] ?? "", width, (candidate) => CanvasTextMetrics.measureText(candidate, probe).width);
83
- const fitted = kept.join("\n");
84
- if (text.text !== fitted) text.text = fitted;
85
- }
86
34
  function linesInRoom(room, lineHeight, max) {
87
35
  if (room < lineHeight * .85) return 0;
88
36
  return Math.max(0, Math.min(max, Math.floor((room + .5) / lineHeight)));
89
37
  }
90
- function containCoverRect(width, height, imageWidth, imageHeight) {
91
- if (width <= 0 || height <= 0 || imageWidth <= 0 || imageHeight <= 0) return {
92
- x: 0,
93
- y: 0,
94
- width: 0,
95
- height: 0
96
- };
97
- const scale = Math.min(width / imageWidth, height / imageHeight);
98
- const renderedWidth = imageWidth * scale;
99
- const renderedHeight = imageHeight * scale;
100
- return {
101
- x: (width - renderedWidth) / 2,
102
- y: (height - renderedHeight) / 2,
103
- width: renderedWidth,
104
- height: renderedHeight
105
- };
106
- }
107
38
  function layoutCover(sprite, width, height, texture) {
108
- const rect = containCoverRect(width, height, texture.width, texture.height);
39
+ const rect = containRect(width, height, texture.width, texture.height);
109
40
  sprite.position.set(rect.x, rect.y);
110
41
  sprite.width = rect.width;
111
42
  sprite.height = rect.height;
112
43
  }
113
- function syncClip(parts, width, height) {
114
- parts.clip.clear().roundRect(0, 0, Math.max(1, width), Math.max(1, height), RADIUS).fill({ color: 16777215 });
44
+ function syncClip(parts, rect, style) {
45
+ traceCardOutline(parts.clip.clear(), rect, style).fill({ color: 16777215 });
115
46
  }
116
47
  function sync(container, item, context) {
117
48
  const parts = partsByContainer.get(container);
118
49
  if (!parts) return;
119
50
  positionShell(parts.root, item);
120
51
  const { width, height } = item.frame;
121
- const selected = context.selectedIds.has(item.id);
122
- const hovered = context.hoveredId === item.id;
123
52
  const detail = detailFor(context.zoom);
53
+ const elevation = cardElevation(context, item.id);
54
+ syncCardTag(parts.tag, {
55
+ glyph: "file",
56
+ text: fileMetaLine(item.props.src, item.props.snapshot?.size),
57
+ width
58
+ }, context);
124
59
  const key = context.assetKey(item);
125
60
  const texture = key ? context.getTexture(key) : null;
126
61
  const coverFailed = Boolean(key && !texture && context.hasError(key));
@@ -136,30 +71,30 @@ function sync(container, item, context) {
136
71
  key ?? "",
137
72
  width,
138
73
  height,
139
- selected,
140
- hovered,
141
74
  detail,
75
+ elevation,
142
76
  texture ? `${texture.width}x${texture.height}` : "none",
143
77
  coverFailed,
144
78
  fileState,
145
79
  kind,
146
80
  category,
147
- context.colorScheme,
148
- context.palette.surface,
149
- context.palette.hover,
81
+ context.themeKey,
150
82
  accent
151
83
  ].join("|");
152
84
  if (visualSig !== parts.visualSig) {
153
85
  parts.visualSig = visualSig;
154
- syncClip(parts, width, height);
155
- parts.plate.clear();
156
- parts.plate.roundRect(0, 0, width, height, RADIUS).fill({
157
- color: context.palette.surface,
158
- alpha: .98
159
- }).roundRect(0, 0, width, height, RADIUS).stroke({
160
- color: selected ? context.palette.brand : hovered ? context.palette.muted : context.palette.border,
161
- width: selected ? 2 : 1,
162
- alpha: selected ? .95 : .85
86
+ const rect = {
87
+ x: 0,
88
+ y: 0,
89
+ width,
90
+ height
91
+ };
92
+ syncClip(parts, rect, context.style);
93
+ drawCardSurface(parts.plate.clear(), rect, context.style, {
94
+ fill: context.palette.surface,
95
+ border: context.palette.border,
96
+ borderAlpha: .85,
97
+ elevation
163
98
  });
164
99
  if (fileState !== "ok") {
165
100
  const dash = Math.max(6, width / MAX_DASHES / 2);
@@ -178,8 +113,7 @@ function sync(container, item, context) {
178
113
  if (showCover && texture) {
179
114
  if (parts.cover.texture !== texture) parts.cover.texture = texture;
180
115
  layoutCover(parts.cover, width, band, texture);
181
- parts.coverMask.clear().roundRect(0, 0, width, band, RADIUS).fill({ color: 16777215 });
182
- parts.coverMask.rect(0, band - RADIUS, width, RADIUS).fill({ color: 16777215 });
116
+ parts.coverMask.clear().rect(0, 0, width, band).fill({ color: 16777215 });
183
117
  }
184
118
  parts.cover.visible = showCover;
185
119
  parts.coverMask.visible = showCover;
@@ -187,13 +121,8 @@ function sync(container, item, context) {
187
121
  color: accent,
188
122
  alpha: .55
189
123
  });
190
- parts.title.visible = detail !== "plate";
191
124
  parts.excerpt.visible = detail === "full";
192
- parts.typeMark.visible = kind === "blank" && detail !== "plate";
193
- }
194
- if (detail === "plate") {
195
- parts.typeMark.visible = false;
196
- return;
125
+ parts.typeMark.visible = kind === "blank";
197
126
  }
198
127
  const title = item.props.snapshot?.title || fileStem(item.props.src);
199
128
  const excerpt = item.props.snapshot?.excerpt ?? "";
@@ -211,10 +140,13 @@ function sync(container, item, context) {
211
140
  kind,
212
141
  context.palette.text,
213
142
  context.palette.muted,
143
+ context.style.font,
214
144
  accent
215
145
  ].join("|");
216
146
  if (textSig === parts.textSig) return;
217
147
  parts.textSig = textSig;
148
+ parts.title.style.fontFamily = context.style.font;
149
+ parts.excerpt.style.fontFamily = context.style.font;
218
150
  const top = band > 0 ? band + PADDING * .8 : PADDING;
219
151
  const contentBottom = height - PADDING;
220
152
  let cursor = top;
@@ -301,8 +233,10 @@ const fileCardRenderer = {
301
233
  });
302
234
  body.mask = clip;
303
235
  body.addChild(cover, coverMask, typeMark, title, excerpt);
304
- root.addChild(plate, body, clip);
236
+ const tag = createCardTag();
237
+ root.addChild(plate, body, clip, tag.root);
305
238
  partsByContainer.set(root, {
239
+ tag,
306
240
  root,
307
241
  plate,
308
242
  body,
@@ -324,6 +258,10 @@ const fileCardRenderer = {
324
258
  update: (container, item, context) => {
325
259
  if (item.type === "file") sync(container, item, context);
326
260
  },
261
+ zoom: (container, zoom) => {
262
+ const tag = partsByContainer.get(container)?.tag;
263
+ if (tag) zoomCardTag(tag, zoom);
264
+ },
327
265
  renderFar: (graphics, item, context) => {
328
266
  const category = fileCategory(item.type === "file" ? item.props.src : "", item.type === "file" ? item.props.snapshot?.mimeType : void 0);
329
267
  drawFarPlate(graphics, item.frame, {
@@ -342,4 +280,4 @@ function isFileItem(item) {
342
280
  return item.type === "file";
343
281
  }
344
282
  //#endregion
345
- export { containCoverRect, ellipsizeWrappedLines, fileCardRenderer, fitLineWithEllipsis, fitTextToLines, isFileItem };
283
+ export { fileCardRenderer, isFileItem };
@@ -1,9 +1,9 @@
1
1
  import { BOARD_FONT_STACK } from "../../../protocol/dist/board-constants.js";
2
2
  import { itemAlpha, itemColor } from "../palette.js";
3
3
  import { syncTextResolution } from "../text-resolution.js";
4
+ import { dashPattern, roundRectPoints, tracePolyline, trimPolyline } from "../stroke.js";
4
5
  import { createLabel, positionShell } from "./base-card-renderer.js";
5
6
  import { drawFarPlate } from "./far-plate.js";
6
- import { dashPattern, roundRectPoints, tracePolyline, trimPolyline } from "../stroke.js";
7
7
  import { Container, Graphics } from "pixi.js";
8
8
  //#region src/board/render/renderers/frame-card-renderer.ts
9
9
  const partsByContainer = /* @__PURE__ */ new WeakMap();
@@ -14,13 +14,10 @@ function sync(container, item, context) {
14
14
  const { frame, style, props, locked } = item;
15
15
  positionShell(parts.root, item);
16
16
  syncTextResolution(parts.label, parts, context.zoom);
17
- const selected = context.selectedIds.has(item.id);
18
- const hovered = context.hoveredId === item.id;
19
17
  const fill = itemColor(context, style.fill ?? style.stroke, "neutral", "fill");
20
18
  const fillAlpha = (style.fillOpacity ?? (style.fill ? 1 : DEFAULT_WASH)) * itemAlpha(context, style.fill ?? style.stroke);
21
19
  const baseStroke = itemColor(context, style.stroke, "neutral");
22
- const stroke = selected ? context.palette.brand : hovered && style.stroke ? context.palette.muted : baseStroke;
23
- const alpha = (selected || style.stroke ? 1 : hovered ? .85 : .55) * itemAlpha(context, style.stroke);
20
+ const alpha = (style.stroke ? 1 : .55) * itemAlpha(context, style.stroke);
24
21
  const radius = style.radius ?? 4;
25
22
  const strokeWidth = style.strokeWidth ?? 1.5;
26
23
  const decorated = props.decoration !== "none";
@@ -28,11 +25,9 @@ function sync(container, item, context) {
28
25
  props.decoration,
29
26
  frame.width,
30
27
  frame.height,
31
- selected,
32
- hovered,
33
28
  fill,
34
29
  fillAlpha,
35
- stroke,
30
+ baseStroke,
36
31
  alpha,
37
32
  radius,
38
33
  strokeWidth,
@@ -46,15 +41,14 @@ function sync(container, item, context) {
46
41
  color: fill,
47
42
  alpha: fillAlpha
48
43
  });
49
- const width = selected ? Math.max(2, strokeWidth) : strokeWidth;
50
- const pattern = decorated ? dashPattern(style.dash, width) : null;
51
- const trim = decorated ? style.trim ?? 1 : 1;
52
- if ((decorated && strokeWidth > 0 || selected || hovered) && trim > 0) {
44
+ const pattern = dashPattern(style.dash, strokeWidth);
45
+ const trim = style.trim ?? 1;
46
+ if (decorated && strokeWidth > 0 && trim > 0) {
53
47
  if (pattern || trim < 1) tracePolyline(parts.box, trimPolyline(roundRectPoints(frame.width, frame.height, radius), trim), pattern);
54
48
  else parts.box.roundRect(0, 0, frame.width, frame.height, radius);
55
49
  parts.box.stroke({
56
- color: stroke,
57
- width,
50
+ color: baseStroke,
51
+ width: strokeWidth,
58
52
  alpha,
59
53
  cap: pattern || trim < 1 ? "round" : "butt",
60
54
  join: "round"
@@ -64,11 +58,16 @@ function sync(container, item, context) {
64
58
  parts.label.visible = decorated;
65
59
  const title = locked ? `🔒 ${props.label}`.trim() : props.label;
66
60
  const labelColor = style.stroke ? baseStroke : context.palette.muted;
67
- const textSig = [title, labelColor].join("|");
61
+ const textSig = [
62
+ title,
63
+ labelColor,
64
+ context.style.font
65
+ ].join("|");
68
66
  if (textSig !== parts.textSig) {
69
67
  parts.textSig = textSig;
70
68
  parts.label.text = title;
71
69
  parts.label.style.fill = labelColor;
70
+ parts.label.style.fontFamily = context.style.font;
72
71
  }
73
72
  parts.label.position.set(4, -18);
74
73
  }