@diagrammo/dgmo 0.21.1 → 0.22.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 (73) hide show
  1. package/README.md +16 -6
  2. package/dist/advanced.cjs +2003 -466
  3. package/dist/advanced.d.cts +5714 -0
  4. package/dist/advanced.d.ts +5714 -0
  5. package/dist/advanced.js +1999 -466
  6. package/dist/auto.cjs +2048 -449
  7. package/dist/auto.d.cts +39 -0
  8. package/dist/auto.d.ts +39 -0
  9. package/dist/auto.js +121 -121
  10. package/dist/auto.mjs +2050 -450
  11. package/dist/cli.cjs +170 -170
  12. package/dist/editor.cjs +13 -16
  13. package/dist/editor.js +13 -16
  14. package/dist/highlight.cjs +15 -13
  15. package/dist/highlight.js +15 -13
  16. package/dist/index.cjs +2032 -435
  17. package/dist/index.d.cts +339 -0
  18. package/dist/index.d.ts +339 -0
  19. package/dist/index.js +2034 -436
  20. package/dist/internal.cjs +2003 -466
  21. package/dist/internal.d.cts +5714 -0
  22. package/dist/internal.d.ts +5714 -0
  23. package/dist/internal.js +1999 -466
  24. package/dist/map-data/water-bodies.json +1 -0
  25. package/docs/language-reference.md +20 -9
  26. package/gallery/fixtures/map-categorical-world.dgmo +16 -0
  27. package/gallery/fixtures/map-categorical.dgmo +0 -1
  28. package/gallery/fixtures/map-choropleth.dgmo +0 -1
  29. package/gallery/fixtures/map-coastline.dgmo +7 -0
  30. package/gallery/fixtures/map-colorize.dgmo +11 -0
  31. package/gallery/fixtures/map-direct-color.dgmo +0 -1
  32. package/gallery/fixtures/map-reference-world.dgmo +11 -0
  33. package/gallery/fixtures/map-region-scope.dgmo +0 -3
  34. package/gallery/fixtures/map-route.dgmo +0 -1
  35. package/package.json +1 -1
  36. package/src/advanced.ts +12 -1
  37. package/src/boxes-and-lines/renderer.ts +39 -12
  38. package/src/cli.ts +1 -1
  39. package/src/completion.ts +32 -25
  40. package/src/cycle/renderer.ts +14 -1
  41. package/src/d3.ts +8 -2
  42. package/src/editor/highlight-api.ts +4 -0
  43. package/src/editor/keywords.ts +13 -16
  44. package/src/infra/renderer.ts +35 -7
  45. package/src/map/colorize.ts +54 -0
  46. package/src/map/context-labels.ts +429 -0
  47. package/src/map/data/types.ts +34 -0
  48. package/src/map/data/water-bodies.json +1 -0
  49. package/src/map/dimensions.ts +117 -0
  50. package/src/map/geo-query.ts +21 -3
  51. package/src/map/geo.ts +47 -1
  52. package/src/map/layout.ts +1300 -251
  53. package/src/map/load-data.ts +10 -2
  54. package/src/map/parser.ts +42 -116
  55. package/src/map/renderer.ts +512 -13
  56. package/src/map/resolved-types.ts +16 -2
  57. package/src/map/resolver.ts +208 -59
  58. package/src/map/types.ts +30 -32
  59. package/src/mindmap/renderer.ts +10 -1
  60. package/src/palettes/atlas.ts +77 -0
  61. package/src/palettes/blueprint.ts +73 -0
  62. package/src/palettes/color-utils.ts +58 -1
  63. package/src/palettes/index.ts +12 -3
  64. package/src/palettes/slate.ts +73 -0
  65. package/src/palettes/tidewater.ts +73 -0
  66. package/src/render.ts +8 -1
  67. package/src/tech-radar/renderer.ts +3 -0
  68. package/src/tech-radar/types.ts +3 -0
  69. package/src/utils/d3-types.ts +5 -0
  70. package/src/utils/legend-layout.ts +21 -4
  71. package/src/utils/legend-types.ts +7 -0
  72. package/src/utils/reserved-key-registry.ts +3 -0
  73. package/src/palettes/bold.ts +0 -67
package/dist/auto.mjs CHANGED
@@ -91,18 +91,18 @@ function computeQuadrantPointLabels(points, chartBounds, obstacles, pointRadius,
91
91
  const results = [];
92
92
  for (let i = 0; i < points.length; i++) {
93
93
  const pt = points[i];
94
- const labelWidth = pt.label.length * fontSize * CHAR_WIDTH_RATIO + 8;
94
+ const labelWidth2 = pt.label.length * fontSize * CHAR_WIDTH_RATIO + 8;
95
95
  let best = null;
96
96
  const directions = [
97
97
  {
98
98
  // Above
99
99
  gen: (offset) => {
100
- const lx = pt.cx - labelWidth / 2;
100
+ const lx = pt.cx - labelWidth2 / 2;
101
101
  const ly = pt.cy - offset - labelHeight;
102
- if (ly < chartBounds.top || lx < chartBounds.left || lx + labelWidth > chartBounds.right)
102
+ if (ly < chartBounds.top || lx < chartBounds.left || lx + labelWidth2 > chartBounds.right)
103
103
  return null;
104
104
  return {
105
- rect: { x: lx, y: ly, w: labelWidth, h: labelHeight },
105
+ rect: { x: lx, y: ly, w: labelWidth2, h: labelHeight },
106
106
  textX: pt.cx,
107
107
  textY: ly + labelHeight / 2,
108
108
  anchor: "middle"
@@ -112,12 +112,12 @@ function computeQuadrantPointLabels(points, chartBounds, obstacles, pointRadius,
112
112
  {
113
113
  // Below
114
114
  gen: (offset) => {
115
- const lx = pt.cx - labelWidth / 2;
115
+ const lx = pt.cx - labelWidth2 / 2;
116
116
  const ly = pt.cy + offset;
117
- if (ly + labelHeight > chartBounds.bottom || lx < chartBounds.left || lx + labelWidth > chartBounds.right)
117
+ if (ly + labelHeight > chartBounds.bottom || lx < chartBounds.left || lx + labelWidth2 > chartBounds.right)
118
118
  return null;
119
119
  return {
120
- rect: { x: lx, y: ly, w: labelWidth, h: labelHeight },
120
+ rect: { x: lx, y: ly, w: labelWidth2, h: labelHeight },
121
121
  textX: pt.cx,
122
122
  textY: ly + labelHeight / 2,
123
123
  anchor: "middle"
@@ -129,10 +129,10 @@ function computeQuadrantPointLabels(points, chartBounds, obstacles, pointRadius,
129
129
  gen: (offset) => {
130
130
  const lx = pt.cx + offset;
131
131
  const ly = pt.cy - labelHeight / 2;
132
- if (lx + labelWidth > chartBounds.right || ly < chartBounds.top || ly + labelHeight > chartBounds.bottom)
132
+ if (lx + labelWidth2 > chartBounds.right || ly < chartBounds.top || ly + labelHeight > chartBounds.bottom)
133
133
  return null;
134
134
  return {
135
- rect: { x: lx, y: ly, w: labelWidth, h: labelHeight },
135
+ rect: { x: lx, y: ly, w: labelWidth2, h: labelHeight },
136
136
  textX: lx,
137
137
  textY: pt.cy,
138
138
  anchor: "start"
@@ -142,13 +142,13 @@ function computeQuadrantPointLabels(points, chartBounds, obstacles, pointRadius,
142
142
  {
143
143
  // Left
144
144
  gen: (offset) => {
145
- const lx = pt.cx - offset - labelWidth;
145
+ const lx = pt.cx - offset - labelWidth2;
146
146
  const ly = pt.cy - labelHeight / 2;
147
147
  if (lx < chartBounds.left || ly < chartBounds.top || ly + labelHeight > chartBounds.bottom)
148
148
  return null;
149
149
  return {
150
- rect: { x: lx, y: ly, w: labelWidth, h: labelHeight },
151
- textX: lx + labelWidth,
150
+ rect: { x: lx, y: ly, w: labelWidth2, h: labelHeight },
151
+ textX: lx + labelWidth2,
152
152
  textY: pt.cy,
153
153
  anchor: "end"
154
154
  };
@@ -198,10 +198,10 @@ function computeQuadrantPointLabels(points, chartBounds, obstacles, pointRadius,
198
198
  }
199
199
  }
200
200
  if (!best) {
201
- const lx = pt.cx - labelWidth / 2;
201
+ const lx = pt.cx - labelWidth2 / 2;
202
202
  const ly = pt.cy - minGap - labelHeight;
203
203
  best = {
204
- rect: { x: lx, y: ly, w: labelWidth, h: labelHeight },
204
+ rect: { x: lx, y: ly, w: labelWidth2, h: labelHeight },
205
205
  textX: pt.cx,
206
206
  textY: ly + labelHeight / 2,
207
207
  anchor: "middle",
@@ -761,6 +761,9 @@ var init_reserved_key_registry = __esm({
761
761
  "value",
762
762
  "label",
763
763
  "style"
764
+ // `surface:` was removed in the 2026-06-02 defaults-on review — it is no longer
765
+ // a recognized metadata key (the route/edge surface feature was cut; §24B.7).
766
+ // A stray `surface: water` is no longer captured as a reserved key.
764
767
  ]);
765
768
  ORG_REGISTRY = staticRegistry([
766
769
  "color",
@@ -1823,77 +1826,266 @@ function getSegmentColors(palette, count) {
1823
1826
  (_, i) => hslToHex(Math.round((startHue + i * step) % 360), avgS, avgL)
1824
1827
  );
1825
1828
  }
1829
+ function politicalTints(palette, count, isDark) {
1830
+ if (count <= 0) return [];
1831
+ const base = isDark ? palette.surface : palette.bg;
1832
+ const c = palette.colors;
1833
+ const swatches = [
1834
+ .../* @__PURE__ */ new Set([
1835
+ c.green,
1836
+ c.yellow,
1837
+ c.orange,
1838
+ c.purple,
1839
+ c.red,
1840
+ c.teal,
1841
+ c.cyan,
1842
+ c.blue
1843
+ ])
1844
+ ];
1845
+ const bands = isDark ? POLITICAL_TINT_BANDS.dark : POLITICAL_TINT_BANDS.light;
1846
+ const out = [];
1847
+ for (const pct of bands) {
1848
+ if (out.length >= count) break;
1849
+ for (const s of swatches) out.push(mix(s, base, pct));
1850
+ }
1851
+ return out.slice(0, count);
1852
+ }
1853
+ var POLITICAL_TINT_BANDS;
1826
1854
  var init_color_utils = __esm({
1827
1855
  "src/palettes/color-utils.ts"() {
1828
1856
  "use strict";
1857
+ POLITICAL_TINT_BANDS = {
1858
+ light: [32, 48, 64, 80],
1859
+ dark: [44, 58, 72, 86]
1860
+ };
1829
1861
  }
1830
1862
  });
1831
1863
 
1832
- // src/palettes/bold.ts
1833
- var boldPalette;
1834
- var init_bold = __esm({
1835
- "src/palettes/bold.ts"() {
1864
+ // src/palettes/atlas.ts
1865
+ var atlasPalette;
1866
+ var init_atlas = __esm({
1867
+ "src/palettes/atlas.ts"() {
1836
1868
  "use strict";
1837
1869
  init_registry();
1838
- boldPalette = {
1839
- id: "bold",
1840
- name: "Bold",
1870
+ atlasPalette = {
1871
+ id: "atlas",
1872
+ name: "Atlas",
1841
1873
  light: {
1842
- bg: "#ffffff",
1843
- surface: "#f0f0f0",
1844
- overlay: "#f0f0f0",
1845
- border: "#cccccc",
1846
- text: "#000000",
1847
- textMuted: "#666666",
1848
- textOnFillLight: "#ffffff",
1849
- textOnFillDark: "#000000",
1850
- primary: "#0000ff",
1851
- secondary: "#ff00ff",
1852
- accent: "#00cccc",
1853
- destructive: "#ff0000",
1874
+ bg: "#f3ead3",
1875
+ // warm manila / parchment
1876
+ surface: "#ece0c0",
1877
+ // deeper paper (cards, panels)
1878
+ overlay: "#e8dab8",
1879
+ // popovers, dropdowns
1880
+ border: "#bcaa86",
1881
+ // muted sepia rule line
1882
+ text: "#463a26",
1883
+ // aged sepia-brown ink
1884
+ textMuted: "#7a6a4f",
1885
+ // faded annotation ink
1886
+ textOnFillLight: "#f7f1de",
1887
+ // parchment (light text on dark fills)
1888
+ textOnFillDark: "#3a2e1c",
1889
+ // deep ink (dark text on light fills)
1890
+ primary: "#5b7a99",
1891
+ // pull-down map ocean (steel-blue)
1892
+ secondary: "#7e9a6f",
1893
+ // lowland sage / celadon
1894
+ accent: "#b07f7c",
1895
+ // dusty rose
1896
+ destructive: "#b25a45",
1897
+ // brick / terracotta
1854
1898
  colors: {
1855
- red: "#ff0000",
1856
- orange: "#ff8000",
1857
- yellow: "#ffcc00",
1858
- green: "#00cc00",
1859
- blue: "#0000ff",
1860
- purple: "#cc00cc",
1861
- teal: "#008080",
1862
- cyan: "#00cccc",
1863
- gray: "#808080",
1864
- black: "#000000",
1865
- white: "#f0f0f0"
1899
+ red: "#bf6a52",
1900
+ // terracotta brick
1901
+ orange: "#cf9a5c",
1902
+ // map tan / ochre
1903
+ yellow: "#cdb35e",
1904
+ // straw / muted lemon
1905
+ green: "#7e9a6f",
1906
+ // sage / celadon lowland
1907
+ blue: "#5b7a99",
1908
+ // steel-blue ocean
1909
+ purple: "#9a7fa6",
1910
+ // dusty lilac / mauve
1911
+ teal: "#6fa094",
1912
+ // muted seafoam
1913
+ cyan: "#79a7b5",
1914
+ // shallow-water blue
1915
+ gray: "#8a7d68",
1916
+ // warm taupe
1917
+ black: "#463a26",
1918
+ // ink
1919
+ white: "#ece0c0"
1920
+ // paper
1866
1921
  }
1867
1922
  },
1868
1923
  dark: {
1869
- bg: "#000000",
1870
- surface: "#111111",
1871
- overlay: "#1a1a1a",
1872
- border: "#333333",
1873
- text: "#ffffff",
1874
- textMuted: "#aaaaaa",
1875
- textOnFillLight: "#ffffff",
1876
- textOnFillDark: "#000000",
1877
- primary: "#00ccff",
1878
- secondary: "#ff00ff",
1879
- accent: "#ffff00",
1880
- destructive: "#ff0000",
1924
+ bg: "#1e2a33",
1925
+ // deep map ocean (night globe)
1926
+ surface: "#27353f",
1927
+ // raised ocean
1928
+ overlay: "#2e3d48",
1929
+ // popovers, dropdowns
1930
+ border: "#3d4f5c",
1931
+ // depth-contour line
1932
+ text: "#e8dcc0",
1933
+ // parchment ink, inverted
1934
+ textMuted: "#a89a7d",
1935
+ // faded label
1936
+ textOnFillLight: "#f7f1de",
1937
+ // parchment
1938
+ textOnFillDark: "#1a242c",
1939
+ // deep ocean ink
1940
+ primary: "#7ba0bf",
1941
+ // brighter ocean
1942
+ secondary: "#9bb588",
1943
+ // sage, lifted
1944
+ accent: "#cf9a96",
1945
+ // dusty rose, lifted
1946
+ destructive: "#c9745c",
1947
+ // brick, lifted
1881
1948
  colors: {
1882
- red: "#ff0000",
1883
- orange: "#ff8000",
1884
- yellow: "#ffff00",
1885
- green: "#00ff00",
1886
- blue: "#0066ff",
1887
- purple: "#ff00ff",
1888
- teal: "#00cccc",
1889
- cyan: "#00ffff",
1890
- gray: "#808080",
1891
- black: "#111111",
1892
- white: "#ffffff"
1949
+ red: "#cf7a60",
1950
+ // terracotta
1951
+ orange: "#d9a96a",
1952
+ // tan / ochre
1953
+ yellow: "#d8c074",
1954
+ // straw
1955
+ green: "#9bb588",
1956
+ // sage lowland
1957
+ blue: "#7ba0bf",
1958
+ // ocean
1959
+ purple: "#b59ac0",
1960
+ // lilac / mauve
1961
+ teal: "#85b3a6",
1962
+ // seafoam
1963
+ cyan: "#92bccb",
1964
+ // shallow-water blue
1965
+ gray: "#9a8d76",
1966
+ // warm taupe
1967
+ black: "#27353f",
1968
+ // raised ocean
1969
+ white: "#e8dcc0"
1970
+ // parchment
1893
1971
  }
1894
1972
  }
1895
1973
  };
1896
- registerPalette(boldPalette);
1974
+ registerPalette(atlasPalette);
1975
+ }
1976
+ });
1977
+
1978
+ // src/palettes/blueprint.ts
1979
+ var blueprintPalette;
1980
+ var init_blueprint = __esm({
1981
+ "src/palettes/blueprint.ts"() {
1982
+ "use strict";
1983
+ init_registry();
1984
+ blueprintPalette = {
1985
+ id: "blueprint",
1986
+ name: "Blueprint",
1987
+ light: {
1988
+ bg: "#f4f8fb",
1989
+ // pale drafting white (faint cyan)
1990
+ surface: "#e6eef4",
1991
+ // drafting panel
1992
+ overlay: "#dde9f1",
1993
+ // popovers, dropdowns
1994
+ border: "#aac3d6",
1995
+ // pale blue grid line
1996
+ text: "#123a5e",
1997
+ // blueprint navy ink
1998
+ textMuted: "#4f7390",
1999
+ // faint draft note
2000
+ textOnFillLight: "#f4f8fb",
2001
+ // drafting white
2002
+ textOnFillDark: "#0c2f4d",
2003
+ // deep blueprint navy
2004
+ primary: "#1f5e8c",
2005
+ // blueprint blue
2006
+ secondary: "#5b7d96",
2007
+ // steel
2008
+ accent: "#b08a3e",
2009
+ // draftsman's ochre highlight
2010
+ destructive: "#c0504d",
2011
+ // correction red
2012
+ colors: {
2013
+ red: "#c25a4e",
2014
+ // correction red
2015
+ orange: "#c2823e",
2016
+ // ochre
2017
+ yellow: "#c2a843",
2018
+ // pencil gold
2019
+ green: "#4f8a6b",
2020
+ // drafting green
2021
+ blue: "#1f5e8c",
2022
+ // blueprint blue
2023
+ purple: "#6f5e96",
2024
+ // indigo pencil
2025
+ teal: "#3a8a8a",
2026
+ // teal
2027
+ cyan: "#3f8fb5",
2028
+ // cyan
2029
+ gray: "#7e8e98",
2030
+ // graphite
2031
+ black: "#123a5e",
2032
+ // navy ink
2033
+ white: "#e6eef4"
2034
+ // panel
2035
+ }
2036
+ },
2037
+ dark: {
2038
+ bg: "#103a5e",
2039
+ // deep blueprint blue (cyanotype ground)
2040
+ surface: "#16466e",
2041
+ // raised sheet
2042
+ overlay: "#1c5180",
2043
+ // popovers, dropdowns
2044
+ border: "#3a6f96",
2045
+ // grid line
2046
+ text: "#eaf2f8",
2047
+ // chalk white
2048
+ textMuted: "#9fc0d6",
2049
+ // faint chalk note
2050
+ textOnFillLight: "#eaf2f8",
2051
+ // chalk white
2052
+ textOnFillDark: "#0c2f4d",
2053
+ // deep blueprint navy
2054
+ primary: "#7fb8d8",
2055
+ // chalk cyan
2056
+ secondary: "#9fb8c8",
2057
+ // pale steel
2058
+ accent: "#d8c27a",
2059
+ // chalk amber
2060
+ destructive: "#e08a7a",
2061
+ // chalk correction red
2062
+ colors: {
2063
+ red: "#e0907e",
2064
+ // chalk red
2065
+ orange: "#e0ab78",
2066
+ // chalk amber
2067
+ yellow: "#e3d089",
2068
+ // chalk gold
2069
+ green: "#93c79e",
2070
+ // chalk green
2071
+ blue: "#8ec3e0",
2072
+ // chalk cyan-blue
2073
+ purple: "#b6a6d8",
2074
+ // chalk indigo
2075
+ teal: "#84c7c2",
2076
+ // chalk teal
2077
+ cyan: "#9fd6e0",
2078
+ // chalk cyan
2079
+ gray: "#aebecb",
2080
+ // chalk graphite
2081
+ black: "#16466e",
2082
+ // raised sheet
2083
+ white: "#eaf2f8"
2084
+ // chalk white
2085
+ }
2086
+ }
2087
+ };
2088
+ registerPalette(blueprintPalette);
1897
2089
  }
1898
2090
  });
1899
2091
 
@@ -2390,6 +2582,120 @@ var init_rose_pine = __esm({
2390
2582
  }
2391
2583
  });
2392
2584
 
2585
+ // src/palettes/slate.ts
2586
+ var slatePalette;
2587
+ var init_slate = __esm({
2588
+ "src/palettes/slate.ts"() {
2589
+ "use strict";
2590
+ init_registry();
2591
+ slatePalette = {
2592
+ id: "slate",
2593
+ name: "Slate",
2594
+ light: {
2595
+ bg: "#ffffff",
2596
+ // clean slide white
2597
+ surface: "#f3f5f8",
2598
+ // light cool-gray panel
2599
+ overlay: "#eaeef3",
2600
+ // popovers, dropdowns
2601
+ border: "#d4dae1",
2602
+ // hairline rule
2603
+ text: "#1f2933",
2604
+ // near-black slate (softer than pure black)
2605
+ textMuted: "#5b6672",
2606
+ // secondary label
2607
+ textOnFillLight: "#ffffff",
2608
+ // light text on dark fills
2609
+ textOnFillDark: "#1f2933",
2610
+ // dark text on light fills
2611
+ primary: "#3b6ea5",
2612
+ // confident corporate blue
2613
+ secondary: "#5b6672",
2614
+ // slate gray
2615
+ accent: "#3a9188",
2616
+ // muted teal accent
2617
+ destructive: "#c0504d",
2618
+ // brick red
2619
+ colors: {
2620
+ red: "#c0504d",
2621
+ // brick
2622
+ orange: "#cc7a33",
2623
+ // muted amber
2624
+ yellow: "#c9a227",
2625
+ // gold (not neon)
2626
+ green: "#5b9357",
2627
+ // forest / sage
2628
+ blue: "#3b6ea5",
2629
+ // corporate blue
2630
+ purple: "#7d5ba6",
2631
+ // muted violet
2632
+ teal: "#3a9188",
2633
+ // teal
2634
+ cyan: "#4f96c4",
2635
+ // steel cyan
2636
+ gray: "#7e8a97",
2637
+ // cool gray
2638
+ black: "#1f2933",
2639
+ // slate ink
2640
+ white: "#f3f5f8"
2641
+ // panel
2642
+ }
2643
+ },
2644
+ dark: {
2645
+ bg: "#161b22",
2646
+ // deep slate (keynote dark)
2647
+ surface: "#202833",
2648
+ // raised panel
2649
+ overlay: "#29323e",
2650
+ // popovers, dropdowns
2651
+ border: "#38424f",
2652
+ // divider
2653
+ text: "#e6eaef",
2654
+ // off-white
2655
+ textMuted: "#9aa5b1",
2656
+ // secondary label
2657
+ textOnFillLight: "#ffffff",
2658
+ // light text on dark fills
2659
+ textOnFillDark: "#161b22",
2660
+ // dark text on light fills
2661
+ primary: "#5b9bd5",
2662
+ // lifted corporate blue
2663
+ secondary: "#8593a3",
2664
+ // slate gray, lifted
2665
+ accent: "#45b3a3",
2666
+ // teal, lifted
2667
+ destructive: "#e07b6e",
2668
+ // brick, lifted
2669
+ colors: {
2670
+ red: "#e07b6e",
2671
+ // brick
2672
+ orange: "#e0975a",
2673
+ // amber
2674
+ yellow: "#d9bd5a",
2675
+ // gold
2676
+ green: "#74b56e",
2677
+ // forest / sage
2678
+ blue: "#5b9bd5",
2679
+ // corporate blue
2680
+ purple: "#a585c9",
2681
+ // violet
2682
+ teal: "#45b3a3",
2683
+ // teal
2684
+ cyan: "#62b0d9",
2685
+ // steel cyan
2686
+ gray: "#95a1ae",
2687
+ // cool gray
2688
+ black: "#202833",
2689
+ // raised panel
2690
+ white: "#e6eaef"
2691
+ // off-white
2692
+ }
2693
+ }
2694
+ };
2695
+ registerPalette(slatePalette);
2696
+ }
2697
+ });
2698
+
2393
2699
  // src/palettes/solarized.ts
2394
2700
  var solarizedPalette;
2395
2701
  var init_solarized = __esm({
@@ -2485,6 +2791,120 @@ var init_solarized = __esm({
2485
2791
  }
2486
2792
  });
2487
2793
 
2794
+ // src/palettes/tidewater.ts
2795
+ var tidewaterPalette;
2796
+ var init_tidewater = __esm({
2797
+ "src/palettes/tidewater.ts"() {
2798
+ "use strict";
2799
+ init_registry();
2800
+ tidewaterPalette = {
2801
+ id: "tidewater",
2802
+ name: "Tidewater",
2803
+ light: {
2804
+ bg: "#eceff0",
2805
+ // weathered sea-mist paper
2806
+ surface: "#e0e4e3",
2807
+ // worn deck panel
2808
+ overlay: "#dadfdf",
2809
+ // popovers, dropdowns
2810
+ border: "#a9b2b3",
2811
+ // muted slate rule
2812
+ text: "#18313f",
2813
+ // ship's-log navy ink
2814
+ textMuted: "#51636b",
2815
+ // faded log entry
2816
+ textOnFillLight: "#f3f5f3",
2817
+ // weathered white
2818
+ textOnFillDark: "#162c38",
2819
+ // deep navy
2820
+ primary: "#1f4e6b",
2821
+ // deep-sea navy
2822
+ secondary: "#b08a4f",
2823
+ // rope / manila tan
2824
+ accent: "#c69a3e",
2825
+ // brass
2826
+ destructive: "#c1433a",
2827
+ // signal-flag red
2828
+ colors: {
2829
+ red: "#c1433a",
2830
+ // signal-flag red
2831
+ orange: "#cc7a38",
2832
+ // weathered amber
2833
+ yellow: "#d6bf5a",
2834
+ // brass gold
2835
+ green: "#4f8a6b",
2836
+ // sea-glass green
2837
+ blue: "#1f4e6b",
2838
+ // deep-sea navy
2839
+ purple: "#6a5a8c",
2840
+ // twilight harbor
2841
+ teal: "#3d8c8c",
2842
+ // sea-glass teal
2843
+ cyan: "#4f9bb5",
2844
+ // shallow water
2845
+ gray: "#8a8d86",
2846
+ // driftwood gray
2847
+ black: "#18313f",
2848
+ // navy ink
2849
+ white: "#e0e4e3"
2850
+ // deck panel
2851
+ }
2852
+ },
2853
+ dark: {
2854
+ bg: "#0f2230",
2855
+ // night-harbor deep sea
2856
+ surface: "#16303f",
2857
+ // raised hull
2858
+ overlay: "#1d3a4a",
2859
+ // popovers, dropdowns
2860
+ border: "#2c4856",
2861
+ // rigging line
2862
+ text: "#e6ebe8",
2863
+ // weathered white
2864
+ textMuted: "#9aaab0",
2865
+ // faded label
2866
+ textOnFillLight: "#f3f5f3",
2867
+ // weathered white
2868
+ textOnFillDark: "#0f2230",
2869
+ // deep sea
2870
+ primary: "#4f9bc4",
2871
+ // lifted sea blue
2872
+ secondary: "#c9a46a",
2873
+ // rope tan, lifted
2874
+ accent: "#d9b25a",
2875
+ // brass, lifted
2876
+ destructive: "#e06a5e",
2877
+ // signal red, lifted
2878
+ colors: {
2879
+ red: "#e06a5e",
2880
+ // signal-flag red
2881
+ orange: "#df9a52",
2882
+ // amber
2883
+ yellow: "#e0c662",
2884
+ // brass gold
2885
+ green: "#6fb58c",
2886
+ // sea-glass green
2887
+ blue: "#4f9bc4",
2888
+ // sea blue
2889
+ purple: "#9486bf",
2890
+ // twilight harbor
2891
+ teal: "#5cb0ac",
2892
+ // sea-glass teal
2893
+ cyan: "#62b4cf",
2894
+ // shallow water
2895
+ gray: "#9aa39c",
2896
+ // driftwood gray
2897
+ black: "#16303f",
2898
+ // raised hull
2899
+ white: "#e6ebe8"
2900
+ // weathered white
2901
+ }
2902
+ }
2903
+ };
2904
+ registerPalette(tidewaterPalette);
2905
+ }
2906
+ });
2907
+
2488
2908
  // src/palettes/tokyo-night.ts
2489
2909
  var tokyoNightPalette;
2490
2910
  var init_tokyo_night = __esm({
@@ -2760,7 +3180,8 @@ var init_monokai = __esm({
2760
3180
  // src/palettes/index.ts
2761
3181
  var palettes_exports = {};
2762
3182
  __export(palettes_exports, {
2763
- boldPalette: () => boldPalette,
3183
+ atlasPalette: () => atlasPalette,
3184
+ blueprintPalette: () => blueprintPalette,
2764
3185
  catppuccinPalette: () => catppuccinPalette,
2765
3186
  contrastText: () => contrastText,
2766
3187
  draculaPalette: () => draculaPalette,
@@ -2781,7 +3202,9 @@ __export(palettes_exports, {
2781
3202
  rosePinePalette: () => rosePinePalette,
2782
3203
  shade: () => shade,
2783
3204
  shapeFill: () => shapeFill,
3205
+ slatePalette: () => slatePalette,
2784
3206
  solarizedPalette: () => solarizedPalette,
3207
+ tidewaterPalette: () => tidewaterPalette,
2785
3208
  tint: () => tint,
2786
3209
  tokyoNightPalette: () => tokyoNightPalette
2787
3210
  });
@@ -2791,17 +3214,21 @@ var init_palettes = __esm({
2791
3214
  "use strict";
2792
3215
  init_registry();
2793
3216
  init_color_utils();
2794
- init_bold();
3217
+ init_atlas();
3218
+ init_blueprint();
2795
3219
  init_catppuccin();
2796
3220
  init_gruvbox();
2797
3221
  init_nord();
2798
3222
  init_one_dark();
2799
3223
  init_rose_pine();
3224
+ init_slate();
2800
3225
  init_solarized();
3226
+ init_tidewater();
2801
3227
  init_tokyo_night();
2802
3228
  init_dracula();
2803
3229
  init_monokai();
2804
- init_bold();
3230
+ init_atlas();
3231
+ init_blueprint();
2805
3232
  init_catppuccin();
2806
3233
  init_dracula();
2807
3234
  init_gruvbox();
@@ -2809,9 +3236,15 @@ var init_palettes = __esm({
2809
3236
  init_nord();
2810
3237
  init_one_dark();
2811
3238
  init_rose_pine();
3239
+ init_slate();
2812
3240
  init_solarized();
3241
+ init_tidewater();
2813
3242
  init_tokyo_night();
2814
3243
  palettes = {
3244
+ atlas: atlasPalette,
3245
+ blueprint: blueprintPalette,
3246
+ slate: slatePalette,
3247
+ tidewater: tidewaterPalette,
2815
3248
  nord: nordPalette,
2816
3249
  catppuccin: catppuccinPalette,
2817
3250
  solarized: solarizedPalette,
@@ -2820,8 +3253,7 @@ var init_palettes = __esm({
2820
3253
  oneDark: oneDarkPalette,
2821
3254
  rosePine: rosePinePalette,
2822
3255
  dracula: draculaPalette,
2823
- monokai: monokaiPalette,
2824
- bold: boldPalette
3256
+ monokai: monokaiPalette
2825
3257
  };
2826
3258
  }
2827
3259
  });
@@ -3331,6 +3763,9 @@ function controlsGroupCapsuleWidth(toggles) {
3331
3763
  }
3332
3764
  return w;
3333
3765
  }
3766
+ function isAppHostedControls(config, isExport) {
3767
+ return !isExport && config.controlsHost === "app" && !!config.controlsGroup && config.controlsGroup.toggles.length > 0;
3768
+ }
3334
3769
  function buildControlsGroupLayout(config, state) {
3335
3770
  const cg = config.controlsGroup;
3336
3771
  if (!cg || cg.toggles.length === 0) return void 0;
@@ -3384,6 +3819,7 @@ function buildControlsGroupLayout(config, state) {
3384
3819
  function computeLegendLayout(config, state, containerWidth) {
3385
3820
  const { groups, controls: configControls, mode } = config;
3386
3821
  const isExport = mode === "export";
3822
+ const gated = isAppHostedControls(config, isExport);
3387
3823
  const activeGroupName = state.activeGroup?.toLowerCase() ?? null;
3388
3824
  if (isExport && !activeGroupName) {
3389
3825
  return {
@@ -3394,7 +3830,7 @@ function computeLegendLayout(config, state, containerWidth) {
3394
3830
  pills: []
3395
3831
  };
3396
3832
  }
3397
- const controlsGroupLayout = isExport ? void 0 : buildControlsGroupLayout(config, state);
3833
+ const controlsGroupLayout = isExport || gated ? void 0 : buildControlsGroupLayout(config, state);
3398
3834
  const visibleGroups = config.showEmptyGroups ? groups : groups.filter((g) => g.entries.length > 0 || !!g.gradient);
3399
3835
  if (visibleGroups.length === 0 && (!configControls || configControls.length === 0) && !controlsGroupLayout) {
3400
3836
  return {
@@ -8296,8 +8732,8 @@ function computeScatterLabelGraphics(points, chartBounds, fontSize, symbolSize,
8296
8732
  const pt = points[i];
8297
8733
  const ptSize = pt.size ?? symbolSize;
8298
8734
  const minGap = ptSize / 2 + 4;
8299
- const labelWidth = pt.name.length * fontSize * 0.6 + 8;
8300
- const labelX = pt.px - labelWidth / 2;
8735
+ const labelWidth2 = pt.name.length * fontSize * 0.6 + 8;
8736
+ const labelX = pt.px - labelWidth2 / 2;
8301
8737
  let bestLabelY = 0;
8302
8738
  let bestOffset = Infinity;
8303
8739
  let placed = false;
@@ -8309,7 +8745,7 @@ function computeScatterLabelGraphics(points, chartBounds, fontSize, symbolSize,
8309
8745
  const candidate = {
8310
8746
  x: labelX,
8311
8747
  y: labelY,
8312
- w: labelWidth,
8748
+ w: labelWidth2,
8313
8749
  h: labelHeight
8314
8750
  };
8315
8751
  let collision = false;
@@ -8351,7 +8787,7 @@ function computeScatterLabelGraphics(points, chartBounds, fontSize, symbolSize,
8351
8787
  const labelRect = {
8352
8788
  x: labelX,
8353
8789
  y: bestLabelY,
8354
- w: labelWidth,
8790
+ w: labelWidth2,
8355
8791
  h: labelHeight
8356
8792
  };
8357
8793
  placedLabels.push(labelRect);
@@ -8387,7 +8823,7 @@ function computeScatterLabelGraphics(points, chartBounds, fontSize, symbolSize,
8387
8823
  shape: {
8388
8824
  x: labelX - bgPad,
8389
8825
  y: bestLabelY - bgPad,
8390
- width: labelWidth + bgPad * 2,
8826
+ width: labelWidth2 + bgPad * 2,
8391
8827
  height: labelHeight + bgPad * 2
8392
8828
  },
8393
8829
  style: { fill: bg },
@@ -15823,10 +16259,6 @@ function parseMap(content) {
15823
16259
  handleTag(trimmed, lineNumber);
15824
16260
  continue;
15825
16261
  }
15826
- if ((firstWord === "muted" || firstWord === "natural") && trimmed === firstWord) {
15827
- handleDirective(firstWord, "", lineNumber);
15828
- continue;
15829
- }
15830
16262
  if (DIRECTIVE_SET.has(firstWord) && !trimmed.slice(firstWord.length).trimStart().startsWith(":")) {
15831
16263
  handleDirective(
15832
16264
  firstWord,
@@ -15873,24 +16305,6 @@ function parseMap(content) {
15873
16305
  pushWarning(line12, `Duplicate directive "${key}" \u2014 last value wins.`);
15874
16306
  };
15875
16307
  switch (key) {
15876
- case "region":
15877
- dup(d.region);
15878
- d.region = value;
15879
- break;
15880
- case "projection":
15881
- dup(d.projection);
15882
- if (value && ![
15883
- "equirectangular",
15884
- "natural-earth",
15885
- "albers-usa",
15886
- "mercator"
15887
- ].includes(value))
15888
- pushWarning(
15889
- line12,
15890
- `Unknown projection "${value}" (expected equirectangular | natural-earth | albers-usa | mercator).`
15891
- );
15892
- d.projection = value;
15893
- break;
15894
16308
  case "region-metric": {
15895
16309
  dup(d.regionMetric);
15896
16310
  const { label: rmLabel, colorName: rmColor } = peelTrailingColorName(value);
@@ -15906,91 +16320,43 @@ function parseMap(content) {
15906
16320
  dup(d.flowMetric);
15907
16321
  d.flowMetric = value;
15908
16322
  break;
15909
- case "scale":
15910
- dup(d.scale);
15911
- {
15912
- const s = parseScale(value, line12);
15913
- if (s) d.scale = s;
15914
- }
15915
- break;
15916
- case "region-labels":
15917
- dup(d.regionLabels);
15918
- if (value && !["full", "abbrev", "off"].includes(value))
15919
- pushWarning(
15920
- line12,
15921
- `Unknown region-labels "${value}" (expected full | abbrev | off).`
15922
- );
15923
- d.regionLabels = value;
15924
- break;
15925
- case "poi-labels":
15926
- dup(d.poiLabels);
15927
- if (value && !["off", "auto", "all"].includes(value))
15928
- pushWarning(
15929
- line12,
15930
- `Unknown poi-labels "${value}" (expected off | auto | all).`
15931
- );
15932
- d.poiLabels = value;
15933
- break;
15934
- case "default-country":
15935
- dup(d.defaultCountry);
15936
- d.defaultCountry = value;
15937
- break;
15938
- case "default-state":
15939
- dup(d.defaultState);
15940
- d.defaultState = value;
16323
+ case "locale":
16324
+ dup(d.locale);
16325
+ d.locale = value;
15941
16326
  break;
15942
16327
  case "active-tag":
15943
16328
  dup(d.activeTag);
15944
16329
  d.activeTag = value;
15945
16330
  break;
16331
+ case "caption":
16332
+ dup(d.caption);
16333
+ d.caption = value;
16334
+ break;
16335
+ // ── Cosmetic `no-*` opt-outs: bare flags, idempotent (mirror `no-legend`,
16336
+ // no dup warning); each defaults the feature ON when absent. ──
15946
16337
  case "no-legend":
15947
16338
  d.noLegend = true;
15948
16339
  break;
15949
- case "no-insets":
15950
- d.noInsets = true;
16340
+ case "no-coastline":
16341
+ d.noCoastline = true;
15951
16342
  break;
15952
- case "relief":
15953
- d.relief = true;
16343
+ case "no-relief":
16344
+ d.noRelief = true;
15954
16345
  break;
15955
- case "muted":
15956
- case "natural":
15957
- if (d.basemapStyle !== void 0 && d.basemapStyle !== key)
15958
- pushWarning(
15959
- line12,
15960
- `Conflicting basemap dress \u2014 "${d.basemapStyle}" then "${key}"; last wins.`
15961
- );
15962
- d.basemapStyle = key;
16346
+ case "no-context-labels":
16347
+ d.noContextLabels = true;
15963
16348
  break;
15964
- case "subtitle":
15965
- dup(d.subtitle);
15966
- d.subtitle = value;
16349
+ case "no-region-labels":
16350
+ d.noRegionLabels = true;
15967
16351
  break;
15968
- case "caption":
15969
- dup(d.caption);
15970
- d.caption = value;
16352
+ case "no-poi-labels":
16353
+ d.noPoiLabels = true;
16354
+ break;
16355
+ case "no-colorize":
16356
+ d.noColorize = true;
15971
16357
  break;
15972
16358
  }
15973
16359
  }
15974
- function parseScale(value, line12) {
15975
- const toks = value.split(/\s+/).filter(Boolean);
15976
- const min = Number(toks[0]);
15977
- const max = Number(toks[1]);
15978
- if (!Number.isFinite(min) || !Number.isFinite(max)) {
15979
- pushError(line12, `scale requires numeric <min> <max> (got "${value}").`);
15980
- return null;
15981
- }
15982
- const scale = { min, max };
15983
- if (toks[2] === "center") {
15984
- const c = Number(toks[3]);
15985
- if (Number.isFinite(c)) scale.center = c;
15986
- else
15987
- pushError(
15988
- line12,
15989
- `scale center requires a number (got "${toks[3] ?? ""}").`
15990
- );
15991
- }
15992
- return scale;
15993
- }
15994
16360
  function handleTag(trimmed, line12) {
15995
16361
  const m = matchTagBlockHeading(trimmed);
15996
16362
  if (!m) {
@@ -16190,13 +16556,15 @@ function parseMap(content) {
16190
16556
  pushError(line12, `Edge has an empty endpoint: "${trimmed}".`);
16191
16557
  continue;
16192
16558
  }
16193
- const meta = k === links.length - 1 ? lastSplit.meta : {};
16559
+ const isLast = k === links.length - 1;
16560
+ const meta = isLast ? lastSplit.meta : {};
16561
+ const style = links[k].style === "arc" ? "arc" : "straight";
16194
16562
  edges.push({
16195
16563
  from,
16196
16564
  to,
16197
16565
  ...links[k].label !== void 0 && { label: links[k].label },
16198
16566
  directed: links[k].directed,
16199
- style: links[k].style,
16567
+ style,
16200
16568
  meta,
16201
16569
  lineNumber: line12
16202
16570
  });
@@ -16282,22 +16650,19 @@ var init_parser12 = __esm({
16282
16650
  LEG_ARROW_RE = /^(-[^>]*?->|->|~[^>]*?~>|~>|--)\s+(.+)$/;
16283
16651
  AT_RE = /(^|[\s,])at\s*:/i;
16284
16652
  DIRECTIVE_SET = /* @__PURE__ */ new Set([
16285
- "region",
16286
- "projection",
16287
16653
  "region-metric",
16288
16654
  "poi-metric",
16289
16655
  "flow-metric",
16290
- "scale",
16291
- "region-labels",
16292
- "poi-labels",
16293
- "default-country",
16294
- "default-state",
16656
+ "locale",
16295
16657
  "active-tag",
16658
+ "caption",
16296
16659
  "no-legend",
16297
- "no-insets",
16298
- "relief",
16299
- "subtitle",
16300
- "caption"
16660
+ "no-coastline",
16661
+ "no-relief",
16662
+ "no-context-labels",
16663
+ "no-region-labels",
16664
+ "no-poi-labels",
16665
+ "no-colorize"
16301
16666
  ]);
16302
16667
  }
16303
16668
  });
@@ -24219,8 +24584,8 @@ function renderKanban(container, parsed, palette, isDark, options) {
24219
24584
  let metaY = separatorY + sCardSeparatorGap + sCardMetaFontSize;
24220
24585
  for (const meta of tagMeta) {
24221
24586
  cg.append("text").attr("x", cx + sCardPaddingX).attr("y", metaY).attr("font-size", sCardMetaFontSize).attr("fill", onCardText).text(`${meta.label}: `);
24222
- const labelWidth = (meta.label.length + 2) * sCardMetaFontSize * 0.6;
24223
- cg.append("text").attr("x", cx + sCardPaddingX + labelWidth).attr("y", metaY).attr("font-size", sCardMetaFontSize).attr("fill", onCardText).text(meta.value);
24587
+ const labelWidth2 = (meta.label.length + 2) * sCardMetaFontSize * 0.6;
24588
+ cg.append("text").attr("x", cx + sCardPaddingX + labelWidth2).attr("y", metaY).attr("font-size", sCardMetaFontSize).attr("fill", onCardText).text(meta.value);
24224
24589
  metaY += sCardMetaLineHeight;
24225
24590
  }
24226
24591
  for (const detail of card.details) {
@@ -24564,8 +24929,8 @@ function renderSwimlaneCard(parent, cardLayout, tagGroups, activeTagGroup, palet
24564
24929
  let metaY = separatorY + sCardSeparatorGap + sCardMetaFontSize;
24565
24930
  for (const meta of tagMeta) {
24566
24931
  cg.append("text").attr("x", cx + sCardPaddingX).attr("y", metaY).attr("font-size", sCardMetaFontSize).attr("fill", palette.textMuted).text(`${meta.label}: `);
24567
- const labelWidth = (meta.label.length + 2) * sCardMetaFontSize * 0.6;
24568
- cg.append("text").attr("x", cx + sCardPaddingX + labelWidth).attr("y", metaY).attr("font-size", sCardMetaFontSize).attr("fill", onCardText).text(meta.value);
24932
+ const labelWidth2 = (meta.label.length + 2) * sCardMetaFontSize * 0.6;
24933
+ cg.append("text").attr("x", cx + sCardPaddingX + labelWidth2).attr("y", metaY).attr("font-size", sCardMetaFontSize).attr("fill", onCardText).text(meta.value);
24569
24934
  metaY += sCardMetaLineHeight;
24570
24935
  }
24571
24936
  for (const detail of card.details) {
@@ -25399,8 +25764,8 @@ function classifyEREntities(tables, relationships) {
25399
25764
  }
25400
25765
  }
25401
25766
  const mmParticipants = /* @__PURE__ */ new Set();
25402
- for (const [id, neighbors] of tableStarNeighbors) {
25403
- if (neighbors.size >= 2) mmParticipants.add(id);
25767
+ for (const [id, neighbors2] of tableStarNeighbors) {
25768
+ if (neighbors2.size >= 2) mmParticipants.add(id);
25404
25769
  }
25405
25770
  const indegreeValues = Object.values(indegreeMap);
25406
25771
  const mean = indegreeValues.reduce((a, b) => a + b, 0) / indegreeValues.length;
@@ -26073,7 +26438,8 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26073
26438
  controlsExpanded,
26074
26439
  onToggleDescriptions,
26075
26440
  onToggleControlsExpand,
26076
- exportMode = false
26441
+ exportMode = false,
26442
+ controlsHost
26077
26443
  } = options ?? {};
26078
26444
  d3Selection6.select(container).selectAll(":not([data-d3-tooltip])").remove();
26079
26445
  const width = exportDims?.width ?? container.clientWidth;
@@ -26091,7 +26457,11 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26091
26457
  const sGroupLabelZone = sctx.structural(GROUP_LABEL_ZONE);
26092
26458
  const sTitleFontSize = sctx.text(TITLE_FONT_SIZE);
26093
26459
  const sTitleY = sctx.structural(TITLE_Y);
26094
- const sLegendHeight = sctx.structural(
26460
+ const reserveHasDescriptions = parsed.nodes.some(
26461
+ (n) => n.description && n.description.length > 0
26462
+ );
26463
+ const willRenderLegend = parsed.tagGroups.length > 0 || reserveHasDescriptions && controlsHost !== "app";
26464
+ const sLegendHeight = willRenderLegend ? sctx.structural(
26095
26465
  getMaxLegendReservedHeight(
26096
26466
  {
26097
26467
  groups: parsed.tagGroups,
@@ -26100,7 +26470,7 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26100
26470
  },
26101
26471
  width
26102
26472
  )
26103
- );
26473
+ ) : 0;
26104
26474
  const activeGroup = resolveActiveTagGroup(
26105
26475
  parsed.tagGroups,
26106
26476
  parsed.options["active-tag"],
@@ -26415,10 +26785,10 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26415
26785
  const hasDescriptions = parsed.nodes.some(
26416
26786
  (n) => n.description && n.description.length > 0
26417
26787
  );
26418
- const hasLegend = parsed.tagGroups.length > 0 || hasDescriptions;
26788
+ const hasLegend = parsed.tagGroups.length > 0 || hasDescriptions && controlsHost !== "app";
26419
26789
  if (hasLegend) {
26420
26790
  let controlsGroup;
26421
- if (hasDescriptions && onToggleDescriptions) {
26791
+ if (hasDescriptions && (onToggleDescriptions || controlsHost === "app")) {
26422
26792
  controlsGroup = {
26423
26793
  toggles: [
26424
26794
  {
@@ -26436,7 +26806,14 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26436
26806
  groups: parsed.tagGroups,
26437
26807
  position: { placement: "top-center", titleRelation: "below-title" },
26438
26808
  mode: exportMode ? "export" : "preview",
26439
- ...controlsGroup !== void 0 && { controlsGroup }
26809
+ // Keep inactive sibling tag groups visible as collapsed pills so the user
26810
+ // can click one to flip the active colouring dimension (preview only —
26811
+ // export shows just the active group). Without this, declaring a second
26812
+ // tag group (e.g. Team) leaves it invisible whenever another group is
26813
+ // active. The app's BoxesAndLinesPreview already wires pill clicks.
26814
+ showInactivePills: true,
26815
+ ...controlsGroup !== void 0 && { controlsGroup },
26816
+ ...controlsHost !== void 0 && { controlsHost }
26440
26817
  };
26441
26818
  const legendState = {
26442
26819
  activeGroup,
@@ -27683,8 +28060,9 @@ function renderMindmap(container, parsed, layout, palette, isDark, onClickItem,
27683
28060
  const containerHeight = exportDims?.height ?? (container.getBoundingClientRect().height || 600);
27684
28061
  d3Selection7.select(container).selectAll("*").remove();
27685
28062
  const svg = d3Selection7.select(container).append("svg").attr("width", containerWidth).attr("height", containerHeight).attr("viewBox", `0 0 ${containerWidth} ${containerHeight}`).attr("preserveAspectRatio", "xMidYMin meet").style("font-family", FONT_FAMILY);
28063
+ const appHosted = options?.controlsHost === "app";
27686
28064
  const hasControls = !!options?.onToggleColorByDepth || !!options?.onToggleDescriptions;
27687
- const hasLegend = parsed.tagGroups.length > 0 || hasControls;
28065
+ const hasLegend = parsed.tagGroups.length > 0 || hasControls && !appHosted;
27688
28066
  const fixedLegend = !isExport && hasLegend;
27689
28067
  const legendReserve = fixedLegend ? getMaxLegendReservedHeight(
27690
28068
  {
@@ -27778,7 +28156,10 @@ function renderMindmap(container, parsed, layout, palette, isDark, onClickItem,
27778
28156
  }),
27779
28157
  position: { placement: "top-center", titleRelation: "below-title" },
27780
28158
  mode: options?.exportMode ? "export" : "preview",
27781
- ...controlsToggles !== void 0 && { controlsGroup: controlsToggles }
28159
+ ...controlsToggles !== void 0 && { controlsGroup: controlsToggles },
28160
+ ...options?.controlsHost !== void 0 && {
28161
+ controlsHost: options.controlsHost
28162
+ }
27782
28163
  };
27783
28164
  const legendState = {
27784
28165
  activeGroup: options?.colorByDepth ? null : activeTagGroup !== void 0 ? activeTagGroup : parsed.options["active-tag"] ?? null,
@@ -28221,8 +28602,8 @@ function computeFieldAlignX(children) {
28221
28602
  for (const child of children) {
28222
28603
  if (child.metadata["_labelField"] === "true" && child.children.length >= 2) {
28223
28604
  const labelEl = child.children[0];
28224
- const labelWidth = labelEl.label.length * CHAR_WIDTH5;
28225
- maxLabelWidth = Math.max(maxLabelWidth, labelWidth);
28605
+ const labelWidth2 = labelEl.label.length * CHAR_WIDTH5;
28606
+ maxLabelWidth = Math.max(maxLabelWidth, labelWidth2);
28226
28607
  labelFieldCount++;
28227
28608
  }
28228
28609
  }
@@ -33187,7 +33568,7 @@ function hasRoles(node) {
33187
33568
  function computeNodeWidth2(node, expanded, options) {
33188
33569
  const badgeVal = node.computedConcurrentInvocations === 0 && node.computedInstances > 1 ? node.computedInstances : 0;
33189
33570
  const badgeLen = badgeVal > 0 ? `${badgeVal}x`.length + 2 : 0;
33190
- const labelWidth = (node.label.length + badgeLen) * CHAR_WIDTH7 + PADDING_X3;
33571
+ const labelWidth2 = (node.label.length + badgeLen) * CHAR_WIDTH7 + PADDING_X3;
33191
33572
  const allKeys = [];
33192
33573
  if (node.computedRps > 0) allKeys.push("RPS");
33193
33574
  if (expanded) {
@@ -33231,7 +33612,7 @@ function computeNodeWidth2(node, expanded, options) {
33231
33612
  allKeys.push("overflow");
33232
33613
  }
33233
33614
  }
33234
- if (allKeys.length === 0) return Math.max(MIN_NODE_WIDTH2, labelWidth);
33615
+ if (allKeys.length === 0) return Math.max(MIN_NODE_WIDTH2, labelWidth2);
33235
33616
  const maxKeyLen = Math.max(...allKeys.map((k) => k.length));
33236
33617
  let maxRowWidth = 0;
33237
33618
  if (node.computedRps > 0) {
@@ -33319,7 +33700,7 @@ function computeNodeWidth2(node, expanded, options) {
33319
33700
  truncated.length * META_CHAR_WIDTH3 + PADDING_X3
33320
33701
  );
33321
33702
  }
33322
- return Math.max(MIN_NODE_WIDTH2, labelWidth, maxRowWidth + 20, descWidth);
33703
+ return Math.max(MIN_NODE_WIDTH2, labelWidth2, maxRowWidth + 20, descWidth);
33323
33704
  }
33324
33705
  function computeNodeHeight2(node, expanded, options) {
33325
33706
  const propCount = countDisplayProps(node, expanded, options);
@@ -34869,8 +35250,9 @@ function computeInfraLegendGroups(nodes, tagGroups, palette, edges) {
34869
35250
  }
34870
35251
  return groups;
34871
35252
  }
34872
- function renderLegend3(rootSvg, legendGroups, totalWidth, legendY, palette, isDark, activeGroup, playback, exportMode = false) {
35253
+ function renderLegend3(rootSvg, legendGroups, totalWidth, legendY, palette, isDark, activeGroup, playback, exportMode = false, controlsHost) {
34873
35254
  if (legendGroups.length === 0 && !playback) return;
35255
+ const appHostedPlayback = controlsHost === "app" && !!playback;
34874
35256
  const legendG = rootSvg.append("g").attr("transform", `translate(0, ${legendY})`);
34875
35257
  if (activeGroup) {
34876
35258
  legendG.attr("data-legend-active", activeGroup.toLowerCase());
@@ -34879,14 +35261,29 @@ function renderLegend3(rootSvg, legendGroups, totalWidth, legendY, palette, isDa
34879
35261
  name: g.name,
34880
35262
  entries: g.entries.map((e) => ({ value: e.value, color: e.color }))
34881
35263
  }));
34882
- if (playback) {
35264
+ if (playback && !appHostedPlayback) {
34883
35265
  allGroups.push({ name: "Playback", entries: [] });
34884
35266
  }
34885
35267
  const legendConfig = {
34886
35268
  groups: allGroups,
34887
35269
  position: { placement: "top-center", titleRelation: "below-title" },
34888
35270
  mode: exportMode ? "export" : "preview",
34889
- showEmptyGroups: true
35271
+ showEmptyGroups: true,
35272
+ ...appHostedPlayback && {
35273
+ controlsHost: "app",
35274
+ controlsGroup: {
35275
+ toggles: [
35276
+ {
35277
+ id: "playback",
35278
+ type: "toggle",
35279
+ label: "Playback",
35280
+ active: true,
35281
+ onToggle: () => {
35282
+ }
35283
+ }
35284
+ ]
35285
+ }
35286
+ }
34890
35287
  };
34891
35288
  const legendState = { activeGroup };
34892
35289
  renderLegendD3(
@@ -34937,8 +35334,9 @@ function renderLegend3(rootSvg, legendGroups, totalWidth, legendY, palette, isDa
34937
35334
  }
34938
35335
  }
34939
35336
  }
34940
- function renderInfra(container, layout, palette, isDark, title, titleLineNumber, tagGroups, activeGroup, animate, playback, expandedNodeIds, exportMode, collapsedNodes) {
35337
+ function renderInfra(container, layout, palette, isDark, title, titleLineNumber, tagGroups, activeGroup, animate, playback, expandedNodeIds, exportMode, collapsedNodes, controlsHost) {
34941
35338
  d3Selection11.select(container).selectAll(":not([data-d3-tooltip])").remove();
35339
+ const appHostedPlayback = controlsHost === "app" && !!playback;
34942
35340
  const ctx = ScaleContext.identity();
34943
35341
  const sc = buildScaledConstants(ctx);
34944
35342
  const legendGroups = computeInfraLegendGroups(
@@ -34947,7 +35345,7 @@ function renderInfra(container, layout, palette, isDark, title, titleLineNumber,
34947
35345
  palette,
34948
35346
  layout.edges
34949
35347
  );
34950
- const hasLegend = legendGroups.length > 0 || !!playback;
35348
+ const hasLegend = legendGroups.length > 0 || !!playback && !appHostedPlayback;
34951
35349
  const fixedLegend = !exportMode && hasLegend;
34952
35350
  const legendDynamicH = hasLegend ? getMaxLegendReservedHeight(
34953
35351
  {
@@ -35091,7 +35489,8 @@ function renderInfra(container, layout, palette, isDark, title, titleLineNumber,
35091
35489
  isDark,
35092
35490
  activeGroup ?? null,
35093
35491
  playback ?? void 0,
35094
- exportMode
35492
+ exportMode,
35493
+ controlsHost
35095
35494
  );
35096
35495
  legendSvg.selectAll(".infra-legend-group").style("pointer-events", "auto");
35097
35496
  } else {
@@ -35104,7 +35503,8 @@ function renderInfra(container, layout, palette, isDark, title, titleLineNumber,
35104
35503
  isDark,
35105
35504
  activeGroup ?? null,
35106
35505
  playback ?? void 0,
35107
- exportMode
35506
+ exportMode,
35507
+ controlsHost
35108
35508
  );
35109
35509
  }
35110
35510
  }
@@ -42738,6 +43138,9 @@ function renderTechRadar(container, parsed, palette, isDark, onClickItem, export
42738
43138
  onToggle: (active) => options.onToggleListing(active)
42739
43139
  }
42740
43140
  ]
43141
+ },
43142
+ ...options.controlsHost !== void 0 && {
43143
+ controlsHost: options.controlsHost
42741
43144
  }
42742
43145
  };
42743
43146
  const legendState = {
@@ -44560,7 +44963,7 @@ function computeCycleLayout(parsed, options) {
44560
44963
  const circleNodes = parsed.options["circle-nodes"] === "true";
44561
44964
  const nodeDims = parsed.nodes.map((node) => {
44562
44965
  const hasDesc = !hideDescriptions && node.description.length > 0;
44563
- const labelWidth = Math.max(
44966
+ const labelWidth2 = Math.max(
44564
44967
  MIN_NODE_WIDTH4,
44565
44968
  node.label.length * LABEL_CHAR_W + NODE_PAD_X * 2
44566
44969
  );
@@ -44569,12 +44972,12 @@ function computeCycleLayout(parsed, options) {
44569
44972
  }
44570
44973
  if (!hasDesc) {
44571
44974
  return {
44572
- width: Math.min(MAX_NODE_WIDTH3, labelWidth),
44975
+ width: Math.min(MAX_NODE_WIDTH3, labelWidth2),
44573
44976
  height: PLAIN_NODE_HEIGHT,
44574
44977
  wrappedDesc: []
44575
44978
  };
44576
44979
  }
44577
- return chooseDescribedRectDims(node.description, labelWidth);
44980
+ return chooseDescribedRectDims(node.description, labelWidth2);
44578
44981
  });
44579
44982
  if (circleNodes) {
44580
44983
  const maxDiam = Math.max(...nodeDims.map((d) => d.width));
@@ -44770,10 +45173,10 @@ function computeCycleLayout(parsed, options) {
44770
45173
  scale
44771
45174
  };
44772
45175
  }
44773
- function chooseDescribedRectDims(description, labelWidth) {
45176
+ function chooseDescribedRectDims(description, labelWidth2) {
44774
45177
  const minW = Math.min(
44775
45178
  MAX_NODE_WIDTH3,
44776
- Math.max(MIN_NODE_WIDTH4, labelWidth, DESC_MIN_WIDTH)
45179
+ Math.max(MIN_NODE_WIDTH4, labelWidth2, DESC_MIN_WIDTH)
44777
45180
  );
44778
45181
  let best = null;
44779
45182
  let bestScore = Infinity;
@@ -45202,7 +45605,8 @@ function renderCycle(container, parsed, palette, isDark, onClickItem, exportDims
45202
45605
  const hideDescriptions = (renderOptions?.hideDescriptions ?? false) || parsed.options["no-descriptions"] === "true" || viewState?.hd === true;
45203
45606
  const showDescriptions = !hideDescriptions;
45204
45607
  const hasDescriptions = parsed.nodes.some((n) => n.description.length > 0) || parsed.edges.some((e) => e.description.length > 0);
45205
- const hasLegend = hasDescriptions && !!renderOptions?.onToggleDescriptions;
45608
+ const appHostedControls = renderOptions?.controlsHost === "app";
45609
+ const hasLegend = !appHostedControls && hasDescriptions && !!renderOptions?.onToggleDescriptions;
45206
45610
  const showTitle = !!parsed.title && parsed.options["no-title"] !== "on";
45207
45611
  const legendOffset = hasLegend ? sLegendHeight : 0;
45208
45612
  const layoutHeight = height - (showTitle ? sTitleAreaHeight : 0) - legendOffset;
@@ -45239,7 +45643,10 @@ function renderCycle(container, parsed, palette, isDark, onClickItem, exportDims
45239
45643
  groups: [],
45240
45644
  position: { placement: "top-center", titleRelation: "below-title" },
45241
45645
  mode: renderOptions?.exportMode ? "export" : "preview",
45242
- controlsGroup
45646
+ controlsGroup,
45647
+ ...renderOptions?.controlsHost !== void 0 && {
45648
+ controlsHost: renderOptions.controlsHost
45649
+ }
45243
45650
  };
45244
45651
  const legendState = {
45245
45652
  activeGroup: null,
@@ -45493,7 +45900,7 @@ var init_renderer15 = __esm({
45493
45900
  });
45494
45901
 
45495
45902
  // src/map/geo.ts
45496
- import { feature } from "topojson-client";
45903
+ import { feature, neighbors } from "topojson-client";
45497
45904
  import { geoBounds, geoArea } from "d3-geo";
45498
45905
  function geomObject(topo) {
45499
45906
  const key = Object.keys(topo.objects)[0];
@@ -45511,6 +45918,107 @@ function featureIndex(topo) {
45511
45918
  }
45512
45919
  return idx;
45513
45920
  }
45921
+ function buildAdjacency(topo) {
45922
+ const cached = adjacencyCache.get(topo);
45923
+ if (cached) return cached;
45924
+ const geometries = geomObject(topo).geometries;
45925
+ const nb = neighbors(geometries);
45926
+ const sets = /* @__PURE__ */ new Map();
45927
+ geometries.forEach((g, i) => {
45928
+ if (!g.type || g.type === "null") return;
45929
+ let set = sets.get(g.id);
45930
+ if (!set) {
45931
+ set = /* @__PURE__ */ new Set();
45932
+ sets.set(g.id, set);
45933
+ }
45934
+ for (const j of nb[i] ?? []) {
45935
+ const nid = geometries[j]?.id;
45936
+ if (nid && nid !== g.id) set.add(nid);
45937
+ }
45938
+ });
45939
+ const out = /* @__PURE__ */ new Map();
45940
+ for (const [iso, set] of sets) out.set(iso, [...set].sort());
45941
+ adjacencyCache.set(topo, out);
45942
+ return out;
45943
+ }
45944
+ function decodeFeatures(topo) {
45945
+ return geomObject(topo).geometries.map((g) => {
45946
+ const f = feature(topo, g);
45947
+ return {
45948
+ type: "Feature",
45949
+ id: g.id,
45950
+ properties: g.properties,
45951
+ geometry: f.geometry
45952
+ };
45953
+ });
45954
+ }
45955
+ function pointInRing(lon, lat, ring) {
45956
+ let inside = false;
45957
+ for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
45958
+ const xi = ring[i][0];
45959
+ const yi = ring[i][1];
45960
+ const xj = ring[j][0];
45961
+ const yj = ring[j][1];
45962
+ const intersect = yi > lat !== yj > lat && lon < (xj - xi) * (lat - yi) / (yj - yi) + xi;
45963
+ if (intersect) inside = !inside;
45964
+ }
45965
+ return inside;
45966
+ }
45967
+ function pointOnRingEdge(lon, lat, ring) {
45968
+ for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
45969
+ const xi = ring[i][0];
45970
+ const yi = ring[i][1];
45971
+ const xj = ring[j][0];
45972
+ const yj = ring[j][1];
45973
+ if (lon < Math.min(xi, xj) - EDGE_EPS || lon > Math.max(xi, xj) + EDGE_EPS)
45974
+ continue;
45975
+ if (lat < Math.min(yi, yj) - EDGE_EPS || lat > Math.max(yi, yj) + EDGE_EPS)
45976
+ continue;
45977
+ const cross = (xj - xi) * (lat - yi) - (yj - yi) * (lon - xi);
45978
+ if (Math.abs(cross) <= EDGE_EPS) return true;
45979
+ }
45980
+ return false;
45981
+ }
45982
+ function pointInGeometry(geometry, lon, lat) {
45983
+ const g = geometry;
45984
+ if (!g) return false;
45985
+ const polys = g.type === "Polygon" ? [g.coordinates] : g.type === "MultiPolygon" ? g.coordinates : [];
45986
+ for (const rings of polys) {
45987
+ if (!rings.length) continue;
45988
+ if (pointOnRingEdge(lon, lat, rings[0])) return true;
45989
+ if (!pointInRing(lon, lat, rings[0])) continue;
45990
+ let inHole = false;
45991
+ for (let h = 1; h < rings.length; h++) {
45992
+ if (pointInRing(lon, lat, rings[h]) && !pointOnRingEdge(lon, lat, rings[h])) {
45993
+ inHole = true;
45994
+ break;
45995
+ }
45996
+ }
45997
+ if (!inHole) return true;
45998
+ }
45999
+ return false;
46000
+ }
46001
+ function regionAt(lonLat, countries, states) {
46002
+ const lon = lonLat[0];
46003
+ const lat = lonLat[1];
46004
+ let country = null;
46005
+ for (const f of countries) {
46006
+ if (pointInGeometry(f.geometry, lon, lat)) {
46007
+ country = { iso: f.id, name: f.properties.name };
46008
+ break;
46009
+ }
46010
+ }
46011
+ let state = null;
46012
+ if (country?.iso === "US" && states) {
46013
+ for (const f of states) {
46014
+ if (pointInGeometry(f.geometry, lon, lat)) {
46015
+ state = { iso: f.id, name: f.properties.name };
46016
+ break;
46017
+ }
46018
+ }
46019
+ }
46020
+ return { country, state };
46021
+ }
45514
46022
  function featureBbox(topo, geomId) {
45515
46023
  const geom = geomObject(topo).geometries.find((g) => g.id === geomId);
45516
46024
  if (!geom) return null;
@@ -45628,11 +46136,13 @@ function unionLongitudes(lons) {
45628
46136
  }
45629
46137
  return { west: pts[gapIdx], east: pts[gapIdx - 1] + 360 };
45630
46138
  }
45631
- var fold, DETACH_GAP_DEG, DETACH_AREA_FRAC;
46139
+ var fold, adjacencyCache, EDGE_EPS, DETACH_GAP_DEG, DETACH_AREA_FRAC;
45632
46140
  var init_geo = __esm({
45633
46141
  "src/map/geo.ts"() {
45634
46142
  "use strict";
45635
46143
  fold = (s) => s.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLowerCase().trim();
46144
+ adjacencyCache = /* @__PURE__ */ new WeakMap();
46145
+ EDGE_EPS = 1e-9;
45636
46146
  DETACH_GAP_DEG = 10;
45637
46147
  DETACH_AREA_FRAC = 0.25;
45638
46148
  }
@@ -45652,6 +46162,12 @@ function looksUS(lat, lon) {
45652
46162
  if (lat < 15 || lat > 72) return false;
45653
46163
  return lon >= -180 && lon <= -64 || lon >= 172;
45654
46164
  }
46165
+ function looksNorthAmericaNeighbor(lat, lon) {
46166
+ return lat >= 14 && lat <= 72 && lon >= -141 && lon <= -52;
46167
+ }
46168
+ function isWholeSphere(bb) {
46169
+ return bb[0][0] <= -179 && bb[1][0] >= 179 && bb[0][1] <= -89 && bb[1][1] >= 89;
46170
+ }
45655
46171
  function resolveMap(parsed, data) {
45656
46172
  const diagnostics = [...parsed.diagnostics];
45657
46173
  const err = (line12, message, code) => {
@@ -45662,9 +46178,6 @@ function resolveMap(parsed, data) {
45662
46178
  };
45663
46179
  const result = {
45664
46180
  title: parsed.title,
45665
- ...parsed.directives.subtitle !== void 0 && {
45666
- subtitle: parsed.directives.subtitle
45667
- },
45668
46181
  ...parsed.directives.caption !== void 0 && {
45669
46182
  caption: parsed.directives.caption
45670
46183
  },
@@ -45674,7 +46187,7 @@ function resolveMap(parsed, data) {
45674
46187
  // renderer's job (step 4) — the resolver only carries `tags` + `tagGroups`
45675
46188
  // through; it never resolves a tag value to a palette color (#10).
45676
46189
  directives: { ...parsed.directives },
45677
- basemaps: { world: "coarse", subdivisions: [] },
46190
+ basemaps: { world: "detail", subdivisions: [] },
45678
46191
  regions: [],
45679
46192
  pois: [],
45680
46193
  edges: [],
@@ -45683,7 +46196,8 @@ function resolveMap(parsed, data) {
45683
46196
  [-180, -85],
45684
46197
  [180, 85]
45685
46198
  ],
45686
- projection: "natural-earth",
46199
+ projection: "equirectangular",
46200
+ poiFrameContainers: [],
45687
46201
  diagnostics,
45688
46202
  error: parsed.error
45689
46203
  };
@@ -45693,7 +46207,10 @@ function resolveMap(parsed, data) {
45693
46207
  ...[...countryIndex.values()].map((v) => v.name),
45694
46208
  ...[...usStateIndex.values()].map((v) => v.name)
45695
46209
  ];
45696
- const usScoped = parsed.directives.region === "us-states" || parsed.directives.defaultCountry?.toUpperCase() === "US" || parsed.regions.some((r) => {
46210
+ const localeRaw = parsed.directives.locale?.toUpperCase();
46211
+ const localeCountry = localeRaw ? localeRaw.split("-")[0] : void 0;
46212
+ const localeSubdivision = localeRaw && /^[A-Z]{2}-/.test(localeRaw) ? localeRaw : void 0;
46213
+ const usScoped = localeCountry === "US" || parsed.regions.some((r) => {
45697
46214
  const f = fold(r.name);
45698
46215
  return usStateIndex.has(f) && !countryIndex.has(f);
45699
46216
  }) || parsed.regions.some(
@@ -45844,7 +46361,7 @@ function resolveMap(parsed, data) {
45844
46361
  if (!scope)
45845
46362
  warn2(
45846
46363
  line12,
45847
- `"${name}" is ambiguous \u2014 resolved to the most-populous match.`,
46364
+ `"${name}" is ambiguous \u2014 resolved to the most-populous match. Set a default with \`locale <ISO>\` (e.g. \`locale US\` / \`locale US-GA\`) to steer it.`,
45848
46365
  "W_MAP_AMBIGUOUS_NAME"
45849
46366
  );
45850
46367
  }
@@ -45857,17 +46374,21 @@ function resolveMap(parsed, data) {
45857
46374
  return fold(pos.name);
45858
46375
  };
45859
46376
  const poiCountries = [];
45860
- let anyNonUsPoi = false;
46377
+ let anyUsPoi = false;
46378
+ let anyNonNaPoi = false;
45861
46379
  const noteCountry = (iso) => {
45862
46380
  if (iso) {
45863
46381
  poiCountries.push(iso);
45864
- if (iso !== "US") anyNonUsPoi = true;
46382
+ if (iso === "US") anyUsPoi = true;
46383
+ if (iso !== "US" && iso !== "CA" && iso !== "MX") anyNonNaPoi = true;
45865
46384
  }
45866
46385
  };
45867
46386
  const deferred = [];
45868
46387
  for (const p of parsed.pois) {
45869
46388
  if (p.pos.kind === "coords") {
45870
- if (!looksUS(p.pos.lat, p.pos.lon)) anyNonUsPoi = true;
46389
+ if (looksUS(p.pos.lat, p.pos.lon)) anyUsPoi = true;
46390
+ else if (!looksNorthAmericaNeighbor(p.pos.lat, p.pos.lon))
46391
+ anyNonNaPoi = true;
45871
46392
  addResolvedPoi(p.pos.lat, p.pos.lon, p);
45872
46393
  continue;
45873
46394
  }
@@ -45885,14 +46406,15 @@ function resolveMap(parsed, data) {
45885
46406
  deferred.push(p);
45886
46407
  }
45887
46408
  }
45888
- const inferredCountry = parsed.directives.defaultCountry?.toUpperCase() ?? mostCommonCountry(regions, poiCountries) ?? void 0;
46409
+ const inferredCountry = localeCountry ?? mostCommonCountry(regions, poiCountries) ?? void 0;
46410
+ const inferredScope = localeSubdivision ?? inferredCountry;
45889
46411
  for (const p of deferred) {
45890
46412
  if (p.pos.kind !== "name") continue;
45891
46413
  const got = lookupName(
45892
46414
  p.pos.name,
45893
46415
  p.pos.scope,
45894
46416
  p.lineNumber,
45895
- inferredCountry,
46417
+ inferredScope,
45896
46418
  true
45897
46419
  );
45898
46420
  if (got.kind === "ok") {
@@ -45962,7 +46484,8 @@ function resolveMap(parsed, data) {
45962
46484
  const meta = sizeValue !== void 0 ? { value: sizeValue } : {};
45963
46485
  if (pos.kind === "coords") {
45964
46486
  const id = alias ? fold(alias) : `@${pos.lat},${pos.lon}`;
45965
- if (!looksUS(pos.lat, pos.lon)) anyNonUsPoi = true;
46487
+ if (looksUS(pos.lat, pos.lon)) anyUsPoi = true;
46488
+ else if (!looksNorthAmericaNeighbor(pos.lat, pos.lon)) anyNonNaPoi = true;
45966
46489
  if (!registry.has(id)) {
45967
46490
  registerPoi(
45968
46491
  id,
@@ -45985,7 +46508,7 @@ function resolveMap(parsed, data) {
45985
46508
  if (registry.has(f)) return f;
45986
46509
  const aliased = declaredByName.get(f);
45987
46510
  if (aliased) return aliased;
45988
- const got = lookupName(pos.name, pos.scope, line12, inferredCountry, true);
46511
+ const got = lookupName(pos.name, pos.scope, line12, inferredScope, true);
45989
46512
  if (got.kind !== "ok") return null;
45990
46513
  noteCountry(got.iso);
45991
46514
  registerPoi(
@@ -46042,9 +46565,12 @@ function resolveMap(parsed, data) {
46042
46565
  }
46043
46566
  routes.push({ stopIds, legs, lineNumber: rt.lineNumber });
46044
46567
  }
46568
+ const hasUsContent = usSubdivisionReferenced || anyUsPoi || localeCountry === "US";
46569
+ const usOriented = !anyNonNaPoi && !regions.some(
46570
+ (r) => r.layer === "country" && !["US", "CA", "MX"].includes(r.iso)
46571
+ ) && hasUsContent;
46045
46572
  const subdivisions = [];
46046
- if (usSubdivisionReferenced || parsed.directives.region === "us-states")
46047
- subdivisions.push("us-states");
46573
+ if (usSubdivisionReferenced || usOriented) subdivisions.push("us-states");
46048
46574
  const regionBoxes = [];
46049
46575
  for (const ref of referencedRegionIds) {
46050
46576
  const bb = featureBbox(data.usStates, ref.id);
@@ -46062,17 +46588,51 @@ function resolveMap(parsed, data) {
46062
46588
  [-180, -85],
46063
46589
  [180, 85]
46064
46590
  ];
46065
- let extent2 = unioned ? pad(unioned, PAD_FRACTION) : DEFAULT_EXTENT;
46591
+ const basePad = regions.length > 0 ? REGION_PAD_FRACTION : PAD_FRACTION;
46592
+ let extent2 = unioned ? pad(unioned, basePad) : DEFAULT_EXTENT;
46593
+ const isPoiOnly = pois.length > 0 && regions.length === 0;
46594
+ const containerRegionIds = [];
46595
+ if (isPoiOnly) {
46596
+ const countries = decodeFeatures(data.worldDetail);
46597
+ const states = decodeFeatures(data.usStates);
46598
+ const seen = /* @__PURE__ */ new Set();
46599
+ const containerBoxes = [];
46600
+ for (const p of pois) {
46601
+ const { country, state } = regionAt([p.lon, p.lat], countries, states);
46602
+ const id = state?.iso ?? country?.iso;
46603
+ if (!id || seen.has(id)) continue;
46604
+ seen.add(id);
46605
+ containerRegionIds.push(id);
46606
+ const bb = state ? featureBbox(data.usStates, id) : featureBboxPrimary(data.worldCoarse, id);
46607
+ if (bb && !isWholeSphere(bb)) containerBoxes.push(bb);
46608
+ }
46609
+ const containerUnion = unionExtent(containerBoxes, points);
46610
+ if (containerUnion) extent2 = pad(containerUnion, PAD_FRACTION);
46611
+ }
46612
+ if (isPoiOnly) {
46613
+ const cx = (extent2[0][0] + extent2[1][0]) / 2;
46614
+ const cy = (extent2[0][1] + extent2[1][1]) / 2;
46615
+ const lon = extent2[1][0] - extent2[0][0];
46616
+ const lat = extent2[1][1] - extent2[0][1];
46617
+ const longer = Math.max(lon, lat);
46618
+ if (longer > 0 && longer < POI_ZOOM_FLOOR_DEG) {
46619
+ const k = POI_ZOOM_FLOOR_DEG / longer;
46620
+ const halfLon = lon * k / 2;
46621
+ const halfLat = lat * k / 2;
46622
+ extent2 = [
46623
+ [cx - halfLon, cy - halfLat],
46624
+ [cx + halfLon, cy + halfLat]
46625
+ ];
46626
+ }
46627
+ }
46066
46628
  const lonSpan = extent2[1][0] - extent2[0][0];
46067
46629
  const latSpan = extent2[1][1] - extent2[0][1];
46068
46630
  const span = Math.max(lonSpan, latSpan);
46069
46631
  const maxAbsLat = Math.max(Math.abs(extent2[0][1]), Math.abs(extent2[1][1]));
46070
- const usDominant = (subdivisions.includes("us-states") || regions.some((r) => r.layer === "us-state")) && !regions.some((r) => r.layer === "country" && r.iso !== "US") && !anyNonUsPoi;
46071
46632
  let projection;
46072
- const override = parsed.directives.projection;
46073
- if (override === "equirectangular" || override === "natural-earth" || override === "albers-usa" || override === "mercator") {
46074
- projection = override;
46075
- } else if (usDominant) {
46633
+ if (isPoiOnly && usOriented && lonSpan < US_NATIONAL_LON_SPAN) {
46634
+ projection = "mercator";
46635
+ } else if (usOriented) {
46076
46636
  projection = "albers-usa";
46077
46637
  } else if (span > WORLD_SPAN || maxAbsLat > MERCATOR_MAX_LAT) {
46078
46638
  projection = "equirectangular";
@@ -46090,11 +46650,20 @@ function resolveMap(parsed, data) {
46090
46650
  result.edges = edges;
46091
46651
  result.routes = routes;
46092
46652
  result.basemaps = {
46093
- world: span > WORLD_SPAN ? "coarse" : "detail",
46653
+ // Tier is intentionally pinned to detail (50m) at ALL scales. Diagrammo maps
46654
+ // are presentational (palette tints, relief hachures, POI hubs), not
46655
+ // survey-grade — recognizability > generalization: 110m coarse drops the
46656
+ // Italian boot to a stump at world scale. `WORLD_SPAN` lives on only for the
46657
+ // projection decision (the `usOriented`/`span > WORLD_SPAN` chain above); it
46658
+ // no longer gates basemap resolution.
46659
+ // `worldCoarse` is still loaded — it's the authoritative name/bbox index
46660
+ // (featureIndex, featureBboxPrimary), not dead code.
46661
+ world: "detail",
46094
46662
  subdivisions
46095
46663
  };
46096
46664
  result.extent = extent2;
46097
46665
  result.projection = projection;
46666
+ result.poiFrameContainers = containerRegionIds;
46098
46667
  result.error = parsed.error ?? firstError(diagnostics);
46099
46668
  return result;
46100
46669
  }
@@ -46131,7 +46700,7 @@ function firstError(diags) {
46131
46700
  const e = diags.find((d) => d.severity === "error");
46132
46701
  return e ? formatDgmoError(e) : null;
46133
46702
  }
46134
- var WORLD_SPAN, MERCATOR_MAX_LAT, PAD_FRACTION, WORLD_LAT_SOUTH, WORLD_LAT_NORTH, REGION_ALIASES, US_STATE_POSTAL;
46703
+ var WORLD_SPAN, MERCATOR_MAX_LAT, PAD_FRACTION, REGION_PAD_FRACTION, WORLD_LAT_SOUTH, WORLD_LAT_NORTH, POI_ZOOM_FLOOR_DEG, US_NATIONAL_LON_SPAN, REGION_ALIASES, US_STATE_POSTAL;
46135
46704
  var init_resolver2 = __esm({
46136
46705
  "src/map/resolver.ts"() {
46137
46706
  "use strict";
@@ -46140,8 +46709,11 @@ var init_resolver2 = __esm({
46140
46709
  WORLD_SPAN = 90;
46141
46710
  MERCATOR_MAX_LAT = 80;
46142
46711
  PAD_FRACTION = 0.05;
46712
+ REGION_PAD_FRACTION = 0.12;
46143
46713
  WORLD_LAT_SOUTH = -58;
46144
46714
  WORLD_LAT_NORTH = 78;
46715
+ POI_ZOOM_FLOOR_DEG = 7;
46716
+ US_NATIONAL_LON_SPAN = 48;
46145
46717
  REGION_ALIASES = {
46146
46718
  // Common everyday names → the Natural-Earth display name actually shipped.
46147
46719
  "united states": "united states of america",
@@ -46219,10 +46791,277 @@ var init_resolver2 = __esm({
46219
46791
  }
46220
46792
  });
46221
46793
 
46794
+ // src/map/colorize.ts
46795
+ function assignColors(isos, adjacency) {
46796
+ const sorted = [...isos].sort();
46797
+ const byIso = /* @__PURE__ */ new Map();
46798
+ let maxIndex = -1;
46799
+ for (const iso of sorted) {
46800
+ const taken = /* @__PURE__ */ new Set();
46801
+ for (const n of adjacency.get(iso) ?? []) {
46802
+ const c = byIso.get(n);
46803
+ if (c !== void 0) taken.add(c);
46804
+ }
46805
+ let h = 0;
46806
+ while (taken.has(h)) h++;
46807
+ byIso.set(iso, h);
46808
+ if (h > maxIndex) maxIndex = h;
46809
+ }
46810
+ return { byIso, huesNeeded: maxIndex + 1 };
46811
+ }
46812
+ var init_colorize = __esm({
46813
+ "src/map/colorize.ts"() {
46814
+ "use strict";
46815
+ }
46816
+ });
46817
+
46818
+ // src/map/context-labels.ts
46819
+ function tierBand(maxSpanDeg) {
46820
+ if (maxSpanDeg >= 90) return "world";
46821
+ if (maxSpanDeg >= 20) return "continental";
46822
+ if (maxSpanDeg >= 5) return "regional";
46823
+ return "local";
46824
+ }
46825
+ function labelBudget(width, height, band) {
46826
+ const bandCap = {
46827
+ world: 6,
46828
+ continental: 5,
46829
+ regional: 4,
46830
+ local: 3
46831
+ };
46832
+ const area2 = Math.floor(Math.sqrt(Math.max(0, width * height)) / 150);
46833
+ return Math.max(0, Math.min(area2, bandCap[band]));
46834
+ }
46835
+ function waterEligible(tier, kind, band) {
46836
+ switch (band) {
46837
+ case "world":
46838
+ return tier <= 1 && (kind === "ocean" || kind === "sea");
46839
+ case "continental":
46840
+ return tier <= 2;
46841
+ case "regional":
46842
+ return tier <= 3;
46843
+ case "local":
46844
+ return tier <= 4;
46845
+ }
46846
+ }
46847
+ function insideViewport(p, width, height) {
46848
+ return !!p && Number.isFinite(p[0]) && Number.isFinite(p[1]) && p[0] >= 0 && p[0] <= width && p[1] >= 0 && p[1] <= height;
46849
+ }
46850
+ function labelWidth(text, letterSpacing) {
46851
+ const spacing = letterSpacing > 0 ? Math.max(0, text.length - 1) * letterSpacing : 0;
46852
+ return measureLegendText(text, FONT) + spacing + 2 * PADX;
46853
+ }
46854
+ function wrapLabel2(text, letterSpacing) {
46855
+ const words = text.split(/\s+/).filter(Boolean);
46856
+ if (words.length <= 1) return [text];
46857
+ const maxLines = words.length >= 4 ? 3 : 2;
46858
+ const n = words.length;
46859
+ let best = null;
46860
+ for (let mask = 0; mask < 1 << n - 1; mask++) {
46861
+ const lines = [];
46862
+ let cur = [words[0]];
46863
+ for (let i = 1; i < n; i++) {
46864
+ if (mask & 1 << i - 1) {
46865
+ lines.push(cur.join(" "));
46866
+ cur = [words[i]];
46867
+ } else cur.push(words[i]);
46868
+ }
46869
+ lines.push(cur.join(" "));
46870
+ if (lines.length > maxLines) continue;
46871
+ const cost = Math.round(
46872
+ Math.max(...lines.map((l) => labelWidth(l, letterSpacing)))
46873
+ );
46874
+ const head = labelWidth(lines[0], letterSpacing);
46875
+ if (!best || cost < best.cost || cost === best.cost && lines.length < best.lines.length || cost === best.cost && lines.length === best.lines.length && head > best.head)
46876
+ best = { lines, cost, head };
46877
+ }
46878
+ return best?.lines ?? [text];
46879
+ }
46880
+ function rectAround(cx, cy, lines, letterSpacing) {
46881
+ const w = Math.max(...lines.map((l) => labelWidth(l, letterSpacing)));
46882
+ const h = (lines.length - 1) * LINE_HEIGHT + FONT + 2 * PADY;
46883
+ return { x: cx - w / 2, y: cy - h / 2, w, h };
46884
+ }
46885
+ function rectFits(r, width, height) {
46886
+ return r.x >= 0 && r.y >= 0 && r.x + r.w <= width && r.y + r.h <= height;
46887
+ }
46888
+ function overlapsPadded(a, b, pad2) {
46889
+ return a.x - pad2 < b.x + b.w && a.x + a.w + pad2 > b.x && a.y - pad2 < b.y + b.h && a.y + a.h + pad2 > b.y;
46890
+ }
46891
+ function placeContextLabels(args) {
46892
+ const {
46893
+ projection,
46894
+ dLonSpan,
46895
+ dLatSpan,
46896
+ width,
46897
+ height,
46898
+ waterBodies,
46899
+ countries,
46900
+ palette,
46901
+ project,
46902
+ collides,
46903
+ overLand
46904
+ } = args;
46905
+ void projection;
46906
+ const band = tierBand(Math.max(dLonSpan, dLatSpan));
46907
+ const budget = labelBudget(width, height, band);
46908
+ if (budget <= 0) return [];
46909
+ const waterColor = mix(palette.colors.blue, palette.textMuted, 50);
46910
+ const countryColor = palette.textMuted;
46911
+ const haloColor = palette.bg;
46912
+ const candidates = [];
46913
+ const center = [width / 2, height / 2];
46914
+ for (const e of waterBodies?.entries ?? []) {
46915
+ const [lat, lon, name, tier, kind, alt] = e;
46916
+ if (!waterEligible(tier, kind, band)) continue;
46917
+ const wlines = wrapLabel2(name, WATER_LETTER_SPACING);
46918
+ const anchorsLngLat = [[lon, lat]];
46919
+ for (const a of alt ?? []) anchorsLngLat.push([a[1], a[0]]);
46920
+ let best = null;
46921
+ let bestD = Infinity;
46922
+ let nearestProj = null;
46923
+ let nearestProjD = Infinity;
46924
+ for (const [aLon, aLat] of anchorsLngLat) {
46925
+ const p = project(aLon, aLat);
46926
+ if (!p || !Number.isFinite(p[0]) || !Number.isFinite(p[1])) continue;
46927
+ const d = (p[0] - center[0]) ** 2 + (p[1] - center[1]) ** 2;
46928
+ if (d < nearestProjD) {
46929
+ nearestProjD = d;
46930
+ nearestProj = p;
46931
+ }
46932
+ if (!insideViewport(p, width, height)) continue;
46933
+ if (d < bestD) {
46934
+ bestD = d;
46935
+ best = p;
46936
+ }
46937
+ }
46938
+ if (!best && tier === 0 && nearestProj) {
46939
+ const overX = Math.max(0, -nearestProj[0], nearestProj[0] - width);
46940
+ const overY = Math.max(0, -nearestProj[1], nearestProj[1] - height);
46941
+ if (overX <= width * EDGE_CLAMP_OVERSHOOT && overY <= height * EDGE_CLAMP_OVERSHOOT) {
46942
+ const halfW = Math.max(...wlines.map((l) => labelWidth(l, WATER_LETTER_SPACING))) / 2;
46943
+ const halfH = ((wlines.length - 1) * LINE_HEIGHT + FONT + 2 * PADY) / 2;
46944
+ const m = EDGE_CLAMP_MARGIN;
46945
+ best = [
46946
+ Math.min(Math.max(nearestProj[0], halfW + m), width - halfW - m),
46947
+ Math.min(Math.max(nearestProj[1], halfH + m), height - halfH - m)
46948
+ ];
46949
+ }
46950
+ }
46951
+ if (!best) continue;
46952
+ candidates.push({
46953
+ text: name,
46954
+ lines: wlines,
46955
+ cx: best[0],
46956
+ cy: best[1],
46957
+ italic: true,
46958
+ letterSpacing: WATER_LETTER_SPACING,
46959
+ color: waterColor,
46960
+ // Water before any country (×1000), then by tier, then kind, then name.
46961
+ sort: tier * 10 + KIND_ORDER[kind]
46962
+ });
46963
+ }
46964
+ const ranked = countries.map((c) => {
46965
+ const [x0, y0, x1, y1] = c.bbox;
46966
+ const w = x1 - x0;
46967
+ const h = y1 - y0;
46968
+ return { c, w, h, area: w * h };
46969
+ }).filter((r) => Number.isFinite(r.area) && r.area > 0).sort((a, b) => b.area - a.area);
46970
+ let ci = 0;
46971
+ for (const r of ranked) {
46972
+ const { c, w, h } = r;
46973
+ if (w > width * 0.66 || h > height * 0.66) continue;
46974
+ if (!insideViewport(c.anchor, width, height)) continue;
46975
+ const text = c.name;
46976
+ const tw = labelWidth(text, 0);
46977
+ if (tw > w || FONT + 2 * PADY > h) continue;
46978
+ candidates.push({
46979
+ text,
46980
+ lines: [text],
46981
+ cx: c.anchor[0],
46982
+ cy: c.anchor[1],
46983
+ italic: false,
46984
+ letterSpacing: 0,
46985
+ color: countryColor,
46986
+ // Always after every water body (+1e6); larger area = earlier.
46987
+ sort: 1e6 + ci++
46988
+ });
46989
+ }
46990
+ candidates.sort((a, b) => a.sort - b.sort);
46991
+ const placed = [];
46992
+ const placedRects = [];
46993
+ for (const cand of candidates) {
46994
+ if (placed.length >= budget) break;
46995
+ const rect = rectAround(cand.cx, cand.cy, cand.lines, cand.letterSpacing);
46996
+ if (!rectFits(rect, width, height)) continue;
46997
+ if (cand.italic && overLand) {
46998
+ const inset = 2;
46999
+ const top = cand.cy - (cand.lines.length - 1) / 2 * LINE_HEIGHT;
47000
+ const touchesLand = cand.lines.some((line12, li) => {
47001
+ const lw = labelWidth(line12, cand.letterSpacing);
47002
+ const x0 = cand.cx - lw / 2 + inset;
47003
+ const x1 = cand.cx + lw / 2 - inset;
47004
+ const xs = [x0, (x0 + cand.cx) / 2, cand.cx, (cand.cx + x1) / 2, x1];
47005
+ const base = top + li * LINE_HEIGHT;
47006
+ return [base, base - FONT * 0.4, base - FONT * 0.8].some(
47007
+ (y) => xs.some((x) => overLand(x, y))
47008
+ );
47009
+ });
47010
+ if (touchesLand) continue;
47011
+ }
47012
+ if (collides(rect)) continue;
47013
+ if (placedRects.some((r) => overlapsPadded(rect, r, CONTEXT_PAD))) continue;
47014
+ placedRects.push(rect);
47015
+ placed.push({
47016
+ x: cand.cx,
47017
+ y: cand.cy,
47018
+ text: cand.text,
47019
+ anchor: "middle",
47020
+ color: cand.color,
47021
+ // No halo: the bg-coloured outline reads as a ghost box behind the text
47022
+ // over the tinted water/land. Context labels are muted enough to sit
47023
+ // cleanly on the basemap without one.
47024
+ halo: false,
47025
+ haloColor,
47026
+ italic: cand.italic,
47027
+ letterSpacing: cand.letterSpacing,
47028
+ ...cand.lines.length > 1 ? { lines: cand.lines } : {},
47029
+ lineNumber: 0
47030
+ });
47031
+ }
47032
+ return placed;
47033
+ }
47034
+ var FONT, LINE_HEIGHT, PADX, PADY, WATER_LETTER_SPACING, CONTEXT_PAD, EDGE_CLAMP_MARGIN, EDGE_CLAMP_OVERSHOOT, KIND_ORDER;
47035
+ var init_context_labels = __esm({
47036
+ "src/map/context-labels.ts"() {
47037
+ "use strict";
47038
+ init_color_utils();
47039
+ init_legend_constants();
47040
+ FONT = 11;
47041
+ LINE_HEIGHT = FONT + 2;
47042
+ PADX = 4;
47043
+ PADY = 3;
47044
+ WATER_LETTER_SPACING = 1.5;
47045
+ CONTEXT_PAD = 4;
47046
+ EDGE_CLAMP_MARGIN = 8;
47047
+ EDGE_CLAMP_OVERSHOOT = 0.35;
47048
+ KIND_ORDER = {
47049
+ ocean: 0,
47050
+ sea: 1,
47051
+ gulf: 2,
47052
+ bay: 3,
47053
+ strait: 4,
47054
+ channel: 5,
47055
+ sound: 6
47056
+ };
47057
+ }
47058
+ });
47059
+
46222
47060
  // src/map/layout.ts
46223
47061
  import {
46224
47062
  geoPath,
46225
47063
  geoNaturalEarth1,
47064
+ geoEqualEarth,
46226
47065
  geoEquirectangular,
46227
47066
  geoConicEqualArea,
46228
47067
  geoMercator,
@@ -46234,12 +47073,34 @@ function geomObject2(topo) {
46234
47073
  const key = Object.keys(topo.objects)[0];
46235
47074
  return topo.objects[key];
46236
47075
  }
47076
+ function mergeFeatures(a, b) {
47077
+ const polysOf = (f) => {
47078
+ const g = f.geometry;
47079
+ if (!g) return null;
47080
+ if (g.type === "Polygon") return [g.coordinates];
47081
+ if (g.type === "MultiPolygon") return g.coordinates;
47082
+ return null;
47083
+ };
47084
+ const pa = polysOf(a);
47085
+ const pb = polysOf(b);
47086
+ if (!pa || !pb) return a;
47087
+ return {
47088
+ ...a,
47089
+ geometry: { type: "MultiPolygon", coordinates: [...pa, ...pb] }
47090
+ };
47091
+ }
46237
47092
  function decodeLayer(topo) {
47093
+ const cached = decodeCache.get(topo);
47094
+ if (cached) return cached;
46238
47095
  const out = /* @__PURE__ */ new Map();
46239
47096
  for (const g of geomObject2(topo).geometries) {
46240
47097
  const f = feature2(topo, g);
46241
- out.set(g.id, { ...f, id: g.id });
47098
+ if (!f.geometry) continue;
47099
+ const tagged = { ...f, id: g.id };
47100
+ const existing = out.get(g.id);
47101
+ out.set(g.id, existing ? mergeFeatures(existing, tagged) : tagged);
46242
47102
  }
47103
+ decodeCache.set(topo, out);
46243
47104
  return out;
46244
47105
  }
46245
47106
  function projectionFor(family) {
@@ -46248,9 +47109,12 @@ function projectionFor(family) {
46248
47109
  return usConusProjection();
46249
47110
  case "mercator":
46250
47111
  return geoMercator();
47112
+ case "equal-earth":
47113
+ return geoEqualEarth();
47114
+ case "equirectangular":
47115
+ return geoEquirectangular();
46251
47116
  case "natural-earth":
46252
47117
  return geoNaturalEarth1();
46253
- case "equirectangular":
46254
47118
  default:
46255
47119
  return geoEquirectangular();
46256
47120
  }
@@ -46269,13 +47133,11 @@ function mapNeutralLandColor(palette, isDark, _dataActive = false) {
46269
47133
  isDark ? LAND_TINT_DARK : LAND_TINT_LIGHT
46270
47134
  );
46271
47135
  }
46272
- function layoutMap(resolved, data, size, opts) {
46273
- const { palette, isDark } = opts;
46274
- const { width, height } = size;
47136
+ function buildMapProjection(resolved, data) {
46275
47137
  const wantsUsStates = resolved.basemaps.subdivisions.includes("us-states");
46276
- const usCrisp = resolved.projection === "albers-usa" && wantsUsStates && !!data.naLand;
47138
+ const usCrisp = (resolved.projection === "albers-usa" || resolved.projection === "mercator") && wantsUsStates && !!data.naLand;
46277
47139
  const worldTopo = usCrisp ? data.worldDetail : resolved.basemaps.world === "detail" ? data.worldDetail : data.worldCoarse;
46278
- const worldLayer = decodeLayer(worldTopo);
47140
+ const worldLayer = new Map(decodeLayer(worldTopo));
46279
47141
  if (usCrisp && data.naLand) {
46280
47142
  const [nbW, nbS, nbE, nbN] = [-140, 10, -52, 66];
46281
47143
  const crisp = decodeLayer(data.naLand);
@@ -46284,16 +47146,109 @@ function layoutMap(resolved, data, size, opts) {
46284
47146
  if (!base) continue;
46285
47147
  const [[bw, bs], [be, bn]] = geoBounds2(base);
46286
47148
  if (bw >= nbW && be <= nbE && bs >= nbS && bn <= nbN)
46287
- worldLayer.set(iso, cf);
47149
+ worldLayer.set(iso, { ...cf, properties: base.properties });
46288
47150
  }
46289
47151
  }
46290
47152
  const usLayer = wantsUsStates ? decodeLayer(data.usStates) : null;
47153
+ const extentOutline = () => {
47154
+ const [[w, s], [e, n]] = resolved.extent;
47155
+ const N = 16;
47156
+ const coords = [];
47157
+ for (let i = 0; i <= N; i++) {
47158
+ const t = i / N;
47159
+ const lon = w + (e - w) * t;
47160
+ const lat = s + (n - s) * t;
47161
+ coords.push([lon, s], [lon, n], [w, lat], [e, lat]);
47162
+ }
47163
+ return {
47164
+ type: "Feature",
47165
+ properties: {},
47166
+ geometry: { type: "MultiPoint", coordinates: coords }
47167
+ };
47168
+ };
47169
+ let fitFeatures;
47170
+ if (resolved.projection === "albers-usa" && usLayer) {
47171
+ fitFeatures = [...usLayer.entries()].filter(([iso]) => !US_NON_CONUS.has(iso)).map(([, f]) => f);
47172
+ const neighborPoints = resolved.pois.filter((p) => !inAlaska(p.lon, p.lat) && !inHawaii(p.lon, p.lat)).map((p) => [p.lon, p.lat]);
47173
+ if (neighborPoints.length > 0) {
47174
+ fitFeatures.push({
47175
+ type: "Feature",
47176
+ properties: {},
47177
+ geometry: { type: "MultiPoint", coordinates: neighborPoints }
47178
+ });
47179
+ }
47180
+ for (const r of resolved.regions) {
47181
+ if (r.layer === "country" && (r.iso === "CA" || r.iso === "MX")) {
47182
+ const cf = worldLayer.get(r.iso);
47183
+ if (cf) fitFeatures.push(cf);
47184
+ }
47185
+ }
47186
+ } else {
47187
+ fitFeatures = [extentOutline()];
47188
+ }
47189
+ const fitTarget = { type: "FeatureCollection", features: fitFeatures };
47190
+ const projection = projectionFor(resolved.projection);
47191
+ if (resolved.projection !== "albers-usa") {
47192
+ let centerLon = (resolved.extent[0][0] + resolved.extent[1][0]) / 2;
47193
+ if (centerLon > 180) centerLon -= 360;
47194
+ projection.rotate([-centerLon, 0]);
47195
+ }
47196
+ const fitGB = geoBounds2(fitTarget);
47197
+ const fitIsGlobal = fitGB[1][0] - fitGB[0][0] >= 270 || fitGB[1][1] - fitGB[0][1] >= 130;
47198
+ return {
47199
+ projection,
47200
+ fitTarget,
47201
+ fitIsGlobal,
47202
+ worldLayer,
47203
+ usLayer,
47204
+ usCrisp,
47205
+ wantsUsStates,
47206
+ worldTopo
47207
+ };
47208
+ }
47209
+ function parsePathRings(d) {
47210
+ const rings = [];
47211
+ let cur = [];
47212
+ const re = /([MLZ])([^MLZ]*)/g;
47213
+ let m;
47214
+ while (m = re.exec(d)) {
47215
+ if (m[1] === "Z") {
47216
+ if (cur.length) rings.push(cur);
47217
+ cur = [];
47218
+ continue;
47219
+ }
47220
+ if (m[1] === "M" && cur.length) {
47221
+ rings.push(cur);
47222
+ cur = [];
47223
+ }
47224
+ const nums = m[2].split(/[ ,]+/).map(Number);
47225
+ for (let i = 0; i + 1 < nums.length; i += 2) {
47226
+ const x = nums[i];
47227
+ const y = nums[i + 1];
47228
+ if (Number.isFinite(x) && Number.isFinite(y)) cur.push([x, y]);
47229
+ }
47230
+ }
47231
+ if (cur.length) rings.push(cur);
47232
+ return rings;
47233
+ }
47234
+ function layoutMap(resolved, data, size, opts) {
47235
+ const { palette, isDark } = opts;
47236
+ const { width, height } = size;
47237
+ const {
47238
+ projection,
47239
+ fitTarget,
47240
+ fitIsGlobal,
47241
+ worldLayer,
47242
+ usLayer,
47243
+ usCrisp,
47244
+ worldTopo
47245
+ } = buildMapProjection(resolved, data);
46291
47246
  const usContext = usLayer !== null;
46292
47247
  const regionStroke = isDark ? mix(palette.bg, palette.text, 78) : mix(palette.text, palette.bg, 78);
46293
47248
  const values = resolved.regions.filter((r) => r.value !== void 0).map((r) => r.value);
46294
- const scaleOverride = resolved.directives.scale;
46295
- const rampMin = scaleOverride ? scaleOverride.min : Math.min(...values);
46296
- const rampMax = scaleOverride ? scaleOverride.max : Math.max(...values);
47249
+ const allNonNegative = values.length > 0 && values.every((v) => v >= 0);
47250
+ const rampMin = allNonNegative ? 0 : Math.min(...values);
47251
+ const rampMax = Math.max(...values);
46297
47252
  const rampHue = resolveColor(resolved.directives.regionMetricColor ?? "", palette) ?? palette.colors.red;
46298
47253
  const hasRamp = values.length > 0;
46299
47254
  const VALUE_NAME = hasRamp ? resolved.directives.regionMetric?.trim() || "Value" : null;
@@ -46314,7 +47269,7 @@ function layoutMap(resolved, data, size, opts) {
46314
47269
  activeGroup = VALUE_NAME ?? (resolved.tagGroups.length > 0 ? resolved.tagGroups[0].name : null);
46315
47270
  }
46316
47271
  const activeIsScore = VALUE_NAME !== null && activeGroup === VALUE_NAME;
46317
- const mutedBasemap = resolved.directives.basemapStyle === "muted" ? true : resolved.directives.basemapStyle === "natural" ? false : activeGroup !== null;
47272
+ const mutedBasemap = activeGroup !== null;
46318
47273
  const neutralFill = mapNeutralLandColor(palette, isDark, mutedBasemap);
46319
47274
  const water = mapBackgroundColor(palette, isDark, mutedBasemap);
46320
47275
  const lakeStroke = mix(regionStroke, water, 45);
@@ -46323,6 +47278,39 @@ function layoutMap(resolved, data, size, opts) {
46323
47278
  palette.bg,
46324
47279
  mutedBasemap ? isDark ? MUTED_FOREIGN_DARK : MUTED_FOREIGN_LIGHT : isDark ? FOREIGN_TINT_DARK : FOREIGN_TINT_LIGHT
46325
47280
  );
47281
+ const colorizeActive = resolved.directives.noColorize !== true && !hasRamp && resolved.tagGroups.length === 0;
47282
+ const colorByIso = /* @__PURE__ */ new Map();
47283
+ if (colorizeActive) {
47284
+ const adjacency = /* @__PURE__ */ new Map();
47285
+ const addEdges = (src) => {
47286
+ for (const [iso, ns] of src) {
47287
+ const cur = adjacency.get(iso);
47288
+ if (cur) cur.push(...ns);
47289
+ else adjacency.set(iso, [...ns]);
47290
+ }
47291
+ };
47292
+ addEdges(buildAdjacency(worldTopo));
47293
+ if (usLayer) {
47294
+ addEdges(buildAdjacency(data.usStates));
47295
+ for (const [country, states] of Object.entries(FOREIGN_BORDER)) {
47296
+ const cn = adjacency.get(country);
47297
+ if (!cn) continue;
47298
+ for (const st of states) {
47299
+ const sn = adjacency.get(st);
47300
+ if (!sn) continue;
47301
+ cn.push(st);
47302
+ sn.push(country);
47303
+ }
47304
+ }
47305
+ }
47306
+ const { byIso, huesNeeded } = assignColors(
47307
+ [...adjacency.keys()],
47308
+ adjacency
47309
+ );
47310
+ const tints = politicalTints(palette, huesNeeded, isDark);
47311
+ for (const [iso, idx] of byIso) colorByIso.set(iso, tints[idx]);
47312
+ }
47313
+ const colorizeStroke = (fill2) => mix(fill2, palette.text, 35);
46326
47314
  const rampBase = isDark ? mix(palette.surface, palette.text, 28) : palette.bg;
46327
47315
  const fillForValue = (s) => {
46328
47316
  const t = rampMax > rampMin ? (s - rampMin) / (rampMax - rampMin) : 1;
@@ -46358,43 +47346,15 @@ function layoutMap(resolved, data, size, opts) {
46358
47346
  if (activeIsScore) {
46359
47347
  return r.value !== void 0 ? fillForValue(r.value) : neutralFill;
46360
47348
  }
47349
+ if (colorizeActive) return (r.iso && colorByIso.get(r.iso)) ?? neutralFill;
46361
47350
  return tagFill(r.tags, activeGroup) ?? neutralFill;
46362
47351
  };
46363
47352
  const regionById = new Map(resolved.regions.map((r) => [r.iso, r]));
46364
- const extentOutline = () => {
46365
- const [[w, s], [e, n]] = resolved.extent;
46366
- const N = 16;
46367
- const coords = [];
46368
- for (let i = 0; i <= N; i++) {
46369
- const t = i / N;
46370
- const lon = w + (e - w) * t;
46371
- const lat = s + (n - s) * t;
46372
- coords.push([lon, s], [lon, n], [w, lat], [e, lat]);
46373
- }
46374
- return {
46375
- type: "Feature",
46376
- properties: {},
46377
- geometry: { type: "MultiPoint", coordinates: coords }
46378
- };
46379
- };
46380
- let fitFeatures;
46381
- if (resolved.projection === "albers-usa" && usLayer) {
46382
- fitFeatures = [...usLayer.entries()].filter(([iso]) => !US_NON_CONUS.has(iso)).map(([, f]) => f);
46383
- } else {
46384
- fitFeatures = [extentOutline()];
46385
- }
46386
- const fitTarget = { type: "FeatureCollection", features: fitFeatures };
46387
- const projection = projectionFor(resolved.projection);
46388
- if (resolved.projection !== "albers-usa") {
46389
- let centerLon = (resolved.extent[0][0] + resolved.extent[1][0]) / 2;
46390
- if (centerLon > 180) centerLon -= 360;
46391
- projection.rotate([-centerLon, 0]);
46392
- }
46393
- const TITLE_GAP = 16;
47353
+ const TITLE_GAP2 = 16;
46394
47354
  let topPad = FIT_PAD;
46395
47355
  if (resolved.title && resolved.pois.length > 0) {
46396
47356
  const bannerBottom = (resolved.subtitle ? TITLE_Y + TITLE_FONT_SIZE : TITLE_Y) + TITLE_FONT_SIZE / 2;
46397
- topPad = Math.max(FIT_PAD, bannerBottom + TITLE_GAP);
47357
+ topPad = Math.max(FIT_PAD, bannerBottom + TITLE_GAP2);
46398
47358
  }
46399
47359
  const fitBox = [
46400
47360
  [FIT_PAD, topPad],
@@ -46404,12 +47364,10 @@ function layoutMap(resolved, data, size, opts) {
46404
47364
  ]
46405
47365
  ];
46406
47366
  projection.fitExtent(fitBox, fitTarget);
46407
- const fitGB = geoBounds2(fitTarget);
46408
- const fitIsGlobal = fitGB[1][0] - fitGB[0][0] >= 270 || fitGB[1][1] - fitGB[0][1] >= 130;
46409
47367
  let path;
46410
47368
  let project;
46411
47369
  let stretchParams = null;
46412
- if (fitIsGlobal) {
47370
+ if (fitIsGlobal && !opts.preferContain) {
46413
47371
  const cb = geoPath(projection).bounds(fitTarget);
46414
47372
  const bx0 = cb[0][0];
46415
47373
  const by0 = cb[0][1];
@@ -46451,7 +47409,9 @@ function layoutMap(resolved, data, size, opts) {
46451
47409
  const insets = [];
46452
47410
  const insetRegions = [];
46453
47411
  const insetLabelSeeds = [];
46454
- if (resolved.projection === "albers-usa" && usLayer && !resolved.directives.noInsets) {
47412
+ const akRef = resolved.regions.some((r) => r.iso === "US-AK") || resolved.pois.some((p) => inAlaska(p.lon, p.lat));
47413
+ const hiRef = resolved.regions.some((r) => r.iso === "US-HI") || resolved.pois.some((p) => inHawaii(p.lon, p.lat));
47414
+ if (resolved.projection === "albers-usa" && usLayer && (akRef || hiRef)) {
46455
47415
  const PAD = 8;
46456
47416
  const GAP = 12;
46457
47417
  const yB = height - FIT_PAD;
@@ -46516,8 +47476,18 @@ function layoutMap(resolved, data, size, opts) {
46516
47476
  );
46517
47477
  const d = geoPath(proj)(f) ?? "";
46518
47478
  if (!d) return xr;
47479
+ let contextLand;
47480
+ if (iso === "US-AK") {
47481
+ const can = worldLayer.get("CA");
47482
+ const cd = can ? geoPath(proj)(can) ?? "" : "";
47483
+ if (cd)
47484
+ contextLand = {
47485
+ d: cd,
47486
+ fill: colorizeActive ? colorByIso.get("CA") ?? foreignFill : foreignFill
47487
+ };
47488
+ }
46519
47489
  const r = regionById.get(iso);
46520
- let fill2 = neutralFill;
47490
+ let fill2 = colorizeActive ? colorByIso.get(iso) ?? neutralFill : neutralFill;
46521
47491
  let lineNumber = -1;
46522
47492
  if (r?.layer === "us-state") {
46523
47493
  fill2 = regionFill(r);
@@ -46536,13 +47506,14 @@ function layoutMap(resolved, data, size, opts) {
46536
47506
  ],
46537
47507
  // The FITTED inset projection (just fit to this box) — captured so the
46538
47508
  // geo-query can invert pixels inside the frame back to AK/HI coords.
46539
- projection: proj
47509
+ projection: proj,
47510
+ ...contextLand && { contextLand }
46540
47511
  });
46541
47512
  insetRegions.push({
46542
47513
  id: iso,
46543
47514
  d,
46544
47515
  fill: fill2,
46545
- stroke: regionStroke,
47516
+ stroke: colorizeActive ? colorizeStroke(fill2) : regionStroke,
46546
47517
  lineNumber,
46547
47518
  layer: "us-state",
46548
47519
  ...r?.value !== void 0 && { value: r.value },
@@ -46555,13 +47526,16 @@ function layoutMap(resolved, data, size, opts) {
46555
47526
  }
46556
47527
  return xr;
46557
47528
  };
46558
- const akRight = placeInset(
46559
- "US-AK",
46560
- alaskaProjection(),
46561
- FIT_PAD,
46562
- width * 0.15
46563
- );
46564
- placeInset("US-HI", hawaiiProjection(), akRight + 24, width * 0.1);
47529
+ let akRight = FIT_PAD;
47530
+ if (akRef)
47531
+ akRight = placeInset("US-AK", alaskaProjection(), FIT_PAD, width * 0.15);
47532
+ if (hiRef)
47533
+ placeInset(
47534
+ "US-HI",
47535
+ hawaiiProjection(),
47536
+ akRef ? akRight + 24 : FIT_PAD,
47537
+ width * 0.1
47538
+ );
46565
47539
  }
46566
47540
  const conusFit = resolved.projection === "albers-usa" && !!usLayer;
46567
47541
  const classifyExtent = conusFit ? geoBounds2(fitTarget) : resolved.extent;
@@ -46577,15 +47551,24 @@ function layoutMap(resolved, data, size, opts) {
46577
47551
  };
46578
47552
  const ringOverlapsView = (ring) => {
46579
47553
  let loMin = Infinity, loMax = -Infinity, rawMin = Infinity, rawMax = -Infinity;
47554
+ const lons = [];
46580
47555
  for (const [rawLon] of ring) {
46581
47556
  const lon = normLon(rawLon);
47557
+ lons.push(lon);
46582
47558
  if (lon < loMin) loMin = lon;
46583
47559
  if (lon > loMax) loMax = lon;
46584
47560
  if (rawLon < rawMin) rawMin = rawLon;
46585
47561
  if (rawLon > rawMax) rawMax = rawLon;
46586
47562
  }
46587
- if (loMax - loMin > 270) return false;
46588
- if (rawMax - rawMin > 180 && loMax - loMin < 90) return false;
47563
+ lons.sort((a, b) => a - b);
47564
+ let maxGap = 0;
47565
+ for (let i = 1; i < lons.length; i++)
47566
+ maxGap = Math.max(maxGap, lons[i] - lons[i - 1]);
47567
+ if (lons.length > 1)
47568
+ maxGap = Math.max(maxGap, lons[0] + 360 - lons[lons.length - 1]);
47569
+ const occupiedArc = 360 - maxGap;
47570
+ if (occupiedArc > 270) return false;
47571
+ if (rawMax - rawMin > 180 && occupiedArc < 90) return false;
46589
47572
  let px0 = Infinity, py0 = Infinity, px1 = -Infinity, py1 = -Infinity, anyFinite = false;
46590
47573
  for (const [lon, lat] of ring) {
46591
47574
  const p = project(lon, lat);
@@ -46658,7 +47641,7 @@ function layoutMap(resolved, data, size, opts) {
46658
47641
  const regions = [];
46659
47642
  const pushRegionLayer = (layerFeatures, layerKind, shouldCull) => {
46660
47643
  for (const [iso, f] of layerFeatures) {
46661
- if (layerKind === "us-state" && usContext && INSET_STATES.has(iso))
47644
+ if (layerKind === "us-state" && usContext && resolved.projection === "albers-usa" && INSET_STATES.has(iso))
46662
47645
  continue;
46663
47646
  if (layerKind === "country" && usContext && iso === "US") continue;
46664
47647
  if (layerKind === "country" && iso === "AQ" && !regionById.has("AQ"))
@@ -46670,7 +47653,8 @@ function layoutMap(resolved, data, size, opts) {
46670
47653
  if (!d) continue;
46671
47654
  const isThisLayer = r?.layer === layerKind;
46672
47655
  const isForeign = layerKind === "country" && usContext && iso !== "US";
46673
- let fill2 = isForeign ? foreignFill : neutralFill;
47656
+ const baseFill = isForeign ? foreignFill : neutralFill;
47657
+ let fill2 = colorizeActive ? colorByIso.get(iso) ?? baseFill : baseFill;
46674
47658
  let label;
46675
47659
  let lineNumber = -1;
46676
47660
  let layer = "base";
@@ -46679,12 +47663,14 @@ function layoutMap(resolved, data, size, opts) {
46679
47663
  lineNumber = r.lineNumber;
46680
47664
  layer = layerKind;
46681
47665
  label = r.name;
47666
+ } else {
47667
+ label = f.properties?.name;
46682
47668
  }
46683
47669
  regions.push({
46684
47670
  id: iso,
46685
47671
  d,
46686
47672
  fill: fill2,
46687
- stroke: regionStroke,
47673
+ stroke: colorizeActive ? colorizeStroke(fill2) : regionStroke,
46688
47674
  lineNumber,
46689
47675
  layer,
46690
47676
  ...label !== void 0 && { label },
@@ -46712,9 +47698,41 @@ function layoutMap(resolved, data, size, opts) {
46712
47698
  });
46713
47699
  }
46714
47700
  }
47701
+ const pointInRings = (px, py, rings) => {
47702
+ let inside = false;
47703
+ for (const ring of rings) {
47704
+ for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
47705
+ const [xi, yi] = ring[i];
47706
+ const [xj, yj] = ring[j];
47707
+ if (yi > py !== yj > py && px < (xj - xi) * (py - yi) / (yj - yi) + xi)
47708
+ inside = !inside;
47709
+ }
47710
+ }
47711
+ return inside;
47712
+ };
47713
+ const fillHitTargets = [...regions, ...insetRegions].map((r) => ({
47714
+ fill: r.fill,
47715
+ rings: parsePathRings(r.d)
47716
+ }));
47717
+ const fillAt = (x, y) => {
47718
+ let hit = water;
47719
+ for (const t of fillHitTargets)
47720
+ if (pointInRings(x, y, t.rings)) hit = t.fill;
47721
+ return hit;
47722
+ };
47723
+ const labelOnFill = (fill2) => {
47724
+ const color = contrastRatio(fill2, palette.textOnFillDark) >= contrastRatio(fill2, palette.textOnFillLight) ? palette.textOnFillDark : palette.textOnFillLight;
47725
+ const haloColor = color === palette.textOnFillLight ? palette.textOnFillDark : palette.textOnFillLight;
47726
+ return {
47727
+ color,
47728
+ halo: contrastRatio(fill2, color) < REGION_LABEL_HALO_RATIO,
47729
+ haloColor
47730
+ };
47731
+ };
47732
+ const reliefAllowed = resolved.directives.noRelief !== true;
46715
47733
  const relief = [];
46716
47734
  let reliefHatch = null;
46717
- if (resolved.directives.relief === true && data.mountainRanges) {
47735
+ if (reliefAllowed && data.mountainRanges) {
46718
47736
  for (const [, f] of decodeLayer(data.mountainRanges)) {
46719
47737
  const viewF = isGlobalView ? dropFrameFillers(f) : cullFeatureToView(f);
46720
47738
  if (!viewF) continue;
@@ -46730,16 +47748,32 @@ function layoutMap(resolved, data, size, opts) {
46730
47748
  if (relief.length) {
46731
47749
  const darkTone = isDark ? palette.bg : palette.text;
46732
47750
  const lightTone = isDark ? palette.text : palette.bg;
46733
- const landLum = relativeLuminance(neutralFill);
47751
+ const reliefLandRef = colorizeActive ? isDark ? palette.surface : palette.bg : neutralFill;
47752
+ const landLum = relativeLuminance(reliefLandRef);
46734
47753
  const tone = Math.abs(landLum - relativeLuminance(darkTone)) > 0.04 ? darkTone : lightTone;
46735
47754
  reliefHatch = {
46736
- color: mix(tone, neutralFill, RELIEF_HATCH_STRENGTH),
47755
+ color: mix(tone, reliefLandRef, RELIEF_HATCH_STRENGTH),
46737
47756
  spacing: RELIEF_HATCH_SPACING,
46738
47757
  width: RELIEF_HATCH_WIDTH
46739
47758
  };
46740
47759
  }
46741
47760
  }
46742
- const riverColor = mix(water, regionStroke, 16);
47761
+ let coastlineStyle = null;
47762
+ if (resolved.directives.noCoastline !== true) {
47763
+ const minDim = Math.min(width, height);
47764
+ coastlineStyle = {
47765
+ color: mix(regionStroke, water, COASTLINE_STROKE_MIX),
47766
+ // N equal-width rings: distance steps outward by COASTLINE_STEP; opacity
47767
+ // fades linearly from NEAR (innermost) to FAR (outermost).
47768
+ lines: Array.from({ length: COASTLINE_RING_COUNT }, (_, k) => ({
47769
+ d: (COASTLINE_D0 + k * COASTLINE_STEP) * minDim,
47770
+ thickness: COASTLINE_THICKNESS * minDim,
47771
+ opacity: COASTLINE_OPACITY_NEAR + (COASTLINE_OPACITY_FAR - COASTLINE_OPACITY_NEAR) * k / (COASTLINE_RING_COUNT - 1)
47772
+ })),
47773
+ minExtent: (isGlobalView ? COASTLINE_MIN_EXTENT_GLOBAL : COASTLINE_MIN_EXTENT) * minDim
47774
+ };
47775
+ }
47776
+ const riverColor = mix(palette.colors.blue, water, 32);
46743
47777
  const rivers = [];
46744
47778
  if (data.rivers) {
46745
47779
  for (const [, f] of decodeLayer(data.rivers)) {
@@ -46795,38 +47829,108 @@ function layoutMap(resolved, data, size, opts) {
46795
47829
  const xy = project(p.lon, p.lat);
46796
47830
  if (xy) projected.push({ p, xy });
46797
47831
  }
46798
- const coloGroups = /* @__PURE__ */ new Map();
47832
+ const placePoi = (e, cx, cy, clusterId) => {
47833
+ const { fill: fill2, stroke: stroke2 } = poiFill(e.p);
47834
+ poiScreen.set(e.p.id, { cx, cy, r: radiusFor(e.p) });
47835
+ const num = routeNumberById.get(e.p.id);
47836
+ pois.push({
47837
+ id: e.p.id,
47838
+ cx,
47839
+ cy,
47840
+ r: radiusFor(e.p),
47841
+ fill: fill2,
47842
+ stroke: stroke2,
47843
+ lineNumber: e.p.lineNumber,
47844
+ implicit: !!e.p.implicit,
47845
+ isOrigin: originIds.has(e.p.id),
47846
+ ...num !== void 0 && { routeNumber: num },
47847
+ ...Object.keys(e.p.tags).length > 0 && { tags: e.p.tags },
47848
+ ...clusterId !== void 0 && { clusterId }
47849
+ });
47850
+ };
47851
+ const clusters = [];
47852
+ const connected = /* @__PURE__ */ new Set();
47853
+ for (const e of resolved.edges) {
47854
+ connected.add(e.fromId);
47855
+ connected.add(e.toId);
47856
+ }
47857
+ for (const rt of resolved.routes) {
47858
+ rt.stopIds.forEach((id) => connected.add(id));
47859
+ }
47860
+ const radiusOf = (e) => radiusFor(e.p);
46799
47861
  for (const e of projected) {
46800
- const key = `${Math.round(e.xy[0] / COLO_EPS)},${Math.round(e.xy[1] / COLO_EPS)}`;
46801
- const arr = coloGroups.get(key);
46802
- if (arr) arr.push(e);
46803
- else coloGroups.set(key, [e]);
46804
- }
46805
- for (const group of coloGroups.values()) {
46806
- group.forEach((e, i) => {
46807
- let cx = e.xy[0];
46808
- let cy = e.xy[1];
46809
- if (group.length > 1) {
46810
- const ang = i * GOLDEN_ANGLE;
46811
- cx += Math.cos(ang) * COLO_R;
46812
- cy += Math.sin(ang) * COLO_R;
46813
- }
46814
- const { fill: fill2, stroke: stroke2 } = poiFill(e.p);
46815
- poiScreen.set(e.p.id, { cx, cy, r: radiusFor(e.p) });
46816
- const num = routeNumberById.get(e.p.id);
46817
- pois.push({
46818
- id: e.p.id,
46819
- cx,
46820
- cy,
46821
- r: radiusFor(e.p),
46822
- fill: fill2,
46823
- stroke: stroke2,
46824
- lineNumber: e.p.lineNumber,
46825
- implicit: !!e.p.implicit,
46826
- isOrigin: originIds.has(e.p.id),
46827
- ...num !== void 0 && { routeNumber: num },
46828
- ...Object.keys(e.p.tags).length > 0 && { tags: e.p.tags }
46829
- });
47862
+ if (connected.has(e.p.id)) placePoi(e, e.xy[0], e.xy[1]);
47863
+ }
47864
+ const groups = [];
47865
+ for (const e of projected) {
47866
+ if (connected.has(e.p.id)) continue;
47867
+ const r = radiusOf(e);
47868
+ const near = groups.find(
47869
+ (g) => g.some(
47870
+ (q) => Math.hypot(q.xy[0] - e.xy[0], q.xy[1] - e.xy[1]) < (r + radiusOf(q)) * STACK_OVERLAP
47871
+ )
47872
+ );
47873
+ if (near) near.push(e);
47874
+ else groups.push([e]);
47875
+ }
47876
+ for (const g of groups) {
47877
+ if (g.length === 1) {
47878
+ placePoi(g[0], g[0].xy[0], g[0].xy[1]);
47879
+ continue;
47880
+ }
47881
+ const clusterId = g[0].p.id;
47882
+ const cx0 = g.reduce((s, e) => s + e.xy[0], 0) / g.length;
47883
+ const cy0 = g.reduce((s, e) => s + e.xy[1], 0) / g.length;
47884
+ const maxR = Math.max(...g.map(radiusOf));
47885
+ const sep = 2 * maxR + STACK_RING_GAP;
47886
+ const ringR = Math.max(
47887
+ COLO_R,
47888
+ sep / (2 * Math.sin(Math.PI / Math.max(g.length, 2)))
47889
+ );
47890
+ const positions = g.map((e, i) => {
47891
+ if (g.length <= STACK_RING_MAX) {
47892
+ const ang2 = -Math.PI / 2 + i * 2 * Math.PI / g.length;
47893
+ return {
47894
+ e,
47895
+ mx: cx0 + Math.cos(ang2) * ringR,
47896
+ my: cy0 + Math.sin(ang2) * ringR
47897
+ };
47898
+ }
47899
+ const ang = i * GOLDEN_ANGLE;
47900
+ const rr = ringR * Math.sqrt((i + 1) / g.length);
47901
+ return { e, mx: cx0 + Math.cos(ang) * rr, my: cy0 + Math.sin(ang) * rr };
47902
+ });
47903
+ let minX = cx0 - maxR;
47904
+ let maxX = cx0 + maxR;
47905
+ let minY = cy0 - maxR;
47906
+ let maxY = cy0 + maxR;
47907
+ for (const { mx, my, e } of positions) {
47908
+ const r = radiusOf(e);
47909
+ minX = Math.min(minX, mx - r);
47910
+ maxX = Math.max(maxX, mx + r);
47911
+ minY = Math.min(minY, my - r);
47912
+ maxY = Math.max(maxY, my + r);
47913
+ }
47914
+ let dx = 0;
47915
+ let dy = 0;
47916
+ if (minX + dx < 2) dx = 2 - minX;
47917
+ if (maxX + dx > width - 2) dx = width - 2 - maxX;
47918
+ if (minY + dy < 2) dy = 2 - minY;
47919
+ if (maxY + dy > height - 2) dy = height - 2 - maxY;
47920
+ const legsOut = [];
47921
+ for (const { e, mx, my } of positions) {
47922
+ const fx = mx + dx;
47923
+ const fy = my + dy;
47924
+ placePoi(e, fx, fy, clusterId);
47925
+ legsOut.push({ x2: fx, y2: fy, color: poiFill(e.p).fill });
47926
+ }
47927
+ clusters.push({
47928
+ id: clusterId,
47929
+ cx: cx0 + dx,
47930
+ cy: cy0 + dy,
47931
+ count: g.length,
47932
+ hitR: ringR + maxR + 6,
47933
+ legs: legsOut
46830
47934
  });
46831
47935
  }
46832
47936
  const legs = [];
@@ -46876,16 +47980,26 @@ function layoutMap(resolved, data, size, opts) {
46876
47980
  if (!a || !b) continue;
46877
47981
  const mx = (a.cx + b.cx) / 2;
46878
47982
  const my = (a.cy + b.cy) / 2;
47983
+ const bow = {
47984
+ curved: leg.style === "arc",
47985
+ offset: 0,
47986
+ labelX: mx,
47987
+ labelY: my - 4
47988
+ };
47989
+ const routeLabelStyle = leg.label !== void 0 ? labelOnFill(fillAt(bow.labelX, bow.labelY)) : void 0;
46879
47990
  legs.push({
46880
- d: legPath(a, b, leg.style === "arc", 0),
47991
+ d: legPath(a, b, bow.curved, bow.offset),
46881
47992
  width: routeWidthFor(Number(leg.value)),
46882
47993
  color: mix(palette.text, palette.bg, 72),
46883
47994
  arrow: true,
46884
47995
  lineNumber: leg.lineNumber,
46885
47996
  ...leg.label !== void 0 && {
46886
47997
  label: leg.label,
46887
- labelX: mx,
46888
- labelY: my - 4
47998
+ labelX: bow.labelX,
47999
+ labelY: bow.labelY,
48000
+ labelColor: routeLabelStyle.color,
48001
+ labelHalo: routeLabelStyle.halo,
48002
+ labelHaloColor: routeLabelStyle.haloColor
46889
48003
  }
46890
48004
  });
46891
48005
  }
@@ -46913,20 +48027,29 @@ function layoutMap(resolved, data, size, opts) {
46913
48027
  const a = poiScreen.get(e.fromId);
46914
48028
  const b = poiScreen.get(e.toId);
46915
48029
  if (!a || !b) return;
46916
- const curved = e.style === "arc" || n > 1;
46917
- const offset = n > 1 ? (i - (n - 1) / 2) * FAN_STEP : 0;
48030
+ const fanOffset = n > 1 ? (i - (n - 1) / 2) * FAN_STEP : 0;
46918
48031
  const mx = (a.cx + b.cx) / 2;
46919
48032
  const my = (a.cy + b.cy) / 2;
48033
+ const bow = {
48034
+ curved: e.style === "arc" || n > 1,
48035
+ offset: fanOffset,
48036
+ labelX: mx,
48037
+ labelY: my - 4
48038
+ };
48039
+ const edgeLabelStyle = e.label !== void 0 ? labelOnFill(fillAt(bow.labelX, bow.labelY)) : void 0;
46920
48040
  legs.push({
46921
- d: legPath(a, b, curved, offset),
48041
+ d: legPath(a, b, bow.curved, bow.offset),
46922
48042
  width: widthFor(e),
46923
48043
  color: mix(palette.text, palette.bg, 66),
46924
48044
  arrow: e.directed,
46925
48045
  lineNumber: e.lineNumber,
46926
48046
  ...e.label !== void 0 && {
46927
48047
  label: e.label,
46928
- labelX: mx,
46929
- labelY: my - 4
48048
+ labelX: bow.labelX,
48049
+ labelY: bow.labelY,
48050
+ labelColor: edgeLabelStyle.color,
48051
+ labelHalo: edgeLabelStyle.halo,
48052
+ labelHaloColor: edgeLabelStyle.haloColor
46930
48053
  }
46931
48054
  });
46932
48055
  });
@@ -46968,25 +48091,25 @@ function layoutMap(resolved, data, size, opts) {
46968
48091
  }
46969
48092
  }
46970
48093
  const collides = (rect) => markers.some((m) => rectCircleOverlap(rect, m)) || obstacles.some((o) => rectsOverlap(rect, o)) || legSegments.some((s) => segmentRectOverlap(s[0], s[1], s[2], s[3], rect));
46971
- const regionLabelMode = resolved.directives.regionLabels ?? "off";
48094
+ const showRegionLabels = resolved.directives.noRegionLabels !== true;
48095
+ const isCompact = width < COMPACT_WIDTH_PX;
46972
48096
  const LABEL_PADX = 6;
46973
48097
  const LABEL_PADY = 3;
46974
- const labelW = (text) => measureLegendText(text, FONT) + 2 * LABEL_PADX;
46975
- const labelH = FONT + 2 * LABEL_PADY;
48098
+ const labelW = (text) => measureLegendText(text, FONT2) + 2 * LABEL_PADX;
48099
+ const labelH = FONT2 + 2 * LABEL_PADY;
46976
48100
  const pushRegionLabel = (x, y, text, fill2, lineNumber) => {
46977
- const color = contrastText(
46978
- fill2,
46979
- palette.textOnFillLight,
46980
- palette.textOnFillDark
48101
+ const { color, haloColor } = labelOnFill(fill2);
48102
+ const halfW = measureLegendText(text, FONT2) / 2;
48103
+ const overflows = [y - FONT2 * 0.55, y - FONT2 * 0.1].some(
48104
+ (sy) => fillAt(x - halfW, sy) !== fill2 || fillAt(x + halfW, sy) !== fill2
46981
48105
  );
46982
- const haloColor = color === palette.textOnFillLight ? palette.textOnFillDark : palette.textOnFillLight;
46983
48106
  labels.push({
46984
48107
  x,
46985
48108
  y,
46986
48109
  text,
46987
48110
  anchor: "middle",
46988
48111
  color,
46989
- halo: true,
48112
+ halo: overflows,
46990
48113
  haloColor,
46991
48114
  lineNumber
46992
48115
  });
@@ -46995,21 +48118,50 @@ function layoutMap(resolved, data, size, opts) {
46995
48118
  US: [-98.5, 39.5]
46996
48119
  // CONUS geographic centre (near Lebanon, Kansas)
46997
48120
  };
46998
- if (regionLabelMode === "full" || regionLabelMode === "abbrev") {
46999
- for (const r of regions) {
47000
- if (r.layer === "base" || r.label === void 0) continue;
47001
- const f = r.layer === "us-state" ? usLayer?.get(r.id) : worldLayer.get(r.id);
47002
- if (!f) continue;
48121
+ const REGION_LABEL_GAP = 2;
48122
+ const regionLabelRect = (cx, cy, text) => {
48123
+ const w = measureLegendText(text, FONT2) + 2 * REGION_LABEL_GAP;
48124
+ return { x: cx - w / 2, y: cy - FONT2 / 2, w, h: FONT2 };
48125
+ };
48126
+ if (showRegionLabels) {
48127
+ const frameContainers = new Set(resolved.poiFrameContainers);
48128
+ const entries = regions.map((r) => {
48129
+ const isContainer = frameContainers.has(r.id);
48130
+ if (r.layer === "base" && !isContainer || r.label === void 0)
48131
+ return null;
48132
+ const isUsState = r.layer === "us-state" || r.id.startsWith("US-");
48133
+ const f = isUsState ? usLayer?.get(r.id) : worldLayer.get(r.id);
48134
+ if (!f) return null;
47003
48135
  const [[x0, y0], [x1, y1]] = path.bounds(f);
47004
- const text = regionLabelMode === "abbrev" ? r.id.replace(/^US-/, "") : r.label;
47005
- if (labelW(text) > x1 - x0 || labelH > y1 - y0) continue;
47006
- const anchor = r.layer !== "us-state" ? WORLD_LABEL_ANCHORS[r.id] : void 0;
48136
+ const boxW = x1 - x0;
48137
+ const boxH = y1 - y0;
48138
+ const abbrev = isUsState ? r.id.replace(/^US-/, "") : void 0;
48139
+ const candidates = abbrev !== void 0 ? isCompact ? [abbrev, r.label] : [r.label, abbrev] : [r.label];
48140
+ const anchor = !isUsState ? WORLD_LABEL_ANCHORS[r.id] : void 0;
47007
48141
  const c = anchor ? project(anchor[0], anchor[1]) : path.centroid(f);
47008
- if (!c || !Number.isFinite(c[0])) continue;
48142
+ if (!c || !Number.isFinite(c[0])) return null;
48143
+ return { r, c, boxW, boxH, area: boxW * boxH, candidates };
48144
+ }).filter((e) => e !== null).sort((a, b) => b.area - a.area || a.r.lineNumber - b.r.lineNumber);
48145
+ const placedRegionRects = [];
48146
+ const POI_LABEL_PAD = 14;
48147
+ const poiObstacles = pois.map((p) => ({
48148
+ x: p.cx - p.r - POI_LABEL_PAD,
48149
+ y: p.cy - p.r - POI_LABEL_PAD,
48150
+ w: 2 * (p.r + POI_LABEL_PAD),
48151
+ h: 2 * (p.r + POI_LABEL_PAD)
48152
+ }));
48153
+ for (const { r, c, boxW, boxH, candidates } of entries) {
48154
+ const text = candidates.find((t) => {
48155
+ if (labelW(t) > boxW || labelH > boxH) return false;
48156
+ const rect = regionLabelRect(c[0], c[1], t);
48157
+ return !placedRegionRects.some((p) => rectsOverlap(rect, p)) && !poiObstacles.some((o) => rectsOverlap(rect, o));
48158
+ });
48159
+ if (text === void 0) continue;
48160
+ placedRegionRects.push(regionLabelRect(c[0], c[1], text));
47009
48161
  pushRegionLabel(c[0], c[1], text, r.fill, r.lineNumber);
47010
48162
  }
47011
48163
  for (const seed of insetLabelSeeds) {
47012
- const text = regionLabelMode === "abbrev" ? seed.iso.replace(/^US-/, "") : seed.name;
48164
+ const text = isCompact ? seed.iso.replace(/^US-/, "") : seed.name;
47013
48165
  const src = regionById.get(seed.iso);
47014
48166
  pushRegionLabel(
47015
48167
  seed.x,
@@ -47020,22 +48172,26 @@ function layoutMap(resolved, data, size, opts) {
47020
48172
  );
47021
48173
  }
47022
48174
  }
47023
- const poiLabelMode = resolved.directives.poiLabels ?? "auto";
47024
- if (poiLabelMode !== "off") {
47025
- const ordered = [...pois].sort(
47026
- (a, b) => a.lineNumber - b.lineNumber || (a.id < b.id ? -1 : 1)
47027
- );
48175
+ if (resolved.directives.noPoiLabels !== true) {
48176
+ const ordered = [...pois].filter((p) => p.clusterId === void 0).sort((a, b) => a.lineNumber - b.lineNumber || (a.id < b.id ? -1 : 1));
47028
48177
  const poiById = new Map(resolved.pois.map((q) => [q.id, q]));
47029
48178
  const labelText = (p) => {
47030
48179
  const src = poiById.get(p.id);
47031
48180
  return src?.label ?? src?.name ?? p.id;
47032
48181
  };
47033
- const poiLabH = FONT * 1.25;
48182
+ const poiLabH = FONT2 * 1.25;
47034
48183
  const labelInfo = (p) => {
47035
48184
  const text = labelText(p);
47036
- return { text, w: measureLegendText(text, FONT) };
48185
+ return { text, w: measureLegendText(text, FONT2) };
47037
48186
  };
47038
48187
  const GAP = 3;
48188
+ const clusterMembersById = /* @__PURE__ */ new Map();
48189
+ for (const p of pois) {
48190
+ if (p.clusterId === void 0) continue;
48191
+ const arr = clusterMembersById.get(p.clusterId);
48192
+ if (arr) arr.push(p);
48193
+ else clusterMembersById.set(p.clusterId, [p]);
48194
+ }
47039
48195
  const inlineRect = (p, w, side) => {
47040
48196
  switch (side) {
47041
48197
  case "right":
@@ -47065,11 +48221,11 @@ function layoutMap(resolved, data, size, opts) {
47065
48221
  const x = side === "right" ? rect.x : side === "left" ? rect.x + w : p.cx;
47066
48222
  labels.push({
47067
48223
  x,
47068
- y: rect.y + poiLabH / 2 + FONT / 3,
48224
+ y: rect.y + poiLabH / 2 + FONT2 / 3,
47069
48225
  text,
47070
48226
  anchor,
47071
48227
  color: palette.text,
47072
- halo: true,
48228
+ halo: false,
47073
48229
  haloColor: palette.bg,
47074
48230
  poiId: p.id,
47075
48231
  lineNumber: p.lineNumber
@@ -47080,43 +48236,60 @@ function layoutMap(resolved, data, size, opts) {
47080
48236
  return rect.x >= 0 && rect.x + rect.w <= width && rect.y >= 0 && rect.y + rect.h <= height && !collides(rect);
47081
48237
  };
47082
48238
  const GROUP_R = 30;
47083
- const groups = [];
48239
+ const groups2 = [];
47084
48240
  for (const p of ordered) {
47085
- const near = groups.find(
48241
+ const near = groups2.find(
47086
48242
  (g) => g.some((q) => Math.hypot(q.cx - p.cx, q.cy - p.cy) < GROUP_R)
47087
48243
  );
47088
48244
  if (near) near.push(p);
47089
- else groups.push([p]);
48245
+ else groups2.push([p]);
47090
48246
  }
47091
48247
  const ROW_GAP2 = 3;
47092
48248
  const step = poiLabH + ROW_GAP2;
47093
48249
  const COL_GAP = 16;
47094
- const placeColumn = (group) => {
47095
- const items = group.map((p) => ({ p, ...labelInfo(p) })).sort((a, b) => a.p.cy - b.p.cy || (a.text < b.text ? -1 : 1));
48250
+ const makeItems = (group) => group.map((p) => ({ p, ...labelInfo(p) })).sort((a, b) => a.p.cy - b.p.cy || (a.text < b.text ? -1 : 1));
48251
+ const columnRows = (items, side) => {
47096
48252
  const left = Math.min(...items.map((o) => o.p.cx - o.p.r));
47097
48253
  const right = Math.max(...items.map((o) => o.p.cx + o.p.r));
47098
- const cyMid = (Math.min(...items.map((o) => o.p.cy)) + Math.max(...items.map((o) => o.p.cy))) / 2;
47099
48254
  const maxW = Math.max(...items.map((o) => o.w));
47100
- const side = right + COL_GAP + maxW <= width - 2 ? "right" : "left";
47101
- const colX = side === "right" ? right + COL_GAP : left - COL_GAP;
48255
+ const cyMid = (Math.min(...items.map((o) => o.p.cy)) + Math.max(...items.map((o) => o.p.cy))) / 2;
48256
+ const colX = side === "right" ? Math.min(right + COL_GAP, width - 2 - maxW) : Math.max(left - COL_GAP, 2 + maxW);
47102
48257
  const totalH = items.length * step;
47103
48258
  let startY = cyMid - totalH / 2;
47104
48259
  startY = Math.max(2, Math.min(startY, height - totalH - 2));
47105
- items.forEach((o, i) => {
48260
+ return items.map((o, i) => {
47106
48261
  const rowCy = startY + i * step + step / 2;
47107
- obstacles.push({
47108
- x: side === "right" ? colX : colX - o.w,
47109
- y: rowCy - poiLabH / 2,
47110
- w: o.w,
47111
- h: poiLabH
47112
- });
48262
+ return {
48263
+ o,
48264
+ colX,
48265
+ rowCy,
48266
+ rect: {
48267
+ x: side === "right" ? colX : colX - o.w,
48268
+ y: rowCy - poiLabH / 2,
48269
+ w: o.w,
48270
+ h: poiLabH
48271
+ }
48272
+ };
48273
+ });
48274
+ };
48275
+ const wouldColumnBeClean = (items, side) => columnRows(items, side).every(
48276
+ ({ rect }) => rect.x >= 0 && rect.x + rect.w <= width && rect.y >= 0 && rect.y + rect.h <= height && !collides(rect)
48277
+ );
48278
+ const defaultColumnSide = (items) => {
48279
+ const right = Math.max(...items.map((o) => o.p.cx + o.p.r));
48280
+ const maxW = Math.max(...items.map((o) => o.w));
48281
+ return right + COL_GAP + maxW <= width - 2 ? "right" : "left";
48282
+ };
48283
+ const commitColumn = (items, side, clusterId) => {
48284
+ for (const { o, colX, rowCy, rect } of columnRows(items, side)) {
48285
+ obstacles.push(rect);
47113
48286
  labels.push({
47114
48287
  x: colX,
47115
- y: rowCy + FONT / 3,
48288
+ y: rowCy + FONT2 / 3,
47116
48289
  text: o.text,
47117
48290
  anchor: side === "right" ? "start" : "end",
47118
48291
  color: palette.text,
47119
- halo: true,
48292
+ halo: false,
47120
48293
  haloColor: palette.bg,
47121
48294
  leader: {
47122
48295
  x1: o.p.cx,
@@ -47126,24 +48299,141 @@ function layoutMap(resolved, data, size, opts) {
47126
48299
  },
47127
48300
  leaderColor: o.p.fill,
47128
48301
  poiId: o.p.id,
47129
- lineNumber: o.p.lineNumber
48302
+ lineNumber: o.p.lineNumber,
48303
+ ...clusterId !== void 0 && { clusterMember: clusterId }
47130
48304
  });
48305
+ }
48306
+ };
48307
+ const pushHidden = (p) => {
48308
+ const { text, w } = labelInfo(p);
48309
+ let x = p.cx + p.r + GAP;
48310
+ let anchor = "start";
48311
+ if (x + w > width) {
48312
+ x = p.cx - p.r - GAP - w;
48313
+ anchor = "end";
48314
+ }
48315
+ const y = Math.max(0, Math.min(p.cy - poiLabH / 2, height - poiLabH));
48316
+ labels.push({
48317
+ x: anchor === "start" ? x : x + w,
48318
+ y: y + poiLabH / 2 + FONT2 / 3,
48319
+ text,
48320
+ anchor,
48321
+ color: palette.text,
48322
+ halo: false,
48323
+ haloColor: palette.bg,
48324
+ poiId: p.id,
48325
+ hidden: true,
48326
+ lineNumber: p.lineNumber
47131
48327
  });
47132
48328
  };
47133
- for (const g of groups) {
48329
+ for (const [clusterId, members] of clusterMembersById) {
48330
+ if (members.length === 0) continue;
48331
+ const items = makeItems(members);
48332
+ const side = wouldColumnBeClean(items, "right") ? "right" : wouldColumnBeClean(items, "left") ? "left" : defaultColumnSide(items);
48333
+ commitColumn(items, side, clusterId);
48334
+ }
48335
+ const maxExtent = MAX_CLUSTER_EXTENT_FACTOR * Math.min(width, height);
48336
+ const clusterPending = [];
48337
+ for (const g of groups2) {
48338
+ const items = makeItems(g);
47134
48339
  if (g.length === 1) {
47135
- const p = g[0];
47136
- const { text, w } = labelInfo(p);
48340
+ const { p, text, w } = items[0];
47137
48341
  const side = ["right", "left", "above", "below"].find(
47138
48342
  (s) => inlineFits(p, w, s)
47139
48343
  );
47140
- if (side) {
47141
- pushInline(p, text, w, side);
47142
- continue;
48344
+ if (side) pushInline(p, text, w, side);
48345
+ else commitColumn(items, defaultColumnSide(items));
48346
+ continue;
48347
+ }
48348
+ const left = Math.min(...items.map((o) => o.p.cx - o.p.r));
48349
+ const right = Math.max(...items.map((o) => o.p.cx + o.p.r));
48350
+ const minCy = Math.min(...items.map((o) => o.p.cy));
48351
+ const maxCy = Math.max(...items.map((o) => o.p.cy));
48352
+ const diag = Math.hypot(right - left, maxCy - minCy);
48353
+ if (diag > maxExtent || items.length > MAX_COLUMN_ROWS) {
48354
+ items.forEach((o) => pushHidden(o.p));
48355
+ } else {
48356
+ clusterPending.push(items);
48357
+ }
48358
+ }
48359
+ for (const items of clusterPending) {
48360
+ const side = ["right", "left"].find(
48361
+ (s) => wouldColumnBeClean(items, s)
48362
+ );
48363
+ if (side) commitColumn(items, side);
48364
+ else items.forEach((o) => pushHidden(o.p));
48365
+ }
48366
+ }
48367
+ if (resolved.directives.noContextLabels !== true) {
48368
+ for (const l of labels) {
48369
+ if (l.hidden) continue;
48370
+ const w = labelW(l.text);
48371
+ const x = l.anchor === "start" ? l.x : l.anchor === "end" ? l.x - w : l.x - w / 2;
48372
+ obstacles.push({ x, y: l.y - labelH / 2, w, h: labelH });
48373
+ }
48374
+ for (const box of insets)
48375
+ obstacles.push({ x: box.x, y: box.y, w: box.w, h: box.h });
48376
+ const countryCandidates = [];
48377
+ for (const f of worldLayer.values()) {
48378
+ const iso = typeof f.id === "string" ? f.id : String(f.id ?? "");
48379
+ if (!iso || regionById.has(iso)) continue;
48380
+ let hasReferencedSub = false;
48381
+ for (const k of regionById.keys())
48382
+ if (k.startsWith(iso + "-")) {
48383
+ hasReferencedSub = true;
48384
+ break;
47143
48385
  }
48386
+ if (hasReferencedSub) continue;
48387
+ const b = path.bounds(f);
48388
+ const [x0, y0] = b[0];
48389
+ const [x1, y1] = b[1];
48390
+ if (!Number.isFinite(x0) || !Number.isFinite(x1)) continue;
48391
+ const anchorLngLat = WORLD_LABEL_ANCHORS[iso];
48392
+ const a = anchorLngLat ? project(anchorLngLat[0], anchorLngLat[1]) : path.centroid(f);
48393
+ countryCandidates.push({
48394
+ name: f.properties?.name ?? iso,
48395
+ bbox: [x0, y0, x1, y1],
48396
+ anchor: a && Number.isFinite(a[0]) ? [a[0], a[1]] : null
48397
+ });
48398
+ }
48399
+ const framedStateContainers = (resolved.poiFrameContainers ?? []).some(
48400
+ (id) => id.startsWith("US-")
48401
+ );
48402
+ if (usLayer && framedStateContainers) {
48403
+ const containerSet = new Set(resolved.poiFrameContainers);
48404
+ for (const [iso, f] of usLayer) {
48405
+ if (containerSet.has(iso) || regionById.has(iso)) continue;
48406
+ const viewF = cullFeatureToView(f);
48407
+ if (!viewF) continue;
48408
+ const b = path.bounds(viewF);
48409
+ const [x0, y0] = b[0];
48410
+ const [x1, y1] = b[1];
48411
+ if (!Number.isFinite(x0) || !Number.isFinite(x1)) continue;
48412
+ const a = path.centroid(viewF);
48413
+ countryCandidates.push({
48414
+ name: f.properties?.name ?? iso,
48415
+ bbox: [x0, y0, x1, y1],
48416
+ anchor: a && Number.isFinite(a[0]) ? [a[0], a[1]] : null
48417
+ });
47144
48418
  }
47145
- placeColumn(g);
47146
48419
  }
48420
+ const contextLabels = placeContextLabels({
48421
+ projection: resolved.projection,
48422
+ dLonSpan,
48423
+ dLatSpan,
48424
+ width,
48425
+ height,
48426
+ waterBodies: data.waterBodies,
48427
+ countries: countryCandidates,
48428
+ palette,
48429
+ project,
48430
+ collides,
48431
+ // Water labels must stay over open water — `fillAt` returns the ocean
48432
+ // backdrop colour off-land and a region fill on-land (lakes/states count
48433
+ // as land here, which is the safe side for an ocean name).
48434
+ overLand: (x, y) => fillAt(x, y) !== water
48435
+ });
48436
+ labels.push(...contextLabels);
47147
48437
  }
47148
48438
  let legend = null;
47149
48439
  if (!resolved.directives.noLegend) {
@@ -47180,25 +48470,31 @@ function layoutMap(resolved, data, size, opts) {
47180
48470
  rivers,
47181
48471
  relief,
47182
48472
  reliefHatch,
48473
+ coastlineStyle,
47183
48474
  legs,
47184
48475
  pois,
48476
+ clusters,
47185
48477
  labels,
47186
48478
  legend,
47187
48479
  insets,
47188
48480
  insetRegions,
47189
48481
  projection,
47190
- stretch: stretchParams
48482
+ stretch: stretchParams,
48483
+ diagnostics: []
47191
48484
  };
47192
48485
  }
47193
- var FIT_PAD, RAMP_FLOOR, R_DEFAULT, R_MIN, R_MAX, W_MIN, W_MAX, FONT, COLO_EPS, LAND_TINT_LIGHT, LAND_TINT_DARK, TAG_TINT_LIGHT, TAG_TINT_DARK, WATER_TINT_LIGHT, WATER_TINT_DARK, RIVER_WIDTH, RELIEF_MIN_AREA, RELIEF_MIN_DIM, RELIEF_HATCH_SPACING, RELIEF_HATCH_WIDTH, RELIEF_HATCH_STRENGTH, FOREIGN_TINT_LIGHT, FOREIGN_TINT_DARK, MUTED_FOREIGN_LIGHT, MUTED_FOREIGN_DARK, COLO_R, GOLDEN_ANGLE, FAN_STEP, ARC_CURVE_FRAC, usConusProjection, alaskaProjection, hawaiiProjection, INSET_STATES, US_NON_CONUS;
48486
+ var FIT_PAD, RAMP_FLOOR, R_DEFAULT, R_MIN, R_MAX, W_MIN, W_MAX, FONT2, MAX_CLUSTER_EXTENT_FACTOR, MAX_COLUMN_ROWS, REGION_LABEL_HALO_RATIO, LAND_TINT_LIGHT, LAND_TINT_DARK, TAG_TINT_LIGHT, TAG_TINT_DARK, WATER_TINT_LIGHT, WATER_TINT_DARK, RIVER_WIDTH, COMPACT_WIDTH_PX, RELIEF_MIN_AREA, RELIEF_MIN_DIM, RELIEF_HATCH_SPACING, RELIEF_HATCH_WIDTH, RELIEF_HATCH_STRENGTH, COASTLINE_RING_COUNT, COASTLINE_D0, COASTLINE_STEP, COASTLINE_THICKNESS, COASTLINE_OPACITY_NEAR, COASTLINE_OPACITY_FAR, COASTLINE_MIN_EXTENT, COASTLINE_MIN_EXTENT_GLOBAL, COASTLINE_STROKE_MIX, FOREIGN_TINT_LIGHT, FOREIGN_TINT_DARK, MUTED_FOREIGN_LIGHT, MUTED_FOREIGN_DARK, COLO_R, GOLDEN_ANGLE, STACK_OVERLAP, STACK_RING_MAX, STACK_RING_GAP, FAN_STEP, ARC_CURVE_FRAC, decodeCache, usConusProjection, alaskaProjection, hawaiiProjection, INSET_STATES, inAlaska, inHawaii, FOREIGN_BORDER, US_NON_CONUS;
47194
48487
  var init_layout15 = __esm({
47195
48488
  "src/map/layout.ts"() {
47196
48489
  "use strict";
47197
48490
  init_color_utils();
48491
+ init_geo();
48492
+ init_colorize();
47198
48493
  init_colors();
47199
48494
  init_label_layout();
47200
48495
  init_legend_constants();
47201
48496
  init_title_constants();
48497
+ init_context_labels();
47202
48498
  FIT_PAD = 24;
47203
48499
  RAMP_FLOOR = 15;
47204
48500
  R_DEFAULT = 6;
@@ -47206,32 +48502,66 @@ var init_layout15 = __esm({
47206
48502
  R_MAX = 22;
47207
48503
  W_MIN = 1.25;
47208
48504
  W_MAX = 8;
47209
- FONT = 11;
47210
- COLO_EPS = 1.5;
48505
+ FONT2 = 11;
48506
+ MAX_CLUSTER_EXTENT_FACTOR = 0.18;
48507
+ MAX_COLUMN_ROWS = 7;
48508
+ REGION_LABEL_HALO_RATIO = 4.5;
47211
48509
  LAND_TINT_LIGHT = 12;
47212
48510
  LAND_TINT_DARK = 24;
47213
48511
  TAG_TINT_LIGHT = 60;
47214
48512
  TAG_TINT_DARK = 68;
47215
- WATER_TINT_LIGHT = 13;
47216
- WATER_TINT_DARK = 14;
48513
+ WATER_TINT_LIGHT = 24;
48514
+ WATER_TINT_DARK = 24;
47217
48515
  RIVER_WIDTH = 1.3;
48516
+ COMPACT_WIDTH_PX = 480;
47218
48517
  RELIEF_MIN_AREA = 12;
47219
48518
  RELIEF_MIN_DIM = 2;
47220
- RELIEF_HATCH_SPACING = 3;
47221
- RELIEF_HATCH_WIDTH = 0.25;
48519
+ RELIEF_HATCH_SPACING = 2;
48520
+ RELIEF_HATCH_WIDTH = 0.15;
47222
48521
  RELIEF_HATCH_STRENGTH = 32;
48522
+ COASTLINE_RING_COUNT = 5;
48523
+ COASTLINE_D0 = 16e-4;
48524
+ COASTLINE_STEP = 28e-4;
48525
+ COASTLINE_THICKNESS = 14e-4;
48526
+ COASTLINE_OPACITY_NEAR = 0.5;
48527
+ COASTLINE_OPACITY_FAR = 0.1;
48528
+ COASTLINE_MIN_EXTENT = 6e-4;
48529
+ COASTLINE_MIN_EXTENT_GLOBAL = 6e-4;
48530
+ COASTLINE_STROKE_MIX = 32;
47223
48531
  FOREIGN_TINT_LIGHT = 30;
47224
48532
  FOREIGN_TINT_DARK = 62;
47225
48533
  MUTED_FOREIGN_LIGHT = 28;
47226
48534
  MUTED_FOREIGN_DARK = 16;
47227
48535
  COLO_R = 9;
47228
48536
  GOLDEN_ANGLE = 2.399963229728653;
48537
+ STACK_OVERLAP = 1;
48538
+ STACK_RING_MAX = 8;
48539
+ STACK_RING_GAP = 4;
47229
48540
  FAN_STEP = 16;
47230
48541
  ARC_CURVE_FRAC = 0.18;
48542
+ decodeCache = /* @__PURE__ */ new WeakMap();
47231
48543
  usConusProjection = () => geoConicEqualArea().parallels([29.5, 45.5]).rotate([96, 0]);
47232
48544
  alaskaProjection = () => geoConicEqualArea().rotate([154, 0]).center([-2, 58.5]).parallels([55, 65]);
47233
48545
  hawaiiProjection = () => geoMercator();
47234
48546
  INSET_STATES = /* @__PURE__ */ new Set(["US-AK", "US-HI"]);
48547
+ inAlaska = (lon, lat) => lat >= 51 && (lon <= -129 || lon >= 172);
48548
+ inHawaii = (lon, lat) => lat >= 18 && lat <= 23 && lon >= -161 && lon <= -154;
48549
+ FOREIGN_BORDER = {
48550
+ CA: [
48551
+ "US-AK",
48552
+ "US-WA",
48553
+ "US-ID",
48554
+ "US-MT",
48555
+ "US-ND",
48556
+ "US-MN",
48557
+ "US-MI",
48558
+ "US-NY",
48559
+ "US-VT",
48560
+ "US-NH",
48561
+ "US-ME"
48562
+ ],
48563
+ MX: ["US-CA", "US-AZ", "US-NM", "US-TX"]
48564
+ };
47235
48565
  US_NON_CONUS = /* @__PURE__ */ new Set([
47236
48566
  "US-AK",
47237
48567
  "US-HI",
@@ -47251,6 +48581,58 @@ __export(renderer_exports16, {
47251
48581
  renderMapForExport: () => renderMapForExport
47252
48582
  });
47253
48583
  import * as d3Selection18 from "d3-selection";
48584
+ function pointInRing2(px, py, ring) {
48585
+ let inside = false;
48586
+ for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
48587
+ const [xi, yi] = ring[i];
48588
+ const [xj, yj] = ring[j];
48589
+ if (yi > py !== yj > py && px < (xj - xi) * (py - yi) / (yj - yi) + xi)
48590
+ inside = !inside;
48591
+ }
48592
+ return inside;
48593
+ }
48594
+ function ringToPath(ring) {
48595
+ let d = "";
48596
+ for (let i = 0; i < ring.length; i++)
48597
+ d += (i ? "L" : "M") + ring[i][0] + "," + ring[i][1];
48598
+ return d + "Z";
48599
+ }
48600
+ function coastlineOuterRings(regions, minExtent) {
48601
+ const paths = [];
48602
+ for (const r of regions) {
48603
+ const rings = parsePathRings(r.d);
48604
+ for (let i = 0; i < rings.length; i++) {
48605
+ const ring = rings[i];
48606
+ if (ring.length < 3) continue;
48607
+ let minX = Infinity;
48608
+ let minY = Infinity;
48609
+ let maxX = -Infinity;
48610
+ let maxY = -Infinity;
48611
+ for (const [x, y] of ring) {
48612
+ if (x < minX) minX = x;
48613
+ if (x > maxX) maxX = x;
48614
+ if (y < minY) minY = y;
48615
+ if (y > maxY) maxY = y;
48616
+ }
48617
+ if (Math.max(maxX - minX, maxY - minY) < minExtent) continue;
48618
+ const [fx, fy] = ring[0];
48619
+ let depth = 0;
48620
+ for (let j = 0; j < rings.length; j++)
48621
+ if (j !== i && pointInRing2(fx, fy, rings[j])) depth++;
48622
+ if (depth % 2 === 1) continue;
48623
+ paths.push(ringToPath(ring));
48624
+ }
48625
+ }
48626
+ return paths;
48627
+ }
48628
+ function appendWaterLines(g, outerRings, style, flatWater) {
48629
+ const d = outerRings.join(" ");
48630
+ const linesOuterFirst = [...style.lines].sort((a, b) => b.d - a.d);
48631
+ for (const line12 of linesOuterFirst) {
48632
+ g.append("path").attr("d", d).attr("stroke", style.color).attr("stroke-width", 2 * (line12.d + line12.thickness)).attr("stroke-opacity", line12.opacity).attr("stroke-linejoin", "round").attr("stroke-linecap", "round");
48633
+ g.append("path").attr("d", d).attr("stroke", flatWater).attr("stroke-width", 2 * line12.d).attr("stroke-linejoin", "round").attr("stroke-linecap", "round");
48634
+ }
48635
+ }
47254
48636
  function renderMap(container, resolved, data, palette, isDark, onClickItem, exportDims, activeGroupOverride) {
47255
48637
  d3Selection18.select(container).selectAll(":not([data-d3-tooltip])").remove();
47256
48638
  const width = exportDims?.width ?? container.clientWidth;
@@ -47263,6 +48645,11 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47263
48645
  {
47264
48646
  palette,
47265
48647
  isDark,
48648
+ // Export-only: forward the contain-fit request from mapExportDimensions so a
48649
+ // clamped/floored (off-aspect) export canvas letterboxes instead of
48650
+ // stretch-distorting. The in-app preview pane passes no exportDims → unset →
48651
+ // keeps the global stretch-fill.
48652
+ preferContain: exportDims?.preferContain ?? false,
47266
48653
  ...activeGroupOverride !== void 0 && {
47267
48654
  activeGroup: activeGroupOverride
47268
48655
  }
@@ -47276,6 +48663,7 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47276
48663
  const gRegions = svg.append("g").attr("class", "dgmo-map-regions");
47277
48664
  const drawRegion = (g, r, strokeWidth) => {
47278
48665
  const p = g.append("path").attr("d", r.d).attr("fill", r.fill).attr("stroke", r.stroke).attr("stroke-width", strokeWidth);
48666
+ if (r.label) p.attr("data-region-name", r.label);
47279
48667
  if (r.layer !== "base") {
47280
48668
  p.classed("dgmo-map-region", true).attr("data-region", r.id);
47281
48669
  if (r.value !== void 0) p.attr("data-value", r.value);
@@ -47310,6 +48698,38 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47310
48698
  gRelief.append("line").attr("x1", 0).attr("y1", y).attr("x2", width).attr("y2", y);
47311
48699
  }
47312
48700
  }
48701
+ if (layout.coastlineStyle) {
48702
+ const cs = layout.coastlineStyle;
48703
+ const maskId = "dgmo-map-water-mask";
48704
+ const mask = defs.append("mask").attr("id", maskId).attr("maskUnits", "userSpaceOnUse").attr("x", 0).attr("y", 0).attr("width", width).attr("height", height);
48705
+ mask.append("rect").attr("x", 0).attr("y", 0).attr("width", width).attr("height", height).attr("fill", "white");
48706
+ const landD = layout.regions.filter((r) => r.id !== "lake").map((r) => r.d).join(" ");
48707
+ const lakeD = layout.regions.filter((r) => r.id === "lake").map((r) => r.d).join(" ");
48708
+ if (landD) mask.append("path").attr("d", landD).attr("fill", "black");
48709
+ if (lakeD) mask.append("path").attr("d", lakeD).attr("fill", "white");
48710
+ if (layout.insets.length) {
48711
+ const reach = Math.max(0, ...cs.lines.map((l) => l.d + l.thickness));
48712
+ for (const box of layout.insets) {
48713
+ const d = box.points.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join("") + "Z";
48714
+ mask.append("path").attr("d", d).attr("fill", "black").attr("stroke", "black").attr("stroke-width", 2 * reach).attr("stroke-linejoin", "round");
48715
+ }
48716
+ }
48717
+ const gWater = svg.append("g").attr("class", "dgmo-map-water-lines").attr("fill", "none").attr("mask", `url(#${maskId})`);
48718
+ appendWaterLines(
48719
+ gWater,
48720
+ coastlineOuterRings(layout.regions, cs.minExtent),
48721
+ cs,
48722
+ layout.background
48723
+ );
48724
+ const byStroke = /* @__PURE__ */ new Map();
48725
+ for (const r of layout.regions) {
48726
+ const arr = byStroke.get(r.stroke);
48727
+ if (arr) arr.push(r.d);
48728
+ else byStroke.set(r.stroke, [r.d]);
48729
+ }
48730
+ for (const [stroke2, ds] of byStroke)
48731
+ gWater.append("path").attr("d", ds.join(" ")).attr("stroke", stroke2).attr("stroke-width", 0.5).attr("stroke-linejoin", "round");
48732
+ }
47313
48733
  if (layout.rivers.length) {
47314
48734
  const gRivers = svg.append("g").attr("class", "dgmo-map-rivers").attr("fill", "none");
47315
48735
  for (const r of layout.rivers) {
@@ -47318,15 +48738,61 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47318
48738
  }
47319
48739
  if (layout.insets.length) {
47320
48740
  const insetG = svg.append("g").attr("class", "dgmo-map-insets");
47321
- for (const box of layout.insets) {
48741
+ layout.insets.forEach((box, bi) => {
47322
48742
  const d = box.points.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join("") + "Z";
47323
48743
  insetG.append("path").attr("d", d).attr("fill", layout.background).attr("stroke", mix(palette.text, palette.bg, 55)).attr("stroke-width", 1).attr("stroke-linejoin", "round");
47324
- }
48744
+ if (box.contextLand) {
48745
+ const clipId = `dgmo-map-inset-clip-${bi}`;
48746
+ defs.append("clipPath").attr("id", clipId).append("path").attr("d", d);
48747
+ insetG.append("path").attr("d", box.contextLand.d).attr("fill", box.contextLand.fill).attr("clip-path", `url(#${clipId})`);
48748
+ }
48749
+ });
47325
48750
  for (const r of layout.insetRegions) drawRegion(insetG, r, 0.5);
47326
- }
48751
+ if (layout.coastlineStyle) {
48752
+ const cs = layout.coastlineStyle;
48753
+ const maskId = "dgmo-map-inset-water-mask";
48754
+ const mask = defs.append("mask").attr("id", maskId).attr("maskUnits", "userSpaceOnUse").attr("x", 0).attr("y", 0).attr("width", width).attr("height", height);
48755
+ for (const box of layout.insets) {
48756
+ const d = box.points.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join("") + "Z";
48757
+ mask.append("path").attr("d", d).attr("fill", "white");
48758
+ }
48759
+ layout.insets.forEach((box, bi) => {
48760
+ if (box.contextLand)
48761
+ mask.append("path").attr("d", box.contextLand.d).attr("fill", "black").attr("clip-path", `url(#dgmo-map-inset-clip-${bi})`);
48762
+ });
48763
+ for (const r of layout.insetRegions)
48764
+ if (r.id !== "lake")
48765
+ mask.append("path").attr("d", r.d).attr("fill", "black");
48766
+ for (const r of layout.insetRegions)
48767
+ if (r.id === "lake")
48768
+ mask.append("path").attr("d", r.d).attr("fill", "white");
48769
+ const clipId = "dgmo-map-inset-water-clip";
48770
+ const clip = defs.append("clipPath").attr("id", clipId);
48771
+ for (const box of layout.insets) {
48772
+ const d = box.points.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join("") + "Z";
48773
+ clip.append("path").attr("d", d);
48774
+ }
48775
+ const gInsetWater = insetG.append("g").attr("clip-path", `url(#${clipId})`).append("g").attr("class", "dgmo-map-inset-water-lines").attr("fill", "none").attr("mask", `url(#${maskId})`);
48776
+ appendWaterLines(
48777
+ gInsetWater,
48778
+ coastlineOuterRings(layout.insetRegions, cs.minExtent),
48779
+ cs,
48780
+ layout.background
48781
+ );
48782
+ for (const r of layout.insetRegions)
48783
+ gInsetWater.append("path").attr("d", r.d).attr("stroke", r.stroke).attr("stroke-width", 0.5).attr("stroke-linejoin", "round");
48784
+ }
48785
+ }
48786
+ const wireSync = (sel, lineNumber) => {
48787
+ if (lineNumber < 1) return;
48788
+ sel.attr("data-line-number", lineNumber);
48789
+ if (onClickItem)
48790
+ sel.style("cursor", "pointer").on("click", () => onClickItem(lineNumber));
48791
+ };
47327
48792
  const gLegs = svg.append("g").attr("class", "dgmo-map-legs").attr("fill", "none");
47328
48793
  layout.legs.forEach((leg, i) => {
47329
48794
  const p = gLegs.append("path").attr("d", leg.d).attr("stroke", leg.color).attr("stroke-width", leg.width).attr("stroke-linecap", "round");
48795
+ wireSync(p, leg.lineNumber);
47330
48796
  if (leg.arrow) {
47331
48797
  const id = `dgmo-map-arrow-${i}`;
47332
48798
  const s = arrowSize(leg.width);
@@ -47334,25 +48800,38 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47334
48800
  p.attr("marker-end", `url(#${id})`);
47335
48801
  }
47336
48802
  if (leg.label !== void 0 && leg.labelX !== void 0) {
47337
- emitText(
48803
+ const lt = emitText(
47338
48804
  gLegs,
47339
48805
  leg.labelX,
47340
48806
  leg.labelY ?? 0,
47341
48807
  leg.label,
47342
48808
  "middle",
47343
- palette.textMuted,
47344
- haloColor,
47345
- true,
48809
+ leg.labelColor ?? palette.textMuted,
48810
+ leg.labelHaloColor ?? haloColor,
48811
+ leg.labelHalo ?? true,
47346
48812
  LABEL_FONT - 1
47347
48813
  );
48814
+ wireSync(lt, leg.lineNumber);
47348
48815
  }
47349
48816
  });
48817
+ const gSpider = svg.append("g").attr("class", "dgmo-map-spider");
48818
+ for (const cl of layout.clusters) {
48819
+ if (!exportDims) {
48820
+ gSpider.append("circle").attr("cx", cl.cx).attr("cy", cl.cy).attr("r", cl.hitR).attr("fill", "transparent").attr("data-cluster-hit", cl.id).style("cursor", "pointer");
48821
+ }
48822
+ for (const leg of cl.legs) {
48823
+ gSpider.append("line").attr("x1", cl.cx).attr("y1", cl.cy).attr("x2", leg.x2).attr("y2", leg.y2).attr("stroke", leg.color).attr("stroke-width", 1).attr("data-cluster-deco", cl.id).style("pointer-events", "none");
48824
+ }
48825
+ gSpider.append("circle").attr("cx", cl.cx).attr("cy", cl.cy).attr("r", 2).attr("fill", mix(palette.textMuted, palette.bg, 40)).attr("data-cluster-deco", cl.id).style("pointer-events", "none");
48826
+ }
47350
48827
  const gPois = svg.append("g").attr("class", "dgmo-map-pois");
47351
48828
  for (const poi of layout.pois) {
47352
48829
  if (poi.isOrigin) {
47353
48830
  gPois.append("circle").attr("cx", poi.cx).attr("cy", poi.cy).attr("r", poi.r + 3).attr("fill", "none").attr("stroke", poi.stroke).attr("stroke-width", 1.5);
47354
48831
  }
47355
48832
  const c = gPois.append("circle").attr("cx", poi.cx).attr("cy", poi.cy).attr("r", poi.r).attr("fill", poi.fill).attr("stroke", poi.stroke).attr("stroke-width", 1).attr("data-line-number", poi.lineNumber).attr("data-poi", poi.id);
48833
+ if (poi.clusterId !== void 0)
48834
+ c.attr("data-cluster-member", poi.clusterId);
47356
48835
  if (poi.tags) {
47357
48836
  for (const [group, value] of Object.entries(poi.tags)) {
47358
48837
  c.attr(`data-tag-${group.toLowerCase()}`, value.toLowerCase());
@@ -47380,12 +48859,32 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47380
48859
  }
47381
48860
  const gLabels = svg.append("g").attr("class", "dgmo-map-labels");
47382
48861
  for (const lab of layout.labels) {
48862
+ if (lab.hidden) {
48863
+ if (exportDims) continue;
48864
+ emitText(
48865
+ gLabels,
48866
+ lab.x,
48867
+ lab.y,
48868
+ lab.text,
48869
+ lab.anchor,
48870
+ lab.color,
48871
+ lab.haloColor,
48872
+ lab.halo,
48873
+ LABEL_FONT,
48874
+ lab.italic,
48875
+ lab.letterSpacing
48876
+ ).attr("data-poi", lab.poiId ?? null).attr("data-poi-hidden", "").style("opacity", 0).style("pointer-events", "none");
48877
+ continue;
48878
+ }
47383
48879
  if (lab.leader) {
47384
48880
  const line12 = gLabels.append("line").attr("x1", lab.leader.x1).attr("y1", lab.leader.y1).attr("x2", lab.leader.x2).attr("y2", lab.leader.y2).attr(
47385
48881
  "stroke",
47386
48882
  lab.leaderColor ?? mix(palette.textMuted, palette.bg, 60)
47387
48883
  ).attr("stroke-width", lab.leaderColor ? 1 : 0.75);
47388
48884
  if (lab.poiId !== void 0) line12.attr("data-poi", lab.poiId);
48885
+ if (lab.clusterMember !== void 0)
48886
+ line12.attr("data-cluster-member", lab.clusterMember);
48887
+ wireSync(line12, lab.lineNumber);
47389
48888
  }
47390
48889
  const t = emitText(
47391
48890
  gLabels,
@@ -47396,11 +48895,38 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47396
48895
  lab.color,
47397
48896
  lab.haloColor,
47398
48897
  lab.halo,
47399
- LABEL_FONT
48898
+ LABEL_FONT,
48899
+ lab.italic,
48900
+ lab.letterSpacing,
48901
+ lab.lines
47400
48902
  );
47401
48903
  if (lab.poiId !== void 0) {
47402
48904
  t.attr("data-poi", lab.poiId).style("cursor", "default");
47403
48905
  }
48906
+ if (lab.clusterMember !== void 0) {
48907
+ t.attr("data-cluster-member", lab.clusterMember);
48908
+ }
48909
+ wireSync(t, lab.lineNumber);
48910
+ }
48911
+ if (!exportDims && layout.clusters.length) {
48912
+ const gBadge = svg.append("g").attr("class", "dgmo-map-cluster-badges");
48913
+ for (const cl of layout.clusters) {
48914
+ const g = gBadge.append("g").attr("data-cluster", cl.id).style("opacity", 0).style("pointer-events", "none");
48915
+ const R = 9;
48916
+ g.append("circle").attr("cx", cl.cx).attr("cy", cl.cy).attr("r", R).attr("fill", mix(palette.textMuted, palette.bg, 35)).attr("stroke", palette.textMuted).attr("stroke-width", 1);
48917
+ g.append("circle").attr("cx", cl.cx).attr("cy", cl.cy).attr("r", R + 2.5).attr("fill", "none").attr("stroke", palette.textMuted).attr("stroke-width", 1);
48918
+ emitText(
48919
+ g,
48920
+ cl.cx,
48921
+ cl.cy + 3,
48922
+ String(cl.count),
48923
+ "middle",
48924
+ palette.text,
48925
+ palette.bg,
48926
+ false,
48927
+ LABEL_FONT
48928
+ );
48929
+ }
47404
48930
  }
47405
48931
  if (layout.legend) {
47406
48932
  const legendY = (layout.title ? TITLE_Y + TITLE_FONT_SIZE : 0) + (layout.subtitle ? TITLE_FONT_SIZE : 0) + 8;
@@ -47437,7 +48963,7 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47437
48963
  svg.append("text").attr("class", "dgmo-map-title").attr("x", width / 2).attr("y", TITLE_Y).attr("text-anchor", "middle").attr("font-size", TITLE_FONT_SIZE).attr("font-weight", TITLE_FONT_WEIGHT).attr("fill", palette.text).attr("paint-order", "stroke fill").attr("stroke", palette.bg).attr("stroke-width", 4).attr("stroke-linejoin", "round").attr("stroke-opacity", 0.7).text(layout.title);
47438
48964
  }
47439
48965
  if (layout.subtitle) {
47440
- svg.append("text").attr("x", width / 2).attr("y", TITLE_Y + TITLE_FONT_SIZE).attr("text-anchor", "middle").attr("font-size", LABEL_FONT + 1).attr("fill", palette.textMuted).attr("paint-order", "stroke fill").attr("stroke", palette.bg).attr("stroke-width", 3).attr("stroke-linejoin", "round").attr("stroke-opacity", 0.7).text(layout.subtitle);
48966
+ svg.append("text").attr("class", "dgmo-map-subtitle").attr("x", width / 2).attr("y", TITLE_Y + TITLE_FONT_SIZE).attr("text-anchor", "middle").attr("font-size", LABEL_FONT + 1).attr("fill", palette.textMuted).attr("paint-order", "stroke fill").attr("stroke", palette.bg).attr("stroke-width", 3).attr("stroke-linejoin", "round").attr("stroke-opacity", 0.7).text(layout.subtitle);
47441
48967
  }
47442
48968
  if (layout.caption) {
47443
48969
  svg.append("text").attr("x", width / 2).attr("y", height - 8).attr("text-anchor", "middle").attr("font-size", LABEL_FONT).attr("fill", palette.textMuted).attr("paint-order", "stroke fill").attr("stroke", palette.bg).attr("stroke-width", 3).attr("stroke-linejoin", "round").attr("stroke-opacity", 0.7).text(layout.caption);
@@ -47446,10 +48972,21 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47446
48972
  function renderMapForExport(container, resolved, data, palette, isDark, exportDims) {
47447
48973
  renderMap(container, resolved, data, palette, isDark, void 0, exportDims);
47448
48974
  }
47449
- function emitText(g, x, y, text, anchor, color, halo, withHalo, fontSize) {
47450
- const t = g.append("text").attr("x", x).attr("y", y).attr("text-anchor", anchor).attr("font-size", fontSize).attr("fill", color).text(text);
48975
+ function emitText(g, x, y, text, anchor, color, halo, withHalo, fontSize, italic, letterSpacing, lines) {
48976
+ const t = g.append("text").attr("x", x).attr("y", y).attr("text-anchor", anchor).attr("font-size", fontSize).attr("fill", color);
48977
+ if (lines && lines.length > 1) {
48978
+ const lineHeight = fontSize + 2;
48979
+ const startDy = -((lines.length - 1) / 2) * lineHeight;
48980
+ lines.forEach((ln, i) => {
48981
+ t.append("tspan").attr("x", x).attr("dy", i === 0 ? startDy : lineHeight).text(ln);
48982
+ });
48983
+ } else {
48984
+ t.text(text);
48985
+ }
48986
+ if (italic) t.attr("font-style", "italic");
48987
+ if (letterSpacing) t.attr("letter-spacing", letterSpacing);
47451
48988
  if (withHalo) {
47452
- t.attr("paint-order", "stroke fill").attr("stroke", halo).attr("stroke-width", 3).attr("stroke-linejoin", "round").attr("stroke-opacity", 0.7);
48989
+ t.attr("paint-order", "stroke fill").attr("stroke", halo).attr("stroke-width", 2).attr("stroke-linejoin", "round").attr("stroke-linecap", "round").attr("stroke-opacity", 0.55);
47453
48990
  }
47454
48991
  return t;
47455
48992
  }
@@ -47466,6 +49003,56 @@ var init_renderer16 = __esm({
47466
49003
  }
47467
49004
  });
47468
49005
 
49006
+ // src/map/dimensions.ts
49007
+ var dimensions_exports = {};
49008
+ __export(dimensions_exports, {
49009
+ mapContentAspect: () => mapContentAspect,
49010
+ mapExportDimensions: () => mapExportDimensions
49011
+ });
49012
+ import { geoPath as geoPath2 } from "d3-geo";
49013
+ function mapContentAspect(resolved, data, ref = REF) {
49014
+ const { projection, fitTarget } = buildMapProjection(resolved, data);
49015
+ projection.fitSize([ref, ref], fitTarget);
49016
+ const b = geoPath2(projection).bounds(fitTarget);
49017
+ const w = b[1][0] - b[0][0];
49018
+ const h = b[1][1] - b[0][1];
49019
+ const aspect = w / h;
49020
+ return Number.isFinite(aspect) && aspect > 0 ? aspect : FALLBACK_ASPECT;
49021
+ }
49022
+ function mapExportDimensions(resolved, data, baseWidth = 1200) {
49023
+ const raw = mapContentAspect(resolved, data);
49024
+ const clamped = Math.max(ASPECT_MIN, Math.min(ASPECT_MAX, raw));
49025
+ const width = baseWidth;
49026
+ let height = Math.round(width / clamped);
49027
+ let chromeReserve = 0;
49028
+ if (resolved.title && resolved.pois.length > 0) {
49029
+ const bannerBottom = (resolved.subtitle ? TITLE_Y + TITLE_FONT_SIZE : TITLE_Y) + TITLE_FONT_SIZE / 2;
49030
+ chromeReserve += Math.max(FIT_PAD2, bannerBottom + TITLE_GAP) - FIT_PAD2;
49031
+ }
49032
+ let floored = false;
49033
+ if (height - chromeReserve < MIN_MAP_BAND) {
49034
+ height = Math.round(chromeReserve + MIN_MAP_BAND);
49035
+ floored = true;
49036
+ }
49037
+ const preferContain = clamped !== raw || floored;
49038
+ return { width, height, preferContain };
49039
+ }
49040
+ var FIT_PAD2, TITLE_GAP, ASPECT_MAX, ASPECT_MIN, MIN_MAP_BAND, FALLBACK_ASPECT, REF;
49041
+ var init_dimensions = __esm({
49042
+ "src/map/dimensions.ts"() {
49043
+ "use strict";
49044
+ init_title_constants();
49045
+ init_layout15();
49046
+ FIT_PAD2 = 24;
49047
+ TITLE_GAP = 16;
49048
+ ASPECT_MAX = 3;
49049
+ ASPECT_MIN = 0.9;
49050
+ MIN_MAP_BAND = 200;
49051
+ FALLBACK_ASPECT = 1.5;
49052
+ REF = 1e3;
49053
+ }
49054
+ });
49055
+
47469
49056
  // src/map/load-data.ts
47470
49057
  var load_data_exports = {};
47471
49058
  __export(load_data_exports, {
@@ -47524,12 +49111,17 @@ function loadMapData() {
47524
49111
  mountainRanges,
47525
49112
  naLand,
47526
49113
  naLakes,
49114
+ waterBodies,
47527
49115
  gazetteer
47528
49116
  ] = await Promise.all([
49117
+ // worldCoarse (110m) is LOAD-BEARING but NOT a render source: the world
49118
+ // basemap renders from worldDetail (50m) at all scales (resolver pins
49119
+ // basemaps.world = 'detail'). Coarse stays as the authoritative region
49120
+ // name index + dominant-landmass bbox source in resolver.ts. Do not drop it.
47529
49121
  readJson(nb, dir, FILES.worldCoarse),
47530
49122
  readJson(nb, dir, FILES.worldDetail),
47531
49123
  readJson(nb, dir, FILES.usStates),
47532
- // Lakes/rivers/mountain/NA assets are optional — older bundles may predate them.
49124
+ // Lakes/rivers/mountain/NA/water assets are optional — older bundles may predate them.
47533
49125
  readJson(nb, dir, FILES.lakes).catch(() => void 0),
47534
49126
  readJson(nb, dir, FILES.rivers).catch(() => void 0),
47535
49127
  readJson(nb, dir, FILES.mountainRanges).catch(
@@ -47537,6 +49129,7 @@ function loadMapData() {
47537
49129
  ),
47538
49130
  readJson(nb, dir, FILES.naLand).catch(() => void 0),
47539
49131
  readJson(nb, dir, FILES.naLakes).catch(() => void 0),
49132
+ readJson(nb, dir, FILES.waterBodies).catch(() => void 0),
47540
49133
  readJson(nb, dir, FILES.gazetteer)
47541
49134
  ]);
47542
49135
  return validate({
@@ -47548,7 +49141,8 @@ function loadMapData() {
47548
49141
  ...rivers && { rivers },
47549
49142
  ...mountainRanges && { mountainRanges },
47550
49143
  ...naLand && { naLand },
47551
- ...naLakes && { naLakes }
49144
+ ...naLakes && { naLakes },
49145
+ ...waterBodies && { waterBodies }
47552
49146
  });
47553
49147
  })().catch((e) => {
47554
49148
  cache = void 0;
@@ -47569,6 +49163,7 @@ var init_load_data = __esm({
47569
49163
  mountainRanges: "mountain-ranges.json",
47570
49164
  naLand: "na-land.json",
47571
49165
  naLakes: "na-lakes.json",
49166
+ waterBodies: "water-bodies.json",
47572
49167
  gazetteer: "gazetteer.json"
47573
49168
  };
47574
49169
  CANDIDATE_DIRS = [
@@ -49582,8 +51177,8 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
49582
51177
  const lines = splitParticipantLabel(p.label, LABEL_MAX_CHARS);
49583
51178
  if (lines.length === 0) continue;
49584
51179
  const widest = Math.max(...lines.map((l) => l.length));
49585
- const labelWidth = widest * LABEL_CHAR_WIDTH + 10;
49586
- uniformBoxWidth = Math.max(uniformBoxWidth, labelWidth);
51180
+ const labelWidth2 = widest * LABEL_CHAR_WIDTH + 10;
51181
+ uniformBoxWidth = Math.max(uniformBoxWidth, labelWidth2);
49587
51182
  }
49588
51183
  uniformBoxWidth = Math.min(MAX_BOX_WIDTH, uniformBoxWidth);
49589
51184
  const effectiveGap = Math.max(PARTICIPANT_GAP, uniformBoxWidth + 30);
@@ -52282,15 +53877,15 @@ function renderArcDiagram(container, parsed, palette, _isDark, onClickItem, expo
52282
53877
  textColor,
52283
53878
  onClickItem
52284
53879
  );
52285
- const neighbors = /* @__PURE__ */ new Map();
52286
- for (const node of nodes) neighbors.set(node, /* @__PURE__ */ new Set());
53880
+ const neighbors2 = /* @__PURE__ */ new Map();
53881
+ for (const node of nodes) neighbors2.set(node, /* @__PURE__ */ new Set());
52287
53882
  for (const link of links) {
52288
- neighbors.get(link.source).add(link.target);
52289
- neighbors.get(link.target).add(link.source);
53883
+ neighbors2.get(link.source).add(link.target);
53884
+ neighbors2.get(link.target).add(link.source);
52290
53885
  }
52291
53886
  const FADE_OPACITY3 = 0.1;
52292
53887
  function handleMouseEnter(hovered) {
52293
- const connected = neighbors.get(hovered);
53888
+ const connected = neighbors2.get(hovered);
52294
53889
  g.selectAll(".arc-link").each(function() {
52295
53890
  const el = d3Selection23.select(this);
52296
53891
  const src = el.attr("data-source");
@@ -54225,7 +55820,7 @@ function renderVenn(container, parsed, palette, _isDark, onClickItem, exportDims
54225
55820
  8,
54226
55821
  Math.floor(OVERLAP_WRAP_TARGET_W / OVERLAP_CH_W)
54227
55822
  );
54228
- function wrapLabel2(text, maxChars) {
55823
+ function wrapLabel3(text, maxChars) {
54229
55824
  const words = text.split(/\s+/).filter(Boolean);
54230
55825
  const lines = [];
54231
55826
  let cur = "";
@@ -54271,7 +55866,7 @@ function renderVenn(container, parsed, palette, _isDark, onClickItem, exportDims
54271
55866
  if (!ov.label) continue;
54272
55867
  const idxs = ov.sets.map((s) => vennSets.findIndex((vs) => vs.name === s));
54273
55868
  if (idxs.some((idx) => idx < 0)) continue;
54274
- const lines = wrapLabel2(ov.label, MAX_WRAP_CHARS);
55869
+ const lines = wrapLabel3(ov.label, MAX_WRAP_CHARS);
54275
55870
  wrappedOverlapLabels.set(ov, lines);
54276
55871
  const dir = predictOverlapDirRaw(idxs);
54277
55872
  const longest = lines.reduce((m, l) => Math.max(m, l.length), 0);
@@ -55709,6 +57304,7 @@ async function renderForExport(content, theme, palette, viewState, options) {
55709
57304
  const { parseMap: parseMap2 } = await Promise.resolve().then(() => (init_parser12(), parser_exports11));
55710
57305
  const { resolveMap: resolveMap2 } = await Promise.resolve().then(() => (init_resolver2(), resolver_exports));
55711
57306
  const { renderMapForExport: renderMapForExport2 } = await Promise.resolve().then(() => (init_renderer16(), renderer_exports16));
57307
+ const { mapExportDimensions: mapExportDimensions2 } = await Promise.resolve().then(() => (init_dimensions(), dimensions_exports));
55712
57308
  const effectivePalette2 = await resolveExportPalette(theme, palette);
55713
57309
  const mapParsed = parseMap2(content);
55714
57310
  let mapData = options?.mapData;
@@ -55721,14 +57317,15 @@ async function renderForExport(content, theme, palette, viewState, options) {
55721
57317
  }
55722
57318
  }
55723
57319
  const mapResolved = resolveMap2(mapParsed, mapData);
55724
- const container2 = createExportContainer(EXPORT_WIDTH, EXPORT_HEIGHT);
57320
+ const dims2 = mapExportDimensions2(mapResolved, mapData, EXPORT_WIDTH);
57321
+ const container2 = createExportContainer(dims2.width, dims2.height);
55725
57322
  renderMapForExport2(
55726
57323
  container2,
55727
57324
  mapResolved,
55728
57325
  mapData,
55729
57326
  effectivePalette2,
55730
57327
  theme === "dark",
55731
- { width: EXPORT_WIDTH, height: EXPORT_HEIGHT }
57328
+ dims2
55732
57329
  );
55733
57330
  return finalizeSvgExport(container2, theme, effectivePalette2);
55734
57331
  }
@@ -56570,7 +58167,8 @@ async function render(content, options) {
56570
58167
  ...options?.c4Container !== void 0 && {
56571
58168
  c4Container: options.c4Container
56572
58169
  },
56573
- ...options?.tagGroup !== void 0 && { tagGroup: options.tagGroup }
58170
+ ...options?.tagGroup !== void 0 && { tagGroup: options.tagGroup },
58171
+ ...options?.mapData !== void 0 && { mapData: options.mapData }
56574
58172
  });
56575
58173
  if (chartType === "map") {
56576
58174
  try {
@@ -56581,7 +58179,7 @@ async function render(content, options) {
56581
58179
  Promise.resolve().then(() => (init_load_data(), load_data_exports))
56582
58180
  ]
56583
58181
  );
56584
- const data = await loadMapData2();
58182
+ const data = options?.mapData ?? await loadMapData2();
56585
58183
  diagnostics = [...resolveMap2(parseMap2(content), data).diagnostics];
56586
58184
  } catch {
56587
58185
  }
@@ -56749,22 +58347,20 @@ var DIRECTIVE_KEYWORDS = /* @__PURE__ */ new Set([
56749
58347
  // Sequence
56750
58348
  "activations",
56751
58349
  "no-activations",
56752
- // Map (§24B) directives
56753
- "region",
56754
- "projection",
58350
+ // Map (§24B) directives — cosmetics on by default, bare `no-*` opt-outs
56755
58351
  "region-metric",
56756
58352
  "poi-metric",
56757
58353
  "flow-metric",
56758
- "region-labels",
56759
- "poi-labels",
56760
- "default-country",
56761
- "default-state",
56762
- "no-legend",
56763
- "no-insets",
56764
- "muted",
56765
- "natural",
56766
- "subtitle",
58354
+ "locale",
58355
+ "active-tag",
56767
58356
  "caption",
58357
+ "no-legend",
58358
+ "no-coastline",
58359
+ "no-relief",
58360
+ "no-context-labels",
58361
+ "no-region-labels",
58362
+ "no-poi-labels",
58363
+ "no-colorize",
56768
58364
  "poi",
56769
58365
  "route",
56770
58366
  // Data charts
@@ -57057,7 +58653,11 @@ var ATTRIBUTE_KEYS = /* @__PURE__ */ new Set([
57057
58653
  "collapsed",
57058
58654
  "tech",
57059
58655
  "span",
57060
- "split"
58656
+ "split",
58657
+ // Map (§24B) reserved keys
58658
+ "value",
58659
+ "label",
58660
+ "style"
57061
58661
  ]);
57062
58662
  function applyAttributeKeys(tokens) {
57063
58663
  for (let i = 0; i < tokens.length - 1; i++) {
@@ -57430,7 +59030,7 @@ pre.dgmo, code.language-dgmo, pre > code.language-dgmo,
57430
59030
 
57431
59031
  // src/auto/index.ts
57432
59032
  init_safe_href();
57433
- var VERSION = "0.21.1";
59033
+ var VERSION = "0.22.0";
57434
59034
  var DEFAULTS = {
57435
59035
  theme: "auto",
57436
59036
  palette: "nord",