konpeki 0.1.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.
- package/.agents/skills/authoring-visuals/SKILL.md +82 -0
- package/AGENTS.md +33 -0
- package/AUTHORING.md +233 -0
- package/LICENSE +201 -0
- package/README.md +70 -0
- package/SETUP.md +86 -0
- package/composition/README.md +166 -0
- package/composition/compile.ts +227 -0
- package/composition/document.ts +709 -0
- package/composition/schema.json +2992 -0
- package/composition/schema.ts +435 -0
- package/composition/theme-tokens.ts +16 -0
- package/composition/types.ts +286 -0
- package/composition/validate.ts +684 -0
- package/composition/vector.ts +143 -0
- package/composition/visualizations.ts +335 -0
- package/design/README.md +17 -0
- package/design/palettes/README.md +14 -0
- package/design/palettes/base.ts +14 -0
- package/design/palettes/candidates.ts +19 -0
- package/design/palettes/index.ts +78 -0
- package/design/review/color-theme.md +44 -0
- package/design/review/layout.md +16 -0
- package/design/review/text.md +18 -0
- package/design/review/typography.md +15 -0
- package/design/review/visuals.md +31 -0
- package/design/semantic-patterns.md +43 -0
- package/design/themes/README.md +22 -0
- package/design/themes/index.ts +13 -0
- package/design/visual-languages/technical-product.md +17 -0
- package/design/visual-review.md +88 -0
- package/docs/development.md +140 -0
- package/docs/workflow.md +61 -0
- package/index.html +17 -0
- package/lib/assets.d.ts +8 -0
- package/lib/charts.ts +18 -0
- package/lib/contrast.ts +16 -0
- package/lib/layouts.ts +50 -0
- package/lib/slide.tsx +42 -0
- package/lib/taste.ts +17 -0
- package/lib/text.tsx +89 -0
- package/lib/typeface.ts +44 -0
- package/package.json +85 -0
- package/runtime/konpeki.mjs +1685 -0
- package/scripts/migrate-react-page.ts +120 -0
- package/slides/README.md +153 -0
- package/slides/architecture/PROMPT.md +31 -0
- package/slides/architecture/index.tsx +102 -0
- package/slides/article-brief/PROMPT.md +35 -0
- package/slides/article-brief/index.tsx +71 -0
- package/slides/bar-chart/PROMPT.md +39 -0
- package/slides/bar-chart/index.tsx +97 -0
- package/slides/comparison/PROMPT.md +29 -0
- package/slides/comparison/index.tsx +95 -0
- package/slides/decision-memo/PROMPT.md +34 -0
- package/slides/decision-memo/index.tsx +85 -0
- package/slides/delivery-plan/PROMPT.md +45 -0
- package/slides/delivery-plan/index.tsx +105 -0
- package/slides/experiment/PROMPT.md +44 -0
- package/slides/experiment/index.tsx +127 -0
- package/slides/incident-workflow/PROMPT.md +57 -0
- package/slides/incident-workflow/index.tsx +78 -0
- package/slides/introducing-konpeki/PROMPT.md +19 -0
- package/slides/introducing-konpeki/README.md +54 -0
- package/slides/introducing-konpeki/SOURCE.md +20 -0
- package/slides/introducing-konpeki/author.ts +163 -0
- package/slides/introducing-konpeki/composition.json +3265 -0
- package/slides/line-chart/PROMPT.md +40 -0
- package/slides/line-chart/index.tsx +72 -0
- package/slides/migration/PROMPT.md +38 -0
- package/slides/migration/index.tsx +89 -0
- package/slides/og-images/PROMPT.md +21 -0
- package/slides/og-images/index.tsx +76 -0
- package/slides/product-introduction/PROMPT.md +24 -0
- package/slides/product-introduction/index.tsx +105 -0
- package/slides/research-brief/PROMPT.md +40 -0
- package/slides/research-brief/index.tsx +104 -0
- package/slides/results-explanation/PROMPT.md +32 -0
- package/slides/results-explanation/index.tsx +96 -0
- package/slides/retrospective/PROMPT.md +43 -0
- package/slides/retrospective/index.tsx +105 -0
- package/slides/sankey/PROMPT.md +11 -0
- package/slides/sankey/index.tsx +93 -0
- package/slides/teaching/PROMPT.md +45 -0
- package/slides/teaching/index.tsx +124 -0
- package/slides/vertical-bar-charts/PROMPT.md +13 -0
- package/slides/vertical-bar-charts/index.tsx +97 -0
- package/src/app/App.tsx +694 -0
- package/src/assets/konpeki-mark.png +0 -0
- package/src/components/Canvas.tsx +1168 -0
- package/src/components/DiagramTypeIcon.tsx +78 -0
- package/src/components/InspectorPanel.tsx +687 -0
- package/src/components/LeftPanel.tsx +107 -0
- package/src/components/PageSizePicker.tsx +30 -0
- package/src/components/Presentation.tsx +105 -0
- package/src/components/RightPanel.tsx +201 -0
- package/src/components/VectorOverflowWarning.tsx +46 -0
- package/src/components/WorkspaceChrome.tsx +199 -0
- package/src/components/ui.tsx +53 -0
- package/src/lib/examples/react-page-migration.json +1295 -0
- package/src/lib/examples.ts +42 -0
- package/src/lib/export-png.ts +101 -0
- package/src/lib/file-session.ts +74 -0
- package/src/lib/history.ts +53 -0
- package/src/lib/model.ts +188 -0
- package/src/lib/page-size.ts +24 -0
- package/src/lib/presentation.ts +17 -0
- package/src/lib/storage.ts +43 -0
- package/src/lib/theme.ts +25 -0
- package/src/lib/use-file-session.ts +162 -0
- package/src/main.tsx +26 -0
- package/src/styles/base.css +105 -0
- package/src/styles/canvas.css +268 -0
- package/src/styles/chrome.css +214 -0
- package/src/styles/component-previews.css +386 -0
- package/src/styles/feedback.css +71 -0
- package/src/styles/left-panel.css +125 -0
- package/src/styles/presentation.css +72 -0
- package/src/styles/right-panel.css +1172 -0
- package/src/styles/shell.css +247 -0
- package/vite.config.ts +6 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { DOMParser } from "@xmldom/xmldom";
|
|
2
|
+
import {
|
|
3
|
+
vectorElementKinds,
|
|
4
|
+
type VectorElement,
|
|
5
|
+
type VectorElementKind,
|
|
6
|
+
} from "./types.ts";
|
|
7
|
+
|
|
8
|
+
export const vectorAttributeNames = new Set([
|
|
9
|
+
"x", "y", "dx", "dy", "x1", "y1", "x2", "y2", "width", "height", "rx", "ry",
|
|
10
|
+
"cx", "cy", "r", "d", "points", "transform", "fill", "fill-opacity",
|
|
11
|
+
"stroke", "stroke-width", "stroke-opacity", "stroke-linecap", "stroke-linejoin",
|
|
12
|
+
"stroke-dasharray", "opacity", "font-family", "font-size", "font-weight",
|
|
13
|
+
"font-style", "text-anchor", "dominant-baseline", "vector-effect",
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
/** Remove a subtree, never leaving orphaned descendants. */
|
|
17
|
+
export function removeVectorElement(elements: VectorElement[], id: string) {
|
|
18
|
+
const removed = new Set([id]);
|
|
19
|
+
for (const element of elements) {
|
|
20
|
+
if (element.parentId && removed.has(element.parentId)) removed.add(element.id);
|
|
21
|
+
}
|
|
22
|
+
return elements.filter((element) => !removed.has(element.id));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Swap sibling subtrees while retaining parent-before-child serialization. */
|
|
26
|
+
export function moveVectorElement(elements: VectorElement[], id: string, direction: -1 | 1) {
|
|
27
|
+
const selected = elements.find((element) => element.id === id);
|
|
28
|
+
if (!selected) return elements;
|
|
29
|
+
const children = new Map<string | undefined, VectorElement[]>();
|
|
30
|
+
for (const element of elements) {
|
|
31
|
+
const peers = children.get(element.parentId) ?? [];
|
|
32
|
+
peers.push(element);
|
|
33
|
+
children.set(element.parentId, peers);
|
|
34
|
+
}
|
|
35
|
+
const siblings = children.get(selected.parentId)!;
|
|
36
|
+
const index = siblings.indexOf(selected);
|
|
37
|
+
const other = siblings[index + direction];
|
|
38
|
+
if (!other) return elements;
|
|
39
|
+
[siblings[index], siblings[index + direction]] = [other, selected];
|
|
40
|
+
const result: VectorElement[] = [];
|
|
41
|
+
function visit(parentId?: string) {
|
|
42
|
+
for (const child of children.get(parentId) ?? []) {
|
|
43
|
+
result.push(child);
|
|
44
|
+
visit(child.id);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
visit();
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function uniqueId(preferred: string, used: Set<string>, index: number) {
|
|
52
|
+
const base = preferred.trim().replace(/[^a-zA-Z0-9_-]+/g, "-") || `vector-${index}`;
|
|
53
|
+
if (!used.has(base)) return base;
|
|
54
|
+
let suffix = 2;
|
|
55
|
+
while (used.has(`${base}-${suffix}`)) suffix += 1;
|
|
56
|
+
return `${base}-${suffix}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function attributesOf(element: Element) {
|
|
60
|
+
const attributes: Record<string, string | number> = {};
|
|
61
|
+
for (let index = 0; index < element.attributes.length; index += 1) {
|
|
62
|
+
const attribute = element.attributes.item(index);
|
|
63
|
+
if (!attribute || attribute.name === "id") continue;
|
|
64
|
+
if (attribute.name === "style") {
|
|
65
|
+
for (const declaration of attribute.value.split(";")) {
|
|
66
|
+
const separator = declaration.indexOf(":");
|
|
67
|
+
if (separator < 0) continue;
|
|
68
|
+
const name = declaration.slice(0, separator).trim();
|
|
69
|
+
const value = declaration.slice(separator + 1).trim();
|
|
70
|
+
if (vectorAttributeNames.has(name) && value && !/url\s*\(/i.test(value))
|
|
71
|
+
attributes[name] = value;
|
|
72
|
+
}
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (vectorAttributeNames.has(attribute.name)) attributes[attribute.name] = attribute.value;
|
|
76
|
+
}
|
|
77
|
+
return attributes;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function parseEditableSvg(source: string) {
|
|
81
|
+
const document = new DOMParser().parseFromString(source, "image/svg+xml");
|
|
82
|
+
const root = document.documentElement;
|
|
83
|
+
if (!root || root.localName !== "svg") throw new Error("The visual must have an SVG root.");
|
|
84
|
+
const values = root.getAttribute("viewBox")?.trim().split(/[\s,]+/).map(Number);
|
|
85
|
+
if (!values || values.length !== 4 || values.some((value) => !Number.isFinite(value)))
|
|
86
|
+
throw new Error("The SVG root needs a numeric viewBox.");
|
|
87
|
+
const [x, y, width, height] = values;
|
|
88
|
+
if (width <= 0 || height <= 0) throw new Error("The SVG viewBox must be positive.");
|
|
89
|
+
|
|
90
|
+
const elements: VectorElement[] = [];
|
|
91
|
+
const used = new Set<string>();
|
|
92
|
+
function visit(element: Element, parentId?: string) {
|
|
93
|
+
const kind = element.localName as VectorElementKind;
|
|
94
|
+
if (!vectorElementKinds.includes(kind))
|
|
95
|
+
throw new Error(`Unsupported editable SVG element: ${element.localName}.`);
|
|
96
|
+
const id = uniqueId(element.getAttribute("id") ?? "", used, elements.length + 1);
|
|
97
|
+
used.add(id);
|
|
98
|
+
const mixedText = (kind === "text" || kind === "tspan") &&
|
|
99
|
+
Array.from({ length: element.childNodes.length }, (_, index) =>
|
|
100
|
+
element.childNodes.item(index),
|
|
101
|
+
).some((node) => node?.nodeType === 1);
|
|
102
|
+
const text = [...Array.from({ length: element.childNodes.length }, (_, index) =>
|
|
103
|
+
element.childNodes.item(index),
|
|
104
|
+
)]
|
|
105
|
+
.filter((node) => node?.nodeType === 3)
|
|
106
|
+
.map((node) => node?.nodeValue ?? "")
|
|
107
|
+
.join("");
|
|
108
|
+
elements.push({
|
|
109
|
+
id,
|
|
110
|
+
kind,
|
|
111
|
+
...(parentId ? { parentId } : {}),
|
|
112
|
+
attributes: attributesOf(element),
|
|
113
|
+
...(!mixedText && text && (kind === "text" || kind === "tspan") ? { text } : {}),
|
|
114
|
+
});
|
|
115
|
+
for (let index = 0; index < element.childNodes.length; index += 1) {
|
|
116
|
+
const child = element.childNodes.item(index);
|
|
117
|
+
if (mixedText && child?.nodeType === 3 && child.nodeValue) {
|
|
118
|
+
const textId = uniqueId("", used, elements.length + 1);
|
|
119
|
+
used.add(textId);
|
|
120
|
+
elements.push({ id: textId, kind: "tspan", parentId: id, attributes: {}, text: child.nodeValue });
|
|
121
|
+
}
|
|
122
|
+
if (child?.nodeType !== 1) continue;
|
|
123
|
+
visit(child as unknown as Element, id);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const rootAttributes = attributesOf(root as unknown as Element);
|
|
128
|
+
let parentId: string | undefined;
|
|
129
|
+
if (Object.keys(rootAttributes).length) {
|
|
130
|
+
parentId = "vector-root";
|
|
131
|
+
used.add(parentId);
|
|
132
|
+
elements.push({ id: parentId, kind: "g", attributes: rootAttributes });
|
|
133
|
+
}
|
|
134
|
+
for (let index = 0; index < root.childNodes.length; index += 1) {
|
|
135
|
+
const child = root.childNodes.item(index);
|
|
136
|
+
if (child?.nodeType !== 1) continue;
|
|
137
|
+
const name = (child as unknown as Element).localName;
|
|
138
|
+
if (name === "title" || name === "desc") continue;
|
|
139
|
+
visit(child as unknown as Element, parentId);
|
|
140
|
+
}
|
|
141
|
+
if (!elements.length) throw new Error("The SVG contains no editable elements.");
|
|
142
|
+
return { viewBox: { x, y, width, height }, elements };
|
|
143
|
+
}
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
export type DiagramLayoutEngine =
|
|
2
|
+
| "linear"
|
|
3
|
+
| "branching"
|
|
4
|
+
| "cyclical"
|
|
5
|
+
| "layered"
|
|
6
|
+
| "request-flow"
|
|
7
|
+
| "hub-and-spoke";
|
|
8
|
+
|
|
9
|
+
export type DiagramDefinition = {
|
|
10
|
+
label: string;
|
|
11
|
+
category:
|
|
12
|
+
| "Systems & technical structure"
|
|
13
|
+
| "Flow, interaction & behavior"
|
|
14
|
+
| "Data & software modeling"
|
|
15
|
+
| "Hierarchy, containment & organization"
|
|
16
|
+
| "Time & work"
|
|
17
|
+
| "Quantitative & analytical"
|
|
18
|
+
| "Conceptual & strategic";
|
|
19
|
+
layout: DiagramLayoutEngine;
|
|
20
|
+
slotRole: "process-step" | "entity";
|
|
21
|
+
expression: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type ChartDefinition = {
|
|
25
|
+
label: string;
|
|
26
|
+
expression: string;
|
|
27
|
+
source: "diagram-design" | "konpeki";
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const diagramDefinitions = {
|
|
31
|
+
architecture: {
|
|
32
|
+
label: "Architecture",
|
|
33
|
+
category: "Systems & technical structure",
|
|
34
|
+
layout: "hub-and-spoke",
|
|
35
|
+
slotRole: "entity",
|
|
36
|
+
expression: "Show system boundaries, responsibilities, interfaces, and directional communication without implying physical deployment.",
|
|
37
|
+
},
|
|
38
|
+
"it-current-state": {
|
|
39
|
+
label: "IT current state",
|
|
40
|
+
category: "Systems & technical structure",
|
|
41
|
+
layout: "hub-and-spoke",
|
|
42
|
+
slotRole: "entity",
|
|
43
|
+
expression: "Show the systems that exist today, their ownership, integration paths, friction, duplication, and known constraints.",
|
|
44
|
+
},
|
|
45
|
+
"high-level": {
|
|
46
|
+
label: "High-level overview",
|
|
47
|
+
category: "Systems & technical structure",
|
|
48
|
+
layout: "layered",
|
|
49
|
+
slotRole: "entity",
|
|
50
|
+
expression: "Reduce the system to a small number of named capabilities and major relationships; suppress implementation detail.",
|
|
51
|
+
},
|
|
52
|
+
deployment: {
|
|
53
|
+
label: "Deployment",
|
|
54
|
+
category: "Systems & technical structure",
|
|
55
|
+
layout: "layered",
|
|
56
|
+
slotRole: "entity",
|
|
57
|
+
expression: "Place software artifacts inside the environments, hosts, zones, or runtimes where they execute and show cross-zone communication.",
|
|
58
|
+
},
|
|
59
|
+
flowchart: {
|
|
60
|
+
label: "Flowchart",
|
|
61
|
+
category: "Flow, interaction & behavior",
|
|
62
|
+
layout: "branching",
|
|
63
|
+
slotRole: "process-step",
|
|
64
|
+
expression: "Distinguish actions, decisions, merge points, and start or end states; label decision branches explicitly.",
|
|
65
|
+
},
|
|
66
|
+
sequence: {
|
|
67
|
+
label: "Sequence",
|
|
68
|
+
category: "Flow, interaction & behavior",
|
|
69
|
+
layout: "request-flow",
|
|
70
|
+
slotRole: "entity",
|
|
71
|
+
expression: "Order actors horizontally and messages vertically through time; distinguish calls, returns, asynchronous messages, and branches.",
|
|
72
|
+
},
|
|
73
|
+
"state-machine": {
|
|
74
|
+
label: "State machine",
|
|
75
|
+
category: "Flow, interaction & behavior",
|
|
76
|
+
layout: "branching",
|
|
77
|
+
slotRole: "entity",
|
|
78
|
+
expression: "Treat nodes as legal states and edges as guarded transitions; preserve initial, terminal, retry, and invalid transition semantics.",
|
|
79
|
+
},
|
|
80
|
+
swimlane: {
|
|
81
|
+
label: "Swimlane",
|
|
82
|
+
category: "Flow, interaction & behavior",
|
|
83
|
+
layout: "layered",
|
|
84
|
+
slotRole: "process-step",
|
|
85
|
+
expression: "Partition steps by responsible actor or team and make every cross-lane handoff visible.",
|
|
86
|
+
},
|
|
87
|
+
process: {
|
|
88
|
+
label: "Process",
|
|
89
|
+
category: "Flow, interaction & behavior",
|
|
90
|
+
layout: "linear",
|
|
91
|
+
slotRole: "process-step",
|
|
92
|
+
expression: "Show ordered work from trigger to outcome, including branches, loops, owners, and exceptional paths when supplied.",
|
|
93
|
+
},
|
|
94
|
+
"data-flow": {
|
|
95
|
+
label: "Data flow",
|
|
96
|
+
category: "Flow, interaction & behavior",
|
|
97
|
+
layout: "hub-and-spoke",
|
|
98
|
+
slotRole: "entity",
|
|
99
|
+
expression: "Show data producers, transformations, stores, and consumers; label payloads and direction independently from control flow.",
|
|
100
|
+
},
|
|
101
|
+
"dp-integration": {
|
|
102
|
+
label: "Data-product integration",
|
|
103
|
+
category: "Flow, interaction & behavior",
|
|
104
|
+
layout: "request-flow",
|
|
105
|
+
slotRole: "entity",
|
|
106
|
+
expression: "Show producer and consumer responsibilities, contracts, integration boundaries, and exchange direction for each data product.",
|
|
107
|
+
},
|
|
108
|
+
"er-data-model": {
|
|
109
|
+
label: "ER / data model",
|
|
110
|
+
category: "Data & software modeling",
|
|
111
|
+
layout: "hub-and-spoke",
|
|
112
|
+
slotRole: "entity",
|
|
113
|
+
expression: "Represent entities with key attributes and label relationship cardinality and optionality at both endpoints.",
|
|
114
|
+
},
|
|
115
|
+
medallion: {
|
|
116
|
+
label: "Medallion",
|
|
117
|
+
category: "Data & software modeling",
|
|
118
|
+
layout: "layered",
|
|
119
|
+
slotRole: "entity",
|
|
120
|
+
expression: "Arrange data products by progressive quality tier and show transformations from raw through refined to consumption-ready data.",
|
|
121
|
+
},
|
|
122
|
+
"dp-security-matrix": {
|
|
123
|
+
label: "Data-product security matrix",
|
|
124
|
+
category: "Data & software modeling",
|
|
125
|
+
layout: "layered",
|
|
126
|
+
slotRole: "entity",
|
|
127
|
+
expression: "Use rows and columns as explicit subject and resource axes; encode permissions or controls at their intersections.",
|
|
128
|
+
},
|
|
129
|
+
"dependency-graph": {
|
|
130
|
+
label: "Dependency graph",
|
|
131
|
+
category: "Data & software modeling",
|
|
132
|
+
layout: "branching",
|
|
133
|
+
slotRole: "entity",
|
|
134
|
+
expression: "Preserve many-parent fan-in, shared dependencies, and cycles; orient every dependency edge consistently.",
|
|
135
|
+
},
|
|
136
|
+
"uml-class": {
|
|
137
|
+
label: "UML class",
|
|
138
|
+
category: "Data & software modeling",
|
|
139
|
+
layout: "hub-and-spoke",
|
|
140
|
+
slotRole: "entity",
|
|
141
|
+
expression: "Keep class names, attributes, and operations in separate compartments and preserve inheritance, aggregation, and composition notation.",
|
|
142
|
+
},
|
|
143
|
+
"database-schema": {
|
|
144
|
+
label: "Database schema",
|
|
145
|
+
category: "Data & software modeling",
|
|
146
|
+
layout: "hub-and-spoke",
|
|
147
|
+
slotRole: "entity",
|
|
148
|
+
expression: "Show tables with typed columns, keys, constraints, and column-level foreign-key endpoints.",
|
|
149
|
+
},
|
|
150
|
+
nested: {
|
|
151
|
+
label: "Nested containment",
|
|
152
|
+
category: "Hierarchy, containment & organization",
|
|
153
|
+
layout: "layered",
|
|
154
|
+
slotRole: "entity",
|
|
155
|
+
expression: "Use enclosure to express scope and ownership; containment must not be replaced by proximity alone.",
|
|
156
|
+
},
|
|
157
|
+
tree: {
|
|
158
|
+
label: "Tree",
|
|
159
|
+
category: "Hierarchy, containment & organization",
|
|
160
|
+
layout: "branching",
|
|
161
|
+
slotRole: "entity",
|
|
162
|
+
expression: "Show a strict one-parent hierarchy with consistent depth and sibling ordering.",
|
|
163
|
+
},
|
|
164
|
+
"org-chart": {
|
|
165
|
+
label: "Org chart",
|
|
166
|
+
category: "Hierarchy, containment & organization",
|
|
167
|
+
layout: "branching",
|
|
168
|
+
slotRole: "entity",
|
|
169
|
+
expression: "Show reporting structure, role ownership, and escalation paths; do not imply technical dependency.",
|
|
170
|
+
},
|
|
171
|
+
"layer-stack": {
|
|
172
|
+
label: "Layer stack",
|
|
173
|
+
category: "Hierarchy, containment & organization",
|
|
174
|
+
layout: "layered",
|
|
175
|
+
slotRole: "entity",
|
|
176
|
+
expression: "Order abstraction or responsibility layers consistently and show only legitimate cross-layer dependencies.",
|
|
177
|
+
},
|
|
178
|
+
"pyramid-funnel": {
|
|
179
|
+
label: "Pyramid / funnel",
|
|
180
|
+
category: "Hierarchy, containment & organization",
|
|
181
|
+
layout: "layered",
|
|
182
|
+
slotRole: "entity",
|
|
183
|
+
expression: "Use width and vertical order to encode rank, concentration, or drop-off; state which meaning applies.",
|
|
184
|
+
},
|
|
185
|
+
timeline: {
|
|
186
|
+
label: "Timeline",
|
|
187
|
+
category: "Time & work",
|
|
188
|
+
layout: "linear",
|
|
189
|
+
slotRole: "process-step",
|
|
190
|
+
expression: "Place dated events on an honest temporal axis and preserve meaningful interval differences.",
|
|
191
|
+
},
|
|
192
|
+
gantt: {
|
|
193
|
+
label: "Gantt",
|
|
194
|
+
category: "Time & work",
|
|
195
|
+
layout: "layered",
|
|
196
|
+
slotRole: "process-step",
|
|
197
|
+
expression: "Encode task start, duration, overlap, milestones, dependencies, and ownership against a shared time scale.",
|
|
198
|
+
},
|
|
199
|
+
kanban: {
|
|
200
|
+
label: "Kanban",
|
|
201
|
+
category: "Time & work",
|
|
202
|
+
layout: "layered",
|
|
203
|
+
slotRole: "entity",
|
|
204
|
+
expression: "Use columns as workflow states, preserve card ownership and priority, and show WIP limits when supplied.",
|
|
205
|
+
},
|
|
206
|
+
"user-journey": {
|
|
207
|
+
label: "User journey",
|
|
208
|
+
category: "Time & work",
|
|
209
|
+
layout: "linear",
|
|
210
|
+
slotRole: "process-step",
|
|
211
|
+
expression: "Order experience stages and align actions, touchpoints, pain points, and sentiment to each stage.",
|
|
212
|
+
},
|
|
213
|
+
"story-map": {
|
|
214
|
+
label: "Story map",
|
|
215
|
+
category: "Time & work",
|
|
216
|
+
layout: "layered",
|
|
217
|
+
slotRole: "entity",
|
|
218
|
+
expression: "Use columns for narrative activity order and rows for release slices; preserve the release-cut line.",
|
|
219
|
+
},
|
|
220
|
+
quadrant: {
|
|
221
|
+
label: "Quadrant",
|
|
222
|
+
category: "Quantitative & analytical",
|
|
223
|
+
layout: "layered",
|
|
224
|
+
slotRole: "entity",
|
|
225
|
+
expression: "Position items against two named axes with meaningful scales and make quadrant interpretation explicit.",
|
|
226
|
+
},
|
|
227
|
+
"loop-flywheel": {
|
|
228
|
+
label: "Loop / flywheel",
|
|
229
|
+
category: "Conceptual & strategic",
|
|
230
|
+
layout: "cyclical",
|
|
231
|
+
slotRole: "process-step",
|
|
232
|
+
expression: "Show a reinforcing cycle with explicit direction and explain how each stage increases momentum or feeds the next.",
|
|
233
|
+
},
|
|
234
|
+
venn: {
|
|
235
|
+
label: "Venn",
|
|
236
|
+
category: "Conceptual & strategic",
|
|
237
|
+
layout: "hub-and-spoke",
|
|
238
|
+
slotRole: "entity",
|
|
239
|
+
expression: "Use overlapping regions to encode set membership and label every meaningful intersection.",
|
|
240
|
+
},
|
|
241
|
+
fishbone: {
|
|
242
|
+
label: "Fishbone",
|
|
243
|
+
category: "Conceptual & strategic",
|
|
244
|
+
layout: "branching",
|
|
245
|
+
slotRole: "entity",
|
|
246
|
+
expression: "Group candidate causes by category along branches that converge on one clearly stated effect.",
|
|
247
|
+
},
|
|
248
|
+
"wardley-map": {
|
|
249
|
+
label: "Wardley map",
|
|
250
|
+
category: "Conceptual & strategic",
|
|
251
|
+
layout: "layered",
|
|
252
|
+
slotRole: "entity",
|
|
253
|
+
expression: "Position components by user visibility and evolution, then show value-chain dependencies and movement without treating the map as runtime architecture.",
|
|
254
|
+
},
|
|
255
|
+
} as const satisfies Record<string, DiagramDefinition>;
|
|
256
|
+
|
|
257
|
+
export type DiagramType = keyof typeof diagramDefinitions;
|
|
258
|
+
|
|
259
|
+
export const diagramTypes = Object.keys(diagramDefinitions) as DiagramType[];
|
|
260
|
+
|
|
261
|
+
export const chartDefinitions = {
|
|
262
|
+
"grouped-bar": {
|
|
263
|
+
label: "Grouped bar",
|
|
264
|
+
expression: "Compare quantitative values across categories with a shared zero baseline; use grouped series only when each category has comparable values.",
|
|
265
|
+
source: "diagram-design",
|
|
266
|
+
},
|
|
267
|
+
line: {
|
|
268
|
+
label: "Line",
|
|
269
|
+
expression: "Encode values on a shared scale and connect observations in meaningful order, usually time; preserve intervals and do not imply missing measurements.",
|
|
270
|
+
source: "diagram-design",
|
|
271
|
+
},
|
|
272
|
+
pie: {
|
|
273
|
+
label: "Pie",
|
|
274
|
+
expression: "Show a small set of mutually exclusive parts of one whole; preserve the total and label values directly when comparison by angle is difficult.",
|
|
275
|
+
source: "konpeki",
|
|
276
|
+
},
|
|
277
|
+
"annotated-detail": {
|
|
278
|
+
label: "Annotated detail",
|
|
279
|
+
expression: "Focus on one visual detail and connect concise annotations to exact evidence without obscuring the underlying image or chart.",
|
|
280
|
+
source: "konpeki",
|
|
281
|
+
},
|
|
282
|
+
radar: {
|
|
283
|
+
label: "Radar",
|
|
284
|
+
expression: "Compare multivariate profiles on consistently normalized radial axes; preserve axis order, scale, and direction across every series.",
|
|
285
|
+
source: "diagram-design",
|
|
286
|
+
},
|
|
287
|
+
treemap: {
|
|
288
|
+
label: "Treemap",
|
|
289
|
+
expression: "Encode part-to-whole magnitude by rectangle area within explicit hierarchy; preserve parent totals and avoid implying rank from position.",
|
|
290
|
+
source: "diagram-design",
|
|
291
|
+
},
|
|
292
|
+
scatter: {
|
|
293
|
+
label: "Scatter",
|
|
294
|
+
expression: "Position observations by two measured quantitative variables; preserve axis scales, units, outliers, and uncertainty about correlation.",
|
|
295
|
+
source: "diagram-design",
|
|
296
|
+
},
|
|
297
|
+
sankey: {
|
|
298
|
+
label: "Sankey",
|
|
299
|
+
expression: "Encode conserved quantity through splits and merges using proportional ribbon width; preserve node totals, flow direction, and units.",
|
|
300
|
+
source: "diagram-design",
|
|
301
|
+
},
|
|
302
|
+
} as const satisfies Record<string, ChartDefinition>;
|
|
303
|
+
|
|
304
|
+
export type ChartTemplate = keyof typeof chartDefinitions;
|
|
305
|
+
|
|
306
|
+
export const chartTemplates = Object.keys(chartDefinitions) as ChartTemplate[];
|
|
307
|
+
|
|
308
|
+
export const visualizationDefinitions = {
|
|
309
|
+
diagram: diagramDefinitions,
|
|
310
|
+
chart: chartDefinitions,
|
|
311
|
+
} as const;
|
|
312
|
+
|
|
313
|
+
export const diagramCategories = Array.from(
|
|
314
|
+
new Set(Object.values(diagramDefinitions).map((definition) => definition.category)),
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
export function diagramDefinition(type: DiagramType): DiagramDefinition {
|
|
318
|
+
return diagramDefinitions[type];
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export function migrateLegacyDiagramType(
|
|
322
|
+
type: unknown,
|
|
323
|
+
layout: unknown,
|
|
324
|
+
): DiagramType | undefined {
|
|
325
|
+
if (typeof type !== "string" || typeof layout !== "string") return undefined;
|
|
326
|
+
const legacyTypes: Record<string, DiagramType> = {
|
|
327
|
+
"process/linear": "process",
|
|
328
|
+
"process/branching": "flowchart",
|
|
329
|
+
"process/cyclical": "loop-flywheel",
|
|
330
|
+
"system/layered": "layer-stack",
|
|
331
|
+
"system/request-flow": "sequence",
|
|
332
|
+
"system/hub-and-spoke": "architecture",
|
|
333
|
+
};
|
|
334
|
+
return legacyTypes[`${type}/${layout}`];
|
|
335
|
+
}
|
package/design/README.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Find an authoring resource
|
|
2
|
+
|
|
3
|
+
[AUTHORING](../AUTHORING.md) owns design defaults and font suggestions. Consult
|
|
4
|
+
the resources below for a specific need; none impose a composition or reading order.
|
|
5
|
+
|
|
6
|
+
| Need | Where | Use |
|
|
7
|
+
| --- | --- | --- |
|
|
8
|
+
| Choose the rules | [Authoring mode](../AUTHORING.md#authoring-mode) | Default (original rules) or dynamic (relaxed visual rules); optional paired comparison |
|
|
9
|
+
| Choose a visual structure | [Semantic patterns](semantic-patterns.md) | Audience question → relationships → visual structure |
|
|
10
|
+
| Review rendered pages | [Review categories](visual-review.md) | Text, typography, layout, color/theme and visuals; optional parallel review |
|
|
11
|
+
| Colors only | [palettes](palettes/README.md) | Base, eight light/dark palettes, Mineral/Botanical light candidates |
|
|
12
|
+
| Typography + colors | [themes](themes/README.md) | Ready theme roles; no content or layout |
|
|
13
|
+
| Art direction | [Technical product language](visual-languages/technical-product.md) | How imagery, emphasis and diagrams work together |
|
|
14
|
+
| Add a deck | [Deck directory guide](../slides/README.md) | Routes, prompt records and example deliverables |
|
|
15
|
+
|
|
16
|
+
Start freely when no example fits. Reuse implementation, not composition.
|
|
17
|
+
A case-specific check is not a design rule.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Color palettes
|
|
2
|
+
|
|
3
|
+
Color resources only; no typography, layout or content.
|
|
4
|
+
|
|
5
|
+
- [index.ts](index.ts): `resolvePalette(name, 'paper' | 'night')` for Plex,
|
|
6
|
+
Precision, Editorial, Blue–cyan, Orange–coral, Yellow, Green and Graphite.
|
|
7
|
+
Contains surface/text roles, categorical colors, sequential ramps and status.
|
|
8
|
+
- [candidates.ts](candidates.ts): `lightPalettes`, Mineral and Botanical,
|
|
9
|
+
including named series and diverging ramps. Light-only; not auto-inverted.
|
|
10
|
+
- [base.ts](base.ts): original specimen roles, still re-exported by
|
|
11
|
+
`lib/taste.ts` for existing runtime consumers. Definitions are not duplicated.
|
|
12
|
+
|
|
13
|
+
Choose only the needed series. Keep category meaning separate from status.
|
|
14
|
+
For typography with a palette, use [themes](../themes/README.md).
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Original specimen palette. Named palettes build on these color roles.
|
|
2
|
+
export const palettes = {
|
|
3
|
+
paper: {
|
|
4
|
+
bg: '#FFFFFF', fg: '#000000', muted: '#3F4A54', line: '#BCC6CE',
|
|
5
|
+
surface: '#EAEFF3', wash: '#E3EAF8', accent: '#2458C7', inverse: '#FFFFFF',
|
|
6
|
+
emphasis: '#98451F', emphasisWash: '#F1E1D4',
|
|
7
|
+
},
|
|
8
|
+
night: {
|
|
9
|
+
bg: '#000000', fg: '#FFFFFF', muted: '#B2C1CC', line: '#4C6072',
|
|
10
|
+
surface: '#202E3C', wash: '#233857', accent: '#97B9FF', inverse: '#000000',
|
|
11
|
+
emphasis: '#F0AC7D', emphasisWash: '#443025',
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
export type Palette = typeof palettes.paper;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Approved light-only color candidates. No layout or typography.
|
|
2
|
+
export const lightPalettes = [
|
|
3
|
+
{
|
|
4
|
+
name: 'Mineral', bg: '#FFFFFF', ink: '#18232D', muted: '#526170',
|
|
5
|
+
surface: '#F0F3F5', line: '#CBD3DA', accent: '#2458B8', wash: '#E8EFFB',
|
|
6
|
+
colors: ['#3569B7', '#167D8D', '#8061A8', '#A27616', '#BA536A', '#59734A'],
|
|
7
|
+
names: ['Cobalt', 'Petrol', 'Iris', 'Ochre', 'Rose', 'Moss'],
|
|
8
|
+
sequential: ['#E8EFFB', '#B9CDEA', '#7CA2D4', '#4677B8', '#24518D'],
|
|
9
|
+
diverging: ['#A14B26', '#D79A73', '#F0F1F2', '#83B9C1', '#216775'],
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
name: 'Botanical', bg: '#FFFFFF', ink: '#222923', muted: '#59625B',
|
|
13
|
+
surface: '#F1F4F1', line: '#CBD4CB', accent: '#32634D', wash: '#E7F0E9',
|
|
14
|
+
colors: ['#43785B', '#5375B5', '#B45A38', '#8B5B96', '#8A781B', '#287F92'],
|
|
15
|
+
names: ['Fern', 'Denim', 'Terracotta', 'Plum', 'Brass', 'Lake'],
|
|
16
|
+
sequential: ['#E7F0E9', '#B9D1BF', '#83AD90', '#528566', '#2D5941'],
|
|
17
|
+
diverging: ['#86508D', '#BE9AC1', '#F0F1F2', '#8DB7A8', '#326C59'],
|
|
18
|
+
},
|
|
19
|
+
];
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { palettes, type Palette } from './base.ts';
|
|
2
|
+
|
|
3
|
+
// Color-only resources. These are our adaptations, not extracted brand tokens.
|
|
4
|
+
// Keep category identity/order stable across modes; status is a separate channel.
|
|
5
|
+
export const paletteNames = ['Plex', 'Precision', 'Editorial', 'Blue–cyan', 'Orange–coral', 'Yellow', 'Green', 'Graphite'] as const;
|
|
6
|
+
export type PaletteName = typeof paletteNames[number];
|
|
7
|
+
export type PaletteMode = 'paper' | 'night';
|
|
8
|
+
type SixColors = [string, string, string, string, string, string];
|
|
9
|
+
type FiveColors = [string, string, string, string, string];
|
|
10
|
+
type PaletteDefinition = {
|
|
11
|
+
accent: [string, string];
|
|
12
|
+
wash: [string, string];
|
|
13
|
+
surface: [string, string];
|
|
14
|
+
categorical: [SixColors, SixColors];
|
|
15
|
+
sequential: [FiveColors, FiveColors];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const definitions: Record<PaletteName, PaletteDefinition> = {
|
|
19
|
+
Plex: {
|
|
20
|
+
accent: ['#2458B8', '#91B9FF'], wash: ['#EAF0FA', '#18273F'], surface: ['#F1F3F5', '#171B21'],
|
|
21
|
+
categorical: [['#3569B7', '#167D8D', '#8061A8', '#A27616', '#BA536A', '#59734A'], ['#82AEF2', '#63C3CD', '#BAA0DF', '#D7B55B', '#E899AA', '#A6C58E']],
|
|
22
|
+
sequential: [['#E8EFFB', '#B9CDEA', '#7CA2D4', '#4677B8', '#24518D'], ['#192B45', '#305381', '#4C7CB4', '#82AEF2', '#C2DAFF']],
|
|
23
|
+
},
|
|
24
|
+
Precision: {
|
|
25
|
+
accent: ['#3D3A8F', '#B6B2FF'], wash: ['#EEEFFD', '#22213D'], surface: ['#F2F2F5', '#19191F'],
|
|
26
|
+
categorical: [['#4B469D', '#2F6A8F', '#76538F', '#8A5A25', '#9A475F', '#4F7040'], ['#AAA5F4', '#82BCE2', '#C69DDF', '#DEB071', '#E89AAF', '#A6C88D']],
|
|
27
|
+
sequential: [['#EEEFFD', '#C9C7EC', '#9792D0', '#6761AE', '#3D3A8F'], ['#23223F', '#454278', '#716CB2', '#AAA5F4', '#D9D7FF']],
|
|
28
|
+
},
|
|
29
|
+
Editorial: {
|
|
30
|
+
accent: ['#93451F', '#F2B38C'], wash: ['#F6ECE5', '#30231D'], surface: ['#F4F3F2', '#1B1918'],
|
|
31
|
+
categorical: [['#AF5934', '#387684', '#8A619A', '#8C791D', '#AD5071', '#4D7757'], ['#EFA77F', '#86C3CD', '#C6A3D5', '#D4C17A', '#E9A2BF', '#A0C9A5']],
|
|
32
|
+
sequential: [['#F7EEE7', '#EACAB4', '#CD9875', '#AB6A45', '#794021'], ['#35271F', '#664832', '#A07350', '#D5A17B', '#F6D5B8']],
|
|
33
|
+
},
|
|
34
|
+
'Blue–cyan': {
|
|
35
|
+
accent: ['#007C88', '#63D6E3'], wash: ['#EAF9FA', '#112E33'], surface: ['#F0F9FA', '#121D20'],
|
|
36
|
+
categorical: [['#007C88', '#007F8E', '#275F9A', '#95631F', '#A64D68', '#39724F'], ['#63D6E3', '#79E3EB', '#8FBCEF', '#DBB267', '#E799AF', '#89C9A1']],
|
|
37
|
+
sequential: [['#EAF9FA', '#B7E7EA', '#72C3CA', '#2B9AA5', '#007C88'], ['#123136', '#225D65', '#388F99', '#63C6D0', '#A8EFF4']],
|
|
38
|
+
},
|
|
39
|
+
'Orange–coral': {
|
|
40
|
+
accent: ['#A23E20', '#FFB28E'], wash: ['#FFF0E8', '#33231D'], surface: ['#F5F3F2', '#1D1917'],
|
|
41
|
+
categorical: [['#BA5A31', '#B94970', '#4876AA', '#247D76', '#8861A3', '#92771D'], ['#F3A47C', '#ED99B7', '#91B8E8', '#76C7BE', '#BFA3DD', '#D7BF70']],
|
|
42
|
+
sequential: [['#FCEDE5', '#EDC3AD', '#D89370', '#B66A44', '#88411E'], ['#36251F', '#6D4430', '#A9704D', '#DF9F73', '#FFD1AC']],
|
|
43
|
+
},
|
|
44
|
+
Yellow: {
|
|
45
|
+
accent: ['#756000', '#F1D66D'], wash: ['#FBF5D9', '#302A17'], surface: ['#F5F5F0', '#1C1C18'],
|
|
46
|
+
categorical: [['#95761C', '#416FA4', '#A95670', '#397D71', '#8862A2', '#AD612C'], ['#E0C264', '#91B6E5', '#E1A1B6', '#8AC8B8', '#BFA5D9', '#E4B181']],
|
|
47
|
+
sequential: [['#F8F3D9', '#E5D9A4', '#C4B369', '#9B8433', '#705C12'], ['#302B19', '#5A4C25', '#917A38', '#C9AF59', '#F5DE91']],
|
|
48
|
+
},
|
|
49
|
+
Green: {
|
|
50
|
+
accent: ['#216B3D', '#98DCAF'], wash: ['#EAF3ED', '#1A2D22'], surface: ['#F1F5F2', '#171D19'],
|
|
51
|
+
categorical: [['#43785B', '#5375B5', '#B45A38', '#8B5B96', '#8A781B', '#287F92'], ['#91C7A6', '#9AB8EA', '#E9AB8A', '#CBA4D5', '#D1C278', '#7BC8D8']],
|
|
52
|
+
sequential: [['#E7F0E9', '#B9D1BF', '#83AD90', '#528566', '#2D5941'], ['#1B3023', '#325A40', '#578569', '#91C7A6', '#CBECD6']],
|
|
53
|
+
},
|
|
54
|
+
Graphite: {
|
|
55
|
+
accent: ['#242424', '#F2F2F2'], wash: ['#EDEDED', '#242424'], surface: ['#F5F5F5', '#171717'],
|
|
56
|
+
categorical: [['#3268B5', '#187F86', '#8A5DA6', '#A87922', '#B55372', '#5A7847'], ['#85B2F3', '#77C9CF', '#C3A2E0', '#DDBB78', '#E6A0B8', '#ADC991']],
|
|
57
|
+
sequential: [['#EFEFEF', '#CBCBCB', '#999999', '#666666', '#303030'], ['#282828', '#505050', '#838383', '#B8B8B8', '#EFEFEF']],
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export function resolvePalette(name: PaletteName, mode: PaletteMode): Palette & {
|
|
62
|
+
categorical: SixColors; sequential: FiveColors; status: { complete: string; attention: string; blocked: string };
|
|
63
|
+
} {
|
|
64
|
+
const d = definitions[name], dark = mode === 'night', i = dark ? 1 : 0;
|
|
65
|
+
return {
|
|
66
|
+
...palettes[mode],
|
|
67
|
+
bg: dark ? '#000000' : '#FFFFFF', fg: dark ? '#F2F2F2' : '#20242A',
|
|
68
|
+
muted: dark ? '#B7BCC4' : '#505B68', line: dark ? '#454B54' : '#C9CFD6',
|
|
69
|
+
...(name === 'Graphite' ? {
|
|
70
|
+
fg: dark ? '#F2F2F2' : '#202020', muted: dark ? '#B8B8B8' : '#595959',
|
|
71
|
+
line: dark ? '#454545' : '#CECECE', emphasis: d.accent[i], emphasisWash: d.wash[i],
|
|
72
|
+
} : {}),
|
|
73
|
+
accent: d.accent[i], wash: d.wash[i], surface: d.surface[i],
|
|
74
|
+
categorical: [...d.categorical[i]], sequential: [...d.sequential[i]],
|
|
75
|
+
status: dark ? { complete: '#94D3AB', attention: '#E3C477', blocked: '#F0A1AF' }
|
|
76
|
+
: { complete: '#276749', attention: '#946200', blocked: '#AC3348' },
|
|
77
|
+
};
|
|
78
|
+
}
|