@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,318 @@
1
+ import type { CSSProperties, ReactNode } from "react";
2
+
3
+ /*
4
+ * Shared pieces for the code-built mockups. Server-safe plain React: no hooks,
5
+ * no context, no StyleX, and no runtime dependency other than React, so a film
6
+ * build can render these with renderToStaticMarkup and one stylesheet,
7
+ * mockups.css.
8
+ */
9
+
10
+ /** A mockup's own color scheme. Omit it to follow the page's `color-scheme`. */
11
+ export type MockupTheme = "light" | "dark";
12
+
13
+ /** Data attributes that tell a product's own runtime to skip the sample text, such as `{ "data-sample-skip": "" }`. */
14
+ export type MockupOptOut = Readonly<Record<`data-${string}`, "">>;
15
+
16
+ /** Props every mockup root takes. */
17
+ export type MockupRootProps = Readonly<{
18
+ /** What the illustration shows, in one or two plain sentences. Becomes the root's accessible name. */
19
+ describe: string;
20
+ /** Pin the mockup to light or dark independently of the page. */
21
+ theme?: MockupTheme;
22
+ /** Data attributes placed on the root and on every sample text leaf the mockup renders. */
23
+ optOut?: MockupOptOut;
24
+ className?: string;
25
+ }>;
26
+
27
+ export function joinMockupClasses(...values: readonly (string | false | null | undefined)[]): string {
28
+ return values.filter((value) => typeof value === "string" && value !== "").join(" ");
29
+ }
30
+
31
+ /** Throws when a mockup has no description, because the root would have no accessible name. */
32
+ export function assertMockupDescription(describe: string, component: string): void {
33
+ if (typeof describe !== "string" || describe.trim() === "") {
34
+ throw new TypeError(`${component} needs a describe string; it is the illustration's accessible name.`);
35
+ }
36
+ }
37
+
38
+ function optOutAttributes(optOut: MockupOptOut | undefined): Record<string, ""> {
39
+ if (optOut === undefined) return {};
40
+ const attributes: Record<string, ""> = {};
41
+ for (const name of Object.keys(optOut)) {
42
+ if (!/^data-[a-z0-9-]+$/u.test(name)) throw new TypeError(`Mockup opt-out attribute ${JSON.stringify(name)} must be a lowercase data- attribute.`);
43
+ attributes[name] = "";
44
+ }
45
+ return attributes;
46
+ }
47
+
48
+ /**
49
+ * The root every mockup renders: one `role="img"` element whose name is the
50
+ * description, marked `data-nosnippet` so search engines do not quote sample
51
+ * text, with the theme and opt-out attributes applied.
52
+ */
53
+ export function MockupRoot({
54
+ children,
55
+ className,
56
+ describe,
57
+ kind,
58
+ optOut,
59
+ style,
60
+ theme,
61
+ }: MockupRootProps & Readonly<{ children: ReactNode; kind: string; style?: CSSProperties | undefined }>) {
62
+ assertMockupDescription(describe, kind);
63
+ return (
64
+ <div
65
+ {...optOutAttributes(optOut)}
66
+ aria-label={describe.trim()}
67
+ className={joinMockupClasses("hkm-root", `hkm-${kind}`, className)}
68
+ data-hkm-kind={kind}
69
+ data-hkm-theme={theme}
70
+ data-nosnippet=""
71
+ role="img"
72
+ style={style}
73
+ >
74
+ {children}
75
+ </div>
76
+ );
77
+ }
78
+
79
+ /**
80
+ * Sample text inside a mockup. It carries the opt-out attributes on the leaf,
81
+ * because some runtimes skip marked descendants but not the unit that holds
82
+ * them.
83
+ */
84
+ export function SampleText({ children, optOut }: Readonly<{ children: ReactNode; optOut?: MockupOptOut }>) {
85
+ return <span {...optOutAttributes(optOut)} className="hkm-sample">{children}</span>;
86
+ }
87
+
88
+ /** Splits text on blank lines into paragraphs of sample text. */
89
+ export function SampleParagraphs({
90
+ className,
91
+ optOut,
92
+ text,
93
+ }: Readonly<{ text: string; optOut?: MockupOptOut; className?: string }>) {
94
+ return (
95
+ <>
96
+ {text.split(/\n{2,}/u).map((paragraph, index) => (
97
+ <p className={className} key={index}>
98
+ <SampleText {...(optOut === undefined ? {} : { optOut })}>{paragraph}</SampleText>
99
+ </p>
100
+ ))}
101
+ </>
102
+ );
103
+ }
104
+
105
+ /**
106
+ * Formats a count the way feeds abbreviate it: 950, 1.2K, 12K, 3.4M.
107
+ * Use it for invented sample metrics only; real numbers come from a facts module.
108
+ */
109
+ export function compact(value: number): string {
110
+ if (!Number.isFinite(value)) throw new RangeError("compact() needs a finite number.");
111
+ const sign = value < 0 ? "-" : "";
112
+ const n = Math.abs(value);
113
+ const scaled = (divisor: number, suffix: string): string => {
114
+ const quotient = n / divisor;
115
+ const text = quotient < 10 ? (Math.floor(quotient * 10) / 10).toFixed(1).replace(/\.0$/u, "") : String(Math.floor(quotient));
116
+ return `${sign}${text}${suffix}`;
117
+ };
118
+ if (n < 1_000) return `${sign}${Math.round(n)}`;
119
+ if (n < 1_000_000) return scaled(1_000, "K");
120
+ if (n < 1_000_000_000) return scaled(1_000_000, "M");
121
+ return scaled(1_000_000_000, "B");
122
+ }
123
+
124
+ /** FNV-1a, so generated avatars and photos are stable across renders and runtimes. */
125
+ export function mockupHash(text: string): number {
126
+ let hash = 2166136261;
127
+ for (let index = 0; index < text.length; index += 1) hash = Math.imul(hash ^ text.charCodeAt(index), 16777619);
128
+ return hash >>> 0;
129
+ }
130
+
131
+ const GRADIENTS = [
132
+ ["#f6a57b", "#c2556b"],
133
+ ["#7fb3ff", "#4c5fd7"],
134
+ ["#8fdcb4", "#2f8f7a"],
135
+ ["#f7d774", "#e0823d"],
136
+ ["#c9a2ff", "#7653c8"],
137
+ ["#ff9fb8", "#c24a86"],
138
+ ["#9ad8e8", "#3a7fa3"],
139
+ ["#d6c2a8", "#8a6a4b"],
140
+ ] as const;
141
+
142
+ function gradientFor(seed: string, shift = 0): readonly [string, string] {
143
+ return GRADIENTS[(mockupHash(seed) >>> shift) % GRADIENTS.length] ?? GRADIENTS[0];
144
+ }
145
+
146
+ /** Up to two initials from a display name. */
147
+ export function mockupInitials(name: string): string {
148
+ // Code points are compared directly: Next.js compiles dependencies with a
149
+ // Babel build that cannot rewrite Unicode property escapes.
150
+ const isNameCharacter = (character: string): boolean =>
151
+ character.toLowerCase() !== character.toUpperCase() || /[0-9]/u.test(character) || (character.codePointAt(0) ?? 0) >= 0x3000;
152
+ return name
153
+ .split(/\s+/u)
154
+ .map((word) => Array.from(word).find(isNameCharacter) ?? "")
155
+ .filter(Boolean)
156
+ .slice(0, 2)
157
+ .map((initial) => initial.toUpperCase())
158
+ .join("");
159
+ }
160
+
161
+ /** A generated avatar: initials on a two-stop gradient picked from the name. Decorative. */
162
+ export function Avatar({
163
+ name,
164
+ size = 40,
165
+ square = false,
166
+ }: Readonly<{ name: string; size?: number; square?: boolean }>) {
167
+ const [from, to] = gradientFor(name);
168
+ const style: CSSProperties = {
169
+ background: `linear-gradient(135deg, ${from}, ${to})`,
170
+ borderRadius: square ? Math.round(size * 0.22) : "50%",
171
+ fontSize: Math.round(size * 0.38),
172
+ height: size,
173
+ width: size,
174
+ };
175
+ return (
176
+ <span aria-hidden="true" className="hkm-avatar" style={style}>
177
+ {mockupInitials(name)}
178
+ </span>
179
+ );
180
+ }
181
+
182
+ /**
183
+ * A soft gradient block that stands in for a photo. It reads as a picture at a
184
+ * glance and never as a real one. Decorative.
185
+ */
186
+ export function PlaceholderPhoto({
187
+ className,
188
+ ratio = "16 / 9",
189
+ seed,
190
+ }: Readonly<{ seed: string; ratio?: string; className?: string }>) {
191
+ const [a, b] = gradientFor(seed);
192
+ const [c] = gradientFor(seed, 3);
193
+ return (
194
+ <span
195
+ aria-hidden="true"
196
+ className={joinMockupClasses("hkm-photo", className)}
197
+ style={{
198
+ aspectRatio: ratio,
199
+ background: `radial-gradient(120% 90% at 20% 15%, ${a}cc, transparent 60%), radial-gradient(90% 80% at 85% 90%, ${c}bb, transparent 65%), linear-gradient(160deg, ${b}, ${a})`,
200
+ }}
201
+ />
202
+ );
203
+ }
204
+
205
+ /**
206
+ * A fixed-height window onto longer content, with a fade at the bottom that
207
+ * says the page keeps going.
208
+ */
209
+ export function ScrollFade({
210
+ children,
211
+ className,
212
+ fadeHeight = 56,
213
+ height,
214
+ }: Readonly<{ height: number; fadeHeight?: number; children: ReactNode; className?: string }>) {
215
+ if (!(height > 0)) throw new RangeError("ScrollFade needs a positive height.");
216
+ return (
217
+ <div
218
+ className={joinMockupClasses("hkm-scroll-fade", className)}
219
+ style={{ "--hkm-fade-height": `${fadeHeight}px`, "--hkm-scroll-height": `${height}px` } as CSSProperties}
220
+ >
221
+ {children}
222
+ </div>
223
+ );
224
+ }
225
+
226
+ /**
227
+ * A pulsing marker that points at one spot in a mockup, with a short label.
228
+ * `x` and `y` are percentages of the nearest positioned ancestor.
229
+ */
230
+ export function Hotspot({ label, x, y }: Readonly<{ x: number; y: number; label?: string }>) {
231
+ for (const [axis, value] of [["x", x], ["y", y]] as const) {
232
+ if (!Number.isFinite(value) || value < 0 || value > 100) throw new RangeError(`Hotspot ${axis} must be a percentage from 0 to 100.`);
233
+ }
234
+ return (
235
+ <span aria-hidden="true" className="hkm-hotspot" data-hkm-side={x > 60 ? "start" : "end"} style={{ insetBlockStart: `${y}%`, insetInlineStart: `${x}%` }}>
236
+ <span className="hkm-hotspot-ring" />
237
+ {label === undefined || label === "" ? null : <span className="hkm-hotspot-label">{label}</span>}
238
+ </span>
239
+ );
240
+ }
241
+
242
+ const GLYPHS = {
243
+ archive: <><rect height="4" rx="1" width="17" x="3.5" y="4.5" /><path d="M5 8.5V19a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V8.5M10 12.5h4" /></>,
244
+ back: <path d="m14.5 6-6 6 6 6" />,
245
+ bell: <><path d="M6 16.5V11a6 6 0 0 1 12 0v5.5l1.5 1.5h-15z" /><path d="M10 20.5a2.2 2.2 0 0 0 4 0" /></>,
246
+ bookmark: <path d="M6.5 4h11v16.5L12 16.5l-5.5 4z" />,
247
+ chart: <path d="M5 20V12M10 20V6M15 20v-9M20 20V9" />,
248
+ check: <path d="m6.5 12.5 3.5 3.5 7.5-8" />,
249
+ clock: <><circle cx="12" cy="12" r="8" /><path d="M12 7.5V12l3 2" /></>,
250
+ comment: <path d="M4.5 5.5h15v10h-9l-4.5 4v-4h-1.5z" />,
251
+ file: <path d="M6.5 3.5h7l4 4v13h-11zM13.5 3.5v4h4" />,
252
+ forward: <path d="m9.5 6 6 6-6 6" />,
253
+ grid: <><rect height="6" rx="1.2" width="6" x="4" y="4" /><rect height="6" rx="1.2" width="6" x="14" y="4" /><rect height="6" rx="1.2" width="6" x="4" y="14" /><rect height="6" rx="1.2" width="6" x="14" y="14" /></>,
254
+ heart: <path d="M12 20s-7.5-4.4-7.5-10A4.3 4.3 0 0 1 12 7.3 4.3 4.3 0 0 1 19.5 10c0 5.6-7.5 10-7.5 10z" />,
255
+ home: <path d="M4 10.5 12 4l8 6.5V20a1 1 0 0 1-1 1h-4.5v-6h-5v6H5a1 1 0 0 1-1-1z" />,
256
+ inbox: <><path d="M4 13.5 6.5 5h11l2.5 8.5V19H4z" /><path d="M4 13.5h4.5l1 2h5l1-2H20" /></>,
257
+ lock: <><rect height="9" rx="1.5" width="12" x="6" y="10.5" /><path d="M8.5 10.5V8a3.5 3.5 0 0 1 7 0v2.5" /></>,
258
+ mail: <><rect height="14" rx="2" width="18" x="3" y="5" /><path d="m3.5 6.5 8.5 6.5 8.5-6.5" /></>,
259
+ menu: <path d="M4 7h16M4 12h16M4 17h16" />,
260
+ more: <><circle cx="5.5" cy="12" fill="currentColor" r="1.4" stroke="none" /><circle cx="12" cy="12" fill="currentColor" r="1.4" stroke="none" /><circle cx="18.5" cy="12" fill="currentColor" r="1.4" stroke="none" /></>,
261
+ pencil: <path d="M4.5 19.5 5.5 15 15.5 5a2 2 0 0 1 3 3l-10 10zM13.5 7l3 3" />,
262
+ people: <><circle cx="9" cy="9" r="3.2" /><path d="M3.5 19c.8-3.3 3-5 5.5-5s4.7 1.7 5.5 5" /><circle cx="16.5" cy="8.5" r="2.6" /><path d="M16 13.6c2.3.1 4 1.6 4.6 4.4" /></>,
263
+ plus: <path d="M12 5v14M5 12h14" />,
264
+ reload: <><path d="M19 12a7 7 0 1 1-2.1-5" /><path d="M17.5 3.5V7.5h-4" /></>,
265
+ reply: <path d="M12 19.5c4.7 0 8.5-3.2 8.5-7.3S16.7 5 12 5 3.5 8.1 3.5 12.2c0 1.8.7 3.4 2 4.7L5 20.5l3.6-1.6c1 .4 2.2.6 3.4.6z" />,
266
+ repost: <><path d="M7 5 4 8l3 3" /><path d="M4 8h11a4 4 0 0 1 4 4v1" /><path d="m17 19 3-3-3-3" /><path d="M20 16H9a4 4 0 0 1-4-4v-1" /></>,
267
+ search: <><circle cx="11" cy="11" r="6.5" /><path d="m16 16 4.5 4.5" /></>,
268
+ send: <path d="m20.5 3.5-17 7 7 2.5 2.5 7zM10.5 13l10-9.5" />,
269
+ share: <><path d="M12 15V4" /><path d="m7.5 8.5 4.5-4.5 4.5 4.5" /><path d="M5 13v6a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-6" /></>,
270
+ sidebar: <><rect height="15" rx="2.5" width="18" x="3" y="4.5" /><path d="M9 4.5v15" /></>,
271
+ sparkle: <path d="M12 3.5 13.8 10l6.7 2-6.7 2L12 20.5 10.2 14l-6.7-2 6.7-2z" />,
272
+ star: <path d="m12 4 2.4 5 5.4.6-4 3.7 1.1 5.4L12 16l-4.9 2.7 1.1-5.4-4-3.7 5.4-.6z" />,
273
+ terminal: <><rect height="15" rx="2.5" width="18" x="3" y="4.5" /><path d="m7 9.5 3 2.5-3 2.5M12 15h5" /></>,
274
+ thumb: <path d="M7.5 10.5v9h-3v-9zM7.5 10.5l3.5-6.5c1.4 0 2.3 1 2 2.4l-.6 3.1h5.1c1.2 0 2 1 1.8 2.2l-1.2 6.2c-.2 1-1 1.6-2 1.6H7.5" />,
275
+ trash: <path d="M5 7h14M9.5 7V4.5h5V7M6.5 7l1 13h9l1-13" />,
276
+ user: <><circle cx="12" cy="8.5" r="3.8" /><path d="M4.5 20.5c1-4 4-6 7.5-6s6.5 2 7.5 6" /></>,
277
+ } as const;
278
+
279
+ /** The neutral stroked glyphs the mockups draw with. */
280
+ export type MockupGlyphName = keyof typeof GLYPHS;
281
+ export const mockupGlyphNames = Object.freeze(Object.keys(GLYPHS) as MockupGlyphName[]);
282
+
283
+ /** A neutral stroked glyph. Decorative: always aria-hidden. */
284
+ export function MockupGlyph({
285
+ className,
286
+ filled = false,
287
+ name,
288
+ size = 20,
289
+ }: Readonly<{ name: MockupGlyphName; size?: number; className?: string; filled?: boolean }>) {
290
+ return (
291
+ <svg
292
+ aria-hidden="true"
293
+ className={joinMockupClasses("hkm-glyph", className)}
294
+ fill={filled ? "currentColor" : "none"}
295
+ focusable="false"
296
+ height={size}
297
+ stroke="currentColor"
298
+ strokeLinecap="round"
299
+ strokeLinejoin="round"
300
+ strokeWidth={1.75}
301
+ viewBox="0 0 24 24"
302
+ width={size}
303
+ >
304
+ {GLYPHS[name]}
305
+ </svg>
306
+ );
307
+ }
308
+
309
+ /** The three window buttons at the start of a title bar. Decorative. */
310
+ export function WindowLights() {
311
+ return (
312
+ <span aria-hidden="true" className="hkm-lights">
313
+ <i />
314
+ <i />
315
+ <i />
316
+ </span>
317
+ );
318
+ }