@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,57 +1,134 @@
1
- import { css } from "lit";
1
+ import { css, unsafeCSS } from "lit";
2
+ import { below } from "../shared/token-scales.js";
2
3
 
3
4
  export default css`
4
5
  :host {
5
6
  display: block;
6
- background: white;
7
- --bd-accesibility-focus: var(--color-blue-500);
8
- padding-top: 32px;
9
- padding-bottom: 32px;
10
- }
11
-
12
- a:focus-visible {
13
- outline: var(--spacing-2) solid var(--bd-accesibility-focus);
14
- outline-offset: var(--spacing-2);
15
- border-radius: var(--space-2xs);
16
- }
17
- a {
18
- color: #000000 !important;
19
- }
20
- ::slotted(a),
21
- ::slotted(*) a {
22
- color: #000000 !important;
23
- }
24
-
25
- ::slotted([slot="disclaimer-bottom"]) a {
26
- color: #000000 !important;
27
- }
28
-
29
- ::slotted([slot="header-image"]) {
7
+ /* --color-surface-default, the role, not --color-neutral-0, the primitive
8
+ it resolves to. Same value; the category is the point, and CLAUDE.md's
9
+ "same primitive value is not same semantic meaning" section exists for
10
+ exactly this. Found by check:color-coverage once the background shorthand came into
11
+ its scope -- it had been invisible because the check counted
12
+ background-color: and this package almost never writes that. */
13
+ background: var(--color-surface-default);
14
+ /* --spacing-16, and the number changed as well as the spelling. It was a
15
+ bare 32px on no scale; Header / v2's chosen configuration pads the hero
16
+ row 16 top and bottom. That 8px per side is not cosmetic -- it is the
17
+ line of headroom the height study bought to get the header under 600,
18
+ and the findings note says so outright: "drop the frame padding 24 -> 16,
19
+ which lands 562px and leaves 38px". The text column takes its own 24
20
+ back below (.bd-header-left), so the copy still breathes; what got
21
+ thinner is the band around the media panel, which needed nothing. */
22
+ padding-top: var(--spacing-16);
23
+ padding-bottom: var(--spacing-16);
24
+ }
25
+
26
+ /* ::slotted, not a bare element selector. This header renders no anchor of its own -- every
27
+ link arrives through a slot -- so a descendant selector never reached one,
28
+ and slotted links fell back to the browser's own ring. The colour rule two
29
+ rules down already had this right. ::slotted only reaches a directly
30
+ slotted anchor, which is what the disclaimer link is; an anchor nested
31
+ inside a slotted wrapper is the consuming page's to style.
32
+
33
+ The pseudo-class goes INSIDE the parentheses. ::slotted() takes a compound
34
+ selector as its argument, so "::slotted(a):focus-visible" is not a valid
35
+ selector -- the parser drops the whole rule silently, which is the same
36
+ failure mode as the bare descendant selector this replaced. */
37
+ ::slotted(a:focus-visible) {
38
+ /* the one focus ring — see Foundations/Focus */
39
+ outline: none;
40
+ box-shadow: var(--focus-outline-primary);
41
+ border-radius: var(--focus-outline-radius);
42
+ }
43
+ /* ::slotted(a) only. A bare element rule stood beside this one and matched
44
+ nothing: this header renders no anchor of its own, which is the same
45
+ reason its focus rule had to move to ::slotted in P5G.1. ::slotted(*) a
46
+ went too -- ::slotted() must be the last compound in a selector, so a
47
+ descendant after it never matches either. An anchor nested inside a
48
+ slotted wrapper is the consuming page's to colour. */
49
+ ::slotted(a) {
50
+ color: var(--color-text-primary) !important;
51
+ }
52
+
53
+ /* ::slotted([slot="disclaimer-bottom"]) a stood here and could never match.
54
+ There is no disclaimer-bottom slot: header.js renders
55
+ disclaimer-bottom-text-start, disclaimer-bottom-link and
56
+ disclaimer-bottom-text-end. The link is handled by
57
+ ::slotted([slot="disclaimer-bottom-link"]) at the end of this sheet, which
58
+ is the rule that actually applies -- and a descendant after ::slotted()
59
+ never matches anyway, since ::slotted() must be the last compound in a
60
+ selector, the same reason already recorded for ::slotted(*) a below. */
61
+
62
+ /* header-picture, which is the slot header.js renders. This rule was written
63
+ for [slot="header-image"] and so has never matched anything: the product
64
+ image was left entirely unsized, rendering at its natural 3500x2338 and
65
+ cropped to whatever fitted by the wrapper's overflow: hidden. At 375 that
66
+ showed a 124px-wide vertical slice of a 3500px photograph.
67
+
68
+ Four of the five slot names this stylesheet targets do not exist --
69
+ header-image, right, extra-content and disclaimer-bottom -- against the
70
+ slots header.js renders: disclaimer-top,
71
+ disclaimer-bottom-text-start/-link/-text-end and header-picture. Only the
72
+ one fixed here is load-bearing for layout; the rest are in the report. */
73
+ /* A REAL COVER FILL, not a zoom. This said height: auto with
74
+ transform: scale(1.35) translateY(-7%), which is how a wide photograph was
75
+ made to fill a tall box before the column stretched: blow it up 135%,
76
+ shove it up 7%, and let the wrapper's overflow: hidden crop whatever
77
+ misses. Two things are wrong with that. It throws away a third of the
78
+ image's resolution for nothing -- object-fit: cover already crops, and
79
+ crops without magnifying -- and the crop it lands on is a magic pair of
80
+ numbers that moves with the viewport, so which part of the photo a
81
+ consumer sees was never a decision anyone made.
82
+ .
83
+ height: 100% is available because .bd-container stretches both columns to
84
+ the taller one, so the wrapper has a definite height to fill. Header / v2
85
+ shows a plain 613-wide media panel filled edge to edge with no transform,
86
+ which is what this now is.
87
+ .
88
+ It also matters to the scrim: the band and the mark are positioned against
89
+ the WRAPPER, while a transformed image is not where the wrapper is. The
90
+ mark sat 24px from the wrapper's corner and the image's own corner was
91
+ somewhere else. */
92
+ ::slotted([slot="header-picture"]) {
30
93
  width: 100%;
31
- height: auto;
94
+ height: 100%;
32
95
  object-fit: cover;
33
- transform: scale(1.35) translateY(-7%);
34
96
  display: block;
35
- z-index: 1;
97
+ z-index: var(--z-base);
36
98
  position: relative;
37
- border-radius: var(--spacing-20);
99
+ border-radius: var(--radius-2xl);
38
100
  }
39
101
 
40
102
  .bd-container {
41
103
  display: grid;
42
- grid-template-columns: 1fr 1fr;
43
- align-items: center;
44
- gap: 30px;
45
- padding: 0 240px;
46
- margin: 0 auto;
104
+ /* minmax(0, 1fr), not 1fr: a bare 1fr floors each column at its own
105
+ min-content width, which held .bd-header-left at 201px however narrow
106
+ the screen got and pushed the right-hand column 29px past a 320px
107
+ viewport. The columns still share the space equally; they are simply
108
+ allowed to go below their content's natural width, which is what lets
109
+ the text inside wrap instead of the layout overflowing. */
110
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
111
+ /* --spacing-64, which is the 64px gap between the two 613px columns in
112
+ Header / v2. It was 30px: on no spacing step at all (28 and 32 are the
113
+ neighbours) and 34px short of the design. */
114
+ gap: var(--spacing-64);
115
+ /* the page column (bd-ensemble), not a 240px gutter: a fixed gutter made the
116
+ hero the one section whose content width depended on the viewport --
117
+ 960 wide at 1440, 1440 wide at 1920 -- while every other module capped
118
+ itself */
47
119
  align-items: stretch;
48
120
  }
49
121
 
50
- bd-button {
51
- font-size: var(--heading-xxsmall) !important;
52
- font-family: var(--typography-fontFamily-sans) !important;
53
- font-weight: var(--typography-fontWeight-semibold) !important;
122
+ /* The content column's own inset, and the second half of the padding change
123
+ on :host. Header / v2 nests a 24-padded content frame inside a 16-padded
124
+ row, so the copy sits 40 from the top of the section while the media panel
125
+ sits 16 -- the image is meant to be the taller of the two. Before this the
126
+ column had no padding and .bd-product-name carried a 16px top margin
127
+ instead, which put the title 48 down and the image 32. */
128
+ .bd-header-left {
129
+ padding-block: var(--spacing-24);
54
130
  }
131
+
55
132
  .bd-header-left,
56
133
  .bd-header-right {
57
134
  flex: 1;
@@ -65,88 +142,248 @@ export default css`
65
142
  position: relative;
66
143
  }
67
144
 
145
+ /* .bd-header-image is declared twice in this sheet, and this is the first of
146
+ the two -- kept for the border-radius, the stacking context and the
147
+ position, which the second does not set. Its width/height/object-fit/
148
+ display went, because the second rule sets all four and wins on order, so
149
+ this copy said height: auto while the rendered image measured 100%. Two
150
+ rules for one class disagreeing about the same property is worth fixing at
151
+ the point one of them starts lying. */
68
152
  .bd-header-image {
69
- width: 100%;
70
- height: auto;
71
- object-fit: cover;
72
- display: block;
73
- border-radius: var(--spacing-20);
153
+ border-radius: var(--radius-2xl);
74
154
  position: relative;
75
- z-index: 1;
76
- }
155
+ z-index: var(--z-base);
156
+ }
157
+ /* ---- the vertical rhythm of the content column --------------------------
158
+ Header / v2's chosen configuration nests four auto-layout frames, and the
159
+ gaps below are theirs read top to bottom:
160
+
161
+ content gap 16 product-name -> body
162
+ body gap 16 copy -> price -> actions
163
+ copy gap 8 headline -> description
164
+ actions gap 8 CTA row -> disclaimer
165
+
166
+ The 16/8/8 triple is not arbitrary and is not a rhythm anyone chose for
167
+ its own sake -- it is variant I from the height study, the one
168
+ configuration that measured the same 578px in all four languages tested.
169
+ Tightening copy and actions to 8 is where most of the 94px came from.
170
+
171
+ These are margins rather than a flex/grid gap because the column is a
172
+ plain block flow and the atoms carry their own flow margins; a gap would
173
+ mean also cancelling those. The !important is inherited from the rules
174
+ these replace, and it earns its keep on exactly one of them: bd-h's inner
175
+ element keeps the UA heading margins, which are not otherwise reachable
176
+ from out here. */
177
+ /* The kicker. It is a <p> rather than a bd-h -- see the note in header.js --
178
+ so this is the one block of text in this component whose type the
179
+ stylesheet has to declare, and it declares the ROLE rather than four
180
+ primitives: heading-h4 is 2xl / semibold / 130 / normal tracking, which is
181
+ what Header / v2 gives the product name.
182
+
183
+ A heading role on a non-heading element is the right way round, not a
184
+ compromise. CLAUDE.md's rule is to name the job and let the role decide
185
+ the value, and the job here is "the most prominent label on the section" --
186
+ there is no 24px label role, and picking body-lead at 20 because it is
187
+ nearer the body would be choosing by number. What the element is NOT is a
188
+ heading, and that is settled by the tag, not by the role's name.
189
+
190
+ text/brand is declared here rather than passed as bd-h's color="primary"
191
+ because there is no atom in the way any more, so the declaration lands on
192
+ the rendered element directly.
193
+
194
+ No !important. The two rules this replaces both carried it, inherited from
195
+ an older version of the file; on a <p> the only thing to beat is the UA
196
+ margin, which a plain declaration already does. The bd-h below still needs
197
+ it, because its inner element keeps the UA heading margins and they are
198
+ not otherwise reachable from out here. */
77
199
  .bd-product-name {
78
- color: var(--color-blue-500);
79
- font-family: var(--typography-fontFamily-sans);
80
- font-weight: var(--typography-fontWeight-semibold);
81
- font-size: var(--typography-fontSize-3xl);
82
- letter-spacing: var(--spacing-0);
83
- margin-top: var(--spacing-16) !important;
84
- margin-bottom: var(--spacing-8) !important;
200
+ font-size: var(--typography-heading-h4-fontSize);
201
+ font-weight: var(--typography-heading-h4-fontWeight);
202
+ line-height: var(--typography-heading-h4-lineHeight);
203
+ letter-spacing: var(--typography-heading-h4-letterSpacing);
204
+ font-family: var(--typography-heading-h4-fontFamily);
205
+ color: var(--color-text-brand);
206
+ margin-top: var(--spacing-0);
207
+ margin-bottom: var(--spacing-16);
85
208
  }
86
209
 
210
+ /* The headline's type does NOT step down on narrow viewports, and that is a
211
+ limit rather than a decision. bd-h's type lives on its inner element
212
+ inside its shadow root, so a font-size out here is inert
213
+ (scripts/atom-inert-properties.json), and neither bd-h nor bd-p carries a
214
+ media query -- no atom in this package changes its type with the viewport.
215
+ Measured, the 48px headline runs two lines down to 500px, three at 375 and
216
+ four at 320, where the section reaches 1066px.
217
+ .
218
+ Two ways out, both bigger than this change. Give bd-h responsive roles, or
219
+ switch visual-level from JS the way bd-footer's _isMobile already reads the
220
+ viewport through matchesBelow(). The second is the package's sanctioned
221
+ escape hatch and would need a step to move to: heading-h2 is 36/bold, which
222
+ is also the final price's role, so at 36 the headline and the price would
223
+ be the same size and the hierarchy the semibold kicker was added to sharpen
224
+ would flatten. That is a design question, not a CSS one. */
87
225
  .bd-value-prop {
88
- font-family: var(--typography-fontFamily-sans);
89
- font-weight: var(--typography-fontWeight-bold);
90
- font-size: var(--heading-xxlarge);
91
- color: #000000;
92
- letter-spacing: var(--spacing-0);
93
- margin: var(--spacing-0) var(--spacing-0) !important;
226
+ margin: var(--spacing-0) !important;
94
227
  }
95
228
 
96
229
  .bd-description {
97
- font-family: var(--typography-fontFamily-sans);
98
- font-weight: var(--typography-body-large-fontWeight);
99
- font-size: var(--spacing-18);
100
- color: #000000;
230
+ /* LIVE, not inert: scripts/atom-inert-properties.json records colour on a
231
+ bd-p host as reaching the text, unlike the five type axes. */
232
+ color: var(--color-text-primary);
101
233
  margin-top: var(--spacing-8) !important;
102
234
  margin-bottom: var(--spacing-0) !important;
103
235
  }
104
236
 
105
237
  .bd-price-zone-top {
106
238
  display: flex;
239
+ /* 8, which is the price-was frame's gap between the struck price and the
240
+ discount badge. */
107
241
  gap: var(--spacing-8);
108
242
  align-items: center;
109
- margin-top: var(--spacing-32);
110
- }
111
-
112
- .bd-full-price {
113
- text-decoration: line-through;
114
- color: #000000;
115
- font-size: var(--spacing-18);
116
- font-family: var(--typography-fontFamily-sans);
243
+ /* the body frame's gap, 16. It was 32 -- the as-built spacing, twice the
244
+ chosen one. */
245
+ margin-top: var(--spacing-16);
117
246
  }
118
247
 
119
- .bd-discount {
120
- background: #c3f4d4;
121
- color: #0b6a26;
122
- padding: var(--spacing-4) var(--spacing-14);
123
- border-radius: var(--spacing-14);
124
- font-size: var(--spacing-16);
125
- font-family: var(--typography-fontFamily-sans);
126
- font-weight: var(--typography-fontWeight-semibold);
127
- }
248
+ /* No rule for the struck price. It is now <s> inside <bd-p variant="small">
249
+ with the class .original-price, matching pricing-card.js and
250
+ pricing-card-pricing.js, and every declaration this rule carried is dead or
251
+ redundant against that:
252
+
253
+ font-size and font-family -- INERT on a bd-p host, per
254
+ scripts/atom-inert-properties.json. body-small answers the size.
255
+ text-decoration -- <s> carries the strike natively, and a host
256
+ declaration does not cross into the atom's shadow tree anyway: the
257
+ inner <p> measures text-decoration: none in the pricing cards, which
258
+ is where the same rule has been dead all along.
259
+ color -- bd-p's own :host already sets --color-text-primary, the
260
+ identical token this declared.
261
+
262
+ So the struck price now resolves through a role rather than a primitive,
263
+ and the element finally says what it is. */
264
+
265
+ /* The one thing this rule still has to do, and it is not typography. bd-p's
266
+ inner <p> carries margin-bottom: var(--spacing-16) for paragraph flow, and
267
+ a bd-p in a flex row is a flex item, so it establishes a block formatting
268
+ context and that margin cannot collapse through the host -- it is trapped
269
+ inside the host's box. Measured: the host renders 37px where its text is
270
+ 21px, which grew this zone from 28px to 37px and shifted the struck price
271
+ 6px against the discount badge beside it.
272
+ .
273
+ The pricing cards do not show this because both of their siblings are bd-p
274
+ and the offset cancels; here the badge is a bare span, so it does not.
275
+ bd-p exposes padding but no margin, so the margin cannot be reached from
276
+ outside -- this cancels it with the same token it is set from, which keeps
277
+ the two in step if the token moves. An atom-level way to say "no flow
278
+ margin" would be better and is not this change's to add. */
279
+ .original-price {
280
+ margin-bottom: calc(-1 * var(--spacing-16));
281
+ /* text/muted, which is what Header / v2 gives the struck price. bd-p's own
282
+ :host sets text/primary; colour on the host is LIVE (see the note on
283
+ .bd-description), so this reaches it. A former price is the one number
284
+ on this card that is deliberately not being offered -- primary ink gave
285
+ it the same weight in the eye as the price you can actually pay.
286
+ .
287
+ The element also moved from variant="small" to "regular": the design
288
+ reads 16px here, and small is 14. */
289
+ color: var(--color-text-muted);
290
+ }
291
+
292
+ /* .bd-discount is a bd-badge now, so there is nothing left to declare. The
293
+ rule that stood here painted --color-green-100 behind --color-text-success
294
+ at 16px semibold in a --radius-xl box, with two "no role:" markers saying
295
+ it was a hand-rolled bd-badge tone="success" and that whether it should be
296
+ one was tracked in plans/foundation-plan.md. It should, it is, and the
297
+ tracked question is answered.
298
+ .
299
+ Worth knowing it was not the same badge: green-100 behind success ink is
300
+ the atom's shade variant, and Header / v2 asks for fill -- a saturated
301
+ status/success/fill ground with status/success/on-fill ink. So this was a
302
+ tint where the design wants a solid, at the wrong size, in the wrong
303
+ corner radius. The class is kept only as a positioning hook. */
128
304
 
129
305
  .bd-price-zone-final {
130
306
  display: block;
131
- font-size: var(--display-large-mobile);
132
- font-family: var(--typography-fontFamily-sans);
133
- font-weight: var(--typography-fontWeight-bold);
307
+ /* THE 44px EXCEPTION IS RETIRED. This carried a recorded "no token:" note
308
+ saying 44 was off the type scale -- 4xl is 36, 5xl is 48 -- and that
309
+ adding the step would be its own decision. It never needed one: Header /
310
+ v2 sets the final price at 36 Bold on a 1.25 leading, which is
311
+ heading-h2 exactly (4xl / bold / tight / normal tracking) on all four
312
+ axes. The literal was standing in for a size the design does not ask
313
+ for, left behind when --display-large-mobile was deleted.
314
+ .
315
+ The role rather than the four primitives, so the size and the weight
316
+ cannot drift apart, and so the "no role:" marker that sat below the
317
+ font-weight -- which only existed because the size could not resolve --
318
+ goes with it. */
319
+ font-size: var(--typography-heading-h2-fontSize);
320
+ font-weight: var(--typography-heading-h2-fontWeight);
321
+ line-height: var(--typography-heading-h2-lineHeight);
322
+ letter-spacing: var(--typography-heading-h2-letterSpacing);
323
+ font-family: var(--typography-heading-h2-fontFamily);
324
+ /* the body frame's gap to the actions frame below, 16. */
134
325
  margin-bottom: var(--spacing-16);
135
326
  }
136
327
 
137
328
  .bd-cta {
138
329
  display: flex;
139
330
  gap: var(--spacing-16);
140
- }
141
-
331
+ /* WRAP, because the alternative is not "stays on one line" -- it is
332
+ "shrinks". Flex items shrink by default, and measured at a 312px column
333
+ the two buttons went from 183/246 to 150/146 and both labels broke over
334
+ three lines inside a box padded for one. The height study had already
335
+ priced the wrap: the row is 446 of the design's 613, so 37% headroom,
336
+ and wrapping costs about 71px. Paying 71px beats crushing the controls,
337
+ and it happens on its own at whatever width runs out rather than at a
338
+ breakpoint somebody picks. */
339
+ flex-wrap: wrap;
340
+ }
341
+
342
+ /* ---- the two disclaimer lines -------------------------------------------
343
+ Header / v2 draws these as ONE text node of two lines at 12 Regular on a
344
+ 1.5 leading in text/muted. The code keeps them as two bd-p blocks, because
345
+ that is the slot API consumers already write against -- disclaimer-top,
346
+ and the three-part disclaimer-bottom run -- and collapsing them into one
347
+ would break every page that fills them.
348
+
349
+ So the change is the type, and both blocks make the same two moves. The
350
+ size now comes from the ATOM: variant="xsmall" is bd-p's 12px step, where
351
+ these were variant="small" (14px) with a font-size and a font-weight
352
+ declared out here on the host -- both INERT per
353
+ scripts/atom-inert-properties.json, since bd-p sets all five type axes on
354
+ its inner element. So the rendered size was 14 and the declarations
355
+ claiming otherwise did nothing. Two dead declarations and a wrong size, in
356
+ a rule that looked like it was in control of both.
357
+
358
+ One divergence, recorded rather than copied: xsmall's leading is 1.45
359
+ (body-small's) where the file says 150%. Figma binds fontSize to a
360
+ variable here and leaves lineHeight raw, so 150% is an unbound number
361
+ rather than a token -- and CLAUDE.md's rule is to read the intent and
362
+ spell it with the role, not to borrow the file's value. 17.4px versus 18. */
142
363
  .bd-disclaimer-top {
143
364
  display: block;
144
- margin-top: var(--spacing-16);
145
- margin-bottom: var(--spacing-8);
146
- font-size: var(--typography-body-small-fontSize);
147
- font-family: var(--typography-fontFamily-sans);
148
- font-weight: var(--typography-body-large-fontWeight);
149
- color: #000000;
365
+ /* the actions frame's gap, 8 -- down from 16. */
366
+ margin-top: var(--spacing-8);
367
+ /* A NEGATIVE 16, not 0. Setting the host's margin to zero was not enough
368
+ and measuring showed it: the two lines still sat 16px apart. bd-p's
369
+ inner <p> carries margin-bottom: var(--spacing-16) for paragraph flow,
370
+ the host has no padding or border to stop it, so it collapses straight
371
+ through the host's bottom edge -- a zero on the host cancels the host's
372
+ own margin and leaves the atom's untouched.
373
+ .
374
+ Same cancellation, same reason and the same token as .original-price
375
+ above, which hit this in a flex row where the margin could not collapse
376
+ at all. bd-p exposes padding but no margin, so there is no way to reach
377
+ it from outside; using the same token it is set from keeps the two in
378
+ step if that token moves. An atom-level "no flow margin" would be better
379
+ and is not this change's to add.
380
+ .
381
+ Zero is right because the design has these as two consecutive lines of
382
+ ONE paragraph -- Header / v2 draws a single 12/1.45 text node of two
383
+ lines -- rather than two blocks with air between them. Measured after:
384
+ 8px from the CTA row, 0 between the lines. */
385
+ margin-bottom: calc(-1 * var(--spacing-16));
386
+ color: var(--color-text-muted);
150
387
  }
151
388
  .bd-header-image-wrapper {
152
389
  position: relative;
@@ -155,123 +392,365 @@ export default css`
155
392
  display: flex;
156
393
  justify-content: center;
157
394
  overflow: hidden;
158
- border-radius: 20px;
395
+ border-radius: var(--radius-2xl);
159
396
  }
160
397
 
398
+ /* The same correction as ::slotted([slot="header-picture"]) above, on the
399
+ internal class. Kept in step deliberately: whichever of the two a page
400
+ ends up using, the image fills the panel the same way. */
161
401
  .bd-header-image {
162
402
  display: block;
163
403
  width: 100%;
164
- height: auto;
404
+ height: 100%;
165
405
  object-fit: cover;
166
- transform: scale(1.35) translateY(-7%);
167
406
  }
168
407
 
169
- .bd-header-watermark {
408
+ /* ---- THE BRAND SIGNATURE AND ITS SCRIM ---------------------------------
409
+ Figma: Header / v2, media-panel, children bottom-to-top
410
+ media -> scrim -> brand-signature. Both rules below are that stack.
411
+
412
+ WHAT WAS HERE BEFORE. A .bd-header-watermark div holding the words
413
+ "Trusted. Always." at 32px/700 in text-inverse, 16px from the corner, with
414
+ a 2px backdrop-blur behind the run. Three things were wrong with it and
415
+ only one was cosmetic: it was type doing a mark's job (CLAUDE.md's icon
416
+ rule -- it inherited the text stack, had no viewBox, and changed shape
417
+ with the font), it was English hardcoded into a stylesheet's neighbour,
418
+ and it clipped -- measured at 320px the 133px run sat in a 96px column
419
+ inside an overflow:hidden wrapper and read "rusted. lways.". The mark
420
+ replaces all three: it is our own artwork, it says nothing that needs
421
+ translating, and it is 182px wide, which is what the media-panel column
422
+ has to be able to hold.
423
+
424
+ WHY THERE IS A SCRIM AT ALL. The mark is white and the image underneath
425
+ is the consumer's. Rendered against a real photo with no scrim the worst
426
+ pixel-neighbourhood under the mark measured 1.00:1, with 33.5% of its area
427
+ under the 3:1 that WCAG 1.4.11 asks of a non-text graphic -- so the mark
428
+ was legible by luck of the crop and nothing else.
429
+
430
+ WHY blue-950 AND NOT THE BRAND BLUE. A bright brand blue cannot do this
431
+ job: white on --color-blue-500 is 4.53:1 SOLID, so no partial coverage of
432
+ it can ever guarantee the minimum. blue-950 at 60% holds white above
433
+ 4.5:1 over any image including a white one, which is the only version of
434
+ this that is safe for an image nobody has seen. Measured over the test
435
+ photo: worst 8.64:1, nothing under 4.5.
436
+
437
+ THE GEOMETRY IS DERIVED, NOT DRAWN. The band is 48 + 112: 48 is the
438
+ mark's own box -- a --spacing-24 inset plus the 24px the artwork is drawn
439
+ at -- and 112 is the fade above it. Because the plateau stop sits at 70%,
440
+ it lands exactly 48px up from the bottom, which is the mark's top edge, so
441
+ the mark is inside the flat part of the ramp by construction rather than
442
+ by a number somebody tuned. That survives a reader enlarging their text:
443
+ the inset is rem and the artwork is px, so at a 24px root the band is 240
444
+ with its plateau at 72 and the mark occupies 36-60. It does NOT survive a
445
+ root SMALLER than 16px -- at 12px the mark's top edge sits about 6px into
446
+ the ramp, where the scrim is still ~0.52 rather than 0.60. Worth knowing;
447
+ not worth pinning the inset to px to avoid, since that would stop the
448
+ corner breathing with the text everywhere else.
449
+
450
+ The first stop is the transparent keyword rather than blue-950 at 0%.
451
+ Gradient interpolation is premultiplied, so that keyword does not fade
452
+ through grey the way it did before CSS Images 3 -- and color-mix(in srgb,
453
+ X 0%, transparent) resolves to rgba(0,0,0,0) anyway, which is the thing
454
+ the old workaround was avoiding. */
455
+ /* The two lengths everything below is derived from. They live on the
456
+ wrapper so the scrim and the mark, both its children, read the SAME inset:
457
+ the mark is positioned by it and the scrim's plateau is sized from it, so
458
+ they cannot disagree. */
459
+ .bd-header-image-wrapper {
460
+ --bd-header-mark-inset: var(--spacing-24);
461
+ /* the height the artwork is drawn at, and the step the atom defaults to */
462
+ --bd-header-mark-height: var(--icon-lg-size);
463
+ --bd-header-scrim-fade: var(--spacing-112);
464
+ --bd-header-scrim-mid: var(--spacing-40);
465
+ }
466
+
467
+ /* LENGTH STOPS, NOT PERCENTAGES. This was written as percentages of a
468
+ fixed 160px band -- 0 / 45% / 70% / 100% -- read straight off Figma, and
469
+ it made the invariant arithmetic rather than structural: the 70% stop
470
+ landed on the mark's top edge only because 0.3 x 160 happened to equal
471
+ 24 + 24. Measured what that cost at 320px, where the old rules capped the
472
+ panel at 200px tall: the band was 104% of the panel, so a gradient meant
473
+ to protect a mark in one corner became a wash over the whole image.
474
+ .
475
+ Written as lengths from the bottom the plateau IS the mark's box, by
476
+ construction, at any size -- so the narrow variant below is two smaller
477
+ numbers rather than a second set of percentages to re-derive. The stops
478
+ still resolve to Figma's exactly at desktop: plateau 0-48, 0.22 at 88,
479
+ zero at 160. */
480
+ .bd-header-scrim {
481
+ position: absolute;
482
+ inset-inline: 0;
483
+ bottom: 0;
484
+ height: calc(
485
+ var(--bd-header-mark-inset) + var(--bd-header-mark-height) +
486
+ var(--bd-header-scrim-fade)
487
+ );
488
+ background: linear-gradient(
489
+ to top,
490
+ color-mix(in srgb, var(--color-blue-950) 60%, transparent) 0,
491
+ color-mix(in srgb, var(--color-blue-950) 60%, transparent)
492
+ calc(var(--bd-header-mark-inset) + var(--bd-header-mark-height)),
493
+ color-mix(in srgb, var(--color-blue-950) 22%, transparent)
494
+ calc(
495
+ var(--bd-header-mark-inset) + var(--bd-header-mark-height) +
496
+ var(--bd-header-scrim-mid)
497
+ ),
498
+ transparent 100%
499
+ );
500
+ z-index: var(--z-raised);
501
+ pointer-events: none;
502
+ }
503
+
504
+ .bd-header-signature {
170
505
  position: absolute;
171
- bottom: var(--spacing-16);
172
- right: var(--spacing-16);
173
- z-index: 2;
174
- color: #ffffff;
175
- font-size: var(--typography-fontSize-3xl);
176
- font-weight: 700;
177
- opacity: 1;
178
- -webkit-backdrop-filter: blur(2px);
506
+ /* the same property the scrim sizes its plateau from, so the band cannot
507
+ stop covering the mark when the inset changes below sm */
508
+ bottom: var(--bd-header-mark-inset);
509
+ right: var(--bd-header-mark-inset);
510
+ z-index: var(--z-raised);
511
+ color: var(--color-text-inverse);
512
+ pointer-events: none;
179
513
  }
180
514
 
181
- .bd-header-watermark span {
182
- color: #fff;
183
- padding: var(--spacing-4) var(--spacing-8);
184
- backdrop-filter: blur(2px);
185
- -webkit-backdrop-filter: blur(2px);
186
- border-radius: var(--spacing-4);
187
- font-family: var(--typography-fontFamily-sans);
188
- font-weight: var(--typography-fontWeight-bold);
189
- }
190
515
  .bd-disclaimer-bottom {
191
516
  display: block;
192
- font-size: var(--typography-body-small-fontSize);
193
- font-family: var(--typography-fontFamily-sans);
194
- font-weight: var(--typography-body-large-fontWeight);
195
- color: #000000;
196
- margin-bottom: var(--spacing-16);
197
- }
198
-
199
- bd-header a {
200
- color: #000000 !important;
201
- }
202
- ::slotted([slot="right"]) {
203
- border-radius: var(--spacing-20);
204
- overflow: hidden;
205
- max-width: 100%;
206
- height: auto;
207
- }
208
-
209
- ::slotted([slot="extra-content"]) {
210
- margin-top: 1em;
211
- display: block;
212
- }
213
-
214
- @media (max-width: 1024px) {
517
+ color: var(--color-text-muted);
518
+ /* the same cancellation as the rule above, for the same reason -- this is
519
+ the last block in the column, so the atom's trailing flow margin was
520
+ adding 16px inside the content frame's own 24px bottom padding. */
521
+ margin-bottom: calc(-1 * var(--spacing-16));
522
+ }
523
+
524
+ /* A "bd-header a" rule stood here and could never match. It is the bare
525
+ element rule the ::slotted(a) comment above already describes: this header
526
+ renders no anchor of its own, and a type selector inside a shadow stylesheet
527
+ cannot reach the host, let alone the consumer's slotted light DOM. The fix
528
+ was made once -- ::slotted(a) forty lines up carries the same declaration --
529
+ and this copy outlived it. Measured before removing: the disclaimer link
530
+ computes rgb(21,23,25), which is --color-text-primary arriving from the
531
+ ::slotted rule, so nothing here was load-bearing. */
532
+ /* ::slotted([slot="right"]) and ::slotted([slot="extra-content"]) are gone.
533
+ Neither slot exists: header.js renders header-picture, disclaimer-top and
534
+ the three disclaimer-bottom-* slots, and nothing else. They are two of the
535
+ four dead slot names the note near the top of this sheet already records,
536
+ and the copies inside the media queries went when those were rewritten --
537
+ these were the originals, left behind. extra-content's margin-top: 1em
538
+ was also the last em literal in the file.
539
+
540
+ Recorded rather than silently dropped, so a reader who finds
541
+ slot="right" in a consumer's markup can learn from this file that it was
542
+ never supported rather than assume a regression. */
543
+
544
+ /* ---- RESPONSIVE ---------------------------------------------------------
545
+ Three steps, each on the breakpoint scale in shared/token-scales.js, and
546
+ each one measured rather than guessed. There is no mobile frame in Figma:
547
+ the height study was run at 1290 only, so every width below that is a
548
+ decision taken here, and the numbers that drove it are recorded with it.
549
+
550
+ WHAT WAS HERE. Three blocks at (max-width: 1024px), below('md') and
551
+ (max-width: 480px). The first two were mostly inert or wrong:
552
+
553
+ flex-direction: column on .bd-container -- inert. The container is
554
+ display: grid, and the identical mistake is already recorded forty
555
+ lines down where below('md') had to fix it with grid-template-columns.
556
+ So this declaration read as "stacks at 1024" and stacked at nothing.
557
+ gap: 2em -- an em literal on no scale, and the only live declaration in
558
+ that block. It was the reason the gap read 32px at exactly 1024 and
559
+ 64px at 1025.
560
+ width: 100% on two grid items -- redundant; a grid item already fills
561
+ its track.
562
+ ::slotted([slot="right"]) -- one of the four slot names this stylesheet
563
+ targets that header.js does not render. Dead in both blocks.
564
+
565
+ So the header's real stacking point was below('md'), and MEASURED at 768
566
+ that is far too late: two 312px columns, the headline at three lines, the
567
+ description at five, the buttons crushed to 150/146 with both labels
568
+ broken over three lines, and the section 737px tall against a design that
569
+ exists to hold it under 600.
570
+
571
+ WHY below('lg') IS THE STACKING POINT. The page column caps at
572
+ --grid-container-max-* with --grid-padding-* either side, so the content
573
+ width per step is fixed and so is the two-column arithmetic once the 64px
574
+ gap comes out of it:
575
+
576
+ xxl 1320 - 80 = 1240 -> 588 per column
577
+ xl 1140 - 80 = 1060 -> 498
578
+ lg 960 - 80 = 880 -> 408
579
+ md 720 - 64 = 656 -> 296
580
+ sm 540 - 48 = 492 -> 214
581
+
582
+ The CTA row is 446px wide. It fits at xl and xxl, wraps at lg, and at md
583
+ there is less room than one button needs. lg is therefore the last step
584
+ where two columns are even arguable, and it is arguable rather than good --
585
+ measured, two columns at lg cost the description a fourth line, the CTA
586
+ row a wrap, and the section **661px** against the design's 566. No gap
587
+ change rescues it: the row needs 445 and dropping the 64 to the lg grid
588
+ gutter's 40, or to 24, or to 16, yields 420, 428 and 432. The column is
589
+ about 40px short however the gutter is spent.
590
+
591
+ It keeps two columns anyway, because the alternative is worse on the same
592
+ screen. Stacked at lg the copy gets one 880px column -- the headline
593
+ collapses to a single line, which the study measured as needing 678 -- and
594
+ then an 880px-wide photograph under it at its own 1.497 ratio, so about
595
+ 1100px of hero in a window 992-1199 wide. 661 beats that. Bounding the
596
+ stacked panel's aspect ratio was tried on paper first: 16/9 at 880 is 495
597
+ tall, which still lands around 978. Stacking is the answer below lg, not
598
+ at it. */
599
+ @media ${unsafeCSS(below('lg'))} {
215
600
  .bd-container {
216
- flex-direction: column;
217
- padding: 0 4em;
218
- gap: 2em;
601
+ /* grid-template-columns, not flex-direction -- see above */
602
+ grid-template-columns: 1fr;
219
603
  }
220
604
 
221
- .bd-header-left,
222
- .bd-header-right {
223
- width: 100%;
224
- }
225
-
226
- .bd-cta {
227
- // flex-direction: column;
228
- align-items: flex-start;
229
- }
230
-
231
- ::slotted([slot="right"]) {
232
- max-width: 100%;
233
- }
234
- }
235
-
236
- @media (max-width: 768px) {
605
+ /* row-gap only. The column gap is the design's 64 and there is no second
606
+ column left to apply it to; 32 is the distance between the copy and the
607
+ image once they are stacked, and it is a step rather than the 2em that
608
+ used to arrive here. */
237
609
  .bd-container {
238
- padding: 0 2em;
239
- text-align: center;
240
- }
241
-
242
- .bd-product-name,
243
- .bd-value-prop,
244
- .bd-description,
245
- .bd-price-zone-top,
246
- .bd-price-zone-final,
247
- .bd-disclaimer {
248
- text-align: center;
610
+ row-gap: var(--spacing-32);
249
611
  }
612
+ }
250
613
 
614
+ /* Narrower than md. Separate from the stacking block above on purpose:
615
+ stacking and the phone treatment are different decisions at different
616
+ widths, and at md the column is 656px where a side-by-side CTA row still
617
+ fits.
618
+
619
+ THE CONTENT STAYS LEFT-ALIGNED AT EVERY WIDTH. It used to centre here, and
620
+ three things say it should not:
621
+
622
+ The design does not ask for it. Header / v2 is left-aligned, and there
623
+ is no mobile frame anywhere in the file that centres. Changing the axis
624
+ at a breakpoint was a decision nobody recorded making.
625
+
626
+ The content is the wrong shape for it. Six stacked blocks of very
627
+ different widths -- a two-line kicker, a two-line headline, a five-line
628
+ description, a price pair, two CTAs, a two-line disclaimer -- share no
629
+ edge when centred, so the eye gets no vertical line to return to down a
630
+ five-line paragraph at 288px. Centring works for a poster: a headline
631
+ and one button. This is a specification.
632
+
633
+ IT NEEDED A PATCH TO LOOK RIGHT, WHICH IS THE ARGUMENT. text-align
634
+ centres a block's inline content, and .bd-price-zone-top is
635
+ display: flex -- so the property reached its children's text and left
636
+ the row itself hard against the left edge while the final price centred
637
+ underneath it. That needed an extra justify-content: center to correct,
638
+ and a treatment that needs a patch to stop looking broken is usually
639
+ the wrong treatment. Removing the centring removes the patch with it:
640
+ the price row is left-aligned like everything else, and nothing has to
641
+ be told twice where its axis is.
642
+
643
+ The disclaimers were being centred by inheritance rather than by any rule
644
+ that named them -- the old rule listed .bd-disclaimer, which does not
645
+ exist in this component. They are left now like the rest. */
646
+ @media ${unsafeCSS(below('md'))} {
647
+ /* stretch, not center. The two labels are different lengths, so hugging
648
+ gave a 183px button above a 246px one -- two different widths on a
649
+ shared axis, which reads as an accident rather than a pair. It is the
650
+ Content Switcher's argument in another control: a translation must not
651
+ be able to make one of two equivalent actions bigger than the other.
652
+ Full-width they are the same object twice and the tap targets stop
653
+ varying with the copy, which holds whether the column is centred or
654
+ left-aligned. */
251
655
  .bd-cta {
252
656
  flex-direction: column;
253
- align-items: center;
657
+ align-items: stretch;
254
658
  }
255
659
 
256
- .bd-header-right {
257
- display: flex;
258
- justify-content: center;
660
+ /* STRETCHING THE COLUMN IS NOT ENOUGH. With align-items: stretch the
661
+ HOSTS go full width, but the anchor inside each one is inline-flex and
662
+ hugs its label, so both buttons end up hard against the left edge of
663
+ their own box. Measured: hosts 492, anchors 183 and 246.
664
+
665
+ The atom's own answer is its fullwidth attribute, which adds
666
+ .max--width -- width: 100%; display: flex -- to that anchor. A media
667
+ query cannot set an attribute, so this reaches the same computed result
668
+ through the host's formatting context instead: the host's display is
669
+ what its shadow child is laid out in, an outer-tree rule beats the
670
+ atom's :host, and a lone child of a single-column grid fills its track.
671
+ Measured after: hosts 492, anchors 492 and 492.
672
+
673
+ The coupling is worth knowing: this depends on the anchor being the
674
+ shadow root's only child, which it is (A.button). If the button ever
675
+ wraps it, prefer fullwidth on the markup and the viewport state it
676
+ needs. */
677
+ .bd-cta bd-button-link {
678
+ display: grid;
259
679
  }
680
+ }
260
681
 
261
- ::slotted([slot="right"]) {
262
- max-width: 100%;
682
+ /* THE BAND STEPS WITH THE PANEL, and it steps at md rather than sm because
683
+ that is where the panel stops being landscape. Measured with the band held
684
+ at its desktop 160 all the way down: 37% of the panel at 768-991, then
685
+ **49% at 600** -- a 492x329 panel with a 160px band across the bottom half
686
+ of it. Below md it is 88, which reads 27% at 600 and 30% at 500.
687
+
688
+ Two things change and both are about the media panel.
689
+
690
+ WHAT WAS AT (max-width: 480px), and a claim worth getting right. The block
691
+ set max-width: 90% and max-height: 200px on .bd-header-image -- a class
692
+ header.js does not render. It is the same kind of miss as the four dead
693
+ slot names recorded at the top of this file, so for the COMPONENT it was
694
+ dead: measured on Components/Header at 375 and 320, the slotted image
695
+ computes max-width and max-height of none and sits flush with the wrapper
696
+ on every edge. It became live only in the Intended design story, which
697
+ draws a real <img class="bd-header-image">, and that is where a 90%-wide
698
+ centred image left the scrim and the mark -- both positioned against the
699
+ WRAPPER -- hanging past it on each side. It is deleted rather than fixed;
700
+ it existed to tame the scale(1.35) crop this file used to do, and a real
701
+ cover fill needs no cap.
702
+
703
+ WHAT WAS REAL for the component is the band's proportion, because the band
704
+ was a constant 160 and the panel is not. The panel's height follows the
705
+ copy beside it when the columns are side by side, and its own 1.497 ratio
706
+ once they stack -- measured 311x208 at 375 and 256x171 at 320, which put a
707
+ 160px band at 77% and 94% of it. A corner treatment covering 94% of the
708
+ picture is a wash.
709
+
710
+ So the band is re-proportioned rather than capped: the inset drops a step
711
+ and the fade drops to 48, giving 40 + 48 = 88. Measured after: 42% at 375
712
+ and 51% at 320. The plateau still covers the mark exactly, because it is
713
+ derived from the inset rather than from a percentage of the band.
714
+
715
+ One thing this does not fix, and it is inherent rather than a defect: a
716
+ constant band on a variable panel is a variable proportion. Components/
717
+ Header's own Default story has less copy than the design, so its panel is
718
+ 399 tall at 1440 and the band reads 40% there against the design's 30%. */
719
+ @media ${unsafeCSS(below('md'))} {
720
+ .bd-header-image-wrapper {
721
+ --bd-header-mark-inset: var(--spacing-16);
722
+ --bd-header-mark-height: var(--icon-sm-size);
723
+ --bd-header-scrim-fade: var(--spacing-48);
724
+ --bd-header-scrim-mid: var(--spacing-16);
263
725
  }
264
- }
265
726
 
266
- @media (max-width: 480px) {
267
- .bd-header-image {
268
- max-width: 90%;
269
- max-height: 200px;
727
+ /* THE MARK STEPS DOWN A SIZE, and this is the other thing only the render
728
+ showed. At 320 the panel is 256 wide and the mark is 182 of it -- 71%,
729
+ against 30% in the design -- so a corner signature became a caption
730
+ laid across the picture. The numbers said it fitted, and it did fit;
731
+ fitting was not the question.
732
+ .
733
+ A height on the host rather than the atom's size attribute, because the
734
+ size is a viewport question and the attribute is not reachable from a
735
+ media query. It is not a reach past the API: the atom's own rule is
736
+ :host { height: var(--bd-icon-size, ...) }, and a consuming rule and a
737
+ :host rule match the SAME element, where the outer tree wins -- the same
738
+ mechanism scripts/atom-inert-properties.json records for colour on a
739
+ bd-p host. --bd-icon-size, which the element sets inline from its size prop, is
740
+ deliberately not what is overridden here: an inline custom property
741
+ would beat this rule.
742
+ .
743
+ sm on the icon scale is 16px, so the mark renders 121 wide -- 47% of a
744
+ 256px panel. --bd-header-mark-height above is set to the same step, so
745
+ the scrim's plateau follows it and still ends exactly on the mark's top
746
+ edge. */
747
+ .bd-header-signature {
748
+ height: var(--icon-sm-size);
270
749
  }
271
750
  }
272
751
 
273
752
  ::slotted([slot="disclaimer-bottom-link"]) {
274
- color: black;
753
+ color: var(--color-text-primary);
275
754
  text-decoration: underline;
276
755
  }
277
756
  `;