@keyfunc/keybricks-ds 1.0.3 → 2.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 (358) hide show
  1. package/DEPLOY.md +4 -4
  2. package/DESIGN.md +154 -53
  3. package/INTEGRATION.md +176 -36
  4. package/SKILL.md +6 -4
  5. package/_adherence.oxlintrc.json +183 -72
  6. package/_scripts/build-index.mjs +9 -0
  7. package/_scripts/build-tokens.mjs +5 -2
  8. package/assets/icons/icon-index.js +1 -1
  9. package/assets/icons/icon-index.json +1 -1
  10. package/components/_cards/accordion.card.html +2 -2
  11. package/components/_cards/action-chip.card.html +8 -3
  12. package/components/_cards/activity.card.html +2 -2
  13. package/components/_cards/alert.card.html +2 -2
  14. package/components/_cards/app-chrome.card.html +4 -4
  15. package/components/_cards/banner.card.html +1 -1
  16. package/components/_cards/breadcrumb.card.html +2 -2
  17. package/components/_cards/button-1-base.card.html +2 -2
  18. package/components/_cards/button-2-states.card.html +14 -14
  19. package/components/_cards/button-3-sizes-icons.card.html +1 -1
  20. package/components/_cards/button-4-group.card.html +5 -5
  21. package/components/_cards/canvas-zoom-controls.card.html +1 -1
  22. package/components/_cards/cards-indicators.card.html +2 -2
  23. package/components/_cards/charts-1-sparkline.card.html +2 -2
  24. package/components/_cards/charts-10-gauge.card.html +4 -4
  25. package/components/_cards/charts-11-funnel.card.html +2 -2
  26. package/components/_cards/charts-12-treemap.card.html +2 -2
  27. package/components/_cards/charts-14-radar.card.html +1 -1
  28. package/components/_cards/charts-15-distribution.card.html +1 -1
  29. package/components/_cards/charts-16-pareto.card.html +2 -2
  30. package/components/_cards/charts-17-candlestick.card.html +2 -2
  31. package/components/_cards/charts-18-sankey.card.html +2 -2
  32. package/components/_cards/charts-19-chord.card.html +2 -2
  33. package/components/_cards/charts-2-line.card.html +2 -2
  34. package/components/_cards/charts-20-sunburst.card.html +2 -2
  35. package/components/_cards/charts-21-worldmap.card.html +2 -2
  36. package/components/_cards/charts-22-gantt.card.html +2 -2
  37. package/components/_cards/charts-23-bar3d.card.html +2 -2
  38. package/components/_cards/charts-24-pie3d.card.html +2 -2
  39. package/components/_cards/charts-25-surface3d.card.html +2 -2
  40. package/components/_cards/charts-26-scatter3d.card.html +2 -2
  41. package/components/_cards/charts-27-ribbon3d.card.html +2 -2
  42. package/components/_cards/charts-3-bar.card.html +2 -2
  43. package/components/_cards/charts-4-barlist.card.html +2 -2
  44. package/components/_cards/charts-5-donut.card.html +3 -3
  45. package/components/_cards/charts-6-scatter.card.html +2 -2
  46. package/components/_cards/charts-7-heatmap.card.html +2 -2
  47. package/components/_cards/charts-8-waterfall.card.html +2 -2
  48. package/components/_cards/charts-9-bullet.card.html +2 -2
  49. package/components/_cards/charts-99-fullpage.card.html +5 -5
  50. package/components/_cards/checkbox.card.html +4 -4
  51. package/components/_cards/codeblock.card.html +1 -1
  52. package/components/_cards/combobox.card.html +3 -2
  53. package/components/_cards/context-menu.card.html +3 -2
  54. package/components/_cards/date-picker.card.html +5 -2
  55. package/components/_cards/diffvalue.card.html +2 -2
  56. package/components/_cards/divider.card.html +3 -3
  57. package/components/_cards/drawer.card.html +2 -2
  58. package/components/_cards/dropdown-menu.card.html +6 -6
  59. package/components/_cards/dropzone.card.html +1 -1
  60. package/components/_cards/empty-state.card.html +5 -5
  61. package/components/_cards/filter-bar.card.html +5 -5
  62. package/components/_cards/icon-button.card.html +2 -2
  63. package/components/_cards/icons-1-core.card.html +2 -2
  64. package/components/_cards/icons-2-feather.card.html +3 -3
  65. package/components/_cards/icons-3-unicons.card.html +3 -3
  66. package/components/_cards/input.card.html +5 -2
  67. package/components/_cards/kbd.card.html +2 -2
  68. package/components/_cards/keyvalue.card.html +2 -2
  69. package/components/_cards/listrow.card.html +2 -2
  70. package/components/_cards/loading.card.html +2 -2
  71. package/components/_cards/log-viewer.card.html +2 -2
  72. package/components/_cards/modal.card.html +9 -9
  73. package/components/_cards/multi-select.card.html +3 -2
  74. package/components/_cards/number-stepper.card.html +3 -2
  75. package/components/_cards/pagination.card.html +3 -2
  76. package/components/_cards/people.card.html +3 -3
  77. package/components/_cards/pipeline-node.card.html +2 -2
  78. package/components/_cards/pipeline-palette.card.html +4 -4
  79. package/components/_cards/radio.card.html +46 -0
  80. package/components/_cards/search.card.html +5 -2
  81. package/components/_cards/segmented-control.card.html +39 -0
  82. package/components/_cards/select.card.html +4 -4
  83. package/components/_cards/slider.card.html +1 -1
  84. package/components/_cards/smart-table.card.html +1 -1
  85. package/components/_cards/spreadsheet.card.html +2 -2
  86. package/components/_cards/stepper.card.html +2 -2
  87. package/components/_cards/table.card.html +103 -0
  88. package/components/_cards/tabs.card.html +3 -2
  89. package/components/_cards/tag.card.html +5 -2
  90. package/components/_cards/textarea.card.html +2 -2
  91. package/components/_cards/timeline.card.html +1 -1
  92. package/components/_cards/toast.card.html +4 -4
  93. package/components/_cards/toggles.card.html +45 -56
  94. package/components/_cards/tooltip.card.html +5 -5
  95. package/components/_cards/tree.card.html +2 -2
  96. package/components/_shared/src-buttons.js +1 -1
  97. package/components/_shared/src-charts.js +1 -1
  98. package/components/_shared/src-data.js +1 -1
  99. package/components/_shared/src-display.js +1 -1
  100. package/components/_shared/src-feedback.js +1 -1
  101. package/components/_shared/src-forms.js +1 -1
  102. package/components/_shared/src-navigation.js +1 -1
  103. package/components/_shared/src-pipeline.js +1 -1
  104. package/components/_shared/src-tabs.js +1 -1
  105. package/components/_shared/view-source.js +9 -4
  106. package/components/_vue/index.mjs +9 -5
  107. package/components/buttons/ActionChip.d.ts +3 -1
  108. package/components/buttons/ActionChip.jsx +4 -4
  109. package/components/buttons/ActionChip.vue +4 -4
  110. package/components/buttons/Button.d.ts +5 -2
  111. package/components/buttons/Button.jsx +15 -18
  112. package/components/buttons/Button.vue +17 -18
  113. package/components/buttons/ButtonGroup.vue +1 -1
  114. package/components/buttons/IconButton.d.ts +2 -1
  115. package/components/buttons/IconButton.jsx +9 -7
  116. package/components/buttons/IconButton.vue +10 -8
  117. package/components/buttons/buttons.prompt.md +6 -5
  118. package/components/charts/Bar3D.jsx +2 -2
  119. package/components/charts/Bar3D.vue +2 -2
  120. package/components/charts/BarChart.jsx +4 -4
  121. package/components/charts/BarChart.vue +4 -4
  122. package/components/charts/BarList.jsx +1 -1
  123. package/components/charts/BarList.vue +1 -1
  124. package/components/charts/BoxPlot.jsx +1 -1
  125. package/components/charts/BoxPlot.vue +2 -2
  126. package/components/charts/BulletChart.jsx +1 -1
  127. package/components/charts/BulletChart.vue +1 -1
  128. package/components/charts/CandlestickChart.jsx +2 -2
  129. package/components/charts/CandlestickChart.vue +3 -3
  130. package/components/charts/ChartKit.jsx +5 -5
  131. package/components/charts/ChartLegend.vue +16 -0
  132. package/components/charts/ChartTooltip.vue +16 -0
  133. package/components/charts/ChordChart.jsx +2 -2
  134. package/components/charts/ChordChart.vue +2 -2
  135. package/components/charts/GanttChart.jsx +2 -2
  136. package/components/charts/GanttChart.vue +2 -2
  137. package/components/charts/GaugeChart.jsx +2 -2
  138. package/components/charts/GaugeChart.vue +2 -2
  139. package/components/charts/HeatmapChart.jsx +2 -2
  140. package/components/charts/HeatmapChart.vue +2 -2
  141. package/components/charts/Histogram.jsx +2 -2
  142. package/components/charts/Histogram.vue +3 -3
  143. package/components/charts/LineChart.jsx +1 -1
  144. package/components/charts/LineChart.vue +3 -3
  145. package/components/charts/ParetoChart.jsx +3 -3
  146. package/components/charts/ParetoChart.vue +4 -4
  147. package/components/charts/PlotGrid.vue +18 -0
  148. package/components/charts/RadarChart.jsx +1 -1
  149. package/components/charts/RadarChart.vue +1 -1
  150. package/components/charts/Ribbon3D.jsx +2 -2
  151. package/components/charts/Ribbon3D.vue +2 -2
  152. package/components/charts/SankeyChart.jsx +2 -2
  153. package/components/charts/SankeyChart.vue +2 -2
  154. package/components/charts/Scatter3D.jsx +1 -1
  155. package/components/charts/Scatter3D.vue +1 -1
  156. package/components/charts/ScatterChart.jsx +3 -3
  157. package/components/charts/ScatterChart.vue +4 -4
  158. package/components/charts/Sparkline.jsx +1 -1
  159. package/components/charts/Sparkline.vue +1 -1
  160. package/components/charts/SunburstChart.jsx +1 -1
  161. package/components/charts/SunburstChart.vue +1 -1
  162. package/components/charts/Surface3D.jsx +2 -2
  163. package/components/charts/Surface3D.vue +2 -2
  164. package/components/charts/TreemapChart.jsx +2 -2
  165. package/components/charts/TreemapChart.vue +2 -2
  166. package/components/charts/WaterfallChart.jsx +2 -2
  167. package/components/charts/WaterfallChart.vue +3 -3
  168. package/components/charts/WorldMap.jsx +2 -2
  169. package/components/charts/WorldMap.vue +2 -2
  170. package/components/charts/XLabels.vue +11 -0
  171. package/components/data/CodeBlock.jsx +1 -1
  172. package/components/data/CodeBlock.vue +1 -1
  173. package/components/data/DiffValue.jsx +1 -1
  174. package/components/data/DiffValue.vue +1 -1
  175. package/components/data/FilterBar.d.ts +2 -0
  176. package/components/data/FilterBar.jsx +10 -9
  177. package/components/data/FilterBar.vue +9 -8
  178. package/components/data/KeyValue.jsx +3 -3
  179. package/components/data/KeyValue.vue +3 -3
  180. package/components/data/ListRow.jsx +2 -2
  181. package/components/data/ListRow.vue +2 -2
  182. package/components/data/Pagination.d.ts +5 -1
  183. package/components/data/Pagination.jsx +16 -12
  184. package/components/data/Pagination.vue +14 -9
  185. package/components/data/SmartTable.d.ts +4 -1
  186. package/components/data/SmartTable.jsx +16 -14
  187. package/components/data/SmartTable.vue +25 -22
  188. package/components/data/Spreadsheet.d.ts +2 -0
  189. package/components/data/Spreadsheet.jsx +12 -11
  190. package/components/data/Spreadsheet.vue +14 -13
  191. package/components/data/Table.d.ts +47 -0
  192. package/components/data/Table.jsx +85 -0
  193. package/components/data/Table.vue +63 -0
  194. package/components/data/data.prompt.md +3 -3
  195. package/components/display/Avatar.vue +3 -2
  196. package/components/display/AvatarGroup.vue +3 -2
  197. package/components/display/Badge.vue +1 -1
  198. package/components/display/Divider.vue +3 -3
  199. package/components/display/Donut.vue +3 -2
  200. package/components/display/IconTile.vue +2 -1
  201. package/components/display/Kbd.vue +1 -1
  202. package/components/display/ProgressBar.jsx +1 -1
  203. package/components/display/ProgressBar.vue +1 -1
  204. package/components/display/StatCard.vue +1 -1
  205. package/components/display/Tag.d.ts +3 -1
  206. package/components/display/Tag.jsx +4 -4
  207. package/components/display/Tag.vue +4 -4
  208. package/components/feedback/Accordion.jsx +2 -2
  209. package/components/feedback/Accordion.vue +2 -2
  210. package/components/feedback/ActivityItem.d.ts +3 -1
  211. package/components/feedback/ActivityItem.jsx +5 -5
  212. package/components/feedback/ActivityItem.vue +5 -4
  213. package/components/feedback/Alert.jsx +4 -4
  214. package/components/feedback/Alert.vue +3 -3
  215. package/components/feedback/Banner.jsx +5 -5
  216. package/components/feedback/Banner.vue +5 -5
  217. package/components/feedback/ContextMenu.d.ts +3 -1
  218. package/components/feedback/ContextMenu.jsx +5 -5
  219. package/components/feedback/ContextMenu.vue +5 -5
  220. package/components/feedback/DotActivity.vue +3 -3
  221. package/components/feedback/Drawer.d.ts +2 -0
  222. package/components/feedback/Drawer.jsx +6 -6
  223. package/components/feedback/Drawer.vue +22 -15
  224. package/components/feedback/EmptyState.vue +2 -2
  225. package/components/feedback/LogViewer.vue +1 -1
  226. package/components/feedback/Modal.d.ts +2 -0
  227. package/components/feedback/Modal.jsx +12 -11
  228. package/components/feedback/Modal.vue +10 -8
  229. package/components/feedback/Skeleton.vue +2 -1
  230. package/components/feedback/Spinner.vue +2 -1
  231. package/components/feedback/Stepper.vue +1 -1
  232. package/components/feedback/Timeline.jsx +1 -1
  233. package/components/feedback/Timeline.vue +1 -1
  234. package/components/feedback/Toast.vue +4 -4
  235. package/components/feedback/ToastStack.vue +11 -0
  236. package/components/feedback/Tooltip.vue +19 -0
  237. package/components/feedback/feedback.prompt.md +4 -4
  238. package/components/forms/Checkbox.d.ts +3 -2
  239. package/components/forms/Checkbox.jsx +7 -7
  240. package/components/forms/Checkbox.vue +8 -8
  241. package/components/forms/Combobox.d.ts +2 -0
  242. package/components/forms/Combobox.jsx +12 -10
  243. package/components/forms/Combobox.vue +11 -10
  244. package/components/forms/DatePicker.d.ts +4 -0
  245. package/components/forms/DatePicker.jsx +16 -12
  246. package/components/forms/DatePicker.vue +15 -11
  247. package/components/forms/Dropzone.jsx +2 -2
  248. package/components/forms/Dropzone.vue +5 -5
  249. package/components/forms/Field.d.ts +5 -1
  250. package/components/forms/Field.jsx +4 -4
  251. package/components/forms/Field.vue +4 -4
  252. package/components/forms/Input.d.ts +5 -1
  253. package/components/forms/Input.jsx +7 -7
  254. package/components/forms/Input.vue +3 -3
  255. package/components/forms/MultiSelect.d.ts +2 -0
  256. package/components/forms/MultiSelect.jsx +13 -11
  257. package/components/forms/MultiSelect.vue +12 -11
  258. package/components/forms/NumberStepper.d.ts +3 -1
  259. package/components/forms/NumberStepper.jsx +7 -7
  260. package/components/forms/NumberStepper.vue +8 -7
  261. package/components/forms/Radio.d.ts +30 -0
  262. package/components/forms/Radio.jsx +65 -0
  263. package/components/forms/Radio.vue +17 -9
  264. package/components/forms/RadioGroup.vue +22 -4
  265. package/components/forms/SearchInput.d.ts +3 -1
  266. package/components/forms/SearchInput.jsx +6 -6
  267. package/components/forms/SearchInput.vue +7 -6
  268. package/components/forms/Select.d.ts +3 -2
  269. package/components/forms/Select.jsx +6 -7
  270. package/components/forms/Select.vue +6 -6
  271. package/components/forms/Slider.jsx +3 -3
  272. package/components/forms/Slider.vue +3 -3
  273. package/components/forms/Switch.d.ts +32 -24
  274. package/components/forms/Switch.jsx +85 -35
  275. package/components/forms/Switch.vue +57 -11
  276. package/components/forms/Textarea.vue +4 -4
  277. package/components/forms/forms.prompt.md +4 -1
  278. package/components/forms/input-base.js +2 -1
  279. package/components/index.d.ts +2 -2
  280. package/components/index.js +5 -5
  281. package/components/navigation/Breadcrumb.jsx +3 -3
  282. package/components/navigation/Breadcrumb.vue +4 -4
  283. package/components/navigation/DocTree.jsx +3 -3
  284. package/components/navigation/DocTree.vue +3 -3
  285. package/components/navigation/PageHeading.vue +1 -1
  286. package/components/navigation/PageToolbar.vue +7 -6
  287. package/components/navigation/Sidebar.jsx +1 -1
  288. package/components/navigation/Sidebar.vue +3 -3
  289. package/components/navigation/ThemeToggle.vue +2 -1
  290. package/components/navigation/TopBar.d.ts +3 -1
  291. package/components/navigation/TopBar.jsx +6 -6
  292. package/components/navigation/Tree.jsx +5 -5
  293. package/components/navigation/Tree.vue +5 -5
  294. package/components/pipeline/ConfigPanel.d.ts +2 -0
  295. package/components/pipeline/ConfigPanel.jsx +9 -9
  296. package/components/pipeline/ConfigPanel.vue +9 -9
  297. package/components/pipeline/DataPreview.d.ts +3 -1
  298. package/components/pipeline/DataPreview.jsx +7 -7
  299. package/components/pipeline/DataPreview.vue +7 -7
  300. package/components/pipeline/NodeCard.jsx +3 -3
  301. package/components/pipeline/NodeCard.vue +3 -3
  302. package/components/pipeline/PaletteItem.vue +2 -2
  303. package/components/pipeline/PaletteListItem.vue +1 -1
  304. package/components/pipeline/PalettePanel.jsx +2 -2
  305. package/components/pipeline/PalettePanel.vue +1 -1
  306. package/components/pipeline/Port.vue +15 -0
  307. package/components/pipeline/PortLabel.vue +15 -0
  308. package/components/pipeline/ZoomControls.vue +1 -1
  309. package/components/pipeline/port-skin.js +13 -0
  310. package/components/px.js +4 -0
  311. package/components/tabs/PanelTabs.vue +5 -4
  312. package/components/tabs/PillTabs.vue +5 -4
  313. package/components/tabs/SegmentedControl.d.ts +11 -4
  314. package/components/tabs/SegmentedControl.jsx +25 -10
  315. package/components/tabs/SegmentedControl.vue +19 -7
  316. package/components/tabs/Tabs.d.ts +5 -3
  317. package/components/tabs/Tabs.jsx +15 -15
  318. package/components/tabs/Tabs.vue +5 -5
  319. package/components/tabs/ViewToggle.d.ts +3 -1
  320. package/components/tabs/ViewToggle.jsx +5 -5
  321. package/components/tabs/ViewToggle.vue +5 -5
  322. package/components/tabs/tabs.prompt.md +4 -2
  323. package/keybricks.css +4 -0
  324. package/package.json +6 -3
  325. package/readme.md +90 -123
  326. package/readme.zh-CN.md +120 -0
  327. package/tokens/charts.css +20 -17
  328. package/tokens/colors.css +2 -2
  329. package/tokens/dark.css +2 -2
  330. package/tokens/hover.css +32 -0
  331. package/tokens/interaction.css +3 -1
  332. package/tokens/layout.css +29 -7
  333. package/tokens/tokens.d.ts +36 -0
  334. package/tokens/tokens.js +143 -89
  335. package/tokens/tokens.json +240 -106
  336. package/tokens/typography.css +27 -14
  337. package/components/_cards/brand-visual.card.html +0 -43
  338. package/components/_cards/datatable.card.html +0 -48
  339. package/components/_shared/src-brand.js +0 -1
  340. package/components/brand/BrandVisual.d.ts +0 -17
  341. package/components/brand/BrandVisual.jsx +0 -29
  342. package/components/brand/BrandVisual.vue +0 -34
  343. package/components/brand/_dir.card.html +0 -13
  344. package/components/brand/brandMotifs.js +0 -210
  345. package/components/buttons/_dir.card.html +0 -13
  346. package/components/charts/ChartKit.vue +0 -47
  347. package/components/charts/_dir.card.html +0 -7
  348. package/components/data/DataTable.d.ts +0 -28
  349. package/components/data/DataTable.jsx +0 -32
  350. package/components/data/DataTable.vue +0 -25
  351. package/components/data/_dir.card.html +0 -13
  352. package/components/display/_dir.card.html +0 -13
  353. package/components/feedback/_dir.card.html +0 -13
  354. package/components/forms/_dir.card.html +0 -13
  355. package/components/icons/_dir.card.html +0 -13
  356. package/components/navigation/_dir.card.html +0 -13
  357. package/components/pipeline/_dir.card.html +0 -13
  358. package/components/tabs/_dir.card.html +0 -13
@@ -1 +1 @@
1
- window.__KB_SRC__=Object.assign(window.__KB_SRC__||{},{"display/Avatar.jsx":"import React from \"react\";\n\nconst palette = [\"var(--cat-join)\", \"var(--cat-source)\", \"var(--cat-transform)\", \"var(--cat-merge)\", \"var(--cat-output)\"];\nfunction initials(name = \"\") {\n const parts = name.trim().split(/\\s+/);\n if (!parts[0]) return \"?\";\n if (/[\\u4e00-\\u9fff]/.test(name)) return name.slice(-2);\n return (parts[0][0] + (parts[1] ? parts[1][0] : \"\")).toUpperCase();\n}\n\n/** Initials avatar — the source file ships no avatar imagery, so people render as tinted initials. */\nexport function Avatar({ name = \"\", src, size = 28, style }) {\n const hue = palette[[...name].reduce((a, c) => a + c.charCodeAt(0), 0) % palette.length];\n if (src) return <img src={src} alt={name} style={{ width: size, height: size, borderRadius: \"var(--radius-full)\", objectFit: \"cover\", flexShrink: 0, ...style }} />;\n return (\n <span title={name} style={{ width: size, height: size, borderRadius: \"var(--radius-full)\", background: \"var(--bg-hover)\", color: hue, display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: Math.max(9, Math.round(size * .4)), letterSpacing: 0, flexShrink: 0, boxSizing: \"border-box\", border: \"1px solid var(--border-card)\", ...style }}>\n {initials(name)}\n </span>\n );\n}\n\n/** Overlapping avatar row (assignees on a task). */\nexport function AvatarGroup({ names = [], size = 24, max = 4, style }) {\n const shown = names.slice(0, max), rest = names.length - shown.length;\n return (\n <div style={{ display: \"flex\", alignItems: \"center\", ...style }}>\n {shown.map((n, i) => <Avatar key={n + i} name={n} size={size} style={{ marginLeft: i ? -8 : 0, boxShadow: \"var(--ring-surface)\" }} />)}\n {rest > 0 ? <span style={{ marginLeft: -8, width: size, height: size, borderRadius: \"var(--radius-full)\", background: \"var(--bg-active)\", color: \"var(--text-secondary)\", boxShadow: \"var(--ring-surface)\", display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", fontFamily: \"var(--font-sans)\", fontSize: Math.max(9, Math.round(size * .38)) }}>+{rest}</span> : null}\n </div>\n );\n}\n","display/Avatar.vue":"<script setup>\nimport { computed } from 'vue'\nconst palette = ['var(--cat-join)', 'var(--cat-source)', 'var(--cat-transform)', 'var(--cat-merge)', 'var(--cat-output)']\nfunction initials(name = '') {\n const parts = name.trim().split(/\\s+/)\n if (!parts[0]) return '?'\n if (/[\\u4e00-\\u9fff]/.test(name)) return name.slice(-2)\n return (parts[0][0] + (parts[1] ? parts[1][0] : '')).toUpperCase()\n}\n// Initials avatar — the source file ships no avatar imagery, so people render as tinted initials.\nconst props = defineProps({ name: { type: String, default: '' }, src: String, size: { type: Number, default: 28 }, style: Object })\nconst hue = computed(() => palette[[...props.name].reduce((a, c) => a + c.charCodeAt(0), 0) % palette.length])\n</script>\n<template>\n <img v-if=\"src\" :src=\"src\" :alt=\"name\" :style=\"{ width: size, height: size, borderRadius: 'var(--radius-full)', objectFit: 'cover', flexShrink: 0, ...style }\" />\n <span v-else :title=\"name\" :style=\"{ width: size, height: size, borderRadius: 'var(--radius-full)', background: 'var(--bg-hover)', color: hue, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: Math.max(9, Math.round(size * .4)), letterSpacing: 0, flexShrink: 0, boxSizing: 'border-box', border: '1px solid var(--border-card)', ...style }\">{{ initials(name) }}</span>\n</template>\n","display/Badge.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\nconst badgeTones = {\n success: \"var(--success)\", danger: \"var(--danger)\", warning: \"var(--warning)\",\n pending: \"var(--dot-neutral)\", info: \"var(--action-primary)\",\n};\n\n/** Circular status badge with white ring (node error/warning dots, step icons). */\nexport function StatusBadge({ tone = \"danger\", icon, size = 20, ring = true, style }) {\n return (\n <span style={{ width: size, height: size, borderRadius: \"var(--radius-full)\", background: badgeTones[tone], boxShadow: ring ? \"var(--ring-surface)\" : \"none\", display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", color: \"var(--on-accent)\", flexShrink: 0, ...style }}>\n {icon ? <Icon name={icon} size={Math.round(size * .6)} strokeWidth={2.5} /> : null}\n </span>\n );\n}\n\n/** Inline tinted text badge (table statuses: Completed / Failed / Partial). */\nexport function Badge({ tone = \"neutral\", children, style }) {\n const tones = {\n success: { background: \"var(--success-bg)\", color: \"var(--success-text)\" },\n danger: { background: \"var(--danger-bg)\", color: \"var(--danger-text)\" },\n warning: { background: \"var(--warning-bg)\", color: \"var(--warning-text)\" },\n info: { background: \"var(--info-bg)\", color: \"var(--action-primary)\" },\n neutral: { background: \"var(--bg-hover)\", color: \"var(--text-body)\" },\n };\n return <span style={{ display: \"inline-flex\", alignItems: \"center\", height: 20, padding: \"0 8px\", borderRadius: \"var(--radius-full)\", fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-xs)\", lineHeight: 1, letterSpacing: \"var(--tracking-xs)\", ...tones[tone], ...style }}>{children}</span>;\n}\n\n/** Category icon tile — the signature tinted square. kind: palette 40/r14 · node 48/r16 · row 32/r8 (hairline). */\nexport function IconTile({ icon, hue = \"source\", kind = \"palette\", style }) {\n const dims = { palette: { box: 40, r: \"var(--radius-tile)\", icon: 18 }, node: { box: 48, r: \"var(--radius-node)\", icon: 22 }, row: { box: 32, r: \"var(--radius-md)\", icon: 16 } };\n const t = dims[kind];\n const tinted = kind !== \"row\";\n return (\n <span style={{ width: t.box, height: t.box, borderRadius: t.r, background: tinted ? `var(--cat-${hue}-bg)` : \"var(--bg-surface)\", border: tinted ? \"none\" : \"1px solid var(--border-control)\", display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", color: tinted ? `var(--cat-${hue})` : \"var(--text-body)\", flexShrink: 0, boxSizing: \"border-box\", ...style }}>\n <Icon name={icon} size={t.icon} strokeWidth={2} />\n </span>\n );\n}\n","display/Badge.vue":"<script setup>\nconst tones = {\n success: { background: 'var(--success-bg)', color: 'var(--success-text)' },\n danger: { background: 'var(--danger-bg)', color: 'var(--danger-text)' },\n warning: { background: 'var(--warning-bg)', color: 'var(--warning-text)' },\n info: { background: 'var(--info-bg)', color: 'var(--action-primary)' },\n neutral: { background: 'var(--bg-hover)', color: 'var(--text-body)' },\n}\ndefineProps({ tone: { type: String, default: 'neutral' }, style: Object })\n</script>\n<template>\n <span :style=\"{ display: 'inline-flex', alignItems: 'center', height: 20, padding: '0 8px', borderRadius: 'var(--radius-full)', fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: 'var(--text-xs)', lineHeight: 1, letterSpacing: 'var(--tracking-xs)', ...tones[tone], ...style }\"><slot /></span>\n</template>\n","display/Card.jsx":"import React from \"react\";\n\n/** White surface card — r10, hairline rgba(229,229,229,.6) border, flat. */\nexport function Card({ title, action, dense, pad = true, children, style }) {\n return (\n <div style={{ background: \"var(--bg-surface)\", borderRadius: \"var(--radius-lg)\", border: \"1px solid var(--border-card)\", boxSizing: \"border-box\", padding: pad ? (dense ? \"var(--card-pad-sm)\" : \"var(--card-pad)\") : 0, ...style }}>\n {title ? (\n <div style={{ display: \"flex\", justifyContent: \"space-between\", alignItems: \"center\", marginBottom: 16 }}>\n <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-base)\", lineHeight: \"var(--leading-base)\", letterSpacing: \"var(--tracking-base)\", color: \"var(--text-heading)\" }}>{title}</span>\n {action}\n </div>\n ) : null}\n {children}\n </div>\n );\n}\n\n/** Dashboard stat card: muted 13px label + light 24px number + optional chart slot. */\nexport function StatCard({ label, value, dense, children, style }) {\n return (\n <Card dense={dense} style={{ display: \"flex\", flexDirection: \"column\", gap: 12, ...style }}>\n <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-base)\", lineHeight: \"var(--leading-base)\", letterSpacing: \"var(--tracking-base)\", color: \"var(--text-secondary)\" }}>{label}</span>\n {children}\n {value != null ? <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-light)\", fontSize: \"var(--text-num)\", lineHeight: \"var(--leading-num)\", letterSpacing: \"var(--tracking-num)\", color: \"var(--text-primary)\" }}>{value}</span> : null}\n </Card>\n );\n}\n","display/Card.vue":"<script setup>\n// White surface card — r10, hairline rgba(229,229,229,.6) border, flat.\ndefineProps({ title: String, dense: Boolean, pad: { type: Boolean, default: true }, style: Object })\n</script>\n<template>\n <div :style=\"{ background: 'var(--bg-surface)', borderRadius: 'var(--radius-lg)', border: '1px solid var(--border-card)', boxSizing: 'border-box', padding: pad ? (dense ? 'var(--card-pad-sm)' : 'var(--card-pad)') : 0, ...style }\">\n <div v-if=\"title\" style=\"display:flex;justify-content:space-between;align-items:center;margin-bottom:16px\">\n <span style=\"font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);line-height:var(--leading-base);letter-spacing:var(--tracking-base);color:var(--text-heading)\">{{ title }}</span>\n <slot name=\"action\" />\n </div>\n <slot />\n </div>\n</template>\n","display/Divider.jsx":"import React from \"react\";\n\n/** Hairline rule. Pass `label` for a centered caption (\"OR\", \"Advanced Settings\"); `vertical` for a column rule. */\nexport function Divider({ label, vertical, style }) {\n if (vertical) return <span style={{ width: 1, alignSelf: \"stretch\", background: \"var(--border-subtle)\", ...style }} />;\n if (!label) return <div style={{ height: 1, background: \"var(--border-subtle)\", ...style }} />;\n return (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 12, ...style }}>\n <div style={{ flex: 1, height: 1, background: \"var(--border-subtle)\" }} />\n <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-xs)\", letterSpacing: \"var(--tracking-xs)\", color: \"var(--text-muted)\", whiteSpace: \"nowrap\" }}>{label}</span>\n <div style={{ flex: 1, height: 1, background: \"var(--border-subtle)\" }} />\n </div>\n );\n}\n","display/Divider.vue":"<script setup>\n// Hairline rule. Pass `label` for a centered caption (\"OR\", \"Advanced Settings\"); `vertical` for a column rule.\ndefineProps({ label: String, vertical: Boolean, style: Object })\n</script>\n<template>\n <span v-if=\"vertical\" :style=\"{ width: 1, alignSelf: 'stretch', background: 'var(--border-subtle)', ...style }\" />\n <div v-else-if=\"!label\" :style=\"{ height: 1, background: 'var(--border-subtle)', ...style }\" />\n <div v-else :style=\"{ display: 'flex', alignItems: 'center', gap: 12, ...style }\">\n <div style=\"flex:1;height:1px;background:var(--border-subtle)\" />\n <span style=\"font-family:var(--font-sans);font-size:var(--text-xs);letter-spacing:var(--tracking-xs);color:var(--text-muted);white-space:nowrap\">{{ label }}</span>\n <div style=\"flex:1;height:1px;background:var(--border-subtle)\" />\n </div>\n</template>\n","display/Kbd.jsx":"import React from \"react\";\n\n/** Keyboard shortcut hint, e.g. <Kbd keys={[\"⌘\",\"K\"]}/>. */\nexport function Kbd({ keys = [], style }) {\n return (\n <span style={{ display: \"inline-flex\", gap: 3, ...style }}>\n {keys.map((k, i) => (\n <span key={i} style={{ display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", minWidth: 20, height: 20, padding: \"0 5px\", borderRadius: \"var(--radius-sm)\", background: \"var(--bg-inset)\", border: \"1px solid var(--border-control)\", boxSizing: \"border-box\", fontFamily: \"var(--font-mono)\", fontSize: \"var(--text-xs)\", color: \"var(--text-secondary)\" }}>{k}</span>\n ))}\n </span>\n );\n}\n","display/Kbd.vue":"<script setup>\n// Keyboard shortcut hint, e.g. <Kbd :keys=\"['⌘','K']\"/>.\ndefineProps({ keys: { type: Array, default: () => [] }, style: Object })\n</script>\n<template>\n <span :style=\"{ display: 'inline-flex', gap: 3, ...style }\">\n <span v-for=\"(k, i) in keys\" :key=\"i\" style=\"display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 5px;border-radius:var(--radius-sm);background:var(--bg-inset);border:1px solid var(--border-control);box-sizing:border-box;font-family:var(--font-mono);font-size:var(--text-xs);color:var(--text-secondary)\">{{ k }}</span>\n </span>\n</template>\n","display/ProgressBar.jsx":"import React from \"react\";\n\n/** Labeled horizontal progress bar (Expense Distribution rows). */\nexport function ProgressBar({ label, value = 50, color = \"var(--chart-blue)\", showValue = true, style }) {\n return (\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: 4, ...style }}>\n {label != null ? (\n <div style={{ display: \"flex\", justifyContent: \"space-between\", fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-base)\", lineHeight: \"var(--leading-base)\", letterSpacing: \"var(--tracking-base)\" }}>\n <span style={{ color: \"var(--text-body)\" }}>{label}</span>\n {showValue ? <span style={{ color: \"var(--text-heading)\" }}>{value}%</span> : null}\n </div>\n ) : null}\n <div style={{ height: 6, borderRadius: \"var(--radius-full)\", background: \"var(--track)\", overflow: \"hidden\" }}>\n <div style={{ width: `${value}%`, height: \"100%\", borderRadius: \"var(--radius-full)\", background: color, transition: \"width .3s ease\" }} />\n </div>\n </div>\n );\n}\n\n/** Ring/donut chart. segments: [{value, color}]; centered light numeral. */\nexport function Donut({ size = 128, thickness = 7.68, segments, value = 75, label, style }) {\n const r = (size - thickness) / 2 - (size * .1 - thickness / 2 > 0 ? size * .1 - thickness / 2 : 0);\n const R = size / 2 - thickness / 2 - size * .02;\n const C = 2 * Math.PI * R;\n const segs = segments || [{ value, color: \"var(--chart-blue)\" }];\n let offset = 0;\n return (\n <div style={{ position: \"relative\", width: size, height: size, ...style }}>\n <svg width={size} height={size} style={{ transform: \"rotate(-90deg)\" }}>\n <circle cx={size / 2} cy={size / 2} r={R} fill=\"none\" stroke=\"var(--track)\" strokeWidth={thickness} />\n {segs.map((s, i) => {\n const len = (s.value / 100) * C;\n const el = <circle key={i} cx={size / 2} cy={size / 2} r={R} fill=\"none\" stroke={s.color} strokeWidth={thickness} strokeDasharray={`${len} ${C - len}`} strokeDashoffset={-offset} strokeLinecap=\"butt\" />;\n offset += len;\n return el;\n })}\n </svg>\n <div style={{ position: \"absolute\", inset: 0, display: \"flex\", alignItems: \"center\", justifyContent: \"center\", fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-light)\", fontSize: 20, letterSpacing: \"var(--tracking-xl)\", color: \"var(--text-primary)\" }}>{label != null ? label : `${value}%`}</div>\n </div>\n );\n}\n\n/** Area line chart (Revenue Trend): 1.22px blue line + fading gradient fill. points: 0–100 values. */\nexport function AreaChart({ points = [65, 75, 60, 85, 70, 90, 85, 95, 100], width = 350, height = 128, color = \"var(--chart-blue)\", style }) {\n const step = width / (points.length - 1);\n const ys = points.map((p) => height - (p / 100) * height * .48 - height * .5);\n const line = ys.map((y, i) => `${i ? \"L\" : \"M\"} ${i * step} ${y}`).join(\" \");\n const id = React.useId ? React.useId().replace(/:/g, \"\") : \"g\" + Math.round(Math.random() * 1e6);\n return (\n <svg width={width} height={height} viewBox={`0 0 ${width} ${height}`} style={{ display: \"block\", ...style }}>\n <defs><linearGradient id={id} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\"><stop offset=\"0\" stopColor={color} stopOpacity=\".3\" /><stop offset=\"1\" stopColor={color} stopOpacity=\"0\" /></linearGradient></defs>\n <path d={`${line} L ${width} ${height} L 0 ${height} Z`} fill={`url(#${id})`} />\n <path d={line} fill=\"none\" stroke={color} strokeWidth=\"1.22\" />\n </svg>\n );\n}\n\n/** Mini bar chart used in stat cards. bars: 0–100 values. */\nexport function MiniBars({ bars = [40, 65, 50, 80, 60, 90, 70], width = 248, height = 96, color = \"var(--chart-blue)\", style }) {\n const gap = 6;\n const bw = (width - gap * (bars.length - 1)) / bars.length;\n return (\n <svg width={width} height={height} viewBox={`0 0 ${width} ${height}`} style={{ display: \"block\", ...style }}>\n {bars.map((b, i) => {\n const h = (b / 100) * height;\n return <rect key={i} x={i * (bw + gap)} y={height - h} width={bw} height={h} rx={2} fill={color} opacity={.85} />;\n })}\n </svg>\n );\n}\n","display/ProgressBar.vue":"<script setup>\n// Labeled horizontal progress bar (Expense Distribution rows).\ndefineProps({ label: String, value: { type: Number, default: 50 }, color: { type: String, default: 'var(--chart-blue)' }, showValue: { type: Boolean, default: true }, style: Object })\n</script>\n<template>\n <div :style=\"{ display: 'flex', flexDirection: 'column', gap: 4, ...style }\">\n <div v-if=\"label != null\" style=\"display:flex;justify-content:space-between;font-family:var(--font-sans);font-size:var(--text-base);line-height:var(--leading-base);letter-spacing:var(--tracking-base)\">\n <span style=\"color:var(--text-body)\">{{ label }}</span>\n <span v-if=\"showValue\" style=\"color:var(--text-heading)\">{{ value }}%</span>\n </div>\n <div style=\"height:6px;border-radius:var(--radius-full);background:var(--track);overflow:hidden\">\n <div :style=\"{ width: value + '%', height: '100%', borderRadius: 'var(--radius-full)', background: color, transition: 'width .3s ease' }\" />\n </div>\n </div>\n</template>\n","display/Tag.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\nconst tones = { neutral: { bg: \"var(--bg-hover)\", fg: \"var(--text-body)\" }, info: { bg: \"var(--info-bg)\", fg: \"var(--info-fg)\" } };\n\n/** Small removable filter tag — distinct from Badge (status) and ActionChip (bulk action). */\nexport function Tag({ children, onRemove, tone = \"neutral\", style }) {\n const t = tones[tone] || tones.neutral;\n return (\n <span style={{ display: \"inline-flex\", alignItems: \"center\", gap: 4, height: 22, padding: onRemove ? \"0 6px 0 10px\" : \"0 10px\", borderRadius: \"var(--radius-full)\", background: t.bg, color: t.fg, fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-xs)\", fontWeight: \"var(--weight-medium)\", letterSpacing: \"var(--tracking-xs)\", ...style }}>\n {children}\n {onRemove ? <button type=\"button\" onClick={onRemove} style={{ border: \"none\", background: \"transparent\", cursor: \"pointer\", color: \"inherit\", opacity: .7, display: \"flex\", padding: 0 }}><Icon name=\"x\" size={11} /></button> : null}\n </span>\n );\n}\n","display/Tag.vue":"<script setup>\nimport { computed } from 'vue'\nimport Icon from '../icons/Icon.vue'\nconst tones = { neutral: { bg: 'var(--bg-hover)', fg: 'var(--text-body)' }, info: { bg: 'var(--info-bg)', fg: 'var(--info-fg)' } }\n// Small removable filter tag — distinct from Badge (status) and ActionChip (bulk action).\nconst props = defineProps({ tone: { type: String, default: 'neutral' }, removable: Boolean, style: Object })\ndefineEmits(['remove'])\nconst t = computed(() => tones[props.tone] || tones.neutral)\n</script>\n<template>\n <span :style=\"{ display: 'inline-flex', alignItems: 'center', gap: 4, height: 22, padding: removable ? '0 6px 0 10px' : '0 10px', borderRadius: 'var(--radius-full)', background: t.bg, color: t.fg, fontFamily: 'var(--font-sans)', fontSize: 'var(--text-xs)', fontWeight: 'var(--weight-medium)', letterSpacing: 'var(--tracking-xs)', ...style }\">\n <slot />\n <button v-if=\"removable\" type=\"button\" @click=\"$emit('remove')\" style=\"border:none;background:transparent;cursor:pointer;color:inherit;opacity:.7;display:flex;padding:0\"><Icon name=\"x\" :size=\"11\" /></button>\n </span>\n</template>\n","display/AreaChart.vue":"<script setup>\nimport { computed } from 'vue'\nconst props = defineProps({ points: { type: Array, default: () => [65, 75, 60, 85, 70, 90, 85, 95, 100] }, width: { type: Number, default: 350 }, height: { type: Number, default: 128 }, color: { type: String, default: 'var(--chart-blue)' }, style: Object })\nconst gid = 'g' + Math.round(Math.random() * 1e6)\nconst step = computed(() => props.width / (props.points.length - 1))\nconst line = computed(() => props.points.map((p, i) => `${i ? 'L' : 'M'} ${i * step.value} ${props.height - (p / 100) * props.height * .48 - props.height * .5}`).join(' '))\n</script>\n<template>\n <svg :width=\"width\" :height=\"height\" :viewBox=\"`0 0 ${width} ${height}`\" :style=\"{ display: 'block', ...style }\">\n <defs><linearGradient :id=\"gid\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\"><stop offset=\"0\" :stop-color=\"color\" stop-opacity=\".3\" /><stop offset=\"1\" :stop-color=\"color\" stop-opacity=\"0\" /></linearGradient></defs>\n <path :d=\"`${line} L ${width} ${height} L 0 ${height} Z`\" :fill=\"`url(#${gid})`\" />\n <path :d=\"line\" fill=\"none\" :stroke=\"color\" stroke-width=\"1.22\" />\n </svg>\n</template>\n","display/AvatarGroup.vue":"<script setup>\nimport { computed } from 'vue'\nimport Avatar from './Avatar.vue'\nconst props = defineProps({ names: { type: Array, default: () => [] }, size: { type: Number, default: 24 }, max: { type: Number, default: 4 }, style: Object })\nconst shown = computed(() => props.names.slice(0, props.max))\nconst rest = computed(() => props.names.length - shown.value.length)\n</script>\n<template>\n <div :style=\"{ display: 'flex', alignItems: 'center', ...style }\">\n <Avatar v-for=\"(n, i) in shown\" :key=\"n + i\" :name=\"n\" :size=\"size\" :style=\"{ marginLeft: i ? -8 : 0, boxShadow: 'var(--ring-surface)' }\" />\n <span v-if=\"rest > 0\" :style=\"{ marginLeft: -8, width: size, height: size, borderRadius: 'var(--radius-full)', background: 'var(--bg-active)', color: 'var(--text-secondary)', boxShadow: 'var(--ring-surface)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontFamily: 'var(--font-sans)', fontSize: Math.max(9, Math.round(size * .38)) }\">+{{ rest }}</span>\n </div>\n</template>\n","display/Donut.vue":"<script setup>\nimport { computed } from 'vue'\nconst props = defineProps({ size: { type: Number, default: 128 }, thickness: { type: Number, default: 7.68 }, segments: Array, value: { type: Number, default: 75 }, label: [String, Number], style: Object })\nconst R = computed(() => props.size / 2 - props.thickness / 2 - props.size * .02)\nconst C = computed(() => 2 * Math.PI * R.value)\nconst segs = computed(() => props.segments || [{ value: props.value, color: 'var(--chart-blue)' }])\nconst arcs = computed(() => { let offset = 0; return segs.value.map(s => { const len = (s.value / 100) * C.value; const a = { ...s, len, offset }; offset += len; return a }) })\n</script>\n<template>\n <div :style=\"{ position: 'relative', width: size, height: size, ...style }\">\n <svg :width=\"size\" :height=\"size\" style=\"transform:rotate(-90deg)\">\n <circle :cx=\"size / 2\" :cy=\"size / 2\" :r=\"R\" fill=\"none\" stroke=\"var(--track)\" :stroke-width=\"thickness\" />\n <circle v-for=\"(s, i) in arcs\" :key=\"i\" :cx=\"size / 2\" :cy=\"size / 2\" :r=\"R\" fill=\"none\" :stroke=\"s.color\" :stroke-width=\"thickness\" :stroke-dasharray=\"`${s.len} ${C - s.len}`\" :stroke-dashoffset=\"-s.offset\" stroke-linecap=\"butt\" />\n </svg>\n <div style=\"position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-family:var(--font-sans);font-weight:var(--weight-light);font-size:20px;letter-spacing:var(--tracking-xl);color:var(--text-primary)\">{{ label != null ? label : `${value}%` }}</div>\n </div>\n</template>\n","display/IconTile.vue":"<script setup>\nimport { computed } from 'vue'\nimport Icon from '../icons/Icon.vue'\nconst dims = { palette: { box: 40, r: 'var(--radius-tile)', icon: 18 }, node: { box: 48, r: 'var(--radius-node)', icon: 22 }, row: { box: 32, r: 'var(--radius-md)', icon: 16 } }\nconst props = defineProps({ icon: String, hue: { type: String, default: 'source' }, kind: { type: String, default: 'palette' }, style: Object })\nconst t = computed(() => dims[props.kind])\nconst tinted = computed(() => props.kind !== 'row')\n</script>\n<template>\n <span :style=\"{ width: t.box, height: t.box, borderRadius: t.r, background: tinted ? `var(--cat-${hue}-bg)` : 'var(--bg-surface)', border: tinted ? 'none' : '1px solid var(--border-control)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', color: tinted ? `var(--cat-${hue})` : 'var(--text-body)', flexShrink: 0, boxSizing: 'border-box', ...style }\">\n <Icon :name=\"icon\" :size=\"t.icon\" :stroke-width=\"2\" />\n </span>\n</template>\n","display/MiniBars.vue":"<script setup>\nimport { computed } from 'vue'\nconst props = defineProps({ bars: { type: Array, default: () => [40, 65, 50, 80, 60, 90, 70] }, width: { type: Number, default: 248 }, height: { type: Number, default: 96 }, color: { type: String, default: 'var(--chart-blue)' }, style: Object })\nconst gap = 6\nconst bw = computed(() => (props.width - gap * (props.bars.length - 1)) / props.bars.length)\n</script>\n<template>\n <svg :width=\"width\" :height=\"height\" :viewBox=\"`0 0 ${width} ${height}`\" :style=\"{ display: 'block', ...style }\">\n <rect v-for=\"(b, i) in bars\" :key=\"i\" :x=\"i * (bw + gap)\" :y=\"height - (b / 100) * height\" :width=\"bw\" :height=\"(b / 100) * height\" rx=\"2\" :fill=\"color\" opacity=\".85\" />\n </svg>\n</template>\n","display/StatCard.vue":"<script setup>\nimport Card from './Card.vue'\ndefineProps({ label: String, value: [String, Number], dense: Boolean, style: Object })\n</script>\n<template>\n <Card :dense=\"dense\" :style=\"{ display: 'flex', flexDirection: 'column', gap: 12, ...style }\">\n <span style=\"font-family:var(--font-sans);font-size:var(--text-base);line-height:var(--leading-base);letter-spacing:var(--tracking-base);color:var(--text-secondary)\">{{ label }}</span>\n <slot />\n <span v-if=\"value != null\" style=\"font-family:var(--font-sans);font-weight:var(--weight-light);font-size:var(--text-num);line-height:var(--leading-num);letter-spacing:var(--tracking-num);color:var(--text-primary)\">{{ value }}</span>\n </Card>\n</template>\n","display/StatusBadge.vue":"<script setup>\nimport Icon from '../icons/Icon.vue'\n// Circular status badge with white ring (node error/warning dots, step icons).\nconst badgeTones = { success: 'var(--success)', danger: 'var(--danger)', warning: 'var(--warning)', pending: 'var(--dot-neutral)', info: 'var(--action-primary)' }\nconst props = defineProps({ tone: { type: String, default: 'danger' }, icon: String, size: { type: Number, default: 20 }, ring: { type: Boolean, default: true }, style: Object })\n</script>\n<template>\n <span :style=\"{ width: size + 'px', height: size + 'px', borderRadius: 'var(--radius-full)', background: badgeTones[tone], boxShadow: ring ? 'var(--ring-surface)' : 'none', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', color: 'var(--on-accent)', flexShrink: 0, ...style }\">\n <Icon v-if=\"icon\" :name=\"icon\" :size=\"Math.round(size * .6)\" :stroke-width=\"2.5\" />\n </span>\n</template>\n"});
1
+ window.__KB_SRC__=Object.assign(window.__KB_SRC__||{},{"display/Avatar.jsx":"import React from \"react\";\n\nconst palette = [\"var(--cat-join)\", \"var(--cat-source)\", \"var(--cat-transform)\", \"var(--cat-merge)\", \"var(--cat-output)\"];\nfunction initials(name = \"\") {\n const parts = name.trim().split(/\\s+/);\n if (!parts[0]) return \"?\";\n if (/[\\u4e00-\\u9fff]/.test(name)) return name.slice(-2);\n return (parts[0][0] + (parts[1] ? parts[1][0] : \"\")).toUpperCase();\n}\n\n/** Initials avatar — the source file ships no avatar imagery, so people render as tinted initials. */\nexport function Avatar({ name = \"\", src, size = 28, style }) {\n const hue = palette[[...name].reduce((a, c) => a + c.charCodeAt(0), 0) % palette.length];\n if (src) return <img src={src} alt={name} style={{ width: size, height: size, borderRadius: \"var(--radius-full)\", objectFit: \"cover\", flexShrink: 0, ...style }} />;\n return (\n <span title={name} style={{ width: size, height: size, borderRadius: \"var(--radius-full)\", background: \"var(--bg-hover)\", color: hue, display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: Math.max(9, Math.round(size * .4)), letterSpacing: 0, flexShrink: 0, boxSizing: \"border-box\", border: \"1px solid var(--border-card)\", ...style }}>\n {initials(name)}\n </span>\n );\n}\n\n/** Overlapping avatar row (assignees on a task). */\nexport function AvatarGroup({ names = [], size = 24, max = 4, style }) {\n const shown = names.slice(0, max), rest = names.length - shown.length;\n return (\n <div style={{ display: \"flex\", alignItems: \"center\", ...style }}>\n {shown.map((n, i) => <Avatar key={n + i} name={n} size={size} style={{ marginLeft: i ? -8 : 0, boxShadow: \"var(--ring-surface)\" }} />)}\n {rest > 0 ? <span style={{ marginLeft: -8, width: size, height: size, borderRadius: \"var(--radius-full)\", background: \"var(--bg-active)\", color: \"var(--text-secondary)\", boxShadow: \"var(--ring-surface)\", display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", fontFamily: \"var(--font-sans)\", fontSize: Math.max(9, Math.round(size * .38)) }}>+{rest}</span> : null}\n </div>\n );\n}\n","display/Avatar.vue":"<script setup>\nimport { px } from '../px.js'\nimport { computed } from 'vue'\nconst palette = ['var(--cat-join)', 'var(--cat-source)', 'var(--cat-transform)', 'var(--cat-merge)', 'var(--cat-output)']\nfunction initials(name = '') {\n const parts = name.trim().split(/\\s+/)\n if (!parts[0]) return '?'\n if (/[\\u4e00-\\u9fff]/.test(name)) return name.slice(-2)\n return (parts[0][0] + (parts[1] ? parts[1][0] : '')).toUpperCase()\n}\n// Initials avatar — the source file ships no avatar imagery, so people render as tinted initials.\nconst props = defineProps({ name: { type: String, default: '' }, src: String, size: { type: Number, default: 28 }, style: Object })\nconst hue = computed(() => palette[[...props.name].reduce((a, c) => a + c.charCodeAt(0), 0) % palette.length])\n</script>\n<template>\n <img v-if=\"src\" :src=\"src\" :alt=\"name\" :style=\"{ width: px(size), height: px(size), borderRadius: 'var(--radius-full)', objectFit: 'cover', flexShrink: 0, ...style }\" />\n <span v-else :title=\"name\" :style=\"{ width: px(size), height: px(size), borderRadius: 'var(--radius-full)', background: 'var(--bg-hover)', color: hue, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: px(Math.max(9, Math.round(size * .4))), letterSpacing: 0, flexShrink: 0, boxSizing: 'border-box', border: '1px solid var(--border-card)', ...style }\">{{ initials(name) }}</span>\n</template>\n","display/Badge.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\nconst badgeTones = {\n success: \"var(--success)\", danger: \"var(--danger)\", warning: \"var(--warning)\",\n pending: \"var(--dot-neutral)\", info: \"var(--action-primary)\",\n};\n\n/** Circular status badge with white ring (node error/warning dots, step icons). */\nexport function StatusBadge({ tone = \"danger\", icon, size = 20, ring = true, style }) {\n return (\n <span style={{ width: size, height: size, borderRadius: \"var(--radius-full)\", background: badgeTones[tone], boxShadow: ring ? \"var(--ring-surface)\" : \"none\", display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", color: \"var(--on-accent)\", flexShrink: 0, ...style }}>\n {icon ? <Icon name={icon} size={Math.round(size * .6)} strokeWidth={2.5} /> : null}\n </span>\n );\n}\n\n/** Inline tinted text badge (table statuses: Completed / Failed / Partial). */\nexport function Badge({ tone = \"neutral\", children, style }) {\n const tones = {\n success: { background: \"var(--success-bg)\", color: \"var(--success-text)\" },\n danger: { background: \"var(--danger-bg)\", color: \"var(--danger-text)\" },\n warning: { background: \"var(--warning-bg)\", color: \"var(--warning-text)\" },\n info: { background: \"var(--info-bg)\", color: \"var(--action-primary)\" },\n neutral: { background: \"var(--bg-hover)\", color: \"var(--text-body)\" },\n };\n return <span style={{ display: \"inline-flex\", alignItems: \"center\", height: 20, padding: \"0 8px\", borderRadius: \"var(--radius-full)\", fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-xs)\", lineHeight: 1, letterSpacing: \"var(--tracking-xs)\", ...tones[tone], ...style }}>{children}</span>;\n}\n\n/** Category icon tile — the signature tinted square. kind: palette 40/r14 · node 48/r16 · row 32/r8 (hairline). */\nexport function IconTile({ icon, hue = \"source\", kind = \"palette\", style }) {\n const dims = { palette: { box: 40, r: \"var(--radius-tile)\", icon: 18 }, node: { box: 48, r: \"var(--radius-node)\", icon: 22 }, row: { box: 32, r: \"var(--radius-md)\", icon: 16 } };\n const t = dims[kind];\n const tinted = kind !== \"row\";\n return (\n <span style={{ width: t.box, height: t.box, borderRadius: t.r, background: tinted ? `var(--cat-${hue}-bg)` : \"var(--bg-surface)\", border: tinted ? \"none\" : \"1px solid var(--border-control)\", display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", color: tinted ? `var(--cat-${hue})` : \"var(--text-body)\", flexShrink: 0, boxSizing: \"border-box\", ...style }}>\n <Icon name={icon} size={t.icon} strokeWidth={2} />\n </span>\n );\n}\n","display/Badge.vue":"<script setup>\nconst tones = {\n success: { background: 'var(--success-bg)', color: 'var(--success-text)' },\n danger: { background: 'var(--danger-bg)', color: 'var(--danger-text)' },\n warning: { background: 'var(--warning-bg)', color: 'var(--warning-text)' },\n info: { background: 'var(--info-bg)', color: 'var(--action-primary)' },\n neutral: { background: 'var(--bg-hover)', color: 'var(--text-body)' },\n}\ndefineProps({ tone: { type: String, default: 'neutral' }, style: Object })\n</script>\n<template>\n <span :style=\"{ display: 'inline-flex', alignItems: 'center', height: '20px', padding: '0 8px', borderRadius: 'var(--radius-full)', fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: 'var(--text-xs)', lineHeight: 1, letterSpacing: 'var(--tracking-xs)', ...tones[tone], ...style }\"><slot /></span>\n</template>\n","display/Card.jsx":"import React from \"react\";\n\n/** White surface card — r10, hairline rgba(229,229,229,.6) border, flat. */\nexport function Card({ title, action, dense, pad = true, children, style }) {\n return (\n <div style={{ background: \"var(--bg-surface)\", borderRadius: \"var(--radius-lg)\", border: \"1px solid var(--border-card)\", boxSizing: \"border-box\", padding: pad ? (dense ? \"var(--card-pad-sm)\" : \"var(--card-pad)\") : 0, ...style }}>\n {title ? (\n <div style={{ display: \"flex\", justifyContent: \"space-between\", alignItems: \"center\", marginBottom: 16 }}>\n <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-base)\", lineHeight: \"var(--leading-base)\", letterSpacing: \"var(--tracking-base)\", color: \"var(--text-heading)\" }}>{title}</span>\n {action}\n </div>\n ) : null}\n {children}\n </div>\n );\n}\n\n/** Dashboard stat card: muted 13px label + light 24px number + optional chart slot. */\nexport function StatCard({ label, value, dense, children, style }) {\n return (\n <Card dense={dense} style={{ display: \"flex\", flexDirection: \"column\", gap: 12, ...style }}>\n <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-base)\", lineHeight: \"var(--leading-base)\", letterSpacing: \"var(--tracking-base)\", color: \"var(--text-secondary)\" }}>{label}</span>\n {children}\n {value != null ? <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-light)\", fontSize: \"var(--text-num)\", lineHeight: \"var(--leading-num)\", letterSpacing: \"var(--tracking-num)\", color: \"var(--text-primary)\" }}>{value}</span> : null}\n </Card>\n );\n}\n","display/Card.vue":"<script setup>\n// White surface card — r10, hairline rgba(229,229,229,.6) border, flat.\ndefineProps({ title: String, dense: Boolean, pad: { type: Boolean, default: true }, style: Object })\n</script>\n<template>\n <div :style=\"{ background: 'var(--bg-surface)', borderRadius: 'var(--radius-lg)', border: '1px solid var(--border-card)', boxSizing: 'border-box', padding: pad ? (dense ? 'var(--card-pad-sm)' : 'var(--card-pad)') : 0, ...style }\">\n <div v-if=\"title\" style=\"display:flex;justify-content:space-between;align-items:center;margin-bottom:16px\">\n <span style=\"font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);line-height:var(--leading-base);letter-spacing:var(--tracking-base);color:var(--text-heading)\">{{ title }}</span>\n <slot name=\"action\" />\n </div>\n <slot />\n </div>\n</template>\n","display/Divider.jsx":"import React from \"react\";\n\n/** Hairline rule. Pass `label` for a centered caption (\"OR\", \"Advanced Settings\"); `vertical` for a column rule. */\nexport function Divider({ label, vertical, style }) {\n if (vertical) return <span style={{ width: 1, alignSelf: \"stretch\", background: \"var(--border-subtle)\", ...style }} />;\n if (!label) return <div style={{ height: 1, background: \"var(--border-subtle)\", ...style }} />;\n return (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 12, ...style }}>\n <div style={{ flex: 1, height: 1, background: \"var(--border-subtle)\" }} />\n <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-xs)\", letterSpacing: \"var(--tracking-xs)\", color: \"var(--text-muted)\", whiteSpace: \"nowrap\" }}>{label}</span>\n <div style={{ flex: 1, height: 1, background: \"var(--border-subtle)\" }} />\n </div>\n );\n}\n","display/Divider.vue":"<script setup>\n// Hairline rule. Pass `label` for a centered caption (\"OR\", \"Advanced Settings\"); `vertical` for a column rule.\ndefineProps({ label: String, vertical: Boolean, style: Object })\n</script>\n<template>\n <span v-if=\"vertical\" :style=\"{ width: '1px', alignSelf: 'stretch', background: 'var(--border-subtle)', ...style }\" />\n <div v-else-if=\"!label\" :style=\"{ height: '1px', background: 'var(--border-subtle)', ...style }\" />\n <div v-else :style=\"{ display: 'flex', alignItems: 'center', gap: '12px', ...style }\">\n <div style=\"flex:1;height:1px;background:var(--border-subtle)\" />\n <span style=\"font-family:var(--font-sans);font-size:var(--text-xs);letter-spacing:var(--tracking-xs);color:var(--text-muted);white-space:nowrap\">{{ label }}</span>\n <div style=\"flex:1;height:1px;background:var(--border-subtle)\" />\n </div>\n</template>\n","display/Kbd.jsx":"import React from \"react\";\n\n/** Keyboard shortcut hint, e.g. <Kbd keys={[\"⌘\",\"K\"]}/>. */\nexport function Kbd({ keys = [], style }) {\n return (\n <span style={{ display: \"inline-flex\", gap: 3, ...style }}>\n {keys.map((k, i) => (\n <span key={i} style={{ display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", minWidth: 20, height: 20, padding: \"0 5px\", borderRadius: \"var(--radius-sm)\", background: \"var(--bg-inset)\", border: \"1px solid var(--border-control)\", boxSizing: \"border-box\", fontFamily: \"var(--font-mono)\", fontSize: \"var(--text-xs)\", color: \"var(--text-secondary)\" }}>{k}</span>\n ))}\n </span>\n );\n}\n","display/Kbd.vue":"<script setup>\n// Keyboard shortcut hint, e.g. <Kbd :keys=\"['⌘','K']\"/>.\ndefineProps({ keys: { type: Array, default: () => [] }, style: Object })\n</script>\n<template>\n <span :style=\"{ display: 'inline-flex', gap: '3px', ...style }\">\n <span v-for=\"(k, i) in keys\" :key=\"i\" style=\"display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 5px;border-radius:var(--radius-sm);background:var(--bg-inset);border:1px solid var(--border-control);box-sizing:border-box;font-family:var(--font-mono);font-size:var(--text-xs);color:var(--text-secondary)\">{{ k }}</span>\n </span>\n</template>\n","display/ProgressBar.jsx":"import React from \"react\";\n\n/** Labeled horizontal progress bar (Expense Distribution rows). */\nexport function ProgressBar({ label, value = 50, color = \"var(--chart-blue)\", showValue = true, style }) {\n return (\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: 4, ...style }}>\n {label != null ? (\n <div style={{ display: \"flex\", justifyContent: \"space-between\", fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-base)\", lineHeight: \"var(--leading-base)\", letterSpacing: \"var(--tracking-base)\" }}>\n <span style={{ color: \"var(--text-body)\" }}>{label}</span>\n {showValue ? <span style={{ color: \"var(--text-heading)\" }}>{value}%</span> : null}\n </div>\n ) : null}\n <div style={{ height: 6, borderRadius: \"var(--radius-full)\", background: \"var(--track)\", overflow: \"hidden\" }}>\n <div style={{ width: `${value}%`, height: \"100%\", borderRadius: \"var(--radius-full)\", background: color, transition: \"width .3s ease\" }} />\n </div>\n </div>\n );\n}\n\n/** Ring/donut chart. segments: [{value, color}]; centered light numeral. */\nexport function Donut({ size = 128, thickness = 7.68, segments, value = 75, label, style }) {\n const r = (size - thickness) / 2 - (size * .1 - thickness / 2 > 0 ? size * .1 - thickness / 2 : 0);\n const R = size / 2 - thickness / 2 - size * .02;\n const C = 2 * Math.PI * R;\n const segs = segments || [{ value, color: \"var(--chart-blue)\" }];\n let offset = 0;\n return (\n <div style={{ position: \"relative\", width: size, height: size, ...style }}>\n <svg width={size} height={size} style={{ transform: \"rotate(-90deg)\" }}>\n <circle cx={size / 2} cy={size / 2} r={R} fill=\"none\" stroke=\"var(--track)\" strokeWidth={thickness} />\n {segs.map((s, i) => {\n const len = (s.value / 100) * C;\n const el = <circle key={i} cx={size / 2} cy={size / 2} r={R} fill=\"none\" stroke={s.color} strokeWidth={thickness} strokeDasharray={`${len} ${C - len}`} strokeDashoffset={-offset} strokeLinecap=\"butt\" />;\n offset += len;\n return el;\n })}\n </svg>\n <div style={{ position: \"absolute\", inset: 0, display: \"flex\", alignItems: \"center\", justifyContent: \"center\", fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-light)\", fontSize: \"var(--text-lg)\", letterSpacing: \"var(--tracking-lg)\", color: \"var(--text-primary)\" }}>{label != null ? label : `${value}%`}</div>\n </div>\n );\n}\n\n/** Area line chart (Revenue Trend): 1.22px blue line + fading gradient fill. points: 0–100 values. */\nexport function AreaChart({ points = [65, 75, 60, 85, 70, 90, 85, 95, 100], width = 350, height = 128, color = \"var(--chart-blue)\", style }) {\n const step = width / (points.length - 1);\n const ys = points.map((p) => height - (p / 100) * height * .48 - height * .5);\n const line = ys.map((y, i) => `${i ? \"L\" : \"M\"} ${i * step} ${y}`).join(\" \");\n const id = React.useId ? React.useId().replace(/:/g, \"\") : \"g\" + Math.round(Math.random() * 1e6);\n return (\n <svg width={width} height={height} viewBox={`0 0 ${width} ${height}`} style={{ display: \"block\", ...style }}>\n <defs><linearGradient id={id} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\"><stop offset=\"0\" stopColor={color} stopOpacity=\".3\" /><stop offset=\"1\" stopColor={color} stopOpacity=\"0\" /></linearGradient></defs>\n <path d={`${line} L ${width} ${height} L 0 ${height} Z`} fill={`url(#${id})`} />\n <path d={line} fill=\"none\" stroke={color} strokeWidth=\"1.22\" />\n </svg>\n );\n}\n\n/** Mini bar chart used in stat cards. bars: 0–100 values. */\nexport function MiniBars({ bars = [40, 65, 50, 80, 60, 90, 70], width = 248, height = 96, color = \"var(--chart-blue)\", style }) {\n const gap = 6;\n const bw = (width - gap * (bars.length - 1)) / bars.length;\n return (\n <svg width={width} height={height} viewBox={`0 0 ${width} ${height}`} style={{ display: \"block\", ...style }}>\n {bars.map((b, i) => {\n const h = (b / 100) * height;\n return <rect key={i} x={i * (bw + gap)} y={height - h} width={bw} height={h} rx={2} fill={color} opacity={.85} />;\n })}\n </svg>\n );\n}\n","display/ProgressBar.vue":"<script setup>\n// Labeled horizontal progress bar (Expense Distribution rows).\ndefineProps({ label: String, value: { type: Number, default: 50 }, color: { type: String, default: 'var(--chart-blue)' }, showValue: { type: Boolean, default: true }, style: Object })\n</script>\n<template>\n <div :style=\"{ display: 'flex', flexDirection: 'column', gap: '4px', ...style }\">\n <div v-if=\"label != null\" style=\"display:flex;justify-content:space-between;font-family:var(--font-sans);font-size:var(--text-base);line-height:var(--leading-base);letter-spacing:var(--tracking-base)\">\n <span style=\"color:var(--text-body)\">{{ label }}</span>\n <span v-if=\"showValue\" style=\"color:var(--text-heading)\">{{ value }}%</span>\n </div>\n <div style=\"height:6px;border-radius:var(--radius-full);background:var(--track);overflow:hidden\">\n <div :style=\"{ width: value + '%', height: '100%', borderRadius: 'var(--radius-full)', background: color, transition: 'width .3s ease' }\" />\n </div>\n </div>\n</template>\n","display/Tag.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\nconst tones = { neutral: { bg: \"var(--bg-hover)\", fg: \"var(--text-body)\" }, info: { bg: \"var(--info-bg)\", fg: \"var(--info-fg)\" } };\n\n/** Small removable filter tag — distinct from Badge (status) and ActionChip (bulk action). Height follows the region's tier (sm 20 · md 24 · lg 28, --tag-h); text stays 12px. */\nexport function Tag({ children, onRemove, tone = \"neutral\", size, style }) {\n const t = tones[tone] || tones.neutral;\n return (\n <span data-size={size} style={{ display: \"inline-flex\", alignItems: \"center\", gap: 4, height: \"var(--tag-h)\", padding: onRemove ? \"0 6px 0 10px\" : \"0 10px\", borderRadius: \"var(--radius-full)\", background: t.bg, color: t.fg, fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-xs)\", lineHeight: \"var(--leading-xs)\", fontWeight: \"var(--weight-medium)\", letterSpacing: \"var(--tracking-xs)\", whiteSpace: \"nowrap\", ...style }}>\n {children}\n {onRemove ? <button type=\"button\" onClick={onRemove} aria-label=\"Remove\" data-ink=\"\" style={{ border: \"none\", background: \"transparent\", cursor: \"pointer\", color: \"inherit\", opacity: \"var(--ink-op, .7)\", display: \"flex\", padding: 0 }}><Icon name=\"x\" size={12} /></button> : null}\n </span>\n );\n}\n","display/Tag.vue":"<script setup>\nimport { computed } from 'vue'\nimport Icon from '../icons/Icon.vue'\nconst tones = { neutral: { bg: 'var(--bg-hover)', fg: 'var(--text-body)' }, info: { bg: 'var(--info-bg)', fg: 'var(--info-fg)' } }\n// Small removable filter tag — distinct from Badge (status) and ActionChip (bulk action). Height follows the region's tier (sm 20 · md 24 · lg 28, --tag-h); text stays 12px.\nconst props = defineProps({ tone: { type: String, default: 'neutral' }, removable: Boolean, size: String, style: Object })\ndefineEmits(['remove'])\nconst t = computed(() => tones[props.tone] || tones.neutral)\n</script>\n<template>\n <span :data-size=\"size\" :style=\"{ display: 'inline-flex', alignItems: 'center', gap: '4px', height: 'var(--tag-h)', padding: removable ? '0 6px 0 10px' : '0 10px', borderRadius: 'var(--radius-full)', background: t.bg, color: t.fg, fontFamily: 'var(--font-sans)', fontSize: 'var(--text-xs)', lineHeight: 'var(--leading-xs)', fontWeight: 'var(--weight-medium)', letterSpacing: 'var(--tracking-xs)', whiteSpace: 'nowrap', ...style }\">\n <slot />\n <button v-if=\"removable\" type=\"button\" aria-label=\"Remove\" data-ink=\"\" @click=\"$emit('remove')\" style=\"border:none;background:transparent;cursor:pointer;color:inherit;opacity:var(--ink-op, .7);display:flex;padding:0\"><Icon name=\"x\" :size=\"12\" /></button>\n </span>\n</template>\n","display/AreaChart.vue":"<script setup>\nimport { computed } from 'vue'\nconst props = defineProps({ points: { type: Array, default: () => [65, 75, 60, 85, 70, 90, 85, 95, 100] }, width: { type: Number, default: 350 }, height: { type: Number, default: 128 }, color: { type: String, default: 'var(--chart-blue)' }, style: Object })\nconst gid = 'g' + Math.round(Math.random() * 1e6)\nconst step = computed(() => props.width / (props.points.length - 1))\nconst line = computed(() => props.points.map((p, i) => `${i ? 'L' : 'M'} ${i * step.value} ${props.height - (p / 100) * props.height * .48 - props.height * .5}`).join(' '))\n</script>\n<template>\n <svg :width=\"width\" :height=\"height\" :viewBox=\"`0 0 ${width} ${height}`\" :style=\"{ display: 'block', ...style }\">\n <defs><linearGradient :id=\"gid\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\"><stop offset=\"0\" :stop-color=\"color\" stop-opacity=\".3\" /><stop offset=\"1\" :stop-color=\"color\" stop-opacity=\"0\" /></linearGradient></defs>\n <path :d=\"`${line} L ${width} ${height} L 0 ${height} Z`\" :fill=\"`url(#${gid})`\" />\n <path :d=\"line\" fill=\"none\" :stroke=\"color\" stroke-width=\"1.22\" />\n </svg>\n</template>\n","display/AvatarGroup.vue":"<script setup>\nimport { px } from '../px.js'\nimport { computed } from 'vue'\nimport Avatar from './Avatar.vue'\nconst props = defineProps({ names: { type: Array, default: () => [] }, size: { type: Number, default: 24 }, max: { type: Number, default: 4 }, style: Object })\nconst shown = computed(() => props.names.slice(0, props.max))\nconst rest = computed(() => props.names.length - shown.value.length)\n</script>\n<template>\n <div :style=\"{ display: 'flex', alignItems: 'center', ...style }\">\n <Avatar v-for=\"(n, i) in shown\" :key=\"n + i\" :name=\"n\" :size=\"size\" :style=\"{ marginLeft: i ? '-8px' : 0, boxShadow: 'var(--ring-surface)' }\" />\n <span v-if=\"rest > 0\" :style=\"{ marginLeft: '-8px', width: px(size), height: px(size), borderRadius: 'var(--radius-full)', background: 'var(--bg-active)', color: 'var(--text-secondary)', boxShadow: 'var(--ring-surface)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontFamily: 'var(--font-sans)', fontSize: px(Math.max(9, Math.round(size * .38))) }\">+{{ rest }}</span>\n </div>\n</template>\n","display/Donut.vue":"<script setup>\nimport { px } from '../px.js'\nimport { computed } from 'vue'\nconst props = defineProps({ size: { type: Number, default: 128 }, thickness: { type: Number, default: 7.68 }, segments: Array, value: { type: Number, default: 75 }, label: [String, Number], style: Object })\nconst R = computed(() => props.size / 2 - props.thickness / 2 - props.size * .02)\nconst C = computed(() => 2 * Math.PI * R.value)\nconst segs = computed(() => props.segments || [{ value: props.value, color: 'var(--chart-blue)' }])\nconst arcs = computed(() => { let offset = 0; return segs.value.map(s => { const len = (s.value / 100) * C.value; const a = { ...s, len, offset }; offset += len; return a }) })\n</script>\n<template>\n <div :style=\"{ position: 'relative', width: px(size), height: px(size), ...style }\">\n <svg :width=\"size\" :height=\"size\" style=\"transform:rotate(-90deg)\">\n <circle :cx=\"size / 2\" :cy=\"size / 2\" :r=\"R\" fill=\"none\" stroke=\"var(--track)\" :stroke-width=\"thickness\" />\n <circle v-for=\"(s, i) in arcs\" :key=\"i\" :cx=\"size / 2\" :cy=\"size / 2\" :r=\"R\" fill=\"none\" :stroke=\"s.color\" :stroke-width=\"thickness\" :stroke-dasharray=\"`${s.len} ${C - s.len}`\" :stroke-dashoffset=\"-s.offset\" stroke-linecap=\"butt\" />\n </svg>\n <div style=\"position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-family:var(--font-sans);font-weight:var(--weight-light);font-size:var(--text-lg);letter-spacing:var(--tracking-lg);color:var(--text-primary)\">{{ label != null ? label : `${value}%` }}</div>\n </div>\n</template>\n","display/IconTile.vue":"<script setup>\nimport { px } from '../px.js'\nimport { computed } from 'vue'\nimport Icon from '../icons/Icon.vue'\nconst dims = { palette: { box: 40, r: 'var(--radius-tile)', icon: 18 }, node: { box: 48, r: 'var(--radius-node)', icon: 22 }, row: { box: 32, r: 'var(--radius-md)', icon: 16 } }\nconst props = defineProps({ icon: String, hue: { type: String, default: 'source' }, kind: { type: String, default: 'palette' }, style: Object })\nconst t = computed(() => dims[props.kind])\nconst tinted = computed(() => props.kind !== 'row')\n</script>\n<template>\n <span :style=\"{ width: px(t.box), height: px(t.box), borderRadius: px(t.r), background: tinted ? `var(--cat-${hue}-bg)` : 'var(--bg-surface)', border: tinted ? 'none' : '1px solid var(--border-control)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', color: tinted ? `var(--cat-${hue})` : 'var(--text-body)', flexShrink: 0, boxSizing: 'border-box', ...style }\">\n <Icon :name=\"icon\" :size=\"t.icon\" :stroke-width=\"2\" />\n </span>\n</template>\n","display/MiniBars.vue":"<script setup>\nimport { computed } from 'vue'\nconst props = defineProps({ bars: { type: Array, default: () => [40, 65, 50, 80, 60, 90, 70] }, width: { type: Number, default: 248 }, height: { type: Number, default: 96 }, color: { type: String, default: 'var(--chart-blue)' }, style: Object })\nconst gap = 6\nconst bw = computed(() => (props.width - gap * (props.bars.length - 1)) / props.bars.length)\n</script>\n<template>\n <svg :width=\"width\" :height=\"height\" :viewBox=\"`0 0 ${width} ${height}`\" :style=\"{ display: 'block', ...style }\">\n <rect v-for=\"(b, i) in bars\" :key=\"i\" :x=\"i * (bw + gap)\" :y=\"height - (b / 100) * height\" :width=\"bw\" :height=\"(b / 100) * height\" rx=\"2\" :fill=\"color\" opacity=\".85\" />\n </svg>\n</template>\n","display/StatCard.vue":"<script setup>\nimport Card from './Card.vue'\ndefineProps({ label: String, value: [String, Number], dense: Boolean, style: Object })\n</script>\n<template>\n <Card :dense=\"dense\" :style=\"{ display: 'flex', flexDirection: 'column', gap: '12px', ...style }\">\n <span style=\"font-family:var(--font-sans);font-size:var(--text-base);line-height:var(--leading-base);letter-spacing:var(--tracking-base);color:var(--text-secondary)\">{{ label }}</span>\n <slot />\n <span v-if=\"value != null\" style=\"font-family:var(--font-sans);font-weight:var(--weight-light);font-size:var(--text-num);line-height:var(--leading-num);letter-spacing:var(--tracking-num);color:var(--text-primary)\">{{ value }}</span>\n </Card>\n</template>\n","display/StatusBadge.vue":"<script setup>\nimport Icon from '../icons/Icon.vue'\n// Circular status badge with white ring (node error/warning dots, step icons).\nconst badgeTones = { success: 'var(--success)', danger: 'var(--danger)', warning: 'var(--warning)', pending: 'var(--dot-neutral)', info: 'var(--action-primary)' }\nconst props = defineProps({ tone: { type: String, default: 'danger' }, icon: String, size: { type: Number, default: 20 }, ring: { type: Boolean, default: true }, style: Object })\n</script>\n<template>\n <span :style=\"{ width: size + 'px', height: size + 'px', borderRadius: 'var(--radius-full)', background: badgeTones[tone], boxShadow: ring ? 'var(--ring-surface)' : 'none', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', color: 'var(--on-accent)', flexShrink: 0, ...style }\">\n <Icon v-if=\"icon\" :name=\"icon\" :size=\"Math.round(size * .6)\" :stroke-width=\"2.5\" />\n </span>\n</template>\n"});
@@ -1 +1 @@
1
- window.__KB_SRC__=Object.assign(window.__KB_SRC__||{},{"feedback/Accordion.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\n/** Generic collapsible section: chevron + title/subtitle header, body slot. Controlled via `open`+`onToggle`, or uncontrolled via `defaultOpen`. */\nexport function Accordion({ title, subtitle, defaultOpen, open: openProp, onToggle, children, style }) {\n const [openState, setOpenState] = React.useState(!!defaultOpen);\n const open = openProp != null ? openProp : openState;\n const toggle = () => (onToggle ? onToggle(!open) : setOpenState(!open));\n return (\n <div style={{ borderBottom: \"1px solid var(--border-card)\", ...style }}>\n <div onClick={toggle} style={{ display: \"flex\", alignItems: \"center\", gap: 10, padding: \"14px 4px\", cursor: \"pointer\" }}>\n <span style={{ color: \"var(--text-muted)\", display: \"inline-flex\", transform: open ? \"rotate(90deg)\" : \"none\", transition: \"transform var(--dur-fast) var(--ease-out)\" }}><Icon name=\"chevron-right\" size={14} /></span>\n <div style={{ display: \"flex\", flexDirection: \"column\", flex: 1 }}>\n <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-base)\", letterSpacing: \"var(--tracking-base)\", color: \"var(--text-heading)\" }}>{title}</span>\n {subtitle ? <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", color: \"var(--text-secondary)\" }}>{subtitle}</span> : null}\n </div>\n </div>\n {open ? <div style={{ padding: \"0 4px 16px 28px\" }}>{children}</div> : null}\n </div>\n );\n}\n","feedback/Accordion.vue":"<script setup>\nimport { ref, computed } from 'vue'\nimport Icon from '../icons/Icon.vue'\n// Generic collapsible section: chevron + title/subtitle header, body slot. Controlled via `open` prop + `toggle` emit, or uncontrolled via `defaultOpen`.\nconst props = defineProps({ title: String, subtitle: String, defaultOpen: Boolean, open: { type: Boolean, default: null }, style: Object })\nconst emit = defineEmits(['toggle'])\nconst openState = ref(!!props.defaultOpen)\nconst open = computed(() => props.open != null ? props.open : openState.value)\nfunction toggle() { if (props.open != null) emit('toggle', !open.value); else openState.value = !openState.value }\n</script>\n<template>\n <div :style=\"{ borderBottom: '1px solid var(--border-card)', ...style }\">\n <div @click=\"toggle\" style=\"display:flex;align-items:center;gap:10px;padding:14px 4px;cursor:pointer\">\n <span :style=\"{ color: 'var(--text-muted)', display: 'inline-flex', transform: open ? 'rotate(90deg)' : 'none', transition: 'transform var(--dur-fast) var(--ease-out)' }\"><Icon name=\"chevron-right\" :size=\"14\" /></span>\n <div style=\"display:flex;flex-direction:column;flex:1\">\n <span style=\"font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);letter-spacing:var(--tracking-base);color:var(--text-heading)\">{{ title }}</span>\n <span v-if=\"subtitle\" style=\"font-family:var(--font-sans);font-size:var(--text-sm);color:var(--text-secondary)\">{{ subtitle }}</span>\n </div>\n </div>\n <div v-if=\"open\" style=\"padding:0 4px 16px 28px\"><slot /></div>\n </div>\n</template>\n","feedback/ActivityItem.jsx":"import React from \"react\";\nimport { IconTile } from \"../display/Badge.jsx\";\n\n/** Activity feed row: 32px icon tile + 13px/500 title + 12px meta + optional right action. */\nexport function ActivityItem({ icon = \"file-text\", title, meta, action, style }) {\n return (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 12, padding: \"12px 16px\", ...style }}>\n <IconTile icon={icon} kind=\"row\" style={{ background: \"var(--info-bg)\", border: \"none\", color: \"var(--info-fg)\" }} />\n <div style={{ display: \"flex\", flexDirection: \"column\", flex: 1, minWidth: 0 }}>\n <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-base)\", lineHeight: \"var(--leading-base)\", letterSpacing: \"var(--tracking-base)\", color: \"var(--text-heading)\" }}>{title}</span>\n {meta ? <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-secondary)\" }}>{meta}</span> : null}\n </div>\n {action ? <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-base)\", letterSpacing: \"var(--tracking-base)\", color: \"var(--text-secondary)\", cursor: \"pointer\" }}>{action}</span> : null}\n </div>\n );\n}\n\n/** Compact dot activity row (Recent Activities card): 8px status dot + 12px text + 11px time. */\nexport function DotActivity({ tone = \"success\", text, time, style }) {\n const tones = { success: \"var(--success)\", warning: \"var(--warning)\", danger: \"var(--danger)\", info: \"var(--action-primary)\", neutral: \"var(--dot-neutral)\" };\n return (\n <div style={{ display: \"flex\", gap: 8, alignItems: \"flex-start\", ...style }}>\n <span style={{ width: 8, height: 8, borderRadius: \"var(--radius-full)\", background: tones[tone], marginTop: 5, flexShrink: 0 }} />\n <div style={{ display: \"flex\", flexDirection: \"column\", minWidth: 0 }}>\n <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-heading)\", overflow: \"hidden\", textOverflow: \"ellipsis\", whiteSpace: \"nowrap\" }}>{text}</span>\n <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-xs)\", lineHeight: \"var(--leading-xs)\", letterSpacing: \"var(--tracking-xs)\", color: \"var(--text-muted)\" }}>{time}</span>\n </div>\n </div>\n );\n}\n\n/** Mono log lines (\"[14:30] Task started: Import\") — Menlo 11px on inset panel. */\nexport function LogViewer({ lines = [], height, style }) {\n return (\n <div style={{ background: \"var(--bg-inset)\", borderRadius: \"var(--radius-md)\", border: \"1px solid var(--border-subtle)\", padding: \"10px 12px\", display: \"flex\", flexDirection: \"column\", gap: 6, overflowY: \"auto\", height, boxSizing: \"border-box\", ...style }}>\n {lines.map((l, i) => <span key={i} style={{ fontFamily: \"var(--font-mono)\", fontSize: \"var(--text-xs)\", lineHeight: \"var(--leading-xs)\", color: typeof l === \"object\" ? l.color || \"var(--text-body)\" : \"var(--text-body)\", whiteSpace: \"pre\" }}>{typeof l === \"object\" ? l.text : l}</span>)}\n </div>\n );\n}\n","feedback/ActivityItem.vue":"<script setup>\nimport IconTile from '../display/Badge.vue'\n// Activity feed row: 32px icon tile + 13px/500 title + 12px meta + optional right action.\ndefineProps({ icon: { type: String, default: 'file-text' }, title: String, meta: String, action: String, style: Object })\n</script>\n<template>\n <div :style=\"{ display: 'flex', alignItems: 'center', gap: 12, padding: '12px 16px', ...style }\">\n <IconTile :icon=\"icon\" kind=\"row\" style=\"background:var(--info-bg);border:none;color:var(--info-fg)\" />\n <div style=\"display:flex;flex-direction:column;flex:1;min-width:0\">\n <span style=\"font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);line-height:var(--leading-base);letter-spacing:var(--tracking-base);color:var(--text-heading)\">{{ title }}</span>\n <span v-if=\"meta\" style=\"font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-secondary)\">{{ meta }}</span>\n </div>\n <span v-if=\"action\" style=\"font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);letter-spacing:var(--tracking-base);color:var(--text-secondary);cursor:pointer\">{{ action }}</span>\n </div>\n</template>\n","feedback/Alert.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\nconst alertTones = {\n info: { bg: \"var(--info-bg)\", fg: \"var(--info-fg)\", icon: \"info\" },\n success: { bg: \"var(--success-bg)\", fg: \"var(--success-text)\", icon: \"circle-check\" },\n warning: { bg: \"var(--warning-bg)\", fg: \"var(--warning-text)\", icon: \"triangle-alert\" },\n danger: { bg: \"var(--danger-bg)\", fg: \"var(--danger-text)\", icon: \"triangle-alert\" },\n};\n\n/** Inline banner — tinted, no border, 13px title + 12px body. */\nexport function Alert({ tone = \"info\", title, children, action, style }) {\n const t = alertTones[tone];\n return (\n <div style={{ display: \"flex\", gap: 10, alignItems: \"flex-start\", background: t.bg, borderRadius: \"var(--radius-md)\", padding: \"10px 12px\", ...style }}>\n <span style={{ color: t.fg, display: \"inline-flex\", marginTop: 1 }}><Icon name={t.icon} size={15} /></span>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: 2, flex: 1, minWidth: 0 }}>\n {title ? <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-base)\", lineHeight: \"var(--leading-base)\", letterSpacing: \"var(--tracking-base)\", color: t.fg }}>{title}</span> : null}\n {children ? <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-body)\" }}>{children}</span> : null}\n </div>\n {action}\n </div>\n );\n}\n\n/** Empty state — 22px muted glyph, 13px/500 title, 12px hint, optional action. */\nexport function EmptyState({ icon = \"file-text\", title, hint, action, style }) {\n return (\n <div style={{ display: \"flex\", flexDirection: \"column\", alignItems: \"center\", justifyContent: \"center\", gap: 6, padding: \"40px 24px\", textAlign: \"center\", ...style }}>\n <span style={{ color: \"var(--text-muted)\", marginBottom: 6 }}><Icon name={icon} size={22} strokeWidth={1.5} /></span>\n <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-base)\", lineHeight: \"var(--leading-base)\", letterSpacing: \"var(--tracking-base)\", color: \"var(--text-heading)\" }}>{title}</span>\n {hint ? <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-secondary)\", maxWidth: 320 }}>{hint}</span> : null}\n {action ? <span style={{ paddingTop: 10 }}>{action}</span> : null}\n </div>\n );\n}\n\n/** Loading placeholder block. */\nexport function Skeleton({ width = \"100%\", height = 12, radius = \"var(--radius-sm)\", style }) {\n return <span style={{ display: \"block\", width, height, borderRadius: radius, background: \"var(--skeleton)\", animation: \"kb-pulse 1.4s var(--ease-in-out) infinite\", ...style }} />;\n}\n\n/** Indeterminate spinner (task-running affordance). */\nexport function Spinner({ size = 16, color = \"var(--action-primary)\", style }) {\n return (\n <span style={{ display: \"inline-block\", width: size, height: size, borderRadius: \"var(--radius-full)\", border: `2px solid var(--track)`, borderTopColor: color, animation: \"kb-spin .7s linear infinite\", ...style }} />\n );\n}\n","feedback/Alert.vue":"<script setup>\nimport { computed } from 'vue'\nimport Icon from '../icons/Icon.vue'\nconst alertTones = {\n info: { bg: 'var(--info-bg)', fg: 'var(--info-fg)', icon: 'info' },\n success: { bg: 'var(--success-bg)', fg: 'var(--success-text)', icon: 'circle-check' },\n warning: { bg: 'var(--warning-bg)', fg: 'var(--warning-text)', icon: 'triangle-alert' },\n danger: { bg: 'var(--danger-bg)', fg: 'var(--danger-text)', icon: 'triangle-alert' },\n}\n// Alert — inline banner — tinted, no border, 13px title + 12px body.\nconst props = defineProps({ tone: { type: String, default: 'info' }, title: String, style: Object })\nconst t = computed(() => alertTones[props.tone])\n</script>\n<template>\n <div :style=\"{ display: 'flex', gap: 10, alignItems: 'flex-start', background: t.bg, borderRadius: 'var(--radius-md)', padding: '10px 12px', ...style }\">\n <span :style=\"{ color: t.fg, display: 'inline-flex', marginTop: 1 }\"><Icon :name=\"t.icon\" :size=\"15\" /></span>\n <div style=\"display:flex;flex-direction:column;gap:2px;flex:1;min-width:0\">\n <span v-if=\"title\" :style=\"{ fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: 'var(--text-base)', lineHeight: 'var(--leading-base)', letterSpacing: 'var(--tracking-base)', color: t.fg }\">{{ title }}</span>\n <span style=\"font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-body)\"><slot /></span>\n </div>\n <slot name=\"action\" />\n </div>\n</template>\n","feedback/Banner.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\nimport { IconButton } from \"../buttons/IconButton.jsx\";\n\nconst bannerTones = {\n info: { bg: \"var(--info-bg)\", fg: \"var(--info-fg)\", icon: \"info\" },\n success: { bg: \"var(--success-bg)\", fg: \"var(--success-text)\", icon: \"circle-check\" },\n warning: { bg: \"var(--warning-bg)\", fg: \"var(--warning-text)\", icon: \"triangle-alert\" },\n danger: { bg: \"var(--danger-bg)\", fg: \"var(--danger-text)\", icon: \"triangle-alert\" },\n};\n\n/** Page-level notification banner (Carbon/SLDS scoped notification): full-width tinted strip with icon, message, optional inline action link and dismiss ×. Distinct from Alert (inline, in-card) and Toast (floating). */\nexport function Banner({ tone = \"info\", title, action, onAction, dismissible, onDismiss, children, style }) {\n const t = bannerTones[tone];\n return (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 10, background: t.bg, borderLeft: `3px solid ${t.fg}`, padding: \"10px 12px 10px 14px\", boxSizing: \"border-box\", ...style }}>\n <span style={{ color: t.fg, display: \"inline-flex\", flexShrink: 0 }}><Icon name={t.icon} size={16} /></span>\n <span style={{ flex: 1, minWidth: 0, fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-body)\" }}>\n {title && <strong style={{ fontWeight: \"var(--weight-medium)\", color: t.fg, paddingRight: 6 }}>{title}</strong>}{children}\n </span>\n {action && <button type=\"button\" onClick={onAction} style={{ border: \"none\", background: \"transparent\", cursor: \"pointer\", padding: 0, fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-sm)\", color: t.fg, textDecoration: \"underline\", whiteSpace: \"nowrap\", flexShrink: 0 }}>{action}</button>}\n {dismissible && <IconButton icon=\"x\" size=\"xs\" muted label=\"Dismiss\" onClick={onDismiss} />}\n </div>\n );\n}\n","feedback/Banner.vue":"<script setup>\nimport { computed } from 'vue'\nimport Icon from '../icons/Icon.vue'\nimport IconButton from '../buttons/IconButton.vue'\nconst bannerTones = {\n info: { bg: 'var(--info-bg)', fg: 'var(--info-fg)', icon: 'info' },\n success: { bg: 'var(--success-bg)', fg: 'var(--success-text)', icon: 'circle-check' },\n warning: { bg: 'var(--warning-bg)', fg: 'var(--warning-text)', icon: 'triangle-alert' },\n danger: { bg: 'var(--danger-bg)', fg: 'var(--danger-text)', icon: 'triangle-alert' },\n}\n// Page-level notification banner: tinted full-width strip, left accent bar, inline action link, dismiss ×.\nconst props = defineProps({ tone: { type: String, default: 'info' }, title: String, action: String, dismissible: Boolean, style: Object })\nconst emit = defineEmits(['action', 'dismiss'])\nconst t = computed(() => bannerTones[props.tone])\n</script>\n<template>\n <div :style=\"{ display: 'flex', alignItems: 'center', gap: '10px', background: t.bg, borderLeft: `3px solid ${t.fg}`, padding: '10px 12px 10px 14px', boxSizing: 'border-box', ...style }\">\n <span :style=\"{ color: t.fg, display: 'inline-flex', flexShrink: 0 }\"><Icon :name=\"t.icon\" :size=\"16\" /></span>\n <span style=\"flex:1;min-width:0;font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-body)\">\n <strong v-if=\"title\" :style=\"{ fontWeight: 'var(--weight-medium)', color: t.fg, paddingRight: '6px' }\">{{ title }}</strong><slot />\n </span>\n <button v-if=\"action\" type=\"button\" @click=\"emit('action')\" :style=\"{ border: 'none', background: 'transparent', cursor: 'pointer', padding: 0, fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: 'var(--text-sm)', color: t.fg, textDecoration: 'underline', whiteSpace: 'nowrap', flexShrink: 0 }\">{{ action }}</button>\n <IconButton v-if=\"dismissible\" icon=\"x\" size=\"xs\" muted label=\"Dismiss\" @click=\"emit('dismiss')\" />\n </div>\n</template>\n","feedback/ContextMenu.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\n/** Right-click context menu (Preview / Duplicate / Delete). items: [{label, icon, danger, divider}]. */\nexport function ContextMenu({ items = [], onSelect, style }) {\n const [hover, setHover] = React.useState(-1);\n return (\n <div style={{ background: \"var(--bg-surface)\", borderRadius: \"var(--radius-lg)\", border: \"1px solid var(--border-card)\", boxShadow: \"var(--shadow-menu)\", padding: 4, minWidth: 148, display: \"inline-flex\", flexDirection: \"column\", ...style }}>\n {items.map((it, i) => it.divider ? <div key={i} style={{ height: 1, background: \"var(--border-subtle)\", margin: \"4px 0\" }} /> : (\n <button key={i} type=\"button\" onClick={() => onSelect && onSelect(it.label)}\n onMouseEnter={() => setHover(i)} onMouseLeave={() => setHover(-1)}\n style={{ display: \"flex\", alignItems: \"center\", gap: 10, padding: \"7px 10px\", borderRadius: \"var(--radius-md)\", border: \"none\", cursor: \"pointer\", background: hover === i ? \"var(--bg-hover)\" : \"transparent\", fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-base)\", lineHeight: \"var(--leading-base)\", letterSpacing: \"var(--tracking-base)\", color: it.danger ? \"var(--danger)\" : \"var(--text-heading)\", textAlign: \"left\" }}>\n {it.icon ? <Icon name={it.icon} size={15} /> : null}{it.label}\n </button>\n ))}\n </div>\n );\n}\n","feedback/ContextMenu.vue":"<script setup>\nimport { ref } from 'vue'\nimport Icon from '../icons/Icon.vue'\n// Right-click context menu (Preview / Duplicate / Delete). items: [{label, icon, danger, divider}].\ndefineProps({ items: { type: Array, default: () => [] }, style: Object })\nconst emit = defineEmits(['select'])\nconst hover = ref(-1)\n</script>\n<template>\n <div :style=\"{ background: 'var(--bg-surface)', borderRadius: 'var(--radius-lg)', border: '1px solid var(--border-card)', boxShadow: 'var(--shadow-menu)', padding: 4, minWidth: 148, display: 'inline-flex', flexDirection: 'column', ...style }\">\n <template v-for=\"(it, i) in items\" :key=\"i\">\n <div v-if=\"it.divider\" style=\"height:1px;background:var(--border-subtle);margin:4px 0\" />\n <button v-else type=\"button\" @click=\"emit('select', it.label)\" @mouseenter=\"hover = i\" @mouseleave=\"hover = -1\"\n :style=\"{ display: 'flex', alignItems: 'center', gap: 10, padding: '7px 10px', borderRadius: 'var(--radius-md)', border: 'none', cursor: 'pointer', background: hover === i ? 'var(--bg-hover)' : 'transparent', fontFamily: 'var(--font-sans)', fontSize: 'var(--text-base)', lineHeight: 'var(--leading-base)', letterSpacing: 'var(--tracking-base)', color: it.danger ? 'var(--danger)' : 'var(--text-heading)', textAlign: 'left' }\">\n <Icon v-if=\"it.icon\" :name=\"it.icon\" :size=\"15\" />{{ it.label }}\n </button>\n </template>\n </div>\n</template>\n","feedback/Drawer.jsx":"import React from \"react\";\nimport { IconButton } from \"../buttons/IconButton.jsx\";\n\n/** Right slide-out panel over a scrim: 17.5px title + subtitle, scrollable body, right-aligned footer slot. inline=true renders it in-flow for embedding in cards/demos. */\nexport function Drawer({ open = true, title, subtitle, width = 400, inline, height, footer, onClose, children, style }) {\n if (!open) return null;\n const panel = (\n <div style={{ width, maxWidth: \"100%\", height: inline ? (height || 420) : \"100%\", background: \"var(--bg-raised)\", borderLeft: \"1px solid var(--border-card)\", boxShadow: inline ? \"var(--shadow-menu)\" : \"-8px 0 24px rgba(0,0,0,.12)\", display: \"flex\", flexDirection: \"column\", boxSizing: \"border-box\", ...(inline ? { border: \"1px solid var(--border-card)\", borderRadius: \"var(--radius-lg)\" } : null), ...style }}>\n <div style={{ display: \"flex\", alignItems: \"flex-start\", justifyContent: \"space-between\", padding: \"16px 16px 12px\", borderBottom: \"1px solid var(--border-card)\", flexShrink: 0 }}>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: 3 }}>\n <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-semibold)\", fontSize: \"var(--text-md)\", lineHeight: \"var(--leading-md)\", letterSpacing: \"var(--tracking-md)\", color: \"var(--text-heading)\" }}>{title}</span>\n {subtitle && <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-secondary)\" }}>{subtitle}</span>}\n </div>\n <IconButton icon=\"x\" size=\"md\" label=\"Close\" onClick={onClose} />\n </div>\n <div style={{ flex: 1, overflowY: \"auto\", padding: 16 }}>{children}</div>\n {footer && <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: 8, padding: \"12px 16px\", borderTop: \"1px solid var(--border-card)\", flexShrink: 0 }}>{footer}</div>}\n </div>\n );\n if (inline) return panel;\n return (\n <div onClick={onClose} style={{ position: \"fixed\", inset: 0, zIndex: \"var(--z-modal)\", background: \"var(--bg-scrim)\", display: \"flex\", justifyContent: \"flex-end\", animation: \"kb-fade-in var(--dur-fast) var(--ease-out)\" }}>\n <div onClick={(e) => e.stopPropagation()} style={{ height: \"100%\", animation: \"kb-slide-in var(--dur) var(--ease-out)\" }}>{panel}</div>\n </div>\n );\n}\n","feedback/Drawer.vue":"<script setup>\nimport IconButton from '../buttons/IconButton.vue'\n// Right slide-out panel over a scrim; inline=true renders in-flow for demos. Header, scrollable body, footer slot.\ndefineProps({ open: { type: Boolean, default: true }, title: String, subtitle: String, width: { type: Number, default: 400 }, inline: Boolean, height: { type: [Number, String], default: 420 }, style: Object })\nconst emit = defineEmits(['close'])\n</script>\n<template>\n <component :is=\"inline ? 'div' : 'teleport'\" v-if=\"open\" v-bind=\"inline ? {} : { to: 'body' }\">\n <div v-if=\"!inline\" @click=\"emit('close')\" style=\"position:fixed;inset:0;z-index:var(--z-modal);background:var(--bg-scrim);display:flex;justify-content:flex-end;animation:kb-fade-in var(--dur-fast) var(--ease-out)\">\n <div @click.stop style=\"height:100%;animation:kb-slide-in var(--dur) var(--ease-out)\"><DrawerPanel /></div>\n </div>\n <div :style=\"{ width: width + 'px', height: inline ? (typeof height === 'number' ? height + 'px' : height) : '100%', background: 'var(--bg-raised)', border: '1px solid var(--border-card)', borderRadius: inline ? 'var(--radius-lg)' : 0, boxShadow: 'var(--shadow-menu)', display: 'flex', flexDirection: 'column', boxSizing: 'border-box', ...style }\">\n <div style=\"display:flex;align-items:flex-start;justify-content:space-between;padding:16px 16px 12px;border-bottom:1px solid var(--border-card);flex-shrink:0\">\n <div style=\"display:flex;flex-direction:column;gap:3px\">\n <span style=\"font-family:var(--font-sans);font-weight:var(--weight-semibold);font-size:var(--text-md);color:var(--text-heading)\">{{ title }}</span>\n <span v-if=\"subtitle\" style=\"font-family:var(--font-sans);font-size:var(--text-sm);color:var(--text-secondary)\">{{ subtitle }}</span>\n </div>\n <IconButton icon=\"x\" size=\"md\" label=\"Close\" @click=\"emit('close')\" />\n </div>\n <div style=\"flex:1;overflow-y:auto;padding:16px\"><slot /></div>\n <div v-if=\"$slots.footer\" style=\"display:flex;justify-content:flex-end;gap:8px;padding:12px 16px;border-top:1px solid var(--border-card);flex-shrink:0\"><slot name=\"footer\" /></div>\n </div>\n </component>\n</template>\n","feedback/Menu.jsx":"import React from \"react\";\nimport { ContextMenu } from \"./ContextMenu.jsx\";\n\n/** Click-to-open dropdown menu anchored under its trigger (vs. ContextMenu, which opens at a click point). Closes on outside click. */\nexport function Menu({ trigger, items = [], onSelect, align = \"start\", style }) {\n const [open, setOpen] = React.useState(false);\n const ref = React.useRef(null);\n React.useEffect(() => {\n if (!open) return;\n const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };\n document.addEventListener(\"mousedown\", onDoc);\n return () => document.removeEventListener(\"mousedown\", onDoc);\n }, [open]);\n return (\n <div ref={ref} style={{ position: \"relative\", display: \"inline-flex\", ...style }}>\n <span onClick={() => setOpen((o) => !o)}>{trigger}</span>\n {open ? (\n <div style={{ position: \"absolute\", top: \"calc(100% + 6px)\", [align === \"end\" ? \"right\" : \"left\"]: 0, zIndex: \"var(--z-menu)\" }}>\n <ContextMenu items={items} onSelect={(l) => { setOpen(false); onSelect && onSelect(l); }} />\n </div>\n ) : null}\n </div>\n );\n}\n","feedback/Menu.vue":"<script setup>\nimport { ref, onMounted, onUnmounted } from 'vue'\nimport ContextMenu from './ContextMenu.vue'\n// Click-to-open dropdown menu anchored under its trigger (vs. ContextMenu, which opens at a click point). Closes on outside click.\nconst props = defineProps({ items: { type: Array, default: () => [] }, align: { type: String, default: 'start' }, style: Object })\nconst emit = defineEmits(['select'])\nconst open = ref(false)\nconst root = ref(null)\nfunction onDoc(e) { if (root.value && !root.value.contains(e.target)) open.value = false }\nonMounted(() => document.addEventListener('mousedown', onDoc))\nonUnmounted(() => document.removeEventListener('mousedown', onDoc))\nfunction select(l) { open.value = false; emit('select', l) }\n</script>\n<template>\n <div ref=\"root\" :style=\"{ position: 'relative', display: 'inline-flex', ...style }\">\n <span @click=\"open = !open\"><slot name=\"trigger\" /></span>\n <div v-if=\"open\" :style=\"{ position: 'absolute', top: 'calc(100% + 6px)', [align === 'end' ? 'right' : 'left']: 0, zIndex: 'var(--z-menu)' }\">\n <ContextMenu :items=\"items\" @select=\"select\" />\n </div>\n </div>\n</template>\n","feedback/Modal.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\nimport { IconButton } from \"../buttons/IconButton.jsx\";\n\n/** Hover tooltip — 11px, inverted surface, 6px offset. Wraps its children. */\nexport function Tooltip({ label, placement = \"top\", children, style }) {\n const [show, setShow] = React.useState(false);\n const pos = {\n top: { bottom: \"100%\", left: \"50%\", transform: \"translateX(-50%)\", marginBottom: 6 },\n bottom: { top: \"100%\", left: \"50%\", transform: \"translateX(-50%)\", marginTop: 6 },\n left: { right: \"100%\", top: \"50%\", transform: \"translateY(-50%)\", marginRight: 6 },\n right: { left: \"100%\", top: \"50%\", transform: \"translateY(-50%)\", marginLeft: 6 },\n }[placement];\n return (\n <span style={{ position: \"relative\", display: \"inline-flex\", ...style }} onMouseEnter={() => setShow(true)} onMouseLeave={() => setShow(false)}>\n {children}\n {show ? (\n <span style={{ position: \"absolute\", zIndex: \"var(--z-tooltip)\", whiteSpace: \"nowrap\", background: \"var(--tooltip-bg)\", color: \"var(--tooltip-fg)\", borderRadius: \"var(--radius-md)\", padding: \"5px 8px\", fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-xs)\", lineHeight: \"var(--leading-xs)\", letterSpacing: \"var(--tracking-xs)\", boxShadow: \"var(--shadow-menu)\", pointerEvents: \"none\", animation: \"kb-fade-in var(--dur-fast) var(--ease-out)\", ...pos }}>{label}</span>\n ) : null}\n </span>\n );\n}\n\n/** Centered modal dialog with scrim. width defaults to 440. */\nexport function Modal({ open, title, subtitle, onClose, footer, width = 440, children, style }) {\n if (!open) return null;\n return (\n <div style={{ position: \"fixed\", inset: 0, zIndex: \"var(--z-modal)\", background: \"var(--bg-scrim)\", display: \"flex\", alignItems: \"center\", justifyContent: \"center\", animation: \"kb-fade-in var(--dur-fast) var(--ease-out)\" }} onClick={onClose}>\n <div onClick={(e) => e.stopPropagation()}\n style={{ width, maxWidth: \"92vw\", background: \"var(--bg-raised)\", borderRadius: \"var(--radius-lg)\", border: \"1px solid var(--border-card)\", boxShadow: \"var(--shadow-modal)\", animation: \"kb-rise var(--dur) var(--ease-out)\", ...style }}>\n <div style={{ display: \"flex\", alignItems: \"flex-start\", justifyContent: \"space-between\", padding: \"17px 17px 0\" }}>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: 4 }}>\n <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-semibold)\", fontSize: \"var(--text-md)\", lineHeight: \"var(--leading-md)\", letterSpacing: \"var(--tracking-md)\", color: \"var(--text-heading)\" }}>{title}</span>\n {subtitle ? <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-secondary)\" }}>{subtitle}</span> : null}\n </div>\n <IconButton icon=\"x\" size=\"md\" onClick={onClose} label=\"Close\" />\n </div>\n {children ? <div style={{ padding: \"16px 17px 0\" }}>{children}</div> : null}\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: 8, padding: 17 }}>{footer}</div>\n </div>\n </div>\n );\n}\n\nconst toastTones = { success: { icon: \"circle-check\", color: \"var(--success-strong)\" }, danger: { icon: \"triangle-alert\", color: \"var(--danger)\" }, warning: { icon: \"triangle-alert\", color: \"var(--warning)\" }, info: { icon: \"info\", color: \"var(--action-primary)\" } };\n\n/** Single toast row. */\nexport function Toast({ tone = \"success\", title, meta, onClose, style }) {\n const t = toastTones[tone];\n return (\n <div style={{ display: \"flex\", alignItems: \"flex-start\", gap: 10, minWidth: 280, maxWidth: 380, background: \"var(--bg-raised)\", border: \"1px solid var(--border-card)\", borderRadius: \"var(--radius-lg)\", boxShadow: \"var(--shadow-menu)\", padding: \"12px 12px 12px 14px\", animation: \"kb-toast-in var(--dur) var(--ease-out)\", ...style }}>\n <span style={{ color: t.color, display: \"inline-flex\", marginTop: 1 }}><Icon name={t.icon} size={16} /></span>\n <div style={{ display: \"flex\", flexDirection: \"column\", flex: 1, minWidth: 0 }}>\n <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-base)\", lineHeight: \"var(--leading-base)\", letterSpacing: \"var(--tracking-base)\", color: \"var(--text-heading)\" }}>{title}</span>\n {meta ? <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-secondary)\" }}>{meta}</span> : null}\n </div>\n {onClose ? <IconButton icon=\"x\" size=\"xs\" muted onClick={onClose} label=\"Dismiss\" /> : null}\n </div>\n );\n}\n\n/** Fixed bottom-right stack of toasts. */\nexport function ToastStack({ toasts = [], onDismiss, style }) {\n return (\n <div style={{ position: \"fixed\", right: 24, bottom: 24, zIndex: \"var(--z-toast)\", display: \"flex\", flexDirection: \"column\", gap: 8, ...style }}>\n {toasts.map((t) => <Toast key={t.id} tone={t.tone} title={t.title} meta={t.meta} onClose={() => onDismiss && onDismiss(t.id)} />)}\n </div>\n );\n}\n","feedback/Modal.vue":"<script setup>\nimport IconButton from '../buttons/IconButton.vue'\ndefineProps({ open: Boolean, title: String, subtitle: String, width: { type: Number, default: 440 }, style: Object })\nconst emit = defineEmits(['close'])\n</script>\n<template>\n <div v-if=\"open\" style=\"position:fixed;inset:0;z-index:var(--z-modal);background:var(--bg-scrim);display:flex;align-items:center;justify-content:center;animation:kb-fade-in var(--dur-fast) var(--ease-out)\" @click=\"emit('close')\">\n <div @click.stop :style=\"{ width, maxWidth: '92vw', background: 'var(--bg-raised)', borderRadius: 'var(--radius-lg)', border: '1px solid var(--border-card)', boxShadow: 'var(--shadow-modal)', animation: 'kb-rise var(--dur) var(--ease-out)', ...style }\">\n <div style=\"display:flex;align-items:flex-start;justify-content:space-between;padding:17px 17px 0\">\n <div style=\"display:flex;flex-direction:column;gap:4px\">\n <span style=\"font-family:var(--font-sans);font-weight:var(--weight-semibold);font-size:var(--text-md);line-height:var(--leading-md);letter-spacing:var(--tracking-md);color:var(--text-heading)\">{{ title }}</span>\n <span v-if=\"subtitle\" style=\"font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-secondary)\">{{ subtitle }}</span>\n </div>\n <IconButton icon=\"x\" size=\"md\" label=\"Close\" @click=\"emit('close')\" />\n </div>\n <div v-if=\"$slots.default\" style=\"padding:16px 17px 0\"><slot /></div>\n <div style=\"display:flex;justify-content:flex-end;gap:8px;padding:17px\"><slot name=\"footer\" /></div>\n </div>\n </div>\n</template>\n","feedback/Stepper.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\n/** Wizard stepper (Upload & Parse → ... → Results). steps: [{label, icon}]; current index; connectors hairline #e5e5e5. */\nexport function Stepper({ steps = [], current = 0, style }) {\n return (\n <div style={{ display: \"flex\", alignItems: \"flex-start\", ...style }}>\n {steps.map((s, i) => {\n const done = i < current, is = i === current;\n return (\n <React.Fragment key={s.label}>\n {i > 0 ? <div style={{ flex: 1, height: 2, background: \"var(--track)\", marginTop: 15.25, marginLeft: 12, marginRight: 12, minWidth: 24 }} /> : null}\n <div style={{ display: \"flex\", flexDirection: \"column\", alignItems: \"center\", gap: 8, flexShrink: 0 }}>\n <span style={{ width: 32, height: 32, borderRadius: \"var(--radius-full)\", boxSizing: \"border-box\", display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", background: done ? \"var(--success)\" : is ? \"var(--action-primary)\" : \"var(--bg-surface)\", border: done || is ? \"none\" : \"2px solid var(--track)\", color: done || is ? \"var(--on-accent)\" : \"var(--text-muted)\" }}>\n <Icon name={done ? \"check\" : s.icon || \"file\"} size={16} />\n </span>\n <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-xs)\", lineHeight: \"var(--leading-xs)\", letterSpacing: \"var(--tracking-xs)\", fontWeight: is ? \"var(--weight-medium)\" : \"var(--weight-regular)\", color: is ? \"var(--text-heading)\" : \"var(--text-secondary)\", whiteSpace: \"nowrap\" }}>{s.label}</span>\n </div>\n </React.Fragment>\n );\n })}\n </div>\n );\n}\n","feedback/Stepper.vue":"<script setup>\nimport Icon from '../icons/Icon.vue'\n// Wizard stepper (Upload & Parse → ... → Results). steps: [{label, icon}]; current index; connectors hairline.\ndefineProps({ steps: { type: Array, default: () => [] }, current: { type: Number, default: 0 }, style: Object })\n</script>\n<template>\n <div :style=\"{ display: 'flex', alignItems: 'flex-start', ...style }\">\n <template v-for=\"(s, i) in steps\" :key=\"s.label\">\n <div v-if=\"i > 0\" style=\"flex:1;height:2px;background:var(--track);margin-top:15.25px;margin-left:12px;margin-right:12px;min-width:24px\" />\n <div style=\"display:flex;flex-direction:column;align-items:center;gap:8px;flex-shrink:0\">\n <span :style=\"{ width: 32, height: 32, borderRadius: 'var(--radius-full)', boxSizing: 'border-box', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', background: i < current ? 'var(--success)' : i === current ? 'var(--action-primary)' : 'var(--bg-surface)', border: (i < current || i === current) ? 'none' : '2px solid var(--track)', color: (i < current || i === current) ? 'var(--on-accent)' : 'var(--text-muted)' }\">\n <Icon :name=\"i < current ? 'check' : (s.icon || 'file')\" :size=\"16\" />\n </span>\n <span :style=\"{ fontFamily: 'var(--font-sans)', fontSize: 'var(--text-xs)', lineHeight: 'var(--leading-xs)', letterSpacing: 'var(--tracking-xs)', fontWeight: i === current ? 'var(--weight-medium)' : 'var(--weight-regular)', color: i === current ? 'var(--text-heading)' : 'var(--text-secondary)', whiteSpace: 'nowrap' }\">{{ s.label }}</span>\n </div>\n </template>\n </div>\n</template>\n","feedback/Timeline.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\nconst tlTones = { success: \"var(--success)\", danger: \"var(--danger)\", warning: \"var(--warning)\", info: \"var(--action-primary)\", neutral: \"var(--dot-neutral)\" };\n\n/** Vertical activity timeline (SLDS pattern): icon nodes on a hairline spine, 13px/500 title + 12px meta + optional body, newest first. items: [{icon,tone,title,meta,body}]. */\nexport function Timeline({ items = [], style }) {\n return (\n <div style={{ display: \"flex\", flexDirection: \"column\", ...style }}>\n {items.map((it, i) => (\n <div key={i} style={{ display: \"flex\", gap: 12 }}>\n <div style={{ display: \"flex\", flexDirection: \"column\", alignItems: \"center\", flexShrink: 0 }}>\n <span style={{ width: 26, height: 26, borderRadius: \"var(--radius-full)\", background: \"var(--bg-surface)\", border: `1.5px solid ${tlTones[it.tone || \"neutral\"]}`, color: tlTones[it.tone || \"neutral\"], display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", boxSizing: \"border-box\", flexShrink: 0 }}>\n <Icon name={it.icon || \"clock\"} size={13} />\n </span>\n {i < items.length - 1 && <span style={{ width: 1.5, flex: 1, minHeight: 14, background: \"var(--border-control)\" }}></span>}\n </div>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: 2, paddingBottom: i < items.length - 1 ? 18 : 0, minWidth: 0, paddingTop: 3 }}>\n <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-base)\", lineHeight: \"var(--leading-base)\", letterSpacing: \"var(--tracking-base)\", color: \"var(--text-heading)\" }}>{it.title}</span>\n {it.meta && <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-xs)\", lineHeight: \"var(--leading-xs)\", letterSpacing: \"var(--tracking-xs)\", color: \"var(--text-muted)\" }}>{it.meta}</span>}\n {it.body && <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-sm)\", lineHeight: \"var(--leading-sm)\", color: \"var(--text-body)\", paddingTop: 4 }}>{it.body}</span>}\n </div>\n </div>\n ))}\n </div>\n );\n}\n","feedback/Timeline.vue":"<script setup>\nimport Icon from '../icons/Icon.vue'\nconst tlTones = { success: 'var(--success)', danger: 'var(--danger)', warning: 'var(--warning)', info: 'var(--action-primary)', neutral: 'var(--dot-neutral)' }\n// Vertical activity timeline: outlined icon nodes on a hairline spine. items: [{icon,tone,title,meta,body}].\ndefineProps({ items: { type: Array, default: () => [] }, style: Object })\n</script>\n<template>\n <div :style=\"{ display: 'flex', flexDirection: 'column', ...style }\">\n <div v-for=\"(it, i) in items\" :key=\"i\" style=\"display:flex;gap:12px\">\n <div style=\"display:flex;flex-direction:column;align-items:center;flex-shrink:0\">\n <span :style=\"{ width: '26px', height: '26px', borderRadius: 'var(--radius-full)', background: 'var(--bg-surface)', border: `1.5px solid ${tlTones[it.tone || 'neutral']}`, color: tlTones[it.tone || 'neutral'], display: 'inline-flex', alignItems: 'center', justifyContent: 'center', boxSizing: 'border-box' }\"><Icon :name=\"it.icon || 'clock'\" :size=\"13\" /></span>\n <span v-if=\"i < items.length - 1\" style=\"width:1.5px;flex:1;min-height:14px;background:var(--border-control)\" />\n </div>\n <div :style=\"{ display: 'flex', flexDirection: 'column', gap: '2px', paddingBottom: i < items.length - 1 ? '18px' : 0, minWidth: 0, paddingTop: '3px' }\">\n <span style=\"font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);color:var(--text-heading)\">{{ it.title }}</span>\n <span v-if=\"it.meta\" style=\"font-family:var(--font-sans);font-size:var(--text-xs);color:var(--text-muted)\">{{ it.meta }}</span>\n <span v-if=\"it.body\" style=\"font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-body);padding-top:4px\">{{ it.body }}</span>\n </div>\n </div>\n </div>\n</template>\n","feedback/DotActivity.vue":"<script setup>\nimport { computed } from 'vue'\nconst tonesMap = { success: 'var(--success)', warning: 'var(--warning)', danger: 'var(--danger)', info: 'var(--action-primary)', neutral: 'var(--dot-neutral)' }\nconst props = defineProps({ tone: { type: String, default: 'success' }, text: String, time: String, style: Object })\nconst dot = computed(() => tonesMap[props.tone])\n</script>\n<template>\n <div :style=\"{ display: 'flex', gap: 8, alignItems: 'flex-start', ...style }\">\n <span :style=\"{ width: 8, height: 8, borderRadius: 'var(--radius-full)', background: dot, marginTop: 5, flexShrink: 0 }\" />\n <div style=\"display:flex;flex-direction:column;min-width:0\">\n <span style=\"font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-heading);overflow:hidden;text-overflow:ellipsis;white-space:nowrap\">{{ text }}</span>\n <span style=\"font-family:var(--font-sans);font-size:var(--text-xs);line-height:var(--leading-xs);letter-spacing:var(--tracking-xs);color:var(--text-muted)\">{{ time }}</span>\n </div>\n </div>\n</template>\n","feedback/EmptyState.vue":"<script setup>\nimport Icon from '../icons/Icon.vue'\ndefineProps({ icon: { type: String, default: 'file-text' }, title: String, hint: String, style: Object })\n</script>\n<template>\n <div :style=\"{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 6, padding: '40px 24px', textAlign: 'center', ...style }\">\n <span style=\"color:var(--text-muted);margin-bottom:6px\"><Icon :name=\"icon\" :size=\"22\" :stroke-width=\"1.5\" /></span>\n <span style=\"font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);line-height:var(--leading-base);letter-spacing:var(--tracking-base);color:var(--text-heading)\">{{ title }}</span>\n <span v-if=\"hint\" style=\"font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-secondary);max-width:320px\">{{ hint }}</span>\n <span v-if=\"$slots.action\" style=\"padding-top:10px\"><slot name=\"action\" /></span>\n </div>\n</template>\n","feedback/LogViewer.vue":"<script setup>\ndefineProps({ lines: { type: Array, default: () => [] }, height: [Number, String], style: Object })\n</script>\n<template>\n <div :style=\"{ background: 'var(--bg-inset)', borderRadius: 'var(--radius-md)', border: '1px solid var(--border-subtle)', padding: '10px 12px', display: 'flex', flexDirection: 'column', gap: 6, overflowY: 'auto', height, boxSizing: 'border-box', ...style }\">\n <span v-for=\"(l, i) in lines\" :key=\"i\" :style=\"{ fontFamily: 'var(--font-mono)', fontSize: 'var(--text-xs)', lineHeight: 'var(--leading-xs)', color: typeof l === 'object' ? (l.color || 'var(--text-body)') : 'var(--text-body)', whiteSpace: 'pre' }\">{{ typeof l === 'object' ? l.text : l }}</span>\n </div>\n</template>\n","feedback/Skeleton.vue":"<script setup>\ndefineProps({ width: { type: [String, Number], default: '100%' }, height: { type: [String, Number], default: 12 }, radius: { type: String, default: 'var(--radius-sm)' }, style: Object })\n</script>\n<template>\n <span :style=\"{ display: 'block', width, height, borderRadius: radius, background: 'var(--skeleton)', animation: 'kb-pulse 1.4s var(--ease-in-out) infinite', ...style }\" />\n</template>\n","feedback/Spinner.vue":"<script setup>\ndefineProps({ size: { type: Number, default: 16 }, color: { type: String, default: 'var(--action-primary)' }, style: Object })\n</script>\n<template>\n <span :style=\"{ display: 'inline-block', width: size, height: size, borderRadius: 'var(--radius-full)', border: '2px solid var(--track)', borderTopColor: color, animation: 'kb-spin .7s linear infinite', ...style }\" />\n</template>\n","feedback/Toast.vue":"<script setup>\nimport { computed } from 'vue'\nimport Icon from '../icons/Icon.vue'\nimport IconButton from '../buttons/IconButton.vue'\nconst toastTones = { success: { icon: 'circle-check', color: 'var(--success-strong)' }, danger: { icon: 'triangle-alert', color: 'var(--danger)' }, warning: { icon: 'triangle-alert', color: 'var(--warning)' }, info: { icon: 'info', color: 'var(--action-primary)' } }\nconst props = defineProps({ tone: { type: String, default: 'success' }, title: String, meta: String, dismissible: Boolean, style: Object })\nconst emit = defineEmits(['close'])\nconst t = computed(() => toastTones[props.tone])\n</script>\n<template>\n <div :style=\"{ display: 'flex', alignItems: 'flex-start', gap: 10, minWidth: 280, maxWidth: 380, background: 'var(--bg-raised)', border: '1px solid var(--border-card)', borderRadius: 'var(--radius-lg)', boxShadow: 'var(--shadow-menu)', padding: '12px 12px 12px 14px', animation: 'kb-toast-in var(--dur) var(--ease-out)', ...style }\">\n <span :style=\"{ color: t.color, display: 'inline-flex', marginTop: 1 }\"><Icon :name=\"t.icon\" :size=\"16\" /></span>\n <div style=\"display:flex;flex-direction:column;flex:1;min-width:0\">\n <span style=\"font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);line-height:var(--leading-base);letter-spacing:var(--tracking-base);color:var(--text-heading)\">{{ title }}</span>\n <span v-if=\"meta\" style=\"font-family:var(--font-sans);font-size:var(--text-sm);line-height:var(--leading-sm);color:var(--text-secondary)\">{{ meta }}</span>\n </div>\n <IconButton v-if=\"dismissible\" icon=\"x\" size=\"xs\" muted label=\"Dismiss\" @click=\"emit('close')\" />\n </div>\n</template>\n"});
1
+ window.__KB_SRC__=Object.assign(window.__KB_SRC__||{},{"feedback/Accordion.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\n/** Generic collapsible section: chevron + title/subtitle header, body slot. Controlled via `open`+`onToggle`, or uncontrolled via `defaultOpen`. */\nexport function Accordion({ title, subtitle, defaultOpen, open: openProp, onToggle, children, style }) {\n const [openState, setOpenState] = React.useState(!!defaultOpen);\n const open = openProp != null ? openProp : openState;\n const toggle = () => (onToggle ? onToggle(!open) : setOpenState(!open));\n return (\n <div style={{ borderBottom: \"1px solid var(--border-card)\", ...style }}>\n <div onClick={toggle} data-wash=\"\" style={{ display: \"flex\", alignItems: \"center\", gap: 10, padding: \"14px 8px\", margin: \"0 -4px\", borderRadius: \"var(--radius-md)\", background: \"var(--wash, transparent)\", transition: \"var(--t-hover)\", cursor: \"pointer\" }}>\n <span style={{ color: \"var(--text-muted)\", display: \"inline-flex\", transform: open ? \"rotate(90deg)\" : \"none\", transition: \"transform var(--dur-fast) var(--ease-out)\" }}><Icon name=\"chevron-right\" size={14} /></span>\n <div style={{ display: \"flex\", flexDirection: \"column\", flex: 1 }}>\n <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-base)\", letterSpacing: \"var(--tracking-base)\", color: \"var(--text-heading)\" }}>{title}</span>\n {subtitle ? <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-xs)\", color: \"var(--text-secondary)\" }}>{subtitle}</span> : null}\n </div>\n </div>\n {open ? <div style={{ padding: \"0 4px 16px 28px\" }}>{children}</div> : null}\n </div>\n );\n}\n","feedback/Accordion.vue":"<script setup>\nimport { ref, computed } from 'vue'\nimport Icon from '../icons/Icon.vue'\n// Generic collapsible section: chevron + title/subtitle header, body slot. Controlled via `open` prop + `toggle` emit, or uncontrolled via `defaultOpen`.\nconst props = defineProps({ title: String, subtitle: String, defaultOpen: Boolean, open: { type: Boolean, default: null }, style: Object })\nconst emit = defineEmits(['toggle'])\nconst openState = ref(!!props.defaultOpen)\nconst open = computed(() => props.open != null ? props.open : openState.value)\nfunction toggle() { if (props.open != null) emit('toggle', !open.value); else openState.value = !openState.value }\n</script>\n<template>\n <div :style=\"{ borderBottom: '1px solid var(--border-card)', ...style }\">\n <div @click=\"toggle\" data-wash=\"\" style=\"display:flex;align-items:center;gap:10px;padding:14px 8px;margin:0 -4px;border-radius:var(--radius-md);background:var(--wash, transparent);transition:var(--t-hover);cursor:pointer\">\n <span :style=\"{ color: 'var(--text-muted)', display: 'inline-flex', transform: open ? 'rotate(90deg)' : 'none', transition: 'transform var(--dur-fast) var(--ease-out)' }\"><Icon name=\"chevron-right\" :size=\"14\" /></span>\n <div style=\"display:flex;flex-direction:column;flex:1\">\n <span style=\"font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);letter-spacing:var(--tracking-base);color:var(--text-heading)\">{{ title }}</span>\n <span v-if=\"subtitle\" style=\"font-family:var(--font-sans);font-size:var(--text-xs);color:var(--text-secondary)\">{{ subtitle }}</span>\n </div>\n </div>\n <div v-if=\"open\" style=\"padding:0 4px 16px 28px\"><slot /></div>\n </div>\n</template>\n","feedback/ActivityItem.jsx":"import React from \"react\";\nimport { IconTile } from \"../display/Badge.jsx\";\n\n/** Activity feed row: 32px icon tile + 14px/500 title + 12px meta + optional right text action (a text link: ink darkens on hover). */\nexport function ActivityItem({ icon = \"file-text\", title, meta, action, onAction, style }) {\n return (\n <div style={{ display: \"flex\", alignItems: \"center\", gap: 12, padding: \"12px 16px\", ...style }}>\n <IconTile icon={icon} kind=\"row\" style={{ background: \"var(--info-bg)\", border: \"none\", color: \"var(--info-fg)\" }} />\n <div style={{ display: \"flex\", flexDirection: \"column\", flex: 1, minWidth: 0 }}>\n <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-base)\", lineHeight: \"var(--leading-base)\", letterSpacing: \"var(--tracking-base)\", color: \"var(--text-heading)\" }}>{title}</span>\n {meta ? <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-xs)\", lineHeight: \"var(--leading-xs)\", color: \"var(--text-secondary)\" }}>{meta}</span> : null}\n </div>\n {action ? <button type=\"button\" onClick={onAction} data-ink=\"\" style={{ border: \"none\", background: \"transparent\", padding: 0, fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-base)\", lineHeight: \"var(--leading-base)\", letterSpacing: \"var(--tracking-base)\", color: \"var(--ink, var(--text-secondary))\", cursor: \"pointer\", whiteSpace: \"nowrap\" }}>{action}</button> : null}\n </div>\n );\n}\n\n/** Compact dot activity row (Recent Activities card): 8px status dot + 12px text + 11px time. */\nexport function DotActivity({ tone = \"success\", text, time, style }) {\n const tones = { success: \"var(--success)\", warning: \"var(--warning)\", danger: \"var(--danger)\", info: \"var(--action-primary)\", neutral: \"var(--dot-neutral)\" };\n return (\n <div style={{ display: \"flex\", gap: 8, alignItems: \"flex-start\", ...style }}>\n <span style={{ width: 8, height: 8, borderRadius: \"var(--radius-full)\", background: tones[tone], marginTop: 5, flexShrink: 0 }} />\n <div style={{ display: \"flex\", flexDirection: \"column\", minWidth: 0 }}>\n <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-base)\", lineHeight: \"var(--leading-base)\", color: \"var(--text-heading)\", overflow: \"hidden\", textOverflow: \"ellipsis\", whiteSpace: \"nowrap\" }}>{text}</span>\n <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-xs)\", lineHeight: \"var(--leading-xs)\", letterSpacing: \"var(--tracking-xs)\", color: \"var(--text-muted)\" }}>{time}</span>\n </div>\n </div>\n );\n}\n\n/** Mono log lines (\"[14:30] Task started: Import\") — Menlo 11px on inset panel. */\nexport function LogViewer({ lines = [], height, style }) {\n return (\n <div style={{ background: \"var(--bg-inset)\", borderRadius: \"var(--radius-md)\", border: \"1px solid var(--border-subtle)\", padding: \"10px 12px\", display: \"flex\", flexDirection: \"column\", gap: 6, overflowY: \"auto\", height, boxSizing: \"border-box\", ...style }}>\n {lines.map((l, i) => <span key={i} style={{ fontFamily: \"var(--font-mono)\", fontSize: \"var(--text-xs)\", lineHeight: \"var(--leading-xs)\", color: typeof l === \"object\" ? l.color || \"var(--text-body)\" : \"var(--text-body)\", whiteSpace: \"pre\" }}>{typeof l === \"object\" ? l.text : l}</span>)}\n </div>\n );\n}\n","feedback/ActivityItem.vue":"<script setup>\nimport IconTile from '../display/Badge.vue'\n// Activity feed row: 32px icon tile + 14px/500 title + 12px meta + optional right text action (emits `action`).\ndefineProps({ icon: { type: String, default: 'file-text' }, title: String, meta: String, action: String, style: Object })\nconst emit = defineEmits(['action'])\n</script>\n<template>\n <div :style=\"{ display: 'flex', alignItems: 'center', gap: '12px', padding: '12px 16px', ...style }\">\n <IconTile :icon=\"icon\" kind=\"row\" style=\"background:var(--info-bg);border:none;color:var(--info-fg)\" />\n <div style=\"display:flex;flex-direction:column;flex:1;min-width:0\">\n <span style=\"font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);line-height:var(--leading-base);letter-spacing:var(--tracking-base);color:var(--text-heading)\">{{ title }}</span>\n <span v-if=\"meta\" style=\"font-family:var(--font-sans);font-size:var(--text-xs);line-height:var(--leading-xs);color:var(--text-secondary)\">{{ meta }}</span>\n </div>\n <button v-if=\"action\" type=\"button\" data-ink=\"\" @click=\"emit('action')\" style=\"border:none;background:transparent;padding:0;font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);line-height:var(--leading-base);letter-spacing:var(--tracking-base);color:var(--ink, var(--text-secondary));cursor:pointer;white-space:nowrap\">{{ action }}</button>\n </div>\n</template>\n","feedback/Alert.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\nconst alertTones = {\n info: { bg: \"var(--info-bg)\", fg: \"var(--info-fg)\", icon: \"info\" },\n success: { bg: \"var(--success-bg)\", fg: \"var(--success-text)\", icon: \"circle-check\" },\n warning: { bg: \"var(--warning-bg)\", fg: \"var(--warning-text)\", icon: \"triangle-alert\" },\n danger: { bg: \"var(--danger-bg)\", fg: \"var(--danger-text)\", icon: \"triangle-alert\" },\n};\n\n/** Inline banner — tinted, no border; title and body at the base size. The icon is centred on the first text line, never on the block. */\nexport function Alert({ tone = \"info\", title, children, action, style }) {\n const t = alertTones[tone];\n return (\n <div style={{ display: \"flex\", gap: 10, alignItems: \"flex-start\", background: t.bg, borderRadius: \"var(--radius-md)\", padding: \"10px 12px\", ...style }}>\n <span style={{ color: t.fg, display: \"inline-flex\", alignItems: \"center\", flexShrink: 0, height: \"var(--leading-base)\" }}><Icon name={t.icon} size={15} /></span>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: 2, flex: 1, minWidth: 0 }}>\n {title ? <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-base)\", lineHeight: \"var(--leading-base)\", letterSpacing: \"var(--tracking-base)\", color: t.fg }}>{title}</span> : null}\n {children ? <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-base)\", lineHeight: \"var(--leading-base)\", color: \"var(--text-body)\" }}>{children}</span> : null}\n </div>\n {action}\n </div>\n );\n}\n\n/** Empty state — 22px muted glyph, 13px/500 title, 12px hint, optional action. */\nexport function EmptyState({ icon = \"file-text\", title, hint, action, style }) {\n return (\n <div style={{ display: \"flex\", flexDirection: \"column\", alignItems: \"center\", justifyContent: \"center\", gap: 6, padding: \"40px 24px\", textAlign: \"center\", ...style }}>\n <span style={{ color: \"var(--text-muted)\", marginBottom: 6 }}><Icon name={icon} size={22} strokeWidth={1.5} /></span>\n <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-base)\", lineHeight: \"var(--leading-base)\", letterSpacing: \"var(--tracking-base)\", color: \"var(--text-heading)\" }}>{title}</span>\n {hint ? <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-base)\", lineHeight: \"var(--leading-base)\", color: \"var(--text-secondary)\", maxWidth: 320 }}>{hint}</span> : null}\n {action ? <span style={{ paddingTop: 10 }}>{action}</span> : null}\n </div>\n );\n}\n\n/** Loading placeholder block. */\nexport function Skeleton({ width = \"100%\", height = 12, radius = \"var(--radius-sm)\", style }) {\n return <span style={{ display: \"block\", width, height, borderRadius: radius, background: \"var(--skeleton)\", animation: \"kb-pulse 1.4s var(--ease-in-out) infinite\", ...style }} />;\n}\n\n/** Indeterminate spinner (task-running affordance). */\nexport function Spinner({ size = 16, color = \"var(--action-primary)\", style }) {\n return (\n <span style={{ display: \"inline-block\", width: size, height: size, borderRadius: \"var(--radius-full)\", border: `2px solid var(--track)`, borderTopColor: color, animation: \"kb-spin .7s linear infinite\", ...style }} />\n );\n}\n","feedback/Alert.vue":"<script setup>\nimport { computed } from 'vue'\nimport Icon from '../icons/Icon.vue'\nconst alertTones = {\n info: { bg: 'var(--info-bg)', fg: 'var(--info-fg)', icon: 'info' },\n success: { bg: 'var(--success-bg)', fg: 'var(--success-text)', icon: 'circle-check' },\n warning: { bg: 'var(--warning-bg)', fg: 'var(--warning-text)', icon: 'triangle-alert' },\n danger: { bg: 'var(--danger-bg)', fg: 'var(--danger-text)', icon: 'triangle-alert' },\n}\n// Alert — inline banner — tinted, no border, 13px title + 12px body.\nconst props = defineProps({ tone: { type: String, default: 'info' }, title: String, style: Object })\nconst t = computed(() => alertTones[props.tone])\n</script>\n<template>\n <div :style=\"{ display: 'flex', gap: '10px', alignItems: 'flex-start', background: t.bg, borderRadius: 'var(--radius-md)', padding: '10px 12px', ...style }\">\n <span :style=\"{ color: t.fg, display: 'inline-flex', alignItems: 'center', flexShrink: 0, height: 'var(--leading-base)' }\"><Icon :name=\"t.icon\" :size=\"15\" /></span>\n <div style=\"display:flex;flex-direction:column;gap:2px;flex:1;min-width:0\">\n <span v-if=\"title\" :style=\"{ fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: 'var(--text-base)', lineHeight: 'var(--leading-base)', letterSpacing: 'var(--tracking-base)', color: t.fg }\">{{ title }}</span>\n <span style=\"font-family:var(--font-sans);font-size:var(--text-base);line-height:var(--leading-base);color:var(--text-body)\"><slot /></span>\n </div>\n <slot name=\"action\" />\n </div>\n</template>\n","feedback/Banner.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\nimport { IconButton } from \"../buttons/IconButton.jsx\";\n\nconst bannerTones = {\n info: { bg: \"var(--info-bg)\", fg: \"var(--info-fg)\", icon: \"info\" },\n success: { bg: \"var(--success-bg)\", fg: \"var(--success-text)\", icon: \"circle-check\" },\n warning: { bg: \"var(--warning-bg)\", fg: \"var(--warning-text)\", icon: \"triangle-alert\" },\n danger: { bg: \"var(--danger-bg)\", fg: \"var(--danger-text)\", icon: \"triangle-alert\" },\n};\n\n/** Page-level notification banner (Carbon/SLDS scoped notification): full-width tinted strip with icon, message, optional inline action link and dismiss ×. Distinct from Alert (inline, in-card) and Toast (floating). */\nexport function Banner({ tone = \"info\", title, action, onAction, dismissible, onDismiss, children, style }) {\n const t = bannerTones[tone];\n return (\n <div style={{ display: \"flex\", alignItems: \"flex-start\", gap: 10, background: t.bg, borderLeft: `3px solid ${t.fg}`, padding: \"10px 12px 10px 14px\", boxSizing: \"border-box\", ...style }}>\n <span style={{ color: t.fg, display: \"inline-flex\", alignItems: \"center\", flexShrink: 0, height: \"var(--leading-base)\" }}><Icon name={t.icon} size={16} /></span>\n <span style={{ flex: 1, minWidth: 0, fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-base)\", lineHeight: \"var(--leading-base)\", color: \"var(--text-body)\" }}>\n {title && <strong style={{ fontWeight: \"var(--weight-medium)\", color: t.fg, paddingRight: 6 }}>{title}</strong>}{children}\n </span>\n {action && <button type=\"button\" onClick={onAction} data-link=\"\" style={{ border: \"none\", background: \"transparent\", cursor: \"pointer\", padding: 0, height: \"var(--leading-base)\", fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-base)\", color: t.fg, textDecorationLine: \"underline\", textDecorationThickness: \"var(--link-thick, 1px)\", textUnderlineOffset: 2, whiteSpace: \"nowrap\", flexShrink: 0 }}>{action}</button>}\n {dismissible && <IconButton icon=\"x\" size=\"sm\" muted label=\"Dismiss\" onClick={onDismiss} style={{ margin: \"-2px 0\" }} />}\n </div>\n );\n}\n","feedback/Banner.vue":"<script setup>\nimport { computed } from 'vue'\nimport Icon from '../icons/Icon.vue'\nimport IconButton from '../buttons/IconButton.vue'\nconst bannerTones = {\n info: { bg: 'var(--info-bg)', fg: 'var(--info-fg)', icon: 'info' },\n success: { bg: 'var(--success-bg)', fg: 'var(--success-text)', icon: 'circle-check' },\n warning: { bg: 'var(--warning-bg)', fg: 'var(--warning-text)', icon: 'triangle-alert' },\n danger: { bg: 'var(--danger-bg)', fg: 'var(--danger-text)', icon: 'triangle-alert' },\n}\n// Page-level notification banner: tinted full-width strip, left accent bar, inline action link, dismiss ×.\nconst props = defineProps({ tone: { type: String, default: 'info' }, title: String, action: String, dismissible: Boolean, style: Object })\nconst emit = defineEmits(['action', 'dismiss'])\nconst t = computed(() => bannerTones[props.tone])\n</script>\n<template>\n <div :style=\"{ display: 'flex', alignItems: 'flex-start', gap: '10px', background: t.bg, borderLeft: `3px solid ${t.fg}`, padding: '10px 12px 10px 14px', boxSizing: 'border-box', ...style }\">\n <span :style=\"{ color: t.fg, display: 'inline-flex', alignItems: 'center', flexShrink: 0, height: 'var(--leading-base)' }\"><Icon :name=\"t.icon\" :size=\"16\" /></span>\n <span style=\"flex:1;min-width:0;font-family:var(--font-sans);font-size:var(--text-base);line-height:var(--leading-base);color:var(--text-body)\">\n <strong v-if=\"title\" :style=\"{ fontWeight: 'var(--weight-medium)', color: t.fg, paddingRight: '6px' }\">{{ title }}</strong><slot />\n </span>\n <button v-if=\"action\" type=\"button\" data-link=\"\" @click=\"emit('action')\" :style=\"{ border: 'none', background: 'transparent', cursor: 'pointer', padding: 0, height: 'var(--leading-base)', fontFamily: 'var(--font-sans)', fontWeight: 'var(--weight-medium)', fontSize: 'var(--text-base)', color: t.fg, textDecorationLine: 'underline', textDecorationThickness: 'var(--link-thick, 1px)', textUnderlineOffset: '2px', whiteSpace: 'nowrap', flexShrink: 0 }\">{{ action }}</button>\n <IconButton v-if=\"dismissible\" icon=\"x\" size=\"sm\" muted label=\"Dismiss\" :style=\"{ margin: '-2px 0' }\" @click=\"emit('dismiss')\" />\n </div>\n</template>\n","feedback/ContextMenu.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\n/** Right-click context menu (Preview / Duplicate / Delete). items: [{label, icon, danger, divider}]. Rows are control-height at the region's tier (a menu inherits the tier of the control that opened it); `size` overrides. */\nexport function ContextMenu({ items = [], onSelect, size, style }) {\n const [hover, setHover] = React.useState(-1);\n return (\n <div role=\"menu\" data-size={size} style={{ background: \"var(--bg-surface)\", borderRadius: \"var(--radius-lg)\", border: \"1px solid var(--border-card)\", boxShadow: \"var(--shadow-menu)\", padding: 4, minWidth: 148, display: \"inline-flex\", flexDirection: \"column\", ...style }}>\n {items.map((it, i) => it.divider ? <div key={i} style={{ height: 1, background: \"var(--border-subtle)\", margin: \"4px 0\" }} /> : (\n <button key={i} type=\"button\" onClick={() => onSelect && onSelect(it.label)}\n onMouseEnter={() => setHover(i)} onMouseLeave={() => setHover(-1)}\n role=\"menuitem\" style={{ display: \"flex\", alignItems: \"center\", gap: 10, height: \"var(--control-h)\", padding: \"0 10px\", boxSizing: \"border-box\", borderRadius: \"var(--radius-md)\", border: \"none\", cursor: \"pointer\", background: hover === i ? \"var(--bg-hover)\" : \"transparent\", fontFamily: \"var(--font-sans)\", fontSize: \"var(--control-text)\", lineHeight: \"var(--control-leading)\", letterSpacing: \"var(--control-tracking)\", color: it.danger ? \"var(--danger)\" : \"var(--text-heading)\", textAlign: \"left\", whiteSpace: \"nowrap\" }}>\n {it.icon ? <Icon name={it.icon} size={16} style={{ width: \"var(--control-icon)\", height: \"var(--control-icon)\" }} /> : null}{it.label}\n </button>\n ))}\n </div>\n );\n}\n","feedback/ContextMenu.vue":"<script setup>\nimport { ref } from 'vue'\nimport Icon from '../icons/Icon.vue'\n// Right-click context menu (Preview / Duplicate / Delete). items: [{label, icon, danger, divider}]. Rows are control-height at the region's tier (a menu inherits the tier of the control that opened it); `size` overrides.\ndefineProps({ items: { type: Array, default: () => [] }, size: String, style: Object })\nconst emit = defineEmits(['select'])\nconst hover = ref(-1)\n</script>\n<template>\n <div role=\"menu\" :data-size=\"size\" :style=\"{ background: 'var(--bg-surface)', borderRadius: 'var(--radius-lg)', border: '1px solid var(--border-card)', boxShadow: 'var(--shadow-menu)', padding: '4px', minWidth: '148px', display: 'inline-flex', flexDirection: 'column', ...style }\">\n <template v-for=\"(it, i) in items\" :key=\"i\">\n <div v-if=\"it.divider\" style=\"height:1px;background:var(--border-subtle);margin:4px 0\" />\n <button v-else type=\"button\" @click=\"emit('select', it.label)\" @mouseenter=\"hover = i\" @mouseleave=\"hover = -1\"\n role=\"menuitem\" :style=\"{ display: 'flex', alignItems: 'center', gap: '10px', height: 'var(--control-h)', padding: '0 10px', boxSizing: 'border-box', borderRadius: 'var(--radius-md)', border: 'none', cursor: 'pointer', background: hover === i ? 'var(--bg-hover)' : 'transparent', fontFamily: 'var(--font-sans)', fontSize: 'var(--control-text)', lineHeight: 'var(--control-leading)', letterSpacing: 'var(--control-tracking)', color: it.danger ? 'var(--danger)' : 'var(--text-heading)', textAlign: 'left', whiteSpace: 'nowrap' }\">\n <Icon v-if=\"it.icon\" :name=\"it.icon\" :size=\"16\" :style=\"{ width: 'var(--control-icon)', height: 'var(--control-icon)' }\" />{{ it.label }}\n </button>\n </template>\n </div>\n</template>\n","feedback/Drawer.jsx":"import React from \"react\";\nimport { IconButton } from \"../buttons/IconButton.jsx\";\n\n/** Right slide-out panel over a scrim: title + subtitle, scrollable body, right-aligned footer slot. inline=true renders it in-flow for embedding in cards/demos. A size region (default inherit → md: drawers are working surfaces, not dialogs); pass `size` to override. */\nexport function Drawer({ open = true, title, subtitle, width = 400, inline, height, size, footer, onClose, children, style }) {\n if (!open) return null;\n const panel = (\n <div role=\"dialog\" aria-label={title} data-size={size} style={{ width, maxWidth: \"100%\", height: inline ? (height || 420) : \"100%\", background: \"var(--bg-raised)\", borderLeft: \"1px solid var(--border-card)\", boxShadow: inline ? \"var(--shadow-menu)\" : \"-8px 0 24px rgba(0,0,0,.12)\", display: \"flex\", flexDirection: \"column\", boxSizing: \"border-box\", ...(inline ? { border: \"1px solid var(--border-card)\", borderRadius: \"var(--radius-lg)\" } : null), ...style }}>\n <div style={{ display: \"flex\", alignItems: \"flex-start\", justifyContent: \"space-between\", padding: \"16px 16px 12px\", borderBottom: \"1px solid var(--border-card)\", flexShrink: 0 }}>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: 3 }}>\n <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-semibold)\", fontSize: \"var(--text-md)\", lineHeight: \"var(--leading-md)\", letterSpacing: \"var(--tracking-md)\", color: \"var(--text-heading)\" }}>{title}</span>\n {subtitle && <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-xs)\", lineHeight: \"var(--leading-xs)\", color: \"var(--text-secondary)\" }}>{subtitle}</span>}\n </div>\n <IconButton icon=\"x\" label=\"Close\" onClick={onClose} style={{ margin: \"calc((var(--leading-md) - var(--control-h)) / 2) -6px 0 0\" }} />\n </div>\n <div style={{ flex: 1, overflowY: \"auto\", padding: 16 }}>{children}</div>\n {footer && <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: \"var(--control-gap)\", padding: \"12px 16px\", borderTop: \"1px solid var(--border-card)\", flexShrink: 0 }}>{footer}</div>}\n </div>\n );\n if (inline) return panel;\n return (\n <div onClick={onClose} style={{ position: \"fixed\", inset: 0, zIndex: \"var(--z-modal)\", background: \"var(--bg-scrim)\", display: \"flex\", justifyContent: \"flex-end\", animation: \"kb-fade-in var(--dur-fast) var(--ease-out)\" }}>\n <div onClick={(e) => e.stopPropagation()} style={{ height: \"100%\", animation: \"kb-slide-in var(--dur) var(--ease-out)\" }}>{panel}</div>\n </div>\n );\n}\n","feedback/Drawer.vue":"<script setup>\nimport { px } from '../px.js'\nimport IconButton from '../buttons/IconButton.vue'\n// Right slide-out panel over a scrim: title + subtitle, scrollable body, right-aligned footer slot.\n// inline=true renders it in-flow (cards, demos); otherwise it teleports to <body> over a scrim. Click the scrim or × to emit close.\n// A size region (default inherit → md: drawers are working surfaces, not dialogs); pass `size` to override.\nconst props = defineProps({ open: { type: Boolean, default: true }, title: String, subtitle: String, width: { type: [Number, String], default: 400 }, inline: Boolean, height: { type: [Number, String], default: 420 }, size: String, style: Object })\nconst emit = defineEmits(['close'])\nconst contents = { display: 'contents' }\n</script>\n<template>\n <Teleport to=\"body\" :disabled=\"inline\">\n <div v-if=\"open\" @click=\"!inline && emit('close')\"\n :style=\"inline ? contents : { position: 'fixed', inset: 0, zIndex: 'var(--z-modal)', background: 'var(--bg-scrim)', display: 'flex', justifyContent: 'flex-end', animation: 'kb-fade-in var(--dur-fast) var(--ease-out)' }\">\n <div @click.stop :style=\"inline ? contents : { height: '100%', animation: 'kb-slide-in var(--dur) var(--ease-out)' }\">\n <div role=\"dialog\" :aria-label=\"title\" :data-size=\"size\"\n :style=\"{ width: px(width), maxWidth: '100%', height: inline ? px(height) : '100%', background: 'var(--bg-raised)', borderLeft: '1px solid var(--border-card)', boxShadow: inline ? 'var(--shadow-menu)' : '-8px 0 24px rgba(0,0,0,.12)', display: 'flex', flexDirection: 'column', boxSizing: 'border-box', ...(inline ? { border: '1px solid var(--border-card)', borderRadius: 'var(--radius-lg)' } : null), ...style }\">\n <div style=\"display:flex;align-items:flex-start;justify-content:space-between;padding:16px 16px 12px;border-bottom:1px solid var(--border-card);flex-shrink:0\">\n <div style=\"display:flex;flex-direction:column;gap:3px\">\n <span style=\"font-family:var(--font-sans);font-weight:var(--weight-semibold);font-size:var(--text-md);line-height:var(--leading-md);letter-spacing:var(--tracking-md);color:var(--text-heading)\">{{ title }}</span>\n <span v-if=\"subtitle\" style=\"font-family:var(--font-sans);font-size:var(--text-xs);line-height:var(--leading-xs);color:var(--text-secondary)\">{{ subtitle }}</span>\n </div>\n <IconButton icon=\"x\" label=\"Close\" :style=\"{ margin: 'calc((var(--leading-md) - var(--control-h)) / 2) -6px 0 0' }\" @click=\"emit('close')\" />\n </div>\n <div style=\"flex:1;overflow-y:auto;padding:16px\"><slot /></div>\n <div v-if=\"$slots.footer\" style=\"display:flex;justify-content:flex-end;gap:var(--control-gap);padding:12px 16px;border-top:1px solid var(--border-card);flex-shrink:0\"><slot name=\"footer\" /></div>\n </div>\n </div>\n </div>\n </Teleport>\n</template>\n","feedback/Menu.jsx":"import React from \"react\";\nimport { ContextMenu } from \"./ContextMenu.jsx\";\n\n/** Click-to-open dropdown menu anchored under its trigger (vs. ContextMenu, which opens at a click point). Closes on outside click. */\nexport function Menu({ trigger, items = [], onSelect, align = \"start\", style }) {\n const [open, setOpen] = React.useState(false);\n const ref = React.useRef(null);\n React.useEffect(() => {\n if (!open) return;\n const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };\n document.addEventListener(\"mousedown\", onDoc);\n return () => document.removeEventListener(\"mousedown\", onDoc);\n }, [open]);\n return (\n <div ref={ref} style={{ position: \"relative\", display: \"inline-flex\", ...style }}>\n <span onClick={() => setOpen((o) => !o)}>{trigger}</span>\n {open ? (\n <div style={{ position: \"absolute\", top: \"calc(100% + 6px)\", [align === \"end\" ? \"right\" : \"left\"]: 0, zIndex: \"var(--z-menu)\" }}>\n <ContextMenu items={items} onSelect={(l) => { setOpen(false); onSelect && onSelect(l); }} />\n </div>\n ) : null}\n </div>\n );\n}\n","feedback/Menu.vue":"<script setup>\nimport { ref, onMounted, onUnmounted } from 'vue'\nimport ContextMenu from './ContextMenu.vue'\n// Click-to-open dropdown menu anchored under its trigger (vs. ContextMenu, which opens at a click point). Closes on outside click.\nconst props = defineProps({ items: { type: Array, default: () => [] }, align: { type: String, default: 'start' }, style: Object })\nconst emit = defineEmits(['select'])\nconst open = ref(false)\nconst root = ref(null)\nfunction onDoc(e) { if (root.value && !root.value.contains(e.target)) open.value = false }\nonMounted(() => document.addEventListener('mousedown', onDoc))\nonUnmounted(() => document.removeEventListener('mousedown', onDoc))\nfunction select(l) { open.value = false; emit('select', l) }\n</script>\n<template>\n <div ref=\"root\" :style=\"{ position: 'relative', display: 'inline-flex', ...style }\">\n <span @click=\"open = !open\"><slot name=\"trigger\" /></span>\n <div v-if=\"open\" :style=\"{ position: 'absolute', top: 'calc(100% + 6px)', [align === 'end' ? 'right' : 'left']: 0, zIndex: 'var(--z-menu)' }\">\n <ContextMenu :items=\"items\" @select=\"select\" />\n </div>\n </div>\n</template>\n","feedback/Modal.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\nimport { IconButton } from \"../buttons/IconButton.jsx\";\n\n/** Hover tooltip — 11px, inverted surface, 6px offset. Wraps its children. */\nexport function Tooltip({ label, placement = \"top\", children, style }) {\n const [show, setShow] = React.useState(false);\n const pos = {\n top: { bottom: \"100%\", left: \"50%\", transform: \"translateX(-50%)\", marginBottom: 6 },\n bottom: { top: \"100%\", left: \"50%\", transform: \"translateX(-50%)\", marginTop: 6 },\n left: { right: \"100%\", top: \"50%\", transform: \"translateY(-50%)\", marginRight: 6 },\n right: { left: \"100%\", top: \"50%\", transform: \"translateY(-50%)\", marginLeft: 6 },\n }[placement];\n return (\n <span style={{ position: \"relative\", display: \"inline-flex\", ...style }} onMouseEnter={() => setShow(true)} onMouseLeave={() => setShow(false)}>\n {children}\n {show ? (\n <span style={{ position: \"absolute\", zIndex: \"var(--z-tooltip)\", whiteSpace: \"nowrap\", background: \"var(--tooltip-bg)\", color: \"var(--tooltip-fg)\", borderRadius: \"var(--radius-md)\", padding: \"5px 8px\", fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-xs)\", lineHeight: \"var(--leading-xs)\", letterSpacing: \"var(--tracking-xs)\", boxShadow: \"var(--shadow-menu)\", pointerEvents: \"none\", animation: \"kb-fade-in var(--dur-fast) var(--ease-out)\", ...pos }}>{label}</span>\n ) : null}\n </span>\n );\n}\n\n/** Centered modal dialog with scrim. width defaults to 440. */\n/** Modal dialog. A size region that defaults to lg (36px controls) whatever the page behind it uses — a dialog does not compete for space; pass `size` to override. 24px padding, footer buttons 8px apart. */\nexport function Modal({ open, title, subtitle, onClose, footer, width = 440, size = \"lg\", children, style }) {\n if (!open) return null;\n return (\n <div style={{ position: \"fixed\", inset: 0, zIndex: \"var(--z-modal)\", background: \"var(--bg-scrim)\", display: \"flex\", alignItems: \"center\", justifyContent: \"center\", animation: \"kb-fade-in var(--dur-fast) var(--ease-out)\" }} onClick={onClose}>\n <div role=\"dialog\" aria-modal=\"true\" data-size={size} onClick={(e) => e.stopPropagation()}\n style={{ width, maxWidth: \"92vw\", background: \"var(--bg-raised)\", borderRadius: \"var(--radius-lg)\", border: \"1px solid var(--border-card)\", boxShadow: \"var(--shadow-modal)\", animation: \"kb-rise var(--dur) var(--ease-out)\", ...style }}>\n <div style={{ display: \"flex\", alignItems: \"flex-start\", justifyContent: \"space-between\", gap: 16, padding: \"24px 24px 0\" }}>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: 2 }}>\n <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-semibold)\", fontSize: \"var(--text-md)\", lineHeight: \"var(--leading-md)\", letterSpacing: \"var(--tracking-md)\", color: \"var(--text-heading)\" }}>{title}</span>\n {subtitle ? <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-xs)\", lineHeight: \"var(--leading-xs)\", color: \"var(--text-secondary)\" }}>{subtitle}</span> : null}\n </div>\n <IconButton icon=\"x\" onClick={onClose} label=\"Close\" style={{ margin: \"calc((var(--leading-md) - var(--control-h)) / 2) -6px 0 0\" }} />\n </div>\n {children ? <div style={{ padding: \"20px 24px 0\" }}>{children}</div> : null}\n <div style={{ display: \"flex\", justifyContent: \"flex-end\", gap: \"var(--control-gap)\", padding: 24 }}>{footer}</div>\n </div>\n </div>\n );\n}\n\nconst toastTones = { success: { icon: \"circle-check\", color: \"var(--success-strong)\" }, danger: { icon: \"triangle-alert\", color: \"var(--danger)\" }, warning: { icon: \"triangle-alert\", color: \"var(--warning)\" }, info: { icon: \"info\", color: \"var(--action-primary)\" } };\n\n/** Single toast row. */\nexport function Toast({ tone = \"success\", title, meta, onClose, style }) {\n const t = toastTones[tone];\n return (\n <div style={{ display: \"flex\", alignItems: \"flex-start\", gap: 10, minWidth: 280, maxWidth: 380, background: \"var(--bg-raised)\", border: \"1px solid var(--border-card)\", borderRadius: \"var(--radius-lg)\", boxShadow: \"var(--shadow-menu)\", padding: \"12px 12px 12px 14px\", animation: \"kb-toast-in var(--dur) var(--ease-out)\", ...style }}>\n <span style={{ color: t.color, display: \"inline-flex\", alignItems: \"center\", flexShrink: 0, height: \"var(--leading-base)\" }}><Icon name={t.icon} size={16} /></span>\n <div style={{ display: \"flex\", flexDirection: \"column\", flex: 1, minWidth: 0 }}>\n <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-base)\", lineHeight: \"var(--leading-base)\", letterSpacing: \"var(--tracking-base)\", color: \"var(--text-heading)\" }}>{title}</span>\n {meta ? <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-xs)\", lineHeight: \"var(--leading-xs)\", color: \"var(--text-secondary)\" }}>{meta}</span> : null}\n </div>\n {onClose ? <IconButton icon=\"x\" size=\"sm\" muted onClick={onClose} label=\"Dismiss\" style={{ margin: \"-2px 0\" }} /> : null}\n </div>\n );\n}\n\n/** Fixed bottom-right stack of toasts. */\nexport function ToastStack({ toasts = [], onDismiss, style }) {\n return (\n <div style={{ position: \"fixed\", right: 24, bottom: 24, zIndex: \"var(--z-toast)\", display: \"flex\", flexDirection: \"column\", gap: 8, ...style }}>\n {toasts.map((t) => <Toast key={t.id} tone={t.tone} title={t.title} meta={t.meta} onClose={() => onDismiss && onDismiss(t.id)} />)}\n </div>\n );\n}\n","feedback/Modal.vue":"<script setup>\nimport { px } from '../px.js'\nimport IconButton from '../buttons/IconButton.vue'\n// Modal dialog. A size region that defaults to lg (36px controls) whatever the page behind it uses — a dialog does not compete for space; pass `size` to override. 24px padding, footer buttons 8px apart.\ndefineProps({ open: Boolean, title: String, subtitle: String, width: { type: [Number, String], default: 440 }, size: { type: String, default: 'lg' }, style: Object })\nconst emit = defineEmits(['close'])\n</script>\n<template>\n <div v-if=\"open\" style=\"position:fixed;inset:0;z-index:var(--z-modal);background:var(--bg-scrim);display:flex;align-items:center;justify-content:center;animation:kb-fade-in var(--dur-fast) var(--ease-out)\" @click=\"emit('close')\">\n <div role=\"dialog\" aria-modal=\"true\" :data-size=\"size\" @click.stop :style=\"{ width: px(width), maxWidth: '92vw', background: 'var(--bg-raised)', borderRadius: 'var(--radius-lg)', border: '1px solid var(--border-card)', boxShadow: 'var(--shadow-modal)', animation: 'kb-rise var(--dur) var(--ease-out)', ...style }\">\n <div style=\"display:flex;align-items:flex-start;justify-content:space-between;gap:16px;padding:24px 24px 0\">\n <div style=\"display:flex;flex-direction:column;gap:2px\">\n <span style=\"font-family:var(--font-sans);font-weight:var(--weight-semibold);font-size:var(--text-md);line-height:var(--leading-md);letter-spacing:var(--tracking-md);color:var(--text-heading)\">{{ title }}</span>\n <span v-if=\"subtitle\" style=\"font-family:var(--font-sans);font-size:var(--text-xs);line-height:var(--leading-xs);color:var(--text-secondary)\">{{ subtitle }}</span>\n </div>\n <IconButton icon=\"x\" label=\"Close\" :style=\"{ margin: 'calc((var(--leading-md) - var(--control-h)) / 2) -6px 0 0' }\" @click=\"emit('close')\" />\n </div>\n <div v-if=\"$slots.default\" style=\"padding:20px 24px 0\"><slot /></div>\n <div style=\"display:flex;justify-content:flex-end;gap:var(--control-gap);padding:24px\"><slot name=\"footer\" /></div>\n </div>\n </div>\n</template>\n","feedback/Stepper.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\n/** Wizard stepper (Upload & Parse → ... → Results). steps: [{label, icon}]; current index; connectors hairline #e5e5e5. */\nexport function Stepper({ steps = [], current = 0, style }) {\n return (\n <div style={{ display: \"flex\", alignItems: \"flex-start\", ...style }}>\n {steps.map((s, i) => {\n const done = i < current, is = i === current;\n return (\n <React.Fragment key={s.label}>\n {i > 0 ? <div style={{ flex: 1, height: 2, background: \"var(--track)\", marginTop: 15.25, marginLeft: 12, marginRight: 12, minWidth: 24 }} /> : null}\n <div style={{ display: \"flex\", flexDirection: \"column\", alignItems: \"center\", gap: 8, flexShrink: 0 }}>\n <span style={{ width: 32, height: 32, borderRadius: \"var(--radius-full)\", boxSizing: \"border-box\", display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", background: done ? \"var(--success)\" : is ? \"var(--action-primary)\" : \"var(--bg-surface)\", border: done || is ? \"none\" : \"2px solid var(--track)\", color: done || is ? \"var(--on-accent)\" : \"var(--text-muted)\" }}>\n <Icon name={done ? \"check\" : s.icon || \"file\"} size={16} />\n </span>\n <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-xs)\", lineHeight: \"var(--leading-xs)\", letterSpacing: \"var(--tracking-xs)\", fontWeight: is ? \"var(--weight-medium)\" : \"var(--weight-regular)\", color: is ? \"var(--text-heading)\" : \"var(--text-secondary)\", whiteSpace: \"nowrap\" }}>{s.label}</span>\n </div>\n </React.Fragment>\n );\n })}\n </div>\n );\n}\n","feedback/Stepper.vue":"<script setup>\nimport Icon from '../icons/Icon.vue'\n// Wizard stepper (Upload & Parse → ... → Results). steps: [{label, icon}]; current index; connectors hairline.\ndefineProps({ steps: { type: Array, default: () => [] }, current: { type: Number, default: 0 }, style: Object })\n</script>\n<template>\n <div :style=\"{ display: 'flex', alignItems: 'flex-start', ...style }\">\n <template v-for=\"(s, i) in steps\" :key=\"s.label\">\n <div v-if=\"i > 0\" style=\"flex:1;height:2px;background:var(--track);margin-top:15.25px;margin-left:12px;margin-right:12px;min-width:24px\" />\n <div style=\"display:flex;flex-direction:column;align-items:center;gap:8px;flex-shrink:0\">\n <span :style=\"{ width: '32px', height: '32px', borderRadius: 'var(--radius-full)', boxSizing: 'border-box', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', background: i < current ? 'var(--success)' : i === current ? 'var(--action-primary)' : 'var(--bg-surface)', border: (i < current || i === current) ? 'none' : '2px solid var(--track)', color: (i < current || i === current) ? 'var(--on-accent)' : 'var(--text-muted)' }\">\n <Icon :name=\"i < current ? 'check' : (s.icon || 'file')\" :size=\"16\" />\n </span>\n <span :style=\"{ fontFamily: 'var(--font-sans)', fontSize: 'var(--text-xs)', lineHeight: 'var(--leading-xs)', letterSpacing: 'var(--tracking-xs)', fontWeight: i === current ? 'var(--weight-medium)' : 'var(--weight-regular)', color: i === current ? 'var(--text-heading)' : 'var(--text-secondary)', whiteSpace: 'nowrap' }\">{{ s.label }}</span>\n </div>\n </template>\n </div>\n</template>\n","feedback/Timeline.jsx":"import React from \"react\";\nimport { Icon } from \"../icons/Icon.jsx\";\n\nconst tlTones = { success: \"var(--success)\", danger: \"var(--danger)\", warning: \"var(--warning)\", info: \"var(--action-primary)\", neutral: \"var(--dot-neutral)\" };\n\n/** Vertical activity timeline (SLDS pattern): icon nodes on a hairline spine, 13px/500 title + 12px meta + optional body, newest first. items: [{icon,tone,title,meta,body}]. */\nexport function Timeline({ items = [], style }) {\n return (\n <div style={{ display: \"flex\", flexDirection: \"column\", ...style }}>\n {items.map((it, i) => (\n <div key={i} style={{ display: \"flex\", gap: 12 }}>\n <div style={{ display: \"flex\", flexDirection: \"column\", alignItems: \"center\", flexShrink: 0 }}>\n <span style={{ width: 26, height: 26, borderRadius: \"var(--radius-full)\", background: \"var(--bg-surface)\", border: `1.5px solid ${tlTones[it.tone || \"neutral\"]}`, color: tlTones[it.tone || \"neutral\"], display: \"inline-flex\", alignItems: \"center\", justifyContent: \"center\", boxSizing: \"border-box\", flexShrink: 0 }}>\n <Icon name={it.icon || \"clock\"} size={13} />\n </span>\n {i < items.length - 1 && <span style={{ width: 1.5, flex: 1, minHeight: 14, background: \"var(--border-control)\" }}></span>}\n </div>\n <div style={{ display: \"flex\", flexDirection: \"column\", gap: 2, paddingBottom: i < items.length - 1 ? 18 : 0, minWidth: 0, paddingTop: 3 }}>\n <span style={{ fontFamily: \"var(--font-sans)\", fontWeight: \"var(--weight-medium)\", fontSize: \"var(--text-base)\", lineHeight: \"var(--leading-base)\", letterSpacing: \"var(--tracking-base)\", color: \"var(--text-heading)\" }}>{it.title}</span>\n {it.meta && <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-xs)\", lineHeight: \"var(--leading-xs)\", letterSpacing: \"var(--tracking-xs)\", color: \"var(--text-muted)\" }}>{it.meta}</span>}\n {it.body && <span style={{ fontFamily: \"var(--font-sans)\", fontSize: \"var(--text-base)\", lineHeight: \"var(--leading-base)\", color: \"var(--text-body)\", paddingTop: 4 }}>{it.body}</span>}\n </div>\n </div>\n ))}\n </div>\n );\n}\n","feedback/Timeline.vue":"<script setup>\nimport Icon from '../icons/Icon.vue'\nconst tlTones = { success: 'var(--success)', danger: 'var(--danger)', warning: 'var(--warning)', info: 'var(--action-primary)', neutral: 'var(--dot-neutral)' }\n// Vertical activity timeline: outlined icon nodes on a hairline spine. items: [{icon,tone,title,meta,body}].\ndefineProps({ items: { type: Array, default: () => [] }, style: Object })\n</script>\n<template>\n <div :style=\"{ display: 'flex', flexDirection: 'column', ...style }\">\n <div v-for=\"(it, i) in items\" :key=\"i\" style=\"display:flex;gap:12px\">\n <div style=\"display:flex;flex-direction:column;align-items:center;flex-shrink:0\">\n <span :style=\"{ width: '26px', height: '26px', borderRadius: 'var(--radius-full)', background: 'var(--bg-surface)', border: `1.5px solid ${tlTones[it.tone || 'neutral']}`, color: tlTones[it.tone || 'neutral'], display: 'inline-flex', alignItems: 'center', justifyContent: 'center', boxSizing: 'border-box' }\"><Icon :name=\"it.icon || 'clock'\" :size=\"13\" /></span>\n <span v-if=\"i < items.length - 1\" style=\"width:1.5px;flex:1;min-height:14px;background:var(--border-control)\" />\n </div>\n <div :style=\"{ display: 'flex', flexDirection: 'column', gap: '2px', paddingBottom: i < items.length - 1 ? '18px' : 0, minWidth: 0, paddingTop: '3px' }\">\n <span style=\"font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);color:var(--text-heading)\">{{ it.title }}</span>\n <span v-if=\"it.meta\" style=\"font-family:var(--font-sans);font-size:var(--text-xs);color:var(--text-muted)\">{{ it.meta }}</span>\n <span v-if=\"it.body\" style=\"font-family:var(--font-sans);font-size:var(--text-base);line-height:var(--leading-base);color:var(--text-body);padding-top:4px\">{{ it.body }}</span>\n </div>\n </div>\n </div>\n</template>\n","feedback/DotActivity.vue":"<script setup>\nimport { computed } from 'vue'\nconst tonesMap = { success: 'var(--success)', warning: 'var(--warning)', danger: 'var(--danger)', info: 'var(--action-primary)', neutral: 'var(--dot-neutral)' }\nconst props = defineProps({ tone: { type: String, default: 'success' }, text: String, time: String, style: Object })\nconst dot = computed(() => tonesMap[props.tone])\n</script>\n<template>\n <div :style=\"{ display: 'flex', gap: '8px', alignItems: 'flex-start', ...style }\">\n <span :style=\"{ width: '8px', height: '8px', borderRadius: 'var(--radius-full)', background: dot, marginTop: '5px', flexShrink: 0 }\" />\n <div style=\"display:flex;flex-direction:column;min-width:0\">\n <span style=\"font-family:var(--font-sans);font-size:var(--text-base);line-height:var(--leading-base);color:var(--text-heading);overflow:hidden;text-overflow:ellipsis;white-space:nowrap\">{{ text }}</span>\n <span style=\"font-family:var(--font-sans);font-size:var(--text-xs);line-height:var(--leading-xs);letter-spacing:var(--tracking-xs);color:var(--text-muted)\">{{ time }}</span>\n </div>\n </div>\n</template>\n","feedback/EmptyState.vue":"<script setup>\nimport Icon from '../icons/Icon.vue'\ndefineProps({ icon: { type: String, default: 'file-text' }, title: String, hint: String, style: Object })\n</script>\n<template>\n <div :style=\"{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: '6px', padding: '40px 24px', textAlign: 'center', ...style }\">\n <span style=\"color:var(--text-muted);margin-bottom:6px\"><Icon :name=\"icon\" :size=\"22\" :stroke-width=\"1.5\" /></span>\n <span style=\"font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);line-height:var(--leading-base);letter-spacing:var(--tracking-base);color:var(--text-heading)\">{{ title }}</span>\n <span v-if=\"hint\" style=\"font-family:var(--font-sans);font-size:var(--text-base);line-height:var(--leading-base);color:var(--text-secondary);max-width:320px\">{{ hint }}</span>\n <span v-if=\"$slots.action\" style=\"padding-top:10px\"><slot name=\"action\" /></span>\n </div>\n</template>\n","feedback/LogViewer.vue":"<script setup>\ndefineProps({ lines: { type: Array, default: () => [] }, height: [Number, String], style: Object })\n</script>\n<template>\n <div :style=\"{ background: 'var(--bg-inset)', borderRadius: 'var(--radius-md)', border: '1px solid var(--border-subtle)', padding: '10px 12px', display: 'flex', flexDirection: 'column', gap: '6px', overflowY: 'auto', height, boxSizing: 'border-box', ...style }\">\n <span v-for=\"(l, i) in lines\" :key=\"i\" :style=\"{ fontFamily: 'var(--font-mono)', fontSize: 'var(--text-xs)', lineHeight: 'var(--leading-xs)', color: typeof l === 'object' ? (l.color || 'var(--text-body)') : 'var(--text-body)', whiteSpace: 'pre' }\">{{ typeof l === 'object' ? l.text : l }}</span>\n </div>\n</template>\n","feedback/Skeleton.vue":"<script setup>\nimport { px } from '../px.js'\ndefineProps({ width: { type: [String, Number], default: '100%' }, height: { type: [String, Number], default: 12 }, radius: { type: String, default: 'var(--radius-sm)' }, style: Object })\n</script>\n<template>\n <span :style=\"{ display: 'block', width, height, borderRadius: px(radius), background: 'var(--skeleton)', animation: 'kb-pulse 1.4s var(--ease-in-out) infinite', ...style }\" />\n</template>\n","feedback/Spinner.vue":"<script setup>\nimport { px } from '../px.js'\ndefineProps({ size: { type: Number, default: 16 }, color: { type: String, default: 'var(--action-primary)' }, style: Object })\n</script>\n<template>\n <span :style=\"{ display: 'inline-block', width: px(size), height: px(size), borderRadius: 'var(--radius-full)', border: '2px solid var(--track)', borderTopColor: color, animation: 'kb-spin .7s linear infinite', ...style }\" />\n</template>\n","feedback/Toast.vue":"<script setup>\nimport { computed } from 'vue'\nimport Icon from '../icons/Icon.vue'\nimport IconButton from '../buttons/IconButton.vue'\nconst toastTones = { success: { icon: 'circle-check', color: 'var(--success-strong)' }, danger: { icon: 'triangle-alert', color: 'var(--danger)' }, warning: { icon: 'triangle-alert', color: 'var(--warning)' }, info: { icon: 'info', color: 'var(--action-primary)' } }\nconst props = defineProps({ tone: { type: String, default: 'success' }, title: String, meta: String, dismissible: Boolean, style: Object })\nconst emit = defineEmits(['close'])\nconst t = computed(() => toastTones[props.tone])\n</script>\n<template>\n <div :style=\"{ display: 'flex', alignItems: 'flex-start', gap: '10px', minWidth: '280px', maxWidth: '380px', background: 'var(--bg-raised)', border: '1px solid var(--border-card)', borderRadius: 'var(--radius-lg)', boxShadow: 'var(--shadow-menu)', padding: '12px 12px 12px 14px', animation: 'kb-toast-in var(--dur) var(--ease-out)', ...style }\">\n <span :style=\"{ color: t.color, display: 'inline-flex', alignItems: 'center', flexShrink: 0, height: 'var(--leading-base)' }\"><Icon :name=\"t.icon\" :size=\"16\" /></span>\n <div style=\"display:flex;flex-direction:column;flex:1;min-width:0\">\n <span style=\"font-family:var(--font-sans);font-weight:var(--weight-medium);font-size:var(--text-base);line-height:var(--leading-base);letter-spacing:var(--tracking-base);color:var(--text-heading)\">{{ title }}</span>\n <span v-if=\"meta\" style=\"font-family:var(--font-sans);font-size:var(--text-xs);line-height:var(--leading-xs);color:var(--text-secondary)\">{{ meta }}</span>\n </div>\n <IconButton v-if=\"dismissible\" icon=\"x\" size=\"sm\" muted label=\"Dismiss\" :style=\"{ margin: '-2px 0' }\" @click=\"emit('close')\" />\n </div>\n</template>\n","feedback/ToastStack.vue":"<script setup>\nimport Toast from './Toast.vue'\n// Fixed bottom-right stack of toasts. toasts: [{ id, tone, title, meta }]. Emits dismiss(id).\ndefineProps({ toasts: { type: Array, default: () => [] }, style: Object })\nconst emit = defineEmits(['dismiss'])\n</script>\n<template>\n <div :style=\"{ position: 'fixed', right: '24px', bottom: '24px', zIndex: 'var(--z-toast)', display: 'flex', flexDirection: 'column', gap: '8px', ...style }\">\n <Toast v-for=\"t in toasts\" :key=\"t.id\" :tone=\"t.tone\" :title=\"t.title\" :meta=\"t.meta\" dismissible @close=\"emit('dismiss', t.id)\" />\n </div>\n</template>\n","feedback/Tooltip.vue":"<script setup>\nimport { ref, computed } from 'vue'\n// Hover tooltip around any inline content. placement: top | bottom | left | right. Dark pill, 11px, fades in.\nconst props = defineProps({ label: String, placement: { type: String, default: 'top' }, style: Object })\nconst show = ref(false)\nconst POS = {\n top: { bottom: '100%', left: '50%', transform: 'translateX(-50%)', marginBottom: '6px' },\n bottom: { top: '100%', left: '50%', transform: 'translateX(-50%)', marginTop: '6px' },\n left: { right: '100%', top: '50%', transform: 'translateY(-50%)', marginRight: '6px' },\n right: { left: '100%', top: '50%', transform: 'translateY(-50%)', marginLeft: '6px' }\n}\nconst pos = computed(() => POS[props.placement] || POS.top)\n</script>\n<template>\n <span :style=\"{ position: 'relative', display: 'inline-flex', ...style }\" @mouseenter=\"show = true\" @mouseleave=\"show = false\">\n <slot />\n <span v-if=\"show\" role=\"tooltip\" :style=\"{ position: 'absolute', zIndex: 'var(--z-tooltip)', whiteSpace: 'nowrap', background: 'var(--tooltip-bg)', color: 'var(--tooltip-fg)', borderRadius: 'var(--radius-md)', padding: '5px 8px', fontFamily: 'var(--font-sans)', fontSize: 'var(--text-xs)', lineHeight: 'var(--leading-xs)', letterSpacing: 'var(--tracking-xs)', boxShadow: 'var(--shadow-menu)', pointerEvents: 'none', animation: 'kb-fade-in var(--dur-fast) var(--ease-out)', ...pos }\">{{ label }}</span>\n </span>\n</template>\n"});