@hraness/design-kit 0.31.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (345) hide show
  1. package/ARTICLE_COPY.md +208 -0
  2. package/HERO_FIELDS.md +55 -0
  3. package/ICONS.md +56 -0
  4. package/LANTERN_MATERIAL.md +60 -0
  5. package/LICENSE +21 -0
  6. package/MARKETING_COPY.md +49 -0
  7. package/MARKETING_PRESET.md +108 -0
  8. package/PALETTES.md +90 -0
  9. package/PAPER_THEME.md +128 -0
  10. package/README.md +745 -0
  11. package/STATUS_PAGES.md +67 -0
  12. package/STYLE.md +245 -0
  13. package/WRITING.md +72 -0
  14. package/dist/browser/index.js +2345 -0
  15. package/dist/chunk-44djxk16.js +6872 -0
  16. package/dist/chunk-5gtx3pza.js +9 -0
  17. package/dist/chunk-77391vmq.js +454 -0
  18. package/dist/chunk-7b1586eb.js +1209 -0
  19. package/dist/chunk-8834fh4n.js +6 -0
  20. package/dist/chunk-9t8xyqte.js +870 -0
  21. package/dist/chunk-cejpzyfh.js +446 -0
  22. package/dist/chunk-eh71jz57.js +429 -0
  23. package/dist/chunk-evb02bc1.js +516 -0
  24. package/dist/chunk-h4k7yv6x.js +368 -0
  25. package/dist/chunk-he8eznb1.js +760 -0
  26. package/dist/chunk-mr1vdcjq.js +404 -0
  27. package/dist/chunk-wzvdn8ey.js +116 -0
  28. package/dist/fonts/nebula-sans/social-fonts.generated.js +53 -0
  29. package/dist/icons.js +48 -0
  30. package/dist/index.js +237 -0
  31. package/dist/launch.js +41 -0
  32. package/dist/mockups/client.js +422 -0
  33. package/dist/mockups/index.js +1850 -0
  34. package/dist/portfolio.js +1549 -0
  35. package/dist/provider-marks.js +19 -0
  36. package/dist/react/charts.js +11 -0
  37. package/dist/react/hero-backdrop.js +8 -0
  38. package/dist/react/index.js +7252 -0
  39. package/dist/react/platform-install.js +10 -0
  40. package/dist/react/server.js +137 -0
  41. package/dist/stylex-manifest.json +1 -0
  42. package/dist/stylex.css +5392 -0
  43. package/dist/syntax-highlighting.js +15 -0
  44. package/dist/testing.js +250 -0
  45. package/package.json +377 -0
  46. package/portfolio-inventory.json +31 -0
  47. package/scripts/check-lantern-material-snapshot.d.mts +26 -0
  48. package/scripts/check-lantern-material-snapshot.mjs +126 -0
  49. package/scripts/check-marketing-snapshot.d.mts +12 -0
  50. package/scripts/check-marketing-snapshot.mjs +58 -0
  51. package/scripts/lantern-material-snapshot.ts +101 -0
  52. package/scripts/marketing-textures.ts +48 -0
  53. package/scripts/paper-theme-snapshot.ts +97 -0
  54. package/scripts/product-marketing-snapshot.ts +149 -0
  55. package/src/appearance-menu.css +266 -0
  56. package/src/appearance.ts +32 -0
  57. package/src/article-html.ts +269 -0
  58. package/src/article.ts +686 -0
  59. package/src/browser/appearance-menu.ts +604 -0
  60. package/src/browser/artifact-share.ts +164 -0
  61. package/src/browser/design-palette.ts +197 -0
  62. package/src/browser/foil.ts +124 -0
  63. package/src/browser/hero-light.ts +11 -0
  64. package/src/browser/index.ts +29 -0
  65. package/src/browser/status-field.ts +432 -0
  66. package/src/browser/status-page.ts +100 -0
  67. package/src/browser/sticky-offset.ts +82 -0
  68. package/src/browser/theme-color-sync.ts +213 -0
  69. package/src/charts.css +7 -0
  70. package/src/compiler-components.css +34 -0
  71. package/src/compiler-foundation.css +18 -0
  72. package/src/compiler-palettes.css +7 -0
  73. package/src/compiler-tokens.css +342 -0
  74. package/src/components.css +3 -0
  75. package/src/design-gallery.css +431 -0
  76. package/src/effects.css +14 -0
  77. package/src/fonts/geist-mono/GeistMono[wght].woff2 +0 -0
  78. package/src/fonts/geist-mono/OFL.txt +93 -0
  79. package/src/fonts/geist-mono/PROVENANCE.md +12 -0
  80. package/src/fonts/instrument-serif/OFL.txt +93 -0
  81. package/src/fonts/instrument-serif/UPSTREAM.md +14 -0
  82. package/src/fonts/instrument-serif/instrument-serif-latin-400.woff2 +0 -0
  83. package/src/fonts/nebula-sans/LICENSE.txt +96 -0
  84. package/src/fonts/nebula-sans/NebulaSans-Black.woff2 +0 -0
  85. package/src/fonts/nebula-sans/NebulaSans-BlackItalic.woff2 +0 -0
  86. package/src/fonts/nebula-sans/NebulaSans-Bold.otf +0 -0
  87. package/src/fonts/nebula-sans/NebulaSans-Bold.woff2 +0 -0
  88. package/src/fonts/nebula-sans/NebulaSans-BoldItalic.woff2 +0 -0
  89. package/src/fonts/nebula-sans/NebulaSans-Book.otf +0 -0
  90. package/src/fonts/nebula-sans/NebulaSans-Book.woff2 +0 -0
  91. package/src/fonts/nebula-sans/NebulaSans-BookItalic.woff2 +0 -0
  92. package/src/fonts/nebula-sans/NebulaSans-Light.woff2 +0 -0
  93. package/src/fonts/nebula-sans/NebulaSans-LightItalic.woff2 +0 -0
  94. package/src/fonts/nebula-sans/NebulaSans-Medium.woff2 +0 -0
  95. package/src/fonts/nebula-sans/NebulaSans-MediumItalic.woff2 +0 -0
  96. package/src/fonts/nebula-sans/NebulaSans-Semibold.woff2 +0 -0
  97. package/src/fonts/nebula-sans/NebulaSans-SemiboldItalic.woff2 +0 -0
  98. package/src/fonts/nebula-sans/PROVENANCE.md +14 -0
  99. package/src/fonts/nebula-sans/social-fonts.generated.ts +3873 -0
  100. package/src/fonts.css +106 -0
  101. package/src/icons/act60/act60.svg +5 -0
  102. package/src/icons/act60/application.svg +9 -0
  103. package/src/icons/act60/chapter-compare.svg +3 -0
  104. package/src/icons/act60/decree-residence.svg +6 -0
  105. package/src/icons/act60/estimate.svg +7 -0
  106. package/src/icons/act60/export-services.svg +4 -0
  107. package/src/icons/act60/investor.svg +13 -0
  108. package/src/icons/act60/presence.svg +7 -0
  109. package/src/icons/aicharts/aicharts.svg +6 -0
  110. package/src/icons/aicharts/cost-compare.svg +4 -0
  111. package/src/icons/aicharts/task-audio.svg +7 -0
  112. package/src/icons/aicharts/task-coding.svg +6 -0
  113. package/src/icons/aicharts/task-images.svg +3 -0
  114. package/src/icons/aicharts/task-reasoning.svg +9 -0
  115. package/src/icons/aicharts/task-research.svg +5 -0
  116. package/src/icons/aicharts/task-video.svg +4 -0
  117. package/src/icons/manifest.json +1371 -0
  118. package/src/icons/platonik/conversation.svg +3 -0
  119. package/src/icons/platonik/creature.svg +7 -0
  120. package/src/icons/platonik/economy.svg +4 -0
  121. package/src/icons/platonik/keep-light.svg +6 -0
  122. package/src/icons/platonik/platonik.svg +7 -0
  123. package/src/icons/platonik/spark-route.svg +15 -0
  124. package/src/icons/receipts/act60-retry.receipt.json +888 -0
  125. package/src/icons/receipts/act60.receipt.json +1366 -0
  126. package/src/icons/receipts/aicharts.receipt.json +1546 -0
  127. package/src/icons/receipts/platonik.receipt.json +1011 -0
  128. package/src/icons/receipts/roughday.receipt.json +770 -0
  129. package/src/icons/receipts/shared.receipt.json +1296 -0
  130. package/src/icons/receipts/slopcamera.receipt.json +1495 -0
  131. package/src/icons/receipts/soundfish-retry.receipt.json +460 -0
  132. package/src/icons/receipts/soundfish.receipt.json +1709 -0
  133. package/src/icons/receipts/sponge.receipt.json +1743 -0
  134. package/src/icons/receipts/stripe-history-retry.receipt.json +145 -0
  135. package/src/icons/receipts/stripe-history.receipt.json +1202 -0
  136. package/src/icons/receipts/wordcell.receipt.json +1924 -0
  137. package/src/icons/roughday/finance.svg +7 -0
  138. package/src/icons/roughday/roughday.svg +6 -0
  139. package/src/icons/roughday/technology.svg +4 -0
  140. package/src/icons/roughday/world.svg +4 -0
  141. package/src/icons/sets/act60.json +63 -0
  142. package/src/icons/sets/aicharts.json +63 -0
  143. package/src/icons/sets/platonik.json +39 -0
  144. package/src/icons/sets/roughday.json +25 -0
  145. package/src/icons/sets/shared.json +12 -0
  146. package/src/icons/sets/slopcamera.json +47 -0
  147. package/src/icons/sets/soundfish.json +43 -0
  148. package/src/icons/sets/sponge.json +51 -0
  149. package/src/icons/sets/stripe-history.json +39 -0
  150. package/src/icons/sets/wordcell.json +51 -0
  151. package/src/icons/shared/agent-skill.svg +4 -0
  152. package/src/icons/shared/cli.svg +5 -0
  153. package/src/icons/shared/privacy.svg +3 -0
  154. package/src/icons/shared/research.svg +7 -0
  155. package/src/icons/shared/sdk.svg +3 -0
  156. package/src/icons/slopcamera/diagram.svg +3 -0
  157. package/src/icons/slopcamera/direct-scene.svg +5 -0
  158. package/src/icons/slopcamera/edit-video.svg +17 -0
  159. package/src/icons/slopcamera/mcp.svg +3 -0
  160. package/src/icons/slopcamera/native-world.svg +3 -0
  161. package/src/icons/slopcamera/slopcamera.svg +11 -0
  162. package/src/icons/soundfish/agent-ui.svg +7 -0
  163. package/src/icons/soundfish/collaborate.svg +5 -0
  164. package/src/icons/soundfish/compose.svg +3 -0
  165. package/src/icons/soundfish/hear.svg +6 -0
  166. package/src/icons/soundfish/human-ui.svg +6 -0
  167. package/src/icons/soundfish/share.svg +3 -0
  168. package/src/icons/soundfish/soundfish.svg +6 -0
  169. package/src/icons/sponge/api.svg +3 -0
  170. package/src/icons/sponge/documents.svg +3 -0
  171. package/src/icons/sponge/library.svg +3 -0
  172. package/src/icons/sponge/one-question.svg +5 -0
  173. package/src/icons/sponge/people-direct.svg +5 -0
  174. package/src/icons/sponge/read.svg +3 -0
  175. package/src/icons/sponge/save.svg +4 -0
  176. package/src/icons/sponge/sponge.svg +7 -0
  177. package/src/icons/stripe-history/company-history.svg +3 -0
  178. package/src/icons/stripe-history/evidence.svg +4 -0
  179. package/src/icons/stripe-history/independence.svg +7 -0
  180. package/src/icons/stripe-history/publications.svg +11 -0
  181. package/src/icons/stripe-history/sources.svg +6 -0
  182. package/src/icons/stripe-history/stripe-history.svg +3 -0
  183. package/src/icons/wordcell/backlinks.svg +6 -0
  184. package/src/icons/wordcell/capture.svg +6 -0
  185. package/src/icons/wordcell/git-provenance.svg +4 -0
  186. package/src/icons/wordcell/kb.svg +4 -0
  187. package/src/icons/wordcell/markdown.svg +4 -0
  188. package/src/icons/wordcell/scopes.svg +3 -0
  189. package/src/icons/wordcell/search.svg +7 -0
  190. package/src/icons.generated.ts +734 -0
  191. package/src/icons.ts +51 -0
  192. package/src/index.ts +275 -0
  193. package/src/lantern-material.css +213 -0
  194. package/src/launch.ts +624 -0
  195. package/src/marketing-assets/UPSTREAM.md +18 -0
  196. package/src/marketing-assets/cells.svg +1 -0
  197. package/src/marketing-assets/grain.svg +1 -0
  198. package/src/marketing-forced-colors.css +12 -0
  199. package/src/mockups/client.tsx +415 -0
  200. package/src/mockups/core.tsx +318 -0
  201. package/src/mockups/frames.tsx +368 -0
  202. package/src/mockups/index.ts +14 -0
  203. package/src/mockups/surfaces.tsx +681 -0
  204. package/src/mockups.css +2607 -0
  205. package/src/palette-appearance.ts +45 -0
  206. package/src/palette-bridge.css +124 -0
  207. package/src/palette-color.ts +41 -0
  208. package/src/palette-system.css +721 -0
  209. package/src/palette-themes.ts +23 -0
  210. package/src/palette-tokens.stylex.ts +519 -0
  211. package/src/palettes.css +3 -0
  212. package/src/palettes.ts +161 -0
  213. package/src/paper-theme.css +244 -0
  214. package/src/plain-publication.css +1241 -0
  215. package/src/plain-site.css +397 -0
  216. package/src/platforms.ts +151 -0
  217. package/src/portfolio.generated.json +1595 -0
  218. package/src/portfolio.generated.ts +1596 -0
  219. package/src/portfolio.ts +226 -0
  220. package/src/product-marketing-foundation.css +433 -0
  221. package/src/product-marketing-preset.css +261 -0
  222. package/src/product-marketing.css +2186 -0
  223. package/src/provider-marks.generated.ts +112 -0
  224. package/src/provider-marks.ts +143 -0
  225. package/src/react/animated-rail-stage.stylex.ts +15 -0
  226. package/src/react/animated-rail-stage.tsx +51 -0
  227. package/src/react/app-shell.stylex.ts +104 -0
  228. package/src/react/app-shell.tsx +121 -0
  229. package/src/react/article.tsx +602 -0
  230. package/src/react/aurora-dots-background.tsx +34 -0
  231. package/src/react/charts.stylex.ts +563 -0
  232. package/src/react/charts.tsx +610 -0
  233. package/src/react/chat.stylex.ts +34 -0
  234. package/src/react/chat.tsx +155 -0
  235. package/src/react/design-gallery.tsx +943 -0
  236. package/src/react/design-palette.stylex.ts +124 -0
  237. package/src/react/design-palette.tsx +196 -0
  238. package/src/react/design-theme-context.tsx +41 -0
  239. package/src/react/effects.stylex.ts +325 -0
  240. package/src/react/fader.stylex.ts +71 -0
  241. package/src/react/fader.tsx +151 -0
  242. package/src/react/foil-card-math.ts +100 -0
  243. package/src/react/foil-card-surface.stylex.ts +326 -0
  244. package/src/react/foil-card-surface.tsx +742 -0
  245. package/src/react/foil-mark.tsx +35 -0
  246. package/src/react/foil.stylex.ts +163 -0
  247. package/src/react/haptics.ts +228 -0
  248. package/src/react/hero-backdrop.tsx +22 -0
  249. package/src/react/index.ts +38 -0
  250. package/src/react/keyboard-shortcuts.ts +228 -0
  251. package/src/react/lantern-material-gallery.tsx +166 -0
  252. package/src/react/lantern-material.stylex.ts +39 -0
  253. package/src/react/launch-beats.tsx +73 -0
  254. package/src/react/navigation-rail.stylex.ts +130 -0
  255. package/src/react/navigation-rail.tsx +228 -0
  256. package/src/react/particle-halo.tsx +115 -0
  257. package/src/react/phaser-dots.tsx +501 -0
  258. package/src/react/platform-icons.tsx +94 -0
  259. package/src/react/platform-install.stylex.ts +385 -0
  260. package/src/react/platform-install.tsx +389 -0
  261. package/src/react/playback-transport.stylex.ts +14 -0
  262. package/src/react/playback-transport.tsx +112 -0
  263. package/src/react/procedural-backdrop.tsx +192 -0
  264. package/src/react/procedural-recipe.ts +323 -0
  265. package/src/react/product-marketing.stylex.ts +4469 -0
  266. package/src/react/product-marketing.tsx +1611 -0
  267. package/src/react/production-data-preview-notice.stylex.ts +40 -0
  268. package/src/react/production-data-preview-notice.tsx +34 -0
  269. package/src/react/provider-mark.stylex.ts +132 -0
  270. package/src/react/provider-mark.tsx +120 -0
  271. package/src/react/relative-time.tsx +117 -0
  272. package/src/react/route-state.stylex.ts +39 -0
  273. package/src/react/route-state.tsx +350 -0
  274. package/src/react/server.ts +11 -0
  275. package/src/react/social-kit-panel.tsx +103 -0
  276. package/src/react/sticky-offset.tsx +33 -0
  277. package/src/react/surfaces.stylex.ts +179 -0
  278. package/src/react/surfaces.tsx +330 -0
  279. package/src/react/syntax-code.tsx +33 -0
  280. package/src/react/theme-color-sync.ts +1 -0
  281. package/src/react/theme-resolution.ts +11 -0
  282. package/src/react/theme.stylex.ts +276 -0
  283. package/src/react/theme.tsx +402 -0
  284. package/src/reading.css +72 -0
  285. package/src/relative-time.ts +185 -0
  286. package/src/reset.css +2 -0
  287. package/src/site-shell.css +16 -0
  288. package/src/status-page-html.ts +63 -0
  289. package/src/status-page.css +276 -0
  290. package/src/status-page.ts +323 -0
  291. package/src/styles.css +21 -0
  292. package/src/syntax-code-lexers.ts +303 -0
  293. package/src/syntax-highlighting.css +78 -0
  294. package/src/syntax-highlighting.ts +477 -0
  295. package/src/syntax-tokens.ts +33 -0
  296. package/src/testing.ts +307 -0
  297. package/src/tokens.css +3 -0
  298. package/src/typography.css +86 -0
  299. package/vendor/evilcharts/LICENSE +21 -0
  300. package/vendor/evilcharts/UPSTREAM.md +14 -0
  301. package/vendor/platform-marks/LICENSE +30 -0
  302. package/vendor/platform-marks/UPSTREAM.md +15 -0
  303. package/vendor/platform-marks/apple.svg +1 -0
  304. package/vendor/platform-marks/linux.svg +1 -0
  305. package/vendor/provider-marks/CRUSH-LICENSE.md +134 -0
  306. package/vendor/provider-marks/LICENSE +21 -0
  307. package/vendor/provider-marks/UPSTREAM.md +27 -0
  308. package/vendor/provider-marks/aider.svg +3 -0
  309. package/vendor/provider-marks/crush-heartbit.svg +8 -0
  310. package/vendor/provider-marks/lobehub/alibabacloud-color.svg +1 -0
  311. package/vendor/provider-marks/lobehub/alibabacloud.svg +1 -0
  312. package/vendor/provider-marks/lobehub/amp-color.svg +1 -0
  313. package/vendor/provider-marks/lobehub/amp.svg +1 -0
  314. package/vendor/provider-marks/lobehub/anthropic.svg +1 -0
  315. package/vendor/provider-marks/lobehub/claude-color.svg +1 -0
  316. package/vendor/provider-marks/lobehub/claude.svg +1 -0
  317. package/vendor/provider-marks/lobehub/claudecode-color.svg +1 -0
  318. package/vendor/provider-marks/lobehub/claudecode.svg +1 -0
  319. package/vendor/provider-marks/lobehub/codex-color.svg +1 -0
  320. package/vendor/provider-marks/lobehub/codex.svg +1 -0
  321. package/vendor/provider-marks/lobehub/cursor.svg +1 -0
  322. package/vendor/provider-marks/lobehub/deepseek-color.svg +1 -0
  323. package/vendor/provider-marks/lobehub/deepseek.svg +1 -0
  324. package/vendor/provider-marks/lobehub/devin-color.svg +1 -0
  325. package/vendor/provider-marks/lobehub/devin.svg +1 -0
  326. package/vendor/provider-marks/lobehub/gemini-color.svg +1 -0
  327. package/vendor/provider-marks/lobehub/gemini.svg +1 -0
  328. package/vendor/provider-marks/lobehub/geminicli-color.svg +1 -0
  329. package/vendor/provider-marks/lobehub/geminicli.svg +1 -0
  330. package/vendor/provider-marks/lobehub/goose.svg +1 -0
  331. package/vendor/provider-marks/lobehub/meta-color.svg +1 -0
  332. package/vendor/provider-marks/lobehub/meta.svg +1 -0
  333. package/vendor/provider-marks/lobehub/mistral-color.svg +1 -0
  334. package/vendor/provider-marks/lobehub/mistral.svg +1 -0
  335. package/vendor/provider-marks/lobehub/moonshot.svg +1 -0
  336. package/vendor/provider-marks/lobehub/nvidia-color.svg +1 -0
  337. package/vendor/provider-marks/lobehub/nvidia.svg +1 -0
  338. package/vendor/provider-marks/lobehub/openai.svg +1 -0
  339. package/vendor/provider-marks/lobehub/opencode.svg +1 -0
  340. package/vendor/provider-marks/lobehub/perplexity-color.svg +1 -0
  341. package/vendor/provider-marks/lobehub/perplexity.svg +1 -0
  342. package/vendor/provider-marks/lobehub/qwen-color.svg +1 -0
  343. package/vendor/provider-marks/lobehub/qwen.svg +1 -0
  344. package/vendor/provider-marks/lobehub/xai.svg +1 -0
  345. package/vendor/provider-marks/lobehub/zai.svg +1 -0
@@ -0,0 +1,2345 @@
1
+ // src/appearance.ts
2
+ var designThemes = ["light", "dark", "system"];
3
+ var defaultDesignTheme = "system";
4
+ var designThemeStorageKey = "hraness-design-theme-v1";
5
+ function isDesignTheme(value) {
6
+ return typeof value === "string" && designThemes.some((theme) => theme === value);
7
+ }
8
+ function normalizeDesignTheme(value) {
9
+ return isDesignTheme(value) ? value : defaultDesignTheme;
10
+ }
11
+ function designThemeLabel(theme, labels) {
12
+ return labels?.[theme] ?? `${theme.charAt(0).toUpperCase()}${theme.slice(1)}`;
13
+ }
14
+ function resolveDesignTheme(theme, systemPrefersDark) {
15
+ return theme === "system" ? systemPrefersDark ? "dark" : "light" : theme;
16
+ }
17
+
18
+ // src/browser/appearance-menu.ts
19
+ import { ComputerIcon, Moon02Icon, Sun03Icon } from "@hugeicons/core-free-icons";
20
+
21
+ // src/browser/theme-color-sync.ts
22
+ var themeColorSyncActiveAttribute = "data-hraness-design-theme-color-sync-active";
23
+ var themeColorSyncDisabledAttribute = "data-hraness-design-theme-color-sync-disabled";
24
+ var managersByDocument = new WeakMap;
25
+ var ownerSequence = 0;
26
+ function exactThemeColorMetas(manager) {
27
+ return Array.from(manager.document.head.querySelectorAll("meta[name]")).filter((meta) => meta.name === manager.metaName);
28
+ }
29
+ function currentRegisteredColor(manager) {
30
+ let color;
31
+ for (const registeredColor of manager.registrations.values())
32
+ color = registeredColor;
33
+ if (color === undefined)
34
+ throw new Error("Theme color synchronization has no active owner.");
35
+ return color;
36
+ }
37
+ function restoreDisabledMeta(meta, original) {
38
+ if (original.media === null)
39
+ meta.removeAttribute("media");
40
+ else
41
+ meta.setAttribute("media", original.media);
42
+ meta.removeAttribute(themeColorSyncDisabledAttribute);
43
+ }
44
+ function createActiveMeta(manager) {
45
+ const meta = manager.document.createElement("meta");
46
+ meta.name = manager.metaName;
47
+ meta.content = currentRegisteredColor(manager);
48
+ meta.setAttribute(themeColorSyncActiveAttribute, manager.owner);
49
+ manager.activeMetas.add(meta);
50
+ const first = exactThemeColorMetas(manager).find((candidate) => candidate.parentElement === manager.document.head);
51
+ manager.document.head.insertBefore(meta, first ?? null);
52
+ return meta;
53
+ }
54
+ function activeMetaIsOwned(manager) {
55
+ const active = manager.activeMeta;
56
+ return active !== null && active.parentElement === manager.document.head && active.name === manager.metaName && !active.hasAttribute("media") && active.getAttribute(themeColorSyncActiveAttribute) === manager.owner;
57
+ }
58
+ function disableCompetingMeta(manager, meta) {
59
+ if (manager.disabledMetas.has(meta)) {
60
+ if (meta.getAttribute(themeColorSyncDisabledAttribute) !== manager.owner) {
61
+ meta.setAttribute(themeColorSyncDisabledAttribute, manager.owner);
62
+ }
63
+ if (meta.getAttribute("media") !== "not all")
64
+ meta.setAttribute("media", "not all");
65
+ return;
66
+ }
67
+ const ownedBy = meta.getAttribute(themeColorSyncDisabledAttribute);
68
+ if (ownedBy !== null || meta.getAttribute("media")?.trim().toLowerCase() === "not all") {
69
+ return;
70
+ }
71
+ manager.disabledMetas.set(meta, {
72
+ media: meta.getAttribute("media")
73
+ });
74
+ meta.setAttribute(themeColorSyncDisabledAttribute, manager.owner);
75
+ meta.setAttribute("media", "not all");
76
+ }
77
+ function reconcileThemeColorMetas(manager) {
78
+ if (manager.registrations.size === 0)
79
+ return;
80
+ for (const [meta, original] of manager.disabledMetas) {
81
+ if (!manager.document.head.contains(meta) || meta.name !== manager.metaName) {
82
+ restoreDisabledMeta(meta, original);
83
+ manager.disabledMetas.delete(meta);
84
+ }
85
+ }
86
+ if (!activeMetaIsOwned(manager))
87
+ manager.activeMeta = createActiveMeta(manager);
88
+ const active = manager.activeMeta;
89
+ if (active === null)
90
+ return;
91
+ const metas = exactThemeColorMetas(manager);
92
+ const first = metas.find((meta) => meta.parentElement === manager.document.head);
93
+ if (first !== undefined && first !== active)
94
+ manager.document.head.insertBefore(active, first);
95
+ const color = currentRegisteredColor(manager);
96
+ if (active.content !== color)
97
+ active.content = color;
98
+ for (const meta of metas) {
99
+ if (meta !== active)
100
+ disableCompetingMeta(manager, meta);
101
+ }
102
+ observeThemeColorMetas(manager);
103
+ }
104
+ function observeThemeColorMetas(manager) {
105
+ const head = manager.document.head;
106
+ if (manager.observer !== null && manager.observedHead === head)
107
+ return;
108
+ manager.observer?.disconnect();
109
+ manager.observedHead = null;
110
+ const Observer = manager.document.defaultView?.MutationObserver;
111
+ if (Observer === undefined)
112
+ return;
113
+ manager.observer ??= new Observer(() => reconcileThemeColorMetas(manager));
114
+ manager.observer.observe(head, {
115
+ attributeFilter: ["content", "media", "name", themeColorSyncActiveAttribute, themeColorSyncDisabledAttribute],
116
+ attributes: true,
117
+ childList: true,
118
+ subtree: true
119
+ });
120
+ manager.observedHead = head;
121
+ }
122
+ function destroyThemeColorManager(manager) {
123
+ manager.observer?.disconnect();
124
+ manager.observer = null;
125
+ manager.observedHead = null;
126
+ for (const meta of manager.activeMetas)
127
+ meta.remove();
128
+ for (const [meta, original] of manager.disabledMetas) {
129
+ restoreDisabledMeta(meta, original);
130
+ }
131
+ manager.activeMetas.clear();
132
+ manager.disabledMetas.clear();
133
+ manager.activeMeta = null;
134
+ const documentManagers = managersByDocument.get(manager.document);
135
+ if (documentManagers?.get(manager.metaName) === manager) {
136
+ documentManagers.delete(manager.metaName);
137
+ }
138
+ }
139
+ function acquireThemeColorMeta(document, metaName, registrationId, color) {
140
+ let documentManagers = managersByDocument.get(document);
141
+ if (documentManagers === undefined) {
142
+ documentManagers = new Map;
143
+ managersByDocument.set(document, documentManagers);
144
+ }
145
+ let manager = documentManagers.get(metaName);
146
+ if (manager === undefined) {
147
+ ownerSequence += 1;
148
+ manager = {
149
+ activeMeta: null,
150
+ activeMetas: new Set,
151
+ disabledMetas: new Map,
152
+ document,
153
+ metaName,
154
+ observer: null,
155
+ observedHead: null,
156
+ owner: String(ownerSequence),
157
+ registrations: new Map
158
+ };
159
+ documentManagers.set(metaName, manager);
160
+ }
161
+ manager.registrations.set(registrationId, color);
162
+ reconcileThemeColorMetas(manager);
163
+ let released = false;
164
+ return {
165
+ release: () => {
166
+ if (released)
167
+ return;
168
+ released = true;
169
+ manager.registrations.delete(registrationId);
170
+ if (manager.registrations.size === 0)
171
+ destroyThemeColorManager(manager);
172
+ else
173
+ reconcileThemeColorMetas(manager);
174
+ },
175
+ update: (nextColor) => {
176
+ if (released || !manager.registrations.has(registrationId))
177
+ return;
178
+ manager.registrations.set(registrationId, nextColor);
179
+ reconcileThemeColorMetas(manager);
180
+ }
181
+ };
182
+ }
183
+
184
+ // src/browser/appearance-menu.ts
185
+ var menuSelector = "[data-hraness-appearance-menu]";
186
+ var triggerSelector = ".hraness-design-theme-toggle__trigger";
187
+ var popoverSelector = ".hraness-design-theme-toggle__popover";
188
+ var menuElementSelector = ".hraness-design-theme-toggle__menu";
189
+ var itemSelector = ".hraness-design-theme-toggle__item[data-theme-value]";
190
+ var systemDarkQuery = "(prefers-color-scheme: dark)";
191
+ var svgNamespace = "http://www.w3.org/2000/svg";
192
+ var appearanceIcons = {
193
+ dark: Moon02Icon,
194
+ light: Sun03Icon,
195
+ system: ComputerIcon
196
+ };
197
+ var installationByDocument = new WeakMap;
198
+ function requireNonBlank(value, name) {
199
+ if (value.trim().length === 0)
200
+ throw new Error(`${name} must not be blank.`);
201
+ return value;
202
+ }
203
+ function resolveDocument(candidate) {
204
+ const ownedDocument = candidate ?? (typeof globalThis.document === "undefined" ? undefined : globalThis.document);
205
+ if (ownedDocument === undefined) {
206
+ throw new Error("installAppearanceMenus requires a browser Document.");
207
+ }
208
+ if (ownedDocument.documentElement === null || ownedDocument.head === null) {
209
+ throw new Error("installAppearanceMenus requires a complete HTML document.");
210
+ }
211
+ return ownedDocument;
212
+ }
213
+ function defaultStorage(document) {
214
+ try {
215
+ return document.defaultView?.localStorage ?? null;
216
+ } catch {
217
+ return null;
218
+ }
219
+ }
220
+ function storedPreference(storage, storageKey) {
221
+ if (storage === null)
222
+ return defaultDesignTheme;
223
+ try {
224
+ const value = storage.getItem(storageKey);
225
+ if (value === null || value === undefined)
226
+ return defaultDesignTheme;
227
+ if (isDesignTheme(value))
228
+ return value;
229
+ storage.setItem(storageKey, defaultDesignTheme);
230
+ } catch {}
231
+ return defaultDesignTheme;
232
+ }
233
+ function persistPreference(storage, storageKey, preference) {
234
+ if (storage === null)
235
+ return;
236
+ try {
237
+ storage.setItem(storageKey, preference);
238
+ } catch {}
239
+ }
240
+ function isElement(value) {
241
+ return value !== null && typeof value === "object" && "nodeType" in value && value.nodeType === 1;
242
+ }
243
+ function attributeName(name) {
244
+ return name.replace(/[A-Z]/gu, (letter) => `-${letter.toLowerCase()}`);
245
+ }
246
+ function appearanceIcon(document, theme) {
247
+ const svg = document.createElementNS(svgNamespace, "svg");
248
+ svg.setAttribute("aria-hidden", "true");
249
+ svg.setAttribute("class", "hraness-icon");
250
+ svg.setAttribute("color", "currentColor");
251
+ svg.setAttribute("data-slot", "icon");
252
+ svg.setAttribute("fill", "none");
253
+ svg.setAttribute("height", "18");
254
+ svg.setAttribute("stroke", "currentColor");
255
+ svg.setAttribute("stroke-width", "1.5");
256
+ svg.setAttribute("viewBox", "0 0 24 24");
257
+ svg.setAttribute("width", "18");
258
+ for (const [tag, attributes] of appearanceIcons[theme]) {
259
+ const child = document.createElementNS(svgNamespace, tag);
260
+ for (const [name, value] of Object.entries(attributes)) {
261
+ if (name !== "key")
262
+ child.setAttribute(attributeName(name), String(value));
263
+ }
264
+ child.setAttribute("stroke", "currentColor");
265
+ child.setAttribute("stroke-width", "1.5");
266
+ svg.append(child);
267
+ }
268
+ return svg;
269
+ }
270
+ function renderIconHost(document, host, theme) {
271
+ host.classList.add("hraness-appearance-icon");
272
+ host.setAttribute("aria-hidden", "true");
273
+ host.setAttribute("data-appearance-icon", theme);
274
+ host.setAttribute("data-slot", "appearance-icon");
275
+ host.replaceChildren(appearanceIcon(document, theme));
276
+ }
277
+ function themeValue(element) {
278
+ const value = element.getAttribute("data-theme-value");
279
+ return isDesignTheme(value) ? value : null;
280
+ }
281
+ function requireMenuElements(root) {
282
+ const triggers = root.querySelectorAll(triggerSelector);
283
+ const popovers = root.querySelectorAll(popoverSelector);
284
+ const menus = root.querySelectorAll(menuElementSelector);
285
+ const trigger = triggers[0];
286
+ const popover = popovers[0];
287
+ const menu = menus[0];
288
+ if (triggers.length !== 1 || popovers.length !== 1 || menus.length !== 1 || trigger?.tagName !== "BUTTON" || popover === undefined || menu === undefined) {
289
+ throw new Error("Appearance menu markup requires one button trigger, popover, and menu.");
290
+ }
291
+ if (menu.id.trim().length === 0 || trigger.getAttribute("aria-controls") !== menu.id || menu.getAttribute("role") !== "menu") {
292
+ throw new Error("Appearance menu trigger and menu ownership is invalid.");
293
+ }
294
+ const items = new Map;
295
+ for (const element of menu.querySelectorAll(itemSelector)) {
296
+ const theme = themeValue(element);
297
+ if (theme === null || items.has(theme) || element.getAttribute("role") !== "menuitemradio") {
298
+ throw new Error("Appearance menu items must be unique Light, Dark, and System radios.");
299
+ }
300
+ items.set(theme, element);
301
+ }
302
+ if (items.size !== designThemes.length || designThemes.some((theme) => !items.has(theme))) {
303
+ throw new Error("Appearance menu must contain Light, Dark, and System exactly once.");
304
+ }
305
+ return {
306
+ items,
307
+ menu,
308
+ popover,
309
+ root,
310
+ trigger,
311
+ typeahead: "",
312
+ typeaheadTimer: null
313
+ };
314
+ }
315
+ function menuItemFromEvent(elements, event) {
316
+ if (!isElement(event.target))
317
+ return null;
318
+ const item = event.target.closest(itemSelector);
319
+ return item !== null && elements.menu.contains(item) ? item : null;
320
+ }
321
+ function focusItem(elements, theme) {
322
+ elements.items.get(theme)?.focus();
323
+ }
324
+ function orderedItems(elements) {
325
+ return designThemes.flatMap((theme) => {
326
+ const item = elements.items.get(theme);
327
+ return item === undefined ? [] : [item];
328
+ });
329
+ }
330
+ function adjacentItem(elements, current, offset) {
331
+ const items = orderedItems(elements);
332
+ if (items.length === 0)
333
+ throw new Error("Appearance menu has no focusable items.");
334
+ const currentIndex = current === null ? -1 : items.indexOf(current);
335
+ const nextIndex = currentIndex < 0 ? offset === 1 ? 0 : items.length - 1 : (currentIndex + offset + items.length) % items.length;
336
+ const next = items[nextIndex];
337
+ if (next === undefined)
338
+ throw new Error("Appearance menu focus order is invalid.");
339
+ return next;
340
+ }
341
+ function storageEventValue(event, storageKey) {
342
+ const candidate = event;
343
+ return candidate.key === storageKey ? candidate.newValue : undefined;
344
+ }
345
+ function installAppearanceMenus(options) {
346
+ const document = resolveDocument(options.document);
347
+ const active = installationByDocument.get(document);
348
+ if (active !== undefined && !active.disposed) {
349
+ throw new Error("Appearance menus are already installed for this document.");
350
+ }
351
+ const darkThemeColor = requireNonBlank(options.darkThemeColor, "darkThemeColor");
352
+ const lightThemeColor = requireNonBlank(options.lightThemeColor, "lightThemeColor");
353
+ const metaName = requireNonBlank(options.metaName ?? "theme-color", "metaName");
354
+ const storageKey = requireNonBlank(options.storageKey ?? designThemeStorageKey, "storageKey");
355
+ const storage = options.storage === undefined ? defaultStorage(document) : options.storage;
356
+ const view = document.defaultView;
357
+ const systemMedia = typeof view?.matchMedia === "function" ? view.matchMedia(systemDarkQuery) : null;
358
+ let preference = storedPreference(storage, storageKey);
359
+ let disposed = false;
360
+ let mounted = false;
361
+ let openMenu = null;
362
+ let metaRegistration = null;
363
+ const menus = new Set;
364
+ const menuIds = new Set;
365
+ const cleanups = [];
366
+ const resolvedTheme = () => resolveDesignTheme(preference, systemMedia?.matches ?? false);
367
+ const currentThemeColor = () => resolvedTheme() === "dark" ? darkThemeColor : lightThemeColor;
368
+ const closeMenu = (elements, returnFocus) => {
369
+ elements.popover.hidden = true;
370
+ elements.root.removeAttribute("data-open");
371
+ elements.trigger.setAttribute("aria-expanded", "false");
372
+ if (openMenu === elements)
373
+ openMenu = null;
374
+ if (returnFocus)
375
+ elements.trigger.focus();
376
+ };
377
+ const syncMenu = (elements) => {
378
+ elements.root.setAttribute("data-theme-value", preference);
379
+ const currentLabel = designThemeLabel(preference);
380
+ const menuLabel = elements.menu.getAttribute("aria-label")?.trim() || "Appearance";
381
+ elements.trigger.setAttribute("aria-label", `${menuLabel}: ${currentLabel}`);
382
+ elements.trigger.setAttribute("title", `${menuLabel}: ${currentLabel}`);
383
+ const currentHost = elements.trigger.querySelector("[data-current-appearance-icon]");
384
+ if (currentHost === null) {
385
+ throw new Error("Appearance menu trigger is missing its current icon host.");
386
+ }
387
+ currentHost.setAttribute("data-current-appearance-icon", preference);
388
+ renderIconHost(document, currentHost, preference);
389
+ for (const theme of designThemes) {
390
+ const item = elements.items.get(theme);
391
+ if (item === undefined)
392
+ continue;
393
+ const selected = theme === preference;
394
+ item.setAttribute("aria-checked", String(selected));
395
+ item.setAttribute("tabindex", "-1");
396
+ item.toggleAttribute("data-selected", selected);
397
+ const iconHost = item.querySelector("[data-appearance-icon]");
398
+ if (iconHost === null) {
399
+ throw new Error(`Appearance menu ${theme} item is missing its icon host.`);
400
+ }
401
+ renderIconHost(document, iconHost, theme);
402
+ }
403
+ };
404
+ const syncAppearance = () => {
405
+ document.documentElement.setAttribute("data-theme", resolvedTheme());
406
+ metaRegistration?.update(currentThemeColor());
407
+ for (const menu of menus)
408
+ syncMenu(menu);
409
+ };
410
+ const setPreference = (nextPreference, persist) => {
411
+ preference = nextPreference;
412
+ if (persist)
413
+ persistPreference(storage, storageKey, preference);
414
+ syncAppearance();
415
+ };
416
+ const focusForOpen = (elements, target) => {
417
+ if (target === "selected")
418
+ focusItem(elements, preference);
419
+ else {
420
+ const items = orderedItems(elements);
421
+ const item = target === "first" ? items[0] : items.at(-1);
422
+ item?.focus();
423
+ }
424
+ };
425
+ const showMenu = (elements, focus) => {
426
+ if (openMenu !== null && openMenu !== elements)
427
+ closeMenu(openMenu, false);
428
+ openMenu = elements;
429
+ elements.popover.hidden = false;
430
+ elements.root.setAttribute("data-open", "true");
431
+ elements.trigger.setAttribute("aria-expanded", "true");
432
+ focusForOpen(elements, focus);
433
+ };
434
+ const selectItem = (elements, item) => {
435
+ const theme = themeValue(item);
436
+ if (theme === null)
437
+ return;
438
+ setPreference(theme, true);
439
+ closeMenu(elements, true);
440
+ };
441
+ const clearTypeahead = (elements) => {
442
+ elements.typeahead = "";
443
+ if (elements.typeaheadTimer !== null)
444
+ clearTimeout(elements.typeaheadTimer);
445
+ elements.typeaheadTimer = null;
446
+ };
447
+ const typeahead = (elements, key) => {
448
+ if (elements.typeaheadTimer !== null)
449
+ clearTimeout(elements.typeaheadTimer);
450
+ elements.typeahead += key.toLocaleLowerCase();
451
+ const find = (query) => orderedItems(elements).find((item) => (item.textContent ?? "").trim().toLocaleLowerCase().startsWith(query));
452
+ const match = find(elements.typeahead) ?? find(key.toLocaleLowerCase());
453
+ match?.focus();
454
+ elements.typeaheadTimer = setTimeout(() => clearTypeahead(elements), 500);
455
+ };
456
+ const mount = () => {
457
+ if (disposed || mounted)
458
+ return;
459
+ mounted = true;
460
+ for (const root of document.querySelectorAll(menuSelector)) {
461
+ const elements = requireMenuElements(root);
462
+ if (menuIds.has(elements.menu.id)) {
463
+ throw new Error("Appearance menu ids must be unique within the document.");
464
+ }
465
+ menuIds.add(elements.menu.id);
466
+ menus.add(elements);
467
+ syncMenu(elements);
468
+ root.setAttribute("data-display", "icons");
469
+ root.setAttribute("data-presentation", "menu");
470
+ root.setAttribute("data-ready", "true");
471
+ root.removeAttribute("aria-busy");
472
+ elements.trigger.disabled = false;
473
+ elements.trigger.setAttribute("aria-expanded", "false");
474
+ elements.trigger.setAttribute("aria-haspopup", "menu");
475
+ elements.popover.hidden = true;
476
+ const onTriggerClick = () => {
477
+ if (openMenu === elements)
478
+ closeMenu(elements, false);
479
+ else
480
+ showMenu(elements, "selected");
481
+ };
482
+ const onTriggerKeyDown = (event) => {
483
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
484
+ event.preventDefault();
485
+ showMenu(elements, event.key === "ArrowDown" ? "first" : "last");
486
+ } else if (event.key === "Escape" && openMenu === elements) {
487
+ event.preventDefault();
488
+ closeMenu(elements, true);
489
+ }
490
+ };
491
+ const onMenuClick = (event) => {
492
+ const item = menuItemFromEvent(elements, event);
493
+ if (item !== null)
494
+ selectItem(elements, item);
495
+ };
496
+ const onMenuKeyDown = (event) => {
497
+ const item = menuItemFromEvent(elements, event);
498
+ if (event.key === "Escape") {
499
+ event.preventDefault();
500
+ event.stopPropagation();
501
+ closeMenu(elements, true);
502
+ return;
503
+ }
504
+ if (event.key === "Tab") {
505
+ closeMenu(elements, false);
506
+ return;
507
+ }
508
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
509
+ event.preventDefault();
510
+ adjacentItem(elements, item, event.key === "ArrowDown" ? 1 : -1).focus();
511
+ return;
512
+ }
513
+ if (event.key === "Home" || event.key === "End") {
514
+ event.preventDefault();
515
+ const items = orderedItems(elements);
516
+ (event.key === "Home" ? items[0] : items.at(-1))?.focus();
517
+ return;
518
+ }
519
+ if ((event.key === "Enter" || event.key === " ") && item !== null) {
520
+ event.preventDefault();
521
+ selectItem(elements, item);
522
+ return;
523
+ }
524
+ if (event.key.length === 1 && !event.altKey && !event.ctrlKey && !event.metaKey) {
525
+ typeahead(elements, event.key);
526
+ }
527
+ };
528
+ elements.trigger.addEventListener("click", onTriggerClick);
529
+ elements.trigger.addEventListener("keydown", onTriggerKeyDown);
530
+ elements.menu.addEventListener("click", onMenuClick);
531
+ elements.menu.addEventListener("keydown", onMenuKeyDown);
532
+ cleanups.push(() => {
533
+ clearTypeahead(elements);
534
+ elements.trigger.removeEventListener("click", onTriggerClick);
535
+ elements.trigger.removeEventListener("keydown", onTriggerKeyDown);
536
+ elements.menu.removeEventListener("click", onMenuClick);
537
+ elements.menu.removeEventListener("keydown", onMenuKeyDown);
538
+ closeMenu(elements, false);
539
+ elements.root.setAttribute("aria-busy", "true");
540
+ elements.root.setAttribute("data-ready", "false");
541
+ elements.trigger.disabled = true;
542
+ });
543
+ }
544
+ };
545
+ const state = {
546
+ disposed: false,
547
+ publicInstallation: {
548
+ dispose: () => {
549
+ if (disposed)
550
+ return;
551
+ disposed = true;
552
+ state.disposed = true;
553
+ for (const cleanup of cleanups.splice(0).reverse())
554
+ cleanup();
555
+ metaRegistration?.release();
556
+ metaRegistration = null;
557
+ menus.clear();
558
+ if (installationByDocument.get(document) === state) {
559
+ installationByDocument.delete(document);
560
+ }
561
+ },
562
+ preference: () => preference
563
+ }
564
+ };
565
+ const {
566
+ publicInstallation
567
+ } = state;
568
+ installationByDocument.set(document, state);
569
+ try {
570
+ metaRegistration = acquireThemeColorMeta(document, metaName, Symbol("hraness-static-appearance"), currentThemeColor());
571
+ document.documentElement.setAttribute("data-theme", resolvedTheme());
572
+ const onSystemChange = () => {
573
+ if (preference === "system")
574
+ syncAppearance();
575
+ };
576
+ systemMedia?.addEventListener("change", onSystemChange);
577
+ if (systemMedia !== null) {
578
+ cleanups.push(() => systemMedia.removeEventListener("change", onSystemChange));
579
+ }
580
+ const onStorage = (event) => {
581
+ const value = storageEventValue(event, storageKey);
582
+ if (value === undefined)
583
+ return;
584
+ if (isDesignTheme(value)) {
585
+ setPreference(value, false);
586
+ return;
587
+ }
588
+ setPreference(defaultDesignTheme, value !== null);
589
+ };
590
+ view?.addEventListener("storage", onStorage);
591
+ if (view !== null) {
592
+ cleanups.push(() => view.removeEventListener("storage", onStorage));
593
+ }
594
+ const closeForOutsideTarget = (target) => {
595
+ if (openMenu !== null && (!isElement(target) || !openMenu.root.contains(target))) {
596
+ closeMenu(openMenu, false);
597
+ }
598
+ };
599
+ const onDocumentPointerDown = (event) => {
600
+ closeForOutsideTarget(event.target);
601
+ };
602
+ const onDocumentFocusIn = (event) => {
603
+ closeForOutsideTarget(event.target);
604
+ };
605
+ document.addEventListener("pointerdown", onDocumentPointerDown, true);
606
+ document.addEventListener("focusin", onDocumentFocusIn);
607
+ cleanups.push(() => {
608
+ document.removeEventListener("pointerdown", onDocumentPointerDown, true);
609
+ document.removeEventListener("focusin", onDocumentFocusIn);
610
+ });
611
+ if (document.readyState === "loading") {
612
+ const mountAfterParse = () => {
613
+ try {
614
+ mount();
615
+ } catch (error) {
616
+ publicInstallation.dispose();
617
+ throw error;
618
+ }
619
+ };
620
+ document.addEventListener("DOMContentLoaded", mountAfterParse, {
621
+ once: true
622
+ });
623
+ cleanups.push(() => document.removeEventListener("DOMContentLoaded", mountAfterParse));
624
+ } else {
625
+ mount();
626
+ }
627
+ } catch (error) {
628
+ publicInstallation.dispose();
629
+ throw error;
630
+ }
631
+ return publicInstallation;
632
+ }
633
+
634
+ // src/browser/artifact-share.ts
635
+ function nonblank(value, label) {
636
+ if (value.trim().length === 0) {
637
+ throw new RangeError(`${label} must contain a non-whitespace character.`);
638
+ }
639
+ return value;
640
+ }
641
+ function publicWebUrl(value) {
642
+ const normalized = nonblank(value, "An artifact share URL").trim();
643
+ let url;
644
+ try {
645
+ url = new URL(normalized);
646
+ } catch {
647
+ throw new RangeError("An artifact share URL must be an absolute URL.");
648
+ }
649
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
650
+ throw new RangeError("An artifact share URL must use HTTP or HTTPS.");
651
+ }
652
+ return url.href;
653
+ }
654
+ function normalizedIntent(input) {
655
+ return {
656
+ text: nonblank(input.text, "Artifact share text"),
657
+ url: publicWebUrl(input.url)
658
+ };
659
+ }
660
+ function buildXShareIntentUrl(input) {
661
+ const {
662
+ text,
663
+ url
664
+ } = normalizedIntent(input);
665
+ const intent = new URL("https://x.com/intent/post");
666
+ intent.searchParams.set("text", text);
667
+ intent.searchParams.set("url", url);
668
+ return intent.href;
669
+ }
670
+ function buildLinkedInShareIntentUrl(url) {
671
+ const intent = new URL("https://www.linkedin.com/sharing/share-offsite/");
672
+ intent.searchParams.set("url", publicWebUrl(url));
673
+ return intent.href;
674
+ }
675
+ function buildBlueskyShareIntentUrl(input) {
676
+ const {
677
+ text,
678
+ url
679
+ } = normalizedIntent(input);
680
+ const intent = new URL("https://bsky.app/intent/compose");
681
+ intent.searchParams.set("text", `${text}
682
+ ${url}`);
683
+ return intent.href;
684
+ }
685
+ async function copyTextToClipboard(text) {
686
+ const clipboard = globalThis.navigator?.clipboard;
687
+ if (clipboard === undefined || typeof clipboard.writeText !== "function") {
688
+ throw new Error("Clipboard text writing is unavailable in this environment.");
689
+ }
690
+ await clipboard.writeText(text);
691
+ }
692
+ function downloadBlob(blob, filename) {
693
+ const normalizedFilename = nonblank(filename, "A download filename").trim();
694
+ const document = globalThis.document;
695
+ if (document === undefined || typeof URL.createObjectURL !== "function" || typeof URL.revokeObjectURL !== "function") {
696
+ throw new Error("Blob downloads are unavailable in this environment.");
697
+ }
698
+ const objectUrl = URL.createObjectURL(blob);
699
+ const anchor = document.createElement("a");
700
+ anchor.download = normalizedFilename;
701
+ anchor.hidden = true;
702
+ anchor.href = objectUrl;
703
+ anchor.rel = "noopener";
704
+ document.body.append(anchor);
705
+ try {
706
+ anchor.click();
707
+ } finally {
708
+ anchor.remove();
709
+ globalThis.setTimeout(() => URL.revokeObjectURL(objectUrl), 0);
710
+ }
711
+ }
712
+ function isAbortError(error) {
713
+ return typeof error === "object" && error !== null && "name" in error && error.name === "AbortError";
714
+ }
715
+ function filesOnlyShareData(file) {
716
+ return {
717
+ files: [file]
718
+ };
719
+ }
720
+ function nativeFileShareNavigator() {
721
+ const shareNavigator = globalThis.navigator;
722
+ if (shareNavigator === undefined || typeof shareNavigator.canShare !== "function" || typeof shareNavigator.share !== "function") {
723
+ return;
724
+ }
725
+ return shareNavigator;
726
+ }
727
+ function canShareFileWithNavigator(shareNavigator, file) {
728
+ try {
729
+ return shareNavigator.canShare(filesOnlyShareData(file));
730
+ } catch {
731
+ return false;
732
+ }
733
+ }
734
+ function canShareFileNatively(file) {
735
+ const shareNavigator = nativeFileShareNavigator();
736
+ return shareNavigator !== undefined && canShareFileWithNavigator(shareNavigator, file);
737
+ }
738
+ async function shareFileNatively(file) {
739
+ const shareNavigator = nativeFileShareNavigator();
740
+ if (shareNavigator === undefined || !canShareFileWithNavigator(shareNavigator, file)) {
741
+ return {
742
+ kind: "unavailable"
743
+ };
744
+ }
745
+ try {
746
+ await shareNavigator.share(filesOnlyShareData(file));
747
+ return {
748
+ kind: "shared"
749
+ };
750
+ } catch (error) {
751
+ if (isAbortError(error))
752
+ return {
753
+ kind: "cancelled"
754
+ };
755
+ return {
756
+ error,
757
+ kind: "failed"
758
+ };
759
+ }
760
+ }
761
+ // src/palette-color.ts
762
+ function channels(hex) {
763
+ if (!/^#[0-9a-f]{6}$/iu.test(hex))
764
+ throw new Error("Palette colors must be six-digit hex values.");
765
+ return [Number.parseInt(hex.slice(1, 3), 16), Number.parseInt(hex.slice(3, 5), 16), Number.parseInt(hex.slice(5, 7), 16)];
766
+ }
767
+ function mixPaletteColor(color, toward, amount) {
768
+ const target = channels(toward);
769
+ return `#${channels(color).map((value, index) => Math.round(value * (1 - amount) + (target[index] ?? 0) * amount).toString(16).padStart(2, "0")).join("")}`;
770
+ }
771
+ function luminance(hex) {
772
+ const linearize = (channel) => {
773
+ const value = channel / 255;
774
+ return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
775
+ };
776
+ const [red, green, blue] = channels(hex);
777
+ return linearize(red) * 0.2126 + linearize(green) * 0.7152 + linearize(blue) * 0.0722;
778
+ }
779
+ function paletteContrast(a, b) {
780
+ const first = luminance(a);
781
+ const second = luminance(b);
782
+ return (Math.max(first, second) + 0.05) / (Math.min(first, second) + 0.05);
783
+ }
784
+ function readablePaletteColor(color, toward, backgrounds, minimum) {
785
+ for (let step = 0;step <= 100; step += 1) {
786
+ const candidate = mixPaletteColor(color, toward, step / 100);
787
+ if (backgrounds.every((background) => paletteContrast(candidate, background) >= minimum))
788
+ return candidate;
789
+ }
790
+ throw new Error("The authored palette cannot meet its contrast contract.");
791
+ }
792
+
793
+ // src/palettes.ts
794
+ var designPalettes = ["catppuccin", "gruvbox", "rose-pine", "tokyo-night", "paper"];
795
+ function isDesignPalette(value) {
796
+ return typeof value === "string" && designPalettes.some((palette) => palette === value);
797
+ }
798
+ var designPaletteSources = {
799
+ paper: {
800
+ light: {
801
+ background: "#f8f7f4",
802
+ surface: "#fffefa",
803
+ raised: "#fffefa",
804
+ hover: "#ebe8e3",
805
+ text: "#1c1917",
806
+ muted: "#6f6962",
807
+ border: "#8b8278",
808
+ primary: "#1f5eea",
809
+ danger: "#b23c34",
810
+ warning: "#a65f00",
811
+ success: "#26814d",
812
+ info: "#1f5eea",
813
+ violet: "#7959a5",
814
+ rose: "#a44b65"
815
+ },
816
+ dark: {
817
+ background: "#12100f",
818
+ surface: "#1d1a18",
819
+ raised: "#211d1a",
820
+ hover: "#292522",
821
+ text: "#f5f2ed",
822
+ muted: "#aaa29a",
823
+ border: "#827a72",
824
+ primary: "#8fb0ff",
825
+ danger: "#ef8c82",
826
+ warning: "#efae55",
827
+ success: "#5fc98b",
828
+ info: "#8fb0ff",
829
+ violet: "#b7a0dc",
830
+ rose: "#db91aa"
831
+ }
832
+ },
833
+ catppuccin: {
834
+ dark: {
835
+ background: "#1e1e2e",
836
+ surface: "#181825",
837
+ raised: "#313244",
838
+ hover: "#45475a",
839
+ text: "#cdd6f4",
840
+ muted: "#a6adc8",
841
+ border: "#7f849c",
842
+ primary: "#89b4fa",
843
+ danger: "#f38ba8",
844
+ warning: "#f9e2af",
845
+ success: "#a6e3a1",
846
+ info: "#89dceb",
847
+ violet: "#cba6f7",
848
+ rose: "#f5c2e7"
849
+ },
850
+ light: {
851
+ background: "#eff1f5",
852
+ surface: "#e6e9ef",
853
+ raised: "#dce0e8",
854
+ hover: "#ccd0da",
855
+ text: "#4c4f69",
856
+ muted: "#6c6f85",
857
+ border: "#7c7f93",
858
+ primary: "#1e66f5",
859
+ danger: "#d20f39",
860
+ warning: "#df8e1d",
861
+ success: "#40a02b",
862
+ info: "#179299",
863
+ violet: "#8839ef",
864
+ rose: "#ea76cb"
865
+ }
866
+ },
867
+ gruvbox: {
868
+ dark: {
869
+ background: "#282828",
870
+ surface: "#1d2021",
871
+ raised: "#3c3836",
872
+ hover: "#504945",
873
+ text: "#ebdbb2",
874
+ muted: "#bdae93",
875
+ border: "#928374",
876
+ primary: "#83a598",
877
+ danger: "#fb4934",
878
+ warning: "#fabd2f",
879
+ success: "#b8bb26",
880
+ info: "#8ec07c",
881
+ violet: "#d3869b",
882
+ rose: "#fe8019"
883
+ },
884
+ light: {
885
+ background: "#fbf1c7",
886
+ surface: "#f9f5d7",
887
+ raised: "#ebdbb2",
888
+ hover: "#d5c4a1",
889
+ text: "#3c3836",
890
+ muted: "#665c54",
891
+ border: "#7c6f64",
892
+ primary: "#076678",
893
+ danger: "#9d0006",
894
+ warning: "#b57614",
895
+ success: "#79740e",
896
+ info: "#427b58",
897
+ violet: "#8f3f71",
898
+ rose: "#af3a03"
899
+ }
900
+ },
901
+ "rose-pine": {
902
+ dark: {
903
+ background: "#191724",
904
+ surface: "#1f1d2e",
905
+ raised: "#26233a",
906
+ hover: "#403d52",
907
+ text: "#e0def4",
908
+ muted: "#908caa",
909
+ border: "#908caa",
910
+ primary: "#c4a7e7",
911
+ danger: "#eb6f92",
912
+ warning: "#f6c177",
913
+ success: "#9ccfd8",
914
+ info: "#ebbcba",
915
+ violet: "#c4a7e7",
916
+ rose: "#ebbcba"
917
+ },
918
+ light: {
919
+ background: "#faf4ed",
920
+ surface: "#fffaf3",
921
+ raised: "#f2e9e1",
922
+ hover: "#dfdad9",
923
+ text: "#575279",
924
+ muted: "#797593",
925
+ border: "#797593",
926
+ primary: "#907aa9",
927
+ danger: "#b4637a",
928
+ warning: "#ea9d34",
929
+ success: "#286983",
930
+ info: "#56949f",
931
+ violet: "#907aa9",
932
+ rose: "#d7827e"
933
+ }
934
+ },
935
+ "tokyo-night": {
936
+ dark: {
937
+ background: "#1a1b26",
938
+ surface: "#16161e",
939
+ raised: "#24283b",
940
+ hover: "#292e42",
941
+ text: "#c0caf5",
942
+ muted: "#a9b1d6",
943
+ border: "#737aa2",
944
+ primary: "#7aa2f7",
945
+ danger: "#f7768e",
946
+ warning: "#e0af68",
947
+ success: "#9ece6a",
948
+ info: "#7dcfff",
949
+ violet: "#bb9af7",
950
+ rose: "#ff9e64"
951
+ },
952
+ light: {
953
+ background: "#e1e2e7",
954
+ surface: "#d0d5e3",
955
+ raised: "#c4c8da",
956
+ hover: "#b7c1e3",
957
+ text: "#3760bf",
958
+ muted: "#6172b0",
959
+ border: "#6172b0",
960
+ primary: "#2e7de9",
961
+ danger: "#f52a65",
962
+ warning: "#8c6c3e",
963
+ success: "#587539",
964
+ info: "#007197",
965
+ violet: "#9854f1",
966
+ rose: "#b15c00"
967
+ }
968
+ }
969
+ };
970
+ function createPalette(source, mode) {
971
+ const surfaces = [source.background, source.surface, source.raised, source.hover];
972
+ const endpoint = mode === "dark" ? "#ffffff" : "#000000";
973
+ const foreground = readablePaletteColor(source.text, endpoint, surfaces, 7);
974
+ const muted = readablePaletteColor(source.muted, endpoint, surfaces, 4.6);
975
+ const status = (seed) => {
976
+ const soft = mixPaletteColor(source.background, seed, 0.12);
977
+ const color = readablePaletteColor(seed, endpoint, [...surfaces, soft], 4.6);
978
+ const onColor = paletteContrast(color, source.background) >= 4.5 ? source.background : endpoint === "#ffffff" ? "#000000" : "#ffffff";
979
+ return {
980
+ color,
981
+ foreground: onColor,
982
+ soft
983
+ };
984
+ };
985
+ const primary = status(source.primary);
986
+ const danger = status(source.danger);
987
+ const warning = status(source.warning);
988
+ const success = status(source.success);
989
+ const info = status(source.info);
990
+ return Object.freeze({
991
+ background: source.background,
992
+ foreground,
993
+ muted,
994
+ faint: muted,
995
+ grid: source.raised,
996
+ line: source.hover,
997
+ controlBorder: readablePaletteColor(source.border, endpoint, surfaces, 3.1),
998
+ surface: source.surface,
999
+ surfaceRaised: source.raised,
1000
+ surfaceHover: source.hover,
1001
+ card: source.surface,
1002
+ cardForeground: foreground,
1003
+ popover: source.raised,
1004
+ popoverForeground: foreground,
1005
+ primary: primary.color,
1006
+ primaryForeground: primary.foreground,
1007
+ primarySoft: primary.soft,
1008
+ secondary: source.raised,
1009
+ secondaryForeground: foreground,
1010
+ accent: primary.soft,
1011
+ accentForeground: foreground,
1012
+ focus: primary.color,
1013
+ scrim: mode === "dark" ? "#000000b8" : "#00000070",
1014
+ disabled: source.raised,
1015
+ disabledForeground: muted,
1016
+ inverse: foreground,
1017
+ inverseForeground: source.background,
1018
+ danger: danger.color,
1019
+ dangerForeground: danger.foreground,
1020
+ dangerSoft: danger.soft,
1021
+ warning: warning.color,
1022
+ warningForeground: warning.foreground,
1023
+ warningSoft: warning.soft,
1024
+ success: success.color,
1025
+ successForeground: success.foreground,
1026
+ successSoft: success.soft,
1027
+ info: info.color,
1028
+ infoForeground: info.foreground,
1029
+ infoSoft: info.soft,
1030
+ chart1: readablePaletteColor(source.rose, endpoint, surfaces, 4.6),
1031
+ chart2: success.color,
1032
+ chart3: info.color,
1033
+ chart4: warning.color,
1034
+ chart5: readablePaletteColor(source.violet, endpoint, surfaces, 4.6)
1035
+ });
1036
+ }
1037
+ var paletteColors = Object.freeze({
1038
+ paper: Object.freeze({
1039
+ light: Object.freeze({
1040
+ ...createPalette(designPaletteSources.paper.light, "light"),
1041
+ grid: "#dfdcd6",
1042
+ line: "#b9b3ab",
1043
+ secondary: "#ebe8e3"
1044
+ }),
1045
+ dark: Object.freeze({
1046
+ ...createPalette(designPaletteSources.paper.dark, "dark"),
1047
+ grid: "#302b27",
1048
+ line: "#514a44",
1049
+ secondary: "#292522"
1050
+ })
1051
+ }),
1052
+ catppuccin: Object.freeze({
1053
+ light: createPalette(designPaletteSources.catppuccin.light, "light"),
1054
+ dark: createPalette(designPaletteSources.catppuccin.dark, "dark")
1055
+ }),
1056
+ gruvbox: Object.freeze({
1057
+ light: createPalette(designPaletteSources.gruvbox.light, "light"),
1058
+ dark: createPalette(designPaletteSources.gruvbox.dark, "dark")
1059
+ }),
1060
+ "rose-pine": Object.freeze({
1061
+ light: createPalette(designPaletteSources["rose-pine"].light, "light"),
1062
+ dark: createPalette(designPaletteSources["rose-pine"].dark, "dark")
1063
+ }),
1064
+ "tokyo-night": Object.freeze({
1065
+ light: createPalette(designPaletteSources["tokyo-night"].light, "light"),
1066
+ dark: createPalette(designPaletteSources["tokyo-night"].dark, "dark")
1067
+ })
1068
+ });
1069
+
1070
+ // src/palette-appearance.ts
1071
+ var defaultDesignPalettePreference = Object.freeze({
1072
+ palette: "catppuccin",
1073
+ mode: "dark"
1074
+ });
1075
+ var designPaletteStorageKey = "hraness-design-palette-v1";
1076
+ function parseDesignPalettePreference(value) {
1077
+ if (typeof value === "string") {
1078
+ if (value.length > 256)
1079
+ return null;
1080
+ try {
1081
+ value = JSON.parse(value);
1082
+ } catch {
1083
+ return null;
1084
+ }
1085
+ }
1086
+ if (typeof value !== "object" || value === null || Array.isArray(value))
1087
+ return null;
1088
+ const record = value;
1089
+ if (!isDesignPalette(record.palette) || !isDesignTheme(record.mode))
1090
+ return null;
1091
+ return Object.freeze({
1092
+ palette: record.palette,
1093
+ mode: record.mode
1094
+ });
1095
+ }
1096
+ function normalizeDesignPalettePreference(value, fallback = defaultDesignPalettePreference) {
1097
+ return parseDesignPalettePreference(value) ?? parseDesignPalettePreference(fallback) ?? defaultDesignPalettePreference;
1098
+ }
1099
+ function resolveDesignPalettePreference(preference, systemPrefersDark) {
1100
+ return {
1101
+ palette: preference.palette,
1102
+ mode: resolveDesignTheme(preference.mode, systemPrefersDark)
1103
+ };
1104
+ }
1105
+
1106
+ // src/palette-themes.ts
1107
+ import * as stylex from "@stylexjs/stylex";
1108
+
1109
+ // src/palette-tokens.stylex.ts
1110
+ var catppuccinLight = {
1111
+ x18acsur: "xadus2s x18acsur",
1112
+ $$css: true
1113
+ };
1114
+ var catppuccinDark = {
1115
+ x18acsur: "x18wthyl x18acsur",
1116
+ $$css: true
1117
+ };
1118
+ var gruvboxLight = {
1119
+ x18acsur: "xdktuxt x18acsur",
1120
+ $$css: true
1121
+ };
1122
+ var gruvboxDark = {
1123
+ x18acsur: "x1fsd05x x18acsur",
1124
+ $$css: true
1125
+ };
1126
+ var rosePineLight = {
1127
+ x18acsur: "x131glkb x18acsur",
1128
+ $$css: true
1129
+ };
1130
+ var rosePineDark = {
1131
+ x18acsur: "xivpxii x18acsur",
1132
+ $$css: true
1133
+ };
1134
+ var tokyoNightLight = {
1135
+ x18acsur: "x11phk89 x18acsur",
1136
+ $$css: true
1137
+ };
1138
+ var tokyoNightDark = {
1139
+ x18acsur: "xgp1gpt x18acsur",
1140
+ $$css: true
1141
+ };
1142
+ var paperLight = {
1143
+ x18acsur: "xjfxk3y x18acsur",
1144
+ $$css: true
1145
+ };
1146
+ var paperDark = {
1147
+ x18acsur: "x1lresar x18acsur",
1148
+ $$css: true
1149
+ };
1150
+
1151
+ // src/palette-themes.ts
1152
+ var classes = {
1153
+ paper: {
1154
+ light: stylex.props(paperLight).className,
1155
+ dark: stylex.props(paperDark).className
1156
+ },
1157
+ catppuccin: {
1158
+ light: stylex.props(catppuccinLight).className,
1159
+ dark: stylex.props(catppuccinDark).className
1160
+ },
1161
+ gruvbox: {
1162
+ light: stylex.props(gruvboxLight).className,
1163
+ dark: stylex.props(gruvboxDark).className
1164
+ },
1165
+ "rose-pine": {
1166
+ light: stylex.props(rosePineLight).className,
1167
+ dark: stylex.props(rosePineDark).className
1168
+ },
1169
+ "tokyo-night": {
1170
+ light: stylex.props(tokyoNightLight).className,
1171
+ dark: stylex.props(tokyoNightDark).className
1172
+ }
1173
+ };
1174
+ function getDesignPaletteTheme(palette, mode) {
1175
+ return {
1176
+ className: `hraness-palette ${classes[palette][mode]}`,
1177
+ background: paletteColors[palette][mode].background
1178
+ };
1179
+ }
1180
+
1181
+ // src/browser/design-palette.ts
1182
+ var installationKey = Symbol.for("hraness.design-palette.controller.v1");
1183
+ var darkQuery = "(prefers-color-scheme: dark)";
1184
+ function designPaletteSnapshot(preference, systemPrefersDark, isForced = false) {
1185
+ const resolved = resolveDesignPalettePreference(preference, systemPrefersDark);
1186
+ const theme = getDesignPaletteTheme(resolved.palette, resolved.mode);
1187
+ return Object.freeze({
1188
+ preference: Object.freeze({
1189
+ ...preference
1190
+ }),
1191
+ resolvedMode: resolved.mode,
1192
+ className: theme.className,
1193
+ background: theme.background,
1194
+ isForced
1195
+ });
1196
+ }
1197
+ function readStorage(storage, key) {
1198
+ try {
1199
+ return storage?.getItem(key);
1200
+ } catch {
1201
+ return;
1202
+ }
1203
+ }
1204
+ function writeStorage(storage, key, preference) {
1205
+ try {
1206
+ storage?.setItem(key, JSON.stringify(preference));
1207
+ } catch {}
1208
+ }
1209
+ function initDesignPalette(options = {}) {
1210
+ const document = options.document ?? globalThis.document;
1211
+ if (document === undefined || document.documentElement === null || document.head === null) {
1212
+ throw new Error("initDesignPalette requires an HTML document with a head.");
1213
+ }
1214
+ const view = document.defaultView;
1215
+ const storageKey = options.storageKey ?? designPaletteStorageKey;
1216
+ const legacyStorageKey = options.legacyStorageKey === undefined ? designThemeStorageKey : options.legacyStorageKey;
1217
+ if (storageKey.trim() === "" || legacyStorageKey?.trim() === "")
1218
+ throw new Error("Appearance storage keys must not be blank.");
1219
+ const fallback = normalizeDesignPalettePreference(options.defaultPreference);
1220
+ const forced = options.forcedPreference === undefined ? null : parseDesignPalettePreference(options.forcedPreference);
1221
+ if (options.forcedPreference !== undefined && (forced === null || forced.mode === "system")) {
1222
+ throw new Error("A forced palette requires a valid palette and a light or dark mode.");
1223
+ }
1224
+ const configuration = JSON.stringify({
1225
+ storageKey,
1226
+ legacyStorageKey,
1227
+ fallback,
1228
+ forced
1229
+ });
1230
+ const ownerDocument = document;
1231
+ const previous = ownerDocument[installationKey];
1232
+ if (previous !== undefined) {
1233
+ if (previous.version !== 1 || previous.configuration !== configuration) {
1234
+ throw new Error("This document already has a different palette configuration.");
1235
+ }
1236
+ return previous.acquire();
1237
+ }
1238
+ let storage = options.storage ?? null;
1239
+ const usesDefaultStorage = options.storage === undefined;
1240
+ if (usesDefaultStorage && forced === null) {
1241
+ try {
1242
+ storage = view?.localStorage ?? null;
1243
+ } catch {}
1244
+ }
1245
+ let media = null;
1246
+ try {
1247
+ media = view?.matchMedia?.(darkQuery) ?? null;
1248
+ } catch {}
1249
+ const readPreference = () => {
1250
+ if (forced !== null)
1251
+ return forced;
1252
+ const raw = readStorage(storage, storageKey);
1253
+ const saved = parseDesignPalettePreference(raw);
1254
+ if (saved !== null)
1255
+ return saved;
1256
+ if ((raw === null || raw === undefined) && legacyStorageKey !== null) {
1257
+ const legacy = readStorage(storage, legacyStorageKey);
1258
+ if (isDesignTheme(legacy))
1259
+ return Object.freeze({
1260
+ palette: fallback.palette,
1261
+ mode: legacy
1262
+ });
1263
+ }
1264
+ return fallback;
1265
+ };
1266
+ let snapshot = designPaletteSnapshot(readPreference(), media?.matches ?? false, forced !== null);
1267
+ const listeners = new Set;
1268
+ const ownedClasses = new Set(designPalettes.flatMap((palette) => ["light", "dark"].flatMap((mode) => getDesignPaletteTheme(palette, mode).className.split(/\s+/u))));
1269
+ const themeColor = acquireThemeColorMeta(document, "theme-color", Symbol("palette-theme-color"), snapshot.background);
1270
+ const apply = () => {
1271
+ const root = document.documentElement;
1272
+ const nextClasses = new Set(snapshot.className.split(/\s+/u).filter(Boolean));
1273
+ for (const token of ownedClasses)
1274
+ if (token !== "" && !nextClasses.has(token))
1275
+ root.classList.remove(token);
1276
+ for (const token of nextClasses)
1277
+ root.classList.add(token);
1278
+ root.setAttribute("data-palette", snapshot.preference.palette);
1279
+ root.setAttribute("data-theme", snapshot.resolvedMode);
1280
+ themeColor.update(snapshot.background);
1281
+ };
1282
+ const update = (preference) => {
1283
+ const next = designPaletteSnapshot(preference, media?.matches ?? false, forced !== null);
1284
+ if (next.preference.palette === snapshot.preference.palette && next.preference.mode === snapshot.preference.mode && next.resolvedMode === snapshot.resolvedMode)
1285
+ return false;
1286
+ snapshot = next;
1287
+ apply();
1288
+ for (const listener of listeners)
1289
+ listener();
1290
+ return true;
1291
+ };
1292
+ const onMedia = () => {
1293
+ update(snapshot.preference);
1294
+ };
1295
+ const onStorage = (event) => {
1296
+ if (forced !== null || event.key !== storageKey && event.key !== null)
1297
+ return;
1298
+ if (usesDefaultStorage && event.storageArea !== null && event.storageArea !== undefined && event.storageArea !== storage)
1299
+ return;
1300
+ update(readPreference());
1301
+ };
1302
+ if (forced === null) {
1303
+ writeStorage(storage, storageKey, snapshot.preference);
1304
+ view?.addEventListener("storage", onStorage);
1305
+ media?.addEventListener("change", onMedia);
1306
+ }
1307
+ let owners = 0;
1308
+ const installation = {
1309
+ version: 1,
1310
+ configuration,
1311
+ acquire: () => {
1312
+ apply();
1313
+ owners += 1;
1314
+ let disposed = false;
1315
+ const subscriptions = new Set;
1316
+ return {
1317
+ getSnapshot: () => snapshot,
1318
+ subscribe: (listener) => {
1319
+ if (disposed)
1320
+ return () => {
1321
+ return;
1322
+ };
1323
+ const subscription = () => {
1324
+ listener();
1325
+ };
1326
+ subscriptions.add(subscription);
1327
+ listeners.add(subscription);
1328
+ return () => {
1329
+ subscriptions.delete(subscription);
1330
+ listeners.delete(subscription);
1331
+ };
1332
+ },
1333
+ setPreference: (value) => {
1334
+ if (disposed || forced !== null)
1335
+ return false;
1336
+ const preference = parseDesignPalettePreference(value);
1337
+ if (preference === null)
1338
+ throw new Error("Choose a supported palette and appearance mode.");
1339
+ writeStorage(storage, storageKey, preference);
1340
+ return update(preference);
1341
+ },
1342
+ dispose: () => {
1343
+ if (disposed)
1344
+ return;
1345
+ disposed = true;
1346
+ for (const listener of subscriptions)
1347
+ listeners.delete(listener);
1348
+ subscriptions.clear();
1349
+ owners -= 1;
1350
+ if (owners !== 0)
1351
+ return;
1352
+ view?.removeEventListener("storage", onStorage);
1353
+ media?.removeEventListener("change", onMedia);
1354
+ themeColor.release();
1355
+ if (ownerDocument[installationKey] === installation)
1356
+ Reflect.deleteProperty(ownerDocument, installationKey);
1357
+ }
1358
+ };
1359
+ }
1360
+ };
1361
+ ownerDocument[installationKey] = installation;
1362
+ return installation.acquire();
1363
+ }
1364
+ // src/browser/foil.ts
1365
+ var REST = 50;
1366
+ var SETTLE = 0.05;
1367
+ var RESPONSE_MS = 85;
1368
+ var MIN_LIGHT = 8;
1369
+ var MAX_LIGHT = 92;
1370
+ var INPUTS = ["--hraness-foil-x", "--hraness-foil-y"];
1371
+ var clampLight = (value) => Math.max(MIN_LIGHT, Math.min(MAX_LIGHT, value));
1372
+ function attachFoil(root) {
1373
+ const document = root.ownerDocument;
1374
+ const view = document.defaultView;
1375
+ if (!view || typeof view.matchMedia !== "function" || typeof view.requestAnimationFrame !== "function" || typeof view.cancelAnimationFrame !== "function")
1376
+ return () => {};
1377
+ const preference = view.matchMedia("(hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) and (forced-colors: none)");
1378
+ const states = new Map;
1379
+ let targets = [];
1380
+ let needsTargets = true;
1381
+ let pointer;
1382
+ let frame = null;
1383
+ let previousTime;
1384
+ let detached = false;
1385
+ const restore = (target) => {
1386
+ for (const input of INPUTS)
1387
+ target.style.removeProperty(input);
1388
+ };
1389
+ const reset = () => {
1390
+ if (frame !== null)
1391
+ view.cancelAnimationFrame(frame);
1392
+ frame = null;
1393
+ previousTime = undefined;
1394
+ pointer = undefined;
1395
+ for (const target of states.keys())
1396
+ restore(target);
1397
+ states.clear();
1398
+ targets = [];
1399
+ needsTargets = true;
1400
+ };
1401
+ const refreshTargets = () => {
1402
+ targets = [...root.matches("[data-foil]") ? [root] : [], ...root.querySelectorAll("[data-foil]")].filter((target) => {
1403
+ const ancestor = target.parentElement?.closest("[data-foil]");
1404
+ return !ancestor || !root.contains(ancestor);
1405
+ });
1406
+ const live = new Set(targets);
1407
+ for (const target of states.keys()) {
1408
+ if (!live.has(target)) {
1409
+ restore(target);
1410
+ states.delete(target);
1411
+ }
1412
+ }
1413
+ needsTargets = false;
1414
+ };
1415
+ const paint = (time) => {
1416
+ frame = null;
1417
+ if (detached || !preference.matches || document.hidden || !pointer) {
1418
+ reset();
1419
+ return;
1420
+ }
1421
+ if (needsTargets)
1422
+ refreshTargets();
1423
+ const elapsed = previousTime === undefined ? 1000 / 60 : Math.max(0, Math.min(64, time - previousTime));
1424
+ previousTime = time;
1425
+ const ease = 1 - Math.exp(-elapsed / RESPONSE_MS);
1426
+ const measured = targets.map((target) => ({
1427
+ target,
1428
+ bounds: target.getBoundingClientRect()
1429
+ }));
1430
+ let moving = false;
1431
+ for (const {
1432
+ target,
1433
+ bounds
1434
+ } of measured) {
1435
+ if (!target.isConnected || !root.contains(target) || bounds.width <= 0 || bounds.height <= 0 || bounds.bottom <= 0 || bounds.right <= 0 || bounds.top >= view.innerHeight || bounds.left >= view.innerWidth) {
1436
+ if (states.has(target)) {
1437
+ restore(target);
1438
+ states.delete(target);
1439
+ }
1440
+ continue;
1441
+ }
1442
+ const goal = {
1443
+ x: clampLight(REST + (pointer.x - bounds.left - bounds.width / 2) / Math.max(160, bounds.width) * 60),
1444
+ y: clampLight(REST + (pointer.y - bounds.top - bounds.height / 2) / Math.max(120, bounds.height) * 50)
1445
+ };
1446
+ const light = states.get(target) ?? {
1447
+ x: REST,
1448
+ y: REST
1449
+ };
1450
+ for (const axis of ["x", "y"]) {
1451
+ const distance = goal[axis] - light[axis];
1452
+ if (Math.abs(distance) > SETTLE) {
1453
+ light[axis] += distance * ease;
1454
+ moving = true;
1455
+ } else
1456
+ light[axis] = goal[axis];
1457
+ }
1458
+ states.set(target, light);
1459
+ for (const axis of ["x", "y"]) {
1460
+ const input = `--hraness-foil-${axis}`;
1461
+ const value = `${light[axis].toFixed(2)}%`;
1462
+ if (target.style.getPropertyValue(input) !== value)
1463
+ target.style.setProperty(input, value);
1464
+ }
1465
+ }
1466
+ if (moving)
1467
+ frame = view.requestAnimationFrame(paint);
1468
+ else
1469
+ previousTime = undefined;
1470
+ };
1471
+ const move = (event) => {
1472
+ if (!preference.matches || document.hidden || event.pointerType === "touch") {
1473
+ reset();
1474
+ return;
1475
+ }
1476
+ if (!Number.isFinite(event.clientX) || !Number.isFinite(event.clientY))
1477
+ return;
1478
+ pointer = {
1479
+ x: event.clientX,
1480
+ y: event.clientY
1481
+ };
1482
+ needsTargets = true;
1483
+ if (frame === null)
1484
+ frame = view.requestAnimationFrame(paint);
1485
+ };
1486
+ const leave = (event) => {
1487
+ if (event.relatedTarget === null)
1488
+ reset();
1489
+ };
1490
+ const visibility = () => {
1491
+ if (document.hidden)
1492
+ reset();
1493
+ };
1494
+ view.addEventListener("pointermove", move, {
1495
+ passive: true
1496
+ });
1497
+ view.addEventListener("pointerdown", move, {
1498
+ passive: true
1499
+ });
1500
+ view.addEventListener("pointerout", leave, {
1501
+ passive: true
1502
+ });
1503
+ view.addEventListener("pointercancel", reset);
1504
+ view.addEventListener("blur", reset);
1505
+ view.addEventListener("scroll", reset, {
1506
+ passive: true,
1507
+ capture: true
1508
+ });
1509
+ view.addEventListener("resize", reset, {
1510
+ passive: true
1511
+ });
1512
+ document.addEventListener("visibilitychange", visibility);
1513
+ preference.addEventListener("change", reset);
1514
+ return () => {
1515
+ detached = true;
1516
+ reset();
1517
+ view.removeEventListener("pointermove", move);
1518
+ view.removeEventListener("pointerdown", move);
1519
+ view.removeEventListener("pointerout", leave);
1520
+ view.removeEventListener("pointercancel", reset);
1521
+ view.removeEventListener("blur", reset);
1522
+ view.removeEventListener("scroll", reset, true);
1523
+ view.removeEventListener("resize", reset);
1524
+ document.removeEventListener("visibilitychange", visibility);
1525
+ preference.removeEventListener("change", reset);
1526
+ };
1527
+ }
1528
+ // src/browser/hero-light.ts
1529
+ function attachHeroLight(root) {
1530
+ return () => {};
1531
+ }
1532
+ // src/status-page.ts
1533
+ var STATUS_PAGE_MAX_ROUTES = 2000;
1534
+ function isSitePath(href) {
1535
+ return /^\/(?![/\\])/u.test(href);
1536
+ }
1537
+ function parseStatusPageRoutes(value) {
1538
+ if (!value)
1539
+ return [];
1540
+ let parsed;
1541
+ try {
1542
+ parsed = JSON.parse(value);
1543
+ } catch {
1544
+ return [];
1545
+ }
1546
+ if (!Array.isArray(parsed))
1547
+ return [];
1548
+ const routes = [];
1549
+ for (const entry of parsed.slice(0, STATUS_PAGE_MAX_ROUTES)) {
1550
+ if (!Array.isArray(entry))
1551
+ continue;
1552
+ const [href, label] = entry;
1553
+ if (typeof href !== "string" || typeof label !== "string")
1554
+ continue;
1555
+ if (!isSitePath(href) || label.trim() === "")
1556
+ continue;
1557
+ routes.push({
1558
+ href,
1559
+ label
1560
+ });
1561
+ }
1562
+ return routes;
1563
+ }
1564
+ var MAX_PATH = 160;
1565
+ function normalizeStatusPath(value) {
1566
+ let path = value.slice(0, MAX_PATH * 4);
1567
+ const end = path.search(/[?#]/u);
1568
+ if (end !== -1)
1569
+ path = path.slice(0, end);
1570
+ try {
1571
+ path = decodeURIComponent(path);
1572
+ } catch {}
1573
+ path = path.toLowerCase().split("/").filter((part, index) => index === 0 || part !== "").join("/");
1574
+ path = path.replace(/(?:\/index)?\.html?$/u, "");
1575
+ while (path.length > 1 && path.endsWith("/"))
1576
+ path = path.slice(0, -1);
1577
+ if (!path.startsWith("/"))
1578
+ path = `/${path}`;
1579
+ return path.slice(0, MAX_PATH);
1580
+ }
1581
+ var MAX_EDITS = 3;
1582
+ var BEYOND = MAX_EDITS + 1;
1583
+ function editDistance(a, b, limit) {
1584
+ if (Math.abs(a.length - b.length) > limit)
1585
+ return limit + 1;
1586
+ const columns = b.length + 1;
1587
+ let before = new Int32Array(columns).fill(BEYOND);
1588
+ let previous = Int32Array.from({
1589
+ length: columns
1590
+ }, (_, index) => Math.min(index, BEYOND));
1591
+ let current = new Int32Array(columns).fill(BEYOND);
1592
+ for (let i = 1;i <= a.length; i++) {
1593
+ const low = Math.max(1, i - limit);
1594
+ const high = Math.min(b.length, i + limit);
1595
+ current[low - 1] = low === 1 ? Math.min(i, BEYOND) : BEYOND;
1596
+ if (high < b.length)
1597
+ current[high + 1] = BEYOND;
1598
+ let rowMin = BEYOND;
1599
+ for (let j = low;j <= high; j++) {
1600
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
1601
+ let value = Math.min((previous[j] ?? BEYOND) + 1, (current[j - 1] ?? BEYOND) + 1, (previous[j - 1] ?? BEYOND) + cost);
1602
+ if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
1603
+ value = Math.min(value, (before[j - 2] ?? BEYOND) + 1);
1604
+ }
1605
+ current[j] = Math.min(value, BEYOND);
1606
+ rowMin = Math.min(rowMin, value);
1607
+ }
1608
+ if (rowMin > limit)
1609
+ return limit + 1;
1610
+ [before, previous, current] = [previous, current, before];
1611
+ }
1612
+ return Math.min(previous[b.length] ?? BEYOND, limit + 1);
1613
+ }
1614
+ function words(path) {
1615
+ return path.split(/[/\-_.]+/u).filter((word) => word.length > 0);
1616
+ }
1617
+ function wordAllowance(word) {
1618
+ if (word.length < 4)
1619
+ return 0;
1620
+ return word.length < 8 ? 1 : 2;
1621
+ }
1622
+ function wordsMatch(missing, candidate) {
1623
+ return missing.every((word) => {
1624
+ const allowance = wordAllowance(word);
1625
+ return candidate.some((other) => other === word || allowance > 0 && editDistance(word, other, allowance) <= allowance);
1626
+ });
1627
+ }
1628
+ function candidate(path) {
1629
+ const parts = words(path);
1630
+ return {
1631
+ path,
1632
+ words: parts,
1633
+ joined: parts.join(""),
1634
+ last: path.slice(path.lastIndexOf("/") + 1)
1635
+ };
1636
+ }
1637
+ function score(missing, page) {
1638
+ if (page.path === missing.path)
1639
+ return 0;
1640
+ if (missing.joined.length >= 6 && missing.joined === page.joined)
1641
+ return 0.1;
1642
+ const longest = Math.max(missing.path.length, page.path.length);
1643
+ const limit = Math.min(MAX_EDITS, Math.floor(longest / 4));
1644
+ if (limit > 0 && wordsMatch(missing.words, page.words)) {
1645
+ const distance = editDistance(missing.path, page.path, limit);
1646
+ if (distance <= limit)
1647
+ return distance / longest;
1648
+ }
1649
+ if (missing.last.length >= 6 && missing.last === page.last)
1650
+ return 0.5;
1651
+ const longer = missing.words.filter((word) => word.length >= 3);
1652
+ if (longer.length >= 2 && longer.length === missing.words.length && longer.every((word) => page.words.includes(word)))
1653
+ return 0.6;
1654
+ return;
1655
+ }
1656
+ function suggestStatusRoute(pathname, routes) {
1657
+ const normalized = normalizeStatusPath(pathname);
1658
+ if (normalized === "/")
1659
+ return;
1660
+ const exactOnly = normalized.length < 5;
1661
+ const missing = candidate(normalized);
1662
+ let best;
1663
+ for (const route of routes.slice(0, STATUS_PAGE_MAX_ROUTES)) {
1664
+ if (!isSitePath(route.href))
1665
+ continue;
1666
+ const path = normalizeStatusPath(route.href);
1667
+ if (path === "/" || exactOnly && path !== normalized)
1668
+ continue;
1669
+ const value = score(missing, candidate(path));
1670
+ if (value === undefined)
1671
+ continue;
1672
+ if (best === undefined || value < best.score || value === best.score && (path.length < best.path.length || path.length === best.path.length && path < best.path)) {
1673
+ best = {
1674
+ route,
1675
+ score: value,
1676
+ path
1677
+ };
1678
+ }
1679
+ }
1680
+ return best?.route;
1681
+ }
1682
+
1683
+ // src/browser/status-field.ts
1684
+ var MAX_DOTS = 5200;
1685
+ var STRIDE = 4;
1686
+ var SPRING = 0.034;
1687
+ var DAMPING = 0.86;
1688
+ var PUSH = 5.5;
1689
+ var BURST = 24;
1690
+ var REST_DISTANCE = 0.08;
1691
+ var ACCENT_SHARE = 0.14;
1692
+ var VERTEX = `
1693
+ attribute vec2 a_position;
1694
+ attribute float a_heat;
1695
+ attribute float a_tone;
1696
+ uniform vec2 u_size;
1697
+ uniform float u_point;
1698
+ varying float v_mix;
1699
+ void main() {
1700
+ vec2 clip = a_position / u_size * 2.0 - 1.0;
1701
+ gl_Position = vec4(clip.x, -clip.y, 0.0, 1.0);
1702
+ gl_PointSize = u_point * (1.0 + a_heat * 0.55);
1703
+ v_mix = clamp(max(a_tone * 0.85, a_heat), 0.0, 1.0);
1704
+ }`;
1705
+ var FRAGMENT = `
1706
+ precision mediump float;
1707
+ uniform vec3 u_ink;
1708
+ uniform vec3 u_accent;
1709
+ uniform float u_alpha;
1710
+ varying float v_mix;
1711
+ void main() {
1712
+ float d = length(gl_PointCoord - 0.5);
1713
+ float a = smoothstep(0.5, 0.18, d) * u_alpha;
1714
+ gl_FragColor = vec4(mix(u_ink, u_accent, v_mix) * a, a);
1715
+ }`;
1716
+ function random(seed) {
1717
+ let state = seed >>> 0 || 2654435769;
1718
+ return () => {
1719
+ state ^= state << 13;
1720
+ state >>>= 0;
1721
+ state ^= state >>> 17;
1722
+ state ^= state << 5;
1723
+ state >>>= 0;
1724
+ return state / 4294967296;
1725
+ };
1726
+ }
1727
+ function compile(gl, type, source) {
1728
+ const shader = gl.createShader(type);
1729
+ if (!shader)
1730
+ return null;
1731
+ gl.shaderSource(shader, source);
1732
+ gl.compileShader(shader);
1733
+ if (gl.getShaderParameter(shader, gl.COMPILE_STATUS))
1734
+ return shader;
1735
+ gl.deleteShader(shader);
1736
+ return null;
1737
+ }
1738
+ function attachStatusField(code, options = {}) {
1739
+ const document = code.ownerDocument;
1740
+ const view = document.defaultView;
1741
+ const canvas = code.querySelector("canvas");
1742
+ const glyph = code.querySelector(".hraness-status-page__glyph");
1743
+ if (!view || !canvas || !glyph || typeof view.matchMedia !== "function" || typeof view.requestAnimationFrame !== "function" || typeof view.ResizeObserver !== "function")
1744
+ return () => {};
1745
+ if (view.matchMedia("(forced-colors: active)").matches)
1746
+ return () => {};
1747
+ const motion = view.matchMedia("(prefers-reduced-motion: reduce)");
1748
+ let gl = null;
1749
+ try {
1750
+ gl = canvas.getContext("webgl", {
1751
+ alpha: true,
1752
+ antialias: false,
1753
+ depth: false,
1754
+ premultipliedAlpha: true,
1755
+ preserveDrawingBuffer: false,
1756
+ stencil: false
1757
+ });
1758
+ } catch {
1759
+ gl = null;
1760
+ }
1761
+ if (!gl || gl.isContextLost())
1762
+ return () => {};
1763
+ const vertex = compile(gl, gl.VERTEX_SHADER, VERTEX);
1764
+ const fragment = compile(gl, gl.FRAGMENT_SHADER, FRAGMENT);
1765
+ const program = gl.createProgram();
1766
+ if (!vertex || !fragment || !program)
1767
+ return () => {};
1768
+ gl.attachShader(program, vertex);
1769
+ gl.attachShader(program, fragment);
1770
+ gl.linkProgram(program);
1771
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS))
1772
+ return () => {};
1773
+ gl.useProgram(program);
1774
+ const buffer = gl.createBuffer();
1775
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
1776
+ const attribute = (name, size, offset) => {
1777
+ const location = gl.getAttribLocation(program, name);
1778
+ gl.enableVertexAttribArray(location);
1779
+ gl.vertexAttribPointer(location, size, gl.FLOAT, false, STRIDE * 4, offset * 4);
1780
+ };
1781
+ attribute("a_position", 2, 0);
1782
+ attribute("a_heat", 1, 2);
1783
+ attribute("a_tone", 1, 3);
1784
+ const uniform = (name) => gl.getUniformLocation(program, name);
1785
+ const uSize = uniform("u_size");
1786
+ const uPoint = uniform("u_point");
1787
+ const uInk = uniform("u_ink");
1788
+ const uAccent = uniform("u_accent");
1789
+ const uAlpha = uniform("u_alpha");
1790
+ gl.enable(gl.BLEND);
1791
+ gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
1792
+ const next = random(options.seed ?? 404);
1793
+ let homes = new Float32Array(0);
1794
+ let velocity = new Float32Array(0);
1795
+ let vertices = new Float32Array(0);
1796
+ let count = 0;
1797
+ let width = 0;
1798
+ let height = 0;
1799
+ let pointSize = 2;
1800
+ let pointer;
1801
+ let frame;
1802
+ let previous;
1803
+ let introStart;
1804
+ let delays = new Float32Array(0);
1805
+ let live = false;
1806
+ let disposed = false;
1807
+ let lost = false;
1808
+ const probe = document.createElement("canvas");
1809
+ probe.width = 1;
1810
+ probe.height = 1;
1811
+ const probeContext = probe.getContext("2d", {
1812
+ willReadFrequently: true
1813
+ });
1814
+ const rgba = (value) => {
1815
+ if (!probeContext)
1816
+ return [0.5, 0.5, 0.5, 1];
1817
+ probeContext.clearRect(0, 0, 1, 1);
1818
+ probeContext.fillStyle = "#808080";
1819
+ probeContext.fillStyle = value;
1820
+ probeContext.fillRect(0, 0, 1, 1);
1821
+ const [r = 128, g = 128, b = 128, a = 255] = probeContext.getImageData(0, 0, 1, 1).data;
1822
+ return [r / 255, g / 255, b / 255, a / 255];
1823
+ };
1824
+ const readColors = () => {
1825
+ const ink = rgba(view.getComputedStyle(glyph).color);
1826
+ const accent = rgba(view.getComputedStyle(canvas).color);
1827
+ gl.uniform3f(uInk, ink[0], ink[1], ink[2]);
1828
+ gl.uniform3f(uAccent, accent[0], accent[1], accent[2]);
1829
+ gl.uniform1f(uAlpha, Math.max(0.35, ink[3]));
1830
+ };
1831
+ const sample = () => {
1832
+ const bounds = canvas.getBoundingClientRect();
1833
+ const box = code.getBoundingClientRect();
1834
+ const centerX = box.left - bounds.left + box.width / 2;
1835
+ const centerY = box.top - bounds.top + box.height / 2;
1836
+ width = Math.round(bounds.width);
1837
+ height = Math.round(bounds.height);
1838
+ if (width < 8 || height < 8)
1839
+ return false;
1840
+ const ratio = Math.min(2, view.devicePixelRatio || 1);
1841
+ canvas.width = Math.round(width * ratio);
1842
+ canvas.height = Math.round(height * ratio);
1843
+ gl.viewport(0, 0, canvas.width, canvas.height);
1844
+ gl.uniform2f(uSize, width, height);
1845
+ const style = view.getComputedStyle(glyph);
1846
+ const fontSize = Number.parseFloat(style.fontSize) || 96;
1847
+ const mask = document.createElement("canvas");
1848
+ mask.width = width;
1849
+ mask.height = height;
1850
+ const context = mask.getContext("2d", {
1851
+ willReadFrequently: true
1852
+ });
1853
+ if (!context)
1854
+ return false;
1855
+ context.font = `${style.fontStyle} ${style.fontWeight} ${fontSize}px ${style.fontFamily}`;
1856
+ context.textAlign = "center";
1857
+ context.textBaseline = "alphabetic";
1858
+ const text = glyph.textContent ?? "";
1859
+ const metrics = context.measureText(text);
1860
+ const ascent = metrics.actualBoundingBoxAscent || fontSize * 0.7;
1861
+ const descent = metrics.actualBoundingBoxDescent || 0;
1862
+ context.fillStyle = "#000";
1863
+ context.fillText(text, centerX, centerY + (ascent - descent) / 2);
1864
+ const pixels = context.getImageData(0, 0, width, height).data;
1865
+ let gap = Math.max(3, Math.min(8, fontSize / 30));
1866
+ let points = [];
1867
+ for (let attempt = 0;attempt < 6; attempt++) {
1868
+ points = [];
1869
+ const rowHeight = gap * 0.866;
1870
+ for (let row = 0, y = rowHeight / 2;y < height; row++, y += rowHeight) {
1871
+ for (let x = row % 2 ? gap : gap / 2;x < width; x += gap) {
1872
+ if ((pixels[(Math.floor(y) * width + Math.floor(x)) * 4 + 3] ?? 0) >= 128)
1873
+ points.push(x, y);
1874
+ }
1875
+ }
1876
+ if (points.length / 2 <= MAX_DOTS)
1877
+ break;
1878
+ gap *= 1.2;
1879
+ }
1880
+ const nextCount = points.length / 2;
1881
+ const nextHomes = Float32Array.from(points);
1882
+ const nextVertices = new Float32Array(nextCount * STRIDE);
1883
+ const nextVelocity = new Float32Array(nextCount * 2);
1884
+ const nextDelays = new Float32Array(nextCount);
1885
+ const intro = count === 0 && !motion.matches;
1886
+ for (let index = 0;index < nextCount; index++) {
1887
+ const base = index * STRIDE;
1888
+ const hx = nextHomes[index * 2] ?? 0;
1889
+ const hy = nextHomes[index * 2 + 1] ?? 0;
1890
+ if (index < count) {
1891
+ nextVertices[base] = vertices[index * STRIDE] ?? 0;
1892
+ nextVertices[base + 1] = vertices[index * STRIDE + 1] ?? 0;
1893
+ nextVertices[base + 3] = vertices[index * STRIDE + 3] ?? 0;
1894
+ } else {
1895
+ const angle = next() * Math.PI * 2;
1896
+ const reach = intro ? (0.25 + next() * 0.6) * Math.max(width, height) * 0.5 : 0;
1897
+ nextVertices[base] = hx + Math.cos(angle) * reach;
1898
+ nextVertices[base + 1] = hy + Math.sin(angle) * reach * 0.6;
1899
+ nextVertices[base + 3] = next() < ACCENT_SHARE ? 1 : 0;
1900
+ nextDelays[index] = intro ? next() * 420 : 0;
1901
+ }
1902
+ }
1903
+ homes = nextHomes;
1904
+ vertices = nextVertices;
1905
+ velocity = nextVelocity;
1906
+ delays = nextDelays;
1907
+ count = nextCount;
1908
+ pointSize = gap * 0.6 * ratio;
1909
+ gl.uniform1f(uPoint, pointSize);
1910
+ gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.DYNAMIC_DRAW);
1911
+ if (intro)
1912
+ introStart = undefined;
1913
+ return count > 0;
1914
+ };
1915
+ const draw = () => {
1916
+ if (disposed || lost)
1917
+ return;
1918
+ gl.clearColor(0, 0, 0, 0);
1919
+ gl.clear(gl.COLOR_BUFFER_BIT);
1920
+ gl.bufferSubData(gl.ARRAY_BUFFER, 0, vertices);
1921
+ gl.drawArrays(gl.POINTS, 0, count);
1922
+ if (!live) {
1923
+ live = true;
1924
+ options.onLive?.(true);
1925
+ }
1926
+ };
1927
+ const settle = () => {
1928
+ for (let index = 0;index < count; index++) {
1929
+ vertices[index * STRIDE] = homes[index * 2] ?? 0;
1930
+ vertices[index * STRIDE + 1] = homes[index * 2 + 1] ?? 0;
1931
+ vertices[index * STRIDE + 2] = 0;
1932
+ velocity[index * 2] = 0;
1933
+ velocity[index * 2 + 1] = 0;
1934
+ }
1935
+ };
1936
+ const step = (time) => {
1937
+ frame = undefined;
1938
+ if (disposed || lost)
1939
+ return;
1940
+ if (document.hidden) {
1941
+ previous = undefined;
1942
+ return;
1943
+ }
1944
+ introStart ??= time;
1945
+ const dt = previous === undefined ? 1 : Math.max(0.25, Math.min(2, (time - previous) / 16.67));
1946
+ previous = time;
1947
+ const since = time - introStart;
1948
+ const radius = Math.max(72, width * 0.16);
1949
+ const damping = DAMPING ** dt;
1950
+ let moving = false;
1951
+ for (let index = 0;index < count; index++) {
1952
+ const base = index * STRIDE;
1953
+ if (since < (delays[index] ?? 0)) {
1954
+ moving = true;
1955
+ continue;
1956
+ }
1957
+ let x = vertices[base] ?? 0;
1958
+ let y = vertices[base + 1] ?? 0;
1959
+ let vx = velocity[index * 2] ?? 0;
1960
+ let vy = velocity[index * 2 + 1] ?? 0;
1961
+ let ax = ((homes[index * 2] ?? 0) - x) * SPRING;
1962
+ let ay = ((homes[index * 2 + 1] ?? 0) - y) * SPRING;
1963
+ if (pointer) {
1964
+ const dx = x - pointer.x;
1965
+ const dy = y - pointer.y;
1966
+ const distance = Math.hypot(dx, dy);
1967
+ if (distance < radius) {
1968
+ const force = (1 - distance / radius) ** 2 * PUSH / Math.max(distance, 0.5);
1969
+ ax += dx * force;
1970
+ ay += dy * force;
1971
+ }
1972
+ }
1973
+ vx = (vx + ax * dt) * damping;
1974
+ vy = (vy + ay * dt) * damping;
1975
+ x += vx * dt;
1976
+ y += vy * dt;
1977
+ vertices[base] = x;
1978
+ vertices[base + 1] = y;
1979
+ velocity[index * 2] = vx;
1980
+ velocity[index * 2 + 1] = vy;
1981
+ const speed = Math.hypot(vx, vy);
1982
+ vertices[base + 2] = Math.max((vertices[base + 2] ?? 0) * 0.93 ** dt, Math.min(1, speed / 2));
1983
+ if (speed > REST_DISTANCE * 0.5 || (vertices[base + 2] ?? 0) > 0.02 || !pointer && Math.abs((homes[index * 2] ?? 0) - x) + Math.abs((homes[index * 2 + 1] ?? 0) - y) > REST_DISTANCE)
1984
+ moving = true;
1985
+ }
1986
+ if (!moving && !pointer)
1987
+ settle();
1988
+ draw();
1989
+ if (moving)
1990
+ frame = view.requestAnimationFrame(step);
1991
+ else
1992
+ previous = undefined;
1993
+ };
1994
+ const wake = () => {
1995
+ if (disposed || lost || motion.matches || frame !== undefined)
1996
+ return;
1997
+ frame = view.requestAnimationFrame(step);
1998
+ };
1999
+ const paintStill = () => {
2000
+ if (disposed || lost)
2001
+ return;
2002
+ if (frame !== undefined)
2003
+ view.cancelAnimationFrame(frame);
2004
+ frame = undefined;
2005
+ settle();
2006
+ draw();
2007
+ };
2008
+ const rebuild = () => {
2009
+ if (disposed || lost)
2010
+ return;
2011
+ if (!sample())
2012
+ return;
2013
+ readColors();
2014
+ if (motion.matches)
2015
+ paintStill();
2016
+ else {
2017
+ draw();
2018
+ wake();
2019
+ }
2020
+ };
2021
+ const local = (event) => {
2022
+ const bounds = canvas.getBoundingClientRect();
2023
+ return {
2024
+ x: event.clientX - bounds.left,
2025
+ y: event.clientY - bounds.top
2026
+ };
2027
+ };
2028
+ const onMove = (event) => {
2029
+ if (motion.matches)
2030
+ return;
2031
+ pointer = local(event);
2032
+ wake();
2033
+ };
2034
+ const onLeave = () => {
2035
+ pointer = undefined;
2036
+ wake();
2037
+ };
2038
+ const onDown = (event) => {
2039
+ if (motion.matches)
2040
+ return;
2041
+ const origin = local(event);
2042
+ const reach = Math.max(120, width * 0.34);
2043
+ for (let index = 0;index < count; index++) {
2044
+ const dx = (vertices[index * STRIDE] ?? 0) - origin.x;
2045
+ const dy = (vertices[index * STRIDE + 1] ?? 0) - origin.y;
2046
+ const distance = Math.hypot(dx, dy);
2047
+ if (distance >= reach)
2048
+ continue;
2049
+ const force = (1 - distance / reach) * BURST / Math.max(distance, 0.5);
2050
+ velocity[index * 2] = (velocity[index * 2] ?? 0) + dx * force;
2051
+ velocity[index * 2 + 1] = (velocity[index * 2 + 1] ?? 0) + dy * force;
2052
+ }
2053
+ if (event.pointerType !== "mouse")
2054
+ pointer = undefined;
2055
+ wake();
2056
+ };
2057
+ const area = code.closest(".hraness-status-page") ?? code;
2058
+ area.addEventListener("pointermove", onMove, {
2059
+ passive: true
2060
+ });
2061
+ area.addEventListener("pointerleave", onLeave, {
2062
+ passive: true
2063
+ });
2064
+ area.addEventListener("pointerdown", onDown, {
2065
+ passive: true
2066
+ });
2067
+ const onVisibility = () => {
2068
+ if (!document.hidden)
2069
+ wake();
2070
+ };
2071
+ document.addEventListener("visibilitychange", onVisibility);
2072
+ const onMotion = () => {
2073
+ if (motion.matches)
2074
+ paintStill();
2075
+ else
2076
+ wake();
2077
+ };
2078
+ motion.addEventListener?.("change", onMotion);
2079
+ const scheme = view.matchMedia("(prefers-color-scheme: dark)");
2080
+ const onScheme = () => {
2081
+ if (disposed || lost)
2082
+ return;
2083
+ readColors();
2084
+ if (frame === undefined)
2085
+ draw();
2086
+ };
2087
+ scheme.addEventListener?.("change", onScheme);
2088
+ const themeObserver = typeof view.MutationObserver === "function" ? new view.MutationObserver(onScheme) : undefined;
2089
+ themeObserver?.observe(document.documentElement, {
2090
+ attributeFilter: ["class", "data-theme", "data-palette", "style"],
2091
+ attributes: true
2092
+ });
2093
+ let resizeFrame;
2094
+ const resizeObserver = new view.ResizeObserver(() => {
2095
+ if (resizeFrame !== undefined)
2096
+ view.cancelAnimationFrame(resizeFrame);
2097
+ resizeFrame = view.requestAnimationFrame(() => {
2098
+ resizeFrame = undefined;
2099
+ rebuild();
2100
+ });
2101
+ });
2102
+ const onLost = () => {
2103
+ lost = true;
2104
+ if (frame !== undefined)
2105
+ view.cancelAnimationFrame(frame);
2106
+ frame = undefined;
2107
+ if (live) {
2108
+ live = false;
2109
+ options.onLive?.(false);
2110
+ }
2111
+ };
2112
+ canvas.addEventListener("webglcontextlost", onLost);
2113
+ const fonts = document.fonts;
2114
+ const onFonts = () => rebuild();
2115
+ let started = false;
2116
+ const start = () => {
2117
+ if (started || disposed)
2118
+ return;
2119
+ started = true;
2120
+ resizeObserver.observe(code);
2121
+ rebuild();
2122
+ };
2123
+ if (fonts?.ready) {
2124
+ fonts.ready.then(start, start);
2125
+ fonts.addEventListener?.("loadingdone", onFonts);
2126
+ } else
2127
+ start();
2128
+ return () => {
2129
+ if (disposed)
2130
+ return;
2131
+ disposed = true;
2132
+ if (frame !== undefined)
2133
+ view.cancelAnimationFrame(frame);
2134
+ if (resizeFrame !== undefined)
2135
+ view.cancelAnimationFrame(resizeFrame);
2136
+ area.removeEventListener("pointermove", onMove);
2137
+ area.removeEventListener("pointerleave", onLeave);
2138
+ area.removeEventListener("pointerdown", onDown);
2139
+ document.removeEventListener("visibilitychange", onVisibility);
2140
+ motion.removeEventListener?.("change", onMotion);
2141
+ scheme.removeEventListener?.("change", onScheme);
2142
+ fonts?.removeEventListener?.("loadingdone", onFonts);
2143
+ themeObserver?.disconnect();
2144
+ resizeObserver.disconnect();
2145
+ canvas.removeEventListener("webglcontextlost", onLost);
2146
+ gl.deleteBuffer(buffer);
2147
+ gl.deleteProgram(program);
2148
+ gl.deleteShader(vertex);
2149
+ gl.deleteShader(fragment);
2150
+ if (live)
2151
+ options.onLive?.(false);
2152
+ const release = () => {
2153
+ if (!canvas.isConnected && !gl.isContextLost())
2154
+ gl.getExtension("WEBGL_lose_context")?.loseContext();
2155
+ };
2156
+ release();
2157
+ view.setTimeout(release, 0);
2158
+ };
2159
+ }
2160
+
2161
+ // src/browser/status-page.ts
2162
+ function parseUrl(value) {
2163
+ if (!value)
2164
+ return;
2165
+ try {
2166
+ return new URL(value);
2167
+ } catch {
2168
+ return;
2169
+ }
2170
+ }
2171
+ function previousPage(view, document) {
2172
+ const navigation = view.navigation;
2173
+ const index = navigation?.currentEntry?.index;
2174
+ if (navigation?.entries !== undefined && typeof index === "number") {
2175
+ return index < 1 ? undefined : parseUrl(navigation.entries()[index - 1]?.url);
2176
+ }
2177
+ return view.history.length > 1 ? parseUrl(document.referrer) : undefined;
2178
+ }
2179
+ function attachStatusPage(root) {
2180
+ const document = root.ownerDocument;
2181
+ const view = document.defaultView;
2182
+ if (!view)
2183
+ return () => {};
2184
+ const cleanups = [];
2185
+ const hint = root.querySelector(".hraness-status-page__hint");
2186
+ const hintLink = hint?.querySelector("a");
2187
+ if (hint && hintLink && root.dataset.kind === "not-found") {
2188
+ const match = suggestStatusRoute(view.location.pathname, parseStatusPageRoutes(root.getAttribute("data-hraness-status-routes")));
2189
+ const target = match === undefined ? undefined : new URL(match.href, view.location.href);
2190
+ if (match && target && target.origin === view.location.origin) {
2191
+ hintLink.href = target.pathname + target.search + target.hash;
2192
+ hintLink.textContent = match.label;
2193
+ hint.hidden = false;
2194
+ root.dataset.hranessStatusSuggestion = match.href;
2195
+ cleanups.push(() => {
2196
+ hint.hidden = true;
2197
+ hintLink.setAttribute("href", "/");
2198
+ hintLink.textContent = "";
2199
+ delete root.dataset.hranessStatusSuggestion;
2200
+ });
2201
+ }
2202
+ }
2203
+ const back = root.querySelector(".hraness-status-page__back");
2204
+ if (back) {
2205
+ const previous = previousPage(view, document);
2206
+ if (previous && previous.origin === view.location.origin && previous.pathname !== view.location.pathname) {
2207
+ back.href = previous.href;
2208
+ back.hidden = false;
2209
+ const onBack = (event) => {
2210
+ if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)
2211
+ return;
2212
+ event.preventDefault();
2213
+ view.history.back();
2214
+ };
2215
+ back.addEventListener("click", onBack);
2216
+ cleanups.push(() => {
2217
+ back.removeEventListener("click", onBack);
2218
+ back.hidden = true;
2219
+ back.setAttribute("href", "/");
2220
+ });
2221
+ }
2222
+ }
2223
+ const code = root.querySelector(".hraness-status-page__code");
2224
+ if (code) {
2225
+ cleanups.push(attachStatusField(code, {
2226
+ onLive: (live) => {
2227
+ if (live)
2228
+ root.dataset.hranessStatusField = "live";
2229
+ else
2230
+ delete root.dataset.hranessStatusField;
2231
+ }
2232
+ }));
2233
+ }
2234
+ cleanups.push(attachFoil(root));
2235
+ return () => {
2236
+ for (const cleanup of cleanups.splice(0).reverse())
2237
+ cleanup();
2238
+ };
2239
+ }
2240
+ // src/browser/sticky-offset.ts
2241
+ var stickyOffsetCustomProperty = "--hraness-sticky-offset";
2242
+ var stickyOffsetHeaderSelector = [".hraness-marketing-header:not([data-position='static'])", ".hraness-marketing-header-surface:not([data-position='static'])"].join(", ");
2243
+ function canQuery(root) {
2244
+ return typeof root.querySelector === "function";
2245
+ }
2246
+ function ownerDocumentOf(node) {
2247
+ if (node === undefined)
2248
+ return globalThis.document;
2249
+ if (node.nodeType === 9)
2250
+ return node;
2251
+ const owner = node.ownerDocument;
2252
+ return owner ?? undefined;
2253
+ }
2254
+ function resolveHeader(root, header) {
2255
+ if (typeof header === "object")
2256
+ return header;
2257
+ if (!canQuery(root))
2258
+ return null;
2259
+ if (typeof header === "string")
2260
+ return root.querySelector(header);
2261
+ return root.querySelector(stickyOffsetHeaderSelector);
2262
+ }
2263
+ function resolveTarget(header, target) {
2264
+ if (target !== undefined)
2265
+ return target;
2266
+ const page = header.closest(".hraness-marketing-page");
2267
+ return page ?? header.ownerDocument.documentElement;
2268
+ }
2269
+ function measureStickyOffset(header) {
2270
+ return `${header.getBoundingClientRect().height}px`;
2271
+ }
2272
+ function publishStickyOffset(header, target) {
2273
+ const value = measureStickyOffset(header);
2274
+ resolveTarget(header, target).style.setProperty(stickyOffsetCustomProperty, value);
2275
+ return value;
2276
+ }
2277
+ function syncStickyOffset(options = {}) {
2278
+ const document = typeof options.header === "object" ? options.header.ownerDocument : ownerDocumentOf(options.root);
2279
+ if (document === undefined)
2280
+ return () => {};
2281
+ const root = options.root ?? document;
2282
+ const header = resolveHeader(root, options.header);
2283
+ if (header === null)
2284
+ return () => {};
2285
+ const target = resolveTarget(header, options.target);
2286
+ const publish = () => {
2287
+ publishStickyOffset(header, target);
2288
+ };
2289
+ publish();
2290
+ const view = document.defaultView;
2291
+ if (view === null || typeof view.ResizeObserver !== "function") {
2292
+ return () => {
2293
+ target.style.removeProperty(stickyOffsetCustomProperty);
2294
+ };
2295
+ }
2296
+ const observer = new view.ResizeObserver(publish);
2297
+ observer.observe(header);
2298
+ return () => {
2299
+ observer.disconnect();
2300
+ target.style.removeProperty(stickyOffsetCustomProperty);
2301
+ };
2302
+ }
2303
+
2304
+ // src/browser/index.ts
2305
+ var defaultDesignTheme2 = defaultDesignTheme;
2306
+ var designThemeLabel2 = designThemeLabel;
2307
+ var designThemes2 = designThemes;
2308
+ var designThemeStorageKey2 = designThemeStorageKey;
2309
+ var installAppearanceMenus2 = installAppearanceMenus;
2310
+ var isDesignTheme2 = isDesignTheme;
2311
+ var normalizeDesignTheme2 = normalizeDesignTheme;
2312
+ var resolveDesignTheme2 = resolveDesignTheme;
2313
+ export {
2314
+ syncStickyOffset,
2315
+ stickyOffsetHeaderSelector,
2316
+ stickyOffsetCustomProperty,
2317
+ shareFileNatively,
2318
+ resolveDesignTheme2 as resolveDesignTheme,
2319
+ resolveDesignPalettePreference,
2320
+ publishStickyOffset,
2321
+ parseDesignPalettePreference,
2322
+ normalizeDesignTheme2 as normalizeDesignTheme,
2323
+ normalizeDesignPalettePreference,
2324
+ measureStickyOffset,
2325
+ isDesignTheme2 as isDesignTheme,
2326
+ installAppearanceMenus2 as installAppearanceMenus,
2327
+ initDesignPalette,
2328
+ downloadBlob,
2329
+ designThemes2 as designThemes,
2330
+ designThemeStorageKey2 as designThemeStorageKey,
2331
+ designThemeLabel2 as designThemeLabel,
2332
+ designPaletteStorageKey,
2333
+ designPaletteSnapshot,
2334
+ defaultDesignTheme2 as defaultDesignTheme,
2335
+ defaultDesignPalettePreference,
2336
+ copyTextToClipboard,
2337
+ canShareFileNatively,
2338
+ buildXShareIntentUrl,
2339
+ buildLinkedInShareIntentUrl,
2340
+ buildBlueskyShareIntentUrl,
2341
+ attachStatusPage,
2342
+ attachStatusField,
2343
+ attachHeroLight,
2344
+ attachFoil
2345
+ };