@repobit/dex-system-design 0.24.8 → 0.24.10

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 CHANGED
@@ -3,6 +3,21 @@
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.24.10](https://github.com/bitdefender/dex-core/compare/@repobit/dex-system-design@0.24.9...@repobit/dex-system-design@0.24.10) (2026-09-08)
7
+
8
+ **Note:** Version bump only for package @repobit/dex-system-design
9
+
10
+
11
+
12
+
13
+
14
+ ## [0.24.9](https://github.com/bitdefender/dex-core/compare/@repobit/dex-system-design@0.24.8...@repobit/dex-system-design@0.24.9) (2026-09-08)
15
+
16
+ ### Bug Fixes
17
+
18
+ * **DEX-1014:** new AI Bitdefender components and anchor nav changes
19
+
20
+
6
21
  ## [0.24.8](https://github.com/bitdefender/dex-core/compare/@repobit/dex-system-design@0.24.7...@repobit/dex-system-design@0.24.8) (2026-09-08)
7
22
 
8
23
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@repobit/dex-system-design",
3
- "version": "0.24.8",
3
+ "version": "0.24.10",
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",
@@ -74,8 +74,8 @@
74
74
  "url": "https://github.com/bitdefender/dex-core/issues"
75
75
  },
76
76
  "dependencies": {
77
- "@repobit/dex-store": "1.6.24",
78
- "@repobit/dex-store-elements": "2.0.29",
77
+ "@repobit/dex-store": "1.6.26",
78
+ "@repobit/dex-store-elements": "2.0.31",
79
79
  "lit": "^3.3.2"
80
80
  },
81
81
  "devDependencies": {
@@ -92,5 +92,5 @@
92
92
  "volta": {
93
93
  "node": "24.14.0"
94
94
  },
95
- "gitHead": "bf388f1fa589bd68fb763b66bb7ed9e2e598724c"
95
+ "gitHead": "bed8fc04b957945c0fccba6068d9395f768cfd5a"
96
96
  }
@@ -93,6 +93,7 @@ function aiGuardianBody() {
93
93
  aria-label="Page sections"
94
94
  cta-label="Download BETA Today"
95
95
  cta-href="#download"
96
+ sticky-top="65px"
96
97
  .items=${[
97
98
  { title: "Overview" },
98
99
  { title: "Compatibility" },
@@ -498,6 +499,7 @@ function skillScannerBody() {
498
499
  <div style="max-width: 1200px; margin: 0 auto;">
499
500
  <bd-anchor-nav
500
501
  aria-label="Skill Scanner sections"
502
+ sticky-top="65px"
501
503
  .items=${[
502
504
  { title: "Install" },
503
505
  { title: "Overview" },
@@ -1034,11 +1036,15 @@ switches, in place, between two full "pages" without any real navigation —
1034
1036
  clicking a tab intercepts the anchor click and swaps local state instead of
1035
1037
  following the href (which has no route in Storybook and would 404).
1036
1038
 
1039
+ Both anchor-nav bars use \`sticky-top="65px"\` so they pin themselves *below*
1040
+ the sticky page-switcher instead of overlapping it once scrolled. That value
1041
+ is an estimate — measure bd-page-switcher's real rendered height in DevTools
1042
+ and adjust if the two bars still overlap or leave a gap.
1043
+
1037
1044
  **AI Guardian** page: \`bd-header-code\`, \`bd-quick-answer\`, sticky \`bd-anchor-nav\`
1038
1045
  (9 items) wrapping \`bd-overview\`, \`bd-compatibility-table\` (default variant), the
1039
1046
  "Protections" section (\`bd-bento-grid\` + \`bd-comparison-panel\`), \`bd-process-steps\`,
1040
- \`bd-compatibility-table\` (\`variant="outcome"\`), two chevron \`bd-accordion-item\`s
1041
- revealing \`bd-compatibility-table\` (\`variant="risk"\` / \`variant="light"\`), an FAQ
1047
+ \`bd-compatibility-table\` (\`variant="risk"\` / \`variant="light"\`), an FAQ
1042
1048
  section (\`bd-accordion-bg\`, 9 questions), \`bd-cta-banner\`, \`bd-agent-endpoints\`,
1043
1049
  and \`bd-trust-panel\`.
1044
1050
 
@@ -1049,8 +1055,9 @@ Install, Overview, What it detects, How it works, Privacy, FAQ) wrapping
1049
1055
  a \`bd-compatibility-table\` inside a chevron accordion, a \`bd-bento-grid\` +
1050
1056
  \`bd-process-steps\` + another \`bd-compatibility-table\` for "How it works",
1051
1057
  another \`bd-install-options\` (generic-slot variant: table + checklist + note)
1052
- for "Privacy", and a \`bd-trust-panel\` under "FAQ". The FAQ accordion itself
1053
- (actual questions) is still a placeholder — content not provided yet.
1058
+ for "Privacy", a \`bd-trust-panel\` + \`bd-accordion-bg\` FAQ (7 questions) under
1059
+ "FAQ", a \`bd-cta-banner\`, and \`bd-agent-endpoints\` with legal copy in its
1060
+ \`rows\` slot.
1054
1061
 
1055
1062
  Both pages share the same \`bd-footer\` at the bottom.
1056
1063
  `
@@ -11,14 +11,17 @@ export const anchorNavStyles = css`
11
11
 
12
12
  :host(.is-sticky) {
13
13
  position: fixed;
14
- top: 0;
14
+ top: var(--sticky-top, 0);
15
15
  left: var(--sticky-left, 0);
16
16
  width: var(--sticky-width, 100%);
17
17
  box-shadow: var(--shadow-sm);
18
18
  z-index: 1000;
19
19
  }
20
20
 
21
- /* Border-urile sunt pe nav - full width */
21
+ :host(.is-sticky) nav {
22
+ background: var(--color-neutral-0);
23
+ }
24
+
22
25
  nav {
23
26
  display: block;
24
27
  margin: var(--spacing-0);
@@ -29,11 +32,6 @@ export const anchorNavStyles = css`
29
32
  border-bottom: var(--border-width-1) solid var(--color-neutral-100);
30
33
  }
31
34
 
32
- /* Când e sticky, păstrăm aceleași border-uri */
33
- :host(.is-sticky) nav {
34
- background: var(--color-neutral-0);
35
- }
36
-
37
35
  .bd-anchor-nav__inner {
38
36
  display: flex;
39
37
  flex-wrap: wrap;
@@ -48,7 +46,6 @@ export const anchorNavStyles = css`
48
46
  box-sizing: border-box;
49
47
  }
50
48
 
51
- /* Restul codului tău CSS rămâne identic de aici în jos */
52
49
 
53
50
  .anchor-link:focus-visible,
54
51
  .bd-anchor-nav__cta:focus-visible,
@@ -118,19 +115,19 @@ export const anchorNavStyles = css`
118
115
  text-decoration: none !important;
119
116
  }
120
117
 
121
- .anchor-link::after {
122
- content: "";
118
+
119
+ .anchor-indicator {
123
120
  position: absolute;
124
- left: var(--spacing-0);
121
+ left: 0;
125
122
  bottom: calc(-1 * var(--spacing-14));
126
- width: 0%;
123
+ width: 0;
127
124
  height: var(--border-width-3);
128
125
  background-color: var(--color-blue-500);
129
- transition: width var(--transition-duration-slow) var(--transition-easing-ease-out);
130
- }
131
-
132
- .anchor-link.active::after {
133
- width: 100%;
126
+ transform: translateX(0);
127
+ transition:
128
+ transform var(--transition-duration-slow) var(--transition-easing-ease-out),
129
+ width var(--transition-duration-slow) var(--transition-easing-ease-out);
130
+ pointer-events: none;
134
131
  }
135
132
 
136
133
  .bd-anchor-nav__dropdown {
@@ -299,10 +296,11 @@ export const anchorNavStyles = css`
299
296
  }
300
297
 
301
298
  @media (prefers-reduced-motion: reduce) {
302
- .anchor-link::after,
299
+ .anchor-indicator,
303
300
  .bd-anchor-nav__dropdown-chevron,
304
301
  .bd-cta,
305
302
  .bd-anchor-nav__dropdown-label--swap {
303
+ transition: none !important;
306
304
  animation: none !important;
307
305
  }
308
306
  }
@@ -33,7 +33,12 @@ class BdAnchorNav extends LitElement {
33
33
  ctaLabel : { type: String, attribute: "cta-label" },
34
34
  ctaHref : { type: String, attribute: "cta-href" },
35
35
  items : { type: Array },
36
- maxWidth : { type: String, attribute: "max-width" }
36
+ maxWidth : { type: String, attribute: "max-width" },
37
+ // How far from the viewport top this nav sticks once pinned — set this
38
+ // to the rendered height of whatever sticky element sits above it (e.g.
39
+ // a page-switcher), so the two don't stack on top of each other at
40
+ // top:0. Accepts any CSS length, e.g. "64px", "4rem".
41
+ stickyTop : { type: String, attribute: "sticky-top" }
37
42
  };
38
43
 
39
44
  static styles = [tokens, anchorNavStyles];
@@ -48,6 +53,7 @@ class BdAnchorNav extends LitElement {
48
53
  this.ctaHref = "";
49
54
  this.items = null;
50
55
  this.maxWidth = "";
56
+ this.stickyTop = "0px";
51
57
  this._panelId = `bd-anchor-nav-panel-${Math.random().toString(36)
52
58
  .slice(2, 11)}`;
53
59
  this._onScrollSpy = this._onScrollSpy.bind(this);
@@ -61,6 +67,10 @@ class BdAnchorNav extends LitElement {
61
67
  this._stickyPlaceholder = null;
62
68
  this._onStickyScroll = null;
63
69
  this._handleResize = this._handleResize.bind(this);
70
+ // sliding underline indicator: false until the first successful
71
+ // measurement + placement, so it doesn't visibly animate in from the
72
+ // left edge on first paint.
73
+ this._indicatorReady = false;
64
74
  }
65
75
 
66
76
  connectedCallback() {
@@ -85,8 +95,76 @@ class BdAnchorNav extends LitElement {
85
95
  this._destroySticky();
86
96
  }
87
97
 
98
+ updated(changedProperties) {
99
+ super.updated(changedProperties);
100
+
101
+ if (changedProperties.has("stickyTop")) {
102
+ this.style.setProperty("--sticky-top", this.stickyTop || "0px");
103
+ // the scroll-spy offset (how far to scroll past the sticky bar when
104
+ // clicking a link) depends on this too, since the nav's own visible
105
+ // top now sits lower than the viewport top while stuck.
106
+ this._runScrollSpy();
107
+ }
108
+
109
+ if (changedProperties.has("activeId") || changedProperties.has("items")) {
110
+ // wait a frame so the .active class has actually painted before
111
+ // measuring its box — requestAnimationFrame is enough here, no need
112
+ // for updateComplete since nothing else in render() depends on this.
113
+ requestAnimationFrame(() => this._updateIndicator());
114
+ }
115
+
116
+ if (!changedProperties.has("activeId")) return;
117
+ const prev = changedProperties.get("activeId");
118
+ if (prev === undefined || prev === "") return;
119
+ if (prev === this.activeId) return;
120
+ queueMicrotask(() => this._playMobileDropdownLabelSwap());
121
+ }
122
+
123
+ firstUpdated() {
124
+ const resolved = this._resolvedItems();
125
+ if (resolved.length) {
126
+ this.activeId = "anchor-1";
127
+ this.requestUpdate();
128
+ }
129
+ queueMicrotask(() => this._runScrollSpy());
130
+ this._handleMobileFullWidth();
131
+ requestAnimationFrame(() => this._updateIndicator());
132
+ }
133
+
134
+ // Positions the sliding underline under the currently active desktop
135
+ // link. Reads offsetLeft/offsetWidth directly off the active bd-link —
136
+ // as a flex item it's block-level regardless of its own :host display,
137
+ // so these are reliable without any extra math against a bounding rect.
138
+ _updateIndicator() {
139
+ const indicator = this.renderRoot?.querySelector(".anchor-indicator");
140
+ const activeLink = this.renderRoot?.querySelector(
141
+ ".anchor-links--desktop .anchor-link.active"
142
+ );
143
+ if (!indicator || !activeLink) return;
144
+
145
+ const left = activeLink.offsetLeft;
146
+ const width = activeLink.offsetWidth;
147
+
148
+ if (!this._indicatorReady) {
149
+ // first placement: skip the transition so it doesn't slide in from
150
+ // the left edge on initial paint, then re-enable it next frame.
151
+ indicator.style.transition = "none";
152
+ indicator.style.transform = `translateX(${left}px)`;
153
+ indicator.style.width = `${width}px`;
154
+ // force a style flush before removing the transition override
155
+ void indicator.offsetWidth;
156
+ indicator.style.transition = "";
157
+ this._indicatorReady = true;
158
+ return;
159
+ }
160
+
161
+ indicator.style.transform = `translateX(${left}px)`;
162
+ indicator.style.width = `${width}px`;
163
+ }
164
+
88
165
  _handleResize() {
89
166
  this._handleMobileFullWidth();
167
+ this._updateIndicator();
90
168
  }
91
169
 
92
170
  _handleMobileFullWidth() {
@@ -109,6 +187,7 @@ class BdAnchorNav extends LitElement {
109
187
 
110
188
  _initSticky() {
111
189
  this._stickyOriginalTop = null;
190
+ this.style.setProperty("--sticky-top", this.stickyTop || "0px");
112
191
 
113
192
  this._onStickyScroll = () => {
114
193
  const parent = this.parentElement;
@@ -118,14 +197,15 @@ class BdAnchorNav extends LitElement {
118
197
  const navHeight = this._stickyPlaceholder
119
198
  ? this._stickyPlaceholder.offsetHeight
120
199
  : this.offsetHeight;
200
+ const stickyTopPx = parseLengthToPx(this.stickyTop) || 0;
121
201
 
122
202
  if (this._stickyOriginalTop === null) {
123
203
  const rect = this.getBoundingClientRect();
124
- this._stickyOriginalTop = rect.top + window.scrollY;
204
+ this._stickyOriginalTop = rect.top + window.scrollY - stickyTopPx;
125
205
  }
126
206
 
127
207
  const passedTop = window.scrollY >= this._stickyOriginalTop;
128
- const parentExited = parentRect.bottom <= navHeight;
208
+ const parentExited = parentRect.bottom <= navHeight + stickyTopPx;
129
209
 
130
210
  if (passedTop && !parentExited) {
131
211
  if (!this.classList.contains("is-sticky")) {
@@ -297,25 +377,6 @@ class BdAnchorNav extends LitElement {
297
377
  }
298
378
  }
299
379
 
300
- firstUpdated() {
301
- const resolved = this._resolvedItems();
302
- if (resolved.length) {
303
- this.activeId = "anchor-1";
304
- this.requestUpdate();
305
- }
306
- queueMicrotask(() => this._runScrollSpy());
307
- this._handleMobileFullWidth();
308
- }
309
-
310
- updated(changedProperties) {
311
- super.updated(changedProperties);
312
- if (!changedProperties.has("activeId")) return;
313
- const prev = changedProperties.get("activeId");
314
- if (prev === undefined || prev === "") return;
315
- if (prev === this.activeId) return;
316
- queueMicrotask(() => this._playMobileDropdownLabelSwap());
317
- }
318
-
319
380
  _playMobileDropdownLabelSwap() {
320
381
  if (typeof window === "undefined") return;
321
382
  if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
@@ -334,10 +395,11 @@ class BdAnchorNav extends LitElement {
334
395
 
335
396
  _stickyOffsetPx() {
336
397
  const barPx = this.offsetHeight;
398
+ const stickyTopPx = parseLengthToPx(this.stickyTop) || 0;
337
399
  const insetRaw = getComputedStyle(this).getPropertyValue("--spacing-8")
338
400
  .trim();
339
401
  const insetPx = parseLengthToPx(insetRaw) || 8;
340
- if (barPx > 0) return barPx + insetPx;
402
+ if (barPx > 0) return barPx + insetPx + stickyTopPx;
341
403
  const fallback = getComputedStyle(this).getPropertyValue("--spacing-128")
342
404
  .trim();
343
405
  return parseLengthToPx(fallback) || 128;
@@ -413,6 +475,7 @@ class BdAnchorNav extends LitElement {
413
475
  >${title}</bd-link>
414
476
  `;
415
477
  })}
478
+ <span class="anchor-indicator" aria-hidden="true"></span>
416
479
  </div>
417
480
  `;
418
481
 
@@ -27,6 +27,9 @@ prin \`ResizeObserver\` — fără a fi necesară setarea manuală a \`max-width
27
27
  - Când navul iese din viewport, devine \`position: fixed\` full-width
28
28
  - Border-ul și background-ul sunt full-width
29
29
  - Conținutul (linkuri + CTA) rămâne centrat și aliniat cu cel mai lat sibling
30
+ - Dacă mai există un alt element sticky **deasupra** lui (ex. un header/page-switcher
31
+ propriu sticky la \`top: 0\`), setează \`sticky-top\` cu înălțimea acelui element —
32
+ altfel cele două se suprapun, fiindcă implicit navul se lipește tot la \`top: 0\`.
30
33
 
31
34
  ---
32
35
 
@@ -76,6 +79,18 @@ prin \`ResizeObserver\` — fără a fi necesară setarea manuală a \`max-width
76
79
  ></bd-anchor-nav>
77
80
  \`\`\`
78
81
 
82
+ ### Utilizare cu \`sticky-top\` (nav secundar sub un header sticky propriu)
83
+ \`\`\`html
84
+ <my-sticky-header></my-sticky-header>
85
+
86
+ <bd-anchor-nav sticky-top="64px" cta-label="Buy now" cta-href="#pricing">
87
+ <bd-anchor-nav-item title="Overview" href="#overview"></bd-anchor-nav-item>
88
+ <bd-anchor-nav-item title="Pricing" href="#pricing"></bd-anchor-nav-item>
89
+ </bd-anchor-nav>
90
+ \`\`\`
91
+ \`64px\` trebuie să fie înălțimea reală (măsurată în DevTools) a elementului sticky de deasupra —
92
+ nu-i o valoare universală, depinde de conținutul acelui header.
93
+
79
94
  ---
80
95
 
81
96
  ### Atribute \`bd-anchor-nav\`
@@ -85,6 +100,7 @@ prin \`ResizeObserver\` — fără a fi necesară setarea manuală a \`max-width
85
100
  | \`cta-href\` | String | \`"#section-pricing"\` | href-ul butonului CTA fallback |
86
101
  | \`aria-label\` | String | \`"Section navigation"\` | Label accesibil pentru \`<nav>\` |
87
102
  | \`items\` | Array | — | \`{ title, href }[]\` — suprascrie child items |
103
+ | \`sticky-top\` | String | \`"0px"\` | Cât de departe de vârful viewport-ului se lipește navul când devine sticky — setează-l la înălțimea oricărui alt element sticky de deasupra, ca să nu se suprapună |
88
104
 
89
105
  ### Atribute \`bd-anchor-nav-item\`
90
106
  | Atribut | Tip | Descriere |
@@ -114,6 +130,12 @@ Pe mobile devine automat full-width prin \`::slotted([slot="cta"])\`.
114
130
  control : "object",
115
131
  description: "Array de { title, href } — suprascrie child elements",
116
132
  table : { type: { summary: "{ title: string, href: string }[]" }, category: "Content" }
133
+ },
134
+ stickyTop: {
135
+ name : "sticky-top",
136
+ control : "text",
137
+ description: "Cât de departe de vârful viewport-ului se lipește navul când devine sticky — setează-l la înălțimea oricărui element sticky de deasupra (ex. un header propriu), ca sa nu se suprapună la scroll",
138
+ table : { type: { summary: "string" }, defaultValue: { summary: "0px" }, category: "Sticky" }
117
139
  }
118
140
  }
119
141
  };
@@ -404,6 +426,65 @@ export const WithScrollTargets = {
404
426
  }
405
427
  };
406
428
 
429
+ export const StickyBelowFixedHeader = {
430
+ name : "Sticky sub un header propriu — sticky-top",
431
+ render: () => html`
432
+ <div>
433
+ <!-- Simuleaza un header sticky separat (ex. bd-page-switcher), la fel
434
+ pozitionat: position: sticky; top: 0. Fara sticky-top pe nav,
435
+ cele doua s-ar suprapune identic la scroll. -->
436
+ <div style="
437
+ position: sticky; top: 0; z-index: 200;
438
+ background: #0a1a2f; color: #fff;
439
+ padding: 16px 24px; box-sizing: border-box;
440
+ font-family: sans-serif; font-weight: 700; font-size: 18px;
441
+ display: flex; align-items: center; justify-content: space-between;
442
+ ">
443
+ <span>Mock Header (simulează bd-page-switcher, ~64px înălțime)</span>
444
+ <span style="opacity: .7; font-weight: 400; font-size: 13px;">Tab A · Tab B</span>
445
+ </div>
446
+
447
+ <bd-anchor-nav
448
+ sticky-top="64px"
449
+ cta-label="Buy now"
450
+ cta-href="#anchor-3-section"
451
+ >
452
+ <bd-anchor-nav-item title="Overview" href="#anchor-1-section"></bd-anchor-nav-item>
453
+ <bd-anchor-nav-item title="Features" href="#anchor-2-section"></bd-anchor-nav-item>
454
+ <bd-anchor-nav-item title="Pricing" href="#anchor-3-section"></bd-anchor-nav-item>
455
+ </bd-anchor-nav>
456
+
457
+ <div style="max-width: 800px; margin: 0 auto;">
458
+ <div id="anchor-1-section" style="min-height: 60vh; padding: 80px 24px; background: #EDF9FF;">
459
+ <bd-h as="h2">Overview Section</bd-h>
460
+ <bd-p kind="regular">
461
+ Scroll în jos: mock header-ul de sus rămâne fix la top: 0, iar
462
+ bd-anchor-nav se lipește exact sub el (la 64px), nu peste el.
463
+ </bd-p>
464
+ </div>
465
+ <div id="anchor-2-section" style="min-height: 60vh; padding: 80px 24px; background: #fff;">
466
+ <bd-h as="h2">Features Section</bd-h>
467
+ </div>
468
+ <div id="anchor-3-section" style="min-height: 60vh; padding: 80px 24px; background: #EDF9FF;">
469
+ <bd-h as="h2">Pricing Section</bd-h>
470
+ </div>
471
+ </div>
472
+ </div>
473
+ `,
474
+ parameters: {
475
+ docs: {
476
+ description: {
477
+ story: `Reproduce cazul real din demo-ul AI Guardian / Skill Scanner: un header sticky
478
+ propriu deasupra (aici simulat printr-un \`<div>\`, în producție \`bd-page-switcher\`)
479
+ și \`bd-anchor-nav\` cu \`sticky-top="64px"\`. Fără acest atribut, ambele s-ar
480
+ fixa la \`top: 0\` și s-ar suprapune perfect la scroll. Valoarea \`64px\` trebuie
481
+ să corespundă înălțimii reale a elementului sticky de deasupra — verifică în
482
+ DevTools dacă diferă la tine.`
483
+ }
484
+ }
485
+ }
486
+ };
487
+
407
488
  export const MobileView = {
408
489
  name : "Mobile View",
409
490
  render : () => renderWithSlotCta(["Overview", "Features", "Reviews", "Pricing"]),
@@ -420,9 +501,10 @@ export const MobileView = {
420
501
  export const Playground = {
421
502
  name: "Playground",
422
503
  args: {
423
- ctaLabel: "Buy now",
424
- ctaHref : "#section-pricing",
425
- items : [
504
+ ctaLabel : "Buy now",
505
+ ctaHref : "#section-pricing",
506
+ stickyTop: "0px",
507
+ items : [
426
508
  { title: "Overview", href: "#anchor-1-section" },
427
509
  { title: "Features", href: "#anchor-2-section" },
428
510
  { title: "Reviews", href: "#anchor-3-section" },
@@ -433,13 +515,14 @@ export const Playground = {
433
515
  <bd-anchor-nav
434
516
  cta-label=${args.ctaLabel}
435
517
  cta-href=${args.ctaHref}
518
+ sticky-top=${args.stickyTop}
436
519
  .items=${args.items}
437
520
  ></bd-anchor-nav>
438
521
  `,
439
522
  parameters: {
440
523
  docs: {
441
524
  description: {
442
- story: "Playground interactiv. Editează `items`, `ctaLabel` și `ctaHref` din Controls."
525
+ story: "Playground interactiv. Editează `items`, `ctaLabel`, `ctaHref` și `sticky-top` din Controls."
443
526
  }
444
527
  }
445
528
  }
@@ -0,0 +1,122 @@
1
+ import { html } from "lit";
2
+ import "../button/button.js";
3
+ import "./cta-banner.js";
4
+
5
+ export default {
6
+ title : "Components/CtaBanner",
7
+ tags : ["autodocs"],
8
+ parameters: {
9
+ layout: "fullscreen",
10
+ docs : {
11
+ description: {
12
+ component: `
13
+ Full-width closing call-to-action banner on a blue gradient background —
14
+ centered title, description, one or two buttons, and an optional small
15
+ mono caption underneath.
16
+
17
+ \`\`\`html
18
+ <bd-cta-banner
19
+ title="Run your AI agents with a guardrail"
20
+ description="AI Guardian is in open BETA on macOS. Download the BETA and secure your agentic workflows."
21
+ caption="Downloads the macOS BETA (.dmg). Free during BETA."
22
+ >
23
+ <bd-button-link slot="primary-button" kind="white" strong>Download BETA Today</bd-button-link>
24
+ <bd-button-link slot="secondary-button" kind="outline-white" strong>Read the docs</bd-button-link>
25
+ </bd-cta-banner>
26
+ \`\`\`
27
+
28
+ ### Attribute table
29
+ | Attribute / slot | Type | Description |
30
+ |---|---|---|
31
+ | \`title\` / \`slot="title"\` | String | Large centred heading |
32
+ | \`description\` / \`slot="description"\` | String | Paragraph under the title |
33
+ | \`slot="primary-button"\` | — | First button, typically \`kind="white"\` |
34
+ | \`slot="secondary-button"\` | — | Second button, typically \`kind="outline-white"\` |
35
+ | \`caption\` / \`slot="caption"\` | String | Optional small mono line under the buttons |
36
+
37
+ Buttons are expected to use \`bd-button-link\` with \`kind="white"\` /
38
+ \`kind="outline-white"\` and \`strong\`, so they read against the blue
39
+ background — that pairing isn't enforced by the component, just the
40
+ convention used everywhere it's been placed so far.
41
+ `
42
+ }
43
+ }
44
+ }
45
+ };
46
+
47
+ // ─── Cu caption (AI Guardian) ──────────────────────────────────────────────
48
+
49
+ export const WithCaption = {
50
+ name : "Cu caption",
51
+ render: () => html`
52
+ <bd-cta-banner
53
+ title="Run your AI agents with a guardrail"
54
+ description="AI Guardian is in open BETA on macOS. Download the BETA and secure your agentic workflows."
55
+ caption="Downloads the macOS BETA (.dmg). Free during BETA."
56
+ >
57
+ <bd-button-link
58
+ slot="primary-button"
59
+ label="Download BETA Today"
60
+ kind="white"
61
+ size="md"
62
+ strong
63
+ >Download BETA Today</bd-button-link>
64
+
65
+ <bd-button-link
66
+ slot="secondary-button"
67
+ label="Read the docs"
68
+ kind="outline-white"
69
+ size="md"
70
+ strong
71
+ >Read the docs</bd-button-link>
72
+ </bd-cta-banner>
73
+ `
74
+ };
75
+
76
+ // ─── Fara caption (Skill Scanner) ──────────────────────────────────────────
77
+
78
+ export const WithoutCaption = {
79
+ name : "Fără caption",
80
+ render: () => html`
81
+ <bd-cta-banner
82
+ title="Scan before you trust"
83
+ description="A skill is code you hand to your agent. One command stands between you and running it blind."
84
+ >
85
+ <bd-button-link
86
+ slot="primary-button"
87
+ label="Install with npx"
88
+ kind="white"
89
+ size="md"
90
+ strong
91
+ >Install with npx</bd-button-link>
92
+
93
+ <bd-button-link
94
+ slot="secondary-button"
95
+ label="View on GitHub"
96
+ kind="outline-white"
97
+ size="md"
98
+ strong
99
+ >View on GitHub</bd-button-link>
100
+ </bd-cta-banner>
101
+ `
102
+ };
103
+
104
+ // ─── Un singur buton ────────────────────────────────────────────────────────
105
+
106
+ export const SingleButton = {
107
+ name : "Un singur buton",
108
+ render: () => html`
109
+ <bd-cta-banner
110
+ title="Ready to get started?"
111
+ description="Free during BETA, no waitlist and no licence key."
112
+ >
113
+ <bd-button-link
114
+ slot="primary-button"
115
+ label="Install now"
116
+ kind="white"
117
+ size="md"
118
+ strong
119
+ >Install now</bd-button-link>
120
+ </bd-cta-banner>
121
+ `
122
+ };
@@ -0,0 +1,219 @@
1
+ import { html } from "lit";
2
+ import "../compatibility-table/compatibility-table.js";
3
+ import "../heading/heading.js";
4
+ import "../paragraph/paragraph.js";
5
+ import "./install-options.js";
6
+
7
+ export default {
8
+ title : "Components/InstallOptions",
9
+ tags : ["autodocs"],
10
+ parameters: {
11
+ layout: "fullscreen",
12
+ docs : {
13
+ description: {
14
+ component: `
15
+ Section container (eyebrow + title + description on a light blue background)
16
+ for "how to get this" style pages. Three ways to fill it, usable on their
17
+ own or combined:
18
+
19
+ 1. **Cards grid** (\`.cards\`) — a row of install-method cards, each with an
20
+ optional badge, a copyable code block, and a link.
21
+ 2. **"Then just ask" prompt panel** (\`prompt-panel\` + slots) — a copyable
22
+ prompt block with a label, a warning-style note, and a footer line.
23
+ 3. **Generic default slot** — anything else (a table, a checklist, a note)
24
+ that doesn't fit the cards grid, while still getting the shared
25
+ eyebrow/title/description header.
26
+
27
+ \`\`\`html
28
+ <!-- Cards + Works in pills -->
29
+ <bd-install-options
30
+ eyebrow="Get it"
31
+ title="Three ways to install"
32
+ description="..."
33
+ ></bd-install-options>
34
+
35
+ <!-- Generic slot instead of cards -->
36
+ <bd-install-options eyebrow="Trust & transparency" title="What leaves your machine">
37
+ <bd-compatibility-table variant="risk" .columns=\${["Mode", "Transmitted"]} .rows=\${[...]}></bd-compatibility-table>
38
+ </bd-install-options>
39
+ \`\`\`
40
+
41
+ ### Attribute / property table
42
+ | Name | Type | Default | Description |
43
+ |---|---|---|---|
44
+ | \`eyebrow\` | String | — | Small blue label above the title |
45
+ | \`title\` | String | — | Section heading |
46
+ | \`description\` | String | — | Paragraph under the title |
47
+ | \`.worksIn\` | Array | \`[]\` | \`[{ label, initial, color }]\` — pill row. \`initial\`/\`color\` are placeholders (a letter on a solid circle) until real brand marks are available |
48
+ | \`.cards\` | Array | \`[]\` | \`[{ title, badge, description, code, linkLabel, linkHref }]\` — rendered only if non-empty |
49
+ | *(default slot)* | — | — | Anything else — renders between the description/works-in row and the cards grid |
50
+ | \`prompt-panel\` | Boolean | \`false\` | Shows the "Then just ask" sub-panel below the cards |
51
+ | \`prompt-label\` | String | — | Small mono label above the prompt code block |
52
+
53
+ ### Slots (only used when \`prompt-panel\` is set)
54
+ | Slot | Description |
55
+ |---|---|
56
+ | \`prompt-title\` | Heading of the sub-panel |
57
+ | \`prompt-description\` | Paragraph under that heading |
58
+ | \`prompt\` | The copyable prompt text — the Copy button reads this slot's \`textContent\` |
59
+ | \`prompt-note\` | Warning-style note box |
60
+ | \`prompt-footer\` | Closing line (e.g. uninstall instructions) |
61
+ `
62
+ }
63
+ }
64
+ }
65
+ };
66
+
67
+ // ─── Carduri + Works in (cazul principal: "Three ways to install") ────────
68
+
69
+ export const CardsWithWorksIn = {
70
+ name : "Carduri + Works in",
71
+ render: () => html`
72
+ <bd-install-options
73
+ eyebrow="Get it"
74
+ title="Three ways to install"
75
+ description="Agent Skill Scanner installs in under a minute with npx via skills.sh, by cloning the GitHub repository, or as a zip archive you drop into your skills directory. It runs inside your agent harness and installs its own Python dependencies on first use."
76
+ .worksIn=${[
77
+ { label: "Claude Code", initial: "✻", color: "#d97706" },
78
+ { label: "Codex", initial: "◒", color: "#111827" },
79
+ { label: "OpenClaw", initial: "▣", color: "#dc2626" },
80
+ { label: "opencode", initial: "▪", color: "#111827" },
81
+ { label: "Hermes Agent", initial: "H", color: "#111827" }
82
+ ]}
83
+ .cards=${[
84
+ {
85
+ title : "With npx, from skills.sh",
86
+ badge : "Recommended",
87
+ description: "One command via the skills.sh installer. Works for any supported harness.",
88
+ code : "npx skills add bitdefender/agent-skill-scanner",
89
+ linkLabel : "View on skills.sh",
90
+ linkHref : "#"
91
+ },
92
+ {
93
+ title : "From GitHub",
94
+ description: "Clone the repository and copy the inner agent-skill-scanner/ folder into your skills directory.",
95
+ code : "git clone https://github.com/bitdefender/agent-skill-scanner.git\ncp -r agent-skill-scanner/agent-skill-scanner ~/.claude/skills/",
96
+ linkLabel : "View the repository",
97
+ linkHref : "#"
98
+ },
99
+ {
100
+ title : "As a zip archive",
101
+ description: "Download the archive, unzip it, and move the inner agent-skill-scanner/ folder into your skills directory.",
102
+ linkLabel : "Download the zip",
103
+ linkHref : "#"
104
+ }
105
+ ]}
106
+ ></bd-install-options>
107
+ `
108
+ };
109
+
110
+ // ─── Carduri + prompt panel ("Then just ask") ──────────────────────────────
111
+
112
+ export const WithPromptPanel = {
113
+ name : "Carduri + prompt panel",
114
+ parameters: {
115
+ docs: {
116
+ description: {
117
+ story: "Aceleași carduri, plus sub-panelul \"Then just ask\" activat prin `prompt-panel`. Butonul Copy citește textul din `slot=\"prompt\"` la click, indiferent de markup."
118
+ }
119
+ }
120
+ },
121
+ render: () => html`
122
+ <bd-install-options
123
+ eyebrow="Get it"
124
+ title="Three ways to install"
125
+ description="Agent Skill Scanner installs in under a minute with npx via skills.sh, by cloning the GitHub repository, or as a zip archive you drop into your skills directory."
126
+ .cards=${[
127
+ {
128
+ title : "With npx, from skills.sh",
129
+ badge : "Recommended",
130
+ description: "One command via the skills.sh installer.",
131
+ code : "npx skills add bitdefender/agent-skill-scanner",
132
+ linkLabel : "View on skills.sh",
133
+ linkHref : "#"
134
+ },
135
+ {
136
+ title : "From GitHub",
137
+ description: "Clone the repository into your skills directory.",
138
+ code : "git clone https://github.com/bitdefender/agent-skill-scanner.git",
139
+ linkLabel : "View the repository",
140
+ linkHref : "#"
141
+ }
142
+ ]}
143
+ prompt-panel
144
+ prompt-label="Prompt for your agent"
145
+ >
146
+ <span slot="prompt-title">Then just ask</span>
147
+ <span slot="prompt-description">There is no command to learn. Once installed, paste this prompt into your agent; it handles invocation, target resolution and update checks.</span>
148
+ <span slot="prompt">Scan the &lt;skill name or path&gt; skill for security risks and show me the report.</span>
149
+ <bd-p slot="prompt-note" kind="small">Requires Python 3.12 or 3.13 (3.13 recommended). Dependencies install on first use.</bd-p>
150
+ <span slot="prompt-footer">To uninstall, delete the agent-skill-scanner folder from your skills directory.</span>
151
+ </bd-install-options>
152
+ `
153
+ };
154
+
155
+ // ─── Slot generic in loc de carduri (tabel + checklist + nota) ────────────
156
+
157
+ export const GenericSlotContent = {
158
+ name : "Slot generic (tabel + checklist + notă)",
159
+ parameters: {
160
+ docs: {
161
+ description: {
162
+ story: "Aceeași secțiune (eyebrow + titlu + descriere pe fundal albastru), dar fără `.cards` deloc — în schimb, orice conținut arbitrar prin slotul generic: aici un `bd-compatibility-table` (variant `risk`, cu antete custom prin `.columns`), o casetă cu checklist, și o notă de avertizare, scrise ca markup simplu în locul consumator."
163
+ }
164
+ }
165
+ },
166
+ render: () => html`
167
+ <bd-install-options
168
+ eyebrow="Trust &amp; transparency"
169
+ title="What leaves your machine"
170
+ description="Two modes, both agent-driven. Scan is the default: only hashes, extracted indicators and metadata are transmitted, and full files are never uploaded. Submit is opt-in: the complete skill archive is uploaded for deeper analysis, and only when you explicitly ask for it."
171
+ >
172
+ <bd-compatibility-table
173
+ variant="risk"
174
+ .columns=${["Mode", "Transmitted"]}
175
+ .rows=${[
176
+ {
177
+ category: "Scan (default)",
178
+ coverage: "File hashes (SHA-256/MD5), extracted indicators and pattern matches. Full file contents are not transmitted."
179
+ },
180
+ {
181
+ category: "Submit (opt-in)",
182
+ coverage: "The complete skill archive, uploaded for deep analysis. The agent will not do this unless you explicitly ask."
183
+ }
184
+ ]}
185
+ ></bd-compatibility-table>
186
+
187
+ <div style="background: var(--color-neutral-0); border: 1px solid var(--color-neutral-100); border-radius: var(--radius-xl); padding: 24px; box-sizing: border-box; margin-top: 24px;">
188
+ <bd-h as="h3" style="margin: 0 0 16px;">The contract the agent is held to</bd-h>
189
+ <ul style="list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 16px;">
190
+ <li style="display: flex; align-items: flex-start; gap: 12px;">
191
+ <span aria-hidden="true" style="flex-shrink: 0; width: 20px; height: 20px; border-radius: 999px; background: var(--color-green-600, #16a34a); color: #fff; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 700; margin-top: 2px;">✓</span>
192
+ <bd-p kind="regular" style="margin:0;">Scan mode is the default: a casual request never uploads your files.</bd-p>
193
+ </li>
194
+ <li style="display: flex; align-items: flex-start; gap: 12px;">
195
+ <span aria-hidden="true" style="flex-shrink: 0; width: 20px; height: 20px; border-radius: 999px; background: var(--color-green-600, #16a34a); color: #fff; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 700; margin-top: 2px;">✓</span>
196
+ <bd-p kind="regular" style="margin:0;">Submit is opt-in only: the agent won't upload the full archive unless you explicitly ask.</bd-p>
197
+ </li>
198
+ </ul>
199
+ </div>
200
+
201
+ <div style="background: var(--color-yellow-50, #fffbeb); border: 1px solid var(--color-yellow-300, #fcd34d); border-radius: var(--radius-md); padding: 16px 20px; color: var(--color-yellow-800, #92400e); margin-top: 24px;">
202
+ <bd-p kind="small" style="margin:0;">A scan that returns no findings means no known threat patterns were detected — not that the skill is safe.</bd-p>
203
+ </div>
204
+ </bd-install-options>
205
+ `
206
+ };
207
+
208
+ // ─── Doar header, fara carduri si fara slot ────────────────────────────────
209
+
210
+ export const HeaderOnly = {
211
+ name : "Doar header (fără carduri, fără slot)",
212
+ render: () => html`
213
+ <bd-install-options
214
+ eyebrow="Get it"
215
+ title="Coming soon"
216
+ description="Installation instructions will appear here once the package is published."
217
+ ></bd-install-options>
218
+ `
219
+ };