@repobit/dex-system-design 0.24.18 → 0.24.20
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 +14 -0
- package/package.json +4 -4
- package/src/components/ai-guardian-demo.stories.js +454 -33
- package/src/components/bento-grid/bento-grid.css.js +12 -1
- package/src/components/bento-grid/bento-grid.js +59 -1
- package/src/components/bento-grid/bento-grid.stories.js +45 -0
- package/src/components/cards/card.css.js +56 -0
- package/src/components/cards/card.js +64 -11
- package/src/components/cards/card.stories.js +65 -2
- package/src/components/comparison-panel/comparison-panel.css.js +52 -1
- package/src/components/comparison-panel/comparison-panel.js +6 -1
- package/src/components/compatibility-table/compatibility-table.css.js +29 -13
- package/src/components/compatibility-table/compatibility-table.js +13 -7
- package/src/components/cta-banner/cta-banner.css.js +11 -0
- package/src/components/cta-banner/cta-banner.js +7 -0
- package/src/components/footer/footer-lp.css.js +10 -21
- package/src/components/footer/footer-lp.js +3 -18
- package/src/components/footer/footer-nav-menu.css.js +0 -2
- package/src/components/footer/footer.css.js +18 -35
- package/src/components/footer/footer.js +5 -1
- package/src/components/header/header-code.css.js +208 -10
- package/src/components/header/header-code.js +148 -5
- package/src/components/header/header-code.stories.js +83 -27
- package/src/components/page-switcher/page-switcher.js +1 -1
- package/src/components/paragraph/paragraph.js +6 -0
- package/src/components/quick-answer/quick-answer.css.js +2 -0
- package/src/components/quick-answer/quick-answer.js +1 -1
- package/src/components/terminal/terminal-message.css.js +153 -13
- package/src/components/terminal/terminal-message.js +37 -11
|
@@ -29,6 +29,29 @@ function resolveBreakpoint(width, maxColumns, minCardWidth) {
|
|
|
29
29
|
return { bp, columns, gap };
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
// Same breakpoint logic as resolveBreakpoint, but allows more than 2
|
|
33
|
+
// columns (up to maxColumns) since the "equal" variant is a plain grid
|
|
34
|
+
// with no wide/narrow pairing to worry about.
|
|
35
|
+
function resolveEqualColumns(width, maxColumns, minCardWidth) {
|
|
36
|
+
const bp =
|
|
37
|
+
width >= BREAKPOINT_WIDTHS.xl
|
|
38
|
+
? "xl" :
|
|
39
|
+
width >= BREAKPOINT_WIDTHS.lg
|
|
40
|
+
? "lg" :
|
|
41
|
+
width >= BREAKPOINT_WIDTHS.md ? "md" : "sm";
|
|
42
|
+
|
|
43
|
+
let columns = bp === "sm" ? 1 : bp === "md" ? Math.min(2, maxColumns) : Math.max(1, maxColumns);
|
|
44
|
+
const gap = DEFAULT_GAP[bp];
|
|
45
|
+
|
|
46
|
+
while (columns > 1) {
|
|
47
|
+
const cardWidth = (width - gap * (columns - 1)) / columns;
|
|
48
|
+
if (cardWidth >= minCardWidth) break;
|
|
49
|
+
columns -= 1;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return { bp, columns, gap };
|
|
53
|
+
}
|
|
54
|
+
|
|
32
55
|
class BentoGrid extends LitElement {
|
|
33
56
|
static properties = {
|
|
34
57
|
eyebrow : { type: String },
|
|
@@ -37,7 +60,20 @@ class BentoGrid extends LitElement {
|
|
|
37
60
|
maxColumns : { type: Number, attribute: "max-columns" },
|
|
38
61
|
minCardWidth: { type: Number, attribute: "min-card-width" },
|
|
39
62
|
// Fixed wide/narrow ratio applied to every pair, e.g. 0.7 = 70/30.
|
|
63
|
+
// Only used when variant="weighted" (the default).
|
|
40
64
|
wideShare : { type: Number, attribute: "wide-share" },
|
|
65
|
+
// "weighted" (default) — pairs of cards alternate wide/narrow, per
|
|
66
|
+
// _applyWeights below. "equal" — every card gets the same width, laid
|
|
67
|
+
// out in a plain N-column grid (see equalColumns) with no pairing.
|
|
68
|
+
variant : { type: String, reflect: true },
|
|
69
|
+
// Column count for variant="equal" at the widest breakpoint (xl/lg);
|
|
70
|
+
// narrows to min(2, this) at md, and 1 at sm, same responsive pattern
|
|
71
|
+
// as the weighted variant. Ignored when variant="weighted".
|
|
72
|
+
equalColumns: { type: Number, attribute: "equal-columns" },
|
|
73
|
+
// "none" (default, transparent) | "tint" (light blue wash behind the
|
|
74
|
+
// whole grid, including header text) — a section-level background, not
|
|
75
|
+
// per-card (bd-bento-item already has its own `tone="tint"` for that).
|
|
76
|
+
background : { type: String, reflect: true },
|
|
41
77
|
_columns : { state: true },
|
|
42
78
|
_gap : { state: true }
|
|
43
79
|
};
|
|
@@ -52,6 +88,9 @@ class BentoGrid extends LitElement {
|
|
|
52
88
|
this.maxColumns = 2;
|
|
53
89
|
this.minCardWidth = 260;
|
|
54
90
|
this.wideShare = 0.7;
|
|
91
|
+
this.variant = "weighted";
|
|
92
|
+
this.equalColumns = 3;
|
|
93
|
+
this.background = "none";
|
|
55
94
|
this._columns = 2;
|
|
56
95
|
this._gap = 24;
|
|
57
96
|
this._resizeObserver = null;
|
|
@@ -124,13 +163,32 @@ class BentoGrid extends LitElement {
|
|
|
124
163
|
}
|
|
125
164
|
}
|
|
126
165
|
|
|
166
|
+
// Every item gets the same span (TOTAL_UNITS / columns) — no pairing, no
|
|
167
|
+
// "featured" full-width exception. Rounds if TOTAL_UNITS doesn't divide
|
|
168
|
+
// evenly by columns; for common column counts (1, 2, 3, 4, 6, 8, 12, 24)
|
|
169
|
+
// against TOTAL_UNITS=24 this is always exact.
|
|
170
|
+
_applyEqualSpans(columns) {
|
|
171
|
+
const span = Math.round(TOTAL_UNITS / Math.max(1, columns));
|
|
172
|
+
this._items.forEach((item) => item.style.setProperty("--bd-bento-span", String(span)));
|
|
173
|
+
}
|
|
174
|
+
|
|
127
175
|
_relayout() {
|
|
128
176
|
const width = this.clientWidth || 960;
|
|
177
|
+
this.style.setProperty("--bd-bento-total-units", String(TOTAL_UNITS));
|
|
178
|
+
|
|
179
|
+
if (this.variant === "equal") {
|
|
180
|
+
const { columns, gap } = resolveEqualColumns(width, this.equalColumns, this.minCardWidth);
|
|
181
|
+
this._columns = columns;
|
|
182
|
+
this._gap = gap;
|
|
183
|
+
this.style.setProperty("--bd-bento-gap", `${gap}px`);
|
|
184
|
+
this._applyEqualSpans(columns);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
129
188
|
const { columns, gap } = resolveBreakpoint(width, this.maxColumns, this.minCardWidth);
|
|
130
189
|
this._columns = columns;
|
|
131
190
|
this._gap = gap;
|
|
132
191
|
this.style.setProperty("--bd-bento-gap", `${gap}px`);
|
|
133
|
-
this.style.setProperty("--bd-bento-total-units", String(TOTAL_UNITS));
|
|
134
192
|
|
|
135
193
|
if (columns <= 1) {
|
|
136
194
|
this._items.forEach((item) => item.style.setProperty("--bd-bento-span", String(TOTAL_UNITS)));
|
|
@@ -437,4 +437,49 @@ export const TwentySevenItems = {
|
|
|
437
437
|
}
|
|
438
438
|
}
|
|
439
439
|
}
|
|
440
|
+
};
|
|
441
|
+
export const EqualColumns = {
|
|
442
|
+
name : "Equal Columns — Protection",
|
|
443
|
+
render: () => html`
|
|
444
|
+
<bd-bento-grid
|
|
445
|
+
variant="equal"
|
|
446
|
+
equal-columns="3"
|
|
447
|
+
eyebrow="Protection"
|
|
448
|
+
title="What does VPN for AI Agents protect against?"
|
|
449
|
+
description="Each protection is structural: every request the agent routes through Bitdefender's MCP tools gets it automatically, with no setting to remember."
|
|
450
|
+
>
|
|
451
|
+
<bd-bento-item>
|
|
452
|
+
<span slot="icon" style="color: var(--color-blue-500, #006DFF); font-weight: 700; font-family: var(--typography-fontFamily-mono, monospace); font-size: 14px; letter-spacing: 0.05em;">01</span>
|
|
453
|
+
<bd-h as="h5" font-weight="700" style="margin: 0 0 8px;">IP masking</bd-h>
|
|
454
|
+
<bd-p color="var(--color-neutral-600)">When your agent reaches a site, it sees the VPN server's exit IP. Not your home address or IP-based location.</bd-p>
|
|
455
|
+
</bd-bento-item>
|
|
456
|
+
<bd-bento-item>
|
|
457
|
+
<span slot="icon" style="color: var(--color-blue-500, #006DFF); font-weight: 700; font-family: var(--typography-fontFamily-mono, monospace); font-size: 14px; letter-spacing: 0.05em;">02</span>
|
|
458
|
+
<bd-h as="h5" font-weight="700" style="margin: 0 0 8px;">Per-prompt encryption</bd-h>
|
|
459
|
+
<bd-p color="var(--color-neutral-600)">Every request opens its own encrypted tunnel from your device to Bitdefender, so whoever runs the Wi-Fi you're on can't read what the agent sends.</bd-p>
|
|
460
|
+
</bd-bento-item>
|
|
461
|
+
<bd-bento-item>
|
|
462
|
+
<span slot="icon" style="color: var(--color-blue-500, #006DFF); font-weight: 700; font-family: var(--typography-fontFamily-mono, monospace); font-size: 14px; letter-spacing: 0.05em;">03</span>
|
|
463
|
+
<bd-h as="h5" font-weight="700" style="margin: 0 0 8px;">Session isolation</bd-h>
|
|
464
|
+
<bd-p color="var(--color-neutral-600)">Each tunnel closes the moment the prompt ends. If a site sees two of your agent's requests, it has no network-level way to tell they came from the same prompt.</bd-p>
|
|
465
|
+
</bd-bento-item>
|
|
466
|
+
<bd-bento-item>
|
|
467
|
+
<span slot="icon" style="color: var(--color-blue-500, #006DFF); font-weight: 700; font-family: var(--typography-fontFamily-mono, monospace); font-size: 14px; letter-spacing: 0.05em;">04</span>
|
|
468
|
+
<bd-h as="h5" font-weight="700" style="margin: 0 0 8px;">Clean-IP browsing</bd-h>
|
|
469
|
+
<bd-p color="var(--color-neutral-600)">Every request exits on the VPN server's IP rather than the same home address every time, useful for QA and monitoring work that needs a neutral, non-residential vantage point.</bd-p>
|
|
470
|
+
</bd-bento-item>
|
|
471
|
+
<bd-bento-item>
|
|
472
|
+
<span slot="icon" style="color: var(--color-blue-500, #006DFF); font-weight: 700; font-family: var(--typography-fontFamily-mono, monospace); font-size: 14px; letter-spacing: 0.05em;">05</span>
|
|
473
|
+
<bd-h as="h5" font-weight="700" style="margin: 0 0 8px;">Regional routing</bd-h>
|
|
474
|
+
<bd-p color="var(--color-neutral-600)">Need to see a page the way a visitor from a specific country would? Route that one request through a Bitdefender server there, just for that prompt.</bd-p>
|
|
475
|
+
</bd-bento-item>
|
|
476
|
+
</bd-bento-grid>
|
|
477
|
+
`,
|
|
478
|
+
parameters: {
|
|
479
|
+
docs: {
|
|
480
|
+
description: {
|
|
481
|
+
story: "`variant=\"equal\"` gives every card the same width in a plain N-column grid (`equal-columns`), with no wide/narrow pairing — unlike the default `\"weighted\"` variant."
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
440
485
|
};
|
|
@@ -50,6 +50,12 @@ export default css`
|
|
|
50
50
|
gap: var(--spacing-8);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/* Outline-variant sections get extra top padding above the header,
|
|
54
|
+
per the reference design. */
|
|
55
|
+
:host([variant="outline"]) .bd-header-light-carousel-s {
|
|
56
|
+
padding-top: 80px;
|
|
57
|
+
}
|
|
58
|
+
|
|
53
59
|
.bd-light-carousel-s {
|
|
54
60
|
display: flex;
|
|
55
61
|
align-items: center;
|
|
@@ -95,6 +101,56 @@ export default css`
|
|
|
95
101
|
background: #E4F2FF !important;
|
|
96
102
|
}
|
|
97
103
|
|
|
104
|
+
/* === OUTLINE VARIANT — bordered, tinted, fully centered card. Set
|
|
105
|
+
variant="outline" on bd-card-section (propagates to items) or on an
|
|
106
|
+
individual bd-card-item to opt it in/out per-card. === */
|
|
107
|
+
.bd-light-box-outline {
|
|
108
|
+
background-color: var(--color-neutral-25);
|
|
109
|
+
border: 2px solid var(--color-blue-500);
|
|
110
|
+
border-radius: var(--radius-2xl, 1rem);
|
|
111
|
+
padding: 40px;
|
|
112
|
+
align-items: center !important;
|
|
113
|
+
justify-content: center !important;
|
|
114
|
+
text-align: center !important;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.bd-light-box-outline .bd-light-box-header-s {
|
|
118
|
+
align-items: center;
|
|
119
|
+
justify-content: center;
|
|
120
|
+
width: 100%;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.bd-light-box-outline .bd-light-title-badge-wrapper-s {
|
|
124
|
+
justify-content: center;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.bd-light-box-outline .bd-card-title-group {
|
|
128
|
+
align-items: center;
|
|
129
|
+
text-align: center;
|
|
130
|
+
margin-top: 60px;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.bd-light-box-outline .bd-card-title-group bd-h {
|
|
134
|
+
color: var(--color-blue-500, #006dff);
|
|
135
|
+
text-align: center;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.bd-light-box-outline .bd-card-item-subtitle,
|
|
139
|
+
.bd-light-box-outline .bd-card-content-wrapper p,
|
|
140
|
+
.bd-light-box-outline .bd-card-content-wrapper ::slotted(*) {
|
|
141
|
+
color: var(--color-neutral-700);
|
|
142
|
+
text-align: center;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.bd-light-box-outline .bd-card-content-wrapper {
|
|
146
|
+
align-items: center;
|
|
147
|
+
text-align: center;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.bd-light-box-outline .bd-card-cta-wrapper {
|
|
151
|
+
justify-content: center;
|
|
152
|
+
}
|
|
153
|
+
|
|
98
154
|
.bd-light-box-center {
|
|
99
155
|
align-items: center !important;
|
|
100
156
|
justify-content: center !important;
|
|
@@ -9,6 +9,13 @@ class Card extends LitElement {
|
|
|
9
9
|
title : { type: String },
|
|
10
10
|
subtitle : { type: String },
|
|
11
11
|
bgBlue : { type: Boolean, reflect: true, attribute: "bg-blue" },
|
|
12
|
+
// "" (default — existing grey-box carousel look) | "outline" — bordered,
|
|
13
|
+
// centered cards with a light-blue tint, blue title and centered icon,
|
|
14
|
+
// matching the reference design. Propagated automatically to every
|
|
15
|
+
// slotted bd-card-item (see _onSlotChange) so callers only set it once,
|
|
16
|
+
// on the section, unless a specific item needs to opt out by setting
|
|
17
|
+
// its own variant attribute explicitly.
|
|
18
|
+
variant : { type: String, reflect: true },
|
|
12
19
|
_itemCount: { state: true }
|
|
13
20
|
};
|
|
14
21
|
|
|
@@ -17,6 +24,7 @@ class Card extends LitElement {
|
|
|
17
24
|
this.title = "";
|
|
18
25
|
this.subtitle = "";
|
|
19
26
|
this.bgBlue = false;
|
|
27
|
+
this.variant = "";
|
|
20
28
|
this._itemCount = 0;
|
|
21
29
|
this._resizeObserver = null;
|
|
22
30
|
}
|
|
@@ -47,8 +55,26 @@ class Card extends LitElement {
|
|
|
47
55
|
this.style.setProperty("--bd-card-width", `${cardW}px`);
|
|
48
56
|
}
|
|
49
57
|
|
|
58
|
+
// Propagates this.variant onto every slotted bd-card-item that didn't
|
|
59
|
+
// itself declare a variant attribute in its ORIGINAL markup. We can't use
|
|
60
|
+
// item.hasAttribute("variant") here — CardItem's own `variant` property
|
|
61
|
+
// has reflect:true, so Lit reflects its default "" onto the attribute on
|
|
62
|
+
// first render, meaning hasAttribute("variant") becomes true for EVERY
|
|
63
|
+
// item almost immediately, even ones the author never touched. Instead
|
|
64
|
+
// we rely on item._authorSetVariant, captured in CardItem's constructor
|
|
65
|
+
// (before Lit's own reflection has a chance to run) — see below.
|
|
66
|
+
_propagateVariant() {
|
|
67
|
+
if (!this.variant) return;
|
|
68
|
+
this.querySelectorAll("bd-card-item").forEach((item) => {
|
|
69
|
+
if (!item._authorSetVariant) {
|
|
70
|
+
item.variant = this.variant;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
50
75
|
_onSlotChange = () => {
|
|
51
76
|
this._itemCount = this.querySelectorAll("bd-card-item").length;
|
|
77
|
+
this._propagateVariant();
|
|
52
78
|
this._updateCardWidth();
|
|
53
79
|
this.requestUpdate();
|
|
54
80
|
this._equalizeHeights();
|
|
@@ -160,6 +186,7 @@ class Card extends LitElement {
|
|
|
160
186
|
}
|
|
161
187
|
|
|
162
188
|
await new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r)));
|
|
189
|
+
this._propagateVariant();
|
|
163
190
|
this._updateCardWidth();
|
|
164
191
|
this._equalizeHeights();
|
|
165
192
|
|
|
@@ -208,38 +235,61 @@ class CardItem extends LitElement {
|
|
|
208
235
|
icon : { type: String },
|
|
209
236
|
align : { type: String },
|
|
210
237
|
bgBlue : { type: Boolean, reflect: true, attribute: "bg-blue" },
|
|
211
|
-
ctaAlign: { type: String, attribute: "cta-align" }
|
|
238
|
+
ctaAlign: { type: String, attribute: "cta-align" },
|
|
239
|
+
// "" (default) | "outline" — see Card.variant above. Reflected so
|
|
240
|
+
// card.css.js can target it with :host([variant="outline"]).
|
|
241
|
+
variant : { type: String, reflect: true }
|
|
212
242
|
};
|
|
213
243
|
|
|
214
244
|
constructor() {
|
|
215
245
|
super();
|
|
246
|
+
// Captured BEFORE Lit's own property-to-attribute reflection runs (that
|
|
247
|
+
// happens on first update/render, not here). At this point the DOM
|
|
248
|
+
// parser has already set any attribute the author wrote in markup, so
|
|
249
|
+
// this reliably answers "did the author explicitly set variant on this
|
|
250
|
+
// element", independent of whatever default value we assign below.
|
|
251
|
+
this._authorSetVariant = this.hasAttribute("variant");
|
|
252
|
+
|
|
216
253
|
this.title = "";
|
|
217
254
|
this.subtitle = "";
|
|
218
255
|
this.icon = "";
|
|
219
256
|
this.align = "start";
|
|
220
257
|
this.bgBlue = false;
|
|
221
258
|
this.ctaAlign = "start";
|
|
259
|
+
this.variant = "";
|
|
222
260
|
}
|
|
223
261
|
|
|
224
262
|
_renderIcon() {
|
|
225
263
|
const iconEl = this.querySelector("[slot='icon']");
|
|
264
|
+
const isOutline = this.variant === "outline";
|
|
265
|
+
const iconW = isOutline ? 65 : 48;
|
|
266
|
+
const iconH = isOutline ? 75 : 48;
|
|
226
267
|
|
|
227
268
|
if (iconEl) {
|
|
228
269
|
const clone = iconEl.cloneNode(true);
|
|
229
270
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
clone.style.width
|
|
233
|
-
|
|
271
|
+
// The icon components (bd-globe-icon etc.) size their inner <svg>
|
|
272
|
+
// from their own width/height attributes, not from CSS on the host —
|
|
273
|
+
// setting only clone.style.width/height left the SVG's actual
|
|
274
|
+
// width="48" height="48" attributes untouched, so nothing visibly
|
|
275
|
+
// changed. Overwrite the attributes themselves for anything that
|
|
276
|
+
// looks like an icon component (custom element) or a raw <svg>.
|
|
277
|
+
clone.setAttribute("width", iconW);
|
|
278
|
+
clone.setAttribute("height", iconH);
|
|
279
|
+
|
|
280
|
+
clone.style.maxWidth = `${iconW}px`;
|
|
281
|
+
clone.style.maxHeight = `${iconH}px`;
|
|
282
|
+
clone.style.width = `${iconW}px`;
|
|
283
|
+
clone.style.height = `${iconH}px`;
|
|
234
284
|
clone.style.objectFit = "contain";
|
|
235
285
|
clone.style.display = "block";
|
|
236
286
|
|
|
237
287
|
const innerImg = clone.querySelector("img");
|
|
238
288
|
if (innerImg) {
|
|
239
|
-
innerImg.style.maxWidth =
|
|
240
|
-
innerImg.style.maxHeight =
|
|
241
|
-
innerImg.style.width =
|
|
242
|
-
innerImg.style.height =
|
|
289
|
+
innerImg.style.maxWidth = `${iconW}px`;
|
|
290
|
+
innerImg.style.maxHeight = `${iconH}px`;
|
|
291
|
+
innerImg.style.width = `${iconW}px`;
|
|
292
|
+
innerImg.style.height = `${iconH}px`;
|
|
243
293
|
innerImg.style.objectFit = "contain";
|
|
244
294
|
innerImg.style.display = "block";
|
|
245
295
|
innerImg.removeAttribute("width");
|
|
@@ -254,6 +304,7 @@ class CardItem extends LitElement {
|
|
|
254
304
|
src="${this.icon}"
|
|
255
305
|
alt="icon"
|
|
256
306
|
class="bd-light-icon-s"
|
|
307
|
+
style="width: ${iconW}px; height: ${iconH}px;"
|
|
257
308
|
/>`;
|
|
258
309
|
}
|
|
259
310
|
|
|
@@ -272,7 +323,8 @@ class CardItem extends LitElement {
|
|
|
272
323
|
}
|
|
273
324
|
|
|
274
325
|
render() {
|
|
275
|
-
const
|
|
326
|
+
const isOutline = this.variant === "outline";
|
|
327
|
+
const isCentered = this.align === "center" || isOutline;
|
|
276
328
|
const hasContent = this._hasContent();
|
|
277
329
|
const hasCtaSlot = !!this.querySelector("[slot='cta']");
|
|
278
330
|
const ctaJustify = this.ctaAlign === "center"
|
|
@@ -285,7 +337,8 @@ class CardItem extends LitElement {
|
|
|
285
337
|
<div
|
|
286
338
|
class="bd-light-box-s
|
|
287
339
|
${isCentered ? "bd-light-box-center" : ""}
|
|
288
|
-
${this.bgBlue ? "bd-light-box-blue" : ""}
|
|
340
|
+
${this.bgBlue ? "bd-light-box-blue" : ""}
|
|
341
|
+
${isOutline ? "bd-light-box-outline" : ""}"
|
|
289
342
|
>
|
|
290
343
|
<div class="bd-light-box-header-s ${isCentered ? "bd-light-box-header-center" : ""}">
|
|
291
344
|
${this._renderIcon()}
|
|
@@ -27,6 +27,13 @@ Grid columns are calculated automatically:
|
|
|
27
27
|
| 7–8 | 4 | 4+3, 4+4 |
|
|
28
28
|
| 9 | 3 | 3+3+3 |
|
|
29
29
|
| 10+ | 4 | 4+4+... |
|
|
30
|
+
|
|
31
|
+
### Variants
|
|
32
|
+
\`variant="outline"\` on \`bd-card-section\` switches every slotted \`bd-card-item\`
|
|
33
|
+
to a bordered, light-blue-tinted, centered card style (blue title, centered
|
|
34
|
+
icon/text) — set it once on the section, it propagates to all items
|
|
35
|
+
automatically. Leaving \`variant\` unset keeps the existing grey-box look.
|
|
36
|
+
An individual \`bd-card-item\` can opt out with \`variant=""\` explicitly.
|
|
30
37
|
`
|
|
31
38
|
}
|
|
32
39
|
}
|
|
@@ -324,6 +331,54 @@ export const MobileView = {
|
|
|
324
331
|
</bd-card-section>
|
|
325
332
|
` };
|
|
326
333
|
|
|
334
|
+
// ─── Outline Variant ────────────────────────────────────────────────────────
|
|
335
|
+
|
|
336
|
+
export const OutlineVariant = {
|
|
337
|
+
name : 'Outline Variant — Trusted For The AI Era',
|
|
338
|
+
render: () => html`
|
|
339
|
+
<bd-card-section
|
|
340
|
+
title="Trusted For The AI Era"
|
|
341
|
+
subtitle="Pioneering AI in Cybersecurity since 2008."
|
|
342
|
+
variant="outline"
|
|
343
|
+
>
|
|
344
|
+
<bd-card-item title="Transparency">
|
|
345
|
+
<bd-individual-icon slot="icon" width="48" height="48" color="#006DFF"></bd-individual-icon>
|
|
346
|
+
<bd-p>We provide clear information about how our AI makes security decisions and what data it uses, balancing technical transparency with usability.</bd-p>
|
|
347
|
+
</bd-card-item>
|
|
348
|
+
<bd-card-item title="Privacy by Design">
|
|
349
|
+
<bd-globe-icon slot="icon" width="48" height="48" color="#006DFF"></bd-globe-icon>
|
|
350
|
+
<bd-p>We implement privacy-preserving techniques like federated learning and differential privacy to minimize data exposure while maintaining AI effectiveness.</bd-p>
|
|
351
|
+
</bd-card-item>
|
|
352
|
+
<bd-card-item title="Human Guided AI Protection">
|
|
353
|
+
<bd-family-icon slot="icon" width="48" height="48" color="#006DFF"></bd-family-icon>
|
|
354
|
+
<bd-p>While our AI is powerful, we augment it with the deep expertise of Bitdefender Labs.</bd-p>
|
|
355
|
+
</bd-card-item>
|
|
356
|
+
</bd-card-section>
|
|
357
|
+
`,
|
|
358
|
+
parameters: { docs: { description: { story: '`variant="outline"` pe `bd-card-section` — se propagă automat la toate cardurile: contur albastru subțire, fundal albastru foarte deschis, titlu albastru, conținut centrat.' } } }
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
export const OutlineVariantMixed = {
|
|
362
|
+
name : 'Outline Variant — Mixed With Default',
|
|
363
|
+
render: () => html`
|
|
364
|
+
<bd-card-section title="Mixed Variants" variant="outline">
|
|
365
|
+
<bd-card-item title="Outline Card One">
|
|
366
|
+
<bd-individual-icon slot="icon" width="48" height="48" color="#006DFF"></bd-individual-icon>
|
|
367
|
+
<bd-p>This card inherits the outline variant from the section.</bd-p>
|
|
368
|
+
</bd-card-item>
|
|
369
|
+
<bd-card-item title="Opted-out Card" variant="">
|
|
370
|
+
<bd-globe-icon slot="icon" width="32" height="32" color="#006DFF"></bd-globe-icon>
|
|
371
|
+
<bd-p>This card explicitly sets variant="" to opt out and keep the default grey-box look.</bd-p>
|
|
372
|
+
</bd-card-item>
|
|
373
|
+
<bd-card-item title="Outline Card Two">
|
|
374
|
+
<bd-machine-learning-icon slot="icon" width="48" height="48" color="#006DFF"></bd-machine-learning-icon>
|
|
375
|
+
<bd-p>This card also inherits the outline variant.</bd-p>
|
|
376
|
+
</bd-card-item>
|
|
377
|
+
</bd-card-section>
|
|
378
|
+
`,
|
|
379
|
+
parameters: { docs: { description: { story: 'Un card poate seta `variant=""` explicit ca să rămână pe stilul default, chiar dacă secțiunea are `variant="outline"`.' } } }
|
|
380
|
+
};
|
|
381
|
+
|
|
327
382
|
// ─── Playground ───────────────────────────────────────────────────────────────
|
|
328
383
|
|
|
329
384
|
export const Playground = {
|
|
@@ -331,13 +386,21 @@ export const Playground = {
|
|
|
331
386
|
args: {
|
|
332
387
|
sectionTitle : 'Why Bitdefender',
|
|
333
388
|
sectionSubtitle: '',
|
|
389
|
+
sectionVariant : '',
|
|
334
390
|
itemTitle : 'AI Protection',
|
|
335
391
|
align : 'start',
|
|
336
392
|
bgBlue : false,
|
|
337
393
|
ctaAlign : 'start'
|
|
338
394
|
},
|
|
395
|
+
argTypes: {
|
|
396
|
+
sectionVariant: {
|
|
397
|
+
control : { type: 'select' },
|
|
398
|
+
options : ['', 'outline'],
|
|
399
|
+
description: 'bd-card-section variant — propagates to all items'
|
|
400
|
+
}
|
|
401
|
+
},
|
|
339
402
|
render: (args) => html`
|
|
340
|
-
<bd-card-section title="${args.sectionTitle}" subtitle="${args.sectionSubtitle}">
|
|
403
|
+
<bd-card-section title="${args.sectionTitle}" subtitle="${args.sectionSubtitle}" variant="${args.sectionVariant}">
|
|
341
404
|
<bd-card-item
|
|
342
405
|
title="${args.itemTitle}"
|
|
343
406
|
align="${args.align}"
|
|
@@ -360,5 +423,5 @@ export const Playground = {
|
|
|
360
423
|
</bd-card-item>
|
|
361
424
|
</bd-card-section>
|
|
362
425
|
`,
|
|
363
|
-
parameters: { docs: { description: { story: 'Playground interactiv
|
|
426
|
+
parameters: { docs: { description: { story: 'Playground interactiv — folosește `sectionVariant` din Controls ca să testezi `variant="outline"`.' } } }
|
|
364
427
|
};
|
|
@@ -4,7 +4,50 @@ export default css`
|
|
|
4
4
|
:host {
|
|
5
5
|
display: block;
|
|
6
6
|
font-family: var(--typography-fontFamily-sans);
|
|
7
|
-
|
|
7
|
+
background: var(--color-blue-50);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/* === DARK VARIANT === */
|
|
11
|
+
:host([variant="dark"]) {
|
|
12
|
+
background: linear-gradient(160deg, var(--color-neutral-800) 0%, #0b1626 100%);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
:host([variant="dark"]) .bd-comparisonpanel-title {
|
|
16
|
+
color: var(--color-neutral-0, #fff);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
:host([variant="dark"]) .bd-comparisonpanel-description {
|
|
20
|
+
color: rgba(255, 255, 255, 0.55);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
:host([variant="dark"]) .bd-comparisonpanel-eyebrow {
|
|
24
|
+
color: var(--color-blue-400, #60a5fa);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
:host([variant="dark"]) .bd-comparisonpanel-column {
|
|
28
|
+
background: rgba(255, 255, 255, 0.03);
|
|
29
|
+
border-color: rgba(255, 255, 255, 0.1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
:host([variant="dark"]) .bd-comparisonpanel-column-title {
|
|
33
|
+
color: rgba(255, 255, 255, 0.5);
|
|
34
|
+
font-family: var(--typography-fontFamily-mono, monospace);
|
|
35
|
+
text-transform: uppercase;
|
|
36
|
+
letter-spacing: 0.08em;
|
|
37
|
+
font-weight: var(--typography-fontWeight-bold, 700);
|
|
38
|
+
font-size: var(--typography-fontSize-xs, 12px);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
:host([variant="dark"]) .bd-comparisonpanel-text {
|
|
42
|
+
color: rgba(255, 255, 255, 0.85);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
:host([variant="dark"]) .bd-comparisonpanel-icon--positive {
|
|
46
|
+
background: var(--color-green-600, #16a34a);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
:host([variant="dark"]) .bd-comparisonpanel-icon--negative {
|
|
50
|
+
background: var(--color-red-500, #ef4444);
|
|
8
51
|
}
|
|
9
52
|
|
|
10
53
|
.bd-comparisonpanel-wrapper {
|
|
@@ -14,6 +57,14 @@ export default css`
|
|
|
14
57
|
box-sizing: border-box;
|
|
15
58
|
}
|
|
16
59
|
|
|
60
|
+
/* Small eyebrow label above the title, e.g. "Clarity" — optional, only
|
|
61
|
+
rendered when the eyebrow prop/slot is used. */
|
|
62
|
+
.bd-comparisonpanel-eyebrow {
|
|
63
|
+
margin: 0 0 var(--spacing-8);
|
|
64
|
+
color: var(--color-blue-500);
|
|
65
|
+
font-weight: var(--typography-fontWeight-bold, 700);
|
|
66
|
+
}
|
|
67
|
+
|
|
17
68
|
.bd-comparisonpanel-title {
|
|
18
69
|
margin: 0 0 var(--spacing-16);
|
|
19
70
|
color: var(--color-neutral-900);
|
|
@@ -11,7 +11,11 @@ class ComparisonPanel extends LitElement {
|
|
|
11
11
|
positiveTitle: { type: String, attribute: "positive-title" },
|
|
12
12
|
negativeTitle: { type: String, attribute: "negative-title" },
|
|
13
13
|
positiveItems: { type: Array }, // [String]
|
|
14
|
-
negativeItems: { type: Array }
|
|
14
|
+
negativeItems: { type: Array }, // [String]
|
|
15
|
+
// "light" (default) — white section, bordered white cards, sans-serif
|
|
16
|
+
// column titles. "dark" — near-black gradient section, dark bordered
|
|
17
|
+
// cards, mono uppercase column titles (e.g. "VPN FOR AI AGENTS IS").
|
|
18
|
+
variant : { type: String, reflect: true }
|
|
15
19
|
};
|
|
16
20
|
|
|
17
21
|
static styles = [tokens, comparisonPanelCSS];
|
|
@@ -24,6 +28,7 @@ class ComparisonPanel extends LitElement {
|
|
|
24
28
|
this.negativeTitle = "";
|
|
25
29
|
this.positiveItems = [];
|
|
26
30
|
this.negativeItems = [];
|
|
31
|
+
this.variant = "light";
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
_renderColumn(kind, colTitle, items, slotName) {
|
|
@@ -9,7 +9,27 @@ export default css`
|
|
|
9
9
|
:host([background="blue"]) {
|
|
10
10
|
background: var(--color-blue-50);
|
|
11
11
|
}
|
|
12
|
+
/* variant="light" + background="bordered": renders its own card border
|
|
13
|
+
and hover lift, so callers don't need a manual wrapper <div> just to
|
|
14
|
+
get the bordered-card look (e.g. side-by-side requirement cards). */
|
|
15
|
+
:host([variant="light"][background="bordered"]) {
|
|
16
|
+
display: block;
|
|
17
|
+
border: 1px solid var(--color-neutral-100, #e5e7eb);
|
|
18
|
+
border-radius: var(--radius-xl, 12px);
|
|
19
|
+
padding: var(--spacing-24, 24px);
|
|
20
|
+
box-sizing: border-box;
|
|
21
|
+
transition: transform 0.25s ease, box-shadow 0.25s ease, border-color 0.25s ease;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
:host([variant="light"][background="bordered"]:hover) {
|
|
25
|
+
transform: translateY(-3px);
|
|
26
|
+
box-shadow: 0 14px 40px #00000012;
|
|
27
|
+
border-color: var(--color-neutral-200);
|
|
28
|
+
}
|
|
12
29
|
|
|
30
|
+
:host([variant="light"][background="bordered"]) .bd-compattable-wrapper {
|
|
31
|
+
padding: 0;
|
|
32
|
+
}
|
|
13
33
|
.bd-compattable-wrapper {
|
|
14
34
|
max-width: var(--layout-ensemble-max-width, 1200px);
|
|
15
35
|
margin: 0 auto;
|
|
@@ -31,7 +51,6 @@ export default css`
|
|
|
31
51
|
max-width: 72ch;
|
|
32
52
|
}
|
|
33
53
|
|
|
34
|
-
/* === TABLE CONTAINER (default / outcome / risk) === */
|
|
35
54
|
.bd-compattable-table-container {
|
|
36
55
|
display: table;
|
|
37
56
|
width: 100%;
|
|
@@ -42,7 +61,6 @@ export default css`
|
|
|
42
61
|
box-sizing: border-box;
|
|
43
62
|
}
|
|
44
63
|
|
|
45
|
-
/* === HEADER ROW === */
|
|
46
64
|
.bd-compattable-header {
|
|
47
65
|
display: grid;
|
|
48
66
|
grid-template-columns: 1.4fr 1.2fr 2.4fr;
|
|
@@ -74,7 +92,6 @@ export default css`
|
|
|
74
92
|
color: var(--color-neutral-600);
|
|
75
93
|
}
|
|
76
94
|
|
|
77
|
-
/* === BODY === */
|
|
78
95
|
.bd-compattable-body {
|
|
79
96
|
display: flex;
|
|
80
97
|
flex-direction: column;
|
|
@@ -141,15 +158,15 @@ export default css`
|
|
|
141
158
|
.bd-compattable-cell--riskcoverage {
|
|
142
159
|
color: var(--color-neutral-900);
|
|
143
160
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
161
|
+
.bd-compattable-lightgrid {
|
|
162
|
+
display: grid;
|
|
163
|
+
grid-template-columns: repeat(var(--bd-compattable-light-columns, 2), 1fr);
|
|
164
|
+
column-gap: var(--spacing-48);
|
|
165
|
+
row-gap: 0;
|
|
166
|
+
}
|
|
167
|
+
.bd-compattable-lightrow:last-of-type {
|
|
168
|
+
border-bottom: none;
|
|
169
|
+
}
|
|
153
170
|
.bd-compattable-lightrow {
|
|
154
171
|
display: flex;
|
|
155
172
|
align-items: baseline;
|
|
@@ -171,7 +188,6 @@ export default css`
|
|
|
171
188
|
text-align: right;
|
|
172
189
|
}
|
|
173
190
|
|
|
174
|
-
/* === RESPONSIVE === */
|
|
175
191
|
@media (max-width: 767px) {
|
|
176
192
|
.bd-compattable-header {
|
|
177
193
|
display: none;
|