@deriv-com/dds-marketing 1.2.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -33,7 +33,7 @@ Everything inside is illustrative: built from the system's real components so it
33
33
 
34
34
  IllustrationCard renders 3 elements. A token binding names the part it applies to.
35
35
 
36
- - `root` — `<div>`, class `dds-illustrationcard`. The pane: one of three proportions whatever is put in it — the aspect tokens — and the glass. It is drawn in the dark theme whatever the page's, so the product it shows looks the same on every page. Its fill is color.bg.surface-glass — a faint frosted white — laid over the dark page's ground when it stands alone and over the photograph in a scene, over a blur of size.backdrop-blur in illustration-card-impl.css, because a blur is a function no binding can name. It holds radius.surface and space.inset.card like a Card, a hairline edge so its outline survives on a light page, and the default ink, color.fg.default — light on the frosted pane. It is one image to assistive technology — `role="img"`, named by `label` — so a screen reader hears what the picture shows in a sentence rather than a list of controls that are not there. It is the host the arrivals inside it follow (`data-arrival-host`): it plays once when it has landed — well into view, or brought in by its block — and every part with a motion of its own waits for it, so the photograph lands, then the pane, then what moves on it.
36
+ - `root` — `<div>`, class `dds-illustrationcard`. The pane: one of three proportions whatever is put in it — the aspect tokens — and the glass. It is drawn in the dark theme whatever the page's, so the product it shows looks the same on every page. Its fill is color.bg.surface-glass — a faint frosted white — laid over the dark page's ground when it stands alone and over the photograph under a scrim of color.bg.overlay in a scene, so the light ink reads over a light photograph too, over a blur of size.backdrop-blur in illustration-card-impl.css, because a blur is a function no binding can name. It holds radius.surface and space.inset.card like a Card, a hairline edge so its outline survives on a light page, and the default ink, color.fg.default — light on the frosted pane. It is one image to assistive technology — `role="img"`, named by `label` — so a screen reader hears what the picture shows in a sentence rather than a list of controls that are not there. It is the host the arrivals inside it follow (`data-arrival-host`): it plays once when it has landed — well into view, or brought in by its block — and every part with a motion of its own waits for it, so the photograph lands, then the pane, then what moves on it.
37
37
  - `content` — `<div>`, class `dds-illustrationcard__content`. What the pane holds, made a picture. It is `inert`: whatever components draw it — a Button, a SegmentedControl, a TextInput — look exactly as they do anywhere, and none of them can be focused, tabbed to, pressed or typed into, so a reader never meets a control that does nothing. Laid out down the pane from the start edge at its own size, and scaled down as a whole where the pane is too small to hold it — the way a picture shrinks — so a pane on a phone shows the same UI smaller rather than overlapped or cut off. The shape is fixed: the UI never stretches the pane.
38
38
  - `stage` — `<div>`, class `dds-illustrationcard__stage`. Under `then`, one of the pane's two states: what it holds first, and `then`, laid in the same cell so the second takes the first one's place. Still, the first is not drawn and the second is; arriving, the first holds for motion.duration.hold and gives way to the second. Both in the DOM, and the pane is one image to assistive technology, so only `label` is read — which describes the second. Rendered only when `then` is set.
39
39
 
@@ -33,7 +33,7 @@ IllustrationChoice renders 7 elements. A token binding names the part it applies
33
33
  - `dot` — `<span>`, class `dds-illustrationchoice__dot`. The fill inside the picked option's circle, in the option's ink. Picking, it pops in once the outline has arrived.
34
34
  - `label` — `<span>`, class `dds-illustrationchoice__label`. The option's name, at font.label.md.
35
35
  - `description` — `<span>`, class `dds-illustrationchoice__description`. The line under the name, at font.body.sm in color.fg.muted. Drawn only for an option that has one.
36
- - `outline` — `<span>`, class `dds-illustrationchoice__outline`. The selection outline, border.width.emphasis in color.border.info at the options' radius, laid in the picked option's row. Picking, it starts on a neighbouring option, holds for motion.duration.hold, and slides to the picked one over motion.duration.shift.
36
+ - `outline` — `<span>`, class `dds-illustrationchoice__outline`. The selection outline, border.width.emphasis in the option's ink (color.fg.default: white on a dark or glass pane, dark on a light one) at the options' radius, laid in the picked option's row. Picking, it starts on a neighbouring option, holds for motion.duration.hold, and slides to the picked one over motion.duration.shift.
37
37
 
38
38
  Apply these classes from the generated `illustrationChoiceParts` constant rather than writing the strings by hand.
39
39
 
@@ -31,7 +31,7 @@ The claim is centred and the product sits under it rather than beside it, whole
31
31
 
32
32
  ProductHero renders 10 elements. A token binding names the part it applies to.
33
33
 
34
- - `root` — `<section>`, class `dds-producthero`. The band. It paints the ground, carries the background image where there is one, and clips everything to its own edge so a visual that overruns cannot widen the page. `container-type` is what its own rules ask their width against, so a band placed in a narrow region of a wide page folds on its own measure rather than the window's. Under `shade` it says so as `data-shade`, and the words and the actions each lay a soft-edged shade of color.bg.overlay under themselves, meeting into one.
34
+ - `root` — `<section>`, class `dds-producthero`. The band. It paints the ground, carries the background image where there is one, and clips everything to its own edge so a visual that overruns cannot widen the page. `container-type` is what its own rules ask their width against, so a band placed in a narrow region of a wide page folds on its own measure rather than the window's.
35
35
  - `inner` — `<div>`, class `dds-producthero__inner`. The measure, and the gutter. It caps the content at the page's own maximum and holds the page inset, which is what keeps a band's text in the same column as the section under it. Positioned so it sits above the photograph without either of them naming a stacking order.
36
36
  - `content` — `<div>`, class `dds-producthero__content`. The column the band is read down: the text, then what a reader can do about it, then the visual in the arrangement that puts one there. One step between them, because the move from reading to acting is the same move on every page. It takes the whole measure rather than hugging its longest line: `inner` is a flex row, so a column that declared no width would shrink to its text and sit at the leading edge — which centring inside it cannot correct, because the box being centred in is already the wrong shape.
37
37
  - `text` — `<div>`, class `dds-producthero__text`. The eyebrow, the title and the description, in that order. It sets no gap of its own: the step from an eyebrow to a title and the step from a title to a description are two different roles, and a single gap could only ever be one of them. Each part carries its own.
@@ -97,15 +97,10 @@ Accepts: AspectRatio
97
97
  Use when: A product picture in a shape the page fixes on purpose — an AspectRatio box, which keeps its ratio across the column. Prefer `image`, which keeps the picture's own shape and adapts to it.
98
98
  Never for: The background. A photograph filling the band is `backgroundImage`; this sits in front of it, in the column, and is never cropped
99
99
 
100
- ### backgroundImage — `string` **Required when shade=true.**
100
+ ### backgroundImage — `string`
101
101
 
102
- Use when: A photograph filling the whole band. It is shown as it is — nothing is painted over it unless `shade` asks for a shade behind the words, so choose one dark enough where the words sit, or turn `shade` on for a bright one
103
- Never for: A flat colour, or a bright photograph left without `shade`. The band already paints its ground, and a pale photograph with nothing behind the words leaves the claim unreadable
104
-
105
- ### shade — `boolean` (default `false`)
106
-
107
- Use when: Under `backgroundImage`, when the photograph is bright where the words sit — a daylight sky — and the description or the actions would fall below AA on it. A soft shade of color.bg.overlay behind the words and the actions only, fading out around them, so the rest of the photograph keeps its light.
108
- Never for: A band with no photograph, or darkening the whole photograph. The band's own ground already carries the words, and a shade over all of it is the gloom a bright photograph was chosen to avoid.
102
+ Use when: A photograph filling the whole band. It is shown as it is — nothing is painted over it, so choose one already dark enough that the title reads on it
103
+ Never for: A flat colour, or a bright photograph. The band already paints its ground, and nothing here darkens an image on the page's behalf — a pale one leaves the claim unreadable
109
104
 
110
105
  ## Accessibility
111
106
 
@@ -211,9 +206,6 @@ Two things genuinely have no logical form and are not covered by this. A CSS gra
211
206
  ❌ ProductHero for: The band closes the page rather than opening it
212
207
  → use `Footer` instead.
213
208
 
214
- ❌ shade=true + backgroundImage missing or empty
215
- A shade is drawn between a photograph and the words; with no photograph it darkens the band's own ground, which the words were already set to read on.
216
-
217
209
  ## Example
218
210
 
219
211
  ```tsx
@@ -288,14 +280,3 @@ import { ProductHero } from '@deriv-com/dds-marketing'
288
280
  // h2 — the page already carries its h1 elsewhere
289
281
  <ProductHero headingLevel="h2" title="Trade global currencies" description="Speculate on over 50 major, minor and exotic currency pairs, with competitive spreads and 0% commission." />
290
282
  ```
291
-
292
- ### A shade behind the words
293
-
294
- backgroundImage with shade: a bright photograph keeps its light, and a soft shade behind the words and actions holds them at AA.
295
-
296
- ```tsx
297
- import { Button, ProductHero } from '@deriv-com/dds-marketing'
298
-
299
- // Bright photograph, shade on
300
- <ProductHero backgroundImage="/sample-banner-art.svg" eyebrow="Forex" title="Trade global currencies" description="Speculate on over 50 major, minor and exotic currency pairs, with competitive spreads and 0% commission." actions={<Button variant="primary">Try free demo</Button>} shade />
301
- ```
@@ -26,7 +26,7 @@ The reference design's DayNightTransition, made generic: nothing in it is about
26
26
  - Words a reader must not miss — a price, a disclaimer, a legal line. Each scene's words are on screen for only part of the pass
27
27
  - Two of these on one page, or one beside another pinned section. Each holds the screen for a screen and a half of scrolling, and two make the page feel stuck
28
28
  - Photographs with words in them. The words are the headings and the lines, where a screen reader and a translation reach them; a photograph saying them is cropped to fill and has the scene's words set over it
29
- - Words over a busy or mid-toned stretch of photograph. Nothing lies between the words and the photograph, so the photograph has to leave the words a calm, light or dark stretch to sit on, and the scene's ink has to name which
29
+ - Words over a busy stretch of photograph. White words lay a shade under themselves, but near-black words sit straight on the photograph, so a light photograph has to leave them a calm stretch to sit on, and the scene's ink has to name which
30
30
 
31
31
  ## Anatomy
32
32
 
@@ -35,9 +35,9 @@ SceneScroll renders 9 elements. A token binding names the part it applies to.
35
35
  - `root` — `<section>`, class `dds-scenescroll`. The section, a region named by the first scene's title. Where the scroll drives the change-over it is the track: two and a half screens tall, and the view timeline the dissolve runs down. Elsewhere it is as tall as the two scenes it holds. It paints color.bg.brand-strong under the photographs, which shows only before they load, and sets color.fg.on-brand-strong, white in both modes, as the ink a scene takes unless it names another. It clips with `overflow: clip` if it ever clips, never `hidden`, which would make it a scroll container and freeze the timeline and the pin.
36
36
  - `stage` — `<div>`, class `dds-scenescroll__stage`. What the reader sees. Where the scroll drives the change-over it is a screen tall and pinned to the top of the window for the length of the track, with both scenes stacked in its one cell. Elsewhere it is a column of the two scenes, one after the other with nothing between them.
37
37
  - `scene` — `<div>`, class `dds-scenescroll__scene`. One scene, drawn twice — `data-scene` says which. A screen tall, the photograph behind and the words over it, inset by the section's air and the page's gutter. Isolated, so the second scene's photograph covers the first scene's words as well as its photograph as it dissolves in. Its ink is the one the scene names — color.fg.on-media-light, near-black in both modes, over a light photograph; color.fg.on-brand-strong, white, over a dark one.
38
- - `media` — `<div>`, class `dds-scenescroll__media`. The scene's photograph, filling the scene edge to edge and square-cornered, with nothing laid over it: the words sit straight on the photograph, in the ink the scene names for it. The second scene's photograph dissolves in over the first's.
38
+ - `media` — `<div>`, class `dds-scenescroll__media`. The scene's photograph, filling the scene edge to edge and square-cornered, anchored at its foot and, from 62rem, at the far side from the words, so a crop takes from the words' side and the subject keeps its own. The second scene's photograph dissolves in over the first's.
39
39
  - `image` — `<img>`, class `dds-scenescroll__image`. The photograph, drawn from the scene's `image` and cropped to fill the scene. Its `alt` is the scene's own: empty for a photograph that is decoration, which most are here.
40
- - `content` — `<div>`, class `dds-scenescroll__content`. The scene's words — its title and the line under it — over the photograph, held to the heading group's measure. At the start on every screen, held to the heading group's measure — and from 62rem to half the stage, whichever is narrower — and to the page's content column, so the far side of the photograph is left to its subject: at the top of the stage below 62rem, under the room a navigation bar takes, and centred down the stage from 62rem — the reference design's arrangement, at its 992px. Where the scroll drives the change-over the first scene's words fade out and then the second scene's fade in, in the same place; both are in the DOM and read in order the whole time, and neither is ever hidden from assistive technology.
40
+ - `content` — `<div>`, class `dds-scenescroll__content`. The scene's words — its title and the line under it — over the photograph, held to the heading group's measure. At the start on every screen, held to the heading group's measure — and from 62rem to half the stage, whichever is narrower — and to the page's content column, so the far side of the photograph is left to its subject. Under white ink it lays a shade of color.bg.overlay under itself, full strength to its own edge and eased out past it — from 62rem a band down its half, fading across three times space.section.default toward the subject; below 62rem down from the top of the stage and out space.section.default under the words — so it fades with the words through the change-over. It sits at the top of the stage below 62rem, under the room a navigation bar takes, and centred down the stage from 62rem — the reference design's arrangement, at its 992px. Where the scroll drives the change-over the first scene's words fade out and then the second scene's fade in, in the same place; both are in the DOM and read in order the whole time, and neither is ever hidden from assistive technology.
41
41
  - `title` — `<h2>`, class `dds-scenescroll__title`. The scene's title. The first scene's is at `headingLevel` and names the section; the second's is one level under it. Both drawn at the size `headingLevel` names, font.h2 or font.h1.
42
42
  - `description` — `<p>`, class `dds-scenescroll__description`. The line under a scene's title, at the large body step and the description measure. Drawn only for a scene that has one.
43
43
  - `action` — `<div>`, class `dds-scenescroll__action`. The section's one thing to do, right under the words. While the scenes change over, both scenes' words share one area of the stage, as tall as the taller of them, and the action is the row under it — under the text on every screen, and still while the text changes. At the start with the words on every screen: at the top of the stage below 62rem, centred down it with them from 62rem. In the plain fallback, where the scenes stack, it follows the second. Rendered only when `action` is set.
@@ -83,7 +83,7 @@ Guaranteed by the component. Write none of it by hand: the ids these attributes
83
83
  - `Tab` — Moves past the section. Nothing in it is operable — the scenes are read, not used — so there is nothing here to land on.
84
84
  - The section is a region named by the first scene's title, through aria-labelledby, so a reader can jump to it and past it. Named by a heading already on the page rather than a hidden label of its own, so what is announced is what is shown
85
85
  - Both scenes' words are in the DOM, in order — the first scene's title and line, then the second's — whatever the scroll has faded. The visual change-over never hides either scene's words from assistive technology, never removes them, and never re-orders them
86
- - The words over each photograph are drawn in the ink its scene names, near-black over a light photograph and white over a dark one, and the author answers for AA contrast against the part of the photograph they sit on. Nothing lies between the two, so no check here can see the pair; the scene's ink is what a reviewer reads
86
+ - The words over each photograph are drawn in the ink its scene names, near-black over a light photograph and white over a dark one. White words lay a shade of color.bg.overlay under themselves, which holds AA under every word as ImageBanner's cover shade does. Near-black words sit straight on the photograph, and the author answers for their contrast against the part of it they sit on: no check here can see that pair, so the scene's ink is what a reviewer reads
87
87
  - Without a scroll timeline, for a reader who asked for less motion, and before any script, nothing is faded and nothing is pinned: the two scenes are drawn one after the other, each with its words at full strength
88
88
  - Each photograph's alt is the one its scene was given — empty for a photograph that is decoration
89
89
 
@@ -42,14 +42,8 @@ Opens a product or market page with a centred headline over a dark background
42
42
  - never: The background — that is `backgroundImage`. And beside `media`: one product, one picture. Given both, `image` is drawn and `media` ignored
43
43
  - **media** `node` · optional — A product picture in a shape the page fixes on purpose — an AspectRatio box, which keeps its ratio across the column. Prefer `image`, which keeps the picture's own shape and adapts to it
44
44
  - never: The background. A photograph filling the band is `backgroundImage`; this sits in front of it, in the column, and is never cropped
45
- - **backgroundImage** `string` · optional — A photograph filling the whole band. It is shown as it is — nothing is painted over it unless `shade` asks for a shade behind the words, so choose one dark enough where the words sit, or turn `shade` on for a bright one
46
- - never: A flat colour, or a bright photograph left without `shade`. The band already paints its ground, and a pale photograph with nothing behind the words leaves the claim unreadable
47
- - **shade** `boolean` · default false — Under `backgroundImage`, when the photograph is bright where the words sit — a daylight sky — and the description or the actions would fall below AA on it. …
48
- - never: A band with no photograph, or darkening the whole photograph. The band's own ground already carries the words, and a shade over all of it is the gloom a bright photograph was chosen to avoid
49
-
50
- ## The type refuses
51
-
52
- - shade=true with backgroundImage=null — A shade is drawn between a photograph and the words; with no photograph it darkens the band's own ground, which the words were already set to read on
45
+ - **backgroundImage** `string` · optional — A photograph filling the whole band. It is shown as it is — nothing is painted over it, so choose one already dark enough that the title reads on it
46
+ - never: A flat colour, or a bright photograph. The band already paints its ground, and nothing here darkens an image on the page's behalf — a pale one leaves the claim unreadable
53
47
 
54
48
  ## Inside it
55
49
 
@@ -25,7 +25,7 @@ Two full-screen photo scenes that cross-fade as the reader scrolls
25
25
  - Words a reader must not miss — a price, a disclaimer, a legal line. Each scene's words are on screen for only part of the pass
26
26
  - Two of these on one page, or one beside another pinned section. Each holds the screen for a screen and a half of scrolling, and two make the page feel stuck
27
27
  - Photographs with words in them. The words are the headings and the lines, where a screen reader and a translation reach them; a photograph saying them is cropped to fill and has the scene's words set over it
28
- - Words over a busy or mid-toned stretch of photograph. Nothing lies between the words and the photograph, so the photograph has to leave the words a calm, light or dark stretch to sit on, and the scene's ink has to name which
28
+ - Words over a busy stretch of photograph. White words lay a shade under themselves, but near-black words sit straight on the photograph, so a light photograph has to leave them a calm stretch to sit on, and the scene's ink has to name which
29
29
 
30
30
  ## Props
31
31
 
@@ -99,11 +99,13 @@
99
99
  }
100
100
 
101
101
  /*
102
- * The cover card's words hold three fifths of it, on the side away from where the photograph
103
- * shows clear — the reference design's measure.
102
+ * The cover card's words hold half of it, on the side away from where the photograph shows
103
+ * clear, and the photograph's subject has the other half. Three fifths left the subject two, and
104
+ * the shade's fade past the words took much of those. Written as a share rather than `1 1 0`: the
105
+ * photograph is out of flow behind, so the words would have the whole row to grow into.
104
106
  */
105
107
  :where([data-dds-context="marketing"]) .dds-imagebanner[data-variant="cover"] .dds-imagebanner__content {
106
- flex: 0 1 60%;
108
+ flex: 0 1 50%;
107
109
  }
108
110
 
109
111
  :where([data-dds-context="marketing"]) .dds-imagebanner[data-variant] .dds-imagebanner__action {
@@ -178,8 +180,8 @@
178
180
  * White body copy has to keep AA over even a pale photograph, so the words' column is
179
181
  * color.bg.overlay at full strength — darkest only at the words' edge was tried and left the end of a
180
182
  * line at about 2:1. But the shade is the words', not the photograph's: it is drawn by the column the
181
- * words take and fades to clear across the column's own margin and space.section.default past its
182
- * edge — a long fade, so it never reads as an edge on the photograph — and the rest of the
183
+ * words take and fades to clear across the column's own margin and past its edge — a long fade, so
184
+ * it never reads as an edge on the photograph — and the rest of the
183
185
  * photograph is shown as it was taken. It once held across three fifths of the card and then thinned
184
186
  * out to the far edge, which turned a bright photograph slate grey nearly all the way across; on a
185
187
  * phone it ran down three fifths of the card's height however short the words were.
@@ -202,17 +204,42 @@
202
204
  position: relative;
203
205
  }
204
206
 
207
+ /* Across from the fold, three times space.section.default. Eased over the shorter fade, the step
208
+ from full strength to clear still read as a band on the photograph, beside the words where the
209
+ eye lands; spread over three it reads as light falling off. Down on a phone it stays one, where a
210
+ longer tail would grey most of a short card. */
211
+ @container (min-width: 48rem) {
212
+ :where([data-dds-context="marketing"]) .dds-imagebanner[data-variant="cover"] .dds-imagebanner__content {
213
+ --dds-imagebanner-shade-fade: calc(var(--space-section-default) * 3);
214
+ }
215
+ }
216
+
205
217
  @container (min-width: 64rem) {
206
218
  :where([data-dds-context="marketing"]) .dds-imagebanner[data-variant="cover"] .dds-imagebanner__content {
207
219
  --dds-imagebanner-words-inset: var(--space-inset-2xl);
208
220
  }
209
221
  }
210
222
 
223
+ /*
224
+ * The fade is eased: its strength follows a smoothstep curve rather than a straight line, which is
225
+ * what made the first one read as a seam on the photograph — a linear fade has its sharpest change at
226
+ * both of its ends, where it leaves full strength and where it lands on clear. The stops are the
227
+ * curve sampled across the fade, which runs the words' inset and the fade's length past it; a
228
+ * gradient between two stops is still linear, so the curve is in how many there are.
229
+ */
211
230
  :where([data-dds-context="marketing"]) .dds-imagebanner[data-variant="cover"] .dds-imagebanner__content::before {
231
+ --dds-imagebanner-shade-ramp: calc(var(--dds-imagebanner-shade-fade) + var(--dds-imagebanner-words-inset));
212
232
  background-image: linear-gradient(
213
233
  var(--dds-imagebanner-shade-toward, to bottom),
214
234
  var(--color-bg-overlay),
215
- var(--color-bg-overlay) calc(100% - var(--dds-imagebanner-shade-fade) - var(--dds-imagebanner-words-inset)),
235
+ var(--color-bg-overlay) calc(100% - var(--dds-imagebanner-shade-ramp) * 1.0),
236
+ color-mix(in srgb, var(--color-bg-overlay) 95.7%, transparent) calc(100% - var(--dds-imagebanner-shade-ramp) * 0.875),
237
+ color-mix(in srgb, var(--color-bg-overlay) 84.4%, transparent) calc(100% - var(--dds-imagebanner-shade-ramp) * 0.75),
238
+ color-mix(in srgb, var(--color-bg-overlay) 68.4%, transparent) calc(100% - var(--dds-imagebanner-shade-ramp) * 0.625),
239
+ color-mix(in srgb, var(--color-bg-overlay) 50.0%, transparent) calc(100% - var(--dds-imagebanner-shade-ramp) * 0.5),
240
+ color-mix(in srgb, var(--color-bg-overlay) 31.6%, transparent) calc(100% - var(--dds-imagebanner-shade-ramp) * 0.375),
241
+ color-mix(in srgb, var(--color-bg-overlay) 15.6%, transparent) calc(100% - var(--dds-imagebanner-shade-ramp) * 0.25),
242
+ color-mix(in srgb, var(--color-bg-overlay) 4.3%, transparent) calc(100% - var(--dds-imagebanner-shade-ramp) * 0.125),
216
243
  transparent
217
244
  );
218
245
  content: '';
@@ -33,4 +33,4 @@ export type { ProductHeroProps };
33
33
  * region landmark once it has a name, and a band that announced itself as an unnamed section is one
34
34
  * more thing a screen reader user walks through rather than a place they can skip to.
35
35
  */
36
- export declare function ProductHero({ eyebrow, title, headingLevel, description, actions, image, media, backgroundImage, shade, }: ProductHeroProps): import("react").JSX.Element;
36
+ export declare function ProductHero({ eyebrow, title, headingLevel, description, actions, image, media, backgroundImage, }: ProductHeroProps): import("react").JSX.Element;
@@ -37,11 +37,11 @@ import { productHeroDefaults, productHeroParts, } from './ProductHero.types.js';
37
37
  * region landmark once it has a name, and a band that announced itself as an unnamed section is one
38
38
  * more thing a screen reader user walks through rather than a place they can skip to.
39
39
  */
40
- export function ProductHero({ eyebrow, title, headingLevel = productHeroDefaults.headingLevel, description, actions, image, media, backgroundImage, shade = productHeroDefaults.shade, }) {
40
+ export function ProductHero({ eyebrow, title, headingLevel = productHeroDefaults.headingLevel, description, actions, image, media, backgroundImage, }) {
41
41
  const titleId = useId();
42
42
  const [band, revealed] = useReveal();
43
43
  const Title = headingLevel === 'h2' ? 'h2' : 'h1';
44
- return (_jsx("section", { className: productHeroParts.root, "aria-labelledby": titleId, "data-shade": shade && given(backgroundImage) ? '' : undefined,
44
+ return (_jsx("section", { className: productHeroParts.root, "aria-labelledby": titleId,
45
45
  /* The one inline style here, and it is a value rather than a decision: which photograph,
46
46
  supplied by the page. How it is drawn — the cover fit and the position —
47
47
  is in the stylesheet, where it can be reasoned about. */
@@ -18,14 +18,10 @@ export interface ProductHeroOwnProps {
18
18
  image?: BlockImage;
19
19
  /** A product picture in a shape the page fixes on purpose — an AspectRatio box, which keeps its ratio across the column. Prefer `image`, which keeps the picture's own shape and adapts to it. */
20
20
  media?: ReactNode;
21
- }
22
- export type ProductHeroProps = ProductHeroOwnProps & ({
23
- shade?: boolean;
24
- backgroundImage: string;
25
- } | {
26
- shade?: false;
21
+ /** A photograph filling the whole band. It is shown as it is — nothing is painted over it, so choose one already dark enough that the title reads on it */
27
22
  backgroundImage?: string;
28
- });
23
+ }
24
+ export type ProductHeroProps = ProductHeroOwnProps;
29
25
  /** The class each part carries. Apply these — do not retype them. */
30
26
  export declare const productHeroParts: {
31
27
  readonly root: "dds-producthero";
@@ -42,5 +38,4 @@ export declare const productHeroParts: {
42
38
  /** Generated so defaults cannot drift from the contract. */
43
39
  export declare const productHeroDefaults: {
44
40
  readonly headingLevel: "h1";
45
- readonly shade: false;
46
41
  };
@@ -13,4 +13,4 @@ export const productHeroParts = {
13
13
  image: 'dds-producthero__image',
14
14
  };
15
15
  /** Generated so defaults cannot drift from the contract. */
16
- export const productHeroDefaults = { headingLevel: 'h1', shade: false };
16
+ export const productHeroDefaults = { headingLevel: 'h1' };
@@ -383,57 +383,3 @@
383
383
  padding-block: var(--space-section-compact);
384
384
  }
385
385
  }
386
-
387
- /*
388
- * `shade`: a shade behind the words, for a bright photograph.
389
- *
390
- * The photograph is shown as it is, and a bright one — the daylight sky the brand's heroes ask for —
391
- * leaves the description and the actions below AA. Darkening the whole photograph fixes the number
392
- * and loses the day. So the shade is drawn from the words themselves: a box of color.bg.overlay under
393
- * the text and another under the actions, each reaching space.inset.2xl past the words and blurred by
394
- * space.inset.xl, so it is full strength under every word and fades to clear in the margin round them.
395
- * It follows the words wherever they sit — at the start on a phone, centred from the fold — and leaves
396
- * the rest of the photograph its light.
397
- *
398
- * The two boxes meet rather than overlap: the text's runs down across the step to the actions, the
399
- * actions' starts at their top edge. Overlapping, the two layers of shade doubled into a darker band
400
- * between the words and the buttons; meeting, their blurred edges add back up to the one strength.
401
- * A blur rather than a gradient because a gradient fills a box to its edge, and the edge then shows.
402
- *
403
- * Under the words, inside their own stacking context, so it can never be drawn over a word or a
404
- * button.
405
- */
406
- :where([data-dds-context="marketing"]) .dds-producthero[data-shade] :is(.dds-producthero__text, .dds-producthero__actions) {
407
- isolation: isolate;
408
- position: relative;
409
- }
410
-
411
- :where([data-dds-context="marketing"]) .dds-producthero[data-shade] :is(.dds-producthero__text, .dds-producthero__actions)::before {
412
- background-color: var(--color-bg-overlay);
413
- content: '';
414
- filter: blur(var(--space-inset-xl));
415
- inset: calc(-1 * var(--space-inset-2xl));
416
- pointer-events: none;
417
- position: absolute;
418
- z-index: -1;
419
- }
420
-
421
- :where([data-dds-context="marketing"]) .dds-producthero[data-shade] .dds-producthero__text:has(+ .dds-producthero__actions)::before {
422
- inset-block-end: calc(-1 * var(--space-heading-description-actions));
423
- }
424
-
425
- :where([data-dds-context="marketing"]) .dds-producthero[data-shade] .dds-producthero__actions::before {
426
- inset-block-start: 0;
427
- }
428
-
429
- /*
430
- * From the fold, where the words are centred, the text's box is the band's whole width while its
431
- * balanced lines take much less of it, so a shade the box's width was a veil across the band. It is
432
- * held to size.heading-group.max — the width a centred title and its line may run as a block — and
433
- * the margin round that, centred with the words.
434
- */
435
- @container (min-width: 48rem) {
436
- :where([data-dds-context="marketing"]) .dds-producthero[data-shade] .dds-producthero__text::before {
437
- inset-inline: max(calc(-1 * var(--space-inset-2xl)), calc(50% - var(--size-heading-group-max) / 2 - var(--space-inset-2xl)));
438
- }
439
- }
@@ -24,9 +24,10 @@ export type { SceneScrollProps };
24
24
  * translate and keep in step. The second scene's title sits one level under it, as the second half
25
25
  * of the section the first one names.
26
26
  *
27
- * The words sit straight on the photographs, as the reference's do, with nothing laid between: each
28
- * scene names its ink — near-black over a daylight photograph, white over a night one — and the
29
- * scene carries it as `data-ink`, so the stylesheet sets it. Each scene's words fade as a whole, so
27
+ * Each scene names its ink — near-black over a daylight photograph, white over a night one — and the
28
+ * scene carries it as `data-ink`, so the stylesheet sets it. White words lay a shade under
29
+ * themselves, eased out toward the photograph's subject; near-black words sit straight on the
30
+ * photograph, as the reference's do, there being no light shade. Each scene's words fade as a whole, so
30
31
  * the two inks are never drawn over each other; what the reference ran as one colour interpolating
31
32
  * with the scroll is here two sets of words, each in its own ink, one giving way to the other.
32
33
  *
@@ -29,9 +29,10 @@ const under = { h1: 'h2', h2: 'h3' };
29
29
  * translate and keep in step. The second scene's title sits one level under it, as the second half
30
30
  * of the section the first one names.
31
31
  *
32
- * The words sit straight on the photographs, as the reference's do, with nothing laid between: each
33
- * scene names its ink — near-black over a daylight photograph, white over a night one — and the
34
- * scene carries it as `data-ink`, so the stylesheet sets it. Each scene's words fade as a whole, so
32
+ * Each scene names its ink — near-black over a daylight photograph, white over a night one — and the
33
+ * scene carries it as `data-ink`, so the stylesheet sets it. White words lay a shade under
34
+ * themselves, eased out toward the photograph's subject; near-black words sit straight on the
35
+ * photograph, as the reference's do, there being no light shade. Each scene's words fade as a whole, so
35
36
  * the two inks are never drawn over each other; what the reference ran as one colour interpolating
36
37
  * with the scroll is here two sets of words, each in its own ink, one giving way to the other.
37
38
  *
@@ -50,13 +50,13 @@
50
50
  }
51
51
 
52
52
  /*
53
- * The words sit straight on the photograph, in the ink the scene names.
53
+ * The words are drawn in the ink the scene names.
54
54
  *
55
- * No scrim: the photograph is shown as it was taken, and the words are drawn in whichever ink it
56
- * leaves room for — color.fg.on-media-light, near-black in both modes, over a light photograph such
57
- * as a daylight sky, and the section's own color.fg.on-brand-strong, white, over a dark one. The
58
- * photograph does not change with the theme, so neither ink does. The block cannot see the
59
- * photograph, so the scene says which, and the author answers for the contrast.
55
+ * Whichever ink the photograph leaves room for — color.fg.on-media-light, near-black in both modes,
56
+ * over a light photograph such as a daylight sky, and the section's own color.fg.on-brand-strong,
57
+ * white, over a dark one. The photograph does not change with the theme, so neither ink does. The
58
+ * block cannot see the photograph, so the scene says which. White words have a shade behind them,
59
+ * below; near-black words sit straight on the photograph, and the author answers for that contrast.
60
60
  *
61
61
  * On the scene, so its title and its line take it together, and inherited through the scene even
62
62
  * where the scroll lays the scenes out as `display: contents`.
@@ -65,6 +65,71 @@
65
65
  color: var(--color-fg-on-media-light);
66
66
  }
67
67
 
68
+ /*
69
+ * The shade under a white-ink scene's words, ImageBanner's cover shade on a full-screen scene.
70
+ *
71
+ * White words were left to the photograph, and a photograph bright anywhere behind them — a sky, a
72
+ * lit wall — took the line under the title below AA. So the words lay color.bg.overlay under
73
+ * themselves: full strength to the edge of their own column and then eased out past it, a
74
+ * smoothstep sampled as stops for the reason ImageBanner gives, so it never reads as a seam on the
75
+ * photograph. From 62rem it is a band the stage's height down the words' half, fading across
76
+ * three times space.section.default toward the subject; below 62rem the words run the width at the
77
+ * top, and it runs down from the top of the stage and out space.section.default under them.
78
+ *
79
+ * Drawn by the words rather than the photograph, so it fades with them through the change-over and
80
+ * is never over a scene whose words are not there. Only under white ink: there is no light shade
81
+ * for near-black words, which sit straight on a light photograph as they always did. It reaches
82
+ * past the stage's edges on purpose and is clipped there — by the stage while the scroll drives the
83
+ * change-over, and by its own scene in the plain fallback, with `clip` rather than `hidden` for the
84
+ * timeline's sake. A gradient takes a physical direction, so the direction is a custom property,
85
+ * mirrored under `:dir(rtl)`.
86
+ */
87
+ :where([data-dds-context="marketing"]) .dds-scenescroll {
88
+ --dds-scenescroll-shade-fade: var(--space-section-default);
89
+ --dds-scenescroll-shade-toward: to bottom;
90
+ }
91
+
92
+ :where([data-dds-context="marketing"]) .dds-scenescroll .dds-scenescroll__scene {
93
+ overflow: clip;
94
+ }
95
+
96
+ :where([data-dds-context="marketing"]) .dds-scenescroll .dds-scenescroll__scene[data-ink="light"] .dds-scenescroll__content::before {
97
+ background-image: linear-gradient(
98
+ var(--dds-scenescroll-shade-toward),
99
+ var(--color-bg-overlay),
100
+ var(--color-bg-overlay) calc(100% - var(--dds-scenescroll-shade-fade) * 1.0),
101
+ color-mix(in srgb, var(--color-bg-overlay) 95.7%, transparent) calc(100% - var(--dds-scenescroll-shade-fade) * 0.875),
102
+ color-mix(in srgb, var(--color-bg-overlay) 84.4%, transparent) calc(100% - var(--dds-scenescroll-shade-fade) * 0.75),
103
+ color-mix(in srgb, var(--color-bg-overlay) 68.4%, transparent) calc(100% - var(--dds-scenescroll-shade-fade) * 0.625),
104
+ color-mix(in srgb, var(--color-bg-overlay) 50.0%, transparent) calc(100% - var(--dds-scenescroll-shade-fade) * 0.5),
105
+ color-mix(in srgb, var(--color-bg-overlay) 31.6%, transparent) calc(100% - var(--dds-scenescroll-shade-fade) * 0.375),
106
+ color-mix(in srgb, var(--color-bg-overlay) 15.6%, transparent) calc(100% - var(--dds-scenescroll-shade-fade) * 0.25),
107
+ color-mix(in srgb, var(--color-bg-overlay) 4.3%, transparent) calc(100% - var(--dds-scenescroll-shade-fade) * 0.125),
108
+ transparent
109
+ );
110
+ content: '';
111
+ inset: calc(-1 * 100cqh) calc(-1 * 100cqw) calc(-1 * var(--dds-scenescroll-shade-fade));
112
+ pointer-events: none;
113
+ position: absolute;
114
+ z-index: -1;
115
+ }
116
+
117
+ @container (min-width: 62rem) {
118
+ :where([data-dds-context="marketing"]) .dds-scenescroll {
119
+ --dds-scenescroll-shade-fade: calc(var(--space-section-default) * 3);
120
+ --dds-scenescroll-shade-toward: to right;
121
+ }
122
+
123
+ :where([data-dds-context="marketing"]) .dds-scenescroll:dir(rtl) {
124
+ --dds-scenescroll-shade-toward: to left;
125
+ }
126
+
127
+ :where([data-dds-context="marketing"]) .dds-scenescroll .dds-scenescroll__scene[data-ink="light"] .dds-scenescroll__content::before {
128
+ inset-block: calc(-1 * 100cqh);
129
+ inset-inline: calc(-1 * 100cqw) calc(-1 * var(--dds-scenescroll-shade-fade));
130
+ }
131
+ }
132
+
68
133
  /*
69
134
  * A title with nothing under it carries no step under it.
70
135
  */
@@ -124,6 +189,22 @@
124
189
  object-position: bottom;
125
190
  }
126
191
 
192
+ /*
193
+ * From 62rem the subject is on the far side from the words, so a crop takes from the words' side
194
+ * rather than the middle: on a window narrower than the photograph's shape, a centred crop cut into
195
+ * the subject at the edge and pushed it toward the words. Physical, since `object-position` has no
196
+ * logical form, and mirrored under `:dir(rtl)`, where the words are on the right.
197
+ */
198
+ @container (min-width: 62rem) {
199
+ :where([data-dds-context="marketing"]) .dds-scenescroll .dds-scenescroll__image {
200
+ object-position: right bottom;
201
+ }
202
+
203
+ :where([data-dds-context="marketing"]) .dds-scenescroll:dir(rtl) .dds-scenescroll__image {
204
+ object-position: left bottom;
205
+ }
206
+ }
207
+
127
208
  /*
128
209
  * The change-over, scrubbed by the scroll.
129
210
  *
@@ -259,7 +259,7 @@
259
259
  * How many figures share a row, read from the figures — FeatureCards' way, `:has()` asking the
260
260
  * markup for the count it already holds rather than a prop restating it.
261
261
  *
262
- * Three to a row by default, which is five as three and two, and six as two rows of three. Three
262
+ * Three to a row by default, which is five as three and two centred under them, and six as two rows of three. Three
263
263
  * or fewer take one row of their own. Four are two rows of two here and one row at full width:
264
264
  * four figures across a tablet's column are each narrower than a figure is wide at this size.
265
265
  *
@@ -270,6 +270,32 @@
270
270
  grid-template-columns: repeat(3, minmax(0, 1fr));
271
271
  }
272
272
 
273
+ /*
274
+ * A last row short of three is centred under the rows above it, as the header is over them, rather
275
+ * than left at the start edge.
276
+ *
277
+ * Five or more is the only set that can leave one: three or fewer is a single row, and four is two
278
+ * rows of two. Its three columns are laid as six half-columns, each figure across two, so a figure
279
+ * is exactly as wide as before — two half-columns and the gutter between them is one column — and
280
+ * a short last row can start half a column in. One left over starts at the third half-column, the
281
+ * middle; the first of two left over at the second, and the other follows it.
282
+ */
283
+ :where([data-dds-context="marketing"]) .dds-stats .dds-stats__list:has(> :nth-child(5)) {
284
+ grid-template-columns: repeat(6, minmax(0, 1fr));
285
+ }
286
+
287
+ :where([data-dds-context="marketing"]) .dds-stats .dds-stats__list:has(> :nth-child(5)) > .dds-stats__item {
288
+ grid-column: span 2;
289
+ }
290
+
291
+ :where([data-dds-context="marketing"]) .dds-stats .dds-stats__list:has(> :nth-child(5)) > .dds-stats__item:nth-child(3n + 1):last-child {
292
+ grid-column: 3 / span 2;
293
+ }
294
+
295
+ :where([data-dds-context="marketing"]) .dds-stats .dds-stats__list:has(> :nth-child(5)) > .dds-stats__item:nth-child(3n + 1):nth-last-child(2) {
296
+ grid-column: 2 / span 2;
297
+ }
298
+
273
299
  :where([data-dds-context="marketing"]) .dds-stats .dds-stats__list:has(> :nth-child(4):last-child),
274
300
  :where([data-dds-context="marketing"]) .dds-stats .dds-stats__list:has(> :nth-child(2):last-child) {
275
301
  grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -24,7 +24,9 @@
24
24
  * Over a light page the same glass came out near-white, behind the dark theme's white ink. So the
25
25
  * dark page's ground goes under it, and the card is the same charcoal on every page.
26
26
  *
27
- * Not in a scene, where the glass is there to show the photograph through it.
27
+ * In a scene the photograph shows through instead, but under a scrim of color.bg.overlay: the
28
+ * glass alone over a light photograph came out near-white behind the same white ink, and the
29
+ * pane's UI could not be read. Darkened, the picture is still there, as colour and shape.
28
30
  */
29
31
  :where([data-dds-context="marketing"]) .dds-illustrationcard {
30
32
  background-color: var(--color-bg-page);
@@ -32,8 +34,7 @@
32
34
  }
33
35
 
34
36
  :where([data-dds-context="marketing"]) .dds-illustrationscene .dds-illustrationcard {
35
- background-color: var(--color-bg-surface-glass);
36
- background-image: none;
37
+ background-color: var(--color-bg-overlay);
37
38
  }
38
39
 
39
40
  /*
@@ -38,7 +38,7 @@
38
38
  :where([data-dds-context="marketing"]) .dds-illustrationchoice .dds-illustrationchoice__outline {
39
39
  border-style: solid;
40
40
  pointer-events: none;
41
- border-color: var(--color-border-info);
41
+ border-color: var(--color-fg-default);
42
42
  border-radius: var(--radius-control-md);
43
43
  border-width: var(--border-width-emphasis);
44
44
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deriv-com/dds-marketing",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Deriv Design System — components and tokens for marketing sites (landing pages, pricing, sign-up).",
5
5
  "license": "MIT",
6
6
  "author": {