mviz 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/README.md +174 -0
  2. package/dist/charts/area.d.ts +14 -0
  3. package/dist/charts/area.d.ts.map +1 -0
  4. package/dist/charts/area.js +137 -0
  5. package/dist/charts/area.js.map +1 -0
  6. package/dist/charts/bar.d.ts +14 -0
  7. package/dist/charts/bar.d.ts.map +1 -0
  8. package/dist/charts/bar.js +191 -0
  9. package/dist/charts/bar.js.map +1 -0
  10. package/dist/charts/boxplot.d.ts +14 -0
  11. package/dist/charts/boxplot.d.ts.map +1 -0
  12. package/dist/charts/boxplot.js +79 -0
  13. package/dist/charts/boxplot.js.map +1 -0
  14. package/dist/charts/bubble.d.ts +14 -0
  15. package/dist/charts/bubble.d.ts.map +1 -0
  16. package/dist/charts/bubble.js +127 -0
  17. package/dist/charts/bubble.js.map +1 -0
  18. package/dist/charts/calendar.d.ts +14 -0
  19. package/dist/charts/calendar.d.ts.map +1 -0
  20. package/dist/charts/calendar.js +94 -0
  21. package/dist/charts/calendar.js.map +1 -0
  22. package/dist/charts/combo.d.ts +14 -0
  23. package/dist/charts/combo.d.ts.map +1 -0
  24. package/dist/charts/combo.js +163 -0
  25. package/dist/charts/combo.js.map +1 -0
  26. package/dist/charts/dumbbell.d.ts +17 -0
  27. package/dist/charts/dumbbell.d.ts.map +1 -0
  28. package/dist/charts/dumbbell.js +368 -0
  29. package/dist/charts/dumbbell.js.map +1 -0
  30. package/dist/charts/funnel.d.ts +14 -0
  31. package/dist/charts/funnel.d.ts.map +1 -0
  32. package/dist/charts/funnel.js +145 -0
  33. package/dist/charts/funnel.js.map +1 -0
  34. package/dist/charts/heatmap.d.ts +14 -0
  35. package/dist/charts/heatmap.d.ts.map +1 -0
  36. package/dist/charts/heatmap.js +202 -0
  37. package/dist/charts/heatmap.js.map +1 -0
  38. package/dist/charts/histogram.d.ts +14 -0
  39. package/dist/charts/histogram.d.ts.map +1 -0
  40. package/dist/charts/histogram.js +103 -0
  41. package/dist/charts/histogram.js.map +1 -0
  42. package/dist/charts/index.d.ts +40 -0
  43. package/dist/charts/index.d.ts.map +1 -0
  44. package/dist/charts/index.js +42 -0
  45. package/dist/charts/index.js.map +1 -0
  46. package/dist/charts/line.d.ts +14 -0
  47. package/dist/charts/line.d.ts.map +1 -0
  48. package/dist/charts/line.js +134 -0
  49. package/dist/charts/line.js.map +1 -0
  50. package/dist/charts/pie.d.ts +14 -0
  51. package/dist/charts/pie.d.ts.map +1 -0
  52. package/dist/charts/pie.js +75 -0
  53. package/dist/charts/pie.js.map +1 -0
  54. package/dist/charts/registry.d.ts +36 -0
  55. package/dist/charts/registry.d.ts.map +1 -0
  56. package/dist/charts/registry.js +55 -0
  57. package/dist/charts/registry.js.map +1 -0
  58. package/dist/charts/sankey.d.ts +14 -0
  59. package/dist/charts/sankey.d.ts.map +1 -0
  60. package/dist/charts/sankey.js +74 -0
  61. package/dist/charts/sankey.js.map +1 -0
  62. package/dist/charts/scatter.d.ts +14 -0
  63. package/dist/charts/scatter.d.ts.map +1 -0
  64. package/dist/charts/scatter.js +130 -0
  65. package/dist/charts/scatter.js.map +1 -0
  66. package/dist/charts/sparkline.d.ts +19 -0
  67. package/dist/charts/sparkline.d.ts.map +1 -0
  68. package/dist/charts/sparkline.js +154 -0
  69. package/dist/charts/sparkline.js.map +1 -0
  70. package/dist/charts/waterfall.d.ts +14 -0
  71. package/dist/charts/waterfall.d.ts.map +1 -0
  72. package/dist/charts/waterfall.js +232 -0
  73. package/dist/charts/waterfall.js.map +1 -0
  74. package/dist/charts/xmr.d.ts +14 -0
  75. package/dist/charts/xmr.d.ts.map +1 -0
  76. package/dist/charts/xmr.js +456 -0
  77. package/dist/charts/xmr.js.map +1 -0
  78. package/dist/cli.d.ts +12 -0
  79. package/dist/cli.d.ts.map +1 -0
  80. package/dist/cli.js +120 -0
  81. package/dist/cli.js.map +1 -0
  82. package/dist/components/alert.d.ts +10 -0
  83. package/dist/components/alert.d.ts.map +1 -0
  84. package/dist/components/alert.js +65 -0
  85. package/dist/components/alert.js.map +1 -0
  86. package/dist/components/big_value.d.ts +10 -0
  87. package/dist/components/big_value.d.ts.map +1 -0
  88. package/dist/components/big_value.js +78 -0
  89. package/dist/components/big_value.js.map +1 -0
  90. package/dist/components/delta.d.ts +10 -0
  91. package/dist/components/delta.d.ts.map +1 -0
  92. package/dist/components/delta.js +83 -0
  93. package/dist/components/delta.js.map +1 -0
  94. package/dist/components/empty_space.d.ts +10 -0
  95. package/dist/components/empty_space.d.ts.map +1 -0
  96. package/dist/components/empty_space.js +29 -0
  97. package/dist/components/empty_space.js.map +1 -0
  98. package/dist/components/index.d.ts +21 -0
  99. package/dist/components/index.d.ts.map +1 -0
  100. package/dist/components/index.js +23 -0
  101. package/dist/components/index.js.map +1 -0
  102. package/dist/components/note.d.ts +10 -0
  103. package/dist/components/note.d.ts.map +1 -0
  104. package/dist/components/note.js +66 -0
  105. package/dist/components/note.js.map +1 -0
  106. package/dist/components/registry.d.ts +24 -0
  107. package/dist/components/registry.d.ts.map +1 -0
  108. package/dist/components/registry.js +36 -0
  109. package/dist/components/registry.js.map +1 -0
  110. package/dist/components/table.d.ts +90 -0
  111. package/dist/components/table.d.ts.map +1 -0
  112. package/dist/components/table.js +610 -0
  113. package/dist/components/table.js.map +1 -0
  114. package/dist/components/text.d.ts +10 -0
  115. package/dist/components/text.d.ts.map +1 -0
  116. package/dist/components/text.js +46 -0
  117. package/dist/components/text.js.map +1 -0
  118. package/dist/components/textarea.d.ts +10 -0
  119. package/dist/components/textarea.d.ts.map +1 -0
  120. package/dist/components/textarea.js +79 -0
  121. package/dist/components/textarea.js.map +1 -0
  122. package/dist/core/colors.d.ts +45 -0
  123. package/dist/core/colors.d.ts.map +1 -0
  124. package/dist/core/colors.js +93 -0
  125. package/dist/core/colors.js.map +1 -0
  126. package/dist/core/css.d.ts +20 -0
  127. package/dist/core/css.d.ts.map +1 -0
  128. package/dist/core/css.js +97 -0
  129. package/dist/core/css.js.map +1 -0
  130. package/dist/core/exceptions.d.ts +59 -0
  131. package/dist/core/exceptions.d.ts.map +1 -0
  132. package/dist/core/exceptions.js +100 -0
  133. package/dist/core/exceptions.js.map +1 -0
  134. package/dist/core/formatting.d.ts +53 -0
  135. package/dist/core/formatting.d.ts.map +1 -0
  136. package/dist/core/formatting.js +491 -0
  137. package/dist/core/formatting.js.map +1 -0
  138. package/dist/core/index.d.ts +10 -0
  139. package/dist/core/index.d.ts.map +1 -0
  140. package/dist/core/index.js +10 -0
  141. package/dist/core/index.js.map +1 -0
  142. package/dist/core/serializer.d.ts +29 -0
  143. package/dist/core/serializer.d.ts.map +1 -0
  144. package/dist/core/serializer.js +84 -0
  145. package/dist/core/serializer.js.map +1 -0
  146. package/dist/core/themes.d.ts +138 -0
  147. package/dist/core/themes.d.ts.map +1 -0
  148. package/dist/core/themes.js +484 -0
  149. package/dist/core/themes.js.map +1 -0
  150. package/dist/core/version-check.d.ts +23 -0
  151. package/dist/core/version-check.d.ts.map +1 -0
  152. package/dist/core/version-check.js +163 -0
  153. package/dist/core/version-check.js.map +1 -0
  154. package/dist/generate_test_harness.d.ts +13 -0
  155. package/dist/generate_test_harness.d.ts.map +1 -0
  156. package/dist/generate_test_harness.js +35 -0
  157. package/dist/generate_test_harness.js.map +1 -0
  158. package/dist/index.d.ts +17 -0
  159. package/dist/index.d.ts.map +1 -0
  160. package/dist/index.js +19 -0
  161. package/dist/index.js.map +1 -0
  162. package/dist/layout/converter.d.ts +22 -0
  163. package/dist/layout/converter.d.ts.map +1 -0
  164. package/dist/layout/converter.js +46 -0
  165. package/dist/layout/converter.js.map +1 -0
  166. package/dist/layout/csv.d.ts +15 -0
  167. package/dist/layout/csv.d.ts.map +1 -0
  168. package/dist/layout/csv.js +88 -0
  169. package/dist/layout/csv.js.map +1 -0
  170. package/dist/layout/dispatcher.d.ts +13 -0
  171. package/dist/layout/dispatcher.d.ts.map +1 -0
  172. package/dist/layout/dispatcher.js +47 -0
  173. package/dist/layout/dispatcher.js.map +1 -0
  174. package/dist/layout/index.d.ts +8 -0
  175. package/dist/layout/index.d.ts.map +1 -0
  176. package/dist/layout/index.js +8 -0
  177. package/dist/layout/index.js.map +1 -0
  178. package/dist/layout/parser.d.ts +19 -0
  179. package/dist/layout/parser.d.ts.map +1 -0
  180. package/dist/layout/parser.js +888 -0
  181. package/dist/layout/parser.js.map +1 -0
  182. package/dist/layout/templates.d.ts +32 -0
  183. package/dist/layout/templates.d.ts.map +1 -0
  184. package/dist/layout/templates.js +1016 -0
  185. package/dist/layout/templates.js.map +1 -0
  186. package/dist/types.d.ts +144 -0
  187. package/dist/types.d.ts.map +1 -0
  188. package/dist/types.js +5 -0
  189. package/dist/types.js.map +1 -0
  190. package/dist/vitest.config.d.ts +3 -0
  191. package/dist/vitest.config.d.ts.map +1 -0
  192. package/dist/vitest.config.js +14 -0
  193. package/dist/vitest.config.js.map +1 -0
  194. package/package.json +79 -0
@@ -0,0 +1,1016 @@
1
+ /**
2
+ * HTML templates for dashboard generation.
3
+ */
4
+ import { ECHARTS_CDN, PALETTE, COLORS, getThemeColors, getLinkColor, } from '../core/themes.js';
5
+ /**
6
+ * Escape HTML special characters
7
+ */
8
+ export function escapeHtml(text) {
9
+ return text
10
+ .replace(/&/g, '&')
11
+ .replace(/</g, '&lt;')
12
+ .replace(/>/g, '&gt;')
13
+ .replace(/"/g, '&quot;')
14
+ .replace(/'/g, '&#039;');
15
+ }
16
+ /**
17
+ * Generate the dashboard CSS with theme variables.
18
+ */
19
+ export function generateDashboardCss() {
20
+ const colorsLight = getThemeColors('light');
21
+ const colorsDark = getThemeColors('dark');
22
+ const linkColor = getLinkColor();
23
+ return `
24
+ @import url('https://fonts.googleapis.com/css2?family=Source+Serif+4:wght@400;600;700&display=swap');
25
+ * { box-sizing: border-box; margin: 0; padding: 0; }
26
+
27
+ /* CSS Variables for theming */
28
+ :root {
29
+ --bg: ${colorsLight.background};
30
+ --text: ${colorsLight.text};
31
+ --text-muted: ${colorsLight.textSecondary};
32
+ --grid: ${colorsLight.border};
33
+ --red: ${COLORS.ERROR_RED};
34
+ --link: ${linkColor};
35
+ }
36
+ body.theme-dark {
37
+ --bg: ${colorsDark.background};
38
+ --text: ${colorsDark.text};
39
+ --text-muted: ${colorsDark.textSecondary};
40
+ --grid: ${colorsDark.border};
41
+ --red: ${COLORS.ERROR_RED};
42
+ --link: ${linkColor};
43
+ }
44
+
45
+ html, body {
46
+ width: 100%; min-height: 100%;
47
+ background-color: var(--bg);
48
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
49
+ color: var(--text);
50
+ font-size: 12px;
51
+ transition: background-color 0.3s, color 0.3s;
52
+ }
53
+ .dashboard {
54
+ max-width: 720px;
55
+ margin: 0 auto;
56
+ padding: 16px;
57
+ position: relative;
58
+ }
59
+ .title-row {
60
+ display: flex;
61
+ align-items: center;
62
+ justify-content: space-between;
63
+ margin-bottom: 12px;
64
+ }
65
+ .title-row .page-title {
66
+ margin-bottom: 0;
67
+ }
68
+ .theme-toggle {
69
+ background: transparent;
70
+ border: none;
71
+ padding: 4px;
72
+ cursor: pointer;
73
+ color: var(--text-muted);
74
+ display: flex;
75
+ align-items: center;
76
+ justify-content: center;
77
+ transition: color 0.3s;
78
+ }
79
+ .theme-toggle:hover {
80
+ color: var(--text);
81
+ }
82
+ .theme-toggle svg {
83
+ width: 16px;
84
+ height: 16px;
85
+ }
86
+ .theme-toggle .icon-moon { display: block; }
87
+ .theme-toggle .icon-sun { display: none; }
88
+ body.theme-dark .theme-toggle .icon-moon { display: none; }
89
+ body.theme-dark .theme-toggle .icon-sun { display: block; }
90
+ .red-line {
91
+ width: 100%;
92
+ height: 2px;
93
+ background-color: var(--red);
94
+ margin-bottom: 8px;
95
+ }
96
+ .page-title {
97
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
98
+ font-size: 18px;
99
+ font-weight: 900;
100
+ margin-bottom: 12px;
101
+ color: var(--text);
102
+ }
103
+ .dashboard-section {
104
+ margin-bottom: 16px;
105
+ }
106
+ .dashboard-section:not(:first-of-type) {
107
+ margin-top: 32px;
108
+ padding-top: 20px;
109
+ border-top: 1px solid var(--grid);
110
+ }
111
+ /* Continuous mode: no section breaks */
112
+ .dashboard-continuous .dashboard-section:not(:first-of-type) {
113
+ margin-top: 16px;
114
+ padding-top: 0;
115
+ border-top: none;
116
+ }
117
+ .section-title {
118
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
119
+ font-size: 14px;
120
+ font-weight: 900;
121
+ letter-spacing: 0.05em;
122
+ text-transform: uppercase;
123
+ color: var(--text);
124
+ margin-bottom: 16px;
125
+ }
126
+ .chart-title {
127
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-weight: 700;
128
+ font-size: 10px;
129
+ font-weight: bold;
130
+ color: var(--text);
131
+ margin-bottom: 4px;
132
+ }
133
+ .row {
134
+ display: grid;
135
+ grid-template-columns: repeat(16, 1fr);
136
+ gap: 8px;
137
+ margin-bottom: 8px;
138
+ }
139
+ @media (max-width: 720px) {
140
+ .row { grid-template-columns: repeat(8, 1fr); }
141
+ /* Scale spans proportionally: preserve original span but cap at grid width */
142
+ .row > * { grid-column: span min(var(--col-span, 8), 8); }
143
+ }
144
+ @media (max-width: 480px) {
145
+ .row { grid-template-columns: 1fr; }
146
+ .row > * { grid-column: span 1; }
147
+ }
148
+ @media print {
149
+ .dashboard { max-width: 100%; padding: 0; }
150
+ .row { gap: 6px; margin-bottom: 6px; break-inside: avoid; page-break-inside: avoid; }
151
+ .grid-item { break-inside: avoid; page-break-inside: avoid; }
152
+ .dashboard-section { break-inside: avoid; page-break-inside: avoid; }
153
+ .dashboard-section.page-break { page-break-before: always; }
154
+ .data-table { break-inside: avoid; page-break-inside: avoid; }
155
+ .theme-toggle { display: none; }
156
+ }
157
+ .grid-item {
158
+ background: var(--bg);
159
+ padding: 4px;
160
+ transition: background-color 0.3s;
161
+ }
162
+ .big-value {
163
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-weight: 700;
164
+ font-size: 28px;
165
+ font-weight: bold;
166
+ color: var(--text);
167
+ line-height: 1;
168
+ }
169
+ .delta {
170
+ display: flex;
171
+ align-items: center;
172
+ gap: 4px;
173
+ }
174
+ .delta .arrow {
175
+ font-size: 16px;
176
+ }
177
+ .delta .value {
178
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
179
+ font-size: 16px;
180
+ font-weight: bold;
181
+ }
182
+ .label {
183
+ font-size: 10px;
184
+ color: var(--text-muted);
185
+ margin-top: 4px;
186
+ }
187
+ .alert {
188
+ display: flex;
189
+ align-items: flex-start;
190
+ gap: 8px;
191
+ padding: 8px 10px;
192
+ border-left: 2px solid;
193
+ }
194
+ .alert .icon {
195
+ font-size: 12px;
196
+ flex-shrink: 0;
197
+ }
198
+ .alert .message {
199
+ font-size: 11px;
200
+ line-height: 1.4;
201
+ }
202
+ .note {
203
+ border-left: 3px solid var(--red);
204
+ background-color: color-mix(in srgb, var(--grid) 25%, transparent);
205
+ padding: 8px 12px;
206
+ }
207
+ .note-warning {
208
+ border-left-color: #C5A000;
209
+ }
210
+ .note-tip {
211
+ border-left-color: #00887d;
212
+ }
213
+ .note-content {
214
+ font-size: 11px;
215
+ color: var(--text);
216
+ line-height: 1.5;
217
+ }
218
+ .note-content strong {
219
+ font-weight: 700;
220
+ }
221
+ .text-content {
222
+ font-size: 11px;
223
+ line-height: 1.5;
224
+ color: var(--text);
225
+ }
226
+ .data-table {
227
+ width: 100%; border-collapse: collapse; font-size: 11px;
228
+ }
229
+ .data-table th {
230
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 9px; font-weight: bold;
231
+ color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.03em;
232
+ padding: 6px 8px; border-bottom: 1px solid var(--text); text-align: left;
233
+ }
234
+ .data-table td {
235
+ padding: 5px 8px; color: var(--text); border-bottom: 1px solid var(--grid);
236
+ }
237
+ .data-table tr.table-striped td {
238
+ background-color: color-mix(in srgb, var(--grid) 19%, transparent);
239
+ }
240
+ .data-table.table-compact th, .data-table.table-compact td {
241
+ padding: 4px 6px;
242
+ }
243
+ .markdown-content {
244
+ font-size: 11px; color: var(--text); line-height: 1.5;
245
+ }
246
+ .markdown-content h1 { font-size: 18px; font-weight: 900; margin: 12px 0 6px 0; }
247
+ .markdown-content h2 { font-size: 14px; font-weight: 700; margin: 10px 0 4px 0; }
248
+ .markdown-content h3 { font-size: 12px; font-weight: 700; margin: 8px 0 4px 0; }
249
+ .markdown-content p { margin: 6px 0; }
250
+ .markdown-content ul, .markdown-content ol { margin: 6px 0; padding-left: 20px; }
251
+ .markdown-content li { margin: 3px 0; }
252
+ .markdown-content code {
253
+ background: color-mix(in srgb, var(--grid) 25%, transparent); padding: 1px 4px; border-radius: 2px;
254
+ font-family: 'Consolas', 'Monaco', monospace; font-size: 10px;
255
+ }
256
+ .markdown-content pre {
257
+ background: color-mix(in srgb, var(--grid) 25%, transparent); padding: 8px; border-radius: 3px;
258
+ overflow-x: auto; margin: 8px 0;
259
+ }
260
+ .markdown-content pre code { background: none; padding: 0; }
261
+ .markdown-content blockquote {
262
+ border-left: 2px solid var(--red); margin: 8px 0; padding-left: 12px;
263
+ color: var(--text-muted); font-style: italic;
264
+ }
265
+ .markdown-content a { color: var(--link); text-decoration: none; }
266
+ .markdown-content a:hover { text-decoration: underline; }
267
+ .markdown-content strong { font-weight: 700; }
268
+ .markdown-content em { font-style: italic; }
269
+ .empty-space { background: transparent; }
270
+ /* Inline header styles (### soft headers) */
271
+ .inline-header {
272
+ margin-top: 12px;
273
+ margin-bottom: 4px;
274
+ }
275
+ .inline-header-text {
276
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
277
+ font-size: 11px;
278
+ font-weight: 700;
279
+ color: var(--text);
280
+ margin: 0;
281
+ }
282
+ /* Progress bar sparkline styles */
283
+ .pct-bar-wrapper {
284
+ display: flex;
285
+ align-items: center;
286
+ gap: 8px;
287
+ padding: 4px 0;
288
+ }
289
+ .pct-bar-container {
290
+ flex: 1;
291
+ height: 12px;
292
+ background-color: var(--grid);
293
+ border-radius: 2px;
294
+ overflow: hidden;
295
+ min-width: 60px;
296
+ }
297
+ .pct-bar-fill {
298
+ height: 100%;
299
+ background-color: ${PALETTE[0]};
300
+ border-radius: 2px 0 0 2px;
301
+ }
302
+ .pct-bar-value {
303
+ font-size: 11px;
304
+ color: var(--text);
305
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
306
+ font-weight: 700;
307
+ min-width: 32px;
308
+ flex-shrink: 0;
309
+ }
310
+ /* Sparkline tooltip styles - immediate display on hover */
311
+ .spark-tooltip {
312
+ position: relative;
313
+ display: inline-block;
314
+ }
315
+ .spark-tooltip::after {
316
+ content: attr(data-tooltip);
317
+ position: absolute;
318
+ bottom: 100%;
319
+ left: 50%;
320
+ transform: translateX(-50%);
321
+ background: rgba(0, 0, 0, 0.85);
322
+ color: #fff;
323
+ padding: 4px 8px;
324
+ border-radius: 4px;
325
+ font-size: 11px;
326
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
327
+ white-space: nowrap;
328
+ opacity: 0;
329
+ visibility: hidden;
330
+ transition: opacity 0.15s ease;
331
+ pointer-events: none;
332
+ z-index: 1000;
333
+ margin-bottom: 4px;
334
+ }
335
+ .spark-tooltip:hover::after {
336
+ opacity: 1;
337
+ visibility: visible;
338
+ }`;
339
+ }
340
+ /**
341
+ * Generate complete dashboard HTML.
342
+ */
343
+ export function generateDashboardHtml(pageTitle, componentsHtml, chartScripts, theme = 'light', extraScripts = '', continuous = false) {
344
+ const initialThemeClass = theme === 'dark' ? 'theme-dark' : 'theme-light';
345
+ const continuousClass = continuous ? ' dashboard-continuous' : '';
346
+ const css = generateDashboardCss();
347
+ return `<!DOCTYPE html>
348
+ <html lang="en">
349
+ <head>
350
+ <meta charset="utf-8">
351
+ <title>${escapeHtml(pageTitle)}</title>
352
+ <script src="${ECHARTS_CDN}"></script>
353
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
354
+ ${extraScripts}
355
+ <style>${css}
356
+ </style>
357
+ </head>
358
+ <body class="${initialThemeClass}">
359
+ <div class="dashboard${continuousClass}">
360
+ <div class="red-line"></div>
361
+ <div class="title-row">
362
+ <h1 class="page-title">${escapeHtml(pageTitle)}</h1>
363
+ <button class="theme-toggle" onclick="toggleTheme()" aria-label="Toggle theme">
364
+ <svg class="icon-moon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/></svg>
365
+ <svg class="icon-sun" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/></svg>
366
+ </button>
367
+ </div>
368
+ ${componentsHtml.join('')}
369
+ </div>
370
+ <script>
371
+ // Theme toggle function
372
+ function toggleTheme() {
373
+ var isDark = document.body.classList.contains('theme-dark');
374
+ var newTheme = isDark ? 'light' : 'dark';
375
+ document.body.classList.remove('theme-light', 'theme-dark');
376
+ document.body.classList.add('theme-' + newTheme);
377
+
378
+ // Update all charts
379
+ if (window.chartInstances && window.chartOptions) {
380
+ for (var id in window.chartInstances) {
381
+ window.chartInstances[id].setOption(window.chartOptions[id][newTheme]);
382
+ }
383
+ }
384
+ }
385
+
386
+ ${chartScripts.join('')}
387
+
388
+ // Sparkline tooltip interactivity
389
+ document.querySelectorAll('.spark-hit').forEach(hit => {
390
+ hit.addEventListener('mouseenter', function() {
391
+ const tooltip = this.closest('.spark-tooltip');
392
+ if (tooltip) {
393
+ tooltip.setAttribute('data-tooltip', this.dataset.idx + ': ' + this.dataset.val);
394
+ }
395
+ });
396
+ hit.addEventListener('mouseleave', function() {
397
+ const tooltip = this.closest('.spark-tooltip');
398
+ if (tooltip && tooltip.dataset.default) {
399
+ tooltip.setAttribute('data-tooltip', tooltip.dataset.default);
400
+ }
401
+ });
402
+ });
403
+ </script>
404
+ </body>
405
+ </html>`;
406
+ }
407
+ /**
408
+ * Generate CSS for the test harness.
409
+ */
410
+ export function generateTestHarnessCss() {
411
+ const colorsLight = getThemeColors('light');
412
+ const colorsDark = getThemeColors('dark');
413
+ const linkColor = getLinkColor();
414
+ return `
415
+ @import url('https://fonts.googleapis.com/css2?family=Cousine:wght@400;700&family=Inter:wght@400;500;600&family=Source+Serif+4:wght@400;600;700&display=swap');
416
+ * { box-sizing: border-box; margin: 0; padding: 0; }
417
+
418
+ /* CSS Variables for theming */
419
+ :root {
420
+ --bg: ${colorsLight.background};
421
+ --text: ${colorsLight.text};
422
+ --text-muted: ${colorsLight.textSecondary};
423
+ --grid: ${colorsLight.border};
424
+ --red: ${COLORS.ERROR_RED};
425
+ --link: ${linkColor};
426
+ }
427
+ body.theme-dark {
428
+ --bg: ${colorsDark.background};
429
+ --text: ${colorsDark.text};
430
+ --text-muted: ${colorsDark.textSecondary};
431
+ --grid: ${colorsDark.border};
432
+ --red: ${COLORS.ERROR_RED};
433
+ --link: ${linkColor};
434
+ }
435
+
436
+ body {
437
+ font-family: 'Inter', sans-serif;
438
+ background: var(--bg);
439
+ color: var(--text);
440
+ transition: background-color 0.3s, color 0.3s;
441
+ }
442
+
443
+ /* Header styles */
444
+ header {
445
+ background: ${COLORS.TEXT_DARK};
446
+ color: ${COLORS.BG_LIGHT};
447
+ padding: 20px 32px;
448
+ position: sticky;
449
+ top: 0;
450
+ z-index: 100;
451
+ display: flex;
452
+ flex-wrap: wrap;
453
+ align-items: flex-start;
454
+ gap: 16px;
455
+ }
456
+ .header-left {
457
+ flex: 1;
458
+ }
459
+ header h1 {
460
+ font-family: 'Cousine', monospace;
461
+ font-size: 20px;
462
+ letter-spacing: 0.05em;
463
+ margin-bottom: 12px;
464
+ }
465
+ .nav {
466
+ display: flex;
467
+ gap: 8px;
468
+ flex-wrap: wrap;
469
+ margin-bottom: 12px;
470
+ }
471
+ .nav-item {
472
+ color: ${COLORS.BG_LIGHT};
473
+ text-decoration: none;
474
+ padding: 4px 10px;
475
+ font-size: 12px;
476
+ border: 1px solid ${COLORS.BG_LIGHT}60;
477
+ border-radius: 3px;
478
+ }
479
+ .nav-item:hover {
480
+ background: ${COLORS.BG_LIGHT}20;
481
+ }
482
+ .nav-toggle {
483
+ display: none;
484
+ background: transparent;
485
+ border: 1px solid ${COLORS.BG_LIGHT}60;
486
+ color: ${COLORS.BG_LIGHT};
487
+ padding: 6px 10px;
488
+ border-radius: 3px;
489
+ cursor: pointer;
490
+ margin-bottom: 12px;
491
+ }
492
+ .nav-toggle:hover {
493
+ background: ${COLORS.BG_LIGHT}20;
494
+ }
495
+ @media (max-width: 600px) {
496
+ .nav-toggle {
497
+ display: block;
498
+ }
499
+ .nav {
500
+ display: none;
501
+ flex-direction: column;
502
+ width: 100%;
503
+ }
504
+ .nav.nav-open {
505
+ display: flex;
506
+ }
507
+ .nav-item {
508
+ padding: 8px 12px;
509
+ }
510
+ header {
511
+ padding: 16px;
512
+ }
513
+ .header-actions {
514
+ width: 100%;
515
+ justify-content: space-between;
516
+ }
517
+ }
518
+ .header-actions {
519
+ display: flex;
520
+ gap: 10px;
521
+ align-items: center;
522
+ }
523
+ .btn {
524
+ padding: 6px 14px;
525
+ border: none;
526
+ border-radius: 3px;
527
+ font-size: 12px;
528
+ font-weight: 500;
529
+ cursor: pointer;
530
+ }
531
+ .btn-primary {
532
+ background: ${PALETTE[0]};
533
+ color: white;
534
+ }
535
+ .btn-secondary {
536
+ background: transparent;
537
+ color: ${COLORS.BG_LIGHT};
538
+ border: 1px solid ${COLORS.BG_LIGHT}60;
539
+ }
540
+ .btn-secondary:hover {
541
+ background: ${COLORS.BG_LIGHT}20;
542
+ }
543
+
544
+ /* Main content */
545
+ main {
546
+ max-width: 1600px;
547
+ margin: 0 auto;
548
+ padding: 32px;
549
+ }
550
+
551
+ /* Summary section */
552
+ .summary {
553
+ display: grid;
554
+ grid-template-columns: repeat(3, 1fr);
555
+ gap: 16px;
556
+ margin-bottom: 32px;
557
+ }
558
+ .summary-item {
559
+ background: var(--bg);
560
+ padding: 16px;
561
+ border: 1px solid var(--grid);
562
+ text-align: center;
563
+ transition: background-color 0.3s;
564
+ }
565
+ .summary-item .count {
566
+ font-family: 'Cousine', monospace;
567
+ font-size: 28px;
568
+ font-weight: bold;
569
+ color: ${PALETTE[0]};
570
+ }
571
+ .summary-item .label {
572
+ font-size: 11px;
573
+ color: var(--text-muted);
574
+ margin-top: 4px;
575
+ }
576
+
577
+ /* Test section styles */
578
+ .test-section {
579
+ margin-bottom: 40px;
580
+ }
581
+ .section-title {
582
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
583
+ font-size: 14px;
584
+ font-weight: 900;
585
+ letter-spacing: 0.05em;
586
+ color: var(--text);
587
+ margin-top: 32px;
588
+ margin-bottom: 16px;
589
+ padding-top: 24px;
590
+ border-top: 1px solid var(--grid);
591
+ }
592
+ .dashboard-section:first-of-type .section-title {
593
+ margin-top: 0;
594
+ padding-top: 0;
595
+ border-top: none;
596
+ }
597
+
598
+ /* Dashboard content styles */
599
+ .dashboard {
600
+ max-width: 720px;
601
+ margin: 0 auto;
602
+ padding: 16px;
603
+ }
604
+ .red-line {
605
+ width: 100%;
606
+ height: 2px;
607
+ background-color: var(--red);
608
+ margin-bottom: 8px;
609
+ }
610
+ .page-title {
611
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
612
+ font-size: 18px;
613
+ font-weight: 900;
614
+ margin-bottom: 12px;
615
+ color: var(--text);
616
+ }
617
+ .dashboard-section {
618
+ margin-bottom: 16px;
619
+ }
620
+ .dashboard-section:not(:first-of-type) {
621
+ margin-top: 32px;
622
+ padding-top: 20px;
623
+ border-top: 1px solid var(--grid);
624
+ }
625
+ .chart-title {
626
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-weight: 700;
627
+ font-size: 10px;
628
+ font-weight: bold;
629
+ color: var(--text);
630
+ margin-bottom: 4px;
631
+ }
632
+
633
+ /* Grid layout */
634
+ .row {
635
+ display: grid;
636
+ grid-template-columns: repeat(16, 1fr);
637
+ gap: 8px;
638
+ margin-bottom: 8px;
639
+ }
640
+ @media (max-width: 720px) {
641
+ .row { grid-template-columns: repeat(8, 1fr); }
642
+ .row > * { grid-column: span min(var(--col-span, 8), 8); }
643
+ }
644
+ @media (max-width: 480px) {
645
+ .row { grid-template-columns: 1fr; }
646
+ .row > * { grid-column: span 1; }
647
+ }
648
+
649
+ /* Grid items */
650
+ .grid-item {
651
+ background: var(--bg);
652
+ padding: 4px;
653
+ transition: background-color 0.3s;
654
+ }
655
+
656
+ /* Component styles */
657
+ .big-value {
658
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-weight: 700;
659
+ font-size: 28px;
660
+ font-weight: bold;
661
+ color: var(--text);
662
+ line-height: 1;
663
+ }
664
+ .delta {
665
+ display: flex;
666
+ align-items: center;
667
+ gap: 4px;
668
+ }
669
+ .delta .arrow {
670
+ font-size: 16px;
671
+ }
672
+ .delta .value {
673
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
674
+ font-size: 16px;
675
+ font-weight: bold;
676
+ }
677
+ .label {
678
+ font-size: 10px;
679
+ color: var(--text-muted);
680
+ margin-top: 4px;
681
+ }
682
+ .alert {
683
+ display: flex;
684
+ align-items: flex-start;
685
+ gap: 8px;
686
+ padding: 8px 10px;
687
+ border-left: 2px solid;
688
+ }
689
+ .alert .icon {
690
+ font-size: 12px;
691
+ flex-shrink: 0;
692
+ }
693
+ .alert .message {
694
+ font-size: 11px;
695
+ line-height: 1.4;
696
+ }
697
+ .note {
698
+ border-left: 3px solid var(--red);
699
+ background-color: color-mix(in srgb, var(--grid) 25%, transparent);
700
+ padding: 8px 12px;
701
+ }
702
+ .note-warning {
703
+ border-left-color: #C5A000;
704
+ }
705
+ .note-tip {
706
+ border-left-color: #00887d;
707
+ }
708
+ .note-content {
709
+ font-size: 11px;
710
+ color: var(--text);
711
+ line-height: 1.5;
712
+ }
713
+ .note-content strong {
714
+ font-weight: 700;
715
+ }
716
+ .text-content {
717
+ font-size: 11px;
718
+ line-height: 1.5;
719
+ color: var(--text);
720
+ }
721
+
722
+ /* Table styles */
723
+ .data-table {
724
+ width: 100%; border-collapse: collapse; font-size: 11px;
725
+ }
726
+ .data-table th {
727
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 9px; font-weight: bold;
728
+ color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.03em;
729
+ padding: 6px 8px; border-bottom: 1px solid var(--text); text-align: left;
730
+ }
731
+ .data-table td {
732
+ padding: 5px 8px; color: var(--text); border-bottom: 1px solid var(--grid);
733
+ }
734
+ .data-table tr.table-striped td {
735
+ background-color: color-mix(in srgb, var(--grid) 19%, transparent);
736
+ }
737
+ .data-table.table-compact th, .data-table.table-compact td {
738
+ padding: 3px 6px;
739
+ font-size: 10px;
740
+ }
741
+
742
+ /* Markdown content */
743
+ .markdown-content {
744
+ font-size: 11px; color: var(--text); line-height: 1.5;
745
+ }
746
+ .empty-space { background: transparent; }
747
+
748
+ /* Inline header styles */
749
+ .inline-header {
750
+ margin-top: 12px;
751
+ margin-bottom: 4px;
752
+ }
753
+ .inline-header-text {
754
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
755
+ font-size: 11px;
756
+ font-weight: 700;
757
+ color: var(--text);
758
+ margin: 0;
759
+ }
760
+
761
+ /* Progress bar sparkline styles */
762
+ .pct-bar-wrapper {
763
+ display: flex;
764
+ align-items: center;
765
+ gap: 8px;
766
+ padding: 4px 0;
767
+ }
768
+ .pct-bar-container {
769
+ flex: 1;
770
+ height: 12px;
771
+ background-color: var(--grid);
772
+ border-radius: 2px;
773
+ overflow: hidden;
774
+ min-width: 60px;
775
+ }
776
+ .pct-bar-fill {
777
+ height: 100%;
778
+ background-color: ${PALETTE[0]};
779
+ border-radius: 2px 0 0 2px;
780
+ }
781
+ .pct-bar-value {
782
+ font-size: 11px;
783
+ color: var(--text);
784
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
785
+ font-weight: 700;
786
+ min-width: 32px;
787
+ flex-shrink: 0;
788
+ }
789
+
790
+ /* Sparkline tooltip styles */
791
+ .spark-tooltip {
792
+ position: relative;
793
+ display: inline-block;
794
+ }
795
+ .spark-tooltip::after {
796
+ content: attr(data-tooltip);
797
+ position: absolute;
798
+ bottom: 100%;
799
+ left: 50%;
800
+ transform: translateX(-50%);
801
+ background: rgba(0, 0, 0, 0.85);
802
+ color: #fff;
803
+ padding: 4px 8px;
804
+ border-radius: 4px;
805
+ font-size: 11px;
806
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
807
+ white-space: nowrap;
808
+ opacity: 0;
809
+ visibility: hidden;
810
+ transition: opacity 0.15s ease;
811
+ pointer-events: none;
812
+ z-index: 1000;
813
+ margin-bottom: 4px;
814
+ }
815
+ .spark-tooltip:hover::after {
816
+ opacity: 1;
817
+ visibility: visible;
818
+ }
819
+
820
+ /* Annotation bar */
821
+ .annotation-bar {
822
+ background: var(--bg);
823
+ border-top: 1px solid var(--grid);
824
+ padding: 10px;
825
+ margin-top: 8px;
826
+ }
827
+ .annotation-bar textarea {
828
+ width: 100%;
829
+ min-height: 40px;
830
+ padding: 6px;
831
+ border: 1px solid var(--grid);
832
+ font-family: inherit;
833
+ font-size: 11px;
834
+ resize: vertical;
835
+ }
836
+
837
+ /* Responsive */
838
+ @media (max-width: 1000px) {
839
+ .summary {
840
+ grid-template-columns: repeat(2, 1fr);
841
+ }
842
+ }
843
+
844
+ /* Print styles */
845
+ @media print {
846
+ .row { break-inside: avoid; page-break-inside: avoid; }
847
+ .grid-item { break-inside: avoid; page-break-inside: avoid; }
848
+ .dashboard-section { break-inside: avoid; page-break-inside: avoid; }
849
+ .dashboard-section.page-break { page-break-before: always; }
850
+ .data-table { break-inside: avoid; page-break-inside: avoid; }
851
+ header { display: none; }
852
+ .annotation-bar { display: none; }
853
+ }`;
854
+ }
855
+ /**
856
+ * Generate JavaScript for test harness functionality.
857
+ */
858
+ export function generateTestHarnessJs(totalCharts, testSpecsJson) {
859
+ return `
860
+ const CHART_SPECS = ${testSpecsJson};
861
+
862
+ // Nav toggle function for mobile
863
+ function toggleNav() {
864
+ var nav = document.getElementById('main-nav');
865
+ if (nav) {
866
+ nav.classList.toggle('nav-open');
867
+ }
868
+ }
869
+
870
+ // Theme toggle function
871
+ function toggleTheme() {
872
+ var isDark = document.body.classList.contains('theme-dark');
873
+ var newTheme = isDark ? 'light' : 'dark';
874
+ document.body.classList.remove('theme-light', 'theme-dark');
875
+ document.body.classList.add('theme-' + newTheme);
876
+
877
+ // Update theme indicator
878
+ document.getElementById('theme-indicator').textContent = newTheme.toUpperCase();
879
+
880
+ // Update all charts
881
+ if (window.chartInstances && window.chartOptions) {
882
+ for (var id in window.chartInstances) {
883
+ window.chartInstances[id].setOption(window.chartOptions[id][newTheme]);
884
+ }
885
+ }
886
+ }
887
+
888
+ // Initialize theme indicator
889
+ document.getElementById('theme-indicator').textContent =
890
+ document.body.classList.contains('theme-dark') ? 'DARK' : 'LIGHT';
891
+
892
+ document.querySelectorAll('textarea').forEach(ta => {
893
+ ta.value = localStorage.getItem(ta.id) || '';
894
+ ta.addEventListener('input', () => localStorage.setItem(ta.id, ta.value));
895
+ });
896
+
897
+ function exportNotes() {
898
+ const notes = document.getElementById('test-notes').value.trim();
899
+ if (!notes) { alert('No notes to export'); return; }
900
+ const data = {
901
+ exported: new Date().toISOString(),
902
+ totalCharts: ${totalCharts},
903
+ notes: notes,
904
+ specs: CHART_SPECS
905
+ };
906
+ const blob = new Blob([JSON.stringify(data, null, 2)], {type: 'application/json'});
907
+ const a = document.createElement('a');
908
+ a.href = URL.createObjectURL(blob);
909
+ a.download = 'test-notes-' + new Date().toISOString().split('T')[0] + '.json';
910
+ a.click();
911
+ }
912
+
913
+ function clearAllNotes() {
914
+ if (confirm('Clear all notes?')) {
915
+ document.querySelectorAll('textarea').forEach(ta => {
916
+ ta.value = '';
917
+ localStorage.removeItem(ta.id);
918
+ });
919
+ }
920
+ }`;
921
+ }
922
+ /**
923
+ * Generate JavaScript for sparkline tooltip interactivity.
924
+ */
925
+ export function generateSparklineTooltipJs() {
926
+ return `
927
+ // Sparkline tooltip interactivity
928
+ document.querySelectorAll('.spark-hit').forEach(hit => {
929
+ hit.addEventListener('mouseenter', function() {
930
+ const tooltip = this.closest('.spark-tooltip');
931
+ if (tooltip) {
932
+ tooltip.setAttribute('data-tooltip', this.dataset.idx + ': ' + this.dataset.val);
933
+ }
934
+ });
935
+ hit.addEventListener('mouseleave', function() {
936
+ const tooltip = this.closest('.spark-tooltip');
937
+ if (tooltip && tooltip.dataset.default) {
938
+ tooltip.setAttribute('data-tooltip', tooltip.dataset.default);
939
+ }
940
+ });
941
+ });`;
942
+ }
943
+ /**
944
+ * Generate complete test harness HTML document.
945
+ */
946
+ export function generateTestHarnessHtml(pageTitle, navItems, totalCharts, typesCount, componentsHtml, chartScripts, testHarnessJs, theme = 'light') {
947
+ const initialThemeClass = theme === 'dark' ? 'theme-dark' : 'theme-light';
948
+ const css = generateTestHarnessCss();
949
+ const sparklineJs = generateSparklineTooltipJs();
950
+ return `<!DOCTYPE html>
951
+ <html lang="en">
952
+ <head>
953
+ <meta charset="utf-8">
954
+ <meta name="viewport" content="width=device-width, initial-scale=1">
955
+ <title>${escapeHtml(pageTitle)} - Test Harness</title>
956
+ <script src="${ECHARTS_CDN}"></script>
957
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
958
+ <style>${css}
959
+ </style>
960
+ </head>
961
+ <body class="${initialThemeClass}">
962
+ <header>
963
+ <div class="header-left">
964
+ <h1>MVIZ TEST HARNESS</h1>
965
+ <button class="nav-toggle" onclick="toggleNav()" aria-label="Toggle navigation">
966
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
967
+ <path d="M3 5h14M3 10h14M3 15h14" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
968
+ </svg>
969
+ </button>
970
+ <nav class="nav" id="main-nav">
971
+ ${navItems}
972
+ </nav>
973
+ </div>
974
+ <div class="header-actions">
975
+ <button class="btn btn-secondary" onclick="toggleTheme()">Toggle Theme</button>
976
+ <button class="btn btn-primary" onclick="exportNotes()">Export Notes</button>
977
+ <button class="btn btn-secondary" onclick="clearAllNotes()">Clear Notes</button>
978
+ </div>
979
+ </header>
980
+ <main>
981
+ <div class="summary">
982
+ <div class="summary-item">
983
+ <div class="count">${totalCharts}</div>
984
+ <div class="label">TOTAL CHARTS</div>
985
+ </div>
986
+ <div class="summary-item">
987
+ <div class="count">${typesCount}</div>
988
+ <div class="label">CHART TYPES</div>
989
+ </div>
990
+ <div class="summary-item">
991
+ <div class="count" id="theme-indicator">LIGHT</div>
992
+ <div class="label">CURRENT THEME</div>
993
+ </div>
994
+ </div>
995
+ <div class="dashboard">
996
+ <div class="red-line"></div>
997
+ <h1 class="page-title">${escapeHtml(pageTitle)}</h1>
998
+ ${componentsHtml}
999
+ </div>
1000
+ <div class="annotation-bar">
1001
+ <h3 style="font-size: 12px; margin-bottom: 8px;">Test Notes</h3>
1002
+ <textarea id="test-notes" placeholder="Add notes about this test run..."></textarea>
1003
+ </div>
1004
+ </main>
1005
+ <script>
1006
+ ${testHarnessJs}
1007
+
1008
+ // Initialize charts
1009
+ ${chartScripts}
1010
+
1011
+ ${sparklineJs}
1012
+ </script>
1013
+ </body>
1014
+ </html>`;
1015
+ }
1016
+ //# sourceMappingURL=templates.js.map