@itsthecsguy/linework 0.0.0-stage → 0.1.1

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 (217) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +11 -0
  3. package/README.md +206 -2
  4. package/assets/drawio/LICENSE +201 -0
  5. package/assets/drawio/VERSION +1 -0
  6. package/assets/drawio/favicon.ico +0 -0
  7. package/assets/drawio/images/1x1.png +0 -0
  8. package/assets/drawio/images/2x2.png +0 -0
  9. package/assets/drawio/images/3x3.png +0 -0
  10. package/assets/drawio/images/ajax-loader.gif +0 -0
  11. package/assets/drawio/images/android-chrome-512x512.png +0 -0
  12. package/assets/drawio/images/apple-touch-icon.png +0 -0
  13. package/assets/drawio/images/aui-wait.gif +0 -0
  14. package/assets/drawio/images/broken.png +0 -0
  15. package/assets/drawio/images/browserconfig.xml +9 -0
  16. package/assets/drawio/images/close.png +0 -0
  17. package/assets/drawio/images/cloud-connector-atlas.png +0 -0
  18. package/assets/drawio/images/collapsed.gif +0 -0
  19. package/assets/drawio/images/delete.png +0 -0
  20. package/assets/drawio/images/document-google-drive-icon-80.png +0 -0
  21. package/assets/drawio/images/draw-search.png +0 -0
  22. package/assets/drawio/images/drawio.svg +19 -0
  23. package/assets/drawio/images/drawlogo-color.svg +50 -0
  24. package/assets/drawio/images/drawlogo-gray.svg +18 -0
  25. package/assets/drawio/images/drawlogo-text-bottom.svg +75 -0
  26. package/assets/drawio/images/drawlogo.svg +18 -0
  27. package/assets/drawio/images/drawlogo128.png +0 -0
  28. package/assets/drawio/images/drawlogo144.png +0 -0
  29. package/assets/drawio/images/drawlogo256.png +0 -0
  30. package/assets/drawio/images/drawlogo48-gray.png +0 -0
  31. package/assets/drawio/images/drawlogo48.png +0 -0
  32. package/assets/drawio/images/drawlogo80.png +0 -0
  33. package/assets/drawio/images/droptarget.png +0 -0
  34. package/assets/drawio/images/edit.gif +0 -0
  35. package/assets/drawio/images/edit16.png +0 -0
  36. package/assets/drawio/images/edit32.png +0 -0
  37. package/assets/drawio/images/edit80.png +0 -0
  38. package/assets/drawio/images/embed-icon.png +0 -0
  39. package/assets/drawio/images/expanded.gif +0 -0
  40. package/assets/drawio/images/facebook.png +0 -0
  41. package/assets/drawio/images/favicon-16x16.png +0 -0
  42. package/assets/drawio/images/favicon-32x32.png +0 -0
  43. package/assets/drawio/images/file.png +0 -0
  44. package/assets/drawio/images/folder.png +0 -0
  45. package/assets/drawio/images/gadget-screenshot.png +0 -0
  46. package/assets/drawio/images/gadget-thumb.png +0 -0
  47. package/assets/drawio/images/github-logo-white.svg +6 -0
  48. package/assets/drawio/images/github-logo.svg +17 -0
  49. package/assets/drawio/images/gitlab-logo-white.svg +1 -0
  50. package/assets/drawio/images/gitlab-logo.svg +1 -0
  51. package/assets/drawio/images/glyphicons_blogger.png +0 -0
  52. package/assets/drawio/images/glyphicons_facebook.png +0 -0
  53. package/assets/drawio/images/glyphicons_github.png +0 -0
  54. package/assets/drawio/images/glyphicons_google.png +0 -0
  55. package/assets/drawio/images/glyphicons_star.png +0 -0
  56. package/assets/drawio/images/glyphicons_twitter.png +0 -0
  57. package/assets/drawio/images/glyphicons_youtube.png +0 -0
  58. package/assets/drawio/images/google-drive-connector-atlas.png +0 -0
  59. package/assets/drawio/images/google-drive-connector-atlas.svg +121 -0
  60. package/assets/drawio/images/google-drive-logo-white.svg +9 -0
  61. package/assets/drawio/images/google-drive-logo.svg +17 -0
  62. package/assets/drawio/images/google-share.png +0 -0
  63. package/assets/drawio/images/icon-192-maskable.png +0 -0
  64. package/assets/drawio/images/icon-192.png +0 -0
  65. package/assets/drawio/images/icon-512-maskable.png +0 -0
  66. package/assets/drawio/images/icon-512.png +0 -0
  67. package/assets/drawio/images/icon-search.svg +21 -0
  68. package/assets/drawio/images/img-hi-res.png +0 -0
  69. package/assets/drawio/images/img-lo-res.png +0 -0
  70. package/assets/drawio/images/list.svg +16 -0
  71. package/assets/drawio/images/locked.png +0 -0
  72. package/assets/drawio/images/log.png +0 -0
  73. package/assets/drawio/images/logo-confluence.png +0 -0
  74. package/assets/drawio/images/logo-flat-small.png +0 -0
  75. package/assets/drawio/images/logo-flat.png +0 -0
  76. package/assets/drawio/images/logo-jira.png +0 -0
  77. package/assets/drawio/images/logo-small.gif +0 -0
  78. package/assets/drawio/images/logo-white.png +0 -0
  79. package/assets/drawio/images/logo.png +0 -0
  80. package/assets/drawio/images/manifest.json +25 -0
  81. package/assets/drawio/images/move.png +0 -0
  82. package/assets/drawio/images/notion-logo-white.svg +1 -0
  83. package/assets/drawio/images/notion-logo.svg +1 -0
  84. package/assets/drawio/images/onedrive-connector-atlas.png +0 -0
  85. package/assets/drawio/images/onedrive-connector-atlas.svg +248 -0
  86. package/assets/drawio/images/onedrive-logo-white.svg +29 -0
  87. package/assets/drawio/images/onedrive-logo.svg +29 -0
  88. package/assets/drawio/images/osa_database.png +0 -0
  89. package/assets/drawio/images/osa_drive-harddisk.png +0 -0
  90. package/assets/drawio/images/plant-uml-atlas.png +0 -0
  91. package/assets/drawio/images/samepage-icon-color.svg +37 -0
  92. package/assets/drawio/images/save.png +0 -0
  93. package/assets/drawio/images/sidebar-android.png +0 -0
  94. package/assets/drawio/images/sidebar-archimate.png +0 -0
  95. package/assets/drawio/images/sidebar-arrows.png +0 -0
  96. package/assets/drawio/images/sidebar-arrows2.png +0 -0
  97. package/assets/drawio/images/sidebar-basic.png +0 -0
  98. package/assets/drawio/images/sidebar-c4.png +0 -0
  99. package/assets/drawio/images/sidebar-cabinets.png +0 -0
  100. package/assets/drawio/images/sidebar-cumulus.png +0 -0
  101. package/assets/drawio/images/sidebar-dfd.png +0 -0
  102. package/assets/drawio/images/sidebar-dynamics365.png +0 -0
  103. package/assets/drawio/images/sidebar-eip.png +0 -0
  104. package/assets/drawio/images/sidebar-er.png +0 -0
  105. package/assets/drawio/images/sidebar-floorplans.png +0 -0
  106. package/assets/drawio/images/sidebar-flowchart.png +0 -0
  107. package/assets/drawio/images/sidebar-gcp3.png +0 -0
  108. package/assets/drawio/images/sidebar-general.png +0 -0
  109. package/assets/drawio/images/sidebar-kubernetes.png +0 -0
  110. package/assets/drawio/images/sidebar-leanmapping.png +0 -0
  111. package/assets/drawio/images/sidebar-mockups.png +0 -0
  112. package/assets/drawio/images/sidebar-network.png +0 -0
  113. package/assets/drawio/images/sidebar-network2.png +0 -0
  114. package/assets/drawio/images/sidebar-openstack.png +0 -0
  115. package/assets/drawio/images/sidebar-sitemap.png +0 -0
  116. package/assets/drawio/images/sidebar-threatmodeling.png +0 -0
  117. package/assets/drawio/images/sidebar-uml.png +0 -0
  118. package/assets/drawio/images/sketch-logo128.png +0 -0
  119. package/assets/drawio/images/spin.gif +0 -0
  120. package/assets/drawio/images/sprites.png +0 -0
  121. package/assets/drawio/images/stop-flat-icon-80.png +0 -0
  122. package/assets/drawio/images/submenu.gif +0 -0
  123. package/assets/drawio/images/touch-submenu.png +0 -0
  124. package/assets/drawio/images/transparent.gif +0 -0
  125. package/assets/drawio/images/trello-logo-white-orange.svg +6 -0
  126. package/assets/drawio/images/trello-logo-white.svg +6 -0
  127. package/assets/drawio/images/trello-logo.svg +1 -0
  128. package/assets/drawio/images/tweet.png +0 -0
  129. package/assets/drawio/images/update16.png +0 -0
  130. package/assets/drawio/images/update32.png +0 -0
  131. package/assets/drawio/images/update80.png +0 -0
  132. package/assets/drawio/images/youtube.png +0 -0
  133. package/assets/drawio/img/clipart/Battery_0_128x128.png +0 -0
  134. package/assets/drawio/img/clipart/Battery_100_128x128.png +0 -0
  135. package/assets/drawio/img/clipart/Battery_50_128x128.png +0 -0
  136. package/assets/drawio/img/clipart/Battery_75_128x128.png +0 -0
  137. package/assets/drawio/img/clipart/Battery_allstates_128x128.png +0 -0
  138. package/assets/drawio/img/clipart/Bluetooth_128x128.png +0 -0
  139. package/assets/drawio/img/clipart/Earth_globe_128x128.png +0 -0
  140. package/assets/drawio/img/clipart/Empty_Folder_128x128.png +0 -0
  141. package/assets/drawio/img/clipart/Full_Folder_128x128.png +0 -0
  142. package/assets/drawio/img/clipart/Gear_128x128.png +0 -0
  143. package/assets/drawio/img/clipart/Keys_128x128.png +0 -0
  144. package/assets/drawio/img/clipart/Lock_128x128.png +0 -0
  145. package/assets/drawio/img/clipart/Mouse_Pointer_128x128.png +0 -0
  146. package/assets/drawio/img/clipart/Plug_128x128.png +0 -0
  147. package/assets/drawio/img/clipart/Ships_Wheel_128x128.png +0 -0
  148. package/assets/drawio/img/clipart/Star_128x128.png +0 -0
  149. package/assets/drawio/img/clipart/Tire_128x128.png +0 -0
  150. package/assets/drawio/index.html +92 -0
  151. package/assets/drawio/js/PostConfig.js +7 -0
  152. package/assets/drawio/js/PreConfig.js +13 -0
  153. package/assets/drawio/js/app.min.js +18466 -0
  154. package/assets/drawio/js/bootstrap.js +428 -0
  155. package/assets/drawio/js/diagramly/LibavoidRouting.js +4459 -0
  156. package/assets/drawio/js/export-init.js +21 -0
  157. package/assets/drawio/js/extensions.min.js +10478 -0
  158. package/assets/drawio/js/libavoid-js/LICENSE +505 -0
  159. package/assets/drawio/js/libavoid-js/libavoid-routing.js +1633 -0
  160. package/assets/drawio/js/libavoid-js/libavoid.min.js +59 -0
  161. package/assets/drawio/js/main.js +22 -0
  162. package/assets/drawio/js/rough/rough.min.js +1 -0
  163. package/assets/drawio/js/shapes-14-6-5.min.js +3457 -0
  164. package/assets/drawio/js/stencils.min.js +447 -0
  165. package/assets/drawio/mxgraph/css/common.css +147 -0
  166. package/assets/drawio/resources/dia.txt +2901 -0
  167. package/assets/drawio/styles/dark-default.xml +109 -0
  168. package/assets/drawio/styles/default-old.xml +109 -0
  169. package/assets/drawio/styles/default.xml +203 -0
  170. package/assets/drawio/styles/fonts/ArchitectsDaughter-Regular.ttf +0 -0
  171. package/assets/drawio/styles/grapheditor.css +4501 -0
  172. package/assets/drawio/styles/high-contrast.css +128 -0
  173. package/assets/drawio/styles/thumb_horz.png +0 -0
  174. package/assets/drawio/styles/thumb_vertical.png +0 -0
  175. package/assets/fonts/ibm-plex-mono-LICENSE.txt +93 -0
  176. package/assets/fonts/ibm-plex-sans-LICENSE.txt +93 -0
  177. package/assets/fonts/kalam-400.woff2 +0 -0
  178. package/assets/fonts/kalam-700.woff2 +0 -0
  179. package/assets/fonts/kalam-LICENSE.txt +93 -0
  180. package/assets/fonts/plex-mono-400.woff2 +0 -0
  181. package/assets/fonts/plex-mono-600.woff2 +0 -0
  182. package/assets/fonts/plex-sans-400.woff2 +0 -0
  183. package/assets/fonts/plex-sans-600.woff2 +0 -0
  184. package/dist/api.js +218 -0
  185. package/dist/cli.js +275 -0
  186. package/dist/drawio/icons.js +121 -0
  187. package/dist/drawio/merge.js +309 -0
  188. package/dist/drawio/read.js +180 -0
  189. package/dist/drawio/show.js +501 -0
  190. package/dist/drawio/theme.js +425 -0
  191. package/dist/drawio/write.js +300 -0
  192. package/dist/edit/server.js +212 -0
  193. package/dist/git.js +46 -0
  194. package/dist/import/mermaid.js +569 -0
  195. package/dist/layout/layout.js +212 -0
  196. package/dist/layout/sequence.js +333 -0
  197. package/dist/layout/text.js +73 -0
  198. package/dist/lint/check.js +188 -0
  199. package/dist/lint/fixlabels.js +128 -0
  200. package/dist/mcp/server.js +239 -0
  201. package/dist/publish.js +121 -0
  202. package/dist/render/browser.js +236 -0
  203. package/dist/render/fonts.js +66 -0
  204. package/dist/report.js +36 -0
  205. package/dist/spec/model.js +53 -0
  206. package/dist/spec/parse.js +497 -0
  207. package/package.json +71 -4
  208. package/skills/linework/SKILL.md +233 -0
  209. package/skills/linework/examples/billing-er.yaml +31 -0
  210. package/skills/linework/examples/checkout.yaml +28 -0
  211. package/skills/linework/examples/pay-sequence.yaml +28 -0
  212. package/skills/linework/examples/payment-state.yaml +22 -0
  213. package/skills/linework/examples/refund-flow.yaml +29 -0
  214. package/skills/linework/examples/serverless-aws.yaml +23 -0
  215. package/skills/linework/references/google-docs.md +46 -0
  216. package/skills/linework/references/spec.md +312 -0
  217. package/skills/linework/references/style-guide.md +53 -0
@@ -0,0 +1,425 @@
1
+ // Styles ("themes"). Output is plain draw.io style strings, so a person editing in
2
+ // draw.io sees and can change ordinary styles.
3
+ //
4
+ // Color rules, used by every style:
5
+ // - Nodes are neutral unless color means something. Semantic kinds (error, success,
6
+ // start/end) always carry their color. `soft` and `sketch` also tint by kind.
7
+ // - `color: <name>` tints a node, group or edge. `highlight: true` makes it the
8
+ // strongest thing on the page: a solid fill for nodes, a thick colored line for edges.
9
+ import { kindDef } from '../spec/model.js';
10
+ const SYSTEM = '-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, sans-serif';
11
+ export const FONT_STACK = `IBM Plex Sans, ${SYSTEM}`;
12
+ const SANS = { face: 'IBM Plex Sans', family: FONT_STACK, size: { label: 14, sub: 12, edge: 12, group: 12, field: 13 }, strong: 600, upperGroups: false };
13
+ const KALAM = { face: 'Kalam', family: `Kalam, Comic Sans MS, ${SYSTEM}`, size: { label: 16, sub: 14, edge: 14, group: 15, field: 15 }, strong: 700, upperGroups: false };
14
+ const MONO = {
15
+ face: 'IBM Plex Mono',
16
+ family: 'IBM Plex Mono, SF Mono, Menlo, Consolas, monospace',
17
+ size: { label: 13, sub: 11, edge: 11, group: 11, field: 12 },
18
+ strong: 600,
19
+ upperGroups: true,
20
+ };
21
+ /** Line height draw.io uses for HTML labels. */
22
+ export const LINE = 1.2;
23
+ export function typeface(theme = 'clean') {
24
+ return theme === 'sketch' ? KALAM : theme === 'blueprint' ? MONO : SANS;
25
+ }
26
+ /** Back-compat: sizes of the default style. */
27
+ export const font = SANS.size;
28
+ const LIGHT_SW = {
29
+ blue: { tint: '#EEF1FF', line: '#A9B8FF', ink: '#1E36B8', solid: '#2B50F5', edge: '#2B50F5' },
30
+ violet: { tint: '#F5F3FF', line: '#C4B5FD', ink: '#5B21B6', solid: '#7C3AED', edge: '#7C3AED' },
31
+ pink: { tint: '#FDF2F8', line: '#F9A8D4', ink: '#9D174D', solid: '#DB2777', edge: '#DB2777' },
32
+ red: { tint: '#FEF2F2', line: '#FCA5A5', ink: '#991B1B', solid: '#DC2626', edge: '#DC2626' },
33
+ orange: { tint: '#FFF7ED', line: '#FDBA74', ink: '#9A3412', solid: '#C2410C', edge: '#EA580C' },
34
+ amber: { tint: '#FFFBEB', line: '#FCD34D', ink: '#92400E', solid: '#B45309', edge: '#D97706' },
35
+ green: { tint: '#F0FDF4', line: '#86EFAC', ink: '#166534', solid: '#15803D', edge: '#16A34A' },
36
+ teal: { tint: '#F0FDFA', line: '#5EEAD4', ink: '#115E59', solid: '#0F766E', edge: '#0D9488' },
37
+ gray: { tint: '#F4F4F5', line: '#D4D4D8', ink: '#3F3F46', solid: '#52525B', edge: '#71717A' },
38
+ };
39
+ // Soft: borderless cards, so the tint has to carry the color on its own.
40
+ const SOFT_TINT = {
41
+ blue: '#E0E6FF',
42
+ violet: '#EDE9FE',
43
+ pink: '#FCE7F3',
44
+ red: '#FEE2E2',
45
+ orange: '#FFEDD5',
46
+ amber: '#FEF3C7',
47
+ green: '#DCFCE7',
48
+ teal: '#CCFBF1',
49
+ gray: '#F4F4F5',
50
+ };
51
+ const DARK_SW = {
52
+ blue: { tint: '#141B47', line: '#2A3A99', ink: '#C7D0FF', solid: '#2B50F5', edge: '#7D93FF' },
53
+ violet: { tint: '#2E1065', line: '#5B21B6', ink: '#DDD6FE', solid: '#7C3AED', edge: '#A78BFA' },
54
+ pink: { tint: '#500724', line: '#9D174D', ink: '#FBCFE8', solid: '#DB2777', edge: '#F472B6' },
55
+ red: { tint: '#450A0A', line: '#991B1B', ink: '#FECACA', solid: '#DC2626', edge: '#F87171' },
56
+ orange: { tint: '#431407', line: '#9A3412', ink: '#FED7AA', solid: '#C2410C', edge: '#FB923C' },
57
+ amber: { tint: '#451A03', line: '#92400E', ink: '#FDE68A', solid: '#B45309', edge: '#FBBF24' },
58
+ green: { tint: '#052E16', line: '#166534', ink: '#BBF7D0', solid: '#15803D', edge: '#4ADE80' },
59
+ teal: { tint: '#042F2E', line: '#115E59', ink: '#99F6E4', solid: '#0F766E', edge: '#2DD4BF' },
60
+ gray: { tint: '#27272A', line: '#52525B', ink: '#E4E4E7', solid: '#52525B', edge: '#A1A1AA' },
61
+ };
62
+ // Blueprint: light lines on deep blue. Color only changes the line.
63
+ const BP_LINE = {
64
+ blue: '#93C5FD',
65
+ violet: '#C4B5FD',
66
+ pink: '#F9A8D4',
67
+ red: '#FCA5A5',
68
+ orange: '#FDBA74',
69
+ amber: '#FDE68A',
70
+ green: '#86EFAC',
71
+ teal: '#5EEAD4',
72
+ gray: '#CBD5E1',
73
+ };
74
+ function lightPaint(sw, neutral, text, muted, accent) {
75
+ return (color, highlight) => {
76
+ if (highlight) {
77
+ const s = sw[color === 'neutral' || color === 'ink' ? accent : color];
78
+ return { fill: s.solid, stroke: s.solid, text: '#FFFFFF', sub: 'rgba(255,255,255,0.78)', strokeWidth: 1 };
79
+ }
80
+ if (color === 'ink')
81
+ return { fill: text, stroke: text, text: '#FFFFFF', sub: muted, strokeWidth: 1 };
82
+ if (color === 'neutral')
83
+ return { ...neutral, text, sub: muted, strokeWidth: 1 };
84
+ const s = sw[color];
85
+ return { fill: s.tint, stroke: s.line, text, sub: s.ink, strokeWidth: 1 };
86
+ };
87
+ }
88
+ const CLEAN = {
89
+ name: 'clean',
90
+ type: SANS,
91
+ canvas: '#FFFFFF',
92
+ text: '#17171C',
93
+ muted: '#6B6B76',
94
+ edge: '#A0A0AB',
95
+ edgeText: '#52525C',
96
+ edgeWidth: 1.25,
97
+ accent: 'blue',
98
+ kindColors: false,
99
+ radius: 10,
100
+ group: { fill: '#F5F5F7', nested: '#EBEBEF', stroke: 'none', dashed: false, text: '#6B6B76' },
101
+ note: { fill: '#FFF4CC', stroke: 'none', text: '#5C4300' },
102
+ well: '#F3F3F5',
103
+ paint: lightPaint(LIGHT_SW, { fill: '#FFFFFF', stroke: '#DADAE0' }, '#17171C', '#6B6B76', 'blue'),
104
+ edgePaint: (color, hl) => {
105
+ const s = LIGHT_SW[color ?? 'blue'];
106
+ if (hl)
107
+ return { stroke: s.edge, text: s.ink, width: 2 };
108
+ return color ? { stroke: s.edge, text: s.ink, width: 1.25 } : { stroke: CLEAN.edge, text: CLEAN.edgeText, width: 1.25 };
109
+ },
110
+ extra: '',
111
+ };
112
+ const SOFT = {
113
+ ...CLEAN,
114
+ name: 'soft',
115
+ kindColors: true,
116
+ radius: 12,
117
+ group: { fill: '#F7F7F9', nested: '#EFEFF3', stroke: 'none', dashed: false, text: '#6B6B76' },
118
+ paint: (color, hl) => {
119
+ if (hl || color === 'ink')
120
+ return CLEAN.paint(color, hl);
121
+ const c = color === 'neutral' ? 'gray' : color;
122
+ return { fill: SOFT_TINT[c], stroke: 'none', text: c === 'gray' ? '#17171C' : LIGHT_SW[c].ink, sub: c === 'gray' ? '#6B6B76' : LIGHT_SW[c].ink + 'B3', strokeWidth: 1 };
123
+ },
124
+ };
125
+ const DARK = {
126
+ name: 'dark',
127
+ type: SANS,
128
+ canvas: '#0D1117',
129
+ text: '#E6EDF3',
130
+ muted: '#9198A1',
131
+ edge: '#6E7681',
132
+ edgeText: '#9198A1',
133
+ edgeWidth: 1.25,
134
+ accent: 'blue',
135
+ kindColors: false,
136
+ radius: 10,
137
+ group: { fill: '#151B23', nested: '#1C232D', stroke: 'none', dashed: false, text: '#9198A1' },
138
+ note: { fill: '#3A2F0B', stroke: 'none', text: '#F8E3A1' },
139
+ well: '#262C36',
140
+ paint: (color, hl) => {
141
+ if (hl) {
142
+ const s = DARK_SW[color === 'neutral' || color === 'ink' ? 'blue' : color];
143
+ return { fill: s.solid, stroke: s.solid, text: '#FFFFFF', sub: 'rgba(255,255,255,0.78)', strokeWidth: 1 };
144
+ }
145
+ if (color === 'ink')
146
+ return { fill: '#E6EDF3', stroke: '#E6EDF3', text: '#0D1117', sub: '#59636E', strokeWidth: 1 };
147
+ if (color === 'neutral')
148
+ return { fill: '#212830', stroke: '#3D444D', text: '#E6EDF3', sub: '#9198A1', strokeWidth: 1 };
149
+ const s = DARK_SW[color];
150
+ return { fill: s.tint, stroke: s.line, text: '#E6EDF3', sub: s.ink, strokeWidth: 1 };
151
+ },
152
+ edgePaint: (color, hl) => {
153
+ const s = DARK_SW[color ?? 'blue'];
154
+ if (hl)
155
+ return { stroke: s.edge, text: s.ink, width: 2 };
156
+ return color ? { stroke: s.edge, text: s.ink, width: 1.25 } : { stroke: DARK.edge, text: DARK.edgeText, width: 1.25 };
157
+ },
158
+ extra: '',
159
+ };
160
+ const SKETCH_INK = '#1E1E1E';
161
+ const SKETCH = {
162
+ name: 'sketch',
163
+ type: KALAM,
164
+ canvas: '#FFFFFF',
165
+ text: SKETCH_INK,
166
+ muted: '#5C5C66',
167
+ edge: '#3F3F46',
168
+ edgeText: '#3F3F46',
169
+ edgeWidth: 1.5,
170
+ accent: 'red',
171
+ kindColors: true,
172
+ radius: 8,
173
+ group: { fill: 'none', stroke: '#71717A', dashed: true, text: '#3F3F46' },
174
+ note: { fill: '#FEF08A', stroke: SKETCH_INK, text: SKETCH_INK },
175
+ well: '#E4E4E7',
176
+ paint: (color, hl) => {
177
+ // Solid pastel fills: hachure lines look the part but run through the text.
178
+ const solid = 'fillStyle=solid;';
179
+ if (color === 'ink')
180
+ return { fill: SKETCH_INK, stroke: SKETCH_INK, text: '#FFFFFF', sub: '#5C5C66', strokeWidth: 1.5, extra: solid };
181
+ if (hl) {
182
+ const s = LIGHT_SW[color === 'neutral' ? 'red' : color];
183
+ return { fill: s.line, stroke: s.solid, text: SKETCH_INK, sub: s.ink, strokeWidth: 2.5, extra: solid };
184
+ }
185
+ if (color === 'neutral')
186
+ return { fill: '#FFFFFF', stroke: SKETCH_INK, text: SKETCH_INK, sub: '#5C5C66', strokeWidth: 1.5, extra: solid };
187
+ return { fill: SOFT_TINT[color], stroke: SKETCH_INK, text: SKETCH_INK, sub: '#5C5C66', strokeWidth: 1.5, extra: solid };
188
+ },
189
+ edgePaint: (color, hl) => {
190
+ if (!color && !hl)
191
+ return { stroke: SKETCH.edge, text: SKETCH.edgeText, width: 1.5 };
192
+ const s = LIGHT_SW[color ?? 'red'];
193
+ return { stroke: s.solid, text: s.ink, width: hl ? 2.5 : 1.5 };
194
+ },
195
+ extra: 'sketch=1;curveFitting=1;jiggle=2;',
196
+ };
197
+ const BP_BG = '#0F3460';
198
+ const BP_INK = '#EAF2FF';
199
+ const BP_HI = '#FFD43B';
200
+ const BLUEPRINT = {
201
+ name: 'blueprint',
202
+ type: MONO,
203
+ canvas: BP_BG,
204
+ text: BP_INK,
205
+ muted: '#9DB8DF',
206
+ edge: '#9DB8DF',
207
+ edgeText: '#C9DBF5',
208
+ edgeWidth: 1.25,
209
+ accent: 'amber',
210
+ kindColors: false,
211
+ radius: 4,
212
+ group: { fill: 'none', stroke: '#5B82B8', dashed: true, text: '#9DB8DF' },
213
+ note: { fill: '#16467E', stroke: '#5B82B8', text: BP_INK },
214
+ well: '#16467E',
215
+ paint: (color, hl) => {
216
+ if (hl)
217
+ return { fill: BP_HI, stroke: BP_HI, text: BP_BG, sub: '#3A4A63', strokeWidth: 1 };
218
+ if (color === 'ink')
219
+ return { fill: BP_INK, stroke: BP_INK, text: BP_BG, sub: BP_BG, strokeWidth: 1 };
220
+ if (color === 'neutral')
221
+ return { fill: BP_BG, stroke: BP_INK, text: BP_INK, sub: '#9DB8DF', strokeWidth: 1.25 };
222
+ return { fill: BP_BG, stroke: BP_LINE[color], text: BP_INK, sub: BP_LINE[color], strokeWidth: 1.25 };
223
+ },
224
+ edgePaint: (color, hl) => {
225
+ if (hl)
226
+ return { stroke: BP_HI, text: BP_HI, width: 2 };
227
+ return color ? { stroke: BP_LINE[color], text: BP_LINE[color], width: 1.25 } : { stroke: BLUEPRINT.edge, text: BLUEPRINT.edgeText, width: 1.25 };
228
+ },
229
+ extra: '',
230
+ };
231
+ // C4-PlantUML's colours for architecture kinds; other diagram types use `clean`.
232
+ const C4_KIND = {
233
+ person: { fill: '#08427B', stroke: '#073B6F', text: '#FFFFFF', type: 'Person' },
234
+ external: { fill: '#999999', stroke: '#8A8A8A', text: '#FFFFFF', type: 'External system' },
235
+ };
236
+ const C4_CONTAINER = { fill: '#438DD5', stroke: '#3C7FC0', text: '#FFFFFF', type: 'Container' };
237
+ const C4 = { ...CLEAN, name: 'c4', group: { fill: 'none', stroke: '#444444', dashed: true, text: '#444444' }, edge: '#707070', edgeText: '#444444' };
238
+ const THEMES_BY_NAME = { clean: CLEAN, soft: SOFT, dark: DARK, sketch: SKETCH, blueprint: BLUEPRINT, c4: C4 };
239
+ export function palette(theme = 'clean') {
240
+ return THEMES_BY_NAME[theme] ?? CLEAN;
241
+ }
242
+ const fontPart = (t, size, color) => `fontFamily=${t.type.family};fontSize=${size};fontColor=${color};`;
243
+ /** How a node of this kind looks. */
244
+ export function paintFor(type, kind, theme, look = {}) {
245
+ const t = palette(theme);
246
+ if (theme === 'c4' && type === 'architecture' && !look.highlight && !look.color) {
247
+ const c = C4_KIND[kind] ?? C4_CONTAINER;
248
+ return { fill: c.fill, stroke: c.stroke, text: c.text, sub: 'rgba(255,255,255,0.8)', strokeWidth: 1 };
249
+ }
250
+ const k = kindDef(type, kind);
251
+ // A highlight is the style's accent unless the spec names a color.
252
+ if (look.highlight)
253
+ return t.paint(look.color ?? 'neutral', true);
254
+ const base = look.color ?? (k.color === 'ink' || k.color === 'neutral' || t.kindColors || k.semantic ? k.color : 'neutral');
255
+ return t.paint(base, false);
256
+ }
257
+ const dashes = (t) => (t.name === 'sketch' ? 'dashed=1;dashPattern=8 6;' : 'dashed=1;dashPattern=5 4;');
258
+ export function nodeStyle(type, kind, theme = 'clean', look = {}) {
259
+ const k = kindDef(type, kind);
260
+ const t = palette(theme);
261
+ const c = paintFor(type, kind, theme, look);
262
+ // Soft cards have no border; a dashed kind still needs one to show its dashes.
263
+ const stroke = c.stroke === 'none' && k.dashed ? '#A1A1AA' : c.stroke;
264
+ const base = `html=1;whiteSpace=wrap;spacingLeft=${PAD_X - 4};spacingRight=${PAD_X - 4};${fontPart(t, t.type.size.label, c.text)}` +
265
+ `fillColor=${c.fill};strokeColor=${stroke};strokeWidth=${c.strokeWidth};` +
266
+ (k.dashed ? dashes(t) : '') +
267
+ t.extra +
268
+ (c.extra ?? '');
269
+ const r = t.radius;
270
+ switch (k.shape) {
271
+ case 'cylinder':
272
+ return `shape=cylinder3;boundedLbl=1;backgroundOutline=1;size=7;${base}`;
273
+ case 'pill':
274
+ return `rounded=1;arcSize=50;${base}`;
275
+ case 'diamond':
276
+ return `rhombus;${base}spacingLeft=0;spacingRight=0;`;
277
+ case 'parallelogram':
278
+ return `shape=parallelogram;perimeter=parallelogramPerimeter;fixedSize=1;size=14;${base}`;
279
+ case 'start':
280
+ return `ellipse;aspect=fixed;${base}`;
281
+ case 'end':
282
+ return `ellipse;shape=endState;aspect=fixed;${base}`;
283
+ case 'table': {
284
+ // An entity: header (startSize) with field rows stacked inside.
285
+ const neutral = !look.highlight && !look.color && !(t.kindColors && k.color !== 'neutral');
286
+ const head = neutral && theme !== 'c4' ? t.well : c.fill;
287
+ const border = stroke === 'none' ? head : neutral && theme !== 'c4' ? palette(theme).paint('neutral', false).stroke : stroke;
288
+ return (`shape=swimlane;childLayout=stackLayout;horizontal=1;startSize=${ER_HEAD};horizontalStack=0;resizeParent=1;resizeParentMax=0;` +
289
+ `resizeLast=0;collapsible=0;marginBottom=0;rounded=1;absoluteArcSize=1;arcSize=${r + 2};swimlaneFillColor=${t.canvas};fontStyle=1;` +
290
+ base.replace(/fillColor=[^;]*;strokeColor=[^;]*;/, `fillColor=${head};strokeColor=${border};`));
291
+ }
292
+ default:
293
+ return `rounded=1;absoluteArcSize=1;arcSize=${r * 2};${base}`;
294
+ }
295
+ }
296
+ /** Horizontal text padding inside a node, each side. */
297
+ export const PAD_X = 16;
298
+ /** Vertical text padding inside a node, each side. */
299
+ export const PAD_Y = 12;
300
+ export const ER_HEAD = 36;
301
+ export const ER_ROW = 28;
302
+ export function fieldStyle(theme = 'clean') {
303
+ const t = palette(theme);
304
+ return ('text;html=1;strokeColor=none;fillColor=none;align=left;verticalAlign=middle;spacingLeft=0;spacingRight=0;overflow=fill;' +
305
+ `rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;whiteSpace=wrap;${fontPart(t, t.type.size.field, t.text)}`);
306
+ }
307
+ export function groupStyle(theme = 'clean', look = {}, depth = 0) {
308
+ const t = palette(theme);
309
+ let { fill, stroke, text } = t.group;
310
+ if (depth > 0 && t.group.nested)
311
+ fill = t.group.nested;
312
+ if (look.color || look.highlight) {
313
+ const p = t.paint(look.color ?? t.accent, false);
314
+ fill = theme === 'blueprint' || theme === 'sketch' ? 'none' : p.fill;
315
+ // A colored ground carries the color; only a highlight adds an outline.
316
+ stroke = look.highlight ? t.edgePaint(look.color, true).stroke : t.group.stroke === 'none' ? 'none' : p.stroke;
317
+ text = p.sub;
318
+ }
319
+ return (`rounded=1;absoluteArcSize=1;arcSize=${t.radius * 2 + 4};html=1;whiteSpace=wrap;container=1;collapsible=0;` +
320
+ `fillColor=${fill};strokeColor=${stroke};strokeWidth=${look.highlight ? 1.5 : 1};` +
321
+ (t.group.dashed ? dashes(t) : '') +
322
+ 'verticalAlign=top;align=left;spacingLeft=14;spacingTop=9;' +
323
+ `${fontPart(t, t.type.size.group, text)}fontStyle=1;${t.extra}`);
324
+ }
325
+ /** Group title HTML: sentence case, except blueprint's drafting capitals. */
326
+ export function groupLabel(label, theme = 'clean') {
327
+ return typeface(theme).upperGroups ? `<span style="text-transform:uppercase;letter-spacing:0.06em">${esc(label)}</span>` : esc(label);
328
+ }
329
+ const ARROW = 'endArrow=block;endFill=1;endSize=5;';
330
+ const START_ARROW = 'startArrow=block;startFill=1;startSize=5;';
331
+ export function edgeStyle(style, theme = 'clean', look = {}, labelBg) {
332
+ const t = palette(theme);
333
+ const p = t.edgePaint(look.color, !!look.highlight);
334
+ const ends = style === 'line' ? 'endArrow=none;startArrow=none;' : style === 'bidir' ? `${ARROW}${START_ARROW}` : `${ARROW}startArrow=none;`;
335
+ return ('edgeStyle=orthogonalEdgeStyle;rounded=1;arcSize=16;html=1;' +
336
+ `strokeColor=${p.stroke};strokeWidth=${p.width};${ends}` +
337
+ (style === 'async' ? dashes(t) : '') +
338
+ `${fontPart(t, t.type.size.edge, p.text)}${look.highlight ? 'fontStyle=1;' : ''}labelBackgroundColor=${labelBg ?? t.canvas};labelPadding=1 5;${t.extra}`);
339
+ }
340
+ const ER_END = { one: 'ERmandOne', zero_or_one: 'ERzeroToOne', many: 'ERzeroToMany', one_or_many: 'ERoneToMany' };
341
+ export function erEdgeStyle(card, theme = 'clean', look = {}) {
342
+ const t = palette(theme);
343
+ const p = t.edgePaint(look.color, !!look.highlight);
344
+ return ('edgeStyle=orthogonalEdgeStyle;rounded=1;arcSize=16;html=1;' +
345
+ `strokeColor=${p.stroke};strokeWidth=${p.width};startArrow=${ER_END[card.from]};endArrow=${ER_END[card.to]};startFill=0;endFill=0;startSize=10;endSize=10;` +
346
+ (card.identifying ? '' : dashes(t)) +
347
+ `${fontPart(t, t.type.size.edge, p.text)}labelBackgroundColor=${t.canvas};labelPadding=1 5;${t.extra}`);
348
+ }
349
+ export function cardFromArrow(arrow) {
350
+ return Object.entries(ER_END).find(([, v]) => v === arrow)?.[0] ?? undefined;
351
+ }
352
+ export function lifelineStyle(kind, theme = 'clean', headH = 48, look = {}) {
353
+ const t = palette(theme);
354
+ const c = paintFor('sequence', kind, theme, look);
355
+ const k = kindDef('sequence', kind);
356
+ const stroke = c.stroke === 'none' ? (k.dashed ? '#A1A1AA' : c.fill) : c.stroke;
357
+ // The lifeline's dashed stem uses the stroke colour: keep it quiet even when the head is loud.
358
+ return (`shape=umlLifeline;perimeter=lifelinePerimeter;container=1;collapsible=0;recursiveResize=0;outlineConnect=0;size=${headH};` +
359
+ `html=1;whiteSpace=wrap;rounded=1;absoluteArcSize=1;arcSize=${t.radius * 2};fillColor=${c.fill};strokeColor=${stroke};strokeWidth=${Math.min(1.5, c.strokeWidth)};` +
360
+ `spacingLeft=${PAD_X - 4};spacingRight=${PAD_X - 4};` +
361
+ (k.dashed ? dashes(t) : '') +
362
+ `${fontPart(t, t.type.size.label, c.text)}${t.extra}${c.extra ?? ''}`);
363
+ }
364
+ export function activationStyle(kind, theme = 'clean', look = {}) {
365
+ const t = palette(theme);
366
+ // A highlighted participant's bar is tinted, not solid: the head already shouts.
367
+ const c = look.highlight ? t.paint(look.color ?? (theme === 'sketch' ? 'red' : t.accent), false) : paintFor('sequence', kind, theme, look);
368
+ const neutral = c.stroke === t.paint('neutral', false).stroke && c.fill === t.paint('neutral', false).fill;
369
+ const fill = neutral ? t.well : c.fill;
370
+ const stroke = c.stroke === 'none' ? fill : neutral ? t.edge : c.stroke;
371
+ return `html=1;points=[];perimeter=orthogonalPerimeter;outlineConnect=0;targetShapes=umlLifeline;portConstraint=eastwest;fillColor=${fill};strokeColor=${stroke};strokeWidth=1;${t.extra}${c.extra ?? ''}`;
372
+ }
373
+ export function messageStyle(style, theme = 'clean', self = false, look = {}) {
374
+ const t = palette(theme);
375
+ const p = t.edgePaint(look.color, !!look.highlight);
376
+ const arrow = style === 'sync' ? 'endArrow=block;endFill=1;' : 'endArrow=open;endFill=0;';
377
+ return (`html=1;verticalAlign=bottom;${arrow}endSize=6;strokeWidth=${p.width};strokeColor=${p.stroke};` +
378
+ (style === 'reply' ? dashes(t) : '') +
379
+ (self ? 'edgeStyle=orthogonalEdgeStyle;rounded=1;align=left;spacingLeft=6;verticalAlign=middle;' : '') +
380
+ `${fontPart(t, t.type.size.edge, p.text)}${look.highlight ? 'fontStyle=1;' : ''}labelBackgroundColor=${t.canvas};labelPadding=1 5;${t.extra}`);
381
+ }
382
+ export function frameStyle(theme = 'clean') {
383
+ const t = palette(theme);
384
+ const line = t.name === 'dark' ? '#3D444D' : t.name === 'clean' || t.name === 'soft' || t.name === 'c4' ? '#DADAE0' : t.muted;
385
+ // The operator (alt, loop, opt) sits in the corner as a small label, not a UML tab.
386
+ return (`rounded=1;absoluteArcSize=1;arcSize=${t.radius * 2};whiteSpace=wrap;html=1;container=0;verticalAlign=top;align=left;spacing=0;spacingLeft=10;spacingTop=4;` +
387
+ `fillColor=none;strokeColor=${line};strokeWidth=1;${fontPart(t, t.type.size.group, t.muted)}fontStyle=1;${t.extra}`);
388
+ }
389
+ export function guardStyle(theme = 'clean') {
390
+ const t = palette(theme);
391
+ return `text;html=1;align=left;verticalAlign=middle;labelBackgroundColor=${t.canvas};${fontPart(t, t.type.size.edge, t.muted)}`;
392
+ }
393
+ export function separatorStyle(theme = 'clean') {
394
+ const t = palette(theme);
395
+ return `endArrow=none;dashed=1;dashPattern=4 4;html=1;strokeColor=${t.name === 'dark' ? '#3D444D' : t.name === 'clean' || t.name === 'soft' || t.name === 'c4' ? '#DADAE0' : t.muted};`;
396
+ }
397
+ export function noteStyle(theme = 'clean') {
398
+ const t = palette(theme);
399
+ const shape = t.name === 'sketch' ? 'shape=note;size=10;' : `rounded=1;absoluteArcSize=1;arcSize=${t.radius};`;
400
+ return `${shape}whiteSpace=wrap;html=1;fillColor=${t.note.fill};strokeColor=${t.note.stroke};strokeWidth=1;${fontPart(t, t.type.size.edge, t.note.text)}align=left;spacingLeft=10;spacingRight=10;${t.extra}`;
401
+ }
402
+ const esc = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
403
+ /** HTML label: strong name, optional quieter tech line. C4 adds the element type. */
404
+ export function nodeLabel(label, tech, opts = {}) {
405
+ const theme = opts.theme ?? 'clean';
406
+ const tf = typeface(theme);
407
+ const main = `<span style="font-weight:${tf.strong}">${esc(label)}</span>`;
408
+ const sub = (text, color) => `${main}<br><span style="font-size:${tf.size.sub}px;color:${color}">${esc(text)}</span>`;
409
+ const p = paintFor(opts.type ?? 'architecture', opts.kind ?? '', theme, opts);
410
+ if (theme === 'c4' && opts.type === 'architecture' && !opts.highlight && !opts.color) {
411
+ const t = (C4_KIND[opts.kind ?? ''] ?? C4_CONTAINER).type;
412
+ return sub(`[${tech ? `${t}: ${tech}` : t}]`, p.sub);
413
+ }
414
+ return tech ? sub(tech, p.sub) : main;
415
+ }
416
+ /** One ER field row: name on the left, type and key markers on the right. */
417
+ export function fieldLabel(name, type, flags, theme = 'clean') {
418
+ const t = palette(theme);
419
+ const keys = flags.filter((f) => f === 'pk' || f === 'fk').map((f) => f.toUpperCase());
420
+ const rest = flags.filter((f) => f !== 'pk' && f !== 'fk');
421
+ const nameHtml = keys.includes('PK') ? `<span style="font-weight:${t.type.strong}">${esc(name)}</span>` : esc(name);
422
+ const right = [type, ...rest, ...keys].filter(Boolean).join(' ');
423
+ return `<table style="width:100%;height:100%;border-collapse:collapse"><tr><td style="padding:0 6px 0 ${FIELD_PAD}px">${nameHtml}</td><td style="text-align:right;padding:0 ${FIELD_PAD}px 0 6px;color:${t.muted}">${esc(right)}</td></tr></table>`;
424
+ }
425
+ export const FIELD_PAD = 14;