@diagrammo/dgmo 0.46.0 → 0.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (232) hide show
  1. package/dist/advanced.d.cts +3 -855
  2. package/dist/advanced.d.ts +3 -855
  3. package/dist/advanced.js +541 -70107
  4. package/dist/analyzer-UYKBXAKR.js +15 -0
  5. package/dist/auto.cjs +1 -1
  6. package/dist/auto.js +1 -1
  7. package/dist/auto.mjs +1 -1
  8. package/dist/block.js +83 -69417
  9. package/dist/calculator-LDYQCDDY.js +11 -0
  10. package/dist/charts-d3-QOZHKTCT.js +25 -0
  11. package/dist/chunk-2DXMZRWJ.js +800 -0
  12. package/dist/chunk-2LACINWN.js +786 -0
  13. package/dist/chunk-33LCUH4H.js +893 -0
  14. package/dist/chunk-3CIOJ5YJ.js +35 -0
  15. package/dist/chunk-3HDBAXF5.js +86 -0
  16. package/dist/chunk-3JLKYGBQ.js +290 -0
  17. package/dist/chunk-3MQQNXPD.js +415 -0
  18. package/dist/chunk-3R5POCTZ.js +967 -0
  19. package/dist/chunk-3SMGIJDD.js +112 -0
  20. package/dist/chunk-44CM72SQ.js +409 -0
  21. package/dist/chunk-4GRI7MEB.js +1604 -0
  22. package/dist/chunk-4OHACBGX.js +606 -0
  23. package/dist/chunk-5DH3KYFH.js +21 -0
  24. package/dist/chunk-5MLYVTYM.js +76 -0
  25. package/dist/chunk-5QXL4NYS.js +553 -0
  26. package/dist/chunk-5WRI5ZAA.js +31 -0
  27. package/dist/chunk-6EK55ART.js +117 -0
  28. package/dist/chunk-6FZYCKXB.js +2065 -0
  29. package/dist/chunk-6HO5ZTXK.js +277 -0
  30. package/dist/chunk-6KFWC4CO.js +7 -0
  31. package/dist/chunk-6MTWIQVO.js +121 -0
  32. package/dist/chunk-6NYWYNBI.js +329 -0
  33. package/dist/chunk-6SM5NJQH.js +620 -0
  34. package/dist/chunk-725LDASD.js +422 -0
  35. package/dist/chunk-76OWZ3BA.js +130 -0
  36. package/dist/chunk-7JEGTRLD.js +338 -0
  37. package/dist/chunk-A44USUV7.js +923 -0
  38. package/dist/chunk-A4GOY3PE.js +25 -0
  39. package/dist/chunk-A4VFQH5G.js +251 -0
  40. package/dist/chunk-AELOBFW5.js +375 -0
  41. package/dist/chunk-AN2MOO5T.js +522 -0
  42. package/dist/chunk-BMR4CIEO.js +41 -0
  43. package/dist/chunk-BS7SV7C3.js +51 -0
  44. package/dist/chunk-BUPDIHML.js +613 -0
  45. package/dist/chunk-BUVJFRPY.js +887 -0
  46. package/dist/chunk-C3Z4DX3V.js +112 -0
  47. package/dist/chunk-CI2G3OEB.js +1818 -0
  48. package/dist/chunk-CKNVO24D.js +54 -0
  49. package/dist/chunk-CUVHQHZ7.js +391 -0
  50. package/dist/chunk-CXK4DZH3.js +114 -0
  51. package/dist/chunk-DKEPS5LH.js +315 -0
  52. package/dist/chunk-DU7W62N5.js +487 -0
  53. package/dist/chunk-DZAQBW35.js +751 -0
  54. package/dist/chunk-EB6FPQUP.js +316 -0
  55. package/dist/chunk-EMS2SK26.js +376 -0
  56. package/dist/chunk-ERLBPDGU.js +510 -0
  57. package/dist/chunk-EVCVDTXE.js +732 -0
  58. package/dist/chunk-FCIIHT24.js +384 -0
  59. package/dist/chunk-FFUN5D66.js +97 -0
  60. package/dist/chunk-FITOLXSX.js +714 -0
  61. package/dist/chunk-FMDSF3OV.js +68 -0
  62. package/dist/chunk-FNB3PNGF.js +410 -0
  63. package/dist/chunk-G2H74XJZ.js +2104 -0
  64. package/dist/chunk-GMR5MFHJ.js +339 -0
  65. package/dist/chunk-H6PI3KJT.js +305 -0
  66. package/dist/chunk-I46X5VUA.js +59 -0
  67. package/dist/chunk-I5IGB56C.js +255 -0
  68. package/dist/chunk-IH6ZMVQ5.js +3950 -0
  69. package/dist/chunk-IJZFN3HM.js +463 -0
  70. package/dist/chunk-IPWPWM6R.js +3079 -0
  71. package/dist/chunk-IYULMRBE.js +23 -0
  72. package/dist/chunk-IZMRON6C.js +167 -0
  73. package/dist/chunk-J5FIXQRT.js +813 -0
  74. package/dist/chunk-JED6ZDEE.js +73 -0
  75. package/dist/chunk-JEUJOX5X.js +136 -0
  76. package/dist/chunk-JM4LEPY3.js +436 -0
  77. package/dist/chunk-L3UHP6TD.js +101 -0
  78. package/dist/chunk-L66GEFB6.js +524 -0
  79. package/dist/chunk-LIOHW36Z.js +469 -0
  80. package/dist/chunk-LP7EC4MI.js +16 -0
  81. package/dist/chunk-MITI6NOL.js +548 -0
  82. package/dist/chunk-MO5YHHMQ.js +168 -0
  83. package/dist/chunk-MZSBFAPJ.js +885 -0
  84. package/dist/chunk-NNAAJVBU.js +9 -0
  85. package/dist/chunk-NOTA6TEI.js +47 -0
  86. package/dist/chunk-NURGB5Z4.js +335 -0
  87. package/dist/chunk-NX4EQS3U.js +1062 -0
  88. package/dist/chunk-NXMESY4J.js +1 -0
  89. package/dist/chunk-OJR3MQYV.js +1458 -0
  90. package/dist/chunk-OURHMODR.js +454 -0
  91. package/dist/chunk-P235SVAH.js +1057 -0
  92. package/dist/chunk-P4RH4OSP.js +1038 -0
  93. package/dist/chunk-P6T66CCZ.js +226 -0
  94. package/dist/chunk-PBAHEPLQ.js +223 -0
  95. package/dist/chunk-PBHE5UG6.js +1530 -0
  96. package/dist/chunk-PDKUANDZ.js +35 -0
  97. package/dist/chunk-PWIHZ6NJ.js +296 -0
  98. package/dist/chunk-QEBUY5NY.js +1685 -0
  99. package/dist/chunk-QLCCJEJI.js +253 -0
  100. package/dist/chunk-QOOUC2J5.js +52 -0
  101. package/dist/chunk-QYTPPDAO.js +402 -0
  102. package/dist/chunk-R3XI2VC7.js +801 -0
  103. package/dist/chunk-RCERWOJR.js +154 -0
  104. package/dist/chunk-RJE5JTVK.js +362 -0
  105. package/dist/chunk-RTLBSQ3G.js +1155 -0
  106. package/dist/chunk-RUNNI6VE.js +448 -0
  107. package/dist/chunk-RVMRZAPB.js +354 -0
  108. package/dist/chunk-RYEGFMUO.js +955 -0
  109. package/dist/chunk-RZCF2VVM.js +528 -0
  110. package/dist/chunk-SML67VPV.js +1025 -0
  111. package/dist/chunk-SQ7IY53Q.js +44 -0
  112. package/dist/chunk-SQAG3UXH.js +161 -0
  113. package/dist/chunk-SRTU6HMQ.js +1851 -0
  114. package/dist/chunk-T2OE3YQB.js +550 -0
  115. package/dist/chunk-T3UBLACL.js +299 -0
  116. package/dist/chunk-TLVIHFRO.js +296 -0
  117. package/dist/chunk-TYS6CSS5.js +52 -0
  118. package/dist/chunk-U6EQI3U6.js +130 -0
  119. package/dist/chunk-UJ4FJLBF.js +86 -0
  120. package/dist/chunk-ULOEATBZ.js +347 -0
  121. package/dist/chunk-UNCCZ3NU.js +127 -0
  122. package/dist/chunk-USZW2HHU.js +197 -0
  123. package/dist/chunk-UWN33VNX.js +1610 -0
  124. package/dist/chunk-UZ7EZW3B.js +40 -0
  125. package/dist/chunk-VOKGH627.js +37 -0
  126. package/dist/chunk-WBRZJOEC.js +204 -0
  127. package/dist/chunk-WCSOJFU3.js +380 -0
  128. package/dist/chunk-WCT3NYCV.js +160 -0
  129. package/dist/chunk-WVAJFAIA.js +529 -0
  130. package/dist/chunk-X5LCYCL5.js +394 -0
  131. package/dist/chunk-XAKEUMEJ.js +1256 -0
  132. package/dist/chunk-Y2ZE3MSG.js +590 -0
  133. package/dist/chunk-Y3J7VR52.js +646 -0
  134. package/dist/chunk-Y5QGVTVC.js +33 -0
  135. package/dist/chunk-YAA6IID3.js +548 -0
  136. package/dist/chunk-YGJTDXVR.js +692 -0
  137. package/dist/chunk-YRYKTYMQ.js +186 -0
  138. package/dist/chunk-YYVTBZZC.js +61 -0
  139. package/dist/chunk-Z5CGSFJB.js +647 -0
  140. package/dist/chunk-Z72XRXD4.js +787 -0
  141. package/dist/chunk-ZGR7ULFJ.js +978 -0
  142. package/dist/chunk-ZJEL4H2R.js +851 -0
  143. package/dist/chunk-ZO27FGJS.js +554 -0
  144. package/dist/collapse-CLPJD2RL.js +11 -0
  145. package/dist/collapse-SLINJQW6.js +9 -0
  146. package/dist/collapse-ZWUFFCK2.js +9 -0
  147. package/dist/compute-G55RG53N.js +8 -0
  148. package/dist/dgmo-router-DZCIDUO5.js +73 -0
  149. package/dist/dimensions-J3FHIMJ6.js +22 -0
  150. package/dist/element.cjs +1 -1
  151. package/dist/element.js +1 -1
  152. package/dist/element.mjs +1 -1
  153. package/dist/flowchart-parser-ZNSKIBVR.js +21 -0
  154. package/dist/flowchart-renderer-ZAEOL4DX.js +35 -0
  155. package/dist/index.cjs +3 -3
  156. package/dist/index.d.cts +3 -469
  157. package/dist/index.d.ts +3 -469
  158. package/dist/index.js +137 -69685
  159. package/dist/layout-46N4JJYK.js +17 -0
  160. package/dist/layout-5RA6QWR6.js +26 -0
  161. package/dist/layout-5T6RH5M6.js +14 -0
  162. package/dist/layout-7QXCNOVS.js +13 -0
  163. package/dist/layout-EE6GE46Y.js +17 -0
  164. package/dist/layout-G23F7LDH.js +9 -0
  165. package/dist/layout-HRBZPFCN.js +23 -0
  166. package/dist/layout-HWDSRX5E.js +20 -0
  167. package/dist/layout-HYZPWAK5.js +18 -0
  168. package/dist/layout-N76NR4ZO.js +19 -0
  169. package/dist/layout-OB3RI63S.js +14 -0
  170. package/dist/layout-S2QBYSK2.js +15 -0
  171. package/dist/layout-UM5F4DJB.js +15 -0
  172. package/dist/layout-search-OYV77HHS.js +1655 -0
  173. package/dist/load-data-EHDDZ5ZS.js +8 -0
  174. package/dist/palettes-PRJ2RI6D.js +56 -0
  175. package/dist/parser-2MFP2H2E.js +11 -0
  176. package/dist/parser-3STE62GB.js +21 -0
  177. package/dist/parser-4FL2CMDG.js +18 -0
  178. package/dist/parser-4OGOBJJ4.js +13 -0
  179. package/dist/parser-6BTRLKRZ.js +15 -0
  180. package/dist/parser-AQETEVRW.js +21 -0
  181. package/dist/parser-CFZD5IBL.js +12 -0
  182. package/dist/parser-FUSKFGZN.js +16 -0
  183. package/dist/parser-I6KXEYFK.js +13 -0
  184. package/dist/parser-IXSZZQHY.js +18 -0
  185. package/dist/parser-JAL2HD4I.js +13 -0
  186. package/dist/parser-KNSQSKQO.js +20 -0
  187. package/dist/parser-N5O4IHUN.js +13 -0
  188. package/dist/parser-NGU3ETO6.js +16 -0
  189. package/dist/parser-NRAI6O25.js +19 -0
  190. package/dist/parser-O5J4VBII.js +11 -0
  191. package/dist/parser-OV7NOBW6.js +21 -0
  192. package/dist/parser-PQ3POX45.js +23 -0
  193. package/dist/parser-TFZFBVWN.js +11 -0
  194. package/dist/parser-TRTHDFPP.js +18 -0
  195. package/dist/parser-VQP2UJ6W.js +11 -0
  196. package/dist/parser-XDVELHRK.js +13 -0
  197. package/dist/parser-XKRP7QKP.js +11 -0
  198. package/dist/parser-Y6WTBU76.js +14 -0
  199. package/dist/parser-ZSDPZYQP.js +12 -0
  200. package/dist/renderer-2XQQQ6QR.js +28 -0
  201. package/dist/renderer-5NDVNDAZ.js +26 -0
  202. package/dist/renderer-6EEZHMH4.js +26 -0
  203. package/dist/renderer-6W5462RB.js +43 -0
  204. package/dist/renderer-7MVLWWQD.js +22 -0
  205. package/dist/renderer-AWWAF6XD.js +33 -0
  206. package/dist/renderer-BYCNMBF7.js +34 -0
  207. package/dist/renderer-CLJHQPBJ.js +29 -0
  208. package/dist/renderer-GHPZMFU2.js +15 -0
  209. package/dist/renderer-JGBKUCZD.js +16 -0
  210. package/dist/renderer-KQB7UGDS.js +19 -0
  211. package/dist/renderer-PGX2TKVS.js +24 -0
  212. package/dist/renderer-QIWVHWZO.js +15 -0
  213. package/dist/renderer-RPOA5YPZ.js +35 -0
  214. package/dist/renderer-SMIBCRVB.js +24 -0
  215. package/dist/renderer-T7YKC5VP.js +28 -0
  216. package/dist/renderer-TOCOVLHS.js +37 -0
  217. package/dist/renderer-UWA5XS74.js +39 -0
  218. package/dist/renderer-VACVQYML.js +25 -0
  219. package/dist/renderer-W4M6QLIJ.js +37 -0
  220. package/dist/renderer-WZRREQ2P.js +23 -0
  221. package/dist/renderer-YY2OSF6E.js +27 -0
  222. package/dist/renderer-Z7I3W2PP.js +24 -0
  223. package/dist/renderer-ZGNTUWC4.js +22 -0
  224. package/dist/renderer-ZTR3DER3.js +19 -0
  225. package/dist/renderer-radial-C32GPJQ7.js +21 -0
  226. package/dist/resolver-NOAA7ID7.js +13 -0
  227. package/dist/state-collapse-HCGD6IVJ.js +8 -0
  228. package/dist/state-parser-Z2V23ATR.js +18 -0
  229. package/dist/state-renderer-RFYHSP4K.js +34 -0
  230. package/dist/themes-1-CuKpeH.d.cts +908 -0
  231. package/dist/themes-1-CuKpeH.d.ts +908 -0
  232. package/package.json +1 -1
@@ -0,0 +1,851 @@
1
+ import {
2
+ TAG_STRIP_HEIGHT,
3
+ layoutJourneyMap,
4
+ scoreToColor,
5
+ scoreToCurveY
6
+ } from "./chunk-3JLKYGBQ.js";
7
+ import {
8
+ wrapNoteBody
9
+ } from "./chunk-TYS6CSS5.js";
10
+ import {
11
+ NOTE_BULLET_INDENT
12
+ } from "./chunk-5DH3KYFH.js";
13
+ import {
14
+ renderInlineText
15
+ } from "./chunk-3HDBAXF5.js";
16
+ import {
17
+ CARD_RADIUS
18
+ } from "./chunk-3CIOJ5YJ.js";
19
+ import {
20
+ renderIntegratedLegend
21
+ } from "./chunk-PWIHZ6NJ.js";
22
+ import {
23
+ ScaleContext
24
+ } from "./chunk-YYVTBZZC.js";
25
+ import {
26
+ FONT_FAMILY
27
+ } from "./chunk-6KFWC4CO.js";
28
+ import {
29
+ measureText,
30
+ truncateText,
31
+ wrapTextToWidth
32
+ } from "./chunk-WCT3NYCV.js";
33
+ import {
34
+ parseJourneyMap
35
+ } from "./chunk-44CM72SQ.js";
36
+ import {
37
+ contrastText,
38
+ mix,
39
+ shapeFill
40
+ } from "./chunk-P6T66CCZ.js";
41
+ import {
42
+ resolveActiveTagGroup,
43
+ tagAttrKey
44
+ } from "./chunk-QLCCJEJI.js";
45
+
46
+ // src/journey-map/renderer.ts
47
+ import * as d3 from "d3-selection";
48
+ import * as d3Shape from "d3-shape";
49
+ var DIAGRAM_PADDING = 20;
50
+ var PADDING = DIAGRAM_PADDING;
51
+ var CARD_PADDING_X = 10;
52
+ var CARD_PADDING_Y = 6;
53
+ var CARD_HEADER_HEIGHT = 24;
54
+ var CARD_STROKE_WIDTH = 1.5;
55
+ var CARD_META_LINE_HEIGHT = 14;
56
+ var CARD_GAP_INTERNAL = 8;
57
+ var COLUMN_RADIUS = 8;
58
+ var COLUMN_HEADER_HEIGHT = 36;
59
+ var COLUMN_PADDING = 12;
60
+ var FONT_SIZE_TITLE = 18;
61
+ var FONT_SIZE_PHASE = 13;
62
+ var FONT_SIZE_STEP = 12;
63
+ var FONT_SIZE_META = 10;
64
+ var GRID_LINE_OPACITY = 0.15;
65
+ var CURVE_STROKE_WIDTH = 2.5;
66
+ var FACE_RADIUS = 14;
67
+ var CURVE_AREA_BOTTOM_GAP = 28;
68
+ var FACE_HOVER_SCALE = 1.5;
69
+ var FACE_HOVER_R = (FACE_RADIUS + 1) * FACE_HOVER_SCALE;
70
+ var DIM_HOVER = 0.25;
71
+ var TITLE_LINE_HEIGHT = 16;
72
+ function renderJourneyMap(container, parsed, palette, isDark, options) {
73
+ const exportDims = options?.exportDims;
74
+ const onNavigateToLine = options?.onNavigateToLine;
75
+ const activeTagGroup = options?.activeTagGroup ?? null;
76
+ const onActiveTagGroupChange = options?.onActiveTagGroupChange;
77
+ const collapsedPhases = options?.collapsedPhases ?? /* @__PURE__ */ new Set();
78
+ const onPhaseToggle = options?.onPhaseToggle;
79
+ const solid = parsed.options["solid-fill"] === "on";
80
+ const layout = layoutJourneyMap(parsed, palette, {
81
+ collapsedPhases,
82
+ ...exportDims !== void 0 && { exportDims },
83
+ isDark
84
+ });
85
+ container.innerHTML = "";
86
+ const containerW = exportDims?.width ?? container.clientWidth;
87
+ const containerH = exportDims?.height ?? container.clientHeight;
88
+ const useContainerFit = !exportDims && containerW > 0 && containerH > 0;
89
+ const sctx = exportDims ? ScaleContext.identity() : ScaleContext.from(containerW, layout.totalWidth);
90
+ const svg = d3.select(container).append("svg").attr("xmlns", "http://www.w3.org/2000/svg").attr(
91
+ "width",
92
+ useContainerFit ? containerW : exportDims?.width ?? layout.totalWidth
93
+ ).attr(
94
+ "height",
95
+ useContainerFit ? containerH : exportDims?.height ?? layout.totalHeight
96
+ ).attr("viewBox", `0 0 ${layout.totalWidth} ${layout.totalHeight}`).attr("preserveAspectRatio", "xMidYMin meet").attr("font-family", FONT_FAMILY);
97
+ if (sctx.isBelowFloor) {
98
+ svg.attr("width", "100%");
99
+ }
100
+ svg.append("rect").attr("width", layout.totalWidth).attr("height", layout.totalHeight).attr("fill", palette.bg);
101
+ const defs = svg.append("defs");
102
+ const curveGradient = defs.append("linearGradient").attr("id", "journey-curve-gradient").attr("x1", "0").attr("y1", "0").attr("x2", "0").attr("y2", "1");
103
+ curveGradient.append("stop").attr("offset", "0%").attr("stop-color", palette.colors.green).attr("stop-opacity", 0.3);
104
+ curveGradient.append("stop").attr("offset", "100%").attr("stop-color", palette.colors.red).attr("stop-opacity", 0.3);
105
+ const showTitle = !!parsed.title && parsed.options["no-title"] !== "on";
106
+ if (showTitle) {
107
+ const titleG = svg.append("g").attr("class", "chart-title");
108
+ if (parsed.titleLineNumber) {
109
+ titleG.attr("data-line-number", parsed.titleLineNumber);
110
+ }
111
+ titleG.append("text").attr("x", PADDING).attr("y", PADDING + FONT_SIZE_TITLE).attr("font-size", FONT_SIZE_TITLE).attr("font-weight", "bold").attr("fill", palette.text).text(parsed.title);
112
+ if (onNavigateToLine && parsed.titleLineNumber) {
113
+ titleG.style("cursor", "pointer").on("click", () => {
114
+ onNavigateToLine(parsed.titleLineNumber);
115
+ });
116
+ }
117
+ }
118
+ if (parsed.persona) {
119
+ const personaColor = parsed.persona.color ?? palette.textMuted;
120
+ const personaG = svg.append("g").attr("class", "journey-persona").attr("data-line-number", parsed.persona.lineNumber);
121
+ const titleRowH = CARD_HEADER_HEIGHT;
122
+ const silhouetteZone = 60;
123
+ const panelWidth = 280;
124
+ const textAreaWidth = panelWidth - silhouetteZone - CARD_PADDING_X;
125
+ const descLineH = 14;
126
+ const descLines = parsed.persona.description ? wrapNoteBody(parsed.persona.description, textAreaWidth, FONT_SIZE_META) : [];
127
+ const descRowH = descLines.length > 0 ? descLines.length * descLineH + 8 : 0;
128
+ const panelHeight = titleRowH + descRowH;
129
+ const panelX = layout.totalWidth - PADDING - panelWidth;
130
+ const panelY = PADDING;
131
+ const textX = panelX + CARD_PADDING_X;
132
+ const clipId = "persona-clip";
133
+ defs.append("clipPath").attr("id", clipId).append("rect").attr("x", panelX).attr("y", panelY).attr("width", panelWidth).attr("height", panelHeight).attr("rx", CARD_RADIUS);
134
+ const personaFill = shapeFill(palette, personaColor, isDark, { solid });
135
+ const onPersonaText = contrastText(
136
+ personaFill,
137
+ palette.textOnFillLight,
138
+ palette.textOnFillDark
139
+ );
140
+ personaG.append("rect").attr("x", panelX).attr("y", panelY).attr("width", panelWidth).attr("height", panelHeight).attr("rx", CARD_RADIUS).attr("fill", personaFill);
141
+ if (descLines.length > 0) {
142
+ personaG.append("line").attr("x1", panelX + 1).attr("x2", panelX + panelWidth - silhouetteZone).attr("y1", panelY + titleRowH).attr("y2", panelY + titleRowH).attr("stroke", solid ? onPersonaText : personaColor).attr("stroke-opacity", 0.3).attr("stroke-width", 1);
143
+ }
144
+ const silX = panelX + panelWidth - 32;
145
+ const silY = panelY + panelHeight / 2 - 6;
146
+ const silClip = personaG.append("g").attr("clip-path", `url(#${clipId})`);
147
+ renderPersonaSilhouette(silClip, silX, silY, personaColor, palette, 1.2);
148
+ personaG.append("rect").attr("x", panelX).attr("y", panelY).attr("width", panelWidth).attr("height", panelHeight).attr("rx", CARD_RADIUS).attr("fill", "none").attr("stroke", personaColor).attr("stroke-width", CARD_STROKE_WIDTH);
149
+ personaG.append("text").attr("x", textX).attr("y", panelY + CARD_PADDING_Y + FONT_SIZE_STEP).attr("font-size", FONT_SIZE_STEP).attr("font-weight", "500").attr("fill", onPersonaText).text(parsed.persona.name);
150
+ for (let li = 0; li < descLines.length; li++) {
151
+ const line2 = descLines[li];
152
+ const indent = line2.kind === "plain" ? 0 : NOTE_BULLET_INDENT;
153
+ const lineY = panelY + titleRowH + descLineH * (li + 1);
154
+ if (line2.kind === "bullet-first") {
155
+ personaG.append("text").attr("x", textX).attr("y", lineY).attr("font-size", FONT_SIZE_META).attr("fill", onPersonaText).text("\u2022");
156
+ }
157
+ const lineEl = personaG.append("text").attr("x", textX + indent).attr("y", lineY).attr("font-size", FONT_SIZE_META).attr("fill", onPersonaText);
158
+ renderInlineText(lineEl, line2.text, palette, FONT_SIZE_META);
159
+ }
160
+ if (onNavigateToLine) {
161
+ personaG.style("cursor", "pointer").on("click", () => {
162
+ onNavigateToLine(parsed.persona.lineNumber);
163
+ });
164
+ }
165
+ }
166
+ const scoreGroup = {
167
+ name: "Score",
168
+ entries: [1, 2, 3, 4, 5].map((s) => ({
169
+ value: String(s),
170
+ color: scoreToColor(s, palette),
171
+ lineNumber: 0
172
+ })),
173
+ lineNumber: 0
174
+ };
175
+ const allLegendGroups = [...parsed.tagGroups, scoreGroup];
176
+ const effectiveActiveGroup = activeTagGroup ?? resolveActiveTagGroup(allLegendGroups, parsed.options["active-tag"]);
177
+ if (parsed.options["no-legend"] !== "on") {
178
+ const legendX = PADDING;
179
+ const legendY = PADDING + (showTitle ? FONT_SIZE_TITLE + 8 : 0);
180
+ const legendG = svg.append("g").attr("class", "journey-legend").attr("transform", `translate(${legendX},${legendY})`);
181
+ const legendCallbacks = {
182
+ ...onActiveTagGroupChange ? {
183
+ onGroupToggle: (groupName) => {
184
+ const isDeactivating = effectiveActiveGroup?.toLowerCase() === groupName.toLowerCase();
185
+ onActiveTagGroupChange(isDeactivating ? null : groupName);
186
+ }
187
+ } : {},
188
+ ...!exportDims ? {
189
+ onEntryHover: (groupName, entryValue) => {
190
+ if (!entryValue) {
191
+ svg.selectAll(".journey-step").style("opacity", null);
192
+ svg.selectAll(".journey-face").style("opacity", null);
193
+ svg.selectAll(".journey-face-icon").attr("transform", null);
194
+ svg.selectAll(".journey-thought").style("opacity", null);
195
+ return;
196
+ }
197
+ const isScore = groupName === "Score";
198
+ const attrName = isScore ? "data-score" : `data-tag-${tagAttrKey(groupName)}`;
199
+ const matches = (el) => el.getAttribute(attrName) === entryValue;
200
+ svg.selectAll(".journey-step").each(function() {
201
+ const hit = matches(this);
202
+ d3.select(this).style(
203
+ "opacity",
204
+ hit ? "1" : String(DIM_HOVER)
205
+ );
206
+ });
207
+ svg.selectAll(".journey-face").each(function() {
208
+ const hit = matches(this);
209
+ const sel = d3.select(this);
210
+ sel.style("opacity", hit ? "1" : String(DIM_HOVER));
211
+ const icon = sel.select(".journey-face-icon");
212
+ if (hit) {
213
+ const fcx = parseFloat(sel.attr("data-cx") ?? "0");
214
+ const fcy = parseFloat(sel.attr("data-cy") ?? "0");
215
+ icon.attr(
216
+ "transform",
217
+ `translate(${fcx},${fcy}) scale(1.3) translate(${-fcx},${-fcy})`
218
+ );
219
+ } else {
220
+ icon.attr("transform", null);
221
+ }
222
+ });
223
+ svg.selectAll(".journey-thought").style("opacity", String(DIM_HOVER));
224
+ }
225
+ } : {}
226
+ };
227
+ renderIntegratedLegend(legendG, {
228
+ groups: parsed.tagGroups,
229
+ activeGroup: effectiveActiveGroup,
230
+ mode: options?.exportMode ? "export" : "preview",
231
+ position: { placement: "top-center", titleRelation: "inline-with-title" },
232
+ titleWidth: 0,
233
+ callbacks: legendCallbacks,
234
+ palette,
235
+ isDark,
236
+ // Reduce available width if persona card exists to avoid collision
237
+ width: parsed.persona ? layout.totalWidth - legendX - PADDING - 280 - PADDING : layout.totalWidth - legendX - PADDING
238
+ });
239
+ }
240
+ const curveG = svg.append("g").attr("class", "journey-curve-area");
241
+ for (let score = 1; score <= 5; score++) {
242
+ const y = scoreToCurveY(score, layout.curveAreaBottom);
243
+ curveG.append("line").attr("x1", PADDING).attr("x2", layout.totalWidth - PADDING).attr("y1", y).attr("y2", y).attr("stroke", palette.textMuted).attr("stroke-opacity", GRID_LINE_OPACITY).attr("stroke-dasharray", "4,4");
244
+ }
245
+ if (layout.curvePoints.length >= 2) {
246
+ const first = layout.curvePoints[0];
247
+ const last = layout.curvePoints[layout.curvePoints.length - 1];
248
+ const extendedPoints = [
249
+ {
250
+ x: PADDING,
251
+ y: first.y,
252
+ score: first.score,
253
+ stepIndex: first.stepIndex
254
+ },
255
+ ...layout.curvePoints,
256
+ {
257
+ x: layout.totalWidth - PADDING,
258
+ y: last.y,
259
+ score: last.score,
260
+ stepIndex: last.stepIndex
261
+ }
262
+ ];
263
+ const areaBottomY = layout.curveAreaBottom + CURVE_AREA_BOTTOM_GAP;
264
+ const areaGen = d3Shape.area().x((d) => d.x).y0(areaBottomY).y1((d) => d.y).curve(d3Shape.curveMonotoneX);
265
+ curveG.append("path").attr("d", areaGen(extendedPoints) ?? "").attr("fill", "url(#journey-curve-gradient)").attr("stroke", "none");
266
+ const leftX = PADDING;
267
+ const rightX = layout.totalWidth - PADDING;
268
+ curveG.append("path").attr(
269
+ "d",
270
+ `M${leftX},${first.y} L${leftX},${areaBottomY} L${rightX},${areaBottomY} L${rightX},${last.y}`
271
+ ).attr("fill", "none").attr("stroke", palette.primary).attr("stroke-width", CURVE_STROKE_WIDTH).attr("stroke-linecap", "round").attr("stroke-linejoin", "round");
272
+ const lineGen = d3Shape.line().x((d) => d.x).y((d) => d.y).curve(d3Shape.curveMonotoneX);
273
+ curveG.append("path").attr("d", lineGen(extendedPoints) ?? "").attr("fill", "none").attr("stroke", palette.primary).attr("stroke-width", CURVE_STROKE_WIDTH).attr("stroke-linecap", "round");
274
+ const allSteps = parsed.phases.length > 0 ? parsed.phases.flatMap((p) => p.steps) : parsed.steps;
275
+ for (const pt of layout.curvePoints) {
276
+ const step = allSteps[pt.stepIndex];
277
+ const faceG = renderScoreFace(
278
+ curveG,
279
+ pt.x,
280
+ pt.y,
281
+ pt.score,
282
+ palette,
283
+ isDark
284
+ );
285
+ addEmotionLabel(faceG, pt, palette);
286
+ if (step) {
287
+ faceG.attr("data-line-number", step.lineNumber);
288
+ faceG.attr("data-score", pt.score);
289
+ for (const [key, value] of Object.entries(step.tags)) {
290
+ faceG.attr(`data-tag-${key.toLowerCase()}`, value);
291
+ }
292
+ const thoughts = step.annotations.filter((a) => a.type === "thought");
293
+ if (thoughts.length > 0) {
294
+ faceG.attr(
295
+ "data-thought",
296
+ thoughts.map((t) => t.text).join(" \u2022 ")
297
+ );
298
+ }
299
+ if (onNavigateToLine) {
300
+ faceG.style("cursor", "pointer").on("click", () => {
301
+ onNavigateToLine(step.lineNumber);
302
+ });
303
+ }
304
+ }
305
+ }
306
+ } else if (layout.curvePoints.length === 1) {
307
+ const pt = layout.curvePoints[0];
308
+ const allSteps = parsed.phases.length > 0 ? parsed.phases.flatMap((p) => p.steps) : parsed.steps;
309
+ const step = allSteps[pt.stepIndex];
310
+ const faceG = renderScoreFace(
311
+ curveG,
312
+ pt.x,
313
+ pt.y,
314
+ pt.score,
315
+ palette,
316
+ isDark
317
+ );
318
+ addEmotionLabel(faceG, pt, palette);
319
+ if (step) {
320
+ faceG.attr("data-line-number", step.lineNumber);
321
+ faceG.attr("data-score", pt.score);
322
+ for (const [key, value] of Object.entries(step.tags)) {
323
+ faceG.attr(`data-tag-${key.toLowerCase()}`, value);
324
+ }
325
+ const thoughts = step.annotations.filter((a) => a.type === "thought");
326
+ if (thoughts.length > 0) {
327
+ faceG.attr(
328
+ "data-thought",
329
+ thoughts.map((t) => t.text).join(" \u2022 ")
330
+ );
331
+ }
332
+ if (onNavigateToLine) {
333
+ faceG.style("cursor", "pointer").on("click", () => {
334
+ onNavigateToLine(step.lineNumber);
335
+ });
336
+ }
337
+ }
338
+ }
339
+ const phasesG = svg.append("g").attr("class", "journey-phases");
340
+ if (layout.phases.length > 0) {
341
+ for (const pl of layout.phases) {
342
+ const isCollapsed = collapsedPhases.has(pl.phase.name);
343
+ const phaseG = phasesG.append("g").attr("class", "journey-phase").attr("data-line-number", pl.phase.lineNumber);
344
+ const colBg = isDark ? mix(palette.surface, palette.bg, 50) : mix(palette.surface, palette.bg, 30);
345
+ phaseG.append("rect").attr("x", pl.x).attr("y", pl.y).attr("width", pl.width).attr("height", pl.height).attr("rx", COLUMN_RADIUS).attr("fill", colBg);
346
+ phaseG.append("rect").attr("x", pl.x).attr("y", pl.y).attr("width", pl.width).attr("height", COLUMN_HEADER_HEIGHT).attr("rx", COLUMN_RADIUS).attr("fill", pl.headerColor);
347
+ phaseG.append("rect").attr("x", pl.x).attr("y", pl.y + COLUMN_HEADER_HEIGHT - COLUMN_RADIUS).attr("width", pl.width).attr("height", COLUMN_RADIUS).attr("fill", pl.headerColor);
348
+ const onHeaderText = contrastText(
349
+ pl.headerColor,
350
+ palette.textOnFillLight,
351
+ palette.textOnFillDark
352
+ );
353
+ phaseG.append("text").attr("x", pl.x + COLUMN_PADDING).attr("y", pl.y + COLUMN_HEADER_HEIGHT / 2 + FONT_SIZE_PHASE / 2 - 2).attr("font-size", FONT_SIZE_PHASE).attr("font-weight", "bold").attr("fill", onHeaderText).text(
354
+ isCollapsed ? truncateText2(
355
+ pl.phase.name,
356
+ pl.width - COLUMN_PADDING * 2,
357
+ FONT_SIZE_PHASE
358
+ ) : pl.phase.name
359
+ );
360
+ if (onPhaseToggle) {
361
+ phaseG.style("cursor", "pointer").on("click", (event) => {
362
+ const target = event.target;
363
+ if (!target.closest(".journey-step") && !target.closest(".journey-face")) {
364
+ onPhaseToggle(pl.phase.name);
365
+ }
366
+ });
367
+ } else if (onNavigateToLine) {
368
+ phaseG.style("cursor", "pointer").on("click", (event) => {
369
+ const target = event.target;
370
+ if (!target.closest(".journey-step")) {
371
+ onNavigateToLine(pl.phase.lineNumber);
372
+ }
373
+ });
374
+ }
375
+ if (isCollapsed) {
376
+ const COLLAPSED_CARD_H = 26;
377
+ const COLLAPSED_GAP = 6;
378
+ const COLLAPSED_FACE_R = 7;
379
+ const listX = pl.x + COLUMN_PADDING;
380
+ const cardW = pl.width - COLUMN_PADDING * 2;
381
+ let itemY = pl.y + COLUMN_HEADER_HEIGHT + CARD_GAP_INTERNAL + 4;
382
+ for (const step of pl.phase.steps) {
383
+ const itemG = phaseG.append("g").attr("class", "journey-step").attr("data-line-number", step.lineNumber);
384
+ if (step.score !== void 0) {
385
+ itemG.attr("data-score", step.score);
386
+ }
387
+ for (const [key, value] of Object.entries(step.tags)) {
388
+ itemG.attr(`data-tag-${key.toLowerCase()}`, value);
389
+ }
390
+ const stepColor = resolveStepColor(
391
+ step,
392
+ step.score !== void 0 ? scoreToColor(step.score, palette) : palette.surface,
393
+ effectiveActiveGroup,
394
+ parsed.tagGroups,
395
+ palette
396
+ );
397
+ const rowFill = shapeFill(
398
+ palette,
399
+ stepColor ?? palette.primary,
400
+ isDark,
401
+ { solid }
402
+ );
403
+ const rowStroke = stepColor ?? palette.textMuted;
404
+ itemG.append("rect").attr("x", listX).attr("y", itemY).attr("width", cardW).attr("height", COLLAPSED_CARD_H).attr("rx", CARD_RADIUS).attr("fill", rowFill).attr("stroke", rowStroke).attr("stroke-width", CARD_STROKE_WIDTH);
405
+ const faceCx = listX + CARD_PADDING_X + COLLAPSED_FACE_R;
406
+ const faceCy = itemY + COLLAPSED_CARD_H / 2;
407
+ if (step.score !== void 0) {
408
+ const faceG = renderScoreFace(
409
+ itemG,
410
+ faceCx,
411
+ faceCy,
412
+ step.score,
413
+ palette,
414
+ isDark,
415
+ COLLAPSED_FACE_R
416
+ );
417
+ faceG.attr("data-line-number", step.lineNumber);
418
+ faceG.attr("data-score", step.score);
419
+ for (const [key, value] of Object.entries(step.tags)) {
420
+ faceG.attr(`data-tag-${key.toLowerCase()}`, value);
421
+ }
422
+ }
423
+ const textX = listX + CARD_PADDING_X + COLLAPSED_FACE_R * 2 + 6;
424
+ const maxTextW = cardW - CARD_PADDING_X * 2 - COLLAPSED_FACE_R * 2 - 6;
425
+ itemG.append("text").attr("x", textX).attr("y", itemY + COLLAPSED_CARD_H / 2 + FONT_SIZE_META / 2 - 1).attr("font-size", FONT_SIZE_META).attr("fill", palette.text).text(truncateText2(step.title, maxTextW, FONT_SIZE_META));
426
+ if (onNavigateToLine) {
427
+ itemG.style("cursor", "pointer").on("click", (event) => {
428
+ event.stopPropagation();
429
+ onNavigateToLine(step.lineNumber);
430
+ });
431
+ }
432
+ itemY += COLLAPSED_CARD_H + COLLAPSED_GAP;
433
+ }
434
+ } else {
435
+ for (const sl of pl.stepLayouts) {
436
+ renderStepCard(
437
+ phaseG,
438
+ sl,
439
+ palette,
440
+ isDark,
441
+ effectiveActiveGroup,
442
+ parsed.tagGroups,
443
+ onNavigateToLine,
444
+ solid
445
+ );
446
+ }
447
+ }
448
+ }
449
+ } else {
450
+ for (const sl of layout.flatStepLayouts) {
451
+ renderStepCard(
452
+ phasesG,
453
+ sl,
454
+ palette,
455
+ isDark,
456
+ effectiveActiveGroup,
457
+ parsed.tagGroups,
458
+ onNavigateToLine,
459
+ solid
460
+ );
461
+ }
462
+ }
463
+ const overlayG = svg.append("g").attr("class", "journey-overlay");
464
+ if (!exportDims) {
465
+ const DIM_OPACITY = 0.35;
466
+ let lockedLine = null;
467
+ const applyDimming = (activeLine) => {
468
+ svg.selectAll(".journey-step").each(function() {
469
+ const el = d3.select(this);
470
+ const ln = parseInt(el.attr("data-line-number") ?? "0", 10);
471
+ el.style("opacity", ln === activeLine ? "1" : String(DIM_OPACITY));
472
+ });
473
+ svg.selectAll(".journey-face").each(function() {
474
+ const el = d3.select(this);
475
+ const ln = parseInt(el.attr("data-line-number") ?? "0", 10);
476
+ const isActive = ln === activeLine;
477
+ el.style("opacity", isActive ? "1" : String(DIM_OPACITY));
478
+ const icon = el.select(".journey-face-icon");
479
+ if (isActive) {
480
+ const fcx = parseFloat(el.attr("data-cx") ?? "0");
481
+ const fcy = parseFloat(el.attr("data-cy") ?? "0");
482
+ icon.attr(
483
+ "transform",
484
+ `translate(${fcx},${fcy}) scale(${FACE_HOVER_SCALE}) translate(${-fcx},${-fcy})`
485
+ );
486
+ } else {
487
+ icon.attr("transform", null);
488
+ }
489
+ });
490
+ svg.selectAll(".journey-phase").each(function() {
491
+ const el = d3.select(this);
492
+ const hasActive = el.select(
493
+ `.journey-step[data-line-number="${activeLine}"]`
494
+ );
495
+ if (!hasActive.node()) {
496
+ el.style("opacity", String(DIM_OPACITY + 0.3));
497
+ }
498
+ });
499
+ };
500
+ const clearDimming = () => {
501
+ svg.selectAll(".journey-step").style("opacity", null);
502
+ svg.selectAll(".journey-face").style("opacity", null);
503
+ svg.selectAll(".journey-face-icon").attr("transform", null);
504
+ svg.selectAll(".journey-phase").style("opacity", null);
505
+ overlayG.selectAll(".journey-thought-hover").remove();
506
+ };
507
+ const THOUGHT_FONT = 11;
508
+ const THOUGHT_PAD_X = 10;
509
+ const THOUGHT_PAD_Y = 6;
510
+ const THOUGHT_MAX_W = 200;
511
+ const THOUGHT_LINE_H = 14;
512
+ const THOUGHT_GAP = 10;
513
+ const showThoughtBubble = (faceEl) => {
514
+ overlayG.selectAll(".journey-thought-hover").remove();
515
+ const thoughtText = faceEl.attr("data-thought");
516
+ if (!thoughtText || !layout.hasThoughts) return;
517
+ const fcx = parseFloat(faceEl.attr("data-cx") ?? "0");
518
+ const fcy = parseFloat(faceEl.attr("data-cy") ?? "0");
519
+ const score = parseInt(faceEl.attr("data-score") ?? "3", 10);
520
+ const lines = wrapText(thoughtText, THOUGHT_MAX_W, THOUGHT_FONT);
521
+ const textW = Math.min(
522
+ THOUGHT_MAX_W,
523
+ Math.max(...lines.map((l) => measureText(l, THOUGHT_FONT)))
524
+ );
525
+ const bw = textW + THOUGHT_PAD_X * 2;
526
+ const bh = lines.length * THOUGHT_LINE_H + THOUGHT_PAD_Y * 2;
527
+ const bx = Math.max(
528
+ PADDING,
529
+ Math.min(fcx - bw / 2, layout.totalWidth - PADDING - bw)
530
+ );
531
+ const by = Math.max(PADDING, fcy - FACE_HOVER_R - THOUGHT_GAP - bh);
532
+ const scoreColor = scoreToColor(score, palette);
533
+ const tintedBg = mix(scoreColor, palette.surface, 20);
534
+ const g = overlayG.append("g").attr("class", "journey-thought-hover");
535
+ g.append("rect").attr("x", bx).attr("y", by).attr("width", bw).attr("height", bh).attr("rx", CARD_RADIUS).attr("fill", tintedBg).attr("stroke", scoreColor).attr("stroke-width", CARD_STROKE_WIDTH);
536
+ g.append("line").attr("x1", fcx).attr("y1", by + bh).attr("x2", fcx).attr("y2", fcy - FACE_HOVER_R - 1).attr("stroke", scoreColor).attr("stroke-width", CARD_STROKE_WIDTH);
537
+ const centerX = bx + bw / 2;
538
+ for (let i = 0; i < lines.length; i++) {
539
+ g.append("text").attr("x", centerX).attr("y", by + THOUGHT_PAD_Y + (i + 1) * THOUGHT_LINE_H - 2).attr("text-anchor", "middle").attr("font-size", THOUGHT_FONT).attr("font-style", "italic").attr("fill", palette.textMuted).text(lines[i]);
540
+ }
541
+ };
542
+ svg.on("click", (event) => {
543
+ const target = event.target;
544
+ if (!target.closest(".journey-face") && !target.closest(".journey-step") && !target.closest(".journey-phase")) {
545
+ lockedLine = null;
546
+ clearDimming();
547
+ }
548
+ });
549
+ const showThoughtForLine = (ln) => {
550
+ svg.selectAll(".journey-curve-area .journey-face").each(function() {
551
+ const face = d3.select(this);
552
+ if (parseInt(face.attr("data-line-number") ?? "0", 10) === ln) {
553
+ showThoughtBubble(face);
554
+ }
555
+ });
556
+ };
557
+ svg.selectAll(".journey-face").each(function() {
558
+ const el = d3.select(this);
559
+ el.on("mouseenter", () => {
560
+ if (lockedLine !== null) return;
561
+ const ln = parseInt(el.attr("data-line-number") ?? "0", 10);
562
+ if (ln) {
563
+ applyDimming(ln);
564
+ showThoughtForLine(ln);
565
+ }
566
+ }).on("mouseleave", () => {
567
+ if (lockedLine !== null) return;
568
+ clearDimming();
569
+ }).on("click", (event) => {
570
+ event.stopPropagation();
571
+ const ln = parseInt(el.attr("data-line-number") ?? "0", 10);
572
+ if (lockedLine === ln) {
573
+ lockedLine = null;
574
+ clearDimming();
575
+ } else {
576
+ lockedLine = ln;
577
+ applyDimming(ln);
578
+ showThoughtForLine(ln);
579
+ if (onNavigateToLine && ln) onNavigateToLine(ln);
580
+ }
581
+ });
582
+ });
583
+ svg.selectAll(".journey-step").each(function() {
584
+ const el = d3.select(this);
585
+ el.on("mouseenter", () => {
586
+ if (lockedLine !== null) return;
587
+ const ln = parseInt(el.attr("data-line-number") ?? "0", 10);
588
+ if (ln) {
589
+ applyDimming(ln);
590
+ showThoughtForLine(ln);
591
+ }
592
+ }).on("mouseleave", () => {
593
+ if (lockedLine !== null) return;
594
+ clearDimming();
595
+ }).on("click", (event) => {
596
+ event.stopPropagation();
597
+ const ln = parseInt(el.attr("data-line-number") ?? "0", 10);
598
+ if (lockedLine === ln) {
599
+ lockedLine = null;
600
+ clearDimming();
601
+ } else {
602
+ lockedLine = ln;
603
+ applyDimming(ln);
604
+ showThoughtForLine(ln);
605
+ if (onNavigateToLine && ln) onNavigateToLine(ln);
606
+ }
607
+ });
608
+ });
609
+ }
610
+ }
611
+ function resolveStepColor(step, scoreColor, activeGroup, tagGroups, _palette) {
612
+ if (!activeGroup) return void 0;
613
+ if (activeGroup.toLowerCase() === "score") {
614
+ return step.score !== void 0 ? scoreColor : void 0;
615
+ }
616
+ const group = tagGroups.find(
617
+ (g) => g.name.toLowerCase() === activeGroup.toLowerCase()
618
+ );
619
+ if (!group) return void 0;
620
+ const tagValue = step.tags[tagAttrKey(group.name)];
621
+ if (!tagValue) return void 0;
622
+ const entry = group.entries.find(
623
+ (e) => e.value.toLowerCase() === tagValue.toLowerCase()
624
+ );
625
+ return entry?.color;
626
+ }
627
+ function renderStepCard(parent, sl, palette, isDark, activeGroup, tagGroups, onNavigateToLine, solid) {
628
+ const stepG = parent.append("g").attr("class", "journey-step").attr("data-line-number", sl.step.lineNumber);
629
+ if (sl.step.score !== void 0) {
630
+ stepG.attr("data-score", sl.step.score);
631
+ }
632
+ for (const [key, value] of Object.entries(sl.step.tags)) {
633
+ stepG.attr(`data-tag-${key.toLowerCase()}`, value);
634
+ }
635
+ const cx = sl.x;
636
+ const cy = sl.y;
637
+ const resolvedColor = resolveStepColor(
638
+ sl.step,
639
+ sl.color,
640
+ activeGroup,
641
+ tagGroups,
642
+ palette
643
+ );
644
+ const cardFill = shapeFill(
645
+ palette,
646
+ resolvedColor ?? palette.primary,
647
+ isDark,
648
+ { ...solid !== void 0 && { solid } }
649
+ );
650
+ const cardStroke = resolvedColor ?? palette.textMuted;
651
+ const onCardText = contrastText(
652
+ cardFill,
653
+ palette.textOnFillLight,
654
+ palette.textOnFillDark
655
+ );
656
+ stepG.append("rect").attr("x", cx).attr("y", cy).attr("width", sl.width).attr("height", sl.height).attr("rx", CARD_RADIUS).attr("fill", cardFill).attr("stroke", cardStroke).attr("stroke-width", CARD_STROKE_WIDTH);
657
+ const titleMaxW = sl.width - CARD_PADDING_X * 2;
658
+ const titleLines = wrapTextToWidth(sl.step.title, FONT_SIZE_STEP, titleMaxW);
659
+ for (let i = 0; i < titleLines.length; i++) {
660
+ stepG.append("text").attr("x", cx + CARD_PADDING_X).attr("y", cy + CARD_PADDING_Y + FONT_SIZE_STEP + i * TITLE_LINE_HEIGHT).attr("font-size", FONT_SIZE_STEP).attr("font-weight", "500").attr("fill", onCardText).text(titleLines[i]);
661
+ }
662
+ const titleBlockH = CARD_PADDING_Y + titleLines.length * TITLE_LINE_HEIGHT + CARD_PADDING_Y;
663
+ const cardAnnotations = sl.step.annotations;
664
+ const hasContent = sl.step.description || cardAnnotations.length > 0;
665
+ if (hasContent) {
666
+ stepG.append("line").attr("x1", cx).attr("y1", cy + titleBlockH).attr("x2", cx + sl.width).attr("y2", cy + titleBlockH).attr("stroke", solid ? onCardText : cardStroke).attr("stroke-opacity", 0.3).attr("stroke-width", 1);
667
+ }
668
+ let metaY = cy + titleBlockH + CARD_META_LINE_HEIGHT;
669
+ if (sl.step.description) {
670
+ const descLines = wrapText(
671
+ sl.step.description,
672
+ sl.width - CARD_PADDING_X * 2,
673
+ FONT_SIZE_META
674
+ );
675
+ for (const line2 of descLines) {
676
+ stepG.append("text").attr("x", cx + CARD_PADDING_X).attr("y", metaY).attr("font-size", FONT_SIZE_META).attr("fill", onCardText).text(line2);
677
+ metaY += CARD_META_LINE_HEIGHT;
678
+ }
679
+ }
680
+ const ANNO_ICON_SIZE = 10;
681
+ const ANNO_ICON_GAP = 4;
682
+ const annoIconIndent = ANNO_ICON_SIZE + ANNO_ICON_GAP;
683
+ const annoTextW = sl.width - CARD_PADDING_X * 2 - annoIconIndent;
684
+ for (const anno of cardAnnotations) {
685
+ const annoColor = annotationColor(anno.type, palette);
686
+ const iconPaths = annotationIconPaths(anno.type);
687
+ const annoLines = wrapText(anno.text, annoTextW, FONT_SIZE_META);
688
+ const iconColor = solid ? onCardText : annoColor;
689
+ renderAnnotationIcon(
690
+ stepG,
691
+ cx + CARD_PADDING_X,
692
+ metaY - ANNO_ICON_SIZE + 1,
693
+ ANNO_ICON_SIZE,
694
+ iconPaths,
695
+ iconColor
696
+ );
697
+ for (let li = 0; li < annoLines.length; li++) {
698
+ stepG.append("text").attr("x", cx + CARD_PADDING_X + annoIconIndent).attr("y", metaY).attr("font-size", FONT_SIZE_META).attr("fill", onCardText).text(annoLines[li]);
699
+ metaY += CARD_META_LINE_HEIGHT;
700
+ }
701
+ }
702
+ for (const [key, value] of Object.entries(sl.step.tags)) {
703
+ const group = tagGroups.find(
704
+ (g) => g.name.toLowerCase() === key.toLowerCase()
705
+ );
706
+ const entry = group?.entries.find(
707
+ (e) => e.value.toLowerCase() === value.toLowerCase()
708
+ );
709
+ const stripColor = entry?.color ?? palette.textMuted;
710
+ const TAG_GAP = 6;
711
+ const stripY = cy - TAG_STRIP_HEIGHT - TAG_GAP;
712
+ const stripFill = shapeFill(palette, stripColor, isDark, {
713
+ ...solid !== void 0 && { solid }
714
+ });
715
+ stepG.append("rect").attr("x", cx).attr("y", stripY).attr("width", sl.width).attr("height", TAG_STRIP_HEIGHT).attr("rx", CARD_RADIUS).attr("fill", stripFill).attr("stroke", stripColor).attr("stroke-width", CARD_STROKE_WIDTH);
716
+ const stripTextColor = contrastText(
717
+ stripFill,
718
+ palette.textOnFillLight,
719
+ palette.textOnFillDark
720
+ );
721
+ stepG.append("text").attr("x", cx + sl.width / 2).attr("y", stripY + TAG_STRIP_HEIGHT / 2 + FONT_SIZE_META / 2 - 1).attr("text-anchor", "middle").attr("font-size", FONT_SIZE_META).attr("fill", stripTextColor).text(value);
722
+ }
723
+ if (onNavigateToLine) {
724
+ stepG.style("cursor", "pointer").on("click", (event) => {
725
+ event.stopPropagation();
726
+ onNavigateToLine(sl.step.lineNumber);
727
+ });
728
+ }
729
+ }
730
+ function renderPersonaSilhouette(parent, cx, cy, color, palette, scale = 1) {
731
+ const fill = mix(color, palette.bg, 70);
732
+ const stroke = color;
733
+ const s = scale;
734
+ parent.append("path").attr(
735
+ "d",
736
+ `M ${cx - 5 * s} ${cy + 6 * s} L ${cx - 5 * s} ${cy + 11 * s} L ${cx - 8 * s} ${cy + 11 * s} Q ${cx - 20 * s} ${cy + 12 * s}, ${cx - 20 * s} ${cy + 22 * s} L ${cx - 20 * s} ${cy + 36 * s} L ${cx + 20 * s} ${cy + 36 * s} L ${cx + 20 * s} ${cy + 22 * s} Q ${cx + 20 * s} ${cy + 12 * s}, ${cx + 8 * s} ${cy + 11 * s} L ${cx + 5 * s} ${cy + 11 * s} L ${cx + 5 * s} ${cy + 6 * s} Z`
737
+ ).attr("fill", fill).attr("stroke", stroke).attr("stroke-width", 1.2);
738
+ parent.append("path").attr(
739
+ "d",
740
+ `M ${cx} ${cy - 12 * s} C ${cx + 10 * s} ${cy - 12 * s}, ${cx + 9 * s} ${cy + 2 * s}, ${cx + 6 * s} ${cy + 6 * s} Q ${cx} ${cy + 9 * s}, ${cx - 6 * s} ${cy + 6 * s} C ${cx - 9 * s} ${cy + 2 * s}, ${cx - 10 * s} ${cy - 12 * s}, ${cx} ${cy - 12 * s} Z`
741
+ ).attr("fill", fill).attr("stroke", stroke).attr("stroke-width", 1.2);
742
+ }
743
+ function renderScoreFace(parent, cx, cy, score, palette, isDark, radius) {
744
+ const r = radius ?? FACE_RADIUS;
745
+ const color = scoreToColor(score, palette);
746
+ const g = parent.append("g").attr("class", "journey-face").attr("data-cx", cx).attr("data-cy", cy);
747
+ const iconG = g.append("g").attr("class", "journey-face-icon");
748
+ const faceFill = shapeFill(palette, color, isDark);
749
+ iconG.append("circle").attr("cx", cx).attr("cy", cy).attr("r", r + 1).attr("fill", palette.bg);
750
+ iconG.append("circle").attr("cx", cx).attr("cy", cy).attr("r", r).attr("fill", faceFill).attr("stroke", color).attr("stroke-width", 2);
751
+ const eyeColor = color;
752
+ const eyeY = cy - r * 0.15;
753
+ const eyeSpacing = r * 0.32;
754
+ const eyeR = r * 0.12;
755
+ iconG.append("circle").attr("cx", cx - eyeSpacing).attr("cy", eyeY).attr("r", eyeR).attr("fill", eyeColor);
756
+ iconG.append("circle").attr("cx", cx + eyeSpacing).attr("cy", eyeY).attr("r", eyeR).attr("fill", eyeColor);
757
+ const mouthW = r * 0.46;
758
+ const curve = (score - 3) / 2 * r * 0.42;
759
+ const apexY = cy + r * 0.32;
760
+ const mouthY = apexY - curve / 2;
761
+ iconG.append("path").attr(
762
+ "d",
763
+ `M ${cx - mouthW} ${mouthY} Q ${cx} ${mouthY + curve} ${cx + mouthW} ${mouthY}`
764
+ ).attr("fill", "none").attr("stroke", eyeColor).attr("stroke-width", 1.2).attr("stroke-linecap", "round");
765
+ return g;
766
+ }
767
+ var EMOTION_LABEL_MAX_WIDTH = 180;
768
+ var EMOTION_LABEL_FONT_SIZE = FONT_SIZE_META;
769
+ function addEmotionLabel(faceG, pt, palette) {
770
+ if (!pt.emotionLabel) return;
771
+ const text = truncateText2(
772
+ pt.emotionLabel,
773
+ EMOTION_LABEL_MAX_WIDTH,
774
+ EMOTION_LABEL_FONT_SIZE
775
+ );
776
+ const y = pt.y + FACE_RADIUS + 5 + EMOTION_LABEL_FONT_SIZE;
777
+ faceG.append("text").attr("class", "journey-emotion-label").attr("x", pt.x).attr("y", y).attr("text-anchor", "middle").attr("font-family", FONT_FAMILY).attr("font-size", EMOTION_LABEL_FONT_SIZE).attr("font-weight", 500).attr("fill", palette.text).attr("stroke", palette.bg).attr("stroke-width", 3).attr("stroke-linejoin", "round").attr("stroke-opacity", 0.8).attr("paint-order", "stroke").attr("pointer-events", "none").text(text);
778
+ }
779
+ function wrapText(text, maxWidth, fontSize) {
780
+ return wrapTextToWidth(text, fontSize, maxWidth);
781
+ }
782
+ function truncateText2(text, maxWidth, fontSize) {
783
+ return truncateText(text, fontSize, maxWidth);
784
+ }
785
+ function annotationColor(type, palette) {
786
+ switch (type) {
787
+ case "pain":
788
+ return palette.colors.red;
789
+ case "opportunity":
790
+ return palette.colors.green;
791
+ case "thought":
792
+ return palette.textMuted;
793
+ }
794
+ }
795
+ var ICON_THUMBS_DOWN = [
796
+ "M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22a3.13 3.13 0 0 1-3-3.88Z",
797
+ "M17 14V2"
798
+ ];
799
+ var ICON_THUMBS_UP = [
800
+ "M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z",
801
+ "M7 10v12"
802
+ ];
803
+ var ICON_THOUGHT = [
804
+ "M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5",
805
+ "M9 18h6",
806
+ "M10 22h4"
807
+ ];
808
+ function annotationIconPaths(type) {
809
+ switch (type) {
810
+ case "pain":
811
+ return ICON_THUMBS_DOWN;
812
+ case "opportunity":
813
+ return ICON_THUMBS_UP;
814
+ case "thought":
815
+ return ICON_THOUGHT;
816
+ }
817
+ }
818
+ function renderAnnotationIcon(parent, x, y, size, paths, color) {
819
+ const g = parent.append("g").attr("transform", `translate(${x}, ${y})`);
820
+ const scale = size / 24;
821
+ const inner = g.append("g").attr("transform", `scale(${scale})`);
822
+ for (const d of paths) {
823
+ inner.append("path").attr("d", d).attr("fill", "none").attr("stroke", color).attr("stroke-width", 2).attr("stroke-linecap", "round").attr("stroke-linejoin", "round");
824
+ }
825
+ }
826
+ function renderJourneyMapForExport(content, theme, palette) {
827
+ const parsed = parseJourneyMap(content, palette);
828
+ if (parsed.error || parsed.phases.length === 0 && parsed.steps.length === 0) {
829
+ return "";
830
+ }
831
+ const isDark = theme === "dark";
832
+ const layout = layoutJourneyMap(parsed, palette, { isDark });
833
+ const container = document.createElement("div");
834
+ renderJourneyMap(container, parsed, palette, isDark, {
835
+ exportDims: { width: layout.totalWidth, height: layout.totalHeight },
836
+ exportMode: true
837
+ });
838
+ const svgEl = container.querySelector("svg");
839
+ if (!svgEl) return "";
840
+ if (theme === "transparent") {
841
+ const bgRect = svgEl.querySelector("rect:first-child");
842
+ if (bgRect) bgRect.remove();
843
+ }
844
+ return svgEl.outerHTML;
845
+ }
846
+
847
+ export {
848
+ renderJourneyMap,
849
+ renderJourneyMapForExport
850
+ };
851
+ //# sourceMappingURL=chunk-ZJEL4H2R.js.map