@repobit/dex-system-design 0.24.7 → 0.24.9

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,20 @@
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.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)
7
+
8
+ ### Bug Fixes
9
+
10
+ * **DEX-1014:** new AI Bitdefender components and anchor nav changes
11
+
12
+
13
+ ## [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)
14
+
15
+ ### Bug Fixes
16
+
17
+ * **DEX-1014:** review component adjustments and AI Bitdefender new component
18
+
19
+
6
20
  ## [0.24.7](https://github.com/bitdefender/dex-core/compare/@repobit/dex-system-design@0.24.6...@repobit/dex-system-design@0.24.7) (2026-09-07)
7
21
 
8
22
  ### Bug Fixes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@repobit/dex-system-design",
3
- "version": "0.24.7",
3
+ "version": "0.24.9",
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.23",
78
- "@repobit/dex-store-elements": "2.0.28",
77
+ "@repobit/dex-store": "1.6.25",
78
+ "@repobit/dex-store-elements": "2.0.30",
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": "fce2975c82772042a3f9164d3bc06df4e99e6898"
95
+ "gitHead": "1f08b0389af256faebe7db77b276d350c9be31ae"
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" },
@@ -430,14 +431,6 @@ function aiGuardianBody() {
430
431
  }
431
432
 
432
433
 
433
-
434
- // ---------------------------------------------------------------------------
435
- // PATCH pentru ai-guardian-demo.stories.js
436
- // 1. Adauga importul: import "./install-options/install-options.js";
437
- // (prompt-panel.js NU mai e nevoie -- totul e integrat in bd-install-options)
438
- // 2. Inlocuieste functia skillScannerBody() cu varianta de mai jos.
439
- // ---------------------------------------------------------------------------
440
-
441
434
  function skillScannerBody() {
442
435
  return html`
443
436
  <bd-header-code
@@ -506,6 +499,7 @@ function skillScannerBody() {
506
499
  <div style="max-width: 1200px; margin: 0 auto;">
507
500
  <bd-anchor-nav
508
501
  aria-label="Skill Scanner sections"
502
+ sticky-top="65px"
509
503
  .items=${[
510
504
  { title: "Install" },
511
505
  { title: "Overview" },
@@ -668,22 +662,242 @@ function skillScannerBody() {
668
662
  </bd-bento-item>
669
663
  </bd-bento-grid>
670
664
  </div>
665
+
666
+ <bd-process-steps
667
+ title="How a scan works"
668
+ description='You ask in plain language, "scan the foo skill for security risks", and the skill does the rest. There is no separate command-line tool to learn.'
669
+ .steps=${[
670
+ {
671
+ title : "1. Resolve",
672
+ description: "The scanner resolves the target (a directory, an installed skill name, a zip, or a URL) into a skill root."
673
+ },
674
+ {
675
+ title : "2. Pack & hash",
676
+ description: "It packs the skill into a normalized archive and computes a skill-level SHA-256, then checks for a cached report before repeating any work."
677
+ },
678
+ {
679
+ title : "3. Collect & enrich",
680
+ description: "It walks, hashes and recurses into every file, then runs all 18 indicator extractors and 71 behavioural patterns over the text content."
681
+ },
682
+ {
683
+ title : "4. Embed",
684
+ description: "It computes semantic embeddings on your machine, unless you disable them."
685
+ },
686
+ {
687
+ title : "5. Submit metadata",
688
+ description: "Hashes, indicators with short matched excerpts, counts, metadata and embeddings go to the backend. In scan mode it sends no full file contents."
689
+ },
690
+ {
691
+ title : "6. Report",
692
+ description: "The backend returns a risk level, summary and findings, rendered as Markdown in your session."
693
+ }
694
+ ]}
695
+ ></bd-process-steps>
696
+
697
+ <div style="padding: 48px 24px 24px;">
698
+ <bd-compatibility-table
699
+ variant="risk"
700
+ title="What you can point it at"
701
+ description="The scanner accepts four target types: a directory, an installed skill name, a zip archive, or a URL."
702
+ .columns=${["Target", "Behaviour"]}
703
+ .rows=${[
704
+ { category: "Directory path", coverage: "Scanned directly as the skill root." },
705
+ { category: "Skill name", coverage: "Resolved via the harness's known skill directories, project- and user-level." },
706
+ { category: ".zip archive", coverage: "Extracted to a temp directory, then scanned." },
707
+ { category: "URL", coverage: "Sent to the backend for scanning (scan mode only)." }
708
+ ]}
709
+ ></bd-compatibility-table>
710
+ </div>
711
+ </section>
712
+
713
+ <section id="anchor-5-section">
714
+ <bd-install-options
715
+ eyebrow="Trust &amp; transparency"
716
+ title="What leaves your machine"
717
+ 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."
718
+ >
719
+ <bd-compatibility-table
720
+ variant="risk"
721
+ .columns=${["Mode", "Transmitted"]}
722
+ .rows=${[
723
+ {
724
+ category: "Scan (default)",
725
+ coverage: "File hashes (SHA-256/MD5), extracted indicators and pattern matches — including short excerpts of matched content (regex fragments ≤100 chars, decoded base64 snippets ≤500 chars) — file metadata, finding counts, SKILL.md frontmatter, dependency metadata, and local embeddings. Full file contents are not transmitted."
726
+ },
727
+ {
728
+ category: "Submit (opt-in)",
729
+ coverage: "The complete skill archive, uploaded for deep analysis. The agent will not do this unless you explicitly ask."
730
+ }
731
+ ]}
732
+ ></bd-compatibility-table>
733
+
734
+ <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;">
735
+ <bd-h as="h3" style="margin: 0 0 16px;">The contract the agent is held to</bd-h>
736
+ <ul style="list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 16px;">
737
+ <li style="display: flex; align-items: flex-start; gap: 12px;">
738
+ <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>
739
+ <bd-p kind="regular" style="margin:0;">Scan mode is the default: a casual request never uploads your files.</bd-p>
740
+ </li>
741
+ <li style="display: flex; align-items: flex-start; gap: 12px;">
742
+ <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>
743
+ <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>
744
+ </li>
745
+ <li style="display: flex; align-items: flex-start; gap: 12px;">
746
+ <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>
747
+ <bd-p kind="regular" style="margin:0;">The agent relays, it doesn't overrule: it presents the backend's report and never substitutes its own verdict.</bd-p>
748
+ </li>
749
+ <li style="display: flex; align-items: flex-start; gap: 12px;">
750
+ <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>
751
+ <bd-p kind="regular" style="margin:0;">Errors are surfaced as they are: on failure you get the specific reason.</bd-p>
752
+ </li>
753
+ <li style="display: flex; align-items: flex-start; gap: 12px;">
754
+ <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>
755
+ <bd-p kind="regular" style="margin:0;">Self-updates are Ed25519-signature-verified against a key pinned in the client, staged with path-traversal-safe extraction, and rolled back automatically on failure.</bd-p>
756
+ </li>
757
+ </ul>
758
+ </div>
759
+
760
+ <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;">
761
+ <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>
762
+ </div>
763
+ </bd-install-options>
671
764
  </section>
672
765
 
673
- <!--
674
- TODO: Privacy / FAQ
675
- (anchor-5-section .. anchor-6-section) nu au fost specificate incă
676
- — se adaugă când vine conținutul.
677
- -->
766
+ <section id="anchor-6-section" class="faq">
767
+ <bd-trust-panel
768
+ eyebrow="Why trust it"
769
+ title="Built by Bitdefender"
770
+ description="Agent Skill Scanner is made by Bitdefender, the company that has protected people and organisations since 2001. The figures and recognitions below describe Bitdefender the company — not the scanner itself."
771
+ link-label="Why millions trust Bitdefender"
772
+ link-href="#"
773
+ .stats=${[
774
+ { value: "50B+", label: "threats blocked every year" },
775
+ { value: "580+", label: "technology patents" },
776
+ { value: "170+", label: "countries protected" },
777
+ { value: "15+", label: "years of AI-driven security" },
778
+ { value: "50%+", label: "of employees work in R&D" },
779
+ { value: "30+", label: "law-enforcement partnerships" }
780
+ ]}
781
+ .cards=${[
782
+ {
783
+ eyebrow : "Brand partnership",
784
+ title : "Trusted by Ferrari as cybersecurity partner",
785
+ description: "A partnership born from a passion for high performance and technological innovation — Bitdefender supports Ferrari with Advanced Threat Intelligence to improve detection and response to cyber threats.",
786
+ linkLabel : "Learn more",
787
+ linkHref : "#"
788
+ },
789
+ {
790
+ eyebrow : "Brand recognition",
791
+ title : "Award-winning security software you can trust. Always.",
792
+ description: "Consistently top-ranked by the world's leading independent testing labs and tech publications.",
793
+ linkLabel : "Learn more",
794
+ linkHref : "#"
795
+ }
796
+ ]}
797
+ >
798
+ <span slot="card-1-logos" style="font-weight:700; color:#006DFF; font-size:18px;">Bitdefender</span>
799
+ <span slot="card-1-logos" style="font-size:12px; color:#374151; font-weight:600;">FERRARI TEAM PARTNER</span>
800
+
801
+ <bd-pc-mag-icon slot="card-2-logos" height="32"></bd-pc-mag-icon>
802
+ <bd-av-comparatives-icon slot="card-2-logos" height="32"></bd-av-comparatives-icon>
803
+ <bd-top-product-icon slot="card-2-logos" height="32"></bd-top-product-icon>
804
+ <bd-best-brands-icon slot="card-2-logos" height="32"></bd-best-brands-icon>
805
+ <bd-laurel-icon slot="card-2-logos" height="32"></bd-laurel-icon>
806
+ </bd-trust-panel>
807
+
808
+ <div style="padding: 24px 24px 48px;">
809
+ <bd-accordion-bg title="Frequently asked questions">
810
+ <bd-accordion-bg-item title="What is Agent Skill Scanner?">
811
+ <bd-p>Agent Skill Scanner is a Bitdefender agent skill that audits other agent skills before you install or trust them. It runs 71 behavioural detectors across 16 threat categories, extracts 18 indicator types from every file, and returns an LLM-generated security report with a risk level, summary, and per-file findings.</bd-p>
812
+ </bd-accordion-bg-item>
813
+
814
+ <bd-accordion-bg-item title="How do I install Agent Skill Scanner?">
815
+ <bd-p>Three ways: run "npx skills add bitdefender/agent-skill-scanner" via skills.sh; clone the GitHub repository and copy the agent-skill-scanner folder into your skills directory; or download the zip archive and unzip it there. It requires Python 3.12 or 3.13 and installs its own dependencies on first use.</bd-p>
816
+ </bd-accordion-bg-item>
817
+
818
+ <bd-accordion-bg-item title="How do I scan a skill?">
819
+ <bd-p>Ask your agent in plain language, for example "scan the foo skill for security risks". There is no separate command to learn: the harness invokes the skill, resolves the target (a directory, an installed skill name, a zip, or a URL), and presents the Markdown report.</bd-p>
820
+ </bd-accordion-bg-item>
821
+
822
+ <bd-accordion-bg-item title="Does it upload my files?">
823
+ <bd-p>Not by default. In scan mode the scanner transmits only file hashes, extracted indicators with short matched excerpts, metadata and local embeddings; full file contents are never uploaded. The opt-in submit mode uploads the complete skill archive for deeper analysis, and the agent will only use it when you explicitly ask.</bd-p>
824
+ </bd-accordion-bg-item>
825
+
826
+ <bd-accordion-bg-item title="Which agents and harnesses does it work with?">
827
+ <bd-p>It is an agent skill for use inside a harness such as Claude Code, Codex, OpenClaw, or opencode. It is not a standalone CLI tool. Any harness that reads a skills directory and lets the agent execute the skill's instructions can run it.</bd-p>
828
+ </bd-accordion-bg-item>
829
+
830
+ <bd-accordion-bg-item title="What does a CLEAN result mean?">
831
+ <bd-p>CLEAN means no known threat patterns were detected, not that the skill is safe. Detection covers 71 behavioural patterns and 18 indicator types, but no scanner proves the absence of malicious intent. Treat the report as strong signal for a trust decision, not a guarantee.</bd-p>
832
+ </bd-accordion-bg-item>
833
+
834
+ <bd-accordion-bg-item title="How much does it cost?">
835
+ <bd-p>Agent Skill Scanner is available at no charge from skills.sh and GitHub. Use is subject to Bitdefender's End User License Agreement and Privacy Policy.</bd-p>
836
+ </bd-accordion-bg-item>
837
+ </bd-accordion-bg>
838
+ </div>
839
+ </section>
678
840
  </div>
679
841
 
842
+ <bd-cta-banner
843
+ title="Scan before you trust"
844
+ description="A skill is code you hand to your agent. One command stands between you and running it blind."
845
+ >
846
+ <bd-button-link
847
+ slot="primary-button"
848
+ label="Install with npx"
849
+ kind="white"
850
+ size="md"
851
+ strong
852
+ >Install with npx</bd-button-link>
853
+
854
+ <bd-button-link
855
+ slot="secondary-button"
856
+ label="View on GitHub"
857
+ kind="outline-white"
858
+ size="md"
859
+ strong
860
+ >View on GitHub</bd-button-link>
861
+ </bd-cta-banner>
862
+
863
+ <section id="anchor-11-section">
864
+ <bd-agent-endpoints
865
+ eyebrow="For AI agents and assistants"
866
+ title="Machine-readable endpoints"
867
+ description="This site is built to be read by AI assistants, browsers, and agents. Structured, curated context is available at:"
868
+ .endpoints=${[
869
+ { label: "/llms.txt", href: "/llms.txt" },
870
+ { label: "/llms-full.txt", href: "/llms-full.txt" },
871
+ { label: "/skill-scanner/index.md", href: "/skill-scanner/index.md" },
872
+ { label: "/sitemap.xml", href: "/sitemap.xml" },
873
+ { label: "/robots.txt", href: "/robots.txt" }
874
+ ]}
875
+ >
876
+ <div slot="rows" style="border-top: 1px solid var(--color-neutral-100); margin-top: 24px; padding-top: 24px;">
877
+ <bd-p kind="regular" style="color: var(--color-neutral-700); margin: 0;">
878
+ By using Bitdefender Agent Skill Scanner, you confirm that you are over 16 years old and you have read and agreed to Bitdefender's
879
+ <bd-link href="#" kind="primary">End User License Agreement</bd-link>
880
+ and
881
+ <bd-link href="#" kind="primary">Privacy Policy</bd-link>.
882
+ </bd-p>
883
+ </div>
884
+
885
+ <div slot="rows" style="border-top: 1px solid var(--color-neutral-100); margin-top: 24px; padding-top: 24px;">
886
+ <bd-p kind="regular" style="color: var(--color-neutral-700); margin: 0;">
887
+ No security product guarantees complete protection. A scan that returns no findings means no known threat patterns were detected — not that the skill is safe. Effectiveness depends on configuration, runtime environment and the skills analysed. Features and availability may vary.
888
+ </bd-p>
889
+ </div>
890
+ </bd-agent-endpoints>
891
+ </section>
892
+
680
893
  <!--
681
- TODO: restul secțiunilor de dedesubtul anchor-nav-ului
682
- (trust panel, cta-banner, agent-endpoints etc.) încă lipsesc,
683
- la fel ca înainte — se adaugă când vine conținutul.
894
+ TODO: daca mai exista sectiuni sub asta pentru Skill Scanner,
895
+ se adauga cand vine continutul.
684
896
  -->
685
897
  `;
686
898
  }
899
+
900
+
687
901
  function sharedFooter() {
688
902
  return html`
689
903
  <bd-footer>
@@ -822,18 +1036,28 @@ switches, in place, between two full "pages" without any real navigation —
822
1036
  clicking a tab intercepts the anchor click and swaps local state instead of
823
1037
  following the href (which has no route in Storybook and would 404).
824
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
+
825
1044
  **AI Guardian** page: \`bd-header-code\`, \`bd-quick-answer\`, sticky \`bd-anchor-nav\`
826
1045
  (9 items) wrapping \`bd-overview\`, \`bd-compatibility-table\` (default variant), the
827
1046
  "Protections" section (\`bd-bento-grid\` + \`bd-comparison-panel\`), \`bd-process-steps\`,
828
- \`bd-compatibility-table\` (\`variant="outcome"\`), two chevron \`bd-accordion-item\`s
829
- revealing \`bd-compatibility-table\` (\`variant="risk"\` / \`variant="light"\`), an FAQ
1047
+ \`bd-compatibility-table\` (\`variant="risk"\` / \`variant="light"\`), an FAQ
830
1048
  section (\`bd-accordion-bg\`, 9 questions), \`bd-cta-banner\`, \`bd-agent-endpoints\`,
831
1049
  and \`bd-trust-panel\`.
832
1050
 
833
1051
  **Skill Scanner** page: same top nav (tab now active), a dedicated
834
- \`bd-header-code\` for Agent Skill Scanner. Only the header is fully specified for
835
- this page today — the rest of its sections are a placeholder until content is
836
- provided.
1052
+ \`bd-header-code\`, \`bd-quick-answer\`, and a sticky \`bd-anchor-nav\` (6 items:
1053
+ Install, Overview, What it detects, How it works, Privacy, FAQ) wrapping
1054
+ \`bd-install-options\` (cards + "Then just ask" prompt panel), \`bd-overview\`,
1055
+ a \`bd-compatibility-table\` inside a chevron accordion, a \`bd-bento-grid\` +
1056
+ \`bd-process-steps\` + another \`bd-compatibility-table\` for "How it works",
1057
+ another \`bd-install-options\` (generic-slot variant: table + checklist + note)
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.
837
1061
 
838
1062
  Both pages share the same \`bd-footer\` at the bottom.
839
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
+ };
@@ -43,6 +43,12 @@ export default css`
43
43
  margin: 0 auto var(--spacing-40);
44
44
  }
45
45
 
46
+ /* Slot generic pentru orice continut in afara grid-ului fix de carduri */
47
+ .bd-installoptions-body {
48
+ max-width: var(--layout-ensemble-max-width, 1200px);
49
+ margin: 0 auto;
50
+ }
51
+
46
52
  .bd-installoptions-worksin-label {
47
53
  color: var(--color-neutral-500);
48
54
  font-family: var(--typography-fontFamily-mono, monospace);
@@ -171,10 +171,25 @@ class BdInstallOptions extends LitElement {
171
171
 
172
172
  ${this._renderWorksIn()}
173
173
 
174
- <div class="bd-installoptions-grid">
175
- ${this.cards.map((card) => this._renderCard(card))}
174
+ <!--
175
+ Slot generic (fara nume) -- pentru orice continut care nu se
176
+ potriveste cu grid-ul fix de carduri (ex: un tabel, o casuta
177
+ checklist, o nota de avertizare). Cardurile ramase implicite
178
+ (this.cards) tot se randeaza mai jos daca sunt pasate; cele doua
179
+ pot coexista sau poti folosi doar unul din ele.
180
+ -->
181
+ <div class="bd-installoptions-body">
182
+ <slot></slot>
176
183
  </div>
177
184
 
185
+ ${this.cards.length
186
+ ? html`
187
+ <div class="bd-installoptions-grid">
188
+ ${this.cards.map((card) => this._renderCard(card))}
189
+ </div>
190
+ `
191
+ : nothing}
192
+
178
193
  ${this._renderPromptPanel()}
179
194
  </div>
180
195
  `;
@@ -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
+ };
@@ -5,8 +5,9 @@ export default css`
5
5
  display: block;
6
6
  width: 100%;
7
7
  box-sizing: border-box;
8
- max-width: 1290px;
9
-
8
+ max-width: var(--layout-ensemble-max-width, 1290px);
9
+ margin: 0 auto;
10
+ padding-inline: var(--layout-ensemble-inline-padding, var(--spacing-24));
10
11
  }
11
12
 
12
13
  .bd-reviewgrid-title {
@@ -6,8 +6,6 @@ export default css`
6
6
  height: 100%;
7
7
  font-family: var(--typography-fontFamily-sans);
8
8
  box-sizing: border-box;
9
- max-width: 1290px;
10
-
11
9
  }
12
10
 
13
11
  .bd-review-card {