@deriv-com/dds-marketing 1.0.0 → 1.0.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 (131) hide show
  1. package/.llm/components/accordion.md +1 -1
  2. package/.llm/components/accordionitem.md +1 -1
  3. package/.llm/components/aspectratio.md +1 -1
  4. package/.llm/components/background.md +371 -0
  5. package/.llm/components/badge.md +1 -1
  6. package/.llm/components/bottomsheet.md +1 -1
  7. package/.llm/components/breadcrumb.md +1 -1
  8. package/.llm/components/breadcrumbitem.md +1 -1
  9. package/.llm/components/button.md +10 -6
  10. package/.llm/components/buttonicon.md +3 -3
  11. package/.llm/components/card.md +1 -1
  12. package/.llm/components/cardbody.md +1 -1
  13. package/.llm/components/cardbutton.md +1 -1
  14. package/.llm/components/checkbox.md +1 -1
  15. package/.llm/components/ctabanner.md +26 -1
  16. package/.llm/components/ctabannerslider.md +22 -1
  17. package/.llm/components/divider.md +1 -1
  18. package/.llm/components/dropdownmenu.md +2 -2
  19. package/.llm/components/faqaccordion.md +1 -1
  20. package/.llm/components/featurebullets.md +1 -1
  21. package/.llm/components/featurecards.md +1 -1
  22. package/.llm/components/featurescroll.md +1 -1
  23. package/.llm/components/featuresplit.md +1 -1
  24. package/.llm/components/filterchip.md +1 -1
  25. package/.llm/components/footer.md +1 -1
  26. package/.llm/components/footergroup.md +1 -1
  27. package/.llm/components/grid.md +1 -1
  28. package/.llm/components/icon.md +13 -3
  29. package/.llm/components/illustrationcard.md +236 -0
  30. package/.llm/components/illustrationscene.md +233 -0
  31. package/.llm/components/landinghero.md +25 -1
  32. package/.llm/components/link.md +4 -4
  33. package/.llm/components/logocolumns.md +1 -1
  34. package/.llm/components/logorows.md +1 -1
  35. package/.llm/components/menuitem.md +1 -1
  36. package/.llm/components/navbar.md +1 -1
  37. package/.llm/components/navbaritem.md +1 -1
  38. package/.llm/components/navbarmenu.md +1 -1
  39. package/.llm/components/navbarmenugroup.md +1 -1
  40. package/.llm/components/navbarpanel.md +1 -1
  41. package/.llm/components/navbarswitcher.md +1 -1
  42. package/.llm/components/navbarswitcheroption.md +1 -1
  43. package/.llm/components/option.md +1 -1
  44. package/.llm/components/pageheader.md +22 -1
  45. package/.llm/components/pagination.md +1 -1
  46. package/.llm/components/paginationdots.md +1 -1
  47. package/.llm/components/producthero.md +1 -1
  48. package/.llm/components/removablechip.md +1 -1
  49. package/.llm/components/row.md +1 -1
  50. package/.llm/components/scroller.md +1 -1
  51. package/.llm/components/searchinput.md +1 -1
  52. package/.llm/components/segmentedcontrol.md +1 -1
  53. package/.llm/components/segmentedcontrolitem.md +1 -1
  54. package/.llm/components/select.md +1 -1
  55. package/.llm/components/selectchip.md +1 -1
  56. package/.llm/components/spinner.md +1 -1
  57. package/.llm/components/stack.md +1 -1
  58. package/.llm/components/statusdot.md +1 -1
  59. package/.llm/components/subdomainnavbar.md +1 -1
  60. package/.llm/components/tab.md +3 -3
  61. package/.llm/components/table.md +1 -1
  62. package/.llm/components/tablebody.md +1 -1
  63. package/.llm/components/tablecell.md +1 -1
  64. package/.llm/components/tablehead.md +1 -1
  65. package/.llm/components/tableheadercell.md +1 -1
  66. package/.llm/components/tablerow.md +1 -1
  67. package/.llm/components/tablist.md +1 -1
  68. package/.llm/components/testimonialrow.md +1 -1
  69. package/.llm/components/testimonialslider.md +1 -1
  70. package/.llm/components/textarea.md +1 -1
  71. package/.llm/components/textinput.md +1 -1
  72. package/.llm/components/tooltip.md +1 -1
  73. package/.llm/components/typography.md +1 -1
  74. package/.llm/digest/background.md +52 -0
  75. package/.llm/digest/ctabanner.md +4 -0
  76. package/.llm/digest/ctabannerslider.md +3 -0
  77. package/.llm/digest/illustrationcard.md +46 -0
  78. package/.llm/digest/illustrationscene.md +51 -0
  79. package/.llm/digest/landinghero.md +7 -0
  80. package/.llm/digest/pageheader.md +3 -0
  81. package/.llm/index.md +7 -0
  82. package/.llm/rules.md +20 -10
  83. package/.llm/tokens.md +11 -0
  84. package/AGENTS.md +7 -5
  85. package/dist/components/blocks/cta-banner/CtaBanner.d.ts +1 -1
  86. package/dist/components/blocks/cta-banner/CtaBanner.js +3 -2
  87. package/dist/components/blocks/cta-banner/CtaBanner.types.d.ts +5 -0
  88. package/dist/components/blocks/cta-banner/cta-banner-impl.css +8 -0
  89. package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.d.ts +1 -1
  90. package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.js +5 -4
  91. package/dist/components/blocks/cta-banner-slider/CtaBannerSlider.types.d.ts +4 -0
  92. package/dist/components/blocks/cta-banner-slider/cta-banner-slider-impl.css +8 -0
  93. package/dist/components/blocks/landing-hero/LandingHero.d.ts +1 -1
  94. package/dist/components/blocks/landing-hero/LandingHero.js +4 -3
  95. package/dist/components/blocks/landing-hero/LandingHero.types.d.ts +7 -3
  96. package/dist/components/blocks/page-header/PageHeader.d.ts +1 -1
  97. package/dist/components/blocks/page-header/PageHeader.js +3 -2
  98. package/dist/components/blocks/page-header/PageHeader.types.d.ts +4 -0
  99. package/dist/components/blocks/page-header/page-header-impl.css +8 -0
  100. package/dist/components/core/background/Background.css +69 -0
  101. package/dist/components/core/background/Background.d.ts +21 -0
  102. package/dist/components/core/background/Background.js +36 -0
  103. package/dist/components/core/background/Background.types.d.ts +36 -0
  104. package/dist/components/core/background/Background.types.js +9 -0
  105. package/dist/components/core/background/background-impl.css +427 -0
  106. package/dist/components/core/background/halftone.d.ts +123 -0
  107. package/dist/components/core/background/halftone.js +682 -0
  108. package/dist/components/core/background/use-halftone.d.ts +15 -0
  109. package/dist/components/core/background/use-halftone.js +27 -0
  110. package/dist/components/core/background/use-offscreen.d.ts +10 -0
  111. package/dist/components/core/background/use-offscreen.js +30 -0
  112. package/dist/components/core/background/use-spotlight.d.ts +13 -0
  113. package/dist/components/core/background/use-spotlight.js +65 -0
  114. package/dist/components/core/illustration-card/IllustrationCard.css +40 -0
  115. package/dist/components/core/illustration-card/IllustrationCard.d.ts +22 -0
  116. package/dist/components/core/illustration-card/IllustrationCard.js +82 -0
  117. package/dist/components/core/illustration-card/IllustrationCard.types.d.ts +23 -0
  118. package/dist/components/core/illustration-card/IllustrationCard.types.js +8 -0
  119. package/dist/components/core/illustration-card/illustration-card-impl.css +27 -0
  120. package/dist/components/core/illustration-scene/IllustrationScene.css +44 -0
  121. package/dist/components/core/illustration-scene/IllustrationScene.d.ts +14 -0
  122. package/dist/components/core/illustration-scene/IllustrationScene.js +16 -0
  123. package/dist/components/core/illustration-scene/IllustrationScene.types.d.ts +33 -0
  124. package/dist/components/core/illustration-scene/IllustrationScene.types.js +8 -0
  125. package/dist/components/core/illustration-scene/illustration-scene-impl.css +40 -0
  126. package/dist/components/core/link/Link.js +5 -5
  127. package/dist/components/core/tab/Tab.types.d.ts +1 -1
  128. package/dist/components/index.d.ts +6 -0
  129. package/dist/components/index.js +6 -0
  130. package/dist/marketing.css +24 -0
  131. package/package.json +1 -1
@@ -0,0 +1,427 @@
1
+ /*
2
+ * Scoped to this package, like the generated sheet beside it.
3
+ *
4
+ * `:where([data-dds-context="marketing"])` confines these rules without contributing
5
+ * specificity, so every override below still ties or beats exactly what it did before.
6
+ */
7
+
8
+ /*
9
+ * The canvas fills the band and sits under what the band holds.
10
+ *
11
+ * Absolutely placed at all four edges rather than sized by width and height, so it follows the
12
+ * band through every resize without the script having to set its CSS size — the script sets only
13
+ * the bitmap, from the box this gives it. No pointer events: the pattern is paint, and every press
14
+ * on the band belongs to the content.
15
+ *
16
+ * These are positions and a pointer rule, not design decisions, and a contract has no way to state
17
+ * an inset of zero on a part — which is why they are here rather than bound.
18
+ */
19
+ :where([data-dds-context="marketing"]) .dds-background .dds-background__canvas {
20
+ position: absolute;
21
+ inset: 0;
22
+ display: block;
23
+ inline-size: 100%;
24
+ block-size: 100%;
25
+ pointer-events: none;
26
+ }
27
+
28
+ /*
29
+ * The content stacks above the canvas by being positioned after it. The root isolates, so this
30
+ * order is settled inside the band and nothing outside it has to know a canvas is there.
31
+ */
32
+ :where([data-dds-context="marketing"]) .dds-background .dds-background__content {
33
+ position: relative;
34
+ }
35
+
36
+ /*
37
+ * A Background with nothing to hold is a layer: it fills the band it is placed in, behind that
38
+ * band's own content, which is how a block draws the pattern under itself without its layout
39
+ * changing. The band has to be positioned, and every block that offers the pattern is. It takes no
40
+ * pointer events — the content is on top and gets every press — and the pointer is listened for on
41
+ * the band instead, in `useHalftone`.
42
+ */
43
+ :where([data-dds-context="marketing"]) .dds-background[data-layer='true'] {
44
+ position: absolute;
45
+ inset: 0;
46
+ pointer-events: none;
47
+ }
48
+
49
+ /*
50
+ * The two hairline patterns: the rule grid, for `pattern="grid"` — the grid PageHeader paints, for
51
+ * any band — and the rings, for `pattern="rings"`.
52
+ *
53
+ * Both are repeating gradients of hairlines painted onto the root under everything it holds, with
54
+ * the band's own ground laid over them as a fade: opaque at the top, clear at the foot, so the
55
+ * lines rise from the bottom of the band the way the halftone does. A background-image rather than
56
+ * an element, because it is a texture on the ground and not content, and rather than a canvas
57
+ * because the only thing about it that moves is where the lines start — see *The idle motion*.
58
+ *
59
+ * The line takes the pattern role for the ground it is on: the inverse on the dark ground, a
60
+ * brighter white on the coral, and the brand's coral on the light — each a step above the subtle
61
+ * border, which a hairline drawn across a whole band thinned out to almost nothing. The fade is each tone's own ground, so
62
+ * the lines disappear into the band rather than into a colour the band is not.
63
+ */
64
+ :where([data-dds-context="marketing"]) .dds-background:is([data-pattern='grid'], [data-pattern='rings']) {
65
+ --dds-background-rule: var(--color-border-pattern-inverse);
66
+ --dds-background-fade: var(--color-bg-brand-strong);
67
+ /*
68
+ * How thick a rule is: border.width.hairline, which is half a CSS pixel — one device pixel on a
69
+ * screen of twice the density, and the line this grid was drawn with. A standard-density screen
70
+ * has no half pixel to draw it on, and there the rules came out faint or not at all depending on
71
+ * where each one fell: on a tall band every horizontal rule vanished. So below twice the density
72
+ * the rule is border.width.default, one pixel — the thinnest line that screen can draw.
73
+ */
74
+ --dds-background-hairline: var(--border-width-hairline);
75
+ }
76
+
77
+ @media (max-resolution: 1.99dppx) {
78
+ :where([data-dds-context="marketing"]) .dds-background:is([data-pattern='grid'], [data-pattern='rings']) {
79
+ --dds-background-hairline: var(--border-width-default);
80
+ }
81
+ }
82
+
83
+ /*
84
+ * On the light ground the lines are the brand's coral rather than a grey, as the halftone's dots
85
+ * and the aurora's pools are there: a third of color.bg.brand, about as strong as the grey it
86
+ * replaces, so the grid stays quiet but reads as the brand's. It lights up to the full coral.
87
+ */
88
+ :where([data-dds-context="marketing"]) .dds-background:is([data-pattern='grid'], [data-pattern='rings'])[data-tone='light'] {
89
+ --dds-background-rule: color-mix(in srgb, var(--color-bg-brand) 35%, transparent);
90
+ --dds-background-fade: var(--color-bg-page);
91
+ }
92
+
93
+ /*
94
+ * On the coral ground the inverse pattern line, white at a quarter, all but vanished into the
95
+ * coral, so the lines rest at the muted inverse there and light up to full white.
96
+ */
97
+ :where([data-dds-context="marketing"]) .dds-background:is([data-pattern='grid'], [data-pattern='rings'])[data-tone='brand'] {
98
+ --dds-background-rule: var(--color-fg-muted-inverse);
99
+ --dds-background-fade: var(--color-bg-brand);
100
+ }
101
+
102
+ /*
103
+ * The grid: two sets of rules at size.rule-grid.pitch, one across and one down.
104
+ *
105
+ * `to right` is physical: a CSS gradient has no logical direction. A grid is symmetric, so it reads
106
+ * the same in either direction, and it is centred across the band — see the positions below.
107
+ */
108
+ :where([data-dds-context="marketing"]) .dds-background[data-pattern='grid'] {
109
+ --dds-background-pitch: var(--size-rule-grid-pitch);
110
+ background-image:
111
+ linear-gradient(to bottom, var(--dds-background-fade), transparent),
112
+ linear-gradient(to right, var(--dds-background-rule) 0 var(--dds-background-hairline), transparent 0),
113
+ linear-gradient(to bottom, var(--dds-background-rule) 0 var(--dds-background-hairline), transparent 0);
114
+ /*
115
+ * Each set of rules is one rule on a tile one pitch long, repeated: a tile the size of the band
116
+ * left a seam wherever the band was not a whole number of pitches, and a rising grid carried
117
+ * that seam up through it. Across, a rule falls on the band's middle — the tile is placed half a
118
+ * pitch right of centre, and its rule is its left edge — so the grid is symmetric about the
119
+ * middle, where the rings are centred and the words sit, and never ends on a sliver at one side
120
+ * only. Down, it counts from the top less the idle shift, so the rules rise a pitch a cycle.
121
+ */
122
+ background-size:
123
+ 100% 100%,
124
+ var(--dds-background-pitch) 100%,
125
+ 100% var(--dds-background-pitch);
126
+ background-repeat: no-repeat, repeat-x, repeat-y;
127
+ background-position:
128
+ left top,
129
+ calc(50% + var(--dds-background-pitch) / 2) top,
130
+ left calc(var(--dds-background-shift, 0%) * -1);
131
+ }
132
+
133
+ /*
134
+ * The rings: circles at size.rings.pitch, centred on the middle of the band's foot, so they spread
135
+ * up and out from where the halftone's glow would rest.
136
+ *
137
+ * Each ring is a hairline at full ink — as much line as a grid rule, so the two read at the same
138
+ * strength — with half a hairline of fade either side. A curve drawn with hard stops steps along
139
+ * every pixel it crosses; the fade anti-aliases it. A ridge that only touched the ink at its peak
140
+ * came out a good deal fainter than the grid in the same colour.
141
+ */
142
+ :where([data-dds-context="marketing"]) .dds-background[data-pattern='rings'] {
143
+ --dds-background-pitch: var(--size-rings-pitch);
144
+ background-image:
145
+ linear-gradient(to bottom, var(--dds-background-fade), transparent),
146
+ var(--dds-background-rings);
147
+ }
148
+
149
+ /*
150
+ * The rings themselves, declared on the root and on its lit copy alike, so each resolves them in
151
+ * its own ink. They start at the idle shift rather than at the centre, which spreads every ring a
152
+ * pitch outward each cycle: a repeating gradient repeats over its first stop to its last, and both
153
+ * move together, so the spacing never changes.
154
+ */
155
+ :where([data-dds-context="marketing"]) .dds-background[data-pattern='rings'],
156
+ :where([data-dds-context="marketing"]) .dds-background[data-pattern='rings']::before {
157
+ --dds-background-rings: repeating-radial-gradient(
158
+ circle at 50% 100%,
159
+ transparent var(--dds-background-shift, 0%)
160
+ calc(var(--dds-background-shift, 0%) + var(--dds-background-pitch) - var(--dds-background-hairline) * 2),
161
+ var(--dds-background-rule)
162
+ calc(var(--dds-background-shift, 0%) + var(--dds-background-pitch) - var(--dds-background-hairline) * 1.5)
163
+ calc(var(--dds-background-shift, 0%) + var(--dds-background-pitch) - var(--dds-background-hairline) * 0.5),
164
+ transparent calc(var(--dds-background-shift, 0%) + var(--dds-background-pitch))
165
+ );
166
+ }
167
+
168
+ /*
169
+ * The pointer's light on the grid and the rings, on an interactive band.
170
+ *
171
+ * A second, brighter copy of the lines, drawn in a pseudo-element behind the band's content and
172
+ * shown only inside a soft circle around the pointer — size.rule-grid.spotlight on the grid,
173
+ * size.rings.spotlight on the rings — so the lines under the pointer brighten and fade back into
174
+ * the resting ones, and nothing else changes. The pointer's position arrives as two custom
175
+ * properties written once a frame by `useSpotlight`, and `data-spotlit` while it is over the band;
176
+ * the circle fades in and out on the element's opacity, which the compositor runs.
177
+ *
178
+ * `::before`, so it comes before the content in the band's painting order and the content stays on
179
+ * top of it. The lit line is a step up from the resting one on each ground: muted-inverse on the
180
+ * dark ground, full white on the coral, the full brand coral on the light one. Same hairline, same pitch, same
181
+ * position, so a lit line is the resting line brighter rather than a line beside it.
182
+ */
183
+ :where([data-dds-context="marketing"]) .dds-background:is([data-pattern='grid'], [data-pattern='rings'])::before {
184
+ --dds-background-lit: var(--color-fg-muted-inverse);
185
+ /* The shift is registered as not inherited — see *The idle motion* — so the lit copy asks for it. */
186
+ --dds-background-shift: inherit;
187
+ content: '';
188
+ position: absolute;
189
+ inset: 0;
190
+ pointer-events: none;
191
+ mask-image: radial-gradient(
192
+ circle var(--dds-background-reach) at var(--dds-background-spot-x, 50%) var(--dds-background-spot-y, 50%),
193
+ #000,
194
+ transparent
195
+ );
196
+ opacity: 0;
197
+ transition-property: opacity;
198
+ transition-duration: var(--motion-duration-normal);
199
+ transition-timing-function: var(--motion-easing-enter);
200
+ }
201
+
202
+ :where([data-dds-context="marketing"]) .dds-background[data-pattern='grid']::before {
203
+ --dds-background-reach: var(--size-rule-grid-spotlight);
204
+ background-image:
205
+ linear-gradient(to right, var(--dds-background-lit) 0 var(--dds-background-hairline), transparent 0),
206
+ linear-gradient(to bottom, var(--dds-background-lit) 0 var(--dds-background-hairline), transparent 0);
207
+ background-size:
208
+ var(--dds-background-pitch) 100%,
209
+ 100% var(--dds-background-pitch);
210
+ background-repeat: repeat-x, repeat-y;
211
+ background-position:
212
+ calc(50% + var(--dds-background-pitch) / 2) top,
213
+ left calc(var(--dds-background-shift, 0%) * -1);
214
+ }
215
+
216
+ /* The rings' lit copy: the same rings, with the rule redeclared as the lit ink. */
217
+ :where([data-dds-context="marketing"]) .dds-background[data-pattern='rings']::before {
218
+ --dds-background-reach: var(--size-rings-spotlight);
219
+ --dds-background-rule: var(--dds-background-lit);
220
+ background-image: var(--dds-background-rings);
221
+ }
222
+
223
+ :where([data-dds-context="marketing"]) .dds-background:is([data-pattern='grid'], [data-pattern='rings'])[data-tone='light']::before {
224
+ --dds-background-lit: var(--color-bg-brand);
225
+ }
226
+
227
+ :where([data-dds-context="marketing"]) .dds-background:is([data-pattern='grid'], [data-pattern='rings'])[data-tone='brand']::before {
228
+ --dds-background-lit: var(--color-fg-on-brand);
229
+ }
230
+
231
+ :where([data-dds-context="marketing"]) .dds-background:is([data-pattern='grid'], [data-pattern='rings'])[data-spotlit='true']::before {
232
+ opacity: 1;
233
+ }
234
+
235
+ /*
236
+ * The aurora, for `pattern="aurora"`: broad soft pools of the brand's colour rising from the
237
+ * band's foot and drifting into one another — the band lit by colour rather than by points.
238
+ *
239
+ * Three ellipses fading to nothing, in two inks, painted on a pseudo-element behind the content
240
+ * and larger than the band, so it can sway either way without its edge showing. The sway is a
241
+ * transform about the middle of the foot, there and back over motion.duration.drift: a small
242
+ * slide, a small turn and a slight swell, and because the turn lifts one side as it lowers the
243
+ * other, the pools pass across each other rather than moving as one. A transform, so the
244
+ * compositor runs it and the gradients are painted once rather than every frame.
245
+ *
246
+ * The inks are the tone's: coral deepening to the brand's darkest on the dark ground; coral and
247
+ * its hover step, washed thin, on the light ground; and on the coral ground white and the brand's
248
+ * darkest, as the halftone's dots turn white there — darker inks alone were lost on the coral, and
249
+ * near-black came out as a muddy stain rather than light. The white stays at the foot, under the
250
+ * words rather than behind them, and the words are checked against the coral alone. How strongly
251
+ * it is painted is a share of the pool's own opacity, as the halftone's is — under half on the
252
+ * dark ground, where coral at seven tenths burned rather than glowed, and faintest on the light,
253
+ * where coral at strength would be a coral band.
254
+ *
255
+ * The band's top stays plain: the pools fade out under a mask before they reach the upper third.
256
+ */
257
+ :where([data-dds-context="marketing"]) .dds-background[data-pattern='aurora'] {
258
+ --dds-background-pool: var(--color-bg-brand);
259
+ --dds-background-pool-deep: var(--color-bg-brand-active);
260
+ --dds-background-strength: 0.4;
261
+ /*
262
+ * How strongly the pool under the pointer is painted, per ground. Its own number rather than a
263
+ * share of the pools': half of the light ground's faint wash was invisible, and half of the
264
+ * coral ground's white washed out the white words the pointer was over.
265
+ */
266
+ --dds-background-hover: 0.25;
267
+ }
268
+
269
+ :where([data-dds-context="marketing"]) .dds-background[data-pattern='aurora'][data-tone='light'] {
270
+ --dds-background-pool-deep: var(--color-bg-brand-hover);
271
+ --dds-background-strength: 0.22;
272
+ --dds-background-hover: 0.3;
273
+ }
274
+
275
+ :where([data-dds-context="marketing"]) .dds-background[data-pattern='aurora'][data-tone='brand'] {
276
+ --dds-background-pool: var(--color-fg-on-brand);
277
+ --dds-background-pool-deep: var(--color-bg-brand-active);
278
+ --dds-background-strength: 0.6;
279
+ --dds-background-hover: 0.2;
280
+ }
281
+
282
+ /*
283
+ * Behind the content without being before it: the root isolates, so a negative stacking order
284
+ * puts both pseudo-elements over the band's ground and under everything it holds.
285
+ */
286
+ :where([data-dds-context="marketing"]) .dds-background[data-pattern='aurora']::before,
287
+ :where([data-dds-context="marketing"]) .dds-background[data-pattern='aurora']::after {
288
+ content: '';
289
+ position: absolute;
290
+ z-index: -1;
291
+ pointer-events: none;
292
+ }
293
+
294
+ :where([data-dds-context="marketing"]) .dds-background[data-pattern='aurora']::before {
295
+ /*
296
+ * Half a band deeper than the band, as well as wider: the turn lowers one end of the layer, and
297
+ * a layer that stopped at the foot showed its own edge there as a hard diagonal. So the foot is
298
+ * two thirds of the way down the layer, and every position and reach below is measured to it.
299
+ */
300
+ inset-block: 0 -50%;
301
+ inset-inline: -25%;
302
+ background-image:
303
+ radial-gradient(ellipse 30% 53% at 32% calc(100% / 1.5), var(--dds-background-pool), transparent),
304
+ radial-gradient(ellipse 26% 43% at 66% calc(100% / 1.5), var(--dds-background-pool-deep), transparent),
305
+ radial-gradient(ellipse 18% 31% at 50% calc(100% / 1.5), var(--dds-background-pool), transparent);
306
+ mask-image: linear-gradient(to bottom, transparent 20%, #000 calc(100% / 1.5));
307
+ opacity: var(--dds-background-strength);
308
+ transform-origin: 50% calc(100% / 1.5);
309
+ animation: dds-background-aurora var(--motion-duration-drift) var(--motion-easing-shift) infinite alternate;
310
+ }
311
+
312
+ @keyframes dds-background-aurora {
313
+ from {
314
+ transform: translateX(-6%) rotate(-3deg);
315
+ }
316
+ to {
317
+ transform: translateX(6%) rotate(3deg) scale(1.08, 1.14);
318
+ }
319
+ }
320
+
321
+ /*
322
+ * The pool that trails the pointer: a disc of the pool's ink, size.aurora.spotlight in radius,
323
+ * moved under the pointer by a transform that eases after it over motion.duration.trail — the
324
+ * halftone's glow and this one keep the same distance behind the hand. Placed from the band's
325
+ * physical top left because the pointer's position is measured from there. It fades in while the
326
+ * pointer is over the band and out when it leaves, at the ground's own hover strength: at the
327
+ * pools' full strength, and twice the size, it washed over a narrow band and drowned its words.
328
+ *
329
+ * It trails the pointer only while lit. Unlit it moves at once, so the pointer arriving puts the
330
+ * pool where the pointer is before it fades in — `useSpotlight` writes the position first —
331
+ * rather than the pool sliding in from wherever it last was, or from the band's corner.
332
+ */
333
+ :where([data-dds-context="marketing"]) .dds-background[data-pattern='aurora']::after {
334
+ left: 0;
335
+ top: 0;
336
+ inline-size: calc(var(--size-aurora-spotlight) * 2);
337
+ block-size: calc(var(--size-aurora-spotlight) * 2);
338
+ /*
339
+ * Eased to nothing rather than straight to it: a straight fade stops abruptly at its rim, and the
340
+ * eye read the stop as the edge of a disc. The steps follow a smooth curve down from the centre.
341
+ */
342
+ background-image: radial-gradient(
343
+ circle closest-side,
344
+ var(--dds-background-pool),
345
+ color-mix(in srgb, var(--dds-background-pool) 62%, transparent) 30%,
346
+ color-mix(in srgb, var(--dds-background-pool) 26%, transparent) 58%,
347
+ color-mix(in srgb, var(--dds-background-pool) 7%, transparent) 82%,
348
+ transparent
349
+ );
350
+ transform: translate(
351
+ calc(var(--dds-background-spot-x, 50%) - var(--size-aurora-spotlight)),
352
+ calc(var(--dds-background-spot-y, 50%) - var(--size-aurora-spotlight))
353
+ );
354
+ opacity: 0;
355
+ transition-property: opacity, transform;
356
+ transition-duration: var(--motion-duration-normal), var(--motion-duration-instant);
357
+ transition-timing-function: var(--motion-easing-enter);
358
+ }
359
+
360
+ :where([data-dds-context="marketing"]) .dds-background[data-pattern='aurora'][data-spotlit='true']::after {
361
+ opacity: var(--dds-background-hover);
362
+ transition-duration: var(--motion-duration-normal), var(--motion-duration-trail);
363
+ }
364
+
365
+ /*
366
+ * The idle motion of the grid and the rings: the grid's rules rise a pitch and the rings spread a
367
+ * pitch outward, over motion.duration.drift at an even pace, and again — a pitch on, the pattern
368
+ * is where it began, so the loop has no seam. The halftone's glow and the
369
+ * aurora's pools drift in the same time, so every pattern is moving at a band's pace at rest.
370
+ *
371
+ * The shift is registered so it can be animated: an unregistered custom property only flips from
372
+ * one value to the next. It is not inherited, so the frames reach the root and the lit copy that
373
+ * asks for it, and never restyle the band's content. What moves is a background's start, which
374
+ * repaints the band whenever it changes — so it changes in two hundred even steps a cycle, about
375
+ * twenty a second, rather than at the display's rate: well under a pixel a step at this pace,
376
+ * so it still reads as smooth, and as cheap as the halftone's drift, which draws at thirty. It
377
+ * stops while the band is off screen (`data-offscreen`, written by Background), and when the band
378
+ * is asked to hold still.
379
+ */
380
+ @property --dds-background-shift {
381
+ syntax: '<length>';
382
+ inherits: false;
383
+ initial-value: 0;
384
+ }
385
+ /*
386
+ * Every use names a fallback of nothing, for an engine without `@property`: there the shift is an
387
+ * unregistered property nobody sets while the band is still, and a bare reference to it would make
388
+ * the whole background invalid — the grid and the rings would vanish exactly when motion is off.
389
+ */
390
+
391
+ :where([data-dds-context="marketing"]) .dds-background:is([data-pattern='grid'], [data-pattern='rings']) {
392
+ animation: dds-background-shift var(--motion-duration-drift) steps(200) infinite;
393
+ }
394
+
395
+ @keyframes dds-background-shift {
396
+ to {
397
+ --dds-background-shift: var(--dds-background-pitch);
398
+ }
399
+ }
400
+
401
+ /* Off screen, nobody sees it move: every pattern's idle motion waits where it is. */
402
+ :where([data-dds-context="marketing"]) .dds-background[data-offscreen='true'],
403
+ :where([data-dds-context="marketing"]) .dds-background[data-offscreen='true']::before {
404
+ animation-play-state: paused;
405
+ }
406
+
407
+ /* `animated={false}`: every pattern holds still at rest. What it does under the pointer is `interactive`. */
408
+ :where([data-dds-context="marketing"]) .dds-background[data-animated='false'],
409
+ :where([data-dds-context="marketing"]) .dds-background[data-animated='false'][data-pattern='aurora']::before {
410
+ animation: none;
411
+ }
412
+
413
+ /*
414
+ * For a reader who asked for less motion, nothing moves at rest, and the pointer's light on every
415
+ * pattern comes and goes at once rather than fading or trailing.
416
+ */
417
+ @media (prefers-reduced-motion: reduce) {
418
+ :where([data-dds-context="marketing"]) .dds-background:is([data-pattern='grid'], [data-pattern='rings']),
419
+ :where([data-dds-context="marketing"]) .dds-background[data-pattern='aurora']::before {
420
+ animation: none;
421
+ }
422
+
423
+ :where([data-dds-context="marketing"]) .dds-background:is([data-pattern='grid'], [data-pattern='rings'], [data-pattern='aurora'])::before,
424
+ :where([data-dds-context="marketing"]) .dds-background[data-pattern='aurora']::after {
425
+ transition: none;
426
+ }
427
+ }
@@ -0,0 +1,123 @@
1
+ /**
2
+ * The halftone, drawn on a canvas and only when it has to be.
3
+ *
4
+ * A grid of dots, each as large and as bright as the glow is where it sits: full at the glow's
5
+ * centre, falling away along a soft ellipse, and gone at the edges. The glow rests at the middle of
6
+ * the band's bottom edge and — where the band allows it — follows the pointer. The dots themselves
7
+ * answer the pointer too: a moving pointer shoves the ones near it aside and a click sends a ripple
8
+ * out from where it landed, and each dot springs back to its place in the grid.
9
+ *
10
+ * What it costs is the reason it is written this way rather than as CSS or as an animation loop:
11
+ *
12
+ * - It draws on change and nowhere else. A resize draws one frame; a pointer draws frames only
13
+ * while the glow is catching up or a dot is still moving. The resting drift is the one thing that
14
+ * keeps it drawing, at half the display's rate, and only while the band is on screen and motion
15
+ * is allowed — with the drift set to nothing, a band nobody is touching runs no code at all.
16
+ * - A band out of view draws nothing, and catches up with one frame when it returns.
17
+ * - The bitmap is held to twice the CSS pixel density, which is all a dot this small can use.
18
+ * - The dots are batched by brightness into a few dozen paths, so a frame is a few dozen fills
19
+ * rather than one per dot. Drawing visits only the rows below the pattern's edge, rejects a dot
20
+ * too small to be seen before working out its light — every dot, while the size floor keeps the
21
+ * screen across the whole band — and draws a dot under a pixel and a half as a square, which
22
+ * nobody can tell from a circle at that size.
23
+ * - Pointer events only record where the pointer is. The frame reads the band's position once and
24
+ * acts on it, so a fast mouse costs one layout read a frame rather than one per event; a shove or
25
+ * a ripple visits the grid cells it can reach, and the spring visits only the dots still moving.
26
+ *
27
+ * Every default comes from a token: the inks are the canvas's computed `color` (the glow's edge) and
28
+ * `outline-color` (its centre), the grid is
29
+ * size.halftone.pitch and size.halftone.dot, and the glow's movement is the canvas's transition
30
+ * duration and timing function. The rest are settings this file names, and `retune` overrides any
31
+ * of them while a pattern is being decided — the preview's Tune panel, and nothing a page uses.
32
+ */
33
+ export interface HalftoneOptions {
34
+ /** Whether the pattern answers the pointer. Ignored for reduced motion. */
35
+ interactive: boolean;
36
+ /** Whether the resting glow drifts on its own. Ignored for reduced motion. */
37
+ animated: boolean;
38
+ /**
39
+ * Where the pointer is listened for, when that is not the band itself — a Background used as a
40
+ * layer behind a block's content, where the content covers the layer and the pointer never
41
+ * reaches it. Positions are still measured against the band.
42
+ */
43
+ host?: HTMLElement;
44
+ }
45
+ export interface Halftone {
46
+ /** Stop listening, stop drawing, and let go of every observer. */
47
+ destroy: () => void;
48
+ }
49
+ /** Every setting the pattern is drawn with. Colours are any CSS colour; lengths are CSS pixels. */
50
+ export interface HalftoneTuning {
51
+ /** The dots' colour at the edge of the glow. Defaults to the canvas's `color`. */
52
+ base: string;
53
+ /** The dots' colour at the glow's centre. Defaults to the canvas's outline colour. */
54
+ active: string;
55
+ /** How opaque the faintest dots are, from 0 to 1 — what makes the edge a fade, not a line. */
56
+ floor: number;
57
+ /**
58
+ * How strong the whole pattern is, from 0 to 1: every dot's opacity, scaled. The pattern's shape
59
+ * is untouched — the glow, the fade, the floor — only how loudly it is drawn.
60
+ */
61
+ opacity: number;
62
+ /** The widest a dot grows, at the glow's centre. size.halftone.dot. */
63
+ dot: number;
64
+ /** Centre to centre, between neighbouring dots. size.halftone.pitch. */
65
+ pitch: number;
66
+ /** How wide the glow reaches, as a share of the band's width. */
67
+ reachX: number;
68
+ /** How tall the glow reaches, as a share of the band's height. */
69
+ reachY: number;
70
+ /**
71
+ * How large the glow is while a pointer is over the band, as a share of its resting size. Below 1
72
+ * the light gathers under the pointer instead of washing across the band it rested across.
73
+ */
74
+ hoverScale: number;
75
+ /**
76
+ * Where the pattern begins, as a share of the band's height from the top. Above it no dot is
77
+ * drawn, whatever the glow is doing — 0.3 would keep the pattern to the band's lower seven tenths.
78
+ */
79
+ top: number;
80
+ /** How soft the pattern's upper edge is, as a share of the band's height. */
81
+ topFade: number;
82
+ /**
83
+ * The smallest a dot gets, however far it is from the glow, as a share of its widest — what
84
+ * keeps the screen of dots across the whole band rather than only around the glow. 0 lets the
85
+ * dots far from the glow shrink to nothing.
86
+ */
87
+ sizeFloor: number;
88
+ /** The highest the glow may rise towards the pointer, as a share of the band's height from the top. */
89
+ ceiling: number;
90
+ /** How sharply the glow falls off — higher is a tighter, harder-edged glow. */
91
+ falloff: number;
92
+ /** Where the glow rests, as a share of the band's width. */
93
+ restX: number;
94
+ /** Where the glow rests, as a share of the band's height. */
95
+ restY: number;
96
+ /** How long the glow takes to catch up with the pointer and to glide back to rest, in ms. */
97
+ follow: number;
98
+ /** How far the resting glow drifts either side of its rest, as a share of the band's width. 0 holds it still. */
99
+ drift: number;
100
+ /** How far it rises and falls as it drifts, as a share of the band's height. */
101
+ driftRise: number;
102
+ /** How long one sweep of the drift takes, in seconds — longer is slower. */
103
+ driftPeriod: number;
104
+ /** How far from a moving pointer the dots are shoved aside, in px. 0 turns the shove off. */
105
+ proximity: number;
106
+ /** How hard the dots resist being shoved — higher moves them less. */
107
+ resistance: number;
108
+ /** How long a shoved dot takes to settle back into the grid, in seconds. */
109
+ returnDuration: number;
110
+ /** How far a click's ripple reaches, in px. 0 turns the ripple off. */
111
+ shockRadius: number;
112
+ /** How hard a click's ripple pushes the dots it reaches. */
113
+ shockStrength: number;
114
+ }
115
+ /**
116
+ * Override settings on every halftone on the page, live. For deciding a pattern — the preview's
117
+ * Tune panel calls it — and never for a page: what a page draws is what the tokens and the
118
+ * settings above say.
119
+ */
120
+ export declare function retune(next: Partial<HalftoneTuning>): void;
121
+ /** The settings the last halftone drew with, overrides included — for a panel to show. */
122
+ export declare function currentTuning(): HalftoneTuning | null;
123
+ export declare function halftone(root: HTMLElement, canvas: HTMLCanvasElement, options: HalftoneOptions): Halftone;