@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,120 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useCallback, useId, useLayoutEffect, useRef, useState } from 'react';
4
+ import './Stats.css';
5
+ import './stats-impl.css';
6
+ import '../block-reveal.css';
7
+ import { given } from '../../given.js';
8
+ import { milliseconds } from '../../css-time.js';
9
+ import { arriving, useReveal } from '../use-reveal.js';
10
+ import { countable, easing, partway } from './stats-count.js';
11
+ import { slotOf, useWheel } from './use-wheel.js';
12
+ import { statsDefaults, statsParts } from './Stats.types.js';
13
+ /**
14
+ * Where a figure comes in the arrival, capped so a long set does not keep a reader waiting.
15
+ *
16
+ * The heading and the line under it take the first two steps; the figures follow one step apart up
17
+ * to the fourth, and the rest arrive with it — FeatureCards' cap and its reason.
18
+ */
19
+ const FIRST_FIGURE = 2;
20
+ const LAST_STEP = FIRST_FIGURE + 3;
21
+ const stepOf = (at) => Math.min(FIRST_FIGURE + at, LAST_STEP);
22
+ /**
23
+ * A heading over a row of key numbers, each counting up once as the section arrives — the reference
24
+ * site's StatsSimple.
25
+ *
26
+ * The count is decoration over a fact that is already written. Every figure is rendered as the page
27
+ * wrote it, from the first render — in the server's HTML, to a reader with no script, to a search
28
+ * engine — and that text is what assistive technology reads throughout. While a figure counts, the
29
+ * running number is laid over it in the same cell, hidden from assistive technology, and the figure
30
+ * beneath turns transparent rather than leaving: it still holds the cell's width, so nothing beside
31
+ * it moves as the digits change.
32
+ *
33
+ * The count is started by the arrival every block shares, `useReveal`, and only by that hook having
34
+ * hidden the section first. A reader who asked for less movement is never hidden, and so never
35
+ * counted at; nor is a renderer that cannot observe. Each figure starts as its own arrival does, a
36
+ * step behind the one before, and runs for motion.duration.reveal — the figure lands as it settles.
37
+ *
38
+ * `'use client'` for the count, the arrival, and `useId`, which names the region by its heading.
39
+ */
40
+ export function Stats({ ground = statsDefaults.ground, title, description, stats }) {
41
+ const titleId = useId();
42
+ const [band, revealed] = useReveal();
43
+ const held = useRef(null);
44
+ const hold = useCallback((element) => {
45
+ held.current = element;
46
+ band(element);
47
+ }, [band]);
48
+ /** Milliseconds since the section arrived, while it counts; `null` at rest. */
49
+ const [elapsed, setElapsed] = useState(null);
50
+ const [timing, setTiming] = useState(null);
51
+ // Whether the section has been hidden to wait for its arrival. Only a section that waited counts:
52
+ // one shown from the start — reduced motion, no observer — was never at nought to count from.
53
+ const waited = useRef(false);
54
+ const figures = stats.length;
55
+ // On a phone the figures turn one at a time in a wheel rather than stacking — see useWheel.
56
+ const section = useRef(null);
57
+ const { wheeling, turn, hold: holdWheel } = useWheel(section, figures);
58
+ useLayoutEffect(() => {
59
+ if (!revealed) {
60
+ waited.current = true;
61
+ return;
62
+ }
63
+ if (!waited.current)
64
+ return;
65
+ waited.current = false;
66
+ const element = held.current;
67
+ if (element === null)
68
+ return;
69
+ // Asked again here, defensively: the hook declines to hide under this preference, so this
70
+ // should never be reached with it set — and if it is, the figures simply show.
71
+ if (typeof window.matchMedia === 'function' && window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
72
+ return;
73
+ }
74
+ const style = getComputedStyle(element);
75
+ const duration = milliseconds(style.getPropertyValue('--motion-duration-reveal')) ?? 0;
76
+ const stagger = milliseconds(style.getPropertyValue('--motion-duration-stagger')) ?? 0;
77
+ if (duration <= 0)
78
+ return;
79
+ const curve = easing(style.getPropertyValue('--motion-easing-reveal'));
80
+ // The last figure starts a step behind the one before it, so the count is over when it lands.
81
+ const total = stepOf(Math.max(figures - 1, 0)) * stagger + duration;
82
+ // At nought before the first frame is painted, in this layout effect: the section is arriving
83
+ // from transparent, and a frame of the final figures first would be a flash of the answer.
84
+ setTiming({ duration, stagger, curve });
85
+ setElapsed(0);
86
+ let start = null;
87
+ let frame = window.requestAnimationFrame(function tick(now) {
88
+ start ??= now;
89
+ const since = now - start;
90
+ if (since >= total) {
91
+ frame = 0;
92
+ setElapsed(null);
93
+ return;
94
+ }
95
+ setElapsed(since);
96
+ frame = window.requestAnimationFrame(tick);
97
+ });
98
+ return () => {
99
+ if (frame !== 0)
100
+ window.cancelAnimationFrame(frame);
101
+ setElapsed(null);
102
+ };
103
+ }, [revealed, figures]);
104
+ // Nothing to show is nothing drawn: a named region with no figure in it leads nowhere.
105
+ if (figures === 0)
106
+ return null;
107
+ return (_jsx("section", { className: statsParts.root, ref: section, "data-ground": ground, "aria-labelledby": titleId, children: _jsxs("div", { className: statsParts.inner, ref: hold, "data-revealed": revealed, children: [_jsxs("div", { className: statsParts.header, children: [_jsx("h2", { className: statsParts.title, id: titleId, ...arriving(0), children: title }), given(description) ? (_jsx("p", { className: statsParts.description, ...arriving(1), children: description })) : null] }), _jsx("span", { className: statsParts.dots, "aria-hidden": "true", ...(wheeling ? arriving(FIRST_FIGURE) : {}), children: stats.map((_, at) => (_jsx("span", { className: statsParts.dot, "data-current": wheeling && at === turn % figures }, at))) }), _jsx("ul", { className: statsParts.list, role: "list", "data-wheel": wheeling || undefined, tabIndex: wheeling ? 0 : undefined, "aria-labelledby": wheeling ? titleId : undefined, ...(wheeling ? { ...arriving(FIRST_FIGURE), ...holdWheel } : {}), children: stats.map((figure, at) => {
108
+ const step = stepOf(at);
109
+ const number = elapsed === null || timing === null ? null : countable(figure.value);
110
+ // The share of its count this figure has run, from its own arrival rather than the
111
+ // section's — so each number starts climbing as it starts to rise.
112
+ const share = number === null || timing === null || elapsed === null
113
+ ? 1
114
+ : timing.curve(Math.min(Math.max((elapsed - step * timing.stagger) / timing.duration, 0), 1));
115
+ const counting = number !== null && share < 1;
116
+ return (
117
+ // A figure's position is its identity: the set is a list, and nothing reorders it.
118
+ _jsxs("li", { className: statsParts.item, "data-slot": wheeling ? slotOf(at, turn, figures) : undefined, ...(wheeling ? {} : arriving(step)), children: [_jsxs("span", { className: statsParts.value, "data-counting": counting, children: [_jsx("span", { className: statsParts.amount, children: _jsx("bdi", { children: figure.value }) }), _jsx("span", { className: statsParts.count, "aria-hidden": "true", children: counting ? _jsx("bdi", { children: partway(number, share) }) : null })] }), _jsx("span", { className: statsParts.label, children: figure.label })] }, at));
119
+ }) })] }) }));
120
+ }
@@ -0,0 +1,36 @@
1
+ import type { StatsFigure } from '../../stats-data.js';
2
+ export interface StatsOwnProps {
3
+ /**
4
+ * `page` — The default, and the page's own white. A row of figures between two sections that need nothing to separate them
5
+ * `subtle` — The grey band, for a row of figures that should read as a section of its own without changing the page's voice. Painted as the band's tint laid over the page's own ground, so it is the same grey whatever is behind the section
6
+ * `strong` — `color.bg.brand-strong`, the dark band, for a set of figures a reader should arrive at — a home page's "Trade with confidence". The heading and the figures turn white and the labels take color.fg.muted-inverse
7
+ */
8
+ ground?: 'page' | 'subtle' | 'strong';
9
+ /** Always. What the numbers prove, in the fewest words — "Trade with confidence", "Deriv in numbers". */
10
+ title: string;
11
+ /** A line under the heading, when the heading alone does not say whose numbers these are or since when — "For over 25 years, a trusted partner of traders worldwide." */
12
+ description?: string;
13
+ /** Always. The figures, in the order they are read along the row — three to five, each a short `value` exactly as it is to be read and a `label` of a few words. A value holding one number counts up to it; '24/7', or a value with no number, simply shows. An empty list draws nothing. */
14
+ stats: StatsFigure[];
15
+ }
16
+ export type StatsProps = StatsOwnProps;
17
+ /** The class each part carries. Apply these — do not retype them. */
18
+ export declare const statsParts: {
19
+ readonly root: "dds-stats";
20
+ readonly inner: "dds-stats__inner";
21
+ readonly header: "dds-stats__header";
22
+ readonly title: "dds-stats__title";
23
+ readonly description: "dds-stats__description";
24
+ readonly list: "dds-stats__list";
25
+ readonly dots: "dds-stats__dots";
26
+ readonly dot: "dds-stats__dot";
27
+ readonly item: "dds-stats__item";
28
+ readonly value: "dds-stats__value";
29
+ readonly amount: "dds-stats__amount";
30
+ readonly count: "dds-stats__count";
31
+ readonly label: "dds-stats__label";
32
+ };
33
+ /** Generated so defaults cannot drift from the contract. */
34
+ export declare const statsDefaults: {
35
+ readonly ground: "page";
36
+ };
@@ -0,0 +1,19 @@
1
+ // GENERATED from stats.contract.json. Do not edit.
2
+ /** The class each part carries. Apply these — do not retype them. */
3
+ export const statsParts = {
4
+ root: 'dds-stats',
5
+ inner: 'dds-stats__inner',
6
+ header: 'dds-stats__header',
7
+ title: 'dds-stats__title',
8
+ description: 'dds-stats__description',
9
+ list: 'dds-stats__list',
10
+ dots: 'dds-stats__dots',
11
+ dot: 'dds-stats__dot',
12
+ item: 'dds-stats__item',
13
+ value: 'dds-stats__value',
14
+ amount: 'dds-stats__amount',
15
+ count: 'dds-stats__count',
16
+ label: 'dds-stats__label',
17
+ };
18
+ /** Generated so defaults cannot drift from the contract. */
19
+ export const statsDefaults = { ground: 'page' };
@@ -0,0 +1,34 @@
1
+ /**
2
+ * The arithmetic of a Stats figure counting up: which figures can, what one reads partway there,
3
+ * and the curve it climbs on. Pure, so it is tested without a browser to animate in.
4
+ */
5
+ /** A figure that can count: the one number in it, and what is written either side. */
6
+ export interface Countable {
7
+ before: string;
8
+ whole: number;
9
+ /** How many places after the point the figure is written to — '1.2B' counts in tenths. */
10
+ places: number;
11
+ /** Whether the figure groups its thousands with commas — '1,000+' climbs as '640', '1,000'. */
12
+ grouped: boolean;
13
+ after: string;
14
+ }
15
+ /**
16
+ * The number in a figure, where it has exactly one.
17
+ *
18
+ * Exactly one, because a value with two is not a quantity: '24/7' counted up is '3/7' on the way,
19
+ * which is a different claim rather than a smaller one. A value with none — 'Free', 'Always' —
20
+ * simply shows. Everything either side of the number is kept as written, so '$' and 'M+' stand
21
+ * still while the digits climb.
22
+ */
23
+ export declare function countable(value: string): Countable | null;
24
+ /** The figure at a share of the way to its value, written the way the value is. */
25
+ export declare function partway(figure: Countable, share: number): string;
26
+ /**
27
+ * A `cubic-bezier()` easing, as a function of time.
28
+ *
29
+ * The count moves on motion.easing.reveal, the curve the figure itself rises on, so the number and
30
+ * the words under it settle together. A transition can name the token; a number drawn frame by
31
+ * frame cannot, so the curve is read off the token and walked here. Anything that is not a
32
+ * `cubic-bezier()` — a keyword, or nothing — counts evenly rather than on a curve invented here.
33
+ */
34
+ export declare function easing(value: string): (time: number) => number;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The arithmetic of a Stats figure counting up: which figures can, what one reads partway there,
3
+ * and the curve it climbs on. Pure, so it is tested without a browser to animate in.
4
+ */
5
+ /**
6
+ * The number in a figure, where it has exactly one.
7
+ *
8
+ * Exactly one, because a value with two is not a quantity: '24/7' counted up is '3/7' on the way,
9
+ * which is a different claim rather than a smaller one. A value with none — 'Free', 'Always' —
10
+ * simply shows. Everything either side of the number is kept as written, so '$' and 'M+' stand
11
+ * still while the digits climb.
12
+ */
13
+ export function countable(value) {
14
+ const match = /^(\D*)(\d{1,3}(?:,\d{3})+|\d+)(?:\.(\d+))?(\D*)$/.exec(value);
15
+ if (match === null)
16
+ return null;
17
+ const [, before = '', digits = '', fraction = '', after = ''] = match;
18
+ return {
19
+ before,
20
+ whole: Number.parseFloat(`${digits.replace(/,/g, '')}.${fraction || '0'}`),
21
+ places: fraction.length,
22
+ grouped: digits.includes(','),
23
+ after,
24
+ };
25
+ }
26
+ /** The figure at a share of the way to its value, written the way the value is. */
27
+ export function partway(figure, share) {
28
+ const [whole = '0', fraction] = (figure.whole * share).toFixed(figure.places).split('.');
29
+ const digits = figure.grouped ? whole.replace(/\B(?=(\d{3})+(?!\d))/g, ',') : whole;
30
+ return `${figure.before}${digits}${fraction === undefined ? '' : `.${fraction}`}${figure.after}`;
31
+ }
32
+ /**
33
+ * A `cubic-bezier()` easing, as a function of time.
34
+ *
35
+ * The count moves on motion.easing.reveal, the curve the figure itself rises on, so the number and
36
+ * the words under it settle together. A transition can name the token; a number drawn frame by
37
+ * frame cannot, so the curve is read off the token and walked here. Anything that is not a
38
+ * `cubic-bezier()` — a keyword, or nothing — counts evenly rather than on a curve invented here.
39
+ */
40
+ export function easing(value) {
41
+ const points = /cubic-bezier\(([^)]*)\)/.exec(value)?.[1]?.split(',').map(Number);
42
+ if (points?.length !== 4 || points.some((point) => !Number.isFinite(point)))
43
+ return (time) => time;
44
+ const [x1 = 0, y1 = 0, x2 = 1, y2 = 1] = points;
45
+ const along = (a, b, s) => 3 * a * s * (1 - s) ** 2 + 3 * b * s * s * (1 - s) + s ** 3;
46
+ return (time) => {
47
+ // Halving for the point on the curve whose x is the time: monotonic in x, so it always lands,
48
+ // and twenty halvings is closer than a pixel of any figure this draws.
49
+ let low = 0;
50
+ let high = 1;
51
+ let s = time;
52
+ for (let step = 0; step < 20; step += 1) {
53
+ s = (low + high) / 2;
54
+ if (along(x1, x2, s) < time)
55
+ low = s;
56
+ else
57
+ high = s;
58
+ }
59
+ return along(y1, y2, s);
60
+ };
61
+ }
@@ -0,0 +1,284 @@
1
+ /*
2
+ * Scoped to this package, like the generated sheet beside it.
3
+ */
4
+
5
+ /*
6
+ * A heading with no line under it closes up. Its bound step is the distance to the line, and with
7
+ * nothing there it would only add to the step from the header to the figures. `:last-child` is the
8
+ * condition itself, TestimonialRow's way, rather than a prop standing in for it.
9
+ */
10
+ :where([data-dds-context="marketing"]) .dds-stats .dds-stats__title:last-child {
11
+ margin-block-end: 0;
12
+ }
13
+
14
+ /*
15
+ * The figure as written and the running number share one cell.
16
+ *
17
+ * Both in the first row and column, so the cell is as wide as the wider of the two — which is the
18
+ * final figure, the running number never having more digits than the one it is climbing to. That
19
+ * is what holds the width while it counts: the label under it and the figures beside it stay where
20
+ * they are. A grid area is a pair of lines rather than a keyword, so it cannot sit in the contract.
21
+ */
22
+ :where([data-dds-context="marketing"]) .dds-stats .dds-stats__value > * {
23
+ grid-area: 1 / 1;
24
+ }
25
+
26
+ /*
27
+ * While it counts, the figure as written steps back behind the running number.
28
+ *
29
+ * Transparent rather than hidden: `visibility` and `display` would take it out of the accessibility
30
+ * tree, and it is the one thing there a screen reader should hear — the final figure, once. It also
31
+ * still holds the cell's width, which a hidden element would not.
32
+ */
33
+ :where([data-dds-context="marketing"]) .dds-stats .dds-stats__value[data-counting="true"] > .dds-stats__amount {
34
+ opacity: var(--opacity-hidden);
35
+ }
36
+
37
+ /*
38
+ * Two to a row on a phone. `minmax(0, 1fr)` rather than `1fr`, so a figure that will not break —
39
+ * a long one, at the figure's size — shrinks to its column instead of pushing the row past the page.
40
+ */
41
+ :where([data-dds-context="marketing"]) .dds-stats .dds-stats__list {
42
+ grid-template-columns: repeat(2, minmax(0, 1fr));
43
+ }
44
+
45
+ /*
46
+ * On a phone, the wheel: one figure in place, the one before it smaller above, the next smaller
47
+ * below, turning on every motion.duration.hold — the reference site's mobile stats. useWheel sets
48
+ * `data-wheel` only below the fold, for two figures or more, and never for a reader who asked for
49
+ * less motion; without it the figures are the grid above.
50
+ *
51
+ * Start-aligned, as the heading is on a phone, and larger than the grid's figures — font.figure-lg
52
+ * for the number and body.lg for its label: one figure has the row to itself, and it is the one
53
+ * thing the band is showing.
54
+ *
55
+ * Every figure stands in the same cell, so the list is as tall as the tallest figure and each one
56
+ * is moved off it rather than laid out beside it. A step is a whole figure's own height — `100%` of
57
+ * the item, which the cell stretches to the tallest — rather than the arithmetic below: that sum is
58
+ * one line of label, and a label that wraps to two in German, or stands taller in Tamil, set every
59
+ * figure on the wheel into the next. The sum still sizes the room above and below. `--item` is a figure's height, the figure's line
60
+ * and the label's under it, so a step on the wheel is one figure: the one above sits a whole figure
61
+ * higher, at --dds-stats-wheel-side of its size (0.65) — the reference's scale, small enough that the one in place is plainly
62
+ * the one being read. The block padding is the room the side figures need, which is what is left of
63
+ * a figure's height once it is that far out and that much smaller.
64
+ *
65
+ * The side figures fade towards the band by a mask rather than by an ink of their own, so the wheel
66
+ * reads on every ground without a colour per ground.
67
+ */
68
+ :where([data-dds-context="marketing"]) .dds-stats .dds-stats__list[data-wheel] {
69
+ --dds-stats-wheel-item: calc(
70
+ var(--font-figure-lg-size) * var(--font-figure-lg-line-height) + var(--space-gap-sm) + var(--font-body-lg-size) *
71
+ var(--font-body-lg-line-height)
72
+ );
73
+
74
+ /* The wheel's geometry, the reference's numbers, named once. The side figures' scale; the scale a
75
+ figure shrinks to out of sight; the share of a figure's height the side figures need as room
76
+ above and below; and where the mask's fade stops, from either edge. */
77
+ --dds-stats-wheel-side: 0.65;
78
+ --dds-stats-wheel-away: 0.4;
79
+ --dds-stats-wheel-room: 0.85;
80
+ --dds-stats-wheel-fade: 35%;
81
+
82
+ grid-template-columns: minmax(0, 1fr);
83
+ mask-image: linear-gradient(
84
+ transparent,
85
+ black var(--dds-stats-wheel-fade),
86
+ black calc(100% - var(--dds-stats-wheel-fade)),
87
+ transparent
88
+ );
89
+ padding-block: calc(var(--dds-stats-wheel-item) * var(--dds-stats-wheel-room));
90
+ }
91
+
92
+ /*
93
+ * The wheel is a tab stop while it turns — focus holds it still — so it shows where focus is. The
94
+ * ring is drawn inside: the mask would fade one drawn outside at its top and foot.
95
+ */
96
+ :where([data-dds-context="marketing"]) .dds-stats .dds-stats__list[data-wheel]:focus-visible {
97
+ outline: var(--border-width-focus) solid var(--color-border-focus);
98
+ outline-offset: calc(-1 * var(--border-width-focus));
99
+ }
100
+
101
+ :where([data-dds-context="marketing"]) .dds-stats[data-ground="strong"] .dds-stats__list[data-wheel]:focus-visible {
102
+ outline-color: var(--color-border-focus-on-brand-strong);
103
+ }
104
+
105
+ :where([data-dds-context="marketing"]) .dds-stats .dds-stats__list[data-wheel] > .dds-stats__item {
106
+ grid-area: 1 / 1;
107
+ transform-origin: left center;
108
+ transition-duration: var(--motion-duration-turn);
109
+ transition-property: opacity, scale, translate;
110
+ transition-timing-function: var(--motion-easing-reveal);
111
+ }
112
+
113
+ :where([data-dds-context="marketing"]) .dds-stats .dds-stats__list[data-wheel] .dds-stats__value {
114
+ font-family: var(--font-figure-lg-family);
115
+ font-size: var(--font-figure-lg-size);
116
+ font-weight: var(--font-figure-lg-weight);
117
+ letter-spacing: var(--font-figure-lg-letter-spacing);
118
+ line-height: var(--font-figure-lg-line-height);
119
+ }
120
+
121
+ :where([data-dds-context="marketing"]) .dds-stats .dds-stats__list[data-wheel] .dds-stats__label {
122
+ font-family: var(--font-body-lg-family);
123
+ font-size: var(--font-body-lg-size);
124
+ font-weight: var(--font-body-lg-weight);
125
+ letter-spacing: var(--font-body-lg-letter-spacing);
126
+ line-height: var(--font-body-lg-line-height);
127
+ }
128
+
129
+ :where([data-dds-context="marketing"]) .dds-stats .dds-stats__list[data-wheel] > [data-slot='above'] {
130
+ scale: var(--dds-stats-wheel-side);
131
+ translate: 0 -100%;
132
+ }
133
+
134
+ :where([data-dds-context="marketing"]) .dds-stats .dds-stats__list[data-wheel] > [data-slot='below'] {
135
+ animation: dds-stats-wheel-in var(--motion-duration-turn) var(--motion-easing-reveal);
136
+ scale: var(--dds-stats-wheel-side);
137
+ translate: 0 100%;
138
+ }
139
+
140
+ /*
141
+ * Out of sight, a figure waits where the one above leaves to — risen and shrunk away — so leaving is
142
+ * a movement on, never back. It comes in below by an arrival of its own, from further down and
143
+ * smaller, rather than by crossing the wheel to get there.
144
+ */
145
+ :where([data-dds-context="marketing"]) .dds-stats .dds-stats__list[data-wheel] > [data-slot='hidden'] {
146
+ opacity: var(--opacity-hidden);
147
+ scale: var(--dds-stats-wheel-away);
148
+ translate: 0 -100%;
149
+ }
150
+
151
+ @keyframes dds-stats-wheel-in {
152
+ from {
153
+ opacity: var(--opacity-hidden);
154
+ scale: var(--dds-stats-wheel-away);
155
+ translate: 0 200%;
156
+ }
157
+ }
158
+
159
+ /*
160
+ * A reader who asked for less motion never gets the wheel — useWheel leaves the figures a grid — so
161
+ * this only says so in the sheet as well: were a wheel ever drawn for them, it would change places
162
+ * without moving.
163
+ */
164
+ @media (prefers-reduced-motion: reduce) {
165
+ :where([data-dds-context="marketing"]) .dds-stats .dds-stats__list[data-wheel] > .dds-stats__item {
166
+ animation: none;
167
+ transition: none;
168
+ }
169
+
170
+ :where([data-dds-context="marketing"]) .dds-stats .dds-stats__dot {
171
+ transition: none;
172
+ }
173
+ }
174
+
175
+ /*
176
+ * The wheel's dots on its start edge, and the figures beside them.
177
+ *
178
+ * While the wheel turns the section is a grid of two columns: the dots as wide as a dot, the figures
179
+ * the rest, the heading across both. The dots stand level with the figure in place, which is the
180
+ * middle of the list. Between the two, space.gap.md — the step between a control and what it
181
+ * describes.
182
+ *
183
+ * The dot in place is drawn long, size.dot.current, at full strength: PaginationDots' current dot
184
+ * on end, growing as the next figure turns in, the dots after it moving along to make room. It is
185
+ * the brand's coral, color.bg.brand, where the rest take the band's ink: the one mark of colour in
186
+ * the section says which figure is being read, and it holds on all three grounds.
187
+ */
188
+ :where([data-dds-context="marketing"]) .dds-stats .dds-stats__inner:has(> .dds-stats__list[data-wheel]) {
189
+ column-gap: var(--space-gap-md);
190
+ display: grid;
191
+ grid-template-columns: auto minmax(0, 1fr);
192
+ }
193
+
194
+ :where([data-dds-context="marketing"]) .dds-stats .dds-stats__inner:has(> .dds-stats__list[data-wheel]) > .dds-stats__header {
195
+ grid-column: 1 / -1;
196
+ }
197
+
198
+ :where([data-dds-context="marketing"]) .dds-stats .dds-stats__inner:has(> .dds-stats__list[data-wheel]) > .dds-stats__dots {
199
+ align-self: center;
200
+ display: flex;
201
+ }
202
+
203
+ :where([data-dds-context="marketing"]) .dds-stats .dds-stats__dot {
204
+ transition-duration: var(--motion-duration-turn);
205
+ transition-property: background-color, block-size, opacity;
206
+ transition-timing-function: var(--motion-easing-reveal);
207
+ }
208
+
209
+ :where([data-dds-context="marketing"]) .dds-stats .dds-stats__dot[data-current="true"] {
210
+ background-color: var(--color-bg-brand);
211
+ block-size: var(--size-dot-current);
212
+ opacity: var(--opacity-dot-current);
213
+ }
214
+
215
+ /*
216
+ * The figures shrink towards the edge they start at, so the one above and the one below stay in line
217
+ * with the one in place and with the heading over them.
218
+ */
219
+ :where([data-dds-context="marketing"]) .dds-stats:dir(rtl) .dds-stats__list[data-wheel] > .dds-stats__item {
220
+ transform-origin: right center;
221
+ }
222
+
223
+ /*
224
+ * From the fold, the header and every figure are centred, the heading over the set it names.
225
+ *
226
+ * FeatureCards' rule and its reason: centred text starts every line at a different edge, which
227
+ * costs little across a wide measure and a great deal across a narrow one. The header and the line
228
+ * under it are capped narrower than the column, so `margin-inline: auto` is what centres those
229
+ * boxes rather than their words inside a box left at the start edge. A figure is centred in its
230
+ * column, so a row of them sits under the middle of the heading rather than to its start.
231
+ *
232
+ * `48rem` longhand because `var()` is not permitted in a container query condition, and it is the
233
+ * width the rest of this package folds at.
234
+ */
235
+ @container (min-width: 48rem) {
236
+ :where([data-dds-context="marketing"]) .dds-stats .dds-stats__header {
237
+ align-items: center;
238
+ margin-inline: auto;
239
+ text-align: center;
240
+ }
241
+
242
+ :where([data-dds-context="marketing"]) .dds-stats .dds-stats__description {
243
+ margin-inline: auto;
244
+ }
245
+
246
+ :where([data-dds-context="marketing"]) .dds-stats .dds-stats__item {
247
+ align-items: center;
248
+ text-align: center;
249
+ }
250
+
251
+ :where([data-dds-context="marketing"]) .dds-stats .dds-stats__value {
252
+ justify-items: center;
253
+ }
254
+
255
+ /*
256
+ * How many figures share a row, read from the figures — FeatureCards' way, `:has()` asking the
257
+ * markup for the count it already holds rather than a prop restating it.
258
+ *
259
+ * Three to a row by default, which is five as three and two, and six as two rows of three. Three
260
+ * or fewer take one row of their own. Four are two rows of two here and one row at full width:
261
+ * four figures across a tablet's column are each narrower than a figure is wide at this size.
262
+ *
263
+ * Where `:has()` is not supported every count takes three to a row: the set still reads, only
264
+ * less exactly.
265
+ */
266
+ :where([data-dds-context="marketing"]) .dds-stats .dds-stats__list {
267
+ grid-template-columns: repeat(3, minmax(0, 1fr));
268
+ }
269
+
270
+ :where([data-dds-context="marketing"]) .dds-stats .dds-stats__list:has(> :nth-child(4):last-child),
271
+ :where([data-dds-context="marketing"]) .dds-stats .dds-stats__list:has(> :nth-child(2):last-child) {
272
+ grid-template-columns: repeat(2, minmax(0, 1fr));
273
+ }
274
+
275
+ :where([data-dds-context="marketing"]) .dds-stats .dds-stats__list:has(> :only-child) {
276
+ grid-template-columns: minmax(0, 1fr);
277
+ }
278
+ }
279
+
280
+ @container (min-width: 64rem) {
281
+ :where([data-dds-context="marketing"]) .dds-stats .dds-stats__list:has(> :nth-child(4):last-child) {
282
+ grid-template-columns: repeat(4, minmax(0, 1fr));
283
+ }
284
+ }
@@ -0,0 +1,37 @@
1
+ import { type FocusEvent, type PointerEvent, type RefObject } from 'react';
2
+ /** Where a figure stands on the wheel, relative to the one in place. */
3
+ export type WheelSlot = 'active' | 'above' | 'below' | 'hidden';
4
+ /**
5
+ * A figure's place on the wheel at a given turn: the one in place, the one it replaced above it, the
6
+ * one coming in below it, and every other figure out of sight.
7
+ *
8
+ * With two figures there is no "above" — the one that leaves comes straight back in below — and with
9
+ * three the one above goes straight to below, which the stylesheet draws as an arrival from below
10
+ * rather than as a figure crossing the wheel.
11
+ */
12
+ export declare function slotOf(at: number, turn: number, count: number): WheelSlot;
13
+ /**
14
+ * The phone's wheel of figures — the reference site's mobile stats: one figure in place, the one
15
+ * before it smaller above and the next smaller below, turning on to the next every
16
+ * motion.duration.hold plus motion.duration.turn.
17
+ *
18
+ * It turns only where every condition for it holds, and otherwise hands back `wheeling: false` and
19
+ * the figures are the grid they are from the fold:
20
+ * - the section is narrower than the fold, read off the section itself — a container, not the
21
+ * viewport, as the stylesheet reads it;
22
+ * - there are two figures or more;
23
+ * - the reader has not asked for less motion — the grid is the still version of the same set;
24
+ * - the section is on screen. A wheel nobody can see does not turn, and starts again where it was;
25
+ * - nothing holds or has stopped it — see `hold`.
26
+ */
27
+ export declare function useWheel(section: RefObject<HTMLElement | null>, count: number): {
28
+ wheeling: boolean;
29
+ turn: number;
30
+ hold: {
31
+ onPointerEnter: (event: PointerEvent<HTMLElement>) => void;
32
+ onPointerLeave: (event: PointerEvent<HTMLElement>) => void;
33
+ onFocus: () => void;
34
+ onBlur: (event: FocusEvent<HTMLElement>) => void;
35
+ onPointerDownCapture: (event: PointerEvent<HTMLElement>) => void;
36
+ };
37
+ };