@repobit/dex-system-design 0.23.10 → 0.23.11
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.
- package/CHANGELOG.md +8 -0
- package/package.json +2 -2
- package/src/components/anchor/anchor-nav.css.js +333 -84
- package/src/components/anchor/anchor-nav.js +333 -58
- package/src/components/awards/awards-light.js +8 -2
- package/src/components/awards/awards-light.stories.js +25 -107
- package/src/components/awards/awards.css.js +1 -1
- package/src/components/compare/compare.css.js +29 -26
- package/src/components/compare/compare.stories.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [0.23.11](https://github.com/bitdefender/dex-core/compare/@repobit/dex-system-design@0.23.10...@repobit/dex-system-design@0.23.11) (2026-04-01)
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **DEX-1014:** fix gap and card sizes
|
|
11
|
+
* **DEX-1014:** new changes for anchor-nav and compare to solve some issues
|
|
12
|
+
|
|
13
|
+
|
|
6
14
|
## [0.23.10](https://github.com/bitdefender/dex-core/compare/@repobit/dex-system-design@0.23.9...@repobit/dex-system-design@0.23.10) (2026-03-31)
|
|
7
15
|
|
|
8
16
|
### Bug Fixes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@repobit/dex-system-design",
|
|
3
|
-
"version": "0.23.
|
|
3
|
+
"version": "0.23.11",
|
|
4
4
|
"description": "Design system based on Web Components.",
|
|
5
5
|
"author": "Iordache Matei Cezar <miordache@bitdefender.com>",
|
|
6
6
|
"homepage": "https://github.com/bitdefender/dex-core#readme",
|
|
@@ -86,5 +86,5 @@
|
|
|
86
86
|
"volta": {
|
|
87
87
|
"node": "24.14.0"
|
|
88
88
|
},
|
|
89
|
-
"gitHead": "
|
|
89
|
+
"gitHead": "d585162a40165e7aa8f96463433be03d5aef2f05"
|
|
90
90
|
}
|
|
@@ -1,92 +1,341 @@
|
|
|
1
1
|
import { css } from "lit";
|
|
2
2
|
|
|
3
3
|
export const anchorNavStyles = css`
|
|
4
|
-
:host {
|
|
5
|
-
padding: var(--spacing-0) 15em;
|
|
6
|
-
margin: var(--spacing-0) auto;
|
|
7
|
-
--bd-accesibility-focus: var(--color-blue-500);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
.bd-container {
|
|
11
|
-
flex-wrap: wrap;
|
|
12
|
-
justify-content: space-between;
|
|
13
|
-
gap: 2.5em;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
a:focus-visible {
|
|
17
|
-
outline: var(--spacing-2) solid var(--bd-accesibility-focus);
|
|
18
|
-
outline-offset: var(--spacing-2);
|
|
19
|
-
border-radius: var(--space-2xs);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
nav {
|
|
23
|
-
position: sticky;
|
|
24
|
-
top: 0;
|
|
25
|
-
z-index: 1000;
|
|
26
|
-
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
|
|
27
|
-
display: flex;
|
|
28
|
-
justify-content: space-between;
|
|
29
|
-
align-items: center;
|
|
30
|
-
border-top: 1px solid #f0f0f0;
|
|
31
|
-
border-bottom: 1px solid #f0f0f0;
|
|
32
|
-
padding: var(--spacing-10) 15em;
|
|
33
|
-
width: 100%;
|
|
34
|
-
box-sizing: border-box;
|
|
35
|
-
background: white;
|
|
36
|
-
margin-bottom: var(--spacing-64);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
.bd-container {
|
|
40
|
-
padding: 50px;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
[id$="-section"] {
|
|
45
|
-
scroll-margin-top: 120px;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
.anchor-links {
|
|
49
|
-
position: relative;
|
|
50
|
-
display: flex;
|
|
51
|
-
gap: var(--spacing-32);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
a {
|
|
55
|
-
position: relative;
|
|
56
|
-
text-decoration: none;
|
|
57
|
-
color: black;
|
|
58
|
-
font-weight: 600;
|
|
59
|
-
padding: var(--spacing-10) var(--spacing-0);
|
|
60
|
-
display: inline-block;
|
|
61
|
-
font-size: var(--typography-body-regular-fontSize) !important;
|
|
62
|
-
font-family: var(--typography-fontFamily-sans) !important;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
a::after {
|
|
66
|
-
content: "";
|
|
67
|
-
position: absolute;
|
|
68
|
-
left: var(--spacing-0);
|
|
69
|
-
bottom: -14px;
|
|
70
|
-
width: 0%;
|
|
71
|
-
height: 3px;
|
|
72
|
-
background-color: #006dff;
|
|
73
|
-
transition: width 0.3s ease;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
a.active::after {
|
|
77
|
-
width: 100%;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
`;
|
|
81
|
-
|
|
82
|
-
export const anchorNavItemStyles = css`
|
|
83
4
|
:host {
|
|
84
|
-
display:
|
|
5
|
+
display: block;
|
|
6
|
+
position: sticky;
|
|
7
|
+
top: var(--spacing-0);
|
|
8
|
+
z-index: 1000;
|
|
9
|
+
border-top: var(--border-width-1) solid var(--color-neutral-100);
|
|
10
|
+
border-bottom: var(--border-width-1) solid var(--color-neutral-100);
|
|
11
|
+
padding-block: var(--spacing-10);
|
|
12
|
+
padding-inline: var(--layout-ensemble-inline-padding);
|
|
13
|
+
width: 100%;
|
|
14
|
+
box-sizing: border-box;
|
|
15
|
+
background: var(--color-neutral-0);
|
|
16
|
+
margin-bottom: var(--spacing-0);
|
|
17
|
+
--bd-accesibility-focus: var(--color-blue-500);
|
|
85
18
|
}
|
|
86
19
|
|
|
87
|
-
:
|
|
20
|
+
a:focus-visible,
|
|
21
|
+
.bd-anchor-nav__cta:focus-visible,
|
|
22
|
+
.bd-anchor-nav__dropdown-toggle:focus-visible,
|
|
23
|
+
.bd-anchor-nav__dropdown-option:focus-visible {
|
|
24
|
+
outline: var(--spacing-2) solid var(--bd-accesibility-focus);
|
|
25
|
+
outline-offset: var(--spacing-2);
|
|
26
|
+
border-radius: var(--space-2xs);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
nav {
|
|
88
30
|
display: block;
|
|
89
|
-
|
|
90
|
-
|
|
31
|
+
margin: var(--spacing-0);
|
|
32
|
+
padding: var(--spacing-0);
|
|
33
|
+
width: 100%;
|
|
34
|
+
box-sizing: border-box;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.bd-anchor-nav__inner {
|
|
38
|
+
display: flex;
|
|
39
|
+
flex-wrap: wrap;
|
|
40
|
+
justify-content: space-between;
|
|
41
|
+
align-items: center;
|
|
42
|
+
gap: var(--spacing-32);
|
|
43
|
+
width: 100%;
|
|
44
|
+
max-width: var(--layout-compare-grid-max);
|
|
45
|
+
margin-inline: auto;
|
|
46
|
+
box-sizing: border-box;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.anchor-links {
|
|
50
|
+
position: relative;
|
|
51
|
+
display: flex;
|
|
52
|
+
flex-wrap: wrap;
|
|
53
|
+
align-items: center;
|
|
54
|
+
gap: var(--spacing-16) var(--spacing-32);
|
|
55
|
+
row-gap: var(--spacing-8);
|
|
56
|
+
flex: 1 1 auto;
|
|
57
|
+
min-width: 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.anchor-links--desktop {
|
|
61
|
+
display: flex;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.bd-anchor-nav__dropdown {
|
|
65
|
+
display: none;
|
|
66
|
+
position: relative;
|
|
67
|
+
flex: 1 1 auto;
|
|
68
|
+
min-width: 0;
|
|
69
|
+
width: 100%;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* ── Dropdown toggle ── */
|
|
73
|
+
|
|
74
|
+
.bd-anchor-nav__dropdown-toggle {
|
|
75
|
+
display: flex;
|
|
76
|
+
align-items: center;
|
|
77
|
+
justify-content: space-between;
|
|
78
|
+
gap: var(--spacing-12);
|
|
79
|
+
width: 100%;
|
|
80
|
+
min-height: var(--dimension-48px);
|
|
81
|
+
padding-block: var(--spacing-10);
|
|
82
|
+
padding-inline: var(--spacing-16);
|
|
83
|
+
box-sizing: border-box;
|
|
84
|
+
font-family: var(--typography-fontFamily-sans);
|
|
85
|
+
font-size: var(--typography-body-regular-fontSize);
|
|
86
|
+
font-weight: var(--typography-fontWeight-semibold);
|
|
87
|
+
color: var(--color-neutral-900);
|
|
88
|
+
background: var(--color-neutral-25);
|
|
89
|
+
border: var(--border-width-1) solid var(--color-neutral-200);
|
|
90
|
+
border-radius: var(--radius-lg);
|
|
91
|
+
cursor: pointer;
|
|
92
|
+
text-align: left;
|
|
93
|
+
transition:
|
|
94
|
+
background var(--transition-duration-fast) var(--transition-easing-smooth),
|
|
95
|
+
border-color var(--transition-duration-fast) var(--transition-easing-smooth),
|
|
96
|
+
border-radius var(--transition-duration-fast) var(--transition-easing-smooth);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.bd-anchor-nav__dropdown-toggle:hover {
|
|
100
|
+
background: var(--color-neutral-50);
|
|
101
|
+
border-color: var(--color-neutral-300);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.bd-anchor-nav__dropdown-toggle[aria-expanded="true"] {
|
|
105
|
+
background: var(--color-neutral-50);
|
|
106
|
+
border-color: var(--color-blue-200);
|
|
107
|
+
border-radius: var(--radius-lg) var(--radius-lg) var(--spacing-0) var(--spacing-0);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.bd-anchor-nav__dropdown-label {
|
|
111
|
+
flex: 1 1 auto;
|
|
112
|
+
min-width: 0;
|
|
113
|
+
overflow: hidden;
|
|
114
|
+
text-overflow: ellipsis;
|
|
115
|
+
white-space: nowrap;
|
|
116
|
+
transform-origin: left center;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.bd-anchor-nav__dropdown-chevron {
|
|
120
|
+
flex: 0 0 auto;
|
|
121
|
+
display: flex;
|
|
122
|
+
color: var(--color-neutral-600);
|
|
123
|
+
transition: transform var(--transition-duration-normal) var(--transition-easing-smooth);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.bd-anchor-nav__dropdown-toggle[aria-expanded="true"] .bd-anchor-nav__dropdown-chevron {
|
|
127
|
+
transform: rotate(180deg);
|
|
128
|
+
color: var(--color-blue-500);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* ── Dropdown panel ── */
|
|
132
|
+
|
|
133
|
+
.bd-anchor-nav__dropdown-panel {
|
|
134
|
+
position: absolute;
|
|
135
|
+
left: var(--spacing-0);
|
|
136
|
+
right: var(--spacing-0);
|
|
137
|
+
top: 100%;
|
|
138
|
+
z-index: 1001;
|
|
139
|
+
max-height: min(50vh, 20rem);
|
|
140
|
+
overflow-y: auto;
|
|
141
|
+
background: var(--color-neutral-0);
|
|
142
|
+
border: var(--border-width-1) solid var(--color-blue-200);
|
|
143
|
+
border-top: none;
|
|
144
|
+
border-radius: var(--spacing-0) var(--spacing-0) var(--radius-lg) var(--radius-lg);
|
|
145
|
+
box-shadow: var(--shadow-md);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.bd-anchor-nav__dropdown-list {
|
|
149
|
+
list-style: none;
|
|
150
|
+
margin: var(--spacing-0);
|
|
151
|
+
padding: var(--spacing-8);
|
|
152
|
+
display: flex;
|
|
153
|
+
flex-direction: column;
|
|
154
|
+
gap: var(--spacing-2);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/* ── Dropdown options ── */
|
|
158
|
+
|
|
159
|
+
.bd-anchor-nav__dropdown-option {
|
|
160
|
+
display: flex;
|
|
161
|
+
align-items: center;
|
|
162
|
+
width: 100%;
|
|
163
|
+
min-height: var(--dimension-48px);
|
|
164
|
+
padding-block: var(--spacing-12);
|
|
165
|
+
padding-inline: var(--spacing-16);
|
|
166
|
+
box-sizing: border-box;
|
|
167
|
+
font-family: var(--typography-fontFamily-sans);
|
|
168
|
+
font-size: var(--typography-body-regular-fontSize);
|
|
169
|
+
font-weight: var(--typography-fontWeight-normal);
|
|
170
|
+
color: var(--color-neutral-900);
|
|
171
|
+
background: transparent;
|
|
172
|
+
border: none;
|
|
173
|
+
border-radius: var(--radius-md);
|
|
174
|
+
cursor: pointer;
|
|
175
|
+
text-align: left;
|
|
176
|
+
transition:
|
|
177
|
+
color var(--transition-duration-fast) var(--transition-easing-smooth),
|
|
178
|
+
background var(--transition-duration-fast) var(--transition-easing-smooth);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.bd-anchor-nav__dropdown-option:hover:not(.bd-anchor-nav__dropdown-option--active) {
|
|
182
|
+
color: var(--color-blue-500);
|
|
183
|
+
background: var(--color-neutral-25);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.bd-anchor-nav__dropdown-option--active {
|
|
187
|
+
font-weight: var(--typography-fontWeight-semibold);
|
|
188
|
+
background: var(--color-blue-50);
|
|
189
|
+
color: var(--color-blue-700);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.bd-anchor-nav__dropdown-option--active:hover {
|
|
193
|
+
color: var(--color-blue-700);
|
|
194
|
+
background: var(--color-blue-50);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/* ── Desktop links ── */
|
|
198
|
+
|
|
199
|
+
a {
|
|
200
|
+
position: relative;
|
|
201
|
+
text-decoration: none;
|
|
202
|
+
color: var(--color-neutral-900);
|
|
203
|
+
font-weight: var(--typography-fontWeight-normal);
|
|
204
|
+
padding: var(--spacing-10) var(--spacing-0);
|
|
205
|
+
display: inline-block;
|
|
206
|
+
font-size: var(--typography-body-regular-fontSize);
|
|
207
|
+
font-family: var(--typography-body-regular-fontFamily);
|
|
208
|
+
transition: color var(--transition-duration-fast) var(--transition-easing-smooth);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
a.active {
|
|
212
|
+
font-weight: var(--typography-fontWeight-semibold);
|
|
213
|
+
color: var(--color-neutral-900);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
a:hover:not(.active) {
|
|
217
|
+
color: var(--color-blue-500);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
a.active:hover {
|
|
221
|
+
color: var(--color-neutral-900);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
a::after {
|
|
225
|
+
content: "";
|
|
226
|
+
position: absolute;
|
|
227
|
+
left: var(--spacing-0);
|
|
228
|
+
bottom: calc(-1 * var(--spacing-14));
|
|
229
|
+
width: 0%;
|
|
230
|
+
height: var(--border-width-3);
|
|
231
|
+
background-color: var(--color-blue-500);
|
|
232
|
+
transition: width var(--transition-duration-slow) var(--transition-easing-ease-out);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
a.active::after {
|
|
236
|
+
width: 100%;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/* ── CTA button ── */
|
|
240
|
+
|
|
241
|
+
.bd-anchor-nav__cta {
|
|
242
|
+
font-family: var(--typography-fontFamily-sans);
|
|
243
|
+
font-size: var(--typography-fontSize-lg);
|
|
244
|
+
font-weight: var(--typography-fontWeight-semibold);
|
|
245
|
+
line-height: var(--typography-lineHeight-normal);
|
|
246
|
+
padding: var(--spacing-10) var(--spacing-20);
|
|
247
|
+
border: none;
|
|
248
|
+
border-radius: var(--radius-md);
|
|
249
|
+
cursor: pointer;
|
|
250
|
+
background: var(--color-red-500);
|
|
251
|
+
color: var(--color-neutral-0);
|
|
252
|
+
transition: background var(--transition-duration-fast) var(--transition-easing-smooth);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.bd-anchor-nav__cta:hover {
|
|
256
|
+
background: var(--color-red-600);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.bd-anchor-nav__cta:active {
|
|
260
|
+
background: var(--color-red-700);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/* ── Animations ── */
|
|
264
|
+
|
|
265
|
+
@keyframes bd-anchor-nav-dropdown-label-swap {
|
|
266
|
+
from {
|
|
267
|
+
opacity: 0.35;
|
|
268
|
+
transform: translateY(var(--spacing-4));
|
|
269
|
+
}
|
|
270
|
+
to {
|
|
271
|
+
opacity: 1;
|
|
272
|
+
transform: translateY(var(--spacing-0));
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
@media (prefers-reduced-motion: reduce) {
|
|
277
|
+
a::after,
|
|
278
|
+
.bd-anchor-nav__dropdown-chevron,
|
|
279
|
+
.bd-cta,
|
|
280
|
+
.bd-anchor-nav__dropdown-label--swap {
|
|
281
|
+
animation: none !important;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/* ── Mobile ── */
|
|
286
|
+
|
|
287
|
+
@media (max-width: 768px) {
|
|
288
|
+
:host {
|
|
289
|
+
padding-block: var(--spacing-10);
|
|
290
|
+
padding-inline: var(--layout-ensemble-inline-padding);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.bd-anchor-nav__inner {
|
|
294
|
+
flex-direction: column;
|
|
295
|
+
align-items: stretch;
|
|
296
|
+
gap: var(--spacing-16);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.anchor-links--desktop {
|
|
300
|
+
display: none !important;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.bd-anchor-nav__dropdown {
|
|
304
|
+
display: block;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.bd-anchor-nav__dropdown-label.bd-anchor-nav__dropdown-label--swap {
|
|
308
|
+
animation: bd-anchor-nav-dropdown-label-swap var(--transition-duration-normal)
|
|
309
|
+
var(--transition-easing-smooth);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.bd-cta {
|
|
313
|
+
align-self: stretch;
|
|
314
|
+
display: block;
|
|
315
|
+
overflow: hidden;
|
|
316
|
+
max-height: var(--spacing-0);
|
|
317
|
+
opacity: 0;
|
|
318
|
+
transform: translateY(calc(-1 * var(--spacing-8)));
|
|
319
|
+
pointer-events: none;
|
|
320
|
+
margin-top: calc(-1 * var(--spacing-16));
|
|
321
|
+
transition:
|
|
322
|
+
max-height var(--transition-duration-slow) var(--transition-easing-ease-out),
|
|
323
|
+
opacity var(--transition-duration-normal) var(--transition-easing-smooth),
|
|
324
|
+
transform var(--transition-duration-normal) var(--transition-easing-smooth),
|
|
325
|
+
margin-top var(--transition-duration-normal) var(--transition-easing-smooth);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.bd-anchor-nav__inner[data-mobile-cta-visible] .bd-cta {
|
|
329
|
+
max-height: var(--spacing-128);
|
|
330
|
+
opacity: 1;
|
|
331
|
+
transform: translateY(var(--spacing-0));
|
|
332
|
+
pointer-events: auto;
|
|
333
|
+
margin-top: var(--spacing-0);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.bd-anchor-nav__cta {
|
|
337
|
+
width: 100%;
|
|
338
|
+
box-sizing: border-box;
|
|
339
|
+
}
|
|
91
340
|
}
|
|
92
|
-
`;
|
|
341
|
+
`;
|
|
@@ -1,8 +1,20 @@
|
|
|
1
|
-
import { LitElement, html } from "lit";
|
|
1
|
+
import { LitElement, html, nothing } from "lit";
|
|
2
2
|
import { tokens } from "../../tokens/tokens.js";
|
|
3
|
-
import "../Button/Button.js";
|
|
4
3
|
import { anchorNavStyles } from "./anchor-nav.css.js";
|
|
5
4
|
|
|
5
|
+
/** Parse token length from computed style (e.g. `8rem` → px). */
|
|
6
|
+
function parseLengthToPx(value) {
|
|
7
|
+
const s = (value || "").trim();
|
|
8
|
+
if (!s) return 0;
|
|
9
|
+
if (s.endsWith("px")) return parseFloat(s);
|
|
10
|
+
if (s.endsWith("rem")) {
|
|
11
|
+
const root =
|
|
12
|
+
parseFloat(getComputedStyle(document.documentElement).fontSize) || 16;
|
|
13
|
+
return parseFloat(s) * root;
|
|
14
|
+
}
|
|
15
|
+
return parseFloat(s) || 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
6
18
|
class BdAnchorNavItem extends HTMLElement {
|
|
7
19
|
constructor() {
|
|
8
20
|
super();
|
|
@@ -14,67 +26,267 @@ class BdAnchorNavItem extends HTMLElement {
|
|
|
14
26
|
}
|
|
15
27
|
|
|
16
28
|
render() {
|
|
17
|
-
this.shadowRoot.innerHTML =
|
|
18
|
-
<nav>
|
|
19
|
-
<slot></slot>
|
|
20
|
-
</nav>
|
|
21
|
-
`;
|
|
29
|
+
this.shadowRoot.innerHTML = `<slot></slot>`;
|
|
22
30
|
}
|
|
23
31
|
}
|
|
24
32
|
|
|
25
|
-
|
|
26
33
|
class BdAnchorNav extends LitElement {
|
|
34
|
+
static properties = {
|
|
35
|
+
activeId : { type: String, state: true },
|
|
36
|
+
/** Landmark label for the outer `<nav>` (screen readers). */
|
|
37
|
+
navLabel : { type: String, attribute: "aria-label" },
|
|
38
|
+
/** Mobile dropdown open state */
|
|
39
|
+
_dropdownOpen : { type: Boolean, state: true },
|
|
40
|
+
/** Mobile: show Buy now only after first anchor section has been scrolled past */
|
|
41
|
+
_mobileCtaRevealed: { type: Boolean, state: true }
|
|
42
|
+
};
|
|
43
|
+
|
|
27
44
|
static styles = [tokens, anchorNavStyles];
|
|
28
45
|
|
|
29
46
|
constructor() {
|
|
30
47
|
super();
|
|
31
48
|
this.activeId = "";
|
|
49
|
+
this.navLabel = "Section navigation";
|
|
50
|
+
this._dropdownOpen = false;
|
|
51
|
+
this._mobileCtaRevealed = false;
|
|
52
|
+
this._panelId = `bd-anchor-nav-panel-${Math.random().toString(36)
|
|
53
|
+
.slice(2, 11)}`;
|
|
54
|
+
this._onScrollSpy = this._onScrollSpy.bind(this);
|
|
55
|
+
this._onDocumentPointerDown = this._onDocumentPointerDown.bind(this);
|
|
56
|
+
this._onKeydown = this._onKeydown.bind(this);
|
|
57
|
+
this._scrollPending = false;
|
|
58
|
+
/** While set, scrollspy must not overwrite activeId during smooth scroll to a clicked section */
|
|
59
|
+
this._programmaticScrollTargetId = null;
|
|
60
|
+
this._unlockScrollSpyTimer = null;
|
|
61
|
+
this._scrollEndUnlockHandler = null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
connectedCallback() {
|
|
65
|
+
super.connectedCallback();
|
|
66
|
+
window.addEventListener("scroll", this._onScrollSpy, { passive: true });
|
|
67
|
+
window.addEventListener("resize", this._onScrollSpy, { passive: true });
|
|
68
|
+
document.addEventListener("pointerdown", this._onDocumentPointerDown, true);
|
|
69
|
+
document.addEventListener("keydown", this._onKeydown, true);
|
|
70
|
+
queueMicrotask(() => this._runScrollSpy());
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
disconnectedCallback() {
|
|
74
|
+
super.disconnectedCallback();
|
|
75
|
+
this._cancelScrollSpyUnlock();
|
|
76
|
+
window.removeEventListener("scroll", this._onScrollSpy);
|
|
77
|
+
window.removeEventListener("resize", this._onScrollSpy);
|
|
78
|
+
document.removeEventListener("pointerdown", this._onDocumentPointerDown, true);
|
|
79
|
+
document.removeEventListener("keydown", this._onKeydown, true);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
_cancelScrollSpyUnlock() {
|
|
83
|
+
if (this._unlockScrollSpyTimer != null) {
|
|
84
|
+
clearTimeout(this._unlockScrollSpyTimer);
|
|
85
|
+
this._unlockScrollSpyTimer = null;
|
|
86
|
+
}
|
|
87
|
+
if (this._scrollEndUnlockHandler) {
|
|
88
|
+
window.removeEventListener("scrollend", this._scrollEndUnlockHandler);
|
|
89
|
+
this._scrollEndUnlockHandler = null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** After in-nav navigation, ignore scrollspy until scroll finishes (avoids flashing 2→3→4). */
|
|
94
|
+
_scheduleScrollSpyUnlock() {
|
|
95
|
+
this._cancelScrollSpyUnlock();
|
|
96
|
+
const onScrollEnd = () => {
|
|
97
|
+
if (this._unlockScrollSpyTimer != null) {
|
|
98
|
+
clearTimeout(this._unlockScrollSpyTimer);
|
|
99
|
+
this._unlockScrollSpyTimer = null;
|
|
100
|
+
}
|
|
101
|
+
window.removeEventListener("scrollend", onScrollEnd);
|
|
102
|
+
this._scrollEndUnlockHandler = null;
|
|
103
|
+
this._programmaticScrollTargetId = null;
|
|
104
|
+
this._runScrollSpy();
|
|
105
|
+
};
|
|
106
|
+
this._scrollEndUnlockHandler = onScrollEnd;
|
|
107
|
+
window.addEventListener("scrollend", onScrollEnd, { passive: true });
|
|
108
|
+
this._unlockScrollSpyTimer = window.setTimeout(() => {
|
|
109
|
+
this._unlockScrollSpyTimer = null;
|
|
110
|
+
if (this._scrollEndUnlockHandler) {
|
|
111
|
+
window.removeEventListener("scrollend", this._scrollEndUnlockHandler);
|
|
112
|
+
this._scrollEndUnlockHandler = null;
|
|
113
|
+
}
|
|
114
|
+
if (this._programmaticScrollTargetId) {
|
|
115
|
+
this._programmaticScrollTargetId = null;
|
|
116
|
+
this._runScrollSpy();
|
|
117
|
+
}
|
|
118
|
+
}, 1000);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
_onKeydown(e) {
|
|
122
|
+
if (e.key === "Escape" && this._dropdownOpen) {
|
|
123
|
+
this._dropdownOpen = false;
|
|
124
|
+
this.requestUpdate();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
_onDocumentPointerDown(e) {
|
|
129
|
+
if (!this._dropdownOpen) return;
|
|
130
|
+
const path = e.composedPath();
|
|
131
|
+
if (path.includes(this)) return;
|
|
132
|
+
this._dropdownOpen = false;
|
|
133
|
+
this.requestUpdate();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
_onScrollSpy() {
|
|
137
|
+
if (this._scrollPending) return;
|
|
138
|
+
this._scrollPending = true;
|
|
139
|
+
requestAnimationFrame(() => {
|
|
140
|
+
this._scrollPending = false;
|
|
141
|
+
this._runScrollSpy();
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
_runScrollSpy() {
|
|
146
|
+
const count = this._navItemCount();
|
|
147
|
+
let next = this.activeId;
|
|
148
|
+
if (count && !this._programmaticScrollTargetId) {
|
|
149
|
+
const y = window.scrollY + this._stickyOffsetPx() + 2;
|
|
150
|
+
next = "anchor-1";
|
|
151
|
+
for (let i = 0; i < count; i++) {
|
|
152
|
+
const el = document.getElementById(`anchor-${i + 1}-section`);
|
|
153
|
+
if (!el) continue;
|
|
154
|
+
const top = el.getBoundingClientRect().top + window.scrollY;
|
|
155
|
+
if (top <= y) next = `anchor-${i + 1}`;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const isMobile = window.matchMedia("(max-width: 768px)").matches;
|
|
160
|
+
const first = document.getElementById("anchor-1-section");
|
|
161
|
+
let nextCta;
|
|
162
|
+
if (!isMobile) {
|
|
163
|
+
nextCta = true;
|
|
164
|
+
} else if (!first) {
|
|
165
|
+
nextCta = true;
|
|
166
|
+
} else {
|
|
167
|
+
/* Show only while first section is fully scrolled past; hide again when it scrolls back into view */
|
|
168
|
+
nextCta = first.getBoundingClientRect().bottom <= 0;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (this.activeId !== next || this._mobileCtaRevealed !== nextCta) {
|
|
172
|
+
this.activeId = next;
|
|
173
|
+
this._mobileCtaRevealed = nextCta;
|
|
174
|
+
this.requestUpdate();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
_navItemCount() {
|
|
179
|
+
return Array.from(this.children).filter(
|
|
180
|
+
(el) => el.tagName === "BD-ANCHOR-NAV-ITEM"
|
|
181
|
+
).length;
|
|
32
182
|
}
|
|
33
183
|
|
|
34
184
|
firstUpdated() {
|
|
35
|
-
const
|
|
36
|
-
if (
|
|
37
|
-
this.activeId =
|
|
185
|
+
const n = this._navItemCount();
|
|
186
|
+
if (n) {
|
|
187
|
+
this.activeId = "anchor-1";
|
|
38
188
|
this.requestUpdate();
|
|
39
189
|
}
|
|
190
|
+
queueMicrotask(() => this._runScrollSpy());
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
updated(changedProperties) {
|
|
194
|
+
super.updated(changedProperties);
|
|
195
|
+
if (!changedProperties.has("activeId")) return;
|
|
196
|
+
const prev = changedProperties.get("activeId");
|
|
197
|
+
if (prev === undefined || prev === "") return;
|
|
198
|
+
if (prev === this.activeId) return;
|
|
199
|
+
queueMicrotask(() => this._playMobileDropdownLabelSwap());
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Mobile dropdown title: subtle fade + slide when scrollspy (or tap) changes section. */
|
|
203
|
+
_playMobileDropdownLabelSwap() {
|
|
204
|
+
if (typeof window === "undefined") return;
|
|
205
|
+
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
|
|
206
|
+
if (!window.matchMedia("(max-width: 768px)").matches) return;
|
|
207
|
+
const el = this.renderRoot?.querySelector(".bd-anchor-nav__dropdown-label");
|
|
208
|
+
if (!el) return;
|
|
209
|
+
el.classList.remove("bd-anchor-nav__dropdown-label--swap");
|
|
210
|
+
void el.offsetWidth;
|
|
211
|
+
el.classList.add("bd-anchor-nav__dropdown-label--swap");
|
|
212
|
+
el.addEventListener(
|
|
213
|
+
"animationend",
|
|
214
|
+
() => el.classList.remove("bd-anchor-nav__dropdown-label--swap"),
|
|
215
|
+
{ once: true }
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Scroll clearance = actual sticky bar height + small inset (not a fixed 128px token).
|
|
221
|
+
* Matches best practice: target headings sit just under the bar, not with extra dead space.
|
|
222
|
+
*/
|
|
223
|
+
_stickyOffsetPx() {
|
|
224
|
+
const barPx = this.offsetHeight;
|
|
225
|
+
const insetRaw = getComputedStyle(this).getPropertyValue("--spacing-8")
|
|
226
|
+
.trim();
|
|
227
|
+
const insetPx = parseLengthToPx(insetRaw) || 8;
|
|
228
|
+
if (barPx > 0) return barPx + insetPx;
|
|
229
|
+
const fallback = getComputedStyle(this).getPropertyValue("--spacing-128")
|
|
230
|
+
.trim();
|
|
231
|
+
return parseLengthToPx(fallback) || 128;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
_scrollToY(event, getTarget) {
|
|
235
|
+
if (event?.preventDefault) event.preventDefault();
|
|
236
|
+
const el = getTarget();
|
|
237
|
+
if (!el) return;
|
|
238
|
+
const offset = this._stickyOffsetPx();
|
|
239
|
+
const rect = el.getBoundingClientRect();
|
|
240
|
+
const scrollTop = window.scrollY ?? window.pageYOffset;
|
|
241
|
+
const elementY = rect.top + scrollTop;
|
|
242
|
+
const prefersReduced =
|
|
243
|
+
typeof window !== "undefined" &&
|
|
244
|
+
window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
245
|
+
window.scrollTo({
|
|
246
|
+
top : elementY - offset,
|
|
247
|
+
behavior: prefersReduced ? "auto" : "smooth"
|
|
248
|
+
});
|
|
40
249
|
}
|
|
41
250
|
|
|
42
251
|
handleClick(event, id) {
|
|
43
252
|
event.preventDefault();
|
|
253
|
+
this._cancelScrollSpyUnlock();
|
|
254
|
+
this._programmaticScrollTargetId = id;
|
|
44
255
|
this.activeId = id;
|
|
45
256
|
|
|
46
|
-
const sectionEl = document.
|
|
257
|
+
const sectionEl = document.getElementById(`${id}-section`);
|
|
47
258
|
if (sectionEl) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const offsetY = elementY - offset;
|
|
53
|
-
|
|
54
|
-
window.scrollTo({
|
|
55
|
-
top : offsetY,
|
|
56
|
-
behavior: "smooth"
|
|
57
|
-
});
|
|
259
|
+
this._scrollToY(event, () => sectionEl);
|
|
260
|
+
this._scheduleScrollSpyUnlock();
|
|
261
|
+
} else {
|
|
262
|
+
this._programmaticScrollTargetId = null;
|
|
58
263
|
}
|
|
59
264
|
|
|
60
265
|
this.requestUpdate();
|
|
61
266
|
}
|
|
62
267
|
|
|
268
|
+
_toggleDropdown(e) {
|
|
269
|
+
e.preventDefault();
|
|
270
|
+
e.stopPropagation();
|
|
271
|
+
this._dropdownOpen = !this._dropdownOpen;
|
|
272
|
+
this.requestUpdate();
|
|
273
|
+
}
|
|
63
274
|
|
|
275
|
+
_selectFromDropdown(e, id) {
|
|
276
|
+
this.handleClick(e, id);
|
|
277
|
+
this._dropdownOpen = false;
|
|
278
|
+
this.requestUpdate();
|
|
279
|
+
}
|
|
64
280
|
|
|
65
281
|
scrollToPricing(event) {
|
|
66
|
-
event.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
top : offsetY,
|
|
75
|
-
behavior: "smooth"
|
|
76
|
-
});
|
|
77
|
-
}
|
|
282
|
+
this._scrollToY(event, () => document.getElementById("section-pricing"));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
_activeLabel(items) {
|
|
286
|
+
const m = /^anchor-(\d+)$/.exec(this.activeId || "");
|
|
287
|
+
const n = m ? parseInt(m[1], 10) : 1;
|
|
288
|
+
const idx = Math.min(Math.max(n - 1, 0), Math.max(0, items.length - 1));
|
|
289
|
+
return items[idx]?.getAttribute("title") || items[0]?.getAttribute("title") || "Section";
|
|
78
290
|
}
|
|
79
291
|
|
|
80
292
|
render() {
|
|
@@ -82,39 +294,102 @@ class BdAnchorNav extends LitElement {
|
|
|
82
294
|
(el) => el.tagName === "BD-ANCHOR-NAV-ITEM"
|
|
83
295
|
);
|
|
84
296
|
|
|
297
|
+
const linkRow = (cls) => html`
|
|
298
|
+
<div class="${cls}">
|
|
299
|
+
${items.map((item, index) => {
|
|
300
|
+
const id = `anchor-${index + 1}`;
|
|
301
|
+
const isActive = this.activeId === id;
|
|
302
|
+
return html`
|
|
303
|
+
<a
|
|
304
|
+
href="#${id}-section"
|
|
305
|
+
class="${isActive ? "active" : ""}"
|
|
306
|
+
aria-current=${isActive ? "true" : nothing}
|
|
307
|
+
@click=${(e) => this.handleClick(e, id)}
|
|
308
|
+
>
|
|
309
|
+
${item.getAttribute("title")}
|
|
310
|
+
</a>
|
|
311
|
+
`;
|
|
312
|
+
})}
|
|
313
|
+
</div>
|
|
314
|
+
`;
|
|
315
|
+
|
|
316
|
+
const dropdownLabel = this._activeLabel(items);
|
|
317
|
+
|
|
85
318
|
return html`
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
319
|
+
<nav aria-label=${this.navLabel}>
|
|
320
|
+
<div
|
|
321
|
+
class="bd-anchor-nav__inner"
|
|
322
|
+
part="inner"
|
|
323
|
+
?data-mobile-cta-visible=${this._mobileCtaRevealed}
|
|
324
|
+
>
|
|
325
|
+
${linkRow("anchor-links anchor-links--desktop")}
|
|
326
|
+
|
|
327
|
+
<div class="bd-anchor-nav__dropdown" part="dropdown">
|
|
328
|
+
<button
|
|
329
|
+
type="button"
|
|
330
|
+
class="bd-anchor-nav__dropdown-toggle"
|
|
331
|
+
aria-expanded=${this._dropdownOpen ? "true" : "false"}
|
|
332
|
+
aria-controls=${this._panelId}
|
|
333
|
+
aria-haspopup="listbox"
|
|
334
|
+
@click=${this._toggleDropdown}
|
|
335
|
+
>
|
|
336
|
+
<span class="bd-anchor-nav__dropdown-label">${dropdownLabel}</span>
|
|
337
|
+
<span class="bd-anchor-nav__dropdown-chevron" aria-hidden="true">
|
|
338
|
+
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
|
339
|
+
<path
|
|
340
|
+
d="M5 8l5 5 5-5"
|
|
341
|
+
stroke="currentColor"
|
|
342
|
+
stroke-width="2"
|
|
343
|
+
stroke-linecap="round"
|
|
344
|
+
stroke-linejoin="round"
|
|
345
|
+
/>
|
|
346
|
+
</svg>
|
|
347
|
+
</span>
|
|
348
|
+
</button>
|
|
349
|
+
<div
|
|
350
|
+
class="bd-anchor-nav__dropdown-panel"
|
|
351
|
+
id=${this._panelId}
|
|
352
|
+
role="listbox"
|
|
353
|
+
aria-label=${this.navLabel}
|
|
354
|
+
?hidden=${!this._dropdownOpen}
|
|
355
|
+
>
|
|
356
|
+
<ul class="bd-anchor-nav__dropdown-list" role="presentation">
|
|
357
|
+
${items.map((item, index) => {
|
|
358
|
+
const id = `anchor-${index + 1}`;
|
|
359
|
+
const isActive = this.activeId === id;
|
|
360
|
+
return html`
|
|
361
|
+
<li role="presentation">
|
|
362
|
+
<button
|
|
363
|
+
type="button"
|
|
364
|
+
role="option"
|
|
365
|
+
class="bd-anchor-nav__dropdown-option ${isActive
|
|
366
|
+
? "bd-anchor-nav__dropdown-option--active"
|
|
367
|
+
: ""}"
|
|
368
|
+
aria-selected=${isActive ? "true" : "false"}
|
|
369
|
+
@click=${(e) => this._selectFromDropdown(e, id)}
|
|
370
|
+
>
|
|
371
|
+
${item.getAttribute("title")}
|
|
372
|
+
</button>
|
|
373
|
+
</li>
|
|
374
|
+
`;
|
|
375
|
+
})}
|
|
376
|
+
</ul>
|
|
377
|
+
</div>
|
|
101
378
|
</div>
|
|
102
379
|
|
|
103
380
|
<div class="bd-cta">
|
|
104
|
-
<
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
size="md"
|
|
381
|
+
<button
|
|
382
|
+
type="button"
|
|
383
|
+
class="bd-anchor-nav__cta"
|
|
108
384
|
part="buy-button"
|
|
109
|
-
font-size="18px"
|
|
110
|
-
font-weight="600"
|
|
111
385
|
@click=${(e) => this.scrollToPricing(e)}
|
|
112
386
|
>
|
|
113
387
|
Buy now
|
|
114
|
-
</
|
|
388
|
+
</button>
|
|
115
389
|
</div>
|
|
116
|
-
</
|
|
117
|
-
|
|
390
|
+
</div>
|
|
391
|
+
</nav>
|
|
392
|
+
`;
|
|
118
393
|
}
|
|
119
394
|
}
|
|
120
395
|
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import { LitElement, html } from "lit";
|
|
1
|
+
import { LitElement, css, html } from "lit";
|
|
2
2
|
import { tokens } from "../../tokens/tokens.js";
|
|
3
3
|
import "../awards/awards-icon.js";
|
|
4
4
|
import { awardsCSS } from "./awards.css.js";
|
|
5
5
|
|
|
6
|
+
const awardslightOverride = css`
|
|
7
|
+
.bd-awards-root {
|
|
8
|
+
gap: 0;
|
|
9
|
+
}
|
|
10
|
+
`;
|
|
6
11
|
export class AwardsSection extends LitElement {
|
|
7
12
|
static properties = {
|
|
8
13
|
tagline : { type: String },
|
|
@@ -16,7 +21,8 @@ export class AwardsSection extends LitElement {
|
|
|
16
21
|
_featureNarrow: { state: true }
|
|
17
22
|
};
|
|
18
23
|
|
|
19
|
-
|
|
24
|
+
|
|
25
|
+
static styles = [tokens, awardsCSS, awardslightOverride];
|
|
20
26
|
|
|
21
27
|
/** @type {MediaQueryList | null} */
|
|
22
28
|
_mql599 = null;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { html } from "lit";
|
|
2
|
-
import "../awards/awards-light.js";
|
|
3
2
|
import "../heading/heading.js";
|
|
4
3
|
import "../image/image.js";
|
|
5
4
|
import "../link/link.js";
|
|
6
5
|
import "../paragraph/paragraph.js";
|
|
6
|
+
import "./awards-light.js";
|
|
7
|
+
import "./awards.js";
|
|
7
8
|
|
|
8
9
|
export default {
|
|
9
10
|
title : "Components/Awards Section Light",
|
|
@@ -14,40 +15,24 @@ export default {
|
|
|
14
15
|
docs : {
|
|
15
16
|
description: {
|
|
16
17
|
component: `
|
|
17
|
-
**
|
|
18
|
+
**AwardsSectionLight** is a lightweight variant of the AwardsSection component that renders only the trust banner and award badges — without the "What's our secret" section or feature cards.
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
-
|
|
22
|
-
|
|
23
|
-
- Two feature cards (Machine Learning & Behavioral Analysis)
|
|
24
|
-
|
|
25
|
-
### Responsive Behavior
|
|
26
|
-
| Breakpoint | Behavior |
|
|
27
|
-
|---|---|
|
|
28
|
-
| \`≤ 599px\` | Narrow layout: smaller typography, compact badges |
|
|
29
|
-
| \`≤ 767px\` | Feature cards use small typography |
|
|
30
|
-
| \`≥ 768px\` | Full desktop layout |
|
|
20
|
+
### Usage
|
|
21
|
+
\`\`\`html
|
|
22
|
+
<bd-awards-section-light></bd-awards-section-light>
|
|
23
|
+
\`\`\`
|
|
31
24
|
|
|
32
25
|
### Attributes
|
|
33
|
-
| Attribute | Type | Default | Description
|
|
34
|
-
|
|
35
|
-
| \`tagline\` | String | — | Small text above the headline
|
|
36
|
-
| \`headline\` | String | — | Main heading of the trust banner
|
|
37
|
-
| \`subtext\` | String | — | Supporting paragraph below the headline
|
|
38
|
-
| \`
|
|
39
|
-
| \`secretSubtext\`| String | — | Subtext of the "What's our secret" block |
|
|
40
|
-
| \`desktopAwards\`| Array | — | Award badge objects (see below) |
|
|
41
|
-
| \`features\` | Array | — | Feature card objects (see below) |
|
|
26
|
+
| Attribute | Type | Default | Description |
|
|
27
|
+
|----------------|--------|---------|-----------------------------------------|
|
|
28
|
+
| \`tagline\` | String | — | Small text above the headline |
|
|
29
|
+
| \`headline\` | String | — | Main heading of the trust banner |
|
|
30
|
+
| \`subtext\` | String | — | Supporting paragraph below the headline |
|
|
31
|
+
| \`desktopAwards\`| Array | — | Award badge objects |
|
|
42
32
|
|
|
43
33
|
### Badge object shape
|
|
44
34
|
\`\`\`js
|
|
45
35
|
{ id: string, label: string, type: "red" | "dark", format: "svg", src: string }
|
|
46
|
-
\`\`\`
|
|
47
|
-
|
|
48
|
-
### Feature object shape
|
|
49
|
-
\`\`\`js
|
|
50
|
-
{ icon: "brain" | "chart", title: string, description: string }
|
|
51
36
|
\`\`\`
|
|
52
37
|
`
|
|
53
38
|
}
|
|
@@ -69,16 +54,6 @@ It includes:
|
|
|
69
54
|
description: "Supporting paragraph below the headline",
|
|
70
55
|
table : { type: { summary: "string" }, category: "Content" }
|
|
71
56
|
},
|
|
72
|
-
secretTitle: {
|
|
73
|
-
control : "text",
|
|
74
|
-
description: "Title of the 'What's our secret' section",
|
|
75
|
-
table : { type: { summary: "string" }, category: "Content" }
|
|
76
|
-
},
|
|
77
|
-
secretSubtext: {
|
|
78
|
-
control : "text",
|
|
79
|
-
description: "Subtext of the 'What's our secret' section",
|
|
80
|
-
table : { type: { summary: "string" }, category: "Content" }
|
|
81
|
-
},
|
|
82
57
|
desktopAwards: {
|
|
83
58
|
control : "object",
|
|
84
59
|
description: "Array of award badge objects",
|
|
@@ -86,14 +61,6 @@ It includes:
|
|
|
86
61
|
type : { summary: "Array<{ id, label, type, format, src }>" },
|
|
87
62
|
category: "Badges"
|
|
88
63
|
}
|
|
89
|
-
},
|
|
90
|
-
features: {
|
|
91
|
-
control : "object",
|
|
92
|
-
description: "Array of feature card objects",
|
|
93
|
-
table : {
|
|
94
|
-
type : { summary: "Array<{ icon, title, description }>" },
|
|
95
|
-
category: "Features"
|
|
96
|
-
}
|
|
97
64
|
}
|
|
98
65
|
}
|
|
99
66
|
};
|
|
@@ -105,8 +72,6 @@ const defaultArgs = {
|
|
|
105
72
|
tagline : "You're Making A Great Choice",
|
|
106
73
|
headline : "World-class security software you can trust. Always.",
|
|
107
74
|
subtext : "We've added 25 new accolades in the past two years to the hundreds we have won since we started in 2001 - so you know you are in good hands.",
|
|
108
|
-
secretTitle : "What's our secret",
|
|
109
|
-
secretSubtext: "At the heart of Bitdefender lies a powerhouse of proprietary technology, setting it miles apart from the competition.",
|
|
110
75
|
desktopAwards: [
|
|
111
76
|
{
|
|
112
77
|
label : "PC MAG\nEDITORS' CHOICE",
|
|
@@ -157,18 +122,6 @@ const defaultArgs = {
|
|
|
157
122
|
src : "/assets/top-product.svg",
|
|
158
123
|
id : "top-product"
|
|
159
124
|
}
|
|
160
|
-
],
|
|
161
|
-
features: [
|
|
162
|
-
{
|
|
163
|
-
icon : "brain",
|
|
164
|
-
title : "Machine Learning",
|
|
165
|
-
description: "Imagine Bitdefender as a super-smart detective, constantly learning and getting better at spotting bad guys. Just like how you learn from your experiences, Bitdefender learns from millions of cyber threats it encounters every day. It studies their patterns, figures out their tricks, and uses that knowledge to protect your devices from future attacks."
|
|
166
|
-
},
|
|
167
|
-
{
|
|
168
|
-
icon : "chart",
|
|
169
|
-
title : "Behavioral Analysis",
|
|
170
|
-
description: "Bitdefender keeps an eye on how you and your devices normally behave. So, when something unusual happens, like a strange file trying to sneak into your computer or a suspicious website trying to steal your info, Bitdefender sounds the alarm. It's like having a loyal sidekick that never sleeps, always on the lookout for trouble and ready to jump into action to keep you safe."
|
|
171
|
-
}
|
|
172
125
|
]
|
|
173
126
|
};
|
|
174
127
|
|
|
@@ -177,10 +130,7 @@ const render = (args) => html`
|
|
|
177
130
|
.tagline=${args.tagline}
|
|
178
131
|
.headline=${args.headline}
|
|
179
132
|
.subtext=${args.subtext}
|
|
180
|
-
.secretTitle=${args.secretTitle}
|
|
181
|
-
.secretSubtext=${args.secretSubtext}
|
|
182
133
|
.desktopAwards=${args.desktopAwards}
|
|
183
|
-
.features=${args.features}
|
|
184
134
|
></bd-awards-section-light>
|
|
185
135
|
`;
|
|
186
136
|
|
|
@@ -195,7 +145,7 @@ export const Default = {
|
|
|
195
145
|
viewport: { defaultViewport: "desktop" },
|
|
196
146
|
docs : {
|
|
197
147
|
description: {
|
|
198
|
-
story: "Full desktop layout with all 7 award badges split across two rows (3 top + 4 bottom)
|
|
148
|
+
story: "Full desktop layout. Trust banner with tagline, headline, subtext, and all 7 award badges split across two rows (3 top + 4 bottom)."
|
|
199
149
|
}
|
|
200
150
|
}
|
|
201
151
|
}
|
|
@@ -212,7 +162,7 @@ export const Mobile = {
|
|
|
212
162
|
viewport: { defaultViewport: "mobile1" },
|
|
213
163
|
docs : {
|
|
214
164
|
description: {
|
|
215
|
-
story: "Mobile layout at 375px. The `_narrow` state activates at ≤599px, switching to smaller typography variants
|
|
165
|
+
story: "Mobile layout at 375px. The `_narrow` state activates at ≤599px, switching to smaller typography variants and compact badge sizes."
|
|
216
166
|
}
|
|
217
167
|
}
|
|
218
168
|
}
|
|
@@ -229,30 +179,28 @@ export const Tablet = {
|
|
|
229
179
|
viewport: { defaultViewport: "tablet" },
|
|
230
180
|
docs : {
|
|
231
181
|
description: {
|
|
232
|
-
story: "Tablet layout at 768px. Desktop typography is
|
|
182
|
+
story: "Tablet layout at 768px. Desktop typography is active since the viewport exceeds 599px."
|
|
233
183
|
}
|
|
234
184
|
}
|
|
235
185
|
}
|
|
236
186
|
};
|
|
237
187
|
|
|
238
188
|
|
|
239
|
-
// ─── Custom text
|
|
189
|
+
// ─── Custom text ──────────────────────────────────────────────────────────────
|
|
240
190
|
|
|
241
191
|
export const CustomContent = {
|
|
242
192
|
name: "Custom Text Content",
|
|
243
193
|
args: {
|
|
244
194
|
...defaultArgs,
|
|
245
|
-
tagline
|
|
246
|
-
headline
|
|
247
|
-
subtext
|
|
248
|
-
secretTitle : "Why we win",
|
|
249
|
-
secretSubtext: "Our proprietary AI engine processes over 500 billion queries per day to stay ahead of the most sophisticated threats."
|
|
195
|
+
tagline : "Trusted by Millions Worldwide",
|
|
196
|
+
headline: "Award-winning protection for every device.",
|
|
197
|
+
subtext : "From homes to enterprises, Bitdefender has been recognised by the world's top security testing labs year after year."
|
|
250
198
|
},
|
|
251
199
|
render,
|
|
252
200
|
parameters: {
|
|
253
201
|
docs: {
|
|
254
202
|
description: {
|
|
255
|
-
story: "All text props replaced with custom values. Use the Controls panel to live-edit
|
|
203
|
+
story: "All text props replaced with custom values. Use the Controls panel to live-edit tagline, headline, and subtext."
|
|
256
204
|
}
|
|
257
205
|
}
|
|
258
206
|
}
|
|
@@ -293,7 +241,7 @@ export const FewBadges = {
|
|
|
293
241
|
parameters: {
|
|
294
242
|
docs: {
|
|
295
243
|
description: {
|
|
296
|
-
story: "Only 3 badges passed. The first row (`slice(0, 3)`) is full, the second row (`slice(3)`) renders empty. Verifies no layout breakage
|
|
244
|
+
story: "Only 3 badges passed. The first row (`slice(0, 3)`) is full, the second row (`slice(3)`) renders empty. Verifies no layout breakage."
|
|
297
245
|
}
|
|
298
246
|
}
|
|
299
247
|
}
|
|
@@ -320,7 +268,7 @@ export const SingleBadge = {
|
|
|
320
268
|
parameters: {
|
|
321
269
|
docs: {
|
|
322
270
|
description: {
|
|
323
|
-
story: "Minimum edge case: a single badge.
|
|
271
|
+
story: "Minimum edge case: a single badge. Ensures no crash or layout breakage."
|
|
324
272
|
}
|
|
325
273
|
}
|
|
326
274
|
}
|
|
@@ -339,7 +287,7 @@ export const AllDarkBadges = {
|
|
|
339
287
|
parameters: {
|
|
340
288
|
docs: {
|
|
341
289
|
description: {
|
|
342
|
-
story: "All badges set to `type: 'dark'`. Verifies the `.bd-awards-badge--dark` CSS class is applied to every badge
|
|
290
|
+
story: "All badges set to `type: 'dark'`. Verifies the `.bd-awards-badge--dark` CSS class is applied to every badge."
|
|
343
291
|
}
|
|
344
292
|
}
|
|
345
293
|
}
|
|
@@ -365,36 +313,6 @@ export const AllRedBadges = {
|
|
|
365
313
|
};
|
|
366
314
|
|
|
367
315
|
|
|
368
|
-
// ─── Custom features ──────────────────────────────────────────────────────────
|
|
369
|
-
|
|
370
|
-
export const CustomFeatures = {
|
|
371
|
-
name: "Custom Feature Cards",
|
|
372
|
-
args: {
|
|
373
|
-
...defaultArgs,
|
|
374
|
-
features: [
|
|
375
|
-
{
|
|
376
|
-
icon : "brain",
|
|
377
|
-
title : "AI-Powered Threat Detection",
|
|
378
|
-
description: "Our next-generation AI engine analyses over 500 billion security events per day, identifying and neutralising threats before they can cause harm to your system."
|
|
379
|
-
},
|
|
380
|
-
{
|
|
381
|
-
icon : "chart",
|
|
382
|
-
title : "Real-Time Protection",
|
|
383
|
-
description: "Continuous monitoring of all active processes, network connections, and file system changes ensures that threats are caught the moment they appear — with zero performance impact."
|
|
384
|
-
}
|
|
385
|
-
]
|
|
386
|
-
},
|
|
387
|
-
render,
|
|
388
|
-
parameters: {
|
|
389
|
-
docs: {
|
|
390
|
-
description: {
|
|
391
|
-
story: "Custom feature card content. The `icon` field accepts `'brain'` (maps to `/assets/machine-learning.svg`) or `'chart'` (maps to `/assets/analysis.svg`)."
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
};
|
|
396
|
-
|
|
397
|
-
|
|
398
316
|
// ─── Small mobile ─────────────────────────────────────────────────────────────
|
|
399
317
|
|
|
400
318
|
export const SmallMobile = {
|
|
@@ -429,7 +347,7 @@ export const Playground = {
|
|
|
429
347
|
parameters: {
|
|
430
348
|
docs: {
|
|
431
349
|
description: {
|
|
432
|
-
story: "Fully interactive story. Use the Controls panel to modify every prop in real time: text content
|
|
350
|
+
story: "Fully interactive story. Use the Controls panel to modify every prop in real time: text content and badge arrays."
|
|
433
351
|
}
|
|
434
352
|
}
|
|
435
353
|
}
|
|
@@ -17,13 +17,17 @@ export default css`
|
|
|
17
17
|
--cs-bar-track-border: var(--color-neutral-200);
|
|
18
18
|
--cs-bar-height: var(--dimension-48px);
|
|
19
19
|
--cs-gap: var(--spacing-32);
|
|
20
|
-
/* Figma layout width; between --content-width-max (1200) and --grid-container-max-xxl (1320) */
|
|
21
20
|
--cs-grid-max: 1228px;
|
|
22
21
|
}
|
|
23
22
|
|
|
24
|
-
:host(compare-card)
|
|
23
|
+
:host(compare-card) {
|
|
24
|
+
display: block;
|
|
25
|
+
height: 100%;
|
|
26
|
+
}
|
|
27
|
+
|
|
25
28
|
:host(compare-bar) {
|
|
26
29
|
display: block;
|
|
30
|
+
width: 100%;
|
|
27
31
|
}
|
|
28
32
|
|
|
29
33
|
|
|
@@ -32,15 +36,15 @@ export default css`
|
|
|
32
36
|
──────────────────────────────────────────────────────────────── */
|
|
33
37
|
|
|
34
38
|
.cs-sr-only {
|
|
35
|
-
position:
|
|
36
|
-
width:
|
|
37
|
-
height:
|
|
38
|
-
padding:
|
|
39
|
-
margin:
|
|
40
|
-
overflow:
|
|
41
|
-
clip:
|
|
39
|
+
position: absolute;
|
|
40
|
+
width: 1px;
|
|
41
|
+
height: 1px;
|
|
42
|
+
padding: 0;
|
|
43
|
+
margin: -1px;
|
|
44
|
+
overflow: hidden;
|
|
45
|
+
clip: rect(0, 0, 0, 0);
|
|
42
46
|
white-space: nowrap;
|
|
43
|
-
border:
|
|
47
|
+
border: 0;
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
.cs-section {
|
|
@@ -70,6 +74,7 @@ export default css`
|
|
|
70
74
|
max-width: var(--cs-grid-max);
|
|
71
75
|
margin: 0 auto;
|
|
72
76
|
justify-content: center;
|
|
77
|
+
align-items: stretch;
|
|
73
78
|
}
|
|
74
79
|
|
|
75
80
|
@media (max-width: 768px) {
|
|
@@ -80,7 +85,7 @@ export default css`
|
|
|
80
85
|
|
|
81
86
|
|
|
82
87
|
/* ──────────────────────────────────────────────────────────────
|
|
83
|
-
3. compare-card
|
|
88
|
+
3. compare-card
|
|
84
89
|
──────────────────────────────────────────────────────────────── */
|
|
85
90
|
|
|
86
91
|
.card {
|
|
@@ -99,6 +104,7 @@ export default css`
|
|
|
99
104
|
flex-direction: column;
|
|
100
105
|
align-items: flex-start;
|
|
101
106
|
gap: var(--spacing-8);
|
|
107
|
+
flex: 1 1 auto;
|
|
102
108
|
}
|
|
103
109
|
|
|
104
110
|
.card-icon-wrap {
|
|
@@ -109,10 +115,10 @@ export default css`
|
|
|
109
115
|
|
|
110
116
|
.card-icon-wrap bd-img {
|
|
111
117
|
display: block;
|
|
112
|
-
width:
|
|
113
|
-
height:
|
|
118
|
+
width: 100%;
|
|
119
|
+
height: 100%;
|
|
114
120
|
}
|
|
115
|
-
|
|
121
|
+
|
|
116
122
|
.card-text-wrap {
|
|
117
123
|
display: flex;
|
|
118
124
|
flex-direction: column;
|
|
@@ -133,22 +139,20 @@ export default css`
|
|
|
133
139
|
flex-direction: column;
|
|
134
140
|
gap: var(--spacing-8);
|
|
135
141
|
align-self: stretch;
|
|
142
|
+
flex: 0 0 auto;
|
|
136
143
|
}
|
|
137
144
|
|
|
138
145
|
.card-footnote {
|
|
139
146
|
margin: 0;
|
|
140
147
|
color: var(--color-neutral-900);
|
|
148
|
+
flex: 0 0 auto;
|
|
141
149
|
}
|
|
142
150
|
|
|
143
151
|
|
|
144
152
|
/* ──────────────────────────────────────────────────────────────
|
|
145
|
-
4. compare-bar
|
|
153
|
+
4. compare-bar
|
|
146
154
|
──────────────────────────────────────────────────────────────── */
|
|
147
155
|
|
|
148
|
-
:host(compare-bar) {
|
|
149
|
-
width: 100%;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
156
|
.bar-track {
|
|
153
157
|
position: relative;
|
|
154
158
|
height: var(--cs-bar-height);
|
|
@@ -172,7 +176,7 @@ export default css`
|
|
|
172
176
|
box-sizing: border-box;
|
|
173
177
|
background: transparent;
|
|
174
178
|
transition: width calc(var(--transition-duration-slower) + 300ms)
|
|
175
|
-
|
|
179
|
+
cubic-bezier(0.22, 1, 0.36, 1);
|
|
176
180
|
min-width: 0;
|
|
177
181
|
}
|
|
178
182
|
|
|
@@ -239,29 +243,28 @@ export default css`
|
|
|
239
243
|
══════════════════════════════════════════════════════════════ */
|
|
240
244
|
|
|
241
245
|
:host(bd-compare-section.theme-green) {
|
|
242
|
-
--cs-bar-primary-bg:
|
|
246
|
+
--cs-bar-primary-bg: var(--color-green-500);
|
|
243
247
|
--cs-bar-primary-fg: var(--color-neutral-0);
|
|
244
248
|
--cs-icon-color: var(--color-green-500);
|
|
245
249
|
--cs-icon-filter: invert(35%) sepia(72%) saturate(500%) hue-rotate(100deg) brightness(95%) contrast(95%);
|
|
246
250
|
}
|
|
247
251
|
|
|
248
252
|
:host(bd-compare-section.theme-red) {
|
|
249
|
-
--cs-bar-primary-bg:
|
|
253
|
+
--cs-bar-primary-bg: var(--color-red-500);
|
|
250
254
|
--cs-bar-primary-fg: var(--color-neutral-0);
|
|
251
255
|
--cs-icon-color: var(--color-red-500);
|
|
252
256
|
--cs-icon-filter: invert(22%) sepia(90%) saturate(700%) hue-rotate(345deg) brightness(95%) contrast(95%);
|
|
253
257
|
}
|
|
254
258
|
|
|
255
|
-
/* No violet in DS; keep marketing accent */
|
|
256
259
|
:host(bd-compare-section.theme-purple) {
|
|
257
|
-
--cs-bar-primary-bg:
|
|
260
|
+
--cs-bar-primary-bg: #7c3aed;
|
|
258
261
|
--cs-bar-primary-fg: var(--color-neutral-0);
|
|
259
262
|
--cs-icon-color: #7c3aed;
|
|
260
263
|
--cs-icon-filter: invert(25%) sepia(80%) saturate(800%) hue-rotate(255deg) brightness(90%) contrast(95%);
|
|
261
264
|
}
|
|
262
265
|
|
|
263
266
|
:host(bd-compare-section.theme-dark) {
|
|
264
|
-
--cs-bar-primary-bg:
|
|
267
|
+
--cs-bar-primary-bg: var(--color-blue-400);
|
|
265
268
|
--cs-bar-primary-fg: var(--color-neutral-0);
|
|
266
269
|
--cs-icon-color: var(--color-blue-400);
|
|
267
270
|
--cs-icon-filter: invert(65%) sepia(50%) saturate(500%) hue-rotate(195deg) brightness(105%) contrast(95%);
|
|
@@ -280,4 +283,4 @@ export default css`
|
|
|
280
283
|
--cs-bar-height: var(--dimension-40px);
|
|
281
284
|
--cs-gap: var(--spacing-24);
|
|
282
285
|
}
|
|
283
|
-
`;
|
|
286
|
+
`;
|
|
@@ -101,7 +101,7 @@ export const Default = {
|
|
|
101
101
|
>
|
|
102
102
|
<compare-card
|
|
103
103
|
title="Best Protection"
|
|
104
|
-
description="Best Protection against e-Threats (On a scale of 0 to 6, with 6 as the highest level of protection)"
|
|
104
|
+
description="Best Protection against e-Threats (On a scale of 0 to 6, with 6 as the highest level of protection)Best Protection against e-Threats (On a scale of 0 to 6, with 6 as the highest level of protection)Best Protection against e-Threats (On a scale of 0 to 6, with 6 as the highest level of protection)"
|
|
105
105
|
footnote="Overall Score. July 2011 – February 2025. Source AV TEST.org"
|
|
106
106
|
footnote-href="${AV_TEST_URL}"
|
|
107
107
|
icon-src="${SHIELD_ICON}"
|