@repobit/dex-system-design 0.23.9 → 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.
@@ -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 firstItem = this.querySelector("bd-anchor-nav-item");
36
- if (firstItem) {
37
- this.activeId = firstItem.id;
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.querySelector(`#${id}-section`);
257
+ const sectionEl = document.getElementById(`${id}-section`);
47
258
  if (sectionEl) {
48
- const offset = 120;
49
- const rect = sectionEl.getBoundingClientRect();
50
- const scrollTop = window.pageYOffset;
51
- const elementY = rect.top + scrollTop;
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.preventDefault();
67
- const pricingSection = document.querySelector("#section-pricing");
68
- if (pricingSection) {
69
- const offset = 120;
70
- const elementY = pricingSection.getBoundingClientRect().top + window.pageYOffset;
71
- const offsetY = elementY - offset;
72
-
73
- window.scrollTo({
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
- <nav>
87
- <div class="anchor-links">
88
- ${items.map((item, index) => {
89
- const id = `anchor-${index + 1}`;
90
- return html`
91
- <a
92
- href="#${id}"
93
- class="${this.activeId === id ? "active" : ""}"
94
- @click=${(e) => this.handleClick(e, id)}
95
-
96
- >
97
- ${item.getAttribute("title")}
98
- </a>
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
- <bd-button
105
- label="Buy now"
106
- kind="danger"
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
- </bd-button>
388
+ </button>
115
389
  </div>
116
- </nav>
117
- `;
390
+ </div>
391
+ </nav>
392
+ `;
118
393
  }
119
394
  }
120
395
 
@@ -0,0 +1,240 @@
1
+ import { LitElement, css, html } from "lit";
2
+ import { tokens } from "../../tokens/tokens.js";
3
+ import "../awards/awards-icon.js";
4
+ import { awardsCSS } from "./awards.css.js";
5
+
6
+ const awardslightOverride = css`
7
+ .bd-awards-root {
8
+ gap: 0;
9
+ }
10
+ `;
11
+ export class AwardsSection extends LitElement {
12
+ static properties = {
13
+ tagline : { type: String },
14
+ headline : { type: String },
15
+ subtext : { type: String },
16
+ secretTitle : { type: String },
17
+ secretSubtext : { type: String },
18
+ desktopAwards : { type: Array },
19
+ features : { type: Array },
20
+ _narrow : { state: true },
21
+ _featureNarrow: { state: true }
22
+ };
23
+
24
+
25
+ static styles = [tokens, awardsCSS, awardslightOverride];
26
+
27
+ /** @type {MediaQueryList | null} */
28
+ _mql599 = null;
29
+ /** @type {MediaQueryList | null} */
30
+ _mql767 = null;
31
+ _on599 = () => {
32
+ this._narrow = this._mql599?.matches ?? false;
33
+ };
34
+ _on767 = () => {
35
+ this._featureNarrow = this._mql767?.matches ?? false;
36
+ };
37
+
38
+ constructor() {
39
+ super();
40
+ this.tagline = "You're Making A Great Choice";
41
+ this.headline =
42
+ "World-class security software you can trust. Always.";
43
+ this.subtext =
44
+ "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.";
45
+ this.secretTitle = "What's our secret";
46
+ this.secretSubtext =
47
+ "At the heart of Bitdefender lies a powerhouse of proprietary technology, setting it miles apart from the competition.";
48
+
49
+ this.desktopAwards = [
50
+ {
51
+ label : "PC MAG\nEDITORS' CHOICE",
52
+ type : "red",
53
+ format: "svg",
54
+ src : "/assets/pc-mag.svg",
55
+ id : "pc-mag"
56
+ },
57
+ {
58
+ label : "AV\ncomparatives",
59
+ type : "dark",
60
+ format: "svg",
61
+ src : "/assets/av-comparatives.svg",
62
+ id : "av-comparatives"
63
+ },
64
+ {
65
+ label : "Best\nBrands\n2024",
66
+ type : "dark",
67
+ format: "svg",
68
+ src : "/assets/best-brands.svg",
69
+ id : "best-brands"
70
+ },
71
+ {
72
+ label : "PC\nChoice\n2024",
73
+ type : "red",
74
+ format: "svg",
75
+ src : "/assets/pc-mag-2024.svg",
76
+ id : "pc-mag-2024"
77
+ },
78
+ {
79
+ label : "PC MAG\nREADERS'\nCHOICE",
80
+ type : "red",
81
+ format: "svg",
82
+ src : "/assets/pc-mag-readers.svg",
83
+ id : "pc-mag-readers"
84
+ },
85
+ {
86
+ label : "PC\nBusiness\nChoice",
87
+ type : "red",
88
+ format: "svg",
89
+ src : "/assets/pc-mag-business.svg",
90
+ id : "pc-mag-business"
91
+ },
92
+ {
93
+ label : "AV\nTEST\nTOP\nPRODUCT",
94
+ type : "red",
95
+ format: "svg",
96
+ src : "/assets/top-product.svg",
97
+ id : "top-product"
98
+ }
99
+ ];
100
+
101
+ this.features = [
102
+ {
103
+ icon : "brain",
104
+ title: "Machine Learning",
105
+ description:
106
+ "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."
107
+ },
108
+ {
109
+ icon : "chart",
110
+ title: "Behavioral Analysis",
111
+ description:
112
+ "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."
113
+ }
114
+ ];
115
+
116
+ this._narrow = false;
117
+ this._featureNarrow = false;
118
+ }
119
+
120
+ connectedCallback() {
121
+ super.connectedCallback();
122
+ if (typeof window !== "undefined" && window.matchMedia) {
123
+ this._mql599 = window.matchMedia("(max-width: 599px)");
124
+ this._mql767 = window.matchMedia("(max-width: 767px)");
125
+ this._narrow = this._mql599.matches;
126
+ this._featureNarrow = this._mql767.matches;
127
+ this._mql599.addEventListener("change", this._on599);
128
+ this._mql767.addEventListener("change", this._on767);
129
+ }
130
+ }
131
+
132
+ disconnectedCallback() {
133
+ super.disconnectedCallback();
134
+ this._mql599?.removeEventListener("change", this._on599);
135
+ this._mql767?.removeEventListener("change", this._on767);
136
+ }
137
+
138
+ _renderBadge(award) {
139
+ const ariaLabel = award.label.replace(/\n/g, " ");
140
+ const classes = `bd-awards-badge ${award.type === "dark" ? "bd-awards-badge--dark" : ""}`;
141
+ return html`
142
+ <div class="${classes}" data-award-id="${award.id}" role="listitem">
143
+ <bd-icon src="${award.src}" label="${ariaLabel}"></bd-icon>
144
+ </div>
145
+ `;
146
+ }
147
+
148
+ _renderFeature(f) {
149
+ const narrow = this._featureNarrow;
150
+ const iconSrc =
151
+ f.icon === "brain"
152
+ ? "/assets/machine-learning.svg"
153
+ : "/assets/analysis.svg";
154
+
155
+ return html`
156
+ <article class="bd-awards-feature-card">
157
+ <div class="bd-awards-feature-icon" aria-hidden="true">
158
+ <bd-icon src="${iconSrc}" label=""></bd-icon>
159
+ </div>
160
+ <div class="bd-awards-feature-text">
161
+ ${narrow
162
+ ? html`<bd-h as="h6" class="bd-awards-feature-title">${f.title}</bd-h>`
163
+ : html`<bd-h as="h4" class="bd-awards-feature-title">${f.title}</bd-h>`}
164
+ ${narrow
165
+ ? html`<bd-p kind="small" class="bd-awards-feature-desc">${f.description}</bd-p>`
166
+ : html`<bd-p kind="regular" class="bd-awards-feature-desc">${f.description}</bd-p>`}
167
+ </div>
168
+ </article>
169
+ `;
170
+ }
171
+
172
+ render() {
173
+ const narrow = this._narrow;
174
+
175
+ return html`
176
+ <section
177
+ class="bd-awards-root"
178
+ aria-label="Awards and recognition"
179
+ >
180
+ <div class="bd-awards-trust-banner">
181
+ <div class="bd-awards-trust-intro">
182
+ <span class="bd-awards-laurel-icon" aria-hidden="true">
183
+ <bd-icon
184
+ label=""
185
+ >
186
+ <svg
187
+ width="80"
188
+ height="80"
189
+ viewBox="0 0 80 80"
190
+ fill="none"
191
+ xmlns="http://www.w3.org/2000/svg"
192
+ aria-hidden="true"
193
+ >
194
+ <path
195
+ d="M48.0751 11.994C48.0751 17.6107 52.457 21.9791 58.0908 21.9791C58.0908 16.4873 53.5837 11.994 48.0751 11.994ZM61.8466 19.7325C62.9734 16.737 65.1017 13.9911 68.1064 12.2437C68.9828 11.7444 70.1096 12.1189 70.6103 12.8677L72.7387 16.6121C75.3678 21.1054 75.493 26.3476 73.615 30.8409C74.9922 30.3417 76.6197 29.9672 78.2473 29.9672C79.2488 29.9672 80 30.8409 80 31.8394V35.9583C80 41.2005 77.4961 45.8186 73.615 48.8141C75.2426 49.0637 76.7449 49.563 78.1221 50.4367C78.9984 50.9359 79.2488 52.0592 78.8732 52.8081L76.7449 56.5525C73.2394 62.5436 66.4789 65.4143 60.0939 64.291L59.4679 64.0414C57.9656 63.2925 56.4632 62.6684 54.9609 62.0443C53.8341 61.6699 52.7074 61.2955 51.4554 61.0458C58.9671 57.0518 64.1002 49.0637 64.1002 39.9523C64.1002 34.835 62.2222 29.9672 59.0923 25.9732H58.0908C50.3286 25.9732 44.0689 19.7325 44.0689 11.994V9.99702C44.0689 8.99851 44.9452 8 46.072 8H48.0751C54.9609 8 60.8451 13.1174 61.8466 19.7325ZM39.9374 67.9106V68.0354H37.5587C32.9264 68.0354 28.2942 69.0339 24.1628 71.1558L22.9108 71.7798C21.9092 72.2791 20.6573 71.9046 20.1565 70.9061C19.6557 69.9076 20.1565 68.6595 21.0329 68.1602L22.2848 67.5362C24.1628 66.6625 26.0407 65.9136 28.0438 65.2895C26.6667 64.6654 25.2895 63.9166 24.0376 63.0429C23.1612 63.4173 22.2848 63.6669 21.5336 63.9166C14.6479 65.7888 7.01095 62.918 3.25509 56.5525L1.25196 52.8081C0.751174 52.0592 1.00156 50.9359 1.87793 50.4367C3.25509 49.563 4.75743 49.0637 6.38498 48.8141C2.50391 45.8186 0 41.2005 0 35.9583V31.8394C0 30.8409 0.751174 29.9672 1.75274 29.9672C3.38028 29.9672 5.00782 30.3417 6.38498 30.8409C4.50704 26.3476 4.75743 21.1054 7.26135 16.6121L9.38967 12.8677C9.89045 12.1189 11.0172 11.7444 11.8936 12.2437C14.8983 13.9911 17.0266 16.737 18.1534 19.7325C19.2801 13.1174 25.0391 8 32.0501 8H34.0532C35.0548 8 36.0563 8.99851 36.0563 9.99702V11.994C36.0563 19.7325 29.6714 25.9732 22.0344 25.9732H20.9077C17.7778 29.9672 16.025 34.835 16.025 39.9523C16.025 53.1826 26.7919 63.9166 40.0626 63.9166H42.4413C47.6995 63.9166 52.9577 65.1647 57.7152 67.5362L58.9671 68.1602C59.9687 68.6595 60.3443 69.9076 59.8435 70.9061C59.3427 71.7798 58.0908 72.2791 57.0892 71.7798L55.8372 71.1558C51.7058 69.0339 47.0736 67.9106 42.4413 67.9106H40.0626H39.9374ZM22.0344 21.9791C27.543 21.9791 32.0501 17.6107 32.0501 11.994C29.1706 11.994 26.6667 13.2422 24.7887 15.1144C23.036 16.8618 22.0344 19.3581 22.0344 21.9791ZM12.0188 44.196C12.0188 39.3282 8.5133 35.2094 4.00626 34.2109V35.9583C4.00626 41.2005 7.26135 45.6937 12.0188 47.3163V44.196ZM68.1064 47.3163C72.7387 45.6937 76.1189 41.2005 76.1189 35.9583V34.2109C71.4867 35.3342 68.1064 39.3282 68.1064 44.196V47.3163ZM17.7778 57.6759C15.3991 53.4322 10.3912 51.56 5.88419 53.0578L6.76056 54.5555C9.38967 59.0488 14.5227 61.1707 19.4053 60.297L17.7778 57.6759ZM12.0188 32.3387L13.6463 29.7176C16.025 25.4739 15.1487 20.2317 11.6432 17.1114L10.7668 18.6092C8.13772 23.1025 8.88889 28.5943 12.0188 32.3387ZM66.3537 29.7176L67.9812 32.3387C71.1111 28.5943 71.8623 23.1025 69.2332 18.6092L68.3568 17.1114C64.8513 20.2317 63.975 25.4739 66.3537 29.7176ZM62.2222 57.6759L60.5947 60.297C65.4773 61.1707 70.6103 59.0488 73.2394 54.5555L74.1158 53.0578C69.6088 51.56 64.6009 53.4322 62.2222 57.6759Z"
196
+ fill="currentColor"
197
+ />
198
+ </svg>
199
+ </bd-icon>
200
+ </span>
201
+
202
+ ${narrow
203
+ ? html`<bd-p kind="small" class="bd-awards-tagline">${this.tagline}</bd-p>`
204
+ : html`<bd-p kind="large" class="bd-awards-tagline">${this.tagline}</bd-p>`}
205
+
206
+ ${narrow
207
+ ? html`
208
+ <bd-h as="h4" class="bd-awards-headline">
209
+ <span class="bd-awards-headline-line">World-class security</span>
210
+ <span class="bd-awards-headline-line">software you can trust.</span>
211
+ <span class="bd-awards-headline-line">Always.</span>
212
+ </bd-h>
213
+ `
214
+ : html`<bd-h as="h3" class="bd-awards-headline">${this.headline}</bd-h>`}
215
+
216
+ ${narrow
217
+ ? html`<bd-p kind="small" class="bd-awards-subtext">${this.subtext}</bd-p>`
218
+ : html`<bd-p kind="regular" class="bd-awards-subtext">${this.subtext}</bd-p>`}
219
+ </div>
220
+
221
+ <div
222
+ class="bd-awards-bar"
223
+ role="list"
224
+ aria-label="Industry awards"
225
+ >
226
+ <div class="bd-awards-row" role="presentation">
227
+ ${this.desktopAwards.slice(0, 3).map((a) => this._renderBadge(a))}
228
+ </div>
229
+ <div class="bd-awards-row" role="presentation">
230
+ ${this.desktopAwards.slice(3).map((a) => this._renderBadge(a))}
231
+ </div>
232
+ </div>
233
+ </div>
234
+
235
+ <div class="bd-awards-lower">
236
+ `;
237
+ }
238
+ }
239
+
240
+ customElements.define("bd-awards-section-light", AwardsSection);