@expressivecss/expressive 0.3.0 → 0.4.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 (74) hide show
  1. package/README.md +1 -1
  2. package/dist/css/expressive.css +1011 -1339
  3. package/dist/css/expressive.css.map +1 -1
  4. package/dist/css/expressive.min.css +1 -1
  5. package/dist/css/expressive.min.css.map +1 -1
  6. package/dist/js/expressive.cjs +701 -853
  7. package/dist/js/expressive.cjs.map +4 -4
  8. package/dist/js/expressive.js +701 -853
  9. package/dist/js/expressive.js.map +4 -4
  10. package/dist/js/expressive.min.js +10 -20
  11. package/dist/js/expressive.min.js.map +4 -4
  12. package/dist/js/expressive.mjs +701 -854
  13. package/dist/js/expressive.mjs.map +4 -4
  14. package/dist/types/components/autocomplete.d.ts +2 -9
  15. package/dist/types/components/buttons.d.ts +2 -2
  16. package/dist/types/components/cards.d.ts +3 -6
  17. package/dist/types/components/carousel.d.ts +3 -3
  18. package/dist/types/components/characterCounter.d.ts +3 -5
  19. package/dist/types/components/chips.d.ts +2 -2
  20. package/dist/types/components/collapsible.d.ts +6 -23
  21. package/dist/types/components/datepicker.d.ts +2 -8
  22. package/dist/types/components/dropdown.d.ts +11 -2
  23. package/dist/types/components/index.d.ts +0 -1
  24. package/dist/types/components/lightbox.d.ts +2 -2
  25. package/dist/types/components/parallax.d.ts +2 -8
  26. package/dist/types/components/pushpin.d.ts +2 -2
  27. package/dist/types/components/range.d.ts +2 -2
  28. package/dist/types/components/registry.d.ts +0 -4
  29. package/dist/types/components/scrollspy.d.ts +26 -50
  30. package/dist/types/components/select.d.ts +20 -6
  31. package/dist/types/components/sidenav.d.ts +40 -55
  32. package/dist/types/components/slider.d.ts +2 -2
  33. package/dist/types/components/tabs.d.ts +2 -2
  34. package/dist/types/components/tapTarget.d.ts +2 -2
  35. package/dist/types/components/timepicker.d.ts +2 -4
  36. package/dist/types/components/tooltip.d.ts +2 -2
  37. package/dist/types/core/component.d.ts +4 -4
  38. package/dist/types/index.d.ts +1 -1
  39. package/package.json +2 -2
  40. package/src/sass/README.md +2 -6
  41. package/src/sass/abstracts/_breakpoints.scss +0 -16
  42. package/src/sass/abstracts/_variables.scss +0 -21
  43. package/src/sass/base/_grid.scss +0 -19
  44. package/src/sass/components/_badges.scss +1 -1
  45. package/src/sass/components/_buttons.scss +22 -54
  46. package/src/sass/components/_cards.scss +50 -123
  47. package/src/sass/components/_carousel.scss +4 -6
  48. package/src/sass/components/_collapsible.scss +13 -15
  49. package/src/sass/components/_collection.scss +9 -27
  50. package/src/sass/components/_datepicker.scss +1 -15
  51. package/src/sass/components/_dropdown.scss +110 -14
  52. package/src/sass/components/_index.scss +1 -0
  53. package/src/sass/components/_modal.scss +13 -31
  54. package/src/sass/components/_navbar.scss +9 -6
  55. package/src/sass/components/_page-footer.scss +134 -15
  56. package/src/sass/components/_parallax.scss +7 -28
  57. package/src/sass/components/_preloader.scss +145 -300
  58. package/src/sass/components/_scrollspy.scss +18 -0
  59. package/src/sass/components/_sidenav.scss +226 -120
  60. package/src/sass/components/_table_of_contents.scss +30 -14
  61. package/src/sass/components/_tabs.scss +54 -35
  62. package/src/sass/components/_tapTarget.scss +2 -6
  63. package/src/sass/components/_toast.scss +10 -11
  64. package/src/sass/components/_toolbar.scss +2 -2
  65. package/src/sass/components/_tooltip.scss +4 -9
  66. package/src/sass/components/_transitions.scss +0 -5
  67. package/src/sass/components/forms/_index.scss +0 -5
  68. package/src/sass/components/forms/_input-fields.scss +49 -52
  69. package/src/sass/components/forms/_select.scss +175 -60
  70. package/src/sass/components/forms/_switches.scss +21 -8
  71. package/src/sass/utilities/_helpers.scss +1 -3
  72. package/src/sass/utilities/_typescale.scss +0 -5
  73. package/src/sass/utilities/_visibility.scss +2 -2
  74. package/dist/types/components/modal.d.ts +0 -45
@@ -5,26 +5,27 @@
5
5
  // Anatomy is the HTML. A <menu id> is the surface; each <li> is an item,
6
6
  // an <hr> is a divider, and an <i> inside the item is the leading icon.
7
7
  // The trigger is still .dropdown-trigger[data-target] — that is the JS
8
- // contract. .dropdown-content is an alias (FormSelect and Autocomplete
9
- // still generate it).
8
+ // contract.
10
9
  //
11
10
  // Tokens follow https://m3.material.io/components/menus/specs
12
- // container surface, 4dp corners, elevation 2, 112–280dp
13
- // item 48dp, label-large / on-surface, 12dp inset
11
+ // plus the 1rem surface / stadium items used by fields and select.
12
+ // container surface, 1rem corners, elevation 2, 112–280dp
13
+ // item 48dp, 2rem corners, label-large / on-surface, 12dp inset
14
14
  // leading icon 24dp, on-surface-variant
15
15
  // divider 1dp outline-variant
16
16
  // state layer 8% hover / 10% selected
17
17
 
18
+ $_menu-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='black' d='M10 6l6 6-6 6'/%3E%3C/svg%3E");
19
+
18
20
  [popover] {
19
21
  outline: none;
20
22
  padding: 0;
21
23
  border: none;
22
24
  }
23
25
 
24
- .dropdown-content,
25
26
  menu[id] {
26
27
  --md-comp-menu-container-color: var(--md-sys-color-surface);
27
- --md-comp-menu-container-shape: 4px;
28
+ --md-comp-menu-container-shape: 1rem;
28
29
  --md-comp-menu-item-container-height: 48px;
29
30
 
30
31
  box-sizing: border-box;
@@ -47,6 +48,7 @@ menu[id] {
47
48
  @include z-depth("2");
48
49
 
49
50
  > li {
51
+ box-sizing: border-box;
50
52
  margin: 0;
51
53
  padding: 0;
52
54
  width: 100%;
@@ -70,6 +72,11 @@ menu[id] {
70
72
  color: var(--md-sys-color-on-surface);
71
73
  text-decoration: none;
72
74
  cursor: pointer;
75
+
76
+ &:hover,
77
+ &:focus {
78
+ background: none;
79
+ }
73
80
  @include typescale("label-large");
74
81
  font-family: $font-stack;
75
82
  }
@@ -86,6 +93,12 @@ menu[id] {
86
93
  }
87
94
  }
88
95
 
96
+ > li:not(.disabled, .divider, .optgroup) {
97
+ width: calc(100% - 16px);
98
+ margin-inline: 8px;
99
+ border-radius: 2rem;
100
+ }
101
+
89
102
  > li:not(.disabled, .divider):hover,
90
103
  > li.active {
91
104
  background-color: color-mix(in oklab, var(--md-sys-color-on-surface) 8%, transparent);
@@ -108,22 +121,105 @@ menu[id] {
108
121
  background-color: var(--md-sys-color-outline-variant);
109
122
  cursor: default;
110
123
  }
124
+
125
+ // Nested flyouts sit outside the list box; do not clip them.
126
+ &:has(> li > menu) {
127
+ overflow: visible;
128
+ }
129
+
130
+ li:has(> menu) {
131
+ position: relative;
132
+ }
133
+
134
+ li:has(> menu) > :is(a, button, span)::after {
135
+ content: "";
136
+ flex: 0 0 24px;
137
+ width: 24px;
138
+ height: 24px;
139
+ margin-inline-start: auto;
140
+ background-color: var(--md-sys-color-on-surface-variant);
141
+ mask: $_menu-chevron center / 24px no-repeat;
142
+ }
143
+ }
144
+
145
+ // Nested menu. Optional, desktop-first. No second Dropdown instance —
146
+ // a <menu> inside an <li> is a flyout. Hover / :focus-within on fine
147
+ // pointers; .open is the tap / keyboard switch.
148
+ menu[id] li > menu {
149
+ --md-comp-menu-container-color: var(--md-sys-color-surface);
150
+ --md-comp-menu-container-shape: 1rem;
151
+ --md-comp-menu-item-container-height: 48px;
152
+
153
+ box-sizing: border-box;
154
+ display: none;
155
+ position: absolute;
156
+ inset-block-start: 0;
157
+ inset-inline-start: 100%;
158
+ z-index: 1;
159
+ min-width: 112px;
160
+ margin: 0;
161
+ padding: 8px 0;
162
+ list-style: none;
163
+ overflow: visible;
164
+ opacity: 1;
165
+ transform: none;
166
+ background-color: var(--md-comp-menu-container-color);
167
+ border-radius: var(--md-comp-menu-container-shape);
168
+ color: var(--md-sys-color-on-surface);
169
+ @include z-depth("2");
170
+
171
+ > li {
172
+ box-sizing: border-box;
173
+ min-height: var(--md-comp-menu-item-container-height);
174
+ }
175
+
176
+ > li:not(.disabled, .divider, .optgroup) {
177
+ width: calc(100% - 16px);
178
+ margin-inline: 8px;
179
+ border-radius: 2rem;
180
+ }
181
+
182
+ > li:not(.disabled, .divider):hover,
183
+ > li.active {
184
+ background-color: color-mix(in oklab, var(--md-sys-color-on-surface) 8%, transparent);
185
+ }
186
+
187
+ > li > :is(a, button, span) {
188
+ display: flex;
189
+ align-items: center;
190
+ gap: 12px;
191
+ box-sizing: border-box;
192
+ width: 100%;
193
+ min-height: var(--md-comp-menu-item-container-height);
194
+ padding: 0 12px;
195
+ color: var(--md-sys-color-on-surface);
196
+ text-decoration: none;
197
+ @include typescale("label-large");
198
+ font-family: $font-stack;
199
+ }
200
+ }
201
+
202
+ @media (hover: hover) and (pointer: fine) {
203
+ menu[id] li:is(:hover, :focus-within) > menu {
204
+ display: block;
205
+ }
206
+ }
207
+
208
+ menu[id] li.open > menu {
209
+ display: block;
210
+ }
211
+
212
+ menu[id] li.submenu-start > menu {
213
+ inset-inline-start: auto;
214
+ inset-inline-end: 100%;
111
215
  }
112
216
 
113
217
  body.keyboard-focused {
114
- .dropdown-content > li:focus,
115
218
  menu[id] > li:focus {
116
219
  background-color: color-mix(in oklab, var(--md-sys-color-on-surface) 10%, transparent);
117
220
  }
118
221
  }
119
222
 
120
- .input-field.col .dropdown-content [type="checkbox"] + label {
121
- top: 1px;
122
- left: 0;
123
- height: 18px;
124
- transform: none;
125
- }
126
-
127
223
  .dropdown-trigger {
128
224
  cursor: pointer;
129
225
  }
@@ -23,6 +23,7 @@
23
23
  @forward "page-footer";
24
24
  @forward "forms";
25
25
  @forward "table_of_contents";
26
+ @forward "scrollspy";
26
27
  @forward "sidenav";
27
28
  @forward "preloader";
28
29
  @forward "slider";
@@ -4,8 +4,7 @@
4
4
  //
5
5
  // Anatomy is the HTML. A <dialog> is a basic dialog. A heading is the
6
6
  // headline, a <p> (or a wrapping <div>) is supporting text, and the last
7
- // child <form method="dialog"> or <nav> is the action row. .modal and
8
- // .modal-header / .modal-content / .modal-footer stay as aliases.
7
+ // child <form method="dialog"> or <nav> is the action row.
9
8
  //
10
9
  // Tokens follow https://m3.material.io/components/dialogs/specs
11
10
  // container surface, 28dp corners, 280–560dp, elevation 3
@@ -16,8 +15,7 @@
16
15
  // scrim 32% + 12px blur
17
16
  // padding 24dp
18
17
 
19
- dialog,
20
- .modal {
18
+ dialog {
21
19
  --md-comp-basic-dialog-container-color: var(--md-sys-color-surface);
22
20
  --md-comp-basic-dialog-container-shape: 28px;
23
21
  --md-comp-basic-dialog-container-min-width: 280px;
@@ -51,8 +49,7 @@ html:has(dialog:modal) {
51
49
  overflow: hidden;
52
50
  }
53
51
 
54
- dialog[open],
55
- .modal[open] {
52
+ dialog[open] {
56
53
  display: flex;
57
54
  flex-direction: column;
58
55
  overflow: hidden;
@@ -71,8 +68,7 @@ dialog > i:first-child {
71
68
  color: var(--md-sys-color-secondary);
72
69
  }
73
70
 
74
- dialog > :is(h1, h2, h3, h4, h5, h6),
75
- .modal-header {
71
+ dialog > :is(h1, h2, h3, h4, h5, h6) {
76
72
  flex: 0 0 auto;
77
73
  margin: 0;
78
74
  padding: 24px 24px 16px;
@@ -90,8 +86,7 @@ dialog > i:first-child + :is(h1, h2, h3, h4, h5, h6) {
90
86
  }
91
87
 
92
88
  dialog > p,
93
- dialog > div,
94
- .modal-content {
89
+ dialog > div {
95
90
  flex: 1 1 auto;
96
91
  min-height: 0;
97
92
  overflow: auto;
@@ -106,14 +101,12 @@ dialog > div,
106
101
  color: color-mix(in oklab, currentColor 72%, transparent);
107
102
  }
108
103
 
109
- dialog > :is(h1, h2, h3, h4, h5, h6) + p,
110
- .modal-header + .modal-content {
104
+ dialog > :is(h1, h2, h3, h4, h5, h6) + p {
111
105
  padding-top: 0;
112
106
  }
113
107
 
114
108
  // Action row. form[method=dialog] is the native close mechanism.
115
- dialog > :is(form, nav):last-child,
116
- .modal-footer {
109
+ dialog > :is(form, nav):last-child {
117
110
  display: flex;
118
111
  flex-wrap: wrap;
119
112
  justify-content: flex-end;
@@ -126,16 +119,11 @@ dialog > :is(form, nav):last-child,
126
119
  background: none;
127
120
  }
128
121
 
129
- .modal-close {
130
- cursor: pointer;
131
- }
132
-
133
122
  // Full-screen (M3). Takes the viewport; no corners.
134
123
  // UA `dialog:modal` sets max-width: calc(100% - 2em - 6px). Without
135
124
  // clearing it, width: 100% is capped and a strip of scrim shows on the
136
125
  // right. Bottom sheet already overrides this.
137
- dialog.max,
138
- .modal.max {
126
+ dialog.max {
139
127
  width: 100%;
140
128
  max-width: none;
141
129
  height: 100%;
@@ -149,8 +137,7 @@ dialog.max,
149
137
  // the edge against the viewport is square. `left` / `right` also float
150
138
  // via the helper utilities (`!important`), so sheets reset that.
151
139
  dialog.bottom,
152
- dialog.bottom-sheet,
153
- .modal.bottom-sheet {
140
+ dialog.bottom-sheet {
154
141
  width: 100%;
155
142
  min-width: 0;
156
143
  max-width: none;
@@ -161,8 +148,7 @@ dialog.bottom-sheet,
161
148
  }
162
149
 
163
150
  dialog.top,
164
- dialog.top-sheet,
165
- .modal.top-sheet {
151
+ dialog.top-sheet {
166
152
  width: 100%;
167
153
  min-width: 0;
168
154
  max-width: none;
@@ -174,10 +160,8 @@ dialog.top-sheet,
174
160
 
175
161
  dialog.left,
176
162
  dialog.left-sheet,
177
- .modal.left-sheet,
178
163
  dialog.right,
179
- dialog.right-sheet,
180
- .modal.right-sheet {
164
+ dialog.right-sheet {
181
165
  float: none !important;
182
166
  width: min(var(--md-comp-basic-dialog-sheet-width), 85%);
183
167
  max-width: none;
@@ -190,8 +174,7 @@ dialog.right-sheet,
190
174
  }
191
175
 
192
176
  dialog.left,
193
- dialog.left-sheet,
194
- .modal.left-sheet {
177
+ dialog.left-sheet {
195
178
  inset-inline-start: 0;
196
179
  inset-inline-end: auto;
197
180
  border-radius: 0 var(--md-comp-basic-dialog-container-shape)
@@ -199,8 +182,7 @@ dialog.left-sheet,
199
182
  }
200
183
 
201
184
  dialog.right,
202
- dialog.right-sheet,
203
- .modal.right-sheet {
185
+ dialog.right-sheet {
204
186
  inset-inline-start: auto;
205
187
  inset-inline-end: 0;
206
188
  border-radius: var(--md-comp-basic-dialog-container-shape) 0 0
@@ -63,7 +63,7 @@ header:has(> nav) {
63
63
  }
64
64
 
65
65
  // Icon-only actions — 48dp target, 24dp glyph, 4dp from the container edge.
66
- > nav:not(.tabs) > :is(a, button):not(.btn):not(.button):not(.btn-floating):not(.btn-large):not(.btn-small):has(> i:only-child) {
66
+ > nav:not(.tabs) > :is(a, button):not(.button):not(.circle):not(.extra):has(> i:only-child) {
67
67
  display: inline-flex;
68
68
  flex: 0 0 auto;
69
69
  align-items: center;
@@ -100,7 +100,7 @@ header:has(> nav) {
100
100
  }
101
101
 
102
102
  // Text destinations, either loose or grouped in a menu/list.
103
- > nav:not(.tabs) > a:not(:has(> i:only-child)):not(.btn):not(.button):not(.btn-floating):not(.breadcrumb) {
103
+ > nav:not(.tabs) > a:not(:has(> i:only-child)):not(.button):not(.circle):not(.breadcrumb) {
104
104
  @extend %app-bar-destination;
105
105
  }
106
106
 
@@ -117,7 +117,10 @@ header:has(> nav) {
117
117
  align-items: stretch;
118
118
  }
119
119
 
120
- a {
120
+ // Direct destinations only. Dropdown JS parks the menu next to the
121
+ // trigger, so a descendant `a` would also paint a square hover on
122
+ // top of the menu's pill.
123
+ > li > a {
121
124
  @extend %app-bar-destination;
122
125
  }
123
126
  }
@@ -293,7 +296,7 @@ header.fixed:has(> nav) {
293
296
  }
294
297
 
295
298
  // Bottom app bar. 80dp row, icons on the start, a FAB (any .btn) pushed to the end.
296
- footer:not(.page-footer):has(> nav) {
299
+ footer:has(> nav:only-child) {
297
300
  --md-comp-bottom-app-bar-container-color: var(--md-sys-color-surface);
298
301
  --md-comp-bottom-app-bar-container-height: 80px;
299
302
 
@@ -312,7 +315,7 @@ footer:not(.page-footer):has(> nav) {
312
315
  margin: 0;
313
316
  }
314
317
 
315
- > nav:not(.tabs) > :is(a, button):not(.btn):not(.button):not(.btn-floating):not(.btn-large):not(.btn-small):has(> i:only-child) {
318
+ > nav:not(.tabs) > :is(a, button):not(.button):not(.circle):not(.extra):has(> i:only-child) {
316
319
  display: inline-flex;
317
320
  flex: 0 0 auto;
318
321
  align-items: center;
@@ -348,7 +351,7 @@ footer:not(.page-footer):has(> nav) {
348
351
  }
349
352
  }
350
353
 
351
- > nav:not(.tabs) > :is(.btn, .btn-floating, .btn-large) {
354
+ > nav:not(.tabs) > :is(.button, .circle, .extra) {
352
355
  margin-inline-start: auto;
353
356
  }
354
357
 
@@ -1,27 +1,146 @@
1
1
  @use "../abstracts" as *;
2
2
 
3
- .page-footer {
3
+ // Site footer.
4
+ //
5
+ // Anatomy is the HTML. A <footer> is the landmark. A heading is a column
6
+ // title. A <nav> (or ul) is a link list. The last child <small> is the
7
+ // legal bar. A footer whose only child is a <nav> is the bottom app bar,
8
+ // not this component.
9
+ //
10
+ // Tokens:
11
+ // --md-comp-footer-padding-block 3rem
12
+ // --md-comp-footer-padding-inline 1rem
13
+ // --md-comp-footer-gap 2rem
14
+ // --md-comp-footer-legal-height 48px
15
+ //
16
+ // Unfilled by default (dashed hairline). .filled uses surface-container.
17
+ // There is no JavaScript.
18
+
19
+ footer:not(:has(> nav:only-child)) {
20
+ --md-comp-footer-padding-block: 3rem;
21
+ --md-comp-footer-padding-inline: 1rem;
22
+ --md-comp-footer-gap: 2rem;
23
+ --md-comp-footer-legal-height: 48px;
24
+
25
+ box-sizing: border-box;
4
26
  margin-top: 5rem;
5
- padding-top: 3rem;
27
+ padding: var(--md-comp-footer-padding-block) var(--md-comp-footer-padding-inline) 1rem;
6
28
  border-top: 1px dashed var(--md-sys-color-outline-variant);
29
+ color: var(--md-sys-color-on-surface);
30
+
7
31
  p {
8
- color: var(--md-sys-color-outline);
32
+ margin: 0 0 8px;
33
+ color: var(--md-sys-color-on-surface-variant);
34
+ @include typescale("body-medium");
35
+ font-family: $font-stack;
9
36
  }
10
- a {
37
+
38
+ a:not(.button, .circle, .extra) {
11
39
  color: var(--md-sys-color-primary);
40
+ text-decoration: none;
41
+ @include focus-visible;
42
+ }
43
+
44
+ :is(h1, h2, h3, h4, h5, h6) {
45
+ margin: 0 0 8px;
46
+ color: var(--md-sys-color-on-surface);
47
+ @include typescale("title-small");
48
+ font-family: $font-stack;
49
+ }
50
+
51
+ ul {
52
+ margin: 0;
53
+ padding: 0;
54
+ list-style: none;
55
+ }
56
+ }
57
+
58
+ footer:not(:has(> nav:only-child)).filled,
59
+ footer:not(:has(> nav:only-child)).surface {
60
+ background-color: var(--md-sys-color-surface-container);
61
+ border-top: 1px solid var(--md-sys-color-outline-variant);
62
+ }
63
+
64
+ // Columns: direct <nav> children. A wrapping .container is the grid
65
+ // when the author uses one. Legacy .row still spans the full measure.
66
+ footer:not(:has(> nav:only-child)) > .container,
67
+ footer:not(:has(> nav:only-child)):not(:has(> .container)) {
68
+ display: grid;
69
+ gap: var(--md-comp-footer-gap);
70
+ grid-template-columns: 1fr;
71
+
72
+ @include bp-up("small") {
73
+ grid-template-columns: repeat(2, 1fr);
74
+ }
75
+
76
+ @include bp-up("large") {
77
+ grid-template-columns: repeat(4, 1fr);
12
78
  }
13
- .footer-copyright,
14
- .footer-copyright a {
15
- overflow: hidden;
16
- min-height: 50px;
17
- display: flex;
18
- align-items: center;
19
- justify-content: space-between;
20
- padding: 10px 0px;
79
+
80
+ @include bp-up("xlarge") {
81
+ grid-template-columns: repeat(5, 1fr);
82
+ }
83
+ }
84
+
85
+ footer:not(:has(> nav:only-child)) > .container > .row,
86
+ footer:not(:has(> nav:only-child)) > .row {
87
+ grid-column: 1 / -1;
88
+ }
89
+
90
+ footer:not(:has(> nav:only-child)) nav {
91
+ display: flex;
92
+ flex-direction: column;
93
+ align-items: flex-start;
94
+ gap: 0;
95
+ min-width: 0;
96
+ }
97
+
98
+ footer:not(:has(> nav:only-child)) nav > a:not(.button, .circle, .extra),
99
+ footer:not(:has(> nav:only-child)) ul a:not(.button, .circle, .extra) {
100
+ display: inline-flex;
101
+ align-items: center;
102
+ box-sizing: border-box;
103
+ min-height: 48px;
104
+ padding: 0 12px;
105
+ margin-inline-start: -12px;
106
+ border-radius: 2rem;
107
+ @include typescale("label-large");
108
+ font-family: $font-stack;
109
+
110
+ &:hover {
111
+ background-color: color-mix(in oklab, var(--md-sys-color-on-surface) 8%, transparent);
112
+ }
113
+ }
114
+
115
+ footer:not(:has(> nav:only-child)) > small:last-child {
116
+ display: flex;
117
+ flex-wrap: wrap;
118
+ align-items: center;
119
+ justify-content: space-between;
120
+ gap: 8px 16px;
121
+ box-sizing: border-box;
122
+ min-height: var(--md-comp-footer-legal-height);
123
+ margin: 0;
124
+ padding-block: 12px;
125
+ grid-column: 1 / -1;
126
+ border-top: 1px solid var(--md-sys-color-outline-variant);
127
+ color: var(--md-sys-color-on-surface-variant);
128
+ @include typescale("body-small");
129
+ font-family: $font-stack;
130
+
131
+ a:not(.button) {
132
+ color: var(--md-sys-color-primary);
21
133
  }
22
134
  }
23
135
 
24
- .page-footer ul {
25
- padding-left: 0;
26
- list-style-type: none;
136
+ // Sticky: pin the footer to the bottom of a short page. Only when the
137
+ // landmark is a direct child of body — not a footer nested in a demo.
138
+ body:has(> footer:not(:has(> nav:only-child))) {
139
+ min-height: 100dvh;
140
+ display: flex;
141
+ flex-direction: column;
142
+ }
143
+
144
+ body:has(> footer:not(:has(> nav:only-child))) > main {
145
+ flex: 1 0 auto;
27
146
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  // Parallax. Anatomy is the HTML: a .parallax (or <figure class="parallax">)
4
4
  // clips an <img>, <picture>, or <video>. A heading / <p> / <figcaption> is
5
- // the overlay. .parallax-container is the old wrapper and still works.
5
+ // the overlay.
6
6
  //
7
7
  // Motion is CSS. A named view timeline on the clip drives the image; without
8
8
  // animation-timeline the image is just object-fit: cover. JS does not scroll.
@@ -12,8 +12,7 @@
12
12
  // --md-comp-parallax-travel how far the image moves (64px)
13
13
  // --md-comp-parallax-shape 12px, same as media / lightbox / slider
14
14
 
15
- .parallax,
16
- .parallax-container {
15
+ .parallax {
17
16
  --md-comp-parallax-height: 500px;
18
17
  --md-comp-parallax-travel: 120px;
19
18
  --md-comp-parallax-shape: 12px;
@@ -29,22 +28,8 @@
29
28
  view-timeline-axis: block;
30
29
  }
31
30
 
32
- // Old markup: container > .parallax > img. The outer box is the clip;
33
- // the inner .parallax is just a fill, not a second 500px window.
34
- .parallax-container > .parallax {
35
- position: absolute;
36
- inset: 0;
37
- height: auto;
38
- margin: 0;
39
- border-radius: 0;
40
- overflow: visible;
41
- view-timeline-name: none;
42
- }
43
-
44
31
  .parallax > :is(img, video),
45
- .parallax > picture,
46
- .parallax-container .parallax > :is(img, video),
47
- .parallax-container .parallax > picture {
32
+ .parallax > picture {
48
33
  position: absolute;
49
34
  inset: 0;
50
35
  display: block;
@@ -53,8 +38,7 @@
53
38
  border: 0;
54
39
  }
55
40
 
56
- .parallax > picture img,
57
- .parallax-container .parallax > picture img {
41
+ .parallax > picture img {
58
42
  display: block;
59
43
  width: 100%;
60
44
  height: 100%;
@@ -62,9 +46,7 @@
62
46
  }
63
47
 
64
48
  .parallax > :is(img, video),
65
- .parallax > picture img,
66
- .parallax-container .parallax > :is(img, video),
67
- .parallax-container .parallax > picture img {
49
+ .parallax > picture img {
68
50
  object-fit: cover;
69
51
  object-position: center;
70
52
  }
@@ -95,8 +77,7 @@
95
77
  }
96
78
 
97
79
  // Full-bleed hero. Meets the viewport edges; no corners.
98
- .parallax.max,
99
- .parallax-container.max {
80
+ .parallax.max {
100
81
  height: 100vh;
101
82
  height: 100dvh;
102
83
  border-radius: 0;
@@ -107,9 +88,7 @@
107
88
  @supports (animation-timeline: view()) {
108
89
  @media (prefers-reduced-motion: no-preference) {
109
90
  .parallax > :is(img, video),
110
- .parallax > picture img,
111
- .parallax-container .parallax > :is(img, video),
112
- .parallax-container .parallax > picture img {
91
+ .parallax > picture img {
113
92
  height: calc(100% + 2 * var(--md-comp-parallax-travel));
114
93
  inset: calc(-1 * var(--md-comp-parallax-travel)) 0 auto;
115
94
  animation: parallax-shift linear both;