@deriv-com/dds-marketing 1.1.1 → 1.1.3

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 (256) hide show
  1. package/.llm/components/accordion.md +4 -0
  2. package/.llm/components/accordionitem.md +4 -0
  3. package/.llm/components/aspectratio.md +4 -0
  4. package/.llm/components/awards.md +4 -0
  5. package/.llm/components/background.md +4 -0
  6. package/.llm/components/badge.md +7 -3
  7. package/.llm/components/bottomsheet.md +7 -3
  8. package/.llm/components/breadcrumb.md +4 -0
  9. package/.llm/components/breadcrumbitem.md +4 -0
  10. package/.llm/components/button.md +4 -0
  11. package/.llm/components/buttonicon.md +10 -6
  12. package/.llm/components/card.md +5 -1
  13. package/.llm/components/cardbody.md +5 -1
  14. package/.llm/components/cardbutton.md +4 -0
  15. package/.llm/components/checkbox.md +1 -1
  16. package/.llm/components/comparisoncards.md +4 -0
  17. package/.llm/components/comparisontable.md +4 -0
  18. package/.llm/components/ctabanner.md +4 -0
  19. package/.llm/components/ctabannerslider.md +4 -0
  20. package/.llm/components/datatable.md +4 -0
  21. package/.llm/components/divider.md +4 -0
  22. package/.llm/components/downloadbanner.md +4 -0
  23. package/.llm/components/dropdownmenu.md +9 -5
  24. package/.llm/components/estimator.md +336 -0
  25. package/.llm/components/faqaccordion.md +4 -0
  26. package/.llm/components/featurebullets.md +4 -0
  27. package/.llm/components/featurecards.md +5 -1
  28. package/.llm/components/featurecardscarousel.md +4 -0
  29. package/.llm/components/featurescroll.md +5 -1
  30. package/.llm/components/featuresplit.md +4 -0
  31. package/.llm/components/featurestack.md +4 -0
  32. package/.llm/components/filterchip.md +5 -1
  33. package/.llm/components/footer.md +4 -0
  34. package/.llm/components/footergroup.md +4 -0
  35. package/.llm/components/grid.md +4 -0
  36. package/.llm/components/icon.md +9 -5
  37. package/.llm/components/illustrationcard.md +4 -0
  38. package/.llm/components/illustrationscene.md +4 -0
  39. package/.llm/components/imagebanner.md +6 -2
  40. package/.llm/components/landinghero.md +4 -0
  41. package/.llm/components/link.md +5 -1
  42. package/.llm/components/logocolumns.md +4 -0
  43. package/.llm/components/logorows.md +4 -0
  44. package/.llm/components/menuitem.md +6 -2
  45. package/.llm/components/navbar.md +5 -1
  46. package/.llm/components/navbaritem.md +4 -0
  47. package/.llm/components/navbarmenu.md +4 -0
  48. package/.llm/components/navbarmenugroup.md +4 -0
  49. package/.llm/components/navbarpanel.md +4 -0
  50. package/.llm/components/navbarswitcher.md +10 -6
  51. package/.llm/components/navbarswitcheroption.md +4 -0
  52. package/.llm/components/option.md +5 -1
  53. package/.llm/components/pageheader.md +4 -0
  54. package/.llm/components/pagination.md +4 -0
  55. package/.llm/components/paginationdots.md +4 -0
  56. package/.llm/components/producthero.md +4 -0
  57. package/.llm/components/removablechip.md +5 -1
  58. package/.llm/components/resourcebanner.md +4 -0
  59. package/.llm/components/row.md +4 -0
  60. package/.llm/components/scenescroll.md +4 -0
  61. package/.llm/components/scroller.md +4 -0
  62. package/.llm/components/searchinput.md +4 -0
  63. package/.llm/components/segmentedcontrol.md +5 -1
  64. package/.llm/components/segmentedcontrolitem.md +6 -2
  65. package/.llm/components/select.md +4 -0
  66. package/.llm/components/selectchip.md +4 -0
  67. package/.llm/components/spinner.md +4 -0
  68. package/.llm/components/stack.md +4 -0
  69. package/.llm/components/stats.md +4 -0
  70. package/.llm/components/statusdot.md +4 -0
  71. package/.llm/components/steps.md +18 -6
  72. package/.llm/components/stickycards.md +4 -0
  73. package/.llm/components/subdomainnavbar.md +5 -1
  74. package/.llm/components/tab.md +6 -2
  75. package/.llm/components/table.md +4 -0
  76. package/.llm/components/tablebody.md +4 -0
  77. package/.llm/components/tablecell.md +4 -0
  78. package/.llm/components/tablehead.md +4 -0
  79. package/.llm/components/tableheadercell.md +4 -0
  80. package/.llm/components/tablerow.md +4 -0
  81. package/.llm/components/tablist.md +4 -0
  82. package/.llm/components/teamgrid.md +4 -0
  83. package/.llm/components/testimonialrow.md +4 -0
  84. package/.llm/components/testimonialslider.md +4 -0
  85. package/.llm/components/textarea.md +4 -0
  86. package/.llm/components/textinput.md +5 -1
  87. package/.llm/components/textscroll.md +4 -0
  88. package/.llm/components/tiers.md +4 -0
  89. package/.llm/components/typography.md +4 -0
  90. package/.llm/digest/accordion.md +1 -1
  91. package/.llm/digest/accordionitem.md +1 -1
  92. package/.llm/digest/aspectratio.md +1 -1
  93. package/.llm/digest/awards.md +1 -1
  94. package/.llm/digest/background.md +1 -1
  95. package/.llm/digest/badge.md +1 -1
  96. package/.llm/digest/bottomsheet.md +1 -1
  97. package/.llm/digest/breadcrumb.md +1 -1
  98. package/.llm/digest/breadcrumbitem.md +1 -1
  99. package/.llm/digest/button.md +1 -1
  100. package/.llm/digest/buttonicon.md +1 -1
  101. package/.llm/digest/card.md +1 -1
  102. package/.llm/digest/cardbody.md +1 -1
  103. package/.llm/digest/cardbutton.md +1 -1
  104. package/.llm/digest/checkbox.md +1 -1
  105. package/.llm/digest/comparisoncards.md +1 -1
  106. package/.llm/digest/comparisontable.md +1 -1
  107. package/.llm/digest/ctabanner.md +1 -1
  108. package/.llm/digest/ctabannerslider.md +1 -1
  109. package/.llm/digest/datatable.md +1 -1
  110. package/.llm/digest/divider.md +1 -1
  111. package/.llm/digest/downloadbanner.md +1 -1
  112. package/.llm/digest/dropdownmenu.md +1 -1
  113. package/.llm/digest/estimator.md +66 -0
  114. package/.llm/digest/faqaccordion.md +1 -1
  115. package/.llm/digest/featurebullets.md +1 -1
  116. package/.llm/digest/featurecards.md +1 -1
  117. package/.llm/digest/featurecardscarousel.md +1 -1
  118. package/.llm/digest/featurescroll.md +1 -1
  119. package/.llm/digest/featuresplit.md +1 -1
  120. package/.llm/digest/featurestack.md +1 -1
  121. package/.llm/digest/filterchip.md +1 -1
  122. package/.llm/digest/footer.md +1 -1
  123. package/.llm/digest/footergroup.md +1 -1
  124. package/.llm/digest/grid.md +1 -1
  125. package/.llm/digest/icon.md +1 -1
  126. package/.llm/digest/illustrationcard.md +1 -1
  127. package/.llm/digest/illustrationscene.md +1 -1
  128. package/.llm/digest/imagebanner.md +2 -2
  129. package/.llm/digest/landinghero.md +1 -1
  130. package/.llm/digest/link.md +1 -1
  131. package/.llm/digest/logocolumns.md +1 -1
  132. package/.llm/digest/logorows.md +1 -1
  133. package/.llm/digest/menuitem.md +1 -1
  134. package/.llm/digest/navbar.md +1 -1
  135. package/.llm/digest/navbaritem.md +1 -1
  136. package/.llm/digest/navbarmenu.md +1 -1
  137. package/.llm/digest/navbarmenugroup.md +1 -1
  138. package/.llm/digest/navbarpanel.md +1 -1
  139. package/.llm/digest/navbarswitcher.md +1 -1
  140. package/.llm/digest/navbarswitcheroption.md +1 -1
  141. package/.llm/digest/option.md +1 -1
  142. package/.llm/digest/pageheader.md +1 -1
  143. package/.llm/digest/pagination.md +1 -1
  144. package/.llm/digest/paginationdots.md +1 -1
  145. package/.llm/digest/producthero.md +1 -1
  146. package/.llm/digest/removablechip.md +1 -1
  147. package/.llm/digest/resourcebanner.md +1 -1
  148. package/.llm/digest/row.md +1 -1
  149. package/.llm/digest/scenescroll.md +1 -1
  150. package/.llm/digest/scroller.md +1 -1
  151. package/.llm/digest/searchinput.md +1 -1
  152. package/.llm/digest/segmentedcontrol.md +1 -1
  153. package/.llm/digest/segmentedcontrolitem.md +2 -2
  154. package/.llm/digest/select.md +1 -1
  155. package/.llm/digest/selectchip.md +1 -1
  156. package/.llm/digest/spinner.md +1 -1
  157. package/.llm/digest/stack.md +1 -1
  158. package/.llm/digest/stats.md +1 -1
  159. package/.llm/digest/statusdot.md +1 -1
  160. package/.llm/digest/steps.md +3 -3
  161. package/.llm/digest/stickycards.md +1 -1
  162. package/.llm/digest/subdomainnavbar.md +1 -1
  163. package/.llm/digest/tab.md +1 -1
  164. package/.llm/digest/table.md +1 -1
  165. package/.llm/digest/tablebody.md +1 -1
  166. package/.llm/digest/tablecell.md +1 -1
  167. package/.llm/digest/tablehead.md +1 -1
  168. package/.llm/digest/tableheadercell.md +1 -1
  169. package/.llm/digest/tablerow.md +1 -1
  170. package/.llm/digest/tablist.md +1 -1
  171. package/.llm/digest/teamgrid.md +1 -1
  172. package/.llm/digest/testimonialrow.md +1 -1
  173. package/.llm/digest/testimonialslider.md +1 -1
  174. package/.llm/digest/textarea.md +1 -1
  175. package/.llm/digest/textinput.md +1 -1
  176. package/.llm/digest/textscroll.md +1 -1
  177. package/.llm/digest/tiers.md +1 -1
  178. package/.llm/digest/typography.md +1 -1
  179. package/.llm/index.md +2 -1
  180. package/.llm/tokens.md +5 -0
  181. package/AGENTS.md +1 -0
  182. package/README.md +28 -3
  183. package/dist/components/blocks/block-parallax.css +62 -0
  184. package/dist/components/blocks/comparison-cards/comparison-cards-impl.css +0 -12
  185. package/dist/components/blocks/download-banner/DownloadBanner.d.ts +1 -0
  186. package/dist/components/blocks/download-banner/DownloadBanner.js +2 -1
  187. package/dist/components/blocks/estimator/Estimator.css +206 -0
  188. package/dist/components/blocks/estimator/Estimator.d.ts +27 -0
  189. package/dist/components/blocks/estimator/Estimator.js +61 -0
  190. package/dist/components/blocks/estimator/Estimator.types.d.ts +63 -0
  191. package/dist/components/blocks/estimator/Estimator.types.js +25 -0
  192. package/dist/components/blocks/estimator/estimator-impl.css +159 -0
  193. package/dist/components/blocks/feature-bullets/FeatureBullets.d.ts +1 -0
  194. package/dist/components/blocks/feature-bullets/FeatureBullets.js +2 -1
  195. package/dist/components/blocks/feature-cards/FeatureCards.d.ts +1 -0
  196. package/dist/components/blocks/feature-cards/FeatureCards.js +1 -0
  197. package/dist/components/blocks/feature-scroll/FeatureScroll.js +9 -3
  198. package/dist/components/blocks/feature-scroll/feature-scroll-impl.css +63 -40
  199. package/dist/components/blocks/feature-split/FeatureSplit.d.ts +1 -0
  200. package/dist/components/blocks/feature-split/FeatureSplit.js +2 -1
  201. package/dist/components/blocks/feature-stack/FeatureStack.d.ts +1 -0
  202. package/dist/components/blocks/feature-stack/FeatureStack.js +2 -1
  203. package/dist/components/blocks/image-banner/ImageBanner.d.ts +1 -0
  204. package/dist/components/blocks/image-banner/ImageBanner.js +2 -1
  205. package/dist/components/blocks/image-banner/ImageBanner.types.d.ts +2 -2
  206. package/dist/components/blocks/image-banner/ImageBanner.types.js +1 -1
  207. package/dist/components/blocks/image-banner/image-banner-impl.css +7 -51
  208. package/dist/components/blocks/landing-hero/LandingHero.css +1 -1
  209. package/dist/components/blocks/landing-hero/LandingHero.d.ts +1 -0
  210. package/dist/components/blocks/landing-hero/LandingHero.js +2 -1
  211. package/dist/components/blocks/product-hero/ProductHero.css +1 -1
  212. package/dist/components/blocks/product-hero/ProductHero.d.ts +1 -0
  213. package/dist/components/blocks/product-hero/ProductHero.js +2 -1
  214. package/dist/components/blocks/steps/Steps.css +2 -2
  215. package/dist/components/blocks/steps/Steps.d.ts +1 -0
  216. package/dist/components/blocks/steps/Steps.js +3 -2
  217. package/dist/components/blocks/steps/Steps.types.d.ts +1 -1
  218. package/dist/components/blocks/steps/steps-impl.css +76 -4
  219. package/dist/components/blocks/steps/use-turn.d.ts +8 -8
  220. package/dist/components/blocks/steps/use-turn.js +19 -16
  221. package/dist/components/blocks/team-grid/TeamGrid.d.ts +1 -0
  222. package/dist/components/blocks/team-grid/TeamGrid.js +2 -1
  223. package/dist/components/core/card/Card.css +1 -1
  224. package/dist/components/core/segmented-control-item/SegmentedControlItem.types.d.ts +1 -1
  225. package/dist/components/core/select/Select.css +2 -0
  226. package/dist/components/core/select/select-impl.css +58 -0
  227. package/dist/components/core/text-area/TextArea.css +2 -0
  228. package/dist/components/core/text-area/text-area-impl.css +54 -0
  229. package/dist/components/core/text-input/TextInput.css +2 -0
  230. package/dist/components/core/text-input/text-input-impl.css +63 -0
  231. package/dist/components/estimator-data.d.ts +29 -0
  232. package/dist/components/estimator-data.js +12 -0
  233. package/dist/components/index.d.ts +3 -0
  234. package/dist/components/index.js +2 -0
  235. package/dist/icons/accounts.d.ts +1 -0
  236. package/dist/icons/accounts.js +1 -0
  237. package/dist/icons/app-stores.d.ts +1 -0
  238. package/dist/icons/app-stores.js +1 -0
  239. package/dist/icons/currency.d.ts +1 -0
  240. package/dist/icons/currency.js +1 -0
  241. package/dist/icons/flag.d.ts +1 -0
  242. package/dist/icons/flag.js +1 -0
  243. package/dist/icons/illustrative.d.ts +1 -0
  244. package/dist/icons/illustrative.js +1 -0
  245. package/dist/icons/index.d.ts +1 -0
  246. package/dist/icons/index.js +4 -0
  247. package/dist/icons/markets.d.ts +1 -0
  248. package/dist/icons/markets.js +1 -0
  249. package/dist/icons/social.d.ts +1 -0
  250. package/dist/icons/social.js +1 -0
  251. package/dist/icons/system.d.ts +1 -0
  252. package/dist/icons/system.js +1 -0
  253. package/dist/icons/trade.d.ts +1 -0
  254. package/dist/icons/trade.js +1 -0
  255. package/dist/marketing.css +10 -0
  256. package/package.json +44 -2
@@ -0,0 +1,159 @@
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
+
8
+ /*
9
+ * The Card fills the tool. The tool part is the box in the section's layout; the Card inside it is
10
+ * the surface, and it takes the whole of the tool's width rather than its content's.
11
+ */
12
+ :where([data-dds-context="marketing"]) .dds-estimator .dds-estimator__tool > .dds-card {
13
+ flex: auto;
14
+ /*
15
+ * Card clips so a picture meets its corner; this card holds no picture, and the clip cut the
16
+ * results panel's shadow off where the panel sits close to the card's edge, which left it flat.
17
+ */
18
+ overflow: visible;
19
+ }
20
+
21
+ /*
22
+ * The page's fields wrap their words only when a word does not fit. The section breaks anywhere so a
23
+ * long title cannot push the page wide, and that is inherited — but in a field it let the suffix
24
+ * and the panel heading give up their width a letter at a time whenever the panel was tight, "lots"
25
+ * stood on its end beside the box. `break-word` still wraps a word too long for the line, and keeps
26
+ * every word whole while there is room for it.
27
+ */
28
+ :where([data-dds-context="marketing"]) .dds-estimator .dds-estimator__inputs {
29
+ overflow-wrap: break-word;
30
+ }
31
+
32
+ /* A centred heading centres its measure too, so the line under it sits under the title. */
33
+ :where([data-dds-context="marketing"]) .dds-estimator[data-layout="stacked"] .dds-estimator__description {
34
+ margin-inline: auto;
35
+ }
36
+
37
+ /*
38
+ * The figures read along a row of digits that change as a reader types, so the digits keep one
39
+ * width: a value going from 1,111 to 8,888 does not shuffle the currency beside it.
40
+ */
41
+ :where([data-dds-context="marketing"]) .dds-estimator .dds-estimator__result-value {
42
+ font-variant-numeric: tabular-nums;
43
+ }
44
+
45
+ /*
46
+ * The figures in two columns from the fold, each ruled off beneath — four read as a block of answers
47
+ * beside the fields that made them, and the figures at h4 keep a column each rather than a line
48
+ * each. One column on a phone, where half the panel is too narrow for a figure at that size.
49
+ */
50
+ :where([data-dds-context="marketing"]) .dds-estimator .dds-estimator__list {
51
+ display: grid;
52
+ grid-template-columns: minmax(0, 1fr);
53
+ }
54
+
55
+ @container (min-width: 48rem) {
56
+ /*
57
+ * Calculate and Reset share the row under the fields, a half each: neither is the smaller one, and
58
+ * two buttons of different widths under a column of full-width fields read as unfinished. Only
59
+ * from the fold, where they sit side by side — in the phone's column a zero basis is a zero
60
+ * height, and the buttons collapsed to a sliver.
61
+ */
62
+ :where([data-dds-context="marketing"]) .dds-estimator .dds-estimator__input-actions > * {
63
+ flex: 1 1 0;
64
+ }
65
+
66
+ :where([data-dds-context="marketing"]) .dds-estimator .dds-estimator__list {
67
+ grid-template-columns: repeat(2, minmax(0, 1fr));
68
+ }
69
+
70
+ /* A figure left on its own — the one figure of a converter, the last of an odd set — takes the
71
+ whole row rather than half of it beside an empty cell. */
72
+ :where([data-dds-context="marketing"]) .dds-estimator .dds-estimator__result:last-child:nth-child(odd) {
73
+ grid-column: 1 / -1;
74
+ }
75
+ }
76
+
77
+ /*
78
+ * The white panel sits close to the card's edge — space.inset.xs from it, where the fields keep the
79
+ * card's full inset. The panel is a surface of its own, and a wide grey margin round it read as a
80
+ * card inside a card; close to the edge it reads as the card's other half.
81
+ *
82
+ * Pulled out by the difference between the two insets. In a column — a phone, and `split` — that is
83
+ * both sides and the foot; beside the fields it is the top, the foot and the far side.
84
+ */
85
+ :where([data-dds-context="marketing"]) .dds-estimator .dds-estimator__results {
86
+ margin-block-end: calc(var(--space-inset-xs) - var(--space-inset-card));
87
+ margin-inline: calc(var(--space-inset-xs) - var(--space-inset-card));
88
+ }
89
+
90
+ @container (min-width: 48rem) {
91
+ :where([data-dds-context="marketing"]) .dds-estimator[data-layout="stacked"] .dds-estimator__results {
92
+ margin-block: calc(var(--space-inset-xs) - var(--space-inset-card));
93
+ margin-inline-start: 0;
94
+ }
95
+ }
96
+
97
+ /*
98
+ * From the fold, under `stacked`: the inputs beside the results, two equal panels. A calculator's
99
+ * fields and its figures are read side by side — change one, watch the other.
100
+ *
101
+ * `48rem` longhand because `var()` is not permitted in a container query condition, and it is the
102
+ * width the rest of this package folds at.
103
+ */
104
+ @container (min-width: 48rem) {
105
+ :where([data-dds-context="marketing"]) .dds-estimator[data-layout="stacked"] .dds-estimator__panels {
106
+ flex-direction: row;
107
+ }
108
+
109
+ :where([data-dds-context="marketing"]) .dds-estimator[data-layout="stacked"] :is(.dds-estimator__inputs, .dds-estimator__results) {
110
+ flex: 1 1 0;
111
+ }
112
+ }
113
+
114
+ /*
115
+ * At full width, under `split`: the heading beside the card, two equal halves — the reference
116
+ * converter's columns, so the tool reads as wide as the words that introduce it. The panels stay one
117
+ * column inside the card, the inputs above the figures.
118
+ *
119
+ * `64rem`, the width a split of words and a tool can both breathe in; below it the heading goes back
120
+ * above the card, at the start.
121
+ */
122
+ @container (min-width: 64rem) {
123
+ :where([data-dds-context="marketing"]) .dds-estimator[data-layout="split"] .dds-estimator__inner {
124
+ align-items: center;
125
+ flex-direction: row;
126
+ }
127
+
128
+ :where([data-dds-context="marketing"]) .dds-estimator[data-layout="split"] :is(.dds-estimator__header, .dds-estimator__tool) {
129
+ flex: 1 1 0;
130
+ }
131
+ }
132
+
133
+ /*
134
+ * On a phone the way onward fills the panel, as every section's actions do — the package's rule:
135
+ * a thumb reaches a full-width button, and a short one stranded at the start reads as an aside.
136
+ */
137
+ @container (max-width: 47.9375rem) {
138
+ :where([data-dds-context="marketing"]) .dds-estimator .dds-estimator__action > * {
139
+ inline-size: 100%;
140
+ }
141
+
142
+ /* And the inputs' own buttons stack, each the full width. */
143
+ :where([data-dds-context="marketing"]) .dds-estimator .dds-estimator__input-actions {
144
+ flex-direction: column;
145
+ }
146
+ }
147
+
148
+ /*
149
+ * The card on the grey band — ComparisonCards' rule for the same card. Card's recessed fill is the
150
+ * band's grey and vanishes, so it takes the raised fill. The block knows its ground and the card does
151
+ * not, so the answer is here rather than in Card.
152
+ *
153
+ * Nothing for the dark band: there the tool is drawn in the dark theme (see Estimator.tsx), which
154
+ * turns the card, the rules between figures, the muted ink and the page's own fields round together.
155
+ */
156
+ :where([data-dds-context="marketing"]) .dds-estimator[data-ground="subtle"] .dds-estimator__tool > .dds-card {
157
+ background-color: var(--color-bg-surface-card-raised);
158
+ }
159
+
@@ -1,6 +1,7 @@
1
1
  import './FeatureBullets.css';
2
2
  import './feature-bullets-impl.css';
3
3
  import '../block-reveal.css';
4
+ import '../block-parallax.css';
4
5
  import { type FeatureBulletsProps } from './FeatureBullets.types.js';
5
6
  export type { FeatureBulletsProps };
6
7
  /**
@@ -5,6 +5,7 @@ import { CircleCheckSolidIcon } from '@deriv-com/quill-icons-web';
5
5
  import './FeatureBullets.css';
6
6
  import './feature-bullets-impl.css';
7
7
  import '../block-reveal.css';
8
+ import '../block-parallax.css';
8
9
  import { arriving, useReveal } from '../use-reveal.js';
9
10
  import { featureBulletsDefaults, featureBulletsParts, } from './FeatureBullets.types.js';
10
11
  /**
@@ -51,5 +52,5 @@ export function FeatureBullets({ ground = featureBulletsDefaults.ground, mediaPo
51
52
 
52
53
  Two behind the claim, which the picture beside them takes — the two halves of
53
54
  the split arrive first and the list follows them. */
54
- _jsxs("li", { className: featureBulletsParts.item, ...arriving(2 + at), children: [_jsx("span", { className: featureBulletsParts.mark, "aria-hidden": "true", children: _jsx(CircleCheckSolidIcon, {}) }), _jsx("span", { className: featureBulletsParts.text, children: bullet })] }, bullet))) })] }), _jsx("div", { className: featureBulletsParts.media, ...arriving(1), "aria-hidden": "true", children: media })] }) }) }));
55
+ _jsxs("li", { className: featureBulletsParts.item, ...arriving(2 + at), children: [_jsx("span", { className: featureBulletsParts.mark, "aria-hidden": "true", children: _jsx(CircleCheckSolidIcon, {}) }), _jsx("span", { className: featureBulletsParts.text, children: bullet })] }, bullet))) })] }), _jsx("div", { "data-dds-parallax": "", className: featureBulletsParts.media, ...arriving(1), "aria-hidden": "true", children: media })] }) }) }));
55
56
  }
@@ -1,6 +1,7 @@
1
1
  import './FeatureCards.css';
2
2
  import './feature-cards-impl.css';
3
3
  import '../block-reveal.css';
4
+ import '../block-parallax.css';
4
5
  import { type FeatureCardsProps } from './FeatureCards.types.js';
5
6
  export type { FeatureCardsProps };
6
7
  /**
@@ -4,6 +4,7 @@ import { useId } from 'react';
4
4
  import './FeatureCards.css';
5
5
  import './feature-cards-impl.css';
6
6
  import '../block-reveal.css';
7
+ import '../block-parallax.css';
7
8
  import { given } from '../../given.js';
8
9
  import { cardsOf } from '../cards-of.js';
9
10
  import { arriving, useReveal } from '../use-reveal.js';
@@ -52,7 +52,13 @@ export function FeatureScroll({ ground = featureScrollDefaults.ground, title, st
52
52
  const root = useRef(null);
53
53
  const titles = useRef([]);
54
54
  const band = useRef(null);
55
- const [showing, setShowing] = useState(0);
55
+ /*
56
+ Which stop's picture is showing, and which way the reader went to reach it. The way is held with
57
+ the stop rather than worked out after it, so the picture arriving and the edge it is uncovered
58
+ from change in the same render — a step behind, it would open from the wrong side for a frame.
59
+ */
60
+ // `going` is absent until the first change, so the picture a reader opens on is simply there.
61
+ const [{ at: showing, going }, setShowing] = useState({ at: 0 });
56
62
  const count = steps.length;
57
63
  useLayoutEffect(() => {
58
64
  if (count === 0)
@@ -92,7 +98,7 @@ export function FeatureScroll({ ground = featureScrollDefaults.ground, title, st
92
98
  reached = i;
93
99
  }
94
100
  const next = Math.min(count - 1, reached + 1);
95
- setShowing((held) => (held === next ? held : next));
101
+ setShowing((held) => (held.at === next ? held : { at: next, going: next > held.at ? 'down' : 'up' }));
96
102
  };
97
103
  const schedule = () => {
98
104
  if (frame !== 0)
@@ -127,7 +133,7 @@ export function FeatureScroll({ ground = featureScrollDefaults.ground, title, st
127
133
  window.cancelAnimationFrame(frame);
128
134
  };
129
135
  }, [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) => (
136
+ return (_jsx("section", { className: featureScrollParts.root, "data-ground": ground, "data-media-position": mediaPosition, "data-going": going, "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
137
  /* Keyed by the title. An index would key the set by position, so filtering a stop out
132
138
  would renumber every stop under it and React would reconcile the wrong ones — and
133
139
  these are the content, so two the same would be a duplicate stop rather than a key
@@ -226,53 +226,50 @@
226
226
  * The duration and the curve are bound on the part, so the generated sheet writes the
227
227
  * reduced-motion block that turns this into a cut.
228
228
  */
229
+ /*
230
+ * One of them is shown, and the next is uncovered over it rather than faded in.
231
+ *
232
+ * The only thing the component's own listener writes is which stop's picture belongs to the words
233
+ * a reader has reached, and which way they went to reach it — a question about where each title
234
+ * has got to, which no query asks. The pinning and the change are this stylesheet's.
235
+ *
236
+ * A wipe rather than a dissolve. Fading the arriving picture in over the last one put both on
237
+ * screen at once for the whole of the change — at its middle the square was two photographs at
238
+ * half strength each, a double exposure that read as the pictures overlapping. Uncovered from an
239
+ * edge instead, every point of the square is one picture or the other at full strength, and the
240
+ * line between them moves the way the page does: up from the foot as a reader scrolls on, down
241
+ * from the head as they scroll back. Nothing is mixed, so nothing overlaps.
242
+ *
243
+ * An animation rather than a transition, because an animation starts from its first frame. A
244
+ * transition starts from wherever the picture last rested, so a reader who turned back mid-section
245
+ * had the next picture open from the edge they were no longer moving towards.
246
+ *
247
+ * The shown picture is brought to the front and uncovered; the one it replaces stays beneath,
248
+ * whole, for the same duration and then goes — a step at the end of a zero-length transition,
249
+ * delayed by the uncovering's own duration, read from the same role so the two cannot drift
250
+ * apart into a gap. Every other picture is already gone.
251
+ *
252
+ * `z-index` is a bare integer, the one kind of value this system keeps out of its tokens: a
253
+ * stacking order inside a component is not a decision any brand or mode has an opinion about.
254
+ */
255
+ :where([data-dds-context="marketing"]) .dds-featurescroll .dds-featurescroll__media {
256
+ transition: opacity 0s linear var(--motion-duration-media-hover);
257
+ }
258
+
229
259
  :where([data-dds-context="marketing"])
230
260
  .dds-featurescroll
231
261
  .dds-featurescroll__media[data-showing="true"] {
232
262
  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
263
  transition-delay: 0s;
264
+ z-index: 1;
252
265
  }
253
266
 
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;
267
+ :where([data-dds-context="marketing"]) .dds-featurescroll[data-going="down"] .dds-featurescroll__media[data-showing="true"] {
268
+ animation: dds-featurescroll-from-foot var(--motion-duration-media-hover) var(--motion-easing-media-hover);
269
+ }
270
+
271
+ :where([data-dds-context="marketing"]) .dds-featurescroll[data-going="up"] .dds-featurescroll__media[data-showing="true"] {
272
+ animation: dds-featurescroll-from-head var(--motion-duration-media-hover) var(--motion-easing-media-hover);
276
273
  }
277
274
 
278
275
  /*
@@ -290,3 +287,29 @@
290
287
  );
291
288
  }
292
289
  }
290
+
291
+ @keyframes dds-featurescroll-from-foot {
292
+ from {
293
+ clip-path: inset(100% 0 0 0);
294
+ }
295
+ to {
296
+ clip-path: inset(0);
297
+ }
298
+ }
299
+
300
+ @keyframes dds-featurescroll-from-head {
301
+ from {
302
+ clip-path: inset(0 0 100% 0);
303
+ }
304
+ to {
305
+ clip-path: inset(0);
306
+ }
307
+ }
308
+
309
+ /* Less movement is a cut: no uncovering, and the last picture gone at once. The generated sheet
310
+ already drops every transition here; the animation is this sheet's to drop. */
311
+ @media (prefers-reduced-motion: reduce) {
312
+ :where([data-dds-context="marketing"]) .dds-featurescroll .dds-featurescroll__media[data-showing="true"] {
313
+ animation: none;
314
+ }
315
+ }
@@ -1,6 +1,7 @@
1
1
  import './FeatureSplit.css';
2
2
  import './feature-split-impl.css';
3
3
  import '../block-reveal.css';
4
+ import '../block-parallax.css';
4
5
  import { type FeatureSplitProps } from './FeatureSplit.types.js';
5
6
  export type { FeatureSplitProps };
6
7
  /**
@@ -4,6 +4,7 @@ import { useId } from 'react';
4
4
  import './FeatureSplit.css';
5
5
  import './feature-split-impl.css';
6
6
  import '../block-reveal.css';
7
+ import '../block-parallax.css';
7
8
  import { arriving, useReveal } from '../use-reveal.js';
8
9
  import { given } from '../../given.js';
9
10
  import { featureSplitDefaults, featureSplitParts, } from './FeatureSplit.types.js';
@@ -34,5 +35,5 @@ import { featureSplitDefaults, featureSplitParts, } from './FeatureSplit.types.j
34
35
  export function FeatureSplit({ ground = featureSplitDefaults.ground, mediaPosition = featureSplitDefaults.mediaPosition, title, description, link, media, }) {
35
36
  const titleId = useId();
36
37
  const [band, revealed] = useReveal();
37
- return (_jsx("section", { className: featureSplitParts.root, "data-ground": ground, "data-media-position": mediaPosition, "aria-labelledby": titleId, children: _jsx("div", { className: featureSplitParts.inner, ref: band, "data-revealed": revealed, children: _jsxs("div", { className: featureSplitParts.split, children: [_jsxs("div", { className: featureSplitParts.content, children: [_jsx("h2", { className: featureSplitParts.title, ...arriving(0), id: titleId, children: title }), given(description) ? (_jsx("p", { className: featureSplitParts.description, ...arriving(1), children: description })) : null, link ? (_jsx("div", { className: featureSplitParts.link, ...arriving(2), children: link })) : null] }), _jsx("div", { className: featureSplitParts.media, ...arriving(3), "aria-hidden": "true", children: media })] }) }) }));
38
+ return (_jsx("section", { className: featureSplitParts.root, "data-ground": ground, "data-media-position": mediaPosition, "aria-labelledby": titleId, children: _jsx("div", { className: featureSplitParts.inner, ref: band, "data-revealed": revealed, children: _jsxs("div", { className: featureSplitParts.split, children: [_jsxs("div", { className: featureSplitParts.content, children: [_jsx("h2", { className: featureSplitParts.title, ...arriving(0), id: titleId, children: title }), given(description) ? (_jsx("p", { className: featureSplitParts.description, ...arriving(1), children: description })) : null, link ? (_jsx("div", { className: featureSplitParts.link, ...arriving(2), children: link })) : null] }), _jsx("div", { "data-dds-parallax": "", className: featureSplitParts.media, ...arriving(3), "aria-hidden": "true", children: media })] }) }) }));
38
39
  }
@@ -1,6 +1,7 @@
1
1
  import './FeatureStack.css';
2
2
  import './feature-stack-impl.css';
3
3
  import '../block-reveal.css';
4
+ import '../block-parallax.css';
4
5
  import { type FeatureStackProps } from './FeatureStack.types.js';
5
6
  export type { FeatureStackProps };
6
7
  /**
@@ -4,6 +4,7 @@ import { useId } from 'react';
4
4
  import './FeatureStack.css';
5
5
  import './feature-stack-impl.css';
6
6
  import '../block-reveal.css';
7
+ import '../block-parallax.css';
7
8
  import { given } from '../../given.js';
8
9
  import { arriving, useReveal } from '../use-reveal.js';
9
10
  import { featureStackDefaults, featureStackParts, } from './FeatureStack.types.js';
@@ -23,5 +24,5 @@ import { featureStackDefaults, featureStackParts, } from './FeatureStack.types.j
23
24
  export function FeatureStack({ ground = featureStackDefaults.ground, title, description, link, image, }) {
24
25
  const titleId = useId();
25
26
  const [band, revealed] = useReveal();
26
- return (_jsx("section", { className: featureStackParts.root, "data-ground": ground, "aria-labelledby": titleId, children: _jsxs("div", { className: featureStackParts.inner, ref: band, "data-revealed": revealed, children: [_jsxs("div", { className: featureStackParts.header, children: [_jsx("h2", { className: featureStackParts.title, id: titleId, ...arriving(0), children: title }), given(description) ? (_jsx("p", { className: featureStackParts.description, ...arriving(1), children: description })) : null, link ? (_jsx("div", { className: featureStackParts.link, ...arriving(2), children: link })) : null] }), _jsx("div", { className: featureStackParts.media, ...arriving(3), children: image ? (_jsx("img", { className: featureStackParts.image, src: image.src, alt: image.alt, width: image.width, height: image.height, loading: "lazy", decoding: "async" })) : null })] }) }));
27
+ return (_jsx("section", { className: featureStackParts.root, "data-ground": ground, "aria-labelledby": titleId, children: _jsxs("div", { className: featureStackParts.inner, ref: band, "data-revealed": revealed, children: [_jsxs("div", { className: featureStackParts.header, children: [_jsx("h2", { className: featureStackParts.title, id: titleId, ...arriving(0), children: title }), given(description) ? (_jsx("p", { className: featureStackParts.description, ...arriving(1), children: description })) : null, link ? (_jsx("div", { className: featureStackParts.link, ...arriving(2), children: link })) : null] }), _jsx("div", { "data-dds-parallax": "", className: featureStackParts.media, ...arriving(3), children: image ? (_jsx("img", { className: featureStackParts.image, src: image.src, alt: image.alt, width: image.width, height: image.height, loading: "lazy", decoding: "async" })) : null })] }) }));
27
28
  }
@@ -1,6 +1,7 @@
1
1
  import './ImageBanner.css';
2
2
  import './image-banner-impl.css';
3
3
  import '../block-reveal.css';
4
+ import '../block-parallax.css';
4
5
  import { type ImageBannerProps } from './ImageBanner.types.js';
5
6
  export type { ImageBannerProps };
6
7
  /**
@@ -4,6 +4,7 @@ import { useId } from 'react';
4
4
  import './ImageBanner.css';
5
5
  import './image-banner-impl.css';
6
6
  import '../block-reveal.css';
7
+ import '../block-parallax.css';
7
8
  import { arriving, useReveal } from '../use-reveal.js';
8
9
  import { given } from '../../given.js';
9
10
  import { imageBannerDefaults, imageBannerParts } from './ImageBanner.types.js';
@@ -37,5 +38,5 @@ export function ImageBanner({ ground = imageBannerDefaults.ground, variant = ima
37
38
  const titleId = useId();
38
39
  const Title = headingLevel;
39
40
  const [band, revealed] = useReveal();
40
- return (_jsx("section", { className: imageBannerParts.root, "data-ground": ground, "data-variant": variant, "data-media-position": mediaPosition, "data-parallax": parallax, "aria-labelledby": titleId, children: _jsx("div", { className: imageBannerParts.inner, ref: band, "data-revealed": revealed, children: _jsxs("div", { className: imageBannerParts.card, children: [_jsxs("div", { className: imageBannerParts.content, children: [_jsx(Title, { className: imageBannerParts.title, id: titleId, ...arriving(0), children: title }), given(description) ? (_jsx("p", { className: imageBannerParts.description, ...arriving(1), children: description })) : null, action ? (_jsx("div", { className: imageBannerParts.action, ...arriving(2), children: action })) : null] }), _jsx("div", { className: imageBannerParts.media, ...(variant === 'cover' ? {} : arriving(3)), children: media })] }) }) }));
41
+ return (_jsx("section", { className: imageBannerParts.root, "data-ground": ground, "data-variant": variant, "data-media-position": mediaPosition, "data-parallax": parallax, "aria-labelledby": titleId, children: _jsx("div", { className: imageBannerParts.inner, ref: band, "data-revealed": revealed, children: _jsxs("div", { className: imageBannerParts.card, children: [_jsxs("div", { className: imageBannerParts.content, children: [_jsx(Title, { className: imageBannerParts.title, id: titleId, ...arriving(0), children: title }), given(description) ? (_jsx("p", { className: imageBannerParts.description, ...arriving(1), children: description })) : null, action ? (_jsx("div", { className: imageBannerParts.action, ...arriving(2), children: action })) : null] }), _jsx("div", { className: imageBannerParts.media, "data-dds-parallax": parallax ? '' : undefined, ...(variant === 'cover' ? {} : arriving(3)), children: media })] }) }) }));
41
42
  }
@@ -29,7 +29,7 @@ export interface ImageBannerOwnProps {
29
29
  action?: ReactNode;
30
30
  /** Always. The page's photograph, through AspectRatio or as an image — people or product. Split and cover crop it to fill the space they give it. Alt text is the page's to give, empty where the photograph is decoration. */
31
31
  media: ReactNode;
32
- /** The photograph drifts against the scroll as the card passes through the view, by motion.distance.drift over the whole pass, so it reads as further away than the card — the reference design's ParallaxBanner, which this replaces. For a careers or about page, where the photograph of the people is the point. Off by default: a card selling one action does not need its picture moving under the button. */
32
+ /** The photograph drifts against the scroll as the card passes through the view, the drift every block's picture shares — drawn at motion.scale.parallax and moved from motion.translate.parallax-from to motion.translate.parallax-to over the whole pass — so it reads as further away than the card — the reference design's ParallaxBanner, which this replaces. On by default, as every block's picture drifts: pass false for a card whose photograph must hold still under its button. */
33
33
  parallax?: boolean;
34
34
  }
35
35
  export type ImageBannerProps = ImageBannerOwnProps;
@@ -50,5 +50,5 @@ export declare const imageBannerDefaults: {
50
50
  readonly variant: "split";
51
51
  readonly mediaPosition: "end";
52
52
  readonly headingLevel: "h2";
53
- readonly parallax: false;
53
+ readonly parallax: true;
54
54
  };
@@ -11,4 +11,4 @@ export const imageBannerParts = {
11
11
  media: 'dds-imagebanner__media',
12
12
  };
13
13
  /** Generated so defaults cannot drift from the contract. */
14
- export const imageBannerDefaults = { ground: 'page', variant: 'split', mediaPosition: 'end', headingLevel: 'h2', parallax: false };
14
+ export const imageBannerDefaults = { ground: 'page', variant: 'split', mediaPosition: 'end', headingLevel: 'h2', parallax: true };
@@ -226,56 +226,12 @@
226
226
 
227
227
  /*
228
228
  * `parallax`: the photograph drifts against the scroll as the card passes through the view — the
229
- * reference design's ParallaxBanner, folded in here as an option on the split and cover cards.
229
+ * reference design's ParallaxBanner, folded in as an option. The drift is the one every block's
230
+ * picture shares, in block-parallax.css: the component marks the frame and the picture is scaled
231
+ * and moved rather than resized.
230
232
  *
231
- * The photograph is drawn taller than its frame by the drift, half above and half below, so the
232
- * movement never shows an edge at either end; the frame clips it. The image itself is the thing
233
- * that moves, so the frame keeps its size and the card its layout.
234
- *
235
- * Scroll-driven, the way LogoColumns moves its wall: the frame names a view timeline and the image
236
- * runs down it from half the drift above centre to half below, over the whole pass (`cover`), so as
237
- * the page carries the card up the photograph falls behind a little and reads as further away.
238
- * Linear, because the scroll is the easing. The card and the frame clip with `overflow: clip`, not
239
- * `hidden`: `hidden` makes a box a scroll container, and a view timeline measured against one that
240
- * never scrolls stands still.
241
- *
242
- * A browser with no scroll timeline, and a reader who asked for less motion, get the photograph
243
- * still and centred, as with `parallax` off.
233
+ * This card once drew its own, by making the photograph taller than the frame with `block-size`.
234
+ * A picture that sets its height inline — Next.js's Image with `fill` writes `height: 100%` on the
235
+ * element — beat that, so the photograph stayed the frame's height and the drift pulled its lower
236
+ * edge up off the card. `scale` is a property nothing sets inline, so the shared drift holds.
244
237
  */
245
- :where([data-dds-context="marketing"]) .dds-imagebanner[data-parallax="true"]:is([data-variant="split"], [data-variant="cover"]) .dds-imagebanner__media img {
246
- block-size: calc(100% + var(--motion-distance-drift));
247
- margin-block-start: calc(var(--motion-distance-drift) / -2);
248
- object-position: center top;
249
- }
250
-
251
- @keyframes dds-imagebanner-drift {
252
- from {
253
- translate: 0 calc(var(--motion-distance-drift) / -2);
254
- }
255
- to {
256
- translate: 0 calc(var(--motion-distance-drift) / 2);
257
- }
258
- }
259
-
260
- @supports (animation-timeline: view()) {
261
- :where([data-dds-context="marketing"]) .dds-imagebanner .dds-imagebanner__media {
262
- view-timeline: --dds-imagebanner-frame block;
263
- }
264
-
265
- /* Only for a reader who has not asked for less motion — asked here, around the rule, rather than
266
- undone after it: the drift's selector outweighs any plain reset, and a reset that loses to it
267
- leaves the photograph moving for exactly the reader who asked it not to. */
268
- @media (prefers-reduced-motion: no-preference) {
269
- :where([data-dds-context="marketing"]) .dds-imagebanner[data-parallax="true"]:is([data-variant="split"], [data-variant="cover"]) .dds-imagebanner__media img {
270
- animation: dds-imagebanner-drift linear both;
271
- animation-range: cover;
272
- animation-timeline: --dds-imagebanner-frame;
273
- }
274
- }
275
- }
276
-
277
- @media (prefers-reduced-motion: reduce) {
278
- :where([data-dds-context="marketing"]) .dds-imagebanner[data-parallax="true"]:is([data-variant="split"], [data-variant="cover"]) .dds-imagebanner__media img {
279
- animation: none;
280
- }
281
- }
@@ -6,7 +6,7 @@
6
6
  display: flex;
7
7
  inline-size: 100%;
8
8
  justify-content: center;
9
- overflow: hidden;
9
+ overflow: clip;
10
10
  overflow-wrap: anywhere;
11
11
  position: relative;
12
12
  background-color: var(--color-bg-brand-strong);
@@ -1,6 +1,7 @@
1
1
  import './LandingHero.css';
2
2
  import './landing-hero-impl.css';
3
3
  import '../block-reveal.css';
4
+ import '../block-parallax.css';
4
5
  import { type LandingHeroProps } from './LandingHero.types.js';
5
6
  export type { LandingHeroProps };
6
7
  /**
@@ -4,6 +4,7 @@ import { useId } from 'react';
4
4
  import './LandingHero.css';
5
5
  import './landing-hero-impl.css';
6
6
  import '../block-reveal.css';
7
+ import '../block-parallax.css';
7
8
  import { arriving, useReveal } from '../use-reveal.js';
8
9
  import { given } from '../../given.js';
9
10
  import { Background } from '../../core/background/Background.js';
@@ -51,5 +52,5 @@ export function LandingHero({ eyebrow, title, headingLevel = landingHeroDefaults
51
52
  /* The one inline style here, and it is a value rather than a decision: which photograph,
52
53
  supplied by the page. Everything about how it is drawn — the cover fit and the position — is in the stylesheet, where it can be reasoned about. A background shorthand
53
54
  would have reset those four properties every time the page passed an image. */
54
- style: given(backgroundImage) ? { backgroundImage: `url(${backgroundImage})` } : undefined, children: [background === 'halftone' ? _jsx(Background, { tone: "dark", children: null }) : null, media ? (_jsx("div", { className: landingHeroParts.media, "aria-hidden": "true", children: media })) : null, _jsx("div", { className: landingHeroParts.inner, ref: band, "data-revealed": revealed, children: _jsxs("div", { className: landingHeroParts.content, children: [_jsxs("div", { className: landingHeroParts.text, children: [given(eyebrow) ? (_jsx("p", { className: landingHeroParts.eyebrow, ...arriving(0), children: eyebrow })) : null, _jsx(Title, { className: landingHeroParts.title, ...arriving(1), id: titleId, children: title }), given(description) ? (_jsx("p", { className: landingHeroParts.description, ...arriving(2), children: description })) : null] }), actions ? (_jsx("div", { className: landingHeroParts.actions, ...arriving(3), children: actions })) : null] }) })] }));
55
+ style: given(backgroundImage) ? { backgroundImage: `url(${backgroundImage})` } : undefined, children: [background === 'halftone' ? _jsx(Background, { tone: "dark", children: null }) : null, media ? (_jsx("div", { "data-dds-parallax": "", className: landingHeroParts.media, "aria-hidden": "true", children: media })) : null, _jsx("div", { className: landingHeroParts.inner, ref: band, "data-revealed": revealed, children: _jsxs("div", { className: landingHeroParts.content, children: [_jsxs("div", { className: landingHeroParts.text, children: [given(eyebrow) ? (_jsx("p", { className: landingHeroParts.eyebrow, ...arriving(0), children: eyebrow })) : null, _jsx(Title, { className: landingHeroParts.title, ...arriving(1), id: titleId, children: title }), given(description) ? (_jsx("p", { className: landingHeroParts.description, ...arriving(2), children: description })) : null] }), actions ? (_jsx("div", { className: landingHeroParts.actions, ...arriving(3), children: actions })) : null] }) })] }));
55
56
  }
@@ -6,7 +6,7 @@
6
6
  display: flex;
7
7
  inline-size: 100%;
8
8
  justify-content: center;
9
- overflow: hidden;
9
+ overflow: clip;
10
10
  overflow-wrap: anywhere;
11
11
  position: relative;
12
12
  background-color: var(--color-bg-brand-strong);
@@ -1,6 +1,7 @@
1
1
  import './ProductHero.css';
2
2
  import './product-hero-impl.css';
3
3
  import '../block-reveal.css';
4
+ import '../block-parallax.css';
4
5
  import { type ProductHeroProps } from './ProductHero.types.js';
5
6
  export type { ProductHeroProps };
6
7
  /**
@@ -4,6 +4,7 @@ import { useId } from 'react';
4
4
  import './ProductHero.css';
5
5
  import './product-hero-impl.css';
6
6
  import '../block-reveal.css';
7
+ import '../block-parallax.css';
7
8
  import { arriving, useReveal } from '../use-reveal.js';
8
9
  import { given } from '../../given.js';
9
10
  import { productHeroDefaults, productHeroParts, } from './ProductHero.types.js';
@@ -47,5 +48,5 @@ export function ProductHero({ eyebrow, title, headingLevel = productHeroDefaults
47
48
  style: given(backgroundImage) ? { backgroundImage: `url(${backgroundImage})` } : undefined, children: _jsx("div", { className: productHeroParts.inner, ref: band, "data-revealed": revealed, children: _jsxs("div", { className: productHeroParts.content, children: [_jsxs("div", { className: productHeroParts.text, children: [given(eyebrow) ? (_jsx("p", { className: productHeroParts.eyebrow, ...arriving(0), children: eyebrow })) : null, _jsx(Title, { className: productHeroParts.title, ...arriving(1), id: titleId, children: title }), given(description) ? (_jsx("p", { className: productHeroParts.description, ...arriving(2), children: description })) : null] }), actions ? (_jsx("div", { className: productHeroParts.actions, ...arriving(3), children: actions })) : null, image ? (
48
49
  // The picture at its own shape: drawn here so the band can size it, and read for its
49
50
  // `alt`, which the page leaves empty where the claim already names it.
50
- _jsx("div", { className: productHeroParts.media, ...arriving(4), children: _jsx("img", { className: productHeroParts.image, src: image.src, alt: image.alt, width: image.width, height: image.height, decoding: "async" }) })) : media ? (_jsx("div", { className: productHeroParts.media, ...arriving(4), "aria-hidden": "true", children: media })) : null] }) }) }));
51
+ _jsx("div", { "data-dds-parallax": "", className: productHeroParts.media, ...arriving(4), children: _jsx("img", { className: productHeroParts.image, src: image.src, alt: image.alt, width: image.width, height: image.height, decoding: "async" }) })) : media ? (_jsx("div", { "data-dds-parallax": "", className: productHeroParts.media, ...arriving(4), "aria-hidden": "true", children: media })) : null] }) }) }));
51
52
  }
@@ -38,8 +38,8 @@
38
38
 
39
39
  :where([data-dds-context="marketing"]) .dds-steps .dds-steps__image {
40
40
  display: block;
41
- transition-duration: var(--motion-duration-shift);
42
- transition-timing-function: var(--motion-easing-standard);
41
+ transition-duration: var(--motion-duration-turn);
42
+ transition-timing-function: var(--motion-easing-reveal);
43
43
  }
44
44
 
45
45
  :where([data-dds-context="marketing"]) .dds-steps .dds-steps__content {
@@ -1,6 +1,7 @@
1
1
  import './Steps.css';
2
2
  import './steps-impl.css';
3
3
  import '../block-reveal.css';
4
+ import '../block-parallax.css';
4
5
  import { type StepsProps } from './Steps.types.js';
5
6
  export type { StepsProps };
6
7
  /**