@repobit/dex-system-design 1.0.1 → 2.0.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 (136) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/README.md +43 -12
  3. package/package.json +4 -2
  4. package/schemas/component-manifest.json +207 -49
  5. package/schemas/manifest-omissions.json +2 -0
  6. package/src/components/accordion/accordion.css.js +12 -1
  7. package/src/components/agent-endpoints/agent-endpoints.js +1 -1
  8. package/src/components/alert/alert.css.js +22 -1
  9. package/src/components/anchor/anchor-nav.css.js +6 -1
  10. package/src/components/annotation/annotation.css.js +6 -1
  11. package/src/components/architecture.stories.js +6 -4
  12. package/src/components/avatar/avatar.js +37 -11
  13. package/src/components/avatar/avatar.stories.js +10 -6
  14. package/src/components/awards/awards-flex.css.js +31 -129
  15. package/src/components/awards/awards-flex.js +66 -116
  16. package/src/components/awards/awards-flex.stories.js +8 -6
  17. package/src/components/back/back.css.js +6 -1
  18. package/src/components/back/back.js +24 -12
  19. package/src/components/back/back.stories.js +3 -3
  20. package/src/components/badge/badge.js +6 -3
  21. package/src/components/badge/badge.stories.js +13 -9
  22. package/src/components/bento-grid/bento-grid.js +1 -1
  23. package/src/components/blockquote/blockquote.css.js +12 -3
  24. package/src/components/blockquote/blockquote.js +12 -5
  25. package/src/components/blockquote/blockquote.stories.js +6 -4
  26. package/src/components/button/button.css.js +46 -25
  27. package/src/components/button/button.js +44 -43
  28. package/src/components/button/button.stories.js +2 -2
  29. package/src/components/caption/caption.css.js +10 -0
  30. package/src/components/caption/caption.stories.js +4 -4
  31. package/src/components/carousel/carousel.css.js +19 -116
  32. package/src/components/carousel/carousel.js +70 -74
  33. package/src/components/carousel/carousel.stories.js +9 -8
  34. package/src/components/checkbox/checkbox.css.js +6 -1
  35. package/src/components/code/code.css.js +6 -1
  36. package/src/components/code/code.js +48 -7
  37. package/src/components/code/code.stories.js +7 -4
  38. package/src/components/divider/divider-horizontal.js +11 -3
  39. package/src/components/divider/divider-vertical.js +12 -5
  40. package/src/components/divider/divider.css.js +4 -1
  41. package/src/components/divider/divider.stories.js +6 -6
  42. package/src/components/dropdown/dropdown.js +67 -13
  43. package/src/components/dropdown/dropdown.stories.js +15 -14
  44. package/src/components/fieldset/fieldset.stories.js +41 -16
  45. package/src/components/footer/footer-nav-menu.css.js +6 -1
  46. package/src/components/footer/footer.css.js +6 -1
  47. package/src/components/header/header-code.js +1 -1
  48. package/src/components/header/header.css.js +9 -4
  49. package/src/components/heading/heading.stories.js +64 -31
  50. package/src/components/highlight/highlight.js +74 -34
  51. package/src/components/highlight/highlight.stories.js +5 -4
  52. package/src/components/icons/icon-element.js +49 -6
  53. package/src/components/icons/icons.stories.js +7 -0
  54. package/src/components/image/image.css.js +9 -98
  55. package/src/components/image/image.js +7 -2
  56. package/src/components/input/custom-form.stories.js +14 -19
  57. package/src/components/input/input-clipboard.css.js +6 -1
  58. package/src/components/input/input-clipboard.js +56 -8
  59. package/src/components/input/input.js +19 -2
  60. package/src/components/label/label.js +87 -1
  61. package/src/components/label/label.stories.js +21 -12
  62. package/src/components/link/link.css.js +52 -3
  63. package/src/components/link/link.js +52 -17
  64. package/src/components/link/link.stories.js +114 -3
  65. package/src/components/list/list-item/list-item.css.js +4 -6
  66. package/src/components/list/list-item/list-item.js +53 -5
  67. package/src/components/list/list-item/list-item.stories.js +22 -0
  68. package/src/components/list/list.js +27 -0
  69. package/src/components/modal/modal.css.js +6 -1
  70. package/src/components/pagination-dots/pagination-dots.css.js +161 -0
  71. package/src/components/pagination-dots/pagination-dots.js +202 -0
  72. package/src/components/pagination-dots/pagination-dots.stories.js +171 -0
  73. package/src/components/paragraph/paragraph.css.js +37 -1
  74. package/src/components/paragraph/paragraph.js +19 -2
  75. package/src/components/paragraph/paragraph.stories.js +40 -6
  76. package/src/components/picture/picture.css.js +8 -67
  77. package/src/components/picture/picture.js +15 -8
  78. package/src/components/pricing-cards/pricing-card.css.js +12 -2
  79. package/src/components/pricing-cards/pricing-card.js +4 -4
  80. package/src/components/progress/progress.js +24 -4
  81. package/src/components/progress/progress.stories.js +8 -6
  82. package/src/components/radio/radio.css.js +6 -1
  83. package/src/components/radio/radio.js +48 -3
  84. package/src/components/radio/radio.stories.js +1 -1
  85. package/src/components/shared/form-field.css.js +30 -4
  86. package/src/components/shared/token-scales.js +16 -0
  87. package/src/components/spinner/spinner.css.js +31 -4
  88. package/src/components/spinner/spinner.js +26 -4
  89. package/src/components/spinner/spinner.stories.js +10 -7
  90. package/src/components/switch/switch.css.js +21 -3
  91. package/src/components/switch/switch.js +8 -1
  92. package/src/components/switch/switch.stories.js +2 -1
  93. package/src/components/tab-list/tab-list.css.js +6 -1
  94. package/src/components/terminal/terminal-cursor.js +11 -0
  95. package/src/components/terminal/terminal-message.js +1 -1
  96. package/src/components/termsOfUse/terms.css.js +6 -1
  97. package/src/components/textarea/textarea.stories.js +23 -0
  98. package/src/components/tooltip/tooltip.css.js +24 -1
  99. package/src/components/tooltip/tooltip.js +135 -5
  100. package/src/components/tooltip/tooltip.stories.js +20 -14
  101. package/src/components/trust-panel/trust-panel.js +2 -2
  102. package/src/components/video/video.js +38 -3
  103. package/src/components/video/video.stories.js +10 -5
  104. package/src/components/video-player/video-player.css.js +6 -1
  105. package/src/tokens/elevation.stories.js +2 -3
  106. package/src/tokens/focus.stories.js +81 -15
  107. package/src/tokens/motion.stories.js +0 -6
  108. package/src/tokens/tokens.css +174 -97
  109. package/src/tokens/tokens.js +174 -97
  110. package/src/tokens/vertical-rhythm.stories.js +1 -1
  111. package/src/stories/Button.js +0 -20
  112. package/src/stories/Button.stories.js +0 -46
  113. package/src/stories/Configure.mdx +0 -364
  114. package/src/stories/Header.js +0 -45
  115. package/src/stories/Header.stories.js +0 -24
  116. package/src/stories/Page.js +0 -62
  117. package/src/stories/Page.stories.js +0 -20
  118. package/src/stories/assets/accessibility.png +0 -0
  119. package/src/stories/assets/accessibility.svg +0 -1
  120. package/src/stories/assets/addon-library.png +0 -0
  121. package/src/stories/assets/assets.png +0 -0
  122. package/src/stories/assets/avif-test-image.avif +0 -0
  123. package/src/stories/assets/context.png +0 -0
  124. package/src/stories/assets/discord.svg +0 -1
  125. package/src/stories/assets/docs.png +0 -0
  126. package/src/stories/assets/figma-plugin.png +0 -0
  127. package/src/stories/assets/github.svg +0 -1
  128. package/src/stories/assets/share.png +0 -0
  129. package/src/stories/assets/styling.png +0 -0
  130. package/src/stories/assets/testing.png +0 -0
  131. package/src/stories/assets/theming.png +0 -0
  132. package/src/stories/assets/tutorials.svg +0 -1
  133. package/src/stories/assets/youtube.svg +0 -1
  134. package/src/stories/button.css +0 -30
  135. package/src/stories/header.css +0 -32
  136. package/src/stories/page.css +0 -68
@@ -51,6 +51,7 @@
51
51
  "bd-compare-section: component declares \"scoreLabel\" but the manifest omits it",
52
52
  "bd-compare-section: component declares \"variant\" but the manifest omits it",
53
53
  "bd-contact-button: component declares \"buyLink\" but the manifest omits it",
54
+ "bd-contact-button: component declares \"description\" but the manifest omits it",
54
55
  "bd-contact-button: component declares \"disabled\" but the manifest omits it",
55
56
  "bd-contact-button: component declares \"fontSize\" but the manifest omits it",
56
57
  "bd-contact-button: component declares \"fontWeight\" but the manifest omits it",
@@ -64,6 +65,7 @@
64
65
  "bd-contact-button: component declares \"strong\" but the manifest omits it",
65
66
  "bd-contact-button: component declares \"surface\" but the manifest omits it",
66
67
  "bd-contact-button: component declares \"target\" but the manifest omits it",
68
+ "bd-contact-button: component declares \"variant\" but the manifest omits it",
67
69
  "bd-feature-col is registered by ./features but absent from the manifest",
68
70
  "bd-features is registered by ./features but absent from the manifest",
69
71
  "bd-features-tab is registered by ./features-tab but absent from the manifest",
@@ -58,6 +58,12 @@ export const accordionItem = css`
58
58
  position: relative;
59
59
  z-index: var(--z-base);
60
60
  box-shadow: var(--focus-outline-primary);
61
+ /* forced-colors mode does not paint box-shadow, so the ring
62
+ vanishes there; it does paint an outline, in a system colour. This one
63
+ is transparent everywhere else, and the offset lands it on the ring's
64
+ outer band. */
65
+ outline: var(--border-width-2) solid transparent;
66
+ outline-offset: var(--focus-outline-offset);
61
67
  }
62
68
 
63
69
  :host(:last-child) {
@@ -367,7 +373,12 @@ export const accordionSection = css`
367
373
  }
368
374
 
369
375
  .user-guide-link a:focus-visible {
370
- outline: none;
376
+ /* forced-colors mode does not paint box-shadow, so the ring
377
+ vanishes there; it does paint an outline, in a system colour. This one
378
+ is transparent everywhere else, and the offset lands it on the ring's
379
+ outer band. */
380
+ outline: var(--border-width-2) solid transparent;
381
+ outline-offset: var(--focus-outline-offset);
371
382
  box-shadow: var(--focus-outline-primary);
372
383
  border-radius: var(--focus-outline-radius);
373
384
  }
@@ -31,7 +31,7 @@ class AgentEndpoints extends LitElement {
31
31
  return html`
32
32
  <div class="bd-ensemble bd-agentendpoints-wrapper">
33
33
  ${this.eyebrow
34
- ? html`<bd-p variant="regular" font-weight="semibold" class="bd-agentendpoints-eyebrow"><slot name="eyebrow">${this.eyebrow}</slot></bd-p>`
34
+ ? html`<bd-p variant="eyebrow" class="bd-agentendpoints-eyebrow"><slot name="eyebrow">${this.eyebrow}</slot></bd-p>`
35
35
  : nothing}
36
36
 
37
37
  ${this.title
@@ -177,7 +177,17 @@ export const alertCSS = css`
177
177
  display: none;
178
178
  }
179
179
 
180
+ /* A 24px target around the 16px icon. The button was the icon and
181
+ nothing else, 16x16, under the 24px WCAG 2.5.8 asks of a pointer target
182
+ -- and an alert's close button is the one control on it a reader is
183
+ trying to hit. The ::before below is that target: it reaches
184
+ --spacing-4 past the button on each side, and a click on it is a click
185
+ on the button -- 24x24 at the default text size, and larger, never
186
+ smaller, for a reader who enlarges their text, since the reach is rem.
187
+ The button's own box is unchanged, so the icon sits exactly where it did
188
+ and the alert is no taller. */
180
189
  .dismiss {
190
+ position: relative;
181
191
  flex: none;
182
192
  display: flex;
183
193
  padding: var(--spacing-0);
@@ -188,9 +198,20 @@ export const alertCSS = css`
188
198
  cursor: pointer;
189
199
  }
190
200
 
201
+ .dismiss::before {
202
+ content: "";
203
+ position: absolute;
204
+ inset: calc(-1 * var(--spacing-4));
205
+ }
206
+
191
207
  /* One token, one painter -- the same invariant accordion.css.js records. */
192
208
  .dismiss:focus-visible {
193
- outline: none;
209
+ /* forced-colors mode does not paint box-shadow, so the ring
210
+ vanishes there; it does paint an outline, in a system colour. This one
211
+ is transparent everywhere else, and the offset lands it on the ring's
212
+ outer band. */
213
+ outline: var(--border-width-2) solid transparent;
214
+ outline-offset: var(--focus-outline-offset);
194
215
  box-shadow: var(--focus-outline-primary);
195
216
  }
196
217
  `;
@@ -54,7 +54,12 @@ export const anchorNavStyles = css`
54
54
  .bd-anchor-nav__dropdown-toggle:focus-visible,
55
55
  .bd-anchor-nav__dropdown-option:focus-visible {
56
56
  /* the one focus ring — see Foundations/Focus */
57
- outline: none;
57
+ /* forced-colors mode does not paint box-shadow, so the ring
58
+ vanishes there; it does paint an outline, in a system colour. This one
59
+ is transparent everywhere else, and the offset lands it on the ring's
60
+ outer band. */
61
+ outline: var(--border-width-2) solid transparent;
62
+ outline-offset: var(--focus-outline-offset);
58
63
  box-shadow: var(--focus-outline-primary);
59
64
  /* No border-radius here, for the reason back.css.js records: the ring is a
60
65
  box-shadow and already follows the shape of what it sits on, and both of
@@ -33,7 +33,12 @@ export const annotationCSS = css`
33
33
  /* the one focus ring — see Foundations/Focus. A two-colour ring whose
34
34
  white inner band keeps it visible on a coloured or dark surface, which
35
35
  a single-colour outline cannot do. */
36
- outline: none;
36
+ /* forced-colors mode does not paint box-shadow, so the ring
37
+ vanishes there; it does paint an outline, in a system colour. This one
38
+ is transparent everywhere else, and the offset lands it on the ring's
39
+ outer band. */
40
+ outline: var(--border-width-2) solid transparent;
41
+ outline-offset: var(--focus-outline-offset);
37
42
  box-shadow: var(--focus-outline-primary);
38
43
  border-radius: var(--focus-outline-radius);
39
44
  }
@@ -146,10 +146,12 @@ export const LayerModel = () =>
146
146
  lede : 'Foundations, Atoms, Components, Modules, Layout, and internal primitives — what each one owns in ' +
147
147
  'this repository, with the rules for adding to them. Derived from the library as it stands, not from a ' +
148
148
  'textbook hierarchy.',
149
- note: html`This page is documentation. It is filed under its own group because
150
- <code>lint:tokens</code> reads the atom tier from a story&rsquo;s <code>title</code>, and
151
- <code>verify:typography</code> builds its population from the <code>Atoms/</code> and
152
- <code>Components/</code> prefixes — so where a page is filed changes what the gates believe.`,
149
+ note: html`This page is documentation, and where it is filed changes nothing the gates
150
+ believe. <code>lint:tokens</code> and <code>verify:typography</code> read each unit&rsquo;s layer
151
+ from <code>scripts/architecture.json</code>, keyed on its source path — not from a story&rsquo;s
152
+ <code>title</code>, which is what they used to read — so a page can be moved between groups
153
+ without changing what is enforced. This one is recorded there as <code>FOUNDATION</code>; see
154
+ <em>Where classification lives</em> below.`,
153
155
  children: html`
154
156
  ${refSection({ name: 'The six layers', meta: 'what belongs where', children: layerTable() })}
155
157
 
@@ -1,4 +1,4 @@
1
- import { LitElement, html } from 'lit';
1
+ import { LitElement, html, nothing } from 'lit';
2
2
  // Installs the design tokens into document scope; see src/tokens/tokens.js
3
3
  import '../../tokens/tokens.js';
4
4
  import { avatarCSS } from './avatar.css.js';
@@ -8,9 +8,10 @@ export class BdAvatar extends LitElement {
8
8
  static styles = [avatarCSS];
9
9
 
10
10
  static properties = {
11
- src : { type: String },
12
- name: { type: String },
13
- size: { type: String, reflect: true }
11
+ src : { type: String },
12
+ name : { type: String },
13
+ size : { type: String, reflect: true },
14
+ _failed: { state: true }
14
15
  };
15
16
 
16
17
  constructor() {
@@ -23,17 +24,42 @@ export class BdAvatar extends LitElement {
23
24
  willUpdate(changed) {
24
25
  // small / default / large still resolve; see shared/prop-vocabulary.js
25
26
  if (changed.has('size')) this.size = resolveSize(this.size, 'md');
27
+ // a new image gets its own chance to load
28
+ if (changed.has('src')) this._failed = false;
26
29
  }
27
30
 
31
+ /* The first letter of each word, at most two, upper-cased. name can be
32
+ null -- a removed attribute sets it so -- and used to throw on split. */
33
+ get _initials() {
34
+ return (this.name ?? '').trim()
35
+ .split(/\s+/)
36
+ .map(w => w[0] || '')
37
+ .join('')
38
+ .slice(0, 2)
39
+ .toUpperCase();
40
+ }
41
+
42
+ /* THE FALLBACKS.
43
+ An image that fails to load used to stay on screen as an empty circle;
44
+ it now falls back to the initials, as having no src does.
45
+
46
+ With no name there is nothing to say, so the avatar is hidden from
47
+ assistive technology instead of being announced: it used to be an img
48
+ named "Avatar", which tells a screen reader user only that a picture is
49
+ there, on every avatar in a list. */
28
50
  render() {
51
+ const name = (this.name ?? '').trim();
52
+ const showImage = this.src && !this._failed;
29
53
  return html`
30
- <span class="avatar" role="img" aria-label=${this.name || 'Avatar'}>
31
- ${this.src
32
- ? html`<img src=${this.src} alt="" />`
33
- : html`${this.name.split(' ').map(w => w[0] || '')
34
- .join('')
35
- .slice(0, 2)
36
- .toUpperCase()}`}
54
+ <span
55
+ class="avatar"
56
+ role=${name ? 'img' : nothing}
57
+ aria-label=${name || nothing}
58
+ aria-hidden=${name ? nothing : 'true'}
59
+ >
60
+ ${showImage
61
+ ? html`<img src=${this.src} alt="" @error=${() => { this._failed = true; }} />`
62
+ : this._initials}
37
63
  </span>
38
64
  `;
39
65
  }
@@ -23,7 +23,7 @@ there is no image.
23
23
  |---|---|---|
24
24
  | \`src\` | image URL — omit to fall back to initials | \`''\` |
25
25
  | \`name\` | the person's name | \`''\` |
26
- | \`size\` | \`small\`, \`default\`, \`large\` | \`default\` |
26
+ | \`size\` | \`sm\`, \`md\`, \`lg\` — \`small\` / \`default\` / \`large\` still resolve | \`md\` |
27
27
 
28
28
  ### Always pass a name
29
29
  \`name\` does two jobs. It is the source of the initials when there is no \`src\`,
@@ -31,9 +31,13 @@ and it is the accessible name in both cases — the wrapper carries
31
31
  \`role="img"\` with \`aria-label\` from \`name\`, and the inner image is given an
32
32
  empty \`alt\` so it is not announced twice.
33
33
 
34
- Without a \`name\` the accessible name falls back to the literal string
35
- "Avatar", which tells a screen reader user nothing. Pass one even when you have
36
- an image.
34
+ Without a \`name\` the avatar has nothing to say, so it is hidden from assistive
35
+ technology rather than announced — it used to be an image named "Avatar", which
36
+ tells a screen reader user nothing. Pass one even when you have an image.
37
+
38
+ ### When the image does not load
39
+ A \`src\` that fails to load falls back to the initials, the same as no \`src\`
40
+ at all. A new \`src\` gets its own chance to load.
37
41
 
38
42
  ### How the initials are derived
39
43
  The first letter of each whitespace-separated word, capped at two characters
@@ -57,9 +61,9 @@ first and last.
57
61
  },
58
62
  size: {
59
63
  control : { type: 'select' },
60
- options : ['small', 'default', 'large'],
64
+ options : ['sm', 'md', 'lg'],
61
65
  description: 'Diameter of the avatar.',
62
- table : { type: { summary: 'string' }, defaultValue: { summary: "default" }, category: 'Appearance' }
66
+ table : { type: { summary: 'string' }, defaultValue: { summary: 'md' }, category: 'Appearance' }
63
67
  }
64
68
  }
65
69
  };
@@ -203,136 +203,21 @@ export const awardsFlexCSS = css`
203
203
  margin: 0;
204
204
  }
205
205
 
206
- /* === DOTS === */
207
- /* Dots and the pause control share one row; the dots keep their own spacing
208
- so the treatment is unchanged when no control is shown. */
209
- .bd-awardsflex-controls {
210
- display: flex;
211
- align-items: center;
212
- gap: var(--spacing-16);
213
- }
214
-
215
- .bd-awardsflex-dots {
216
- display: flex;
217
- /* The buttons below carry their own 24px of target, so the space between
218
- two dots now comes from the boxes rather than from a gap. Adding one on
219
- top would push them apart without making either easier to hit. */
220
- gap: 0;
206
+ /* === SLIDE CONTROLS ===
207
+ bd-pagination-dots, which also runs the autoplay clock. This file used to
208
+ draw its own dots and pause button -- a dot row that called itself a
209
+ tablist with no tabpanels, resting dots at neutral-300 (2.10:1 on the
210
+ tint, under the 3:1 a control needs), and a setInterval the dots knew
211
+ nothing about. The atom owns all of that now; the margin is all that is
212
+ left to decide here. */
213
+ bd-pagination-dots {
221
214
  margin-top: var(--spacing-8);
222
215
  }
223
216
 
224
- .bd-awardsflex-autoplay-toggle {
225
- margin-top: var(--spacing-8);
226
- padding: var(--spacing-2) var(--spacing-8);
227
- background: transparent;
228
- border: var(--border-width-1) solid var(--color-border-default);
229
- border-radius: var(--radius-full);
230
- color: var(--color-text-muted);
231
- font-family: var(--typography-fontFamily-sans);
232
- /* no role: four roles sit at this size and each fails differently, so the
233
- size stays a primitive. Note the weight is not declared here at all --
234
- it inherits 400 -- which is why two of these are about what adopting a
235
- role would ADD.
236
- .
237
- caption-default matches every value exactly, including the colour token:
238
- xs / 400 / 1.5 / tracking 0 / sans, and its own colour axis is
239
- --color-text-muted, which is what this rule already declares two lines
240
- up. It renders as the same pixels. It is declined anyway, because
241
- vertical-rhythm.stories.js documents it as "text under an image or a
242
- figure" and this is the visible label of a toggle button. A
243
- five-axis-plus-colour identity is the most seductive form of choosing a
244
- role by its value rather than its meaning -- not an exemption from
245
- CLAUDE.md's rule against that, but the case the rule is for.
246
- .
247
- caption-small carries the same wrong meaning and adds
248
- letterSpacing-wide, which has no job on a five-letter word.
249
- .
250
- label-small is the right family -- "a small label: a badge, a tag, a
251
- field hint" -- but the wrong result: it would declare a weight of 500
252
- where nothing is declared today, and add wide tracking, making the
253
- control bolder and 2.2px wider. This control is deliberately recessive:
254
- muted, 2px of vertical padding, sitting beside the carousel dots as
255
- chrome whose job is not to compete with the quote cards. Adopting a role
256
- should not invert that.
257
- .
258
- control-label-small is the right meaning -- built for the name of a
259
- control -- and fails on the leading. See the line below. */
260
- font-size: var(--typography-fontSize-xs);
261
- /* no role: and this leading in particular cannot move. The line box is
262
- 18px, which with 2px of padding and a 1px border makes the button
263
- exactly 24px -- the same 24px as the .bd-awardsflex-dot buttons it shares
264
- a row with, which are 24 square deliberately for the WCAG 2.5.8 target
265
- size the comment further down explains.
266
- .
267
- control-label-small would take the line box to 15px and the button to
268
- 21px, misaligning it from the dots. Its tight leading exists for controls
269
- whose height is DERIVED from padding + fontSize x lineHeight, the way
270
- form-field computes one. This button's height is not derived -- it is
271
- matched to a sibling. So the constraint is a pixel alignment between two
272
- controls in one row, and nothing else in this file says so: a later pass
273
- adopting control-label-small on meaning alone would quietly break the
274
- row. */
275
- line-height: var(--typography-lineHeight-normal);
276
- cursor: pointer;
277
- }
278
-
279
- .bd-awardsflex-autoplay-toggle:hover {
280
- color: var(--color-text-primary);
281
- border-color: var(--color-border-strong);
282
- }
283
-
284
- .bd-awardsflex-autoplay-toggle:focus-visible {
285
- /* the one focus ring — see Foundations/Focus */
286
- outline: none;
287
- box-shadow: var(--focus-outline-primary);
288
- border-radius: var(--focus-outline-radius);
289
- }
290
-
291
- /* An 8px dot was an 8px target, which is a third of what WCAG 2.5.8 asks and
292
- close enough to its neighbour that the 24px circles would have overlapped
293
- as well. The button is the target and the dot is only what you see, so the
294
- button is 24 square and the dot is drawn inside it at the size it always
295
- was. Nothing about the component looks bigger; the part you can hit does. */
296
- .bd-awardsflex-dot {
297
- width: var(--dimension-24);
298
- height: var(--dimension-24);
299
- display: grid;
300
- place-items: center;
301
- border: none;
302
- background: transparent;
303
- padding: 0;
304
- cursor: pointer;
305
- }
306
-
307
- .bd-awardsflex-dot::before {
308
- content: "";
309
- width: var(--spacing-8);
310
- height: var(--spacing-8);
311
- border-radius: 50%;
312
- background: var(--color-neutral-300);
313
- transition: background var(--transition-duration-normal) var(--transition-easing-ease),
314
- transform var(--transition-duration-normal) var(--transition-easing-ease);
315
- }
316
-
317
- .bd-awardsflex-dot.active::before {
318
- /* The current slide's mark, and it takes the indicator role now that the
319
- role is not named for a shape. --indicator-color IS blue-500, so nothing
320
- moves; what changes is that this dot and the tab/nav underlines can no
321
- longer drift apart, which is the reason the role exists.
322
- .
323
- The geometry stays local on purpose: an underline slides its width, a dot
324
- scales, so --indicator-underline-height and its transition are not this
325
- component's to borrow. */
326
- background: var(--indicator-color);
327
- transform: scale(1.2);
328
- }
329
-
330
- .bd-awardsflex-dot:focus-visible {
331
- /* the one focus ring — see Foundations/Focus. It follows the target, not
332
- the dot, so keyboard focus is visible at the size the button really is. */
333
- outline: none;
334
- box-shadow: var(--focus-outline-primary);
335
- border-radius: var(--radius-full);
217
+ @media (prefers-reduced-motion: reduce) {
218
+ .bd-awardsflex-carousel-track {
219
+ transition: none;
220
+ }
336
221
  }
337
222
 
338
223
  @media ${unsafeCSS(below('sm'))} {
@@ -342,12 +227,29 @@ export const awardsFlexCSS = css`
342
227
  min-height: var(--bd-awardsflex-min-height-mobile, 280px);
343
228
  }
344
229
 
345
- .bd-awardsflex-quotes-row,
346
- .bd-awardsflex-carousel-slide {
230
+ /* The static row stacks. The carousel slide does not: below sm it holds
231
+ one card, and in a column the card's 200px flex-basis became its
232
+ height. In the row it fills the width and takes its own height. */
233
+ .bd-awardsflex-quotes-row {
347
234
  flex-direction: column;
348
235
  align-items: center;
349
236
  }
350
237
 
238
+ /* A stacked card is as tall as what it holds. In the column, the card's
239
+ 200px flex-basis became its height and the 160px min-height had nothing
240
+ left to do -- it keeps cards that stand side by side the same height,
241
+ and stacked cards stand side by side with nothing. Measured at 390:
242
+ 200px cards whose content ended at 111 and 132. The width is the
243
+ column's, so a short quote no longer hugs its source line.
244
+ .
245
+ The carousel slide keeps the min-height: there it stops the section
246
+ jumping in height from one slide to the next. */
247
+ .bd-awardsflex-quotes-row .bd-awardsflex-quote-card {
248
+ flex: none;
249
+ width: 100%;
250
+ min-height: 0;
251
+ }
252
+
351
253
  .bd-awardsflex-quote-card {
352
254
  max-width: none;
353
255
  }