@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,164 @@
1
+ export interface ArtifactShareIntent {
2
+ readonly text: string;
3
+ readonly url: string;
4
+ }
5
+
6
+ export type NativeFileShareResult =
7
+ | Readonly<{ kind: "shared" }>
8
+ | Readonly<{ kind: "cancelled" }>
9
+ | Readonly<{ kind: "unavailable" }>
10
+ | Readonly<{ error: unknown; kind: "failed" }>;
11
+
12
+ function nonblank(value: string, label: string): string {
13
+ if (value.trim().length === 0) {
14
+ throw new RangeError(`${label} must contain a non-whitespace character.`);
15
+ }
16
+ return value;
17
+ }
18
+
19
+ function publicWebUrl(value: string): string {
20
+ const normalized = nonblank(value, "An artifact share URL").trim();
21
+ let url: URL;
22
+ try {
23
+ url = new URL(normalized);
24
+ } catch {
25
+ throw new RangeError("An artifact share URL must be an absolute URL.");
26
+ }
27
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
28
+ throw new RangeError("An artifact share URL must use HTTP or HTTPS.");
29
+ }
30
+ return url.href;
31
+ }
32
+
33
+ function normalizedIntent(input: ArtifactShareIntent): ArtifactShareIntent {
34
+ return {
35
+ text: nonblank(input.text, "Artifact share text"),
36
+ url: publicWebUrl(input.url),
37
+ };
38
+ }
39
+
40
+ /** Builds an X compose intent without opening a window or posting content. */
41
+ export function buildXShareIntentUrl(input: ArtifactShareIntent): string {
42
+ const { text, url } = normalizedIntent(input);
43
+ const intent = new URL("https://x.com/intent/post");
44
+ intent.searchParams.set("text", text);
45
+ intent.searchParams.set("url", url);
46
+ return intent.href;
47
+ }
48
+
49
+ /** Builds LinkedIn's URL-only offsite share dialog. */
50
+ export function buildLinkedInShareIntentUrl(url: string): string {
51
+ const intent = new URL("https://www.linkedin.com/sharing/share-offsite/");
52
+ intent.searchParams.set("url", publicWebUrl(url));
53
+ return intent.href;
54
+ }
55
+
56
+ /** Builds a Bluesky compose intent with the URL on its own editable line. */
57
+ export function buildBlueskyShareIntentUrl(input: ArtifactShareIntent): string {
58
+ const { text, url } = normalizedIntent(input);
59
+ const intent = new URL("https://bsky.app/intent/compose");
60
+ intent.searchParams.set("text", `${text}\n${url}`);
61
+ return intent.href;
62
+ }
63
+
64
+ /** Copies exact caller-prepared text through the secure-context Clipboard API. */
65
+ export async function copyTextToClipboard(text: string): Promise<void> {
66
+ const clipboard = globalThis.navigator?.clipboard;
67
+ if (clipboard === undefined || typeof clipboard.writeText !== "function") {
68
+ throw new Error("Clipboard text writing is unavailable in this environment.");
69
+ }
70
+ await clipboard.writeText(text);
71
+ }
72
+
73
+ /**
74
+ * Starts a browser download and releases the temporary object URL in the next
75
+ * task, after the synthetic link activation has been dispatched.
76
+ */
77
+ export function downloadBlob(blob: Blob, filename: string): void {
78
+ const normalizedFilename = nonblank(filename, "A download filename").trim();
79
+ const document = globalThis.document;
80
+ if (
81
+ document === undefined
82
+ || typeof URL.createObjectURL !== "function"
83
+ || typeof URL.revokeObjectURL !== "function"
84
+ ) {
85
+ throw new Error("Blob downloads are unavailable in this environment.");
86
+ }
87
+
88
+ const objectUrl = URL.createObjectURL(blob);
89
+ const anchor = document.createElement("a");
90
+ anchor.download = normalizedFilename;
91
+ anchor.hidden = true;
92
+ anchor.href = objectUrl;
93
+ anchor.rel = "noopener";
94
+ document.body.append(anchor);
95
+ try {
96
+ anchor.click();
97
+ } finally {
98
+ anchor.remove();
99
+ globalThis.setTimeout(() => URL.revokeObjectURL(objectUrl), 0);
100
+ }
101
+ }
102
+
103
+ function isAbortError(error: unknown): boolean {
104
+ return typeof error === "object"
105
+ && error !== null
106
+ && "name" in error
107
+ && error.name === "AbortError";
108
+ }
109
+
110
+ function filesOnlyShareData(file: File): ShareData {
111
+ return { files: [file] };
112
+ }
113
+
114
+ function nativeFileShareNavigator(): Navigator | undefined {
115
+ const shareNavigator = globalThis.navigator;
116
+ if (
117
+ shareNavigator === undefined
118
+ || typeof shareNavigator.canShare !== "function"
119
+ || typeof shareNavigator.share !== "function"
120
+ ) {
121
+ return undefined;
122
+ }
123
+ return shareNavigator;
124
+ }
125
+
126
+ function canShareFileWithNavigator(
127
+ shareNavigator: Navigator,
128
+ file: File,
129
+ ): boolean {
130
+ try {
131
+ return shareNavigator.canShare(filesOnlyShareData(file));
132
+ } catch {
133
+ return false;
134
+ }
135
+ }
136
+
137
+ /** Checks native file-share support using a files-only Web Share payload. */
138
+ export function canShareFileNatively(file: File): boolean {
139
+ const shareNavigator = nativeFileShareNavigator();
140
+ return shareNavigator !== undefined
141
+ && canShareFileWithNavigator(shareNavigator, file);
142
+ }
143
+
144
+ /**
145
+ * Opens the native share sheet with one already-prepared file. No text, title,
146
+ * or URL is added to either capability detection or the share call.
147
+ */
148
+ export async function shareFileNatively(file: File): Promise<NativeFileShareResult> {
149
+ const shareNavigator = nativeFileShareNavigator();
150
+ if (
151
+ shareNavigator === undefined
152
+ || !canShareFileWithNavigator(shareNavigator, file)
153
+ ) {
154
+ return { kind: "unavailable" };
155
+ }
156
+
157
+ try {
158
+ await shareNavigator.share(filesOnlyShareData(file));
159
+ return { kind: "shared" };
160
+ } catch (error) {
161
+ if (isAbortError(error)) return { kind: "cancelled" };
162
+ return { error, kind: "failed" };
163
+ }
164
+ }
@@ -0,0 +1,197 @@
1
+ import { designThemeStorageKey, isDesignTheme, type ConcreteDesignTheme } from "../appearance.js";
2
+ import {
3
+ designPaletteStorageKey,
4
+ normalizeDesignPalettePreference,
5
+ parseDesignPalettePreference,
6
+ resolveDesignPalettePreference,
7
+ type ConcreteDesignPalettePreference,
8
+ type DesignPalettePreference,
9
+ } from "../palette-appearance.js";
10
+ import { getDesignPaletteTheme } from "../palette-themes.js";
11
+ import { designPalettes } from "../palettes.js";
12
+ import type { AppearanceStorage } from "./appearance-menu.js";
13
+ import { acquireThemeColorMeta } from "./theme-color-sync.js";
14
+
15
+ export interface DesignPaletteSnapshot {
16
+ readonly preference: DesignPalettePreference;
17
+ readonly resolvedMode: ConcreteDesignTheme;
18
+ readonly className: string;
19
+ readonly background: string;
20
+ readonly isForced: boolean;
21
+ }
22
+
23
+ export interface DesignPaletteOptions {
24
+ readonly document?: Document;
25
+ readonly storage?: AppearanceStorage | null;
26
+ readonly storageKey?: string;
27
+ readonly legacyStorageKey?: string | null;
28
+ readonly defaultPreference?: DesignPalettePreference;
29
+ readonly forcedPreference?: ConcreteDesignPalettePreference;
30
+ }
31
+
32
+ export interface DesignPaletteController {
33
+ readonly getSnapshot: () => DesignPaletteSnapshot;
34
+ readonly subscribe: (listener: () => void) => () => void;
35
+ readonly setPreference: (preference: DesignPalettePreference) => boolean;
36
+ readonly dispose: () => void;
37
+ }
38
+
39
+ interface PaletteInstallation {
40
+ readonly version: 1;
41
+ readonly configuration: string;
42
+ readonly acquire: () => DesignPaletteController;
43
+ }
44
+
45
+ // A document property lets the head bootstrap and a separately bundled React
46
+ // client adopt the same controller. Importing this module does not install it.
47
+ const installationKey = Symbol.for("hraness.design-palette.controller.v1");
48
+ type PaletteDocument = Document & { [installationKey]?: PaletteInstallation };
49
+ const darkQuery = "(prefers-color-scheme: dark)";
50
+
51
+ export function designPaletteSnapshot(
52
+ preference: DesignPalettePreference,
53
+ systemPrefersDark: boolean,
54
+ isForced = false,
55
+ ): DesignPaletteSnapshot {
56
+ const resolved = resolveDesignPalettePreference(preference, systemPrefersDark);
57
+ const theme = getDesignPaletteTheme(resolved.palette, resolved.mode);
58
+ return Object.freeze({
59
+ preference: Object.freeze({ ...preference }),
60
+ resolvedMode: resolved.mode,
61
+ className: theme.className,
62
+ background: theme.background,
63
+ isForced,
64
+ });
65
+ }
66
+
67
+ function readStorage(storage: AppearanceStorage | null, key: string): unknown {
68
+ try { return storage?.getItem(key); } catch { return undefined; }
69
+ }
70
+
71
+ function writeStorage(storage: AppearanceStorage | null, key: string, preference: DesignPalettePreference): void {
72
+ try { storage?.setItem(key, JSON.stringify(preference)); } catch { /* Persistence is optional. */ }
73
+ }
74
+
75
+ /** Installs palette selection explicitly; it never injects scripts or inline styles. */
76
+ export function initDesignPalette(options: DesignPaletteOptions = {}): DesignPaletteController {
77
+ const document = options.document ?? globalThis.document;
78
+ if (document === undefined || document.documentElement === null || document.head === null) {
79
+ throw new Error("initDesignPalette requires an HTML document with a head.");
80
+ }
81
+ const view = document.defaultView;
82
+ const storageKey = options.storageKey ?? designPaletteStorageKey;
83
+ const legacyStorageKey = options.legacyStorageKey === undefined ? designThemeStorageKey : options.legacyStorageKey;
84
+ if (storageKey.trim() === "" || legacyStorageKey?.trim() === "") throw new Error("Appearance storage keys must not be blank.");
85
+ const fallback = normalizeDesignPalettePreference(options.defaultPreference);
86
+ const forced = options.forcedPreference === undefined ? null : parseDesignPalettePreference(options.forcedPreference);
87
+ if (options.forcedPreference !== undefined && (forced === null || forced.mode === "system")) {
88
+ throw new Error("A forced palette requires a valid palette and a light or dark mode.");
89
+ }
90
+ const configuration = JSON.stringify({ storageKey, legacyStorageKey, fallback, forced });
91
+ const ownerDocument = document as PaletteDocument;
92
+ const previous = ownerDocument[installationKey];
93
+ if (previous !== undefined) {
94
+ if (previous.version !== 1 || previous.configuration !== configuration) {
95
+ throw new Error("This document already has a different palette configuration.");
96
+ }
97
+ return previous.acquire();
98
+ }
99
+
100
+ let storage = options.storage ?? null;
101
+ const usesDefaultStorage = options.storage === undefined;
102
+ if (usesDefaultStorage && forced === null) {
103
+ try { storage = view?.localStorage ?? null; } catch { /* Storage can be denied. */ }
104
+ }
105
+ let media: MediaQueryList | null = null;
106
+ try { media = view?.matchMedia?.(darkQuery) ?? null; } catch { /* Explicit modes remain available. */ }
107
+ const readPreference = (): DesignPalettePreference => {
108
+ if (forced !== null) return forced;
109
+ const raw = readStorage(storage, storageKey);
110
+ const saved = parseDesignPalettePreference(raw);
111
+ if (saved !== null) return saved;
112
+ if ((raw === null || raw === undefined) && legacyStorageKey !== null) {
113
+ const legacy = readStorage(storage, legacyStorageKey);
114
+ if (isDesignTheme(legacy)) return Object.freeze({ palette: fallback.palette, mode: legacy });
115
+ }
116
+ return fallback;
117
+ };
118
+ let snapshot = designPaletteSnapshot(readPreference(), media?.matches ?? false, forced !== null);
119
+ const listeners = new Set<() => void>();
120
+ const ownedClasses = new Set(designPalettes.flatMap((palette) =>
121
+ (["light", "dark"] as const).flatMap((mode) => getDesignPaletteTheme(palette, mode).className.split(/\s+/u))));
122
+ const themeColor = acquireThemeColorMeta(document, "theme-color", Symbol("palette-theme-color"), snapshot.background);
123
+ const apply = (): void => {
124
+ const root = document.documentElement;
125
+ const nextClasses = new Set(snapshot.className.split(/\s+/u).filter(Boolean));
126
+ for (const token of ownedClasses) if (token !== "" && !nextClasses.has(token)) root.classList.remove(token);
127
+ for (const token of nextClasses) root.classList.add(token);
128
+ root.setAttribute("data-palette", snapshot.preference.palette);
129
+ root.setAttribute("data-theme", snapshot.resolvedMode);
130
+ themeColor.update(snapshot.background);
131
+ };
132
+ const update = (preference: DesignPalettePreference): boolean => {
133
+ const next = designPaletteSnapshot(preference, media?.matches ?? false, forced !== null);
134
+ if (next.preference.palette === snapshot.preference.palette
135
+ && next.preference.mode === snapshot.preference.mode
136
+ && next.resolvedMode === snapshot.resolvedMode) return false;
137
+ snapshot = next;
138
+ apply();
139
+ for (const listener of listeners) listener();
140
+ return true;
141
+ };
142
+ const onMedia = (): void => { update(snapshot.preference); };
143
+ const onStorage = (event: StorageEvent): void => {
144
+ if (forced !== null || (event.key !== storageKey && event.key !== null)) return;
145
+ if (usesDefaultStorage && event.storageArea !== null && event.storageArea !== undefined && event.storageArea !== storage) return;
146
+ update(readPreference());
147
+ };
148
+ if (forced === null) {
149
+ writeStorage(storage, storageKey, snapshot.preference);
150
+ view?.addEventListener("storage", onStorage);
151
+ media?.addEventListener("change", onMedia);
152
+ }
153
+ let owners = 0;
154
+ const installation: PaletteInstallation = {
155
+ version: 1,
156
+ configuration,
157
+ acquire: () => {
158
+ // An error document can replace the root or reset its server attributes.
159
+ // Restore the current choice without another controller or storage read.
160
+ apply();
161
+ owners += 1;
162
+ let disposed = false;
163
+ const subscriptions = new Set<() => void>();
164
+ return {
165
+ getSnapshot: () => snapshot,
166
+ subscribe: (listener) => {
167
+ if (disposed) return () => undefined;
168
+ const subscription = (): void => { listener(); };
169
+ subscriptions.add(subscription);
170
+ listeners.add(subscription);
171
+ return () => { subscriptions.delete(subscription); listeners.delete(subscription); };
172
+ },
173
+ setPreference: (value) => {
174
+ if (disposed || forced !== null) return false;
175
+ const preference = parseDesignPalettePreference(value);
176
+ if (preference === null) throw new Error("Choose a supported palette and appearance mode.");
177
+ writeStorage(storage, storageKey, preference);
178
+ return update(preference);
179
+ },
180
+ dispose: () => {
181
+ if (disposed) return;
182
+ disposed = true;
183
+ for (const listener of subscriptions) listeners.delete(listener);
184
+ subscriptions.clear();
185
+ owners -= 1;
186
+ if (owners !== 0) return;
187
+ view?.removeEventListener("storage", onStorage);
188
+ media?.removeEventListener("change", onMedia);
189
+ themeColor.release();
190
+ if (ownerDocument[installationKey] === installation) Reflect.deleteProperty(ownerDocument, installationKey);
191
+ },
192
+ };
193
+ },
194
+ };
195
+ ownerDocument[installationKey] = installation;
196
+ return installation.acquire();
197
+ }
@@ -0,0 +1,124 @@
1
+ /** Version 2: moving light over a steady material. Safe to snapshot without imports. */
2
+ const REST = 50;
3
+ const SETTLE = 0.05;
4
+ const RESPONSE_MS = 85;
5
+ const MIN_LIGHT = 8;
6
+ const MAX_LIGHT = 92;
7
+ const INPUTS = ["--hraness-foil-x", "--hraness-foil-y"] as const;
8
+ type Light = { x: number; y: number };
9
+ const clampLight = (value: number) => Math.max(MIN_LIGHT, Math.min(MAX_LIGHT, value));
10
+
11
+ /**
12
+ * One damped light source per outermost [data-foil] lockup. Nested marks inherit
13
+ * its coordinates. Shapes and material direction never rotate. Resting CSS is
14
+ * complete without this enhancement; touch, reduced motion and forced colors
15
+ * leave it untouched. Return the cleanup function when the owning root unmounts.
16
+ */
17
+ export function attachFoil(root: HTMLElement): () => void {
18
+ const document = root.ownerDocument;
19
+ const view = document.defaultView;
20
+ if (!view || typeof view.matchMedia !== "function"
21
+ || typeof view.requestAnimationFrame !== "function"
22
+ || typeof view.cancelAnimationFrame !== "function") return () => {};
23
+ const preference = view.matchMedia("(hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) and (forced-colors: none)");
24
+ const states = new Map<HTMLElement, Light>();
25
+ let targets: HTMLElement[] = [];
26
+ let needsTargets = true;
27
+ let pointer: Light | undefined;
28
+ let frame: number | null = null;
29
+ let previousTime: number | undefined;
30
+ let detached = false;
31
+ const restore = (target: HTMLElement) => {
32
+ for (const input of INPUTS) target.style.removeProperty(input);
33
+ };
34
+ const reset = () => {
35
+ if (frame !== null) view.cancelAnimationFrame(frame);
36
+ frame = null;
37
+ previousTime = undefined;
38
+ pointer = undefined;
39
+ for (const target of states.keys()) restore(target);
40
+ states.clear();
41
+ targets = [];
42
+ needsTargets = true;
43
+ };
44
+ const refreshTargets = () => {
45
+ targets = [...(root.matches("[data-foil]") ? [root] : []), ...root.querySelectorAll<HTMLElement>("[data-foil]")]
46
+ .filter((target) => {
47
+ const ancestor = target.parentElement?.closest("[data-foil]");
48
+ return !ancestor || !root.contains(ancestor);
49
+ });
50
+ const live = new Set(targets);
51
+ for (const target of states.keys()) {
52
+ if (!live.has(target)) { restore(target); states.delete(target); }
53
+ }
54
+ needsTargets = false;
55
+ };
56
+ const paint = (time: number) => {
57
+ frame = null;
58
+ if (detached || !preference.matches || document.hidden || !pointer) { reset(); return; }
59
+ if (needsTargets) refreshTargets();
60
+ const elapsed = previousTime === undefined ? 1000 / 60 : Math.max(0, Math.min(64, time - previousTime));
61
+ previousTime = time;
62
+ const ease = 1 - Math.exp(-elapsed / RESPONSE_MS);
63
+ // Read every rectangle before writing styles; there is no read/write/read loop.
64
+ const measured = targets.map((target) => ({ target, bounds: target.getBoundingClientRect() }));
65
+ let moving = false;
66
+ for (const { target, bounds } of measured) {
67
+ if (!target.isConnected || !root.contains(target) || bounds.width <= 0 || bounds.height <= 0
68
+ || bounds.bottom <= 0 || bounds.right <= 0 || bounds.top >= view.innerHeight || bounds.left >= view.innerWidth) {
69
+ if (states.has(target)) { restore(target); states.delete(target); }
70
+ continue;
71
+ }
72
+ // A minimum light field avoids tiny marks behaving like a pointer joystick.
73
+ const goal = {
74
+ x: clampLight(REST + (pointer.x - bounds.left - bounds.width / 2) / Math.max(160, bounds.width) * 60),
75
+ y: clampLight(REST + (pointer.y - bounds.top - bounds.height / 2) / Math.max(120, bounds.height) * 50),
76
+ };
77
+ const light = states.get(target) ?? { x: REST, y: REST };
78
+ for (const axis of ["x", "y"] as const) {
79
+ const distance = goal[axis] - light[axis];
80
+ if (Math.abs(distance) > SETTLE) { light[axis] += distance * ease; moving = true; }
81
+ else light[axis] = goal[axis];
82
+ }
83
+ states.set(target, light);
84
+ for (const axis of ["x", "y"] as const) {
85
+ const input = `--hraness-foil-${axis}`;
86
+ const value = `${light[axis].toFixed(2)}%`;
87
+ if (target.style.getPropertyValue(input) !== value) target.style.setProperty(input, value);
88
+ }
89
+ }
90
+ if (moving) frame = view.requestAnimationFrame(paint);
91
+ else previousTime = undefined;
92
+ };
93
+ const move = (event: PointerEvent) => {
94
+ if (!preference.matches || document.hidden || event.pointerType === "touch") { reset(); return; }
95
+ if (!Number.isFinite(event.clientX) || !Number.isFinite(event.clientY)) return;
96
+ pointer = { x: event.clientX, y: event.clientY };
97
+ needsTargets = true;
98
+ if (frame === null) frame = view.requestAnimationFrame(paint);
99
+ };
100
+ const leave = (event: PointerEvent) => { if (event.relatedTarget === null) reset(); };
101
+ const visibility = () => { if (document.hidden) reset(); };
102
+ view.addEventListener("pointermove", move, { passive: true });
103
+ view.addEventListener("pointerdown", move, { passive: true });
104
+ view.addEventListener("pointerout", leave, { passive: true });
105
+ view.addEventListener("pointercancel", reset);
106
+ view.addEventListener("blur", reset);
107
+ view.addEventListener("scroll", reset, { passive: true, capture: true });
108
+ view.addEventListener("resize", reset, { passive: true });
109
+ document.addEventListener("visibilitychange", visibility);
110
+ preference.addEventListener("change", reset);
111
+ return () => {
112
+ detached = true;
113
+ reset();
114
+ view.removeEventListener("pointermove", move);
115
+ view.removeEventListener("pointerdown", move);
116
+ view.removeEventListener("pointerout", leave);
117
+ view.removeEventListener("pointercancel", reset);
118
+ view.removeEventListener("blur", reset);
119
+ view.removeEventListener("scroll", reset, true);
120
+ view.removeEventListener("resize", reset);
121
+ document.removeEventListener("visibilitychange", visibility);
122
+ preference.removeEventListener("change", reset);
123
+ };
124
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @deprecated Retired with the Quiet marketing direction. Marketing heroes no
3
+ * longer have a pointer-driven light field, drift, or `data-hraness-hero-item`
4
+ * proximity. The function stays exported so existing callers compile; it
5
+ * attaches no listeners, writes no styles, schedules no frames, and returns an
6
+ * idempotent disposer. Remove the call when convenient.
7
+ */
8
+ export function attachHeroLight(root: HTMLElement): () => void {
9
+ void root;
10
+ return () => {};
11
+ }
@@ -0,0 +1,29 @@
1
+ import * as appearance from "../appearance.js";
2
+ import { installAppearanceMenus as installMenus } from "./appearance-menu.js";
3
+
4
+ export * from "./artifact-share.js";
5
+ export * from "./design-palette.js";
6
+ export * from "./foil.js";
7
+ export * from "./hero-light.js";
8
+ export * from "./status-page.js";
9
+ export * from "./sticky-offset.js";
10
+ export * from "../palette-appearance.js";
11
+
12
+ export type {
13
+ ConcreteDesignTheme,
14
+ DesignTheme,
15
+ DesignThemeLabels,
16
+ } from "../appearance.js";
17
+ export type {
18
+ AppearanceMenuInstallation,
19
+ AppearanceMenuOptions,
20
+ AppearanceStorage,
21
+ } from "./appearance-menu.js";
22
+ export const defaultDesignTheme = appearance.defaultDesignTheme;
23
+ export const designThemeLabel = appearance.designThemeLabel;
24
+ export const designThemes = appearance.designThemes;
25
+ export const designThemeStorageKey = appearance.designThemeStorageKey;
26
+ export const installAppearanceMenus = installMenus;
27
+ export const isDesignTheme = appearance.isDesignTheme;
28
+ export const normalizeDesignTheme = appearance.normalizeDesignTheme;
29
+ export const resolveDesignTheme = appearance.resolveDesignTheme;