ts-maps 0.3.2 → 0.3.4

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 (54) hide show
  1. package/dist/core-map/control/FullscreenControl.d.ts +39 -0
  2. package/dist/core-map/control/GeocoderControl.d.ts +61 -0
  3. package/dist/core-map/control/LocateControl.d.ts +5 -2
  4. package/dist/core-map/control/NavigationControl.d.ts +39 -0
  5. package/dist/core-map/control/index.d.ts +6 -0
  6. package/dist/core-map/game/TerritoryLog.d.ts +66 -0
  7. package/dist/core-map/game/TerritoryStore.d.ts +59 -0
  8. package/dist/core-map/game/index.d.ts +11 -0
  9. package/dist/core-map/game/loop.d.ts +61 -0
  10. package/dist/core-map/geo/area.d.ts +52 -0
  11. package/dist/core-map/geo/index.d.ts +30 -0
  12. package/dist/core-map/geo/index.js +826 -1
  13. package/dist/core-map/geo/polygonClip.d.ts +75 -0
  14. package/dist/core-map/geo/validate.d.ts +54 -0
  15. package/dist/core-map/index.d.ts +7 -2
  16. package/dist/core-map/layer/GeoJSONTileSource.d.ts +50 -0
  17. package/dist/core-map/layer/RunTrailLayer.d.ts +35 -0
  18. package/dist/core-map/layer/TerritoryLayer.d.ts +62 -0
  19. package/dist/core-map/layer/index.d.ts +6 -0
  20. package/dist/core-map/layer/tile/RasterDEMLayer.d.ts +4 -0
  21. package/dist/core-map/layer/tile/VectorTileMapLayer.d.ts +59 -11
  22. package/dist/core-map/layer/tile/urlTemplate.d.ts +1 -2
  23. package/dist/core-map/map/Map.d.ts +32 -1
  24. package/dist/core-map/mvt/DecodedTile.d.ts +20 -0
  25. package/dist/core-map/mvt/FlatTile.d.ts +25 -0
  26. package/dist/core-map/mvt/VectorTileFeature.d.ts +8 -0
  27. package/dist/core-map/style-spec/expressions/formatted.d.ts +36 -0
  28. package/dist/core-map/style-spec/expressions/operators/binding.d.ts +1 -0
  29. package/dist/core-map/style-spec/expressions/operators/geometry.d.ts +19 -0
  30. package/dist/core-map/style-spec/expressions/types.d.ts +3 -0
  31. package/dist/core-map/style-spec/index.js +459 -42
  32. package/dist/core-map/style-spec/schema.d.ts +43 -3
  33. package/dist/core-map/style-spec/types.d.ts +47 -7
  34. package/dist/core-map/styles/basemap.d.ts +41 -0
  35. package/dist/core-map/styles/index.d.ts +5 -0
  36. package/dist/core-map/styles/palette.d.ts +31 -0
  37. package/dist/core-map/symbols/CollisionIndex.d.ts +22 -5
  38. package/dist/core-map/symbols/GlyphAtlas.d.ts +6 -1
  39. package/dist/core-map/symbols/GlyphProvider.d.ts +21 -0
  40. package/dist/core-map/symbols/GlyphRenderer.d.ts +48 -0
  41. package/dist/core-map/symbols/IconAtlas.d.ts +10 -1
  42. package/dist/core-map/symbols/SymbolOverlay.d.ts +47 -0
  43. package/dist/core-map/symbols/index.d.ts +4 -0
  44. package/dist/core-map/symbols/index.js +1071 -67
  45. package/dist/core-map/symbols/loadGlyphs.d.ts +53 -0
  46. package/dist/core-map/symbols/loadSprite.d.ts +57 -0
  47. package/dist/core-map/symbols/placement.d.ts +67 -0
  48. package/dist/core-map/symbols/sdf.d.ts +37 -0
  49. package/dist/core-map/symbols/sections.d.ts +32 -0
  50. package/dist/core-map/workers/WorkerPool.d.ts +40 -0
  51. package/dist/core-map/workers/decodeMvtFlat.d.ts +52 -0
  52. package/dist/index.js +11548 -5996
  53. package/package.json +1 -1
  54. package/src/core-map/ts-maps.css +392 -99
@@ -116,6 +116,10 @@
116
116
  z-index: 200;
117
117
  }
118
118
 
119
+ .tsmap-symbol-pane {
120
+ z-index: 350;
121
+ }
122
+
119
123
  .tsmap-overlay-pane {
120
124
  z-index: 400;
121
125
  }
@@ -293,25 +297,79 @@ svg.tsmap-image-layer.tsmap-interactive path {
293
297
 
294
298
  /* visual tweaks */
295
299
 
300
+ /*
301
+ * Theme tokens.
302
+ *
303
+ * Every colour the chrome draws with resolves through one of these, so a dark
304
+ * map is a single class flip rather than a parallel stylesheet. They live on
305
+ * the container and not on :root deliberately: two maps on one page can then
306
+ * carry different themes, and a host page's own custom properties can't
307
+ * collide with them.
308
+ *
309
+ * The switch is `.tsmap-dark`, set by the map's `theme` option (see
310
+ * Map.setTheme). It is a class and not a bare `prefers-color-scheme` query
311
+ * because the map's theme should follow the basemap being displayed, which is
312
+ * an application decision — a dark style on a machine set to light mode still
313
+ * needs dark chrome. `theme: 'auto'` is what re-attaches it to the OS setting.
314
+ */
296
315
  .tsmap-container {
316
+ --tsmap-accent: #1a73e8;
317
+ --tsmap-surface: #fff;
318
+ --tsmap-surface-hover: #f1f3f4;
319
+ --tsmap-fg: #3c4043;
320
+ --tsmap-fg-muted: #5f6368;
321
+ --tsmap-fg-disabled: #bdc1c6;
322
+ --tsmap-divider: rgb(0 0 0 / 12%);
323
+ --tsmap-scrim: rgb(255 255 255 / 80%);
324
+ --tsmap-hairline: #ddd;
325
+ --tsmap-scale-line: #777;
326
+ --tsmap-shadow:
327
+ 0 1px 2px rgb(60 64 67 / 30%),
328
+ 0 2px 6px 2px rgb(60 64 67 / 15%);
329
+ --tsmap-shadow-lg:
330
+ 0 2px 6px rgb(60 64 67 / 28%),
331
+ 0 6px 18px 4px rgb(60 64 67 / 15%);
332
+
297
333
  /* Matched to the land colour of a light basemap, so tiles fade in against
298
- their own background instead of flashing a dark band on every pan. */
299
- background: #e8eaed;
334
+ their own background instead of flashing a dark band on every pan. */
335
+ --tsmap-tile-bg: #e8eaed;
336
+
337
+ background: var(--tsmap-tile-bg);
300
338
  outline-offset: 1px;
301
339
 
302
340
  a {
303
- color: #1a73e8;
341
+ color: var(--tsmap-accent);
304
342
  }
305
343
  }
306
344
 
345
+ .tsmap-container.tsmap-dark {
346
+ --tsmap-accent: #8ab4f8;
347
+ --tsmap-surface: #292a2d;
348
+ --tsmap-surface-hover: #35363a;
349
+ --tsmap-fg: #e8eaed;
350
+ --tsmap-fg-muted: #9aa0a6;
351
+ --tsmap-fg-disabled: #5f6368;
352
+ --tsmap-divider: rgb(255 255 255 / 14%);
353
+ --tsmap-scrim: rgb(32 33 36 / 80%);
354
+ --tsmap-hairline: #3c4043;
355
+ --tsmap-scale-line: #9aa0a6;
356
+ --tsmap-shadow:
357
+ 0 1px 2px rgb(0 0 0 / 50%),
358
+ 0 2px 6px 2px rgb(0 0 0 / 30%);
359
+ --tsmap-shadow-lg:
360
+ 0 2px 6px rgb(0 0 0 / 45%),
361
+ 0 6px 18px 4px rgb(0 0 0 / 30%);
362
+ --tsmap-tile-bg: #16171a;
363
+ }
364
+
307
365
  /* prevent showing outline-box on Chromium when clicking on a vector with a tooltip */
308
366
  path.tsmap-interactive:focus:not(:focus-visible) {
309
367
  outline: 0;
310
368
  }
311
369
 
312
370
  .tsmap-zoom-box {
313
- border: 2px dotted #38f;
314
- background: rgba(255, 255, 255, 0.5);
371
+ border: 2px dotted var(--tsmap-accent);
372
+ background: var(--tsmap-scrim);
315
373
  }
316
374
 
317
375
  /* general typography */
@@ -346,38 +404,35 @@ path.tsmap-interactive:focus:not(:focus-visible) {
346
404
  * focus ring, which the old chrome suppressed entirely.
347
405
  */
348
406
  .tsmap-bar {
349
- box-shadow:
350
- 0 1px 2px rgba(60, 64, 67, 0.3),
351
- 0 2px 6px 2px rgba(60, 64, 67, 0.15);
407
+ box-shadow: var(--tsmap-shadow);
352
408
  border-radius: 8px;
353
409
  overflow: hidden;
354
410
 
355
411
  a {
356
412
  position: relative;
357
- background-color: #fff;
413
+ background-color: var(--tsmap-surface);
358
414
  width: 40px;
359
415
  height: 40px;
360
416
  line-height: 40px;
361
417
  display: block;
362
418
  text-align: center;
363
419
  text-decoration: none;
364
- color: black;
365
420
  background-position: 50% 50%;
366
421
  background-repeat: no-repeat;
367
422
  display: block;
368
- color: #3c4043;
423
+ color: var(--tsmap-fg);
369
424
  transition: background-color 120ms ease;
370
425
 
371
426
  &:hover {
372
- background-color: #f1f3f4;
427
+ background-color: var(--tsmap-surface-hover);
373
428
  }
374
429
 
375
430
  /* Visible only for keyboard users: a ring on every mouse click is noise,
376
- and no ring at all makes the control unusable without a pointer. */
431
+ and no ring at all makes the control unusable without a pointer. */
377
432
  &:focus-visible {
378
- outline: 2px solid #1a73e8;
433
+ outline: 2px solid var(--tsmap-accent);
379
434
  outline-offset: -2px;
380
- background-color: #f1f3f4;
435
+ background-color: var(--tsmap-surface-hover);
381
436
  }
382
437
 
383
438
  /*
@@ -396,7 +451,7 @@ path.tsmap-interactive:focus:not(:focus-visible) {
396
451
  bottom: 0;
397
452
  left: 8px;
398
453
  height: 1px;
399
- background: rgba(0, 0, 0, 0.12);
454
+ background: var(--tsmap-divider);
400
455
  }
401
456
 
402
457
  &:first-child {
@@ -411,30 +466,8 @@ path.tsmap-interactive:focus:not(:focus-visible) {
411
466
 
412
467
  &.tsmap-disabled {
413
468
  cursor: default;
414
- background-color: #fff;
415
- color: #bdc1c6;
416
- }
417
- }
418
- }
419
-
420
- /* Dark basemaps get dark chrome; white cards on a dark map glare. */
421
- @media (prefers-color-scheme: dark) {
422
- .tsmap-dark .tsmap-bar a:not(:last-child)::after {
423
- background: rgba(255, 255, 255, 0.14);
424
- }
425
-
426
- .tsmap-dark .tsmap-bar a {
427
- background-color: #292a2d;
428
- color: #e8eaed;
429
-
430
- &:hover,
431
- &:focus-visible {
432
- background-color: #35363a;
433
- }
434
-
435
- &.tsmap-disabled {
436
- background-color: #292a2d;
437
- color: #5f6368;
469
+ background-color: var(--tsmap-surface);
470
+ color: var(--tsmap-fg-disabled);
438
471
  }
439
472
  }
440
473
  }
@@ -460,11 +493,11 @@ path.tsmap-interactive:focus:not(:focus-visible) {
460
493
  /* zoom control */
461
494
 
462
495
  /* Scoped under .tsmap-bar so it outranks the `.tsmap-bar a` colour above —
463
- the zoom glyphs sit a shade lighter than label ink, as they do on every
464
- map that draws them as characters rather than icons. */
496
+ the zoom glyphs sit a shade lighter than label ink, as they do on every
497
+ map that draws them as characters rather than icons. */
465
498
  .tsmap-bar a.tsmap-control-zoom-in,
466
499
  .tsmap-bar a.tsmap-control-zoom-out {
467
- color: #5f6368;
500
+ color: var(--tsmap-fg-muted);
468
501
 
469
502
  font:
470
503
  400 21px / 40px -apple-system,
@@ -478,10 +511,8 @@ path.tsmap-interactive:focus:not(:focus-visible) {
478
511
  /* layers control */
479
512
 
480
513
  .tsmap-control-layers {
481
- box-shadow:
482
- 0 1px 2px rgba(60, 64, 67, 0.3),
483
- 0 2px 6px 2px rgba(60, 64, 67, 0.15);
484
- background: #fff;
514
+ box-shadow: var(--tsmap-shadow);
515
+ background: var(--tsmap-surface);
485
516
  border-radius: 8px;
486
517
 
487
518
  .tsmap-control-layers-list {
@@ -500,6 +531,12 @@ path.tsmap-interactive:focus:not(:focus-visible) {
500
531
  width: 44px;
501
532
  height: 44px;
502
533
  }
534
+
535
+ /* The stacked-sheets glyph is drawn with light fills, which disappear into a
536
+ dark card. Same shape, inverted ink. */
537
+ .tsmap-dark & {
538
+ background-image: url('data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 width=%2736%27 height=%2736%27 viewBox=%270 0 36 36%27%3E%3Crect x=%274%27 y=%274%27 width=%2714%27 height=%2714%27 fill=%27%23202124%27 stroke=%27%23bdc1c6%27 stroke-width=%271.5%27/%3E%3Crect x=%2710%27 y=%2710%27 width=%2714%27 height=%2714%27 fill=%27%232f3134%27 stroke=%27%23bdc1c6%27 stroke-width=%271.5%27/%3E%3Crect x=%2716%27 y=%2716%27 width=%2714%27 height=%2714%27 fill=%27%233f4144%27 stroke=%27%23bdc1c6%27 stroke-width=%271.5%27/%3E%3C/svg%3E');
539
+ }
503
540
  }
504
541
 
505
542
  .tsmap-control-layers-expanded {
@@ -521,8 +558,8 @@ path.tsmap-interactive:focus:not(:focus-visible) {
521
558
 
522
559
  .tsmap-control-layers-expanded {
523
560
  padding: 6px 10px 6px 6px;
524
- color: #333;
525
- background: #fff;
561
+ color: var(--tsmap-fg);
562
+ background: var(--tsmap-surface);
526
563
  }
527
564
 
528
565
  .tsmap-control-layers-scrollbar {
@@ -545,7 +582,7 @@ path.tsmap-interactive:focus:not(:focus-visible) {
545
582
 
546
583
  .tsmap-control-layers-separator {
547
584
  height: 0;
548
- border-top: 1px solid #ddd;
585
+ border-top: 1px solid var(--tsmap-hairline);
549
586
  margin: 5px -10px 5px -6px;
550
587
  }
551
588
 
@@ -557,15 +594,14 @@ path.tsmap-interactive:focus:not(:focus-visible) {
557
594
  /* attribution and scale controls */
558
595
 
559
596
  .tsmap-container .tsmap-control-attribution {
560
- background: #fff;
561
- background: rgba(255, 255, 255, 0.8);
597
+ background: var(--tsmap-scrim);
562
598
  margin: 0;
563
599
  }
564
600
 
565
601
  .tsmap-control-attribution,
566
602
  .tsmap-control-scale-line {
567
603
  padding: 0 5px;
568
- color: #333;
604
+ color: var(--tsmap-fg);
569
605
  line-height: 1.4;
570
606
  }
571
607
 
@@ -595,22 +631,21 @@ path.tsmap-interactive:focus:not(:focus-visible) {
595
631
  }
596
632
 
597
633
  .tsmap-control-scale-line {
598
- border: 2px solid #777;
634
+ border: 2px solid var(--tsmap-scale-line);
599
635
  border-top: none;
600
636
  line-height: 1.1;
601
637
  padding: 2px 5px 1px;
602
638
  white-space: nowrap;
603
639
  box-sizing: border-box;
604
- background: rgba(255, 255, 255, 0.8);
605
- text-shadow: 1px 1px #fff;
640
+ background: var(--tsmap-scrim);
606
641
 
607
642
  &:not(:first-child) {
608
- border-top: 2px solid #777;
643
+ border-top: 2px solid var(--tsmap-scale-line);
609
644
  border-bottom: none;
610
645
  margin-top: -2px;
611
646
 
612
647
  &:not(:last-child) {
613
- border-bottom: 2px solid #777;
648
+ border-bottom: 2px solid var(--tsmap-scale-line);
614
649
  }
615
650
  }
616
651
  }
@@ -683,11 +718,9 @@ path.tsmap-interactive:focus:not(:focus-visible) {
683
718
 
684
719
  .tsmap-popup-content-wrapper,
685
720
  .tsmap-popup-tip {
686
- background: white;
687
- color: #333;
688
- box-shadow:
689
- 0 2px 6px rgba(60, 64, 67, 0.28),
690
- 0 6px 18px 4px rgba(60, 64, 67, 0.15);
721
+ background: var(--tsmap-surface);
722
+ color: var(--tsmap-fg);
723
+ box-shadow: var(--tsmap-shadow-lg);
691
724
  }
692
725
 
693
726
  .tsmap-container a.tsmap-popup-close-button {
@@ -699,13 +732,13 @@ path.tsmap-interactive:focus:not(:focus-visible) {
699
732
  width: 24px;
700
733
  height: 24px;
701
734
  font: 16px/24px Tahoma, Verdana, sans-serif;
702
- color: #757575;
735
+ color: var(--tsmap-fg-muted);
703
736
  text-decoration: none;
704
737
  background: transparent;
705
738
 
706
739
  &:hover,
707
740
  &:focus {
708
- color: #585858;
741
+ color: var(--tsmap-fg);
709
742
  }
710
743
  }
711
744
 
@@ -716,8 +749,8 @@ path.tsmap-interactive:focus:not(:focus-visible) {
716
749
  /* div icon */
717
750
 
718
751
  .tsmap-div-icon {
719
- background: #fff;
720
- border: 1px solid #666;
752
+ background: var(--tsmap-surface);
753
+ border: 1px solid var(--tsmap-fg-muted);
721
754
  }
722
755
 
723
756
  /* Tooltip */
@@ -725,14 +758,14 @@ path.tsmap-interactive:focus:not(:focus-visible) {
725
758
  .tsmap-tooltip {
726
759
  position: absolute;
727
760
  padding: 6px;
728
- background-color: #fff;
729
- border: 1px solid #fff;
761
+ background-color: var(--tsmap-surface);
762
+ border: 1px solid var(--tsmap-surface);
730
763
  border-radius: 3px;
731
- color: #222;
764
+ color: var(--tsmap-fg);
732
765
  white-space: nowrap;
733
766
  user-select: none;
734
767
  pointer-events: none;
735
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
768
+ box-shadow: 0 1px 3px rgb(0 0 0 / 40%);
736
769
 
737
770
  &.tsmap-interactive {
738
771
  cursor: pointer;
@@ -762,7 +795,7 @@ path.tsmap-interactive:focus:not(:focus-visible) {
762
795
  top: 0;
763
796
  margin-top: -12px;
764
797
  margin-left: -6px;
765
- border-bottom-color: #fff;
798
+ border-bottom-color: var(--tsmap-surface);
766
799
  }
767
800
  }
768
801
 
@@ -774,7 +807,7 @@ path.tsmap-interactive:focus:not(:focus-visible) {
774
807
  margin-left: -6px;
775
808
  bottom: 0;
776
809
  margin-bottom: -12px;
777
- border-top-color: #fff;
810
+ border-top-color: var(--tsmap-surface);
778
811
  }
779
812
  }
780
813
 
@@ -786,7 +819,7 @@ path.tsmap-interactive:focus:not(:focus-visible) {
786
819
  margin-top: -6px;
787
820
  right: 0;
788
821
  margin-right: -12px;
789
- border-left-color: #fff;
822
+ border-left-color: var(--tsmap-surface);
790
823
  }
791
824
  }
792
825
 
@@ -798,7 +831,7 @@ path.tsmap-interactive:focus:not(:focus-visible) {
798
831
  margin-top: -6px;
799
832
  left: 0;
800
833
  margin-left: -12px;
801
- border-right-color: #fff;
834
+ border-right-color: var(--tsmap-surface);
802
835
  }
803
836
  }
804
837
 
@@ -813,11 +846,11 @@ path.tsmap-interactive:focus:not(:focus-visible) {
813
846
  }
814
847
 
815
848
  /* locate control
816
- ==========================================================================
817
- The crosshair button. Drawn in CSS rather than shipped as an icon file so it
818
- inherits colour from the button's state and stays crisp at any pixel ratio —
819
- the control has four states and a bitmap would need four assets.
820
- ========================================================================== */
849
+ ==========================================================================
850
+ The crosshair button. Drawn in CSS rather than shipped as an icon file so it
851
+ inherits colour from the button's state and stays crisp at any pixel ratio —
852
+ the control has four states and a bitmap would need four assets.
853
+ ========================================================================== */
821
854
 
822
855
  .tsmap-control-locate a {
823
856
  display: flex;
@@ -830,7 +863,7 @@ path.tsmap-interactive:focus:not(:focus-visible) {
830
863
  position: relative;
831
864
  width: 18px;
832
865
  height: 18px;
833
- color: #5f6368;
866
+ color: var(--tsmap-fg-muted);
834
867
 
835
868
  /* The ring. */
836
869
  &::before {
@@ -856,15 +889,15 @@ path.tsmap-interactive:focus:not(:focus-visible) {
856
889
  }
857
890
 
858
891
  /* Following the device: the crosshair fills in, the way a "you are here"
859
- control does everywhere else. */
892
+ control does everywhere else. */
860
893
  a.tsmap-control-locate-active .tsmap-locate-icon {
861
- color: #1a73e8;
894
+ color: var(--tsmap-accent);
862
895
  }
863
896
 
864
897
  /* Acquiring a first fix. The pulse is the only thing telling the user that a
865
- press did anything at all — a GPS fix can take several seconds. */
898
+ press did anything at all — a GPS fix can take several seconds. */
866
899
  a.tsmap-control-locate-locating .tsmap-locate-icon {
867
- color: #1a73e8;
900
+ color: var(--tsmap-accent);
868
901
  animation: tsmap-locate-pulse 1s ease-in-out infinite;
869
902
  }
870
903
 
@@ -875,8 +908,8 @@ a.tsmap-control-locate-locating .tsmap-locate-icon {
875
908
  }
876
909
 
877
910
  /* Denied or unavailable. Struck through rather than merely greyed: grey reads
878
- as "disabled, try later" when the real state is "this will not work until
879
- you change a browser setting". */
911
+ as "disabled, try later" when the real state is "this will not work until
912
+ you change a browser setting". */
880
913
  /*
881
914
  * The state class lands on the <a> itself (see LocateControl._setState), so
882
915
  * these select the anchor rather than a parent. Written as `.tsmap-control-
@@ -884,7 +917,7 @@ a.tsmap-control-locate-locating .tsmap-locate-icon {
884
917
  * nothing and left a denied control looking merely idle.
885
918
  */
886
919
  a.tsmap-control-locate-denied .tsmap-locate-icon {
887
- color: #bdc1c6;
920
+ color: var(--tsmap-fg-disabled);
888
921
  }
889
922
 
890
923
  a.tsmap-control-locate-denied::after {
@@ -896,34 +929,294 @@ a.tsmap-control-locate-denied::after {
896
929
  height: 2px;
897
930
  border-radius: 1px;
898
931
  /* Cut out from the strike so it reads as a line crossing the glyph rather
899
- than a line drawn on top of it. */
900
- background: #80868b;
901
- box-shadow: 0 -2px 0 #fff;
932
+ than a line drawn on top of it. */
933
+ background: var(--tsmap-fg-muted);
934
+ box-shadow: 0 -2px 0 var(--tsmap-surface);
902
935
  transform: translate(-50%, -50%) rotate(-45deg);
903
936
  }
904
937
 
905
938
  /* The accuracy halo around the reported position. */
906
939
  .tsmap-locate-accuracy {
907
- fill: #1a73e8;
940
+ fill: var(--tsmap-accent);
908
941
  fill-opacity: 0.12;
909
- stroke: #1a73e8;
942
+ stroke: var(--tsmap-accent);
910
943
  stroke-opacity: 0.35;
911
944
  stroke-width: 1;
912
945
  }
913
946
 
947
+ /* The "you are here" dot. A fixed screen size at every zoom — the accuracy
948
+ halo is the thing that carries scale — with a white ring so it stays legible
949
+ over both a near-black basemap and a pale one. */
950
+ .tsmap-locate-dot {
951
+ background: var(--tsmap-accent);
952
+ border: 2px solid #fff;
953
+ border-radius: 50%;
954
+ box-shadow: 0 1px 4px rgb(0 0 0 / 40%);
955
+ box-sizing: border-box;
956
+ }
957
+
958
+ /* The halo breathing outwards. Drawn on a child rather than ::after because
959
+ the icon element itself is positioned by the marker layer, and a transform
960
+ on it would fight the marker's own translate. */
961
+ .tsmap-locate-dot-pulse {
962
+ position: absolute;
963
+ inset: -2px;
964
+ border-radius: 50%;
965
+ background: var(--tsmap-accent);
966
+ opacity: 0.4;
967
+ animation: tsmap-locate-dot-pulse 2s ease-out infinite;
968
+ }
969
+
970
+ @keyframes tsmap-locate-dot-pulse {
971
+ 0% {
972
+ transform: scale(1);
973
+ opacity: 0.4;
974
+ }
975
+
976
+ 100% {
977
+ transform: scale(3);
978
+ opacity: 0;
979
+ }
980
+ }
981
+
914
982
  @media (prefers-reduced-motion: reduce) {
915
983
  a.tsmap-control-locate-locating .tsmap-locate-icon {
916
984
  animation: none;
917
985
  }
986
+
987
+ .tsmap-locate-dot-pulse {
988
+ animation: none;
989
+ }
918
990
  }
919
991
 
920
- @media (prefers-color-scheme: dark) {
921
- .tsmap-dark .tsmap-locate-icon {
922
- color: #9aa0a6;
992
+
993
+ /* geocoder control
994
+ ==========================================================================
995
+ The search box. Sized to sit comfortably on a phone, where it is usually the
996
+ widest piece of chrome on the map.
997
+ ========================================================================== */
998
+
999
+ .tsmap-control-geocoder {
1000
+ overflow: visible;
1001
+ min-width: 44px;
1002
+ }
1003
+
1004
+ .tsmap-control-geocoder-form {
1005
+ display: none;
1006
+ }
1007
+
1008
+ .tsmap-control-geocoder-expanded .tsmap-control-geocoder-form {
1009
+ display: block;
1010
+ }
1011
+
1012
+ .tsmap-control-geocoder-input {
1013
+ width: 240px;
1014
+ max-width: 60vw;
1015
+ height: 40px;
1016
+ padding: 0 12px;
1017
+ border: 0;
1018
+ border-radius: 8px;
1019
+ background: var(--tsmap-surface);
1020
+ color: var(--tsmap-fg);
1021
+ font: inherit;
1022
+ box-sizing: border-box;
1023
+
1024
+ &::placeholder {
1025
+ color: var(--tsmap-fg-muted);
923
1026
  }
924
1027
 
925
- .tsmap-dark a.tsmap-control-locate-active .tsmap-locate-icon,
926
- .tsmap-dark a.tsmap-control-locate-locating .tsmap-locate-icon {
927
- color: #8ab4f8;
1028
+ &:focus-visible {
1029
+ outline: 2px solid var(--tsmap-accent);
1030
+ outline-offset: -2px;
928
1031
  }
929
1032
  }
1033
+
1034
+ /* The magnifier, shown only in collapsed mode. */
1035
+ .tsmap-geocoder-icon {
1036
+ display: inline-block;
1037
+ width: 15px;
1038
+ height: 15px;
1039
+ border: 2px solid currentcolor;
1040
+ border-radius: 50%;
1041
+ position: relative;
1042
+ vertical-align: middle;
1043
+
1044
+ &::after {
1045
+ content: '';
1046
+ position: absolute;
1047
+ right: -5px;
1048
+ bottom: -4px;
1049
+ width: 2px;
1050
+ height: 7px;
1051
+ border-radius: 1px;
1052
+ background: currentcolor;
1053
+ transform: rotate(-45deg);
1054
+ }
1055
+ }
1056
+
1057
+ .tsmap-control-geocoder-expanded .tsmap-control-geocoder-toggle {
1058
+ display: none;
1059
+ }
1060
+
1061
+ /* A spinner would be more literal, but the input is already the thing the eye
1062
+ is on — dimming its text says "working" without adding a moving part. */
1063
+ .tsmap-control-geocoder-busy .tsmap-control-geocoder-input {
1064
+ color: var(--tsmap-fg-muted);
1065
+ }
1066
+
1067
+ .tsmap-control-geocoder-list {
1068
+ display: none;
1069
+ list-style: none;
1070
+ margin: 6px 0 0;
1071
+ padding: 4px 0;
1072
+ border-radius: 8px;
1073
+ background: var(--tsmap-surface);
1074
+ box-shadow: var(--tsmap-shadow);
1075
+ max-height: 40vh;
1076
+ overflow-y: auto;
1077
+ }
1078
+
1079
+ .tsmap-control-geocoder-open .tsmap-control-geocoder-list {
1080
+ display: block;
1081
+ }
1082
+
1083
+ .tsmap-control-geocoder-item {
1084
+ padding: 8px 12px;
1085
+ color: var(--tsmap-fg);
1086
+ cursor: pointer;
1087
+ white-space: nowrap;
1088
+ overflow: hidden;
1089
+ text-overflow: ellipsis;
1090
+
1091
+ &:hover,
1092
+ &.tsmap-control-geocoder-item-active {
1093
+ background: var(--tsmap-surface-hover);
1094
+ }
1095
+ }
1096
+
1097
+ .tsmap-control-geocoder-marker {
1098
+ background: var(--tsmap-accent);
1099
+ border: 2px solid #fff;
1100
+ border-radius: 50%;
1101
+ box-shadow: 0 1px 4px rgb(0 0 0 / 40%);
1102
+ box-sizing: border-box;
1103
+ }
1104
+
1105
+ /* navigation control
1106
+ ==========================================================================
1107
+ Zoom pair plus a compass. The needle is drawn in CSS for the same reason the
1108
+ locate crosshair is: it has to recolour with the theme and stay sharp while
1109
+ being rotated by a transform.
1110
+ ========================================================================== */
1111
+
1112
+ .tsmap-control-navigation a.tsmap-control-navigation-zoom-in,
1113
+ .tsmap-control-navigation a.tsmap-control-navigation-zoom-out {
1114
+ color: var(--tsmap-fg-muted);
1115
+ font:
1116
+ 400 21px / 40px -apple-system,
1117
+ BlinkMacSystemFont,
1118
+ 'Segoe UI',
1119
+ Roboto,
1120
+ sans-serif;
1121
+ }
1122
+
1123
+ .tsmap-control-navigation a.tsmap-control-navigation-compass {
1124
+ display: flex;
1125
+ align-items: center;
1126
+ justify-content: center;
1127
+ }
1128
+
1129
+ .tsmap-compass-needle {
1130
+ width: 4px;
1131
+ height: 22px;
1132
+ position: relative;
1133
+ transition: transform 120ms ease-out;
1134
+
1135
+ /* North half carries the colour; the south half is muted, which is how a
1136
+ compass reads as a compass rather than a pin. */
1137
+ &::before,
1138
+ &::after {
1139
+ content: '';
1140
+ position: absolute;
1141
+ left: 0;
1142
+ width: 0;
1143
+ height: 0;
1144
+ border-left: 2px solid transparent;
1145
+ border-right: 2px solid transparent;
1146
+ }
1147
+
1148
+ &::before {
1149
+ top: 0;
1150
+ border-bottom: 11px solid #ea4335;
1151
+ }
1152
+
1153
+ &::after {
1154
+ bottom: 0;
1155
+ border-top: 11px solid var(--tsmap-fg-disabled);
1156
+ }
1157
+ }
1158
+
1159
+ @media (prefers-reduced-motion: reduce) {
1160
+ .tsmap-compass-needle {
1161
+ transition: none;
1162
+ }
1163
+ }
1164
+
1165
+ /* fullscreen control
1166
+ ========================================================================== */
1167
+
1168
+ .tsmap-fullscreen-icon {
1169
+ display: block;
1170
+ width: 16px;
1171
+ height: 16px;
1172
+ position: relative;
1173
+
1174
+ /* Four corner brackets, drawn as two boxes clipped by their own borders. */
1175
+ &::before,
1176
+ &::after {
1177
+ content: '';
1178
+ position: absolute;
1179
+ width: 5px;
1180
+ height: 5px;
1181
+ border: 2px solid currentcolor;
1182
+ }
1183
+
1184
+ &::before {
1185
+ top: 0;
1186
+ left: 0;
1187
+ border-right: 0;
1188
+ border-bottom: 0;
1189
+ }
1190
+
1191
+ &::after {
1192
+ right: 0;
1193
+ bottom: 0;
1194
+ border-top: 0;
1195
+ border-left: 0;
1196
+ }
1197
+ }
1198
+
1199
+ .tsmap-control-fullscreen-active .tsmap-fullscreen-icon {
1200
+ &::before {
1201
+ border-right: 0;
1202
+ border-bottom: 0;
1203
+ transform: rotate(180deg);
1204
+ }
1205
+
1206
+ &::after {
1207
+ transform: rotate(180deg);
1208
+ }
1209
+ }
1210
+
1211
+ /*
1212
+ * Fallback for browsers and embeds where the Fullscreen API is unavailable —
1213
+ * an iframe without `allowfullscreen`, or iPhone Safari, which has never
1214
+ * implemented it. Browser chrome stays visible, but the map fills the viewport.
1215
+ */
1216
+ .tsmap-pseudo-fullscreen {
1217
+ position: fixed !important;
1218
+ inset: 0 !important;
1219
+ width: 100% !important;
1220
+ height: 100% !important;
1221
+ z-index: 99999;
1222
+ }