@undrr/undrr-mangrove 2.0.0-rc.2 → 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 (124) hide show
  1. package/README.md +11 -6
  2. package/components/BookCard.js +1 -1
  3. package/components/Breadcrumbs.js +2 -2
  4. package/components/Checkbox.js +1 -1
  5. package/components/Chips.js +1 -1
  6. package/components/CopyButton.js +2 -2
  7. package/components/CtaButton.js +1 -1
  8. package/components/Drawer.js +1 -1
  9. package/components/EmbedContainer.js +1 -1
  10. package/components/Footer.js +1 -1
  11. package/components/FormAction.js +1 -1
  12. package/components/FormErrorSummary.js +1 -1
  13. package/components/FormGroup.js +1 -1
  14. package/components/FullWidth.js +1 -1
  15. package/components/Gallery.js +2 -2
  16. package/components/Hero.js +2 -2
  17. package/components/HighlightBox.js +1 -1
  18. package/components/HorizontalBookCard.js +1 -1
  19. package/components/HorizontalCard.js +1 -1
  20. package/components/IconCard.js +1 -1
  21. package/components/Legend.js +1 -1
  22. package/components/Loader.js +1 -1
  23. package/components/MegaMenu.js +2 -2
  24. package/components/Notice.js +2 -2
  25. package/components/PageHeader.js +2 -2
  26. package/components/Pager.js +2 -2
  27. package/components/QuoteHighlight.js +2 -2
  28. package/components/QuoteHighlight.js.LICENSE.txt +2 -0
  29. package/components/Radio.js +1 -1
  30. package/components/Range.js +1 -1
  31. package/components/ScrollContainer.js +2 -2
  32. package/components/SegmentedControl.js +9 -0
  33. package/components/SegmentedControl.js.LICENSE.txt +9 -0
  34. package/components/Select.js +1 -1
  35. package/components/ServiceNotice.js +2 -2
  36. package/components/ShareButtons.js +2 -2
  37. package/components/ShowMore.js +2 -2
  38. package/components/SkipLink.js +1 -1
  39. package/components/StatsCard.js +1 -1
  40. package/components/SyndicationSearchWidget.js +2 -2
  41. package/components/Tab.js +2 -2
  42. package/components/TextCta.js +1 -1
  43. package/components/TextInput.js +1 -1
  44. package/components/Textarea.js +1 -1
  45. package/components/Tree.js +1 -1
  46. package/components/UserFeedback.js +1 -1
  47. package/components/VerticalCard.js +1 -1
  48. package/components/hydrate.js +2 -2
  49. package/css/style-all.css +60 -40
  50. package/css/style-all.css.map +1 -1
  51. package/css/style-delta.css +59 -39
  52. package/css/style-delta.css.map +1 -1
  53. package/css/style-gutenberg.css +16 -7
  54. package/css/style-gutenberg.css.map +1 -1
  55. package/css/style-irp.css +60 -40
  56. package/css/style-irp.css.map +1 -1
  57. package/css/style-mcr.css +59 -39
  58. package/css/style-mcr.css.map +1 -1
  59. package/css/style-preventionweb.css +60 -40
  60. package/css/style-preventionweb.css.map +1 -1
  61. package/css/style.css +59 -39
  62. package/css/style.css.map +1 -1
  63. package/error-pages/401.html +3 -3
  64. package/error-pages/403.html +3 -3
  65. package/error-pages/404.html +4 -4
  66. package/error-pages/429.html +3 -3
  67. package/error-pages/500.html +6 -6
  68. package/error-pages/502.html +3 -3
  69. package/error-pages/503.html +3 -3
  70. package/error-pages/504.html +3 -3
  71. package/error-pages/5xx.html +4 -4
  72. package/error-pages/challenge.html +3 -3
  73. package/error-pages/managed-challenge.html +3 -3
  74. package/js/README.md +31 -25
  75. package/js/copy-button.js +17 -0
  76. package/js/drawer.js +752 -0
  77. package/js/hub-header.js +217 -0
  78. package/js/show-more.js +144 -10
  79. package/js/switch-pending.js +195 -46
  80. package/package.json +8 -3
  81. package/scss/Atom/BaseTypography/Blockquote/blockquote.scss +2 -1
  82. package/scss/Atom/BaseTypography/Cite/cite.scss +2 -1
  83. package/scss/Atom/BaseTypography/Code/code.scss +8 -2
  84. package/scss/Atom/Icons/icons.scss +1 -1
  85. package/scss/Atom/Images/AuthorImage/author-image.scss +16 -1
  86. package/scss/Atom/StatusLabel/status-label.scss +331 -8
  87. package/scss/Atom/Table/table.scss +8 -3
  88. package/scss/Atom/Tag/tag.scss +20 -2
  89. package/scss/Components/Breadcrumbs/breadcrumbs.scss +8 -4
  90. package/scss/Components/Buttons/CtaButton/cta-button.scss +15 -0
  91. package/scss/Components/Cards/Card/card.scss +37 -10
  92. package/scss/Components/Cards/StatsCard/stats-card.scss +2 -10
  93. package/scss/Components/DataViz/Legend/legend.scss +2 -1
  94. package/scss/Components/ErrorPages/error-pages.scss +8 -0
  95. package/scss/Components/Forms/SegmentedControl/segmented-control.scss +412 -0
  96. package/scss/Components/Forms/_form-base.scss +109 -18
  97. package/scss/Components/Hero/hero.scss +296 -57
  98. package/scss/Components/MegaMenu/mega-menu.scss +26 -6
  99. package/scss/Components/OnThisPageNav/on-this-page-nav.scss +3 -1
  100. package/scss/Components/PageHeader/page-header.scss +13 -28
  101. package/scss/Components/Pager/pager.scss +3 -1
  102. package/scss/Components/QuoteHighlight/quote-highlight.scss +6 -1
  103. package/scss/Components/ScrollContainer/scroll-container.scss +11 -0
  104. package/scss/Components/SyndicationSearchWidget/syndication-search-widget.scss +51 -24
  105. package/scss/Components/Tab/tab.scss +1 -1
  106. package/scss/Components/TextCta/text-cta.scss +11 -3
  107. package/scss/Patterns/HubHeader/hub-header.scss +14 -3
  108. package/scss/Utilities/FullWidth/full-width.scss +14 -1
  109. package/scss/assets/scss/_components.scss +1 -0
  110. package/scss/assets/scss/_fonts.scss +11 -0
  111. package/scss/assets/scss/_foundational.scss +7 -0
  112. package/scss/assets/scss/_mixins.scss +32 -0
  113. package/scss/assets/scss/_tokens-data-viz.scss +9 -0
  114. package/scss/assets/scss/_variables.scss +16 -3
  115. package/scss/assets/scss/generated/_tokens-irp.scss +20 -4
  116. package/scss/assets/scss/generated/_tokens-mangrove.scss +6 -0
  117. package/scss/assets/scss/generated/_tokens-preventionweb.scss +20 -4
  118. package/js/__tests__/copy-button.test.js +0 -112
  119. package/js/__tests__/show-more.test.js +0 -80
  120. package/js/__tests__/table-of-contents.test.js +0 -196
  121. package/scss/Components/SyndicationSearchWidget/SyndicationSearchWidget.scss +0 -11
  122. package/scss/Components/TableOfContents/TableOfContents.scss +0 -11
  123. package/scss/Utilities/FullWidth/FullWidth.scss +0 -11
  124. package/scss/Utilities/ShowMore/ShowMore.scss +0 -11
@@ -16,6 +16,28 @@
16
16
  // DELTA states are modifiers.
17
17
  // -
18
18
  // The name is always present, so the colour is never the only cue (SC 1.4.1).
19
+ // The indicator still carries a shape as well as a colour, for two reasons:
20
+ // several of the colours are close to each other under red-green colour vision
21
+ // deficiency, and this is a CSS-only component, so consumers write the markup
22
+ // and some will place an indicator without its label — in a table cell, a card
23
+ // corner, a dense list. A second cue also makes a long list faster to scan for
24
+ // everyone.
25
+ // -
26
+ // Degraded and offline are 3.3 CIE76 / 1.1 dE2000 apart simulated for
27
+ // deuteranopia (Machado), 1.7 / 0.6 under Viénot — both well under the ~2.3
28
+ // dE2000 just-noticeable difference, so they are the same mark to a deuteranope
29
+ // and only the shape separates them. Draft and waiting-for-more-information are
30
+ // 19.5 CIE76 / 13.4 dE2000 for everyone. The matrices have to be applied in
31
+ // LINEAR rgb; against gamma-encoded sRGB the degraded/offline pair measures
32
+ // 36 CIE76 and the collision disappears from the numbers.
33
+ // -
34
+ // The palette is not rebalanced to fix this because it cannot be: holding the
35
+ // hue associations and requiring 3:1 on white, the best worst-pair available
36
+ // from the tokens this system owns is 2.34 dE2000, still at the JND, while the
37
+ // normal-vision worst pair drops from 13.4 to 6.8. Seven unordered categories
38
+ // is past what colour carries — the same wall _tokens-data-viz.scss records as
39
+ // --mg-dataviz-categorical-safe-count: 4. See StatusLabel.mdx, "Why the palette
40
+ // is not rebalanced instead".
19
41
  // -
20
42
  // Usage:
21
43
  // <span class="mg-status-label mg-status-label--published">
@@ -23,6 +45,178 @@
23
45
  // Published
24
46
  // </span>
25
47
 
48
+ // The marks
49
+ // -
50
+ // Seven silhouettes that have to read as one family and still be told apart at
51
+ // 14px. Three rules hold the family together:
52
+ // -
53
+ // 1. Every mark is convex and simple. A concave mark (the plus this component
54
+ // carried in its first shaped release) reads as a different species beside
55
+ // six convex ones, and its inner corners are where a stroke has least room.
56
+ // 2. Every corner is softened, at one radius proportional to the indicator
57
+ // size — the same move Mangrove 2 makes with --mg-radius-button, -tag,
58
+ // -form-input and --mg-card-border-radius, which are all one value.
59
+ // Nothing in the set ends in a sharp point. The radius is a build-time
60
+ // fraction rather than a token because it is part of the mark, not
61
+ // something a consumer should tune shape by shape.
62
+ // 3. Every mark carries the same ring, exactly
63
+ // --mg-status-label-indicator-border-width wide on every edge, so the ring
64
+ // is one device rather than a different treatment per shape.
65
+ // -
66
+ // Five of the seven are a rounded rectangle under the hood, so they keep a real
67
+ // CSS `border` and rule 3 costs nothing:
68
+ // -
69
+ // circle (base, published) border-radius: 50%
70
+ // rounded square (draft) border-radius: the corner token
71
+ // capsule (waiting info) a wide, short rounded rectangle
72
+ // rounded diamond (waiting the draft square, rotate(45deg) — the same mark
73
+ // validation) turned on its point, which is the relationship
74
+ // the two statuses have
75
+ // -
76
+ // The remaining two, the caution triangle and the stop octagon, are polygons
77
+ // that no border-radius can describe, so they are clipped. See
78
+ // $mg-status-label-triangle below for how their ring is made uniform.
79
+ $mg-status-label-corner: 0.13; // of --mg-status-label-indicator-size
80
+
81
+ // Clipped shapes, and their rings.
82
+ // -
83
+ // A clip path cuts through a border, so these two paint the ring colour as
84
+ // their own background, clip it to the outer polygon, and lay the fill on a
85
+ // ::before clipped to an INNER polygon. Insetting the ::before box and reusing
86
+ // the same percentage polygon — what this component did before #1208 — scales
87
+ // the polygon instead of offsetting its edges, so the ring came out a different
88
+ // width on every edge and pinched to nothing at a sharp corner.
89
+ // -
90
+ // The inner polygon instead moves each vertex along its own angle bisector by
91
+ // the standard miter offset, w * (n1 + n2) / (1 + n1 · n2), where n1 and n2 are
92
+ // the inward unit normals of the two edges meeting there and w is the ring
93
+ // width. That offsets every edge by exactly w, so the ring is a true uniform
94
+ // stroke at any size and at any --mg-status-label-indicator-border-width,
95
+ // including 0, which collapses the inner polygon back onto the outer one and
96
+ // removes the ring. The percentages are the shape; the `calc()` terms are the
97
+ // bisector coefficients. Both are derived independently, and asserted, in
98
+ // __tests__/status-label.shapes.test.js — change one and the test recomputes
99
+ // the other.
100
+ // -
101
+ // Both outer polygons are chamfered rather than pointed. That is rule 2 of the
102
+ // family, and it is also what keeps the miter coefficients near 1: an
103
+ // unchamfered triangle apex needs a 2.24 x w offset, which at 14px would eat
104
+ // the mark.
105
+ // -
106
+ // --mg-status-label-indicator-ring is internal and is not a token to override;
107
+ // set --mg-status-label-indicator-border-width as before. It exists because
108
+ // a bisector term has to be added to a percentage, and `calc(50% + 0.618 * X)`
109
+ // is only valid when X is a length. The documented way to drop the ring is
110
+ // `--mg-status-label-indicator-border-width: 0`, and a bare `0` is a <number>
111
+ // in calc, which would invalidate the whole clip path and lose the shape.
112
+ // Registering this property as a <length> makes the browser resolve `0` to
113
+ // `0px` before the calc sees it, so the ring collapses instead of the mark.
114
+ @property --mg-status-label-indicator-ring {
115
+ syntax: "<length>";
116
+ inherits: true;
117
+ initial-value: 1px;
118
+ }
119
+
120
+ $mg-status-label-ring: var(--mg-status-label-indicator-ring);
121
+
122
+ // Caution triangle, its three corners chamfered at a 6% equivalent radius —
123
+ // enough to take the point off the apex at 48px without turning the mark into
124
+ // a hexagon, and to stop the ring mitring out to 2.24 x its width there.
125
+ $mg-status-label-triangle: polygon(
126
+ 44.63344% 10.73313%,
127
+ 55.36656% 10.73313%,
128
+ 95.65836% 91.31672%,
129
+ 90.2918% 100%,
130
+ 9.7082% 100%,
131
+ 4.34164% 91.31672%
132
+ );
133
+ $mg-status-label-triangle-inner: polygon(
134
+ calc(44.63344% + 0.61803 * #{$mg-status-label-ring})
135
+ calc(10.73313% + 1 * #{$mg-status-label-ring}),
136
+ calc(55.36656% - 0.61803 * #{$mg-status-label-ring})
137
+ calc(10.73313% + 1 * #{$mg-status-label-ring}),
138
+ calc(95.65836% - 1.14377 * #{$mg-status-label-ring})
139
+ calc(91.31672% - 0.05146 * #{$mg-status-label-ring}),
140
+ calc(90.2918% - 0.55754 * #{$mg-status-label-ring})
141
+ calc(100% - 1 * #{$mg-status-label-ring}),
142
+ calc(9.7082% + 0.55754 * #{$mg-status-label-ring})
143
+ calc(100% - 1 * #{$mg-status-label-ring}),
144
+ calc(4.34164% + 1.14377 * #{$mg-status-label-ring})
145
+ calc(91.31672% - 0.05146 * #{$mg-status-label-ring})
146
+ );
147
+
148
+ // Stop octagon, chamfered at 25% rather than the 29.3% of a regular octagon.
149
+ // A regular octagon is the most circle-like octagon there is, and at 14px it
150
+ // read as the published dot — and identically to it in forced colours, where
151
+ // the fills are gone. The deeper chamfer gives longer straight edges, so the
152
+ // silhouette separates from both circles at small sizes.
153
+ $mg-status-label-octagon: polygon(
154
+ 25% 0%,
155
+ 75% 0%,
156
+ 100% 25%,
157
+ 100% 75%,
158
+ 75% 100%,
159
+ 25% 100%,
160
+ 0% 75%,
161
+ 0% 25%
162
+ );
163
+ $mg-status-label-octagon-inner: polygon(
164
+ calc(25% + 0.41421 * #{$mg-status-label-ring})
165
+ calc(0% + 1 * #{$mg-status-label-ring}),
166
+ calc(75% - 0.41421 * #{$mg-status-label-ring})
167
+ calc(0% + 1 * #{$mg-status-label-ring}),
168
+ calc(100% - 1 * #{$mg-status-label-ring})
169
+ calc(25% + 0.41421 * #{$mg-status-label-ring}),
170
+ calc(100% - 1 * #{$mg-status-label-ring})
171
+ calc(75% - 0.41421 * #{$mg-status-label-ring}),
172
+ calc(75% - 0.41421 * #{$mg-status-label-ring})
173
+ calc(100% - 1 * #{$mg-status-label-ring}),
174
+ calc(25% + 0.41421 * #{$mg-status-label-ring})
175
+ calc(100% - 1 * #{$mg-status-label-ring}),
176
+ calc(0% + 1 * #{$mg-status-label-ring})
177
+ calc(75% - 0.41421 * #{$mg-status-label-ring}),
178
+ calc(0% + 1 * #{$mg-status-label-ring})
179
+ calc(25% + 0.41421 * #{$mg-status-label-ring})
180
+ );
181
+
182
+ // Equal bounding boxes do not read as equal weight: a square of the indicator
183
+ // size looks heavier than the circle, a diamond and a triangle lighter. Each
184
+ // mark is sized as a multiple of --mg-status-label-indicator-size, tuned by eye
185
+ // against the circle, which is what makes the set look like one family rather
186
+ // than seven unrelated marks.
187
+ // -
188
+ // The sizing changes the box, which would move the label text with it: in a
189
+ // column of statuses the text would start up to 4px further along on one row
190
+ // than the next, and a dense list is exactly what the second cue is for. The
191
+ // inline margin gives that back, so every indicator occupies
192
+ // --mg-status-label-indicator-size of inline space whatever its shape, and the
193
+ // wider marks overhang the alignment edge the way an optically centred glyph
194
+ // should.
195
+ @mixin mg-status-label-indicator-size($inline, $block: $inline) {
196
+ inline-size: calc(var(--mg-status-label-indicator-size) * #{$inline});
197
+ block-size: calc(var(--mg-status-label-indicator-size) * #{$block});
198
+ margin-inline: calc(
199
+ var(--mg-status-label-indicator-size) * #{(1 - $inline) * 0.5}
200
+ );
201
+ }
202
+
203
+ @mixin mg-status-label-shape($outer, $inner, $scale) {
204
+ @include mg-status-label-indicator-size($scale);
205
+
206
+ position: relative;
207
+ clip-path: $outer;
208
+ background-color: var(--mg-status-label-indicator-border-color);
209
+ border: 0;
210
+ border-radius: 0;
211
+
212
+ &::before {
213
+ position: absolute;
214
+ inset: 0;
215
+ clip-path: $inner;
216
+ content: "";
217
+ }
218
+ }
219
+
26
220
  .mg-status-label {
27
221
  display: inline-flex;
28
222
  align-items: center;
@@ -32,8 +226,14 @@
32
226
  line-height: 1.5;
33
227
  color: var(--mg-status-label-color);
34
228
 
35
- // The dot is decoration; the label text carries the meaning.
229
+ // The indicator is decoration; the label text carries the meaning.
36
230
  &__indicator {
231
+ // The ring width, coerced to a length so the clipped shapes can offset
232
+ // their edges by it. See the @property block above.
233
+ --mg-status-label-indicator-ring: var(
234
+ --mg-status-label-indicator-border-width
235
+ );
236
+
37
237
  flex: 0 0 auto;
38
238
  inline-size: var(--mg-status-label-indicator-size);
39
239
  block-size: var(--mg-status-label-indicator-size);
@@ -45,29 +245,148 @@
45
245
  }
46
246
 
47
247
  // DELTA record/event workflow states.
248
+ // Draft: a rounded square. A flat, inert block — nothing has moved yet.
48
249
  .mg-status-label--draft .mg-status-label__indicator {
49
- background-color: var(--mg-status-label-indicator--draft);
50
- }
250
+ @include mg-status-label-indicator-size(0.9);
51
251
 
52
- .mg-status-label--waiting-validation .mg-status-label__indicator {
53
- background-color: var(--mg-status-label-indicator--waiting-validation);
252
+ background-color: var(--mg-status-label-indicator--draft);
253
+ border-radius: calc(
254
+ var(--mg-status-label-indicator-size) * #{$mg-status-label-corner}
255
+ );
54
256
  }
55
257
 
258
+ // Waiting for more information: a capsule — a blank line still to be filled
259
+ // in. It is the widest mark in the set and the only one that is not as tall as
260
+ // it is wide, so it separates from the square and the circle on silhouette
261
+ // alone.
56
262
  .mg-status-label--waiting-information .mg-status-label__indicator {
263
+ @include mg-status-label-indicator-size(1.22, 0.72);
264
+
57
265
  background-color: var(--mg-status-label-indicator--waiting-information);
266
+
267
+ // Larger than half the block size, so it resolves to a true capsule and
268
+ // keeps doing so when a consumer resizes the indicator.
269
+ border-radius: var(--mg-status-label-indicator-size);
270
+ }
271
+
272
+ // Waiting for validation: a rounded diamond, the flowchart decision mark —
273
+ // someone else has to make a call before the record moves. It is the draft
274
+ // square rotated onto its point, so the two statuses that are still in the
275
+ // author's hands share a mark and a corner. Rotation is a paint-time transform:
276
+ // the layout box, and so the text alignment edge, is unaffected.
277
+ .mg-status-label--waiting-validation .mg-status-label__indicator {
278
+ @include mg-status-label-indicator-size(0.82);
279
+
280
+ background-color: var(--mg-status-label-indicator--waiting-validation);
281
+ border-radius: calc(
282
+ var(--mg-status-label-indicator-size) * #{$mg-status-label-corner}
283
+ );
284
+ transform: rotate(45deg);
58
285
  }
59
286
 
287
+ // Published: the circle, unchanged. It is the settled end state and the mark
288
+ // the DELTA board itself draws.
60
289
  .mg-status-label--published .mg-status-label__indicator {
61
290
  background-color: var(--mg-status-label-indicator--published);
62
291
  }
63
292
 
64
293
  // Service health states (degraded / offline), used by ServiceNotice.
294
+ // Degraded: the caution triangle.
65
295
  .mg-status-label--warning .mg-status-label__indicator {
66
- background-color: var(--mg-status-label-indicator--warning);
296
+ @include mg-status-label-shape(
297
+ $mg-status-label-triangle,
298
+ $mg-status-label-triangle-inner,
299
+ 1.16
300
+ );
301
+
302
+ &::before {
303
+ background-color: var(--mg-status-label-indicator--warning);
304
+ }
67
305
  }
68
306
 
307
+ // Offline: the octagon of a stop sign.
69
308
  .mg-status-label--negative .mg-status-label__indicator {
70
- background-color: var(--mg-status-label-indicator--negative);
309
+ @include mg-status-label-shape(
310
+ $mg-status-label-octagon,
311
+ $mg-status-label-octagon-inner,
312
+ 0.97
313
+ );
314
+
315
+ &::before {
316
+ background-color: var(--mg-status-label-indicator--negative);
317
+ }
318
+ }
319
+
320
+ // Forced colours repaint every background as a system colour, so the fills
321
+ // stop telling the statuses apart and only the shape is left. The indicators
322
+ // opt out of forcing and draw themselves in system colours: each named status
323
+ // a solid CanvasText mark, the base indicator a Canvas centre inside a
324
+ // CanvasText ring, so it stays the one hollow mark and does not collapse into
325
+ // published.
326
+ @media (forced-colors: active) {
327
+ .mg-status-label__indicator {
328
+ forced-color-adjust: none;
329
+ background-color: Canvas;
330
+ border-color: CanvasText;
331
+
332
+ // The ring is the only thing describing the base indicator's edge here,
333
+ // and dropping it with --mg-status-label-indicator-border-width: 0 is a
334
+ // documented opt-out — so a page that follows the docs could end up with a
335
+ // Canvas mark on a Canvas background and no mark at all
336
+ // (unisdr/undrr-mangrove#1211). The six named statuses are solid CanvasText
337
+ // and survive a missing ring; only the hollow one does not.
338
+ // -
339
+ // An inset outline draws the same 1px CanvasText edge in the same place the
340
+ // ring occupies, so it is invisible under a ring of any width and is all
341
+ // that is left when there is none. It is an outline rather than a minimum
342
+ // border width because an outline takes no space in any box model: the
343
+ // mark keeps its size, its fill area and its alignment edge whatever the
344
+ // ring is set to, and the clipped shapes' inner polygons still offset by
345
+ // the author's ring width rather than by a forced minimum. It lives inside
346
+ // this media block, so normal rendering at a zero ring is untouched.
347
+ outline: 1px solid CanvasText;
348
+ outline-offset: -1px;
349
+ }
350
+
351
+ // Each clipped variant names its own ::before, because the rule that puts the
352
+ // fill there is more specific than a bare .mg-status-label__indicator::before
353
+ // and would otherwise keep its author colour.
354
+ .mg-status-label--warning .mg-status-label__indicator::before,
355
+ .mg-status-label--negative .mg-status-label__indicator::before {
356
+ background-color: CanvasText;
357
+ }
358
+
359
+ // Every named status is a solid system-coloured mark. On the two clipped
360
+ // shapes this fills the ring the ::before sits inside.
361
+ // -
362
+ // The zero-ring outline above belongs to the hollow base indicator alone, so
363
+ // it is taken off again here rather than left to coincide invisibly with a
364
+ // solid CanvasText fill: these six are already whole without an edge, and the
365
+ // fix should not ride on a second mark happening to hide it.
366
+ .mg-status-label--draft .mg-status-label__indicator,
367
+ .mg-status-label--waiting-validation .mg-status-label__indicator,
368
+ .mg-status-label--waiting-information .mg-status-label__indicator,
369
+ .mg-status-label--published .mg-status-label__indicator,
370
+ .mg-status-label--warning .mg-status-label__indicator,
371
+ .mg-status-label--negative .mg-status-label__indicator {
372
+ background-color: CanvasText;
373
+ outline: 0;
374
+ }
375
+ }
376
+
377
+ // Print: the two clipped shapes carry no border, so their ring and their fill
378
+ // are both background-color, and the five rounded ones carry their fill as a
379
+ // background too. Browsers drop backgrounds when "background graphics" is off,
380
+ // which is the default in Chrome's print dialog, and generic print resets clear
381
+ // them outright — the triangle and the octagon would disappear altogether while
382
+ // the other five were reduced to an empty ring. Declaring the backgrounds essential
383
+ // keeps all seven marks on the page. Follows the print block in icons.scss,
384
+ // which fixes the same failure for mask-image icons.
385
+ @media print {
386
+ .mg-status-label__indicator,
387
+ .mg-status-label__indicator::before {
388
+ print-color-adjust: exact;
389
+ }
71
390
  }
72
391
 
73
392
  // A list of statuses — a legend, or a filter summary.
@@ -77,7 +396,11 @@
77
396
  gap: var(--mg-spacing-100) var(--mg-spacing-200);
78
397
  padding: 0;
79
398
  margin: 0;
80
- list-style: none;
399
+
400
+ // Keeps the implicit `list` role Safari would otherwise drop, so VoiceOver
401
+ // still announces the item count this class exists to provide
402
+ // (unisdr/undrr-mangrove#1209). The mixin carries the full reasoning.
403
+ @include mg-list-unmarked;
81
404
 
82
405
  // The global RTL list rules add inline padding to ul/ol and li with a more
83
406
  // specific selector, which pushed the labels away from the inline start.
@@ -149,11 +149,12 @@
149
149
  }
150
150
 
151
151
  // Data table variant: compact density, subtle borders, styled header row
152
- .mg-table--data {
152
+ .mg-table.mg-table--data {
153
153
  font-size: var(--mg-font-size-250);
154
154
 
155
155
  th,
156
156
  td {
157
+ font-size: var(--mg-font-size-250);
157
158
  border-bottom: 1px solid rgb(var(--mg-color-neutral-200));
158
159
  padding: var(--mg-spacing-50) var(--mg-spacing-100);
159
160
  }
@@ -184,8 +185,8 @@
184
185
  }
185
186
 
186
187
  // Right-aligned tabular numeric cells
187
- .mg-table__td--numeric,
188
- .mg-table__th--numeric {
188
+ .mg-table .mg-table__td--numeric,
189
+ .mg-table .mg-table__th--numeric {
189
190
  font-variant-numeric: tabular-nums;
190
191
  text-align: end;
191
192
  }
@@ -193,6 +194,10 @@
193
194
  // Monospace code / file path cells
194
195
  .mg-table__td--code {
195
196
  font-family: var(--mg-font-family-code);
197
+ }
198
+
199
+ // Match the density selectors at cell level, including --small + --data.
200
+ .mg-table td.mg-table__td--code {
196
201
  font-size: var(--mg-font-size-200);
197
202
  }
198
203
 
@@ -61,15 +61,33 @@
61
61
  border-color: currentColor;
62
62
  }
63
63
 
64
+ // The orange accent carries no text at any size, so the accent tag wears it as
65
+ // its edge instead of its fill: the chip is the pale end of the same ramp, the
66
+ // label is --mg-color-text on it at 15.7:1, and the Sendai orange is the border
67
+ // — a rule, with no copy over it. The leading edge is thickened because an
68
+ // orange rule is 2.95 on white and 2.66 on orange-50, under the 3:1 of
69
+ // SC 1.4.11, so it is emphasis and not the chip's only boundary; the fill and
70
+ // the label carry that. See
71
+ // docs/COLOUR-CONTRAST-METHODOLOGY.md#the-orange-accent-carries-no-text
64
72
  .mg-tag--accent {
65
73
  --mg-tag-background: var(
66
74
  --tag-color-background-accent,
67
- rgb(var(--mg-color-tag-accent))
75
+ rgb(var(--mg-color-orange-50))
68
76
  );
69
77
  --mg-tag-background-hover: var(
70
78
  --tag-color-background-accent-hover,
71
- rgb(var(--mg-color-tag-accent--hover))
79
+ rgb(var(--mg-color-orange-100))
72
80
  );
81
+ --mg-tag-foreground: rgb(var(--mg-color-text));
82
+
83
+ // Held steady across states on purpose. The obvious move is to swap in
84
+ // --mg-color-tag-accent--hover, but that is orange-800, which is LIGHTER than
85
+ // the orange-900 resting border, and it would land on the darker hover fill:
86
+ // 2.15 against the resting 2.66, so the rule would fade at the moment the
87
+ // chip is meant to respond. The hover is carried by the fill stepping from
88
+ // orange-50 to orange-100 and by the underline the base rule adds.
89
+ border-color: rgb(var(--mg-color-tag-accent));
90
+ border-inline-start-width: mg-rem(4);
73
91
  }
74
92
 
75
93
  // Subtle, non-interactive metadata badge (e.g. file formats PDF, SVG, JSON)
@@ -14,17 +14,21 @@
14
14
  font-size: var(--mg-font-size-300);
15
15
  padding: 0;
16
16
 
17
- // Support both the React nav > ul and legacy ul.mg-breadcrumb markup.
17
+ // Support the React nav > ul, the documented nav > ol, and the legacy
18
+ // ul.mg-breadcrumb markup. The <ol> arm matters: every curated breadcrumb
19
+ // example in the AI manifest uses one, and until it was listed here a
20
+ // consumer copying that snippet got numbered markers and no flex row.
18
21
  &,
19
- > ul {
22
+ > :is(ul, ol) {
20
23
  display: flex;
21
24
  flex-wrap: wrap;
22
25
  align-items: baseline;
23
26
  row-gap: var(--mg-spacing-50);
24
- list-style: none;
27
+
28
+ @include mg-list-unmarked;
25
29
  }
26
30
 
27
- > ul {
31
+ > :is(ul, ol) {
28
32
  padding: 0;
29
33
  margin: 0;
30
34
  max-inline-size: 100%;
@@ -184,6 +184,21 @@
184
184
  @include mg-focus-ring;
185
185
  }
186
186
 
187
+ // Contrast guard, not an endorsement. The variant-capable icon button is
188
+ // .mg-button.mg-button-{variant}.mg-button--icon; .mg-icon-button is the
189
+ // standalone ghost primitive and carries no variant colours. Pairing it with a
190
+ // filled variant is a common mistake because the variant paints a dark fill but
191
+ // the readable foreground lives on .mg-button, which this primitive never has,
192
+ // so the glyph stayed at the inherited body colour on the fill. The rest state
193
+ // now matches the hover, which both variants already set to the same token, so
194
+ // the mistake fails as a redundant class rather than as a 1.4.3 failure. Outline
195
+ // variants are excluded: they set their own foreground on a transparent surface.
196
+ .mg-icon-button:is(.mg-button-primary, .mg-button-secondary):not(
197
+ .mg-button-outline
198
+ ) {
199
+ color: rgb(var(--mg-color-button));
200
+ }
201
+
187
202
  // Removed: a bare `button:focus-visible { box-shadow: 0 0 4px
188
203
  // var(--mg-color-button-background); outline: 0; }` rule used to live here.
189
204
 
@@ -184,7 +184,7 @@
184
184
  cursor: pointer;
185
185
  display: inline-block;
186
186
  font-size: var(--mg-font-size-250);
187
- margin-right: var(--mg-spacing-50);
187
+ margin-inline-end: var(--mg-spacing-50);
188
188
 
189
189
  &:hover {
190
190
  color: rgb(var(--mg-color-tag));
@@ -274,9 +274,15 @@
274
274
  }
275
275
  }
276
276
 
277
- // Color variants (shared accent colours from _variables.scss)
277
+ // Color variants (shared accent colours from _variables.scss). This map is ink:
278
+ // every value here paints a card title, so only a colour that carries text
279
+ // belongs in it. "secondary" resolves to the Sendai orange accent, which is
280
+ // 2.65 on the card surface and 2.37 on DELTA's tinted one, so its title takes
281
+ // the interactive colour like any other card link and the accent moves to a
282
+ // rule instead — see below.
283
+ // docs/COLOUR-CONTRAST-METHODOLOGY.md#the-orange-accent-carries-no-text
278
284
  $card-variant-colours: (
279
- "secondary": rgb(var(--mg-color-secondary)),
285
+ "secondary": rgb(var(--mg-color-interactive)),
280
286
  "tertiary": rgb(var(--mg-color-tertiary)),
281
287
  "quaternary": rgb(var(--mg-color-quaternary)),
282
288
  );
@@ -287,6 +293,12 @@ $card-variant-colours: (
287
293
  }
288
294
  }
289
295
 
296
+ // What now tells a secondary card apart: a solid accent rule along its leading
297
+ // edge, with no copy over it.
298
+ .mg-card--secondary {
299
+ border-inline-start: mg-rem(4) solid rgb(var(--mg-color-secondary));
300
+ }
301
+
290
302
  // Opt-out modifier: plain title (no blue, no underline, no chevron)
291
303
  // Double class selector ensures this overrides variant colors regardless of source order
292
304
  .mg-card.mg-card--plain-title {
@@ -358,9 +370,15 @@ span.mg-card__label {
358
370
  text-wrap: pretty;
359
371
  }
360
372
 
361
- .mg-card__summary a,
362
- .mg-card__text-link {
373
+ // Links inside running summary text carry a permanent underline: the
374
+ // interactive blue sits at 2.09:1 against the body colour, so colour alone
375
+ // cannot distinguish them (WCAG 1.4.1 Use of Colour). Button-styled links
376
+ // inside a summary keep their own treatment.
377
+ .mg-card__summary a:not(.mg-button),
378
+ .mg-card__text-link:not(.mg-button) {
363
379
  color: rgb(var(--mg-color-interactive));
380
+ text-decoration: underline;
381
+ text-underline-offset: 0.15em;
364
382
  }
365
383
 
366
384
  .mg-card__summary a,
@@ -376,9 +394,9 @@ span.mg-card__label {
376
394
  font-weight: 700;
377
395
  }
378
396
 
379
- .mg-card__summary a:hover,
380
- .mg-card__text-link:hover {
381
- text-decoration: underline;
397
+ .mg-card__summary a:not(.mg-button):hover,
398
+ .mg-card__text-link:not(.mg-button):hover {
399
+ text-decoration-thickness: 2px;
382
400
  }
383
401
 
384
402
  // Book Card variant
@@ -639,9 +657,18 @@ span.mg-card__label {
639
657
  }
640
658
  }
641
659
 
642
- // Bordered variant (colored border via CSS custom property)
660
+ // Bordered variant (colored border via CSS custom property).
661
+
662
+ // --mg-card-border is an input hook: IconCard's `borderColor` prop sets it
663
+ // inline, and no stylesheet defines it. It carries a fallback because the
664
+ // class is public and a consumer writing the markup by hand does not always
665
+ // set the property — and an unset custom property drops the whole `border`
666
+ // declaration, so the modifier silently did nothing at all. The fallback is
667
+ // the theme's interactive colour, which makes the accent border read as the
668
+ // brand's in every theme. Set --mg-card-border on the element (or any
669
+ // ancestor) to override it. See unisdr/undrr-mangrove#1200.
643
670
  &--bordered {
644
- border: 2px solid var(--mg-card-border);
671
+ border: 2px solid var(--mg-card-border, rgb(var(--mg-color-interactive)));
645
672
  padding: max(var(--mg-card-padding), var(--mg-spacing-150));
646
673
  }
647
674
 
@@ -173,7 +173,7 @@
173
173
  .mg-stats-card--highlighted {
174
174
  .mg-stats-card-item {
175
175
  background-color: rgb(var(--mg-color-blue-900) / 0.05);
176
- border-left: 4px solid rgb(var(--mg-color-interactive));
176
+ border-inline-start: 4px solid rgb(var(--mg-color-interactive));
177
177
 
178
178
  &:hover {
179
179
  background-color: rgb(var(--mg-color-blue-900) / 0.08);
@@ -227,12 +227,4 @@
227
227
  }
228
228
  }
229
229
 
230
- /* RTL Support */
231
- [dir="rtl"] {
232
- .mg-stats-card--highlighted {
233
- .mg-stats-card-item {
234
- border-left: none;
235
- border-right: 4px solid rgb(var(--mg-color-interactive));
236
- }
237
- }
238
- }
230
+ /* RTL support is automatic: the highlighted accent uses `border-inline-start`. */
@@ -101,7 +101,8 @@
101
101
 
102
102
  /* Categorical */
103
103
  &__list {
104
- list-style: none;
104
+ @include mg-list-unmarked;
105
+
105
106
  padding: 0;
106
107
  margin: 0;
107
108
  display: flex;
@@ -19,6 +19,14 @@
19
19
  margin: 0;
20
20
  }
21
21
 
22
+ // Recovery links sit inside running copy ("go to the homepage, browse the
23
+ // directory..."), where colour alone cannot distinguish them (WCAG 1.4.1
24
+ // Use of Colour). Button-styled actions keep their own treatment.
25
+ p a:not(.mg-button) {
26
+ text-decoration: underline;
27
+ text-underline-offset: 0.15em;
28
+ }
29
+
22
30
  &__search {
23
31
  display: flex;
24
32
  gap: mg-rem(5);