@repobit/dex-system-design 0.24.18 → 0.24.19

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 (26) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/package.json +4 -4
  3. package/src/components/ai-guardian-demo.stories.js +454 -33
  4. package/src/components/bento-grid/bento-grid.css.js +12 -1
  5. package/src/components/bento-grid/bento-grid.js +59 -1
  6. package/src/components/bento-grid/bento-grid.stories.js +45 -0
  7. package/src/components/comparison-panel/comparison-panel.css.js +52 -1
  8. package/src/components/comparison-panel/comparison-panel.js +6 -1
  9. package/src/components/compatibility-table/compatibility-table.css.js +29 -13
  10. package/src/components/compatibility-table/compatibility-table.js +13 -7
  11. package/src/components/cta-banner/cta-banner.css.js +11 -0
  12. package/src/components/cta-banner/cta-banner.js +7 -0
  13. package/src/components/footer/footer-lp.css.js +10 -21
  14. package/src/components/footer/footer-lp.js +3 -18
  15. package/src/components/footer/footer-nav-menu.css.js +0 -2
  16. package/src/components/footer/footer.css.js +18 -35
  17. package/src/components/footer/footer.js +5 -1
  18. package/src/components/header/header-code.css.js +208 -10
  19. package/src/components/header/header-code.js +148 -5
  20. package/src/components/header/header-code.stories.js +83 -27
  21. package/src/components/page-switcher/page-switcher.js +1 -1
  22. package/src/components/paragraph/paragraph.js +6 -0
  23. package/src/components/quick-answer/quick-answer.css.js +2 -0
  24. package/src/components/quick-answer/quick-answer.js +1 -1
  25. package/src/components/terminal/terminal-message.css.js +153 -13
  26. package/src/components/terminal/terminal-message.js +37 -11
@@ -9,7 +9,7 @@ export default css`
9
9
  font-size: var(--typography-body-small-fontSize);
10
10
  line-height: var(--typography-lineHeight-normal);
11
11
  }
12
- .footer-nav-main a:focus,
12
+ .footer-nav-main-container a:focus,
13
13
  .footer-links a:focus,
14
14
  .footer-countries-container a:focus,
15
15
  .country-toggle:focus,
@@ -21,14 +21,12 @@ export default css`
21
21
  border-radius: 4px;
22
22
  }
23
23
 
24
- /* Stiluri specifice pentru butoane */
25
24
  .country-toggle:focus,
26
25
  .close-countries-button:focus {
27
26
  border-color: var(--color-blue-500);
28
27
  box-shadow: var(--focus-outline-primary, 0 0 0 2px rgba(0, 109, 255, 0.3));
29
28
  }
30
29
 
31
- /* Stiluri pentru link-urile din containerul de țări */
32
30
  .footer-countries-container a:focus {
33
31
  border-color: var(--color-blue-500);
34
32
  box-shadow: var(--focus-outline-primary, 0 0 0 2px rgba(0, 109, 255, 0.3));
@@ -36,8 +34,7 @@ export default css`
36
34
  padding: 2px 6px;
37
35
  }
38
36
 
39
- /* Stiluri pentru link-urile din navigația principală */
40
- .footer-nav-main a:focus {
37
+ .footer-nav-main-container a:focus {
41
38
  padding: 2px 6px;
42
39
  margin: -2px -6px;
43
40
  }
@@ -45,20 +42,12 @@ export default css`
45
42
  padding-top: var(--spacing-24);
46
43
  padding-bottom: var(--spacing-24);
47
44
  }
48
- .footer-nav-main-container {
49
- padding-top: var(--spacing-12);
50
- padding-bottom: var(--spacing-12);
51
- }
52
- /* Stiluri pentru link-urile din grupurile de link-uri */
53
45
  bd-footer-links-group a:focus {
54
46
  padding: 2px 6px;
55
47
  margin: -2px -6px;
56
48
  display: inline-block;
57
49
  }
58
50
 
59
-
60
-
61
-
62
51
  bd-divider-vertical {
63
52
  display: inline-block;
64
53
  width: var(--form-input-default-borderWidth);
@@ -85,14 +74,14 @@ bd-footer-links-group a:focus {
85
74
  background-color: var(--color-blue-500);
86
75
  }
87
76
 
88
- .footer-nav-main {
77
+ .footer-nav-main-container {
89
78
  display: flex;
90
79
  align-items: center;
91
80
  justify-content: flex-start;
92
81
  gap: var(--spacing-16);
93
82
  flex-wrap: wrap;
94
- padding-top: var(--spacing-24);
95
- padding-bottom: var(--spacing-24);
83
+ padding-top: var(--spacing-12);
84
+ padding-bottom: var(--spacing-12);
96
85
  font-size: var(--typography-body-regular-fontSize);
97
86
  font-family: var(--typography-fontFamily-sans, 'IBM Plex Sans');
98
87
  }
@@ -106,7 +95,6 @@ bd-footer-links-group a:focus {
106
95
  justify-content: space-between;
107
96
  align-items: flex-start;
108
97
  gap: var(--spacing-32);
109
- // margin-bottom: var(--spacing-24);
110
98
  flex-wrap: nowrap;
111
99
  padding-top: var(--spacing-24);
112
100
  padding-bottom: var(--spacing-24);
@@ -119,7 +107,13 @@ bd-footer-links-group a:focus {
119
107
  gap: var(--spacing-16);
120
108
  padding-top: var(--spacing-24);
121
109
  padding-bottom: var(--spacing-24);
110
+ }
122
111
 
112
+ .secondary-nav-container {
113
+ display: flex;
114
+ align-items: center;
115
+ flex-wrap: wrap;
116
+ gap: var(--spacing-16);
123
117
  }
124
118
 
125
119
  .footer-middle-line > :last-child {
@@ -242,7 +236,6 @@ bd-footer-links-group a:focus {
242
236
  content: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M4.14286 1C2.40937 1 1 2.40937 1 4.14286V19.8571C1 21.5906 2.40937 23 4.14286 23H19.8571C21.5906 23 23 21.5906 23 19.8571V4.14286C23 2.40937 21.5906 1 19.8571 1H4.14286ZM18.7326 5.125L13.6353 10.9491L19.6312 18.875H14.9366L11.2634 14.0674L7.05491 18.875H4.72232L10.1732 12.6433L4.42277 5.125H9.23527L12.5598 9.52009L16.4 5.125H18.7326ZM16.8763 17.4804L8.53304 6.44598H7.1433L15.5799 17.4804H16.8714H16.8763Z" fill="white"/></svg>');
243
237
  }
244
238
 
245
-
246
239
  a {
247
240
  color: var(--color-neutral-0);
248
241
  text-decoration: none;
@@ -493,13 +486,13 @@ bd-footer-links-group a:focus {
493
486
  .footer-address{
494
487
  display: none !important;
495
488
  }
496
- .footer-nav-main {
489
+ .footer-nav-main-container {
497
490
  flex-direction: column;
498
491
  align-items: flex-start;
499
492
  gap: var(--spacing-8);
500
493
  }
501
494
 
502
- .footer-nav-main bd-divider-vertical {
495
+ .footer-nav-main-container bd-divider-vertical {
503
496
  display: none !important;
504
497
  }
505
498
 
@@ -537,7 +530,7 @@ bd-footer-links-group a:focus {
537
530
  width: 100%;
538
531
  }
539
532
 
540
- .footer-nav-main a[slot="nav"] {
533
+ .footer-nav-main-container a[slot="nav"] {
541
534
  color: #151719 !important;
542
535
  }
543
536
 
@@ -545,17 +538,17 @@ bd-footer-links-group a:focus {
545
538
  margin-bottom: 10px;
546
539
  }
547
540
 
548
- .second-nav .footer-nav-main {
541
+ .second-nav .footer-nav-main-container {
549
542
  flex-direction: column;
550
543
  align-items: flex-start;
551
544
  gap: var(--spacing-14);
552
545
  }
553
546
 
554
- .second-nav .footer-nav-main bd-divider-vertical {
547
+ .second-nav .footer-nav-main-container bd-divider-vertical {
555
548
  display: none !important;
556
549
  }
557
550
 
558
- .second-nav .footer-nav-main a {
551
+ .second-nav .footer-nav-main-container a {
559
552
  color: #151719 !important;
560
553
  padding: var(--spacing-4) 0;
561
554
  display: block;
@@ -578,7 +571,7 @@ bd-footer-links-group a:focus {
578
571
  gap: var(--spacing-8);
579
572
  }
580
573
 
581
- .second-nav.footer-nav-main {
574
+ .second-nav.footer-nav-main-container {
582
575
  display: inline-grid;
583
576
  }
584
577
 
@@ -620,7 +613,6 @@ bd-footer-links-group a:focus {
620
613
  width: 100%;
621
614
  }
622
615
 
623
- /* STILURI MODIFICATE PENTRU FOOTER-LINKS PE MOBILE */
624
616
  .footer-links {
625
617
  display: flex;
626
618
  flex-direction: column;
@@ -632,20 +624,16 @@ bd-footer-links-group a:focus {
632
624
  width: 100%;
633
625
  }
634
626
 
635
- /* QUICK LINKS - PRIMA POZIȚIE */
636
627
  .footer-links-left {
637
628
  order: 1;
638
629
  }
639
630
 
640
- /* DIVIDER - ÎNTRE QUICK LINKS ȘI SOCIAL LINKS */
641
- /* Targetăm divider-ul care este copil direct în .footer-links */
642
631
  .footer-links > bd-divider-horizontal {
643
632
  order: 2;
644
633
  margin: var(--spacing-16) 0;
645
634
  width: 100%;
646
635
  }
647
636
 
648
- /* SOCIAL LINKS - A TREIA POZIȚIE (DUPĂ DIVIDER) */
649
637
  .footer-links-right {
650
638
  order: 3;
651
639
  display: flex;
@@ -654,7 +642,6 @@ bd-footer-links-group a:focus {
654
642
  margin-bottom: 0;
655
643
  }
656
644
 
657
- /* STILURI PENTRU COMPONENTELE BD-FOOTER-LINKS-GROUP */
658
645
  bd-footer-links-group {
659
646
  width: 100% !important;
660
647
  min-width: 100% !important;
@@ -669,7 +656,6 @@ bd-footer-links-group a:focus {
669
656
  margin-bottom: var(--spacing-8) !important;
670
657
  }
671
658
 
672
- /* GRID PENTRU QUICK LINKS */
673
659
  .footer-links-left bd-grid {
674
660
  display: flex !important;
675
661
  flex-direction: column !important;
@@ -696,7 +682,6 @@ bd-footer-links-group a:focus {
696
682
  width: 100%;
697
683
  }
698
684
 
699
- /* Divider-ul imediat după secondary-nav */
700
685
  .secondary-nav-container bd-divider-horizontal {
701
686
  margin-top: var(--spacing-16);
702
687
  margin-bottom: var(--spacing-16);
@@ -713,7 +698,6 @@ bd-footer-links-group a:focus {
713
698
  display: none;
714
699
  }
715
700
 
716
- /* Afișează divider-ul doar pe tablete */
717
701
  @media (min-width: 768px) and (max-width: 1024px) {
718
702
  .tablet-divider {
719
703
  display: block;
@@ -744,7 +728,6 @@ bd-footer-links-group a:focus {
744
728
  margin: var(--spacing-16) 0;
745
729
  width: 100%;
746
730
  }
747
- /* Ascunde ultimul divider (cel din afara .footer-links) pe tablete */
748
731
  .footer-links + bd-divider-horizontal {
749
732
  display: none !important;
750
733
  }
@@ -241,7 +241,11 @@ export class BdFooter extends LitElement {
241
241
  }
242
242
 
243
243
  _updateNavLinks() {
244
- const links = this.shadowRoot.querySelectorAll('.footer-nav-main bd-link');
244
+ // Fixed: the wrapping div in render() below is class
245
+ // "footer-nav-main-container", not "footer-nav-main" — the old
246
+ // selector never matched anything, so link hrefs were never updated
247
+ // when the locale changed.
248
+ const links = this.shadowRoot.querySelectorAll('.footer-nav-main-container bd-link');
245
249
  const linkPaths = ['consumer', 'small-business', 'business', 'partners'];
246
250
  const currentLocale = this.locale;
247
251
 
@@ -4,6 +4,32 @@ export default css`
4
4
  :host {
5
5
  display: block;
6
6
  font-family: var(--typography-fontFamily-sans);
7
+ position: relative;
8
+ background-color: var(--color-neutral-0, #fff);
9
+ overflow: hidden; /* clips the ::after floor grid, which extends past the edges */
10
+ }
11
+
12
+ :host([variant="tint"]) {
13
+ background-image: radial-gradient(ellipse 900px 560px at 84% 8%, rgba(59, 130, 246, 0.16), transparent 65%);
14
+ background-repeat: no-repeat;
15
+ }
16
+
17
+ :host([variant="tint"])::after {
18
+ content: "";
19
+ position: absolute;
20
+ left: -20%;
21
+ right: -20%;
22
+ bottom: -38%;
23
+ height: 72%;
24
+ transform-origin: 50% 0;
25
+ transform: perspective(620px) rotateX(64deg);
26
+ background:
27
+ linear-gradient(#006dff1a 1px, #0000 1px) 0 0 / 100% 44px,
28
+ linear-gradient(90deg, #006dff1a 1px, #0000 1px) 0 0 / 44px 100%;
29
+ -webkit-mask-image: linear-gradient(#0000 0%, #000 45%, #0000 100%);
30
+ mask-image: linear-gradient(#0000 0%, #000 45%, #0000 100%);
31
+ pointer-events: none;
32
+ z-index: 0;
7
33
  }
8
34
 
9
35
  .bd-headercode-container {
@@ -15,6 +41,8 @@ export default css`
15
41
  margin: 0 auto;
16
42
  padding: var(--spacing-48) var(--layout-ensemble-inline-padding, var(--spacing-24));
17
43
  box-sizing: border-box;
44
+ position: relative;
45
+ z-index: 1;
18
46
  }
19
47
 
20
48
  /* === ENTRANCE ANIMATION === */
@@ -53,9 +81,6 @@ export default css`
53
81
  gap: var(--spacing-16);
54
82
  }
55
83
 
56
- /* Slot for a small breadcrumb-style line above the eyebrow
57
- (e.g. "Bitdefender AI skill-scanner"). Empty by default —
58
- collapses to nothing when no content is slotted in. */
59
84
  .bd-headercode-breadcrumb {
60
85
  display: flex;
61
86
  flex-wrap: wrap;
@@ -89,45 +114,200 @@ export default css`
89
114
  }
90
115
 
91
116
  .bd-headercode-cta {
92
- display: flex;
117
+ display: inline-flex;
93
118
  flex-wrap: wrap;
94
119
  align-items: center;
95
120
  gap: var(--spacing-16);
96
121
  margin-top: var(--spacing-8);
97
122
  }
98
123
 
99
- /* Slot for a small status badge next to the CTA buttons (e.g. "AVAILABLE").
100
- Empty by default — collapses to nothing when no content is slotted in. */
101
124
  .bd-headercode-badge {
102
125
  display: inline-flex;
103
126
  align-items: center;
104
- color: var(--color-neutral-500);
127
+ color: var(--color-neutral-600);
105
128
  font-family: var(--typography-fontFamily-mono, monospace);
106
129
  font-size: var(--typography-fontSize-xs, 12px);
107
130
  letter-spacing: 0.05em;
131
+ padding: var(--spacing-4, 4px) var(--spacing-12, 12px);
132
+ border: 1px solid var(--color-neutral-200, #e5e7eb);
133
+ border-radius: var(--radius-full, 999px);
134
+ background: var(--color-neutral-0, #fff);
108
135
  }
109
136
 
110
137
  .bd-headercode-badge:not(:has(*)) {
111
138
  display: none;
112
139
  }
113
140
 
141
+ .bd-headercode-meta {
142
+ display: flex;
143
+ flex-direction: column;
144
+ width: 100%;
145
+ }
146
+
147
+ .bd-headercode-meta-spec {
148
+ color: var(--color-neutral-500);
149
+ margin: 0;
150
+ padding-top: var(--spacing-16);
151
+ }
152
+
153
+ .bd-headercode-meta-facts {
154
+ display: grid;
155
+ grid-template-columns: repeat(3, auto);
156
+ gap: var(--spacing-8) var(--spacing-32);
157
+ margin-top: var(--spacing-40);
158
+ padding-top: var(--spacing-24);
159
+ border-top: 1px solid var(--color-neutral-100);
160
+ }
161
+
162
+ .bd-headercode-meta-fact {
163
+ display: flex;
164
+ align-items: baseline;
165
+ gap: var(--spacing-8, 8px);
166
+ white-space: nowrap;
167
+ }
168
+
169
+ .bd-headercode-meta-fact-label {
170
+ margin: 0;
171
+ color: var(--color-neutral-400);
172
+ text-transform: uppercase;
173
+ }
174
+
175
+ .bd-headercode-meta-fact-value {
176
+ margin: 0;
177
+ color: var(--color-neutral-900);
178
+ }
179
+
180
+ @media (max-width: 480px) {
181
+ .bd-headercode-meta-facts {
182
+ grid-template-columns: repeat(2, auto);
183
+ }
184
+ }
185
+
114
186
  /* === RIGHT COLUMN === */
115
187
  .bd-headercode-right {
116
188
  display: flex;
117
189
  flex-direction: column;
118
190
  align-items: flex-end;
119
191
  gap: var(--spacing-8);
192
+ position: relative;
120
193
  }
121
194
 
122
- .bd-headercode-right ::slotted(*) {
195
+ /* === 3D SCENE === */
196
+ .bd-headercode-scene {
197
+ width: 100%;
198
+ display: flex;
199
+ justify-content: flex-end;
200
+ align-items: center;
201
+ perspective: 1300px;
202
+ min-height: 400px;
203
+ padding: var(--spacing-16, 16px) 0;
204
+ margin: 0;
205
+ position: relative;
206
+ }
207
+
208
+ .bd-headercode-stack {
209
+ position: relative;
123
210
  width: 100%;
124
211
  max-width: 520px;
125
- box-shadow: 0 1px 2px #0003,0 18px 48px #000a1e73
212
+ transform-style: preserve-3d;
213
+ }
214
+
215
+ :host([variant="tint"]) .bd-headercode-stack {
216
+ transform: perspective(1300px)
217
+ rotateY(var(--ry, -10deg))
218
+ rotateX(var(--rx, 6deg))
219
+ rotateZ(1deg);
220
+ transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
221
+ }
222
+
223
+ @media (prefers-reduced-motion: reduce) {
224
+ :host([variant="tint"]) .bd-headercode-stack {
225
+ transition: none;
226
+ }
227
+ }
228
+
229
+ .bd-headercode-right ::slotted(*) {
230
+ display: block;
231
+ width: 100%;
232
+ box-shadow: 0 1px 2px #0003, 0 18px 48px #000a1e73;
233
+ position: relative;
234
+ z-index: 1; /* sits above the rings */
235
+ }
236
+
237
+ @media (prefers-reduced-motion: reduce) {
238
+ :host([variant="tint"]) .bd-headercode-stack {
239
+ transform: none;
240
+ }
126
241
  }
127
242
 
128
243
  .bd-headercode-terminal-caption {
129
244
  color: var(--color-neutral-500);
130
245
  margin: 0;
246
+ position: relative;
247
+ z-index: 1;
248
+ }
249
+
250
+ .bd-headercode-rings {
251
+ position: absolute;
252
+ inset: 0;
253
+ transform-style: preserve-3d;
254
+ pointer-events: none;
255
+ z-index: 0;
256
+ }
257
+
258
+ .bd-headercode-ring {
259
+ position: absolute;
260
+ inset: 0;
261
+ border-radius: 28px;
262
+ border: 1.5px solid rgba(0, 109, 255, calc(0.5 - var(--d) * 0.08));
263
+ background: rgba(0, 109, 255, calc(0.05 - var(--d) * 0.008));
264
+ box-shadow: 0 0 26px rgba(0, 109, 255, calc(0.16 - var(--d) * 0.025));
265
+ animation: bd-headercode-ring-breathe 6s ease-in-out calc(var(--d) * -1.2s) infinite;
266
+ transform: translate(calc(var(--d) * 10px), calc(var(--d) * 8px));
267
+ }
268
+
269
+ @keyframes bd-headercode-ring-breathe {
270
+ 0%, 100% {
271
+ opacity: 0.85;
272
+ }
273
+ 50% {
274
+ opacity: 0.45;
275
+ }
276
+ }
277
+
278
+ @media (prefers-reduced-motion: reduce) {
279
+ .bd-headercode-ring {
280
+ animation: none;
281
+ }
282
+ }
283
+
284
+ /* === FLOATING ANNOTATION PILLS === */
285
+ .bd-headercode-annotation {
286
+ position: absolute;
287
+ z-index: 2;
288
+ pointer-events: none;
289
+ }
290
+
291
+ .bd-headercode-annotation--top {
292
+ top: -14px;
293
+ right: 32px;
294
+ }
295
+
296
+ .bd-headercode-annotation--bottom {
297
+ bottom: -14px;
298
+ left: 16px;
299
+ right: auto;
300
+ }
301
+
302
+ .bd-headercode-annotation:not(:has(*)) {
303
+ display: none;
304
+ }
305
+
306
+ .bd-headercode-annotation ::slotted(*) {
307
+ width: auto;
308
+ max-width: none;
309
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
310
+ pointer-events: auto;
131
311
  }
132
312
 
133
313
  /* === RESPONSIVE === */
@@ -142,12 +322,30 @@ export default css`
142
322
  align-items: stretch;
143
323
  }
144
324
 
145
- .bd-headercode-right ::slotted(*) {
325
+ .bd-headercode-scene {
326
+ min-height: 0;
327
+ perspective: none;
328
+ justify-content: stretch;
329
+ }
330
+
331
+ .bd-headercode-stack {
146
332
  max-width: none;
147
333
  }
148
334
 
335
+ :host([variant="tint"]) .bd-headercode-stack {
336
+ transform: none;
337
+ }
338
+
339
+ .bd-headercode-rings {
340
+ display: none;
341
+ }
342
+
149
343
  .bd-headercode-description {
150
344
  max-width: none;
151
345
  }
346
+
347
+ .bd-headercode-annotation {
348
+ display: none;
349
+ }
152
350
  }
153
351
  `;
@@ -11,7 +11,11 @@ class HeaderCode extends LitElement {
11
11
  eyebrow : { type: String },
12
12
  title : { type: String },
13
13
  description : { type: String },
14
- terminalCaption: { type: String, attribute: "terminal-caption" }
14
+ terminalCaption: { type: String, attribute: "terminal-caption" },
15
+ variant : { type: String, reflect: true },
16
+ metaSpec : { type: String, attribute: "meta-spec" },
17
+ metaFacts : { type: Array },
18
+ _hasBadge : { state: true }
15
19
  };
16
20
 
17
21
  static styles = [tokens, headerCodeCSS];
@@ -22,6 +26,131 @@ class HeaderCode extends LitElement {
22
26
  this.title = "";
23
27
  this.description = "";
24
28
  this.terminalCaption = "";
29
+ this.variant = "default";
30
+ this.metaSpec = "";
31
+ this.metaFacts = [];
32
+ this._hasBadge = false;
33
+
34
+ this._scene = null;
35
+ this._onPointerMove = this._onPointerMove.bind(this);
36
+ this._onPointerLeave = this._onPointerLeave.bind(this);
37
+ }
38
+
39
+ firstUpdated() {
40
+ this._bindScene();
41
+ }
42
+
43
+ updated(changed) {
44
+ if (changed.has("variant")) this._bindScene();
45
+ }
46
+
47
+ disconnectedCallback() {
48
+ super.disconnectedCallback();
49
+ this._unbindScene();
50
+ }
51
+
52
+ _bindScene() {
53
+ this._unbindScene();
54
+ if (this.variant !== "tint") return;
55
+
56
+ this._scene = this.shadowRoot?.querySelector(".bd-headercode-scene") || null;
57
+ if (!this._scene) return;
58
+
59
+ this._scene.addEventListener("pointermove", this._onPointerMove);
60
+ this._scene.addEventListener("pointerleave", this._onPointerLeave);
61
+ }
62
+
63
+ _unbindScene() {
64
+ if (!this._scene) return;
65
+ this._scene.removeEventListener("pointermove", this._onPointerMove);
66
+ this._scene.removeEventListener("pointerleave", this._onPointerLeave);
67
+ this._scene = null;
68
+ }
69
+
70
+ _onPointerMove(event) {
71
+ const rect = this._scene.getBoundingClientRect();
72
+ const px = (event.clientX - rect.left) / rect.width - 0.5;
73
+ const py = (event.clientY - rect.top) / rect.height - 0.5;
74
+
75
+ const ry = px * 18; // deg
76
+ const rx = -py * 12; // deg
77
+
78
+ this.style.setProperty("--rx", `${rx.toFixed(2)}deg`);
79
+ this.style.setProperty("--ry", `${ry.toFixed(2)}deg`);
80
+ }
81
+
82
+ _onPointerLeave() {
83
+ this.style.removeProperty("--rx");
84
+ this.style.removeProperty("--ry");
85
+ }
86
+
87
+ _onBadgeSlotChange(event) {
88
+ this._hasBadge = event.target.assignedNodes({ flatten: true }).length > 0;
89
+ }
90
+
91
+
92
+ _renderRings() {
93
+ if (this.variant !== "tint") return nothing;
94
+
95
+ return html`
96
+ <div class="bd-headercode-rings" aria-hidden="true">
97
+ <span class="bd-headercode-ring" style="--d:1"></span>
98
+ <span class="bd-headercode-ring" style="--d:2"></span>
99
+ <span class="bd-headercode-ring" style="--d:3"></span>
100
+ <span class="bd-headercode-ring" style="--d:4"></span>
101
+ <span class="bd-headercode-ring" style="--d:5"></span>
102
+ </div>
103
+ `;
104
+ }
105
+
106
+ _renderBadge() {
107
+ const badgeSlot = html`<slot name="badge" @slotchange=${this._onBadgeSlotChange}></slot>`;
108
+
109
+ return this._hasBadge
110
+ ? html`<span class="bd-headercode-badge">${badgeSlot}</span>`
111
+ : html`<span class="bd-headercode-badge-slot-holder" hidden>${badgeSlot}</span>`;
112
+ }
113
+
114
+ _renderMeta() {
115
+ const hasSpec = !!this.metaSpec;
116
+ const hasFacts = this.metaFacts && this.metaFacts.length > 0;
117
+
118
+ if (!hasSpec && !hasFacts) return nothing;
119
+
120
+ const monoFontFamily = "'IBM Plex Mono', ui-monospace, SFMono-Regular, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace";
121
+
122
+ return html`
123
+ <div class="bd-headercode-meta">
124
+ ${hasSpec
125
+ ? html`<bd-p
126
+ kind="xsmall"
127
+ class="bd-headercode-meta-spec"
128
+ font-family=${monoFontFamily}
129
+ letter-spacing=".1em"
130
+ >${this.metaSpec}</bd-p>`
131
+ : nothing}
132
+
133
+ ${hasFacts
134
+ ? html`
135
+ <div class="bd-headercode-meta-facts">
136
+ ${this.metaFacts.map(
137
+ (fact) => html`
138
+ <div class="bd-headercode-meta-fact">
139
+ <bd-p
140
+ kind="xsmall"
141
+ class="bd-headercode-meta-fact-label"
142
+ font-family=${monoFontFamily}
143
+ letter-spacing=".1em"
144
+ >${fact.label}</bd-p>
145
+ <bd-p kind="regular" font-weight="700" class="bd-headercode-meta-fact-value">${fact.value}</bd-p>
146
+ </div>
147
+ `
148
+ )}
149
+ </div>
150
+ `
151
+ : nothing}
152
+ </div>
153
+ `;
25
154
  }
26
155
 
27
156
  render() {
@@ -47,14 +176,28 @@ class HeaderCode extends LitElement {
47
176
  <div class="bd-headercode-cta">
48
177
  <slot name="primary-button"></slot>
49
178
  <slot name="secondary-button"></slot>
50
- <span class="bd-headercode-badge">
51
- <slot name="badge"></slot>
52
- </span>
179
+ ${this._renderBadge()}
53
180
  </div>
181
+
182
+ ${this._renderMeta()}
54
183
  </div>
55
184
 
56
185
  <div class="bd-headercode-right">
57
- <slot name="terminal-content"></slot>
186
+ <div class="bd-headercode-annotation bd-headercode-annotation--top">
187
+ <slot name="annotation-top"></slot>
188
+ </div>
189
+
190
+
191
+ <div class="bd-headercode-scene">
192
+ <div class="bd-headercode-stack">
193
+ ${this._renderRings()}
194
+ <slot name="terminal-content"></slot>
195
+ </div>
196
+ </div>
197
+
198
+ <div class="bd-headercode-annotation bd-headercode-annotation--bottom">
199
+ <slot name="annotation-bottom"></slot>
200
+ </div>
58
201
 
59
202
  ${this.terminalCaption
60
203
  ? html`<bd-p kind="small" class="bd-headercode-terminal-caption" font-family="'IBM Plex Mono', ui-monospace, SFMono-Regular, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace"