@repobit/dex-system-design 0.25.11 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (336) hide show
  1. package/CHANGELOG.md +195 -0
  2. package/README.md +131 -8
  3. package/package.json +95 -13
  4. package/schemas/component-manifest.json +3294 -405
  5. package/schemas/manifest-omissions.json +173 -0
  6. package/src/blog/src/breakpoints.js +6 -6
  7. package/src/blog/src/content/blog-atoms-showcase.js +1 -1
  8. package/src/blog/src/content/blog-page-layout.js +2 -2
  9. package/src/blog/src/lib/article-toc.js +7 -7
  10. package/src/blog/src/shell/blog-shell.js +1 -1
  11. package/src/blog/src/tokens/blog.js +2 -2
  12. package/src/blog/src/tokens/core.js +34 -35
  13. package/src/blog/src/tokens/legacy.js +0 -9
  14. package/src/blog/tokens/blog.js +2 -2
  15. package/src/blog/tokens/core.js +34 -35
  16. package/src/blog/tokens/legacy.js +0 -9
  17. package/src/blog/tokens.css +36 -46
  18. package/src/components/accordion/accordion-bg.css.js +83 -59
  19. package/src/components/accordion/accordion-bg.js +29 -10
  20. package/src/components/accordion/accordion-bg.stories.js +67 -67
  21. package/src/components/accordion/accordion-keyboard.js +80 -0
  22. package/src/components/accordion/accordion.css.js +142 -80
  23. package/src/components/accordion/accordion.js +37 -10
  24. package/src/components/accordion/accordion.stories.js +7 -7
  25. package/src/components/agent-endpoints/agent-endpoints.css.js +36 -39
  26. package/src/components/agent-endpoints/agent-endpoints.js +30 -20
  27. package/src/components/agent-endpoints/agent-endpoints.stories.js +1 -1
  28. package/src/components/ai-guardian-demo.stories.js +91 -91
  29. package/src/components/alert/alert.css.js +198 -0
  30. package/src/components/alert/alert.js +103 -0
  31. package/src/components/alert/alert.stories.js +206 -0
  32. package/src/components/anchor/anchor-nav.css.js +213 -101
  33. package/src/components/anchor/anchor-nav.js +26 -40
  34. package/src/components/anchor/anchor.stories.js +22 -12
  35. package/src/components/annotation/annotation.css.js +58 -0
  36. package/src/components/annotation/annotation.js +67 -0
  37. package/src/components/annotation/annotation.stories.js +63 -0
  38. package/src/components/architecture.stories.js +285 -0
  39. package/src/components/atoms.stories.js +342 -0
  40. package/src/components/avatar/avatar.css.js +67 -0
  41. package/src/components/avatar/avatar.js +42 -0
  42. package/src/components/avatar/avatar.stories.js +95 -0
  43. package/src/components/awards/awards-flex.css.js +209 -23
  44. package/src/components/awards/awards-flex.js +66 -7
  45. package/src/components/awards/awards-flex.stories.js +1 -1
  46. package/src/components/awards/{awards-light.stories.js → awards-summary.stories.js} +54 -64
  47. package/src/components/awards/awards.css.js +116 -58
  48. package/src/components/awards/awards.js +102 -28
  49. package/src/components/awards/awards.stories.js +1 -1
  50. package/src/components/back/back.css.js +51 -22
  51. package/src/components/back/back.js +28 -7
  52. package/src/components/back/back.stories.js +9 -3
  53. package/src/components/badge/badge.css.js +158 -46
  54. package/src/components/badge/badge.js +62 -74
  55. package/src/components/badge/badge.stories.js +176 -248
  56. package/src/components/bento-grid/bento-grid.css.js +21 -16
  57. package/src/components/bento-grid/bento-grid.js +16 -4
  58. package/src/components/bento-grid/bento-grid.stories.js +19 -18
  59. package/src/components/bento-grid/bento-item.css.js +52 -28
  60. package/src/components/bento-grid/bento-item.js +2 -2
  61. package/src/components/blockquote/blockquote.css.js +47 -0
  62. package/src/components/blockquote/blockquote.js +29 -0
  63. package/src/components/blockquote/blockquote.stories.js +62 -0
  64. package/src/components/breadcrumb/breadcrumb.css.js +92 -22
  65. package/src/components/breadcrumb/breadcrumb.js +56 -11
  66. package/src/components/button/button.css.js +531 -161
  67. package/src/components/button/button.js +206 -214
  68. package/src/components/button/button.stories.js +322 -174
  69. package/src/components/caption/caption.css.js +21 -0
  70. package/src/components/caption/caption.js +31 -0
  71. package/src/components/caption/caption.stories.js +62 -0
  72. package/src/components/card-features/card-features.css.js +14 -22
  73. package/src/components/card-features/card-features.js +68 -9
  74. package/src/components/card-features/card-features.stories.js +11 -11
  75. package/src/components/cards/card.css.js +60 -81
  76. package/src/components/cards/card.js +56 -25
  77. package/src/components/cards/card.stories.js +39 -39
  78. package/src/components/carousel/carousel.css.js +93 -99
  79. package/src/components/carousel/carousel.js +32 -4
  80. package/src/components/checkbox/checkbox.css.js +111 -93
  81. package/src/components/checkbox/checkbox.js +64 -90
  82. package/src/components/checkbox/checkbox.stories.js +96 -29
  83. package/src/components/code/code.css.js +70 -0
  84. package/src/components/code/code.js +40 -0
  85. package/src/components/code/code.stories.js +73 -0
  86. package/src/components/compare/compare.css.js +179 -43
  87. package/src/components/compare/compare.js +52 -25
  88. package/src/components/compare/compare.stories.js +61 -4
  89. package/src/components/comparison-panel/comparison-panel.css.js +38 -51
  90. package/src/components/comparison-panel/comparison-panel.js +5 -4
  91. package/src/components/comparison-panel/comparison-panel.stories.js +1 -1
  92. package/src/components/compatibility-table/compatibility-table.css.js +180 -80
  93. package/src/components/compatibility-table/compatibility-table.js +37 -9
  94. package/src/components/control-alignment.stories.js +393 -0
  95. package/src/components/cta-banner/cta-banner.css.js +41 -13
  96. package/src/components/cta-banner/cta-banner.js +6 -5
  97. package/src/components/cta-banner/cta-banner.stories.js +43 -98
  98. package/src/components/display/display.css.js +25 -1
  99. package/src/components/display/display.js +46 -13
  100. package/src/components/display/display.stories.js +270 -40
  101. package/src/components/divider/divider-horizontal.js +21 -5
  102. package/src/components/divider/divider-vertical.js +15 -7
  103. package/src/components/divider/divider.css.js +2 -2
  104. package/src/components/divider/divider.stories.js +38 -34
  105. package/src/components/dropdown/dropdown.css.js +93 -126
  106. package/src/components/dropdown/dropdown.js +114 -34
  107. package/src/components/dropdown/dropdown.stories.js +56 -24
  108. package/src/components/features/features.css.js +24 -11
  109. package/src/components/features/features.js +7 -3
  110. package/src/components/features/features.stories.js +37 -37
  111. package/src/components/features-tab/features-tab.css.js +19 -8
  112. package/src/components/features-tab/features-tab.js +6 -5
  113. package/src/components/features-tab/features-tab.stories.js +23 -23
  114. package/src/components/fieldset/fieldset.css.js +30 -0
  115. package/src/components/fieldset/fieldset.js +28 -0
  116. package/src/components/fieldset/fieldset.stories.js +84 -0
  117. package/src/components/footer/footer-links-group.css.js +10 -14
  118. package/src/components/footer/footer-links-group.js +4 -3
  119. package/src/components/footer/footer-lp.css.js +59 -30
  120. package/src/components/footer/footer-lp.js +17 -12
  121. package/src/components/footer/footer-lp.stories.js +63 -75
  122. package/src/components/footer/footer-nav-menu.css.js +26 -4
  123. package/src/components/footer/footer-nav-menu.js +3 -2
  124. package/src/components/footer/footer.css.js +146 -264
  125. package/src/components/footer/footer.js +176 -106
  126. package/src/components/footer/footer.stories.js +9 -9
  127. package/src/components/footer/localeMap.js +1 -1
  128. package/src/components/grid/grid.css.js +1 -1
  129. package/src/components/grid/grid.js +49 -7
  130. package/src/components/grid/grid.stories.js +111 -0
  131. package/src/components/header/header-code.css.js +61 -51
  132. package/src/components/header/header-code.js +28 -14
  133. package/src/components/header/header-code.stories.js +5 -5
  134. package/src/components/header/header.css.js +655 -176
  135. package/src/components/header/header.js +269 -36
  136. package/src/components/header/header.stories.js +841 -18
  137. package/src/components/heading/heading.css.js +14 -17
  138. package/src/components/heading/heading.js +48 -33
  139. package/src/components/heading/heading.stories.js +27 -25
  140. package/src/components/help-text/help-text.css.js +21 -0
  141. package/src/components/help-text/help-text.js +25 -0
  142. package/src/components/help-text/help-text.stories.js +66 -0
  143. package/src/components/highlight/highlight.css.js +86 -64
  144. package/src/components/highlight/highlight.js +18 -27
  145. package/src/components/highlight/highlight.stories.js +14 -3
  146. package/src/components/icons/analysis-icon.js +6 -16
  147. package/src/components/icons/arrow-down-icon.js +6 -19
  148. package/src/components/icons/arrow-icon.js +44 -0
  149. package/src/components/icons/arrow-right-icon.js +30 -23
  150. package/src/components/icons/arrow-up-icon.js +19 -18
  151. package/src/components/icons/bd-ai-icon.js +1 -1
  152. package/src/components/icons/brand-marks.stories.js +279 -0
  153. package/src/components/icons/brand-signature-icon.js +42 -0
  154. package/src/components/icons/check-icon.js +27 -0
  155. package/src/components/icons/circle-info-icon.js +33 -0
  156. package/src/components/icons/close-icon.js +6 -18
  157. package/src/components/icons/family-icon.js +8 -20
  158. package/src/components/icons/globe-icon.js +6 -18
  159. package/src/components/icons/guide-icon.js +6 -18
  160. package/src/components/icons/icon-element.js +99 -0
  161. package/src/components/icons/icons.js +36 -0
  162. package/src/components/icons/icons.stories.js +184 -52
  163. package/src/components/icons/individual-icon.js +6 -18
  164. package/src/components/icons/laurel-icon.js +9 -19
  165. package/src/components/icons/machine-learning-icon.js +6 -17
  166. package/src/components/icons/marks.js +41 -0
  167. package/src/components/icons/minus-icon.js +7 -21
  168. package/src/components/icons/missing-icon.js +52 -0
  169. package/src/components/icons/plus-icon.js +7 -21
  170. package/src/components/icons/quote-icon.js +19 -19
  171. package/src/components/image/image.css.js +72 -25
  172. package/src/components/image/image.js +11 -6
  173. package/src/components/image/image.stories.js +2 -2
  174. package/src/components/input/custom-form.stories.js +214 -62
  175. package/src/components/input/input-clipboard.css.js +51 -144
  176. package/src/components/input/input-clipboard.js +31 -15
  177. package/src/components/input/input.css.js +8 -146
  178. package/src/components/input/input.js +93 -38
  179. package/src/components/install-options/install-options.css.js +84 -77
  180. package/src/components/install-options/install-options.js +33 -30
  181. package/src/components/install-options/install-options.stories.js +3 -3
  182. package/src/components/label/label.css.js +35 -0
  183. package/src/components/label/label.js +38 -0
  184. package/src/components/label/label.stories.js +82 -0
  185. package/src/components/light-carousel/light-carousel.css.js +111 -63
  186. package/src/components/light-carousel/light-carousel.js +85 -11
  187. package/src/components/light-carousel/light-carousel.stories.js +126 -82
  188. package/src/components/link/link.css.js +150 -99
  189. package/src/components/link/link.js +86 -11
  190. package/src/components/link/link.stories.js +124 -30
  191. package/src/components/list/list-item/list-item.css.js +74 -22
  192. package/src/components/list/list-item/list-item.js +32 -10
  193. package/src/components/list/list-item/list-item.stories.js +23 -23
  194. package/src/components/list/list.css.js +8 -2
  195. package/src/components/list/list.js +3 -2
  196. package/src/components/list/list.stories.js +222 -0
  197. package/src/components/modal/modal.css.js +114 -24
  198. package/src/components/modal/modal.js +83 -7
  199. package/src/components/modal/modal.stories.js +9 -9
  200. package/src/components/overview/overview.css.js +19 -14
  201. package/src/components/overview/overview.js +6 -5
  202. package/src/components/overview/overview.stories.js +10 -10
  203. package/src/components/page-switcher/page-switcher.css.js +97 -40
  204. package/src/components/page-switcher/page-switcher.js +28 -5
  205. package/src/components/page-switcher/page-switcher.stories.js +95 -0
  206. package/src/components/paragraph/paragraph.css.js +15 -10
  207. package/src/components/paragraph/paragraph.js +41 -44
  208. package/src/components/paragraph/paragraph.stories.js +56 -21
  209. package/src/components/picture/picture.css.js +103 -44
  210. package/src/components/picture/picture.js +100 -30
  211. package/src/components/picture/picture.stories.js +1 -1
  212. package/src/components/pricing-cards/new-pricing-card.js +3 -3
  213. package/src/components/pricing-cards/new-pricing.css.js +24 -15
  214. package/src/components/pricing-cards/pricing-card-actions.css.js +1 -1
  215. package/src/components/pricing-cards/pricing-card-actions.js +3 -4
  216. package/src/components/pricing-cards/pricing-card-container.css.js +3 -3
  217. package/src/components/pricing-cards/pricing-card-container.js +3 -2
  218. package/src/components/pricing-cards/pricing-card-header.css.js +10 -8
  219. package/src/components/pricing-cards/pricing-card-header.js +6 -7
  220. package/src/components/pricing-cards/pricing-card-pricing.css.js +16 -9
  221. package/src/components/pricing-cards/pricing-card-pricing.js +8 -6
  222. package/src/components/pricing-cards/pricing-card-show-more.css.js +11 -3
  223. package/src/components/pricing-cards/pricing-card-show-more.js +8 -3
  224. package/src/components/pricing-cards/pricing-card.css.js +244 -190
  225. package/src/components/pricing-cards/pricing-card.js +54 -70
  226. package/src/components/pricing-cards/pricing-feature-item.css.js +25 -8
  227. package/src/components/pricing-cards/pricing-feature-item.js +8 -3
  228. package/src/components/process-steps/process-steps.css.js +26 -14
  229. package/src/components/process-steps/process-steps.js +23 -6
  230. package/src/components/process-steps/process-steps.stories.js +1 -1
  231. package/src/components/progress/progress.css.js +80 -0
  232. package/src/components/progress/progress.js +42 -0
  233. package/src/components/progress/progress.stories.js +79 -0
  234. package/src/components/quick-answer/quick-answer.css.js +75 -13
  235. package/src/components/quick-answer/quick-answer.js +8 -3
  236. package/src/components/radio/radio.css.js +92 -128
  237. package/src/components/radio/radio.js +106 -127
  238. package/src/components/radio/radio.stories.js +54 -19
  239. package/src/components/review/review-grid.css.js +5 -6
  240. package/src/components/review/review-grid.js +14 -11
  241. package/src/components/review/review.css.js +11 -6
  242. package/src/components/review/review.js +3 -8
  243. package/src/components/shared/ensemble.css.js +93 -0
  244. package/src/components/shared/form-field.css.js +206 -0
  245. package/src/components/shared/prop-vocabulary.js +48 -0
  246. package/src/components/shared/token-scales.js +306 -0
  247. package/src/components/skeleton/skeleton.css.js +34 -0
  248. package/src/components/skeleton/skeleton.js +32 -0
  249. package/src/components/skeleton/skeleton.stories.js +70 -0
  250. package/src/components/spinner/spinner.css.js +62 -0
  251. package/src/components/spinner/spinner.js +38 -0
  252. package/src/components/spinner/spinner.stories.js +92 -0
  253. package/src/components/spotlight-section/spotlight-section.css.js +14 -13
  254. package/src/components/spotlight-section/spotlight-section.js +42 -39
  255. package/src/components/switch/switch.css.js +103 -0
  256. package/src/components/switch/switch.js +44 -0
  257. package/src/components/switch/switch.stories.js +86 -0
  258. package/src/components/tab-list/content-switcher.stories.js +189 -0
  259. package/src/components/tab-list/tab-list.css.js +250 -0
  260. package/src/components/tab-list/tab-list.js +184 -0
  261. package/src/components/tab-list/tab-list.stories.js +221 -0
  262. package/src/components/tabs/tabs.css.js +14 -123
  263. package/src/components/tabs/tabs.js +16 -19
  264. package/src/components/tabs/tabs.stories.js +95 -95
  265. package/src/components/tag/tag.css.js +54 -0
  266. package/src/components/tag/tag.js +32 -0
  267. package/src/components/tag/tag.stories.js +126 -0
  268. package/src/components/terminal/terminal-message.css.js +164 -60
  269. package/src/components/terminal/terminal-message.js +5 -5
  270. package/src/components/terminal/terminal-message.stories.js +70 -1
  271. package/src/components/termsOfUse/terms.css.js +25 -20
  272. package/src/components/termsOfUse/terms.js +13 -9
  273. package/src/components/termsOfUse/terms.stories.js +1 -1
  274. package/src/components/textarea/textarea.css.js +51 -0
  275. package/src/components/textarea/textarea.js +136 -0
  276. package/src/components/textarea/textarea.stories.js +191 -0
  277. package/src/components/tooltip/tooltip.css.js +75 -0
  278. package/src/components/tooltip/tooltip.js +156 -0
  279. package/src/components/tooltip/tooltip.stories.js +161 -0
  280. package/src/components/trust-panel/trust-panel.css.js +85 -33
  281. package/src/components/trust-panel/trust-panel.js +18 -10
  282. package/src/components/trust-panel/trust-panel.stories.js +1 -1
  283. package/src/components/ultimate-security-demo.stories.js +431 -0
  284. package/src/components/video/video.css.js +17 -0
  285. package/src/components/video/video.js +40 -0
  286. package/src/components/video/video.stories.js +106 -0
  287. package/src/components/video-player/video-player.css.js +67 -11
  288. package/src/components/video-player/video-player.js +35 -4
  289. package/src/components/video-player/video-player.stories.js +1 -1
  290. package/src/components/visual-alignment.stories.js +411 -0
  291. package/src/components/visually-hidden/visually-hidden.css.js +24 -0
  292. package/src/components/visually-hidden/visually-hidden.js +16 -0
  293. package/src/components/visually-hidden/visually-hidden.stories.js +56 -0
  294. package/src/stories/Button.stories.js +17 -17
  295. package/src/stories/Header.stories.js +10 -10
  296. package/src/stories/Page.js +1 -1
  297. package/src/stories/Page.stories.js +6 -6
  298. package/src/tokens/borders.stories.js +339 -0
  299. package/src/tokens/color-responsibilities.stories.js +227 -0
  300. package/src/tokens/dark-derivation.js +309 -0
  301. package/src/tokens/dark-findings.js +197 -0
  302. package/src/tokens/dark-mode.stories.js +207 -0
  303. package/src/tokens/dimension.stories.js +251 -0
  304. package/src/tokens/elevation.stories.js +143 -0
  305. package/src/tokens/focus.stories.js +276 -0
  306. package/src/tokens/fonts.css +20 -20
  307. package/src/tokens/fonts.stories.js +354 -374
  308. package/src/tokens/layout.css +25 -11
  309. package/src/tokens/motion.stories.js +231 -0
  310. package/src/tokens/shape.stories.js +272 -0
  311. package/src/tokens/spacing-rhythm.stories.js +359 -0
  312. package/src/tokens/spacing.stories.js +208 -703
  313. package/src/tokens/story-chrome.js +503 -0
  314. package/src/tokens/surfaces.stories.js +224 -0
  315. package/src/tokens/text-on-surface.stories.js +164 -0
  316. package/src/tokens/token-map.js +220 -0
  317. package/src/tokens/token-map.stories.js +257 -0
  318. package/src/tokens/token-purposes.js +266 -0
  319. package/src/tokens/tokens-grid.stories.js +388 -900
  320. package/src/tokens/tokens.css +1633 -1045
  321. package/src/tokens/tokens.js +1130 -492
  322. package/src/tokens/tokens.stories.js +137 -47
  323. package/src/tokens/type-ceiling-words.js +299 -0
  324. package/src/tokens/typography.stories.js +1003 -965
  325. package/src/tokens/vertical-rhythm.stories.js +224 -0
  326. package/src/components/awards/awards-icon.js +0 -56
  327. package/src/components/awards/awards-light.js +0 -240
  328. package/src/components/banner/banner.js +0 -60
  329. package/src/components/banner/banner.stories.js +0 -51
  330. package/src/components/button/icons.js +0 -24
  331. package/src/components/input/index.js +0 -0
  332. package/src/components/light-carousel/light-carousel-simple.css.js +0 -183
  333. package/src/components/light-carousel/light-carousel-simple.js +0 -74
  334. package/src/tokens/colors.js +0 -121
  335. package/src/tokens/typography.css.js +0 -207
  336. package/src/tokens/typography.js +0 -0
@@ -1,23 +1,144 @@
1
1
  import { LitElement, html } from "lit";
2
- import { tokens } from "../../tokens/tokens.js";
2
+ // Installs the design tokens into document scope; see src/tokens/tokens.js
3
+ import "../../tokens/tokens.js";
3
4
  import "../button/button.js";
5
+ import "../badge/badge.js";
6
+ import "../icons/brand-signature-icon.js";
4
7
  import headerCSS from "../header/header.css.js";
8
+ import { ensembleCSS } from "../shared/ensemble.css.js";
5
9
  import "../heading/heading.js";
6
10
  import "../paragraph/paragraph.js";
7
11
 
8
12
  class Header extends LitElement {
9
13
  static properties = {
10
- title : { type: String },
11
- subtitle : { type: String },
12
- description : { type: String },
13
- fullPrice : { type: String },
14
- discount : { type: String },
15
- finalPrice : { type: String },
16
- productId : { type: String },
17
- devices : { type: String },
18
- subscription : { type: String },
19
- shadow : { type: Boolean, reflect: true },
20
- dataLayerEvent: { type: String }
14
+ title : { type: String },
15
+ subtitle : { type: String },
16
+ description : { type: String },
17
+ /* The outline level of the headline. `h1` by default, because that is what
18
+ a product hero is on the page it leads, and every current consumer wants
19
+ it -- so nothing has to change to keep the behaviour this component
20
+ already had.
21
+
22
+ It exists because the level is not this component's call. CLAUDE.md is
23
+ unambiguous: `as=` is the document outline and the composing page owns
24
+ it, `visual-level=` is the type role and the component owns that. This
25
+ component used to hardcode `as="h1"` and expose no way to say otherwise,
26
+ which took a page-level decision away from the page. Decided 2026-09-17:
27
+ the consuming dev team and the SEO team set the heading levels on this
28
+ section, so the component has to let them.
29
+
30
+ `visual-level="h1"` is PINNED in the template, deliberately, and that is
31
+ the half worth understanding. Passing a different level moves the
32
+ heading in the outline and changes nothing about how it looks: the hero
33
+ stays 48/700/1.25 at `heading-h1`, because the size is the design's and
34
+ only the level is the page's. Without the pin, `bd-h` follows `as` for
35
+ both, so `heading-level="h3"` would have quietly rendered the hero at
36
+ the h3 role instead -- measured 32px at 1024 and 24px at 320, against
37
+ 48 and 30 -- an SEO decision repainting the design.
38
+
39
+ Anything that is not h1-h6 (or a bare 1-6) falls through bd-h's own
40
+ normalisation to h2 rather than h1: still a heading, never the page's.
41
+ That fallback is heading.js's, not repeated here. */
42
+ headingLevel : { type: String, attribute: "heading-level" },
43
+ /* THE TWO CTA LABELS, and the secondary's destination. Props, not slots,
44
+ and they default to the strings this component has always rendered --
45
+ so no consumer changes and every consumer can now change them.
46
+
47
+ This is the half of the CTA question that was ours. The other half is
48
+ not: Header / v2 reads "Get Protection" and "Start your 30-day trial"
49
+ where these say "Buy now" and "View more plans", and which words sell a
50
+ subscription is purchase-funnel copy rather than a design-system value.
51
+ Until 2026-09-17 there was no way for the page to answer it even after
52
+ someone decided -- the strings were literals in this template. Now the
53
+ decision lands in markup, where copy belongs, and the default is only
54
+ what the component happened to ship.
55
+
56
+ PROPS RATHER THAN SLOTS, deliberately. A label is a plain string with no
57
+ markup in it, which is what `title` and `subtitle` already are; props
58
+ appear in schemas/component-manifest.json, which is the contract a page
59
+ generator is handed, and a slot does not. Adding both would give one
60
+ value two sources that can disagree -- the reason the three price props
61
+ were deleted rather than wired up.
62
+
63
+ THE SECONDARY CTA HAS NO DESTINATION UNTIL A PAGE GIVES IT ONE, and
64
+ that is not new: it never had one. bd-button-link drops the href
65
+ attribute entirely when href is empty, so the secondary has always
66
+ rendered an <a> with no href -- which is not focusable, is not a link in
67
+ the accessibility tree, and does nothing when clicked. The primary is
68
+ fine: `buyLink` makes the store inject the buy URL at runtime. So
69
+ cta-secondary-href is the fix for a dead control, not a nicety, and
70
+ there is no default because a URL is content this package cannot
71
+ invent. */
72
+ ctaLabel : { type: String, attribute: "cta-label" },
73
+ ctaSecondaryLabel: { type: String, attribute: "cta-secondary-label" },
74
+ ctaSecondaryHref : { type: String, attribute: "cta-secondary-href" },
75
+ /* fullPrice, discount and finalPrice USED TO BE DECLARED HERE and are gone.
76
+ Nothing read them -- not this template, not this stylesheet, not the
77
+ store. The three price zones below are store-driven
78
+ (data-store-render with data-store-price / data-store-discount), and the
79
+ store computes the numbers from product-id, devices and subscription; it
80
+ never looks at a host property. So setting full-price="$159.99" did
81
+ exactly nothing, in every consumer, since the props were added.
82
+
83
+ Removed rather than wired up, and that is the decision worth recording.
84
+ Wiring them would give the same value two sources that can disagree --
85
+ the store's computed price and a hardcoded string -- with no answer to
86
+ which wins when both are present. The price belongs to the store; the
87
+ API for it is product-id plus devices plus subscription.
88
+
89
+ It matters more than a dead prop usually would because the manifest is
90
+ the contract a page generator is handed, and check-manifest.mjs's own
91
+ header says a stale entry is worse than a missing one: the model emits
92
+ props that do not exist. All three were listed there as if they worked.
93
+
94
+ The one consumer was packages/dex-system-design/index.html, Vite's dev
95
+ scratch page, which sets all three, loads no store, and has therefore
96
+ never displayed a price. It demonstrated the bug rather than a need, and
97
+ it is updated in the same commit. */
98
+ /* `product-id`, the spelling this component documents and the only one
99
+ anything writes. Lit derives an observed attribute by lowercasing the
100
+ property name rather than dash-casing it, so `productId` was listening
101
+ for `productid` while header.stories.js writes `product-id` in seven
102
+ places -- including its own usage example and its argTypes-driven
103
+ playground. The attribute was inert and the property stayed on its
104
+ "vsb" default: measured on Components/Header "Different Product IDs",
105
+ all three headers reported productId "vsb" while their markup asked for
106
+ "av", "ts" and "ps", so the same product was handed to <bd-product> for
107
+ every price lookup on the page.
108
+
109
+ Zero consumers of the working lowercase spelling: grepped `productid`
110
+ and `productId=` as an attribute across src, schemas and docs -- none,
111
+ and no `.productId=` property binding either. So naming the attribute
112
+ breaks nothing. Same root cause and same one-line repair as bd-grid's
113
+ cols-sm and bd-picture's fallback-src in P7H. */
114
+ productId : { type: String, attribute: "product-id" },
115
+ devices : { type: String },
116
+ subscription : { type: String },
117
+ /* NOT styling, despite what this was documented as for a long time. It is
118
+ the store's opt-in for walking a shadow root.
119
+ @repobit/dex-store-elements' renders/index.ts selects shadow hosts with
120
+ SHADOW_HOST_SELECTOR = '[shadow], [data-bd-watch-shadow]', so an element
121
+ that does not reflect this attribute never has its shadow tree searched
122
+ for [data-store-render] or [data-store-hide] nodes -- and every price
123
+ zone in this component is inside the shadow root. Reflected and
124
+ defaulting to true for that reason, and load-bearing: drop it and the
125
+ prices silently stop rendering on a real page.
126
+
127
+ Nearly deleted as inert during the prop audit, because nothing in this
128
+ component or its stylesheet reads it. The consumer is another package. */
129
+ shadow : { type: Boolean, reflect: true },
130
+ dataLayerEvent : { type: String },
131
+ /* Whether the header-picture slot has anything in it. Internal state, not
132
+ an attribute: the consumer says this by slotting a picture or not, and a
133
+ second way to say it could only ever disagree with the first.
134
+
135
+ It exists because the scrim is only correct over an image. It is a
136
+ blue-950 band, and the NoImage story is a documented state -- rendering
137
+ the band there would paint a blue wash across the bottom of an empty
138
+ column, and the mark would sit on the page ground in text-inverse,
139
+ which is white on white. Nothing in CSS can ask whether a slot received
140
+ anything, so the slot is asked directly. */
141
+ _hasPicture : { state: true }
21
142
  };
22
143
 
23
144
  constructor() {
@@ -25,17 +146,30 @@ class Header extends LitElement {
25
146
  this.title = "";
26
147
  this.subtitle = "";
27
148
  this.description = "";
149
+ this.headingLevel = "h1";
150
+ this.ctaLabel = "Buy now";
151
+ this.ctaSecondaryLabel = "View more plans";
152
+ this.ctaSecondaryHref = "";
28
153
  this.productId = "vsb";
29
154
  this.devices = "5";
30
155
  this.subscription = "1";
31
156
  this.shadow = true;
32
157
  this.dataLayerEvent = "info";
158
+ this._hasPicture = false;
33
159
  }
34
160
 
35
161
  // attributeChangedCallback(name, oldVal, newVal) {
36
162
  // super.attributeChangedCallback(name, oldVal, newVal);
37
163
  // }
38
164
 
165
+ /* assignedElements rather than assignedNodes: a slot fed nothing but
166
+ whitespace from the consumer's markup still reports text nodes, and that
167
+ is not a picture. flatten so a picture arriving through another slot in a
168
+ composed tree still counts. */
169
+ _pictureSlotChanged(event) {
170
+ this._hasPicture = event.target.assignedElements({ flatten: true }).length > 0;
171
+ }
172
+
39
173
  render() {
40
174
  return html`
41
175
  <bd-context ignore-events-parent>
@@ -45,49 +179,141 @@ class Header extends LitElement {
45
179
  subscription="${this.subscription}"
46
180
  data-layer-event="${this.dataLayerEvent}">
47
181
 
48
- <div class="bd-container">
182
+ <div class="bd-ensemble bd-container">
49
183
  <div class="bd-header-left">
50
- <bd-h as="h2" class="bd-product-name">${this.title}</bd-h>
51
- <bd-h as="h4" class="bd-value-prop">${this.subtitle}</bd-h>
52
- <bd-p kind="regular" class="bd-description">${this.description}</bd-p>
184
+ <!-- ONE HEADING HERE, AND THE EYEBROW IS NOT IT.
185
+
186
+ What this replaces: the product name was as="h2" and the
187
+ headline as="h4", so the outline ran h2 then h4 in that DOM
188
+ order -- an eyebrow announced as a landmark, above the actual
189
+ promise announced two levels below it. Every page rendering
190
+ this header carried that.
191
+
192
+ The headline is the page's main heading, so it is h1 --
193
+ h1 by DEFAULT now rather than hardcoded, see headingLevel
194
+ above. This component is the product hero and there is one per
195
+ page, which is the condition an h1 needs, so h1 is the right
196
+ default; the page can still say otherwise. bd-h's h1 role is
197
+ 5xl/bold/tight -- 48/700/1.25 -- which is Header / v2's
198
+ value-prop exactly, which is why visual-level is pinned to h1
199
+ and not left to follow as: the two agree at the default, and
200
+ when a page separates them the design keeps the size.
201
+
202
+ The product name is a kicker -- a label naming the product, and
203
+ in Figma a plain text layer called product-name rather than a
204
+ heading. So it is a <p>, out of the heading list entirely, and
205
+ it resolves through the heading-h4 ROLE in header.css.js, which
206
+ is where a role on a non-heading element belongs. A kicker in
207
+ the outline is worse than no kicker in the outline: it adds a
208
+ rung nobody navigates by and pushes the real heading down.
209
+
210
+ THE ALTERNATIVE, and why not. The other accessible pattern is
211
+ to put both inside the h1 -- <h1><span>Bitdefender Ultimate
212
+ Security</span><span>Your Ultimate Digital Protection</span>
213
+ </h1> -- which gives the heading an accessible name carrying
214
+ the product as well as the promise. It was rejected on two
215
+ counts: every heading list, skip link and rotor entry would
216
+ then read the whole run, and it would mean rendering the h1
217
+ here by hand instead of through bd-h, since the atom takes one
218
+ slot. The product is named by the document title, the price
219
+ block and the CTA; the heading does not have to carry it. -->
220
+ <p class="bd-product-name">${this.title}</p>
221
+ <bd-h
222
+ as="${this.headingLevel}"
223
+ visual-level="h1"
224
+ class="bd-value-prop">${this.subtitle}</bd-h>
225
+ <bd-p variant="regular" class="bd-description">${this.description}</bd-p>
53
226
 
54
227
  <div class="bd-price-zone-top" data-store-hide="!it.option.price.discounted">
55
- <span class="bd-full-price" data-store-render data-store-price="full"></span>
56
- <span class="bd-discount" data-store-render data-store-discount="percentage"></span>
228
+ <!-- font-weight="medium", the atom's own attribute rather than a
229
+ declaration out in the stylesheet. Header / v2 sets the struck
230
+ price at 16 Medium, and a weight on a bd-p HOST is inert --
231
+ the atom sets all five type axes on its inner element -- so
232
+ the only way in from outside is the attribute, which
233
+ paragraph.js resolves through the weight scale and applies to
234
+ that inner element. Measured before adding it: 400. -->
235
+ <bd-p variant="regular" font-weight="medium" class="original-price">
236
+ <s data-store-render data-store-price="full"></s>
237
+ </bd-p>
238
+ <!-- bd-badge, not the span this used to be. Header / v2 places the
239
+ Badge component here at tone=success variant=fill size=lg, and
240
+ the atom's own lg step is that spec exactly: 8/12 padding, the
241
+ control-label size, a full radius. The span it replaces was a
242
+ hand-rolled badge and a DIFFERENT one -- green-100 behind
243
+ text/success, which is the atom's shade variant, at 16px
244
+ semibold in a --radius-xl box. Its two "no role:" markers went
245
+ with it: they existed to say "this should be a bd-badge", and
246
+ the answer to that is a bd-badge. -->
247
+ <bd-badge
248
+ tone="success"
249
+ variant="fill"
250
+ size="lg"
251
+ class="bd-discount"
252
+ data-store-render
253
+ data-store-discount="percentage"
254
+ ></bd-badge>
57
255
  </div>
58
256
 
59
257
  <span class="bd-price-zone-final" data-store-render data-store-price="discounted||full"></span>
60
258
 
61
259
  <div class="bd-cta">
260
+ <!-- size="lg", and no font-size override. Header / v2 draws both
261
+ CTAs at 16/32 padding with an 18px label in an 8px-radius box,
262
+ and --button-large-* is that set exactly: spacing-16/32,
263
+ fontSize-lg, radius-lg. These were size="md" (12/24, 16px,
264
+ radius-md) with font-size="lg" bolted on top, so the label was
265
+ the large step's and the box was the medium step's -- 18px type
266
+ in a box padded for 16px type. Dropping the override puts the
267
+ whole control back on one answer.
268
+
269
+ THE LABELS come from cta-label and cta-secondary-label as of
270
+ 2026-09-17, defaulting to the strings this component always
271
+ rendered. Which words sell a subscription is still not ours
272
+ -- see the prop declarations above -- but the page can now
273
+ act on the answer without editing this file.
274
+
275
+ NO label ATTRIBUTE ON EITHER. On bd-button-link, label
276
+ is the accessible name and the slot is the visible text, and
277
+ these passed the same string to both -- so the anchor
278
+ carried an aria-label duplicating its own content. Dropping
279
+ it lets the accessible name come from the text, which is one
280
+ source instead of two saying the same thing.
281
+
282
+ The secondary's href is empty by default and the atom then
283
+ renders an anchor with no href at all. Measured on
284
+ Components/Header -> Design copy & a live secondary: it
285
+ refuses focus and matches neither a[href] nor :enabled, so
286
+ it is not focusable, not a link to a screen reader, and does
287
+ nothing on click. It has been that way since it was written.
288
+ Set cta-secondary-href.
289
+
290
+ In Storybook the PRIMARY is inert too, for the same reason
291
+ the price zones are empty there -- no store to inject an
292
+ href. On a real page the store fills the primary in and
293
+ never touches the secondary. -->
62
294
  <bd-button-link
63
- label="Buy now"
64
- kind="danger"
65
- size="md"
295
+ variant="accent"
296
+ size="lg"
66
297
  part="buy-button"
67
- font-size="18px"
68
- font-weight="600"
69
298
  buyLink
70
- >Buy now</bd-button-link
299
+ >${this.ctaLabel}</bd-button-link
71
300
  >
72
301
  <bd-button-link
73
- label="View more plans"
74
- kind="primary-outline"
75
- size="md"
76
- font-size="18px"
77
- font-weight="600"
302
+ href="${this.ctaSecondaryHref}"
303
+ variant="tertiary"
304
+ size="lg"
305
+ >${this.ctaSecondaryLabel}</bd-button-link
78
306
  >
79
- View more plans
80
- </bd-button-link>
81
307
  </div>
82
308
 
83
- <bd-p kind="small" class="bd-disclaimer-top">
309
+ <bd-p variant="xsmall" class="bd-disclaimer-top">
84
310
  <slot name="disclaimer-top">
85
311
  Protection for 1 account & 5 PCs, Macs, tablets, or smartphones.
86
312
  Windows® | macOS® | Android™ | iOS®
87
313
  </slot>
88
314
  </bd-p>
89
315
 
90
- <bd-p kind="small" class="bd-disclaimer-bottom">
316
+ <bd-p variant="xsmall" class="bd-disclaimer-bottom">
91
317
  <slot name="disclaimer-bottom-text-start"></slot>
92
318
  <slot name="disclaimer-bottom-link"></slot>
93
319
  <slot name="disclaimer-bottom-text-end"></slot>
@@ -96,8 +322,15 @@ class Header extends LitElement {
96
322
 
97
323
  <div class="bd-header-right">
98
324
  <div class="bd-header-image-wrapper">
99
- <slot name="header-picture"></slot>
100
- <div class="bd-header-watermark"><span>Trusted. Always.</span></div>
325
+ <slot name="header-picture" @slotchange=${this._pictureSlotChanged}></slot>
326
+ ${this._hasPicture
327
+ ? html`
328
+ <div class="bd-header-scrim" aria-hidden="true"></div>
329
+ <bd-brand-signature-icon
330
+ class="bd-header-signature"
331
+ ></bd-brand-signature-icon>
332
+ `
333
+ : null}
101
334
  </div>
102
335
  </div>
103
336
  </div>
@@ -108,6 +341,6 @@ class Header extends LitElement {
108
341
  }
109
342
  }
110
343
 
111
- Header.styles = [tokens, headerCSS];
344
+ Header.styles = [ensembleCSS, headerCSS];
112
345
 
113
346
  customElements.define("bd-header", Header);