viteboard 0.1.1 → 0.1.2
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.
- package/content/docs/index.md +1 -1
- package/content/docs/product-roadmap.board.json +4 -4
- package/content/machine/board-v1.schema.json +138 -0
- package/content/machine/example.board.json +217 -0
- package/content/machine/guide.md +270 -0
- package/content/machine/llms.txt +31 -0
- package/content/machine/viteboard.json +62 -0
- package/dist/assets/index-D2W76uu9.js +96 -0
- package/dist/assets/index-rMkUjGXP.css +1 -0
- package/dist/index.html +4 -2
- package/package.json +10 -12
- package/vite.config.ts +619 -38
- package/dist/assets/index-C30zRPsW.js +0 -60
- package/dist/assets/index-e5aqtN8W.css +0 -1
- package/index.html +0 -13
- package/src/app/AppController.ts +0 -962
- package/src/app/BoardState.ts +0 -130
- package/src/app/ClipboardService.ts +0 -159
- package/src/app/CommandManager.ts +0 -66
- package/src/app/ElementActions.ts +0 -152
- package/src/app/EmbedRegionSelector.ts +0 -103
- package/src/app/ExportPng.ts +0 -107
- package/src/app/ImageInsertService.ts +0 -141
- package/src/app/KeyboardShortcuts.ts +0 -124
- package/src/app/main.ts +0 -6
- package/src/board/BoardPreview.ts +0 -189
- package/src/board/BoardService.ts +0 -222
- package/src/canvas/CanvasRenderer.ts +0 -253
- package/src/canvas/CanvasSurface.ts +0 -70
- package/src/canvas/HitTester.ts +0 -110
- package/src/canvas/ImageCache.ts +0 -123
- package/src/canvas/PerformanceMonitor.ts +0 -31
- package/src/canvas/RenderScheduler.ts +0 -26
- package/src/canvas/Viewport.ts +0 -77
- package/src/content/ContentApi.ts +0 -258
- package/src/content/Markdown.ts +0 -431
- package/src/content/MarkdownView.ts +0 -70
- package/src/editor/DocEditor.ts +0 -799
- package/src/editor/htmlToMarkdown.ts +0 -333
- package/src/elements/renderElement.ts +0 -598
- package/src/elements/types.ts +0 -119
- package/src/shell/Shell.ts +0 -2948
- package/src/shell/Sidebar.ts +0 -1651
- package/src/storage/AssetStore.ts +0 -86
- package/src/storage/BoardSerializer.ts +0 -114
- package/src/storage/ImportExportService.ts +0 -153
- package/src/storage/IndexedDbStore.ts +0 -92
- package/src/storage/LocalBoardStore.ts +0 -104
- package/src/styles.css +0 -3332
- package/src/templates/handoffTemplate.ts +0 -34
- package/src/templates/helpers.ts +0 -132
- package/src/templates/index.ts +0 -79
- package/src/templates/journeyMapTemplate.ts +0 -52
- package/src/templates/opportunityTreeTemplate.ts +0 -45
- package/src/templates/personaTemplate.ts +0 -41
- package/src/templates/prioritizationMatrixTemplate.ts +0 -44
- package/src/templates/requirementsFlowTemplate.ts +0 -45
- package/src/templates/screenshotReviewTemplate.ts +0 -52
- package/src/templates/systemDiagramTemplate.ts +0 -41
- package/src/templates/workflowTemplate.ts +0 -35
- package/src/testing/StressTestGenerator.ts +0 -134
- package/src/testing/StressTestPanel.ts +0 -64
- package/src/tools/ArrowTool.ts +0 -97
- package/src/tools/ImageTool.ts +0 -39
- package/src/tools/PanTool.ts +0 -34
- package/src/tools/SelectTool.ts +0 -377
- package/src/tools/ShapeTool.ts +0 -106
- package/src/tools/StickyTool.ts +0 -69
- package/src/tools/TaskTool.ts +0 -52
- package/src/tools/TextTool.ts +0 -45
- package/src/tools/ToolContext.ts +0 -51
- package/src/tools/ToolController.ts +0 -178
- package/src/ui/Modal.ts +0 -58
- package/src/ui/PerformanceOverlay.ts +0 -75
- package/src/ui/StorageManager.ts +0 -94
- package/src/ui/TemplatePicker.ts +0 -67
- package/src/ui/TextEditorOverlay.ts +0 -262
- package/src/ui/Toolbar.ts +0 -189
- package/src/ui/TopControls.ts +0 -137
- package/src/ui/icons.ts +0 -56
- package/tsconfig.json +0 -19
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import type { BoardElement } from "../elements/types";
|
|
2
|
-
import { arrow, beginTemplate, label, palette, rect } from "./helpers";
|
|
3
|
-
|
|
4
|
-
/** A neutral collaboration map for decisions, ownership, and feedback. */
|
|
5
|
-
export function handoffTemplate(
|
|
6
|
-
ox: number,
|
|
7
|
-
oy: number,
|
|
8
|
-
theme: "light" | "dark",
|
|
9
|
-
baseZ: number
|
|
10
|
-
): BoardElement[] {
|
|
11
|
-
const p = palette(theme);
|
|
12
|
-
beginTemplate(baseZ);
|
|
13
|
-
const els: BoardElement[] = [];
|
|
14
|
-
els.push(label(ox, oy, 800, "Handoff map", 26, p.heading, "700"));
|
|
15
|
-
const cards = [
|
|
16
|
-
["Explore", "#dbeafe"],
|
|
17
|
-
["Decide", "#fef3c7"],
|
|
18
|
-
["Act", "#dcfce7"],
|
|
19
|
-
] as const;
|
|
20
|
-
cards.forEach(([text, fill], index) => {
|
|
21
|
-
els.push(rect(ox + index * 270, oy + 150, 170, 92, {
|
|
22
|
-
fill, stroke: p.cardStroke, strokeWidth: 1.5, borderRadius: 12,
|
|
23
|
-
shadow: true, fontSize: 16, fontWeight: "650", textColor: "#1f2933",
|
|
24
|
-
}, text));
|
|
25
|
-
});
|
|
26
|
-
els.push(arrow(ox + 170, oy + 196, ox + 270, oy + 196, p.accent, { label: "proposal" }));
|
|
27
|
-
els.push(arrow(ox + 440, oy + 196, ox + 540, oy + 196, p.accent, { label: "owner | outcome", sides: true, arrowStart: true }));
|
|
28
|
-
els.push(arrow(ox + 625, oy + 245, ox + 355, oy + 330, "#9ca3af", { label: "feedback", dashed: true }));
|
|
29
|
-
els.push(rect(ox + 265, oy + 320, 180, 70, {
|
|
30
|
-
fill: p.zone, stroke: p.cardStroke, strokeWidth: 1.5, borderRadius: 10,
|
|
31
|
-
fontSize: 14, textColor: p.text,
|
|
32
|
-
}, "Review signal"));
|
|
33
|
-
return els;
|
|
34
|
-
}
|
package/src/templates/helpers.ts
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import { createElement, type BoardElement, type ElementStyle } from "../elements/types";
|
|
2
|
-
|
|
3
|
-
export type TemplateBuild = {
|
|
4
|
-
name: string;
|
|
5
|
-
elements: BoardElement[];
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
export type Palette = {
|
|
9
|
-
card: string;
|
|
10
|
-
cardStroke: string;
|
|
11
|
-
heading: string;
|
|
12
|
-
text: string;
|
|
13
|
-
accent: string;
|
|
14
|
-
sticky: string;
|
|
15
|
-
zone: string;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export function palette(theme: "light" | "dark"): Palette {
|
|
19
|
-
if (theme === "dark") {
|
|
20
|
-
return {
|
|
21
|
-
card: "#1e2126",
|
|
22
|
-
cardStroke: "#3a3f46",
|
|
23
|
-
heading: "#f2f2f2",
|
|
24
|
-
text: "#c8cdd4",
|
|
25
|
-
accent: "#4dabf7",
|
|
26
|
-
sticky: "#fef08a",
|
|
27
|
-
zone: "#22262c",
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
return {
|
|
31
|
-
card: "#ffffff",
|
|
32
|
-
cardStroke: "#d8dade",
|
|
33
|
-
heading: "#1f2933",
|
|
34
|
-
text: "#4b5563",
|
|
35
|
-
accent: "#6e6e6e",
|
|
36
|
-
sticky: "#fef08a",
|
|
37
|
-
zone: "#f0f0ed",
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
let z = 0;
|
|
42
|
-
|
|
43
|
-
export function beginTemplate(baseZ: number): void {
|
|
44
|
-
z = baseZ;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function rect(
|
|
48
|
-
x: number,
|
|
49
|
-
y: number,
|
|
50
|
-
w: number,
|
|
51
|
-
h: number,
|
|
52
|
-
style: ElementStyle,
|
|
53
|
-
text = ""
|
|
54
|
-
): BoardElement {
|
|
55
|
-
return createElement("rectangle", {
|
|
56
|
-
x, y, width: w, height: h,
|
|
57
|
-
zIndex: ++z,
|
|
58
|
-
style: { borderRadius: 10, ...style },
|
|
59
|
-
data: { text },
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function label(
|
|
64
|
-
x: number,
|
|
65
|
-
y: number,
|
|
66
|
-
w: number,
|
|
67
|
-
text: string,
|
|
68
|
-
fontSize: number,
|
|
69
|
-
color: string,
|
|
70
|
-
fontWeight = "600"
|
|
71
|
-
): BoardElement {
|
|
72
|
-
return createElement("text", {
|
|
73
|
-
x, y, width: w, height: fontSize * 1.6,
|
|
74
|
-
zIndex: ++z,
|
|
75
|
-
style: { fontSize, textColor: color, fontWeight },
|
|
76
|
-
data: { text },
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export function sticky(
|
|
81
|
-
x: number,
|
|
82
|
-
y: number,
|
|
83
|
-
text: string,
|
|
84
|
-
fill: string,
|
|
85
|
-
w = 180,
|
|
86
|
-
h = 120
|
|
87
|
-
): BoardElement {
|
|
88
|
-
return createElement("sticky", {
|
|
89
|
-
x, y, width: w, height: h,
|
|
90
|
-
zIndex: ++z,
|
|
91
|
-
style: { fill, borderRadius: 12, shadow: true, fontSize: 13, textColor: "#1f2933" },
|
|
92
|
-
data: { text },
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export function arrow(
|
|
97
|
-
x1: number,
|
|
98
|
-
y1: number,
|
|
99
|
-
x2: number,
|
|
100
|
-
y2: number,
|
|
101
|
-
stroke: string,
|
|
102
|
-
options: { label?: string; dashed?: boolean; arrowStart?: boolean; sides?: boolean } = {}
|
|
103
|
-
): BoardElement {
|
|
104
|
-
return createElement("arrow", {
|
|
105
|
-
x: x1, y: y1, width: x2 - x1, height: y2 - y1,
|
|
106
|
-
zIndex: ++z,
|
|
107
|
-
style: {
|
|
108
|
-
stroke,
|
|
109
|
-
strokeWidth: 2,
|
|
110
|
-
arrowEnd: true,
|
|
111
|
-
arrowStart: options.arrowStart,
|
|
112
|
-
strokeDash: options.dashed ? [8, 6] : undefined,
|
|
113
|
-
},
|
|
114
|
-
data: { text: options.label ?? "", labelPosition: options.sides ? "sides" : "center" },
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export function ellipseEl(
|
|
119
|
-
x: number,
|
|
120
|
-
y: number,
|
|
121
|
-
w: number,
|
|
122
|
-
h: number,
|
|
123
|
-
style: ElementStyle,
|
|
124
|
-
text = ""
|
|
125
|
-
): BoardElement {
|
|
126
|
-
return createElement("ellipse", {
|
|
127
|
-
x, y, width: w, height: h,
|
|
128
|
-
zIndex: ++z,
|
|
129
|
-
style,
|
|
130
|
-
data: { text },
|
|
131
|
-
});
|
|
132
|
-
}
|
package/src/templates/index.ts
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import type { BoardElement } from "../elements/types";
|
|
2
|
-
import { personaTemplate } from "./personaTemplate";
|
|
3
|
-
import { journeyMapTemplate } from "./journeyMapTemplate";
|
|
4
|
-
import { prioritizationMatrixTemplate } from "./prioritizationMatrixTemplate";
|
|
5
|
-
import { requirementsFlowTemplate } from "./requirementsFlowTemplate";
|
|
6
|
-
import { systemDiagramTemplate } from "./systemDiagramTemplate";
|
|
7
|
-
import { opportunityTreeTemplate } from "./opportunityTreeTemplate";
|
|
8
|
-
import { screenshotReviewTemplate } from "./screenshotReviewTemplate";
|
|
9
|
-
import { workflowTemplate } from "./workflowTemplate";
|
|
10
|
-
import { handoffTemplate } from "./handoffTemplate";
|
|
11
|
-
|
|
12
|
-
export type TemplateDef = {
|
|
13
|
-
id: string;
|
|
14
|
-
name: string;
|
|
15
|
-
description: string;
|
|
16
|
-
build: (
|
|
17
|
-
ox: number,
|
|
18
|
-
oy: number,
|
|
19
|
-
theme: "light" | "dark",
|
|
20
|
-
baseZ: number
|
|
21
|
-
) => BoardElement[];
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
export const TEMPLATES: TemplateDef[] = [
|
|
25
|
-
{
|
|
26
|
-
id: "persona",
|
|
27
|
-
name: "User Persona Card",
|
|
28
|
-
description: "Name, role, goals, pain points, JTBD, success criteria.",
|
|
29
|
-
build: personaTemplate,
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
id: "journey",
|
|
33
|
-
name: "User Journey Map",
|
|
34
|
-
description: "Awareness → Retention stages with sticky note areas.",
|
|
35
|
-
build: journeyMapTemplate,
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
id: "matrix",
|
|
39
|
-
name: "Prioritization Matrix",
|
|
40
|
-
description: "Impact vs effort: quick wins, strategic bets, fill-ins, avoid.",
|
|
41
|
-
build: prioritizationMatrixTemplate,
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
id: "requirements",
|
|
45
|
-
name: "Requirements Flow",
|
|
46
|
-
description: "Problem, user, context, solution, edge cases, risks.",
|
|
47
|
-
build: requirementsFlowTemplate,
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
id: "system",
|
|
51
|
-
name: "System Diagram Starter",
|
|
52
|
-
description: "User, frontend, API, database, external services + arrows.",
|
|
53
|
-
build: systemDiagramTemplate,
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
id: "ost",
|
|
57
|
-
name: "Opportunity Solution Tree",
|
|
58
|
-
description: "Outcome, opportunities, solutions, experiments, signals.",
|
|
59
|
-
build: opportunityTreeTemplate,
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
id: "screenshot-review",
|
|
63
|
-
name: "Screenshot Review Board",
|
|
64
|
-
description: "Screenshot zone with observations, problems, UX notes.",
|
|
65
|
-
build: screenshotReviewTemplate,
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
id: "workflow",
|
|
69
|
-
name: "Workflow Map",
|
|
70
|
-
description: "Labeled handoffs, a dashed feedback loop, and editable steps.",
|
|
71
|
-
build: workflowTemplate,
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
id: "handoff",
|
|
75
|
-
name: "Handoff Map",
|
|
76
|
-
description: "A shared decision flow with ownership, outcomes, and feedback.",
|
|
77
|
-
build: handoffTemplate,
|
|
78
|
-
},
|
|
79
|
-
];
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import type { BoardElement } from "../elements/types";
|
|
2
|
-
import { palette, beginTemplate, rect, label, sticky, arrow } from "./helpers";
|
|
3
|
-
|
|
4
|
-
const STAGES = [
|
|
5
|
-
"Awareness",
|
|
6
|
-
"Discovery",
|
|
7
|
-
"Evaluation",
|
|
8
|
-
"Onboarding",
|
|
9
|
-
"Activation",
|
|
10
|
-
"Retention",
|
|
11
|
-
];
|
|
12
|
-
|
|
13
|
-
const STAGE_COLORS = ["#93c5fd", "#a5b4fc", "#d8b4fe", "#f9a8d4", "#fdba74", "#86efac"];
|
|
14
|
-
|
|
15
|
-
export function journeyMapTemplate(
|
|
16
|
-
ox: number,
|
|
17
|
-
oy: number,
|
|
18
|
-
theme: "light" | "dark",
|
|
19
|
-
baseZ: number
|
|
20
|
-
): BoardElement[] {
|
|
21
|
-
const p = palette(theme);
|
|
22
|
-
beginTemplate(baseZ);
|
|
23
|
-
const els: BoardElement[] = [];
|
|
24
|
-
const colW = 230;
|
|
25
|
-
const totalW = STAGES.length * colW + 56;
|
|
26
|
-
|
|
27
|
-
els.push(rect(ox, oy, totalW, 640, { fill: p.card, stroke: p.cardStroke, strokeWidth: 1.5, borderRadius: 16, shadow: true }));
|
|
28
|
-
els.push(label(ox + 28, oy + 22, 500, "User Journey Map", 22, p.heading, "700"));
|
|
29
|
-
|
|
30
|
-
STAGES.forEach((stage, i) => {
|
|
31
|
-
const x = ox + 28 + i * colW;
|
|
32
|
-
const y = oy + 70;
|
|
33
|
-
els.push(rect(x, y, colW - 16, 48, { fill: STAGE_COLORS[i], borderRadius: 10 }, stage));
|
|
34
|
-
if (i < STAGES.length - 1) {
|
|
35
|
-
els.push(arrow(x + colW - 14, y + 24, x + colW - 2, y + 24, p.heading));
|
|
36
|
-
}
|
|
37
|
-
// sticky zones per stage
|
|
38
|
-
els.push(rect(x, y + 64, colW - 16, 150, { fill: p.zone, borderRadius: 10 }, ""));
|
|
39
|
-
els.push(label(x + 10, y + 70, colW - 36, "Actions", 12, p.accent));
|
|
40
|
-
els.push(sticky(x + 14, y + 96, "", "#fef08a", colW - 44, 100));
|
|
41
|
-
|
|
42
|
-
els.push(rect(x, y + 226, colW - 16, 150, { fill: p.zone, borderRadius: 10 }, ""));
|
|
43
|
-
els.push(label(x + 10, y + 232, colW - 36, "Pain points", 12, p.accent));
|
|
44
|
-
els.push(sticky(x + 14, y + 258, "", "#fca5a5", colW - 44, 100));
|
|
45
|
-
|
|
46
|
-
els.push(rect(x, y + 388, colW - 16, 150, { fill: p.zone, borderRadius: 10 }, ""));
|
|
47
|
-
els.push(label(x + 10, y + 394, colW - 36, "Opportunities", 12, p.accent));
|
|
48
|
-
els.push(sticky(x + 14, y + 420, "", "#86efac", colW - 44, 100));
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
return els;
|
|
52
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import type { BoardElement } from "../elements/types";
|
|
2
|
-
import { palette, beginTemplate, rect, label, sticky, arrow } from "./helpers";
|
|
3
|
-
|
|
4
|
-
export function opportunityTreeTemplate(
|
|
5
|
-
ox: number,
|
|
6
|
-
oy: number,
|
|
7
|
-
theme: "light" | "dark",
|
|
8
|
-
baseZ: number
|
|
9
|
-
): BoardElement[] {
|
|
10
|
-
const p = palette(theme);
|
|
11
|
-
beginTemplate(baseZ);
|
|
12
|
-
const els: BoardElement[] = [];
|
|
13
|
-
|
|
14
|
-
els.push(label(ox + 280, oy - 40, 500, "Opportunity Solution Tree", 22, p.heading, "700"));
|
|
15
|
-
|
|
16
|
-
// outcome
|
|
17
|
-
els.push(rect(ox + 300, oy, 280, 80, { fill: "#a5b4fc", borderRadius: 12, shadow: true, fontSize: 15, fontWeight: "600", textColor: "#1f2933" }, "Desired outcome"));
|
|
18
|
-
|
|
19
|
-
// opportunities
|
|
20
|
-
const oppY = oy + 160;
|
|
21
|
-
const oppXs = [ox, ox + 300, ox + 600];
|
|
22
|
-
for (const x of oppXs) {
|
|
23
|
-
els.push(sticky(x, oppY, "Opportunity", "#93c5fd", 220, 100));
|
|
24
|
-
els.push(arrow(ox + 440, oy + 85, x + 110, oppY - 5, p.heading));
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// solutions
|
|
28
|
-
const solY = oppY + 180;
|
|
29
|
-
for (const x of oppXs) {
|
|
30
|
-
els.push(sticky(x, solY, "Solution", "#86efac", 220, 100));
|
|
31
|
-
els.push(arrow(x + 110, oppY + 105, x + 110, solY - 5, p.heading));
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// experiments + signals
|
|
35
|
-
const expY = solY + 180;
|
|
36
|
-
for (const x of oppXs) {
|
|
37
|
-
els.push(sticky(x, expY, "Experiment", "#fdba74", 220, 90));
|
|
38
|
-
els.push(arrow(x + 110, solY + 105, x + 110, expY - 5, p.heading));
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
els.push(label(ox, expY + 120, 400, "Signals / metrics", 14, p.accent));
|
|
42
|
-
els.push(sticky(ox, expY + 150, "What signal tells us this is working?", "#fef08a", 380, 100));
|
|
43
|
-
|
|
44
|
-
return els;
|
|
45
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import type { BoardElement } from "../elements/types";
|
|
2
|
-
import { palette, beginTemplate, rect, label, sticky } from "./helpers";
|
|
3
|
-
|
|
4
|
-
export function personaTemplate(
|
|
5
|
-
ox: number,
|
|
6
|
-
oy: number,
|
|
7
|
-
theme: "light" | "dark",
|
|
8
|
-
baseZ: number
|
|
9
|
-
): BoardElement[] {
|
|
10
|
-
const p = palette(theme);
|
|
11
|
-
beginTemplate(baseZ);
|
|
12
|
-
const els: BoardElement[] = [];
|
|
13
|
-
|
|
14
|
-
els.push(rect(ox, oy, 760, 560, { fill: p.card, stroke: p.cardStroke, strokeWidth: 1.5, borderRadius: 16, shadow: true }));
|
|
15
|
-
els.push(label(ox + 28, oy + 22, 400, "User Persona", 22, p.heading, "700"));
|
|
16
|
-
els.push(label(ox + 28, oy + 60, 300, "Name", 13, p.accent));
|
|
17
|
-
els.push(sticky(ox + 28, oy + 86, "Jordan Lee", p.sticky, 200, 64));
|
|
18
|
-
els.push(label(ox + 260, oy + 60, 300, "Role", 13, p.accent));
|
|
19
|
-
els.push(sticky(ox + 260, oy + 86, "Senior Product Manager", "#a5b4fc", 220, 64));
|
|
20
|
-
els.push(label(ox + 510, oy + 60, 300, "Buying trigger", 13, p.accent));
|
|
21
|
-
els.push(sticky(ox + 510, oy + 86, "Team scaling pains", "#fdba74", 220, 64));
|
|
22
|
-
|
|
23
|
-
const fields: [string, string, string][] = [
|
|
24
|
-
["Goals", "Ship the right features faster", "#86efac"],
|
|
25
|
-
["Pain points", "Scattered docs, unclear priorities", "#fca5a5"],
|
|
26
|
-
["Jobs to be done", "Align team on what to build next", "#93c5fd"],
|
|
27
|
-
["Current workaround", "Spreadsheets + slide decks", "#d8b4fe"],
|
|
28
|
-
["Success criteria", "Stakeholders aligned in 1 meeting", "#f9a8d4"],
|
|
29
|
-
["Key quote", '"I need clarity, not more tools."', "#fef08a"],
|
|
30
|
-
];
|
|
31
|
-
fields.forEach(([title, text, color], i) => {
|
|
32
|
-
const col = i % 3;
|
|
33
|
-
const row = Math.floor(i / 3);
|
|
34
|
-
const x = ox + 28 + col * 242;
|
|
35
|
-
const y = oy + 188 + row * 178;
|
|
36
|
-
els.push(label(x, y, 230, title, 13, p.accent));
|
|
37
|
-
els.push(sticky(x, y + 26, text, color, 220, 124));
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
return els;
|
|
41
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import type { BoardElement } from "../elements/types";
|
|
2
|
-
import { palette, beginTemplate, rect, label, sticky, arrow } from "./helpers";
|
|
3
|
-
|
|
4
|
-
export function prioritizationMatrixTemplate(
|
|
5
|
-
ox: number,
|
|
6
|
-
oy: number,
|
|
7
|
-
theme: "light" | "dark",
|
|
8
|
-
baseZ: number
|
|
9
|
-
): BoardElement[] {
|
|
10
|
-
const p = palette(theme);
|
|
11
|
-
beginTemplate(baseZ);
|
|
12
|
-
const els: BoardElement[] = [];
|
|
13
|
-
const size = 680;
|
|
14
|
-
|
|
15
|
-
els.push(rect(ox, oy, size + 120, size + 130, { fill: p.card, stroke: p.cardStroke, strokeWidth: 1.5, borderRadius: 16, shadow: true }));
|
|
16
|
-
els.push(label(ox + 28, oy + 22, 500, "Feature Prioritization Matrix", 22, p.heading, "700"));
|
|
17
|
-
|
|
18
|
-
const gx = ox + 80;
|
|
19
|
-
const gy = oy + 80;
|
|
20
|
-
const half = size / 2;
|
|
21
|
-
|
|
22
|
-
// quadrants
|
|
23
|
-
els.push(rect(gx, gy, half, half, { fill: "#86efac55", borderRadius: 8 }));
|
|
24
|
-
els.push(rect(gx + half, gy, half, half, { fill: "#93c5fd55", borderRadius: 8 }));
|
|
25
|
-
els.push(rect(gx, gy + half, half, half, { fill: "#fef08a55", borderRadius: 8 }));
|
|
26
|
-
els.push(rect(gx + half, gy + half, half, half, { fill: "#fca5a555", borderRadius: 8 }));
|
|
27
|
-
|
|
28
|
-
els.push(label(gx + 16, gy + 12, 300, "Quick wins", 16, p.heading, "700"));
|
|
29
|
-
els.push(label(gx + half + 16, gy + 12, 300, "Strategic bets", 16, p.heading, "700"));
|
|
30
|
-
els.push(label(gx + 16, gy + half + 12, 300, "Fill-ins", 16, p.heading, "700"));
|
|
31
|
-
els.push(label(gx + half + 16, gy + half + 12, 300, "Avoid", 16, p.heading, "700"));
|
|
32
|
-
|
|
33
|
-
// axes
|
|
34
|
-
els.push(arrow(gx - 24, gy + size, gx - 24, gy, p.heading));
|
|
35
|
-
els.push(label(gx - 64, gy + half - 12, 60, "Impact", 14, p.text, "600"));
|
|
36
|
-
els.push(arrow(gx, gy + size + 24, gx + size, gy + size + 24, p.heading));
|
|
37
|
-
els.push(label(gx + half - 30, gy + size + 34, 80, "Effort", 14, p.text, "600"));
|
|
38
|
-
|
|
39
|
-
// sample stickies
|
|
40
|
-
els.push(sticky(gx + 40, gy + 60, "Feature idea", "#fef08a", 150, 90));
|
|
41
|
-
els.push(sticky(gx + half + 40, gy + 60, "Big bet", "#a5b4fc", 150, 90));
|
|
42
|
-
|
|
43
|
-
return els;
|
|
44
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import type { BoardElement } from "../elements/types";
|
|
2
|
-
import { palette, beginTemplate, rect, label, sticky } from "./helpers";
|
|
3
|
-
|
|
4
|
-
const SECTIONS: [string, string][] = [
|
|
5
|
-
["Problem", "#fca5a5"],
|
|
6
|
-
["User", "#93c5fd"],
|
|
7
|
-
["Context", "#d8b4fe"],
|
|
8
|
-
["Current workflow", "#fdba74"],
|
|
9
|
-
["Proposed solution", "#86efac"],
|
|
10
|
-
["Edge cases", "#f9a8d4"],
|
|
11
|
-
["Acceptance criteria", "#a5b4fc"],
|
|
12
|
-
["Dependencies", "#fef08a"],
|
|
13
|
-
["Risks", "#fca5a5"],
|
|
14
|
-
];
|
|
15
|
-
|
|
16
|
-
export function requirementsFlowTemplate(
|
|
17
|
-
ox: number,
|
|
18
|
-
oy: number,
|
|
19
|
-
theme: "light" | "dark",
|
|
20
|
-
baseZ: number
|
|
21
|
-
): BoardElement[] {
|
|
22
|
-
const p = palette(theme);
|
|
23
|
-
beginTemplate(baseZ);
|
|
24
|
-
const els: BoardElement[] = [];
|
|
25
|
-
const cols = 3;
|
|
26
|
-
const cellW = 280;
|
|
27
|
-
const cellH = 210;
|
|
28
|
-
const w = cols * cellW + 56;
|
|
29
|
-
const rows = Math.ceil(SECTIONS.length / cols);
|
|
30
|
-
const h = rows * cellH + 100;
|
|
31
|
-
|
|
32
|
-
els.push(rect(ox, oy, w, h, { fill: p.card, stroke: p.cardStroke, strokeWidth: 1.5, borderRadius: 16, shadow: true }));
|
|
33
|
-
els.push(label(ox + 28, oy + 22, 500, "Product Requirements Flow", 22, p.heading, "700"));
|
|
34
|
-
|
|
35
|
-
SECTIONS.forEach(([title, color], i) => {
|
|
36
|
-
const col = i % cols;
|
|
37
|
-
const row = Math.floor(i / cols);
|
|
38
|
-
const x = ox + 28 + col * cellW;
|
|
39
|
-
const y = oy + 72 + row * cellH;
|
|
40
|
-
els.push(label(x, y, cellW - 30, `${i + 1}. ${title}`, 14, p.accent));
|
|
41
|
-
els.push(sticky(x, y + 30, "", color, cellW - 28, cellH - 60));
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
return els;
|
|
45
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import type { BoardElement } from "../elements/types";
|
|
2
|
-
import { createElement } from "../elements/types";
|
|
3
|
-
import { palette, beginTemplate, rect, label, sticky } from "./helpers";
|
|
4
|
-
|
|
5
|
-
export function screenshotReviewTemplate(
|
|
6
|
-
ox: number,
|
|
7
|
-
oy: number,
|
|
8
|
-
theme: "light" | "dark",
|
|
9
|
-
baseZ: number
|
|
10
|
-
): BoardElement[] {
|
|
11
|
-
const p = palette(theme);
|
|
12
|
-
beginTemplate(baseZ);
|
|
13
|
-
const els: BoardElement[] = [];
|
|
14
|
-
|
|
15
|
-
els.push(rect(ox, oy, 1060, 620, { fill: p.card, stroke: p.cardStroke, strokeWidth: 1.5, borderRadius: 16, shadow: true }));
|
|
16
|
-
els.push(label(ox + 28, oy + 22, 500, "Screenshot Review", 22, p.heading, "700"));
|
|
17
|
-
|
|
18
|
-
// screenshot drop zone (placeholder image element with no asset = drop target visual)
|
|
19
|
-
els.push(label(ox + 28, oy + 64, 300, "Screenshot", 13, p.accent));
|
|
20
|
-
const placeholder = createElement("frame", {
|
|
21
|
-
x: ox + 28,
|
|
22
|
-
y: oy + 92,
|
|
23
|
-
width: 480,
|
|
24
|
-
height: 360,
|
|
25
|
-
zIndex: baseZ + 100,
|
|
26
|
-
style: { fill: p.zone, stroke: p.cardStroke, strokeWidth: 1.5, borderRadius: 12, fontSize: 13, textColor: p.text },
|
|
27
|
-
data: { text: "Paste a screenshot here (Cmd/Ctrl+V)" },
|
|
28
|
-
});
|
|
29
|
-
els.push(placeholder);
|
|
30
|
-
|
|
31
|
-
const sections: [string, string][] = [
|
|
32
|
-
["Observations", "#93c5fd"],
|
|
33
|
-
["Problems", "#fca5a5"],
|
|
34
|
-
["UX notes", "#d8b4fe"],
|
|
35
|
-
["Requirements", "#86efac"],
|
|
36
|
-
];
|
|
37
|
-
sections.forEach(([title, color], i) => {
|
|
38
|
-
const col = i % 2;
|
|
39
|
-
const row = Math.floor(i / 2);
|
|
40
|
-
const x = ox + 540 + col * 250;
|
|
41
|
-
const y = oy + 64 + row * 200;
|
|
42
|
-
els.push(label(x, y, 230, title, 13, p.accent));
|
|
43
|
-
els.push(sticky(x, y + 28, "", color, 230, 140));
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
els.push(label(ox + 28, oy + 470, 300, "Follow-up tasks", 13, p.accent));
|
|
47
|
-
els.push(sticky(ox + 28, oy + 498, "", "#fef08a", 480, 96));
|
|
48
|
-
els.push(label(ox + 540, oy + 470, 300, "Decision", 13, p.accent));
|
|
49
|
-
els.push(sticky(ox + 540, oy + 498, "", "#fdba74", 480, 96));
|
|
50
|
-
|
|
51
|
-
return els;
|
|
52
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import type { BoardElement } from "../elements/types";
|
|
2
|
-
import { palette, beginTemplate, rect, label, arrow, ellipseEl } from "./helpers";
|
|
3
|
-
|
|
4
|
-
export function systemDiagramTemplate(
|
|
5
|
-
ox: number,
|
|
6
|
-
oy: number,
|
|
7
|
-
theme: "light" | "dark",
|
|
8
|
-
baseZ: number
|
|
9
|
-
): BoardElement[] {
|
|
10
|
-
const p = palette(theme);
|
|
11
|
-
beginTemplate(baseZ);
|
|
12
|
-
const els: BoardElement[] = [];
|
|
13
|
-
|
|
14
|
-
els.push(label(ox, oy - 40, 400, "System Diagram", 22, p.heading, "700"));
|
|
15
|
-
|
|
16
|
-
const box = (x: number, y: number, w: number, h: number, fill: string, text: string) =>
|
|
17
|
-
rect(x, y, w, h, { fill, stroke: p.cardStroke, strokeWidth: 1.5, borderRadius: 12, shadow: true, fontSize: 15, fontWeight: "600", textColor: "#1f2933" }, text);
|
|
18
|
-
|
|
19
|
-
// user
|
|
20
|
-
els.push(ellipseEl(ox, oy + 130, 110, 110, { fill: "#f9a8d4", stroke: p.cardStroke, strokeWidth: 1.5, fontSize: 14, fontWeight: "600", textColor: "#1f2933" }, "User"));
|
|
21
|
-
|
|
22
|
-
// frontend
|
|
23
|
-
els.push(box(ox + 200, oy + 140, 180, 90, "#93c5fd", "Frontend"));
|
|
24
|
-
// api
|
|
25
|
-
els.push(box(ox + 470, oy + 140, 180, 90, "#a5b4fc", "API / Server"));
|
|
26
|
-
// database
|
|
27
|
-
els.push(box(ox + 740, oy + 60, 180, 90, "#86efac", "Database"));
|
|
28
|
-
// external service
|
|
29
|
-
els.push(box(ox + 740, oy + 200, 180, 90, "#fdba74", "External Service"));
|
|
30
|
-
// notifications
|
|
31
|
-
els.push(box(ox + 470, oy + 330, 180, 90, "#fef08a", "Notification Service"));
|
|
32
|
-
|
|
33
|
-
els.push(arrow(ox + 115, oy + 185, ox + 195, oy + 185, p.heading, { label: "uses" }));
|
|
34
|
-
els.push(arrow(ox + 385, oy + 185, ox + 465, oy + 185, p.heading, { label: "request | response", sides: true, arrowStart: true }));
|
|
35
|
-
els.push(arrow(ox + 655, oy + 165, ox + 735, oy + 110, p.heading, { label: "read" }));
|
|
36
|
-
els.push(arrow(ox + 655, oy + 205, ox + 735, oy + 240, p.heading, { label: "webhook", dashed: true }));
|
|
37
|
-
els.push(arrow(ox + 560, oy + 235, ox + 560, oy + 325, p.heading, { label: "event", dashed: true }));
|
|
38
|
-
els.push(arrow(ox + 470, oy + 375, ox + 80, oy + 245, p.heading, { label: "notify", dashed: true }));
|
|
39
|
-
|
|
40
|
-
return els;
|
|
41
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import type { BoardElement } from "../elements/types";
|
|
2
|
-
import { arrow, beginTemplate, label, palette, rect } from "./helpers";
|
|
3
|
-
|
|
4
|
-
/** A general-purpose process diagram that demonstrates labeled/dashed arrows. */
|
|
5
|
-
export function workflowTemplate(
|
|
6
|
-
ox: number,
|
|
7
|
-
oy: number,
|
|
8
|
-
theme: "light" | "dark",
|
|
9
|
-
baseZ: number
|
|
10
|
-
): BoardElement[] {
|
|
11
|
-
const p = palette(theme);
|
|
12
|
-
beginTemplate(baseZ);
|
|
13
|
-
const els: BoardElement[] = [];
|
|
14
|
-
els.push(label(ox, oy, 880, "Workflow map", 26, p.heading, "700"));
|
|
15
|
-
const steps = [
|
|
16
|
-
["Request", "#dbeafe"],
|
|
17
|
-
["Review", "#fef3c7"],
|
|
18
|
-
["Deliver", "#dcfce7"],
|
|
19
|
-
] as const;
|
|
20
|
-
for (let i = 0; i < steps.length; i++) {
|
|
21
|
-
const x = ox + i * 280;
|
|
22
|
-
els.push(rect(x, oy + 130, 180, 96, {
|
|
23
|
-
fill: steps[i][1], stroke: p.cardStroke, strokeWidth: 1.5, borderRadius: 12,
|
|
24
|
-
fontSize: 16, fontWeight: "650", textColor: "#1f2933", shadow: true,
|
|
25
|
-
}, steps[i][0]));
|
|
26
|
-
}
|
|
27
|
-
els.push(arrow(ox + 180, oy + 178, ox + 280, oy + 178, p.accent, { label: "approve" }));
|
|
28
|
-
els.push(arrow(ox + 460, oy + 178, ox + 560, oy + 178, p.accent, { label: "ship" }));
|
|
29
|
-
els.push(arrow(ox + 650, oy + 226, ox + 370, oy + 310, "#9ca3af", { label: "feedback", dashed: true }));
|
|
30
|
-
els.push(rect(ox + 280, oy + 300, 180, 72, {
|
|
31
|
-
fill: p.zone, stroke: p.cardStroke, strokeWidth: 1.5, borderRadius: 10,
|
|
32
|
-
fontSize: 14, textColor: p.text,
|
|
33
|
-
}, "Learn and iterate"));
|
|
34
|
-
return els;
|
|
35
|
-
}
|