@celestia-island/hikari 0.4.4 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (385) hide show
  1. package/README.md +1 -1
  2. package/package.json +22 -7
  3. package/src/DemoApp.tsx +491 -0
  4. package/src/animation/registerAnimations.test.ts +94 -0
  5. package/src/animation/registerAnimations.ts +103 -0
  6. package/src/components/HkAboutModal.scss +95 -0
  7. package/src/components/HkAboutModal.tsx +114 -0
  8. package/src/components/HkAdaptiveDialog.scss +22 -0
  9. package/src/components/HkAdaptiveDialog.test.tsx +271 -0
  10. package/src/components/HkAdaptiveDialog.tsx +167 -0
  11. package/src/components/HkAdminHeader.test.tsx +185 -0
  12. package/src/components/HkAdminHeader.tsx +308 -0
  13. package/src/components/HkAdminShell.test.tsx +237 -0
  14. package/src/components/HkAdminShell.tsx +129 -0
  15. package/src/components/HkAdminTablePage.tsx +70 -0
  16. package/src/components/HkAdminWidgets.test.ts +89 -0
  17. package/src/components/HkAlert.tsx +1 -1
  18. package/src/components/HkAltSignIn.scss +55 -0
  19. package/src/components/HkAltSignIn.test.tsx +88 -0
  20. package/src/components/HkAltSignIn.tsx +92 -0
  21. package/src/components/HkAttachmentModal.scss +49 -0
  22. package/src/components/HkAttachmentModal.tsx +318 -0
  23. package/src/components/HkAuthCard.tsx +50 -0
  24. package/src/components/HkAuthSubmitButton.scss +12 -0
  25. package/src/components/HkAuthSubmitButton.tsx +94 -0
  26. package/src/components/HkAvatar.scss +93 -0
  27. package/src/components/HkAvatar.tsx +2 -2
  28. package/src/components/HkBlockingToast.scss +123 -0
  29. package/src/components/HkBlockingToast.test.tsx +221 -0
  30. package/src/components/HkBlockingToast.tsx +182 -0
  31. package/src/components/HkBreadcrumb.scss +624 -0
  32. package/src/components/HkBreadcrumb.tsx +2 -2
  33. package/src/components/HkButton.scss +16 -2
  34. package/src/components/HkCaptchaModal.scss +14 -0
  35. package/src/components/HkCaptchaModal.tsx +62 -0
  36. package/src/components/HkCaptchaWidget.scss +30 -0
  37. package/src/components/HkCaptchaWidget.tsx +181 -0
  38. package/src/components/HkCard.scss +1 -1
  39. package/src/components/HkChatTypes.ts +65 -0
  40. package/src/components/HkCheckbox.scss +4 -9
  41. package/src/components/HkCheckbox.tsx +18 -6
  42. package/src/components/HkColorPicker.scss +120 -3
  43. package/src/components/HkColorPicker.tsx +130 -19
  44. package/src/components/HkColorPickerTouch.test.tsx +145 -0
  45. package/src/components/HkColorSchemeDialog.scss +52 -0
  46. package/src/components/HkColorSchemeDialog.test.ts +201 -0
  47. package/src/components/HkColorSchemeDialog.tsx +96 -0
  48. package/src/components/HkColorSchemeEditor.test.ts +270 -0
  49. package/src/components/HkColorSchemeEditor.tsx +410 -0
  50. package/src/components/HkConfirmDialog.scss +43 -3
  51. package/src/components/HkConfirmDialog.tsx +7 -4
  52. package/src/components/HkConnectionInfo.ts +80 -0
  53. package/src/components/HkConnectionStatus.tsx +158 -0
  54. package/src/components/HkCookieConsent.scss +22 -0
  55. package/src/components/HkCookieConsent.test.tsx +70 -0
  56. package/src/components/HkCookieConsent.tsx +56 -0
  57. package/src/components/HkCountdownDigit.tsx +105 -0
  58. package/src/components/HkDatePicker.scss +361 -0
  59. package/src/components/HkDatePicker.test.ts +459 -0
  60. package/src/components/HkDatePicker.tsx +568 -0
  61. package/src/components/HkDateTimePicker.scss +65 -37
  62. package/src/components/HkDateTimePicker.test.ts +344 -0
  63. package/src/components/HkDateTimePicker.tsx +249 -82
  64. package/src/components/HkDraggableGrid.scss +4 -1
  65. package/src/components/HkDraggableList.scss +4 -1
  66. package/src/components/HkDrawer.scss +27 -13
  67. package/src/components/HkDrawer.test.tsx +73 -0
  68. package/src/components/HkDrawer.tsx +157 -10
  69. package/src/components/HkErrorBoundary.scss +5 -2
  70. package/src/components/HkErrorBoundary.tsx +7 -4
  71. package/src/components/HkExpansionPanel.scss +143 -0
  72. package/src/components/HkExpansionPanel.test.tsx +109 -0
  73. package/src/components/HkExpansionPanel.tsx +101 -0
  74. package/src/components/HkFab.scss +162 -0
  75. package/src/components/HkFab.test.tsx +217 -0
  76. package/src/components/HkFab.tsx +214 -0
  77. package/src/components/HkHoverRevealAction.scss +6 -0
  78. package/src/components/HkHoverRevealAction.test.ts +68 -0
  79. package/src/components/HkHoverRevealAction.tsx +84 -6
  80. package/src/components/HkIcon.scss +123 -0
  81. package/src/components/HkIcon.tsx +7 -23
  82. package/src/components/HkIconButton.scss +170 -0
  83. package/src/components/HkIconButton.test.tsx +75 -0
  84. package/src/components/HkIconButton.tsx +4 -3
  85. package/src/components/HkIconButtonVars.scss +180 -0
  86. package/src/components/HkInput.affix.test.ts +253 -0
  87. package/src/components/HkInput.enter.test.ts +88 -0
  88. package/src/components/HkInput.scss +127 -8
  89. package/src/components/HkInput.tsx +245 -13
  90. package/src/components/HkKeywordSearchModal.scss +13 -0
  91. package/src/components/HkKeywordSearchModal.tsx +46 -9
  92. package/src/components/HkLabel.scss +34 -0
  93. package/src/components/HkLabel.test.tsx +61 -0
  94. package/src/components/HkLabel.tsx +45 -0
  95. package/src/components/HkListTransition.scss +26 -0
  96. package/src/components/HkListTransition.tsx +57 -0
  97. package/src/components/HkLocalePicker.scss +37 -0
  98. package/src/components/HkLocalePicker.tsx +133 -0
  99. package/src/components/HkLocalePickerPopup.tsx +35 -19
  100. package/src/components/HkLocalizedInput.scss +277 -0
  101. package/src/components/HkLocalizedInput.test.tsx +693 -0
  102. package/src/components/HkLocalizedInput.tsx +495 -0
  103. package/src/components/HkLogWindow.scss +121 -0
  104. package/src/components/HkLogWindow.tsx +195 -0
  105. package/src/components/HkLogo.scss +1 -1
  106. package/src/components/HkLogo.tsx +1 -1
  107. package/src/components/HkMarkdownRenderer.scss +22 -2
  108. package/src/components/HkMarkdownRenderer.tsx +33 -3
  109. package/src/components/HkMediaControlBar.tsx +20 -13
  110. package/src/components/HkMenu.scss +200 -0
  111. package/src/components/HkMenu.test.ts +779 -0
  112. package/src/components/HkMenu.tsx +770 -0
  113. package/src/components/HkMenuActionItem.scss +58 -0
  114. package/src/components/HkMenuActionItem.tsx +41 -0
  115. package/src/components/HkMenuIdentityItem.scss +70 -0
  116. package/src/components/HkMenuIdentityItem.tsx +42 -0
  117. package/src/components/HkMenuPanel.scss +20 -0
  118. package/src/components/HkMenuPanel.test.tsx +118 -0
  119. package/src/components/HkMenuPanel.tsx +32 -0
  120. package/src/components/HkMinimap.tsx +16 -8
  121. package/src/components/HkModal.autofollow.test.ts +44 -0
  122. package/src/components/HkModal.scss +170 -53
  123. package/src/components/HkModal.sheetgap.test.ts +82 -0
  124. package/src/components/HkModal.test.tsx +334 -0
  125. package/src/components/HkModal.tsx +314 -68
  126. package/src/components/HkModalBreadcrumb.scss +4 -1
  127. package/src/components/HkModalBreadcrumb.test.tsx +111 -0
  128. package/src/components/HkModalBreadcrumb.tsx +34 -11
  129. package/src/components/HkModelCatalog.ts +98 -0
  130. package/src/components/HkModelTag.scss +130 -0
  131. package/src/components/HkModelTag.test.tsx +111 -0
  132. package/src/components/HkModelTag.tsx +190 -0
  133. package/src/components/HkNavItem.scss +13 -2
  134. package/src/components/HkNavItem.tsx +2 -1
  135. package/src/components/HkNavSidebar.tsx +99 -0
  136. package/src/components/HkNumberInput.scss +12 -0
  137. package/src/components/HkNumberInput.tsx +15 -8
  138. package/src/components/HkPageHeader.scss +79 -0
  139. package/src/components/HkPageHeader.test.ts +67 -0
  140. package/src/components/HkPageHeader.tsx +51 -0
  141. package/src/components/HkPasswordInput.scss +29 -17
  142. package/src/components/HkPasswordInput.test.ts +281 -0
  143. package/src/components/HkPasswordInput.tsx +337 -59
  144. package/src/components/HkPickerPane.scss +158 -0
  145. package/src/components/HkPlaceholderMarquee.scss +109 -0
  146. package/src/components/HkPlaceholderMarquee.test.ts +305 -0
  147. package/src/components/HkPlaceholderMarquee.tsx +170 -0
  148. package/src/components/HkPopover.scss +134 -10
  149. package/src/components/HkPopover.sheetdock.test.ts +34 -0
  150. package/src/components/HkPopover.surfacevars.test.ts +20 -0
  151. package/src/components/HkPopover.titleSuppress.test.tsx +265 -0
  152. package/src/components/HkPopover.tsx +225 -50
  153. package/src/components/HkPopoverSheet.test.tsx +130 -0
  154. package/src/components/HkPopupSelect.scss +30 -10
  155. package/src/components/HkPopupSelect.tsx +21 -1
  156. package/src/components/HkProgressDialog.scss +31 -0
  157. package/src/components/HkProgressDialog.tsx +37 -19
  158. package/src/components/HkProtocolModal.scss +26 -0
  159. package/src/components/HkProtocolModal.tsx +112 -0
  160. package/src/components/HkRadio.scss +1 -16
  161. package/src/components/HkRadio.tsx +5 -2
  162. package/src/components/HkRichInput.scss +162 -0
  163. package/src/components/HkRichInput.tsx +335 -0
  164. package/src/components/HkRollingNumber.test.ts +90 -0
  165. package/src/components/HkRollingNumber.tsx +39 -1
  166. package/src/components/HkScrollContainer.scss +71 -54
  167. package/src/components/HkScrollContainer.test.ts +160 -0
  168. package/src/components/HkScrollContainer.tsx +187 -160
  169. package/src/components/HkSearchInput.tsx +3 -3
  170. package/src/components/HkSecretRevealModal.scss +61 -0
  171. package/src/components/HkSecretRevealModal.tsx +91 -0
  172. package/src/components/HkSelect.scss +263 -12
  173. package/src/components/HkSelect.sheetdock.test.ts +30 -0
  174. package/src/components/HkSelect.test.tsx +88 -0
  175. package/src/components/HkSelect.tsx +59 -74
  176. package/src/components/HkSelectPanel.test.tsx +666 -0
  177. package/src/components/HkSelectPanel.tsx +595 -0
  178. package/src/components/HkSelectSheet.test.tsx +132 -0
  179. package/src/components/HkSelectionGrid.scss +1 -1
  180. package/src/components/HkSelectionGrid.tsx +2 -1
  181. package/src/components/HkSelectionWaterfall.scss +1 -1
  182. package/src/components/HkShareBar.scss +40 -0
  183. package/src/components/HkShareBar.tsx +50 -0
  184. package/src/components/HkSidebar.scss +15 -10
  185. package/src/components/HkSidebar.tsx +26 -2
  186. package/src/components/HkSignInCard.test.ts +154 -0
  187. package/src/components/HkSignInCard.tsx +155 -0
  188. package/src/components/HkSlider.scss +99 -0
  189. package/src/components/HkSlider.test.tsx +155 -0
  190. package/src/components/HkSlider.tsx +173 -0
  191. package/src/components/HkSpinner.scss +36 -1
  192. package/src/components/HkSpinner.test.tsx +63 -0
  193. package/src/components/HkSpinner.tsx +16 -1
  194. package/src/components/HkSplash.scss +1 -1
  195. package/src/components/HkSplash.tsx +3 -1
  196. package/src/components/HkStatCard.scss +73 -0
  197. package/src/components/HkStatCard.tsx +46 -0
  198. package/src/components/HkStatusBar.test.tsx +178 -0
  199. package/src/components/HkStatusBar.tsx +379 -0
  200. package/src/components/HkStatusPill.scss +73 -0
  201. package/src/components/HkStatusPill.tsx +41 -0
  202. package/src/components/HkStepFlow.scss +75 -0
  203. package/src/components/HkStepFlow.test.tsx +246 -0
  204. package/src/components/HkStepFlow.tsx +115 -0
  205. package/src/components/HkSwitch.scss +5 -17
  206. package/src/components/HkSwitch.tsx +6 -3
  207. package/src/components/HkTable.scss +20 -1
  208. package/src/components/HkTable.tsx +28 -3
  209. package/src/components/HkTabs.scss +222 -48
  210. package/src/components/HkTabs.test.ts +517 -0
  211. package/src/components/HkTabs.tsx +567 -56
  212. package/src/components/HkTag.tsx +2 -1
  213. package/src/components/HkTextarea.tsx +5 -0
  214. package/src/components/HkThemeToggle.scss +232 -0
  215. package/src/components/HkThemeToggle.test.ts +231 -0
  216. package/src/components/HkThemeToggle.tsx +289 -0
  217. package/src/components/HkTimeline.scss +134 -1
  218. package/src/components/HkTimeline.test.tsx +319 -0
  219. package/src/components/HkTimeline.tsx +303 -54
  220. package/src/components/HkToast.scss +34 -5
  221. package/src/components/HkToast.test.tsx +109 -0
  222. package/src/components/HkToast.tsx +57 -8
  223. package/src/components/HkTokenUsageBadge.scss +53 -0
  224. package/src/components/HkTokenUsageBadge.test.tsx +59 -0
  225. package/src/components/HkTokenUsageBadge.tsx +54 -0
  226. package/src/components/HkTokenUsagePanel.scss +95 -0
  227. package/src/components/HkTokenUsagePanel.tsx +140 -0
  228. package/src/components/HkToolBlock.scss +428 -0
  229. package/src/components/HkToolBlock.tsx +639 -0
  230. package/src/components/HkTooltip.scss +14 -2
  231. package/src/components/HkTooltip.tsx +25 -2
  232. package/src/components/HkTrendChart.tsx +27 -16
  233. package/src/components/HkVoiceInputPopup.scss +77 -0
  234. package/src/components/HkVoiceInputPopup.tsx +97 -0
  235. package/src/components/HkZoomToolbar.scss +4 -1
  236. package/src/components/HkZoomToolbar.tsx +7 -4
  237. package/src/components/_hikari-vars.scss +6 -0
  238. package/src/composables/highlightLanguages.ts +109 -0
  239. package/src/composables/iconRegistry.ts +127 -0
  240. package/src/composables/messaging/browserTransport.ts +96 -0
  241. package/src/composables/messaging/index.ts +204 -0
  242. package/src/composables/messaging/messaging.test.ts +256 -0
  243. package/src/composables/messaging/toastTransport.ts +26 -0
  244. package/src/composables/messaging/types.ts +115 -0
  245. package/src/composables/useActionBar.ts +66 -0
  246. package/src/composables/useAttachments.ts +154 -0
  247. package/src/composables/useConnectionProbe.ts +142 -0
  248. package/src/composables/useDeferredTransition.ts +16 -0
  249. package/src/composables/useEngineHealth.ts +69 -0
  250. package/src/composables/useHighlight.ts +124 -0
  251. package/src/composables/useMeasuredHighlight.ts +150 -0
  252. package/src/composables/useOverlayScrollbar.test.ts +221 -0
  253. package/src/composables/useOverlayScrollbar.ts +296 -0
  254. package/src/composables/usePageTitle.ts +37 -0
  255. package/src/composables/useReducedMotion.ts +34 -0
  256. package/src/composables/useResourceListModal.tsx +147 -0
  257. package/src/composables/useSafeArea.ts +137 -0
  258. package/src/composables/useSendShortcut.ts +35 -0
  259. package/src/composables/useSizeMorph.test.ts +156 -0
  260. package/src/composables/useSizeMorph.ts +149 -0
  261. package/src/composables/useSurfaceTransition.test.ts +96 -0
  262. package/src/composables/useSurfaceTransition.ts +71 -0
  263. package/src/composables/useZoomPan.ts +192 -0
  264. package/src/demo.scss +9 -1
  265. package/src/demo.ts +5 -1
  266. package/src/i18n/locales/ar/chat.json +38 -0
  267. package/src/i18n/locales/ar/components.json +141 -40
  268. package/src/i18n/locales/ar/components.toml +59 -0
  269. package/src/i18n/locales/ar/platform.json +65 -0
  270. package/src/i18n/locales/de/chat.json +38 -0
  271. package/src/i18n/locales/de/components.json +139 -38
  272. package/src/i18n/locales/de/components.toml +59 -0
  273. package/src/i18n/locales/de/platform.json +65 -0
  274. package/src/i18n/locales/en/chat.json +38 -0
  275. package/src/i18n/locales/en/components.json +104 -3
  276. package/src/i18n/locales/en/components.toml +59 -0
  277. package/src/i18n/locales/en/platform.json +65 -0
  278. package/src/i18n/locales/es/chat.json +38 -0
  279. package/src/i18n/locales/es/components.json +141 -40
  280. package/src/i18n/locales/es/components.toml +59 -0
  281. package/src/i18n/locales/es/platform.json +65 -0
  282. package/src/i18n/locales/fr/chat.json +38 -0
  283. package/src/i18n/locales/fr/components.json +137 -36
  284. package/src/i18n/locales/fr/components.toml +59 -0
  285. package/src/i18n/locales/fr/platform.json +65 -0
  286. package/src/i18n/locales/ja/chat.json +38 -0
  287. package/src/i18n/locales/ja/components.json +104 -3
  288. package/src/i18n/locales/ja/components.toml +59 -0
  289. package/src/i18n/locales/ja/platform.json +65 -0
  290. package/src/i18n/locales/ko/chat.json +38 -0
  291. package/src/i18n/locales/ko/components.json +104 -3
  292. package/src/i18n/locales/ko/components.toml +59 -0
  293. package/src/i18n/locales/ko/platform.json +65 -0
  294. package/src/i18n/locales/pt/chat.json +38 -0
  295. package/src/i18n/locales/pt/components.json +141 -40
  296. package/src/i18n/locales/pt/components.toml +59 -0
  297. package/src/i18n/locales/pt/platform.json +65 -0
  298. package/src/i18n/locales/ru/chat.json +38 -0
  299. package/src/i18n/locales/ru/components.json +141 -40
  300. package/src/i18n/locales/ru/components.toml +59 -0
  301. package/src/i18n/locales/ru/platform.json +65 -0
  302. package/src/i18n/locales/zh-Hans/chat.json +38 -0
  303. package/src/i18n/locales/zh-Hans/components.json +109 -4
  304. package/src/i18n/locales/zh-Hans/components.toml +59 -0
  305. package/src/i18n/locales/zh-Hans/platform.json +65 -0
  306. package/src/i18n/locales/zh-Hant/chat.json +38 -0
  307. package/src/i18n/locales/zh-Hant/components.json +109 -4
  308. package/src/i18n/locales/zh-Hant/components.toml +59 -0
  309. package/src/i18n/locales/zh-Hant/platform.json +65 -0
  310. package/src/index.ts +225 -6
  311. package/src/plugins/bootScriptsPlugin.ts +47 -0
  312. package/src/plugins/buildHashPlugin.ts +67 -0
  313. package/src/plugins/index.ts +2 -0
  314. package/src/render.ts +1 -1
  315. package/src/runtime/animationBus.test.ts +112 -0
  316. package/src/runtime/animationBus.ts +33 -2
  317. package/src/runtime/backStack.test.ts +334 -0
  318. package/src/runtime/backStack.ts +377 -0
  319. package/src/runtime/env.ts +4 -0
  320. package/src/runtime/index.ts +15 -3
  321. package/src/runtime/intervalBus.test.ts +118 -0
  322. package/src/runtime/intervalBus.ts +189 -0
  323. package/src/runtime/mobileViewport.test.ts +132 -0
  324. package/src/runtime/mobileViewport.ts +157 -0
  325. package/src/runtime/pageLifecycle.ts +91 -0
  326. package/src/runtime/useBlockingToast.ts +112 -0
  327. package/src/runtime/useClipboard.ts +28 -0
  328. package/src/runtime/useConfirm.ts +15 -7
  329. package/src/runtime/useMediaQuery.test.ts +67 -0
  330. package/src/runtime/useMediaQuery.ts +89 -0
  331. package/src/runtime/useOverlay.test.ts +160 -0
  332. package/src/runtime/useOverlay.ts +59 -12
  333. package/src/runtime/usePopupManager.test.ts +200 -0
  334. package/src/runtime/usePopupManager.ts +106 -10
  335. package/src/runtime/useToast.test.ts +90 -0
  336. package/src/runtime/useToast.ts +20 -2
  337. package/src/styles/admin-tokens.scss +744 -0
  338. package/src/styles/index.scss +5 -3
  339. package/src/styles/theme/_glass.scss +33 -0
  340. package/src/styles/theme/_layout.scss +123 -0
  341. package/src/styles/theme/_scrollbar.scss +73 -0
  342. package/src/styles/theme/_tokens.scss +69 -0
  343. package/src/styles/theme/base.scss +617 -0
  344. package/src/styles/theme/foundation.scss +219 -0
  345. package/src/styles/theme/mixins.scss +793 -0
  346. package/src/styles/theme/themes.scss +25 -0
  347. package/src/styles/theme/variables.scss +125 -0
  348. package/src/theme/_field.scss +116 -0
  349. package/src/theme/fontContext.test.ts +179 -0
  350. package/src/theme/fontContext.ts +169 -0
  351. package/src/theme/index.ts +19 -2
  352. package/src/theme/presets.ts +86 -18
  353. package/src/theme/theme.scss +61 -1
  354. package/src/theme/tokenGroups.test.ts +534 -0
  355. package/src/theme/tokenGroups.ts +552 -0
  356. package/src/theme/useSolarTime.test.ts +113 -0
  357. package/src/theme/useSolarTime.ts +95 -20
  358. package/src/theme/useTheme.test.ts +72 -0
  359. package/src/theme/useTheme.ts +144 -9
  360. package/src/tokens.scss +110 -0
  361. package/src/utils/base64.ts +31 -0
  362. package/src/utils/connectivity.ts +51 -0
  363. package/src/utils/dom.test.ts +131 -0
  364. package/src/utils/dom.ts +133 -0
  365. package/src/utils/download.ts +22 -0
  366. package/src/utils/fileType.ts +127 -0
  367. package/src/utils/format.ts +98 -0
  368. package/src/utils/fuzzy.ts +77 -0
  369. package/src/utils/fuzzySearch.test.ts +87 -0
  370. package/src/utils/healthProbe.ts +64 -0
  371. package/src/utils/objectPath.ts +59 -0
  372. package/src/utils/password.ts +53 -0
  373. package/src/utils/pow.test.ts +45 -0
  374. package/src/utils/pow.ts +226 -0
  375. package/src/utils/powNonce.ts +95 -0
  376. package/src/utils/validation.test.ts +78 -0
  377. package/src/DemoApp.vue +0 -400
  378. package/src/components/HkCollapse.tsx +0 -48
  379. package/src/components/HkMorphingTabs.scss +0 -141
  380. package/src/components/HkMorphingTabs.tsx +0 -113
  381. package/src/composables/useBreakpoint.ts +0 -41
  382. package/src/composables/useClipboard.ts +0 -37
  383. package/src/composables/useConfirm.ts +0 -35
  384. package/src/composables/useToast.ts +0 -2
  385. package/src/runtime/useAsyncData.ts +0 -30
@@ -0,0 +1,25 @@
1
+ // Vendored byte-identical from packages/theme/styles/themes.scss (hikari repo) so npm registry installs can resolve it — edit the upstream file, then re-copy; do not hand-edit here.
2
+ // hikari-theme/styles/themes.scss
3
+ // 主题变体(使用 hikari-palette)
4
+
5
+ @use './variables.scss' as *;
6
+
7
+ // ------
8
+ // 响应式主题切换
9
+ // ------
10
+
11
+ @media (prefers-color-scheme: dark) {
12
+ :root:not([data-theme="fresh"]) {
13
+ --hi-color-background: #0F172A;
14
+ --hi-color-surface: #1E293B;
15
+ --hi-color-text-primary: rgba(255, 255, 255, 0.9);
16
+ --hi-color-text-secondary: rgba(255, 255, 255, 0.6);
17
+ --hi-color-text-on-primary: rgba(0, 0, 0, 0.9);
18
+ --hi-color-text-on-secondary: var(--hi-color-white-100);
19
+ --hi-color-text-on-ghost: rgba(255, 255, 255, 0.9);
20
+ --hi-color-text-on-danger: var(--hi-color-white-100);
21
+ --hi-color-text-on-success: rgba(0, 0, 0, 0.9);
22
+ --hi-color-text-on-warning: rgba(0, 0, 0, 0.9);
23
+ --hi-color-text-on-info: rgba(0, 0, 0, 0.9);
24
+ }
25
+ }
@@ -0,0 +1,125 @@
1
+ // Vendored byte-identical from packages/theme/styles/variables.scss (hikari repo) so npm registry installs can resolve it — edit the upstream file, then re-copy; do not hand-edit here.
2
+ // hikari-theme/styles/variables.scss
3
+ // CSS 变量(使用 hikari-palette)
4
+
5
+ // ------
6
+ // 1. Color System
7
+ // ------
8
+ // Colors are defined via CSS variables in the index.scss
9
+ // and by the hikari-palette crate
10
+
11
+ // ------
12
+ // 2. Typography Variables
13
+ // ------
14
+
15
+ /* Apple-style stack + CJK UI tail — keep in sync with packages/vue/src/theme/fontContext.ts */
16
+ $hikari-font-family-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", sans-serif;
17
+ $hikari-font-family-mono: ui-monospace, "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", "Liberation Mono", "PingFang SC", "Microsoft YaHei", "Noto Sans CJK SC", monospace;
18
+
19
+ $hikari-font-size-xs: 0.75rem;
20
+ $hikari-font-size-sm: 0.875rem;
21
+ $hikari-font-size-base: 1rem;
22
+ $hikari-font-size-lg: 1.125rem;
23
+ $hikari-font-size-xl: 1.25rem;
24
+ $hikari-font-size-2xl: 1.5rem;
25
+ $hikari-font-size-3xl: 1.875rem;
26
+
27
+ // ------
28
+ // 间距变量
29
+ // ------
30
+
31
+ $hikari-spacing-xs: 0.25rem;
32
+ $hikari-spacing-sm: 0.5rem;
33
+ $hikari-spacing-md: 1rem;
34
+ $hikari-spacing-lg: 1.5rem;
35
+ $hikari-spacing-xl: 2rem;
36
+ $hikari-spacing-2xl: 3rem;
37
+
38
+ // ------
39
+ // 圆角变量
40
+ // ------
41
+
42
+ $hikari-radius-sm: 0.25rem;
43
+ $hikari-radius-md: 0.5rem;
44
+ $hikari-radius-lg: 0.75rem;
45
+ $hikari-radius-xl: 1rem;
46
+ $hikari-radius-full: 9999px;
47
+
48
+ // ------
49
+ // 阴影变量
50
+ // ------
51
+
52
+ $hikari-shadow-sm: var(--hi-shadow-sm);
53
+ $hikari-shadow-md: var(--hi-shadow-md);
54
+ $hikari-shadow-lg: var(--hi-shadow-lg);
55
+ $hikari-shadow-xl: var(--hi-shadow-xl);
56
+
57
+ // ------
58
+ // 过渡变量
59
+ // ------
60
+
61
+ $hikari-transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
62
+ $hikari-transition-base: 200ms cubic-bezier(0.4, 0, 0.2, 1);
63
+ $hikari-transition-slow: 300ms cubic-bezier(0.4, 0, 0.2, 1);
64
+
65
+ // ------
66
+ // FUI 设计系统变量
67
+ // ------
68
+
69
+ // 亚克力效果参数
70
+ $hikari-acrylic-blur: 20px;
71
+ $hikari-acrylic-noise: 0.5;
72
+ $hikari-acrylic-opacity: 0.85;
73
+
74
+ // 发光效果参数
75
+ $hikari-glow-sm: var(--hi-glow-sm);
76
+ $hikari-glow-md: var(--hi-glow-md);
77
+ $hikari-glow-lg: var(--hi-glow-lg);
78
+ $hikari-glow-focus: var(--hi-glow-focus);
79
+
80
+ // 强调色条参数
81
+ $hikari-accent-bar-width: 3px;
82
+ $hikari-accent-bar-position: left; // left, right, top, bottom
83
+
84
+ // 细微圆角(FUI 风格)
85
+ $hikari-radius-fui-sm: 4px;
86
+ $hikari-radius-fui-md: 6px;
87
+ $hikari-radius-fui-lg: 8px;
88
+
89
+ // 边框发光
90
+ $hikari-border-glow-color: var(--hi-border-glow);
91
+ $hikari-border-glow-width: var(--hi-border-glow-width);
92
+
93
+ // 动画缓动函数(GSAP 风格)
94
+ $hikari-ease-smooth: cubic-bezier(0.25, 0.1, 0.25, 1);
95
+ $hikari-ease-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55);
96
+ $hikari-ease-elastic: cubic-bezier(0.5, 1.5, 0.5, 1);
97
+
98
+ // 动画时长
99
+ $hikari-duration-instant: 100ms;
100
+ $hikari-duration-fast: 200ms;
101
+ $hikari-duration-normal: 300ms;
102
+ $hikari-duration-slow: 500ms;
103
+
104
+ // 背景微动参数
105
+ $hikari-ambient-duration: 8s;
106
+ $hikari-ambient-easing: ease-in-out;
107
+
108
+ // ------
109
+ // Icon Button 尺寸变量
110
+ // ------
111
+
112
+ $hikari-icon-button-size-16: 16px;
113
+ $hikari-icon-button-size-24: 24px;
114
+ $hikari-icon-button-size-32: 32px;
115
+ $hikari-icon-button-size-36: 36px;
116
+
117
+ // ------
118
+ // 常用图标尺寸变量
119
+ // ------
120
+
121
+ $hikari-icon-size-xs: 12px;
122
+ $hikari-icon-size-sm: 14px;
123
+ $hikari-icon-size-md: 16px;
124
+ $hikari-icon-size-lg: 20px;
125
+ $hikari-icon-size-xl: 24px;
@@ -0,0 +1,116 @@
1
+ @mixin field-wrapper {
2
+ display: block;
3
+ width: 100%;
4
+ }
5
+
6
+ @mixin field-label {
7
+ display: block;
8
+ font-size: var(--text-base);
9
+ font-weight: 500;
10
+ margin-bottom: var(--space-6);
11
+ color: rgb(var(--color-text));
12
+ }
13
+
14
+ @mixin field-required {
15
+ margin-left: var(--space-2);
16
+ color: rgb(var(--color-error));
17
+ }
18
+
19
+ @mixin field-box {
20
+ position: relative;
21
+ display: flex;
22
+ align-items: center;
23
+ gap: var(--space-6);
24
+ padding: var(--space-8) var(--space-12);
25
+ min-height: 2.5rem;
26
+ background: color-mix(in srgb, rgb(var(--color-surface)) 55%, transparent);
27
+ border: 1px solid color-mix(in srgb, rgb(var(--color-text)) 14%, transparent);
28
+ border-radius: var(--radius-sm);
29
+ overflow: hidden;
30
+ transition: background-color, border-color, color, box-shadow, opacity, transform, filter var(--duration-normal) var(--ease-standard);
31
+ color: rgb(var(--color-text));
32
+ }
33
+
34
+ @mixin field-box-hover($disabled-selector) {
35
+ &:hover:not(#{$disabled-selector}) {
36
+ border-color: var(--c-primary-strong);
37
+ }
38
+ }
39
+
40
+ @mixin field-box-focused {
41
+ border-color: rgb(var(--color-focused-border));
42
+ box-shadow: var(--shadow-focus);
43
+ background: rgb(var(--color-surface));
44
+ }
45
+
46
+ @mixin field-box-error {
47
+ border-color: var(--c-error-strong);
48
+ }
49
+
50
+ @mixin field-box-error-focused {
51
+ border-color: rgb(var(--color-error));
52
+ box-shadow: var(--shadow-focus-error);
53
+ }
54
+
55
+ @mixin field-box-disabled {
56
+ opacity: 0.5;
57
+ cursor: not-allowed;
58
+ }
59
+
60
+ @mixin field-placeholder {
61
+ position: absolute;
62
+ inset: 0;
63
+ display: flex;
64
+ align-items: center;
65
+ justify-content: center;
66
+ pointer-events: none;
67
+ user-select: none;
68
+ font-size: var(--text-base);
69
+ padding: var(--space-8) var(--space-12);
70
+ color: color-mix(in srgb, rgb(var(--color-muted)) 70%, rgb(var(--color-background)));
71
+ }
72
+
73
+ @mixin field-error-msg {
74
+ margin-top: var(--space-4);
75
+ font-size: var(--text-xs);
76
+ color: rgb(var(--color-error));
77
+ }
78
+
79
+ @mixin field-hint {
80
+ margin-top: var(--space-4);
81
+ font-size: var(--text-xs);
82
+ color: color-mix(in srgb, rgb(var(--color-muted)) 70%, rgb(var(--color-background)));
83
+ }
84
+
85
+ @mixin field-autofill-overrides($box-class) {
86
+ .#{$box-class} input,
87
+ .#{$box-class} textarea {
88
+ transition: color calc(infinity * 1s) step-end, background-color calc(infinity * 1s) step-end;
89
+ }
90
+
91
+ .#{$box-class} input:-webkit-autofill,
92
+ .#{$box-class} input:-webkit-autofill:hover,
93
+ .#{$box-class} input:-webkit-autofill:focus,
94
+ .#{$box-class} input:-webkit-autofill:active,
95
+ .#{$box-class} textarea:-webkit-autofill,
96
+ .#{$box-class} textarea:-webkit-autofill:hover,
97
+ .#{$box-class} textarea:-webkit-autofill:focus {
98
+ -webkit-text-fill-color: rgb(var(--color-text)) !important;
99
+ -webkit-box-shadow: 0 0 0 1000px rgb(var(--color-surface)) inset !important;
100
+ caret-color: rgb(var(--color-text));
101
+ }
102
+
103
+ .#{$box-class} input:-webkit-autofill::first-line,
104
+ .#{$box-class} textarea:-webkit-autofill::first-line {
105
+ font-size: inherit !important;
106
+ font-family: inherit !important;
107
+ color: rgb(var(--color-text));
108
+ }
109
+
110
+ .#{$box-class} input:-internal-autofill-previewed,
111
+ .#{$box-class} input:-internal-autofill-selected {
112
+ -webkit-text-fill-color: rgb(var(--color-text)) !important;
113
+ -webkit-box-shadow: 0 0 0 1000px rgb(var(--color-surface)) inset !important;
114
+ caret-color: rgb(var(--color-text));
115
+ }
116
+ }
@@ -0,0 +1,179 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+
3
+ // The module keeps singleton state (initialized flag + refs), so every
4
+ // test gets a fresh module instance via vi.resetModules + dynamic import.
5
+ type FontContextModule = typeof import("./fontContext");
6
+
7
+ describe("fontContext", () => {
8
+ let fc: FontContextModule;
9
+
10
+ const el = () => document.documentElement;
11
+ const applied = (name: string) => el().style.getPropertyValue(name);
12
+
13
+ beforeEach(async () => {
14
+ vi.resetModules();
15
+ localStorage.clear();
16
+ el().style.cssText = "";
17
+ el().removeAttribute("data-font-context");
18
+ fc = await import("./fontContext");
19
+ });
20
+
21
+ it("init applies the default stacks as inline vars and sets data-font-context", () => {
22
+ fc.initFontContext();
23
+ expect(applied("--font-sans")).toBe(fc.HIKARI_FONT_SANS);
24
+ expect(applied("--font-mono")).toBe(fc.HIKARI_FONT_MONO);
25
+ expect(applied("--font-reading")).toBe(fc.HIKARI_FONT_READING);
26
+ expect(el().getAttribute("data-font-context")).toBe("on");
27
+ });
28
+
29
+ it("is idempotent: a repeated bare init does not fall back to defaults", () => {
30
+ localStorage.setItem(
31
+ "hikari-font-context",
32
+ JSON.stringify({ sans: '"Stored Sans", sans-serif' }),
33
+ );
34
+ fc.initFontContext();
35
+ fc.initFontContext();
36
+ fc.initFontContext();
37
+ expect(applied("--font-sans")).toBe('"Stored Sans", sans-serif');
38
+ });
39
+
40
+ it("applies localStorage overrides", () => {
41
+ localStorage.setItem(
42
+ "hikari-font-context",
43
+ JSON.stringify({
44
+ sans: '"A Sans", sans-serif',
45
+ mono: '"A Mono", monospace',
46
+ reading: '"A Reading", serif',
47
+ }),
48
+ );
49
+ fc.initFontContext();
50
+ expect(applied("--font-sans")).toBe('"A Sans", sans-serif');
51
+ expect(applied("--font-mono")).toBe('"A Mono", monospace');
52
+ expect(applied("--font-reading")).toBe('"A Reading", serif');
53
+ });
54
+
55
+ it("explicit options beat stored overrides on first init", () => {
56
+ localStorage.setItem(
57
+ "hikari-font-context",
58
+ JSON.stringify({ sans: '"Stored Sans", sans-serif' }),
59
+ );
60
+ fc.initFontContext({ sans: '"Explicit Sans", sans-serif' });
61
+ expect(applied("--font-sans")).toBe('"Explicit Sans", sans-serif');
62
+ });
63
+
64
+ it("merges explicit options from a repeated init", () => {
65
+ fc.initFontContext();
66
+ fc.initFontContext({ mono: '"Later Mono", monospace' });
67
+ expect(applied("--font-mono")).toBe('"Later Mono", monospace');
68
+ expect(applied("--font-sans")).toBe(fc.HIKARI_FONT_SANS);
69
+ });
70
+
71
+ it("rejects injection-shaped values and falls back to the next source", () => {
72
+ localStorage.setItem(
73
+ "hikari-font-context",
74
+ JSON.stringify({
75
+ sans: 'evil; } { --',
76
+ mono: "",
77
+ reading: "x".repeat(501),
78
+ }),
79
+ );
80
+ fc.initFontContext({ sans: '"Also; Evil", sans-serif' });
81
+ // sans: explicit rejected → stored rejected → default.
82
+ expect(applied("--font-sans")).toBe(fc.HIKARI_FONT_SANS);
83
+ // mono/reading: stored rejected → default.
84
+ expect(applied("--font-mono")).toBe(fc.HIKARI_FONT_MONO);
85
+ expect(applied("--font-reading")).toBe(fc.HIKARI_FONT_READING);
86
+ });
87
+
88
+ it("explicit options rejected by sanitization fall through to the stored override", () => {
89
+ localStorage.setItem(
90
+ "hikari-font-context",
91
+ JSON.stringify({ sans: '"Good Sans", sans-serif' }),
92
+ );
93
+ fc.initFontContext({ sans: '"Bad; Sans"' });
94
+ expect(applied("--font-sans")).toBe('"Good Sans", sans-serif');
95
+ });
96
+
97
+ it("ignores malformed stored JSON", () => {
98
+ localStorage.setItem("hikari-font-context", "{not json");
99
+ fc.initFontContext();
100
+ expect(applied("--font-sans")).toBe(fc.HIKARI_FONT_SANS);
101
+ });
102
+
103
+ it("exposes refs whose setters persist and re-apply", () => {
104
+ fc.initFontContext();
105
+ const ctx = fc.useFontContext();
106
+ ctx.setSans('"My Sans", sans-serif');
107
+ expect(ctx.sans.value).toBe('"My Sans", sans-serif');
108
+ expect(applied("--font-sans")).toBe('"My Sans", sans-serif');
109
+ const stored = JSON.parse(localStorage.getItem("hikari-font-context") ?? "{}");
110
+ expect(stored.sans).toBe('"My Sans", sans-serif');
111
+ });
112
+
113
+ it("setter rejects unsanitizable values without persisting", () => {
114
+ fc.initFontContext();
115
+ const ctx = fc.useFontContext();
116
+ ctx.setSans('"Bad; Sans"');
117
+ expect(ctx.sans.value).toBe(fc.HIKARI_FONT_SANS);
118
+ expect(applied("--font-sans")).toBe(fc.HIKARI_FONT_SANS);
119
+ expect(localStorage.getItem("hikari-font-context")).toBeNull();
120
+ });
121
+
122
+ it("reset clears storage and restores the built-in defaults", () => {
123
+ fc.initFontContext();
124
+ const ctx = fc.useFontContext();
125
+ ctx.setMono('"My Mono", monospace');
126
+ expect(applied("--font-mono")).toBe('"My Mono", monospace');
127
+ ctx.reset();
128
+ expect(localStorage.getItem("hikari-font-context")).toBeNull();
129
+ expect(applied("--font-mono")).toBe(fc.HIKARI_FONT_MONO);
130
+ expect(ctx.mono.value).toBe(fc.HIKARI_FONT_MONO);
131
+ expect(el().getAttribute("data-font-context")).toBe("on");
132
+ });
133
+
134
+ it("applyFontContext re-applies the current state over DOM mutations", () => {
135
+ fc.initFontContext();
136
+ el().style.setProperty("--font-sans", "junk");
137
+ fc.applyFontContext();
138
+ expect(applied("--font-sans")).toBe(fc.HIKARI_FONT_SANS);
139
+ });
140
+
141
+ it("is a safe no-op in non-browser environments", () => {
142
+ vi.stubGlobal("document", undefined);
143
+ try {
144
+ expect(() => fc.initFontContext({ sans: '"X", sans-serif' })).not.toThrow();
145
+ expect(() => fc.applyFontContext()).not.toThrow();
146
+ expect(() => fc.resetFontContext()).not.toThrow();
147
+ } finally {
148
+ vi.unstubAllGlobals();
149
+ }
150
+ });
151
+
152
+ it("default stacks lead with the Apple-style system heads", () => {
153
+ expect(fc.HIKARI_FONT_SANS.startsWith("-apple-system, BlinkMacSystemFont")).toBe(true);
154
+ expect(fc.HIKARI_FONT_MONO.startsWith(`ui-monospace, "SF Mono"`)).toBe(true);
155
+ });
156
+
157
+ it("default stacks carry the required CJK UI faces", () => {
158
+ const required = ["PingFang SC", "Microsoft YaHei", "Noto Sans CJK SC"];
159
+ for (const stack of [fc.HIKARI_FONT_SANS, fc.HIKARI_FONT_MONO]) {
160
+ for (const face of required) {
161
+ expect(stack.toLowerCase()).toContain(face.toLowerCase());
162
+ }
163
+ }
164
+ });
165
+
166
+ it("default stacks never name a kai or serif face", () => {
167
+ const banned = ["KaiTi", "STKaiti", "Kaiti SC", "SimSun", "Songti", "Noto Serif", "Source Han Serif"];
168
+ for (const stack of [fc.HIKARI_FONT_SANS, fc.HIKARI_FONT_MONO, fc.HIKARI_FONT_READING]) {
169
+ for (const face of banned) {
170
+ // Assert absence of the whole quoted family name, case-insensitively.
171
+ expect(stack.toLowerCase()).not.toContain(`"${face.toLowerCase()}"`);
172
+ }
173
+ }
174
+ });
175
+
176
+ it("reading defaults to the UI sans stack", () => {
177
+ expect(fc.HIKARI_FONT_READING).toBe(fc.HIKARI_FONT_SANS);
178
+ });
179
+ });
@@ -0,0 +1,169 @@
1
+ import { ref, type Ref } from "vue";
2
+
3
+ // fontContext.ts — bootstrap-level font facility, distributed like the
4
+ // startup hooks (initTheme / applyViewportPolicy): every webui calls
5
+ // initFontContext() once at bootstrap and the canonical stacks land as
6
+ // inline CSS vars on document.documentElement, so no app has to restate
7
+ // font-family literals.
8
+
9
+ // Apple-style system stack; the CJK tail keeps non-Apple platforms off kai-style last-resort fallbacks.
10
+ // Kai/serif faces are deliberately never listed.
11
+ export const HIKARI_FONT_SANS = `-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC", sans-serif`;
12
+ export const HIKARI_FONT_MONO = `ui-monospace, "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", "Liberation Mono", "PingFang SC", "Microsoft YaHei", "Noto Sans CJK SC", monospace`;
13
+ export const HIKARI_FONT_READING = HIKARI_FONT_SANS;
14
+
15
+ export interface FontContextOverrides {
16
+ sans?: string;
17
+ mono?: string;
18
+ reading?: string;
19
+ }
20
+
21
+ const STORAGE_KEY = "hikari-font-context";
22
+ const MAX_VALUE_LENGTH = 500;
23
+ // CSS-injection guard: font-family lists never need these characters.
24
+ const FORBIDDEN_CHARS = /[;{}]/;
25
+
26
+ type FontSlot = keyof FontContextOverrides;
27
+
28
+ const FONT_VAR: Record<FontSlot, string> = {
29
+ sans: "--font-sans",
30
+ mono: "--font-mono",
31
+ reading: "--font-reading",
32
+ };
33
+
34
+ // Shared singleton state (mirrors useTheme's module-level refs): every
35
+ // useFontContext() call in the app sees the same stacks.
36
+ const sansRef = ref<string>(HIKARI_FONT_SANS);
37
+ const monoRef = ref<string>(HIKARI_FONT_MONO);
38
+ const readingRef = ref<string>(HIKARI_FONT_READING);
39
+
40
+ function slotRef(slot: FontSlot): Ref<string> {
41
+ return slot === "sans" ? sansRef : slot === "mono" ? monoRef : readingRef;
42
+ }
43
+
44
+ function isBrowser(): boolean {
45
+ return typeof document !== "undefined";
46
+ }
47
+
48
+ // Sanitize a candidate value: empty, over-long, or injection-shaped
49
+ // values are rejected (the caller falls back to the next source).
50
+ function sanitizeFontValue(value: unknown): string | null {
51
+ if (typeof value !== "string") return null;
52
+ const trimmed = value.trim();
53
+ if (!trimmed || trimmed.length > MAX_VALUE_LENGTH) return null;
54
+ if (FORBIDDEN_CHARS.test(trimmed)) return null;
55
+ return trimmed;
56
+ }
57
+
58
+ function readStoredOverrides(): FontContextOverrides {
59
+ if (!isBrowser() || typeof localStorage === "undefined") return {};
60
+ let raw: string | null;
61
+ try {
62
+ raw = localStorage.getItem(STORAGE_KEY);
63
+ } catch {
64
+ return {};
65
+ }
66
+ if (!raw) return {};
67
+ let parsed: unknown;
68
+ try {
69
+ parsed = JSON.parse(raw);
70
+ } catch {
71
+ // Malformed JSON: ignore it; the next setter overwrites the key.
72
+ return {};
73
+ }
74
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
75
+ const record = parsed as Record<string, unknown>;
76
+ const out: FontContextOverrides = {};
77
+ for (const slot of Object.keys(FONT_VAR) as FontSlot[]) {
78
+ const value = sanitizeFontValue(record[slot]);
79
+ if (value) out[slot] = value;
80
+ }
81
+ return out;
82
+ }
83
+
84
+ function persistOverrides(overrides: FontContextOverrides): void {
85
+ if (!isBrowser() || typeof localStorage === "undefined") return;
86
+ const merged: FontContextOverrides = { ...readStoredOverrides(), ...overrides };
87
+ const hasAny = (Object.keys(FONT_VAR) as FontSlot[]).some((slot) => merged[slot]);
88
+ try {
89
+ if (hasAny) localStorage.setItem(STORAGE_KEY, JSON.stringify(merged));
90
+ else localStorage.removeItem(STORAGE_KEY);
91
+ } catch {
92
+ // Storage unavailable (private mode etc.): vars still apply inline.
93
+ }
94
+ }
95
+
96
+ // Re-apply the current state as inline CSS vars. Safe to call any time.
97
+ export function applyFontContext(): void {
98
+ if (!isBrowser()) return;
99
+ const el = document.documentElement;
100
+ el.style.setProperty(FONT_VAR.sans, sansRef.value);
101
+ el.style.setProperty(FONT_VAR.mono, monoRef.value);
102
+ el.style.setProperty(FONT_VAR.reading, readingRef.value);
103
+ el.setAttribute("data-font-context", "on");
104
+ }
105
+
106
+ let initialized = false;
107
+
108
+ // Bootstrap hook. Idempotent via a module-level flag like initTheme, BUT
109
+ // explicit options on any call (including repeated ones) always win: they
110
+ // are merged into the current state and re-applied. Explicit options are
111
+ // not persisted — persistence belongs to the useFontContext setters.
112
+ export function initFontContext(options?: FontContextOverrides): void {
113
+ if (!isBrowser()) return;
114
+ const explicit: FontContextOverrides = {};
115
+ if (options) {
116
+ for (const slot of Object.keys(FONT_VAR) as FontSlot[]) {
117
+ const value = sanitizeFontValue(options[slot]);
118
+ if (value) explicit[slot] = value;
119
+ }
120
+ }
121
+ if (!initialized) {
122
+ initialized = true;
123
+ const stored = readStoredOverrides();
124
+ sansRef.value = explicit.sans ?? stored.sans ?? HIKARI_FONT_SANS;
125
+ monoRef.value = explicit.mono ?? stored.mono ?? HIKARI_FONT_MONO;
126
+ readingRef.value = explicit.reading ?? stored.reading ?? HIKARI_FONT_READING;
127
+ } else {
128
+ // Repeated call: merge the explicit options over the current state.
129
+ if (explicit.sans) sansRef.value = explicit.sans;
130
+ if (explicit.mono) monoRef.value = explicit.mono;
131
+ if (explicit.reading) readingRef.value = explicit.reading;
132
+ }
133
+ applyFontContext();
134
+ }
135
+
136
+ // Clear the persisted override and restore the built-in defaults.
137
+ export function resetFontContext(): void {
138
+ sansRef.value = HIKARI_FONT_SANS;
139
+ monoRef.value = HIKARI_FONT_MONO;
140
+ readingRef.value = HIKARI_FONT_READING;
141
+ if (isBrowser() && typeof localStorage !== "undefined") {
142
+ try {
143
+ localStorage.removeItem(STORAGE_KEY);
144
+ } catch {
145
+ // ignore
146
+ }
147
+ }
148
+ applyFontContext();
149
+ }
150
+
151
+ export function useFontContext() {
152
+ function setSlot(slot: FontSlot, value: string): void {
153
+ const clean = sanitizeFontValue(value);
154
+ if (!clean) return; // rejected: don't persist, don't apply
155
+ slotRef(slot).value = clean;
156
+ persistOverrides({ [slot]: clean });
157
+ applyFontContext();
158
+ }
159
+
160
+ return {
161
+ sans: sansRef,
162
+ mono: monoRef,
163
+ reading: readingRef,
164
+ setSans: (value: string) => setSlot("sans", value),
165
+ setMono: (value: string) => setSlot("mono", value),
166
+ setReading: (value: string) => setSlot("reading", value),
167
+ reset: resetFontContext,
168
+ };
169
+ }
@@ -1,5 +1,22 @@
1
1
  export { initTheme, useTheme } from "./useTheme";
2
+ export { initFontContext, applyFontContext, resetFontContext, useFontContext, HIKARI_FONT_SANS, HIKARI_FONT_MONO, HIKARI_FONT_READING } from "./fontContext";
3
+ export type { FontContextOverrides } from "./fontContext";
2
4
  export { themePresets, tokensToCSSVars, getThemeTokens, loadCustomThemes, saveCustomThemes, addCustomTheme, removeCustomTheme } from "./presets";
3
- export type { ThemeTokenRGB, ThemeSchemeTokens, ThemePreset, CustomThemePreset, ThemeId, ThemeMode } from "./presets";
4
- export { getTimePeriod, getGeolocation, solarAltitude, DEFAULT_GEO_LOCATION } from "./useSolarTime";
5
+ export type { ThemeTokenRGB, ThemeSchemeTokens, ThemePreset, CustomThemePreset, ThemeId, ThemeMode, ThemeTokenGroupValues, ThemeTokenGroupModes } from "./presets";
6
+ export type { ThemeTokens } from "./presets";
7
+ export {
8
+ registerTokenGroup, getTokenGroups, resolveGroupTokens, clampToSlot,
9
+ clampRgbToBands, clampHue, hueDelta, groupTokensToCSSVars, rgbToHsl, hslToRgb, wrapHue,
10
+ tokenGroupsVersion, setTokenGroupsReapply,
11
+ allGroupSlots, resolveLocalizedText, parseTokenGroupConfig, registerTokenGroupConfig,
12
+ } from "./tokenGroups";
13
+ export type {
14
+ TokenGroupDefinition, TokenGroupSlot, TokenGroupSection, HueClamp,
15
+ ResolvedGroupTokens, ColorHSL, TokenGroupsReapplyFn,
16
+ LocalizedText, ParseTokenGroupResult,
17
+ } from "./tokenGroups";
18
+ export { getTimePeriod, getGeolocation, solarAltitude, DEFAULT_GEO_LOCATION, timezoneFallback, setGeolocationProvider } from "./useSolarTime";
19
+ export type { GeoLocation, GeoLocationProvider } from "./useSolarTime";
20
+ export { refreshThemeClock, stopThemeClock } from "./useTheme";
21
+ export { startLuminanceSampler, stopLuminanceSampler, sampleLuminanceNow, invalidateLuminanceCache } from "./useBackgroundLuminance";
5
22
  export type { TimePeriod } from "./useSolarTime";