@repobit/dex-system-design 0.24.5 → 0.24.7
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 +15 -0
- package/package.json +4 -4
- package/src/components/ai-guardian-demo.stories.js +187 -8
- package/src/components/bento-grid/bento-item.css.js +26 -0
- package/src/components/bento-grid/bento-item.js +4 -1
- package/src/components/icons/quote-icon.js +28 -0
- package/src/components/install-options/install-options.css.js +276 -0
- package/src/components/install-options/install-options.js +184 -0
- package/src/components/paragraph/paragraph.css.js +1 -5
- package/src/components/review/review-grid.css.js +36 -0
- package/src/components/review/review-grid.js +63 -0
- package/src/components/review/review.css.js +52 -0
- package/src/components/review/review.js +58 -0
- package/src/components/review/review.stories.js +228 -0
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.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
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **DEX-1014:** new review component adjustments
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## [0.24.6](https://github.com/bitdefender/dex-core/compare/@repobit/dex-system-design@0.24.5...@repobit/dex-system-design@0.24.6) (2026-09-04)
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* **DEX-1014:** new review component
|
|
18
|
+
* **DEX-1014:** new review component
|
|
19
|
+
|
|
20
|
+
|
|
6
21
|
## [0.24.5](https://github.com/bitdefender/dex-core/compare/@repobit/dex-system-design@0.24.4...@repobit/dex-system-design@0.24.5) (2026-09-01)
|
|
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.
|
|
3
|
+
"version": "0.24.7",
|
|
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.
|
|
78
|
-
"@repobit/dex-store-elements": "2.0.
|
|
77
|
+
"@repobit/dex-store": "1.6.23",
|
|
78
|
+
"@repobit/dex-store-elements": "2.0.28",
|
|
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": "
|
|
95
|
+
"gitHead": "fce2975c82772042a3f9164d3bc06df4e99e6898"
|
|
96
96
|
}
|
|
@@ -24,6 +24,7 @@ import "./icons/laurel-icon.js";
|
|
|
24
24
|
import "./icons/machine-learning-icon.js";
|
|
25
25
|
import "./icons/pc-mag-icon.js";
|
|
26
26
|
import "./icons/top-product-icon.js";
|
|
27
|
+
import "./install-options/install-options.js";
|
|
27
28
|
import "./overview/overview.js";
|
|
28
29
|
import "./page-switcher/page-switcher.js";
|
|
29
30
|
import "./process-steps/process-steps.js";
|
|
@@ -31,10 +32,6 @@ import "./quick-answer/quick-answer.js";
|
|
|
31
32
|
import "./terminal/terminal-message.js";
|
|
32
33
|
import "./trust-panel/trust-panel.js";
|
|
33
34
|
|
|
34
|
-
// ---------------------------------------------------------------------------
|
|
35
|
-
// Conținutul (fără page-switcher, fără footer — alea sunt comune,
|
|
36
|
-
// randate o singură dată de wrapper-ul de mai jos) pentru fiecare "pagină".
|
|
37
|
-
// ---------------------------------------------------------------------------
|
|
38
35
|
|
|
39
36
|
function aiGuardianBody() {
|
|
40
37
|
return html`
|
|
@@ -432,6 +429,15 @@ function aiGuardianBody() {
|
|
|
432
429
|
`;
|
|
433
430
|
}
|
|
434
431
|
|
|
432
|
+
|
|
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
|
+
|
|
435
441
|
function skillScannerBody() {
|
|
436
442
|
return html`
|
|
437
443
|
<bd-header-code
|
|
@@ -497,14 +503,187 @@ function skillScannerBody() {
|
|
|
497
503
|
</div>
|
|
498
504
|
</section>
|
|
499
505
|
|
|
506
|
+
<div style="max-width: 1200px; margin: 0 auto;">
|
|
507
|
+
<bd-anchor-nav
|
|
508
|
+
aria-label="Skill Scanner sections"
|
|
509
|
+
.items=${[
|
|
510
|
+
{ title: "Install" },
|
|
511
|
+
{ title: "Overview" },
|
|
512
|
+
{ title: "What it detects" },
|
|
513
|
+
{ title: "How it works" },
|
|
514
|
+
{ title: "Privacy" },
|
|
515
|
+
{ title: "FAQ" }
|
|
516
|
+
]}
|
|
517
|
+
style="margin-bottom: 0;"
|
|
518
|
+
>
|
|
519
|
+
</bd-anchor-nav>
|
|
520
|
+
|
|
521
|
+
<section id="anchor-1-section" style="margin-top: 0; padding-top: 0;">
|
|
522
|
+
<bd-install-options
|
|
523
|
+
eyebrow="Get it"
|
|
524
|
+
title="Three ways to install"
|
|
525
|
+
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."
|
|
526
|
+
.worksIn=${[
|
|
527
|
+
{ label: "Claude Code", initial: "✻", color: "#d97706" },
|
|
528
|
+
{ label: "Codex", initial: "◒", color: "#111827" },
|
|
529
|
+
{ label: "OpenClaw", initial: "▣", color: "#dc2626" },
|
|
530
|
+
{ label: "opencode", initial: "▪", color: "#111827" },
|
|
531
|
+
{ label: "Hermes Agent", initial: "H", color: "#111827" }
|
|
532
|
+
]}
|
|
533
|
+
.cards=${[
|
|
534
|
+
{
|
|
535
|
+
title : "With npx, from skills.sh",
|
|
536
|
+
badge : "Recommended",
|
|
537
|
+
description: "One command via the skills.sh installer. Works for any supported harness.",
|
|
538
|
+
code : "npx skills add bitdefender/agent-skill-scanner",
|
|
539
|
+
linkLabel : "View on skills.sh",
|
|
540
|
+
linkHref : "#"
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
title : "From GitHub",
|
|
544
|
+
description: "Clone the repository and copy the inner agent-skill-scanner/ folder into your skills directory.",
|
|
545
|
+
code : "git clone https://github.com/bitdefender/agent-skill-scanner.git\ncp -r agent-skill-scanner/agent-skill-scanner ~/.claude/skills/",
|
|
546
|
+
linkLabel : "View the repository",
|
|
547
|
+
linkHref : "#"
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
title : "As a zip archive",
|
|
551
|
+
description: "Download the archive, unzip it, and move the inner agent-skill-scanner/ folder into your skills directory: ~/.claude/skills/, ~/.openclaw/skills/, or your project's skills/.",
|
|
552
|
+
linkLabel : "Download the zip",
|
|
553
|
+
linkHref : "#"
|
|
554
|
+
}
|
|
555
|
+
]}
|
|
556
|
+
prompt-panel
|
|
557
|
+
prompt-label="Prompt for your agent"
|
|
558
|
+
>
|
|
559
|
+
<span slot="prompt-title">Then just ask</span>
|
|
560
|
+
<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. Replace the placeholder with a skill name, a directory, a zip, or a URL:</span>
|
|
561
|
+
<span slot="prompt">Scan the <skill name or path> skill for security risks and show me the report.</span>
|
|
562
|
+
<bd-p slot="prompt-note" kind="small">Requires Python 3.12 or 3.13 (3.13 recommended). Dependencies install on first use, and optional pieces degrade gracefully: .7z/.rar support, embeddings and signed-update verification each activate only when needed. Scanning .rar targets additionally needs a system unrar or unar binary on your PATH.</bd-p>
|
|
563
|
+
<span slot="prompt-footer">To uninstall, delete the agent-skill-scanner folder from your skills directory.</span>
|
|
564
|
+
</bd-install-options>
|
|
565
|
+
</section>
|
|
566
|
+
|
|
567
|
+
<section id="anchor-2-section">
|
|
568
|
+
<bd-overview
|
|
569
|
+
title="What is Agent Skill Scanner?"
|
|
570
|
+
description="Agent skills are executable code plus natural-language instructions that run with your agent's privileges. A single malicious or careless skill can exfiltrate secrets, open a reverse shell, mine cryptocurrency, phone home, or hide a delayed trigger. Agent Skill Scanner audits a skill before you trust it: it walks every file, extracts security indicators, runs behavioural detectors, and returns an LLM-generated security report with a risk level, a summary, and per-file findings."
|
|
571
|
+
.facts=${[
|
|
572
|
+
{ label: "TYPE", value: "Agent skill, runs inside your harness" },
|
|
573
|
+
{ label: "WORKS IN", value: "Claude Code, Codex, OpenClaw, opencode, and similar" },
|
|
574
|
+
{ label: "REQUIRES", value: "Python 3.12 or 3.13" },
|
|
575
|
+
{ label: "DETECTION", value: "71 behavioural detectors, 16 threat categories" },
|
|
576
|
+
{ label: "INDICATORS", value: "18 indicator types extracted per text file" },
|
|
577
|
+
{ label: "GET IT FROM", value: "skills.sh · GitHub · zip archive" }
|
|
578
|
+
]}
|
|
579
|
+
></bd-overview>
|
|
580
|
+
</section>
|
|
581
|
+
|
|
582
|
+
<section id="anchor-3-section">
|
|
583
|
+
<div style="padding: 24px 24px 48px;">
|
|
584
|
+
<bd-p kind="normal" style="color: var(--color-blue-500); font-weight: 600; margin: 0 0 8px;">Detection coverage</bd-p>
|
|
585
|
+
<bd-h as="h2" font-weight="700" style="margin: 0 0 8px;">What it detects</bd-h>
|
|
586
|
+
<bd-p kind="regular" style="color: var(--color-neutral-700); margin: 0 0 24px;">
|
|
587
|
+
Agent Skill Scanner runs 71 behavioural detectors across 16 threat categories, including code execution, obfuscation, reverse shells, crypto-mining, persistence, time bombs and stealth natural-language directives. It also extracts 18 indicator types from every text file: URLs, IPs, base64 blobs, dangerous shell commands, secret env-var names and invisible Unicode characters among them.
|
|
588
|
+
</bd-p>
|
|
589
|
+
|
|
590
|
+
<!--
|
|
591
|
+
.columns suprascrie antetele implicite ale variantei "risk"
|
|
592
|
+
("Risk category" / "AI Guardian coverage"), care nu au sens
|
|
593
|
+
pe pagina Skill Scanner -- prop deja existent in componenta,
|
|
594
|
+
nicio modificare pe compatibility-table.js nu a fost necesara.
|
|
595
|
+
-->
|
|
596
|
+
<bd-accordion-item chevron title="Show all 16 threat categories">
|
|
597
|
+
<bd-compatibility-table
|
|
598
|
+
variant="risk"
|
|
599
|
+
.columns=${["Threat category", "Examples"]}
|
|
600
|
+
.rows=${[
|
|
601
|
+
{ category: "Code execution", coverage: "eval/exec, dynamic __import__, JS Function(), PowerShell Invoke-Expression" },
|
|
602
|
+
{ category: "Subprocess usage", coverage: "shell=True, os.system, os.popen" },
|
|
603
|
+
{ category: "Obfuscation", coverage: "base64-decode-then-exec, hex decoding, chr() construction, known encoded payloads" },
|
|
604
|
+
{ category: "Network", coverage: "requests/httpx/urllib, raw sockets, fetch(), connections to raw IPs" },
|
|
605
|
+
{ category: "File operations", coverage: "writes, deletions, bulk shutil ops, destructive rm -rf" },
|
|
606
|
+
{ category: "Crypto-mining", coverage: "xmrig, stratum, mining-pool indicators" },
|
|
607
|
+
{ category: "Reverse shells", coverage: "/dev/tcp, nc -e, bash -i >&, pty.spawn" },
|
|
608
|
+
{ category: "Download-and-execute", coverage: "curl … | sh, pip/npm install from URL, npx without approval" },
|
|
609
|
+
{ category: "Credential access", coverage: "bulk environment-variable harvesting" },
|
|
610
|
+
{ category: "System persistence", coverage: "cron, systemd, launchd, Windows registry run-keys" },
|
|
611
|
+
{ category: "Privilege escalation", coverage: "sudo, dangerous chmod, chown root, writes to system paths" },
|
|
612
|
+
{ category: "Path traversal", coverage: "../../.., URL-encoded traversal" },
|
|
613
|
+
{ category: "Telemetry", coverage: "analytics and tracking SDKs, sendBeacon, phone-home" },
|
|
614
|
+
{ category: "Symlink attacks", coverage: "ln -s, os.symlink, Node fs.symlink" },
|
|
615
|
+
{ category: "Time bombs", coverage: "date and timestamp-comparison triggers, delayed execution" },
|
|
616
|
+
{ category: "Stealth directives", coverage: "natural-language exfiltration, covert-action and hidden-memory instructions" }
|
|
617
|
+
]}
|
|
618
|
+
></bd-compatibility-table>
|
|
619
|
+
</bd-accordion-item>
|
|
620
|
+
|
|
621
|
+
<bd-p kind="regular" style="color: var(--color-neutral-700); margin-top: 16px;">
|
|
622
|
+
18 indicator types are extracted from every text file: URLs, IPv4 addresses, data/javascript: URIs, emails, base64 blobs (decoded and inspected), percent-encoded URLs, environment-variable references, 25 dangerous shell commands, sensitive file paths, suspicious imports, private/loopback IPs, crypto-wallet addresses, known secret env-var names, paste-service domains, DNS-exfiltration patterns, and invisible Unicode characters.
|
|
623
|
+
</bd-p>
|
|
624
|
+
<bd-p kind="regular" style="color: var(--color-neutral-700); margin-top: 8px;">
|
|
625
|
+
Structural checks cover hidden dotfiles, package install-hooks (preinstall/postinstall, setup.py cmdclass), per-file SHA-256 and MD5 hashing, and a normalized skill-level hash used for report caching.
|
|
626
|
+
</bd-p>
|
|
627
|
+
</div>
|
|
628
|
+
</section>
|
|
629
|
+
|
|
630
|
+
<section id="anchor-4-section">
|
|
631
|
+
<div style="padding: 24px 24px 48px;">
|
|
632
|
+
<bd-bento-grid
|
|
633
|
+
title="How it works"
|
|
634
|
+
>
|
|
635
|
+
<!--
|
|
636
|
+
Iconițele exacte din poză (stive/layers, scut+lupă, eprubetă,
|
|
637
|
+
ceas, lacăt deschis, lupă+insectă) nu există în setul curent de
|
|
638
|
+
iconițe -- am ales cele mai apropiate semantic ca placeholder.
|
|
639
|
+
-->
|
|
640
|
+
<bd-bento-item title="Layered analysis" tone="tint">
|
|
641
|
+
<bd-analysis-icon slot="icon" width="32" height="32" color="#006DFF"></bd-analysis-icon>
|
|
642
|
+
<bd-p>Local indicator extraction feeds a server-side LLM that produces the report: a risk level, a plain-language summary, and per-file findings. Enough to decide, without wading through raw indicators.</bd-p>
|
|
643
|
+
</bd-bento-item>
|
|
644
|
+
|
|
645
|
+
<bd-bento-item title="Deep archive inspection">
|
|
646
|
+
<bd-guide-icon slot="icon" width="32" height="32" color="#006DFF"></bd-guide-icon>
|
|
647
|
+
<bd-p>The scanner recursively unpacks .zip, .tar.gz, .tar.bz2, .tar, .7z and .rar up to 10 levels deep, with limits of 500 MB uncompressed and 10,000 entries per archive. It skips symlinks rather than following them.</bd-p>
|
|
648
|
+
</bd-bento-item>
|
|
649
|
+
|
|
650
|
+
<bd-bento-item title="Prose-aware detection">
|
|
651
|
+
<bd-machine-learning-icon slot="icon" width="32" height="32" color="#006DFF"></bd-machine-learning-icon>
|
|
652
|
+
<bd-p>Natural-language threats hidden in SKILL.md instructions (stealth exfiltration, covert actions, keyword and counter triggers) are first-class detections.</bd-p>
|
|
653
|
+
</bd-bento-item>
|
|
654
|
+
|
|
655
|
+
<bd-bento-item title="Semantic embeddings">
|
|
656
|
+
<bd-globe-icon slot="icon" width="32" height="32" color="#006DFF"></bd-globe-icon>
|
|
657
|
+
<bd-p>The scanner computes local sentence embeddings (all-MiniLM-L6-v2, ONNX) to support the analysis. They run on your machine, and you can disable them.</bd-p>
|
|
658
|
+
</bd-bento-item>
|
|
659
|
+
|
|
660
|
+
<bd-bento-item title="Privacy-respecting default">
|
|
661
|
+
<bd-individual-icon slot="icon" width="32" height="32" color="#006DFF"></bd-individual-icon>
|
|
662
|
+
<bd-p>In scan mode the scanner transmits only hashes, extracted indicators and metadata off the machine.</bd-p>
|
|
663
|
+
</bd-bento-item>
|
|
664
|
+
|
|
665
|
+
<bd-bento-item title="Flexible targets" tone="dark">
|
|
666
|
+
<bd-family-icon slot="icon" width="32" height="32" color="#60a5fa"></bd-family-icon>
|
|
667
|
+
<bd-p>Scan a directory, an installed skill by name, a .zip archive, or a URL. The skill resolves whatever you refer to in plain language.</bd-p>
|
|
668
|
+
</bd-bento-item>
|
|
669
|
+
</bd-bento-grid>
|
|
670
|
+
</div>
|
|
671
|
+
</section>
|
|
672
|
+
|
|
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
|
+
-->
|
|
678
|
+
</div>
|
|
679
|
+
|
|
500
680
|
<!--
|
|
501
|
-
TODO: restul secțiunilor
|
|
502
|
-
(
|
|
503
|
-
|
|
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.
|
|
504
684
|
-->
|
|
505
685
|
`;
|
|
506
686
|
}
|
|
507
|
-
|
|
508
687
|
function sharedFooter() {
|
|
509
688
|
return html`
|
|
510
689
|
<bd-footer>
|
|
@@ -71,6 +71,32 @@ box-shadow: 0px 14px 40px #00000012;
|
|
|
71
71
|
width: 100%;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
/* === TONE VARIANTS === */
|
|
75
|
+
|
|
76
|
+
:host([tone="tint"]) {
|
|
77
|
+
--background-card-grey: var(--color-blue-50);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
:host([tone="tint"]) .bd-bentoitem-box {
|
|
81
|
+
border-color: var(--color-blue-50);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
:host([tone="dark"]) {
|
|
85
|
+
--background-card-grey: var(--color-neutral-900);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
:host([tone="dark"]) .bd-bentoitem-box {
|
|
89
|
+
border-color: var(--color-neutral-900);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
:host([tone="dark"]) .bd-bentoitem-title-wrap bd-h {
|
|
93
|
+
color: var(--color-neutral-0);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
:host([tone="dark"]) .bd-bentoitem-body {
|
|
97
|
+
--bd-paragraph-color: var(--color-neutral-0);
|
|
98
|
+
}
|
|
99
|
+
|
|
74
100
|
@media (max-width: 599px) {
|
|
75
101
|
.bd-bentoitem-box {
|
|
76
102
|
padding: var(--spacing-24);
|
|
@@ -10,7 +10,9 @@ class BentoItem extends LitElement {
|
|
|
10
10
|
body : { type: String },
|
|
11
11
|
icon : { type: String },
|
|
12
12
|
priority : { type: String, reflect: true },
|
|
13
|
-
orientation: { type: String, attribute: "orientation" }
|
|
13
|
+
orientation: { type: String, attribute: "orientation" },
|
|
14
|
+
// "default" (neutral-25 fill) | "tint" (light blue fill) | "dark" (dark fill, light text)
|
|
15
|
+
tone : { type: String, reflect: true }
|
|
14
16
|
};
|
|
15
17
|
|
|
16
18
|
static styles = [tokens, bentoItemCSS];
|
|
@@ -22,6 +24,7 @@ class BentoItem extends LitElement {
|
|
|
22
24
|
this.icon = "";
|
|
23
25
|
this.priority = "standard";
|
|
24
26
|
this.orientation = "auto";
|
|
27
|
+
this.tone = "default";
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
render() {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// icons/quote-icon.js
|
|
2
|
+
import { LitElement, css, html } from "lit";
|
|
3
|
+
|
|
4
|
+
class QuoteIcon extends LitElement {
|
|
5
|
+
|
|
6
|
+
static properties = {
|
|
7
|
+
width : { type: Number },
|
|
8
|
+
height: { type: Number },
|
|
9
|
+
color : { type: String }
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
constructor() {
|
|
13
|
+
super();
|
|
14
|
+
this.width = 51;
|
|
15
|
+
this.height = 52;
|
|
16
|
+
this.color = "#2E6BF6";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static styles = css`:host { display: inline-flex; }`;
|
|
20
|
+
render() {
|
|
21
|
+
return html`
|
|
22
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="${this.width}" height="${this.height}" viewBox="0 0 51 52" aria-hidden="true">
|
|
23
|
+
<text fill="${this.color}" font-size="128" font-family="Helvetica"><tspan x="0" y="99">“</tspan></text>
|
|
24
|
+
</svg>
|
|
25
|
+
`;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
customElements.define("bd-quote-icon", QuoteIcon);
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { css } from "lit";
|
|
2
|
+
|
|
3
|
+
export default css`
|
|
4
|
+
:host {
|
|
5
|
+
display: block;
|
|
6
|
+
font-family: var(--typography-fontFamily-sans);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.bd-installoptions-container {
|
|
10
|
+
background: var(--color-blue-50);
|
|
11
|
+
padding: var(--spacing-64) var(--layout-ensemble-inline-padding, var(--spacing-24));
|
|
12
|
+
box-sizing: border-box;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.bd-installoptions-eyebrow {
|
|
16
|
+
color: var(--color-blue-500);
|
|
17
|
+
font-weight: var(--typography-fontWeight-semibold);
|
|
18
|
+
margin: 0 0 var(--spacing-16);
|
|
19
|
+
max-width: var(--layout-ensemble-max-width, 1200px);
|
|
20
|
+
margin-inline: auto;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.bd-installoptions-title {
|
|
24
|
+
margin: 0 0 var(--spacing-16);
|
|
25
|
+
color: var(--color-neutral-1000);
|
|
26
|
+
line-height: var(--typography-lineHeight-tight);
|
|
27
|
+
max-width: var(--layout-ensemble-max-width, 1200px);
|
|
28
|
+
margin-inline: auto;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.bd-installoptions-description {
|
|
32
|
+
color: var(--color-neutral-700);
|
|
33
|
+
max-width: 640px;
|
|
34
|
+
margin: 0 auto var(--spacing-32);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.bd-installoptions-worksin {
|
|
38
|
+
display: flex;
|
|
39
|
+
align-items: center;
|
|
40
|
+
flex-wrap: wrap;
|
|
41
|
+
gap: var(--spacing-16);
|
|
42
|
+
max-width: var(--layout-ensemble-max-width, 1200px);
|
|
43
|
+
margin: 0 auto var(--spacing-40);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.bd-installoptions-worksin-label {
|
|
47
|
+
color: var(--color-neutral-500);
|
|
48
|
+
font-family: var(--typography-fontFamily-mono, monospace);
|
|
49
|
+
font-size: var(--typography-fontSize-xs);
|
|
50
|
+
text-transform: uppercase;
|
|
51
|
+
letter-spacing: 0.06em;
|
|
52
|
+
flex-shrink: 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.bd-installoptions-pills {
|
|
56
|
+
display: flex;
|
|
57
|
+
flex-wrap: wrap;
|
|
58
|
+
gap: var(--spacing-12);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.bd-installoptions-pill {
|
|
62
|
+
display: inline-flex;
|
|
63
|
+
align-items: center;
|
|
64
|
+
gap: var(--spacing-8);
|
|
65
|
+
padding: var(--spacing-8) var(--spacing-16);
|
|
66
|
+
background: var(--color-neutral-0);
|
|
67
|
+
border: var(--border-width-1) solid var(--color-neutral-200);
|
|
68
|
+
border-radius: var(--radius-full);
|
|
69
|
+
font-size: var(--typography-fontSize-sm);
|
|
70
|
+
font-weight: var(--typography-fontWeight-semibold);
|
|
71
|
+
color: var(--color-neutral-900);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/*
|
|
75
|
+
Placeholder brand marks: these render an initial letter on a solid
|
|
76
|
+
background because the real Claude Code / Codex / OpenClaw / opencode /
|
|
77
|
+
Hermes Agent logos are not in this icon set. Swap the initial+color pairs
|
|
78
|
+
for real <img>/<svg> marks as soon as brand assets are available.
|
|
79
|
+
*/
|
|
80
|
+
.bd-installoptions-pill-icon {
|
|
81
|
+
display: inline-flex;
|
|
82
|
+
align-items: center;
|
|
83
|
+
justify-content: center;
|
|
84
|
+
width: 20px;
|
|
85
|
+
height: 20px;
|
|
86
|
+
border-radius: var(--radius-md);
|
|
87
|
+
color: var(--color-neutral-0);
|
|
88
|
+
font-size: var(--typography-fontSize-xxs);
|
|
89
|
+
font-weight: var(--typography-fontWeight-bold);
|
|
90
|
+
flex-shrink: 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.bd-installoptions-grid {
|
|
94
|
+
display: grid;
|
|
95
|
+
grid-template-columns: repeat(3, 1fr);
|
|
96
|
+
gap: var(--spacing-24);
|
|
97
|
+
max-width: var(--layout-ensemble-max-width, 1200px);
|
|
98
|
+
margin-inline: auto;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.bd-installoptions-card {
|
|
102
|
+
background: var(--color-neutral-0);
|
|
103
|
+
border: var(--border-width-1) solid var(--color-neutral-100);
|
|
104
|
+
border-radius: var(--radius-xl);
|
|
105
|
+
padding: var(--spacing-24);
|
|
106
|
+
box-sizing: border-box;
|
|
107
|
+
display: flex;
|
|
108
|
+
flex-direction: column;
|
|
109
|
+
gap: var(--spacing-12);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.bd-installoptions-card-head {
|
|
113
|
+
display: flex;
|
|
114
|
+
align-items: flex-start;
|
|
115
|
+
justify-content: space-between;
|
|
116
|
+
gap: var(--spacing-12);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.bd-installoptions-card-title {
|
|
120
|
+
margin: 0;
|
|
121
|
+
color: var(--color-neutral-900);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.bd-installoptions-badge {
|
|
125
|
+
flex-shrink: 0;
|
|
126
|
+
padding: var(--spacing-4) var(--spacing-8);
|
|
127
|
+
background: var(--color-blue-50);
|
|
128
|
+
color: var(--color-blue-700);
|
|
129
|
+
border-radius: var(--radius-md);
|
|
130
|
+
font-size: var(--typography-fontSize-xxs);
|
|
131
|
+
font-weight: var(--typography-fontWeight-bold);
|
|
132
|
+
text-transform: uppercase;
|
|
133
|
+
letter-spacing: 0.04em;
|
|
134
|
+
white-space: nowrap;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.bd-installoptions-card-description {
|
|
138
|
+
color: var(--color-neutral-600);
|
|
139
|
+
margin: 0;
|
|
140
|
+
flex-grow: 1;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.bd-installoptions-code {
|
|
144
|
+
position: relative;
|
|
145
|
+
background: var(--color-neutral-900);
|
|
146
|
+
border-radius: var(--radius-md);
|
|
147
|
+
padding: var(--spacing-16);
|
|
148
|
+
box-sizing: border-box;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.bd-installoptions-code-text {
|
|
152
|
+
margin: 0;
|
|
153
|
+
color: var(--color-neutral-0);
|
|
154
|
+
font-family: var(--typography-fontFamily-mono, monospace);
|
|
155
|
+
font-size: var(--typography-fontSize-sm);
|
|
156
|
+
white-space: pre-wrap;
|
|
157
|
+
word-break: break-word;
|
|
158
|
+
padding-right: var(--spacing-56);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.bd-installoptions-copy {
|
|
162
|
+
position: absolute;
|
|
163
|
+
top: var(--spacing-12);
|
|
164
|
+
right: var(--spacing-12);
|
|
165
|
+
padding: var(--spacing-4) var(--spacing-10);
|
|
166
|
+
background: rgba(255, 255, 255, 0.12);
|
|
167
|
+
color: var(--color-neutral-0);
|
|
168
|
+
border: none;
|
|
169
|
+
border-radius: var(--radius-sm);
|
|
170
|
+
font-family: var(--typography-fontFamily-sans);
|
|
171
|
+
font-size: var(--typography-fontSize-xs);
|
|
172
|
+
font-weight: var(--typography-fontWeight-semibold);
|
|
173
|
+
cursor: pointer;
|
|
174
|
+
transition: background var(--transition-duration-fast) var(--transition-easing-smooth);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.bd-installoptions-copy:hover {
|
|
178
|
+
background: rgba(255, 255, 255, 0.2);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.bd-installoptions-copy:focus-visible {
|
|
182
|
+
outline: none;
|
|
183
|
+
box-shadow: var(--focus-outline-primary);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.bd-installoptions-card-link {
|
|
187
|
+
font-weight: var(--typography-fontWeight-semibold);
|
|
188
|
+
margin-top: auto;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/* === "Then just ask" prompt panel (prompt-panel attribute) === */
|
|
192
|
+
|
|
193
|
+
.bd-installoptions-promptpanel {
|
|
194
|
+
max-width: var(--layout-ensemble-max-width, 1200px);
|
|
195
|
+
margin: var(--spacing-64) auto 0;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.bd-installoptions-prompt-title {
|
|
199
|
+
margin: 0 0 var(--spacing-16);
|
|
200
|
+
color: var(--color-neutral-1000);
|
|
201
|
+
line-height: var(--typography-lineHeight-tight);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.bd-installoptions-prompt-description {
|
|
205
|
+
color: var(--color-neutral-700);
|
|
206
|
+
max-width: 640px;
|
|
207
|
+
margin: 0 0 var(--spacing-24);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.bd-installoptions-prompt-label {
|
|
211
|
+
display: block;
|
|
212
|
+
color: var(--color-neutral-500);
|
|
213
|
+
font-family: var(--typography-fontFamily-mono, monospace);
|
|
214
|
+
font-size: var(--typography-fontSize-xs);
|
|
215
|
+
text-transform: uppercase;
|
|
216
|
+
letter-spacing: 0.06em;
|
|
217
|
+
margin-bottom: var(--spacing-8);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.bd-installoptions-prompt-code {
|
|
221
|
+
position: relative;
|
|
222
|
+
background: var(--color-neutral-900);
|
|
223
|
+
border: var(--border-width-2) solid var(--color-blue-500);
|
|
224
|
+
border-radius: var(--radius-md);
|
|
225
|
+
padding: var(--spacing-16);
|
|
226
|
+
padding-right: var(--spacing-64);
|
|
227
|
+
box-sizing: border-box;
|
|
228
|
+
margin-bottom: var(--spacing-24);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.bd-installoptions-prompt-code-text {
|
|
232
|
+
color: var(--color-neutral-0);
|
|
233
|
+
font-family: var(--typography-fontFamily-mono, monospace);
|
|
234
|
+
font-size: var(--typography-fontSize-sm);
|
|
235
|
+
line-height: var(--typography-lineHeight-normal);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.bd-installoptions-prompt-code-text ::slotted(*) {
|
|
239
|
+
margin: 0;
|
|
240
|
+
font: inherit;
|
|
241
|
+
color: inherit;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.bd-installoptions-copy--prompt {
|
|
245
|
+
top: var(--spacing-12);
|
|
246
|
+
right: var(--spacing-12);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.bd-installoptions-prompt-note {
|
|
250
|
+
background: var(--color-yellow-50, #fffbeb);
|
|
251
|
+
border: var(--border-width-1) solid var(--color-yellow-300, #fcd34d);
|
|
252
|
+
border-radius: var(--radius-md);
|
|
253
|
+
padding: var(--spacing-16) var(--spacing-20);
|
|
254
|
+
color: var(--color-yellow-800, #92400e);
|
|
255
|
+
font-size: var(--typography-fontSize-sm);
|
|
256
|
+
line-height: var(--typography-lineHeight-normal);
|
|
257
|
+
margin-bottom: var(--spacing-24);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.bd-installoptions-prompt-note ::slotted(*) {
|
|
261
|
+
margin: 0;
|
|
262
|
+
font: inherit;
|
|
263
|
+
color: inherit;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.bd-installoptions-prompt-footer {
|
|
267
|
+
color: var(--color-neutral-600);
|
|
268
|
+
margin: 0;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
@media (max-width: 899px) {
|
|
272
|
+
.bd-installoptions-grid {
|
|
273
|
+
grid-template-columns: 1fr;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
`;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { LitElement, html, nothing } from "lit";
|
|
2
|
+
import { tokens } from "../../tokens/tokens.js";
|
|
3
|
+
import "../heading/heading.js";
|
|
4
|
+
import "../link/link.js";
|
|
5
|
+
import "../paragraph/paragraph.js";
|
|
6
|
+
import installOptionsCSS from "./install-options.css.js";
|
|
7
|
+
|
|
8
|
+
class BdInstallOptions extends LitElement {
|
|
9
|
+
static properties = {
|
|
10
|
+
eyebrow : { type: String },
|
|
11
|
+
title : { type: String },
|
|
12
|
+
description: { type: String },
|
|
13
|
+
worksIn : { type: Array }, // [{ label, initial, color }]
|
|
14
|
+
cards : { type: Array }, // [{ title, badge, description, code, linkLabel, linkHref }]
|
|
15
|
+
|
|
16
|
+
// "Then just ask" sub-panel — rendered only when this is set, so a
|
|
17
|
+
// consumer of bd-install-options who doesn't need it gets nothing extra.
|
|
18
|
+
promptPanel: { type: Boolean, reflect: true, attribute: "prompt-panel" },
|
|
19
|
+
promptLabel: { type: String, attribute: "prompt-label" } // small mono label above the prompt block
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
static styles = [tokens, installOptionsCSS];
|
|
23
|
+
|
|
24
|
+
constructor() {
|
|
25
|
+
super();
|
|
26
|
+
this.eyebrow = "";
|
|
27
|
+
this.title = "";
|
|
28
|
+
this.description = "";
|
|
29
|
+
this.worksIn = [];
|
|
30
|
+
this.cards = [];
|
|
31
|
+
this.promptPanel = false;
|
|
32
|
+
this.promptLabel = "";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async _copy(text, btn) {
|
|
36
|
+
try {
|
|
37
|
+
await navigator.clipboard.writeText(text);
|
|
38
|
+
const original = btn.textContent;
|
|
39
|
+
btn.textContent = "Copied";
|
|
40
|
+
btn.disabled = true;
|
|
41
|
+
setTimeout(() => {
|
|
42
|
+
btn.textContent = original;
|
|
43
|
+
btn.disabled = false;
|
|
44
|
+
}, 1500);
|
|
45
|
+
} catch {
|
|
46
|
+
// clipboard permission denied or unavailable — button stays as "Copy"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async _copyPromptSlot(btn) {
|
|
51
|
+
const slot = this.renderRoot?.querySelector('slot[name="prompt"]');
|
|
52
|
+
const nodes = slot?.assignedNodes({ flatten: true }) || [];
|
|
53
|
+
const text = nodes.map((n) => n.textContent || "").join("")
|
|
54
|
+
.trim();
|
|
55
|
+
if (!text) return;
|
|
56
|
+
this._copy(text, btn);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
_renderWorksIn() {
|
|
60
|
+
if (!this.worksIn.length) return nothing;
|
|
61
|
+
return html`
|
|
62
|
+
<div class="bd-installoptions-worksin">
|
|
63
|
+
<span class="bd-installoptions-worksin-label">Works in</span>
|
|
64
|
+
<div class="bd-installoptions-pills">
|
|
65
|
+
${this.worksIn.map(
|
|
66
|
+
({ label, initial, color }) => html`
|
|
67
|
+
<span class="bd-installoptions-pill">
|
|
68
|
+
<span
|
|
69
|
+
class="bd-installoptions-pill-icon"
|
|
70
|
+
style="background:${color || "var(--color-neutral-800)"}"
|
|
71
|
+
aria-hidden="true"
|
|
72
|
+
>${initial || label?.[0] || "?"}</span>
|
|
73
|
+
${label}
|
|
74
|
+
</span>
|
|
75
|
+
`
|
|
76
|
+
)}
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
_renderCard(card) {
|
|
83
|
+
return html`
|
|
84
|
+
<div class="bd-installoptions-card">
|
|
85
|
+
<div class="bd-installoptions-card-head">
|
|
86
|
+
<bd-h as="h3" class="bd-installoptions-card-title">${card.title}</bd-h>
|
|
87
|
+
${card.badge
|
|
88
|
+
? html`<span class="bd-installoptions-badge">${card.badge}</span>`
|
|
89
|
+
: nothing}
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
${card.description
|
|
93
|
+
? html`<bd-p kind="small" class="bd-installoptions-card-description">${card.description}</bd-p>`
|
|
94
|
+
: nothing}
|
|
95
|
+
|
|
96
|
+
${card.code
|
|
97
|
+
? html`
|
|
98
|
+
<div class="bd-installoptions-code">
|
|
99
|
+
<pre class="bd-installoptions-code-text">${card.code}</pre>
|
|
100
|
+
<button
|
|
101
|
+
type="button"
|
|
102
|
+
class="bd-installoptions-copy"
|
|
103
|
+
@click=${(e) => this._copy(card.code, e.currentTarget)}
|
|
104
|
+
>Copy</button>
|
|
105
|
+
</div>
|
|
106
|
+
`
|
|
107
|
+
: nothing}
|
|
108
|
+
|
|
109
|
+
${card.linkLabel
|
|
110
|
+
? html`
|
|
111
|
+
<bd-link href=${card.linkHref || "#"} kind="primary" class="bd-installoptions-card-link">
|
|
112
|
+
${card.linkLabel} →
|
|
113
|
+
</bd-link>
|
|
114
|
+
`
|
|
115
|
+
: nothing}
|
|
116
|
+
</div>
|
|
117
|
+
`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
_renderPromptPanel() {
|
|
121
|
+
if (!this.promptPanel) return nothing;
|
|
122
|
+
|
|
123
|
+
return html`
|
|
124
|
+
<div class="bd-installoptions-promptpanel">
|
|
125
|
+
<bd-h as="h2" class="bd-installoptions-prompt-title">
|
|
126
|
+
<slot name="prompt-title"></slot>
|
|
127
|
+
</bd-h>
|
|
128
|
+
|
|
129
|
+
<bd-p kind="regular" class="bd-installoptions-prompt-description">
|
|
130
|
+
<slot name="prompt-description"></slot>
|
|
131
|
+
</bd-p>
|
|
132
|
+
|
|
133
|
+
${this.promptLabel
|
|
134
|
+
? html`<span class="bd-installoptions-prompt-label">${this.promptLabel}</span>`
|
|
135
|
+
: nothing}
|
|
136
|
+
|
|
137
|
+
<div class="bd-installoptions-prompt-code">
|
|
138
|
+
<div class="bd-installoptions-prompt-code-text">
|
|
139
|
+
<slot name="prompt"></slot>
|
|
140
|
+
</div>
|
|
141
|
+
<button
|
|
142
|
+
type="button"
|
|
143
|
+
class="bd-installoptions-copy bd-installoptions-copy--prompt"
|
|
144
|
+
@click=${(e) => this._copyPromptSlot(e.currentTarget)}
|
|
145
|
+
>Copy</button>
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
<div class="bd-installoptions-prompt-note">
|
|
149
|
+
<slot name="prompt-note"></slot>
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
<bd-p kind="regular" class="bd-installoptions-prompt-footer">
|
|
153
|
+
<slot name="prompt-footer"></slot>
|
|
154
|
+
</bd-p>
|
|
155
|
+
</div>
|
|
156
|
+
`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
render() {
|
|
160
|
+
return html`
|
|
161
|
+
<div class="bd-installoptions-container">
|
|
162
|
+
${this.eyebrow
|
|
163
|
+
? html`<bd-p kind="normal" class="bd-installoptions-eyebrow">${this.eyebrow}</bd-p>`
|
|
164
|
+
: nothing}
|
|
165
|
+
|
|
166
|
+
<bd-h as="h2" class="bd-installoptions-title">${this.title}</bd-h>
|
|
167
|
+
|
|
168
|
+
${this.description
|
|
169
|
+
? html`<bd-p kind="regular" class="bd-installoptions-description">${this.description}</bd-p>`
|
|
170
|
+
: nothing}
|
|
171
|
+
|
|
172
|
+
${this._renderWorksIn()}
|
|
173
|
+
|
|
174
|
+
<div class="bd-installoptions-grid">
|
|
175
|
+
${this.cards.map((card) => this._renderCard(card))}
|
|
176
|
+
</div>
|
|
177
|
+
|
|
178
|
+
${this._renderPromptPanel()}
|
|
179
|
+
</div>
|
|
180
|
+
`;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
customElements.define("bd-install-options", BdInstallOptions);
|
|
@@ -3,7 +3,7 @@ import { css } from "lit";
|
|
|
3
3
|
export default css`
|
|
4
4
|
:host {
|
|
5
5
|
display: block;
|
|
6
|
-
color: var(--color-neutral-1000);
|
|
6
|
+
color: var(--bd-paragraph-color, var(--color-neutral-1000));
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
p {
|
|
@@ -47,8 +47,4 @@ export default css`
|
|
|
47
47
|
line-height: var(--typography-body-lead-lineHeight);
|
|
48
48
|
letter-spacing: var(--typography-body-lead-letterSpacing);
|
|
49
49
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
50
|
`;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { css } from "lit";
|
|
2
|
+
|
|
3
|
+
export default css`
|
|
4
|
+
:host {
|
|
5
|
+
display: block;
|
|
6
|
+
width: 100%;
|
|
7
|
+
box-sizing: border-box;
|
|
8
|
+
max-width: 1290px;
|
|
9
|
+
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.bd-reviewgrid-title {
|
|
13
|
+
text-align: center;
|
|
14
|
+
margin: 0 0 var(--spacing-32);
|
|
15
|
+
color: var(--color-neutral-1000);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.bd-reviewgrid-grid {
|
|
19
|
+
display: grid;
|
|
20
|
+
grid-template-columns: repeat(var(--bd-review-columns, 3), 1fr);
|
|
21
|
+
gap: var(--spacing-24);
|
|
22
|
+
width: 100%;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
::slotted(bd-review) {
|
|
26
|
+
min-width: 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* Below md, every count collapses to a single column, regardless of how
|
|
30
|
+
many cards are slotted in. */
|
|
31
|
+
@media (max-width: 899px) {
|
|
32
|
+
.bd-reviewgrid-grid {
|
|
33
|
+
grid-template-columns: 1fr;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
`;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { LitElement, html, nothing } from "lit";
|
|
2
|
+
import { tokens } from "../../tokens/tokens.js";
|
|
3
|
+
import "../heading/heading.js";
|
|
4
|
+
import reviewGridCSS from "./review-grid.css.js";
|
|
5
|
+
import "./review.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Responsive wrapper for bd-review cards.
|
|
9
|
+
*
|
|
10
|
+
* <bd-review-grid title="What They Say">
|
|
11
|
+
* <bd-review variant="tint">...</bd-review>
|
|
12
|
+
* <bd-review variant="tint">...</bd-review>
|
|
13
|
+
* </bd-review-grid>
|
|
14
|
+
*
|
|
15
|
+
* Column count is derived from how many bd-review children are slotted in,
|
|
16
|
+
* capped at 3 per row (extra cards wrap onto further rows at 3-per-row):
|
|
17
|
+
* 1 child -> 1 column (full width, "col-md-12")
|
|
18
|
+
* 2 children -> 2 columns (half each, "col-md-6")
|
|
19
|
+
* 3+ children -> 3 columns (third each, "col-md-4")
|
|
20
|
+
*
|
|
21
|
+
* Below the md breakpoint every count collapses to a single column.
|
|
22
|
+
*/
|
|
23
|
+
class BdReviewGrid extends LitElement {
|
|
24
|
+
static properties = {
|
|
25
|
+
title : { type: String },
|
|
26
|
+
_columns: { state: true }
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
static styles = [tokens, reviewGridCSS];
|
|
30
|
+
|
|
31
|
+
constructor() {
|
|
32
|
+
super();
|
|
33
|
+
this.title = "";
|
|
34
|
+
this._columns = 3;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get _items() {
|
|
38
|
+
return [...this.children].filter((el) => el.tagName === "BD-REVIEW");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
_onSlotChange() {
|
|
42
|
+
const count = this._items.length;
|
|
43
|
+
this._columns = count <= 0 ? 1 : Math.min(count, 3);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
render() {
|
|
47
|
+
return html`
|
|
48
|
+
${this.title
|
|
49
|
+
? html`<bd-h as="h2" class="bd-reviewgrid-title"><slot name="title">${this.title}</slot></bd-h>`
|
|
50
|
+
: nothing}
|
|
51
|
+
|
|
52
|
+
<div
|
|
53
|
+
class="bd-reviewgrid-grid"
|
|
54
|
+
style="--bd-review-columns: ${this._columns};"
|
|
55
|
+
role="list"
|
|
56
|
+
>
|
|
57
|
+
<slot @slotchange=${this._onSlotChange}></slot>
|
|
58
|
+
</div>
|
|
59
|
+
`;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
customElements.define("bd-review-grid", BdReviewGrid);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { css } from "lit";
|
|
2
|
+
|
|
3
|
+
export default css`
|
|
4
|
+
:host {
|
|
5
|
+
display: block;
|
|
6
|
+
height: 100%;
|
|
7
|
+
font-family: var(--typography-fontFamily-sans);
|
|
8
|
+
box-sizing: border-box;
|
|
9
|
+
max-width: 1290px;
|
|
10
|
+
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.bd-review-card {
|
|
14
|
+
background: var(--color-blue-50);
|
|
15
|
+
border-radius: var(--radius-3xl);
|
|
16
|
+
padding: var(--spacing-28);
|
|
17
|
+
box-sizing: border-box;
|
|
18
|
+
height: 100%;
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
:host([variant="neutral"]) .bd-review-card {
|
|
24
|
+
background: var(--color-neutral-25, #fafafa);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.bd-review-mark {
|
|
28
|
+
display: block;
|
|
29
|
+
margin-bottom: var(--spacing-8);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.bd-review-quote {
|
|
33
|
+
--bd-paragraph-color: var(--color-neutral-900);
|
|
34
|
+
margin-bottom: var(--spacing-16);
|
|
35
|
+
flex-grow: 1;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.bd-review-meta {
|
|
39
|
+
display: flex;
|
|
40
|
+
flex-direction: column;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.bd-review-source {
|
|
44
|
+
--bd-paragraph-color: var(--color-neutral-900);
|
|
45
|
+
margin: 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.bd-review-date {
|
|
49
|
+
color: var(--color-neutral-600);
|
|
50
|
+
font-size: var(--typography-fontSize-sm);
|
|
51
|
+
}
|
|
52
|
+
`;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { LitElement, html } from "lit";
|
|
2
|
+
import { tokens } from "../../tokens/tokens.js";
|
|
3
|
+
import "../icons/quote-icon.js";
|
|
4
|
+
import "../paragraph/paragraph.js";
|
|
5
|
+
import reviewCSS from "./review.css.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A single testimonial/review card. Everything is slotted rather than
|
|
9
|
+
* passed as props, so callers can put arbitrary markup (a bd-p, a link,
|
|
10
|
+
* bold text) in any of the three slots:
|
|
11
|
+
*
|
|
12
|
+
* <bd-review variant="tint">
|
|
13
|
+
* <span slot="quote">If you're looking to buy a security suite...</span>
|
|
14
|
+
* <span slot="source">PCMag</span>
|
|
15
|
+
* <span slot="date">February 3, 2026</span>
|
|
16
|
+
* </bd-review>
|
|
17
|
+
*
|
|
18
|
+
* `variant` is the only prop, since it's an appearance switch (background
|
|
19
|
+
* colour), not content:
|
|
20
|
+
* "tint" — light blue background (var(--color-blue-50))
|
|
21
|
+
* "neutral" — light grey background (var(--color-neutral-25), #fafafa)
|
|
22
|
+
*/
|
|
23
|
+
class BdReview extends LitElement {
|
|
24
|
+
static properties = {
|
|
25
|
+
variant: { type: String, reflect: true } // "tint" | "neutral"
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
static styles = [tokens, reviewCSS];
|
|
29
|
+
|
|
30
|
+
constructor() {
|
|
31
|
+
super();
|
|
32
|
+
this.variant = "tint";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
render() {
|
|
36
|
+
return html`
|
|
37
|
+
<div class="bd-review-card">
|
|
38
|
+
<bd-quote-icon
|
|
39
|
+
class="bd-review-mark"
|
|
40
|
+
width="34"
|
|
41
|
+
height="35"
|
|
42
|
+
color="var(--color-blue-500)"
|
|
43
|
+
></bd-quote-icon>
|
|
44
|
+
|
|
45
|
+
<bd-p kind="regular" class="bd-review-quote">
|
|
46
|
+
<slot name="quote"></slot>
|
|
47
|
+
</bd-p>
|
|
48
|
+
|
|
49
|
+
<div class="bd-review-meta">
|
|
50
|
+
<bd-p kind="small" font-weight="bold" class="bd-review-source"><slot name="source"></slot></bd-p>
|
|
51
|
+
<span class="bd-review-date"><slot name="date"></slot></span>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
`;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
customElements.define("bd-review", BdReview);
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { html } from "lit";
|
|
2
|
+
import "./review-grid.js";
|
|
3
|
+
import "./review.js";
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
title : "Components/Review",
|
|
7
|
+
tags : ["autodocs"],
|
|
8
|
+
parameters: {
|
|
9
|
+
layout: "padded",
|
|
10
|
+
docs : {
|
|
11
|
+
description: {
|
|
12
|
+
component: `
|
|
13
|
+
Testimonial / review card (\`bd-review\`) plus a responsive grid wrapper
|
|
14
|
+
(\`bd-review-grid\`) that auto-sizes columns from how many cards are
|
|
15
|
+
slotted in.
|
|
16
|
+
|
|
17
|
+
\`\`\`html
|
|
18
|
+
<bd-review-grid title="What They Say">
|
|
19
|
+
<bd-review variant="tint">
|
|
20
|
+
<span slot="quote">If you're looking to buy a security suite, our experts and readers agree: Bitdefender should be your top choice.</span>
|
|
21
|
+
<span slot="source">PCMag</span>
|
|
22
|
+
<span slot="date">February 3, 2026</span>
|
|
23
|
+
</bd-review>
|
|
24
|
+
|
|
25
|
+
<bd-review variant="tint">
|
|
26
|
+
<span slot="quote">Bitdefender Premium Security combines award-winning security with a scam-detection chatbot, server-level email protection, and a VPN with no bandwidth or server limits.</span>
|
|
27
|
+
<span slot="source">PCMag</span>
|
|
28
|
+
<span slot="date">April 20, 2026</span>
|
|
29
|
+
</bd-review>
|
|
30
|
+
</bd-review-grid>
|
|
31
|
+
\`\`\`
|
|
32
|
+
|
|
33
|
+
### Column count (bd-review-grid)
|
|
34
|
+
Derived automatically from how many \`bd-review\` children are slotted in,
|
|
35
|
+
capped at 3 per row — extra cards wrap onto further rows at 3-per-row:
|
|
36
|
+
|
|
37
|
+
| Cards | Columns | Each card |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| 1 | 1 | full width ("col-md-12") |
|
|
40
|
+
| 2 | 2 | half each ("col-md-6") |
|
|
41
|
+
| 3+ | 3 | third each ("col-md-4") |
|
|
42
|
+
|
|
43
|
+
Below the md breakpoint (899px), every count collapses to a single column.
|
|
44
|
+
|
|
45
|
+
### Attributes \`bd-review\`
|
|
46
|
+
| Attribute | Type | Default | Description |
|
|
47
|
+
|---|---|---|---|
|
|
48
|
+
| \`variant\` | String | \`"tint"\` | \`"tint"\` (light blue) or \`"neutral"\` (light grey, #fafafa) |
|
|
49
|
+
|
|
50
|
+
### Slots \`bd-review\`
|
|
51
|
+
| Slot | Description |
|
|
52
|
+
|---|---|
|
|
53
|
+
| \`quote\` | The testimonial text |
|
|
54
|
+
| \`source\` | Publication / attribution (bold) |
|
|
55
|
+
| \`date\` | Date line, under the source |
|
|
56
|
+
|
|
57
|
+
### Attributes \`bd-review-grid\`
|
|
58
|
+
| Attribute | Type | Default | Description |
|
|
59
|
+
|---|---|---|---|
|
|
60
|
+
| \`title\` | String | — | Optional centred heading above the grid |
|
|
61
|
+
`
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// ─── Un singur card (full width) ──────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
export const OneCard = {
|
|
70
|
+
name : "1 card — full width",
|
|
71
|
+
render: () => html`
|
|
72
|
+
<bd-review-grid title="What They Say">
|
|
73
|
+
<bd-review variant="tint">
|
|
74
|
+
<span slot="quote">If you're looking to buy a security suite, our experts and readers agree: Bitdefender should be your top choice.</span>
|
|
75
|
+
<span slot="source">PCMag</span>
|
|
76
|
+
<span slot="date">February 3, 2026</span>
|
|
77
|
+
</bd-review>
|
|
78
|
+
</bd-review-grid>
|
|
79
|
+
`
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// ─── Doua carduri (jumatate fiecare) ──────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
export const TwoCards = {
|
|
85
|
+
name : "2 carduri — jumătate fiecare",
|
|
86
|
+
render: () => html`
|
|
87
|
+
<bd-review-grid title="What They Say">
|
|
88
|
+
<bd-review variant="tint">
|
|
89
|
+
<span slot="quote">If you're looking to buy a security suite, our experts and readers agree: Bitdefender should be your top choice.</span>
|
|
90
|
+
<span slot="source">PCMag</span>
|
|
91
|
+
<span slot="date">February 3, 2026</span>
|
|
92
|
+
</bd-review>
|
|
93
|
+
|
|
94
|
+
<bd-review variant="tint">
|
|
95
|
+
<span slot="quote">Bitdefender Premium Security combines award-winning security with a scam-detection chatbot, server-level email protection, and a VPN with no bandwidth or server limits.</span>
|
|
96
|
+
<span slot="source">PCMag</span>
|
|
97
|
+
<span slot="date">April 20, 2026</span>
|
|
98
|
+
</bd-review>
|
|
99
|
+
</bd-review-grid>
|
|
100
|
+
`
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// ─── Trei carduri (o treime fiecare) ──────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
export const ThreeCards = {
|
|
106
|
+
name : "3 carduri — o treime fiecare",
|
|
107
|
+
render: () => html`
|
|
108
|
+
<bd-review-grid title="What They Say">
|
|
109
|
+
<bd-review variant="tint">
|
|
110
|
+
<span slot="quote">If you're looking to buy a security suite, our experts and readers agree: Bitdefender should be your top choice.</span>
|
|
111
|
+
<span slot="source">PCMag</span>
|
|
112
|
+
<span slot="date">February 3, 2026</span>
|
|
113
|
+
</bd-review>
|
|
114
|
+
|
|
115
|
+
<bd-review variant="neutral">
|
|
116
|
+
<span slot="quote">Bitdefender Premium Security combines award-winning security with a scam-detection chatbot, server-level email protection, and a VPN with no bandwidth or server limits.</span>
|
|
117
|
+
<span slot="source">PCMag</span>
|
|
118
|
+
<span slot="date">April 20, 2026</span>
|
|
119
|
+
</bd-review>
|
|
120
|
+
|
|
121
|
+
<bd-review variant="tint">
|
|
122
|
+
<span slot="quote">Consistently top-ranked by the world's leading independent testing labs and tech publications.</span>
|
|
123
|
+
<span slot="source">AV-Comparatives</span>
|
|
124
|
+
<span slot="date">June 12, 2026</span>
|
|
125
|
+
</bd-review>
|
|
126
|
+
</bd-review-grid>
|
|
127
|
+
`
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// ─── Cinci carduri (wrap la 3 pe rand) ────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
export const FiveCardsWrapping = {
|
|
133
|
+
name : "5 carduri — se leagă la maxim 3 pe rând",
|
|
134
|
+
parameters: {
|
|
135
|
+
docs: {
|
|
136
|
+
description: {
|
|
137
|
+
story: "Peste 3 carduri, grid-ul rămâne la 3 coloane și cardurile suplimentare trec pe rândul următor, tot la o treime fiecare — regula \"maxim 3 pe rând\" se aplică indiferent de câte carduri sunt slotate."
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
render: () => html`
|
|
142
|
+
<bd-review-grid title="What They Say">
|
|
143
|
+
${[1, 2, 3, 4, 5].map(
|
|
144
|
+
(n) => html`
|
|
145
|
+
<bd-review variant=${n % 2 === 0 ? "neutral" : "tint"}>
|
|
146
|
+
<span slot="quote">Review number ${n} — placeholder testimonial text to show how the grid wraps once more than three cards are slotted in.</span>
|
|
147
|
+
<span slot="source">Publication ${n}</span>
|
|
148
|
+
<span slot="date">2026</span>
|
|
149
|
+
</bd-review>
|
|
150
|
+
`
|
|
151
|
+
)}
|
|
152
|
+
</bd-review-grid>
|
|
153
|
+
`
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// ─── Text mai lung (verifica wrapping și înălțimea egală pe rând) ─────────
|
|
157
|
+
|
|
158
|
+
export const LongQuotes = {
|
|
159
|
+
name : "Text mai lung — wrapping și înălțime egală",
|
|
160
|
+
parameters: {
|
|
161
|
+
docs: {
|
|
162
|
+
description: {
|
|
163
|
+
story: "Citate mult mai lungi decât în exemplele scurte, ca să se vadă comportarea reală a cardurilor: textul se leagă pe mai multe rânduri, iar cardurile de pe același rând rămân la aceeași înălțime (citatul se întinde cu flex-grow, meta-ul rămâne lipit de jos)."
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
render: () => html`
|
|
168
|
+
<bd-review-grid title="What They Say">
|
|
169
|
+
<bd-review variant="tint">
|
|
170
|
+
<span slot="quote">If you're looking to buy a security suite, our experts and readers agree: Bitdefender should be your top choice. It combines consistently excellent malware protection with a surprisingly light footprint on system performance, and the extras — VPN, password manager, parental controls — are genuinely useful rather than bolted-on filler.</span>
|
|
171
|
+
<span slot="source">PCMag</span>
|
|
172
|
+
<span slot="date">February 3, 2026</span>
|
|
173
|
+
</bd-review>
|
|
174
|
+
|
|
175
|
+
<bd-review variant="neutral">
|
|
176
|
+
<span slot="quote">Bitdefender Premium Security combines award-winning security with a scam-detection chatbot, server-level email protection, and a VPN with no bandwidth or server limits. In our testing across three months and multiple platforms, it caught every real-world threat we threw at it without a single false positive on legitimate software.</span>
|
|
177
|
+
<span slot="source">PCMag</span>
|
|
178
|
+
<span slot="date">April 20, 2026</span>
|
|
179
|
+
</bd-review>
|
|
180
|
+
|
|
181
|
+
<bd-review variant="tint">
|
|
182
|
+
<span slot="quote">Consistently top-ranked by the world's leading independent testing labs and tech publications, year after year, across every category we measure: protection, performance, usability, and — increasingly relevant for households with kids — how well it handles parental controls without becoming a nuisance to manage.</span>
|
|
183
|
+
<span slot="source">AV-Comparatives</span>
|
|
184
|
+
<span slot="date">June 12, 2026</span>
|
|
185
|
+
</bd-review>
|
|
186
|
+
</bd-review-grid>
|
|
187
|
+
`
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// ─── Text mai lung, doar doua carduri (verifica jumatate-jumatate) ────────
|
|
191
|
+
|
|
192
|
+
export const LongQuotesTwoCards = {
|
|
193
|
+
name : "Text mai lung — 2 carduri",
|
|
194
|
+
render: () => html`
|
|
195
|
+
<bd-review-grid title="What They Say">
|
|
196
|
+
<bd-review variant="tint">
|
|
197
|
+
<span slot="quote">If you're looking to buy a security suite, our experts and readers agree: Bitdefender should be your top choice. It combines consistently excellent malware protection with a surprisingly light footprint on system performance.</span>
|
|
198
|
+
<span slot="source">PCMag</span>
|
|
199
|
+
<span slot="date">February 3, 2026</span>
|
|
200
|
+
</bd-review>
|
|
201
|
+
|
|
202
|
+
<bd-review variant="neutral">
|
|
203
|
+
<span slot="quote">Bitdefender Premium Security combines award-winning security with a scam-detection chatbot, server-level email protection, and a VPN with no bandwidth or server limits — a genuinely complete package rather than a marketing checklist.</span>
|
|
204
|
+
<span slot="source">PCMag</span>
|
|
205
|
+
<span slot="date">April 20, 2026</span>
|
|
206
|
+
</bd-review>
|
|
207
|
+
</bd-review-grid>
|
|
208
|
+
`
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
export const NoTitle = {
|
|
212
|
+
name : "Fără titlu",
|
|
213
|
+
render: () => html`
|
|
214
|
+
<bd-review-grid>
|
|
215
|
+
<bd-review variant="neutral">
|
|
216
|
+
<span slot="quote">If you're looking to buy a security suite, our experts and readers agree: Bitdefender should be your top choice.</span>
|
|
217
|
+
<span slot="source">PCMag</span>
|
|
218
|
+
<span slot="date">February 3, 2026</span>
|
|
219
|
+
</bd-review>
|
|
220
|
+
|
|
221
|
+
<bd-review variant="neutral">
|
|
222
|
+
<span slot="quote">Bitdefender Premium Security combines award-winning security with a scam-detection chatbot, server-level email protection, and a VPN with no bandwidth or server limits.</span>
|
|
223
|
+
<span slot="source">PCMag</span>
|
|
224
|
+
<span slot="date">April 20, 2026</span>
|
|
225
|
+
</bd-review>
|
|
226
|
+
</bd-review-grid>
|
|
227
|
+
`
|
|
228
|
+
};
|