@cyberskill/design 1.2.0 → 1.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (232) hide show
  1. package/DESIGN.md +11 -9
  2. package/README.md +28 -24
  3. package/SKILL.md +18 -9
  4. package/THIRD-PARTY-NOTICES.md +35 -0
  5. package/VERSION +1 -1
  6. package/_ds_bundle.js +701 -1342
  7. package/_esm/cs.mjs +2 -2
  8. package/_esm/react.mjs +6 -1
  9. package/_vendor/NOTICE.md +16 -0
  10. package/_vendor/babel/README.md +7 -0
  11. package/_vendor/babel/babel.min.js +4 -0
  12. package/_vendor/react/README.md +3 -0
  13. package/_vendor/react/react-dom.production.min.js +1 -0
  14. package/_vendor/react/react.production.min.js +1 -0
  15. package/assets/aurora-hoa.webp +0 -0
  16. package/assets/aurora-kim.webp +0 -0
  17. package/assets/aurora-moc.webp +0 -0
  18. package/assets/aurora-tho.webp +0 -0
  19. package/assets/aurora-thuy.webp +0 -0
  20. package/base/ai.css +8 -3
  21. package/base/collections.css +10 -4
  22. package/base/components.css +5 -2
  23. package/base/controls.css +58 -21
  24. package/base/data.css +1 -1
  25. package/base/feedback.css +9 -2
  26. package/base/forms.css +1 -1
  27. package/base/glass.css +1 -1
  28. package/base/interaction.css +6 -2
  29. package/base/navigation.css +1 -1
  30. package/base/reset.css +7 -1
  31. package/base/responsive.css +13 -12
  32. package/base/shell.css +8 -2
  33. package/components/_i18n/i18n.js +28 -3
  34. package/components/_i18n/strings.js +42 -24
  35. package/components/ai/ChatMessage.d.ts +2 -0
  36. package/components/ai/ChatMessage.jsx +8 -4
  37. package/components/ai/ConfidenceMeter.d.ts +2 -0
  38. package/components/ai/HumanReviewGate.d.ts +2 -0
  39. package/components/ai/PromptInput.d.ts +2 -0
  40. package/components/ai/TypingIndicator.d.ts +2 -0
  41. package/components/ai/ai-conversation.card.html +3 -3
  42. package/components/ai/ai-lumi.card.html +3 -3
  43. package/components/ai/ai.card.html +3 -3
  44. package/components/brand/brand.card.html +3 -3
  45. package/components/button/buttons.card.html +3 -3
  46. package/components/data/Image.jsx +18 -2
  47. package/components/data/Sortable.d.ts +5 -1
  48. package/components/data/Sortable.jsx +42 -9
  49. package/components/data/Sortable.prompt.md +4 -2
  50. package/components/data/Terminal.jsx +3 -2
  51. package/components/data/Tooltip.d.ts +2 -0
  52. package/components/data/Tooltip.jsx +6 -3
  53. package/components/data/Tree.d.ts +2 -1
  54. package/components/data/Tree.jsx +137 -16
  55. package/components/data/Tree.prompt.md +1 -1
  56. package/components/data/collections.card.html +3 -3
  57. package/components/data/data.card.html +3 -3
  58. package/components/datatable/DataTable.d.ts +24 -1
  59. package/components/datatable/DataTable.jsx +78 -5
  60. package/components/datatable/DataTable.prompt.md +2 -1
  61. package/components/datatable/datatable.card.html +20 -16
  62. package/components/dialog/dialog.card.html +3 -3
  63. package/components/feedback/Alert.d.ts +2 -0
  64. package/components/feedback/Alert.jsx +8 -2
  65. package/components/feedback/EmptyState.d.ts +2 -0
  66. package/components/feedback/EmptyState.jsx +6 -3
  67. package/components/feedback/ProgressBar.d.ts +2 -0
  68. package/components/feedback/ProgressBar.jsx +6 -3
  69. package/components/feedback/Toast.jsx +15 -1
  70. package/components/feedback/Toast.prompt.md +2 -1
  71. package/components/feedback/feedback-2.card.html +3 -3
  72. package/components/feedback/feedback.card.html +3 -3
  73. package/components/forms/Cascader.jsx +7 -2
  74. package/components/forms/FileUpload.d.ts +2 -0
  75. package/components/forms/Form.d.ts +3 -1
  76. package/components/forms/Form.jsx +9 -1
  77. package/components/forms/Form.prompt.md +1 -1
  78. package/components/forms/Mentions.d.ts +1 -1
  79. package/components/forms/Mentions.jsx +59 -7
  80. package/components/forms/Mentions.prompt.md +3 -1
  81. package/components/forms/Rating.d.ts +3 -2
  82. package/components/forms/Rating.jsx +34 -3
  83. package/components/forms/Rating.prompt.md +1 -1
  84. package/components/forms/Select.d.ts +2 -0
  85. package/components/forms/Select.jsx +6 -2
  86. package/components/forms/Slider.d.ts +2 -0
  87. package/components/forms/Slider.jsx +5 -2
  88. package/components/forms/Switch.d.ts +2 -0
  89. package/components/forms/Switch.jsx +7 -9
  90. package/components/forms/Toggle.d.ts +2 -0
  91. package/components/forms/Toggle.jsx +14 -2
  92. package/components/forms/forms-2.card.html +3 -3
  93. package/components/forms/forms.card.html +3 -3
  94. package/components/icon/Icon.d.ts +1 -1
  95. package/components/icon/Icon.prompt.md +1 -1
  96. package/components/icon/icon.card.html +3 -3
  97. package/components/logo/logo.card.html +3 -3
  98. package/components/navigation/CommandPalette.d.ts +2 -0
  99. package/components/navigation/Menu.d.ts +2 -0
  100. package/components/navigation/Menu.jsx +60 -6
  101. package/components/navigation/Steps.d.ts +2 -0
  102. package/components/navigation/Steps.jsx +6 -3
  103. package/components/navigation/Tabs.d.ts +2 -0
  104. package/components/navigation/Tabs.jsx +6 -3
  105. package/components/navigation/Toolbar.d.ts +3 -2
  106. package/components/navigation/Toolbar.jsx +117 -11
  107. package/components/navigation/Toolbar.prompt.md +1 -1
  108. package/components/navigation/navigation.card.html +3 -3
  109. package/components/navigation/shell.card.html +3 -3
  110. package/components/overlays/ContextMenu.d.ts +2 -0
  111. package/components/overlays/ContextMenu.jsx +6 -3
  112. package/components/overlays/Drawer.d.ts +2 -0
  113. package/components/overlays/alertdialog.card.html +3 -3
  114. package/components/overlays/overlays.card.html +3 -3
  115. package/components/textfield/textfield.card.html +3 -3
  116. package/dist/styles.min.css +312 -64
  117. package/docs/benchmark-rubric.md +2 -2
  118. package/docs/ci-cd.md +18 -13
  119. package/docs/consumer-grant.md +4 -1
  120. package/docs/consuming.md +16 -12
  121. package/docs/contrast-report.md +81 -1
  122. package/docs/conventions.md +18 -5
  123. package/docs/decisions.md +41 -3
  124. package/docs/deploy.md +19 -6
  125. package/docs/design-styles.md +2 -2
  126. package/docs/doctrine.md +51 -0
  127. package/docs/figma.md +2 -0
  128. package/docs/live-hub.md +3 -1
  129. package/docs/products.md +1 -1
  130. package/docs/quality-gates.md +24 -12
  131. package/docs/release-notes.md +1 -1
  132. package/docs/release-runbook.md +78 -0
  133. package/docs/storybook.md +3 -3
  134. package/docs/sync.md +8 -8
  135. package/docs/template-schema-v2.md +1 -1
  136. package/docs/vi/benchmark-rubric.md +2 -2
  137. package/docs/vi/ci-cd.md +17 -14
  138. package/docs/vi/consumer-grant.md +4 -1
  139. package/docs/vi/consuming.md +16 -12
  140. package/docs/vi/contrast-report.md +81 -1
  141. package/docs/vi/conventions.md +19 -6
  142. package/docs/vi/decisions.md +40 -3
  143. package/docs/vi/deploy.md +19 -6
  144. package/docs/vi/design-styles.md +1 -1
  145. package/docs/vi/doctrine.md +51 -0
  146. package/docs/vi/figma.md +2 -0
  147. package/docs/vi/live-hub.md +3 -1
  148. package/docs/vi/products.md +1 -1
  149. package/docs/vi/quality-gates.md +23 -12
  150. package/docs/vi/release-notes.md +1 -1
  151. package/docs/vi/release-runbook.md +78 -0
  152. package/docs/vi/storybook.md +3 -3
  153. package/docs/vi/sync.md +7 -7
  154. package/docs/vi/template-schema-v2.md +1 -1
  155. package/docs/viewer.html +16 -5
  156. package/fonts/OFL.txt +98 -0
  157. package/guidelines/atomic-view.html +2 -2
  158. package/guidelines/brand-imagery.html +1 -1
  159. package/guidelines/elements-variants.html +5 -5
  160. package/guidelines/spacing-breakpoints.html +2 -1
  161. package/guidelines/surfaces-glass.html +1 -1
  162. package/llms.txt +18 -11
  163. package/package.json +28 -8
  164. package/templates/app-shell/AppShell.dc.html +3 -3
  165. package/templates/app-shell/content-schema.json +7 -0
  166. package/templates/article/Article.dc.html +3 -3
  167. package/templates/article/content-schema.json +7 -0
  168. package/templates/auth/Auth.dc.html +26 -10
  169. package/templates/auth/content-schema.json +25 -0
  170. package/templates/dashboard/Dashboard.dc.html +4 -4
  171. package/templates/dashboard/content-schema.json +7 -0
  172. package/templates/delivery-change-note/DeliveryChangeNote.dc.html +2 -2
  173. package/templates/delivery-runbook/DeliveryRunbook.dc.html +2 -2
  174. package/templates/email-safe/email-announcement.html +2 -2
  175. package/templates/email-safe/email-dunning.html +2 -2
  176. package/templates/email-safe/email-investor.html +2 -2
  177. package/templates/email-safe/email-launch.html +2 -2
  178. package/templates/email-safe/email-newsletter.html +2 -2
  179. package/templates/email-safe/email-status.html +2 -2
  180. package/templates/email-safe/email-transactional.html +2 -2
  181. package/templates/finance-dunning-email/FinanceDunningEmail.dc.html +2 -2
  182. package/templates/finance-expense-report/FinanceExpenseReport.dc.html +2 -2
  183. package/templates/finance-invoice/FinanceInvoice.dc.html +2 -2
  184. package/templates/finance-quote/FinanceQuote.dc.html +4 -4
  185. package/templates/hr-interview-kit/HrInterviewKit.dc.html +1 -1
  186. package/templates/marketing-case-study/MarketingCaseStudy.dc.html +1 -1
  187. package/templates/marketing-page/MarketingPage.dc.html +3 -3
  188. package/templates/marketing-page/content-schema.json +1 -0
  189. package/templates/settings/Settings.dc.html +3 -3
  190. package/templates/settings/content-schema.json +7 -0
  191. package/tokens/element-seeds.json +2 -2
  192. package/tokens/elements.css +193 -19
  193. package/tokens/native/CSTokens.kt +3 -2
  194. package/tokens/native/CSTokens.swift +3 -2
  195. package/tokens/native/cs_tokens.dart +3 -2
  196. package/tokens/provenance.json +8 -8
  197. package/tokens/spacing.css +2 -1
  198. package/tokens/tokens.dtcg.json +19 -14
  199. package/tokens/tokens.js +16 -15
  200. package/tokens/tokens.json +15 -14
  201. package/ui_kits/deck/index.html +1 -1
  202. package/ui_kits/status-hub/StatusHub.jsx +1 -1
  203. package/ui_kits/status-hub/login.html +52 -10
  204. package/ui_kits/status-hub/project.html +1 -1
  205. package/ui_kits/status-hub/settings.html +1 -1
  206. package/ui_kits/website/Website.jsx +1 -1
  207. package/ui_kits/website/careers.html +2 -2
  208. package/ui_kits/website/chat.html +1 -1
  209. package/ui_kits/website/site.css +1 -1
  210. package/ui_kits/website/work.html +2 -2
  211. package/assets/aurora-hoa.png +0 -0
  212. package/assets/aurora-kim.png +0 -0
  213. package/assets/aurora-moc.png +0 -0
  214. package/assets/aurora-thuy.png +0 -0
  215. package/docs/decisions/container-query-migration.md +0 -18
  216. package/docs/decisions/overlay-manager-design.md +0 -101
  217. package/docs/plans/cyberskill-design-system-audit-and-12-month-evolution-plan.md +0 -1490
  218. package/docs/status/assets/favicon.svg +0 -5
  219. package/docs/status/assets/status-legacy.css +0 -399
  220. package/docs/status/assets/status-legacy.js +0 -505
  221. package/docs/status/assets/status.css +0 -644
  222. package/docs/status/assets/status.js +0 -1308
  223. package/docs/status/data/status-feed.json +0 -1
  224. package/docs/status/data/task/TASK-REL-001.js +0 -1
  225. package/docs/status/data/task/TASK-REL-002.js +0 -1
  226. package/docs/status/index.html +0 -46
  227. package/docs/status/roadmap.html +0 -4
  228. package/docs/status/status-legacy.html +0 -122
  229. package/docs/tasks/BACKLOG.md +0 -26
  230. package/docs/tasks/_state/commit-links.yaml +0 -14
  231. package/docs/tasks/release/TASK-REL-001-launch-1-1-0/spec.md +0 -55
  232. package/docs/tasks/release/TASK-REL-002-republish-1-1-1/spec.md +0 -54
package/_ds_bundle.js CHANGED
@@ -1,4 +1,4 @@
1
- /* @ds-bundle: {"format":4,"namespace":"CyberSkillDesignSystem_847b3c","components":[{"name":"AIDisclosureBadge","sourcePath":"components/ai/AIDisclosureBadge.jsx"},{"name":"ChatMessage","sourcePath":"components/ai/ChatMessage.jsx"},{"name":"CitationList","sourcePath":"components/ai/CitationList.jsx"},{"name":"ConfidenceMeter","sourcePath":"components/ai/ConfidenceMeter.jsx"},{"name":"HumanReviewGate","sourcePath":"components/ai/HumanReviewGate.jsx"},{"name":"PromptInput","sourcePath":"components/ai/PromptInput.jsx"},{"name":"PromptSuggestions","sourcePath":"components/ai/PromptSuggestions.jsx"},{"name":"TypingIndicator","sourcePath":"components/ai/TypingIndicator.jsx"},{"name":"LumiAvatar","sourcePath":"components/brand/LumiAvatar.jsx"},{"name":"Button","sourcePath":"components/button/Button.jsx"},{"name":"ButtonGroup","sourcePath":"components/button/ButtonGroup.jsx"},{"name":"FloatingActionButton","sourcePath":"components/button/FloatingActionButton.jsx"},{"name":"Accordion","sourcePath":"components/data/Accordion.jsx"},{"name":"AspectRatio","sourcePath":"components/data/AspectRatio.jsx"},{"name":"Avatar","sourcePath":"components/data/Avatar.jsx"},{"name":"AvatarGroup","sourcePath":"components/data/Avatar.jsx"},{"name":"Card","sourcePath":"components/data/Card.jsx"},{"name":"CardHeader","sourcePath":"components/data/Card.jsx"},{"name":"CardBody","sourcePath":"components/data/Card.jsx"},{"name":"CardFooter","sourcePath":"components/data/Card.jsx"},{"name":"Carousel","sourcePath":"components/data/Carousel.jsx"},{"name":"Chart","sourcePath":"components/data/Chart.jsx"},{"name":"CodeBlock","sourcePath":"components/data/CodeBlock.jsx"},{"name":"Collapsible","sourcePath":"components/data/Collapsible.jsx"},{"name":"Comment","sourcePath":"components/data/Comment.jsx"},{"name":"DescriptionList","sourcePath":"components/data/DescriptionList.jsx"},{"name":"Divider","sourcePath":"components/data/Divider.jsx"},{"name":"Image","sourcePath":"components/data/Image.jsx"},{"name":"Item","sourcePath":"components/data/Item.jsx"},{"name":"Kbd","sourcePath":"components/data/Kbd.jsx"},{"name":"List","sourcePath":"components/data/List.jsx"},{"name":"ListItem","sourcePath":"components/data/List.jsx"},{"name":"Masonry","sourcePath":"components/data/Masonry.jsx"},{"name":"QRCode","sourcePath":"components/data/QRCode.jsx"},{"name":"ScrollArea","sourcePath":"components/data/ScrollArea.jsx"},{"name":"Sortable","sourcePath":"components/data/Sortable.jsx"},{"name":"Splitter","sourcePath":"components/data/Splitter.jsx"},{"name":"Stat","sourcePath":"components/data/Stat.jsx"},{"name":"Terminal","sourcePath":"components/data/Terminal.jsx"},{"name":"Timeline","sourcePath":"components/data/Timeline.jsx"},{"name":"Tooltip","sourcePath":"components/data/Tooltip.jsx"},{"name":"Tree","sourcePath":"components/data/Tree.jsx"},{"name":"Watermark","sourcePath":"components/data/Watermark.jsx"},{"name":"DataGrid","sourcePath":"components/datatable/DataGrid.jsx"},{"name":"DataTable","sourcePath":"components/datatable/DataTable.jsx"},{"name":"TreeTable","sourcePath":"components/datatable/TreeTable.jsx"},{"name":"Dialog","sourcePath":"components/dialog/Dialog.jsx"},{"name":"Alert","sourcePath":"components/feedback/Alert.jsx"},{"name":"Badge","sourcePath":"components/feedback/Badge.jsx"},{"name":"EmptyState","sourcePath":"components/feedback/EmptyState.jsx"},{"name":"ProgressBar","sourcePath":"components/feedback/ProgressBar.jsx"},{"name":"Result","sourcePath":"components/feedback/Result.jsx"},{"name":"Skeleton","sourcePath":"components/feedback/Skeleton.jsx"},{"name":"Spinner","sourcePath":"components/feedback/Spinner.jsx"},{"name":"StatusIndicator","sourcePath":"components/feedback/StatusIndicator.jsx"},{"name":"Tag","sourcePath":"components/feedback/Tag.jsx"},{"name":"ToastStack","sourcePath":"components/feedback/Toast.jsx"},{"name":"Toast","sourcePath":"components/feedback/Toast.jsx"},{"name":"Calendar","sourcePath":"components/forms/Calendar.jsx"},{"name":"Cascader","sourcePath":"components/forms/Cascader.jsx"},{"name":"Checkbox","sourcePath":"components/forms/Checkbox.jsx"},{"name":"ColorPicker","sourcePath":"components/forms/ColorPicker.jsx"},{"name":"Combobox","sourcePath":"components/forms/Combobox.jsx"},{"name":"DatePicker","sourcePath":"components/forms/DatePicker.jsx"},{"name":"Editor","sourcePath":"components/forms/Editor.jsx"},{"name":"FileUpload","sourcePath":"components/forms/FileUpload.jsx"},{"name":"Form","sourcePath":"components/forms/Form.jsx"},{"name":"FormField","sourcePath":"components/forms/Form.jsx"},{"name":"FormFieldArray","sourcePath":"components/forms/Form.jsx"},{"name":"FormWizard","sourcePath":"components/forms/Form.jsx"},{"name":"InlineEdit","sourcePath":"components/forms/InlineEdit.jsx"},{"name":"InputGroup","sourcePath":"components/forms/InputGroup.jsx"},{"name":"InputOTP","sourcePath":"components/forms/InputOTP.jsx"},{"name":"Mentions","sourcePath":"components/forms/Mentions.jsx"},{"name":"NativeSelect","sourcePath":"components/forms/NativeSelect.jsx"},{"name":"NumberField","sourcePath":"components/forms/NumberField.jsx"},{"name":"Radio","sourcePath":"components/forms/RadioGroup.jsx"},{"name":"RadioGroup","sourcePath":"components/forms/RadioGroup.jsx"},{"name":"Rating","sourcePath":"components/forms/Rating.jsx"},{"name":"SearchField","sourcePath":"components/forms/SearchField.jsx"},{"name":"SegmentedControl","sourcePath":"components/forms/SegmentedControl.jsx"},{"name":"Select","sourcePath":"components/forms/Select.jsx"},{"name":"Slider","sourcePath":"components/forms/Slider.jsx"},{"name":"Switch","sourcePath":"components/forms/Switch.jsx"},{"name":"TagInput","sourcePath":"components/forms/TagInput.jsx"},{"name":"Textarea","sourcePath":"components/forms/Textarea.jsx"},{"name":"TimePicker","sourcePath":"components/forms/TimePicker.jsx"},{"name":"Toggle","sourcePath":"components/forms/Toggle.jsx"},{"name":"Transfer","sourcePath":"components/forms/Transfer.jsx"},{"name":"TreeSelect","sourcePath":"components/forms/TreeSelect.jsx"},{"name":"CS_ICONS","sourcePath":"components/icon/Icon.jsx"},{"name":"Icon","sourcePath":"components/icon/Icon.jsx"},{"name":"Logo","sourcePath":"components/logo/Logo.jsx"},{"name":"CS_LOGO_VIEWBOX","sourcePath":"components/logo/logo-data.js"},{"name":"CS_LOGO_MARK_INNER","sourcePath":"components/logo/logo-data.js"},{"name":"Anchor","sourcePath":"components/navigation/Anchor.jsx"},{"name":"BackTop","sourcePath":"components/navigation/BackTop.jsx"},{"name":"Breadcrumb","sourcePath":"components/navigation/Breadcrumb.jsx"},{"name":"CommandPalette","sourcePath":"components/navigation/CommandPalette.jsx"},{"name":"Dock","sourcePath":"components/navigation/Dock.jsx"},{"name":"HotKeys","sourcePath":"components/navigation/HotKeys.jsx"},{"name":"Link","sourcePath":"components/navigation/Link.jsx"},{"name":"Menu","sourcePath":"components/navigation/Menu.jsx"},{"name":"MenuItem","sourcePath":"components/navigation/Menu.jsx"},{"name":"Menubar","sourcePath":"components/navigation/Menubar.jsx"},{"name":"NavigationMenu","sourcePath":"components/navigation/NavigationMenu.jsx"},{"name":"Pagination","sourcePath":"components/navigation/Pagination.jsx"},{"name":"Sidebar","sourcePath":"components/navigation/Sidebar.jsx"},{"name":"NavItem","sourcePath":"components/navigation/Sidebar.jsx"},{"name":"Steps","sourcePath":"components/navigation/Steps.jsx"},{"name":"Tabs","sourcePath":"components/navigation/Tabs.jsx"},{"name":"Tab","sourcePath":"components/navigation/Tabs.jsx"},{"name":"Toolbar","sourcePath":"components/navigation/Toolbar.jsx"},{"name":"AlertDialog","sourcePath":"components/overlays/AlertDialog.jsx"},{"name":"ContextMenu","sourcePath":"components/overlays/ContextMenu.jsx"},{"name":"Drawer","sourcePath":"components/overlays/Drawer.jsx"},{"name":"HoverCard","sourcePath":"components/overlays/HoverCard.jsx"},{"name":"OverlayProvider","sourcePath":"components/overlays/OverlayManager.jsx"},{"name":"Popconfirm","sourcePath":"components/overlays/Popconfirm.jsx"},{"name":"Popover","sourcePath":"components/overlays/Popover.jsx"},{"name":"Tour","sourcePath":"components/overlays/Tour.jsx"},{"name":"TextField","sourcePath":"components/textfield/TextField.jsx"}],"sourceHashes":{"components/_i18n/i18n.js":"63f973d61113","components/_i18n/strings.js":"cea7ef153e18","components/_utils/cx.js":"ad6a747ea116","components/ai/AIDisclosureBadge.jsx":"4c8954aa2d79","components/ai/ChatMessage.jsx":"84076f3488f2","components/ai/CitationList.jsx":"4d12ef868ff3","components/ai/ConfidenceMeter.jsx":"68cf2bba3a9e","components/ai/HumanReviewGate.jsx":"922b278dca5c","components/ai/PromptInput.jsx":"7634643dafa5","components/ai/PromptSuggestions.jsx":"02d64117ecfa","components/ai/TypingIndicator.jsx":"149e3a0c7ef8","components/brand/LumiAvatar.jsx":"b6444360cf80","components/button/Button.jsx":"6d902a925eed","components/button/ButtonGroup.jsx":"1a1481f6fb93","components/button/FloatingActionButton.jsx":"ce49fe4129a3","components/data/Accordion.jsx":"fed81278cefd","components/data/AspectRatio.jsx":"6c1e8703891e","components/data/Avatar.jsx":"88c8f59334e6","components/data/Card.jsx":"96e1f9ef44a9","components/data/Carousel.jsx":"caf09a766c42","components/data/Chart.jsx":"1953e9fef120","components/data/CodeBlock.jsx":"04e0bd2507e2","components/data/Collapsible.jsx":"3d961fef83f8","components/data/Comment.jsx":"81469a910d80","components/data/DescriptionList.jsx":"ce50d72546bb","components/data/Divider.jsx":"2d08b4a7b18e","components/data/Image.jsx":"da5c7d4c69af","components/data/Item.jsx":"7e1ac68c4e96","components/data/Kbd.jsx":"3b3d867cbdd8","components/data/List.jsx":"5c221b19efe0","components/data/Masonry.jsx":"a65508731d79","components/data/QRCode.jsx":"6acaf577a7fc","components/data/ScrollArea.jsx":"c2e2ec304b1e","components/data/Sortable.jsx":"315b50256128","components/data/Splitter.jsx":"d231064601e9","components/data/Stat.jsx":"35734a801a71","components/data/Terminal.jsx":"3a3cd4477814","components/data/Timeline.jsx":"22058a781a98","components/data/Tooltip.jsx":"4d027aff5559","components/data/Tree.jsx":"9097456de50f","components/data/Watermark.jsx":"66b7b71488d8","components/data/qr-encode.js":"14e8de77df5f","components/datatable/DataGrid.jsx":"c84fe5d5b8cc","components/datatable/DataTable.jsx":"fbfad5570592","components/datatable/TreeTable.jsx":"597c35135ebb","components/dialog/Dialog.jsx":"8ef7722c37c4","components/feedback/Alert.jsx":"0bbe4e5f467a","components/feedback/Badge.jsx":"e691ae1634f8","components/feedback/EmptyState.jsx":"9a9c00523cec","components/feedback/ProgressBar.jsx":"3986118b0da8","components/feedback/Result.jsx":"c168f8dcb009","components/feedback/Skeleton.jsx":"86ee345cb412","components/feedback/Spinner.jsx":"cad2ed4008a6","components/feedback/StatusIndicator.jsx":"d27a5fc3db2a","components/feedback/Tag.jsx":"13d288da9f38","components/feedback/Toast.jsx":"38cc9a82ede9","components/forms/Calendar.jsx":"8a66cbe766af","components/forms/Cascader.jsx":"7b830b11edb1","components/forms/Checkbox.jsx":"2badb7b3b72b","components/forms/ColorPicker.jsx":"d04db0ee7678","components/forms/Combobox.jsx":"3f2fb4d28922","components/forms/DatePicker.jsx":"7a6c18e1e635","components/forms/Editor.jsx":"194484fb9dc4","components/forms/FileUpload.jsx":"509dfaff8bea","components/forms/Form.jsx":"f7952da4b73a","components/forms/InlineEdit.jsx":"5ffb40598769","components/forms/InputGroup.jsx":"06d6c22420d0","components/forms/InputOTP.jsx":"c3a5d02ffebc","components/forms/Mentions.jsx":"04bc4b5a4ace","components/forms/NativeSelect.jsx":"d6f6d300f75d","components/forms/NumberField.jsx":"44975fd25721","components/forms/RadioGroup.jsx":"1a64f7600e01","components/forms/Rating.jsx":"06d345ecc5f4","components/forms/SearchField.jsx":"2a12c65900ec","components/forms/SegmentedControl.jsx":"1f143c62fb56","components/forms/Select.jsx":"1b5ba905b339","components/forms/Slider.jsx":"69aa1a5f1af8","components/forms/Switch.jsx":"ed54f4bceb60","components/forms/TagInput.jsx":"2d1554422097","components/forms/Textarea.jsx":"cf244fee0e37","components/forms/TimePicker.jsx":"abd6e54b2983","components/forms/Toggle.jsx":"338453a34669","components/forms/Transfer.jsx":"dd0c02d1014e","components/forms/TreeSelect.jsx":"a5b1b91d1d6a","components/icon/Icon.jsx":"280fba8477db","components/logo/Logo.jsx":"445c5f5fbd40","components/logo/logo-data.js":"f1a251a50c8f","components/navigation/Anchor.jsx":"491fb330e718","components/navigation/BackTop.jsx":"5cac0081fc20","components/navigation/Breadcrumb.jsx":"45636f3b0a51","components/navigation/CommandPalette.jsx":"51c25b8416eb","components/navigation/Dock.jsx":"3073e2870c65","components/navigation/HotKeys.jsx":"f0f707a3335d","components/navigation/Link.jsx":"d4fe3fcffd2c","components/navigation/Menu.jsx":"6786788f13f8","components/navigation/Menubar.jsx":"1aa64f59891e","components/navigation/NavigationMenu.jsx":"020279fd7f79","components/navigation/Pagination.jsx":"a1fc19bfd104","components/navigation/Sidebar.jsx":"d578b013ad33","components/navigation/Steps.jsx":"85cb20bd45da","components/navigation/Tabs.jsx":"5d5921da1644","components/navigation/Toolbar.jsx":"ba1e0d0578b3","components/overlays/AlertDialog.jsx":"dcfbb7f61506","components/overlays/ContextMenu.jsx":"e7e3386d89ac","components/overlays/Drawer.jsx":"a1c719da3f62","components/overlays/HoverCard.jsx":"5cfabde80960","components/overlays/OverlayManager.jsx":"6b58c4dad9da","components/overlays/Popconfirm.jsx":"55f08dba5f38","components/overlays/Popover.jsx":"1582f7fa5533","components/overlays/Tour.jsx":"707cd7464f0c","components/textfield/TextField.jsx":"c06bd9729f20","image-slot.js":"26886a229370","tokens/tokens.js":"ed883dc757ae","ui_kits/status-hub/StatusHub.jsx":"8674a9fcf8fc","ui_kits/status-hub/data.js":"664bbbb30cb3","ui_kits/website/Website.jsx":"c6a98330480b","ui_kits/website/copy.js":"9d42c52c17cb"},"inlinedExternals":[],"unexposedExports":[{"name":"attachFocusTrap","sourcePath":"components/overlays/OverlayManager.jsx"},{"name":"cx","sourcePath":"components/_utils/cx.js"},{"name":"focusableSelector","sourcePath":"components/overlays/OverlayManager.jsx"},{"name":"formatCurrency","sourcePath":"components/_i18n/i18n.js"},{"name":"formatDate","sourcePath":"components/_i18n/i18n.js"},{"name":"formatNumber","sourcePath":"components/_i18n/i18n.js"},{"name":"getOverlayManager","sourcePath":"components/overlays/OverlayManager.jsx"},{"name":"getPath","sourcePath":"components/forms/Form.jsx"},{"name":"makeT","sourcePath":"components/_i18n/i18n.js"},{"name":"monthName","sourcePath":"components/_i18n/i18n.js"},{"name":"qrMatrix","sourcePath":"components/data/qr-encode.js"},{"name":"resolveLang","sourcePath":"components/_i18n/i18n.js"},{"name":"setPath","sourcePath":"components/forms/Form.jsx"},{"name":"strings","sourcePath":"components/_i18n/strings.js"},{"name":"tokens","sourcePath":"tokens/tokens.js"},{"name":"tr","sourcePath":"components/_i18n/i18n.js"},{"name":"useLang","sourcePath":"components/_i18n/i18n.js"},{"name":"useOverlayLayer","sourcePath":"components/overlays/OverlayManager.jsx"}]} */
1
+ /* @ds-bundle: {"format":4,"namespace":"CyberSkillDesignSystem_847b3c","components":[{"name":"AIDisclosureBadge","sourcePath":"components/ai/AIDisclosureBadge.jsx"},{"name":"ChatMessage","sourcePath":"components/ai/ChatMessage.jsx"},{"name":"CitationList","sourcePath":"components/ai/CitationList.jsx"},{"name":"ConfidenceMeter","sourcePath":"components/ai/ConfidenceMeter.jsx"},{"name":"HumanReviewGate","sourcePath":"components/ai/HumanReviewGate.jsx"},{"name":"PromptInput","sourcePath":"components/ai/PromptInput.jsx"},{"name":"PromptSuggestions","sourcePath":"components/ai/PromptSuggestions.jsx"},{"name":"TypingIndicator","sourcePath":"components/ai/TypingIndicator.jsx"},{"name":"LumiAvatar","sourcePath":"components/brand/LumiAvatar.jsx"},{"name":"Button","sourcePath":"components/button/Button.jsx"},{"name":"ButtonGroup","sourcePath":"components/button/ButtonGroup.jsx"},{"name":"FloatingActionButton","sourcePath":"components/button/FloatingActionButton.jsx"},{"name":"Accordion","sourcePath":"components/data/Accordion.jsx"},{"name":"AspectRatio","sourcePath":"components/data/AspectRatio.jsx"},{"name":"Avatar","sourcePath":"components/data/Avatar.jsx"},{"name":"AvatarGroup","sourcePath":"components/data/Avatar.jsx"},{"name":"Card","sourcePath":"components/data/Card.jsx"},{"name":"CardHeader","sourcePath":"components/data/Card.jsx"},{"name":"CardBody","sourcePath":"components/data/Card.jsx"},{"name":"CardFooter","sourcePath":"components/data/Card.jsx"},{"name":"Carousel","sourcePath":"components/data/Carousel.jsx"},{"name":"Chart","sourcePath":"components/data/Chart.jsx"},{"name":"CodeBlock","sourcePath":"components/data/CodeBlock.jsx"},{"name":"Collapsible","sourcePath":"components/data/Collapsible.jsx"},{"name":"Comment","sourcePath":"components/data/Comment.jsx"},{"name":"DescriptionList","sourcePath":"components/data/DescriptionList.jsx"},{"name":"Divider","sourcePath":"components/data/Divider.jsx"},{"name":"Image","sourcePath":"components/data/Image.jsx"},{"name":"Item","sourcePath":"components/data/Item.jsx"},{"name":"Kbd","sourcePath":"components/data/Kbd.jsx"},{"name":"List","sourcePath":"components/data/List.jsx"},{"name":"ListItem","sourcePath":"components/data/List.jsx"},{"name":"Masonry","sourcePath":"components/data/Masonry.jsx"},{"name":"QRCode","sourcePath":"components/data/QRCode.jsx"},{"name":"ScrollArea","sourcePath":"components/data/ScrollArea.jsx"},{"name":"Sortable","sourcePath":"components/data/Sortable.jsx"},{"name":"Splitter","sourcePath":"components/data/Splitter.jsx"},{"name":"Stat","sourcePath":"components/data/Stat.jsx"},{"name":"Terminal","sourcePath":"components/data/Terminal.jsx"},{"name":"Timeline","sourcePath":"components/data/Timeline.jsx"},{"name":"Tooltip","sourcePath":"components/data/Tooltip.jsx"},{"name":"Tree","sourcePath":"components/data/Tree.jsx"},{"name":"Watermark","sourcePath":"components/data/Watermark.jsx"},{"name":"DataGrid","sourcePath":"components/datatable/DataGrid.jsx"},{"name":"DataTable","sourcePath":"components/datatable/DataTable.jsx"},{"name":"TreeTable","sourcePath":"components/datatable/TreeTable.jsx"},{"name":"Dialog","sourcePath":"components/dialog/Dialog.jsx"},{"name":"Alert","sourcePath":"components/feedback/Alert.jsx"},{"name":"Badge","sourcePath":"components/feedback/Badge.jsx"},{"name":"EmptyState","sourcePath":"components/feedback/EmptyState.jsx"},{"name":"ProgressBar","sourcePath":"components/feedback/ProgressBar.jsx"},{"name":"Result","sourcePath":"components/feedback/Result.jsx"},{"name":"Skeleton","sourcePath":"components/feedback/Skeleton.jsx"},{"name":"Spinner","sourcePath":"components/feedback/Spinner.jsx"},{"name":"StatusIndicator","sourcePath":"components/feedback/StatusIndicator.jsx"},{"name":"Tag","sourcePath":"components/feedback/Tag.jsx"},{"name":"ToastStack","sourcePath":"components/feedback/Toast.jsx"},{"name":"Toast","sourcePath":"components/feedback/Toast.jsx"},{"name":"Calendar","sourcePath":"components/forms/Calendar.jsx"},{"name":"Cascader","sourcePath":"components/forms/Cascader.jsx"},{"name":"Checkbox","sourcePath":"components/forms/Checkbox.jsx"},{"name":"ColorPicker","sourcePath":"components/forms/ColorPicker.jsx"},{"name":"Combobox","sourcePath":"components/forms/Combobox.jsx"},{"name":"DatePicker","sourcePath":"components/forms/DatePicker.jsx"},{"name":"Editor","sourcePath":"components/forms/Editor.jsx"},{"name":"FileUpload","sourcePath":"components/forms/FileUpload.jsx"},{"name":"Form","sourcePath":"components/forms/Form.jsx"},{"name":"FormField","sourcePath":"components/forms/Form.jsx"},{"name":"FormFieldArray","sourcePath":"components/forms/Form.jsx"},{"name":"FormWizard","sourcePath":"components/forms/Form.jsx"},{"name":"InlineEdit","sourcePath":"components/forms/InlineEdit.jsx"},{"name":"InputGroup","sourcePath":"components/forms/InputGroup.jsx"},{"name":"InputOTP","sourcePath":"components/forms/InputOTP.jsx"},{"name":"Mentions","sourcePath":"components/forms/Mentions.jsx"},{"name":"NativeSelect","sourcePath":"components/forms/NativeSelect.jsx"},{"name":"NumberField","sourcePath":"components/forms/NumberField.jsx"},{"name":"Radio","sourcePath":"components/forms/RadioGroup.jsx"},{"name":"RadioGroup","sourcePath":"components/forms/RadioGroup.jsx"},{"name":"Rating","sourcePath":"components/forms/Rating.jsx"},{"name":"SearchField","sourcePath":"components/forms/SearchField.jsx"},{"name":"SegmentedControl","sourcePath":"components/forms/SegmentedControl.jsx"},{"name":"Select","sourcePath":"components/forms/Select.jsx"},{"name":"Slider","sourcePath":"components/forms/Slider.jsx"},{"name":"Switch","sourcePath":"components/forms/Switch.jsx"},{"name":"TagInput","sourcePath":"components/forms/TagInput.jsx"},{"name":"Textarea","sourcePath":"components/forms/Textarea.jsx"},{"name":"TimePicker","sourcePath":"components/forms/TimePicker.jsx"},{"name":"Toggle","sourcePath":"components/forms/Toggle.jsx"},{"name":"Transfer","sourcePath":"components/forms/Transfer.jsx"},{"name":"TreeSelect","sourcePath":"components/forms/TreeSelect.jsx"},{"name":"CS_ICONS","sourcePath":"components/icon/Icon.jsx"},{"name":"Icon","sourcePath":"components/icon/Icon.jsx"},{"name":"Logo","sourcePath":"components/logo/Logo.jsx"},{"name":"CS_LOGO_VIEWBOX","sourcePath":"components/logo/logo-data.js"},{"name":"CS_LOGO_MARK_INNER","sourcePath":"components/logo/logo-data.js"},{"name":"Anchor","sourcePath":"components/navigation/Anchor.jsx"},{"name":"BackTop","sourcePath":"components/navigation/BackTop.jsx"},{"name":"Breadcrumb","sourcePath":"components/navigation/Breadcrumb.jsx"},{"name":"CommandPalette","sourcePath":"components/navigation/CommandPalette.jsx"},{"name":"Dock","sourcePath":"components/navigation/Dock.jsx"},{"name":"HotKeys","sourcePath":"components/navigation/HotKeys.jsx"},{"name":"Link","sourcePath":"components/navigation/Link.jsx"},{"name":"Menu","sourcePath":"components/navigation/Menu.jsx"},{"name":"MenuItem","sourcePath":"components/navigation/Menu.jsx"},{"name":"Menubar","sourcePath":"components/navigation/Menubar.jsx"},{"name":"NavigationMenu","sourcePath":"components/navigation/NavigationMenu.jsx"},{"name":"Pagination","sourcePath":"components/navigation/Pagination.jsx"},{"name":"Sidebar","sourcePath":"components/navigation/Sidebar.jsx"},{"name":"NavItem","sourcePath":"components/navigation/Sidebar.jsx"},{"name":"Steps","sourcePath":"components/navigation/Steps.jsx"},{"name":"Tabs","sourcePath":"components/navigation/Tabs.jsx"},{"name":"Tab","sourcePath":"components/navigation/Tabs.jsx"},{"name":"Toolbar","sourcePath":"components/navigation/Toolbar.jsx"},{"name":"AlertDialog","sourcePath":"components/overlays/AlertDialog.jsx"},{"name":"ContextMenu","sourcePath":"components/overlays/ContextMenu.jsx"},{"name":"Drawer","sourcePath":"components/overlays/Drawer.jsx"},{"name":"HoverCard","sourcePath":"components/overlays/HoverCard.jsx"},{"name":"OverlayProvider","sourcePath":"components/overlays/OverlayManager.jsx"},{"name":"Popconfirm","sourcePath":"components/overlays/Popconfirm.jsx"},{"name":"Popover","sourcePath":"components/overlays/Popover.jsx"},{"name":"Tour","sourcePath":"components/overlays/Tour.jsx"},{"name":"TextField","sourcePath":"components/textfield/TextField.jsx"}],"sourceHashes":{"components/_i18n/i18n.js":"c8fc9f74a4c0","components/_i18n/strings.js":"0e63dc7515d8","components/_utils/cx.js":"ad6a747ea116","components/ai/AIDisclosureBadge.jsx":"4c8954aa2d79","components/ai/ChatMessage.jsx":"344e9c13fcfe","components/ai/CitationList.jsx":"4d12ef868ff3","components/ai/ConfidenceMeter.jsx":"68cf2bba3a9e","components/ai/HumanReviewGate.jsx":"922b278dca5c","components/ai/PromptInput.jsx":"7634643dafa5","components/ai/PromptSuggestions.jsx":"02d64117ecfa","components/ai/TypingIndicator.jsx":"149e3a0c7ef8","components/brand/LumiAvatar.jsx":"b6444360cf80","components/button/Button.jsx":"6d902a925eed","components/button/ButtonGroup.jsx":"1a1481f6fb93","components/button/FloatingActionButton.jsx":"ce49fe4129a3","components/data/Accordion.jsx":"fed81278cefd","components/data/AspectRatio.jsx":"6c1e8703891e","components/data/Avatar.jsx":"88c8f59334e6","components/data/Card.jsx":"96e1f9ef44a9","components/data/Carousel.jsx":"caf09a766c42","components/data/Chart.jsx":"1953e9fef120","components/data/CodeBlock.jsx":"04e0bd2507e2","components/data/Collapsible.jsx":"3d961fef83f8","components/data/Comment.jsx":"81469a910d80","components/data/DescriptionList.jsx":"ce50d72546bb","components/data/Divider.jsx":"2d08b4a7b18e","components/data/Image.jsx":"0ea06cfe1532","components/data/Item.jsx":"7e1ac68c4e96","components/data/Kbd.jsx":"3b3d867cbdd8","components/data/List.jsx":"5c221b19efe0","components/data/Masonry.jsx":"a65508731d79","components/data/QRCode.jsx":"6acaf577a7fc","components/data/ScrollArea.jsx":"c2e2ec304b1e","components/data/Sortable.jsx":"ed7615a9469a","components/data/Splitter.jsx":"d231064601e9","components/data/Stat.jsx":"35734a801a71","components/data/Terminal.jsx":"9d419686bea8","components/data/Timeline.jsx":"22058a781a98","components/data/Tooltip.jsx":"3f30c6b1bf1d","components/data/Tree.jsx":"a1d33dec0451","components/data/Watermark.jsx":"66b7b71488d8","components/data/qr-encode.js":"14e8de77df5f","components/datatable/DataGrid.jsx":"c84fe5d5b8cc","components/datatable/DataTable.jsx":"2a7c3bda5142","components/datatable/TreeTable.jsx":"597c35135ebb","components/dialog/Dialog.jsx":"8ef7722c37c4","components/feedback/Alert.jsx":"4dbf685af3e1","components/feedback/Badge.jsx":"e691ae1634f8","components/feedback/EmptyState.jsx":"5ba1437e6d81","components/feedback/ProgressBar.jsx":"c63a431e9eed","components/feedback/Result.jsx":"c168f8dcb009","components/feedback/Skeleton.jsx":"86ee345cb412","components/feedback/Spinner.jsx":"cad2ed4008a6","components/feedback/StatusIndicator.jsx":"d27a5fc3db2a","components/feedback/Tag.jsx":"13d288da9f38","components/feedback/Toast.jsx":"6a7832751669","components/forms/Calendar.jsx":"8a66cbe766af","components/forms/Cascader.jsx":"63f35db7101d","components/forms/Checkbox.jsx":"2badb7b3b72b","components/forms/ColorPicker.jsx":"d04db0ee7678","components/forms/Combobox.jsx":"3f2fb4d28922","components/forms/DatePicker.jsx":"7a6c18e1e635","components/forms/Editor.jsx":"194484fb9dc4","components/forms/FileUpload.jsx":"509dfaff8bea","components/forms/Form.jsx":"6945025a205f","components/forms/InlineEdit.jsx":"5ffb40598769","components/forms/InputGroup.jsx":"06d6c22420d0","components/forms/InputOTP.jsx":"c3a5d02ffebc","components/forms/Mentions.jsx":"3924619137f3","components/forms/NativeSelect.jsx":"d6f6d300f75d","components/forms/NumberField.jsx":"44975fd25721","components/forms/RadioGroup.jsx":"1a64f7600e01","components/forms/Rating.jsx":"bd5c94741d63","components/forms/SearchField.jsx":"2a12c65900ec","components/forms/SegmentedControl.jsx":"1f143c62fb56","components/forms/Select.jsx":"009495d18845","components/forms/Slider.jsx":"213b7b9edec9","components/forms/Switch.jsx":"3cdf185f757c","components/forms/TagInput.jsx":"2d1554422097","components/forms/Textarea.jsx":"cf244fee0e37","components/forms/TimePicker.jsx":"abd6e54b2983","components/forms/Toggle.jsx":"a5378fd66713","components/forms/Transfer.jsx":"dd0c02d1014e","components/forms/TreeSelect.jsx":"a5b1b91d1d6a","components/icon/Icon.jsx":"280fba8477db","components/logo/Logo.jsx":"445c5f5fbd40","components/logo/logo-data.js":"f1a251a50c8f","components/navigation/Anchor.jsx":"491fb330e718","components/navigation/BackTop.jsx":"5cac0081fc20","components/navigation/Breadcrumb.jsx":"45636f3b0a51","components/navigation/CommandPalette.jsx":"51c25b8416eb","components/navigation/Dock.jsx":"3073e2870c65","components/navigation/HotKeys.jsx":"f0f707a3335d","components/navigation/Link.jsx":"d4fe3fcffd2c","components/navigation/Menu.jsx":"999e1659f8b3","components/navigation/Menubar.jsx":"1aa64f59891e","components/navigation/NavigationMenu.jsx":"020279fd7f79","components/navigation/Pagination.jsx":"a1fc19bfd104","components/navigation/Sidebar.jsx":"d578b013ad33","components/navigation/Steps.jsx":"e26c4a18cdfc","components/navigation/Tabs.jsx":"16964d61f349","components/navigation/Toolbar.jsx":"0f1f25d9e0c5","components/overlays/AlertDialog.jsx":"dcfbb7f61506","components/overlays/ContextMenu.jsx":"e3fb63e7b74f","components/overlays/Drawer.jsx":"a1c719da3f62","components/overlays/HoverCard.jsx":"5cfabde80960","components/overlays/OverlayManager.jsx":"6b58c4dad9da","components/overlays/Popconfirm.jsx":"55f08dba5f38","components/overlays/Popover.jsx":"1582f7fa5533","components/overlays/Tour.jsx":"707cd7464f0c","components/textfield/TextField.jsx":"c06bd9729f20","tokens/tokens.js":"e0af03c04975"},"inlinedExternals":[],"unexposedExports":[{"name":"attachFocusTrap","sourcePath":"components/overlays/OverlayManager.jsx"},{"name":"cx","sourcePath":"components/_utils/cx.js"},{"name":"focusableSelector","sourcePath":"components/overlays/OverlayManager.jsx"},{"name":"formatCurrency","sourcePath":"components/_i18n/i18n.js"},{"name":"formatDate","sourcePath":"components/_i18n/i18n.js"},{"name":"formatNumber","sourcePath":"components/_i18n/i18n.js"},{"name":"getOverlayManager","sourcePath":"components/overlays/OverlayManager.jsx"},{"name":"getPath","sourcePath":"components/forms/Form.jsx"},{"name":"makeT","sourcePath":"components/_i18n/i18n.js"},{"name":"monthName","sourcePath":"components/_i18n/i18n.js"},{"name":"qrMatrix","sourcePath":"components/data/qr-encode.js"},{"name":"reorderItems","sourcePath":"components/data/Sortable.jsx"},{"name":"resolveLang","sourcePath":"components/_i18n/i18n.js"},{"name":"setPath","sourcePath":"components/forms/Form.jsx"},{"name":"strings","sourcePath":"components/_i18n/strings.js"},{"name":"tokens","sourcePath":"tokens/tokens.js"},{"name":"tr","sourcePath":"components/_i18n/i18n.js"},{"name":"useLang","sourcePath":"components/_i18n/i18n.js"},{"name":"useOverlayLayer","sourcePath":"components/overlays/OverlayManager.jsx"}]} */
2
2
 
3
3
  (() => {
4
4
 
@@ -20,8 +20,8 @@ const strings = {
20
20
  vi: { label: "Đường dẫn" }
21
21
  },
22
22
  CommandPalette: {
23
- en: { placeholder: "Type a command or search…", empty: "No results found", aria: "Command palette", esc: "Esc" },
24
- vi: { placeholder: "Nhập lệnh hoặc tìm kiếm…", empty: "Không tìm thấy kết quả", aria: "Bảng lệnh", esc: "Esc" }
23
+ en: { placeholder: "Type a command or search…", empty: "No results", aria: "Command palette", esc: "Esc" },
24
+ vi: { placeholder: "Nhập lệnh hoặc tìm kiếm…", empty: "Không có kết quả", aria: "Bảng lệnh", esc: "Esc" }
25
25
  },
26
26
  SearchField: {
27
27
  en: { placeholder: "Search…", clear: "Clear search" },
@@ -44,12 +44,22 @@ const strings = {
44
44
  vi: { confirm: "Xác nhận", cancel: "Hủy" }
45
45
  },
46
46
  Drawer: {
47
- en: { close: "Close", panel: "Panel" },
48
- vi: { close: "Đóng", panel: "Bảng" }
47
+ en: { close: "Close", panel: "Side panel" },
48
+ vi: { close: "Đóng", panel: "Bảng bên" }
49
49
  },
50
50
  DataTable: {
51
- en: { empty: "No records" },
52
- vi: { empty: "Không có bản ghi" }
51
+ en: {
52
+ empty: "No results",
53
+ loading: "Loading table",
54
+ error: "We couldn't load this table",
55
+ errorHint: "Check your connection, then try again."
56
+ },
57
+ vi: {
58
+ empty: "Không có kết quả",
59
+ loading: "Đang tải bảng",
60
+ error: "Không tải được bảng này",
61
+ errorHint: "Kiểm tra kết nối, rồi thử lại."
62
+ }
53
63
  },
54
64
  ConfidenceMeter: {
55
65
  en: { label: "Confidence", low: "Low", medium: "Medium", high: "High" },
@@ -61,10 +71,10 @@ const strings = {
61
71
  },
62
72
  Tag: {
63
73
  en: { remove: "Remove" },
64
- vi: { remove: "Gỡ bỏ" }
74
+ vi: { remove: "Gỡ" }
65
75
  },
66
76
  Toast: {
67
- en: { notifications: "Notifications", dismiss: "Dismiss" },
77
+ en: { notifications: "Notifications", dismiss: "Close" },
68
78
  vi: { notifications: "Thông báo", dismiss: "Đóng" }
69
79
  },
70
80
  TypingIndicator: {
@@ -76,12 +86,12 @@ const strings = {
76
86
  vi: { toolbar: "Định dạng", bold: "Đậm", italic: "Nghiêng", list: "Danh sách", area: "Văn bản" }
77
87
  },
78
88
  Terminal: {
79
- en: { input: "Terminal input" },
80
- vi: { input: "Ô nhập lệnh" }
89
+ en: { input: "Terminal input", title: "cyberskill — zsh" },
90
+ vi: { input: "Ô nhập lệnh", title: "cyberskill — zsh" }
81
91
  },
82
92
  DataGrid: {
83
- en: { empty: "No records", selectAll: "Select all rows", selectRow: "Select row", pin: "Pin this column", pinNamed: "Pin column {name}" },
84
- vi: { empty: "Không có bản ghi", selectAll: "Chọn tất cả", selectRow: "Chọn dòng", pin: "Ghim cột này", pinNamed: "Ghim cột {name}" }
93
+ en: { empty: "No results", selectAll: "Select all rows", selectRow: "Select row", pin: "Pin this column", pinNamed: "Pin column {name}" },
94
+ vi: { empty: "Không có kết quả", selectAll: "Chọn tất cả", selectRow: "Chọn dòng", pin: "Ghim cột này", pinNamed: "Ghim cột {name}" }
85
95
  },
86
96
  Splitter: {
87
97
  en: { label: "Resize panes" },
@@ -115,9 +125,13 @@ const strings = {
115
125
  en: { placeholder: "Add a note — @ to mention" },
116
126
  vi: { placeholder: "Thêm ghi chú — gõ @ để nhắc tên" }
117
127
  },
128
+ Sortable: {
129
+ en: { moveUp: "Move up", moveDown: "Move down" },
130
+ vi: { moveUp: "Di chuyển lên", moveDown: "Di chuyển xuống" }
131
+ },
118
132
  Form: {
119
- en: { summary: "Please fix the following:", required: "required", requiredField: "This field is required" },
120
- vi: { summary: "Vui lòng sửa các mục sau:", required: "bắt buộc", requiredField: "Trường này là bắt buộc" }
133
+ en: { summary: "Please fix the following:", required: "required", requiredField: "Enter a value" },
134
+ vi: { summary: "Vui lòng sửa các mục sau:", required: "bắt buộc", requiredField: "Vui lòng nhập giá trị" }
121
135
  },
122
136
  BackTop: {
123
137
  en: { label: "Back to top" },
@@ -125,7 +139,7 @@ const strings = {
125
139
  },
126
140
  Carousel: {
127
141
  en: { prev: "Previous slide", next: "Next slide", slide: "Slide" },
128
- vi: { prev: "Trang trước", next: "Trang sau", slide: "Trang" }
142
+ vi: { prev: "Ảnh trước", next: "Ảnh sau", slide: "Ảnh" }
129
143
  },
130
144
  Comment: {
131
145
  en: { reply: "Reply" },
@@ -140,12 +154,12 @@ const strings = {
140
154
  vi: { more: "Thao tác khác" }
141
155
  },
142
156
  Popconfirm: {
143
- en: { ok: "OK", cancel: "Cancel" },
144
- vi: { ok: "Đồng ý", cancel: "Hủy" }
157
+ en: { ok: "Confirm", cancel: "Cancel" },
158
+ vi: { ok: "Xác nhận", cancel: "Hủy" }
145
159
  },
146
160
  Result: {
147
- en: { success: "Done", error: "Something went wrong", warning: "Please check", info: "For your information" },
148
- vi: { success: "Hoàn tất", error: "Đã có lỗi xảy ra", warning: "Vui lòng kiểm tra", info: "Thông tin" }
161
+ en: { success: "Done", error: "We couldn't complete that", warning: "Please check", info: "Here's what happened" },
162
+ vi: { success: "Hoàn tất", error: "Chúng tôi chưa hoàn tất được", warning: "Vui lòng kiểm tra", info: "Đây là điều đã xảy ra" }
149
163
  },
150
164
  InlineEdit: {
151
165
  en: { edit: "Edit", empty: "Add text" },
@@ -164,8 +178,8 @@ const strings = {
164
178
  vi: { label: "Giờ" }
165
179
  },
166
180
  Combobox: {
167
- en: { placeholder: "Select or type…", empty: "No matches" },
168
- vi: { placeholder: "Chọn hoặc nhập…", empty: "Không có kết quả phù hợp" }
181
+ en: { placeholder: "Select or type…", empty: "No results" },
182
+ vi: { placeholder: "Chọn hoặc nhập…", empty: "Không có kết quả" }
169
183
  },
170
184
  InputGroup: {
171
185
  en: { clear: "Clear", show: "Show password", hide: "Hide password", input: "Input" },
@@ -200,11 +214,11 @@ const strings = {
200
214
  vi: { list: "Tab" }
201
215
  },
202
216
  EmptyState: {
203
- en: { title: "Nothing here yet", action: "Get started" },
204
- vi: { title: "Chưa có gì ở đây", action: "Bắt đầu" }
217
+ en: { title: "Nothing here yet", action: "Create your first item" },
218
+ vi: { title: "Chưa có gì ở đây", action: "Tạo mục đầu tiên" }
205
219
  },
206
220
  Alert: {
207
- en: { dismiss: "Dismiss" },
221
+ en: { dismiss: "Close" },
208
222
  vi: { dismiss: "Đóng" }
209
223
  },
210
224
  Select: {
@@ -238,6 +252,10 @@ const strings = {
238
252
  ProgressBar: {
239
253
  en: { label: "Progress" },
240
254
  vi: { label: "Tiến độ" }
255
+ },
256
+ ChatMessage: {
257
+ en: { you: "You" },
258
+ vi: { you: "Bạn" }
241
259
  }
242
260
  };
243
261
  Object.assign(__ds_scope, { strings });
@@ -300,8 +318,21 @@ function formatNumber(n, lang) {
300
318
  if (n == null || isNaN(n)) return "";
301
319
  return new Intl.NumberFormat(lang === "vi" ? "vi-VN" : "en-US").format(n);
302
320
  }
303
- function formatCurrency(n, lang) {
321
+ function formatCurrency(n, langOrOpts) {
304
322
  if (n == null || isNaN(n)) return "";
323
+ const opts = langOrOpts && typeof langOrOpts === "object" ? langOrOpts : null;
324
+ if (opts && opts.currency) {
325
+ let locale = opts.locale;
326
+ if (!locale) {
327
+ locale = opts.lang === "vi" ? "vi-VN" : "en-US";
328
+ }
329
+ return new Intl.NumberFormat(locale, {
330
+ style: "currency",
331
+ currency: String(opts.currency).toUpperCase(),
332
+ currencyDisplay: "symbol"
333
+ }).format(Number(n));
334
+ }
335
+ const lang = typeof langOrOpts === "string" ? langOrOpts : opts && opts.lang || "en";
305
336
  return lang === "vi" ? new Intl.NumberFormat("vi-VN").format(n) + " ₫" : "$" + new Intl.NumberFormat("en-US").format(n);
306
337
  }
307
338
  Object.assign(__ds_scope, { resolveLang, tr, makeT, useLang, formatDate, monthName, formatNumber, formatCurrency });
@@ -344,11 +375,15 @@ Object.assign(__ds_scope, { AIDisclosureBadge });
344
375
 
345
376
  // components/ai/ChatMessage.jsx
346
377
  try { (() => {
378
+ const { makeT, useLang } = __ds_scope;
347
379
  const { cx } = __ds_scope;
348
- function ChatMessage({ role = "lumi", name, avatar, children, className }) {
380
+ function ChatMessage({ role = "lumi", name, avatar, lang, className, children }) {
349
381
  const isUser = role === "user";
350
- const defaultAvatar = isUser ? /* @__PURE__ */ React.createElement("span", { "aria-hidden": "true" }, "You") : /* @__PURE__ */ React.createElement("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.9", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("path", { d: "M12 3l1.8 5.4L19 10l-5.2 1.6L12 17l-1.8-5.4L5 10l5.2-1.6z" }));
351
- return /* @__PURE__ */ React.createElement("div", { className: cx("cs-chat-msg", isUser ? "cs-chat-msg--user" : "cs-chat-msg--lumi", className) }, /* @__PURE__ */ React.createElement("div", { className: "cs-chat-msg__avatar" }, avatar ?? defaultAvatar), /* @__PURE__ */ React.createElement("div", { className: "cs-chat-msg__col" }, /* @__PURE__ */ React.createElement("div", { className: "cs-chat-msg__name" }, name ?? (isUser ? "You" : "Lumi")), /* @__PURE__ */ React.createElement("div", { className: "cs-chat-msg__bubble" }, children)));
382
+ const [ref, L] = useLang(lang);
383
+ const t = makeT("ChatMessage", L);
384
+ const you = t("you");
385
+ const defaultAvatar = isUser ? /* @__PURE__ */ React.createElement("span", { "aria-hidden": "true" }, you) : /* @__PURE__ */ React.createElement("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.9", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("path", { d: "M12 3l1.8 5.4L19 10l-5.2 1.6L12 17l-1.8-5.4L5 10l5.2-1.6z" }));
386
+ return /* @__PURE__ */ React.createElement("div", { ref, className: cx("cs-chat-msg", isUser ? "cs-chat-msg--user" : "cs-chat-msg--lumi", className) }, /* @__PURE__ */ React.createElement("div", { className: "cs-chat-msg__avatar" }, avatar ?? defaultAvatar), /* @__PURE__ */ React.createElement("div", { className: "cs-chat-msg__col" }, /* @__PURE__ */ React.createElement("div", { className: "cs-chat-msg__name" }, name ?? (isUser ? you : "Lumi")), /* @__PURE__ */ React.createElement("div", { className: "cs-chat-msg__bubble" }, children)));
352
387
  }
353
388
  Object.assign(__ds_scope, { ChatMessage });
354
389
  })(); } catch (e) { __ds_ns.__errors.push({ path: "components/ai/ChatMessage.jsx", error: String((e && e.message) || e) }); }
@@ -881,10 +916,23 @@ Object.assign(__ds_scope, { ScrollArea });
881
916
 
882
917
  // components/data/Sortable.jsx
883
918
  try { (() => {
919
+ const { makeT, useLang } = __ds_scope;
884
920
  const { cx } = __ds_scope;
885
- function Sortable({ items = [], onChange, className }) {
921
+ function reorderItems(items, from, to) {
922
+ if (from < 0 || to < 0 || from >= items.length || to >= items.length || from === to) return items;
923
+ const next = [...items];
924
+ next.splice(to, 0, next.splice(from, 1)[0]);
925
+ return next;
926
+ }
927
+ function Sortable({ items = [], onChange, lang, className }) {
886
928
  const [dragKey, setDragKey] = React.useState(null);
887
929
  const [over, setOver] = React.useState(null);
930
+ const [ref, L] = useLang(lang);
931
+ const t = makeT("Sortable", L);
932
+ const move = (from, to) => {
933
+ const next = reorderItems(items, from, to);
934
+ if (next !== items) onChange && onChange(next);
935
+ };
888
936
  const drop = () => {
889
937
  if (dragKey == null || over == null || dragKey === over) {
890
938
  setDragKey(null);
@@ -893,19 +941,23 @@ function Sortable({ items = [], onChange, className }) {
893
941
  }
894
942
  const from = items.findIndex((i) => i.key === dragKey);
895
943
  const to = items.findIndex((i) => i.key === over);
896
- const next = [...items];
897
- next.splice(to, 0, next.splice(from, 1)[0]);
898
- onChange && onChange(next);
944
+ move(from, to);
899
945
  setDragKey(null);
900
946
  setOver(null);
901
947
  };
902
- return /* @__PURE__ */ React.createElement("ul", { className: cx("cs-sortable", className) }, items.map((it) => /* @__PURE__ */ React.createElement(
948
+ return /* @__PURE__ */ React.createElement("ul", { ref, className: cx("cs-sortable", className) }, items.map((it, idx) => /* @__PURE__ */ React.createElement(
903
949
  "li",
904
950
  {
905
951
  key: it.key,
906
952
  draggable: true,
907
953
  className: cx("cs-sortable__item", dragKey === it.key && "is-dragging", over === it.key && "is-over"),
908
- onDragStart: () => setDragKey(it.key),
954
+ onDragStart: (e) => {
955
+ if (e.target.closest && e.target.closest(".cs-sortable__ops")) {
956
+ e.preventDefault();
957
+ return;
958
+ }
959
+ setDragKey(it.key);
960
+ },
909
961
  onDragOver: (e) => {
910
962
  e.preventDefault();
911
963
  setOver(it.key);
@@ -914,10 +966,31 @@ function Sortable({ items = [], onChange, className }) {
914
966
  onDragEnd: drop
915
967
  },
916
968
  /* @__PURE__ */ React.createElement("span", { className: "cs-sortable__grip", "aria-hidden": "true" }, "⠿"),
917
- it.label
969
+ /* @__PURE__ */ React.createElement("span", { className: "cs-sortable__label" }, it.label),
970
+ /* @__PURE__ */ React.createElement("span", { className: "cs-sortable__ops", onMouseDown: (e) => e.stopPropagation() }, /* @__PURE__ */ React.createElement(
971
+ "button",
972
+ {
973
+ type: "button",
974
+ className: "cs-button cs-button--secondary cs-button--xs",
975
+ "aria-label": t("moveUp"),
976
+ disabled: idx === 0,
977
+ onClick: () => move(idx, idx - 1)
978
+ },
979
+ "↑"
980
+ ), /* @__PURE__ */ React.createElement(
981
+ "button",
982
+ {
983
+ type: "button",
984
+ className: "cs-button cs-button--secondary cs-button--xs",
985
+ "aria-label": t("moveDown"),
986
+ disabled: idx === items.length - 1,
987
+ onClick: () => move(idx, idx + 1)
988
+ },
989
+ "↓"
990
+ ))
918
991
  )));
919
992
  }
920
- Object.assign(__ds_scope, { Sortable });
993
+ Object.assign(__ds_scope, { reorderItems, Sortable });
921
994
  })(); } catch (e) { __ds_ns.__errors.push({ path: "components/data/Sortable.jsx", error: String((e && e.message) || e) }); }
922
995
 
923
996
  // components/data/Splitter.jsx
@@ -981,18 +1054,19 @@ Object.assign(__ds_scope, { Stat });
981
1054
  try { (() => {
982
1055
  const { makeT, useLang } = __ds_scope;
983
1056
  const { cx } = __ds_scope;
984
- function Terminal({ title = "cyberskill — zsh", welcome, onCommand, prompt = "➜", lang, className }) {
1057
+ function Terminal({ title, welcome, onCommand, prompt = "➜", lang, className }) {
985
1058
  const [hist, setHist] = React.useState(() => welcome ? [{ out: welcome }] : []);
986
1059
  const [q, setQ] = React.useState("");
987
1060
  const [ref, L] = useLang(lang);
988
1061
  const t = makeT("Terminal", L);
1062
+ const barTitle = title ?? t("title");
989
1063
  const run = () => {
990
1064
  if (!q.trim()) return;
991
1065
  const res = onCommand ? onCommand(q.trim()) : "";
992
1066
  setHist((h) => [...h, { cmd: q }, ...res ? [{ out: res }] : []]);
993
1067
  setQ("");
994
1068
  };
995
- return /* @__PURE__ */ React.createElement("div", { ref, className: cx("cs-terminal", className) }, /* @__PURE__ */ React.createElement("div", { className: "cs-terminal__bar" }, /* @__PURE__ */ React.createElement("i", null), /* @__PURE__ */ React.createElement("i", null), /* @__PURE__ */ React.createElement("i", null), /* @__PURE__ */ React.createElement("span", null, title)), /* @__PURE__ */ React.createElement("div", { className: "cs-terminal__body" }, hist.map((l, i) => l.cmd != null ? /* @__PURE__ */ React.createElement("div", { key: i, className: "cs-terminal__line" }, /* @__PURE__ */ React.createElement("span", { className: "p" }, prompt), " ", l.cmd) : /* @__PURE__ */ React.createElement("div", { key: i, className: "cs-terminal__out" }, l.out)), /* @__PURE__ */ React.createElement("div", { className: "cs-terminal__line" }, /* @__PURE__ */ React.createElement("span", { className: "p" }, prompt), /* @__PURE__ */ React.createElement(
1069
+ return /* @__PURE__ */ React.createElement("div", { ref, className: cx("cs-terminal", className) }, /* @__PURE__ */ React.createElement("div", { className: "cs-terminal__bar" }, /* @__PURE__ */ React.createElement("i", null), /* @__PURE__ */ React.createElement("i", null), /* @__PURE__ */ React.createElement("i", null), /* @__PURE__ */ React.createElement("span", null, barTitle)), /* @__PURE__ */ React.createElement("div", { className: "cs-terminal__body" }, hist.map((l, i) => l.cmd != null ? /* @__PURE__ */ React.createElement("div", { key: i, className: "cs-terminal__line" }, /* @__PURE__ */ React.createElement("span", { className: "p" }, prompt), " ", l.cmd) : /* @__PURE__ */ React.createElement("div", { key: i, className: "cs-terminal__out" }, l.out)), /* @__PURE__ */ React.createElement("div", { className: "cs-terminal__line" }, /* @__PURE__ */ React.createElement("span", { className: "p" }, prompt), /* @__PURE__ */ React.createElement(
996
1070
  "input",
997
1071
  {
998
1072
  value: q,
@@ -1019,9 +1093,12 @@ Object.assign(__ds_scope, { Timeline });
1019
1093
 
1020
1094
  // components/data/Tooltip.jsx
1021
1095
  try { (() => {
1096
+ const { makeT, useLang } = __ds_scope;
1022
1097
  const { cx } = __ds_scope;
1023
- function Tooltip({ label, children, className }) {
1024
- return /* @__PURE__ */ React.createElement("span", { className: cx("cs-tooltip", className) }, children, /* @__PURE__ */ React.createElement("span", { className: "cs-tooltip__bubble", role: "tooltip" }, label));
1098
+ function Tooltip({ label, children, lang, className }) {
1099
+ const [ref, L] = useLang(lang);
1100
+ const t = makeT("Tooltip", L);
1101
+ return /* @__PURE__ */ React.createElement("span", { ref, className: cx("cs-tooltip", className) }, children, /* @__PURE__ */ React.createElement("span", { className: "cs-tooltip__bubble", role: "tooltip" }, label ?? t("label")));
1025
1102
  }
1026
1103
  Object.assign(__ds_scope, { Tooltip });
1027
1104
  })(); } catch (e) { __ds_ns.__errors.push({ path: "components/data/Tooltip.jsx", error: String((e && e.message) || e) }); }
@@ -1029,16 +1106,135 @@ Object.assign(__ds_scope, { Tooltip });
1029
1106
  // components/data/Tree.jsx
1030
1107
  try { (() => {
1031
1108
  const { cx } = __ds_scope;
1032
- function Node({ n, depth, selected, onSelect, defaultOpen }) {
1033
- const [open, setOpen] = React.useState(!!defaultOpen);
1034
- const kids = n.children || [];
1035
- return /* @__PURE__ */ React.createElement("li", { role: "treeitem", "aria-expanded": kids.length ? open : void 0, "aria-selected": selected === n.key }, /* @__PURE__ */ React.createElement("span", { className: cx("cs-tree__row", selected === n.key && "is-selected"), style: { paddingInlineStart: depth * 18 + 6 } }, kids.length ? /* @__PURE__ */ React.createElement("button", { type: "button", className: "cs-tree__twist", "aria-hidden": "true", tabIndex: -1, onClick: () => setOpen((o) => !o) }, open ? "▾" : "▸") : /* @__PURE__ */ React.createElement("span", { className: "cs-tree__twist", "aria-hidden": "true" }), /* @__PURE__ */ React.createElement("button", { type: "button", className: "cs-tree__label", onClick: () => {
1036
- onSelect && onSelect(n.key, n);
1037
- if (kids.length) setOpen((o) => !o);
1038
- } }, n.label)), kids.length && open ? /* @__PURE__ */ React.createElement("ul", { role: "group" }, kids.map((c) => /* @__PURE__ */ React.createElement(Node, { key: c.key, n: c, depth: depth + 1, selected, onSelect, defaultOpen }))) : null);
1109
+ function collectVisible(nodes, openMap, out = []) {
1110
+ for (const n of nodes) {
1111
+ out.push(n);
1112
+ const kids = n.children || [];
1113
+ if (kids.length && openMap[n.key]) collectVisible(kids, openMap, out);
1114
+ }
1115
+ return out;
1116
+ }
1117
+ function parentOf(nodes, key, parent = null) {
1118
+ for (const n of nodes) {
1119
+ if (n.key === key) return parent;
1120
+ const kids = n.children || [];
1121
+ if (kids.length) {
1122
+ const p = parentOf(kids, key, n);
1123
+ if (p !== void 0) return p;
1124
+ }
1125
+ }
1126
+ return void 0;
1127
+ }
1128
+ function findNode(nodes, key) {
1129
+ for (const n of nodes) {
1130
+ if (n.key === key) return n;
1131
+ const kids = n.children || [];
1132
+ if (kids.length) {
1133
+ const hit = findNode(kids, key);
1134
+ if (hit) return hit;
1135
+ }
1136
+ }
1137
+ return null;
1138
+ }
1139
+ function seedOpen(nodes, defaultOpen, map = {}) {
1140
+ for (const n of nodes) {
1141
+ const kids = n.children || [];
1142
+ if (kids.length) {
1143
+ map[n.key] = !!defaultOpen;
1144
+ seedOpen(kids, defaultOpen, map);
1145
+ }
1146
+ }
1147
+ return map;
1039
1148
  }
1040
1149
  function Tree({ nodes = [], selected, onSelect, defaultOpen = false, className }) {
1041
- return /* @__PURE__ */ React.createElement("ul", { role: "tree", className: cx("cs-tree", className) }, nodes.map((n) => /* @__PURE__ */ React.createElement(Node, { key: n.key, n, depth: 0, selected, onSelect, defaultOpen })));
1150
+ const [openMap, setOpenMap] = React.useState(() => seedOpen(nodes, defaultOpen));
1151
+ const [focusKey, setFocusKey] = React.useState(() => {
1152
+ if (selected && findNode(nodes, selected)) return selected;
1153
+ return nodes[0] ? nodes[0].key : null;
1154
+ });
1155
+ const itemRefs = React.useRef({});
1156
+ React.useEffect(() => {
1157
+ setOpenMap((prev) => {
1158
+ const next = seedOpen(nodes, defaultOpen);
1159
+ for (const k of Object.keys(next)) if (k in prev) next[k] = prev[k];
1160
+ return next;
1161
+ });
1162
+ }, [nodes, defaultOpen]);
1163
+ const visible = React.useMemo(() => collectVisible(nodes, openMap), [nodes, openMap]);
1164
+ React.useEffect(() => {
1165
+ if (!visible.length) {
1166
+ setFocusKey(null);
1167
+ return;
1168
+ }
1169
+ if (!focusKey || !visible.some((n) => n.key === focusKey)) setFocusKey(visible[0].key);
1170
+ }, [visible, focusKey]);
1171
+ const toggle = (key) => setOpenMap((m) => ({ ...m, [key]: !m[key] }));
1172
+ const focusItem = (key) => {
1173
+ setFocusKey(key);
1174
+ requestAnimationFrame(() => {
1175
+ const el = itemRefs.current[key];
1176
+ if (el) el.focus();
1177
+ });
1178
+ };
1179
+ const onKeyDown = (e, key) => {
1180
+ if (e.nativeEvent.isComposing || e.keyCode === 229) return;
1181
+ const idx = visible.findIndex((n) => n.key === key);
1182
+ if (idx < 0) return;
1183
+ const node = visible[idx];
1184
+ const kids = node.children || [];
1185
+ const open = !!openMap[key];
1186
+ if (e.key === "ArrowDown") {
1187
+ e.preventDefault();
1188
+ if (idx < visible.length - 1) focusItem(visible[idx + 1].key);
1189
+ } else if (e.key === "ArrowUp") {
1190
+ e.preventDefault();
1191
+ if (idx > 0) focusItem(visible[idx - 1].key);
1192
+ } else if (e.key === "Home") {
1193
+ e.preventDefault();
1194
+ focusItem(visible[0].key);
1195
+ } else if (e.key === "End") {
1196
+ e.preventDefault();
1197
+ focusItem(visible[visible.length - 1].key);
1198
+ } else if (e.key === "ArrowRight") {
1199
+ e.preventDefault();
1200
+ if (kids.length && !open) toggle(key);
1201
+ else if (kids.length && open) focusItem(kids[0].key);
1202
+ } else if (e.key === "ArrowLeft") {
1203
+ e.preventDefault();
1204
+ if (kids.length && open) toggle(key);
1205
+ else {
1206
+ const p = parentOf(nodes, key);
1207
+ if (p) focusItem(p.key);
1208
+ }
1209
+ } else if (e.key === "Enter" || e.key === " ") {
1210
+ e.preventDefault();
1211
+ onSelect && onSelect(key, node);
1212
+ if (kids.length) toggle(key);
1213
+ }
1214
+ };
1215
+ const renderNodes = (list, depth) => list.map((n) => {
1216
+ const kids = n.children || [];
1217
+ const open = !!openMap[n.key];
1218
+ return /* @__PURE__ */ React.createElement("li", { key: n.key, role: "treeitem", "aria-expanded": kids.length ? open : void 0, "aria-selected": selected === n.key }, /* @__PURE__ */ React.createElement("span", { className: cx("cs-tree__row", selected === n.key && "is-selected"), style: { paddingInlineStart: depth * 18 + 6 } }, kids.length ? /* @__PURE__ */ React.createElement("button", { type: "button", className: "cs-tree__twist", "aria-hidden": "true", tabIndex: -1, onClick: () => toggle(n.key) }, open ? "▾" : "▸") : /* @__PURE__ */ React.createElement("span", { className: "cs-tree__twist", "aria-hidden": "true" }), /* @__PURE__ */ React.createElement(
1219
+ "button",
1220
+ {
1221
+ type: "button",
1222
+ className: "cs-tree__label",
1223
+ tabIndex: focusKey === n.key ? 0 : -1,
1224
+ ref: (el) => {
1225
+ itemRefs.current[n.key] = el;
1226
+ },
1227
+ onClick: () => {
1228
+ onSelect && onSelect(n.key, n);
1229
+ if (kids.length) toggle(n.key);
1230
+ },
1231
+ onKeyDown: (e) => onKeyDown(e, n.key),
1232
+ onFocus: () => setFocusKey(n.key)
1233
+ },
1234
+ n.label
1235
+ )), kids.length && open ? /* @__PURE__ */ React.createElement("ul", { role: "group" }, renderNodes(kids, depth + 1)) : null);
1236
+ });
1237
+ return /* @__PURE__ */ React.createElement("ul", { role: "tree", className: cx("cs-tree", className) }, renderNodes(nodes, 0));
1042
1238
  }
1043
1239
  Object.assign(__ds_scope, { Tree });
1044
1240
  })(); } catch (e) { __ds_ns.__errors.push({ path: "components/data/Tree.jsx", error: String((e && e.message) || e) }); }
@@ -1197,27 +1393,6 @@ function QRCode({ value = "", size = 128, color = "var(--cs-color-text-primary)"
1197
1393
  Object.assign(__ds_scope, { QRCode });
1198
1394
  })(); } catch (e) { __ds_ns.__errors.push({ path: "components/data/QRCode.jsx", error: String((e && e.message) || e) }); }
1199
1395
 
1200
- // components/datatable/DataTable.jsx
1201
- try { (() => {
1202
- const { makeT, useLang } = __ds_scope;
1203
- const { cx } = __ds_scope;
1204
- function DataTable({
1205
- caption,
1206
- columns,
1207
- rows,
1208
- rowKey = "id",
1209
- emptyState,
1210
- lang,
1211
- className
1212
- }) {
1213
- const normalized = Array.isArray(rows) ? rows : [];
1214
- const [ref, L] = useLang(lang);
1215
- const es = emptyState != null ? emptyState : makeT("DataTable", L)("empty");
1216
- return /* @__PURE__ */ React.createElement("div", { ref, className: cx("cs-table-wrap", className) }, /* @__PURE__ */ React.createElement("table", { className: "cs-table" }, caption ? /* @__PURE__ */ React.createElement("caption", null, caption) : null, /* @__PURE__ */ React.createElement("thead", null, /* @__PURE__ */ React.createElement("tr", null, columns.map((c) => /* @__PURE__ */ React.createElement("th", { key: c.key, scope: "col" }, c.header)))), /* @__PURE__ */ React.createElement("tbody", null, normalized.length === 0 ? /* @__PURE__ */ React.createElement("tr", null, /* @__PURE__ */ React.createElement("td", { colSpan: columns.length, className: "cs-table__empty" }, es)) : normalized.map((row, i) => /* @__PURE__ */ React.createElement("tr", { key: row[rowKey] ?? i }, columns.map((c) => /* @__PURE__ */ React.createElement("td", { key: c.key }, c.render ? c.render(row) : row[c.key])))))));
1217
- }
1218
- Object.assign(__ds_scope, { DataTable });
1219
- })(); } catch (e) { __ds_ns.__errors.push({ path: "components/datatable/DataTable.jsx", error: String((e && e.message) || e) }); }
1220
-
1221
1396
  // components/datatable/TreeTable.jsx
1222
1397
  try { (() => {
1223
1398
  const { makeT, useLang } = __ds_scope;
@@ -1238,6 +1413,7 @@ Object.assign(__ds_scope, { TreeTable });
1238
1413
 
1239
1414
  // components/feedback/Alert.jsx
1240
1415
  try { (() => {
1416
+ const { makeT, useLang } = __ds_scope;
1241
1417
  const { cx } = __ds_scope;
1242
1418
  const PATHS = {
1243
1419
  info: "M12 8h.01M11 12h1v4h1 M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18z",
@@ -1248,8 +1424,10 @@ const PATHS = {
1248
1424
  function DefaultIcon({ variant }) {
1249
1425
  return /* @__PURE__ */ React.createElement("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.9", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("path", { d: PATHS[variant] || PATHS.info }));
1250
1426
  }
1251
- function Alert({ variant = "info", title, icon, children, className, ...props }) {
1252
- return /* @__PURE__ */ React.createElement("div", { role: "status", className: cx("cs-alert", `cs-alert--${variant}`, className), ...props }, /* @__PURE__ */ React.createElement("span", { className: "cs-alert__icon" }, icon ?? /* @__PURE__ */ React.createElement(DefaultIcon, { variant })), /* @__PURE__ */ React.createElement("div", null, title ? /* @__PURE__ */ React.createElement("p", { className: "cs-alert__title" }, title) : null, children ? /* @__PURE__ */ React.createElement("div", { className: "cs-alert__body" }, children) : null));
1427
+ function Alert({ variant = "info", title, icon, children, onDismiss, lang, className, ...props }) {
1428
+ const [ref, L] = useLang(lang);
1429
+ const t = makeT("Alert", L);
1430
+ return /* @__PURE__ */ React.createElement("div", { ref, role: "status", className: cx("cs-alert", `cs-alert--${variant}`, className), ...props }, /* @__PURE__ */ React.createElement("span", { className: "cs-alert__icon" }, icon ?? /* @__PURE__ */ React.createElement(DefaultIcon, { variant })), /* @__PURE__ */ React.createElement("div", null, title ? /* @__PURE__ */ React.createElement("p", { className: "cs-alert__title" }, title) : null, children ? /* @__PURE__ */ React.createElement("div", { className: "cs-alert__body" }, children) : null), onDismiss ? /* @__PURE__ */ React.createElement("button", { type: "button", className: "cs-alert__dismiss", "aria-label": t("dismiss"), onClick: onDismiss }, "×") : null);
1253
1431
  }
1254
1432
  Object.assign(__ds_scope, { Alert });
1255
1433
  })(); } catch (e) { __ds_ns.__errors.push({ path: "components/feedback/Alert.jsx", error: String((e && e.message) || e) }); }
@@ -1265,29 +1443,36 @@ Object.assign(__ds_scope, { Badge });
1265
1443
 
1266
1444
  // components/feedback/EmptyState.jsx
1267
1445
  try { (() => {
1446
+ const { makeT, useLang } = __ds_scope;
1268
1447
  const { cx } = __ds_scope;
1269
- function EmptyState({ icon, title, children, actions, className }) {
1270
- return /* @__PURE__ */ React.createElement("div", { className: cx("cs-empty", className) }, /* @__PURE__ */ React.createElement("span", { className: "cs-empty__icon" }, icon ?? /* @__PURE__ */ React.createElement("svg", { width: "26", height: "26", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("path", { d: "M3 8l2-4h14l2 4M3 8v10a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1V8M3 8h6l1 3h4l1-3h6" }))), title ? /* @__PURE__ */ React.createElement("div", { className: "cs-empty__title" }, title) : null, children ? /* @__PURE__ */ React.createElement("div", { className: "cs-empty__body" }, children) : null, actions ? /* @__PURE__ */ React.createElement("div", { className: "cs-empty__actions" }, actions) : null);
1448
+ function EmptyState({ icon, title, children, actions, lang, className }) {
1449
+ const [ref, L] = useLang(lang);
1450
+ const t = makeT("EmptyState", L);
1451
+ return /* @__PURE__ */ React.createElement("div", { ref, className: cx("cs-empty", className) }, /* @__PURE__ */ React.createElement("span", { className: "cs-empty__icon" }, icon ?? /* @__PURE__ */ React.createElement("svg", { width: "26", height: "26", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("path", { d: "M3 8l2-4h14l2 4M3 8v10a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1V8M3 8h6l1 3h4l1-3h6" }))), /* @__PURE__ */ React.createElement("div", { className: "cs-empty__title" }, title ?? t("title")), children ? /* @__PURE__ */ React.createElement("div", { className: "cs-empty__body" }, children) : null, actions ? /* @__PURE__ */ React.createElement("div", { className: "cs-empty__actions" }, actions) : null);
1271
1452
  }
1272
1453
  Object.assign(__ds_scope, { EmptyState });
1273
1454
  })(); } catch (e) { __ds_ns.__errors.push({ path: "components/feedback/EmptyState.jsx", error: String((e && e.message) || e) }); }
1274
1455
 
1275
1456
  // components/feedback/ProgressBar.jsx
1276
1457
  try { (() => {
1458
+ const { makeT, useLang } = __ds_scope;
1277
1459
  const { cx } = __ds_scope;
1278
- function ProgressBar({ value = 0, max = 100, variant, label, className }) {
1460
+ function ProgressBar({ value = 0, max = 100, variant, label, lang, className }) {
1279
1461
  const safeMax = max > 0 ? max : 100;
1280
1462
  const clamped = Math.max(0, Math.min(safeMax, Number(value) || 0));
1281
1463
  const pct = Math.max(0, Math.min(100, clamped / safeMax * 100));
1464
+ const [ref, L] = useLang(lang);
1465
+ const t = makeT("ProgressBar", L);
1282
1466
  return /* @__PURE__ */ React.createElement(
1283
1467
  "div",
1284
1468
  {
1469
+ ref,
1285
1470
  className: cx("cs-progress", variant && `cs-progress--${variant}`, className),
1286
1471
  role: "progressbar",
1287
1472
  "aria-valuenow": Math.round(clamped),
1288
1473
  "aria-valuemin": 0,
1289
1474
  "aria-valuemax": safeMax,
1290
- "aria-label": label
1475
+ "aria-label": label ?? t("label")
1291
1476
  },
1292
1477
  /* @__PURE__ */ React.createElement("span", { className: "cs-progress__fill", style: { inlineSize: pct + "%", width: pct + "%" } })
1293
1478
  );
@@ -1333,6 +1518,41 @@ function Skeleton({ variant = "block", width, height, lines, radius, className,
1333
1518
  Object.assign(__ds_scope, { Skeleton });
1334
1519
  })(); } catch (e) { __ds_ns.__errors.push({ path: "components/feedback/Skeleton.jsx", error: String((e && e.message) || e) }); }
1335
1520
 
1521
+ // components/datatable/DataTable.jsx
1522
+ try { (() => {
1523
+ const { makeT, useLang } = __ds_scope;
1524
+ const { Skeleton } = __ds_scope;
1525
+ const { Result } = __ds_scope;
1526
+ const { cx } = __ds_scope;
1527
+ function DataTable({
1528
+ caption,
1529
+ columns,
1530
+ rows,
1531
+ rowKey = "id",
1532
+ emptyState,
1533
+ state = "idle",
1534
+ errorState,
1535
+ loadingState,
1536
+ loadingRows = 5,
1537
+ lang,
1538
+ className
1539
+ }) {
1540
+ const normalized = Array.isArray(rows) ? rows : [];
1541
+ const [ref, L] = useLang(lang);
1542
+ const t = makeT("DataTable", L);
1543
+ const es = emptyState != null ? emptyState : t("empty");
1544
+ const colCount = Math.max(1, columns && columns.length || 1);
1545
+ if (state === "loading") {
1546
+ return /* @__PURE__ */ React.createElement("div", { ref, className: cx("cs-table-wrap", className), "aria-busy": "true" }, /* @__PURE__ */ React.createElement("span", { className: "cs-sr-only" }, t("loading")), loadingState != null ? loadingState : /* @__PURE__ */ React.createElement("table", { className: "cs-table" }, caption ? /* @__PURE__ */ React.createElement("caption", null, caption) : null, /* @__PURE__ */ React.createElement("thead", null, /* @__PURE__ */ React.createElement("tr", null, (columns || []).map((c) => /* @__PURE__ */ React.createElement("th", { key: c.key, scope: "col" }, c.header)))), /* @__PURE__ */ React.createElement("tbody", { "aria-hidden": "true" }, Array.from({ length: Math.max(1, loadingRows) }).map((_, i) => /* @__PURE__ */ React.createElement("tr", { key: i }, Array.from({ length: colCount }).map((__, j) => /* @__PURE__ */ React.createElement("td", { key: j }, /* @__PURE__ */ React.createElement(Skeleton, { lines: 1 }))))))));
1547
+ }
1548
+ if (state === "error") {
1549
+ return /* @__PURE__ */ React.createElement("div", { ref, className: cx("cs-table-wrap", "cs-table-wrap--status", className), role: "alert" }, errorState != null ? errorState : /* @__PURE__ */ React.createElement(Result, { status: "error", lang: L, title: t("error") }, t("errorHint")));
1550
+ }
1551
+ return /* @__PURE__ */ React.createElement("div", { ref, className: cx("cs-table-wrap", className) }, /* @__PURE__ */ React.createElement("table", { className: "cs-table" }, caption ? /* @__PURE__ */ React.createElement("caption", null, caption) : null, /* @__PURE__ */ React.createElement("thead", null, /* @__PURE__ */ React.createElement("tr", null, (columns || []).map((c) => /* @__PURE__ */ React.createElement("th", { key: c.key, scope: "col" }, c.header)))), /* @__PURE__ */ React.createElement("tbody", null, normalized.length === 0 ? /* @__PURE__ */ React.createElement("tr", null, /* @__PURE__ */ React.createElement("td", { colSpan: colCount, className: "cs-table__empty" }, es)) : normalized.map((row, i) => /* @__PURE__ */ React.createElement("tr", { key: row[rowKey] ?? i }, (columns || []).map((c) => /* @__PURE__ */ React.createElement("td", { key: c.key }, c.render ? c.render(row) : row[c.key])))))));
1552
+ }
1553
+ Object.assign(__ds_scope, { DataTable });
1554
+ })(); } catch (e) { __ds_ns.__errors.push({ path: "components/datatable/DataTable.jsx", error: String((e && e.message) || e) }); }
1555
+
1336
1556
  // components/feedback/Spinner.jsx
1337
1557
  try { (() => {
1338
1558
  const { makeT, useLang } = __ds_scope;
@@ -1370,13 +1590,21 @@ Object.assign(__ds_scope, { Tag });
1370
1590
  try { (() => {
1371
1591
  const { makeT, useLang } = __ds_scope;
1372
1592
  const { cx } = __ds_scope;
1593
+ const PATHS = {
1594
+ default: "M12 8h.01M11 12h1v4h1 M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18z",
1595
+ success: "M4 12.5l5 5 11-11",
1596
+ danger: "M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18z M9 9l6 6 M15 9l-6 6"
1597
+ };
1598
+ function DefaultIcon({ variant }) {
1599
+ return /* @__PURE__ */ React.createElement("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("path", { d: PATHS[variant] || PATHS.default }));
1600
+ }
1373
1601
  function ToastStack({ children, lang, className }) {
1374
1602
  const [ref, L] = useLang(lang);
1375
1603
  return /* @__PURE__ */ React.createElement("div", { ref, className: cx("cs-toast-stack", className), role: "region", "aria-label": makeT("Toast", L)("notifications") }, children);
1376
1604
  }
1377
1605
  function Toast({ variant = "default", title, icon, onClose, lang, children, className, ...props }) {
1378
1606
  const [ref, L] = useLang(lang);
1379
- return /* @__PURE__ */ React.createElement("div", { ref, className: cx("cs-toast", `cs-toast--${variant}`, className), role: "status", ...props }, /* @__PURE__ */ React.createElement("span", { className: "cs-toast__icon", "aria-hidden": "true" }, icon ?? /* @__PURE__ */ React.createElement("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("path", { d: "M4 12.5l5 5 11-11" }))), /* @__PURE__ */ React.createElement("div", null, title ? /* @__PURE__ */ React.createElement("div", { className: "cs-toast__title" }, title) : null, children ? /* @__PURE__ */ React.createElement("div", { className: "cs-toast__body" }, children) : null), onClose ? /* @__PURE__ */ React.createElement("button", { type: "button", className: "cs-toast__close", "aria-label": makeT("Toast", L)("dismiss"), onClick: onClose }, /* @__PURE__ */ React.createElement("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }, /* @__PURE__ */ React.createElement("path", { d: "M6 6l12 12M18 6L6 18" }))) : null);
1607
+ return /* @__PURE__ */ React.createElement("div", { ref, className: cx("cs-toast", `cs-toast--${variant}`, className), role: "status", ...props }, /* @__PURE__ */ React.createElement("span", { className: "cs-toast__icon", "aria-hidden": "true" }, icon ?? /* @__PURE__ */ React.createElement(DefaultIcon, { variant })), /* @__PURE__ */ React.createElement("div", null, title ? /* @__PURE__ */ React.createElement("div", { className: "cs-toast__title" }, title) : null, children ? /* @__PURE__ */ React.createElement("div", { className: "cs-toast__body" }, children) : null), onClose ? /* @__PURE__ */ React.createElement("button", { type: "button", className: "cs-toast__close", "aria-label": makeT("Toast", L)("dismiss"), onClick: onClose }, /* @__PURE__ */ React.createElement("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }, /* @__PURE__ */ React.createElement("path", { d: "M6 6l12 12M18 6L6 18" }))) : null);
1380
1608
  }
1381
1609
  Object.assign(__ds_scope, { ToastStack, Toast });
1382
1610
  })(); } catch (e) { __ds_ns.__errors.push({ path: "components/feedback/Toast.jsx", error: String((e && e.message) || e) }); }
@@ -1425,7 +1653,8 @@ Object.assign(__ds_scope, { Calendar });
1425
1653
  try { (() => {
1426
1654
  const { makeT, useLang } = __ds_scope;
1427
1655
  const { cx } = __ds_scope;
1428
- function Cascader({ nodes = [], value = [], onChange, placeholder, label, disabled = false, lang, className }) {
1656
+ const EMPTY_PATH = [];
1657
+ function Cascader({ nodes = [], value = EMPTY_PATH, onChange, placeholder, label, disabled = false, lang, className }) {
1429
1658
  const [open, setOpen] = React.useState(false);
1430
1659
  const [path, setPath] = React.useState(value);
1431
1660
  const wrap = React.useRef(null);
@@ -1433,9 +1662,10 @@ function Cascader({ nodes = [], value = [], onChange, placeholder, label, disabl
1433
1662
  const [ref, L] = useLang(lang);
1434
1663
  const t = makeT("Cascader", L);
1435
1664
  const ph = placeholder != null ? placeholder : t("placeholder");
1665
+ const valueKey = Array.isArray(value) ? value.join("\0") : "";
1436
1666
  React.useEffect(() => {
1437
- setPath(value);
1438
- }, [open]);
1667
+ setPath(Array.isArray(value) ? value : EMPTY_PATH);
1668
+ }, [valueKey]);
1439
1669
  React.useEffect(() => {
1440
1670
  if (!open) return;
1441
1671
  const d = (e) => {
@@ -1915,6 +2145,7 @@ function Form({ onSubmit, errors = {}, rules, asyncRules, initialValues, childre
1915
2145
  const [values, setValues] = React.useState(() => initialValues || {});
1916
2146
  const [ruleErrors, setRuleErrors] = React.useState({});
1917
2147
  const [pending, setPending] = React.useState(false);
2148
+ const pendingRef = React.useRef(false);
1918
2149
  const setValue = React.useCallback((name, v) => {
1919
2150
  setValues((s) => String(name).includes(".") ? setPath(s, name, v) : { ...s, [name]: v });
1920
2151
  setRuleErrors((e) => e[name] ? { ...e, [name]: void 0 } : e);
@@ -1961,8 +2192,10 @@ function Form({ onSubmit, errors = {}, rules, asyncRules, initialValues, childre
1961
2192
  ref,
1962
2193
  className: cx("cs-form", pending && "is-pending", className),
1963
2194
  noValidate: true,
2195
+ "aria-busy": pending || void 0,
1964
2196
  onSubmit: async (e) => {
1965
2197
  e.preventDefault();
2198
+ if (pendingRef.current) return;
1966
2199
  const fd = new FormData(e.currentTarget);
1967
2200
  const v = {};
1968
2201
  fd.forEach((val, k) => {
@@ -1974,6 +2207,7 @@ function Form({ onSubmit, errors = {}, rules, asyncRules, initialValues, childre
1974
2207
  setRuleErrors(errs);
1975
2208
  return;
1976
2209
  }
2210
+ pendingRef.current = true;
1977
2211
  setPending(true);
1978
2212
  try {
1979
2213
  const aerrs = await runAsyncRules(v);
@@ -1981,6 +2215,7 @@ function Form({ onSubmit, errors = {}, rules, asyncRules, initialValues, childre
1981
2215
  if (Object.keys(aerrs).some((k) => aerrs[k])) return;
1982
2216
  onSubmit && onSubmit(v);
1983
2217
  } finally {
2218
+ pendingRef.current = false;
1984
2219
  setPending(false);
1985
2220
  }
1986
2221
  }
@@ -2281,6 +2516,7 @@ Object.assign(__ds_scope, { InputOTP });
2281
2516
  try { (() => {
2282
2517
  const { makeT, useLang } = __ds_scope;
2283
2518
  const { cx } = __ds_scope;
2519
+ let mentionsUid = 0;
2284
2520
  function Mentions({ value, defaultValue = "", onChange, users = [], placeholder, rows = 3, lang, className }) {
2285
2521
  const [inner, setInner] = React.useState(defaultValue);
2286
2522
  const val = value != null ? value : inner;
@@ -2289,23 +2525,80 @@ function Mentions({ value, defaultValue = "", onChange, users = [], placeholder,
2289
2525
  onChange && onChange(v);
2290
2526
  };
2291
2527
  const [q, setQ] = React.useState(null);
2528
+ const [activeIndex, setActiveIndex] = React.useState(0);
2529
+ const [listId] = React.useState(() => "cs-mentions-" + ++mentionsUid);
2292
2530
  const [ref, L] = useLang(lang);
2293
2531
  const t = makeT("Mentions", L);
2294
2532
  const ph = placeholder != null ? placeholder : t("placeholder");
2533
+ const hits = q == null ? [] : users.filter((u) => u.toLowerCase().includes(q)).slice(0, 6);
2534
+ const open = hits.length > 0;
2535
+ React.useEffect(() => {
2536
+ setActiveIndex((i) => hits.length ? Math.min(i, hits.length - 1) : 0);
2537
+ }, [q, users]);
2295
2538
  const onInput = (v) => {
2296
2539
  set(v);
2297
2540
  const m = /(^|\s)@(\w*)$/.exec(v);
2298
- setQ(m ? m[2].toLowerCase() : null);
2541
+ const next = m ? m[2].toLowerCase() : null;
2542
+ setQ(next);
2543
+ if (next != null) setActiveIndex(0);
2299
2544
  };
2300
- const hits = q == null ? [] : users.filter((u) => u.toLowerCase().includes(q)).slice(0, 6);
2301
2545
  const pick = (u) => {
2302
2546
  set(val.replace(/(^|\s)@\w*$/, "$1@" + u + " "));
2303
2547
  setQ(null);
2304
2548
  };
2305
- return /* @__PURE__ */ React.createElement("span", { ref, className: cx("cs-mentions", className) }, /* @__PURE__ */ React.createElement("textarea", { className: "cs-field__control", rows, value: val, placeholder: ph, onChange: (e) => onInput(e.target.value) }), hits.length ? /* @__PURE__ */ React.createElement("span", { className: "cs-mentions__pop", role: "listbox" }, hits.map((u) => /* @__PURE__ */ React.createElement("button", { key: u, type: "button", role: "option", onMouseDown: (e) => {
2306
- e.preventDefault();
2307
- pick(u);
2308
- } }, "@", u))) : null);
2549
+ const optionId = (i) => listId + "-opt-" + i;
2550
+ const onKeyDown = (e) => {
2551
+ if (e.nativeEvent.isComposing || e.keyCode === 229) return;
2552
+ if (!open) return;
2553
+ if (e.key === "ArrowDown") {
2554
+ e.preventDefault();
2555
+ setActiveIndex((i) => Math.min(hits.length - 1, i + 1));
2556
+ } else if (e.key === "ArrowUp") {
2557
+ e.preventDefault();
2558
+ setActiveIndex((i) => Math.max(0, i - 1));
2559
+ } else if (e.key === "Enter") {
2560
+ if (hits[activeIndex]) {
2561
+ e.preventDefault();
2562
+ pick(hits[activeIndex]);
2563
+ }
2564
+ } else if (e.key === "Escape") {
2565
+ e.preventDefault();
2566
+ setQ(null);
2567
+ }
2568
+ };
2569
+ return /* @__PURE__ */ React.createElement("span", { ref, className: cx("cs-mentions", className) }, /* @__PURE__ */ React.createElement(
2570
+ "textarea",
2571
+ {
2572
+ className: "cs-field__control",
2573
+ rows,
2574
+ value: val,
2575
+ placeholder: ph,
2576
+ role: "combobox",
2577
+ "aria-autocomplete": "list",
2578
+ "aria-expanded": open,
2579
+ "aria-controls": listId,
2580
+ "aria-activedescendant": open && hits[activeIndex] ? optionId(activeIndex) : void 0,
2581
+ onChange: (e) => onInput(e.target.value),
2582
+ onKeyDown
2583
+ }
2584
+ ), open ? /* @__PURE__ */ React.createElement("span", { className: "cs-mentions__pop", role: "listbox", id: listId }, hits.map((u, i) => /* @__PURE__ */ React.createElement(
2585
+ "button",
2586
+ {
2587
+ key: u,
2588
+ type: "button",
2589
+ id: optionId(i),
2590
+ role: "option",
2591
+ "aria-selected": i === activeIndex,
2592
+ className: cx(i === activeIndex && "hl"),
2593
+ onMouseEnter: () => setActiveIndex(i),
2594
+ onMouseDown: (e) => {
2595
+ e.preventDefault();
2596
+ pick(u);
2597
+ }
2598
+ },
2599
+ "@",
2600
+ u
2601
+ ))) : null);
2309
2602
  }
2310
2603
  Object.assign(__ds_scope, { Mentions });
2311
2604
  })(); } catch (e) { __ds_ns.__errors.push({ path: "components/forms/Mentions.jsx", error: String((e && e.message) || e) }); }
@@ -2435,15 +2728,57 @@ const STAR = "M12 2l2.9 6.2 6.6.8-4.9 4.6 1.3 6.5L12 16.9 6.1 20l1.3-6.5L2.5 9l6
2435
2728
  function Rating({ value, defaultValue = 0, onChange, max = 5, readOnly = false, label, lang, className }) {
2436
2729
  const [inner, setInner] = React.useState(defaultValue);
2437
2730
  const val = value != null ? value : inner;
2438
- const set = (n) => {
2731
+ const commit = (n) => {
2439
2732
  if (readOnly) return;
2440
- const v = n === val ? 0 : n;
2441
- if (value == null) setInner(v);
2442
- onChange && onChange(v);
2733
+ if (value == null) setInner(n);
2734
+ onChange && onChange(n);
2443
2735
  };
2736
+ const set = (n) => commit(n === val ? 0 : n);
2444
2737
  const [ref, L] = useLang(lang);
2445
2738
  const lbl = label != null ? label : makeT("Rating", L)("label");
2446
- return /* @__PURE__ */ React.createElement("div", { ref, className: cx("cs-rating", className), role: "radiogroup", "aria-label": lbl + ": " + val + " / " + max, "data-readonly": readOnly ? "true" : void 0 }, Array.from({ length: max }).map((_, i) => /* @__PURE__ */ React.createElement("button", { key: i, type: "button", role: "radio", "aria-checked": i < val, "aria-label": i + 1 + " / " + max, className: i < val ? "on" : void 0, onClick: () => set(i + 1), tabIndex: readOnly ? -1 : 0 }, /* @__PURE__ */ React.createElement("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: i < val ? "currentColor" : "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinejoin: "round", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("path", { d: STAR })))));
2739
+ const refs = React.useRef([]);
2740
+ const focusIdx = val > 0 ? Math.min(max, val) - 1 : 0;
2741
+ const move = (from, delta) => {
2742
+ if (readOnly) return;
2743
+ const next = Math.max(0, Math.min(max - 1, from + delta));
2744
+ commit(next + 1);
2745
+ const b = refs.current[next];
2746
+ if (b) b.focus();
2747
+ };
2748
+ const onKeyDown = (e, i) => {
2749
+ if (readOnly || e.nativeEvent.isComposing || e.keyCode === 229) return;
2750
+ if (e.key === "ArrowRight" || e.key === "ArrowUp") {
2751
+ e.preventDefault();
2752
+ move(i, 1);
2753
+ } else if (e.key === "ArrowLeft" || e.key === "ArrowDown") {
2754
+ e.preventDefault();
2755
+ move(i, -1);
2756
+ } else if (e.key === "Home") {
2757
+ e.preventDefault();
2758
+ move(i, -i);
2759
+ } else if (e.key === "End") {
2760
+ e.preventDefault();
2761
+ move(i, max - 1 - i);
2762
+ }
2763
+ };
2764
+ return /* @__PURE__ */ React.createElement("div", { ref, className: cx("cs-rating", className), role: "radiogroup", "aria-label": lbl + ": " + val + " / " + max, "data-readonly": readOnly ? "true" : void 0 }, Array.from({ length: max }).map((_, i) => /* @__PURE__ */ React.createElement(
2765
+ "button",
2766
+ {
2767
+ key: i,
2768
+ type: "button",
2769
+ role: "radio",
2770
+ "aria-checked": i < val,
2771
+ "aria-label": i + 1 + " / " + max,
2772
+ className: i < val ? "on" : void 0,
2773
+ onClick: () => set(i + 1),
2774
+ tabIndex: readOnly ? -1 : i === focusIdx ? 0 : -1,
2775
+ ref: (el) => {
2776
+ refs.current[i] = el;
2777
+ },
2778
+ onKeyDown: (e) => onKeyDown(e, i)
2779
+ },
2780
+ /* @__PURE__ */ React.createElement("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: i < val ? "currentColor" : "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinejoin: "round", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("path", { d: STAR }))
2781
+ )));
2447
2782
  }
2448
2783
  Object.assign(__ds_scope, { Rating });
2449
2784
  })(); } catch (e) { __ds_ns.__errors.push({ path: "components/forms/Rating.jsx", error: String((e && e.message) || e) }); }
@@ -2506,44 +2841,49 @@ Object.assign(__ds_scope, { SegmentedControl });
2506
2841
 
2507
2842
  // components/forms/Select.jsx
2508
2843
  try { (() => {
2844
+ const { makeT, useLang } = __ds_scope;
2509
2845
  const { cx } = __ds_scope;
2510
- function Select({ id, label, description, error, options, children, disabled = false, className, ...props }) {
2846
+ function Select({ id, label, description, error, options, children, disabled = false, lang, className, placeholder, ...props }) {
2847
+ const [ref, L] = useLang(lang);
2848
+ const t = makeT("Select", L);
2511
2849
  const gid = React.useId();
2512
2850
  const sid = id ?? gid;
2513
2851
  const descId = description ? sid + "-desc" : void 0;
2514
2852
  const errId = error ? sid + "-err" : void 0;
2515
2853
  const describedBy = [descId, errId].filter(Boolean).join(" ") || void 0;
2516
- return /* @__PURE__ */ React.createElement("label", { className: cx("cs-field", disabled && "is-disabled", error && "is-invalid", className), htmlFor: sid }, label ? /* @__PURE__ */ React.createElement("span", { className: "cs-field__label" }, label) : null, description ? /* @__PURE__ */ React.createElement("span", { id: descId, className: "cs-field__description" }, description) : null, /* @__PURE__ */ React.createElement("span", { className: "cs-select" }, /* @__PURE__ */ React.createElement("select", { ...props, id: sid, disabled, "aria-invalid": error ? true : void 0, "aria-describedby": describedBy, className: "cs-field__control" }, options ? options.map((o) => /* @__PURE__ */ React.createElement("option", { key: o.value, value: o.value }, o.label)) : children), /* @__PURE__ */ React.createElement("span", { className: "cs-select__chevron", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("path", { d: "M6 9l6 6 6-6" })))), error ? /* @__PURE__ */ React.createElement("span", { id: errId, className: "cs-field__error", role: "alert" }, error) : null);
2854
+ return /* @__PURE__ */ React.createElement("label", { ref, className: cx("cs-field", disabled && "is-disabled", error && "is-invalid", className), htmlFor: sid }, label ? /* @__PURE__ */ React.createElement("span", { className: "cs-field__label" }, label) : null, description ? /* @__PURE__ */ React.createElement("span", { id: descId, className: "cs-field__description" }, description) : null, /* @__PURE__ */ React.createElement("span", { className: "cs-select" }, /* @__PURE__ */ React.createElement("select", { ...props, id: sid, disabled, "aria-invalid": error ? true : void 0, "aria-describedby": describedBy, className: "cs-field__control" }, placeholder != null || !children ? /* @__PURE__ */ React.createElement("option", { value: "", disabled: true, hidden: true }, placeholder ?? t("placeholder")) : null, options ? options.map((o) => /* @__PURE__ */ React.createElement("option", { key: o.value, value: o.value }, o.label)) : children), /* @__PURE__ */ React.createElement("span", { className: "cs-select__chevron", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("path", { d: "M6 9l6 6 6-6" })))), error ? /* @__PURE__ */ React.createElement("span", { id: errId, className: "cs-field__error", role: "alert" }, error) : null);
2517
2855
  }
2518
2856
  Object.assign(__ds_scope, { Select });
2519
2857
  })(); } catch (e) { __ds_ns.__errors.push({ path: "components/forms/Select.jsx", error: String((e && e.message) || e) }); }
2520
2858
 
2521
2859
  // components/forms/Slider.jsx
2522
2860
  try { (() => {
2861
+ const { makeT, useLang } = __ds_scope;
2523
2862
  const { cx } = __ds_scope;
2524
- function Slider({ className, children, ...props }) {
2525
- return /* @__PURE__ */ React.createElement("input", { type: "range", className: cx("cs-slider", className), ...props });
2863
+ function Slider({ className, children, lang, "aria-label": ariaLabel, ...props }) {
2864
+ const [ref, L] = useLang(lang);
2865
+ const t = makeT("Slider", L);
2866
+ return /* @__PURE__ */ React.createElement("input", { ref, type: "range", className: cx("cs-slider", className), "aria-label": ariaLabel ?? t("label"), ...props });
2526
2867
  }
2527
2868
  Object.assign(__ds_scope, { Slider });
2528
2869
  })(); } catch (e) { __ds_ns.__errors.push({ path: "components/forms/Slider.jsx", error: String((e && e.message) || e) }); }
2529
2870
 
2530
2871
  // components/forms/Switch.jsx
2531
2872
  try { (() => {
2873
+ const { makeT, useLang } = __ds_scope;
2532
2874
  const { cx } = __ds_scope;
2533
- let warnedNameless = false;
2534
- function Switch({ label, disabled = false, className, children, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, ...props }) {
2875
+ function Switch({ label, disabled = false, className, children, lang, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, ...props }) {
2876
+ const [ref, L] = useLang(lang);
2877
+ const t = makeT("Switch", L);
2535
2878
  const named = !!(label || ariaLabel || ariaLabelledby);
2536
- if (!named && !warnedNameless && typeof console !== "undefined" && console.warn) {
2537
- warnedNameless = true;
2538
- console.warn("CyberSkill Switch: render with label, aria-label, or aria-labelledby.");
2539
- }
2540
- return /* @__PURE__ */ React.createElement("label", { className: cx("cs-switch", disabled && "is-disabled", className) }, /* @__PURE__ */ React.createElement(
2879
+ const fallback = props.checked || props.defaultChecked ? t("on") : t("off");
2880
+ return /* @__PURE__ */ React.createElement("label", { ref, className: cx("cs-switch", disabled && "is-disabled", className) }, /* @__PURE__ */ React.createElement(
2541
2881
  "input",
2542
2882
  {
2543
2883
  type: "checkbox",
2544
2884
  role: "switch",
2545
2885
  disabled,
2546
- "aria-label": ariaLabel,
2886
+ "aria-label": ariaLabel ?? (named ? void 0 : fallback),
2547
2887
  "aria-labelledby": ariaLabelledby,
2548
2888
  ...props
2549
2889
  }
@@ -2660,16 +3000,33 @@ Object.assign(__ds_scope, { TimePicker });
2660
3000
 
2661
3001
  // components/forms/Toggle.jsx
2662
3002
  try { (() => {
3003
+ const { makeT, useLang } = __ds_scope;
2663
3004
  const { cx } = __ds_scope;
2664
- function Toggle({ pressed, defaultPressed = false, onChange, icon, children, disabled = false, className, ...props }) {
3005
+ function Toggle({ pressed, defaultPressed = false, onChange, icon, children, disabled = false, lang, className, "aria-label": ariaLabel, ...props }) {
2665
3006
  const [inner, setInner] = React.useState(defaultPressed);
2666
3007
  const on = pressed != null ? pressed : inner;
3008
+ const [ref, L] = useLang(lang);
3009
+ const t = makeT("Toggle", L);
2667
3010
  const flip = () => {
2668
3011
  const v = !on;
2669
3012
  if (pressed == null) setInner(v);
2670
3013
  onChange && onChange(v);
2671
3014
  };
2672
- return /* @__PURE__ */ React.createElement("button", { type: "button", className: cx("cs-toggle", className), "aria-pressed": on, disabled, onClick: flip, ...props }, icon ? /* @__PURE__ */ React.createElement("span", { "aria-hidden": "true", style: { display: "inline-flex" } }, icon) : null, children);
3015
+ return /* @__PURE__ */ React.createElement(
3016
+ "button",
3017
+ {
3018
+ ref,
3019
+ type: "button",
3020
+ className: cx("cs-toggle", className),
3021
+ "aria-pressed": on,
3022
+ "aria-label": ariaLabel ?? (on ? t("pressed") : t("unpressed")),
3023
+ disabled,
3024
+ onClick: flip,
3025
+ ...props
3026
+ },
3027
+ icon ? /* @__PURE__ */ React.createElement("span", { "aria-hidden": "true", style: { display: "inline-flex" } }, icon) : null,
3028
+ children
3029
+ );
2673
3030
  }
2674
3031
  Object.assign(__ds_scope, { Toggle });
2675
3032
  })(); } catch (e) { __ds_ns.__errors.push({ path: "components/forms/Toggle.jsx", error: String((e && e.message) || e) }); }
@@ -2835,16 +3192,26 @@ function Image({ src, alt = "", ratio, preview = false, fallback, lang, classNam
2835
3192
  document.addEventListener("keydown", k);
2836
3193
  return () => document.removeEventListener("keydown", k);
2837
3194
  }, [zoom]);
3195
+ const openPreview = () => setZoom(true);
3196
+ const onPreviewKey = (e) => {
3197
+ if (e.key === "Enter" || e.key === " ") {
3198
+ e.preventDefault();
3199
+ openPreview();
3200
+ }
3201
+ };
2838
3202
  const body = state === "error" ? /* @__PURE__ */ React.createElement("span", { className: "cs-image__fallback" }, fallback || /* @__PURE__ */ React.createElement("svg", { width: "28", height: "28", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }), /* @__PURE__ */ React.createElement("circle", { cx: "9", cy: "9", r: "2" }), /* @__PURE__ */ React.createElement("path", { d: "M21 15l-5-5-9 9" }))) : /* @__PURE__ */ React.createElement("img", { ...props, src, alt, onLoad: () => setState("ok"), onError: () => setState("error") });
3203
+ const canPreview = preview && state === "ok";
2839
3204
  return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
2840
3205
  "span",
2841
3206
  {
2842
3207
  ref,
2843
- className: cx("cs-image", state === "loading" && "is-loading", preview && state === "ok" && "is-zoomable", className),
3208
+ className: cx("cs-image", state === "loading" && "is-loading", canPreview && "is-zoomable", className),
2844
3209
  style: ratio ? { aspectRatio: ratio } : void 0,
2845
- onClick: preview && state === "ok" ? () => setZoom(true) : void 0,
2846
- role: preview && state === "ok" ? "button" : void 0,
2847
- "aria-label": preview && state === "ok" ? t("preview") + (alt ? ": " + alt : "") : void 0
3210
+ onClick: canPreview ? openPreview : void 0,
3211
+ onKeyDown: canPreview ? onPreviewKey : void 0,
3212
+ role: canPreview ? "button" : void 0,
3213
+ tabIndex: canPreview ? 0 : void 0,
3214
+ "aria-label": canPreview ? t("preview") + (alt ? ": " + alt : "") : void 0
2848
3215
  },
2849
3216
  body
2850
3217
  ), zoom ? /* @__PURE__ */ React.createElement("span", { className: "cs-image__zoom", role: "dialog", "aria-label": alt || t("preview"), onClick: () => setZoom(false) }, /* @__PURE__ */ React.createElement("img", { src, alt }), /* @__PURE__ */ React.createElement("button", { type: "button", "aria-label": t("close"), onClick: () => setZoom(false) }, /* @__PURE__ */ React.createElement(Icon, { name: "close", size: "sm", style: { verticalAlign: "middle" } }))) : null);
@@ -3375,21 +3742,51 @@ Object.assign(__ds_scope, { Link });
3375
3742
 
3376
3743
  // components/navigation/Menu.jsx
3377
3744
  try { (() => {
3745
+ const { makeT, useLang } = __ds_scope;
3378
3746
  const { cx } = __ds_scope;
3379
- function Menu({ trigger, children, align = "start", open: controlledOpen, onOpenChange, className }) {
3747
+ function Menu({ trigger, children, align = "start", open: controlledOpen, onOpenChange, lang, className }) {
3380
3748
  const [uOpen, setUOpen] = React.useState(false);
3381
3749
  const open = controlledOpen != null ? controlledOpen : uOpen;
3382
3750
  const set = (v) => {
3383
3751
  onOpenChange ? onOpenChange(v) : setUOpen(v);
3384
3752
  };
3385
- const ref = React.useRef(null);
3753
+ const [ref, L] = useLang(lang);
3754
+ const t = makeT("Menu", L);
3755
+ const triggerRef = React.useRef(null);
3756
+ const listRef = React.useRef(null);
3386
3757
  React.useEffect(() => {
3387
3758
  if (!open) return;
3759
+ const items = () => listRef.current ? [...listRef.current.querySelectorAll('[role="menuitem"]:not([disabled])')] : [];
3760
+ const first = items()[0];
3761
+ if (first && first.focus) first.focus();
3388
3762
  const onDoc = (e) => {
3389
3763
  if (ref.current && !ref.current.contains(e.target)) set(false);
3390
3764
  };
3391
3765
  const onKey = (e) => {
3392
- if (e.key === "Escape") set(false);
3766
+ const opts = items();
3767
+ if (e.key === "Escape") {
3768
+ e.preventDefault();
3769
+ set(false);
3770
+ const el = triggerRef.current;
3771
+ if (el && el.focus) el.focus();
3772
+ return;
3773
+ }
3774
+ if (!opts.length) return;
3775
+ const active = document.activeElement;
3776
+ const idx = opts.indexOf(active);
3777
+ if (e.key === "ArrowDown") {
3778
+ e.preventDefault();
3779
+ (opts[idx < 0 ? 0 : Math.min(opts.length - 1, idx + 1)] || opts[0]).focus();
3780
+ } else if (e.key === "ArrowUp") {
3781
+ e.preventDefault();
3782
+ (opts[idx < 0 ? opts.length - 1 : Math.max(0, idx - 1)] || opts[0]).focus();
3783
+ } else if (e.key === "Home") {
3784
+ e.preventDefault();
3785
+ opts[0].focus();
3786
+ } else if (e.key === "End") {
3787
+ e.preventDefault();
3788
+ opts[opts.length - 1].focus();
3789
+ }
3393
3790
  };
3394
3791
  document.addEventListener("mousedown", onDoc);
3395
3792
  document.addEventListener("keydown", onKey);
@@ -3400,14 +3797,29 @@ function Menu({ trigger, children, align = "start", open: controlledOpen, onOpen
3400
3797
  }, [open]);
3401
3798
  const toggle = () => set(!open);
3402
3799
  const triggerNode = React.isValidElement(trigger) ? React.cloneElement(trigger, {
3800
+ ref: (node) => {
3801
+ triggerRef.current = node;
3802
+ const r = trigger.ref;
3803
+ if (typeof r === "function") r(node);
3804
+ else if (r && typeof r === "object") r.current = node;
3805
+ },
3403
3806
  "aria-haspopup": "menu",
3404
3807
  "aria-expanded": open,
3405
3808
  onClick: (e) => {
3406
3809
  if (typeof trigger.props.onClick === "function") trigger.props.onClick(e);
3407
3810
  if (!e.defaultPrevented) toggle();
3408
3811
  }
3409
- }) : /* @__PURE__ */ React.createElement("button", { type: "button", "aria-haspopup": "menu", "aria-expanded": open, onClick: toggle }, trigger);
3410
- return /* @__PURE__ */ React.createElement("div", { className: cx("cs-menu", className), ref }, triggerNode, open ? /* @__PURE__ */ React.createElement("div", { className: cx("cs-menu__list", align === "end" && "cs-menu__list--end"), role: "menu" }, children) : null);
3812
+ }) : /* @__PURE__ */ React.createElement("button", { type: "button", ref: triggerRef, "aria-haspopup": "menu", "aria-expanded": open, onClick: toggle }, trigger);
3813
+ return /* @__PURE__ */ React.createElement("div", { className: cx("cs-menu", className), ref }, triggerNode, open ? /* @__PURE__ */ React.createElement(
3814
+ "div",
3815
+ {
3816
+ ref: listRef,
3817
+ className: cx("cs-menu__list", align === "end" && "cs-menu__list--end"),
3818
+ role: "menu",
3819
+ "aria-label": t("menu")
3820
+ },
3821
+ children
3822
+ ) : null);
3411
3823
  }
3412
3824
  function MenuItem({ danger = false, icon, children, className, ...props }) {
3413
3825
  return /* @__PURE__ */ React.createElement("button", { type: "button", role: "menuitem", className: cx("cs-menu__item", danger && "cs-menu__item--danger", className), ...props }, icon ? /* @__PURE__ */ React.createElement("span", { "aria-hidden": "true" }, icon) : null, children);
@@ -3568,11 +3980,14 @@ Object.assign(__ds_scope, { Sidebar, NavItem });
3568
3980
 
3569
3981
  // components/navigation/Steps.jsx
3570
3982
  try { (() => {
3983
+ const { makeT, useLang } = __ds_scope;
3571
3984
  const { cx } = __ds_scope;
3572
- function Steps({ steps = [], current = 0, className }) {
3573
- return /* @__PURE__ */ React.createElement("div", { className: cx("cs-steps", className) }, steps.map((s, i) => {
3985
+ function Steps({ steps = [], current = 0, lang, className }) {
3986
+ const [ref, L] = useLang(lang);
3987
+ const t = makeT("Steps", L);
3988
+ return /* @__PURE__ */ React.createElement("div", { ref, className: cx("cs-steps", className), role: "list", "aria-label": t("label") }, steps.map((s, i) => {
3574
3989
  const state = i < current ? "done" : i === current ? "active" : "todo";
3575
- return /* @__PURE__ */ React.createElement("div", { key: i, className: cx("cs-step", `cs-step--${state}`) }, /* @__PURE__ */ React.createElement("span", { className: "cs-step__marker" }, state === "done" ? "✓" : s.n || i + 1), /* @__PURE__ */ React.createElement("span", { className: "cs-step__title" }, s.title), s.body ? /* @__PURE__ */ React.createElement("span", { className: "cs-step__body" }, s.body) : null);
3990
+ return /* @__PURE__ */ React.createElement("div", { key: i, className: cx("cs-step", `cs-step--${state}`), role: "listitem" }, /* @__PURE__ */ React.createElement("span", { className: "cs-step__marker" }, state === "done" ? "✓" : s.n || i + 1), /* @__PURE__ */ React.createElement("span", { className: "cs-step__title" }, s.title), s.body ? /* @__PURE__ */ React.createElement("span", { className: "cs-step__body" }, s.body) : null);
3576
3991
  }));
3577
3992
  }
3578
3993
  Object.assign(__ds_scope, { Steps });
@@ -3580,10 +3995,13 @@ Object.assign(__ds_scope, { Steps });
3580
3995
 
3581
3996
  // components/navigation/Tabs.jsx
3582
3997
  try { (() => {
3998
+ const { makeT, useLang } = __ds_scope;
3583
3999
  const { cx } = __ds_scope;
3584
- function Tabs({ tabs = [], value, onChange, className, ...props }) {
4000
+ function Tabs({ tabs = [], value, onChange, lang, className, "aria-label": ariaLabel, ...props }) {
4001
+ const [ref, L] = useLang(lang);
4002
+ const t = makeT("Tabs", L);
3585
4003
  const refs = React.useRef([]);
3586
- const idx = Math.max(0, tabs.findIndex((t) => t.value === value));
4004
+ const idx = Math.max(0, tabs.findIndex((tab) => tab.value === value));
3587
4005
  const key = (e, i) => {
3588
4006
  let n = null;
3589
4007
  if (e.key === "ArrowRight") n = (i + 1) % tabs.length;
@@ -3596,21 +4014,21 @@ function Tabs({ tabs = [], value, onChange, className, ...props }) {
3596
4014
  const b = refs.current[n];
3597
4015
  if (b) b.focus();
3598
4016
  };
3599
- return /* @__PURE__ */ React.createElement("div", { role: "tablist", className: cx("cs-tabs", className), ...props }, tabs.map((t, i) => /* @__PURE__ */ React.createElement(
4017
+ return /* @__PURE__ */ React.createElement("div", { ref, role: "tablist", "aria-label": ariaLabel ?? t("list"), className: cx("cs-tabs", className), ...props }, tabs.map((t2, i) => /* @__PURE__ */ React.createElement(
3600
4018
  "button",
3601
4019
  {
3602
- key: t.value,
4020
+ key: t2.value,
3603
4021
  type: "button",
3604
4022
  role: "tab",
3605
- "aria-selected": value === t.value,
4023
+ "aria-selected": value === t2.value,
3606
4024
  tabIndex: i === idx ? 0 : -1,
3607
4025
  ref: (el) => refs.current[i] = el,
3608
4026
  onKeyDown: (e) => key(e, i),
3609
4027
  className: "cs-tab",
3610
- onClick: () => onChange && onChange(t.value)
4028
+ onClick: () => onChange && onChange(t2.value)
3611
4029
  },
3612
- t.label,
3613
- t.count != null ? /* @__PURE__ */ React.createElement("span", { className: "cs-tab__count" }, t.count) : null
4030
+ t2.label,
4031
+ t2.count != null ? /* @__PURE__ */ React.createElement("span", { className: "cs-tab__count" }, t2.count) : null
3614
4032
  )));
3615
4033
  }
3616
4034
  function Tab({ selected = false, count, children, className, ...props }) {
@@ -3625,12 +4043,21 @@ const { makeT, useLang } = __ds_scope;
3625
4043
  const { cx } = __ds_scope;
3626
4044
  function Toolbar({ items = [], overflowAfter, label, lang, className }) {
3627
4045
  const [open, setOpen] = React.useState(false);
4046
+ const [focusIdx, setFocusIdx] = React.useState(0);
3628
4047
  const wrap = React.useRef(null);
4048
+ const btnRefs = React.useRef([]);
4049
+ const menuRefs = React.useRef([]);
3629
4050
  const [ref, L] = useLang(lang);
3630
4051
  const t = makeT("Toolbar", L);
3631
4052
  const cut = overflowAfter != null ? overflowAfter : items.length;
3632
4053
  const head = items.slice(0, cut);
3633
4054
  const tail = items.slice(cut).filter((x) => x !== "-");
4055
+ const hasMore = tail.length > 0;
4056
+ const controlMeta = [];
4057
+ head.forEach((it, i) => {
4058
+ if (it !== "-") controlMeta.push({ kind: "head", headIndex: i });
4059
+ });
4060
+ if (hasMore) controlMeta.push({ kind: "more" });
3634
4061
  React.useEffect(() => {
3635
4062
  if (!open) return;
3636
4063
  const d = (e) => {
@@ -3639,22 +4066,149 @@ function Toolbar({ items = [], overflowAfter, label, lang, className }) {
3639
4066
  document.addEventListener("mousedown", d);
3640
4067
  return () => document.removeEventListener("mousedown", d);
3641
4068
  }, [open]);
4069
+ React.useEffect(() => {
4070
+ if (focusIdx >= controlMeta.length) setFocusIdx(Math.max(0, controlMeta.length - 1));
4071
+ }, [focusIdx, controlMeta.length]);
4072
+ const focusControl = (i) => {
4073
+ const next = Math.max(0, Math.min(controlMeta.length - 1, i));
4074
+ setFocusIdx(next);
4075
+ requestAnimationFrame(() => {
4076
+ const el = btnRefs.current[next];
4077
+ if (el) el.focus();
4078
+ });
4079
+ };
4080
+ const onToolbarKeyDown = (e, i) => {
4081
+ if (e.nativeEvent.isComposing || e.keyCode === 229) return;
4082
+ if (e.key === "ArrowRight") {
4083
+ e.preventDefault();
4084
+ focusControl(i + 1);
4085
+ } else if (e.key === "ArrowLeft") {
4086
+ e.preventDefault();
4087
+ focusControl(i - 1);
4088
+ } else if (e.key === "Home") {
4089
+ e.preventDefault();
4090
+ focusControl(0);
4091
+ } else if (e.key === "End") {
4092
+ e.preventDefault();
4093
+ focusControl(controlMeta.length - 1);
4094
+ } else if (e.key === "Escape" && open) {
4095
+ e.preventDefault();
4096
+ setOpen(false);
4097
+ }
4098
+ };
4099
+ const onMenuKeyDown = (e, i) => {
4100
+ if (e.nativeEvent.isComposing || e.keyCode === 229) return;
4101
+ if (e.key === "ArrowDown") {
4102
+ e.preventDefault();
4103
+ const n = Math.min(tail.length - 1, i + 1);
4104
+ const el = menuRefs.current[n];
4105
+ if (el) el.focus();
4106
+ } else if (e.key === "ArrowUp") {
4107
+ e.preventDefault();
4108
+ const n = Math.max(0, i - 1);
4109
+ const el = menuRefs.current[n];
4110
+ if (el) el.focus();
4111
+ } else if (e.key === "Escape") {
4112
+ e.preventDefault();
4113
+ setOpen(false);
4114
+ const moreIdx = controlMeta.findIndex((c) => c.kind === "more");
4115
+ if (moreIdx >= 0) focusControl(moreIdx);
4116
+ } else if (e.key === "Home") {
4117
+ e.preventDefault();
4118
+ const el = menuRefs.current[0];
4119
+ if (el) el.focus();
4120
+ } else if (e.key === "End") {
4121
+ e.preventDefault();
4122
+ const el = menuRefs.current[tail.length - 1];
4123
+ if (el) el.focus();
4124
+ }
4125
+ };
4126
+ let controlI = 0;
3642
4127
  return /* @__PURE__ */ React.createElement("div", { ref: (el) => {
3643
4128
  wrap.current = el;
3644
4129
  ref.current = el;
3645
- }, className: cx("cs-toolbar", className), role: "toolbar", "aria-label": label }, head.map((it, i) => it === "-" ? /* @__PURE__ */ React.createElement("span", { key: i, className: "cs-toolbar__sep", "aria-hidden": "true" }) : /* @__PURE__ */ React.createElement("button", { key: i, type: "button", className: "cs-toolbar__btn", onClick: () => it.onSelect && it.onSelect() }, it.icon, it.label ? /* @__PURE__ */ React.createElement("span", null, it.label) : null)), tail.length ? /* @__PURE__ */ React.createElement("span", { className: "cs-toolbar__more" }, /* @__PURE__ */ React.createElement("button", { type: "button", className: "cs-toolbar__btn", "aria-haspopup": "menu", "aria-expanded": open, "aria-label": t("more"), onClick: () => setOpen((o) => !o) }, "⋯"), open ? /* @__PURE__ */ React.createElement("span", { className: "cs-menu__list", role: "menu" }, tail.map((it, i) => /* @__PURE__ */ React.createElement("button", { key: i, type: "button", role: "menuitem", className: "cs-menu__item", onClick: () => {
3646
- setOpen(false);
3647
- it.onSelect && it.onSelect();
3648
- } }, it.label))) : null) : null);
4130
+ }, className: cx("cs-toolbar", className), role: "toolbar", "aria-label": label, "aria-orientation": "horizontal" }, head.map((it, i) => {
4131
+ if (it === "-") return /* @__PURE__ */ React.createElement("span", { key: "sep-" + i, className: "cs-toolbar__sep", "aria-hidden": "true" });
4132
+ const ci = controlI++;
4133
+ return /* @__PURE__ */ React.createElement(
4134
+ "button",
4135
+ {
4136
+ key: "btn-" + i,
4137
+ type: "button",
4138
+ className: "cs-toolbar__btn",
4139
+ tabIndex: ci === focusIdx ? 0 : -1,
4140
+ ref: (el) => {
4141
+ btnRefs.current[ci] = el;
4142
+ },
4143
+ onFocus: () => setFocusIdx(ci),
4144
+ onKeyDown: (e) => onToolbarKeyDown(e, ci),
4145
+ onClick: () => it.onSelect && it.onSelect()
4146
+ },
4147
+ it.icon,
4148
+ it.label ? /* @__PURE__ */ React.createElement("span", null, it.label) : null
4149
+ );
4150
+ }), hasMore ? (() => {
4151
+ const ci = controlI++;
4152
+ return /* @__PURE__ */ React.createElement("span", { className: "cs-toolbar__more" }, /* @__PURE__ */ React.createElement(
4153
+ "button",
4154
+ {
4155
+ type: "button",
4156
+ className: "cs-toolbar__btn",
4157
+ "aria-haspopup": "menu",
4158
+ "aria-expanded": open,
4159
+ "aria-label": t("more"),
4160
+ tabIndex: ci === focusIdx ? 0 : -1,
4161
+ ref: (el) => {
4162
+ btnRefs.current[ci] = el;
4163
+ },
4164
+ onFocus: () => setFocusIdx(ci),
4165
+ onKeyDown: (e) => {
4166
+ onToolbarKeyDown(e, ci);
4167
+ if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") {
4168
+ if (!open) {
4169
+ e.preventDefault();
4170
+ setOpen(true);
4171
+ requestAnimationFrame(() => {
4172
+ const el = menuRefs.current[0];
4173
+ if (el) el.focus();
4174
+ });
4175
+ }
4176
+ }
4177
+ },
4178
+ onClick: () => setOpen((o) => !o)
4179
+ },
4180
+ "⋯"
4181
+ ), open ? /* @__PURE__ */ React.createElement("span", { className: "cs-menu__list", role: "menu" }, tail.map((it, i) => /* @__PURE__ */ React.createElement(
4182
+ "button",
4183
+ {
4184
+ key: i,
4185
+ type: "button",
4186
+ role: "menuitem",
4187
+ className: "cs-menu__item",
4188
+ ref: (el) => {
4189
+ menuRefs.current[i] = el;
4190
+ },
4191
+ onKeyDown: (e) => onMenuKeyDown(e, i),
4192
+ onClick: () => {
4193
+ setOpen(false);
4194
+ it.onSelect && it.onSelect();
4195
+ }
4196
+ },
4197
+ it.label
4198
+ ))) : null);
4199
+ })() : null);
3649
4200
  }
3650
4201
  Object.assign(__ds_scope, { Toolbar });
3651
4202
  })(); } catch (e) { __ds_ns.__errors.push({ path: "components/navigation/Toolbar.jsx", error: String((e && e.message) || e) }); }
3652
4203
 
3653
4204
  // components/overlays/ContextMenu.jsx
3654
4205
  try { (() => {
4206
+ const { makeT, useLang } = __ds_scope;
3655
4207
  const { cx } = __ds_scope;
3656
- function ContextMenu({ items = [], children, className }) {
4208
+ function ContextMenu({ items = [], children, lang, className }) {
3657
4209
  const [pos, setPos] = React.useState(null);
4210
+ const [ref, L] = useLang(lang);
4211
+ const t = makeT("ContextMenu", L);
3658
4212
  React.useEffect(() => {
3659
4213
  if (!pos) return;
3660
4214
  const close = () => setPos(null);
@@ -3671,6 +4225,7 @@ function ContextMenu({ items = [], children, className }) {
3671
4225
  return /* @__PURE__ */ React.createElement(
3672
4226
  "div",
3673
4227
  {
4228
+ ref,
3674
4229
  className: cx("cs-ctxmenu-zone", className),
3675
4230
  onContextMenu: (e) => {
3676
4231
  e.preventDefault();
@@ -3679,7 +4234,7 @@ function ContextMenu({ items = [], children, className }) {
3679
4234
  }
3680
4235
  },
3681
4236
  children,
3682
- pos ? /* @__PURE__ */ React.createElement("div", { className: "cs-menu__list", role: "menu", style: { position: "absolute", insetInlineStart: pos.x, insetBlockStart: pos.y } }, items.map((it, i) => it === "-" ? /* @__PURE__ */ React.createElement("div", { key: i, className: "cs-menu__sep" }) : /* @__PURE__ */ React.createElement("button", { key: i, type: "button", role: "menuitem", className: cx("cs-menu__item", it.danger && "cs-menu__item--danger"), onClick: () => {
4237
+ pos ? /* @__PURE__ */ React.createElement("div", { className: "cs-menu__list", role: "menu", "aria-label": t("menu"), style: { position: "absolute", insetInlineStart: pos.x, insetBlockStart: pos.y } }, items.map((it, i) => it === "-" ? /* @__PURE__ */ React.createElement("div", { key: i, className: "cs-menu__sep" }) : /* @__PURE__ */ React.createElement("button", { key: i, type: "button", role: "menuitem", className: cx("cs-menu__item", it.danger && "cs-menu__item--danger"), onClick: () => {
3683
4238
  setPos(null);
3684
4239
  it.onSelect && it.onSelect();
3685
4240
  } }, it.label))) : null
@@ -4314,738 +4869,12 @@ function TextField({
4314
4869
  Object.assign(__ds_scope, { TextField });
4315
4870
  })(); } catch (e) { __ds_ns.__errors.push({ path: "components/textfield/TextField.jsx", error: String((e && e.message) || e) }); }
4316
4871
 
4317
- // image-slot.js
4318
- try { (() => {
4319
- (() => {
4320
- const STATE_FILE = ".image-slots.state.json";
4321
- const UNSPLASH_HOMEPAGE_HREF = "https://unsplash.com/?utm_source=claude_design&utm_medium=referral";
4322
- const isUnsplashHost = (u) => {
4323
- try {
4324
- return /(^|\.)unsplash\.com$/.test(
4325
- new URL(u, document.baseURI).hostname.replace(/\.$/, "")
4326
- );
4327
- } catch {
4328
- return false;
4329
- }
4330
- };
4331
- const withReferral = (href) => {
4332
- try {
4333
- const u = new URL(href);
4334
- if (!/(^|\.)unsplash\.com$/.test(u.hostname.replace(/\.$/, ""))) {
4335
- return href;
4336
- }
4337
- if (!u.searchParams.has("utm_source")) {
4338
- u.searchParams.set("utm_source", "claude_design");
4339
- }
4340
- if (!u.searchParams.has("utm_medium")) {
4341
- u.searchParams.set("utm_medium", "referral");
4342
- }
4343
- return u.toString();
4344
- } catch (e) {
4345
- return href;
4346
- }
4347
- };
4348
- const MAX_DIM = 1200;
4349
- const ACCEPT = ["image/png", "image/jpeg", "image/webp", "image/avif"];
4350
- const subs = /* @__PURE__ */ new Set();
4351
- let slots = {};
4352
- const tombstones = /* @__PURE__ */ new Set();
4353
- let loaded = false;
4354
- let loadP = null;
4355
- function load() {
4356
- if (loadP) return loadP;
4357
- loadP = fetch(STATE_FILE).then((r) => r.ok ? r.json() : null).then((j) => {
4358
- if (j && typeof j === "object") {
4359
- const merged = Object.assign({}, j, slots);
4360
- for (const k in slots) {
4361
- if (merged[k] && !merged[k].u && j[k]) {
4362
- merged[k].u = typeof j[k] === "string" ? j[k] : j[k].u;
4363
- }
4364
- }
4365
- for (const id of tombstones) delete merged[id];
4366
- slots = merged;
4367
- }
4368
- tombstones.clear();
4369
- }).catch(() => {
4370
- }).then(() => {
4371
- loaded = true;
4372
- subs.forEach((fn) => fn());
4373
- });
4374
- return loadP;
4375
- }
4376
- let saving = false;
4377
- let saveDirty = false;
4378
- function flushNow() {
4379
- if (!loaded) return;
4380
- const w = window.omelette && window.omelette.writeFile;
4381
- if (!w) return;
4382
- try {
4383
- Promise.resolve(w(STATE_FILE, JSON.stringify(slots))).catch(() => {
4384
- });
4385
- } catch (e) {
4386
- }
4387
- }
4388
- function save() {
4389
- if (saving) {
4390
- saveDirty = true;
4391
- return;
4392
- }
4393
- const w = window.omelette && window.omelette.writeFile;
4394
- if (!w) return;
4395
- saving = true;
4396
- Promise.resolve(w(STATE_FILE, JSON.stringify(slots))).catch(() => {
4397
- }).then(() => {
4398
- saving = false;
4399
- if (saveDirty) {
4400
- saveDirty = false;
4401
- save();
4402
- }
4403
- });
4404
- }
4405
- const S_MAX = 5;
4406
- const clampS = (s) => Math.max(1, Math.min(S_MAX, s));
4407
- function getSlot(id) {
4408
- const v = slots[id];
4409
- if (!v) return null;
4410
- return typeof v === "string" ? { u: v, s: 1, x: 0, y: 0 } : v;
4411
- }
4412
- function setSlot(id, val) {
4413
- if (!id) return;
4414
- if (val) {
4415
- slots[id] = val;
4416
- tombstones.delete(id);
4417
- } else {
4418
- delete slots[id];
4419
- if (!loaded) tombstones.add(id);
4420
- }
4421
- subs.forEach((fn) => fn());
4422
- if (loaded) save();
4423
- else load().then(save);
4424
- }
4425
- async function toDataUrl(file, targetW) {
4426
- const bitmap = await createImageBitmap(file);
4427
- try {
4428
- const cap = Math.min(MAX_DIM, Math.max(1, Math.round(targetW * 2)) || MAX_DIM);
4429
- const scale = Math.min(1, cap / Math.max(bitmap.width, bitmap.height));
4430
- const w = Math.max(1, Math.round(bitmap.width * scale));
4431
- const h = Math.max(1, Math.round(bitmap.height * scale));
4432
- const canvas = document.createElement("canvas");
4433
- canvas.width = w;
4434
- canvas.height = h;
4435
- canvas.getContext("2d").drawImage(bitmap, 0, 0, w, h);
4436
- return canvas.toDataURL("image/webp", 0.85);
4437
- } finally {
4438
- bitmap.close && bitmap.close();
4439
- }
4440
- }
4441
- const stylesheet = (
4442
- // Fill the container by default: slots are usually placed inside a
4443
- // sized wrapper (a hero frame, a grid cell, an inset:0 layer) and are
4444
- // expected to take that box — a fixed intrinsic size would render as
4445
- // a small tile in the corner of a full-bleed wrapper instead.
4446
- // aspect-ratio is the companion fallback that keeps a bare slot
4447
- // visible when the parent's height is indefinite: height:100%
4448
- // resolves to auto there, and the ratio then derives height from
4449
- // width instead of letting the slot collapse to zero height.
4450
- // Explicit width/height on the element override all of this.
4451
- ':host{display:block;position:relative; font:13px/1.3 system-ui,-apple-system,sans-serif;color:rgba(0,0,0,.55); width:100%;height:100%;aspect-ratio:3/2}.frame{position:absolute;inset:0;overflow:hidden;background:rgba(0,0,0,.04)}.frame img{position:absolute;max-width:none;transform:translate(-50%,-50%); -webkit-user-drag:none;user-select:none;touch-action:none}.spill{position:fixed;margin:0;inset:auto;border:0;padding:0;background:transparent; overflow:visible;transform:translate(-50%,-50%);z-index:1;cursor:grab;touch-action:none}:host([data-panning]) .spill{cursor:grabbing}.spill .ghost{position:absolute;inset:0;width:100%;height:100%;opacity:.35; pointer-events:none;-webkit-user-drag:none;user-select:none; box-shadow:0 0 0 1px rgba(0,0,0,.2),0 12px 32px rgba(0,0,0,.2)}.spill .handle{position:absolute;width:12px;height:12px;border-radius:50%; background:#fff;box-shadow:0 0 0 1.5px #c96442,0 1px 3px rgba(0,0,0,.3); transform:translate(-50%,-50%)}.spill .handle[data-c=nw]{left:0;top:0;cursor:nwse-resize}.spill .handle[data-c=ne]{left:100%;top:0;cursor:nesw-resize}.spill .handle[data-c=sw]{left:0;top:100%;cursor:nesw-resize}.spill .handle[data-c=se]{left:100%;top:100%;cursor:nwse-resize}:host([data-reframe]){z-index:10}:host([data-reframe]) .frame{box-shadow:0 0 0 2px #c96442}.empty{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center; justify-content:center;gap:6px;text-align:center;padding:12px;box-sizing:border-box; cursor:pointer;user-select:none}.empty svg{opacity:.45}.empty .cap{max-width:90%;font-weight:500;letter-spacing:.01em}.empty .sub{font-size:11px}.empty .sub u{text-underline-offset:2px;text-decoration-color:rgba(0,0,0,.25)}.empty:hover .sub u{color:rgba(0,0,0,.75);text-decoration-color:currentColor}:host([data-over]) .frame{outline:2px solid #c96442;outline-offset:-2px; background:rgba(201,100,66,.10)}.ring{position:absolute;inset:0;pointer-events:none;border:1.5px dashed rgba(0,0,0,.25); transition:border-color .12s}:host([data-over]) .ring{border-color:#c96442}:host([data-filled]) .ring{display:none}.ctl{position:absolute;inset:auto;top:8px;right:8px;margin:0;border:0;padding:0; background:transparent;overflow:visible; display:flex;gap:6px;opacity:0;pointer-events:none;transition:opacity .12s;z-index:2; white-space:nowrap}.ctl:popover-open{position:fixed;inset:auto;transform:translateX(-100%)}:host([data-filled][data-editable]:hover) .ctl,:host([data-reframe]) .ctl {opacity:1;pointer-events:auto}.ctl button{appearance:none;border:0;border-radius:6px;padding:5px 10px;cursor:pointer; background:rgba(0,0,0,.65);color:#fff;font:11px/1 system-ui,-apple-system,sans-serif; backdrop-filter:blur(6px)}.ctl button:hover{background:rgba(0,0,0,.8)}.err{position:absolute;left:8px;bottom:8px;right:8px;color:#b3261e;font-size:11px; background:rgba(255,255,255,.85);padding:4px 6px;border-radius:5px;pointer-events:none}:host([data-swapping]) .frame img{visibility:hidden}.loading{position:absolute;inset:0;display:none;align-items:center; justify-content:center;pointer-events:none}:host([data-swapping]) .loading{display:flex}.loading::after{content:"";width:22px;height:22px;border-radius:50%; border:2px solid rgba(0,0,0,.12);border-top-color:rgba(0,0,0,.45); animation:om-slot-spin .7s linear infinite}@keyframes om-slot-spin{to{transform:rotate(360deg)}}@media (prefers-reduced-motion:reduce){.loading::after{animation:none}}.credit{position:absolute;left:6px;bottom:6px;max-width:calc(100% - 12px);display:none; padding:3px 7px;border-radius:5px;background:rgba(0,0,0,.55);color:#fff; font:10px/1.2 system-ui,-apple-system,sans-serif;text-decoration:none; white-space:nowrap;overflow:hidden;text-overflow:ellipsis;backdrop-filter:blur(6px)}.credit a{color:inherit;text-decoration:none}.credit a:hover,.credit a:focus-visible{text-decoration:underline}:host([data-filled][data-credit]) .credit{display:block}:host-context([data-om-exporting]) .ctl,:host-context([data-om-exporting]) .credit{display:none !important}.attr-error{position:absolute;inset:0;display:none;flex-direction:column;align-items:center; justify-content:center;gap:6px;text-align:center;padding:12px;box-sizing:border-box; background:#f2f1ef;color:#6e6c66;user-select:none; font:13px/1.45 system-ui,-apple-system,sans-serif}.attr-error svg{opacity:.55}.attr-error .cap{max-width:92%;font-weight:500;letter-spacing:.01em}:host([data-attribution-error]) .attr-error{display:flex}:host([data-attribution-error]) .ring{display:none}'
4452
- );
4453
- const icon = '<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="m21 15-5-5L5 21"/></svg>';
4454
- const warnIcon = '<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.46 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>';
4455
- class ImageSlot extends HTMLElement {
4456
- static get observedAttributes() {
4457
- return ["shape", "radius", "mask", "fit", "placeholder", "src", "id", "credit", "credit-href"];
4458
- }
4459
- /** Duplicate-slide hook (called by deck-stage, see its
4460
- * _remintDuplicateIds): copy this id's stored image, if any, under a
4461
- * freshly minted key and return that key — so a duplicated slide's
4462
- * slot keeps its dropped photo instead of reverting to the
4463
- * placeholder. 'isFree' is the caller's uniqueness check (document
4464
- * ids); candidates must ALSO be unused in the sidecar, which can
4465
- * hold keys from other pages sharing the project root. (An EMPTY
4466
- * slot on another page leaves no sidecar entry, so its id is not
4467
- * detectable here — a minted key can collide with it and that slot
4468
- * would show this photo. Same blast radius as two pages reusing an
4469
- * id by hand, which the shared sidecar already permits.) Returns null
4470
- * when no id could be minted (caller strips the id, today's
4471
- * behavior). */
4472
- static cloneSlot(fromId, isFree) {
4473
- if (typeof fromId !== "string" || !fromId) return null;
4474
- if (!loaded) return null;
4475
- const stem = fromId.replace(/-\d+$/, "") || fromId;
4476
- for (let n = 2; n < 100; n++) {
4477
- const toId = stem + "-" + n;
4478
- if (toId === fromId) continue;
4479
- if (slots[toId] !== void 0) {
4480
- const prev = getSlot(toId);
4481
- const cur = getSlot(fromId);
4482
- if (!(prev && cur && prev.u && prev.u === cur.u && prev.s === cur.s && prev.x === cur.x && prev.y === cur.y && (typeof isFree !== "function" || isFree(toId)))) continue;
4483
- return toId;
4484
- }
4485
- if (typeof isFree === "function" && !isFree(toId)) continue;
4486
- const v = getSlot(fromId);
4487
- if (v) setSlot(toId, Object.assign({}, v));
4488
- return toId;
4489
- }
4490
- return null;
4491
- }
4492
- constructor() {
4493
- super();
4494
- const root = this.shadowRoot || this.attachShadow({ mode: "open", clonable: true });
4495
- root.innerHTML = "<style>" + stylesheet + '</style><div class="frame" part="frame"> <img part="image" alt="" draggable="false" style="display:none"> <div class="empty" part="empty">' + icon + ' <div class="cap"></div> <div class="sub">or <u>browse files</u></div></div> <div class="attr-error" part="attribution-error">' + warnIcon + ' <div class="cap">This photo needs attribution</div></div> <div class="loading" part="loading"></div> <div class="ring" part="ring"></div></div><span class="credit" part="credit"></span><div class="spill" popover="manual" data-dc-edit-transparent> <img class="ghost" alt="" draggable="false"> <div class="handle" data-c="nw"></div><div class="handle" data-c="ne"></div> <div class="handle" data-c="sw"></div><div class="handle" data-c="se"></div></div><div class="ctl" popover="manual" data-dc-edit-transparent><button data-act="replace" title="Replace image">Replace</button> <button data-act="edit" title="Reframe image">Edit</button></div><input type="file" accept="' + ACCEPT.join(",") + '" hidden>';
4496
- this._frame = root.querySelector(".frame");
4497
- this._ring = root.querySelector(".ring");
4498
- this._img = root.querySelector(".frame img");
4499
- this._empty = root.querySelector(".empty");
4500
- this._cap = root.querySelector(".cap");
4501
- this._sub = root.querySelector(".sub");
4502
- this._spill = root.querySelector(".spill");
4503
- this._ctl = root.querySelector(".ctl");
4504
- this._credit = root.querySelector(".credit");
4505
- this._attrError = root.querySelector(".attr-error");
4506
- this._credit.addEventListener("click", (e) => e.stopPropagation());
4507
- this._credit.addEventListener("dblclick", (e) => e.stopPropagation());
4508
- this._ghost = root.querySelector(".ghost");
4509
- this._err = null;
4510
- this._input = root.querySelector("input");
4511
- this._depth = 0;
4512
- this._gen = 0;
4513
- this._swapGen = 0;
4514
- this._loadPending = false;
4515
- this._hidShowing = false;
4516
- this._view = { s: 1, x: 0, y: 0 };
4517
- this._subFn = () => this._render();
4518
- this._empty.addEventListener("click", () => this._input.click());
4519
- root.addEventListener("click", (e) => {
4520
- const act = e.target && e.target.getAttribute && e.target.getAttribute("data-act");
4521
- if (!act) return;
4522
- if (!this.hasAttribute("data-editable")) return;
4523
- if (act === "replace") {
4524
- this._exitReframe(true);
4525
- this.dispatchEvent(new CustomEvent("image-slot:pick", {
4526
- bubbles: true,
4527
- composed: true,
4528
- detail: { id: this.id || null }
4529
- }));
4530
- }
4531
- if (act === "edit") {
4532
- if (!this._reframes()) return;
4533
- if (this.hasAttribute("data-reframe")) this._exitReframe(true);
4534
- else this._enterReframe();
4535
- }
4536
- });
4537
- this._input.addEventListener("change", () => {
4538
- const f = this._input.files && this._input.files[0];
4539
- if (f) this._ingest(f);
4540
- this._input.value = "";
4541
- });
4542
- this._img.addEventListener("load", () => {
4543
- this._loadPending = false;
4544
- this._releaseMask(true);
4545
- this._applyView();
4546
- });
4547
- this._img.addEventListener("error", () => {
4548
- this._loadPending = false;
4549
- this._releaseMask(true);
4550
- });
4551
- this.addEventListener("dblclick", (e) => {
4552
- if (!this.hasAttribute("data-editable") || !this._reframes()) return;
4553
- e.preventDefault();
4554
- if (this.hasAttribute("data-reframe")) this._exitReframe(true);
4555
- else this._enterReframe();
4556
- });
4557
- this._spill.addEventListener("pointerdown", (e) => {
4558
- if (e.button !== 0 || !this.hasAttribute("data-reframe")) return;
4559
- e.preventDefault();
4560
- e.stopPropagation();
4561
- this._spill.setPointerCapture(e.pointerId);
4562
- const rect = this.getBoundingClientRect();
4563
- const fw = rect.width || 1, fh = rect.height || 1;
4564
- const corner = e.target.getAttribute && e.target.getAttribute("data-c");
4565
- let move;
4566
- if (corner) {
4567
- const iw = this._img.naturalWidth || 1, ih = this._img.naturalHeight || 1;
4568
- const contain = (this.getAttribute("fit") || "cover").toLowerCase() === "contain";
4569
- const base = contain ? Math.min(fw / iw, fh / ih) : Math.max(fw / iw, fh / ih);
4570
- const sx = corner.includes("e") ? 1 : -1;
4571
- const sy = corner.includes("s") ? 1 : -1;
4572
- const s0 = this._view.s;
4573
- const w0 = iw * base * s0, h0 = ih * base * s0;
4574
- const cx0 = (50 + this._view.x) / 100 * fw;
4575
- const cy0 = (50 + this._view.y) / 100 * fh;
4576
- const ox = cx0 - sx * w0 / 2, oy = cy0 - sy * h0 / 2;
4577
- const diag0 = Math.hypot(w0, h0);
4578
- const ux = sx * w0 / diag0, uy = sy * h0 / diag0;
4579
- move = (ev) => {
4580
- const proj = (ev.clientX - rect.left - ox) * ux + (ev.clientY - rect.top - oy) * uy;
4581
- const s = clampS(s0 * proj / diag0);
4582
- const d = diag0 * s / s0;
4583
- this._view.s = s;
4584
- this._view.x = (ox + ux * d / 2) / fw * 100 - 50;
4585
- this._view.y = (oy + uy * d / 2) / fh * 100 - 50;
4586
- this._clampView();
4587
- this._applyView();
4588
- };
4589
- } else {
4590
- this.setAttribute("data-panning", "");
4591
- const start = { px: e.clientX, py: e.clientY, x: this._view.x, y: this._view.y };
4592
- move = (ev) => {
4593
- this._view.x = start.x + (ev.clientX - start.px) / fw * 100;
4594
- this._view.y = start.y + (ev.clientY - start.py) / fh * 100;
4595
- this._clampView();
4596
- this._applyView();
4597
- };
4598
- }
4599
- const up = () => {
4600
- try {
4601
- this._spill.releasePointerCapture(e.pointerId);
4602
- } catch {
4603
- }
4604
- this._spill.removeEventListener("pointermove", move);
4605
- this._spill.removeEventListener("pointerup", up);
4606
- this._spill.removeEventListener("pointercancel", up);
4607
- this.removeAttribute("data-panning");
4608
- this._dragUp = null;
4609
- };
4610
- this._dragUp = up;
4611
- this._spill.addEventListener("pointermove", move);
4612
- this._spill.addEventListener("pointerup", up);
4613
- this._spill.addEventListener("pointercancel", up);
4614
- });
4615
- this.addEventListener("wheel", (e) => {
4616
- if (!this.hasAttribute("data-reframe")) return;
4617
- e.preventDefault();
4618
- const r = this.getBoundingClientRect();
4619
- const cx = (e.clientX - r.left) / r.width * 100 - 50;
4620
- const cy = (e.clientY - r.top) / r.height * 100 - 50;
4621
- const prev = this._view.s;
4622
- const next = clampS(prev * Math.pow(1.0015, -e.deltaY));
4623
- if (next === prev) return;
4624
- const k = next / prev;
4625
- this._view.s = next;
4626
- this._view.x = cx * (1 - k) + this._view.x * k;
4627
- this._view.y = cy * (1 - k) + this._view.y * k;
4628
- this._clampView();
4629
- this._applyView();
4630
- }, { passive: false });
4631
- }
4632
- connectedCallback() {
4633
- if (!this.id && !ImageSlot._warned) {
4634
- ImageSlot._warned = true;
4635
- console.warn("<image-slot> without an id will not persist its dropped image.");
4636
- }
4637
- this.addEventListener("dragenter", this);
4638
- this.addEventListener("dragover", this);
4639
- this.addEventListener("dragleave", this);
4640
- this.addEventListener("drop", this);
4641
- subs.add(this._subFn);
4642
- this.addEventListener("pointerenter", this._subFn);
4643
- this._ro = new ResizeObserver(() => this._render());
4644
- this._ro.observe(this);
4645
- load();
4646
- this._render();
4647
- }
4648
- disconnectedCallback() {
4649
- subs.delete(this._subFn);
4650
- this.removeEventListener("pointerenter", this._subFn);
4651
- this.removeEventListener("dragenter", this);
4652
- this.removeEventListener("dragover", this);
4653
- this.removeEventListener("dragleave", this);
4654
- this.removeEventListener("drop", this);
4655
- if (this._ro) {
4656
- this._ro.disconnect();
4657
- this._ro = null;
4658
- }
4659
- this._exitReframe(false);
4660
- }
4661
- _enterReframe() {
4662
- if (this.hasAttribute("data-reframe")) return;
4663
- this.setAttribute("data-reframe", "");
4664
- this._signalReframe(true);
4665
- this._pagehide = () => {
4666
- this._exitReframe(true);
4667
- flushNow();
4668
- };
4669
- window.addEventListener("pagehide", this._pagehide);
4670
- try {
4671
- this._spill.showPopover();
4672
- } catch {
4673
- }
4674
- try {
4675
- this._ctl.showPopover();
4676
- } catch {
4677
- }
4678
- this._reposition = () => {
4679
- if (this.hasAttribute("data-reframe")) this._applyView();
4680
- };
4681
- window.addEventListener("scroll", this._reposition, true);
4682
- window.addEventListener("resize", this._reposition);
4683
- this._lastRect = "";
4684
- this._watch = () => {
4685
- if (!this.hasAttribute("data-reframe")) return;
4686
- const r = this.getBoundingClientRect();
4687
- const key = r.left + "," + r.top + "," + r.width + "," + r.height;
4688
- if (key !== this._lastRect) {
4689
- this._lastRect = key;
4690
- this._applyView();
4691
- }
4692
- this._watchId = requestAnimationFrame(this._watch);
4693
- };
4694
- this._watchId = requestAnimationFrame(this._watch);
4695
- this._applyView();
4696
- this._outside = (e) => {
4697
- if (e.composedPath && e.composedPath().includes(this)) return;
4698
- this._exitReframe(true);
4699
- };
4700
- this._esc = (e) => {
4701
- if (e.key === "Escape") this._exitReframe(true);
4702
- };
4703
- document.addEventListener("pointerdown", this._outside, true);
4704
- document.addEventListener("keydown", this._esc, true);
4705
- }
4706
- _exitReframe(commit) {
4707
- if (!this.hasAttribute("data-reframe")) return;
4708
- if (this._dragUp) this._dragUp();
4709
- this.removeAttribute("data-reframe");
4710
- this.removeAttribute("data-panning");
4711
- if (this._outside) document.removeEventListener("pointerdown", this._outside, true);
4712
- if (this._esc) document.removeEventListener("keydown", this._esc, true);
4713
- this._outside = this._esc = null;
4714
- if (this._reposition) {
4715
- window.removeEventListener("scroll", this._reposition, true);
4716
- window.removeEventListener("resize", this._reposition);
4717
- this._reposition = null;
4718
- }
4719
- if (this._watchId) {
4720
- cancelAnimationFrame(this._watchId);
4721
- this._watchId = 0;
4722
- }
4723
- if (this._pagehide) {
4724
- window.removeEventListener("pagehide", this._pagehide);
4725
- this._pagehide = null;
4726
- }
4727
- try {
4728
- this._spill.hidePopover();
4729
- } catch {
4730
- }
4731
- try {
4732
- this._ctl.hidePopover();
4733
- } catch {
4734
- }
4735
- this._ctl.style.left = "";
4736
- this._ctl.style.top = "";
4737
- if (commit) this._commitView();
4738
- this._signalReframe(false);
4739
- }
4740
- // Reframe state lives only in this DOM until commit, invisible to the
4741
- // host's dirty signals — announce enter/exit so the host can hold
4742
- // auto-reloads for exactly the gesture (the guest bundle forwards
4743
- // image-slot:reframe to the host as imageSlotReframe). Dispatched on
4744
- // the element (composed, so it escapes shadow roots) while connected;
4745
- // a disconnected exit (disconnectedCallback) falls back to document so
4746
- // the host still hears it.
4747
- _signalReframe(active) {
4748
- const target = this.isConnected ? this : document;
4749
- target.dispatchEvent(new CustomEvent("image-slot:reframe", {
4750
- bubbles: true,
4751
- composed: true,
4752
- detail: { active, id: this.id || null }
4753
- }));
4754
- }
4755
- // Public: host's "Import from computer" calls this to run local browse.
4756
- openFilePicker() {
4757
- this._exitReframe(true);
4758
- this._input.click();
4759
- }
4760
- attributeChangedCallback() {
4761
- if (this.shadowRoot) this._render();
4762
- }
4763
- // handleEvent — one listener object for all four drag events keeps the
4764
- // add/remove symmetric and the depth counter correct.
4765
- handleEvent(e) {
4766
- if (e.type === "dragenter" || e.type === "dragover") {
4767
- e.preventDefault();
4768
- e.stopPropagation();
4769
- if (e.dataTransfer) e.dataTransfer.dropEffect = "copy";
4770
- if (e.type === "dragenter") this._depth++;
4771
- this.setAttribute("data-over", "");
4772
- } else if (e.type === "dragleave") {
4773
- if (--this._depth <= 0) {
4774
- this._depth = 0;
4775
- this.removeAttribute("data-over");
4776
- }
4777
- } else if (e.type === "drop") {
4778
- e.preventDefault();
4779
- e.stopPropagation();
4780
- this._depth = 0;
4781
- this.removeAttribute("data-over");
4782
- const f = e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files[0];
4783
- if (f) this._ingest(f);
4784
- }
4785
- }
4786
- async _ingest(file) {
4787
- this._setError(null);
4788
- if (!file || ACCEPT.indexOf(file.type) < 0) {
4789
- this._setError("Drop a PNG, JPEG, WebP, or AVIF image.");
4790
- return;
4791
- }
4792
- const gen = ++this._gen;
4793
- if (this.hasAttribute("data-filled")) {
4794
- this.setAttribute("data-swapping", "");
4795
- this._swapGen = gen;
4796
- }
4797
- try {
4798
- const w = this.clientWidth || this.offsetWidth || MAX_DIM;
4799
- const url = await toDataUrl(file, w);
4800
- if (gen !== this._gen) return;
4801
- this._exitReframe(false);
4802
- this._swapGen = 0;
4803
- const val = { u: url, s: 1, x: 0, y: 0 };
4804
- setSlot(this.id || "", val);
4805
- if (!this.id) {
4806
- this._local = val;
4807
- this._render();
4808
- }
4809
- } catch (err) {
4810
- if (gen !== this._gen) return;
4811
- this._swapGen = 0;
4812
- this._releaseMask();
4813
- this._setError("Could not read that image.");
4814
- console.warn("<image-slot> ingest failed:", err);
4815
- }
4816
- }
4817
- _setError(msg) {
4818
- if (this._err) {
4819
- this._err.remove();
4820
- this._err = null;
4821
- }
4822
- if (!msg) return;
4823
- const d = document.createElement("div");
4824
- d.className = "err";
4825
- d.textContent = msg;
4826
- this.shadowRoot.appendChild(d);
4827
- this._err = d;
4828
- setTimeout(() => {
4829
- if (this._err === d) {
4830
- d.remove();
4831
- this._err = null;
4832
- }
4833
- }, 3e3);
4834
- }
4835
- // Reframing (pan/resize) is available on any filled slot — the user can
4836
- // always reposition/scale. `fit` only sets the initial baseline (see
4837
- // _geom): contain starts fully-visible, cover starts frame-filling.
4838
- _reframes() {
4839
- return this.hasAttribute("data-filled");
4840
- }
4841
- // The single release discipline for the replacement-in-flight mask
4842
- // (data-swapping). The mask comes off only when BOTH hold:
4843
- // - no encode is pending (_swapGen) — mid-encode the stored value
4844
- // still resolves to the old image, so any reveal paints it;
4845
- // - the frame img has settled on its current src — an unsettled src
4846
- // means some replacement is still in flight (e.g. a remote pick),
4847
- // whoever started it, and revealing would paint the previous
4848
- // frame. The load/error listeners pass settled=true (the event IS
4849
- // the settlement signal, per spec complete is true by then);
4850
- // other callers rely on the complete flag (covers loaded AND
4851
- // failed).
4852
- // Every release path funnels through here EXCEPT _render's empty
4853
- // branch (the img is being cleared — nothing will ever settle).
4854
- _releaseMask(settled) {
4855
- if (!this._swapGen && !this._loadPending && (settled || this._img.complete)) {
4856
- this.removeAttribute("data-swapping");
4857
- }
4858
- }
4859
- // Baseline geometry, shared by clamp/apply/resize. `base` is the scale at
4860
- // view-scale s=1: cover = fill the frame (overflow on the looser axis),
4861
- // contain = fit fully inside (letterboxed). Zooming a contain image past
4862
- // s where it overflows naturally becomes a crop. Null until the img has
4863
- // loaded (naturalWidth is 0 before that) or when the slot has no layout
4864
- // box — ResizeObserver fires with a 0×0 rect under display:none, and
4865
- // clamping against a degenerate 1×1 frame would silently pull the stored
4866
- // pan toward zero.
4867
- _geom() {
4868
- const iw = this._img.naturalWidth, ih = this._img.naturalHeight;
4869
- const fw = this.clientWidth, fh = this.clientHeight;
4870
- if (!iw || !ih || !fw || !fh) return null;
4871
- const contain = (this.getAttribute("fit") || "cover").toLowerCase() === "contain";
4872
- const base = contain ? Math.min(fw / iw, fh / ih) : Math.max(fw / iw, fh / ih);
4873
- return { iw, ih, fw, fh, base };
4874
- }
4875
- _clampView() {
4876
- const g = this._geom();
4877
- if (!g) return;
4878
- const mx = Math.max(0, (g.iw * g.base * this._view.s / g.fw - 1) * 50);
4879
- const my = Math.max(0, (g.ih * g.base * this._view.s / g.fh - 1) * 50);
4880
- this._view.x = Math.max(-mx, Math.min(mx, this._view.x));
4881
- this._view.y = Math.max(-my, Math.min(my, this._view.y));
4882
- }
4883
- _applyView() {
4884
- const g = this._geom();
4885
- if (this.hasAttribute("data-reframe")) {
4886
- let onTop = false;
4887
- try {
4888
- onTop = this._ctl.matches(":popover-open");
4889
- } catch {
4890
- }
4891
- if (onTop) {
4892
- const r = this.getBoundingClientRect();
4893
- this._ctl.style.left = r.right - 8 + "px";
4894
- this._ctl.style.top = r.top + 8 + "px";
4895
- }
4896
- }
4897
- if (!g) {
4898
- const contain = (this.getAttribute("fit") || "cover").toLowerCase() === "contain";
4899
- this._img.style.width = "100%";
4900
- this._img.style.height = "100%";
4901
- this._img.style.left = "50%";
4902
- this._img.style.top = "50%";
4903
- this._img.style.objectFit = contain ? "contain" : "cover";
4904
- return;
4905
- }
4906
- const k = g.base * this._view.s;
4907
- const w = g.iw * k / g.fw * 100 + "%";
4908
- const h = g.ih * k / g.fh * 100 + "%";
4909
- const l = 50 + this._view.x + "%";
4910
- const t = 50 + this._view.y + "%";
4911
- this._img.style.width = w;
4912
- this._img.style.height = h;
4913
- this._img.style.left = l;
4914
- this._img.style.top = t;
4915
- this._img.style.objectFit = "";
4916
- if (this.hasAttribute("data-reframe")) {
4917
- const r = this.getBoundingClientRect();
4918
- const sx = g.fw ? r.width / g.fw : 1;
4919
- const sy = g.fh ? r.height / g.fh : 1;
4920
- this._spill.style.width = g.iw * k * sx + "px";
4921
- this._spill.style.height = g.ih * k * sy + "px";
4922
- this._spill.style.left = r.left + (50 + this._view.x) / 100 * r.width + "px";
4923
- this._spill.style.top = r.top + (50 + this._view.y) / 100 * r.height + "px";
4924
- }
4925
- }
4926
- _commitView() {
4927
- const v = { s: this._view.s, x: this._view.x, y: this._view.y };
4928
- if (this._userUrl) v.u = this._userUrl;
4929
- if (this.id) setSlot(this.id, v);
4930
- else {
4931
- this._local = v;
4932
- }
4933
- }
4934
- _render() {
4935
- const mask = this.getAttribute("mask");
4936
- const shape = (this.getAttribute("shape") || "rounded").toLowerCase();
4937
- let radius = "";
4938
- if (shape === "circle") radius = "50%";
4939
- else if (shape === "pill") radius = "9999px";
4940
- else if (shape === "rounded") {
4941
- const n = parseFloat(this.getAttribute("radius"));
4942
- radius = (Number.isFinite(n) ? n : 12) + "px";
4943
- }
4944
- this._frame.style.borderRadius = mask ? "" : radius;
4945
- this._frame.style.clipPath = mask || "";
4946
- this._ring.style.borderRadius = mask ? "" : radius;
4947
- this._ring.style.display = mask ? "none" : "";
4948
- const editable = !!(window.omelette && window.omelette.writeFile);
4949
- this.toggleAttribute("data-editable", editable);
4950
- this._sub.style.display = editable ? "" : "none";
4951
- let stored = this.id ? getSlot(this.id) : this._local;
4952
- if (stored && stored.u && !/^data:image\//i.test(stored.u)) stored = null;
4953
- const srcAttr = this.getAttribute("src") || "";
4954
- this._userUrl = stored && stored.u || null;
4955
- const url = this._userUrl || srcAttr;
4956
- if (!this.hasAttribute("data-reframe")) {
4957
- this._view = {
4958
- s: stored && Number.isFinite(stored.s) ? clampS(stored.s) : 1,
4959
- x: stored && Number.isFinite(stored.x) ? stored.x : 0,
4960
- y: stored && Number.isFinite(stored.y) ? stored.y : 0
4961
- };
4962
- }
4963
- const authoring = typeof window !== "undefined" && typeof window.__dcSetProps === "function";
4964
- const phEn = this.getAttribute("placeholder") || "Drop an image";
4965
- const phVi = this.getAttribute("placeholder-vi") || "Thả ảnh (không bắt buộc)";
4966
- const lang = (document.documentElement.getAttribute("lang") || "").toLowerCase();
4967
- this._cap.textContent = authoring ? lang.startsWith("vi") ? phVi : phEn : "";
4968
- if (this._cap) this._cap.style.display = authoring ? "" : "none";
4969
- const credit = (this.getAttribute("credit") || "").trim();
4970
- const attrError = !!(!credit && !this._userUrl && srcAttr && isUnsplashHost(srcAttr));
4971
- this.toggleAttribute("data-attribution-error", attrError);
4972
- if (url && !attrError) {
4973
- const prev = this._img.getAttribute("src");
4974
- if (prev !== url) {
4975
- if (prev || this._hidShowing) this.setAttribute("data-swapping", "");
4976
- this._loadPending = true;
4977
- this._img.src = url;
4978
- this._ghost.src = url;
4979
- } else {
4980
- this._releaseMask();
4981
- }
4982
- this._hidShowing = false;
4983
- this._img.style.display = "block";
4984
- this._empty.style.display = "none";
4985
- this.setAttribute("data-filled", "");
4986
- this._clampView();
4987
- this._applyView();
4988
- } else {
4989
- this.removeAttribute("data-swapping");
4990
- this._loadPending = false;
4991
- this._hidShowing = attrError && !!this._img.getAttribute("src");
4992
- this._img.style.display = "none";
4993
- this._img.removeAttribute("src");
4994
- this._ghost.removeAttribute("src");
4995
- this._empty.style.display = attrError ? "none" : "flex";
4996
- this.removeAttribute("data-filled");
4997
- }
4998
- const showCredit = !!(url && credit && !this._userUrl && !attrError);
4999
- this._credit.textContent = "";
5000
- if (showCredit) {
5001
- let href = "";
5002
- const rawHref = this.getAttribute("credit-href") || "";
5003
- if (rawHref) {
5004
- try {
5005
- const u = new URL(rawHref, document.baseURI);
5006
- if (u.protocol === "http:" || u.protocol === "https:") {
5007
- href = withReferral(u.href);
5008
- }
5009
- } catch {
5010
- }
5011
- }
5012
- const mkLink = (text, linkHref) => {
5013
- const a = document.createElement("a");
5014
- a.setAttribute("target", "_blank");
5015
- a.setAttribute("rel", "noopener noreferrer");
5016
- a.setAttribute("href", linkHref);
5017
- a.textContent = text;
5018
- return a;
5019
- };
5020
- const m = /^Photo by (.+) on Unsplash$/.exec(credit);
5021
- if (m) {
5022
- this._credit.appendChild(document.createTextNode("Photo by "));
5023
- this._credit.appendChild(
5024
- href ? mkLink(m[1], href) : document.createTextNode(m[1])
5025
- );
5026
- this._credit.appendChild(document.createTextNode(" on "));
5027
- this._credit.appendChild(mkLink("Unsplash", UNSPLASH_HOMEPAGE_HREF));
5028
- } else if (href) {
5029
- this._credit.appendChild(mkLink(credit, href));
5030
- } else {
5031
- this._credit.textContent = credit;
5032
- }
5033
- }
5034
- this.toggleAttribute("data-credit", showCredit);
5035
- }
5036
- }
5037
- if (!customElements.get("image-slot")) {
5038
- customElements.define("image-slot", ImageSlot);
5039
- }
5040
- })();
5041
- })(); } catch (e) { __ds_ns.__errors.push({ path: "image-slot.js", error: String((e && e.message) || e) }); }
5042
-
5043
4872
  // tokens/tokens.js
5044
4873
  try { (() => {
5045
4874
  const tokens = {
5046
4875
  "$meta": {
5047
4876
  "system": "CyberSkill Design System",
5048
- "version": "1.2.0",
4877
+ "version": "1.3.9",
5049
4878
  "generated": "2026-07-23",
5050
4879
  "note": "Generated from tokens/*.css. Root grouped by category; theme/element scopes carry overrides only. Regenerate after any token change (see docs/conventions.md)."
5051
4880
  },
@@ -5109,7 +4938,7 @@ const tokens = {
5109
4938
  "--cs-accent-ink": "#45210E",
5110
4939
  "--cs-accent-glow": "rgba(244, 186, 23, 0.48)",
5111
4940
  "--cs-accent-grad-a": "#F4BA17",
5112
- "--cs-accent-grad-b": "#FCAF91",
4941
+ "--cs-accent-grad-b": "#F3B958",
5113
4942
  "--cs-accent-on-strong": "#1A1108"
5114
4943
  },
5115
4944
  "depth": {
@@ -5178,6 +5007,7 @@ const tokens = {
5178
5007
  },
5179
5008
  "breakpoint": {
5180
5009
  "--cs-breakpoint-xs": "0px",
5010
+ "--cs-breakpoint-phone": "360px",
5181
5011
  "--cs-breakpoint-sm": "640px",
5182
5012
  "--cs-breakpoint-md": "768px",
5183
5013
  "--cs-breakpoint-lg": "1024px",
@@ -5317,9 +5147,9 @@ const tokens = {
5317
5147
  "--cs-accent-ink": "#292414",
5318
5148
  "--cs-accent-glow": "rgba(248, 233, 192, 0.48)",
5319
5149
  "--cs-accent-grad-a": "#F8E9C0",
5320
- "--cs-accent-grad-b": "#FCAF91",
5150
+ "--cs-accent-grad-b": "#F3B958",
5321
5151
  "--cs-color-text-accent": "#90825E",
5322
- "--cs-color-link": "#90825E"
5152
+ "--cs-color-link": "#7B6D4A"
5323
5153
  },
5324
5154
  "tho": {
5325
5155
  "--cs-accent": "#F4BA17",
@@ -5331,7 +5161,7 @@ const tokens = {
5331
5161
  "--cs-accent-ink": "#45210E",
5332
5162
  "--cs-accent-glow": "rgba(244, 186, 23, 0.48)",
5333
5163
  "--cs-accent-grad-a": "#F4BA17",
5334
- "--cs-accent-grad-b": "#FCAF91"
5164
+ "--cs-accent-grad-b": "#F3B958"
5335
5165
  },
5336
5166
  "tho.clay": {
5337
5167
  "--cs-accent": "#513801",
@@ -5343,7 +5173,7 @@ const tokens = {
5343
5173
  "--cs-accent-ink": "#2D220E",
5344
5174
  "--cs-accent-glow": "rgba(81, 56, 1, 0.48)",
5345
5175
  "--cs-accent-grad-a": "#513801",
5346
- "--cs-accent-grad-b": "#FCAF91",
5176
+ "--cs-accent-grad-b": "#F3B958",
5347
5177
  "--cs-color-text-accent": "#201300",
5348
5178
  "--cs-color-link": "#201300"
5349
5179
  },
@@ -5359,7 +5189,7 @@ const tokens = {
5359
5189
  "--cs-accent-grad-a": "#FFE1D8",
5360
5190
  "--cs-accent-grad-b": "#F4BA17",
5361
5191
  "--cs-color-text-accent": "#967C75",
5362
- "--cs-color-link": "#967C75"
5192
+ "--cs-color-link": "#846B64"
5363
5193
  },
5364
5194
  "hoa": {
5365
5195
  "--cs-accent": "#FCAF91",
@@ -5373,7 +5203,7 @@ const tokens = {
5373
5203
  "--cs-accent-grad-a": "#FCAF91",
5374
5204
  "--cs-accent-grad-b": "#F4BA17",
5375
5205
  "--cs-color-text-accent": "#B47054",
5376
- "--cs-color-link": "#B47054"
5206
+ "--cs-color-link": "#A15E43"
5377
5207
  },
5378
5208
  "hoa.lava": {
5379
5209
  "--cs-accent": "#622B06",
@@ -5401,7 +5231,7 @@ const tokens = {
5401
5231
  "--cs-accent-grad-a": "#C0F5FC",
5402
5232
  "--cs-accent-grad-b": "#A3D768",
5403
5233
  "--cs-color-text-accent": "#5A8A90",
5404
- "--cs-color-link": "#5A8A90"
5234
+ "--cs-color-link": "#48787E"
5405
5235
  },
5406
5236
  "thuy": {
5407
5237
  "--cs-accent": "#2EDBF7",
@@ -5415,7 +5245,7 @@ const tokens = {
5415
5245
  "--cs-accent-grad-a": "#2EDBF7",
5416
5246
  "--cs-accent-grad-b": "#A3D768",
5417
5247
  "--cs-color-text-accent": "#108EA0",
5418
- "--cs-color-link": "#108EA0"
5248
+ "--cs-color-link": "#027B8B"
5419
5249
  },
5420
5250
  "thuy.ocean": {
5421
5251
  "--cs-accent": "#004554",
@@ -5443,7 +5273,7 @@ const tokens = {
5443
5273
  "--cs-accent-grad-a": "#E1F1C8",
5444
5274
  "--cs-accent-grad-b": "#FCAF91",
5445
5275
  "--cs-color-text-accent": "#7C8965",
5446
- "--cs-color-link": "#7C8965"
5276
+ "--cs-color-link": "#687451"
5447
5277
  },
5448
5278
  "moc": {
5449
5279
  "--cs-accent": "#A3D768",
@@ -5457,7 +5287,7 @@ const tokens = {
5457
5287
  "--cs-accent-grad-a": "#A3D768",
5458
5288
  "--cs-accent-grad-b": "#FCAF91",
5459
5289
  "--cs-color-text-accent": "#629122",
5460
- "--cs-color-link": "#629122"
5290
+ "--cs-color-link": "#517B13"
5461
5291
  },
5462
5292
  "moc.forest": {
5463
5293
  "--cs-accent": "#1A4A01",
@@ -5485,7 +5315,7 @@ const tokens = {
5485
5315
  "--cs-accent-grad-a": "#FEE5C7",
5486
5316
  "--cs-accent-grad-b": "#2EDBF7",
5487
5317
  "--cs-color-text-accent": "#947F65",
5488
- "--cs-color-link": "#947F65"
5318
+ "--cs-color-link": "#826D54"
5489
5319
  },
5490
5320
  "kim": {
5491
5321
  "--cs-accent": "#F3B958",
@@ -5499,7 +5329,7 @@ const tokens = {
5499
5329
  "--cs-accent-grad-a": "#F3B958",
5500
5330
  "--cs-accent-grad-b": "#2EDBF7",
5501
5331
  "--cs-color-text-accent": "#AC780B",
5502
- "--cs-color-link": "#AC780B"
5332
+ "--cs-color-link": "#966700"
5503
5333
  },
5504
5334
  "kim.steel": {
5505
5335
  "--cs-accent": "#503901",
@@ -5520,477 +5350,6 @@ const tokens = {
5520
5350
  Object.assign(__ds_scope, { tokens, __ds_default_tokens_tokens_9gob35: tokens });
5521
5351
  })(); } catch (e) { __ds_ns.__errors.push({ path: "tokens/tokens.js", error: String((e && e.message) || e) }); }
5522
5352
 
5523
- // ui_kits/status-hub/StatusHub.jsx
5524
- try { (() => {
5525
- const { useState, useMemo } = React;
5526
- const ICON = {
5527
- search: "M11 4a7 7 0 1 0 4.2 12.6L20 21l1.4-1.4-4.4-4.4A7 7 0 0 0 11 4zm0 2a5 5 0 1 1 0 10 5 5 0 0 1 0-10z",
5528
- close: "M6 6l12 12M18 6L6 18",
5529
- sun: null,
5530
- moon: null
5531
- };
5532
- function Glyph({ d, size = 18 }) {
5533
- return /* @__PURE__ */ React.createElement("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.9", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("path", { d }));
5534
- }
5535
- function SearchGlyph() {
5536
- return /* @__PURE__ */ React.createElement("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("path", { d: ICON.search }));
5537
- }
5538
- function ThemeGlyph({ dark }) {
5539
- return dark ? /* @__PURE__ */ React.createElement("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("circle", { cx: "12", cy: "12", r: "4" }), /* @__PURE__ */ React.createElement("path", { d: "M12 2v2M12 20v2M2 12h2M20 12h2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M19.1 4.9l-1.4 1.4M6.3 17.7l-1.4 1.4" })) : /* @__PURE__ */ React.createElement("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("path", { d: "M20 13.5A8 8 0 1 1 10.5 4a6.5 6.5 0 0 0 9.5 9.5z" }));
5540
- }
5541
- const T = {
5542
- en: {
5543
- sub: "CyberSkill · one page, three lenses · Hiện Thực Hoá Ý Chí",
5544
- updated: "Updated",
5545
- portfolio: "Portfolio",
5546
- projects: "Projects",
5547
- inprog: "In progress",
5548
- shipped: "Shipped",
5549
- blocked: "Blocked",
5550
- planned: "Planned",
5551
- avg: "Avg progress",
5552
- velocity: "Velocity · tasks shipped / week",
5553
- perShipped: " shipped",
5554
- nowTitle: "Now shipping",
5555
- search: "Search projects…",
5556
- view: "View",
5557
- lenses: { board: "Board", table: "Table", releases: "Releases" },
5558
- status: "Status",
5559
- all: "All",
5560
- of: "of",
5561
- key: "Key",
5562
- project: "Project",
5563
- squad: "Squad",
5564
- owner: "Owner",
5565
- progress: "Progress",
5566
- thisWeek: "This week",
5567
- tasks: "Tasks",
5568
- risk: "Risk",
5569
- close: "Close",
5570
- langBtn: "VN",
5571
- theme: "Toggle theme"
5572
- },
5573
- vi: {
5574
- sub: "CyberSkill · một trang, ba lăng kính · Hiện Thực Hoá Ý Chí",
5575
- updated: "Cập nhật",
5576
- portfolio: "Danh mục",
5577
- projects: "Dự án",
5578
- inprog: "Đang làm",
5579
- shipped: "Đã ra mắt",
5580
- blocked: "Bị chặn",
5581
- planned: "Dự kiến",
5582
- avg: "Tiến độ TB",
5583
- velocity: "Tốc độ · task giao / tuần",
5584
- perShipped: " đã giao",
5585
- nowTitle: "Đang ra mắt",
5586
- search: "Tìm dự án…",
5587
- view: "Chế độ xem",
5588
- lenses: { board: "Bảng công việc", table: "Bảng dữ liệu", releases: "Phát hành" },
5589
- status: "Trạng thái",
5590
- all: "Tất cả",
5591
- of: "trên",
5592
- key: "Mã",
5593
- project: "Dự án",
5594
- squad: "Nhóm",
5595
- owner: "Phụ trách",
5596
- progress: "Tiến độ",
5597
- thisWeek: "Tuần này",
5598
- tasks: "Công việc",
5599
- risk: "Rủi ro",
5600
- close: "Đóng",
5601
- langBtn: "EN",
5602
- theme: "Đổi giao diện"
5603
- }
5604
- };
5605
- const statusLabels = (t) => ({ done: t.shipped, active: t.inprog, hold: t.blocked, todo: t.planned });
5606
- function Segments({ seg, className }) {
5607
- return /* @__PURE__ */ React.createElement("div", { className }, seg.done > 0 && /* @__PURE__ */ React.createElement("i", { className: "seg-done", style: { width: seg.done + "%" } }), seg.active > 0 && /* @__PURE__ */ React.createElement("i", { className: "seg-active", style: { width: seg.active + "%" } }), seg.hold > 0 && /* @__PURE__ */ React.createElement("i", { className: "seg-hold", style: { width: seg.hold + "%" } }), seg.todo > 0 && /* @__PURE__ */ React.createElement("i", { className: "seg-todo", style: { width: seg.todo + "%" } }));
5608
- }
5609
- function Drawer({ p, onClose, t }) {
5610
- if (!p) return null;
5611
- const SL = statusLabels(t);
5612
- return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { className: "scrim", onClick: onClose }), /* @__PURE__ */ React.createElement("aside", { className: "drawer", role: "dialog", "aria-modal": "true", "aria-label": p.name }, /* @__PURE__ */ React.createElement("div", { className: "dw-h" }, /* @__PURE__ */ React.createElement("div", { className: "row" }, /* @__PURE__ */ React.createElement("span", { className: "mono muted", style: { fontWeight: 700 } }, p.key), /* @__PURE__ */ React.createElement("span", { className: "pill " + p.status }, SL[p.status]), /* @__PURE__ */ React.createElement("button", { className: "dw-x", onClick: onClose, "aria-label": t.close }, /* @__PURE__ */ React.createElement(Glyph, { d: ICON.close, size: 20 }))), /* @__PURE__ */ React.createElement("h2", null, p.name)), /* @__PURE__ */ React.createElement("div", { className: "dw-b" }, /* @__PURE__ */ React.createElement("p", { className: "muted", style: { marginTop: 0 } }, p.blurb), /* @__PURE__ */ React.createElement("dl", { className: "meta" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("dt", null, t.owner), /* @__PURE__ */ React.createElement("dd", null, p.owner)), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("dt", null, t.squad), /* @__PURE__ */ React.createElement("dd", null, p.squad)), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("dt", null, t.progress), /* @__PURE__ */ React.createElement("dd", null, p.pct, "%")), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("dt", null, t.updated), /* @__PURE__ */ React.createElement("dd", null, p.updated))), /* @__PURE__ */ React.createElement(Segments, { seg: p.seg, className: "minibar" }), /* @__PURE__ */ React.createElement("div", { className: "sect" }, /* @__PURE__ */ React.createElement("h3", null, t.tasks), /* @__PURE__ */ React.createElement("div", { className: "task-chips" }, p.tasks.map((tk) => /* @__PURE__ */ React.createElement("span", { key: tk[0], className: "chip " + tk[2], title: tk[1] }, tk[0]))), /* @__PURE__ */ React.createElement("ul", { className: "tasks" }, p.tasks.map((tk) => /* @__PURE__ */ React.createElement("li", { key: tk[0] }, tk[1], " — ", /* @__PURE__ */ React.createElement("span", { className: "muted" }, SL[tk[2]]))))), p.risk ? /* @__PURE__ */ React.createElement("div", { className: "sect" }, /* @__PURE__ */ React.createElement("h3", null, t.risk), /* @__PURE__ */ React.createElement("div", { className: "risk" }, p.risk)) : null)));
5613
- }
5614
- function StatusHub() {
5615
- const D = window.SH_DATA;
5616
- const [dark, setDark] = useState(false);
5617
- const [lang, setLang] = useState("en");
5618
- const [lens, setLens] = useState("board");
5619
- const [q, setQ] = useState("");
5620
- const [facet, setFacet] = useState("all");
5621
- const [kpi, setKpi] = useState(null);
5622
- const [sel, setSel] = useState(null);
5623
- const t = T[lang];
5624
- const SL = statusLabels(t);
5625
- const VI = lang === "vi" ? window.SH_VI || null : null;
5626
- const loc = (p) => VI ? {
5627
- ...p,
5628
- blurb: VI.blurbs[p.key] || p.blurb,
5629
- risk: VI.risks[p.key] != null ? VI.risks[p.key] : p.risk,
5630
- owner: VI.owners[p.owner] || p.owner,
5631
- squad: VI.squads[p.squad] || p.squad,
5632
- updated: VI.updatedRel[p.updated] || p.updated
5633
- } : p;
5634
- const counts = useMemo(() => {
5635
- const c = { active: 0, done: 0, hold: 0 };
5636
- D.projects.forEach((p) => {
5637
- if (c[p.status] != null) c[p.status]++;
5638
- });
5639
- const avg = Math.round(D.projects.reduce((a, p) => a + p.pct, 0) / D.projects.length);
5640
- return { total: D.projects.length, ...c, avg };
5641
- }, [D]);
5642
- const shown = useMemo(() => {
5643
- const needle = q.trim().toLowerCase();
5644
- const eff = kpi || (facet === "all" ? null : facet);
5645
- return D.projects.filter((p) => {
5646
- if (eff && p.status !== eff) return false;
5647
- if (!needle) return true;
5648
- return (p.name + " " + p.key + " " + p.blurb).toLowerCase().includes(needle);
5649
- });
5650
- }, [D, q, facet, kpi]);
5651
- const maxV = Math.max(...D.velocity.map((v) => v.n));
5652
- const toggleKpi = (k) => setKpi((cur) => cur === k ? null : k);
5653
- return /* @__PURE__ */ React.createElement("div", { className: "sh", lang, "data-theme": dark ? "dark" : void 0 }, /* @__PURE__ */ React.createElement("a", { className: "cs-skip", href: "#main", style: { position: "absolute", left: -999, width: 1, height: 1, overflow: "hidden" } }, "Skip to content"), /* @__PURE__ */ React.createElement("header", { className: "hd" }, /* @__PURE__ */ React.createElement("div", { className: "hd-in" }, /* @__PURE__ */ React.createElement("div", { className: "hd-id" }, /* @__PURE__ */ React.createElement("span", { className: "hd-mark" }, /* @__PURE__ */ React.createElement("img", { src: "../../assets/logo-mark.svg", alt: "" })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h1", null, "Status Hub"), /* @__PURE__ */ React.createElement("p", { className: "hd-sub" }, t.sub))), /* @__PURE__ */ React.createElement("div", { className: "hd-side" }, /* @__PURE__ */ React.createElement("p", { className: "hd-meta" }, t.updated, /* @__PURE__ */ React.createElement("br", null), VI ? VI.updated : D.updated), /* @__PURE__ */ React.createElement("button", { className: "btn ghost", onClick: () => setLang((l) => l === "en" ? "vi" : "en"), "aria-label": "Toggle language", style: { fontWeight: 700, fontSize: 13 } }, t.langBtn), /* @__PURE__ */ React.createElement("button", { className: "btn ghost", onClick: () => setDark((v) => !v), "aria-label": t.theme }, /* @__PURE__ */ React.createElement(ThemeGlyph, { dark }))))), /* @__PURE__ */ React.createElement("main", { id: "main", className: "wrap" }, /* @__PURE__ */ React.createElement("div", { className: "deck" }, /* @__PURE__ */ React.createElement("section", { className: "panel" }, /* @__PURE__ */ React.createElement("h2", null, t.portfolio), /* @__PURE__ */ React.createElement("div", { className: "kpis" }, /* @__PURE__ */ React.createElement("button", { className: "kpi", "data-on": kpi === null ? "1" : "0", onClick: () => setKpi(null) }, /* @__PURE__ */ React.createElement("b", null, counts.total), /* @__PURE__ */ React.createElement("span", null, t.projects)), /* @__PURE__ */ React.createElement("button", { className: "kpi", "data-on": kpi === "active" ? "1" : "0", onClick: () => toggleKpi("active") }, /* @__PURE__ */ React.createElement("b", null, counts.active), /* @__PURE__ */ React.createElement("span", null, t.inprog)), /* @__PURE__ */ React.createElement("button", { className: "kpi", "data-on": kpi === "done" ? "1" : "0", onClick: () => toggleKpi("done") }, /* @__PURE__ */ React.createElement("b", null, counts.done), /* @__PURE__ */ React.createElement("span", null, t.shipped)), /* @__PURE__ */ React.createElement("button", { className: "kpi", "data-on": kpi === "hold" ? "1" : "0", onClick: () => toggleKpi("hold") }, /* @__PURE__ */ React.createElement("b", null, counts.hold), /* @__PURE__ */ React.createElement("span", null, t.blocked)), /* @__PURE__ */ React.createElement("button", { className: "kpi", onClick: () => setKpi(null) }, /* @__PURE__ */ React.createElement("b", null, counts.avg, "%"), /* @__PURE__ */ React.createElement("span", null, t.avg))), /* @__PURE__ */ React.createElement(Segments, { seg: { done: 60, active: 22, hold: 6, todo: 12 }, className: "bar-seg" }), /* @__PURE__ */ React.createElement("div", { className: "legend" }, /* @__PURE__ */ React.createElement("span", null, /* @__PURE__ */ React.createElement("i", { className: "dot", style: { background: "var(--cs-color-semantic-success)" } }), t.shipped), /* @__PURE__ */ React.createElement("span", null, /* @__PURE__ */ React.createElement("i", { className: "dot", style: { background: "var(--cs-color-brand-ochre)" } }), t.inprog), /* @__PURE__ */ React.createElement("span", null, /* @__PURE__ */ React.createElement("i", { className: "dot", style: { background: "var(--cs-color-semantic-danger)" } }), t.blocked), /* @__PURE__ */ React.createElement("span", null, /* @__PURE__ */ React.createElement("i", { className: "dot", style: { background: "var(--cs-status-todo)" } }), t.planned))), /* @__PURE__ */ React.createElement("section", { className: "panel" }, /* @__PURE__ */ React.createElement("h2", null, t.velocity), /* @__PURE__ */ React.createElement("div", { className: "spark" }, D.velocity.map((v) => /* @__PURE__ */ React.createElement("a", { key: v.wk, title: v.n + t.perShipped }, /* @__PURE__ */ React.createElement("b", null, v.n), /* @__PURE__ */ React.createElement("i", { style: { height: v.n / maxV * 72 + "px" } }), /* @__PURE__ */ React.createElement("span", null, v.wk)))))), /* @__PURE__ */ React.createElement("div", { className: "now" }, /* @__PURE__ */ React.createElement("h2", null, t.nowTitle), lang === "vi" ? /* @__PURE__ */ React.createElement("p", null, /* @__PURE__ */ React.createElement("b", null, "v1.6.0 — Liên kết Glass + gói Ngũ Hành v2."), " Liên kết component ", /* @__PURE__ */ React.createElement("span", { className: "mono" }, ".cs-surface-*"), " (tùy chọn), trình kiểm 0 cảnh báo trên cả 15 gói, và kiểm tra lại APCA cho chế độ tối. Ra mắt tuần này.") : /* @__PURE__ */ React.createElement("p", null, /* @__PURE__ */ React.createElement("b", null, "v1.6.0 — Glass bindings + element-pack v2."), " Opt-in ", /* @__PURE__ */ React.createElement("span", { className: "mono" }, ".cs-surface-*"), " component bindings, a zero-warning verifier across all 15 packs, and a dark-mode APCA re-check. Ships this week.")), /* @__PURE__ */ React.createElement("div", { className: "bar" }, /* @__PURE__ */ React.createElement("div", { className: "bar-top" }, /* @__PURE__ */ React.createElement("label", { className: "srch" }, /* @__PURE__ */ React.createElement(SearchGlyph, null), /* @__PURE__ */ React.createElement("input", { placeholder: t.search, value: q, onChange: (e) => setQ(e.target.value) })), /* @__PURE__ */ React.createElement(
5654
- "div",
5655
- {
5656
- className: "lenses",
5657
- role: "tablist",
5658
- "aria-label": t.view,
5659
- onKeyDown: (e) => {
5660
- const order = ["board", "table", "releases"];
5661
- const i = order.indexOf(lens);
5662
- let next = i;
5663
- if (e.key === "ArrowRight") next = (i + 1) % order.length;
5664
- else if (e.key === "ArrowLeft") next = (i - 1 + order.length) % order.length;
5665
- else if (e.key === "Home") next = 0;
5666
- else if (e.key === "End") next = order.length - 1;
5667
- else return;
5668
- e.preventDefault();
5669
- setLens(order[next]);
5670
- const tabs = e.currentTarget.querySelectorAll('[role="tab"]');
5671
- tabs[next] && tabs[next].focus && tabs[next].focus();
5672
- }
5673
- },
5674
- ["board", "table", "releases"].map((l) => /* @__PURE__ */ React.createElement(
5675
- "button",
5676
- {
5677
- key: l,
5678
- className: "ln",
5679
- role: "tab",
5680
- "aria-selected": lens === l,
5681
- tabIndex: lens === l ? 0 : -1,
5682
- onClick: () => setLens(l)
5683
- },
5684
- t.lenses[l]
5685
- ))
5686
- ), /* @__PURE__ */ React.createElement("label", { className: "facet" }, t.status, /* @__PURE__ */ React.createElement("select", { value: kpi || facet, onChange: (e) => {
5687
- setFacet(e.target.value);
5688
- setKpi(null);
5689
- } }, /* @__PURE__ */ React.createElement("option", { value: "all" }, t.all), /* @__PURE__ */ React.createElement("option", { value: "active" }, t.inprog), /* @__PURE__ */ React.createElement("option", { value: "done" }, t.shipped), /* @__PURE__ */ React.createElement("option", { value: "hold" }, t.blocked))), /* @__PURE__ */ React.createElement("span", { className: "cnt" }, shown.length, " ", t.of, " ", D.projects.length))), lens === "board" && /* @__PURE__ */ React.createElement("div", { className: "grid" }, shown.map((p0) => {
5690
- const p = loc(p0);
5691
- return /* @__PURE__ */ React.createElement("button", { className: "card" + (p.status === "active" ? " hot" : ""), key: p.key, onClick: () => setSel(p0) }, /* @__PURE__ */ React.createElement("div", { className: "card-h" }, /* @__PURE__ */ React.createElement("h3", null, /* @__PURE__ */ React.createElement("span", { className: "k mono" }, p.key), " · ", p.name), /* @__PURE__ */ React.createElement("span", { className: "pct" }, p.pct, "%")), /* @__PURE__ */ React.createElement("p", null, p.blurb), /* @__PURE__ */ React.createElement(Segments, { seg: p.seg, className: "minibar" }), /* @__PURE__ */ React.createElement("div", { className: "task-chips" }, /* @__PURE__ */ React.createElement("span", { className: "pill " + p.status }, SL[p.status]), p.tasks.slice(0, 3).map((tk) => /* @__PURE__ */ React.createElement("span", { key: tk[0], className: "chip " + tk[2] }, tk[0]))));
5692
- })), lens === "table" && /* @__PURE__ */ React.createElement("div", { className: "tbl-wrap" }, /* @__PURE__ */ React.createElement("table", { className: "tbl" }, /* @__PURE__ */ React.createElement("thead", null, /* @__PURE__ */ React.createElement("tr", null, /* @__PURE__ */ React.createElement("th", null, t.key), /* @__PURE__ */ React.createElement("th", null, t.project), /* @__PURE__ */ React.createElement("th", null, t.squad), /* @__PURE__ */ React.createElement("th", null, t.owner), /* @__PURE__ */ React.createElement("th", null, t.progress), /* @__PURE__ */ React.createElement("th", null, t.status))), /* @__PURE__ */ React.createElement("tbody", null, shown.map((p0) => {
5693
- const p = loc(p0);
5694
- return /* @__PURE__ */ React.createElement("tr", { key: p.key, onClick: () => setSel(p0) }, /* @__PURE__ */ React.createElement("td", { className: "mono", style: { fontWeight: 700 } }, p.key), /* @__PURE__ */ React.createElement("td", null, p.name), /* @__PURE__ */ React.createElement("td", null, p.squad), /* @__PURE__ */ React.createElement("td", null, p.owner), /* @__PURE__ */ React.createElement("td", null, p.pct, "%"), /* @__PURE__ */ React.createElement("td", null, /* @__PURE__ */ React.createElement("span", { className: "pill " + p.status }, SL[p.status])));
5695
- })))), lens === "releases" && /* @__PURE__ */ React.createElement("div", { className: "rels" }, D.releases.map((r) => /* @__PURE__ */ React.createElement("div", { className: "rel" + (r.now ? " now" : ""), key: r.ver }, /* @__PURE__ */ React.createElement("div", { className: "tick" }, r.done ? "✓" : "→"), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "rel-h" }, /* @__PURE__ */ React.createElement("b", null, r.ver), /* @__PURE__ */ React.createElement("span", { className: "pill " + (r.now ? "active" : "done") }, r.now ? t.thisWeek : t.shipped), /* @__PURE__ */ React.createElement("span", { className: "muted" }, VI ? VI.relWhen[r.ver] || r.when : r.when)), /* @__PURE__ */ React.createElement("div", { className: "rel-sec" }, /* @__PURE__ */ React.createElement("h4", null, VI ? VI.relTitles[r.ver] || r.title : r.title), /* @__PURE__ */ React.createElement("ul", null, (VI ? VI.relItems[r.ver] || r.items : r.items).map((it, i) => /* @__PURE__ */ React.createElement("li", { key: i }, it)))))))), /* @__PURE__ */ React.createElement("p", { className: "ft" }, lang === "vi" ? /* @__PURE__ */ React.createElement(React.Fragment, null, "CyberSkill Status Hub — bản tái hiện UI kit của design system. Dựng trên token ", /* @__PURE__ */ React.createElement("span", { className: "mono" }, "styles.css"), ". Turn Your Will Into Real.") : /* @__PURE__ */ React.createElement(React.Fragment, null, "CyberSkill Status Hub — a design-system UI kit recreation. Built on ", /* @__PURE__ */ React.createElement("span", { className: "mono" }, "styles.css"), " tokens. Turn Your Will Into Real."))), /* @__PURE__ */ React.createElement(Drawer, { p: sel && loc(sel), onClose: () => setSel(null), t }));
5696
- }
5697
- window.StatusHub = StatusHub;
5698
- })(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/status-hub/StatusHub.jsx", error: String((e && e.message) || e) }); }
5699
-
5700
- // ui_kits/status-hub/data.js
5701
- try { (() => {
5702
- window.SH_DATA = {
5703
- updated: "18 Jul 2026, 13:20 ICT",
5704
- velocity: [
5705
- { wk: "W23", n: 4 },
5706
- { wk: "W24", n: 7 },
5707
- { wk: "W25", n: 5 },
5708
- { wk: "W26", n: 9 },
5709
- { wk: "W27", n: 6 },
5710
- { wk: "W28", n: 11 },
5711
- { wk: "W29", n: 8 }
5712
- ],
5713
- projects: [
5714
- {
5715
- key: "CDS",
5716
- name: "Design System v1.6",
5717
- status: "active",
5718
- pct: 72,
5719
- blurb: "Liquid Glass component bindings + 15 element packs.",
5720
- seg: { done: 62, active: 20, hold: 4, todo: 14 },
5721
- owner: "Design Lead",
5722
- updated: "2h ago",
5723
- squad: "Platform",
5724
- tasks: [["TASK-DS-041", "Glass surface bindings", "done"], ["TASK-DS-047", "Element-pack verifier v2", "active"], ["TASK-DS-052", "Dark-mode APCA re-check", "active"], ["TASK-DS-055", "Figma variable sync", "todo"]],
5725
- risk: ""
5726
- },
5727
- {
5728
- key: "GENIE",
5729
- name: "Lumi Genie Chat",
5730
- status: "active",
5731
- pct: 58,
5732
- blurb: "Keyless Claude proxy; scripted wish-capture flow.",
5733
- seg: { done: 48, active: 24, hold: 8, todo: 20 },
5734
- owner: "AI Lead",
5735
- updated: "5h ago",
5736
- squad: "AI",
5737
- tasks: [["TASK-CHAR-026", "In-chat wish flow", "done"], ["TASK-CHAR-030", "One-time mascot hint", "active"], ["TASK-CHAR-021", "Golden-genie GLB", "hold"]],
5738
- risk: "GLB art asset is a hard external dependency — commission not yet confirmed (TASK-CHAR-021)."
5739
- },
5740
- {
5741
- key: "SITE",
5742
- name: "cyberskill.world",
5743
- status: "active",
5744
- pct: 84,
5745
- blurb: "Bilingual storytelling landing; lead capture live.",
5746
- seg: { done: 78, active: 12, hold: 0, todo: 10 },
5747
- owner: "Web Lead",
5748
- updated: "1h ago",
5749
- squad: "Growth",
5750
- tasks: [["TASK-BIZ-003", "Lead pipeline e2e", "done"], ["TASK-SEO-012", "llms.txt route", "done"], ["TASK-CTA-020", "Teardown funnel polish", "active"]],
5751
- risk: ""
5752
- },
5753
- {
5754
- key: "OS",
5755
- name: "CyberOS Agent Spine",
5756
- status: "hold",
5757
- pct: 41,
5758
- blurb: "BRAIN memory store + HITL acceptance gates.",
5759
- seg: { done: 36, active: 8, hold: 30, todo: 26 },
5760
- owner: "Eng Lead",
5761
- updated: "1d ago",
5762
- squad: "Platform",
5763
- tasks: [["TASK-OS-018", "Gate runner", "done"], ["TASK-OS-022", "Memory compaction", "hold"], ["TASK-OS-025", "Provenance ledger", "hold"]],
5764
- risk: "Blocked on infra: provenance ledger needs the signing service (target Q1 2027)."
5765
- },
5766
- {
5767
- key: "AUDIT",
5768
- name: "DS Audit Framework",
5769
- status: "done",
5770
- pct: 100,
5771
- blurb: "125-criteria vendor-neutral audit; L3 tier held.",
5772
- seg: { done: 100, active: 0, hold: 0, todo: 0 },
5773
- owner: "DesignOps",
5774
- updated: "3d ago",
5775
- squad: "Platform",
5776
- tasks: [["TASK-AUD-009", "Deterministic engine", "done"], ["TASK-AUD-011", "No-regression CI gate", "done"]],
5777
- risk: ""
5778
- },
5779
- {
5780
- key: "TOKENS",
5781
- name: "Token Pipeline",
5782
- status: "active",
5783
- pct: 66,
5784
- blurb: "DTCG → CSS/TS/Swift/Kotlin/Flutter/Figma.",
5785
- seg: { done: 58, active: 22, hold: 0, todo: 20 },
5786
- owner: "Eng Lead",
5787
- updated: "6h ago",
5788
- squad: "Platform",
5789
- tasks: [["TASK-TOK-014", "Contrast grid export", "done"], ["TASK-TOK-019", "Flutter target", "active"], ["TASK-TOK-021", "Provenance JSON", "todo"]],
5790
- risk: ""
5791
- }
5792
- ],
5793
- releases: [
5794
- {
5795
- ver: "v1.6.0",
5796
- when: "This week",
5797
- now: true,
5798
- done: false,
5799
- title: "Glass bindings + element-pack v2",
5800
- items: ["Opt-in .cs-surface-* component bindings", "Element-pack verifier v2 (0 warnings across 15)", "Dark-mode APCA re-check"]
5801
- },
5802
- {
5803
- ver: "v1.5.1",
5804
- when: "04 Jul 2026",
5805
- now: false,
5806
- done: true,
5807
- title: "15 element packs ship",
5808
- items: ["All 15 packs promoted to shipped", "Per-pack screenshot regression", "Gallery build script"]
5809
- },
5810
- {
5811
- ver: "v1.4.0",
5812
- when: "13 Jun 2026",
5813
- now: false,
5814
- done: true,
5815
- title: "Liquid Glass in code",
5816
- items: ["--cs-glass-* + --cs-depth-* scalars", "glass.css material layer", "Reduced-transparency fallbacks"]
5817
- },
5818
- {
5819
- ver: "v1.3.0",
5820
- when: "14 May 2026",
5821
- now: false,
5822
- done: true,
5823
- title: "Brand assets + Logo",
5824
- items: ["Official master mark ships", "React <Logo> from master artwork", "Light becomes default theme"]
5825
- }
5826
- ]
5827
- };
5828
- window.SH_VI = {
5829
- updated: "18/07/2026, 13:20 ICT",
5830
- blurbs: { CDS: "Liên kết component Liquid Glass + 15 gói Ngũ Hành.", GENIE: "Proxy Claude không cần khóa; luồng tiếp nhận điều ước theo kịch bản.", SITE: "Trang giới thiệu song ngữ kể chuyện; thu lead đang chạy.", OS: "Kho nhớ BRAIN + cổng nghiệm thu có con người.", AUDIT: "Bộ kiểm định trung lập 125 tiêu chí; giữ hạng L3.", TOKENS: "DTCG → CSS/TS/Swift/Kotlin/Flutter/Figma." },
5831
- risks: { GENIE: "Tài sản GLB là phụ thuộc ngoài khó — chưa chốt đặt hàng (TASK-CHAR-021).", OS: "Bị chặn bởi hạ tầng: sổ cái nguồn gốc cần dịch vụ ký số (mục tiêu Q1 2027)." },
5832
- owners: { "Design Lead": "Trưởng thiết kế", "AI Lead": "Trưởng AI", "Web Lead": "Trưởng web", "Eng Lead": "Trưởng kỹ thuật", "DesignOps": "DesignOps" },
5833
- squads: { Platform: "Nền tảng", AI: "AI", Growth: "Tăng trưởng" },
5834
- updatedRel: { "2h ago": "2 giờ trước", "5h ago": "5 giờ trước", "1h ago": "1 giờ trước", "1d ago": "1 ngày trước", "3d ago": "3 ngày trước", "6h ago": "6 giờ trước" },
5835
- relWhen: { "v1.6.0": "Tuần này", "v1.5.1": "04/07/2026", "v1.4.0": "13/06/2026", "v1.3.0": "14/05/2026" },
5836
- relTitles: { "v1.6.0": "Liên kết Glass + gói Ngũ Hành v2", "v1.5.1": "15 gói Ngũ Hành ra mắt", "v1.4.0": "Liquid Glass vào mã", "v1.3.0": "Tài sản thương hiệu + Logo" },
5837
- relItems: { "v1.6.0": ["Liên kết component .cs-surface-* (tùy chọn)", "Trình kiểm gói Ngũ Hành v2 (0 cảnh báo trên 15 gói)", "Kiểm tra lại APCA cho chế độ tối"], "v1.5.1": ["Cả 15 gói lên trạng thái đã ra mắt", "Regression ảnh chụp từng gói", "Script dựng gallery"], "v1.4.0": ["Scalar --cs-glass-* + --cs-depth-*", "Lớp chất liệu glass.css", "Fallback khi giảm độ trong suốt"], "v1.3.0": ["Master mark chính thức ra mắt", "<Logo> React từ artwork gốc", "Light thành theme mặc định"] }
5838
- };
5839
- })(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/status-hub/data.js", error: String((e && e.message) || e) }); }
5840
-
5841
- // ui_kits/website/Website.jsx
5842
- try { (() => {
5843
- const { useState, useRef, useEffect } = React;
5844
- function I({ name, size = 20, stroke = 1.9 }) {
5845
- const p = {
5846
- sparkle: "M12 3l1.8 5.4L19 10l-5.2 1.6L12 17l-1.8-5.4L5 10l5.2-1.6z",
5847
- chat: "M4 5h16v11H8l-4 4z",
5848
- check: "M4 12.5l5 5 11-11",
5849
- "arrow-right": "M5 12h14M13 6l6 6-6 6",
5850
- close: "M6 6l12 12M18 6L6 18",
5851
- sun: "M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8zM12 2v2M12 20v2M2 12h2M20 12h2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M19.1 4.9l-1.4 1.4M6.3 17.7l-1.4 1.4",
5852
- moon: "M20 13.5A8 8 0 1 1 10.5 4a6.5 6.5 0 0 0 9.5 9.5z"
5853
- }[name];
5854
- return /* @__PURE__ */ React.createElement("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: stroke, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("path", { d: p }));
5855
- }
5856
- function Chat({ t, seed, onClose }) {
5857
- const [msgs, setMsgs] = useState([{ who: "bot", text: t.genie.greeting }]);
5858
- const [val, setVal] = useState("");
5859
- const bodyRef = useRef(null);
5860
- useEffect(() => {
5861
- if (seed) send(seed);
5862
- }, []);
5863
- useEffect(() => {
5864
- const b = bodyRef.current;
5865
- if (b) b.scrollTop = b.scrollHeight;
5866
- }, [msgs]);
5867
- function send(text) {
5868
- const m = (text || "").trim();
5869
- if (!m) return;
5870
- setMsgs((cur) => [...cur, { who: "me", text: m }]);
5871
- setVal("");
5872
- setTimeout(() => setMsgs((cur) => [...cur, { who: "bot", text: "A wish worth granting. I’ll hand it to the team — first, what should I call you?" }]), 500);
5873
- }
5874
- return /* @__PURE__ */ React.createElement("div", { className: "chat" }, /* @__PURE__ */ React.createElement("div", { className: "chat-h" }, /* @__PURE__ */ React.createElement("img", { src: "../../assets/lumi-poster.webp", alt: "" }), /* @__PURE__ */ React.createElement("b", null, t.genie.title), /* @__PURE__ */ React.createElement("button", { onClick: onClose, "aria-label": "Close chat" }, /* @__PURE__ */ React.createElement(I, { name: "close", size: 18 }))), /* @__PURE__ */ React.createElement("div", { className: "chat-b", ref: bodyRef }, msgs.map((m, i) => /* @__PURE__ */ React.createElement("div", { key: i, className: "msg " + m.who }, m.text)), msgs.length === 1 && /* @__PURE__ */ React.createElement("div", { className: "chips" }, t.genie.chips.map((c) => /* @__PURE__ */ React.createElement("button", { key: c, onClick: () => send(c) }, c)))), /* @__PURE__ */ React.createElement("div", { className: "chat-f" }, /* @__PURE__ */ React.createElement("input", { value: val, onChange: (e) => setVal(e.target.value), onKeyDown: (e) => e.key === "Enter" && send(val), placeholder: t.genie.placeholder }), /* @__PURE__ */ React.createElement("button", { onClick: () => send(val) }, t.genie.send)));
5875
- }
5876
- function Website() {
5877
- const [loc, setLoc] = useState("en");
5878
- const [dark, setDark] = useState(false);
5879
- const [chat, setChat] = useState(false);
5880
- const [seed, setSeed] = useState("");
5881
- const [wish, setWish] = useState("");
5882
- const [sent, setSent] = useState(false);
5883
- const [fabVisible, setFabVisible] = useState(false);
5884
- const t = window.SITE_COPY[loc];
5885
- function openChat(s) {
5886
- setSeed(s || "");
5887
- setChat(true);
5888
- }
5889
- useEffect(() => {
5890
- const sync = () => setFabVisible(window.scrollY > 140);
5891
- sync();
5892
- window.addEventListener("scroll", sync, { passive: true });
5893
- return () => window.removeEventListener("scroll", sync);
5894
- }, []);
5895
- return /* @__PURE__ */ React.createElement("div", { className: "site", "data-theme": dark ? "dark" : void 0 }, /* @__PURE__ */ React.createElement("a", { className: "cs-skip", href: "#main", style: { position: "absolute", left: -999, width: 1, height: 1, overflow: "hidden" } }, "Skip to content"), /* @__PURE__ */ React.createElement("header", { className: "hdr cs-surface-light" }, /* @__PURE__ */ React.createElement("div", { className: "container hdr-in" }, /* @__PURE__ */ React.createElement("a", { className: "brand", href: "#top" }, /* @__PURE__ */ React.createElement("img", { src: "../../assets/logo-mark.svg", alt: "" }), "CyberSkill"), /* @__PURE__ */ React.createElement("nav", { className: "nav" }, t.nav.map((n, i) => /* @__PURE__ */ React.createElement("a", { key: i, href: "#" + ["services", "work", "process", "careers", "contact"][i] }, n))), /* @__PURE__ */ React.createElement("div", { className: "hdr-side" }, /* @__PURE__ */ React.createElement("button", { className: "mini", onClick: () => setLoc(loc === "en" ? "vi" : "en") }, t.langLabel), /* @__PURE__ */ React.createElement("button", { className: "mini icon", onClick: () => setDark(!dark), "aria-label": "Toggle theme" }, /* @__PURE__ */ React.createElement(I, { name: dark ? "sun" : "moon", size: 18 })), /* @__PURE__ */ React.createElement("button", { className: "mini", style: { background: "var(--cs-color-brand-umber)", color: "var(--cs-color-text-inverse,#fff)", borderColor: "transparent" }, onClick: () => openChat("") }, t.talk)))), /* @__PURE__ */ React.createElement("main", { id: "main" }, /* @__PURE__ */ React.createElement("section", { className: "hero", id: "top" }, /* @__PURE__ */ React.createElement("div", { className: "container hero-in" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "eyebrow" }, t.eyebrow), /* @__PURE__ */ React.createElement("h1", null, t.slogan), /* @__PURE__ */ React.createElement("div", { className: "vn" }, t.sub), /* @__PURE__ */ React.createElement("p", { className: "lead" }, t.lead), /* @__PURE__ */ React.createElement("div", { className: "wish" }, /* @__PURE__ */ React.createElement("input", { value: wish, onChange: (e) => setWish(e.target.value), onKeyDown: (e) => e.key === "Enter" && openChat(wish), placeholder: t.wishPlaceholder, "aria-label": t.wishPlaceholder }), /* @__PURE__ */ React.createElement("button", { className: "btn-gold", onClick: () => openChat(wish) }, /* @__PURE__ */ React.createElement(I, { name: "sparkle", size: 18 }), t.wishCta))), /* @__PURE__ */ React.createElement("div", { className: "hero-art" }, /* @__PURE__ */ React.createElement("img", { src: "../../assets/lumi-poster.webp", alt: "Lumi, the golden genie" })))), /* @__PURE__ */ React.createElement("div", { className: "marq" }, /* @__PURE__ */ React.createElement("div", { className: "container marq-in" }, t.marquee.map((m, i) => /* @__PURE__ */ React.createElement(React.Fragment, { key: i }, i > 0 && /* @__PURE__ */ React.createElement("b", null, "·"), /* @__PURE__ */ React.createElement("span", null, m))))), /* @__PURE__ */ React.createElement("section", { className: "sec", id: "services" }, /* @__PURE__ */ React.createElement("div", { className: "container" }, /* @__PURE__ */ React.createElement("div", { className: "eyebrow" }, t.nav[0]), /* @__PURE__ */ React.createElement("h2", null, t.servicesTitle), /* @__PURE__ */ React.createElement("p", { className: "sec-lead" }, t.servicesLead), /* @__PURE__ */ React.createElement("div", { className: "svc-grid" }, t.services.map((s, i) => /* @__PURE__ */ React.createElement("article", { className: "svc cs-surface-standard", key: i }, /* @__PURE__ */ React.createElement("div", { className: "svc-ic" }, /* @__PURE__ */ React.createElement(I, { name: s.icon })), /* @__PURE__ */ React.createElement("h3", null, s.t), /* @__PURE__ */ React.createElement("p", null, s.s), /* @__PURE__ */ React.createElement("ul", null, s.o.map((o) => /* @__PURE__ */ React.createElement("li", { key: o }, o)))))))), /* @__PURE__ */ React.createElement("section", { className: "sec sec-alt", id: "process" }, /* @__PURE__ */ React.createElement("div", { className: "container" }, /* @__PURE__ */ React.createElement("div", { className: "eyebrow" }, t.nav[2]), /* @__PURE__ */ React.createElement("h2", null, t.processTitle), /* @__PURE__ */ React.createElement("p", { className: "sec-lead" }, t.processLead), /* @__PURE__ */ React.createElement("div", { className: "steps" }, t.steps.map((s) => /* @__PURE__ */ React.createElement("div", { className: "step", key: s.n }, /* @__PURE__ */ React.createElement("span", { className: "n" }, s.n), /* @__PURE__ */ React.createElement("h3", null, s.t), /* @__PURE__ */ React.createElement("p", null, s.s)))))), /* @__PURE__ */ React.createElement("section", { className: "sec", id: "careers" }, /* @__PURE__ */ React.createElement("div", { className: "container" }, /* @__PURE__ */ React.createElement("div", { className: "band" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h2", null, t.careersTitle), /* @__PURE__ */ React.createElement("p", null, t.careersLead)), /* @__PURE__ */ React.createElement("button", { className: "btn-gold", onClick: () => openChat("") }, t.careersCta, /* @__PURE__ */ React.createElement(I, { name: "arrow-right", size: 18 }))))), /* @__PURE__ */ React.createElement("section", { className: "sec sec-alt", id: "contact" }, /* @__PURE__ */ React.createElement("div", { className: "container contact-grid" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "eyebrow" }, t.nav[4]), /* @__PURE__ */ React.createElement("h2", null, t.contactTitle), /* @__PURE__ */ React.createElement("p", { className: "sec-lead", style: { marginBottom: 16 } }, t.contactLead), /* @__PURE__ */ React.createElement("p", { className: "trust" }, /* @__PURE__ */ React.createElement(I, { name: "check", size: 16 }), t.form.trust)), sent ? /* @__PURE__ */ React.createElement("div", { className: "sent" }, /* @__PURE__ */ React.createElement(I, { name: "check", size: 18 }), " ", loc === "vi" ? "Đã nhận được lời nhắn. Cảm ơn bạn!" : "Message received. Thank you!") : /* @__PURE__ */ React.createElement("form", { className: "form", onSubmit: (e) => {
5896
- e.preventDefault();
5897
- setSent(true);
5898
- } }, /* @__PURE__ */ React.createElement("label", { className: "cs-field" }, /* @__PURE__ */ React.createElement("span", { className: "cs-field__label" }, t.form.name), /* @__PURE__ */ React.createElement("input", { className: "cs-field__control", required: true })), /* @__PURE__ */ React.createElement("label", { className: "cs-field" }, /* @__PURE__ */ React.createElement("span", { className: "cs-field__label" }, t.form.email), /* @__PURE__ */ React.createElement("input", { className: "cs-field__control", type: "email", required: true })), /* @__PURE__ */ React.createElement("label", { className: "cs-field" }, /* @__PURE__ */ React.createElement("span", { className: "cs-field__label" }, t.form.company, " ", /* @__PURE__ */ React.createElement("span", { className: "muted", style: { fontWeight: 400, color: "var(--cs-color-text-muted)" } }, "· ", t.form.optional)), /* @__PURE__ */ React.createElement("input", { className: "cs-field__control" })), /* @__PURE__ */ React.createElement("label", { className: "cs-field" }, /* @__PURE__ */ React.createElement("span", { className: "cs-field__label" }, t.form.message), /* @__PURE__ */ React.createElement("textarea", { className: "cs-field__control", rows: "3" })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("button", { type: "submit", className: "btn-gold" }, t.form.submit, /* @__PURE__ */ React.createElement(I, { name: "arrow-right", size: 18 }))))))), /* @__PURE__ */ React.createElement("footer", { className: "ft" }, /* @__PURE__ */ React.createElement("div", { className: "container ft-in" }, /* @__PURE__ */ React.createElement("a", { className: "brand", href: "#top" }, /* @__PURE__ */ React.createElement("img", { src: "../../assets/logo-mark.svg", alt: "" }), "CyberSkill"), /* @__PURE__ */ React.createElement("div", { className: "ft-links" }, t.footerLinks.map((l) => /* @__PURE__ */ React.createElement("a", { key: l, href: "#top" }, l))), /* @__PURE__ */ React.createElement("small", null, "© 2026 CyberSkill · ", t.footerRights, " · Saigon"))), !chat && /* @__PURE__ */ React.createElement(
5899
- "button",
5900
- {
5901
- className: "fab",
5902
- type: "button",
5903
- "data-fab-deferred": "1",
5904
- onClick: () => openChat(""),
5905
- style: { opacity: fabVisible ? 1 : 0, pointerEvents: fabVisible ? "auto" : "none", transition: "opacity 160ms ease" },
5906
- "aria-hidden": !fabVisible,
5907
- tabIndex: fabVisible ? 0 : -1
5908
- },
5909
- /* @__PURE__ */ React.createElement("img", { src: "../../assets/lumi-poster.webp", alt: "" }),
5910
- t.talk
5911
- ), chat && /* @__PURE__ */ React.createElement(Chat, { t, seed, onClose: () => setChat(false) }));
5912
- }
5913
- window.Website = Website;
5914
- })(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/website/Website.jsx", error: String((e && e.message) || e) }); }
5915
-
5916
- // ui_kits/website/copy.js
5917
- try { (() => {
5918
- window.SITE_COPY = {
5919
- en: {
5920
- nav: ["Services", "Work", "Process", "Careers", "Contact"],
5921
- langLabel: "Tiếng Việt",
5922
- eyebrow: "A Saigon studio that turns will into working software. Since 2020.",
5923
- slogan: "Turn Your Will Into Real",
5924
- sub: "Hiện Thực Hoá Ý Chí",
5925
- lead: "Lumi, our golden genie, takes a clear wish and helps the human team turn it into software your people actually run — built to ship, and built to last.",
5926
- wishPlaceholder: "Make a wish… a faster checkout, an app, a tool",
5927
- wishCta: "Make a wish",
5928
- talk: "Talk to Lumi",
5929
- marquee: ["Web apps", "Mobile apps", "Internal systems", "Automation", "Design systems", "Built to last"],
5930
- servicesTitle: "What we build",
5931
- servicesLead: "Three focused practices. One team that answers for the result.",
5932
- services: [
5933
- { icon: "sparkle", t: "Web & mobile apps", s: "Product apps your customers reach for — fast, accessible, bilingual by default.", o: ["Web apps", "Mobile apps", "Design systems"] },
5934
- { icon: "chat", t: "Internal systems & automation", s: "The tools your team runs on: dashboards, workflows, and the automation behind them.", o: ["Internal systems", "Automation"] },
5935
- { icon: "check", t: "Built to last", s: "Shipped software that keeps running — tested, documented, handed over cleanly.", o: ["Testing", "Docs", "Handover"] }
5936
- ],
5937
- processTitle: "The arc of a wish",
5938
- processLead: "Every project follows one arc: from a clear wish to software running in the real world.",
5939
- steps: [
5940
- { n: "01", t: "Make a wish", s: "Tell us the outcome in a sentence or two. We reply within one business day." },
5941
- { n: "02", t: "Shape it", s: "A small senior team scopes the work and is honest about trade-offs." },
5942
- { n: "03", t: "Build to ship", s: "Short cycles, real software you can run — not slideware." },
5943
- { n: "04", t: "Built to last", s: "Tested, documented, handed over. It keeps running after we leave." }
5944
- ],
5945
- careersTitle: "Build with us",
5946
- careersLead: "We hire for craft and care — senior, small, and honest about trade-offs.",
5947
- careersCta: "See open roles",
5948
- contactTitle: "Tell us your wish",
5949
- contactLead: "Send a short note. We reply within one business day.",
5950
- form: { name: "Your name", email: "Work email", company: "Company", message: "Anything we should know?", submit: "Send", optional: "optional", trust: "Your details go straight to the team. We never share them." },
5951
- genie: { title: "Lumi · CyberSkill’s golden genie", greeting: "Hello, I’m Lumi. Think of me as a friendly guide: tell me a wish, and I’ll share plain advice before asking for anything. When a wish is clear, it goes to a real person on the team.", placeholder: "Type your wish…", send: "Send", chips: ["A web app", "An internal tool", "A design system"] },
5952
- footerRights: "All rights reserved.",
5953
- footerLinks: ["Privacy", "Accessibility", "Terms"]
5954
- },
5955
- vi: {
5956
- nav: ["Dịch vụ", "Dự án", "Quy trình", "Tuyển dụng", "Liên hệ"],
5957
- langLabel: "English",
5958
- eyebrow: "Studio Sài Gòn. Biến ý chí thành phần mềm chạy thật. Từ năm 2020.",
5959
- slogan: "Hiện Thực Hoá Ý Chí",
5960
- sub: "Turn Your Will Into Real",
5961
- lead: "Lumi, thần đèn vàng của CyberSkill, cùng đội ngũ biến điều ước rõ thành phần mềm đội bạn dùng mỗi ngày: bàn giao được, dùng lâu được.",
5962
- wishPlaceholder: "Ước một điều… thanh toán nhanh hơn, một ứng dụng, một công cụ",
5963
- wishCta: "Ước điều này",
5964
- talk: "Trò chuyện với Lumi",
5965
- marquee: ["Ứng dụng web", "Ứng dụng di động", "Hệ thống nội bộ", "Tự động hóa", "Hệ thống thiết kế", "Bền bỉ lâu dài"],
5966
- servicesTitle: "Chúng tôi xây dựng những gì",
5967
- servicesLead: "Ba mảng chuyên sâu. Một đội chịu trách nhiệm đến kết quả cuối cùng.",
5968
- services: [
5969
- { icon: "sparkle", t: "Ứng dụng web & di động", s: "Sản phẩm khách hàng dùng mỗi ngày — nhanh, dễ tiếp cận, song ngữ mặc định.", o: ["Ứng dụng web", "Di động", "Hệ thống thiết kế"] },
5970
- { icon: "chat", t: "Hệ thống nội bộ & tự động hóa", s: "Công cụ đội bạn vận hành: bảng điều khiển, quy trình và tự động hóa phía sau.", o: ["Hệ thống nội bộ", "Tự động hóa"] },
5971
- { icon: "check", t: "Bền bỉ lâu dài", s: "Phần mềm bàn giao vẫn chạy tốt — được kiểm thử, tài liệu đầy đủ, bàn giao gọn.", o: ["Kiểm thử", "Tài liệu", "Bàn giao"] }
5972
- ],
5973
- processTitle: "Hành trình của một điều ước",
5974
- processLead: "Mỗi dự án một hành trình: từ điều ước rõ đến phần mềm chạy giữa đời thật.",
5975
- steps: [
5976
- { n: "01", t: "Ước một điều", s: "Kể kết quả bạn muốn trong một hai câu. Chúng tôi phản hồi trong một ngày làm việc." },
5977
- { n: "02", t: "Định hình", s: "Đội nhỏ, kinh nghiệm bóc tách công việc và nói thẳng về đánh đổi." },
5978
- { n: "03", t: "Xây để bàn giao", s: "Chu kỳ ngắn, phần mềm chạy thật — không phải slide." },
5979
- { n: "04", t: "Dùng lâu được", s: "Kiểm thử, tài liệu, bàn giao. Vẫn chạy tốt sau khi chúng tôi rời đi." }
5980
- ],
5981
- careersTitle: "Cùng chúng tôi kiến tạo",
5982
- careersLead: "Tuyển người có tay nghề và chỉn chu — đội gọn, kinh nghiệm, nói thẳng đánh đổi.",
5983
- careersCta: "Xem vị trí đang mở",
5984
- contactTitle: "Kể chúng tôi nghe điều bạn ước",
5985
- contactLead: "Một lời nhắn ngắn là đủ. Chúng tôi phản hồi trong một ngày làm việc.",
5986
- form: { name: "Tên của bạn", email: "Email công việc", company: "Công ty", message: "Bạn muốn chia sẻ thêm điều gì?", submit: "Gửi", optional: "không bắt buộc", trust: "Thông tin của bạn được gửi thẳng đến đội ngũ. Chúng tôi không chia sẻ cho bên thứ ba." },
5987
- genie: { title: "Lumi · thần đèn vàng CyberSkill", greeting: "Chào bạn, mình là Lumi. Cứ kể mình một điều ước; mình tư vấn trước, chỉ xin thông tin khi bạn muốn gửi. Điều ước rõ sẽ được chuyển cho người thật trong đội.", placeholder: "Viết điều ước của bạn…", send: "Gửi", chips: ["Một ứng dụng web", "Công cụ nội bộ", "Hệ thống thiết kế"] },
5988
- footerRights: "Bảo lưu mọi quyền.",
5989
- footerLinks: ["Quyền riêng tư", "Khả năng tiếp cận", "Điều khoản"]
5990
- }
5991
- };
5992
- })(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/website/copy.js", error: String((e && e.message) || e) }); }
5993
-
5994
5353
  __ds_ns.AIDisclosureBadge = __ds_scope.AIDisclosureBadge;
5995
5354
 
5996
5355
  __ds_ns.ChatMessage = __ds_scope.ChatMessage;