@expressivecss/expressive 0.7.0-rc.0 → 0.8.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 (105) hide show
  1. package/CHANGELOG.md +1311 -1
  2. package/README.md +14 -11
  3. package/dist/css/expressive.css +4215 -1668
  4. package/dist/css/expressive.css.map +1 -1
  5. package/dist/css/expressive.min.css +1 -1
  6. package/dist/css/expressive.min.css.map +1 -1
  7. package/dist/js/expressive.cjs +1231 -1253
  8. package/dist/js/expressive.cjs.map +4 -4
  9. package/dist/js/expressive.js +1231 -1253
  10. package/dist/js/expressive.js.map +4 -4
  11. package/dist/js/expressive.min.js +12 -12
  12. package/dist/js/expressive.min.js.map +4 -4
  13. package/dist/js/expressive.mjs +1231 -1253
  14. package/dist/js/expressive.mjs.map +4 -4
  15. package/dist/types/behaviors/bottomSheets.d.ts +19 -11
  16. package/dist/types/behaviors/sheetDrag.d.ts +40 -0
  17. package/dist/types/behaviors/sideSheets.d.ts +2 -9
  18. package/dist/types/components/autocomplete.d.ts +15 -3
  19. package/dist/types/components/buttons.d.ts +5 -1
  20. package/dist/types/components/carousel.d.ts +87 -53
  21. package/dist/types/components/chips.d.ts +21 -1
  22. package/dist/types/components/expandingCard.d.ts +31 -0
  23. package/dist/types/components/index.d.ts +21 -20
  24. package/dist/types/components/lightbox.d.ts +1 -0
  25. package/dist/types/components/menu.d.ts +7 -1
  26. package/dist/types/components/{sidenav.d.ts → navigationDrawer.d.ts} +16 -16
  27. package/dist/types/components/registry.d.ts +8 -8
  28. package/dist/types/components/slider.d.ts +26 -85
  29. package/dist/types/components/snackbar.d.ts +20 -5
  30. package/dist/types/components/tabs.d.ts +12 -0
  31. package/dist/types/components/timepicker.d.ts +12 -9
  32. package/dist/types/core/utils.d.ts +64 -53
  33. package/dist/types/index.d.ts +8 -9
  34. package/package.json +15 -1
  35. package/src/sass/README.md +61 -11
  36. package/src/sass/abstracts/_breakpoints.scss +17 -19
  37. package/src/sass/abstracts/_mixins.scss +103 -21
  38. package/src/sass/abstracts/_variables.scss +50 -5
  39. package/src/sass/base/_global.scss +6 -24
  40. package/src/sass/base/_grid.scss +15 -12
  41. package/src/sass/base/_normalize.scss +34 -38
  42. package/src/sass/base/_typography.scss +4 -18
  43. package/src/sass/components/_badges.scss +2 -3
  44. package/src/sass/components/_banners.scss +298 -0
  45. package/src/sass/components/_bottom-app-bar.scss +180 -0
  46. package/src/sass/components/_bottom-sheet.scss +12 -4
  47. package/src/sass/components/_button-groups.scss +186 -0
  48. package/src/sass/components/_buttons.scss +245 -92
  49. package/src/sass/components/_cards.scss +494 -73
  50. package/src/sass/components/_carousel.scss +562 -68
  51. package/src/sass/components/_chips.scss +64 -5
  52. package/src/sass/components/_datepicker.scss +24 -24
  53. package/src/sass/components/_dialog.scss +17 -11
  54. package/src/sass/components/_drag-handle.scss +130 -0
  55. package/src/sass/components/_expanding-card.scss +260 -0
  56. package/src/sass/components/_fab-menu.scss +232 -0
  57. package/src/sass/components/_floating-sheet.scss +86 -0
  58. package/src/sass/components/_icon-buttons.scss +194 -0
  59. package/src/sass/components/_icons-material-design.scss +16 -4
  60. package/src/sass/components/_index.scss +22 -6
  61. package/src/sass/components/_list.scss +16 -13
  62. package/src/sass/components/_loading-indicator.scss +106 -0
  63. package/src/sass/components/_menu.scss +335 -61
  64. package/src/sass/components/_navbar.scss +71 -24
  65. package/src/sass/components/_navigation-bar.scss +13 -10
  66. package/src/sass/components/{_sidenav.scss → _navigation-drawer.scss} +123 -33
  67. package/src/sass/components/_navigation-rail.scss +22 -22
  68. package/src/sass/components/_page-footer.scss +19 -16
  69. package/src/sass/components/_pagination.scss +24 -6
  70. package/src/sass/components/_panes.scss +52 -11
  71. package/src/sass/components/{_preloader.scss → _progress.scss} +11 -3
  72. package/src/sass/components/_scrim.scss +37 -0
  73. package/src/sass/components/_search.scss +259 -0
  74. package/src/sass/components/_segmented-buttons.scss +188 -0
  75. package/src/sass/components/_side-sheet.scss +2 -2
  76. package/src/sass/components/_snackbar.scss +20 -15
  77. package/src/sass/components/_split-button.scss +196 -0
  78. package/src/sass/components/_tabs.scss +7 -8
  79. package/src/sass/components/_timepicker.scss +67 -58
  80. package/src/sass/components/_toolbar.scss +206 -80
  81. package/src/sass/components/forms/_checkboxes.scss +49 -12
  82. package/src/sass/components/forms/_fieldset.scss +4 -4
  83. package/src/sass/components/forms/_file-input.scss +8 -1
  84. package/src/sass/components/forms/_index.scss +1 -1
  85. package/src/sass/components/forms/_input-fields.scss +50 -44
  86. package/src/sass/components/forms/_radio-buttons.scss +42 -10
  87. package/src/sass/components/forms/_select.scss +0 -1
  88. package/src/sass/components/forms/{_range.scss → _slider.scss} +8 -9
  89. package/src/sass/components/forms/_switches.scss +6 -7
  90. package/src/sass/tokens/_index.scss +4 -1
  91. package/src/sass/tokens/_reference.scss +70 -61
  92. package/src/sass/tokens/_state.scss +24 -0
  93. package/src/sass/tokens/_theme.scss +6 -3
  94. package/src/sass/tokens/_vibrant.scss +76 -0
  95. package/src/sass/utilities/_colors.scss +14 -1
  96. package/src/sass/utilities/_helpers.scss +5 -5
  97. package/src/sass/utilities/_visibility.scss +48 -26
  98. package/dist/types/behaviors/waves.d.ts +0 -15
  99. package/dist/types/components/parallax.d.ts +0 -16
  100. package/dist/types/components/range.d.ts +0 -43
  101. package/dist/types/core/bounding.d.ts +0 -6
  102. package/dist/types/core/edges.d.ts +0 -6
  103. package/src/sass/components/_parallax.scss +0 -104
  104. package/src/sass/components/_pulse.scss +0 -37
  105. package/src/sass/components/_slider.scss +0 -129
@@ -3,16 +3,16 @@
3
3
  // Cards.
4
4
  //
5
5
  // Anatomy is the HTML. An <article> is an elevated card. A heading is the
6
- // headline, a <p> is supporting text, a direct <nav> (not .tabs) is the
7
- // action row,
8
- // <img> / <figure> is media. Helpers switch the M3 variant (filled, outlined)
9
- // or the layout (horizontal).
6
+ // headline, .subhead is the subhead, a <p> is supporting text, direct media
7
+ // is <img> / <picture> / <figure>, and direct .actions is the action row.
8
+ // Helpers switch the M3 variant (filled, outlined) or the layout
9
+ // (horizontal).
10
10
  //
11
11
  // Tokens follow https://m3.material.io/components/cards/specs
12
12
  // container surface-container-low, 12dp corners
13
- // elevated level 1, hover level 2
14
- // filled surface-container-highest, level 0
15
- // outlined surface + 1dp outline, level 0
13
+ // elevated level 1; interactive hover level 2
14
+ // filled surface-container-highest, level 0; interactive hover level 1
15
+ // outlined surface + 1dp outline, level 0; interactive hover level 1
16
16
  // headline title-medium / on-surface
17
17
  // body body-medium / on-surface-variant
18
18
  // padding 16dp
@@ -21,65 +21,222 @@ $_card: "article";
21
21
 
22
22
  :where(#{$_card}) {
23
23
  --md-comp-elevated-card-container-color: var(--md-sys-color-surface-container-low);
24
- --md-comp-elevated-card-container-shape: 12px;
24
+ --md-comp-filled-card-container-color: var(--md-sys-color-surface-container-highest);
25
+ --md-comp-outlined-card-container-color: var(--md-sys-color-surface);
26
+ --md-comp-outlined-card-outline-color: var(--md-sys-color-outline-variant);
27
+ --md-comp-outlined-card-disabled-outline-color: var(--md-sys-color-outline);
28
+ --md-comp-card-container-color: var(--md-comp-elevated-card-container-color);
29
+ --md-comp-card-container-shape: 12px;
30
+ --md-comp-card-media-shape: var(--md-comp-card-container-shape);
31
+ --md-comp-card-media-overlay-container-color: var(--md-sys-color-surface);
32
+ --md-comp-card-media-overlay-ink-color: var(--md-sys-color-on-surface);
33
+ --md-comp-card-media-overlay-shape: 8px;
34
+ --md-comp-card-headline-color: var(--md-sys-color-on-surface);
35
+ --md-comp-card-subhead-color: var(--md-sys-color-on-surface);
36
+ --md-comp-card-supporting-text-color: var(--md-sys-color-on-surface-variant);
37
+ --md-comp-card-content-padding: 16px;
38
+ --md-comp-card-between-space: 8px;
39
+ --md-comp-card-icon-color: var(--md-sys-color-primary);
40
+ --md-comp-card-icon-size: 24px;
41
+ --md-comp-card-state-layer-color: var(--md-sys-color-on-surface);
42
+ --md-comp-card-hover-state-layer-opacity: var(--md-sys-state-hover-state-layer-opacity);
43
+ --md-comp-card-focus-state-layer-opacity: var(--md-sys-state-focus-state-layer-opacity);
44
+ --md-comp-card-pressed-state-layer-opacity: var(--md-sys-state-pressed-state-layer-opacity);
45
+ --md-comp-card-dragged-state-layer-opacity: var(--md-sys-state-dragged-state-layer-opacity);
46
+ --md-comp-card-disabled-container-opacity: 38%;
47
+ --md-comp-outlined-card-disabled-outline-opacity: 12%;
48
+ --md-comp-card-focus-indicator-color: var(--md-sys-color-secondary);
49
+ --md-comp-card-focus-indicator-thickness: 3px;
50
+ --md-comp-card-focus-indicator-offset: 2px;
51
+ --md-comp-card-horizontal-media-width: 40%;
52
+ --md-comp-card-horizontal-min-height: 240px;
25
53
 
26
54
  position: relative;
27
55
  box-sizing: border-box;
28
- background-color: var(--md-comp-elevated-card-container-color);
56
+ background-color: var(--md-comp-card-container-color);
29
57
  color: var(--md-sys-color-on-surface);
30
- border-radius: var(--md-comp-elevated-card-container-shape);
58
+ border-radius: var(--md-comp-card-container-shape);
31
59
  overflow: hidden;
32
- transition: box-shadow .2s ease-out;
60
+ transition: background-color .2s ease-out, box-shadow .2s ease-out;
61
+ }
62
+
63
+ // M3 state layers belong only to an interactive card. A direct
64
+ // `.primary-action` link supplies the card's large action area without making
65
+ // secondary buttons invalid nested controls.
66
+ :where(#{$_card})::after {
67
+ content: "";
68
+ position: absolute;
69
+ inset: 0;
70
+ z-index: 2;
71
+ border-radius: inherit;
72
+ background-color: var(--md-comp-card-state-layer-color);
73
+ opacity: 0;
74
+ pointer-events: none;
75
+ transition: opacity .2s ease-out;
76
+ }
77
+
78
+ :where(#{$_card}) > .primary-action {
79
+ display: block;
80
+ color: inherit;
81
+ border-radius: inherit;
82
+ outline: 0;
33
83
  }
34
84
 
35
85
  // Default is elevated. :where() keeps specificity at zero so .outlined wins.
36
86
  :where(#{$_card}):not(.filled, .outlined, .border) {
37
87
  @include z-depth("1");
38
-
39
- &:hover {
40
- @include z-depth("2");
41
- }
42
88
  }
43
89
 
44
90
  :where(#{$_card}).filled {
91
+ --md-comp-card-container-color: var(--md-comp-filled-card-container-color);
45
92
  @include z-depth("0");
46
- background-color: var(--md-sys-color-surface-container-highest);
47
93
  }
48
94
 
49
95
  :where(#{$_card}).outlined,
50
96
  :where(#{$_card}).border {
97
+ --md-comp-card-container-color: var(--md-comp-outlined-card-container-color);
98
+ @include z-depth("0");
99
+ border: 1px solid var(--md-comp-outlined-card-outline-color);
100
+ }
101
+
102
+ // Hovered cards lift one level and apply an 8% on-surface state layer.
103
+ :where(#{$_card}):not(.filled, .outlined, .border):has(> .primary-action:hover) {
104
+ @include z-depth("2");
105
+ }
106
+
107
+ :where(#{$_card}):is(.filled, .outlined, .border):has(> .primary-action:hover) {
108
+ @include z-depth("1");
109
+ }
110
+
111
+ :where(#{$_card}):has(> .primary-action:hover)::after {
112
+ opacity: var(--md-comp-card-hover-state-layer-opacity);
113
+ }
114
+
115
+ // Focus and press retain their variant elevation. Focus gets M3's external
116
+ // 3dp indicator at a 2dp offset; the outlined variant also darkens its stroke.
117
+ :where(#{$_card}):has(> .primary-action:focus-visible) {
118
+ outline: var(--md-comp-card-focus-indicator-thickness) solid var(--md-comp-card-focus-indicator-color);
119
+ outline-offset: var(--md-comp-card-focus-indicator-offset);
120
+ }
121
+
122
+ :where(#{$_card}):has(> .primary-action:focus-visible)::after {
123
+ opacity: var(--md-comp-card-focus-state-layer-opacity);
124
+ }
125
+
126
+ :where(#{$_card}):is(.outlined, .border):has(> .primary-action:focus-visible) {
127
+ border-color: var(--md-sys-color-on-surface);
128
+ }
129
+
130
+ :where(#{$_card}):has(> .primary-action:active)::after {
131
+ opacity: var(--md-comp-card-pressed-state-layer-opacity);
132
+ }
133
+
134
+ // A reorder interaction toggles `.dragged`; every variant rises to level 3.
135
+ :where(#{$_card}).dragged {
136
+ @include z-depth("3");
137
+ }
138
+
139
+ :where(#{$_card}).dragged::after {
140
+ opacity: var(--md-comp-card-dragged-state-layer-opacity);
141
+ }
142
+
143
+ // Disabled tokens differ by variant in M3. `aria-disabled` belongs on the
144
+ // primary action; `.disabled` / article[aria-disabled] cover app-controlled
145
+ // cards that do not use a link.
146
+ :where(#{$_card}):is(.disabled, [aria-disabled="true"]),
147
+ :where(#{$_card}):has(> .primary-action[aria-disabled="true"]) {
51
148
  @include z-depth("0");
52
- border: 1px solid var(--md-sys-color-outline-variant);
149
+ outline: 0;
150
+ }
151
+
152
+ :where(#{$_card}):not(.filled, .outlined, .border):is(.disabled, [aria-disabled="true"]),
153
+ :where(#{$_card}):not(.filled, .outlined, .border):has(> .primary-action[aria-disabled="true"]) {
154
+ background-color: color-mix(
155
+ in oklab,
156
+ var(--md-sys-color-surface) var(--md-comp-card-disabled-container-opacity),
157
+ transparent
158
+ );
159
+ }
160
+
161
+ :where(#{$_card}).filled:is(.disabled, [aria-disabled="true"]),
162
+ :where(#{$_card}).filled:has(> .primary-action[aria-disabled="true"]) {
163
+ background-color: color-mix(
164
+ in oklab,
165
+ var(--md-sys-color-on-surface) var(--md-comp-card-disabled-container-opacity),
166
+ transparent
167
+ );
168
+ }
169
+
170
+ :where(#{$_card}):is(.outlined, .border):is(.disabled, [aria-disabled="true"]),
171
+ :where(#{$_card}):is(.outlined, .border):has(> .primary-action[aria-disabled="true"]) {
172
+ background-color: var(--md-comp-outlined-card-container-color);
173
+ border-color: color-mix(
174
+ in oklab,
175
+ var(--md-comp-outlined-card-disabled-outline-color) var(--md-comp-outlined-card-disabled-outline-opacity),
176
+ transparent
177
+ );
178
+ }
179
+
180
+ :where(#{$_card}):is(.disabled, [aria-disabled="true"])::after,
181
+ :where(#{$_card}):has(> .primary-action[aria-disabled="true"])::after {
182
+ opacity: 0;
183
+ }
184
+
185
+ :where(#{$_card}).disabled,
186
+ :where(#{$_card})[aria-disabled="true"],
187
+ :where(#{$_card}) > .primary-action[aria-disabled="true"] {
188
+ pointer-events: none;
189
+ }
190
+
191
+ :where(#{$_card}) .card-icon {
192
+ width: var(--md-comp-card-icon-size);
193
+ height: var(--md-comp-card-icon-size);
194
+ color: var(--md-comp-card-icon-color);
195
+ font-size: var(--md-comp-card-icon-size);
53
196
  }
54
197
 
55
198
  // --- Semantic children ----------------------------------------------------
56
199
 
57
200
  article > :is(h1, h2, h3, h4, h5, h6),
58
- article > header > :is(h1, h2, h3, h4, h5, h6) {
201
+ article > :is(header, .primary-action) > :is(h1, h2, h3, h4, h5, h6) {
59
202
  margin: 0;
60
- padding: 16px 16px 0;
203
+ padding: var(--md-comp-card-content-padding) var(--md-comp-card-content-padding) 0;
61
204
  font-family: var(--md-sys-typescale-title-medium-font-family-name);
62
205
  font-size: var(--md-sys-typescale-title-medium-font-size);
63
206
  font-weight: var(--md-sys-typescale-title-medium-font-weight);
64
207
  line-height: var(--md-sys-typescale-title-medium-line-height);
65
208
  letter-spacing: var(--md-sys-typescale-title-medium-letter-spacing);
66
- color: var(--md-sys-color-on-surface);
209
+ color: var(--md-comp-card-headline-color);
67
210
  }
68
211
 
69
212
  article > p,
70
- article > header > p {
213
+ article > :is(header, .primary-action) > p {
71
214
  margin: 0;
72
- padding: 8px 16px 16px;
215
+ padding: var(--md-comp-card-between-space) var(--md-comp-card-content-padding) var(--md-comp-card-content-padding);
73
216
  font-family: var(--md-sys-typescale-body-medium-font-family-name);
74
217
  font-size: var(--md-sys-typescale-body-medium-font-size);
75
218
  font-weight: var(--md-sys-typescale-body-medium-font-weight);
76
219
  line-height: var(--md-sys-typescale-body-medium-line-height);
77
220
  letter-spacing: var(--md-sys-typescale-body-medium-letter-spacing);
78
- color: color-mix(in oklab, currentColor 72%, transparent);
221
+ color: var(--md-comp-card-supporting-text-color);
222
+ }
223
+
224
+ // Optional subhead between the headline and supporting copy. Keep the class
225
+ // on a paragraph so the document retains useful reading semantics.
226
+ article > p.subhead,
227
+ article > :is(header, .primary-action) > p.subhead {
228
+ margin: 0;
229
+ padding: 4px var(--md-comp-card-content-padding) 0;
230
+ font-family: var(--md-sys-typescale-title-small-font-family-name);
231
+ font-size: var(--md-sys-typescale-title-small-font-size);
232
+ font-weight: var(--md-sys-typescale-title-small-font-weight);
233
+ line-height: var(--md-sys-typescale-title-small-line-height);
234
+ letter-spacing: var(--md-sys-typescale-title-small-letter-spacing);
235
+ color: var(--md-comp-card-subhead-color);
79
236
  }
80
237
 
81
238
  article > :is(h1, h2, h3, h4, h5, h6) + p,
82
- article > header > :is(h1, h2, h3, h4, h5, h6) + p {
239
+ article > :is(header, .primary-action) > :is(h1, h2, h3, h4, h5, h6) + p {
83
240
  padding-top: 0;
84
241
  }
85
242
 
@@ -89,63 +246,204 @@ article > header {
89
246
  color: inherit;
90
247
  }
91
248
 
92
- // Action row. nav.tabs is a tab bar, not actions — leave it to _tabs.scss.
93
- article > nav:not(.tabs) {
249
+ // Action row. A row of buttons, not destinations.
250
+ article > .actions {
251
+ position: relative;
252
+ z-index: 3;
94
253
  display: flex;
95
254
  flex-wrap: wrap;
96
255
  align-items: center;
97
- gap: 8px;
256
+ gap: var(--md-comp-card-between-space);
98
257
  padding: 4px 8px 8px 8px;
99
258
  margin: 0;
100
259
  }
101
260
 
102
261
  article > img,
103
262
  article > picture,
104
- article > figure {
263
+ article > figure,
264
+ article > .primary-action > img,
265
+ article > .primary-action > picture,
266
+ article > .primary-action > figure {
105
267
  display: block;
106
268
  width: 100%;
107
269
  margin: 0;
270
+ border-radius: var(--md-comp-card-media-shape);
271
+ overflow: hidden;
108
272
  }
109
273
 
110
274
  article > img,
111
275
  article > picture img,
112
- article > figure img {
276
+ article > figure img,
277
+ article > .primary-action > img,
278
+ article > .primary-action > picture img,
279
+ article > .primary-action > figure img {
113
280
  display: block;
114
281
  width: 100%;
115
282
  object-fit: cover;
283
+ border-radius: var(--md-comp-card-media-shape);
116
284
  }
117
285
 
118
286
  article > figure {
119
287
  position: relative;
120
288
  }
121
289
 
122
- article > figure > figcaption {
290
+ article > .primary-action > figure {
291
+ position: relative;
292
+ }
293
+
294
+ article > figure > figcaption,
295
+ article > .primary-action > figure > figcaption {
123
296
  position: absolute;
124
- inset-inline: 0;
125
- bottom: 0;
297
+ inset-inline-start: var(--md-comp-card-content-padding);
298
+ inset-inline-end: auto;
299
+ bottom: var(--md-comp-card-content-padding);
300
+ display: inline-flex;
301
+ align-items: center;
302
+ gap: var(--md-comp-card-between-space);
303
+ box-sizing: border-box;
304
+ width: fit-content;
305
+ max-width: calc(100% - (2 * var(--md-comp-card-content-padding)));
126
306
  margin: 0;
127
- padding: 16px;
128
- color: var(--md-sys-color-on-surface);
129
- background: color-mix(in oklab, var(--md-sys-color-surface) 72%, transparent);
307
+ padding: 8px 12px;
308
+ color: var(--md-comp-card-media-overlay-ink-color);
309
+ background-color: var(--md-comp-card-media-overlay-container-color);
310
+ border-radius: var(--md-comp-card-media-overlay-shape);
130
311
  font-family: var(--md-sys-typescale-title-medium-font-family-name);
131
312
  font-size: var(--md-sys-typescale-title-medium-font-size);
132
313
  font-weight: var(--md-sys-typescale-title-medium-font-weight);
133
314
  line-height: var(--md-sys-typescale-title-medium-line-height);
134
315
  }
135
316
 
136
- // Horizontal: media on the start, body stacked beside it.
137
- article.horizontal {
138
- display: flex;
139
- flex-direction: row;
317
+ // Keep meaningful icons on the same opaque role pair as caption text. The
318
+ // surface/on-surface pairing isolates both from unpredictable image colors.
319
+ article > figure > figcaption > :is(.material-symbols, .card-icon, svg),
320
+ article > .primary-action > figure > figcaption > :is(.material-symbols, .card-icon, svg) {
321
+ flex: 0 0 auto;
322
+ color: inherit;
323
+ font-size: var(--md-comp-card-icon-size);
324
+ line-height: 1;
325
+ }
326
+
327
+ article > figure > figcaption > svg,
328
+ article > .primary-action > figure > figcaption > svg {
329
+ fill: currentColor;
330
+ }
331
+
332
+ // Collections are a grid by default. The full track definition is a token so
333
+ // applications can supply fixed, responsive, or asymmetric columns without
334
+ // fighting component selectors. Every card rests on the same plane; only the
335
+ // picked-up / dragged state rises above its neighbours.
336
+ :where(.card-collection) {
337
+ --md-comp-card-collection-gap: 8px;
338
+ --md-comp-card-collection-min-card-width: 240px;
339
+ --md-comp-card-collection-grid-template-columns: repeat(
340
+ auto-fit,
341
+ minmax(min(100%, var(--md-comp-card-collection-min-card-width)), 1fr)
342
+ );
343
+ --md-comp-card-collection-resting-shadow: none;
344
+ --md-comp-card-collection-picked-up-shadow:
345
+ 0 8px 17px 2px rgba(0, 0, 0, 0.14),
346
+ 0 3px 14px 2px rgba(0, 0, 0, 0.12),
347
+ 0 5px 5px -3px rgba(0, 0, 0, 0.2);
348
+ --md-comp-card-collection-mosaic-row-size: 80px;
349
+ --md-comp-card-collection-carousel-height: 320px;
350
+ --md-comp-card-collection-carousel-card-width: min(82%, 360px);
351
+
352
+ display: grid;
353
+ grid-template-columns: var(--md-comp-card-collection-grid-template-columns);
354
+ gap: var(--md-comp-card-collection-gap);
140
355
  align-items: stretch;
356
+ margin: 0;
357
+ padding: 0;
358
+ }
359
+
360
+ :where(.card-collection article):not(.dragged, .picked-up) {
361
+ box-shadow: var(--md-comp-card-collection-resting-shadow) !important;
362
+ }
363
+
364
+ :where(.card-collection article):is(.dragged, .picked-up) {
365
+ z-index: 4;
366
+ box-shadow: var(--md-comp-card-collection-picked-up-shadow) !important;
367
+ }
368
+
369
+ :where(.card-collection article):not(.horizontal) {
370
+ display: flex;
371
+ flex-direction: column;
372
+ min-width: 0;
373
+ }
374
+
375
+ :where(.card-collection article):not(.horizontal) > .actions {
376
+ margin-block-start: auto;
377
+ }
378
+
379
+ // A list is the same collection with one column. The cards retain their own
380
+ // vertical / horizontal orientation.
381
+ .card-collection.list {
382
+ --md-comp-card-collection-grid-template-columns: minmax(0, 1fr);
383
+
384
+ margin: 0;
385
+ }
386
+
387
+ // Staggered uses multicolumn flow for masonry-like intrinsic card heights.
388
+ :where(.card-collection.staggered) {
389
+ display: block;
390
+ columns: var(
391
+ --md-comp-card-collection-staggered-column-width,
392
+ var(--md-comp-card-collection-min-card-width)
393
+ );
394
+ column-gap: var(--md-comp-card-collection-gap);
395
+ }
396
+
397
+ :where(.card-collection.staggered) > article {
398
+ break-inside: avoid;
399
+ width: 100%;
400
+ margin-block: 0 var(--md-comp-card-collection-gap);
401
+ }
402
+
403
+ // Mosaic keeps source order in a dense grid. Per-card custom properties make
404
+ // spans data-driven instead of baking a decorative nth-child pattern into the
405
+ // framework.
406
+ :where(.card-collection.mosaic) {
407
+ grid-auto-flow: dense;
408
+ grid-auto-rows: var(--md-comp-card-collection-mosaic-row-size);
409
+ }
410
+
411
+ :where(.card-collection.mosaic) > article {
412
+ grid-column: span var(--md-comp-card-collection-column-span, 1);
413
+ grid-row: span var(--md-comp-card-collection-row-span, 2);
414
+ }
415
+
416
+ // Reuse the M3 uncontained Carousel for a horizontally scrolling collection.
417
+ // Carousel.Init()/AutoInit() supplies the track and interaction behavior.
418
+ .card-collection.carousel {
419
+ --carousel-height: var(--md-comp-card-collection-carousel-height);
420
+ --md-comp-carousel-gap: var(--md-comp-card-collection-gap);
421
+ --md-comp-carousel-uncontained-item-width: var(
422
+ --md-comp-card-collection-carousel-card-width
423
+ );
424
+
425
+ display: block;
426
+ }
427
+
428
+ // Horizontal uses the same direct semantic slots as the vertical card. Grid
429
+ // moves the media to the start and stacks headline, subhead, supporting text,
430
+ // and actions beside it without requiring a second content wrapper.
431
+ article.horizontal {
432
+ display: grid;
433
+ grid-template-columns:
434
+ minmax(0, var(--md-comp-card-horizontal-media-width))
435
+ minmax(0, 1fr);
436
+ grid-template-rows: auto auto minmax(0, 1fr) auto;
437
+ min-height: var(--md-comp-card-horizontal-min-height);
141
438
  overflow: hidden;
142
439
 
143
440
  > img,
144
441
  > picture,
145
442
  > figure {
146
- flex: 0 0 40%;
147
- max-width: 40%;
148
- width: 40%;
443
+ grid-column: 1;
444
+ grid-row: 1 / -1;
445
+ width: 100%;
446
+ height: 100%;
149
447
  align-self: stretch;
150
448
  }
151
449
 
@@ -156,11 +454,27 @@ article.horizontal {
156
454
  object-fit: cover;
157
455
  }
158
456
 
159
- > div {
160
- flex: 1 1 auto;
161
- min-width: 0;
162
- display: flex;
163
- flex-direction: column;
457
+ > :is(h1, h2, h3, h4, h5, h6),
458
+ > header {
459
+ grid-column: 2;
460
+ grid-row: 1;
461
+ }
462
+
463
+ > p.subhead {
464
+ grid-column: 2;
465
+ grid-row: 2;
466
+ }
467
+
468
+ > p:not(.subhead) {
469
+ grid-column: 2;
470
+ grid-row: 3;
471
+ }
472
+
473
+ > .actions {
474
+ grid-column: 2;
475
+ grid-row: 4;
476
+ align-self: end;
477
+ justify-content: flex-end;
164
478
  }
165
479
  }
166
480
 
@@ -189,7 +503,7 @@ article.small, article.medium, article.large {
189
503
  overflow: hidden;
190
504
  }
191
505
 
192
- > nav:not(.tabs) {
506
+ > .actions {
193
507
  margin-top: auto;
194
508
  }
195
509
  }
@@ -197,44 +511,156 @@ article.small, article.medium, article.large {
197
511
  article.horizontal.small,
198
512
  article.horizontal.medium,
199
513
  article.horizontal.large {
200
- flex-direction: row;
514
+ display: grid;
201
515
 
202
516
  > img, > picture, > figure {
203
- flex: 0 0 40%;
204
517
  max-height: none;
205
518
  }
206
519
  }
207
520
 
208
- // Reveal overlay. aside is the panel. Parked under the card (translateY 100%)
209
- // and clipped by overflow:hidden. Open is aria-expanded="true" → slide up
210
- // to cover. Do not use inset:0 as the closed geometry and then
211
- // translateY(-100%): that slides the panel *off* the card, and isOpen stays
212
- // true so a second click is a no-op.
521
+ // Reveal panel. The summary and media remain in place while the card grows
522
+ // downward to expose additional actions, matching the M3 expand/collapse
523
+ // example. `interpolate-size` lets intrinsic content height animate without a
524
+ // fixed cap; the @supports fallback keeps the interaction functional.
525
+ :where(#{$_card}):has(> aside) {
526
+ interpolate-size: allow-keywords;
527
+ }
528
+
213
529
  article > aside {
214
- --md-comp-card-reveal-duration: 300ms;
215
- --md-comp-card-reveal-easing: ease;
530
+ --md-comp-card-reveal-duration: 500ms;
531
+ --md-comp-card-reveal-easing: cubic-bezier(0.2, 0, 0, 1);
216
532
 
217
- position: absolute;
218
- inset: 0;
219
- z-index: 3;
533
+ position: relative;
534
+ inset: auto;
535
+ z-index: 1;
220
536
  box-sizing: border-box;
221
- padding: 16px;
222
- overflow-y: auto;
223
- background-color: var(--md-sys-color-surface);
537
+ height: 0;
538
+ min-height: 0;
539
+ padding: 0 var(--md-comp-card-content-padding);
540
+ overflow: clip;
541
+ background-color: inherit;
224
542
  color: var(--md-sys-color-on-surface);
225
- transform: translateY(100%);
543
+ opacity: 0;
226
544
  visibility: hidden;
227
545
  pointer-events: none;
228
546
  transition:
229
- transform var(--md-comp-card-reveal-duration) var(--md-comp-card-reveal-easing),
547
+ height var(--md-comp-card-reveal-duration) var(--md-comp-card-reveal-easing),
548
+ padding-block var(--md-comp-card-reveal-duration) var(--md-comp-card-reveal-easing),
549
+ opacity 150ms ease-out,
230
550
  visibility 0s var(--md-comp-card-reveal-duration);
231
551
  }
232
552
 
233
553
  article > aside[aria-expanded="true"] {
234
- transform: translateY(0);
554
+ height: auto;
555
+ padding-block: 0 var(--md-comp-card-content-padding);
556
+ opacity: 1;
235
557
  visibility: visible;
236
558
  pointer-events: auto;
237
- transition: transform var(--md-comp-card-reveal-duration) var(--md-comp-card-reveal-easing);
559
+ transition:
560
+ height var(--md-comp-card-reveal-duration) var(--md-comp-card-reveal-easing),
561
+ padding-block var(--md-comp-card-reveal-duration) var(--md-comp-card-reveal-easing),
562
+ opacity 200ms 100ms ease-out,
563
+ visibility 0s;
564
+ }
565
+
566
+ @supports not (interpolate-size: allow-keywords) {
567
+ article > aside {
568
+ height: auto;
569
+ max-height: 0;
570
+ transition:
571
+ max-height var(--md-comp-card-reveal-duration) var(--md-comp-card-reveal-easing),
572
+ padding-block var(--md-comp-card-reveal-duration) var(--md-comp-card-reveal-easing),
573
+ opacity 150ms ease-out,
574
+ visibility 0s var(--md-comp-card-reveal-duration);
575
+ }
576
+
577
+ article > aside[aria-expanded="true"] {
578
+ max-height: var(--md-comp-card-reveal-fallback-height, 320px);
579
+ }
580
+ }
581
+
582
+ // A media-sized button supplies the repeated open/close target and carries the
583
+ // state layer for the tap feedback shown by the M3 example.
584
+ article > figure > .card-reveal-trigger {
585
+ position: absolute;
586
+ inset: 0;
587
+ z-index: 1;
588
+ display: block;
589
+ width: 100%;
590
+ height: 100%;
591
+ min-width: 0;
592
+ min-height: 0;
593
+ margin: 0;
594
+ padding: 0;
595
+ background: none;
596
+ border: 0;
597
+ border-radius: inherit;
598
+ box-shadow: none;
599
+ color: transparent;
600
+ cursor: pointer;
601
+ }
602
+
603
+ // The trigger paints its own state layer: the card's `::after` keys on
604
+ // `> .primary-action`, which this is not. It reads the same `--md-comp-card-*`
605
+ // opacities, so tuning one card still reaches it.
606
+ article > figure > .card-reveal-trigger:hover {
607
+ background-color: color-mix(in oklab, var(--md-comp-card-state-layer-color) calc(var(--md-comp-card-hover-state-layer-opacity) * 100%), transparent);
608
+ box-shadow: none;
609
+ }
610
+
611
+ article > figure > .card-reveal-trigger:focus-visible {
612
+ background-color: color-mix(in oklab, var(--md-comp-card-state-layer-color) calc(var(--md-comp-card-focus-state-layer-opacity) * 100%), transparent);
613
+ box-shadow: none;
614
+ outline: var(--md-comp-card-focus-indicator-thickness) solid var(--md-comp-card-focus-indicator-color);
615
+ outline-offset: calc(-1 * var(--md-comp-card-focus-indicator-thickness));
616
+ }
617
+
618
+ article > figure > .card-reveal-trigger:active {
619
+ background-color: color-mix(in oklab, var(--md-comp-card-state-layer-color) calc(var(--md-comp-card-pressed-state-layer-opacity) * 100%), transparent);
620
+ box-shadow: none;
621
+ }
622
+
623
+ article > header.card-reveal-summary {
624
+ padding-bottom: var(--md-comp-card-content-padding);
625
+ }
626
+
627
+ article > aside > .reveal-actions {
628
+ display: grid;
629
+ gap: 0;
630
+ margin: 0;
631
+ font-style: normal;
632
+ }
633
+
634
+ article > aside > .reveal-actions > .reveal-action {
635
+ display: grid;
636
+ grid-template-columns: var(--md-comp-card-icon-size) minmax(0, 1fr);
637
+ align-items: center;
638
+ gap: var(--md-comp-card-content-padding);
639
+ min-height: 48px;
640
+ padding-inline: 0;
641
+ color: var(--md-sys-color-on-surface);
642
+ border-radius: 8px;
643
+ font-family: var(--md-sys-typescale-body-medium-font-family-name);
644
+ font-size: var(--md-sys-typescale-body-medium-font-size);
645
+ font-weight: var(--md-sys-typescale-body-medium-font-weight);
646
+ line-height: var(--md-sys-typescale-body-medium-line-height);
647
+ letter-spacing: var(--md-sys-typescale-body-medium-letter-spacing);
648
+ }
649
+
650
+ article > aside > .reveal-actions > .reveal-action:hover {
651
+ background-color: color-mix(
652
+ in oklab,
653
+ var(--md-sys-color-on-surface) calc(var(--md-sys-state-hover-state-layer-opacity) * 100%),
654
+ transparent
655
+ );
656
+ }
657
+
658
+ article > aside > .reveal-actions > .reveal-action > :is(.material-symbols, .card-icon, svg) {
659
+ width: var(--md-comp-card-icon-size);
660
+ height: var(--md-comp-card-icon-size);
661
+ color: var(--md-sys-color-on-surface-variant);
662
+ font-size: var(--md-comp-card-icon-size);
663
+ fill: currentColor;
238
664
  }
239
665
 
240
666
  @media (prefers-reduced-motion: reduce) {
@@ -243,13 +669,8 @@ article > aside[aria-expanded="true"] {
243
669
  }
244
670
  }
245
671
 
246
- article.sticky > nav {
672
+ article.sticky > :is(nav, .actions) {
247
673
  position: relative;
248
674
  z-index: 4;
249
675
  background-color: inherit;
250
676
  }
251
-
252
- article.sticky > aside {
253
- bottom: auto;
254
- height: calc(100% - 52px);
255
- }