@deriv-com/dds-marketing 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (315) hide show
  1. package/.llm/components/awards.md +209 -0
  2. package/.llm/components/background.md +89 -40
  3. package/.llm/components/bottomsheet.md +5 -0
  4. package/.llm/components/comparisoncards.md +257 -0
  5. package/.llm/components/comparisontable.md +231 -0
  6. package/.llm/components/ctabanner.md +9 -12
  7. package/.llm/components/ctabannerslider.md +15 -2
  8. package/.llm/components/datatable.md +273 -0
  9. package/.llm/components/downloadbanner.md +283 -0
  10. package/.llm/components/featurecards.md +41 -3
  11. package/.llm/components/featurecardscarousel.md +311 -0
  12. package/.llm/components/featuresplit.md +5 -0
  13. package/.llm/components/featurestack.md +227 -0
  14. package/.llm/components/illustrationcard.md +2 -2
  15. package/.llm/components/illustrationscene.md +6 -10
  16. package/.llm/components/imagebanner.md +278 -0
  17. package/.llm/components/navbar.md +1 -1
  18. package/.llm/components/navbaritem.md +4 -0
  19. package/.llm/components/pageheader.md +4 -3
  20. package/.llm/components/paginationdots.md +6 -1
  21. package/.llm/components/producthero.md +35 -11
  22. package/.llm/components/resourcebanner.md +247 -0
  23. package/.llm/components/scenescroll.md +222 -0
  24. package/.llm/components/select.md +17 -0
  25. package/.llm/components/stats.md +219 -0
  26. package/.llm/components/steps.md +286 -0
  27. package/.llm/components/stickycards.md +251 -0
  28. package/.llm/components/tableheadercell.md +19 -0
  29. package/.llm/components/teamgrid.md +243 -0
  30. package/.llm/components/testimonialrow.md +5 -0
  31. package/.llm/components/testimonialslider.md +14 -1
  32. package/.llm/components/textarea.md +13 -0
  33. package/.llm/components/textinput.md +13 -0
  34. package/.llm/components/textscroll.md +187 -0
  35. package/.llm/components/tiers.md +227 -0
  36. package/.llm/digest/awards.md +45 -0
  37. package/.llm/digest/background.md +11 -10
  38. package/.llm/digest/bottomsheet.md +2 -0
  39. package/.llm/digest/comparisoncards.md +51 -0
  40. package/.llm/digest/comparisontable.md +53 -0
  41. package/.llm/digest/ctabanner.md +4 -4
  42. package/.llm/digest/ctabannerslider.md +4 -0
  43. package/.llm/digest/datatable.md +65 -0
  44. package/.llm/digest/downloadbanner.md +65 -0
  45. package/.llm/digest/featurecards.md +1 -1
  46. package/.llm/digest/featurecardscarousel.md +54 -0
  47. package/.llm/digest/featuresplit.md +1 -0
  48. package/.llm/digest/featurestack.md +48 -0
  49. package/.llm/digest/illustrationscene.md +2 -3
  50. package/.llm/digest/imagebanner.md +64 -0
  51. package/.llm/digest/navbaritem.md +1 -0
  52. package/.llm/digest/pageheader.md +2 -1
  53. package/.llm/digest/paginationdots.md +3 -1
  54. package/.llm/digest/producthero.md +4 -2
  55. package/.llm/digest/resourcebanner.md +53 -0
  56. package/.llm/digest/scenescroll.md +49 -0
  57. package/.llm/digest/select.md +5 -0
  58. package/.llm/digest/stats.md +46 -0
  59. package/.llm/digest/steps.md +55 -0
  60. package/.llm/digest/stickycards.md +52 -0
  61. package/.llm/digest/tableheadercell.md +4 -0
  62. package/.llm/digest/teamgrid.md +53 -0
  63. package/.llm/digest/testimonialrow.md +2 -0
  64. package/.llm/digest/testimonialslider.md +4 -0
  65. package/.llm/digest/textarea.md +4 -0
  66. package/.llm/digest/textinput.md +4 -0
  67. package/.llm/digest/textscroll.md +43 -0
  68. package/.llm/digest/tiers.md +50 -0
  69. package/.llm/index.md +19 -3
  70. package/.llm/tokens.md +26 -1
  71. package/AGENTS.md +17 -1
  72. package/dist/components/awards-data.d.ts +24 -0
  73. package/dist/components/awards-data.js +11 -0
  74. package/dist/components/block-image-data.d.ts +24 -0
  75. package/dist/components/block-image-data.js +9 -0
  76. package/dist/components/blocks/awards/Awards.css +134 -0
  77. package/dist/components/blocks/awards/Awards.d.ts +24 -0
  78. package/dist/components/blocks/awards/Awards.js +114 -0
  79. package/dist/components/blocks/awards/Awards.types.d.ts +31 -0
  80. package/dist/components/blocks/awards/Awards.types.js +16 -0
  81. package/dist/components/blocks/awards/awards-impl.css +222 -0
  82. package/dist/components/blocks/cards-of.d.ts +16 -0
  83. package/dist/components/blocks/cards-of.js +23 -0
  84. package/dist/components/blocks/comparison-cards/ComparisonCards.css +195 -0
  85. package/dist/components/blocks/comparison-cards/ComparisonCards.d.ts +24 -0
  86. package/dist/components/blocks/comparison-cards/ComparisonCards.js +75 -0
  87. package/dist/components/blocks/comparison-cards/ComparisonCards.types.d.ts +49 -0
  88. package/dist/components/blocks/comparison-cards/ComparisonCards.types.js +26 -0
  89. package/dist/components/blocks/comparison-cards/comparison-cards-impl.css +262 -0
  90. package/dist/components/blocks/comparison-table/ComparisonTable.css +111 -0
  91. package/dist/components/blocks/comparison-table/ComparisonTable.d.ts +25 -0
  92. package/dist/components/blocks/comparison-table/ComparisonTable.js +100 -0
  93. package/dist/components/blocks/comparison-table/ComparisonTable.types.d.ts +36 -0
  94. package/dist/components/blocks/comparison-table/ComparisonTable.types.js +15 -0
  95. package/dist/components/blocks/comparison-table/comparison-table-impl.css +143 -0
  96. package/dist/components/blocks/cta-banner/CtaBanner.css +1 -0
  97. package/dist/components/blocks/cta-banner/CtaBanner.js +2 -2
  98. package/dist/components/blocks/cta-banner/CtaBanner.types.d.ts +4 -3
  99. package/dist/components/blocks/cta-banner/CtaBanner.types.js +1 -1
  100. package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.css +1 -0
  101. package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.d.ts +1 -1
  102. package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.js +6 -2
  103. package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.types.d.ts +8 -0
  104. package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.types.js +1 -1
  105. package/dist/components/blocks/data-table/DataTable.css +170 -0
  106. package/dist/components/blocks/data-table/DataTable.d.ts +29 -0
  107. package/dist/components/blocks/data-table/DataTable.js +84 -0
  108. package/dist/components/blocks/data-table/DataTable.types.d.ts +69 -0
  109. package/dist/components/blocks/data-table/DataTable.types.js +19 -0
  110. package/dist/components/blocks/data-table/data-table-impl.css +155 -0
  111. package/dist/components/blocks/download-banner/DownloadBanner.css +228 -0
  112. package/dist/components/blocks/download-banner/DownloadBanner.d.ts +28 -0
  113. package/dist/components/blocks/download-banner/DownloadBanner.js +75 -0
  114. package/dist/components/blocks/download-banner/DownloadBanner.types.d.ts +56 -0
  115. package/dist/components/blocks/download-banner/DownloadBanner.types.js +23 -0
  116. package/dist/components/blocks/download-banner/download-banner-impl.css +216 -0
  117. package/dist/components/blocks/faq-accordion/FaqAccordion.css +1 -0
  118. package/dist/components/blocks/faq-accordion/faq-accordion-impl.css +3 -7
  119. package/dist/components/blocks/feature-bullets/FeatureBullets.css +1 -0
  120. package/dist/components/blocks/feature-bullets/feature-bullets-impl.css +14 -1
  121. package/dist/components/blocks/feature-cards/FeatureCards.css +1 -0
  122. package/dist/components/blocks/feature-cards/FeatureCards.d.ts +2 -1
  123. package/dist/components/blocks/feature-cards/FeatureCards.js +4 -21
  124. package/dist/components/blocks/feature-cards/feature-cards-impl.css +10 -9
  125. package/dist/components/blocks/feature-cards-carousel/FeatureCardsCarousel.css +115 -0
  126. package/dist/components/blocks/feature-cards-carousel/FeatureCardsCarousel.d.ts +30 -0
  127. package/dist/components/blocks/feature-cards-carousel/FeatureCardsCarousel.js +160 -0
  128. package/dist/components/blocks/feature-cards-carousel/FeatureCardsCarousel.types.d.ts +52 -0
  129. package/dist/components/blocks/feature-cards-carousel/FeatureCardsCarousel.types.js +17 -0
  130. package/dist/components/blocks/feature-cards-carousel/feature-cards-carousel-impl.css +207 -0
  131. package/dist/components/blocks/feature-scroll/FeatureScroll.css +1 -0
  132. package/dist/components/blocks/feature-split/FeatureSplit.css +1 -0
  133. package/dist/components/blocks/feature-split/feature-split-impl.css +14 -1
  134. package/dist/components/blocks/feature-stack/FeatureStack.css +86 -0
  135. package/dist/components/blocks/feature-stack/FeatureStack.d.ts +19 -0
  136. package/dist/components/blocks/feature-stack/FeatureStack.js +27 -0
  137. package/dist/components/blocks/feature-stack/FeatureStack.types.d.ts +34 -0
  138. package/dist/components/blocks/feature-stack/FeatureStack.types.js +14 -0
  139. package/dist/components/blocks/feature-stack/feature-stack-impl.css +87 -0
  140. package/dist/components/blocks/image-banner/ImageBanner.css +133 -0
  141. package/dist/components/blocks/image-banner/ImageBanner.d.ts +32 -0
  142. package/dist/components/blocks/image-banner/ImageBanner.js +41 -0
  143. package/dist/components/blocks/image-banner/ImageBanner.types.d.ts +54 -0
  144. package/dist/components/blocks/image-banner/ImageBanner.types.js +14 -0
  145. package/dist/components/blocks/image-banner/image-banner-impl.css +281 -0
  146. package/dist/components/blocks/landing-hero/LandingHero.css +1 -0
  147. package/dist/components/blocks/logo-columns/LogoColumns.css +1 -0
  148. package/dist/components/blocks/logo-rows/LogoRows.css +1 -0
  149. package/dist/components/blocks/page-header/PageHeader.css +1 -0
  150. package/dist/components/blocks/page-header/PageHeader.js +2 -2
  151. package/dist/components/blocks/page-header/PageHeader.types.d.ts +4 -2
  152. package/dist/components/blocks/page-header/PageHeader.types.js +1 -1
  153. package/dist/components/blocks/page-header/page-header-impl.css +5 -3
  154. package/dist/components/blocks/product-hero/ProductHero.css +8 -3
  155. package/dist/components/blocks/product-hero/ProductHero.d.ts +1 -1
  156. package/dist/components/blocks/product-hero/ProductHero.js +5 -2
  157. package/dist/components/blocks/product-hero/ProductHero.types.d.ts +5 -1
  158. package/dist/components/blocks/product-hero/ProductHero.types.js +1 -0
  159. package/dist/components/blocks/product-hero/product-hero-impl.css +43 -12
  160. package/dist/components/blocks/resource-banner/ResourceBanner.css +106 -0
  161. package/dist/components/blocks/resource-banner/ResourceBanner.d.ts +32 -0
  162. package/dist/components/blocks/resource-banner/ResourceBanner.js +51 -0
  163. package/dist/components/blocks/resource-banner/ResourceBanner.types.d.ts +44 -0
  164. package/dist/components/blocks/resource-banner/ResourceBanner.types.js +14 -0
  165. package/dist/components/blocks/resource-banner/resource-banner-impl.css +69 -0
  166. package/dist/components/blocks/scene-scroll/SceneScroll.css +93 -0
  167. package/dist/components/blocks/scene-scroll/SceneScroll.d.ts +36 -0
  168. package/dist/components/blocks/scene-scroll/SceneScroll.js +54 -0
  169. package/dist/components/blocks/scene-scroll/SceneScroll.types.d.ts +32 -0
  170. package/dist/components/blocks/scene-scroll/SceneScroll.types.js +15 -0
  171. package/dist/components/blocks/scene-scroll/scene-scroll-impl.css +275 -0
  172. package/dist/components/blocks/stats/Stats.css +139 -0
  173. package/dist/components/blocks/stats/Stats.d.ts +24 -0
  174. package/dist/components/blocks/stats/Stats.js +120 -0
  175. package/dist/components/blocks/stats/Stats.types.d.ts +36 -0
  176. package/dist/components/blocks/stats/Stats.types.js +19 -0
  177. package/dist/components/blocks/stats/stats-count.d.ts +34 -0
  178. package/dist/components/blocks/stats/stats-count.js +61 -0
  179. package/dist/components/blocks/stats/stats-impl.css +284 -0
  180. package/dist/components/blocks/stats/use-wheel.d.ts +37 -0
  181. package/dist/components/blocks/stats/use-wheel.js +126 -0
  182. package/dist/components/blocks/steps/Steps.css +202 -0
  183. package/dist/components/blocks/steps/Steps.d.ts +32 -0
  184. package/dist/components/blocks/steps/Steps.js +65 -0
  185. package/dist/components/blocks/steps/Steps.types.d.ts +48 -0
  186. package/dist/components/blocks/steps/Steps.types.js +21 -0
  187. package/dist/components/blocks/steps/steps-impl.css +216 -0
  188. package/dist/components/blocks/steps/use-turn.d.ts +33 -0
  189. package/dist/components/blocks/steps/use-turn.js +75 -0
  190. package/dist/components/blocks/sticky-cards/StickyCards.css +154 -0
  191. package/dist/components/blocks/sticky-cards/StickyCards.d.ts +35 -0
  192. package/dist/components/blocks/sticky-cards/StickyCards.js +153 -0
  193. package/dist/components/blocks/sticky-cards/StickyCards.types.d.ts +43 -0
  194. package/dist/components/blocks/sticky-cards/StickyCards.types.js +18 -0
  195. package/dist/components/blocks/sticky-cards/sticky-cards-impl.css +393 -0
  196. package/dist/components/blocks/team-grid/TeamGrid.css +159 -0
  197. package/dist/components/blocks/team-grid/TeamGrid.d.ts +28 -0
  198. package/dist/components/blocks/team-grid/TeamGrid.js +74 -0
  199. package/dist/components/blocks/team-grid/TeamGrid.types.d.ts +51 -0
  200. package/dist/components/blocks/team-grid/TeamGrid.types.js +20 -0
  201. package/dist/components/blocks/team-grid/team-grid-impl.css +129 -0
  202. package/dist/components/blocks/testimonial-row/TestimonialRow.css +1 -0
  203. package/dist/components/blocks/testimonial-row/TestimonialRow.d.ts +1 -1
  204. package/dist/components/blocks/testimonial-row/TestimonialRow.js +4 -3
  205. package/dist/components/blocks/testimonial-row/TestimonialRow.types.d.ts +3 -0
  206. package/dist/components/blocks/testimonial-row/TestimonialRow.types.js +1 -1
  207. package/dist/components/blocks/testimonial-row/testimonial-row-impl.css +8 -0
  208. package/dist/components/blocks/testimonial-slider/TestimonialSlider.css +1 -0
  209. package/dist/components/blocks/testimonial-slider/TestimonialSlider.d.ts +1 -1
  210. package/dist/components/blocks/testimonial-slider/TestimonialSlider.js +2 -2
  211. package/dist/components/blocks/testimonial-slider/TestimonialSlider.types.d.ts +8 -0
  212. package/dist/components/blocks/testimonial-slider/TestimonialSlider.types.js +1 -1
  213. package/dist/components/blocks/text-scroll/TextScroll.css +53 -0
  214. package/dist/components/blocks/text-scroll/TextScroll.d.ts +5 -0
  215. package/dist/components/blocks/text-scroll/TextScroll.js +71 -0
  216. package/dist/components/blocks/text-scroll/TextScroll.types.d.ts +22 -0
  217. package/dist/components/blocks/text-scroll/TextScroll.types.js +10 -0
  218. package/dist/components/blocks/text-scroll/text-scroll-impl.css +115 -0
  219. package/dist/components/blocks/tiers/Tiers.css +233 -0
  220. package/dist/components/blocks/tiers/Tiers.d.ts +32 -0
  221. package/dist/components/blocks/tiers/Tiers.js +68 -0
  222. package/dist/components/blocks/tiers/Tiers.types.d.ts +45 -0
  223. package/dist/components/blocks/tiers/Tiers.types.js +25 -0
  224. package/dist/components/blocks/tiers/tiers-impl.css +185 -0
  225. package/dist/components/blocks/use-rotation.d.ts +12 -2
  226. package/dist/components/blocks/use-rotation.js +6 -28
  227. package/dist/components/comparison-cards-data.d.ts +59 -0
  228. package/dist/components/comparison-cards-data.js +12 -0
  229. package/dist/components/comparison-table-data.d.ts +39 -0
  230. package/dist/components/comparison-table-data.js +11 -0
  231. package/dist/components/core/accordion-item/AccordionItem.css +1 -5
  232. package/dist/components/core/background/Background.css +23 -0
  233. package/dist/components/core/background/Background.d.ts +10 -4
  234. package/dist/components/core/background/Background.js +16 -7
  235. package/dist/components/core/background/Background.types.d.ts +11 -8
  236. package/dist/components/core/background/Background.types.js +3 -1
  237. package/dist/components/core/background/background-impl.css +334 -1
  238. package/dist/components/core/background/halftone.d.ts +11 -0
  239. package/dist/components/core/background/halftone.js +43 -3
  240. package/dist/components/core/background/patterns.d.ts +66 -0
  241. package/dist/components/core/background/patterns.js +244 -0
  242. package/dist/components/core/background/shader.d.ts +44 -0
  243. package/dist/components/core/background/shader.js +307 -0
  244. package/dist/components/core/background/use-shader.d.ts +11 -0
  245. package/dist/components/core/background/use-shader.js +25 -0
  246. package/dist/components/core/background/use-spotlight.d.ts +2 -1
  247. package/dist/components/core/background/use-spotlight.js +9 -1
  248. package/dist/components/core/bottom-sheet/BottomSheet.js +2 -2
  249. package/dist/components/core/bottom-sheet/BottomSheet.types.d.ts +3 -1
  250. package/dist/components/core/breadcrumb/Breadcrumb.d.ts +1 -0
  251. package/dist/components/core/breadcrumb/Breadcrumb.js +1 -0
  252. package/dist/components/core/breadcrumb/breadcrumb-impl.css +11 -0
  253. package/dist/components/core/button/button-impl.css +14 -1
  254. package/dist/components/core/illustration-card/IllustrationCard.js +1 -1
  255. package/dist/components/core/illustration-card/illustration-card-impl.css +20 -0
  256. package/dist/components/core/illustration-scene/IllustrationScene.css +4 -7
  257. package/dist/components/core/illustration-scene/IllustrationScene.types.d.ts +2 -3
  258. package/dist/components/core/illustration-scene/illustration-scene-impl.css +7 -1
  259. package/dist/components/core/link/Link.js +7 -3
  260. package/dist/components/core/link/link-impl.css +29 -2
  261. package/dist/components/core/navbar/Navbar.css +2 -2
  262. package/dist/components/core/navbar/Navbar.types.d.ts +1 -1
  263. package/dist/components/core/navbar-item/NavbarItem.d.ts +2 -1
  264. package/dist/components/core/navbar-item/NavbarItem.js +3 -10
  265. package/dist/components/core/navbar-item/NavbarItem.types.d.ts +4 -1
  266. package/dist/components/core/navbar-item/NavbarItem.types.js +1 -1
  267. package/dist/components/core/navbar-item/navbar-item-impl.css +10 -0
  268. package/dist/components/core/navbar-menu/navbar-menu-impl.css +9 -0
  269. package/dist/components/core/navbar-switcher/navbar-switcher-impl.css +19 -0
  270. package/dist/components/core/pagination/Pagination.js +1 -1
  271. package/dist/components/core/pagination/pagination-impl.css +9 -0
  272. package/dist/components/core/pagination-dots/PaginationDots.d.ts +1 -1
  273. package/dist/components/core/pagination-dots/PaginationDots.js +3 -3
  274. package/dist/components/core/pagination-dots/PaginationDots.types.d.ts +3 -1
  275. package/dist/components/core/segmented-control/SegmentedControl.css +4 -3
  276. package/dist/components/core/segmented-control/SegmentedControl.js +17 -3
  277. package/dist/components/core/segmented-control-item/SegmentedControlItem.css +2 -1
  278. package/dist/components/core/select/Select.js +3 -3
  279. package/dist/components/core/select/Select.types.d.ts +11 -1
  280. package/dist/components/core/select/Select.types.js +1 -1
  281. package/dist/components/core/tab-list/TabList.js +9 -1
  282. package/dist/components/core/table-header-cell/TableHeaderCell.d.ts +1 -1
  283. package/dist/components/core/table-header-cell/TableHeaderCell.js +5 -2
  284. package/dist/components/core/table-header-cell/TableHeaderCell.types.d.ts +7 -1
  285. package/dist/components/core/table-header-cell/TableHeaderCell.types.js +1 -1
  286. package/dist/components/core/text-area/TextArea.d.ts +1 -1
  287. package/dist/components/core/text-area/TextArea.js +2 -2
  288. package/dist/components/core/text-area/TextArea.types.d.ts +9 -1
  289. package/dist/components/core/text-area/TextArea.types.js +1 -1
  290. package/dist/components/core/text-input/TextInput.d.ts +1 -1
  291. package/dist/components/core/text-input/TextInput.js +2 -2
  292. package/dist/components/core/text-input/TextInput.types.d.ts +9 -1
  293. package/dist/components/core/text-input/TextInput.types.js +1 -1
  294. package/dist/components/data-table-data.d.ts +33 -0
  295. package/dist/components/data-table-data.js +12 -0
  296. package/dist/components/download-banner-data.d.ts +52 -0
  297. package/dist/components/download-banner-data.js +11 -0
  298. package/dist/components/index.d.ts +45 -0
  299. package/dist/components/index.js +32 -0
  300. package/dist/components/resource-banner-data.d.ts +34 -0
  301. package/dist/components/resource-banner-data.js +13 -0
  302. package/dist/components/scene-scroll-data.d.ts +24 -0
  303. package/dist/components/scene-scroll-data.js +9 -0
  304. package/dist/components/stats-data.d.ts +28 -0
  305. package/dist/components/stats-data.js +11 -0
  306. package/dist/components/steps-data.d.ts +38 -0
  307. package/dist/components/steps-data.js +13 -0
  308. package/dist/components/sticky-cards-data.d.ts +43 -0
  309. package/dist/components/sticky-cards-data.js +12 -0
  310. package/dist/components/team-grid-data.d.ts +38 -0
  311. package/dist/components/team-grid-data.js +11 -0
  312. package/dist/components/tiers-data.d.ts +47 -0
  313. package/dist/components/tiers-data.js +13 -0
  314. package/dist/marketing.css +77 -28
  315. package/package.json +1 -1
@@ -0,0 +1,244 @@
1
+ /**
2
+ * The fragment shaders behind the patterns the stylesheet can only approximate.
3
+ *
4
+ * Each is a function of where it is, the time and the light: no state, no texture, nothing to
5
+ * load. What they share — noise, a folded noise, the grain, the inks and measures read from the
6
+ * stylesheet — is in `shader.ts`. Every one ends in `grain`, which is the difference between a
7
+ * gradient and a surface: it gives the field a tooth, and dithers a long fade so it never bands.
8
+ *
9
+ * Inks arrive as `u_ink0` (the ground) to `u_ink5`, each pattern naming in the stylesheet what its
10
+ * own are, per tone. `u_strength` is how loudly it is painted on that tone.
11
+ */
12
+ /** The band's coordinates, set up the same way at the top of every pattern. */
13
+ const PLACE = `
14
+ vec2 frag = gl_FragCoord.xy / u_scale;
15
+ vec2 px = vec2(frag.x, u_size.y - frag.y);
16
+ vec2 uv = px / u_size;
17
+ float aspect = u_size.x / u_size.y;
18
+ // The field's own scale, in CSS pixels, so a narrow band and a wide one show folds of a like size
19
+ // — measured against the band's proportions, a phone-width band got one fold stretched tall. It
20
+ // grows a little with the band, so a full-width hero is not a field of small eddies.
21
+ vec2 field = px / (280.0 + 0.25 * max(u_size.x, u_size.y));
22
+ vec2 lamp0 = u_pointer * u_size / (280.0 + 0.25 * max(u_size.x, u_size.y));
23
+ `;
24
+ /**
25
+ * The aurora: curtains of light rising from the band's foot.
26
+ *
27
+ * Sheets of noise stretched upright, swaying as a slower noise moves under them, with a ragged
28
+ * crest and fine rays running up through them — light falling through folds rather than pools of
29
+ * colour. The pointer bends the sheets towards it and brightens them where it is.
30
+ */
31
+ export const AURORA = `
32
+ void main() {
33
+ ${PLACE}
34
+ vec2 p = vec2(field.x, uv.y);
35
+ float t = u_time * 0.06;
36
+
37
+ vec2 toward = vec2(field.x - lamp0.x, uv.y - u_pointer.y);
38
+ float near = exp(-dot(toward, toward) * 5.0) * u_hover;
39
+
40
+ float sway = fbm(vec2(p.x * 0.8 + t, t * 0.7)) - 0.5;
41
+ float x = p.x * 1.5 + sway * 1.6 - toward.x * near * 0.8;
42
+
43
+ float folds = fbm(vec2(x * 2.2, p.y * 0.35 - t * 1.6));
44
+ float curtain = smoothstep(0.3, 0.72, folds);
45
+ float rays = 0.6 + 0.4 * noise(vec2(x * 26.0, p.y * 1.4 - t * 3.0));
46
+
47
+ float crest = 0.08 + 0.38 * fbm(vec2(x * 1.2 + 4.0, t * 0.8));
48
+ float rise = smoothstep(crest, 0.82, uv.y);
49
+ float base = smoothstep(0.55, 1.0, uv.y);
50
+
51
+ float light = (curtain * rays * rise + base * 0.3 + near * 0.45) * u_strength;
52
+
53
+ vec3 tint = mix(u_ink1, u_ink2, smoothstep(0.3, 0.8, fbm(vec2(x * 1.1 - 3.0, p.y * 0.8 + t))));
54
+ tint = mix(tint, u_ink3, pow(clamp(curtain * rise, 0.0, 1.0), 2.5) * 0.65);
55
+
56
+ vec3 colour = mix(u_ink0, tint, clamp(light, 0.0, 1.0));
57
+ gl_FragColor = vec4(grain(colour, gl_FragCoord.xy), 1.0);
58
+ }
59
+ `;
60
+ /**
61
+ * The mesh: the design system's own front page — five soft pools of the brand's reds, run together.
62
+ *
63
+ * The same drawing as the front page's panel and as this pattern's stylesheet version: each pool
64
+ * in its place, its red, its size, drifting and stretching on loops of its own length, and leaning
65
+ * towards the pointer at its own depth, so the colours slide through one another rather than moving
66
+ * as one. Where the stylesheet blurs five shapes, this draws each as a soft-edged pool outright —
67
+ * an uneven round whose outline wobbles as it turns — and lays them over the ground in order.
68
+ *
69
+ * The first pool is the deepest red, held back by `u_shade` so the words keep a red to stand on.
70
+ */
71
+ export const MESH = `
72
+ // One pool: where it rests, in shares of the band; how big, in shares of its longer side; how far it leans after the
73
+ // pointer; and its loops — the drift and the stretch — in seconds, offset by a phase of its own.
74
+ float pool(vec2 px, vec2 at, float size, float depth, float drift, float morph, float phase) {
75
+ // Sized by the band's longer side rather than its width: on a phone the band is tall and narrow,
76
+ // and pools sized by its width came out small — the deepest a hard, dark blot in one corner and
77
+ // the rest of the band flat. Measured against the longer side they cover a tall band as they do
78
+ // a wide one.
79
+ float w = max(u_size.x, u_size.y);
80
+ float t = u_time;
81
+ float tau = 6.2831853;
82
+
83
+ // The drift: a slow wander and a swell, there and back.
84
+ vec2 wander = vec2(
85
+ 0.10 * sin(tau * t / drift + phase),
86
+ 0.08 * sin(tau * t / (drift * 1.3) + phase * 1.7)
87
+ ) * size * w;
88
+ float swell = 1.0 + 0.1 * sin(tau * t / (drift * 0.8) + phase * 0.6);
89
+
90
+ // Leaning after the pointer by its depth, a hundredth of the pool per step, as the front page's do.
91
+ vec2 lean = (u_pointer - 0.5) * 2.0 * u_hover * depth * 0.01 * size * w;
92
+
93
+ vec2 centre = at * u_size + wander + lean;
94
+ vec2 d = px - centre;
95
+
96
+ // The stretch: one way then the other, turning as it goes.
97
+ float turn = 1.4 * sin(tau * t / (morph * 2.0) + phase);
98
+ float c = cos(turn);
99
+ float s = sin(turn);
100
+ d = mat2(c, -s, s, c) * d;
101
+ float stretch = sin(tau * t / morph + phase);
102
+ d /= vec2(1.0 + 0.18 * stretch, 1.0 - 0.15 * stretch);
103
+
104
+ // An uneven round, its outline wobbling as it turns.
105
+ float angle = atan(d.y, d.x);
106
+ float radius = size * w * 0.5 * swell * (1.0 + 0.1 * sin(3.0 * angle + phase) + 0.06 * sin(5.0 * angle - phase * 2.0));
107
+ float r = length(d) / radius;
108
+ // Soft all the way out: a blur's falloff rather than an edge.
109
+ return exp(-r * r * 1.1);
110
+ }
111
+
112
+ void main() {
113
+ ${PLACE}
114
+ vec3 c = u_ink0;
115
+ // The deepest red, off the near side low down — a shade under the words, held back.
116
+ c = mix(c, u_ink1, pool(px, vec2(0.06, 0.82), 0.62, -8.0, 26.0, 13.0, 0.0) * u_shade * u_strength);
117
+ // The pressed red, high in the far corner, the largest.
118
+ c = mix(c, u_ink2, pool(px, vec2(0.92, -0.08), 0.85, 10.0, 31.0, 9.0, 1.7) * u_strength);
119
+ // The hover red, through the middle.
120
+ c = mix(c, u_ink3, pool(px, vec2(0.6, 0.5), 0.62, 16.0, 22.0, 7.0, 3.1) * u_strength);
121
+ // The pressed red again, swelling up from the foot.
122
+ c = mix(c, u_ink4, pool(px, vec2(0.5, 1.1), 0.75, -12.0, 35.0, 12.0, 4.4) * u_strength);
123
+ // The coral itself, high in the far corner: the brightest the pools go.
124
+ c = mix(c, u_ink5, pool(px, vec2(0.74, 0.06), 0.42, 20.0, 28.0, 8.0, 5.9) * u_strength);
125
+
126
+ // A little more colour, as the front page's blur saturates its pools.
127
+ float grey = dot(c, vec3(0.299, 0.587, 0.114));
128
+ c = mix(vec3(grey), c, 1.15);
129
+
130
+ gl_FragColor = vec4(grain(clamp(c, 0.0, 1.0), gl_FragCoord.xy), 1.0);
131
+ }
132
+ `;
133
+ /**
134
+ * The lines the grid and the rings share: a hairline at an exact width, anti-aliased by its
135
+ * distance in device pixels rather than by a blur, so it stays a hairline at any density.
136
+ */
137
+ const HAIRLINE = `
138
+ float hairline(float distance) {
139
+ float halfWidth = u_hairline * 0.5;
140
+ float pixel = 1.0 / u_scale;
141
+ return 1.0 - smoothstep(halfWidth, halfWidth + pixel, distance);
142
+ }
143
+
144
+ // A soft bloom at the middle of the band's foot — where the halftone's glow rests — so the lines
145
+ // rise out of light rather than out of a flat ground.
146
+ float bloom(vec2 uv) {
147
+ vec2 d = vec2((uv.x - 0.5) * 1.5, (1.0 - uv.y) * 2.2);
148
+ return exp(-dot(d, d));
149
+ }
150
+
151
+ // The pointer's light, a soft circle a pitch and a half across.
152
+ float lamp(vec2 px) {
153
+ vec2 d = px - u_pointer * u_size;
154
+ float reach = u_pitch * 1.6;
155
+ return exp(-dot(d, d) / (reach * reach)) * u_hover;
156
+ }
157
+
158
+ // A short bright dash running along a line, at a place along it, fading off behind.
159
+ float dash(float along) {
160
+ return smoothstep(0.0, 0.06, along) * (1.0 - smoothstep(0.06, 0.3, along));
161
+ }
162
+ `;
163
+ /**
164
+ * The grid: hairlines a pitch apart, rising from the foot as they did, now lit.
165
+ *
166
+ * A bloom at the foot they rise out of, a brighter point where two lines cross, and every few
167
+ * lines a short dash of light running along it — up a rule, across another — at a pace of its
168
+ * own, so the grid reads as carrying something. The pointer lights the lines around it.
169
+ */
170
+ export const GRID = `
171
+ ${HAIRLINE}
172
+ void main() {
173
+ ${PLACE}
174
+ float pitch = u_pitch;
175
+ float shift = mod(u_time / 10.0, 1.0) * pitch;
176
+ vec2 g = vec2(px.x - u_size.x * 0.5 + pitch * 0.5, px.y + shift);
177
+ vec2 d = abs(fract(g / pitch) - 0.5) * pitch;
178
+ vec2 lines = vec2(hairline(pitch * 0.5 - d.x), hairline(pitch * 0.5 - d.y));
179
+ float line = max(lines.x, lines.y);
180
+ float meet = lines.x * lines.y;
181
+
182
+ vec2 cell = floor(g / pitch);
183
+ float upward = fract(px.y / (pitch * 7.0) + u_time * 0.07 + hash(vec2(cell.x, 3.1)));
184
+ float across = fract(px.x / (pitch * 9.0) - u_time * 0.05 + hash(vec2(cell.y, 7.7)));
185
+ float pulses =
186
+ lines.x * dash(upward) * step(0.7, hash(vec2(cell.x, 9.7))) +
187
+ lines.y * dash(across) * step(0.78, hash(vec2(cell.y, 4.2)));
188
+
189
+ float fade = smoothstep(0.0, 1.0, uv.y);
190
+ float glow = bloom(uv);
191
+ float lit = lamp(px);
192
+
193
+ vec3 c = mix(u_ink0, u_ink3, glow * 0.45 * u_strength);
194
+ c = mix(c, u_ink1, line * fade * (0.55 + 0.45 * glow) * u_strength);
195
+ c = mix(c, u_ink2, clamp(pulses * fade + line * lit + meet * fade * 0.35, 0.0, 1.0) * u_strength);
196
+
197
+ gl_FragColor = vec4(grain(c, gl_FragCoord.xy), 1.0);
198
+ }
199
+ `;
200
+ /**
201
+ * The rings: circles spreading from the middle of the band's foot, now lit.
202
+ *
203
+ * The same bloom at the foot, and on every ring a few arcs of light travelling round it, each ring
204
+ * its own way and pace, so the rings shimmer as they spread. The pointer lights the rings around it.
205
+ */
206
+ export const RINGS = `
207
+ ${HAIRLINE}
208
+ void main() {
209
+ ${PLACE}
210
+ float pitch = u_pitch;
211
+ vec2 centre = vec2(u_size.x * 0.5, u_size.y);
212
+ vec2 from = px - centre;
213
+ float radius = length(from);
214
+ float shift = mod(u_time / 10.0, 1.0) * pitch;
215
+ float place = (radius - shift) / pitch;
216
+ float d = abs(fract(place) - 0.5) * pitch;
217
+ float line = hairline(pitch * 0.5 - d);
218
+
219
+ float ring = floor(place);
220
+ float angle = atan(from.x, -from.y);
221
+ float turn = (hash(vec2(ring, 1.7)) > 0.5 ? 1.0 : -1.0) * (0.15 + 0.25 * hash(vec2(ring, 5.3)));
222
+ float arc = noise(vec2(angle * 3.0 + u_time * turn, ring * 3.1));
223
+ float sparkle = line * smoothstep(0.62, 0.9, arc);
224
+
225
+ float fade = smoothstep(0.0, 1.0, uv.y);
226
+ float glow = bloom(uv);
227
+ float lit = lamp(px);
228
+
229
+ vec3 c = mix(u_ink0, u_ink3, glow * 0.45 * u_strength);
230
+ c = mix(c, u_ink1, line * fade * (0.55 + 0.45 * glow) * u_strength);
231
+ c = mix(c, u_ink2, clamp(sparkle * fade + line * lit, 0.0, 1.0) * u_strength);
232
+
233
+ gl_FragColor = vec4(grain(c, gl_FragCoord.xy), 1.0);
234
+ }
235
+ `;
236
+ /** Each pattern's shader and the highest density it draws at: soft fields need little, hairlines two. */
237
+ export const SHADERS = {
238
+ // The two heaviest, and the two softest: drawn at half the CSS density and scaled up by the
239
+ // browser, a quarter of the pixels for a field that has no edge for the scaling to soften.
240
+ aurora: { fragment: AURORA, density: 0.5 },
241
+ mesh: { fragment: MESH, density: 0.5 },
242
+ grid: { fragment: GRID, density: 2 },
243
+ rings: { fragment: RINGS, density: 2 },
244
+ };
@@ -0,0 +1,44 @@
1
+ /**
2
+ * A pattern drawn by a fragment shader, on a canvas, and only when it has to be.
3
+ *
4
+ * The aurora, the mesh, the grid and the rings are painted here when the browser can run
5
+ * WebGL — light with structure in it, noise folded into noise, a grain over all of it — and by the
6
+ * stylesheet when it cannot. The stylesheet's version is drawn first and stays until this has drawn
7
+ * a frame, so a band is finished from the first paint and the shader only ever replaces it: the root
8
+ * is marked `data-shaded` once a frame is on the canvas, and the stylesheet hides its own paint then.
9
+ *
10
+ * What it costs, and why it is written this way:
11
+ *
12
+ * - One full-band triangle and one fragment shader: the whole frame is a single draw call.
13
+ * - It draws while the band is on screen and something moves — the pattern at rest, or the light
14
+ * catching up with the pointer — and not otherwise. A band out of view draws nothing; a band
15
+ * asked to hold still draws one frame and stops.
16
+ * - The soft patterns are drawn at the CSS pixel density or below — the two heaviest at half of
17
+ * it, scaled up by the browser — which is all a field with no edge in it can use; the hairline
18
+ * patterns at up to twice it, which their lines need.
19
+ * - At rest it draws at half the display's rate. Under the pointer, at its full rate.
20
+ * - For a reader who asked for less motion it draws one frame and never moves.
21
+ * - A lost context puts the stylesheet's paint back and stops; the next mount draws afresh.
22
+ *
23
+ * Every colour comes from a token: the stylesheet names each pattern's inks as custom properties on
24
+ * the band, per tone, and they are read back as the browser resolved them.
25
+ */
26
+ export interface ShaderOptions {
27
+ /** The fragment shader's body — see `PRELUDE` for what it may use. */
28
+ fragment: string;
29
+ /** The custom properties holding the pattern's inks, ground first. */
30
+ inks: readonly string[];
31
+ /** The highest pixel density to draw at. A soft field wants less than a hairline does. */
32
+ density: number;
33
+ /** Whether the pattern answers the pointer. Ignored for reduced motion. */
34
+ interactive: boolean;
35
+ /** Whether the pattern moves at rest. Ignored for reduced motion. */
36
+ animated: boolean;
37
+ /** Where the pointer is listened for, when that is not the band itself. */
38
+ host?: HTMLElement;
39
+ }
40
+ export interface Shader {
41
+ /** Stop listening, stop drawing, give back the context, and put the stylesheet's paint back. */
42
+ destroy: () => void;
43
+ }
44
+ export declare function shader(root: HTMLElement, canvas: HTMLCanvasElement, options: ShaderOptions): Shader;
@@ -0,0 +1,307 @@
1
+ 'use client';
2
+ /**
3
+ * What every pattern's shader is given. Positions are in the band's own terms: `uv` runs 0 to 1 left
4
+ * to right and top to bottom, `px` is the same point in CSS pixels, and the pointer is in `uv`.
5
+ */
6
+ const PRELUDE = `
7
+ #ifdef GL_FRAGMENT_PRECISION_HIGH
8
+ precision highp float;
9
+ #else
10
+ precision mediump float;
11
+ #endif
12
+ uniform vec2 u_size; // the band, in CSS pixels
13
+ uniform float u_scale; // device pixels to the CSS pixel, as drawn
14
+ uniform float u_time; // seconds of motion so far
15
+ uniform vec2 u_pointer; // where the light is, eased after the pointer, in uv
16
+ uniform float u_hover; // 0 to 1, how present the pointer is, eased
17
+ uniform float u_strength; // how strongly the pattern is painted on this ground
18
+ uniform float u_grain; // how much grain is laid over it
19
+ uniform float u_pitch; // a length the pattern repeats at, in CSS pixels, where it has one
20
+ uniform float u_hairline; // a hairline's width, in CSS pixels
21
+ uniform float u_shade; // how far a pattern's deepest ink is held back, where it has one
22
+ uniform vec3 u_ink0; // the ground
23
+ uniform vec3 u_ink1;
24
+ uniform vec3 u_ink2;
25
+ uniform vec3 u_ink3;
26
+ uniform vec3 u_ink4;
27
+ uniform vec3 u_ink5;
28
+
29
+ float hash(vec2 p) {
30
+ p = fract(p * vec2(123.34, 456.21));
31
+ p += dot(p, p + 45.32);
32
+ return fract(p.x * p.y);
33
+ }
34
+
35
+ // Value noise with a quintic fade, smooth to the second derivative so no crease shows in a fold.
36
+ float noise(vec2 p) {
37
+ vec2 i = floor(p);
38
+ vec2 f = fract(p);
39
+ vec2 u = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);
40
+ float a = hash(i);
41
+ float b = hash(i + vec2(1.0, 0.0));
42
+ float c = hash(i + vec2(0.0, 1.0));
43
+ float d = hash(i + vec2(1.0, 1.0));
44
+ return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
45
+ }
46
+
47
+ // Four octaves, each turned against the last, so the layers never line up into a grid. A fifth
48
+ // added detail too fine to see under the grain, at a fifth more of the cost.
49
+ float fbm(vec2 p) {
50
+ float sum = 0.0;
51
+ float amp = 0.5;
52
+ mat2 turn = mat2(0.8, 0.6, -0.6, 0.8);
53
+ for (int k = 0; k < 4; k++) {
54
+ sum += amp * noise(p);
55
+ p = turn * p * 2.03 + 17.1;
56
+ amp *= 0.5;
57
+ }
58
+ return sum;
59
+ }
60
+
61
+ // A grain that changes every frame, and the dither that keeps a long gradient from banding.
62
+ vec3 grain(vec3 colour, vec2 frag) {
63
+ float n = hash(frag + fract(u_time * 7.13) * 91.7) - 0.5;
64
+ return colour + n * u_grain;
65
+ }
66
+ `;
67
+ const VERTEX = `
68
+ attribute vec2 a_position;
69
+ void main() { gl_Position = vec4(a_position, 0.0, 1.0); }
70
+ `;
71
+ /** A computed colour as linear-ish RGB from 0 to 1. Accepts rgb(), rgba() and color(srgb …). */
72
+ function channels(value) {
73
+ const numbers = value.match(/-?[\d.]+/g)?.map(Number) ?? [];
74
+ if (value.startsWith('color('))
75
+ return [numbers[0] ?? 0, numbers[1] ?? 0, numbers[2] ?? 0];
76
+ return [(numbers[0] ?? 0) / 255, (numbers[1] ?? 0) / 255, (numbers[2] ?? 0) / 255];
77
+ }
78
+ /** A custom property's colour, as the browser resolves it on this element. */
79
+ function ink(element, property) {
80
+ const before = element.style.color;
81
+ element.style.color = `var(${property})`;
82
+ const resolved = getComputedStyle(element).color;
83
+ element.style.color = before;
84
+ return channels(resolved);
85
+ }
86
+ function numberOf(element, property, fallback) {
87
+ const parsed = Number.parseFloat(getComputedStyle(element).getPropertyValue(property));
88
+ return Number.isFinite(parsed) ? parsed : fallback;
89
+ }
90
+ function milliseconds(value, fallback) {
91
+ const amount = Number.parseFloat(value);
92
+ if (!Number.isFinite(amount))
93
+ return fallback;
94
+ return /ms$/i.test(value.trim()) ? amount : amount * 1000;
95
+ }
96
+ function compile(gl, type, source) {
97
+ const shader = gl.createShader(type);
98
+ if (shader === null)
99
+ return null;
100
+ gl.shaderSource(shader, source);
101
+ gl.compileShader(shader);
102
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
103
+ // A pattern that does not compile falls back to the stylesheet; say why, once, for whoever is
104
+ // writing the shader.
105
+ console.warn('Background shader:', gl.getShaderInfoLog(shader));
106
+ gl.deleteShader(shader);
107
+ return null;
108
+ }
109
+ return shader;
110
+ }
111
+ export function shader(root, canvas, options) {
112
+ const nothing = { destroy: () => { } };
113
+ const gl = canvas.getContext('webgl', { alpha: false, antialias: false, premultipliedAlpha: false, powerPreference: 'low-power' });
114
+ if (gl === null)
115
+ return nothing;
116
+ const vertex = compile(gl, gl.VERTEX_SHADER, VERTEX);
117
+ const fragment = compile(gl, gl.FRAGMENT_SHADER, PRELUDE + options.fragment);
118
+ if (vertex === null || fragment === null)
119
+ return nothing;
120
+ const program = gl.createProgram();
121
+ if (program === null)
122
+ return nothing;
123
+ gl.attachShader(program, vertex);
124
+ gl.attachShader(program, fragment);
125
+ gl.linkProgram(program);
126
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS))
127
+ return nothing;
128
+ gl.useProgram(program);
129
+ // One triangle larger than the screen: every pixel of the band, one draw.
130
+ const buffer = gl.createBuffer();
131
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
132
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);
133
+ const position = gl.getAttribLocation(program, 'a_position');
134
+ gl.enableVertexAttribArray(position);
135
+ gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0);
136
+ const at = (name) => gl.getUniformLocation(program, name);
137
+ const u = {
138
+ size: at('u_size'),
139
+ scale: at('u_scale'),
140
+ time: at('u_time'),
141
+ pointer: at('u_pointer'),
142
+ hover: at('u_hover'),
143
+ strength: at('u_strength'),
144
+ grain: at('u_grain'),
145
+ pitch: at('u_pitch'),
146
+ hairline: at('u_hairline'),
147
+ shade: at('u_shade'),
148
+ inks: [0, 1, 2, 3, 4, 5].map((k) => at(`u_ink${k}`)),
149
+ };
150
+ const still = typeof window.matchMedia === 'function' && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
151
+ const moving = options.animated && !still;
152
+ const answering = options.interactive && !still;
153
+ /** Everything read from the stylesheet: the inks, the strengths, the measures, the timing. */
154
+ let follow = 500;
155
+ const read = () => {
156
+ options.inks.forEach((property, k) => {
157
+ const location = u.inks[k];
158
+ if (location !== undefined)
159
+ gl.uniform3fv(location, ink(root, property));
160
+ });
161
+ gl.uniform1f(u.strength, numberOf(root, '--dds-background-gl-strength', 1));
162
+ gl.uniform1f(u.grain, numberOf(root, '--dds-background-gl-grain', 0.04));
163
+ gl.uniform1f(u.pitch, numberOf(root, '--dds-background-gl-pitch', 96));
164
+ gl.uniform1f(u.hairline, numberOf(root, '--dds-background-gl-hairline', 1));
165
+ // Read off the mesh's own element, which is where the stylesheet keeps it per tone.
166
+ const mesh = root.querySelector('.dds-background__mesh') ?? root;
167
+ gl.uniform1f(u.shade, numberOf(mesh, '--dds-background-mesh-shade', 1));
168
+ gl.uniform1f(u.shade, numberOf(root, '--dds-background-mesh-shade', 1));
169
+ follow = milliseconds(getComputedStyle(root).getPropertyValue('--motion-duration-trail'), 500);
170
+ };
171
+ let width = 0;
172
+ let height = 0;
173
+ const resize = () => {
174
+ const box = root.getBoundingClientRect();
175
+ width = Math.max(1, box.width);
176
+ height = Math.max(1, box.height);
177
+ const density = Math.min(window.devicePixelRatio || 1, options.density);
178
+ canvas.width = Math.round(width * density);
179
+ canvas.height = Math.round(height * density);
180
+ gl.viewport(0, 0, canvas.width, canvas.height);
181
+ gl.uniform2f(u.size, width, height);
182
+ gl.uniform1f(u.scale, density);
183
+ };
184
+ // Where the light is and where it is going. It rests a little above the middle of the band's foot.
185
+ const rest = { x: 0.5, y: 0.85 };
186
+ const light = { ...rest, hover: 0 };
187
+ const target = { ...rest, hover: 0 };
188
+ let time = 0;
189
+ let last = 0;
190
+ let frame = 0;
191
+ let visible = true;
192
+ let drawn = false;
193
+ let lost = false;
194
+ let skip = false;
195
+ const draw = (now) => {
196
+ frame = 0;
197
+ if (lost)
198
+ return;
199
+ const step = last === 0 ? 0 : Math.min(0.1, (now - last) / 1000);
200
+ last = now;
201
+ // At rest it moves only if asked to; under the pointer it moves either way — a band asked to
202
+ // hold still flows while somebody is over it, and stops where it is when they leave.
203
+ const flowing = moving || (answering && target.hover > 0);
204
+ if (flowing)
205
+ time = (time + step) % 3600;
206
+ // Ease the light after its target: most of the gap closed within the trail duration.
207
+ const ease = step === 0 ? 1 : 1 - Math.exp((-step * 1000 * 3) / follow);
208
+ light.x += (target.x - light.x) * ease;
209
+ light.y += (target.y - light.y) * ease;
210
+ light.hover += (target.hover - light.hover) * ease;
211
+ const settling = Math.abs(target.x - light.x) + Math.abs(target.y - light.y) + Math.abs(target.hover - light.hover) > 0.001;
212
+ gl.uniform1f(u.time, time);
213
+ gl.uniform2f(u.pointer, light.x, light.y);
214
+ gl.uniform1f(u.hover, light.hover);
215
+ gl.drawArrays(gl.TRIANGLES, 0, 3);
216
+ if (!drawn) {
217
+ drawn = true;
218
+ root.dataset['shaded'] = 'true';
219
+ }
220
+ // Keep going while there is something to show: the pattern moving, or the light still arriving.
221
+ if (visible && (flowing || settling))
222
+ schedule(settling);
223
+ };
224
+ // At rest, every other frame; while the light is arriving, every frame.
225
+ const schedule = (urgent) => {
226
+ if (frame !== 0)
227
+ return;
228
+ frame = requestAnimationFrame((now) => {
229
+ skip = !skip;
230
+ if (!urgent && skip) {
231
+ frame = 0;
232
+ schedule(false);
233
+ return;
234
+ }
235
+ draw(now);
236
+ });
237
+ };
238
+ read();
239
+ resize();
240
+ schedule(true);
241
+ const sizes = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(() => {
242
+ resize();
243
+ schedule(true);
244
+ });
245
+ sizes?.observe(root);
246
+ const sight = typeof IntersectionObserver === 'undefined'
247
+ ? null
248
+ : new IntersectionObserver(([entry]) => {
249
+ visible = entry?.isIntersecting ?? true;
250
+ last = 0;
251
+ if (visible)
252
+ schedule(true);
253
+ });
254
+ sight?.observe(root);
255
+ // A tone or a theme that changes under the band changes its inks.
256
+ const restyle = new MutationObserver(() => {
257
+ read();
258
+ schedule(true);
259
+ });
260
+ restyle.observe(root, { attributes: true, attributeFilter: ['data-tone'] });
261
+ const themed = root.closest('[data-theme]');
262
+ if (themed !== null)
263
+ restyle.observe(themed, { attributes: true, attributeFilter: ['data-theme'] });
264
+ const host = options.host ?? root;
265
+ const move = (event) => {
266
+ if (event.pointerType === 'touch')
267
+ return;
268
+ const box = root.getBoundingClientRect();
269
+ target.x = (event.clientX - box.left) / box.width;
270
+ target.y = (event.clientY - box.top) / box.height;
271
+ target.hover = 1;
272
+ schedule(true);
273
+ };
274
+ const leave = () => {
275
+ target.x = rest.x;
276
+ target.y = rest.y;
277
+ target.hover = 0;
278
+ schedule(true);
279
+ };
280
+ if (answering) {
281
+ host.addEventListener('pointermove', move, { passive: true });
282
+ host.addEventListener('pointerleave', leave, { passive: true });
283
+ }
284
+ const onLost = (event) => {
285
+ event.preventDefault();
286
+ lost = true;
287
+ delete root.dataset['shaded'];
288
+ };
289
+ canvas.addEventListener('webglcontextlost', onLost);
290
+ return {
291
+ destroy: () => {
292
+ if (frame !== 0)
293
+ cancelAnimationFrame(frame);
294
+ sizes?.disconnect();
295
+ sight?.disconnect();
296
+ restyle.disconnect();
297
+ host.removeEventListener('pointermove', move);
298
+ host.removeEventListener('pointerleave', leave);
299
+ canvas.removeEventListener('webglcontextlost', onLost);
300
+ delete root.dataset['shaded'];
301
+ gl.deleteProgram(program);
302
+ gl.deleteShader(vertex);
303
+ gl.deleteShader(fragment);
304
+ gl.deleteBuffer(buffer);
305
+ },
306
+ };
307
+ }
@@ -0,0 +1,11 @@
1
+ import { type RefObject } from 'react';
2
+ import { type ShadedPattern } from './patterns.js';
3
+ /**
4
+ * A shaded pattern mounted on a band: the band to observe and listen on, and the canvas to draw in.
5
+ * `pattern` undefined mounts nothing — the halftone, which has its own painter.
6
+ */
7
+ export declare function useShader<Band extends HTMLElement>(band: RefObject<Band | null>, pattern: ShadedPattern | undefined, interactive: boolean, animated: boolean,
8
+ /** Anything whose change should re-read the inks — a tone. */
9
+ key?: string,
10
+ /** Listen for the pointer on the band's parent rather than the band: a Background used as a layer. */
11
+ onParent?: boolean): RefObject<HTMLCanvasElement | null>;
@@ -0,0 +1,25 @@
1
+ 'use client';
2
+ import { useEffect, useRef } from 'react';
3
+ import { SHADERS } from './patterns.js';
4
+ import { shader } from './shader.js';
5
+ /** The inks every shaded pattern reads, ground first; the stylesheet sets them per pattern and tone. */
6
+ const INKS = [0, 1, 2, 3, 4, 5].map((k) => `--dds-background-gl-ink${k}`);
7
+ /**
8
+ * A shaded pattern mounted on a band: the band to observe and listen on, and the canvas to draw in.
9
+ * `pattern` undefined mounts nothing — the halftone, which has its own painter.
10
+ */
11
+ export function useShader(band, pattern, interactive, animated,
12
+ /** Anything whose change should re-read the inks — a tone. */
13
+ key,
14
+ /** Listen for the pointer on the band's parent rather than the band: a Background used as a layer. */
15
+ onParent = false) {
16
+ const canvas = useRef(null);
17
+ useEffect(() => {
18
+ if (pattern === undefined || band.current === null || canvas.current === null)
19
+ return undefined;
20
+ const host = onParent ? (band.current.parentElement ?? undefined) : undefined;
21
+ const { fragment, density } = SHADERS[pattern];
22
+ return shader(band.current, canvas.current, { fragment, density, inks: INKS, interactive, animated, host }).destroy;
23
+ }, [band, pattern, interactive, animated, key, onParent]);
24
+ return canvas;
25
+ }
@@ -2,7 +2,8 @@ import { type RefObject } from 'react';
2
2
  /**
3
3
  * The pointer's light on a pattern the stylesheet paints: where the pointer is, written onto the
4
4
  * band as two custom properties the stylesheet centres its light on — a brighter copy of the grid
5
- * or the rings, the aurora's trailing pool — and whether it is over the band.
5
+ * or the rings, the aurora's trailing pool — the same place as a lean from the middle, which the
6
+ * mesh's pools follow, and whether it is over the band.
6
7
  *
7
8
  * Events only record the pointer; the position is written once a frame, so a fast mouse costs one
8
9
  * style write a frame rather than one per event. Nothing runs while the pointer is elsewhere. A
@@ -3,7 +3,8 @@ import { useEffect } from 'react';
3
3
  /**
4
4
  * The pointer's light on a pattern the stylesheet paints: where the pointer is, written onto the
5
5
  * band as two custom properties the stylesheet centres its light on — a brighter copy of the grid
6
- * or the rings, the aurora's trailing pool — and whether it is over the band.
6
+ * or the rings, the aurora's trailing pool — the same place as a lean from the middle, which the
7
+ * mesh's pools follow, and whether it is over the band.
7
8
  *
8
9
  * Events only record the pointer; the position is written once a frame, so a fast mouse costs one
9
10
  * style write a frame rather than one per event. Nothing runs while the pointer is elsewhere. A
@@ -27,6 +28,10 @@ onParent = false) {
27
28
  const box = root.getBoundingClientRect();
28
29
  root.style.setProperty('--dds-background-spot-x', `${clientX - box.left}px`);
29
30
  root.style.setProperty('--dds-background-spot-y', `${clientY - box.top}px`);
31
+ // And as a lean, -1 to 1 each way from the band's middle, for the mesh's pools to follow by
32
+ // their depth: CSS cannot divide one length by another to get there itself.
33
+ root.style.setProperty('--dds-background-lean-x', String(((clientX - box.left) / box.width) * 2 - 1));
34
+ root.style.setProperty('--dds-background-lean-y', String(((clientY - box.top) / box.height) * 2 - 1));
30
35
  };
31
36
  const move = (event) => {
32
37
  if (event.pointerType === 'touch')
@@ -51,6 +56,9 @@ onParent = false) {
51
56
  };
52
57
  const leave = () => {
53
58
  delete root.dataset['spotlit'];
59
+ // The mesh's pools ease back to where they rest.
60
+ root.style.removeProperty('--dds-background-lean-x');
61
+ root.style.removeProperty('--dds-background-lean-y');
54
62
  };
55
63
  host.addEventListener('pointermove', move, { passive: true });
56
64
  host.addEventListener('pointerleave', leave, { passive: true });