@deriv-com/dds-marketing 1.1.0 → 1.1.2

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 (40) hide show
  1. package/.llm/components/featurescroll.md +1 -1
  2. package/.llm/components/imagebanner.md +2 -2
  3. package/.llm/components/steps.md +14 -6
  4. package/.llm/digest/imagebanner.md +1 -1
  5. package/.llm/digest/steps.md +2 -2
  6. package/.llm/tokens.md +4 -0
  7. package/dist/components/blocks/block-parallax.css +62 -0
  8. package/dist/components/blocks/download-banner/DownloadBanner.d.ts +1 -0
  9. package/dist/components/blocks/download-banner/DownloadBanner.js +2 -1
  10. package/dist/components/blocks/feature-bullets/FeatureBullets.d.ts +1 -0
  11. package/dist/components/blocks/feature-bullets/FeatureBullets.js +2 -1
  12. package/dist/components/blocks/feature-cards/FeatureCards.d.ts +1 -0
  13. package/dist/components/blocks/feature-cards/FeatureCards.js +1 -0
  14. package/dist/components/blocks/feature-scroll/FeatureScroll.js +9 -3
  15. package/dist/components/blocks/feature-scroll/feature-scroll-impl.css +63 -40
  16. package/dist/components/blocks/feature-split/FeatureSplit.d.ts +1 -0
  17. package/dist/components/blocks/feature-split/FeatureSplit.js +2 -1
  18. package/dist/components/blocks/feature-stack/FeatureStack.d.ts +1 -0
  19. package/dist/components/blocks/feature-stack/FeatureStack.js +2 -1
  20. package/dist/components/blocks/image-banner/ImageBanner.types.d.ts +2 -2
  21. package/dist/components/blocks/image-banner/ImageBanner.types.js +1 -1
  22. package/dist/components/blocks/landing-hero/LandingHero.css +1 -1
  23. package/dist/components/blocks/landing-hero/LandingHero.d.ts +1 -0
  24. package/dist/components/blocks/landing-hero/LandingHero.js +2 -1
  25. package/dist/components/blocks/product-hero/ProductHero.css +1 -1
  26. package/dist/components/blocks/product-hero/ProductHero.d.ts +1 -0
  27. package/dist/components/blocks/product-hero/ProductHero.js +2 -1
  28. package/dist/components/blocks/steps/Steps.css +2 -2
  29. package/dist/components/blocks/steps/Steps.d.ts +1 -0
  30. package/dist/components/blocks/steps/Steps.js +3 -2
  31. package/dist/components/blocks/steps/Steps.types.d.ts +1 -1
  32. package/dist/components/blocks/steps/steps-impl.css +76 -4
  33. package/dist/components/blocks/steps/use-turn.d.ts +8 -8
  34. package/dist/components/blocks/steps/use-turn.js +19 -16
  35. package/dist/components/blocks/team-grid/TeamGrid.d.ts +1 -0
  36. package/dist/components/blocks/team-grid/TeamGrid.js +2 -1
  37. package/dist/components/core/card/Card.css +1 -1
  38. package/dist/components/core/illustration-scene/illustration-scene-impl.css +15 -0
  39. package/dist/marketing.css +8 -0
  40. package/package.json +1 -1
@@ -64,7 +64,7 @@ FeatureScroll renders 11 elements. A token binding names the part it applies to.
64
64
 
65
65
  Every stop has one and at full width they are stacked in the same column of the grid, pinned at the same line under the bar. One is shown and the rest are held at nothing, so a reader who scrolls past a stop's title sees the square change rather than travel — which is the section's whole effect and the reason the stops give up their boxes for it.
66
66
 
67
- One picture gives way to the next by fading in over it, rather than by the pair fading past each other. The arriving one is brought to the front and takes the full duration to become opaque, and the one beneath holds exactly as it was until it is covered — so at no moment is the square anything less than a complete picture. Fading both at once would leave them near half way together, the section's ground showing through the pair, and the square would brighten and dim: a pulse made of nothing but two opacities crossing.
67
+ One picture gives way to the next by being uncovered over it from an edge — up from the foot as a reader scrolls on, down from the head as they scroll back — rather than by fading in. The arriving one is brought to the front and uncovered over the bound duration and curve, and the one beneath holds whole until it is covered, then goes. A fade put both pictures on screen at half strength at once, a double exposure that read as the two overlapping; a wipe never mixes them, so every point of the square is one complete picture or the other.
68
68
 
69
69
  Front is the shown one rather than the later one in the markup, because a reader scrolls back as well as forward — going up, the arriving picture is earlier in the document and would fade in underneath what it is replacing.
70
70
 
@@ -98,9 +98,9 @@ Accepts: *
98
98
  Use when: 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.
99
99
  Never for: Artwork with words in it. The cover photograph sits under a scrim and the text, and the split crops to fill
100
100
 
101
- ### parallax — `boolean` (default `false`)
101
+ ### parallax — `boolean` (default `true`)
102
102
 
103
- Use when: 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.
103
+ Use when: 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. On by default, as every block's picture drifts: pass false for a card whose photograph must hold still under its button.
104
104
  Never for: Holding still for a reader who asked for less motion — it already holds still for them, and in a browser that cannot drive it from the scroll.
105
105
 
106
106
  ## Accessibility
@@ -33,7 +33,7 @@ Steps renders 15 elements. A token binding names the part it applies to.
33
33
  It paints one of the page's three grounds and the ink that goes with it, in one decision — see `ground`. The ink is set here and inherited by everything inside.
34
34
  - `inner` — `<div>`, class `dds-steps__inner`. The measure and the gutter, holding the section's content in the same column as every other section on the page.
35
35
  - `split` — `<div>`, class `dds-steps__split`. The two halves. A row at full width with the picture on whichever side `mediaPosition` names, and one column below the fold with the picture first — the order the reference design takes and the one a sequence reads in.
36
- - `media` — `<div>`, class `dds-steps__media`. The picture, and one of the section's two halves rather than an addition to it. Held to a square and cropped to fill it, so the section is the same shape whatever the page passes. With pictures on the steps it holds all of them in one cell, and the current step's is the one shown: the first while the list is still, each in turn under `autoplay`, crossing over in motion.duration.shift.
36
+ - `media` — `<div>`, class `dds-steps__media`. The picture, and one of the section's two halves rather than an addition to it. Held to a square and cropped to fill it, so the section is the same shape whatever the page passes. With pictures on the steps it holds all of them in one cell, and the current step's is the one shown: the first while the list is still, each in turn under `autoplay`, uncovered over one another in motion.duration.turn.
37
37
  - `image` — `<img>`, class `dds-steps__image`. One step's picture, from its `image`, stacked in the media cell with the others and shown while its step is current. Decoration, as the media cell is: what the step says is its title and its line.
38
38
  - `content` — `<div>`, class `dds-steps__content`. The title and the steps under it.
39
39
 
@@ -56,7 +56,7 @@ Steps renders 15 elements. A token binding names the part it applies to.
56
56
  - `step-title` — `<h3>`, class `dds-steps__step-title`. What the reader does at this step. `h3` under the section's `h2`, so a reader moving by heading walks the steps one at a time.
57
57
  - `trigger` — `<button>`, class `dds-steps__trigger`. Under `autoplay`, the step's title as a button: pressing it makes the step current and stops the turn for good. Inside the heading, so a reader moving by heading still walks the steps; `aria-current="step"` on the current one says which step is shown. Rendered only when `autoplay` is set.
58
58
  - `step-description` — `<p>`, class `dds-steps__step-description`. What the step involves, in a sentence.
59
- - `progress` — `<span>`, class `dds-steps__progress`. Under `autoplay`, the current step's rule filling from the start edge over motion.duration.dwell — how long until the next step — in the brand's coral. Held where the turn holds, full and still once it stops or for a reader who asked for less motion. Hidden from assistive technology: the current step is said by `aria-current`.
59
+ - `progress` — `<span>`, class `dds-steps__progress`. Under `autoplay`, the current step's rule filling from the start edge over motion.duration.step — how long until the next step — in the brand's coral. Held where the turn holds, full and still once it stops or for a reader who asked for less motion. Hidden from assistive technology: the current step is said by `aria-current`.
60
60
 
61
61
  Apply these classes from the generated `stepsParts` constant rather than writing the strings by hand.
62
62
 
@@ -90,8 +90,8 @@ Never for: Moving the picture on a phone. There is one column there and the pict
90
90
 
91
91
  ### autoplay — `boolean` (default `false`)
92
92
 
93
- Use when: Whether the steps take their turn on their own: one step is current, its rule fills over motion.duration.dwell, then the next takes over, and back to the first after the last. The current step's number is drawn at full strength and the rest recede; with an `image` on each step the picture changes to the current one's. Each step's title becomes a button that makes it current. Off by default, so a list nobody configured stays still. On, it holds while the pointer is over the section or focus is inside it, waits until the section is on screen, never moves for a reader who asked for less motion, and stops for good once the reader presses a step — the WAI carousel rule CtaBannerSlider follows: a reader who has chosen a step is reading it. That gives a reader a way to stop it, which WCAG 2.2.2 asks for, but not a visible one, so a page that needs 2.2.2 met plainly should leave it off.
94
- Never for: Steps whose descriptions take longer to read than motion.duration.dwell — leave it off rather than cut the copy to the timer. And a list with no picture per step, where the only thing that moves is a rule: the turn is worth it when the picture changes with it.
93
+ Use when: Whether the steps take their turn on their own: one step is current, its rule fills over motion.duration.step, then the next takes over, and back to the first after the last. The current step's number is drawn at full strength and the rest recede; with an `image` on each step the picture changes to the current one's. Each step's title becomes a button that makes it current. Off by default, so a list nobody configured stays still. On, it holds while focus is inside the section — not under the pointer, so it keeps turning while a reader watches it — waits until the section is on screen, never moves for a reader who asked for less motion, and stops for good once the reader presses a step — the WAI carousel rule CtaBannerSlider follows: a reader who has chosen a step is reading it. That gives a reader a way to stop it, which WCAG 2.2.2 asks for, but not a visible one, so a page that needs 2.2.2 met plainly should leave it off.
94
+ Never for: Steps whose descriptions take longer to read than motion.duration.step — leave it off rather than cut the copy to the timer. And a list with no picture per step, where the only thing that moves is a rule: the turn is worth it when the picture changes with it.
95
95
 
96
96
  ### title — `string` **Required.**
97
97
 
@@ -122,7 +122,7 @@ Guaranteed by the component. Write none of it by hand: the ids these attributes
122
122
  - The number drawn beside each step is decoration and is hidden from assistive technology. The ordered list already announces each step's place, and reading the drawn number as well says it twice
123
123
  - Each step's title is a heading one level under the section's, so a reader moving by heading walks the steps one at a time
124
124
  - The picture is decoration and is hidden from assistive technology — the one picture and every step's alike. What the section says is the title and the steps; a picture carrying something they do not is a section that reads as half empty to anyone who cannot see it
125
- - Under autoplay, a hold while the pointer is over the section or focus is inside it, no turn until the section is on screen, no movement for a reader who asked for less motion, and a stop for good once a step is pressed — the way to stop a list that would otherwise turn for as long as it is shown, which WCAG 2.2.2 asks for. The current step's title carries aria-current="step", so the step a reader is shown is the step a screen reader names
125
+ - Under autoplay, a hold while focus is inside the section, no turn until the section is on screen, no movement for a reader who asked for less motion, and a stop for good once a step is pressed — the way to stop a list that would otherwise turn for as long as it is shown, which WCAG 2.2.2 asks for. The current step's title carries aria-current="step", so the step a reader is shown is the step a screen reader names
126
126
 
127
127
  ## Rules
128
128
 
@@ -270,7 +270,7 @@ import { AspectRatio, Steps } from '@deriv-com/dds-marketing'
270
270
 
271
271
  ### Taking turns
272
272
 
273
- With `autoplay` each step takes its turn: its rule fills, its picture shows, then the next. Hover or focus holds it; pressing a step chooses it and stops the turn.
273
+ With `autoplay` each step takes its turn: its rule fills, its picture shows, then the next. Focus holds it, hovering does not; pressing a step stops it.
274
274
 
275
275
  ```tsx
276
276
  import { Steps } from '@deriv-com/dds-marketing'
@@ -280,6 +280,14 @@ import { Steps } from '@deriv-com/dds-marketing'
280
280
 
281
281
  // autoplay on the dark band
282
282
  <Steps title="Get started in three steps" steps={[{"title":"Open an account","description":"Sign up in minutes with your email and a few details.","image":{"src":"/sample-stop-brand.svg","alt":"","width":800,"height":600}},{"title":"Fund it","description":"Deposit with the local payment method you already use.","image":{"src":"/sample-stop-teal.svg","alt":"","width":800,"height":600}},{"title":"Start trading","description":"Pick a market and place your first trade.","image":{"src":"/sample-stop-amber.svg","alt":"","width":800,"height":600}}]} autoplay ground="strong" mediaPosition="end" />
283
+ ```
284
+
285
+ ### A picture on each step
286
+
287
+ Pictures on the steps without autoplay: the list stays still, the first step's picture is shown, and each title is a button that shows its own.
288
+
289
+ ```tsx
290
+ import { Steps } from '@deriv-com/dds-marketing'
283
291
 
284
292
  // still, a picture on each step — the first is shown
285
293
  <Steps title="Get started in three steps" steps={[{"title":"Open an account","description":"Sign up in minutes with your email and a few details.","image":{"src":"/sample-stop-brand.svg","alt":"","width":800,"height":600}},{"title":"Fund it","description":"Deposit with the local payment method you already use.","image":{"src":"/sample-stop-teal.svg","alt":"","width":800,"height":600}},{"title":"Start trading","description":"Pick a market and place your first trade.","image":{"src":"/sample-stop-amber.svg","alt":"","width":800,"height":600}}]} />
@@ -51,7 +51,7 @@ A dark card in the middle of a page, pairing a headline and a line under it with
51
51
  - never: A second action. The card asks for one thing
52
52
  - **media** `node` · required — 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
53
53
  - never: Artwork with words in it. The cover photograph sits under a scrim and the text, and the split crops to fill
54
- - **parallax** `boolean` · default false — 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. …
54
+ - **parallax** `boolean` · default true — 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. …
55
55
  - never: Holding still for a reader who asked for less motion — it already holds still for them, and in a browser that cannot drive it from the scroll
56
56
 
57
57
  ## Inside it
@@ -36,8 +36,8 @@ A two-column how-it-works section pairing a supporting picture with a numbered,
36
36
  - `start` — The default, and the reference design's: the picture first, then the steps it introduces
37
37
  - `end` — The steps first, for a section sitting under a split whose picture is already at the start
38
38
  - never: Moving the picture on a phone. There is one column there and the picture leads it
39
- - **autoplay** `boolean` · default false — Whether the steps take their turn on their own: one step is current, its rule fills over motion.duration.dwell, then the next takes over, and back to the first after the last. …
40
- - never: Steps whose descriptions take longer to read than motion.duration.dwell — leave it off rather than cut the copy to the timer. And a list with no picture per step, where the only thing that moves is a rule: the turn is worth it when the picture changes with it
39
+ - **autoplay** `boolean` · default false — Whether the steps take their turn on their own: one step is current, its rule fills over motion.duration.step, then the next takes over, and back to the first after the last. …
40
+ - never: Steps whose descriptions take longer to read than motion.duration.step — leave it off rather than cut the copy to the timer. And a list with no picture per step, where the only thing that moves is a rule: the turn is worth it when the picture changes with it
41
41
  - **title** `string` · required — Always. What the steps get a reader to, in the fewest words that say it — 'Get started in 3 steps'
42
42
  - never: A sentence. The steps underneath are the argument; this only has to name where they lead
43
43
  - **steps** `data` · required — Always. The steps, in the order a reader takes them — three or four is right, six the most. Each is a short imperative and a sentence under about two hundred characters. …
package/.llm/tokens.md CHANGED
@@ -392,6 +392,7 @@ one value at every width.
392
392
  - `--motion-duration-slow` — Large UI transitions, e.g. accordions expanding, page transitions, interactive demos.
393
393
  - `--motion-duration-spinner` — One full rotation of a spinner. Fast enough to read as working, slow enough that the stroke does not blur into a solid ring.
394
394
  - `--motion-duration-stagger` — The step between one part of a section arriving and the next. A gap rather than a length: five parts at this step trail the first by less than half a second, so a block still arrives as one thing rather than assembling itself while a reader waits.
395
+ - `--motion-duration-step` — How long one step holds before the next takes its turn, in a Steps list under autoplay. Longer than dwell: a step is a title, a sentence and a picture to take in, not a headline to glance at, and at dwell's pace the list moved on before a reader had finished the line.
395
396
  - `--motion-duration-ticker` — How long a running row takes to carry one item past — Awards' row on a phone, multiplied by the number of awards for one whole loop. The reference site's pace: thirty-two seconds for eight awards, slow enough to read an award as it goes by.
396
397
  - `--motion-duration-trail` — How long something takes to catch up with a pointer it trails — a background's glow following the pointer across a band. Half a second, so the light drifts after the hand rather than sticking to it: a trail is felt, a cursor is tracked.
397
398
  - `--motion-duration-turn` — One turn of a wheel of figures: the one in place rising away as the next rises into it. The reference site's 700 — long enough that the eye follows the number in rather than seeing it swap.
@@ -406,9 +407,12 @@ one value at every width.
406
407
  - `--motion-scale-enter` — What a component scales from when something reveals it — a tooltip appearing. Needs a transform origin on the edge nearest its anchor, or it grows out of its own middle. The counterpart to scale.hover, which is a response rather than an entrance.
407
408
  - `--motion-scale-hover` — How much a control grows under the cursor on hover. Deliberately small — a control that grows past the pointer fires its own mouse-out.
408
409
  - `--motion-scale-media-hover` — How much art grows inside a frame that holds still — an editorial card's picture under the cursor. Twice motion.scale.hover, which stays smaller because a control can outrun its own pointer and a framed picture cannot.
410
+ - `--motion-scale-parallax` — How much larger a block's picture is drawn inside its frame, so it can drift with the scroll without its edge showing. A tenth: room for the parallax travel either way with a margin to spare, and too little to feel like a crop.
409
411
  - `--motion-scale-pile-covered` — How far a card in a StickyCards pile draws back once the next card has slid over it, from its top edge, so the pile reads as depth. Not motion.scale.enter, which is for something arriving.
410
412
  - `--motion-translate-enter` — Where a surface waits before something reveals it: level on the inline axis, its own full height down the block one. A share of the element rather than of the page, so a sheet travels exactly its own height whatever that turns out to be.
411
413
  - `--motion-translate-hover` — How far a pressable surface rises under the cursor. A fixed distance rather than a share of the element, so a tall card and a short one lift by the same amount and a row of them stays level. Small enough that nothing it sits beside looks disturbed.
414
+ - `--motion-translate-parallax-from` — Where a block's picture stands in its frame as the frame enters the view from below: a little above centre, as a share of its own height. It runs to parallax-to as the frame crosses the view, so the picture moves slower than the page and reads as further away.
415
+ - `--motion-translate-parallax-to` — Where a block's picture stands as its frame leaves the view at the top: a little below centre. The pair stays inside the room motion.scale.parallax gives, so no edge ever shows.
412
416
  - `--motion-translate-reveal` — How far a section waits below where it lands, before the scroll brings it up. A fixed distance rather than a share of the block, so a hero a screen tall and a split half that arrive by the same movement. Pairs with motion.easing.reveal.
413
417
 
414
418
  ## opacity
@@ -0,0 +1,62 @@
1
+ /*
2
+ * The drift every block's picture shares, defined once.
3
+ *
4
+ * As a block passes through the view its picture moves a little slower than the page, so it reads
5
+ * as further away than the words beside it — the reference design's parallax, on every block that
6
+ * holds a picture rather than as an option one banner had. A copy per block is how ten sections
7
+ * come to disagree about how far a picture moves, so the movement is stated here and each block
8
+ * only marks its frame: `data-dds-parallax` on the box that holds the picture and clips it.
9
+ *
10
+ * Imported by each block's component, which is what puts it in the bundle once however many blocks
11
+ * a page uses.
12
+ *
13
+ * Driven by the scroll and nothing else. The frame names a view timeline and the picture runs down
14
+ * it over the frame's whole pass across the view (`cover`), from motion.translate.parallax-from to
15
+ * motion.translate.parallax-to. No script measures anything and no listener runs on scroll: the
16
+ * browser advances the timeline itself, and the only properties it animates are `translate` and
17
+ * `scale`, which it composites without laying the page out or painting it again — so a page full
18
+ * of pictures scrolls as smoothly as a page with none. Linear, because the scroll is the easing.
19
+ *
20
+ * The picture is drawn at motion.scale.parallax inside a frame that clips it, which leaves room
21
+ * above and below for the travel, so no edge of the picture is ever seen. The frame keeps its size,
22
+ * so nothing around it moves.
23
+ *
24
+ * The frame clips with `overflow: clip`, not `hidden`. `hidden` makes a box a scroll container, and
25
+ * a view timeline is measured against its nearest one: under a box that never scrolls it stands
26
+ * still at its first frame. The same holds for every box above the frame, which is why the blocks
27
+ * that carry one clip their roots rather than hiding their overflow.
28
+ *
29
+ * Every picture inside the frame drifts, except one inside an IllustrationCard: the card is a
30
+ * picture of the product laid over the photograph, and the photograph moving behind it is the
31
+ * effect; the product's own icons sliding inside the card would be the card coming apart.
32
+ *
33
+ * Only where the browser can drive it from the scroll, and only for a reader who has not asked for
34
+ * less motion. Everywhere else the picture stands still, centred and at its own size.
35
+ */
36
+ /* FeatureCards' pictures are drawn by CardBody, a core component that marks nothing, so its frame is
37
+ named here for the one block that wants it moving. */
38
+ :where([data-dds-context="marketing"]) :is([data-dds-parallax], .dds-featurecards .dds-cardbody__media-frame) {
39
+ overflow: clip;
40
+ view-timeline-axis: block;
41
+ view-timeline-name: --dds-parallax;
42
+ }
43
+
44
+ @supports (animation-timeline: view()) {
45
+ @media (prefers-reduced-motion: no-preference) {
46
+ :where([data-dds-context="marketing"]) :is([data-dds-parallax], .dds-featurecards .dds-cardbody__media-frame) img:not(.dds-illustrationcard img) {
47
+ animation: dds-parallax linear both;
48
+ animation-range: cover;
49
+ animation-timeline: --dds-parallax;
50
+ scale: var(--motion-scale-parallax);
51
+ }
52
+ }
53
+ }
54
+
55
+ @keyframes dds-parallax {
56
+ from {
57
+ translate: var(--motion-translate-parallax-from);
58
+ }
59
+ to {
60
+ translate: var(--motion-translate-parallax-to);
61
+ }
62
+ }
@@ -1,6 +1,7 @@
1
1
  import './DownloadBanner.css';
2
2
  import './download-banner-impl.css';
3
3
  import '../block-reveal.css';
4
+ import '../block-parallax.css';
4
5
  import { type DownloadBannerProps } from './DownloadBanner.types.js';
5
6
  export type { DownloadBannerProps };
6
7
  /**
@@ -5,6 +5,7 @@ import { AndroidApkFillColoredIcon, AppleAppStoreFillColoredIcon, AppleDmgFillCo
5
5
  import './DownloadBanner.css';
6
6
  import './download-banner-impl.css';
7
7
  import '../block-reveal.css';
8
+ import '../block-parallax.css';
8
9
  import { Background } from '../../core/background/Background.js';
9
10
  import { given } from '../../given.js';
10
11
  import { arriving, useReveal } from '../use-reveal.js';
@@ -71,5 +72,5 @@ export function DownloadBanner({ ground = downloadBannerDefaults.ground, variant
71
72
  builds of the same desktop app — and a key repeated is a badge React confuses
72
73
  with the other. */
73
74
  _jsx("li", { className: downloadBannerParts.badge, children: _jsx("a", { className: downloadBannerParts['badge-link'], href: href, "aria-label": given(label) ? label : name, children: _jsx(Mark, { className: downloadBannerParts.mark, width: undefined, height: undefined, "aria-hidden": "true", focusable: "false" }) }) }, `${platform}-${at}`));
74
- }) })) : null, variant === 'featured' && media !== undefined && media !== null ? (_jsx("div", { className: downloadBannerParts.media, ...arriving(3), children: media })) : null] }) }) }));
75
+ }) })) : null, variant === 'featured' && media !== undefined && media !== null ? (_jsx("div", { "data-dds-parallax": "", className: downloadBannerParts.media, ...arriving(3), children: media })) : null] }) }) }));
75
76
  }
@@ -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
  }
@@ -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, 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. 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 };
@@ -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
  /**
@@ -4,6 +4,7 @@ import { useId } from 'react';
4
4
  import './Steps.css';
5
5
  import './steps-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 { useTurn } from './use-turn.js';
@@ -41,11 +42,11 @@ export function Steps({ ground = stepsDefaults.ground, mediaPosition = stepsDefa
41
42
  // records as they are typed, and a number against nothing is a step the page did not write — so
42
43
  // it is dropped before the count is taken, and the numbers close up rather than skipping one.
43
44
  const shown = steps.filter((step) => given(step.title));
44
- const { current, turning, playing, next, choose, section, hold } = useTurn(shown.length, autoplay);
45
+ const { current, going, turning, playing, next, choose, section, hold } = useTurn(shown.length, autoplay);
45
46
  // The steps' own pictures replace the one picture only where every step has one: a turn that
46
47
  // reached a step without a picture would show nothing beside it.
47
48
  const pictured = shown.length > 0 && shown.every((step) => given(step.image?.src));
48
- return (_jsx("section", { className: stepsParts.root, ref: section, "data-ground": ground, "data-media-position": mediaPosition, "data-autoplay": autoplay || undefined, "aria-labelledby": titleId, ...(autoplay ? hold : {}), children: _jsx("div", { className: stepsParts.inner, ref: band, "data-revealed": revealed, children: _jsxs("div", { className: stepsParts.split, children: [_jsx("div", { className: stepsParts.media, ...arriving(0), "aria-hidden": "true", children: pictured
49
+ return (_jsx("section", { className: stepsParts.root, ref: section, "data-ground": ground, "data-media-position": mediaPosition, "data-autoplay": autoplay || undefined, "data-going": going, "aria-labelledby": titleId, ...(autoplay ? hold : {}), children: _jsx("div", { className: stepsParts.inner, ref: band, "data-revealed": revealed, children: _jsxs("div", { className: stepsParts.split, children: [_jsx("div", { "data-dds-parallax": "", className: stepsParts.media, ...arriving(0), "aria-hidden": "true", children: pictured
49
50
  ? shown.map((step, at) => (_jsx("img", { className: stepsParts.image, src: step.image?.src, alt: "", width: step.image?.width, height: step.image?.height, "data-current": at === current, loading: at === 0 ? undefined : 'lazy' }, `${at}:${step.title}`)))
50
51
  : media }), _jsxs("div", { className: stepsParts.content, children: [_jsx("h2", { className: stepsParts.title, ...arriving(0), id: titleId, children: title }), _jsx("ol", { className: stepsParts.list, role: "list", "data-turning": autoplay ? turning : undefined, "data-playing": autoplay ? playing : undefined, children: shown.map((step, at) => (
51
52
  /* Keyed by the step's own name. An index would key the list by position, so
@@ -12,7 +12,7 @@ export interface StepsOwnProps {
12
12
  * `end` — The steps first, for a section sitting under a split whose picture is already at the start
13
13
  */
14
14
  mediaPosition?: 'start' | 'end';
15
- /** Whether the steps take their turn on their own: one step is current, its rule fills over motion.duration.dwell, then the next takes over, and back to the first after the last. The current step's number is drawn at full strength and the rest recede; with an `image` on each step the picture changes to the current one's. Each step's title becomes a button that makes it current. Off by default, so a list nobody configured stays still. On, it holds while the pointer is over the section or focus is inside it, waits until the section is on screen, never moves for a reader who asked for less motion, and stops for good once the reader presses a step — the WAI carousel rule CtaBannerSlider follows: a reader who has chosen a step is reading it. That gives a reader a way to stop it, which WCAG 2.2.2 asks for, but not a visible one, so a page that needs 2.2.2 met plainly should leave it off. */
15
+ /** Whether the steps take their turn on their own: one step is current, its rule fills over motion.duration.step, then the next takes over, and back to the first after the last. The current step's number is drawn at full strength and the rest recede; with an `image` on each step the picture changes to the current one's. Each step's title becomes a button that makes it current. Off by default, so a list nobody configured stays still. On, it holds while focus is inside the section — not under the pointer, so it keeps turning while a reader watches it — waits until the section is on screen, never moves for a reader who asked for less motion, and stops for good once the reader presses a step — the WAI carousel rule CtaBannerSlider follows: a reader who has chosen a step is reading it. That gives a reader a way to stop it, which WCAG 2.2.2 asks for, but not a visible one, so a page that needs 2.2.2 met plainly should leave it off. */
16
16
  autoplay?: boolean;
17
17
  /** Always. What the steps get a reader to, in the fewest words that say it — 'Get started in 3 steps' */
18
18
  title: string;
@@ -152,7 +152,7 @@
152
152
  }
153
153
 
154
154
  /*
155
- * The fill: the current step's rule, drawn from the start edge over motion.duration.dwell. When it
155
+ * The fill: the current step's rule, drawn from the start edge over motion.duration.step. When it
156
156
  * ends the next step takes over — the component listens for the end, so the bar and the turn are
157
157
  * one clock. Paused wherever the turn is held; full and still where the turn has stopped or never
158
158
  * runs, so the current step is still marked.
@@ -175,7 +175,7 @@
175
175
  }
176
176
 
177
177
  :where([data-dds-context="marketing"]) .dds-steps .dds-steps__list[data-turning="true"] > .dds-steps__step[data-current="true"] > .dds-steps__progress {
178
- animation: dds-steps-fill var(--motion-duration-dwell) linear;
178
+ animation: dds-steps-fill var(--motion-duration-step) linear;
179
179
  }
180
180
 
181
181
  :where([data-dds-context="marketing"]) .dds-steps .dds-steps__list[data-playing="false"] > .dds-steps__step[data-current="true"] > .dds-steps__progress {
@@ -184,7 +184,20 @@
184
184
 
185
185
  /*
186
186
  * The steps' own pictures share the media cell, and the current step's is the one shown — the
187
- * first while the list is still. They cross over rather than cut, in motion.duration.shift.
187
+ * first while the list is still.
188
+ *
189
+ * The next is uncovered over the last rather than faded into it, FeatureScroll's change. A fade put
190
+ * both pictures on screen at half strength at once — a double exposure that read as the two
191
+ * overlapping. Uncovered from an edge, every point of the square is one picture or the other at full
192
+ * strength: up from the foot as the turn moves on, the wrap to the first step included, and down
193
+ * from the head when a reader presses an earlier step.
194
+ *
195
+ * An animation rather than a transition, because an animation starts from its first frame and a
196
+ * transition from wherever the picture last rested. The current picture is brought to the front and
197
+ * uncovered over motion.duration.turn — the figure-to-figure step a turning set already takes — and
198
+ * the one it replaces stays beneath, whole, for that long, then goes: a zero-length transition
199
+ * delayed by the same role, so the two cannot drift into a gap. Every other picture is already gone.
200
+ * No `data-going` before the first change, so the picture a reader opens on is simply there.
188
201
  */
189
202
  :where([data-dds-context="marketing"]) .dds-steps .dds-steps__media:has(> .dds-steps__image) {
190
203
  display: grid;
@@ -193,11 +206,66 @@
193
206
  :where([data-dds-context="marketing"]) .dds-steps .dds-steps__media > .dds-steps__image {
194
207
  grid-area: 1 / 1;
195
208
  opacity: 0;
196
- transition-property: opacity;
209
+ transition: opacity 0s linear var(--motion-duration-turn);
197
210
  }
198
211
 
199
212
  :where([data-dds-context="marketing"]) .dds-steps .dds-steps__media > .dds-steps__image[data-current="true"] {
200
213
  opacity: 1;
214
+ transition-delay: 0s;
215
+ z-index: 1;
216
+ }
217
+
218
+ :where([data-dds-context="marketing"]) .dds-steps[data-going="on"] .dds-steps__media > .dds-steps__image[data-current="true"] {
219
+ animation: dds-steps-from-foot var(--motion-duration-turn) var(--motion-easing-reveal);
220
+ }
221
+
222
+ :where([data-dds-context="marketing"]) .dds-steps[data-going="back"] .dds-steps__media > .dds-steps__image[data-current="true"] {
223
+ animation: dds-steps-from-head var(--motion-duration-turn) var(--motion-easing-reveal);
224
+ }
225
+
226
+ /*
227
+ * The wipe and the parallax together. The current picture's wipe is an `animation`, and so is the
228
+ * drift block-parallax.css gives every picture in the frame — one shorthand would replace the other,
229
+ * and the current picture, the only one a reader sees, would stop drifting the moment it arrived. So
230
+ * both are listed here: the wipe on the document's clock, the drift on the frame's view timeline,
231
+ * where the browser can drive one and the reader has not asked for less motion.
232
+ */
233
+ @supports (animation-timeline: view()) {
234
+ @media (prefers-reduced-motion: no-preference) {
235
+ :where([data-dds-context="marketing"]) .dds-steps[data-going="on"] .dds-steps__media > .dds-steps__image[data-current="true"] {
236
+ animation:
237
+ dds-steps-from-foot var(--motion-duration-turn) var(--motion-easing-reveal),
238
+ dds-parallax linear both;
239
+ animation-range: normal, cover;
240
+ animation-timeline: auto, --dds-parallax;
241
+ }
242
+
243
+ :where([data-dds-context="marketing"]) .dds-steps[data-going="back"] .dds-steps__media > .dds-steps__image[data-current="true"] {
244
+ animation:
245
+ dds-steps-from-head var(--motion-duration-turn) var(--motion-easing-reveal),
246
+ dds-parallax linear both;
247
+ animation-range: normal, cover;
248
+ animation-timeline: auto, --dds-parallax;
249
+ }
250
+ }
251
+ }
252
+
253
+ @keyframes dds-steps-from-foot {
254
+ from {
255
+ clip-path: inset(100% 0 0 0);
256
+ }
257
+ to {
258
+ clip-path: inset(0);
259
+ }
260
+ }
261
+
262
+ @keyframes dds-steps-from-head {
263
+ from {
264
+ clip-path: inset(0 0 100% 0);
265
+ }
266
+ to {
267
+ clip-path: inset(0);
268
+ }
201
269
  }
202
270
 
203
271
  /*
@@ -213,4 +281,8 @@
213
281
  :where([data-dds-context="marketing"]) .dds-steps[data-autoplay] .dds-steps__number {
214
282
  transition: none;
215
283
  }
284
+
285
+ :where([data-dds-context="marketing"]) .dds-steps .dds-steps__media > .dds-steps__image[data-current="true"] {
286
+ animation: none;
287
+ }
216
288
  }
@@ -1,9 +1,9 @@
1
- import { type FocusEvent, type PointerEvent } from 'react';
1
+ import { type FocusEvent } from 'react';
2
2
  /**
3
3
  * The steps taking their turn under `autoplay`: which step is current, and whether the turn is
4
4
  * running.
5
5
  *
6
- * The timing is not here. The current step's rule fills over motion.duration.dwell as a CSS
6
+ * The timing is not here. The current step's rule fills over motion.duration.step as a CSS
7
7
  * animation, and the turn moves on when that animation ends (`next`), so the bar a reader watches
8
8
  * and the step changing are one clock rather than two that drift. Holding the turn is pausing that
9
9
  * animation, which is what `playing` is for: the stylesheet pauses the fill wherever it is false.
@@ -12,21 +12,21 @@ import { type FocusEvent, type PointerEvent } from 'react';
12
12
  * - `autoplay` is on and there are two steps or more;
13
13
  * - the reader has not asked for less motion — asked live, so turning it on mid-visit stops it;
14
14
  * - the section is on screen. A turn nobody can see does not run, and starts again where it was;
15
- * - nothing holds it: a pointer over the section or focus inside it holds it for as long as it
16
- * lasts, and pressing a step stops it for good — CtaBannerSlider's rule, by way of the WAI
17
- * carousel pattern: a reader who has chosen a step is reading it. The pointer's way, the
18
- * keyboard's and a touch screen's, which WCAG 2.2.2 asks for of anything that moves on its own.
15
+ * - nothing holds it: focus inside the section holds it for as long as it lasts, and pressing a
16
+ * step stops it for good — a reader who has chosen a step is reading it. The keyboard's way and
17
+ * the pointer's and a touch screen's, which WCAG 2.2.2 asks for of anything that moves on its own.
18
+ * A pointer merely resting over the section does not hold it: the list is meant to be watched as
19
+ * it turns, and pausing whenever the pointer crossed it read as the list having stopped.
19
20
  */
20
21
  export declare function useTurn(count: number, autoplay: boolean): {
21
22
  current: number;
23
+ going: "on" | "back" | undefined;
22
24
  turning: boolean;
23
25
  playing: boolean;
24
26
  next: () => void;
25
27
  choose: (at: number) => void;
26
28
  section: import("react").RefObject<HTMLElement | null>;
27
29
  hold: {
28
- onPointerEnter: (event: PointerEvent<HTMLElement>) => void;
29
- onPointerLeave: (event: PointerEvent<HTMLElement>) => void;
30
30
  onFocus: () => void;
31
31
  onBlur: (event: FocusEvent<HTMLElement>) => void;
32
32
  };
@@ -4,7 +4,7 @@ import { useCallback, useEffect, useRef, useState } from 'react';
4
4
  * The steps taking their turn under `autoplay`: which step is current, and whether the turn is
5
5
  * running.
6
6
  *
7
- * The timing is not here. The current step's rule fills over motion.duration.dwell as a CSS
7
+ * The timing is not here. The current step's rule fills over motion.duration.step as a CSS
8
8
  * animation, and the turn moves on when that animation ends (`next`), so the bar a reader watches
9
9
  * and the step changing are one clock rather than two that drift. Holding the turn is pausing that
10
10
  * animation, which is what `playing` is for: the stylesheet pauses the fill wherever it is false.
@@ -13,13 +13,21 @@ import { useCallback, useEffect, useRef, useState } from 'react';
13
13
  * - `autoplay` is on and there are two steps or more;
14
14
  * - the reader has not asked for less motion — asked live, so turning it on mid-visit stops it;
15
15
  * - the section is on screen. A turn nobody can see does not run, and starts again where it was;
16
- * - nothing holds it: a pointer over the section or focus inside it holds it for as long as it
17
- * lasts, and pressing a step stops it for good — CtaBannerSlider's rule, by way of the WAI
18
- * carousel pattern: a reader who has chosen a step is reading it. The pointer's way, the
19
- * keyboard's and a touch screen's, which WCAG 2.2.2 asks for of anything that moves on its own.
16
+ * - nothing holds it: focus inside the section holds it for as long as it lasts, and pressing a
17
+ * step stops it for good — a reader who has chosen a step is reading it. The keyboard's way and
18
+ * the pointer's and a touch screen's, which WCAG 2.2.2 asks for of anything that moves on its own.
19
+ * A pointer merely resting over the section does not hold it: the list is meant to be watched as
20
+ * it turns, and pausing whenever the pointer crossed it read as the list having stopped.
20
21
  */
21
22
  export function useTurn(count, autoplay) {
22
23
  const [current, setCurrent] = useState(0);
24
+ /*
25
+ Which way the last change went, for the picture to be uncovered from the matching edge: on, from
26
+ the foot, for every turn — the wrap from the last step to the first included, because the turn
27
+ still moves on — and back, from the head, when a reader presses an earlier step. Absent until the
28
+ first change, so the picture a reader opens on is simply there.
29
+ */
30
+ const [going, setGoing] = useState(undefined);
23
31
  const [seen, setSeen] = useState(false);
24
32
  const [still, setStill] = useState(false);
25
33
  const [held, setHeld] = useState(false);
@@ -46,24 +54,19 @@ export function useTurn(count, autoplay) {
46
54
  }, [autoplay]);
47
55
  /** The current step's fill has run: the next step takes over, and the first after the last. */
48
56
  const next = useCallback(() => {
49
- if (count > 0)
57
+ if (count > 0) {
58
+ setGoing('on');
50
59
  setCurrent((at) => (at + 1) % count);
60
+ }
51
61
  }, [count]);
52
62
  /** A step pressed: it is current, and the turn stops for good. */
53
63
  const choose = useCallback((at) => {
64
+ setGoing(at >= current ? 'on' : 'back');
54
65
  setCurrent(at);
55
66
  setStopped(true);
56
- }, []);
67
+ }, [current]);
57
68
  /** Spread on the section: what holds the turn. */
58
69
  const hold = {
59
- onPointerEnter: (event) => {
60
- if (event.pointerType === 'mouse')
61
- setHeld(true);
62
- },
63
- onPointerLeave: (event) => {
64
- if (event.pointerType === 'mouse')
65
- setHeld(false);
66
- },
67
70
  onFocus: () => setHeld(true),
68
71
  onBlur: (event) => {
69
72
  if (!event.currentTarget.contains(event.relatedTarget))
@@ -71,5 +74,5 @@ export function useTurn(count, autoplay) {
71
74
  },
72
75
  };
73
76
  // A list that shrank under the current step falls back to its first.
74
- return { current: current < count ? current : 0, turning, playing, next, choose, section, hold };
77
+ return { current: current < count ? current : 0, going, turning, playing, next, choose, section, hold };
75
78
  }
@@ -1,6 +1,7 @@
1
1
  import './TeamGrid.css';
2
2
  import './team-grid-impl.css';
3
3
  import '../block-reveal.css';
4
+ import '../block-parallax.css';
4
5
  import { type TeamGridProps } from './TeamGrid.types.js';
5
6
  export type { TeamGridProps };
6
7
  /**
@@ -4,6 +4,7 @@ import { useId, useState } from 'react';
4
4
  import './TeamGrid.css';
5
5
  import './team-grid-impl.css';
6
6
  import '../block-reveal.css';
7
+ import '../block-parallax.css';
7
8
  import { given } from '../../given.js';
8
9
  import { Button } from '../../core/button/Button.js';
9
10
  import { Link } from '../../core/link/Link.js';
@@ -69,6 +70,6 @@ export function TeamGrid({ ground = teamGridDefaults.ground, title, description,
69
70
  style: { '--dds-teamgrid-extra-at': at - shown },
70
71
  }
71
72
  : arriving(Math.min(FIRST_PERSON + at, LAST_STEP));
72
- return (_jsxs("li", { className: teamGridParts.item, hidden: extra && !expanded, ...motion, children: [_jsx("div", { className: teamGridParts.media, children: _jsx(AspectRatio, { ratio: "square", children: _jsx("img", { src: person.image.src, alt: person.image.alt, loading: "lazy", decoding: "async" }) }) }), _jsxs("div", { className: teamGridParts.text, children: [_jsx("h3", { className: teamGridParts.name, children: person.name }), _jsx("p", { className: teamGridParts.role, children: person.role }), person.profile !== undefined ? (_jsx("div", { className: teamGridParts.profile, children: _jsx(Link, { href: person.profile.href, external: true, tone: inverse ? 'inherit' : 'brand', "aria-label": `${profileName(person)} ${externalLabel}`, children: person.profile.label }) })) : null] })] }, `${person.name}-${at}`));
73
+ return (_jsxs("li", { className: teamGridParts.item, hidden: extra && !expanded, ...motion, children: [_jsx("div", { "data-dds-parallax": "", className: teamGridParts.media, children: _jsx(AspectRatio, { ratio: "square", children: _jsx("img", { src: person.image.src, alt: person.image.alt, loading: "lazy", decoding: "async" }) }) }), _jsxs("div", { className: teamGridParts.text, children: [_jsx("h3", { className: teamGridParts.name, children: person.name }), _jsx("p", { className: teamGridParts.role, children: person.role }), person.profile !== undefined ? (_jsx("div", { className: teamGridParts.profile, children: _jsx(Link, { href: person.profile.href, external: true, tone: inverse ? 'inherit' : 'brand', "aria-label": `${profileName(person)} ${externalLabel}`, children: person.profile.label }) })) : null] })] }, `${person.name}-${at}`));
73
74
  }) }), longer ? (_jsx("div", { className: teamGridParts.actions, ...arriving(Math.min(FIRST_PERSON + shown, LAST_STEP + 1)), children: _jsx(Button, { variant: "secondary", size: "md", tone: inverse ? 'inverse' : 'default', "aria-expanded": expanded, "aria-controls": listId, onClick: () => setExpanded((open) => !open), children: expanded ? collapseLabel : expandLabel }) })) : null] })] }) }));
74
75
  }
@@ -4,7 +4,7 @@
4
4
  box-sizing: border-box;
5
5
  display: flex;
6
6
  flex-direction: column;
7
- overflow: hidden;
7
+ overflow: clip;
8
8
  border-radius: var(--radius-surface);
9
9
  }
10
10
 
@@ -44,3 +44,18 @@
44
44
  :where([data-dds-context="marketing"]) .dds-illustrationscene[data-ratio='upright'] > .dds-illustrationcard {
45
45
  inline-size: 78%;
46
46
  }
47
+
48
+ /*
49
+ * At the foot, the card is a strip across the frame rather than a pane in the middle of it: as wide
50
+ * as the frame less the inset either side, and only as tall as what it holds — the card's own shape
51
+ * would stand a square of glass over most of the photograph, where the point of the foot is to leave
52
+ * the subject above it clear. The same inset below it as either side.
53
+ *
54
+ * After the per-shape widths, which it replaces, and only here: a centred card keeps its shape.
55
+ */
56
+ :where([data-dds-context="marketing"]) .dds-illustrationscene[data-placement='bottom'] > .dds-illustrationcard {
57
+ aspect-ratio: auto;
58
+ inline-size: auto;
59
+ justify-self: stretch;
60
+ margin-block-end: var(--space-inset-card);
61
+ }
@@ -238,6 +238,7 @@
238
238
  --motion-duration-slow: 300ms;
239
239
  --motion-duration-spinner: 500ms;
240
240
  --motion-duration-stagger: 100ms;
241
+ --motion-duration-step: 8000ms;
241
242
  --motion-duration-ticker: 4000ms;
242
243
  --motion-duration-trail: 500ms;
243
244
  --motion-duration-turn: 700ms;
@@ -252,9 +253,12 @@
252
253
  --motion-scale-enter: 0.96;
253
254
  --motion-scale-hover: 1.02;
254
255
  --motion-scale-media-hover: 1.04;
256
+ --motion-scale-parallax: 1.1;
255
257
  --motion-scale-pile-covered: 0.96;
256
258
  --motion-translate-enter: 0 100%;
257
259
  --motion-translate-hover: 0 -4px;
260
+ --motion-translate-parallax-from: 0 -4%;
261
+ --motion-translate-parallax-to: 0 4%;
258
262
  --motion-translate-reveal: 0 24px;
259
263
  --opacity-disabled: 0.22;
260
264
  --opacity-dot-current: 1;
@@ -591,6 +595,7 @@
591
595
  --motion-duration-slow: 300ms;
592
596
  --motion-duration-spinner: 500ms;
593
597
  --motion-duration-stagger: 100ms;
598
+ --motion-duration-step: 8000ms;
594
599
  --motion-duration-ticker: 4000ms;
595
600
  --motion-duration-trail: 500ms;
596
601
  --motion-duration-turn: 700ms;
@@ -605,9 +610,12 @@
605
610
  --motion-scale-enter: 0.96;
606
611
  --motion-scale-hover: 1.02;
607
612
  --motion-scale-media-hover: 1.04;
613
+ --motion-scale-parallax: 1.1;
608
614
  --motion-scale-pile-covered: 0.96;
609
615
  --motion-translate-enter: 0 100%;
610
616
  --motion-translate-hover: 0 -4px;
617
+ --motion-translate-parallax-from: 0 -4%;
618
+ --motion-translate-parallax-to: 0 4%;
611
619
  --motion-translate-reveal: 0 24px;
612
620
  --opacity-disabled: 0.22;
613
621
  --opacity-dot-current: 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deriv-com/dds-marketing",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "Deriv Design System — components and tokens for marketing sites (landing pages, pricing, sign-up).",
5
5
  "license": "MIT",
6
6
  "author": {