@deriv-com/dds-marketing 0.2.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (281) hide show
  1. package/.llm/components/accordion.md +9 -1
  2. package/.llm/components/accordionitem.md +9 -1
  3. package/.llm/components/aspectratio.md +9 -1
  4. package/.llm/components/badge.md +9 -1
  5. package/.llm/components/bottomsheet.md +9 -1
  6. package/.llm/components/breadcrumb.md +9 -1
  7. package/.llm/components/breadcrumbitem.md +9 -1
  8. package/.llm/components/button.md +55 -27
  9. package/.llm/components/buttonicon.md +16 -8
  10. package/.llm/components/card.md +16 -2
  11. package/.llm/components/cardbody.md +29 -3
  12. package/.llm/components/cardbutton.md +16 -2
  13. package/.llm/components/checkbox.md +9 -1
  14. package/.llm/components/ctabanner.md +186 -0
  15. package/.llm/components/ctabannerslider.md +204 -0
  16. package/.llm/components/divider.md +9 -1
  17. package/.llm/components/dropdownmenu.md +15 -7
  18. package/.llm/components/faqaccordion.md +217 -0
  19. package/.llm/components/featurebullets.md +254 -0
  20. package/.llm/components/featurecards.md +313 -0
  21. package/.llm/components/featurescroll.md +284 -0
  22. package/.llm/components/featuresplit.md +249 -0
  23. package/.llm/components/filterchip.md +9 -1
  24. package/.llm/components/footer.md +9 -1
  25. package/.llm/components/footergroup.md +9 -1
  26. package/.llm/components/grid.md +9 -1
  27. package/.llm/components/icon.md +21 -3
  28. package/.llm/components/landinghero.md +254 -0
  29. package/.llm/components/link.md +13 -5
  30. package/.llm/components/logocolumns.md +235 -0
  31. package/.llm/components/logorows.md +212 -0
  32. package/.llm/components/menuitem.md +9 -1
  33. package/.llm/components/navbar.md +80 -16
  34. package/.llm/components/navbaritem.md +10 -2
  35. package/.llm/components/navbarmenu.md +9 -1
  36. package/.llm/components/navbarmenugroup.md +9 -1
  37. package/.llm/components/navbarpanel.md +10 -2
  38. package/.llm/components/navbarswitcher.md +10 -2
  39. package/.llm/components/navbarswitcheroption.md +9 -1
  40. package/.llm/components/option.md +9 -1
  41. package/.llm/components/pageheader.md +231 -0
  42. package/.llm/components/pagination.md +27 -90
  43. package/.llm/components/paginationdots.md +215 -0
  44. package/.llm/components/producthero.md +248 -0
  45. package/.llm/components/removablechip.md +9 -1
  46. package/.llm/components/row.md +9 -1
  47. package/.llm/components/scroller.md +9 -1
  48. package/.llm/components/searchinput.md +9 -1
  49. package/.llm/components/segmentedcontrol.md +9 -1
  50. package/.llm/components/segmentedcontrolitem.md +9 -1
  51. package/.llm/components/select.md +9 -1
  52. package/.llm/components/selectchip.md +57 -16
  53. package/.llm/components/spinner.md +9 -1
  54. package/.llm/components/stack.md +9 -1
  55. package/.llm/components/statusdot.md +9 -1
  56. package/.llm/components/subdomainnavbar.md +9 -1
  57. package/.llm/components/tab.md +11 -3
  58. package/.llm/components/table.md +9 -1
  59. package/.llm/components/tablebody.md +9 -1
  60. package/.llm/components/tablecell.md +9 -1
  61. package/.llm/components/tablehead.md +9 -1
  62. package/.llm/components/tableheadercell.md +9 -1
  63. package/.llm/components/tablerow.md +9 -1
  64. package/.llm/components/tablist.md +9 -1
  65. package/.llm/components/testimonialrow.md +235 -0
  66. package/.llm/components/testimonialslider.md +254 -0
  67. package/.llm/components/textarea.md +9 -1
  68. package/.llm/components/textinput.md +9 -1
  69. package/.llm/components/tooltip.md +9 -1
  70. package/.llm/components/typography.md +9 -1
  71. package/.llm/digest/button.md +11 -6
  72. package/.llm/digest/buttonicon.md +2 -2
  73. package/.llm/digest/card.md +1 -0
  74. package/.llm/digest/cardbody.md +3 -1
  75. package/.llm/digest/cardbutton.md +1 -0
  76. package/.llm/digest/ctabanner.md +44 -0
  77. package/.llm/digest/ctabannerslider.md +45 -0
  78. package/.llm/digest/dropdownmenu.md +1 -1
  79. package/.llm/digest/faqaccordion.md +43 -0
  80. package/.llm/digest/featurebullets.md +52 -0
  81. package/.llm/digest/featurecards.md +49 -0
  82. package/.llm/digest/featurescroll.md +52 -0
  83. package/.llm/digest/featuresplit.md +54 -0
  84. package/.llm/digest/landinghero.md +53 -0
  85. package/.llm/digest/logocolumns.md +55 -0
  86. package/.llm/digest/logorows.md +48 -0
  87. package/.llm/digest/navbar.md +18 -3
  88. package/.llm/digest/pageheader.md +49 -0
  89. package/.llm/digest/pagination.md +6 -17
  90. package/.llm/digest/paginationdots.md +43 -0
  91. package/.llm/digest/producthero.md +53 -0
  92. package/.llm/digest/selectchip.md +6 -1
  93. package/.llm/digest/testimonialrow.md +51 -0
  94. package/.llm/digest/testimonialslider.md +51 -0
  95. package/.llm/index.md +24 -3
  96. package/.llm/rules.md +43 -9
  97. package/.llm/tokens.md +56 -12
  98. package/AGENTS.md +32 -8
  99. package/README.md +41 -21
  100. package/dist/components/banner-data.d.ts +38 -0
  101. package/dist/components/banner-data.js +15 -0
  102. package/dist/components/blocks/block-reveal.css +90 -0
  103. package/dist/components/blocks/cta-banner/CtaBanner.css +66 -0
  104. package/dist/components/blocks/cta-banner/CtaBanner.d.ts +22 -0
  105. package/dist/components/blocks/cta-banner/CtaBanner.js +31 -0
  106. package/dist/components/blocks/cta-banner/CtaBanner.types.d.ts +29 -0
  107. package/dist/components/blocks/cta-banner/CtaBanner.types.js +13 -0
  108. package/dist/components/blocks/cta-banner/cta-banner-impl.css +29 -0
  109. package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.css +90 -0
  110. package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.d.ts +39 -0
  111. package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.js +60 -0
  112. package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.types.d.ts +41 -0
  113. package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.types.js +15 -0
  114. package/dist/components/blocks/cta-banner-slider/cta-banner-slider-impl.css +73 -0
  115. package/dist/components/blocks/faq-accordion/FaqAccordion.css +54 -0
  116. package/dist/components/blocks/faq-accordion/FaqAccordion.d.ts +20 -0
  117. package/dist/components/blocks/faq-accordion/FaqAccordion.js +28 -0
  118. package/dist/components/blocks/faq-accordion/FaqAccordion.types.d.ts +25 -0
  119. package/dist/components/blocks/faq-accordion/FaqAccordion.types.js +10 -0
  120. package/dist/components/blocks/faq-accordion/faq-accordion-impl.css +54 -0
  121. package/dist/components/blocks/feature-bullets/FeatureBullets.css +139 -0
  122. package/dist/components/blocks/feature-bullets/FeatureBullets.d.ts +31 -0
  123. package/dist/components/blocks/feature-bullets/FeatureBullets.js +55 -0
  124. package/dist/components/blocks/feature-bullets/FeatureBullets.types.d.ts +39 -0
  125. package/dist/components/blocks/feature-bullets/FeatureBullets.types.js +16 -0
  126. package/dist/components/blocks/feature-bullets/feature-bullets-impl.css +104 -0
  127. package/dist/components/blocks/feature-cards/FeatureCards.css +84 -0
  128. package/dist/components/blocks/feature-cards/FeatureCards.d.ts +24 -0
  129. package/dist/components/blocks/feature-cards/FeatureCards.js +66 -0
  130. package/dist/components/blocks/feature-cards/FeatureCards.types.d.ts +33 -0
  131. package/dist/components/blocks/feature-cards/FeatureCards.types.js +14 -0
  132. package/dist/components/blocks/feature-cards/feature-cards-impl.css +245 -0
  133. package/dist/components/blocks/feature-scroll/FeatureScroll.css +137 -0
  134. package/dist/components/blocks/feature-scroll/FeatureScroll.d.ts +39 -0
  135. package/dist/components/blocks/feature-scroll/FeatureScroll.js +167 -0
  136. package/dist/components/blocks/feature-scroll/FeatureScroll.types.d.ts +40 -0
  137. package/dist/components/blocks/feature-scroll/FeatureScroll.types.js +17 -0
  138. package/dist/components/blocks/feature-scroll/feature-scroll-impl.css +292 -0
  139. package/dist/components/blocks/feature-split/FeatureSplit.css +95 -0
  140. package/dist/components/blocks/feature-split/FeatureSplit.d.ts +30 -0
  141. package/dist/components/blocks/feature-split/FeatureSplit.js +38 -0
  142. package/dist/components/blocks/feature-split/FeatureSplit.types.d.ts +39 -0
  143. package/dist/components/blocks/feature-split/FeatureSplit.types.js +14 -0
  144. package/dist/components/blocks/feature-split/feature-split-impl.css +83 -0
  145. package/dist/components/blocks/landing-hero/LandingHero.css +85 -0
  146. package/dist/components/blocks/landing-hero/LandingHero.d.ts +41 -0
  147. package/dist/components/blocks/landing-hero/LandingHero.js +54 -0
  148. package/dist/components/blocks/landing-hero/LandingHero.types.d.ts +37 -0
  149. package/dist/components/blocks/landing-hero/LandingHero.types.js +15 -0
  150. package/dist/components/blocks/landing-hero/landing-hero-impl.css +355 -0
  151. package/dist/components/blocks/logo-columns/LogoColumns.css +161 -0
  152. package/dist/components/blocks/logo-columns/LogoColumns.d.ts +23 -0
  153. package/dist/components/blocks/logo-columns/LogoColumns.js +46 -0
  154. package/dist/components/blocks/logo-columns/LogoColumns.types.d.ts +46 -0
  155. package/dist/components/blocks/logo-columns/LogoColumns.types.js +18 -0
  156. package/dist/components/blocks/logo-columns/logo-columns-impl.css +188 -0
  157. package/dist/components/blocks/logo-rows/LogoRows.css +122 -0
  158. package/dist/components/blocks/logo-rows/LogoRows.d.ts +20 -0
  159. package/dist/components/blocks/logo-rows/LogoRows.js +43 -0
  160. package/dist/components/blocks/logo-rows/LogoRows.types.d.ts +36 -0
  161. package/dist/components/blocks/logo-rows/LogoRows.types.js +16 -0
  162. package/dist/components/blocks/logo-rows/logo-rows-impl.css +179 -0
  163. package/dist/components/blocks/page-header/PageHeader.css +79 -0
  164. package/dist/components/blocks/page-header/PageHeader.d.ts +34 -0
  165. package/dist/components/blocks/page-header/PageHeader.js +43 -0
  166. package/dist/components/blocks/page-header/PageHeader.types.d.ts +32 -0
  167. package/dist/components/blocks/page-header/PageHeader.types.js +14 -0
  168. package/dist/components/blocks/page-header/page-header-impl.css +176 -0
  169. package/dist/components/blocks/product-hero/ProductHero.css +86 -0
  170. package/dist/components/blocks/product-hero/ProductHero.d.ts +35 -0
  171. package/dist/components/blocks/product-hero/ProductHero.js +48 -0
  172. package/dist/components/blocks/product-hero/ProductHero.types.d.ts +37 -0
  173. package/dist/components/blocks/product-hero/ProductHero.types.js +15 -0
  174. package/dist/components/blocks/product-hero/product-hero-impl.css +354 -0
  175. package/dist/components/blocks/testimonial-row/TestimonialRow.css +245 -0
  176. package/dist/components/blocks/testimonial-row/TestimonialRow.d.ts +20 -0
  177. package/dist/components/blocks/testimonial-row/TestimonialRow.js +128 -0
  178. package/dist/components/blocks/testimonial-row/TestimonialRow.types.d.ts +54 -0
  179. package/dist/components/blocks/testimonial-row/TestimonialRow.types.js +26 -0
  180. package/dist/components/blocks/testimonial-row/testimonial-row-impl.css +195 -0
  181. package/dist/components/blocks/testimonial-slider/TestimonialSlider.css +181 -0
  182. package/dist/components/blocks/testimonial-slider/TestimonialSlider.d.ts +22 -0
  183. package/dist/components/blocks/testimonial-slider/TestimonialSlider.js +36 -0
  184. package/dist/components/blocks/testimonial-slider/TestimonialSlider.types.d.ts +49 -0
  185. package/dist/components/blocks/testimonial-slider/TestimonialSlider.types.js +21 -0
  186. package/dist/components/blocks/testimonial-slider/testimonial-slider-impl.css +202 -0
  187. package/dist/components/blocks/use-reveal.d.ts +46 -0
  188. package/dist/components/blocks/use-reveal.js +120 -0
  189. package/dist/components/blocks/use-rotation.d.ts +62 -0
  190. package/dist/components/blocks/use-rotation.js +176 -0
  191. package/dist/components/core/accordion-item/AccordionItem.css +6 -5
  192. package/dist/components/core/bottom-sheet/BottomSheet.css +1 -1
  193. package/dist/components/core/button/Button.css +63 -16
  194. package/dist/components/core/button/Button.d.ts +14 -12
  195. package/dist/components/core/button/Button.js +43 -14
  196. package/dist/components/core/button/Button.types.d.ts +9 -14
  197. package/dist/components/core/button/button-impl.css +25 -0
  198. package/dist/components/core/button-icon/ButtonIcon.css +31 -15
  199. package/dist/components/core/button-icon/ButtonIcon.d.ts +2 -2
  200. package/dist/components/core/button-icon/ButtonIcon.js +2 -2
  201. package/dist/components/core/button-icon/ButtonIcon.types.d.ts +4 -4
  202. package/dist/components/core/card/Card.css +13 -4
  203. package/dist/components/core/card/Card.types.d.ts +2 -1
  204. package/dist/components/core/card-body/CardBody.css +11 -6
  205. package/dist/components/core/card-body/CardBody.d.ts +1 -1
  206. package/dist/components/core/card-body/CardBody.js +2 -2
  207. package/dist/components/core/card-body/CardBody.types.d.ts +4 -1
  208. package/dist/components/core/card-body/CardBody.types.js +1 -0
  209. package/dist/components/core/card-button/CardButton.css +13 -4
  210. package/dist/components/core/card-button/CardButton.types.d.ts +2 -1
  211. package/dist/components/core/checkbox/Checkbox.css +8 -1
  212. package/dist/components/core/dropdown-menu/DropdownMenu.css +13 -12
  213. package/dist/components/core/dropdown-menu/DropdownMenu.d.ts +5 -3
  214. package/dist/components/core/dropdown-menu/DropdownMenu.js +5 -3
  215. package/dist/components/core/filter-chip/FilterChip.css +2 -1
  216. package/dist/components/core/footer/footer-impl.css +1 -1
  217. package/dist/components/core/footer-group/FooterGroup.css +8 -7
  218. package/dist/components/core/icon/Icon.css +1 -1
  219. package/dist/components/core/link/Link.css +1 -1
  220. package/dist/components/core/link/Link.js +5 -5
  221. package/dist/components/core/menu-item/MenuItem.css +1 -1
  222. package/dist/components/core/navbar/Navbar.css +7 -8
  223. package/dist/components/core/navbar/Navbar.d.ts +17 -1
  224. package/dist/components/core/navbar/Navbar.js +139 -12
  225. package/dist/components/core/navbar/Navbar.types.d.ts +23 -4
  226. package/dist/components/core/navbar/Navbar.types.js +1 -1
  227. package/dist/components/core/navbar/navbar-impl.css +10 -3
  228. package/dist/components/core/navbar/navbar-material.css +96 -0
  229. package/dist/components/core/navbar-item/NavbarItem.css +4 -4
  230. package/dist/components/core/navbar-menu/NavbarMenu.css +7 -5
  231. package/dist/components/core/navbar-menu/NavbarMenu.d.ts +1 -0
  232. package/dist/components/core/navbar-menu/NavbarMenu.js +1 -0
  233. package/dist/components/core/navbar-menu/navbar-menu-impl.css +7 -7
  234. package/dist/components/core/navbar-switcher/NavbarSwitcher.css +6 -5
  235. package/dist/components/core/navbar-switcher/NavbarSwitcher.d.ts +1 -0
  236. package/dist/components/core/navbar-switcher/NavbarSwitcher.js +2 -1
  237. package/dist/components/core/option/Option.css +1 -1
  238. package/dist/components/core/pagination/Pagination.css +1 -75
  239. package/dist/components/core/pagination/Pagination.d.ts +4 -4
  240. package/dist/components/core/pagination/Pagination.js +11 -51
  241. package/dist/components/core/pagination/Pagination.types.d.ts +5 -17
  242. package/dist/components/core/pagination/Pagination.types.js +1 -3
  243. package/dist/components/core/pagination-dots/PaginationDots.css +60 -0
  244. package/dist/components/core/pagination-dots/PaginationDots.d.ts +25 -0
  245. package/dist/components/core/pagination-dots/PaginationDots.js +104 -0
  246. package/dist/components/core/pagination-dots/PaginationDots.types.d.ts +38 -0
  247. package/dist/components/core/pagination-dots/PaginationDots.types.js +9 -0
  248. package/dist/components/core/removable-chip/RemovableChip.css +3 -3
  249. package/dist/components/core/search-input/SearchInput.css +1 -1
  250. package/dist/components/core/segmented-control/SegmentedControl.css +1 -1
  251. package/dist/components/core/segmented-control-item/SegmentedControlItem.css +1 -1
  252. package/dist/components/core/select/Select.css +2 -1
  253. package/dist/components/core/select-chip/SelectChip.css +14 -7
  254. package/dist/components/core/select-chip/SelectChip.d.ts +1 -1
  255. package/dist/components/core/select-chip/SelectChip.js +7 -4
  256. package/dist/components/core/select-chip/SelectChip.types.d.ts +7 -1
  257. package/dist/components/core/select-chip/SelectChip.types.js +1 -1
  258. package/dist/components/core/select-chip/select-chip-impl.css +14 -3
  259. package/dist/components/core/status-dot/StatusDot.css +1 -1
  260. package/dist/components/core/subdomain-navbar/SubdomainNavbar.css +3 -3
  261. package/dist/components/core/tab/Tab.css +1 -1
  262. package/dist/components/core/tab/Tab.types.d.ts +1 -1
  263. package/dist/components/core/tab-list/TabList.css +1 -1
  264. package/dist/components/core/table/table-impl.css +36 -14
  265. package/dist/components/core/table-header-cell/TableHeaderCell.css +5 -5
  266. package/dist/components/core/text-area/TextArea.css +1 -1
  267. package/dist/components/core/text-input/TextInput.css +1 -1
  268. package/dist/components/core/tooltip/Tooltip.css +1 -1
  269. package/dist/components/core/typography/Typography.css +5 -5
  270. package/dist/components/index.d.ts +35 -0
  271. package/dist/components/index.js +30 -0
  272. package/dist/components/logo-data.d.ts +24 -0
  273. package/dist/components/logo-data.js +11 -0
  274. package/dist/components/scroll-data.d.ts +64 -0
  275. package/dist/components/scroll-data.js +17 -0
  276. package/dist/components/testimonial-row-data.d.ts +51 -0
  277. package/dist/components/testimonial-row-data.js +11 -0
  278. package/dist/components/testimonial-slider-data.d.ts +26 -0
  279. package/dist/components/testimonial-slider-data.js +10 -0
  280. package/dist/marketing.css +385 -14
  281. package/package.json +1 -6
@@ -0,0 +1,167 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useId, useLayoutEffect, useRef, useState } from 'react';
4
+ import './FeatureScroll.css';
5
+ import './feature-scroll-impl.css';
6
+ import '../block-reveal.css';
7
+ import { arriving, useReveal } from '../use-reveal.js';
8
+ import { Link } from '../../core/link/Link.js';
9
+ import { featureScrollDefaults, featureScrollParts, } from './FeatureScroll.types.js';
10
+ /**
11
+ * Where the fold is, in pixels — the stylesheet's `48rem`, read against the page's own root font
12
+ * size, so the two agree when a page or a reader has changed it. See `showing` below.
13
+ */
14
+ const FOLD_REM = 48;
15
+ const foldsAt = () => FOLD_REM * (Number.parseFloat(getComputedStyle(document.documentElement).fontSize) || 16);
16
+ /**
17
+ * A section that gives a set of things one screen each, with the picture pinned beside the words.
18
+ *
19
+ * Two mechanisms, and which does what is the thing to understand here. Holding a picture in place
20
+ * while words move past it is `position: sticky`, and nothing in this file measures it. Knowing
21
+ * *which* picture belongs to the words a reader has reached is a question about where each title
22
+ * has got to, and no stylesheet can ask it — so that much is below, and only that much.
23
+ *
24
+ * The rule the reference design settles on: a stop's picture is the one showing once the *next*
25
+ * stop's title has risen to the line the pictures are pinned at. Read as "the last title at or
26
+ * above the line" plus one, clamped to the end. It means the square changes at the moment a reader
27
+ * reaches a new heading rather than at some fraction of the way through, which is the only moment
28
+ * they would notice either way.
29
+ *
30
+ * Throttled to one read per frame through `requestAnimationFrame`, on a passive listener, and the
31
+ * read itself is `getBoundingClientRect` on the titles — no layout is written back, so the loop
32
+ * cannot thrash. `useLayoutEffect` rather than `useEffect` so the first measurement lands before
33
+ * the browser paints; the effect otherwise ran after the first frame and the section opened on
34
+ * step one for that frame however far down the page a reader had already scrolled.
35
+ *
36
+ * It measures the section's own width rather than the window's, because the fold is a container
37
+ * query on the section and the two disagree wherever a frame stands in for a window. The fold is
38
+ * the stylesheet's own `48rem`, turned into pixels against the root font size each time it is
39
+ * asked: a fixed 768 agreed with it only while the root stayed at sixteen, and a page that set it
40
+ * smaller pinned the picture on the first stop for the width in between.
41
+ *
42
+ * The parts of a stop are in the order a phone reads them, which is the order they are written:
43
+ * name, picture, line, way onward. At full width each stop gives up its box — `display: contents`
44
+ * — and the grid places the words in one column and every picture in the other, stacked, so the
45
+ * change is a change in one square rather than one picture leaving and another arriving. The
46
+ * reference design ships two DOM trees to get that; one tree means a reader's assistive technology
47
+ * and anybody reading the markup meet each stop once.
48
+ */
49
+ export function FeatureScroll({ ground = featureScrollDefaults.ground, title, steps, mediaPosition = featureScrollDefaults.mediaPosition, }) {
50
+ const titleId = useId();
51
+ const [heading, revealed] = useReveal();
52
+ const root = useRef(null);
53
+ const titles = useRef([]);
54
+ const band = useRef(null);
55
+ const [showing, setShowing] = useState(0);
56
+ const count = steps.length;
57
+ useLayoutEffect(() => {
58
+ if (count === 0)
59
+ return;
60
+ /**
61
+ * The line the pictures are pinned at, read off the pinned band itself.
62
+ *
63
+ * Asked of the element rather than held here, because the offset is the stylesheet's — it is
64
+ * `size.bar-height`, so that a picture comes to rest under a navigation bar rather than behind
65
+ * one. A copy of the number in this file would be a second answer to drift from it, and the
66
+ * band already knows.
67
+ */
68
+ const line = () => {
69
+ const pinned = band.current;
70
+ if (pinned === null)
71
+ return 0;
72
+ const top = Number.parseFloat(getComputedStyle(pinned).insetBlockStart);
73
+ return Number.isFinite(top) ? top : 0;
74
+ };
75
+ let frame = 0;
76
+ const read = () => {
77
+ frame = 0;
78
+ const section = root.current;
79
+ if (section === null)
80
+ return;
81
+ // The fold is the section's own width, which is what the stylesheet asks. Below it every
82
+ // stop carries its own picture and there is nothing to choose between.
83
+ if (section.clientWidth < foldsAt())
84
+ return;
85
+ const at = line();
86
+ let reached = -1;
87
+ for (let i = 0; i < count; i += 1) {
88
+ const heading = titles.current[i];
89
+ if (heading === null || heading === undefined)
90
+ continue;
91
+ if (heading.getBoundingClientRect().top <= at)
92
+ reached = i;
93
+ }
94
+ const next = Math.min(count - 1, reached + 1);
95
+ setShowing((held) => (held === next ? held : next));
96
+ };
97
+ const schedule = () => {
98
+ if (frame !== 0)
99
+ return;
100
+ frame = window.requestAnimationFrame(read);
101
+ };
102
+ /**
103
+ * Heard on the way down, because a scroll does not come back up.
104
+ *
105
+ * A `scroll` event fired at an element does not bubble, so a listener on the window hears only
106
+ * the window's own scrolling. Wherever the section sits inside something that scrolls — a
107
+ * pane, a dialog, the workbench's own stage — the pictures simply never changed, while the
108
+ * same section on a page of its own worked perfectly. That is a difference no reader should be
109
+ * able to find, and the reference design has it too: it listens on the window alone.
110
+ *
111
+ * The capture phase is what catches them. Every scroll passes through the window on its way to
112
+ * its target whether or not it comes back, so one listener here answers for every scroller on
113
+ * the page, and the read itself is unchanged — `getBoundingClientRect` is measured against the
114
+ * viewport, which is the right frame however the section came to move within it.
115
+ *
116
+ * The flag has to be given to the removal as well: a capturing listener and a bubbling one are
117
+ * two different registrations, and taking off the wrong one leaves the other listening.
118
+ */
119
+ const heard = { passive: true, capture: true };
120
+ read();
121
+ window.addEventListener('scroll', schedule, heard);
122
+ window.addEventListener('resize', schedule, { passive: true });
123
+ return () => {
124
+ window.removeEventListener('scroll', schedule, { capture: true });
125
+ window.removeEventListener('resize', schedule);
126
+ if (frame !== 0)
127
+ window.cancelAnimationFrame(frame);
128
+ };
129
+ }, [count]);
130
+ return (_jsx("section", { className: featureScrollParts.root, "data-ground": ground, "data-media-position": mediaPosition, "aria-labelledby": titleId, ref: root, children: _jsxs("div", { className: featureScrollParts.inner, children: [_jsx("h2", { className: featureScrollParts.heading, ref: heading, "data-revealed": revealed, ...arriving(0), id: titleId, children: title }), _jsx("div", { className: featureScrollParts.steps, style: { '--dds-featurescroll-stops': count }, children: steps.map((step, index) => (
131
+ /* Keyed by the title. An index would key the set by position, so filtering a stop out
132
+ would renumber every stop under it and React would reconcile the wrong ones — and
133
+ these are the content, so two the same would be a duplicate stop rather than a key
134
+ collision worth tolerating. */
135
+ _jsx(Stop, { step: step, showing: index === showing, onTitle: (held) => {
136
+ titles.current[index] = held;
137
+ }, onBand: index === 0 ? band : undefined }, step.title))) })] }) }));
138
+ }
139
+ /**
140
+ * One stop, which arrives on its own below the fold and not at all above it.
141
+ *
142
+ * A component of its own for one reason: it holds a `useReveal` each, and a hook cannot be called
143
+ * inside a loop. Private to this file, because a stop is not a thing a page composes — the section
144
+ * takes its set as data, and this is how one record is drawn.
145
+ *
146
+ * Two arrivals rather than one, and the gap between them is the layout's rather than a number. The
147
+ * words and the picture each watch for themselves, and on a phone the picture sits under the line
148
+ * it belongs to — so it comes up a beat later because it reaches the viewport a beat later. A delay
149
+ * would have been a guess about how far apart they are; this is the distance itself.
150
+ *
151
+ * Above the fold neither moves, and the stylesheet says so. What moves there is the picture, which
152
+ * is pinned beside the words and exchanged as a reader passes each title — the section's own
153
+ * motion, and enough of it. It is also the one place a transform cannot go: the band is
154
+ * `position: sticky`, and the crossfade owns its opacity.
155
+ *
156
+ * Below the fold there is no pinning and nothing is exchanged: the stops are ordinary blocks in a
157
+ * column, so without this the only thing on a phone that moved was the section's heading — one
158
+ * flourish at the top and six screens of nothing after it.
159
+ */
160
+ function Stop({ step, showing, onTitle, onBand, }) {
161
+ const [words, wordsHere] = useReveal();
162
+ const [picture, pictureHere] = useReveal();
163
+ return (_jsxs("div", { className: featureScrollParts.step, children: [_jsxs("div", { className: featureScrollParts.content, "data-revealed": wordsHere, children: [_jsx("h3", { className: featureScrollParts.title, ...arriving(0), ref: (held) => {
164
+ words(held);
165
+ onTitle(held);
166
+ }, children: step.title }), _jsx("p", { className: featureScrollParts.description, ...arriving(1), children: step.description }), step.href !== undefined && step.linkLabel !== undefined ? (_jsx("div", { className: featureScrollParts.link, ...arriving(2), children: _jsx(Link, { href: step.href, children: step.linkLabel }) })) : null] }), _jsx("div", { className: featureScrollParts.media, "data-showing": showing, "data-revealed": pictureHere, "aria-hidden": "true", ref: onBand, children: _jsx("img", { className: featureScrollParts.picture, src: step.image, alt: "", ...arriving(0), ref: picture }) })] }));
167
+ }
@@ -0,0 +1,40 @@
1
+ import type { FeatureScrollStep } from '../../scroll-data.js';
2
+ export interface FeatureScrollOwnProps {
3
+ /**
4
+ * `page` — The default, and the page's own white. A section that has nothing to separate itself from what is above it — most of them — and the ground a reader is already on
5
+ * `subtle` — A quiet step off the page, for a band that wants to read as its own section without changing the page's voice. The one to reach for first when two sections need telling apart. Painted as the band's tint laid over the page's own ground, so it is the same grey whatever is behind the section — in dark mode the tint alone took on whatever it was set against, and on a light surface it was light ink on white.
6
+ * `strong` — `color.bg.brand-strong`, the dark band. It stops the page rather than dividing it, so it is for a section a reader should arrive at — and one per page is usually one more than enough
7
+ */
8
+ ground?: 'page' | 'subtle' | 'strong';
9
+ /**
10
+ * `end` — The default. The words are read first, which is the order a reader’s eye takes a two-up row in
11
+ * `start` — The pictures lead, for a section following one that put its own the other way — two scrolls facing the same way read as one very long column
12
+ */
13
+ mediaPosition?: 'end' | 'start';
14
+ /** Always. What the set is a set of, in the fewest words that say it — 'All your markets in one place'. It is the only thing a reader sees before committing to several screens of scrolling, so it has to say what is coming */
15
+ title: string;
16
+ /** Always. The stops, in the order a reader meets them. Three at least — the section is built on each one having a screen, and a pair is worth one screen with both on it.
17
+
18
+ Every stop is the same shape, which is what makes the set a set: a name, a line, a picture, and a way onward where there is one. */
19
+ steps: FeatureScrollStep[];
20
+ }
21
+ export type FeatureScrollProps = FeatureScrollOwnProps;
22
+ /** The class each part carries. Apply these — do not retype them. */
23
+ export declare const featureScrollParts: {
24
+ readonly root: "dds-featurescroll";
25
+ readonly inner: "dds-featurescroll__inner";
26
+ readonly heading: "dds-featurescroll__heading";
27
+ readonly steps: "dds-featurescroll__steps";
28
+ readonly step: "dds-featurescroll__step";
29
+ readonly content: "dds-featurescroll__content";
30
+ readonly title: "dds-featurescroll__title";
31
+ readonly description: "dds-featurescroll__description";
32
+ readonly link: "dds-featurescroll__link";
33
+ readonly media: "dds-featurescroll__media";
34
+ readonly picture: "dds-featurescroll__picture";
35
+ };
36
+ /** Generated so defaults cannot drift from the contract. */
37
+ export declare const featureScrollDefaults: {
38
+ readonly ground: "page";
39
+ readonly mediaPosition: "end";
40
+ };
@@ -0,0 +1,17 @@
1
+ // GENERATED from feature-scroll.contract.json. Do not edit.
2
+ /** The class each part carries. Apply these — do not retype them. */
3
+ export const featureScrollParts = {
4
+ root: 'dds-featurescroll',
5
+ inner: 'dds-featurescroll__inner',
6
+ heading: 'dds-featurescroll__heading',
7
+ steps: 'dds-featurescroll__steps',
8
+ step: 'dds-featurescroll__step',
9
+ content: 'dds-featurescroll__content',
10
+ title: 'dds-featurescroll__title',
11
+ description: 'dds-featurescroll__description',
12
+ link: 'dds-featurescroll__link',
13
+ media: 'dds-featurescroll__media',
14
+ picture: 'dds-featurescroll__picture',
15
+ };
16
+ /** Generated so defaults cannot drift from the contract. */
17
+ export const featureScrollDefaults = { ground: 'page', mediaPosition: 'end' };
@@ -0,0 +1,292 @@
1
+ /*
2
+ * Scoped to this package, like the generated sheet beside it.
3
+ *
4
+ * `:where([data-dds-context="marketing"])` confines these rules without contributing specificity,
5
+ * so each is decided against the generated rule on its own terms.
6
+ *
7
+ * Every query here is `@container` rather than `@media`. The section declares `container-type`,
8
+ * so the set folds on the band's own width — which is what the rest of this package does, and
9
+ * what makes the workbench's viewport control mean something: it resizes the stage rather than
10
+ * the browser, and a section asking the window would hold its desktop layout at every setting.
11
+ *
12
+ * `48rem` longhand throughout because `var()` is not permitted in a container query condition,
13
+ * and it is the width the rest of this package folds at.
14
+ */
15
+
16
+ /*
17
+ * One column below the fold, and the picture between the name and the line about it.
18
+ *
19
+ * The row direction is the contract's to say — it varies by a styling axis, which is what
20
+ * `structure[]` is for, and the generated sheet carries both values. What is left here is the
21
+ * fold, which a binding cannot express: `structure` has no width to attach a rule to.
22
+ *
23
+ * Written with the `data-media-position` attribute so it carries the weight of the generated rules
24
+ * keyed on it — `.dds-featurescroll[data-media-position="end"] .dds-featurescroll__content` and its
25
+ * kin, a class, an attribute and a class — and wins over anything at that weight by loading later.
26
+ */
27
+ @container (max-width: 47.9375rem) {
28
+ :where([data-dds-context="marketing"])
29
+ .dds-featurescroll[data-media-position]
30
+ .dds-featurescroll__step {
31
+ flex-direction: column;
32
+ }
33
+
34
+ /*
35
+ * The text column stops being a box so the picture can sit inside its order.
36
+ *
37
+ * A phone has one column, and the picture is what tells a reader which of six stops they have
38
+ * reached — put after the words it arrives once they have already decided whether to read them.
39
+ * The name, the picture, then the line and the way onward is the reference design's own order,
40
+ * and the only other way to get it is a second copy of the markup for the second layout.
41
+ *
42
+ * `display: contents` takes the box out and leaves its children to the row above, so the four
43
+ * parts become four flex items and `order` can interleave them. The children keep their own
44
+ * styling; what is lost is anything the box itself drew, which here is one `row-gap` that is
45
+ * `space.gap.none` at every width anyway.
46
+ */
47
+ :where([data-dds-context="marketing"]) .dds-featurescroll .dds-featurescroll__content {
48
+ display: contents;
49
+ }
50
+
51
+ :where([data-dds-context="marketing"]) .dds-featurescroll .dds-featurescroll__title {
52
+ order: 1;
53
+ }
54
+
55
+ /*
56
+ * The band hugs its picture and the picture runs the column.
57
+ *
58
+ * There is one column below the fold, nothing to pin against, and no band to be the height of —
59
+ * so the air the band holds the picture off its edges by would be a hole in the stack, where the
60
+ * gap between the parts already does that job.
61
+ */
62
+ :where([data-dds-context="marketing"]) .dds-featurescroll .dds-featurescroll__media {
63
+ order: 2;
64
+ padding-block: 0;
65
+ }
66
+
67
+ :where([data-dds-context="marketing"]) .dds-featurescroll .dds-featurescroll__picture {
68
+ inline-size: 100%;
69
+ }
70
+
71
+ :where([data-dds-context="marketing"]) .dds-featurescroll .dds-featurescroll__description {
72
+ order: 3;
73
+ }
74
+
75
+ :where([data-dds-context="marketing"]) .dds-featurescroll .dds-featurescroll__link {
76
+ order: 4;
77
+ }
78
+
79
+ /*
80
+ * The steps between the parts come off the parts and onto the stack.
81
+ *
82
+ * At full width the column carries them on each part's own `margin-block-end`, because
83
+ * title-to-description and description-to-what-follows are two different roles. Below the fold
84
+ * there is a fifth thing in the order — the picture — and a margin belonging to the title would
85
+ * land under the picture instead of over it. So the row's own gap governs here, and the margins
86
+ * that would add to it are cleared.
87
+ */
88
+ :where([data-dds-context="marketing"]) .dds-featurescroll .dds-featurescroll__title,
89
+ :where([data-dds-context="marketing"]) .dds-featurescroll .dds-featurescroll__description {
90
+ margin-block-end: 0;
91
+ }
92
+ }
93
+
94
+ /*
95
+ * A screen each at full width, the picture pinned for the length of it, and the set closed up.
96
+ *
97
+ * The height is the whole idea rather than a decoration on it: a reader arrives at one stop at a
98
+ * time and scrolls on to the next, which is what a set too long to compare needs and what a grid
99
+ * of six cards cannot give. `100cqh` rather than `100svh` for the reason LandingHero gives — with
100
+ * no ancestor declaring size containment the two are the same number, and the container unit is
101
+ * what lets a frame standing in for a window say otherwise. The section's own `container-type` is
102
+ * `inline-size`, which answers no question about height, so the unit passes straight through it.
103
+ *
104
+ * `position: sticky` and nothing else. The reference design measures scroll position in React,
105
+ * keeps an active index and crossfades between a stack of absolutely-placed pictures; the dissolve
106
+ * is what that buys and three listeners on a marketing page is what it costs. The browser already
107
+ * knows where the box is.
108
+ *
109
+ * It pins at `size.bar-height`, which is what a collapsed navigation bar takes — so the picture
110
+ * comes to rest just under the bar rather than behind it. On a page with no bar the offset is a
111
+ * margin, which is what a picture at the very top of the window wants anyway.
112
+ *
113
+ * The square is capped at the band it has to fit inside: the bar's height above it and the same
114
+ * again below, so a short window compacts the picture instead of running it past the fold. Both
115
+ * axes take the same cap because the shape is the point — capping the height alone would clamp a
116
+ * square into a letterbox, and the picture inside is cropped to fill whatever it is given.
117
+ *
118
+ * The gap between stops goes to nothing here. Below the fold they are ordinary blocks and want air
119
+ * between them, which is the gap the contract binds; at full width a picture is released at the
120
+ * moment the next arrives, and a gap there opens a stretch of scrolling with no picture pinned at
121
+ * all — which reads as the section having ended and started again.
122
+ */
123
+ @container (min-width: 48rem) {
124
+ :where([data-dds-context="marketing"]) .dds-featurescroll .dds-featurescroll__heading {
125
+ /* `margin-inline: auto` as well as the alignment, because the heading is held to
126
+ `size.measure.heading` — centred text inside a box still pinned to the start edge is a
127
+ heading that looks centred only while it is short enough not to reach the cap. */
128
+ margin-inline: auto;
129
+ text-align: center;
130
+ }
131
+
132
+ /*
133
+ * Two columns, and every picture in one of them.
134
+ *
135
+ * The rows are named rather than numbered so the contract can place the two halves: `structure`
136
+ * may hold a keyword and may not hold a 2, on the argument that a ratio between siblings is a
137
+ * layout decision — and the names say which half is which where a number would only say where.
138
+ *
139
+ * The row count arrives as a custom property because `grid-row: 1 / -1` cannot reach the end of
140
+ * an implicit grid: with no explicit rows, `-1` is the end of the explicit one, which is the
141
+ * first row. Stated, the pictures span every row and land on top of one another.
142
+ */
143
+ :where([data-dds-context="marketing"]) .dds-featurescroll .dds-featurescroll__steps {
144
+ display: grid;
145
+ grid-template-columns: [words] 1fr [picture] 1fr;
146
+ grid-template-rows: repeat(var(--dds-featurescroll-stops, 1), auto);
147
+ row-gap: 0;
148
+ }
149
+
150
+ /*
151
+ * A stop gives up its box, so the grid can place what is inside it.
152
+ *
153
+ * This is what lets one tree serve both layouts. A picture inside its own stop can only be
154
+ * pinned within it — released a screen later, arriving from below, travelling rather than
155
+ * changing. Placed by the grid instead, every picture is in one column across every row, so
156
+ * they overlap and one can be exchanged for another in the same square.
157
+ *
158
+ * The stop stays in the markup and keeps its four parts together; what it gives up is a box
159
+ * nothing needed at this width.
160
+ */
161
+ :where([data-dds-context="marketing"]) .dds-featurescroll .dds-featurescroll__step {
162
+ display: contents;
163
+ }
164
+
165
+ /*
166
+ * Nothing arrives on its own up here, whatever it was marked.
167
+ *
168
+ * Below the fold the words and then the picture come up as a reader reaches them, because there
169
+ * is nothing else moving in a column of stacked blocks. Above it the picture is pinned beside
170
+ * the words and exchanged as each title is passed — that is the section's motion, and a stop
171
+ * sliding in under it would be two arrivals for one thing.
172
+ *
173
+ * There is a harder reason for the picture. It sits inside a `position: sticky` band, and a
174
+ * transform on it is a transform the pinning has to survive. `none` rather than a zero value, so
175
+ * no transform exists at all.
176
+ *
177
+ * The attribute stays on both elements and is simply not read here, which is what keeps the
178
+ * component free of a width it cannot measure: the fold is a container query, and no observer
179
+ * can see one.
180
+ */
181
+ :where([data-dds-context="marketing"]) .dds-featurescroll [data-revealed] [data-reveal-at] {
182
+ opacity: 1;
183
+ translate: none;
184
+ }
185
+
186
+ /*
187
+ * The words take a screen each, which is the rhythm the whole section is built on: a reader
188
+ * arrives at one stop at a time and scrolls on to the next. Centred in it so they sit level with
189
+ * the picture rather than at the head of a screen-tall row.
190
+ */
191
+ :where([data-dds-context="marketing"]) .dds-featurescroll .dds-featurescroll__content {
192
+ justify-content: center;
193
+ min-block-size: 100cqh;
194
+ }
195
+
196
+ /*
197
+ * Every band pinned at one line, and all of them stacked.
198
+ *
199
+ * `grid-row` spans the whole set, so the containing block a sticky box measures its room against
200
+ * is the entire list rather than one stop — which is what keeps a picture pinned from the first
201
+ * stop to the last instead of being released at each boundary.
202
+ *
203
+ * `align-self: start` because the band is a screen tall and its area is the whole list; stretched
204
+ * it would be as tall as the section and there would be nothing to pin.
205
+ *
206
+ * `100cqh` rather than `100svh` for the reason LandingHero gives: with no ancestor declaring size
207
+ * containment the two are the same number, and the container unit is what lets a frame standing
208
+ * in for a window say otherwise.
209
+ */
210
+ :where([data-dds-context="marketing"]) .dds-featurescroll .dds-featurescroll__media {
211
+ align-self: start;
212
+ block-size: calc(100cqh - var(--size-bar-height));
213
+ grid-row: 1 / -1;
214
+ inset-block-start: var(--size-bar-height);
215
+ opacity: 0;
216
+ position: sticky;
217
+ }
218
+
219
+ /*
220
+ * And one of them is shown.
221
+ *
222
+ * The only thing the component's own listener writes. Which stop's picture belongs to the words
223
+ * a reader has reached is a question about where each title has got to, and no query asks it —
224
+ * everything else here, the pinning and the dissolve, is this stylesheet's.
225
+ *
226
+ * The duration and the curve are bound on the part, so the generated sheet writes the
227
+ * reduced-motion block that turns this into a cut.
228
+ */
229
+ :where([data-dds-context="marketing"])
230
+ .dds-featurescroll
231
+ .dds-featurescroll__media[data-showing="true"] {
232
+ opacity: 1;
233
+ /*
234
+ * On top, and that is what makes this a dissolve rather than a dip.
235
+ *
236
+ * The arriving picture fades in over whatever is beneath it, so at every moment of the change
237
+ * the square is covered by something opaque. Fading both at once instead leaves them near half
238
+ * way together, the section's own ground shows through the pair, and the square brightens and
239
+ * darkens — a pulse, from nothing but two opacities crossing.
240
+ *
241
+ * It has to be the shown one rather than the later one in the document, because a reader
242
+ * scrolls back as well as forward. Going up, the arriving picture is earlier in the markup and
243
+ * would fade in underneath the one it is replacing: invisible until that one left, which is a
244
+ * cut with a pause in front of it.
245
+ *
246
+ * A bare integer, which is the one kind of value this system keeps out of its tokens: a
247
+ * stacking order inside a component is not a decision any brand or mode has an opinion about.
248
+ */
249
+ z-index: 1;
250
+ /* No wait on the way in. The pair of delays is the whole mechanism — see the rule below. */
251
+ transition-delay: 0s;
252
+ }
253
+
254
+ /*
255
+ * The one leaving holds until it has been covered, then goes.
256
+ *
257
+ * This is the other half of the dissolve. The arriving picture takes the whole duration to reach
258
+ * full opacity, and for all of it the one underneath stays exactly as it was — so there is never
259
+ * a frame where the square is anything less than a complete picture. Only once it is hidden
260
+ * behind the new one does it drop, which costs nothing to look at because nobody can see it.
261
+ *
262
+ * The delay is the arriving picture's own duration, read from the same role, so the two cannot
263
+ * drift apart into a gap or an overlap.
264
+ *
265
+ * There is no scale here any more. It read as a pulse, which is what it was: a square this large
266
+ * carries a change of picture on its own, and a sixth of a second of zoom on top of it is the
267
+ * section drawing attention to its own machinery.
268
+ */
269
+ :where([data-dds-context="marketing"]) .dds-featurescroll .dds-featurescroll__media {
270
+ transition-delay: var(--motion-duration-media-hover);
271
+ /* Named here rather than in the contract because `staticCss` takes keywords, and this is the
272
+ one property the pair of rules is about. The duration and the curve stay bound there, which
273
+ is what the generated reduced-motion block is written from — so a reader who asked for less
274
+ still gets a cut. */
275
+ transition-property: opacity;
276
+ }
277
+
278
+ /*
279
+ * The largest square the band can hold.
280
+ *
281
+ * Both bounds in one measure, because a square capped on one axis alone is not a square: giving
282
+ * it a `max-block-size` clamps the height and leaves the width, and the browser hands back a
283
+ * flattened picture. `min()` picks whichever runs out first — the half it sits in, or the band's
284
+ * own inner height, which is the band less the air it holds the picture off its edges by.
285
+ */
286
+ :where([data-dds-context="marketing"]) .dds-featurescroll .dds-featurescroll__picture {
287
+ inline-size: min(
288
+ 100%,
289
+ calc(100cqh - var(--size-bar-height) - var(--space-gap-lg) * 2)
290
+ );
291
+ }
292
+ }
@@ -0,0 +1,95 @@
1
+ /* GENERATED from featuresplit.contract.json. Do not edit. */
2
+
3
+ :where([data-dds-context="marketing"]) .dds-featuresplit {
4
+ box-sizing: border-box;
5
+ container-type: inline-size;
6
+ display: flex;
7
+ inline-size: 100%;
8
+ justify-content: center;
9
+ }
10
+
11
+ :where([data-dds-context="marketing"]) .dds-featuresplit .dds-featuresplit__inner {
12
+ box-sizing: border-box;
13
+ display: flex;
14
+ flex-direction: column;
15
+ inline-size: 100%;
16
+ min-inline-size: 0;
17
+ max-inline-size: var(--size-content-max);
18
+ padding-block: var(--space-section-default);
19
+ padding-inline: var(--space-inset-page);
20
+ }
21
+
22
+ :where([data-dds-context="marketing"]) .dds-featuresplit .dds-featuresplit__split {
23
+ align-items: stretch;
24
+ display: flex;
25
+ min-inline-size: 0;
26
+ column-gap: var(--space-gap-3xl);
27
+ row-gap: var(--space-gap-2xl);
28
+ }
29
+
30
+ :where([data-dds-context="marketing"]) .dds-featuresplit .dds-featuresplit__content {
31
+ display: flex;
32
+ flex-direction: column;
33
+ min-inline-size: 0;
34
+ row-gap: var(--space-gap-none);
35
+ }
36
+
37
+ :where([data-dds-context="marketing"]) .dds-featuresplit .dds-featuresplit__title {
38
+ margin-block: 0;
39
+ text-wrap: balance;
40
+ font-family: var(--font-h2-family);
41
+ font-size: var(--font-h2-size);
42
+ font-weight: var(--font-h2-weight);
43
+ letter-spacing: var(--font-h2-letter-spacing);
44
+ line-height: var(--font-h2-line-height);
45
+ margin-block-end: var(--space-heading-title-description);
46
+ }
47
+
48
+ :where([data-dds-context="marketing"]) .dds-featuresplit .dds-featuresplit__description {
49
+ margin-block: 0;
50
+ font-family: var(--font-body-md-family);
51
+ font-size: var(--font-body-md-size);
52
+ font-weight: var(--font-body-md-weight);
53
+ letter-spacing: var(--font-body-md-letter-spacing);
54
+ line-height: var(--font-body-md-line-height);
55
+ margin-block-end: var(--space-heading-description-actions);
56
+ max-inline-size: var(--size-measure-description);
57
+ }
58
+
59
+ :where([data-dds-context="marketing"]) .dds-featuresplit .dds-featuresplit__link {
60
+ display: flex;
61
+ margin-block-start: var(--space-gap-none);
62
+ }
63
+
64
+ :where([data-dds-context="marketing"]) .dds-featuresplit .dds-featuresplit__media {
65
+ min-inline-size: 0;
66
+ overflow: hidden;
67
+ position: relative;
68
+ aspect-ratio: var(--aspect-square);
69
+ border-radius: var(--radius-container);
70
+ }
71
+
72
+ :where([data-dds-context="marketing"]) .dds-featuresplit[data-media-position="end"] .dds-featuresplit__split {
73
+ flex-direction: row;
74
+ }
75
+
76
+ :where([data-dds-context="marketing"]) .dds-featuresplit[data-media-position="start"] .dds-featuresplit__split {
77
+ flex-direction: row-reverse;
78
+ }
79
+
80
+ :where([data-dds-context="marketing"]) .dds-featuresplit[data-ground="page"] {
81
+ background-color: var(--color-bg-page);
82
+ color: var(--color-fg-default);
83
+ }
84
+
85
+ :where([data-dds-context="marketing"]) .dds-featuresplit[data-ground="strong"] {
86
+ background-color: var(--color-bg-brand-strong);
87
+ color: var(--color-fg-on-brand-strong);
88
+ }
89
+
90
+ :where([data-dds-context="marketing"]) .dds-featuresplit[data-ground="subtle"] {
91
+ background-color: var(--color-bg-page);
92
+ background-image: linear-gradient(var(--color-bg-band-subtle), var(--color-bg-band-subtle));
93
+ color: var(--color-fg-default);
94
+ }
95
+
@@ -0,0 +1,30 @@
1
+ import './FeatureSplit.css';
2
+ import './feature-split-impl.css';
3
+ import '../block-reveal.css';
4
+ import { type FeatureSplitProps } from './FeatureSplit.types.js';
5
+ export type { FeatureSplitProps };
6
+ /**
7
+ * A section putting one claim, a line explaining it and a way onward beside a single picture.
8
+ *
9
+ * The plainest block this package has, and that is what it is for: a page that has said what
10
+ * something is needs somewhere to say one more thing about it without opening a list, a grid or a
11
+ * set of cards.
12
+ *
13
+ * A split rather than a stack, because the picture is the other half of the argument — the same
14
+ * words over a full-width photograph read as a caption, and beside it they read as a claim with
15
+ * evidence.
16
+ *
17
+ * The steps inside the column are carried by each part's own `margin-block-end` rather than by a
18
+ * gap on the column, because title-to-description and description-to-what-follows are two
19
+ * different roles that a single gap could only ever be one of. `content` binds `space.gap.none`
20
+ * to say the absence is deliberate rather than forgotten.
21
+ *
22
+ * The source template carried `showDescription` and `showLink` beside the values themselves. Both
23
+ * are gone: absence is the switch, which is the rule Footer set and `given` exists for. A flag
24
+ * saying whether to draw a value the caller already decided not to pass is two ways to say one
25
+ * thing, and they disagree the first time somebody sets only one of them.
26
+ *
27
+ * The title carries the section's accessible name through `aria-labelledby`. A `<section>` is only
28
+ * a region landmark once it has a name.
29
+ */
30
+ export declare function FeatureSplit({ ground, mediaPosition, title, description, link, media, }: FeatureSplitProps): import("react").JSX.Element;