@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,742 @@
1
+ "use client";
2
+
3
+ import * as stylex from "@stylexjs/stylex";
4
+ import { cn } from "@hraness/ui";
5
+ import {
6
+ createContext,
7
+ useCallback,
8
+ useContext,
9
+ useEffect,
10
+ useMemo,
11
+ useRef,
12
+ type CSSProperties,
13
+ type HTMLAttributes,
14
+ type ReactNode,
15
+ } from "react";
16
+
17
+ import {
18
+ createFoilCardPointerPose,
19
+ createFoilCardSeedPose,
20
+ type FoilCardPose,
21
+ } from "./foil-card-math.js";
22
+ import { foilCardSurfaceStyles as styles } from "./foil-card-surface.stylex.js";
23
+
24
+ export const foilCardPresets = [
25
+ "prism",
26
+ "aurora",
27
+ "etched",
28
+ "gold",
29
+ "fast",
30
+ "max",
31
+ ] as const;
32
+ export const foilCardIntensities = ["subtle", "standard", "vivid"] as const;
33
+ export const foilCardRenderModes = ["interactive", "static"] as const;
34
+ export const foilCardOrnaments = [
35
+ "none",
36
+ "corners",
37
+ "rails",
38
+ "circuit",
39
+ "radial",
40
+ "facets",
41
+ ] as const;
42
+
43
+ export type FoilCardPreset = (typeof foilCardPresets)[number];
44
+ export type FoilCardIntensity = (typeof foilCardIntensities)[number];
45
+ export type FoilCardRenderMode = (typeof foilCardRenderModes)[number];
46
+ export type FoilCardOrnament = (typeof foilCardOrnaments)[number];
47
+
48
+ export interface FoilCardSurfaceProps {
49
+ readonly children: ReactNode;
50
+ readonly className?: string;
51
+ readonly intensity: FoilCardIntensity;
52
+ /** Product-neutral edge treatment. Defaults to `none`. */
53
+ readonly ornament?: FoilCardOrnament;
54
+ readonly preset: FoilCardPreset;
55
+ readonly renderMode: FoilCardRenderMode;
56
+ readonly seed: string;
57
+ }
58
+
59
+ export interface FoilCardDeckProps
60
+ extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
61
+ readonly children: ReactNode;
62
+ }
63
+
64
+ type FoilCustomProperties = CSSProperties & Readonly<{
65
+ "--foil-light-x": string;
66
+ "--foil-light-y": string;
67
+ "--foil-rotate-x": string;
68
+ "--foil-rotate-y": string;
69
+ "--foil-spectrum-angle": string;
70
+ "--foil-activity": string;
71
+ "--foil-base-opacity": string;
72
+ "--foil-spectrum-opacity": string;
73
+ "--foil-sheen-opacity": string;
74
+ "--foil-texture-opacity": string;
75
+ "--foil-ornament-opacity": string;
76
+ }>;
77
+
78
+ interface FoilDeckRegistration {
79
+ readonly activeClassName: string | undefined;
80
+ readonly seedPose: FoilCardPose;
81
+ }
82
+
83
+ interface FoilDeckContextValue {
84
+ readonly register: (
85
+ element: HTMLElement,
86
+ registration: FoilDeckRegistration,
87
+ ) => () => void;
88
+ }
89
+
90
+ type PendingDeckInteraction =
91
+ | Readonly<{ clientX: number; clientY: number; element: HTMLElement; kind: "pointer" }>
92
+ | Readonly<{ element: HTMLElement; kind: "reset" }>;
93
+
94
+ const FoilDeckContext = createContext<FoilDeckContextValue | null>(null);
95
+ const delegatedSurfaceSelector =
96
+ ".hraness-design-foil-card-surface[data-foil-controller='deck']";
97
+
98
+ const presetStyles = {
99
+ aurora: {
100
+ base: styles.auroraBase,
101
+ spectrum: styles.auroraSpectrum,
102
+ texture: styles.auroraTexture,
103
+ },
104
+ etched: {
105
+ base: styles.etchedBase,
106
+ spectrum: styles.etchedSpectrum,
107
+ texture: styles.etchedTexture,
108
+ },
109
+ fast: {
110
+ base: styles.fastBase,
111
+ spectrum: styles.fastSpectrum,
112
+ texture: styles.fastTexture,
113
+ },
114
+ gold: {
115
+ base: styles.goldBase,
116
+ spectrum: styles.goldSpectrum,
117
+ texture: styles.goldTexture,
118
+ },
119
+ max: {
120
+ base: styles.maxBase,
121
+ spectrum: styles.maxSpectrum,
122
+ texture: styles.maxTexture,
123
+ },
124
+ prism: {
125
+ base: styles.prismBase,
126
+ spectrum: styles.prismSpectrum,
127
+ texture: styles.prismTexture,
128
+ },
129
+ } as const;
130
+
131
+ const intensityStyles = {
132
+ subtle: {
133
+ base: styles.baseSubtle,
134
+ sheen: styles.sheenSubtle,
135
+ spectrum: styles.spectrumSubtle,
136
+ texture: styles.textureSubtle,
137
+ ornament: styles.ornamentSubtle,
138
+ },
139
+ standard: {
140
+ base: styles.baseStandard,
141
+ sheen: styles.sheenStandard,
142
+ spectrum: styles.spectrumStandard,
143
+ texture: styles.textureStandard,
144
+ ornament: styles.ornamentStandard,
145
+ },
146
+ vivid: {
147
+ base: styles.baseVivid,
148
+ sheen: styles.sheenVivid,
149
+ spectrum: styles.spectrumVivid,
150
+ texture: styles.textureVivid,
151
+ ornament: styles.ornamentVivid,
152
+ },
153
+ } as const;
154
+
155
+ const ornamentStyles = {
156
+ circuit: styles.ornamentCircuit,
157
+ corners: styles.ornamentCorners,
158
+ facets: styles.ornamentFacets,
159
+ none: styles.ornamentNone,
160
+ radial: styles.ornamentRadial,
161
+ rails: styles.ornamentRails,
162
+ } as const;
163
+
164
+ const foilOpacityByIntensity = {
165
+ subtle: {
166
+ active: { base: 0.52, ornament: 0.46, sheen: 0.22, spectrum: 0.28, texture: 0.1 },
167
+ idle: { base: 0.34, ornament: 0.14, sheen: 0.03, spectrum: 0.08, texture: 0.04 },
168
+ },
169
+ standard: {
170
+ active: { base: 0.68, ornament: 0.66, sheen: 0.35, spectrum: 0.42, texture: 0.16 },
171
+ idle: { base: 0.44, ornament: 0.2, sheen: 0.04, spectrum: 0.12, texture: 0.06 },
172
+ },
173
+ vivid: {
174
+ active: { base: 0.84, ornament: 0.88, sheen: 0.52, spectrum: 0.6, texture: 0.24 },
175
+ idle: { base: 0.56, ornament: 0.26, sheen: 0.06, spectrum: 0.18, texture: 0.08 },
176
+ },
177
+ } as const;
178
+
179
+ function poseStyle(
180
+ pose: FoilCardPose,
181
+ intensity: FoilCardIntensity,
182
+ ): FoilCustomProperties {
183
+ const opacity = foilOpacityByIntensity[intensity].idle;
184
+ return {
185
+ "--foil-activity": "0",
186
+ "--foil-base-opacity": String(opacity.base),
187
+ "--foil-light-x": `${String(pose.highlightX)}%`,
188
+ "--foil-light-y": `${String(pose.highlightY)}%`,
189
+ "--foil-rotate-x": `${String(pose.rotateX)}deg`,
190
+ "--foil-rotate-y": `${String(pose.rotateY)}deg`,
191
+ "--foil-ornament-opacity": String(opacity.ornament),
192
+ "--foil-sheen-opacity": String(opacity.sheen),
193
+ "--foil-spectrum-opacity": String(opacity.spectrum),
194
+ "--foil-spectrum-angle": `${String(pose.spectrumAngle ?? 0)}deg`,
195
+ "--foil-texture-opacity": String(opacity.texture),
196
+ };
197
+ }
198
+
199
+ function applyPose(element: HTMLElement, pose: FoilCardPose): void {
200
+ element.style.setProperty("--foil-light-x", `${String(pose.highlightX)}%`);
201
+ element.style.setProperty("--foil-light-y", `${String(pose.highlightY)}%`);
202
+ element.style.setProperty("--foil-rotate-x", `${String(pose.rotateX)}deg`);
203
+ element.style.setProperty("--foil-rotate-y", `${String(pose.rotateY)}deg`);
204
+ if (pose.spectrumAngle !== undefined) {
205
+ element.style.setProperty(
206
+ "--foil-spectrum-angle",
207
+ `${String(pose.spectrumAngle)}deg`,
208
+ );
209
+ }
210
+ }
211
+
212
+ function focusPose(seedPose: FoilCardPose): FoilCardPose {
213
+ const pose: FoilCardPose = {
214
+ highlightX: seedPose.highlightX,
215
+ highlightY: seedPose.highlightY,
216
+ rotateX: 0,
217
+ rotateY: 0,
218
+ };
219
+ return seedPose.spectrumAngle === undefined
220
+ ? pose
221
+ : { ...pose, spectrumAngle: seedPose.spectrumAngle };
222
+ }
223
+
224
+ function setActive(
225
+ element: HTMLElement,
226
+ activeClassName: string | undefined,
227
+ active: boolean,
228
+ ): void {
229
+ const intensity = element.getAttribute("data-foil-intensity");
230
+ const selectedIntensity = intensity !== null && intensity in foilOpacityByIntensity
231
+ ? intensity as FoilCardIntensity
232
+ : "standard";
233
+ const opacity = foilOpacityByIntensity[selectedIntensity][active ? "active" : "idle"];
234
+ element.style.setProperty("--foil-activity", active ? "1" : "0");
235
+ element.style.setProperty("--foil-base-opacity", String(opacity.base));
236
+ element.style.setProperty("--foil-spectrum-opacity", String(opacity.spectrum));
237
+ element.style.setProperty("--foil-sheen-opacity", String(opacity.sheen));
238
+ element.style.setProperty("--foil-texture-opacity", String(opacity.texture));
239
+ element.style.setProperty("--foil-ornament-opacity", String(opacity.ornament));
240
+ if (active) element.setAttribute("data-foil-active", "true");
241
+ else element.removeAttribute("data-foil-active");
242
+ if (activeClassName !== undefined) {
243
+ for (const className of activeClassName.split(/\s+/u)) {
244
+ if (className.length > 0) element.classList.toggle(className, active);
245
+ }
246
+ }
247
+ }
248
+
249
+ function motionIsEnabled(
250
+ finePointer: MediaQueryList,
251
+ reducedMotion: MediaQueryList,
252
+ forcedColors: MediaQueryList,
253
+ ): boolean {
254
+ return finePointer.matches && !reducedMotion.matches && !forcedColors.matches;
255
+ }
256
+
257
+ function addMediaListener(media: MediaQueryList, listener: () => void): () => void {
258
+ if (typeof media.addEventListener !== "function") return () => undefined;
259
+ media.addEventListener("change", listener);
260
+ return () => media.removeEventListener("change", listener);
261
+ }
262
+
263
+ /**
264
+ * Delegates pointer-driven foil paint for a card collection to one controller.
265
+ * Descendant surfaces remain ordinary semantic DOM and automatically opt in.
266
+ */
267
+ export function FoilCardDeck({
268
+ children,
269
+ className,
270
+ ...props
271
+ }: FoilCardDeckProps) {
272
+ const rootRef = useRef<HTMLDivElement>(null);
273
+ const registrations = useRef(new Map<HTMLElement, FoilDeckRegistration>());
274
+ const activeElement = useRef<HTMLElement | null>(null);
275
+ const focusedElement = useRef<HTMLElement | null>(null);
276
+ const pointerElement = useRef<HTMLElement | null>(null);
277
+ const activeBounds = useRef<DOMRect | null>(null);
278
+ const pendingInteraction = useRef<PendingDeckInteraction | null>(null);
279
+ const frame = useRef<number | null>(null);
280
+ const resizeObserver = useRef<ResizeObserver | null>(null);
281
+
282
+ const deactivate = useCallback((element: HTMLElement): void => {
283
+ const registration = registrations.current.get(element);
284
+ if (registration !== undefined) {
285
+ applyPose(element, registration.seedPose);
286
+ setActive(element, registration.activeClassName, false);
287
+ }
288
+ if (activeElement.current === element) {
289
+ resizeObserver.current?.unobserve(element);
290
+ activeElement.current = null;
291
+ activeBounds.current = null;
292
+ }
293
+ }, []);
294
+
295
+ const register = useCallback((
296
+ element: HTMLElement,
297
+ registration: FoilDeckRegistration,
298
+ ): (() => void) => {
299
+ registrations.current.set(element, registration);
300
+ return () => {
301
+ if (pendingInteraction.current?.element === element) {
302
+ pendingInteraction.current = null;
303
+ }
304
+ if (focusedElement.current === element) focusedElement.current = null;
305
+ if (pointerElement.current === element) pointerElement.current = null;
306
+ deactivate(element);
307
+ registrations.current.delete(element);
308
+ };
309
+ }, [deactivate]);
310
+
311
+ const contextValue = useMemo<FoilDeckContextValue>(
312
+ () => ({ register }),
313
+ [register],
314
+ );
315
+
316
+ useEffect(() => {
317
+ const root = rootRef.current;
318
+ if (
319
+ root === null
320
+ || typeof window.matchMedia !== "function"
321
+ || typeof window.requestAnimationFrame !== "function"
322
+ || typeof window.cancelAnimationFrame !== "function"
323
+ ) {
324
+ return;
325
+ }
326
+
327
+ const finePointer = window.matchMedia("(hover: hover) and (pointer: fine)");
328
+ const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)");
329
+ const forcedColors = window.matchMedia("(forced-colors: active)");
330
+
331
+ if (typeof window.ResizeObserver === "function") {
332
+ resizeObserver.current = new window.ResizeObserver(() => {
333
+ activeBounds.current = null;
334
+ });
335
+ }
336
+
337
+ const deactivateCurrent = (): void => {
338
+ const current = activeElement.current;
339
+ if (current !== null) deactivate(current);
340
+ };
341
+ const activate = (element: HTMLElement): void => {
342
+ if (activeElement.current !== element) {
343
+ deactivateCurrent();
344
+ activeElement.current = element;
345
+ activeBounds.current = null;
346
+ resizeObserver.current?.observe(element);
347
+ }
348
+ const registration = registrations.current.get(element);
349
+ if (registration !== undefined) {
350
+ setActive(element, registration.activeClassName, true);
351
+ }
352
+ };
353
+ const activateFocus = (element: HTMLElement): void => {
354
+ const registration = registrations.current.get(element);
355
+ if (registration === undefined) return;
356
+ activate(element);
357
+ activeBounds.current = null;
358
+ applyPose(element, focusPose(registration.seedPose));
359
+ };
360
+ const restoreFocus = (): void => {
361
+ const focused = focusedElement.current;
362
+ if (focused !== null && registrations.current.has(focused)) {
363
+ activateFocus(focused);
364
+ }
365
+ };
366
+ const renderPendingInteraction = (): void => {
367
+ frame.current = null;
368
+ const interaction = pendingInteraction.current;
369
+ pendingInteraction.current = null;
370
+ if (interaction === null) return;
371
+ if (interaction.kind === "reset") {
372
+ deactivate(interaction.element);
373
+ restoreFocus();
374
+ return;
375
+ }
376
+
377
+ let bounds = activeBounds.current;
378
+ if (bounds === null) {
379
+ bounds = interaction.element.getBoundingClientRect();
380
+ activeBounds.current = bounds;
381
+ }
382
+ if (bounds.width <= 0 || bounds.height <= 0) return;
383
+ applyPose(interaction.element, createFoilCardPointerPose(
384
+ (interaction.clientX - bounds.left) / bounds.width,
385
+ (interaction.clientY - bounds.top) / bounds.height,
386
+ ));
387
+ };
388
+ const schedule = (interaction: PendingDeckInteraction): void => {
389
+ pendingInteraction.current = interaction;
390
+ if (frame.current === null) {
391
+ frame.current = window.requestAnimationFrame(renderPendingInteraction);
392
+ }
393
+ };
394
+ const findRegisteredSurface = (target: EventTarget | null): HTMLElement | null => {
395
+ if (!(target instanceof Element)) return null;
396
+ const surface = target.closest<HTMLElement>(delegatedSurfaceSelector);
397
+ return surface !== null && root.contains(surface) && registrations.current.has(surface)
398
+ ? surface
399
+ : null;
400
+ };
401
+ const findRegisteredFocusSurface = (
402
+ target: EventTarget | null,
403
+ ): HTMLElement | null => {
404
+ const ancestorSurface = findRegisteredSurface(target);
405
+ if (ancestorSurface !== null) return ancestorSurface;
406
+ if (!(target instanceof Element) || !root.contains(target)) return null;
407
+ let match: HTMLElement | null = null;
408
+ for (const candidate of target.querySelectorAll<HTMLElement>(
409
+ delegatedSurfaceSelector,
410
+ )) {
411
+ if (!registrations.current.has(candidate)) continue;
412
+ if (match !== null) return null;
413
+ match = candidate;
414
+ }
415
+ return match;
416
+ };
417
+ const handlePointerMove = (event: PointerEvent): void => {
418
+ if (event.pointerType !== "mouse" || !motionIsEnabled(
419
+ finePointer,
420
+ reducedMotion,
421
+ forcedColors,
422
+ )) return;
423
+ const element = findRegisteredSurface(event.target);
424
+ if (element === null) {
425
+ pointerElement.current = null;
426
+ const current = activeElement.current;
427
+ if (current !== null) schedule({ element: current, kind: "reset" });
428
+ return;
429
+ }
430
+ pointerElement.current = element;
431
+ activate(element);
432
+ schedule({
433
+ clientX: event.clientX,
434
+ clientY: event.clientY,
435
+ element,
436
+ kind: "pointer",
437
+ });
438
+ };
439
+ const handlePointerLeave = (event: PointerEvent): void => {
440
+ if (event.pointerType !== "mouse") return;
441
+ pointerElement.current = null;
442
+ const current = activeElement.current;
443
+ if (current !== null) schedule({ element: current, kind: "reset" });
444
+ };
445
+ const handleFocusIn = (event: FocusEvent): void => {
446
+ if (forcedColors.matches) return;
447
+ const element = findRegisteredFocusSurface(event.target);
448
+ if (element === null) return;
449
+ focusedElement.current = element;
450
+ activateFocus(element);
451
+ };
452
+ const handleFocusOut = (event: FocusEvent): void => {
453
+ const element = findRegisteredFocusSurface(event.target);
454
+ if (element === null || focusedElement.current !== element) return;
455
+ const next = findRegisteredFocusSurface(event.relatedTarget);
456
+ if (next === element) return;
457
+ focusedElement.current = next;
458
+ if (next !== null && !forcedColors.matches) {
459
+ activateFocus(next);
460
+ } else if (
461
+ activeElement.current === element
462
+ && pointerElement.current !== element
463
+ ) {
464
+ deactivate(element);
465
+ }
466
+ };
467
+ const invalidateBounds = (): void => {
468
+ activeBounds.current = null;
469
+ };
470
+ const handleMediaChange = (): void => {
471
+ if (!motionIsEnabled(finePointer, reducedMotion, forcedColors)) {
472
+ pendingInteraction.current = null;
473
+ pointerElement.current = null;
474
+ deactivateCurrent();
475
+ if (!forcedColors.matches) restoreFocus();
476
+ }
477
+ };
478
+
479
+ root.addEventListener("pointermove", handlePointerMove, { passive: true });
480
+ root.addEventListener("pointerleave", handlePointerLeave, { passive: true });
481
+ root.addEventListener("focusin", handleFocusIn);
482
+ root.addEventListener("focusout", handleFocusOut);
483
+ window.addEventListener("resize", invalidateBounds, { passive: true });
484
+ window.addEventListener("scroll", invalidateBounds, { capture: true, passive: true });
485
+ const removeMediaListeners = [
486
+ addMediaListener(finePointer, handleMediaChange),
487
+ addMediaListener(reducedMotion, handleMediaChange),
488
+ addMediaListener(forcedColors, handleMediaChange),
489
+ ];
490
+
491
+ return () => {
492
+ root.removeEventListener("pointermove", handlePointerMove);
493
+ root.removeEventListener("pointerleave", handlePointerLeave);
494
+ root.removeEventListener("focusin", handleFocusIn);
495
+ root.removeEventListener("focusout", handleFocusOut);
496
+ window.removeEventListener("resize", invalidateBounds);
497
+ window.removeEventListener("scroll", invalidateBounds, true);
498
+ for (const remove of removeMediaListeners) remove();
499
+ if (frame.current !== null) window.cancelAnimationFrame(frame.current);
500
+ pendingInteraction.current = null;
501
+ focusedElement.current = null;
502
+ pointerElement.current = null;
503
+ deactivateCurrent();
504
+ resizeObserver.current?.disconnect();
505
+ resizeObserver.current = null;
506
+ };
507
+ }, [deactivate]);
508
+
509
+ return (
510
+ <FoilDeckContext.Provider value={contextValue}>
511
+ <div
512
+ {...props}
513
+ className={cn("hraness-design-foil-card-deck", className)}
514
+ data-foil-card-deck=""
515
+ ref={rootRef}
516
+ >
517
+ {children}
518
+ </div>
519
+ </FoilDeckContext.Provider>
520
+ );
521
+ }
522
+
523
+ function requirePublicValue(
524
+ value: string,
525
+ supported: readonly string[],
526
+ label: string,
527
+ ): void {
528
+ if (!supported.includes(value)) {
529
+ throw new RangeError(`Unsupported foil card ${label}: ${value}.`);
530
+ }
531
+ }
532
+
533
+ /**
534
+ * Adds deterministic foil paint around ordinary semantic descendants. The
535
+ * component never turns its children into a canvas or interaction surrogate.
536
+ */
537
+ export function FoilCardSurface({
538
+ children,
539
+ className,
540
+ intensity,
541
+ ornament = "none",
542
+ preset,
543
+ renderMode,
544
+ seed,
545
+ }: FoilCardSurfaceProps) {
546
+ requirePublicValue(intensity, foilCardIntensities, "intensity");
547
+ requirePublicValue(preset, foilCardPresets, "preset");
548
+ requirePublicValue(renderMode, foilCardRenderModes, "render mode");
549
+ requirePublicValue(ornament, foilCardOrnaments, "ornament");
550
+ const deck = useContext(FoilDeckContext);
551
+ const rootRef = useRef<HTMLDivElement>(null);
552
+ const seedPose = useMemo(() => createFoilCardSeedPose(seed), [seed]);
553
+ const seededStyle = poseStyle(seedPose, intensity);
554
+ const selectedPreset = presetStyles[preset];
555
+ const selectedIntensity = intensityStyles[intensity];
556
+ const rootPresentation = stylex.props(
557
+ styles.base,
558
+ renderMode === "interactive" ? styles.interactive : styles.static,
559
+ );
560
+ const basePresentation = stylex.props(
561
+ styles.layer,
562
+ styles.baseLayer,
563
+ selectedPreset.base,
564
+ selectedIntensity.base,
565
+ );
566
+ const spectrumPresentation = stylex.props(
567
+ styles.layer,
568
+ styles.spectrumLayer,
569
+ selectedPreset.spectrum,
570
+ selectedIntensity.spectrum,
571
+ );
572
+ const sheenPresentation = stylex.props(
573
+ styles.layer,
574
+ styles.sheenLayer,
575
+ selectedIntensity.sheen,
576
+ );
577
+ const texturePresentation = stylex.props(
578
+ styles.layer,
579
+ styles.textureLayer,
580
+ selectedPreset.texture,
581
+ selectedIntensity.texture,
582
+ );
583
+ const ornamentPresentation = stylex.props(
584
+ styles.layer,
585
+ styles.ornamentLayer,
586
+ ornamentStyles[ornament],
587
+ selectedIntensity.ornament,
588
+ );
589
+ const contentPresentation = stylex.props(styles.content);
590
+ const activePresentation = stylex.props(styles.active);
591
+
592
+ useEffect(() => {
593
+ if (renderMode !== "interactive") return;
594
+ const root = rootRef.current;
595
+ if (root === null) return;
596
+ if (deck !== null) {
597
+ return deck.register(root, {
598
+ activeClassName: activePresentation.className,
599
+ seedPose,
600
+ });
601
+ }
602
+ if (
603
+ typeof window.matchMedia !== "function"
604
+ || typeof window.requestAnimationFrame !== "function"
605
+ || typeof window.cancelAnimationFrame !== "function"
606
+ ) {
607
+ return;
608
+ }
609
+
610
+ const finePointer = window.matchMedia("(hover: hover) and (pointer: fine)");
611
+ const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)");
612
+ const forcedColors = window.matchMedia("(forced-colors: active)");
613
+ let pointerMotionEnabled = motionIsEnabled(
614
+ finePointer,
615
+ reducedMotion,
616
+ forcedColors,
617
+ );
618
+
619
+ let bounds: DOMRect | null = null;
620
+ let frame: number | null = null;
621
+ let pendingInteraction:
622
+ | Readonly<{ clientX: number; clientY: number; kind: "pointer" }>
623
+ | Readonly<{ kind: "pose"; pose: FoilCardPose }>
624
+ | null = null;
625
+
626
+ const renderPendingInteraction = (): void => {
627
+ frame = null;
628
+ const interaction = pendingInteraction;
629
+ pendingInteraction = null;
630
+ if (interaction === null) return;
631
+ if (interaction.kind === "pose") {
632
+ applyPose(root, interaction.pose);
633
+ setActive(root, activePresentation.className, false);
634
+ bounds = null;
635
+ return;
636
+ }
637
+ bounds ??= root.getBoundingClientRect();
638
+ if (bounds.width <= 0 || bounds.height <= 0) return;
639
+ applyPose(root, createFoilCardPointerPose(
640
+ (interaction.clientX - bounds.left) / bounds.width,
641
+ (interaction.clientY - bounds.top) / bounds.height,
642
+ ));
643
+ };
644
+ const scheduleInteraction = (
645
+ interaction: Exclude<typeof pendingInteraction, null>,
646
+ ): void => {
647
+ pendingInteraction = interaction;
648
+ if (frame === null) {
649
+ frame = window.requestAnimationFrame(renderPendingInteraction);
650
+ }
651
+ };
652
+ const handlePointerMove = (event: PointerEvent): void => {
653
+ if (event.pointerType !== "mouse" || !pointerMotionEnabled) return;
654
+ setActive(root, activePresentation.className, true);
655
+ scheduleInteraction({
656
+ clientX: event.clientX,
657
+ clientY: event.clientY,
658
+ kind: "pointer",
659
+ });
660
+ };
661
+ const handlePointerLeave = (event: PointerEvent): void => {
662
+ if (event.pointerType === "mouse" && pointerMotionEnabled) {
663
+ scheduleInteraction({ kind: "pose", pose: seedPose });
664
+ }
665
+ };
666
+ const handleFocusIn = (): void => {
667
+ if (forcedColors.matches) return;
668
+ applyPose(root, focusPose(seedPose));
669
+ setActive(root, activePresentation.className, true);
670
+ };
671
+ const handleFocusOut = (event: FocusEvent): void => {
672
+ if (event.relatedTarget instanceof Node && root.contains(event.relatedTarget)) return;
673
+ applyPose(root, seedPose);
674
+ setActive(root, activePresentation.className, false);
675
+ bounds = null;
676
+ };
677
+ const handleMediaChange = (): void => {
678
+ const containsFocus = document.activeElement instanceof Node
679
+ && root.contains(document.activeElement);
680
+ pointerMotionEnabled = motionIsEnabled(
681
+ finePointer,
682
+ reducedMotion,
683
+ forcedColors,
684
+ );
685
+ if (pointerMotionEnabled && !forcedColors.matches) return;
686
+ pendingInteraction = null;
687
+ if (frame !== null) window.cancelAnimationFrame(frame);
688
+ frame = null;
689
+ applyPose(root, seedPose);
690
+ setActive(root, activePresentation.className, false);
691
+ bounds = null;
692
+ if (containsFocus && !forcedColors.matches) {
693
+ applyPose(root, focusPose(seedPose));
694
+ setActive(root, activePresentation.className, true);
695
+ }
696
+ };
697
+
698
+ root.addEventListener("pointermove", handlePointerMove, { passive: true });
699
+ root.addEventListener("pointerleave", handlePointerLeave, { passive: true });
700
+ root.addEventListener("focusin", handleFocusIn);
701
+ root.addEventListener("focusout", handleFocusOut);
702
+ const removeMediaListeners = [
703
+ addMediaListener(finePointer, handleMediaChange),
704
+ addMediaListener(reducedMotion, handleMediaChange),
705
+ addMediaListener(forcedColors, handleMediaChange),
706
+ ];
707
+ return () => {
708
+ root.removeEventListener("pointermove", handlePointerMove);
709
+ root.removeEventListener("pointerleave", handlePointerLeave);
710
+ root.removeEventListener("focusin", handleFocusIn);
711
+ root.removeEventListener("focusout", handleFocusOut);
712
+ for (const remove of removeMediaListeners) remove();
713
+ if (frame !== null) window.cancelAnimationFrame(frame);
714
+ setActive(root, activePresentation.className, false);
715
+ };
716
+ }, [activePresentation.className, deck, renderMode, seedPose]);
717
+
718
+ return (
719
+ <div
720
+ {...rootPresentation}
721
+ className={cn(
722
+ "hraness-design-foil-card-surface",
723
+ rootPresentation.className,
724
+ className,
725
+ )}
726
+ data-foil-intensity={intensity}
727
+ data-foil-controller={deck === null ? "standalone" : "deck"}
728
+ data-foil-ornament={ornament}
729
+ data-foil-preset={preset}
730
+ data-foil-render-mode={renderMode}
731
+ ref={rootRef}
732
+ style={seededStyle}
733
+ >
734
+ <span {...basePresentation} aria-hidden="true" />
735
+ <div {...contentPresentation}>{children}</div>
736
+ <span {...spectrumPresentation} aria-hidden="true" />
737
+ <span {...sheenPresentation} aria-hidden="true" />
738
+ <span {...texturePresentation} aria-hidden="true" />
739
+ <span {...ornamentPresentation} aria-hidden="true" />
740
+ </div>
741
+ );
742
+ }