@mailwoman/react 9.4.0 → 10.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 (127) hide show
  1. package/lib/common/About.tsx +87 -71
  2. package/lib/common/index.ts +22 -0
  3. package/lib/index.ts +4 -99
  4. package/lib/map/BackendControl.tsx +7 -3
  5. package/lib/map/CompareToggle.tsx +3 -2
  6. package/lib/map/Geocoder.tsx +48 -28
  7. package/lib/map/GeocoderControls.tsx +258 -99
  8. package/lib/map/MapChipRow.tsx +77 -0
  9. package/lib/map/MapCompass.tsx +77 -0
  10. package/lib/map/MapControlStack.tsx +98 -0
  11. package/lib/map/MapFooter.tsx +115 -0
  12. package/lib/map/MapProgressBar.tsx +58 -0
  13. package/lib/map/MapSearchBar.tsx +68 -0
  14. package/lib/map/MapSheet.tsx +62 -0
  15. package/lib/map/index.ts +19 -0
  16. package/lib/map/types.ts +18 -4
  17. package/lib/map/useMapBearing.ts +56 -0
  18. package/lib/map/useMapLabelPick.ts +123 -0
  19. package/lib/pipeline/index.ts +36 -0
  20. package/lib/pipeline/useParsePipeline.ts +29 -18
  21. package/lib/poi/index.ts +39 -0
  22. package/lib/runtime/index.ts +16 -0
  23. package/out/common/About.d.ts +13 -4
  24. package/out/common/About.d.ts.map +1 -1
  25. package/out/common/About.js +5 -2
  26. package/out/common/About.js.map +1 -1
  27. package/out/common/index.d.ts +22 -0
  28. package/out/common/index.d.ts.map +1 -0
  29. package/out/common/index.js +15 -0
  30. package/out/common/index.js.map +1 -0
  31. package/out/index.d.ts +4 -59
  32. package/out/index.d.ts.map +1 -1
  33. package/out/index.js +4 -36
  34. package/out/index.js.map +1 -1
  35. package/out/map/BackendControl.d.ts.map +1 -1
  36. package/out/map/BackendControl.js +1 -1
  37. package/out/map/BackendControl.js.map +1 -1
  38. package/out/map/CompareToggle.d.ts.map +1 -1
  39. package/out/map/CompareToggle.js +1 -1
  40. package/out/map/CompareToggle.js.map +1 -1
  41. package/out/map/Geocoder.d.ts +11 -1
  42. package/out/map/Geocoder.d.ts.map +1 -1
  43. package/out/map/Geocoder.js +26 -26
  44. package/out/map/Geocoder.js.map +1 -1
  45. package/out/map/GeocoderControls.d.ts +29 -11
  46. package/out/map/GeocoderControls.d.ts.map +1 -1
  47. package/out/map/GeocoderControls.js +65 -16
  48. package/out/map/GeocoderControls.js.map +1 -1
  49. package/out/map/MapChipRow.d.ts +47 -0
  50. package/out/map/MapChipRow.d.ts.map +1 -0
  51. package/out/map/MapChipRow.js +12 -0
  52. package/out/map/MapChipRow.js.map +1 -0
  53. package/out/map/MapCompass.d.ts +42 -0
  54. package/out/map/MapCompass.d.ts.map +1 -0
  55. package/out/map/MapCompass.js +11 -0
  56. package/out/map/MapCompass.js.map +1 -0
  57. package/out/map/MapControlStack.d.ts +57 -0
  58. package/out/map/MapControlStack.d.ts.map +1 -0
  59. package/out/map/MapControlStack.js +15 -0
  60. package/out/map/MapControlStack.js.map +1 -0
  61. package/out/map/MapFooter.d.ts +40 -0
  62. package/out/map/MapFooter.d.ts.map +1 -0
  63. package/out/map/MapFooter.js +51 -0
  64. package/out/map/MapFooter.js.map +1 -0
  65. package/out/map/MapProgressBar.d.ts +34 -0
  66. package/out/map/MapProgressBar.d.ts.map +1 -0
  67. package/out/map/MapProgressBar.js +8 -0
  68. package/out/map/MapProgressBar.js.map +1 -0
  69. package/out/map/MapSearchBar.d.ts +49 -0
  70. package/out/map/MapSearchBar.d.ts.map +1 -0
  71. package/out/map/MapSearchBar.js +6 -0
  72. package/out/map/MapSearchBar.js.map +1 -0
  73. package/out/map/MapSheet.d.ts +32 -0
  74. package/out/map/MapSheet.d.ts.map +1 -0
  75. package/out/map/MapSheet.js +33 -0
  76. package/out/map/MapSheet.js.map +1 -0
  77. package/out/map/OverlayLayers.js +1 -1
  78. package/out/map/ResolvedPlaceLayers.js +1 -1
  79. package/out/map/ResultPanel.js +1 -1
  80. package/out/map/fake-runtime.js.map +1 -1
  81. package/out/map/index.d.ts +17 -0
  82. package/out/map/index.d.ts.map +1 -1
  83. package/out/map/index.js +10 -0
  84. package/out/map/index.js.map +1 -1
  85. package/out/map/map-debug.d.ts.map +1 -1
  86. package/out/map/map-debug.js.map +1 -1
  87. package/out/map/types.d.ts +20 -4
  88. package/out/map/types.d.ts.map +1 -1
  89. package/out/map/useMapBearing.d.ts +28 -0
  90. package/out/map/useMapBearing.d.ts.map +1 -0
  91. package/out/map/useMapBearing.js +35 -0
  92. package/out/map/useMapBearing.js.map +1 -0
  93. package/out/map/useMapLabelPick.d.ts +23 -0
  94. package/out/map/useMapLabelPick.d.ts.map +1 -0
  95. package/out/map/useMapLabelPick.js +104 -0
  96. package/out/map/useMapLabelPick.js.map +1 -0
  97. package/out/pipeline/CandidatePicker.js +1 -1
  98. package/out/pipeline/PipelineExplorer.js +1 -1
  99. package/out/pipeline/QueryForm.js +1 -1
  100. package/out/pipeline/ResolvedPlace.js +1 -1
  101. package/out/pipeline/TimingPanel.js +1 -1
  102. package/out/pipeline/index.d.ts +33 -0
  103. package/out/pipeline/index.d.ts.map +1 -0
  104. package/out/pipeline/index.js +21 -0
  105. package/out/pipeline/index.js.map +1 -0
  106. package/out/pipeline/useParsePipeline.d.ts +5 -2
  107. package/out/pipeline/useParsePipeline.d.ts.map +1 -1
  108. package/out/pipeline/useParsePipeline.js +6 -2
  109. package/out/pipeline/useParsePipeline.js.map +1 -1
  110. package/out/poi/LiveResultsBlock.js +1 -1
  111. package/out/poi/POIExplorer.js +1 -1
  112. package/out/poi/SubjectPanel.js +1 -1
  113. package/out/poi/index.d.ts +23 -0
  114. package/out/poi/index.d.ts.map +1 -0
  115. package/out/poi/index.js +15 -0
  116. package/out/poi/index.js.map +1 -0
  117. package/out/poi/runtime.d.ts.map +1 -1
  118. package/out/runtime/index.d.ts +9 -0
  119. package/out/runtime/index.d.ts.map +1 -0
  120. package/out/runtime/index.js +8 -0
  121. package/out/runtime/index.js.map +1 -0
  122. package/package.json +87 -6
  123. package/styleframe.config.ts +9 -1
  124. package/styles.css +948 -26
  125. package/tokens/index.css +2 -1
  126. package/tokens/tokens.json +7 -0
  127. package/tokens/tokens.resolver.json +1 -1
package/styles.css CHANGED
@@ -29,7 +29,39 @@
29
29
  */
30
30
  @import url("./tokens/index.css") layer(tokens);
31
31
 
32
+ @layer reset {
33
+ /*
34
+ * Every box is measured by its border. The default is `content-box`, where `width`, `height` and `max-height`
35
+ * describe the content ALONE and any padding and border stand outside them — so a sheet capped at 52% of the
36
+ * viewport shipped 34px taller than its cap and ran off the bottom of the screen, and every rule that pairs a size
37
+ * with padding is one edit away from the same defect.
38
+ *
39
+ * A rule that wants the other measure states `box-sizing: content-box` for itself; the footer strip does, because
40
+ * its height names one line of type and its safe-area padding has to sit outside that.
41
+ *
42
+ * It is in the `reset` layer, so a host's own rules and every layer below win over it without out-specifying it.
43
+ */
44
+ *,
45
+ *::before,
46
+ *::after {
47
+ box-sizing: border-box;
48
+ }
49
+ }
50
+
32
51
  @layer base {
52
+ /*
53
+ * The theme attribute has to reach the USER AGENT, not only our own rules. `color-scheme` is what paints a
54
+ * scrollbar, a focus ring, a `<select>` popup and — the one this app depends on — the native clear button inside
55
+ * `input[type="search"]`, which renders as a dark cross on a dark field until the document declares its scheme.
56
+ */
57
+ :root[data-theme="dark"] {
58
+ color-scheme: dark;
59
+ }
60
+
61
+ :root[data-theme="light"] {
62
+ color-scheme: light;
63
+ }
64
+
33
65
  /*
34
66
  * Form controls do not inherit the document's font. Left alone, a `<button>` renders in the user agent's default
35
67
  * — measured as Arial on the deployed Earth app, beside a body in system-ui and labels in ui-monospace, which is
@@ -43,6 +75,27 @@
43
75
  font-size: inherit;
44
76
  line-height: inherit;
45
77
  }
78
+
79
+ /*
80
+ * A button carries no color of its own: unset, it paints the user agent's `buttontext`, which is near-black and
81
+ * stays near-black inside a dark sheet. Every button in this stylesheet that sets a background but no color read
82
+ * that way — the candidate list was the visible one.
83
+ */
84
+ button {
85
+ color: inherit;
86
+ }
87
+
88
+ /*
89
+ * A default face for a host that sets none. Wrapped in `:where()` so it carries ZERO specificity: Docusaurus's own
90
+ * `body` rule wins on the docs site and keeps that page's typography, while Storybook and a bare React host — which
91
+ * declare nothing — stop falling back to the user agent's serif.
92
+ */
93
+ :where(body) {
94
+ font-family: var(--font-family-text);
95
+ font-size: var(--font-size-body);
96
+ line-height: var(--line-height-body);
97
+ color: var(--color-text-primary);
98
+ }
46
99
  }
47
100
 
48
101
  @layer components {
@@ -101,12 +154,33 @@
101
154
 
102
155
  /* #region Preset chips */
103
156
 
157
+ /*
158
+ * One line that scrolls, never a wrap. Twelve examples wrapped down five ragged rows (3/3/2/1/1/1) and took a
159
+ * third of the panel; a scrolling row costs a fixed height whatever the number of examples. The scrollbar is
160
+ * hidden because the row sits inside a floating panel where a track reads as chrome, and every chip stays
161
+ * reachable by keyboard.
162
+ */
104
163
  .mw-presets {
105
164
  margin-top: 0.75rem;
106
165
  display: flex;
107
- flex-wrap: wrap;
166
+ flex-wrap: nowrap;
167
+ overflow-x: auto;
168
+ scrollbar-width: none;
108
169
  gap: 0.4rem;
109
170
  align-items: baseline;
171
+ padding-bottom: 0.15rem;
172
+ }
173
+
174
+ .mw-presets::-webkit-scrollbar {
175
+ display: none;
176
+ }
177
+
178
+ .mw-presets > * {
179
+ flex: 0 0 auto;
180
+ }
181
+
182
+ .mw-chip {
183
+ white-space: nowrap;
110
184
  }
111
185
 
112
186
  .mw-presets__label {
@@ -503,21 +577,33 @@
503
577
  text-align: left;
504
578
  padding: 0.4rem 0.6rem;
505
579
  border: 1px solid var(--color-separator);
506
- border-radius: 6px;
507
- background: transparent;
580
+ border-radius: var(--radius-control);
581
+ background: var(--control-background);
582
+ color: var(--color-text-primary);
508
583
  cursor: pointer;
509
584
  font: inherit;
510
585
  }
511
586
 
587
+ .mw-candidates__btn:hover:not(:disabled) {
588
+ background: var(--control-background-pressed);
589
+ border-color: var(--control-border);
590
+ }
591
+
592
+ .mw-candidates__btn:focus-visible {
593
+ outline: 2px solid var(--color-selection);
594
+ outline-offset: 1px;
595
+ }
596
+
512
597
  .mw-candidates__btn--active {
513
598
  border-color: var(--color-accent);
514
599
  background: var(--color-accent-background);
515
600
  }
516
601
 
602
+ /* Tertiary, not translucent: an opacity here also fades the text under it toward the pane behind the sheet. */
517
603
  .mw-candidates__rank {
518
604
  font-family: var(--font-family-code);
519
- font-size: 0.8rem;
520
- opacity: 0.6;
605
+ font-size: var(--font-size-caption);
606
+ color: var(--color-text-tertiary);
521
607
  }
522
608
 
523
609
  .mw-candidates__name {
@@ -771,14 +857,16 @@
771
857
  /* #region Demo control field (version / compare-version selects) */
772
858
 
773
859
  .mw-demo-control {
774
- margin-bottom: 0.75rem;
860
+ display: grid;
861
+ gap: 0.3rem;
862
+ width: 100%;
775
863
  }
776
864
 
777
865
  .mw-demo-control__label {
778
866
  display: block;
779
- margin-bottom: 0.25rem;
780
- font-weight: 600;
781
- font-size: 0.9rem;
867
+ font-weight: var(--font-weight-medium);
868
+ font-size: var(--font-size-footnote);
869
+ color: var(--color-text-secondary);
782
870
  }
783
871
 
784
872
  .mw-demo-control__select {
@@ -799,35 +887,26 @@
799
887
 
800
888
  /* #region Backend indicator + WASM toggle / compare toggle */
801
889
 
890
+ /* Stacked, because both controls now sit in a sheet row where a label sits over its control, not beside it. */
802
891
  .mw-demo-backend,
803
892
  .mw-demo-compare {
804
- display: flex;
805
- align-items: center;
806
- gap: 0.75rem;
807
- flex-wrap: wrap;
808
- margin-bottom: 0.75rem;
809
- font-size: 0.85rem;
810
- }
811
-
812
- .mw-demo-compare {
813
- display: block;
893
+ display: grid;
894
+ gap: 0.35rem;
895
+ justify-items: start;
896
+ font-size: var(--font-size-footnote);
814
897
  }
815
898
 
816
899
  .mw-demo-backend__active {
817
- opacity: 0.7;
900
+ color: var(--color-text-tertiary);
901
+ font-size: var(--font-size-caption);
818
902
  }
819
903
 
820
904
  .mw-demo-backend__toggle,
821
905
  .mw-demo-compare__toggle {
822
906
  display: inline-flex;
823
907
  align-items: center;
824
- gap: 0.25rem;
908
+ gap: 0.5rem;
825
909
  cursor: pointer;
826
- opacity: 0.8;
827
- }
828
-
829
- .mw-demo-compare__toggle {
830
- margin-bottom: 0.5rem;
831
910
  }
832
911
 
833
912
  .mw-demo-compare__status {
@@ -1230,4 +1309,847 @@
1230
1309
  .mw-about__swatch--low {
1231
1310
  background: #ef4444;
1232
1311
  }
1312
+
1313
+ /* #endregion */
1314
+
1315
+ /* #region Developer disclosure */
1316
+
1317
+ /*
1318
+ * The expert set lives here, below the result rather than above the address field. A hairline and a quieter
1319
+ * summary keep it legible without competing with the query, which is what the panel is for.
1320
+ */
1321
+ .mw-demo-developer {
1322
+ margin-top: 1rem;
1323
+ padding-top: 0.75rem;
1324
+ border-top: 1px solid var(--color-separator);
1325
+ }
1326
+
1327
+ .mw-demo-developer > summary {
1328
+ cursor: pointer;
1329
+ user-select: none;
1330
+ font-size: var(--font-size-footnote);
1331
+ font-weight: var(--font-weight-medium);
1332
+ color: var(--color-text-secondary);
1333
+ letter-spacing: 0.02em;
1334
+ text-transform: uppercase;
1335
+ }
1336
+
1337
+ .mw-demo-developer > summary:hover {
1338
+ color: var(--color-text-primary);
1339
+ }
1340
+
1341
+ .mw-demo-developer[open] > summary {
1342
+ margin-bottom: 0.75rem;
1343
+ }
1344
+
1345
+ /* #endregion */
1346
+
1347
+ /* #region Chrome layout — where the floating pieces sit over a map */
1348
+
1349
+ /*
1350
+ * The search pill and the chips ride at the top, centred and width-capped rather than stretched: a search field
1351
+ * the width of a desktop screen reads as a page header, not as a control over a map. The column clears the
1352
+ * device's top and side safe-area insets because both apps install as PWAs.
1353
+ */
1354
+ .mw-map-chrome--top {
1355
+ position: absolute;
1356
+ top: calc(1rem + var(--safe-area-top));
1357
+ left: calc(1rem + var(--safe-area-left));
1358
+ right: calc(1rem + var(--safe-area-right));
1359
+ z-index: 3;
1360
+ display: grid;
1361
+ gap: 0.5rem;
1362
+ justify-items: stretch;
1363
+ max-width: 34rem;
1364
+ pointer-events: none;
1365
+ }
1366
+
1367
+ /* The container is click-through so the map stays draggable between the controls; each control takes its own. */
1368
+ .mw-map-chrome--top > * {
1369
+ pointer-events: auto;
1370
+ }
1371
+
1372
+ .mw-map-chrome__search {
1373
+ margin: 0;
1374
+ }
1375
+
1376
+ .mw-map-searchbar__clear {
1377
+ border: 0;
1378
+ background: transparent;
1379
+ color: inherit;
1380
+ cursor: pointer;
1381
+ font: inherit;
1382
+ line-height: 1;
1383
+ padding: 0 0.15rem;
1384
+ }
1385
+
1386
+ /*
1387
+ * A sheet: the glass surface a result or a panel arrives on. The bottom one rises over the map's lower edge with
1388
+ * a drag handle, the side one sits under the control column it belongs to.
1389
+ */
1390
+ .mw-map-sheet {
1391
+ position: absolute;
1392
+ z-index: 3;
1393
+ /*
1394
+ * `max-height` measures the CONTENT box by default, so a sheet with 1rem of padding stood 34px taller than its
1395
+ * cap and ran past the bottom of the screen. Every height here is meant as the whole box.
1396
+ */
1397
+ box-sizing: border-box;
1398
+ box-shadow: var(--material-glass-shadow);
1399
+ overflow: auto;
1400
+ overscroll-behavior: contain;
1401
+ }
1402
+
1403
+ .mw-map-sheet--bottom {
1404
+ left: calc(1rem + var(--safe-area-left));
1405
+ right: calc(1rem + var(--safe-area-right));
1406
+ bottom: calc(2.5rem + var(--safe-area-bottom));
1407
+ max-height: 52vh;
1408
+ max-width: 34rem;
1409
+ padding: 0.5rem 1rem 1rem;
1410
+ border-radius: var(--radius-sheet);
1411
+ }
1412
+
1413
+ /*
1414
+ * BESIDE the control column, not below it. Opening below meant the sheet's top depended on how many controls the
1415
+ * capsule held, and every app that added one covered the button that opened it. Beside, it clears the column
1416
+ * whatever it holds: 2.5rem of control plus its inset and a gap.
1417
+ */
1418
+ .mw-map-sheet--side {
1419
+ top: calc(1rem + var(--safe-area-top));
1420
+ right: calc(4rem + var(--safe-area-right));
1421
+ width: min(24rem, calc(100vw - 6rem));
1422
+ /*
1423
+ * Measured against the VIEWPORT, not the containing block: the map wrapper is taller than the window on a page
1424
+ * that scrolls, and a percentage there let the sheet run past the bottom of the screen.
1425
+ */
1426
+ max-height: calc(100dvh - 2rem - var(--map-footer-height) - var(--safe-area-top) - var(--safe-area-bottom));
1427
+ padding: 1rem;
1428
+ border-radius: var(--radius-panel);
1429
+ /* Above the result sheet: a panel is a different task, and it covers the answer rather than sharing an edge. */
1430
+ z-index: 5;
1431
+ }
1432
+
1433
+ /*
1434
+ * A sheet's own furniture: a title, then rows separated by hairlines, then the small print. Without these a sheet
1435
+ * is a stack of raw form controls behind glass, which reads as a debug panel rather than as part of the app.
1436
+ */
1437
+ /*
1438
+ * The title and its close button ride together and stay put while the body scrolls. The close is not decoration:
1439
+ * on a phone the sheet is the whole panel and covers the control that opened it, so this is the only way out.
1440
+ */
1441
+ .mw-map-sheet__header {
1442
+ position: sticky;
1443
+ top: 0;
1444
+ z-index: 1;
1445
+ display: flex;
1446
+ align-items: center;
1447
+ justify-content: space-between;
1448
+ gap: 1rem;
1449
+ margin: -1rem -1rem 0.75rem;
1450
+ padding: 0.75rem 1rem;
1451
+ background: var(--material-glass-fallback-background);
1452
+ border-bottom: 1px solid var(--color-separator);
1453
+ }
1454
+
1455
+ .mw-map-sheet__title {
1456
+ margin: 0;
1457
+ font-family: var(--font-family-display);
1458
+ font-size: var(--font-size-headline);
1459
+ font-weight: var(--font-weight-semibold);
1460
+ color: var(--color-text-primary);
1461
+ }
1462
+
1463
+ .mw-map-sheet__close {
1464
+ display: flex;
1465
+ align-items: center;
1466
+ justify-content: center;
1467
+ flex: 0 0 auto;
1468
+ width: 1.75rem;
1469
+ height: 1.75rem;
1470
+ font-size: 1.15rem;
1471
+ line-height: 1;
1472
+ color: var(--color-text-secondary);
1473
+ background: transparent;
1474
+ border: 0;
1475
+ border-radius: var(--radius-pill);
1476
+ cursor: pointer;
1477
+ }
1478
+
1479
+ .mw-map-sheet__close:hover {
1480
+ color: var(--color-text-primary);
1481
+ background: var(--control-background);
1482
+ }
1483
+
1484
+ .mw-map-sheet__close:focus-visible {
1485
+ outline: 2px solid var(--color-selection);
1486
+ outline-offset: 1px;
1487
+ }
1488
+
1489
+ .mw-map-sheet__row {
1490
+ display: grid;
1491
+ gap: 0.3rem;
1492
+ padding: 0.7rem 0;
1493
+ border-top: 1px solid var(--color-separator);
1494
+ }
1495
+
1496
+ .mw-map-sheet__row:first-of-type {
1497
+ border-top: 0;
1498
+ padding-top: 0;
1499
+ }
1500
+
1501
+ .mw-map-sheet__label {
1502
+ font-size: var(--font-size-footnote);
1503
+ font-weight: var(--font-weight-medium);
1504
+ color: var(--color-text-secondary);
1505
+ }
1506
+
1507
+ .mw-map-sheet__hint {
1508
+ margin: 0;
1509
+ font-size: var(--font-size-caption);
1510
+ color: var(--color-text-tertiary);
1511
+ }
1512
+
1513
+ /* An intro paragraph is not a row, so it carries its own space before the first hairline. */
1514
+ .mw-map-sheet > .mw-map-sheet__hint {
1515
+ margin-bottom: 0.75rem;
1516
+ }
1517
+
1518
+ /* A value in a row reads at the row's size; the browser's default `code` is a step larger than the label above it. */
1519
+ .mw-map-sheet__row code {
1520
+ font-family: var(--font-family-code);
1521
+ font-size: var(--font-size-footnote);
1522
+ color: var(--color-text-primary);
1523
+ }
1524
+
1525
+ /* A checkbox and its words are one target, and the words sit beside the box rather than under it. */
1526
+ .mw-map-sheet__check {
1527
+ display: flex;
1528
+ align-items: baseline;
1529
+ gap: 0.5rem;
1530
+ cursor: pointer;
1531
+ font-size: var(--font-size-footnote);
1532
+ color: var(--color-text-primary);
1533
+ }
1534
+
1535
+ .mw-map-sheet__check input[type="checkbox"] {
1536
+ margin: 0;
1537
+ accent-color: var(--color-accent);
1538
+ cursor: pointer;
1539
+ }
1540
+
1541
+ /* Every form control in a sheet takes the app's type and the control palette, never the browser's defaults. */
1542
+ .mw-map-sheet select,
1543
+ .mw-map-sheet input[type="text"],
1544
+ .mw-map-sheet input[type="search"] {
1545
+ width: 100%;
1546
+ box-sizing: border-box;
1547
+ padding: 0.4rem 0.55rem;
1548
+ font: inherit;
1549
+ font-size: var(--font-size-footnote);
1550
+ color: var(--control-foreground);
1551
+ background: var(--control-background);
1552
+ border: 1px solid var(--control-border);
1553
+ border-radius: var(--radius-control);
1554
+ }
1555
+
1556
+ .mw-map-sheet select:focus-visible,
1557
+ .mw-map-sheet input:focus-visible {
1558
+ outline: 2px solid var(--color-selection);
1559
+ outline-offset: 1px;
1560
+ }
1561
+
1562
+ /* The grab affordance the reference sheets carry. Decorative: the sheet scrolls, it does not drag. */
1563
+ .mw-map-sheet__handle {
1564
+ width: 2.25rem;
1565
+ height: 0.25rem;
1566
+ margin: 0 auto 0.6rem;
1567
+ border-radius: var(--radius-pill);
1568
+ background: var(--color-separator);
1569
+ }
1570
+
1571
+ .mw-map-sheet__actions {
1572
+ margin-top: 0.75rem;
1573
+ display: flex;
1574
+ justify-content: flex-end;
1575
+ }
1576
+
1577
+ /*
1578
+ * On a phone the chrome spans the width and the sheet becomes a true bottom sheet against the edge, which is what
1579
+ * leaves the map its full height.
1580
+ */
1581
+ /*
1582
+ * The phone layout. The map keeps the whole screen and every floating piece is measured against the footer strip,
1583
+ * whose height is `--map-footer-height` plus the safe-area inset: a sheet that ends at the screen's edge hides its
1584
+ * own last rows behind the credits.
1585
+ *
1586
+ * A RESULT is a bottom sheet, because it answers the query in the field above it and both stay in view. A SIDE
1587
+ * sheet is the whole panel, because it is a different task — layers, settings, the explainer — and a phone has no
1588
+ * room to hold it beside anything. That also settles the overlap: the panel covers the result rather than sharing
1589
+ * an edge with it.
1590
+ */
1591
+ @media (max-width: 600px) {
1592
+ /*
1593
+ * The column spans the width, so it has to stop short of the control capsule in the same corner — 2.5rem of
1594
+ * control plus its 1rem inset plus a 0.5rem gap. Without this the capsule sits on the search pill's clear
1595
+ * button, which is the one control at that end of the pill.
1596
+ */
1597
+ .mw-map-chrome--top {
1598
+ max-width: none;
1599
+ right: calc(4rem + var(--safe-area-right));
1600
+ }
1601
+
1602
+ .mw-map-sheet--bottom {
1603
+ left: 0;
1604
+ right: 0;
1605
+ bottom: calc(var(--map-footer-height) + var(--safe-area-bottom));
1606
+ max-width: none;
1607
+ max-height: 55dvh;
1608
+ border-radius: var(--radius-sheet) var(--radius-sheet) 0 0;
1609
+ border-inline: 0;
1610
+ border-bottom: 0;
1611
+ padding-bottom: 1rem;
1612
+ }
1613
+
1614
+ .mw-map-sheet--side {
1615
+ top: 0;
1616
+ left: 0;
1617
+ right: 0;
1618
+ bottom: calc(var(--map-footer-height) + var(--safe-area-bottom));
1619
+ width: auto;
1620
+ max-width: none;
1621
+ max-height: none;
1622
+ border-radius: 0;
1623
+ border: 0;
1624
+ padding: calc(1rem + var(--safe-area-top)) 1rem 1rem;
1625
+ }
1626
+
1627
+ /* The chips take the full width on a phone, so the fade has less to hide behind. */
1628
+ .mw-map-chiprow {
1629
+ -webkit-mask-image: linear-gradient(to right, black calc(100% - 1.25rem), transparent);
1630
+ mask-image: linear-gradient(to right, black calc(100% - 1.25rem), transparent);
1631
+ }
1632
+
1633
+ /* The header spans the panel's full width once the sheet loses its own inset. */
1634
+ .mw-map-sheet--side .mw-map-sheet__header {
1635
+ margin: calc(-1rem - var(--safe-area-top)) -1rem 0.75rem;
1636
+ padding-top: calc(0.75rem + var(--safe-area-top));
1637
+ }
1638
+ }
1639
+
1640
+ /* #endregion */
1641
+
1642
+ /* #region Map chrome */
1643
+
1644
+ /*
1645
+ * The floating controls over a map: a search pill on top, example chips beneath it, a control column down one
1646
+ * edge, a footer along the bottom. Each is a glass material composed from tokens, and each carries the opaque
1647
+ * fallback below — no control depends on `backdrop-filter` to stay readable.
1648
+ */
1649
+
1650
+ /*
1651
+ * EVERY glass surface is listed here, and the two fallback blocks at the end of this region repeat exactly this
1652
+ * list. Composing the material at each component instead left the sheets and the sources popover out of both
1653
+ * fallbacks, so a viewer who asked for reduced transparency still got a translucent panel.
1654
+ */
1655
+ .mw-map-searchbar,
1656
+ .mw-map-chip,
1657
+ .mw-map-control-group,
1658
+ .mw-map-footer,
1659
+ .mw-map-footer__popover,
1660
+ .mw-map-sheet {
1661
+ background: var(--material-glass-background);
1662
+ border: 1px solid var(--material-glass-border);
1663
+ /*
1664
+ * PREFIXED FIRST, STANDARD LAST. The minifier collapses two declarations carrying the same value and keeps the
1665
+ * last one; written the other way round the shipped rule had `-webkit-backdrop-filter` alone, so every glass
1666
+ * surface lost its blur on Firefox, which implements the standard property and not the alias.
1667
+ */
1668
+ -webkit-backdrop-filter: blur(var(--material-glass-blur)) saturate(var(--material-glass-saturation));
1669
+ backdrop-filter: blur(var(--material-glass-blur)) saturate(var(--material-glass-saturation));
1670
+ color: var(--color-text-primary);
1671
+ }
1672
+
1673
+ .mw-map-searchbar {
1674
+ display: flex;
1675
+ align-items: center;
1676
+ gap: 0.5rem;
1677
+ padding: 0.4rem 0.75rem;
1678
+ border-radius: var(--radius-pill);
1679
+ box-shadow: var(--material-glass-shadow);
1680
+ }
1681
+
1682
+ .mw-map-searchbar__field {
1683
+ flex: 1;
1684
+ min-width: 0;
1685
+ }
1686
+
1687
+ /*
1688
+ * Busy is a hairline along the pill's lower edge, inside its radius. It is drawn on a pseudo-element so it costs
1689
+ * no layout — a spinner in a slot changed the pill's height on every submit, which moved the field under the
1690
+ * pointer.
1691
+ */
1692
+ .mw-map-searchbar {
1693
+ position: relative;
1694
+ overflow: hidden;
1695
+ }
1696
+
1697
+ .mw-map-searchbar--busy::after {
1698
+ content: "";
1699
+ position: absolute;
1700
+ left: 0;
1701
+ right: 0;
1702
+ bottom: 0;
1703
+ height: 2px;
1704
+ background: var(--color-accent);
1705
+ transform-origin: left center;
1706
+ animation: mw-map-searchbar-sweep 1.1s var(--easing-standard) infinite;
1707
+ }
1708
+
1709
+ @keyframes mw-map-searchbar-sweep {
1710
+ 0% {
1711
+ transform: scaleX(0);
1712
+ transform-origin: left center;
1713
+ }
1714
+
1715
+ 50% {
1716
+ transform: scaleX(1);
1717
+ transform-origin: left center;
1718
+ }
1719
+
1720
+ 51% {
1721
+ transform: scaleX(1);
1722
+ transform-origin: right center;
1723
+ }
1724
+
1725
+ 100% {
1726
+ transform: scaleX(0);
1727
+ transform-origin: right center;
1728
+ }
1729
+ }
1730
+
1731
+ /*
1732
+ * The native clear button of `input[type="search"]`. Its COLOUR is the user agent's, decided by the document's
1733
+ * `color-scheme` — nothing here repaints it. Only the two things the scheme does not say are stated: that it is a
1734
+ * pointer target, and that it sits back until the field is under the pointer or holding focus.
1735
+ */
1736
+ .mw-map-searchbar input[type="search"]::-webkit-search-cancel-button {
1737
+ cursor: pointer;
1738
+ opacity: 0.55;
1739
+ }
1740
+
1741
+ .mw-map-searchbar input[type="search"]:hover::-webkit-search-cancel-button,
1742
+ .mw-map-searchbar input[type="search"]:focus::-webkit-search-cancel-button {
1743
+ opacity: 1;
1744
+ }
1745
+
1746
+ /* The host's input carries no chrome of its own — the pill is the chrome. */
1747
+ .mw-map-searchbar__field input {
1748
+ width: 100%;
1749
+ border: 0;
1750
+ outline: 0;
1751
+ background: transparent;
1752
+ color: inherit;
1753
+ font: inherit;
1754
+ padding: 0.25rem 0;
1755
+ }
1756
+
1757
+ .mw-map-searchbar:focus-within {
1758
+ outline: 2px solid var(--color-selection);
1759
+ outline-offset: 2px;
1760
+ }
1761
+
1762
+ /* The glyph face draws its control marks small, so a leading or trailing mark is sized up to the field's own weight. */
1763
+ .mw-map-searchbar__slot {
1764
+ display: inline-flex;
1765
+ align-items: center;
1766
+ color: var(--color-text-secondary);
1767
+ font-family: var(--font-family-glyph);
1768
+ font-size: 1.15rem;
1769
+ line-height: 1;
1770
+ }
1771
+
1772
+ /*
1773
+ * One line that scrolls, never a wrap. The scrollbar is hidden because the row sits over a map where a track
1774
+ * would read as chrome; the row stays reachable by keyboard through its buttons.
1775
+ */
1776
+ .mw-map-chiprow {
1777
+ display: flex;
1778
+ gap: 0.4rem;
1779
+ overflow-x: auto;
1780
+ scrollbar-width: none;
1781
+ padding: 0.15rem;
1782
+ scroll-padding-inline: 0.15rem;
1783
+ /* With the track hidden, a chip clipped at the edge reads as broken; the fade is what says the row scrolls. */
1784
+ -webkit-mask-image: linear-gradient(to right, black calc(100% - 2rem), transparent);
1785
+ mask-image: linear-gradient(to right, black calc(100% - 2rem), transparent);
1786
+ }
1787
+
1788
+ .mw-map-chiprow::-webkit-scrollbar {
1789
+ display: none;
1790
+ }
1791
+
1792
+ .mw-map-chip {
1793
+ flex: 0 0 auto;
1794
+ display: inline-flex;
1795
+ align-items: center;
1796
+ gap: 0.35rem;
1797
+ padding: 0.35rem 0.7rem;
1798
+ border-radius: var(--radius-pill);
1799
+ font-size: var(--font-size-footnote);
1800
+ font-weight: var(--font-weight-medium);
1801
+ white-space: nowrap;
1802
+ cursor: pointer;
1803
+ }
1804
+
1805
+ .mw-map-chip:hover:not(:disabled) {
1806
+ background: var(--material-glass-fallback-background);
1807
+ border-color: var(--control-border);
1808
+ }
1809
+
1810
+ .mw-map-chip:focus-visible {
1811
+ outline: 2px solid var(--color-selection);
1812
+ outline-offset: 1px;
1813
+ }
1814
+
1815
+ /* A chip that carries a state reports it through `aria-pressed`, so the accent follows the attribute, not a class. */
1816
+ .mw-map-chip--active,
1817
+ .mw-map-chip[aria-pressed="true"] {
1818
+ border-color: var(--color-accent);
1819
+ color: var(--color-accent);
1820
+ }
1821
+
1822
+ .mw-map-chip:disabled {
1823
+ opacity: 0.5;
1824
+ cursor: not-allowed;
1825
+ }
1826
+
1827
+ .mw-map-chip__icon {
1828
+ display: inline-flex;
1829
+ font-family: var(--font-family-glyph);
1830
+ }
1831
+
1832
+ .mw-map-control-stack {
1833
+ position: absolute;
1834
+ top: calc(1rem + var(--safe-area-top));
1835
+ display: flex;
1836
+ flex-direction: column;
1837
+ gap: 0.6rem;
1838
+ z-index: 4;
1839
+ }
1840
+
1841
+ /*
1842
+ * The compass capsule collapses to nothing while the compass is hidden, so the capsules below it do not sit under
1843
+ * a gap the map shows through. `visibility` alone kept the box.
1844
+ */
1845
+ .mw-map-control-group--compass:has(.mw-map-compass--north) {
1846
+ box-shadow: none;
1847
+ border-color: transparent;
1848
+ background: transparent;
1849
+ -webkit-backdrop-filter: none;
1850
+ backdrop-filter: none;
1851
+ }
1852
+
1853
+ .mw-map-control-stack--right {
1854
+ right: calc(1rem + var(--safe-area-right));
1855
+ }
1856
+
1857
+ .mw-map-control-stack--left {
1858
+ left: calc(1rem + var(--safe-area-left));
1859
+ }
1860
+
1861
+ /* A capsule: related controls read as one object, divided by hairlines rather than gaps. */
1862
+ .mw-map-control-group {
1863
+ display: flex;
1864
+ flex-direction: column;
1865
+ border-radius: var(--radius-control);
1866
+ overflow: hidden;
1867
+ box-shadow: var(--material-glass-shadow);
1868
+ }
1869
+
1870
+ .mw-map-control-group > * + * {
1871
+ border-top: 1px solid var(--material-glass-border);
1872
+ }
1873
+
1874
+ .mw-map-control {
1875
+ display: flex;
1876
+ align-items: center;
1877
+ justify-content: center;
1878
+ width: 2.5rem;
1879
+ height: 2.5rem;
1880
+ border: 0;
1881
+ background: transparent;
1882
+ color: var(--color-text-primary);
1883
+ font-family: var(--font-family-glyph);
1884
+ cursor: pointer;
1885
+ }
1886
+
1887
+ .mw-map-control:hover:not(:disabled) {
1888
+ background: var(--control-background);
1889
+ }
1890
+
1891
+ .mw-map-control:active:not(:disabled) {
1892
+ background: var(--control-background-pressed);
1893
+ }
1894
+
1895
+ .mw-map-control--active {
1896
+ color: var(--color-accent);
1897
+ }
1898
+
1899
+ .mw-map-control:disabled {
1900
+ opacity: 0.4;
1901
+ cursor: not-allowed;
1902
+ }
1903
+
1904
+ /*
1905
+ * The compass keeps its box while invisible so the capsules above it do not shift as the map rotates.
1906
+ * `visibility` rather than `display` is what lets the opacity transition run at all.
1907
+ */
1908
+ /*
1909
+ * The compass sits INSIDE a control capsule like the other controls, so it carries no material of its own — the
1910
+ * capsule supplies the glass, and a second background here would read as a button on a button.
1911
+ */
1912
+ .mw-map-compass {
1913
+ display: flex;
1914
+ align-items: center;
1915
+ justify-content: center;
1916
+ width: 2.5rem;
1917
+ height: 2.5rem;
1918
+ border: 0;
1919
+ background: transparent;
1920
+ color: var(--color-text-primary);
1921
+ cursor: pointer;
1922
+ opacity: 1;
1923
+ visibility: visible;
1924
+ transition:
1925
+ opacity var(--duration-standard) var(--easing-standard),
1926
+ visibility 0s linear 0s;
1927
+ }
1928
+
1929
+ .mw-map-compass:hover:not(:disabled) {
1930
+ background: var(--control-background);
1931
+ }
1932
+
1933
+ .mw-map-compass:active:not(:disabled) {
1934
+ background: var(--control-background-pressed);
1935
+ }
1936
+
1937
+ .mw-map-compass:focus-visible {
1938
+ outline: 2px solid var(--color-selection);
1939
+ outline-offset: -2px;
1940
+ }
1941
+
1942
+ /* The dial: a ring, not a disc — the map has to stay readable through the middle of the control. */
1943
+ .mw-map-compass__dial {
1944
+ fill: none;
1945
+ stroke: var(--color-separator);
1946
+ stroke-width: 1.5;
1947
+ }
1948
+
1949
+ /* Red north, pale south. Two tones are what say which end points north without the reader knowing a convention. */
1950
+ .mw-map-compass__north {
1951
+ fill: var(--color-state-danger);
1952
+ }
1953
+
1954
+ .mw-map-compass__south {
1955
+ fill: var(--color-text-tertiary);
1956
+ }
1957
+
1958
+ /*
1959
+ * Invisible, and out of the way: `visibility: hidden` keeps the element in flow, so without the pointer rule a
1960
+ * compass nobody can see still swallows every click on the map beneath it.
1961
+ */
1962
+ .mw-map-compass--north {
1963
+ opacity: 0;
1964
+ visibility: hidden;
1965
+ pointer-events: none;
1966
+ transition:
1967
+ opacity var(--duration-standard) var(--easing-standard),
1968
+ visibility 0s linear var(--duration-standard);
1969
+ }
1970
+
1971
+ /*
1972
+ * One line, always. Wrapping was the alternative and it grew the strip to three lines on a phone, where it then
1973
+ * covered the bottom sheet's last rows — the credits are the least important thing on the screen and must never
1974
+ * take height from the thing a visitor asked for. Long credits scroll instead.
1975
+ */
1976
+ /*
1977
+ * THE STRIP ITSELF DOES NOT CLIP. A non-visible overflow on one axis makes the other clip as well, so an
1978
+ * `overflow-x` here erased the sources popover outright — it is positioned above the strip, entirely outside the
1979
+ * box that would have been doing the clipping. The scroll belongs to the identity region, which is the part
1980
+ * carrying text long enough to need one.
1981
+ */
1982
+ .mw-map-footer {
1983
+ display: flex;
1984
+ align-items: center;
1985
+ justify-content: space-between;
1986
+ gap: 1rem;
1987
+ flex-wrap: nowrap;
1988
+ white-space: nowrap;
1989
+ box-sizing: content-box;
1990
+ height: var(--map-footer-height);
1991
+ padding: 0 0.75rem;
1992
+ padding-bottom: var(--safe-area-bottom);
1993
+ border-radius: 0;
1994
+ border-inline: 0;
1995
+ border-bottom: 0;
1996
+ font-size: var(--font-size-caption);
1997
+ color: var(--color-text-secondary);
1998
+ }
1999
+
2000
+ /* Identity, then whatever is loading. The status is where the loader says WHAT; the bar on top says how far. */
2001
+ .mw-map-footer__identity {
2002
+ flex: 1 1 auto;
2003
+ min-width: 0;
2004
+ display: flex;
2005
+ align-items: baseline;
2006
+ gap: 0.6rem;
2007
+ overflow-x: auto;
2008
+ scrollbar-width: none;
2009
+ }
2010
+
2011
+ .mw-map-footer__identity::-webkit-scrollbar {
2012
+ display: none;
2013
+ }
2014
+
2015
+ .mw-map-footer__status {
2016
+ min-width: 0;
2017
+ overflow: hidden;
2018
+ text-overflow: ellipsis;
2019
+ color: var(--color-text-tertiary);
2020
+ }
2021
+
2022
+ .mw-map-footer__sources {
2023
+ position: relative;
2024
+ flex: 0 0 auto;
2025
+ }
2026
+
2027
+ .mw-map-footer__attribution-button {
2028
+ padding: 0.1rem 0.5rem;
2029
+ font: inherit;
2030
+ color: var(--color-text-secondary);
2031
+ background: transparent;
2032
+ border: 1px solid transparent;
2033
+ border-radius: var(--radius-pill);
2034
+ cursor: pointer;
2035
+ }
2036
+
2037
+ .mw-map-footer__attribution-button:hover,
2038
+ .mw-map-footer__attribution-button[aria-expanded="true"] {
2039
+ color: var(--color-text-primary);
2040
+ background: var(--control-background);
2041
+ border-color: var(--control-border);
2042
+ }
2043
+
2044
+ .mw-map-footer__attribution-button:focus-visible {
2045
+ outline: 2px solid var(--color-selection);
2046
+ outline-offset: 1px;
2047
+ }
2048
+
2049
+ /* The credits open UPWARD from the strip, because the strip is already against the bottom of the viewport. */
2050
+ .mw-map-footer__popover {
2051
+ position: absolute;
2052
+ right: 0;
2053
+ bottom: calc(100% + 0.4rem);
2054
+ z-index: 1;
2055
+ display: grid;
2056
+ gap: 0.25rem;
2057
+ min-width: max-content;
2058
+ max-width: min(22rem, calc(100vw - 2rem));
2059
+ padding: 0.6rem 0.8rem;
2060
+ white-space: normal;
2061
+ text-align: right;
2062
+ box-shadow: var(--material-glass-shadow);
2063
+ border-radius: var(--radius-panel);
2064
+ color: var(--color-text-secondary);
2065
+ }
2066
+
2067
+ /* #region Loading */
2068
+
2069
+ /*
2070
+ * The bundle-load bar, pinned across the top of the viewport. It reports at the page's edge because loading the
2071
+ * model is a page-level event: in the search field it resized the control being typed into, and in the result
2072
+ * sheet it held open a panel that had no answer in it yet.
2073
+ */
2074
+ .mw-map-progress {
2075
+ position: fixed;
2076
+ top: 0;
2077
+ left: 0;
2078
+ right: 0;
2079
+ z-index: 10;
2080
+ height: 3px;
2081
+ overflow: hidden;
2082
+ background: var(--control-background);
2083
+ opacity: 1;
2084
+ transition: opacity var(--duration-standard) var(--easing-standard);
2085
+ }
2086
+
2087
+ .mw-map-progress--idle {
2088
+ opacity: 0;
2089
+ pointer-events: none;
2090
+ }
2091
+
2092
+ .mw-map-progress__fill {
2093
+ height: 100%;
2094
+ background: var(--color-accent);
2095
+ transform: scaleX(0);
2096
+ transform-origin: left center;
2097
+ transition: transform var(--duration-standard) var(--easing-decelerate);
2098
+ }
2099
+
2100
+ /* Work whose length is unknown sweeps rather than filling, so the bar never invents a percentage. */
2101
+ .mw-map-progress__fill--indeterminate {
2102
+ width: 35%;
2103
+ transform: none;
2104
+ animation: mw-map-progress-sweep 1.4s var(--easing-standard) infinite;
2105
+ }
2106
+
2107
+ @keyframes mw-map-progress-sweep {
2108
+ from {
2109
+ margin-left: -35%;
2110
+ }
2111
+
2112
+ to {
2113
+ margin-left: 100%;
2114
+ }
2115
+ }
2116
+
2117
+ /* #endregion */
2118
+
2119
+ @media (prefers-reduced-motion: reduce) {
2120
+ .mw-map-compass,
2121
+ .mw-map-compass--north {
2122
+ transition: none;
2123
+ }
2124
+ }
2125
+
2126
+ /*
2127
+ * Where the blur cannot run, or the viewer has asked for less transparency, every material becomes opaque.
2128
+ * Text contrast holds in all three states; the glass is never what makes a control legible.
2129
+ */
2130
+ @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
2131
+ .mw-map-searchbar,
2132
+ .mw-map-chip,
2133
+ .mw-map-control-group,
2134
+ .mw-map-footer,
2135
+ .mw-map-footer__popover,
2136
+ .mw-map-sheet {
2137
+ background: var(--material-glass-fallback-background);
2138
+ }
2139
+ }
2140
+
2141
+ @media (prefers-reduced-transparency: reduce) {
2142
+ .mw-map-searchbar,
2143
+ .mw-map-chip,
2144
+ .mw-map-control-group,
2145
+ .mw-map-footer,
2146
+ .mw-map-footer__popover,
2147
+ .mw-map-sheet {
2148
+ background: var(--material-glass-fallback-background);
2149
+ -webkit-backdrop-filter: none;
2150
+ backdrop-filter: none;
2151
+ }
2152
+ }
2153
+
2154
+ /* #endregion */
1233
2155
  }