@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.cjs CHANGED
@@ -93,18 +93,18 @@ function computeQuadrantPointLabels(points, chartBounds, obstacles, pointRadius,
93
93
  const results = [];
94
94
  for (let i = 0; i < points.length; i++) {
95
95
  const pt = points[i];
96
- const labelWidth = pt.label.length * fontSize * CHAR_WIDTH_RATIO + 8;
96
+ const labelWidth2 = pt.label.length * fontSize * CHAR_WIDTH_RATIO + 8;
97
97
  let best = null;
98
98
  const directions = [
99
99
  {
100
100
  // Above
101
101
  gen: (offset) => {
102
- const lx = pt.cx - labelWidth / 2;
102
+ const lx = pt.cx - labelWidth2 / 2;
103
103
  const ly = pt.cy - offset - labelHeight;
104
- if (ly < chartBounds.top || lx < chartBounds.left || lx + labelWidth > chartBounds.right)
104
+ if (ly < chartBounds.top || lx < chartBounds.left || lx + labelWidth2 > chartBounds.right)
105
105
  return null;
106
106
  return {
107
- rect: { x: lx, y: ly, w: labelWidth, h: labelHeight },
107
+ rect: { x: lx, y: ly, w: labelWidth2, h: labelHeight },
108
108
  textX: pt.cx,
109
109
  textY: ly + labelHeight / 2,
110
110
  anchor: "middle"
@@ -114,12 +114,12 @@ function computeQuadrantPointLabels(points, chartBounds, obstacles, pointRadius,
114
114
  {
115
115
  // Below
116
116
  gen: (offset) => {
117
- const lx = pt.cx - labelWidth / 2;
117
+ const lx = pt.cx - labelWidth2 / 2;
118
118
  const ly = pt.cy + offset;
119
- if (ly + labelHeight > chartBounds.bottom || lx < chartBounds.left || lx + labelWidth > chartBounds.right)
119
+ if (ly + labelHeight > chartBounds.bottom || lx < chartBounds.left || lx + labelWidth2 > chartBounds.right)
120
120
  return null;
121
121
  return {
122
- rect: { x: lx, y: ly, w: labelWidth, h: labelHeight },
122
+ rect: { x: lx, y: ly, w: labelWidth2, h: labelHeight },
123
123
  textX: pt.cx,
124
124
  textY: ly + labelHeight / 2,
125
125
  anchor: "middle"
@@ -131,10 +131,10 @@ function computeQuadrantPointLabels(points, chartBounds, obstacles, pointRadius,
131
131
  gen: (offset) => {
132
132
  const lx = pt.cx + offset;
133
133
  const ly = pt.cy - labelHeight / 2;
134
- if (lx + labelWidth > chartBounds.right || ly < chartBounds.top || ly + labelHeight > chartBounds.bottom)
134
+ if (lx + labelWidth2 > chartBounds.right || ly < chartBounds.top || ly + labelHeight > chartBounds.bottom)
135
135
  return null;
136
136
  return {
137
- rect: { x: lx, y: ly, w: labelWidth, h: labelHeight },
137
+ rect: { x: lx, y: ly, w: labelWidth2, h: labelHeight },
138
138
  textX: lx,
139
139
  textY: pt.cy,
140
140
  anchor: "start"
@@ -144,13 +144,13 @@ function computeQuadrantPointLabels(points, chartBounds, obstacles, pointRadius,
144
144
  {
145
145
  // Left
146
146
  gen: (offset) => {
147
- const lx = pt.cx - offset - labelWidth;
147
+ const lx = pt.cx - offset - labelWidth2;
148
148
  const ly = pt.cy - labelHeight / 2;
149
149
  if (lx < chartBounds.left || ly < chartBounds.top || ly + labelHeight > chartBounds.bottom)
150
150
  return null;
151
151
  return {
152
- rect: { x: lx, y: ly, w: labelWidth, h: labelHeight },
153
- textX: lx + labelWidth,
152
+ rect: { x: lx, y: ly, w: labelWidth2, h: labelHeight },
153
+ textX: lx + labelWidth2,
154
154
  textY: pt.cy,
155
155
  anchor: "end"
156
156
  };
@@ -200,10 +200,10 @@ function computeQuadrantPointLabels(points, chartBounds, obstacles, pointRadius,
200
200
  }
201
201
  }
202
202
  if (!best) {
203
- const lx = pt.cx - labelWidth / 2;
203
+ const lx = pt.cx - labelWidth2 / 2;
204
204
  const ly = pt.cy - minGap - labelHeight;
205
205
  best = {
206
- rect: { x: lx, y: ly, w: labelWidth, h: labelHeight },
206
+ rect: { x: lx, y: ly, w: labelWidth2, h: labelHeight },
207
207
  textX: pt.cx,
208
208
  textY: ly + labelHeight / 2,
209
209
  anchor: "middle",
@@ -763,6 +763,9 @@ var init_reserved_key_registry = __esm({
763
763
  "value",
764
764
  "label",
765
765
  "style"
766
+ // `surface:` was removed in the 2026-06-02 defaults-on review — it is no longer
767
+ // a recognized metadata key (the route/edge surface feature was cut; §24B.7).
768
+ // A stray `surface: water` is no longer captured as a reserved key.
766
769
  ]);
767
770
  ORG_REGISTRY = staticRegistry([
768
771
  "color",
@@ -1825,77 +1828,266 @@ function getSegmentColors(palette, count) {
1825
1828
  (_, i) => hslToHex(Math.round((startHue + i * step) % 360), avgS, avgL)
1826
1829
  );
1827
1830
  }
1831
+ function politicalTints(palette, count, isDark) {
1832
+ if (count <= 0) return [];
1833
+ const base = isDark ? palette.surface : palette.bg;
1834
+ const c = palette.colors;
1835
+ const swatches = [
1836
+ .../* @__PURE__ */ new Set([
1837
+ c.green,
1838
+ c.yellow,
1839
+ c.orange,
1840
+ c.purple,
1841
+ c.red,
1842
+ c.teal,
1843
+ c.cyan,
1844
+ c.blue
1845
+ ])
1846
+ ];
1847
+ const bands = isDark ? POLITICAL_TINT_BANDS.dark : POLITICAL_TINT_BANDS.light;
1848
+ const out = [];
1849
+ for (const pct of bands) {
1850
+ if (out.length >= count) break;
1851
+ for (const s of swatches) out.push(mix(s, base, pct));
1852
+ }
1853
+ return out.slice(0, count);
1854
+ }
1855
+ var POLITICAL_TINT_BANDS;
1828
1856
  var init_color_utils = __esm({
1829
1857
  "src/palettes/color-utils.ts"() {
1830
1858
  "use strict";
1859
+ POLITICAL_TINT_BANDS = {
1860
+ light: [32, 48, 64, 80],
1861
+ dark: [44, 58, 72, 86]
1862
+ };
1831
1863
  }
1832
1864
  });
1833
1865
 
1834
- // src/palettes/bold.ts
1835
- var boldPalette;
1836
- var init_bold = __esm({
1837
- "src/palettes/bold.ts"() {
1866
+ // src/palettes/atlas.ts
1867
+ var atlasPalette;
1868
+ var init_atlas = __esm({
1869
+ "src/palettes/atlas.ts"() {
1838
1870
  "use strict";
1839
1871
  init_registry();
1840
- boldPalette = {
1841
- id: "bold",
1842
- name: "Bold",
1872
+ atlasPalette = {
1873
+ id: "atlas",
1874
+ name: "Atlas",
1843
1875
  light: {
1844
- bg: "#ffffff",
1845
- surface: "#f0f0f0",
1846
- overlay: "#f0f0f0",
1847
- border: "#cccccc",
1848
- text: "#000000",
1849
- textMuted: "#666666",
1850
- textOnFillLight: "#ffffff",
1851
- textOnFillDark: "#000000",
1852
- primary: "#0000ff",
1853
- secondary: "#ff00ff",
1854
- accent: "#00cccc",
1855
- destructive: "#ff0000",
1876
+ bg: "#f3ead3",
1877
+ // warm manila / parchment
1878
+ surface: "#ece0c0",
1879
+ // deeper paper (cards, panels)
1880
+ overlay: "#e8dab8",
1881
+ // popovers, dropdowns
1882
+ border: "#bcaa86",
1883
+ // muted sepia rule line
1884
+ text: "#463a26",
1885
+ // aged sepia-brown ink
1886
+ textMuted: "#7a6a4f",
1887
+ // faded annotation ink
1888
+ textOnFillLight: "#f7f1de",
1889
+ // parchment (light text on dark fills)
1890
+ textOnFillDark: "#3a2e1c",
1891
+ // deep ink (dark text on light fills)
1892
+ primary: "#5b7a99",
1893
+ // pull-down map ocean (steel-blue)
1894
+ secondary: "#7e9a6f",
1895
+ // lowland sage / celadon
1896
+ accent: "#b07f7c",
1897
+ // dusty rose
1898
+ destructive: "#b25a45",
1899
+ // brick / terracotta
1856
1900
  colors: {
1857
- red: "#ff0000",
1858
- orange: "#ff8000",
1859
- yellow: "#ffcc00",
1860
- green: "#00cc00",
1861
- blue: "#0000ff",
1862
- purple: "#cc00cc",
1863
- teal: "#008080",
1864
- cyan: "#00cccc",
1865
- gray: "#808080",
1866
- black: "#000000",
1867
- white: "#f0f0f0"
1901
+ red: "#bf6a52",
1902
+ // terracotta brick
1903
+ orange: "#cf9a5c",
1904
+ // map tan / ochre
1905
+ yellow: "#cdb35e",
1906
+ // straw / muted lemon
1907
+ green: "#7e9a6f",
1908
+ // sage / celadon lowland
1909
+ blue: "#5b7a99",
1910
+ // steel-blue ocean
1911
+ purple: "#9a7fa6",
1912
+ // dusty lilac / mauve
1913
+ teal: "#6fa094",
1914
+ // muted seafoam
1915
+ cyan: "#79a7b5",
1916
+ // shallow-water blue
1917
+ gray: "#8a7d68",
1918
+ // warm taupe
1919
+ black: "#463a26",
1920
+ // ink
1921
+ white: "#ece0c0"
1922
+ // paper
1868
1923
  }
1869
1924
  },
1870
1925
  dark: {
1871
- bg: "#000000",
1872
- surface: "#111111",
1873
- overlay: "#1a1a1a",
1874
- border: "#333333",
1875
- text: "#ffffff",
1876
- textMuted: "#aaaaaa",
1877
- textOnFillLight: "#ffffff",
1878
- textOnFillDark: "#000000",
1879
- primary: "#00ccff",
1880
- secondary: "#ff00ff",
1881
- accent: "#ffff00",
1882
- destructive: "#ff0000",
1926
+ bg: "#1e2a33",
1927
+ // deep map ocean (night globe)
1928
+ surface: "#27353f",
1929
+ // raised ocean
1930
+ overlay: "#2e3d48",
1931
+ // popovers, dropdowns
1932
+ border: "#3d4f5c",
1933
+ // depth-contour line
1934
+ text: "#e8dcc0",
1935
+ // parchment ink, inverted
1936
+ textMuted: "#a89a7d",
1937
+ // faded label
1938
+ textOnFillLight: "#f7f1de",
1939
+ // parchment
1940
+ textOnFillDark: "#1a242c",
1941
+ // deep ocean ink
1942
+ primary: "#7ba0bf",
1943
+ // brighter ocean
1944
+ secondary: "#9bb588",
1945
+ // sage, lifted
1946
+ accent: "#cf9a96",
1947
+ // dusty rose, lifted
1948
+ destructive: "#c9745c",
1949
+ // brick, lifted
1883
1950
  colors: {
1884
- red: "#ff0000",
1885
- orange: "#ff8000",
1886
- yellow: "#ffff00",
1887
- green: "#00ff00",
1888
- blue: "#0066ff",
1889
- purple: "#ff00ff",
1890
- teal: "#00cccc",
1891
- cyan: "#00ffff",
1892
- gray: "#808080",
1893
- black: "#111111",
1894
- white: "#ffffff"
1951
+ red: "#cf7a60",
1952
+ // terracotta
1953
+ orange: "#d9a96a",
1954
+ // tan / ochre
1955
+ yellow: "#d8c074",
1956
+ // straw
1957
+ green: "#9bb588",
1958
+ // sage lowland
1959
+ blue: "#7ba0bf",
1960
+ // ocean
1961
+ purple: "#b59ac0",
1962
+ // lilac / mauve
1963
+ teal: "#85b3a6",
1964
+ // seafoam
1965
+ cyan: "#92bccb",
1966
+ // shallow-water blue
1967
+ gray: "#9a8d76",
1968
+ // warm taupe
1969
+ black: "#27353f",
1970
+ // raised ocean
1971
+ white: "#e8dcc0"
1972
+ // parchment
1895
1973
  }
1896
1974
  }
1897
1975
  };
1898
- registerPalette(boldPalette);
1976
+ registerPalette(atlasPalette);
1977
+ }
1978
+ });
1979
+
1980
+ // src/palettes/blueprint.ts
1981
+ var blueprintPalette;
1982
+ var init_blueprint = __esm({
1983
+ "src/palettes/blueprint.ts"() {
1984
+ "use strict";
1985
+ init_registry();
1986
+ blueprintPalette = {
1987
+ id: "blueprint",
1988
+ name: "Blueprint",
1989
+ light: {
1990
+ bg: "#f4f8fb",
1991
+ // pale drafting white (faint cyan)
1992
+ surface: "#e6eef4",
1993
+ // drafting panel
1994
+ overlay: "#dde9f1",
1995
+ // popovers, dropdowns
1996
+ border: "#aac3d6",
1997
+ // pale blue grid line
1998
+ text: "#123a5e",
1999
+ // blueprint navy ink
2000
+ textMuted: "#4f7390",
2001
+ // faint draft note
2002
+ textOnFillLight: "#f4f8fb",
2003
+ // drafting white
2004
+ textOnFillDark: "#0c2f4d",
2005
+ // deep blueprint navy
2006
+ primary: "#1f5e8c",
2007
+ // blueprint blue
2008
+ secondary: "#5b7d96",
2009
+ // steel
2010
+ accent: "#b08a3e",
2011
+ // draftsman's ochre highlight
2012
+ destructive: "#c0504d",
2013
+ // correction red
2014
+ colors: {
2015
+ red: "#c25a4e",
2016
+ // correction red
2017
+ orange: "#c2823e",
2018
+ // ochre
2019
+ yellow: "#c2a843",
2020
+ // pencil gold
2021
+ green: "#4f8a6b",
2022
+ // drafting green
2023
+ blue: "#1f5e8c",
2024
+ // blueprint blue
2025
+ purple: "#6f5e96",
2026
+ // indigo pencil
2027
+ teal: "#3a8a8a",
2028
+ // teal
2029
+ cyan: "#3f8fb5",
2030
+ // cyan
2031
+ gray: "#7e8e98",
2032
+ // graphite
2033
+ black: "#123a5e",
2034
+ // navy ink
2035
+ white: "#e6eef4"
2036
+ // panel
2037
+ }
2038
+ },
2039
+ dark: {
2040
+ bg: "#103a5e",
2041
+ // deep blueprint blue (cyanotype ground)
2042
+ surface: "#16466e",
2043
+ // raised sheet
2044
+ overlay: "#1c5180",
2045
+ // popovers, dropdowns
2046
+ border: "#3a6f96",
2047
+ // grid line
2048
+ text: "#eaf2f8",
2049
+ // chalk white
2050
+ textMuted: "#9fc0d6",
2051
+ // faint chalk note
2052
+ textOnFillLight: "#eaf2f8",
2053
+ // chalk white
2054
+ textOnFillDark: "#0c2f4d",
2055
+ // deep blueprint navy
2056
+ primary: "#7fb8d8",
2057
+ // chalk cyan
2058
+ secondary: "#9fb8c8",
2059
+ // pale steel
2060
+ accent: "#d8c27a",
2061
+ // chalk amber
2062
+ destructive: "#e08a7a",
2063
+ // chalk correction red
2064
+ colors: {
2065
+ red: "#e0907e",
2066
+ // chalk red
2067
+ orange: "#e0ab78",
2068
+ // chalk amber
2069
+ yellow: "#e3d089",
2070
+ // chalk gold
2071
+ green: "#93c79e",
2072
+ // chalk green
2073
+ blue: "#8ec3e0",
2074
+ // chalk cyan-blue
2075
+ purple: "#b6a6d8",
2076
+ // chalk indigo
2077
+ teal: "#84c7c2",
2078
+ // chalk teal
2079
+ cyan: "#9fd6e0",
2080
+ // chalk cyan
2081
+ gray: "#aebecb",
2082
+ // chalk graphite
2083
+ black: "#16466e",
2084
+ // raised sheet
2085
+ white: "#eaf2f8"
2086
+ // chalk white
2087
+ }
2088
+ }
2089
+ };
2090
+ registerPalette(blueprintPalette);
1899
2091
  }
1900
2092
  });
1901
2093
 
@@ -2392,6 +2584,120 @@ var init_rose_pine = __esm({
2392
2584
  }
2393
2585
  });
2394
2586
 
2587
+ // src/palettes/slate.ts
2588
+ var slatePalette;
2589
+ var init_slate = __esm({
2590
+ "src/palettes/slate.ts"() {
2591
+ "use strict";
2592
+ init_registry();
2593
+ slatePalette = {
2594
+ id: "slate",
2595
+ name: "Slate",
2596
+ light: {
2597
+ bg: "#ffffff",
2598
+ // clean slide white
2599
+ surface: "#f3f5f8",
2600
+ // light cool-gray panel
2601
+ overlay: "#eaeef3",
2602
+ // popovers, dropdowns
2603
+ border: "#d4dae1",
2604
+ // hairline rule
2605
+ text: "#1f2933",
2606
+ // near-black slate (softer than pure black)
2607
+ textMuted: "#5b6672",
2608
+ // secondary label
2609
+ textOnFillLight: "#ffffff",
2610
+ // light text on dark fills
2611
+ textOnFillDark: "#1f2933",
2612
+ // dark text on light fills
2613
+ primary: "#3b6ea5",
2614
+ // confident corporate blue
2615
+ secondary: "#5b6672",
2616
+ // slate gray
2617
+ accent: "#3a9188",
2618
+ // muted teal accent
2619
+ destructive: "#c0504d",
2620
+ // brick red
2621
+ colors: {
2622
+ red: "#c0504d",
2623
+ // brick
2624
+ orange: "#cc7a33",
2625
+ // muted amber
2626
+ yellow: "#c9a227",
2627
+ // gold (not neon)
2628
+ green: "#5b9357",
2629
+ // forest / sage
2630
+ blue: "#3b6ea5",
2631
+ // corporate blue
2632
+ purple: "#7d5ba6",
2633
+ // muted violet
2634
+ teal: "#3a9188",
2635
+ // teal
2636
+ cyan: "#4f96c4",
2637
+ // steel cyan
2638
+ gray: "#7e8a97",
2639
+ // cool gray
2640
+ black: "#1f2933",
2641
+ // slate ink
2642
+ white: "#f3f5f8"
2643
+ // panel
2644
+ }
2645
+ },
2646
+ dark: {
2647
+ bg: "#161b22",
2648
+ // deep slate (keynote dark)
2649
+ surface: "#202833",
2650
+ // raised panel
2651
+ overlay: "#29323e",
2652
+ // popovers, dropdowns
2653
+ border: "#38424f",
2654
+ // divider
2655
+ text: "#e6eaef",
2656
+ // off-white
2657
+ textMuted: "#9aa5b1",
2658
+ // secondary label
2659
+ textOnFillLight: "#ffffff",
2660
+ // light text on dark fills
2661
+ textOnFillDark: "#161b22",
2662
+ // dark text on light fills
2663
+ primary: "#5b9bd5",
2664
+ // lifted corporate blue
2665
+ secondary: "#8593a3",
2666
+ // slate gray, lifted
2667
+ accent: "#45b3a3",
2668
+ // teal, lifted
2669
+ destructive: "#e07b6e",
2670
+ // brick, lifted
2671
+ colors: {
2672
+ red: "#e07b6e",
2673
+ // brick
2674
+ orange: "#e0975a",
2675
+ // amber
2676
+ yellow: "#d9bd5a",
2677
+ // gold
2678
+ green: "#74b56e",
2679
+ // forest / sage
2680
+ blue: "#5b9bd5",
2681
+ // corporate blue
2682
+ purple: "#a585c9",
2683
+ // violet
2684
+ teal: "#45b3a3",
2685
+ // teal
2686
+ cyan: "#62b0d9",
2687
+ // steel cyan
2688
+ gray: "#95a1ae",
2689
+ // cool gray
2690
+ black: "#202833",
2691
+ // raised panel
2692
+ white: "#e6eaef"
2693
+ // off-white
2694
+ }
2695
+ }
2696
+ };
2697
+ registerPalette(slatePalette);
2698
+ }
2699
+ });
2700
+
2395
2701
  // src/palettes/solarized.ts
2396
2702
  var solarizedPalette;
2397
2703
  var init_solarized = __esm({
@@ -2487,6 +2793,120 @@ var init_solarized = __esm({
2487
2793
  }
2488
2794
  });
2489
2795
 
2796
+ // src/palettes/tidewater.ts
2797
+ var tidewaterPalette;
2798
+ var init_tidewater = __esm({
2799
+ "src/palettes/tidewater.ts"() {
2800
+ "use strict";
2801
+ init_registry();
2802
+ tidewaterPalette = {
2803
+ id: "tidewater",
2804
+ name: "Tidewater",
2805
+ light: {
2806
+ bg: "#eceff0",
2807
+ // weathered sea-mist paper
2808
+ surface: "#e0e4e3",
2809
+ // worn deck panel
2810
+ overlay: "#dadfdf",
2811
+ // popovers, dropdowns
2812
+ border: "#a9b2b3",
2813
+ // muted slate rule
2814
+ text: "#18313f",
2815
+ // ship's-log navy ink
2816
+ textMuted: "#51636b",
2817
+ // faded log entry
2818
+ textOnFillLight: "#f3f5f3",
2819
+ // weathered white
2820
+ textOnFillDark: "#162c38",
2821
+ // deep navy
2822
+ primary: "#1f4e6b",
2823
+ // deep-sea navy
2824
+ secondary: "#b08a4f",
2825
+ // rope / manila tan
2826
+ accent: "#c69a3e",
2827
+ // brass
2828
+ destructive: "#c1433a",
2829
+ // signal-flag red
2830
+ colors: {
2831
+ red: "#c1433a",
2832
+ // signal-flag red
2833
+ orange: "#cc7a38",
2834
+ // weathered amber
2835
+ yellow: "#d6bf5a",
2836
+ // brass gold
2837
+ green: "#4f8a6b",
2838
+ // sea-glass green
2839
+ blue: "#1f4e6b",
2840
+ // deep-sea navy
2841
+ purple: "#6a5a8c",
2842
+ // twilight harbor
2843
+ teal: "#3d8c8c",
2844
+ // sea-glass teal
2845
+ cyan: "#4f9bb5",
2846
+ // shallow water
2847
+ gray: "#8a8d86",
2848
+ // driftwood gray
2849
+ black: "#18313f",
2850
+ // navy ink
2851
+ white: "#e0e4e3"
2852
+ // deck panel
2853
+ }
2854
+ },
2855
+ dark: {
2856
+ bg: "#0f2230",
2857
+ // night-harbor deep sea
2858
+ surface: "#16303f",
2859
+ // raised hull
2860
+ overlay: "#1d3a4a",
2861
+ // popovers, dropdowns
2862
+ border: "#2c4856",
2863
+ // rigging line
2864
+ text: "#e6ebe8",
2865
+ // weathered white
2866
+ textMuted: "#9aaab0",
2867
+ // faded label
2868
+ textOnFillLight: "#f3f5f3",
2869
+ // weathered white
2870
+ textOnFillDark: "#0f2230",
2871
+ // deep sea
2872
+ primary: "#4f9bc4",
2873
+ // lifted sea blue
2874
+ secondary: "#c9a46a",
2875
+ // rope tan, lifted
2876
+ accent: "#d9b25a",
2877
+ // brass, lifted
2878
+ destructive: "#e06a5e",
2879
+ // signal red, lifted
2880
+ colors: {
2881
+ red: "#e06a5e",
2882
+ // signal-flag red
2883
+ orange: "#df9a52",
2884
+ // amber
2885
+ yellow: "#e0c662",
2886
+ // brass gold
2887
+ green: "#6fb58c",
2888
+ // sea-glass green
2889
+ blue: "#4f9bc4",
2890
+ // sea blue
2891
+ purple: "#9486bf",
2892
+ // twilight harbor
2893
+ teal: "#5cb0ac",
2894
+ // sea-glass teal
2895
+ cyan: "#62b4cf",
2896
+ // shallow water
2897
+ gray: "#9aa39c",
2898
+ // driftwood gray
2899
+ black: "#16303f",
2900
+ // raised hull
2901
+ white: "#e6ebe8"
2902
+ // weathered white
2903
+ }
2904
+ }
2905
+ };
2906
+ registerPalette(tidewaterPalette);
2907
+ }
2908
+ });
2909
+
2490
2910
  // src/palettes/tokyo-night.ts
2491
2911
  var tokyoNightPalette;
2492
2912
  var init_tokyo_night = __esm({
@@ -2762,7 +3182,8 @@ var init_monokai = __esm({
2762
3182
  // src/palettes/index.ts
2763
3183
  var palettes_exports = {};
2764
3184
  __export(palettes_exports, {
2765
- boldPalette: () => boldPalette,
3185
+ atlasPalette: () => atlasPalette,
3186
+ blueprintPalette: () => blueprintPalette,
2766
3187
  catppuccinPalette: () => catppuccinPalette,
2767
3188
  contrastText: () => contrastText,
2768
3189
  draculaPalette: () => draculaPalette,
@@ -2783,7 +3204,9 @@ __export(palettes_exports, {
2783
3204
  rosePinePalette: () => rosePinePalette,
2784
3205
  shade: () => shade,
2785
3206
  shapeFill: () => shapeFill,
3207
+ slatePalette: () => slatePalette,
2786
3208
  solarizedPalette: () => solarizedPalette,
3209
+ tidewaterPalette: () => tidewaterPalette,
2787
3210
  tint: () => tint,
2788
3211
  tokyoNightPalette: () => tokyoNightPalette
2789
3212
  });
@@ -2793,17 +3216,21 @@ var init_palettes = __esm({
2793
3216
  "use strict";
2794
3217
  init_registry();
2795
3218
  init_color_utils();
2796
- init_bold();
3219
+ init_atlas();
3220
+ init_blueprint();
2797
3221
  init_catppuccin();
2798
3222
  init_gruvbox();
2799
3223
  init_nord();
2800
3224
  init_one_dark();
2801
3225
  init_rose_pine();
3226
+ init_slate();
2802
3227
  init_solarized();
3228
+ init_tidewater();
2803
3229
  init_tokyo_night();
2804
3230
  init_dracula();
2805
3231
  init_monokai();
2806
- init_bold();
3232
+ init_atlas();
3233
+ init_blueprint();
2807
3234
  init_catppuccin();
2808
3235
  init_dracula();
2809
3236
  init_gruvbox();
@@ -2811,9 +3238,15 @@ var init_palettes = __esm({
2811
3238
  init_nord();
2812
3239
  init_one_dark();
2813
3240
  init_rose_pine();
3241
+ init_slate();
2814
3242
  init_solarized();
3243
+ init_tidewater();
2815
3244
  init_tokyo_night();
2816
3245
  palettes = {
3246
+ atlas: atlasPalette,
3247
+ blueprint: blueprintPalette,
3248
+ slate: slatePalette,
3249
+ tidewater: tidewaterPalette,
2817
3250
  nord: nordPalette,
2818
3251
  catppuccin: catppuccinPalette,
2819
3252
  solarized: solarizedPalette,
@@ -2822,8 +3255,7 @@ var init_palettes = __esm({
2822
3255
  oneDark: oneDarkPalette,
2823
3256
  rosePine: rosePinePalette,
2824
3257
  dracula: draculaPalette,
2825
- monokai: monokaiPalette,
2826
- bold: boldPalette
3258
+ monokai: monokaiPalette
2827
3259
  };
2828
3260
  }
2829
3261
  });
@@ -3333,6 +3765,9 @@ function controlsGroupCapsuleWidth(toggles) {
3333
3765
  }
3334
3766
  return w;
3335
3767
  }
3768
+ function isAppHostedControls(config, isExport) {
3769
+ return !isExport && config.controlsHost === "app" && !!config.controlsGroup && config.controlsGroup.toggles.length > 0;
3770
+ }
3336
3771
  function buildControlsGroupLayout(config, state) {
3337
3772
  const cg = config.controlsGroup;
3338
3773
  if (!cg || cg.toggles.length === 0) return void 0;
@@ -3386,6 +3821,7 @@ function buildControlsGroupLayout(config, state) {
3386
3821
  function computeLegendLayout(config, state, containerWidth) {
3387
3822
  const { groups, controls: configControls, mode } = config;
3388
3823
  const isExport = mode === "export";
3824
+ const gated = isAppHostedControls(config, isExport);
3389
3825
  const activeGroupName = state.activeGroup?.toLowerCase() ?? null;
3390
3826
  if (isExport && !activeGroupName) {
3391
3827
  return {
@@ -3396,7 +3832,7 @@ function computeLegendLayout(config, state, containerWidth) {
3396
3832
  pills: []
3397
3833
  };
3398
3834
  }
3399
- const controlsGroupLayout = isExport ? void 0 : buildControlsGroupLayout(config, state);
3835
+ const controlsGroupLayout = isExport || gated ? void 0 : buildControlsGroupLayout(config, state);
3400
3836
  const visibleGroups = config.showEmptyGroups ? groups : groups.filter((g) => g.entries.length > 0 || !!g.gradient);
3401
3837
  if (visibleGroups.length === 0 && (!configControls || configControls.length === 0) && !controlsGroupLayout) {
3402
3838
  return {
@@ -8276,8 +8712,8 @@ function computeScatterLabelGraphics(points, chartBounds, fontSize, symbolSize,
8276
8712
  const pt = points[i];
8277
8713
  const ptSize = pt.size ?? symbolSize;
8278
8714
  const minGap = ptSize / 2 + 4;
8279
- const labelWidth = pt.name.length * fontSize * 0.6 + 8;
8280
- const labelX = pt.px - labelWidth / 2;
8715
+ const labelWidth2 = pt.name.length * fontSize * 0.6 + 8;
8716
+ const labelX = pt.px - labelWidth2 / 2;
8281
8717
  let bestLabelY = 0;
8282
8718
  let bestOffset = Infinity;
8283
8719
  let placed = false;
@@ -8289,7 +8725,7 @@ function computeScatterLabelGraphics(points, chartBounds, fontSize, symbolSize,
8289
8725
  const candidate = {
8290
8726
  x: labelX,
8291
8727
  y: labelY,
8292
- w: labelWidth,
8728
+ w: labelWidth2,
8293
8729
  h: labelHeight
8294
8730
  };
8295
8731
  let collision = false;
@@ -8331,7 +8767,7 @@ function computeScatterLabelGraphics(points, chartBounds, fontSize, symbolSize,
8331
8767
  const labelRect = {
8332
8768
  x: labelX,
8333
8769
  y: bestLabelY,
8334
- w: labelWidth,
8770
+ w: labelWidth2,
8335
8771
  h: labelHeight
8336
8772
  };
8337
8773
  placedLabels.push(labelRect);
@@ -8367,7 +8803,7 @@ function computeScatterLabelGraphics(points, chartBounds, fontSize, symbolSize,
8367
8803
  shape: {
8368
8804
  x: labelX - bgPad,
8369
8805
  y: bestLabelY - bgPad,
8370
- width: labelWidth + bgPad * 2,
8806
+ width: labelWidth2 + bgPad * 2,
8371
8807
  height: labelHeight + bgPad * 2
8372
8808
  },
8373
8809
  style: { fill: bg },
@@ -15807,10 +16243,6 @@ function parseMap(content) {
15807
16243
  handleTag(trimmed, lineNumber);
15808
16244
  continue;
15809
16245
  }
15810
- if ((firstWord === "muted" || firstWord === "natural") && trimmed === firstWord) {
15811
- handleDirective(firstWord, "", lineNumber);
15812
- continue;
15813
- }
15814
16246
  if (DIRECTIVE_SET.has(firstWord) && !trimmed.slice(firstWord.length).trimStart().startsWith(":")) {
15815
16247
  handleDirective(
15816
16248
  firstWord,
@@ -15857,24 +16289,6 @@ function parseMap(content) {
15857
16289
  pushWarning(line12, `Duplicate directive "${key}" \u2014 last value wins.`);
15858
16290
  };
15859
16291
  switch (key) {
15860
- case "region":
15861
- dup(d.region);
15862
- d.region = value;
15863
- break;
15864
- case "projection":
15865
- dup(d.projection);
15866
- if (value && ![
15867
- "equirectangular",
15868
- "natural-earth",
15869
- "albers-usa",
15870
- "mercator"
15871
- ].includes(value))
15872
- pushWarning(
15873
- line12,
15874
- `Unknown projection "${value}" (expected equirectangular | natural-earth | albers-usa | mercator).`
15875
- );
15876
- d.projection = value;
15877
- break;
15878
16292
  case "region-metric": {
15879
16293
  dup(d.regionMetric);
15880
16294
  const { label: rmLabel, colorName: rmColor } = peelTrailingColorName(value);
@@ -15890,91 +16304,43 @@ function parseMap(content) {
15890
16304
  dup(d.flowMetric);
15891
16305
  d.flowMetric = value;
15892
16306
  break;
15893
- case "scale":
15894
- dup(d.scale);
15895
- {
15896
- const s = parseScale(value, line12);
15897
- if (s) d.scale = s;
15898
- }
15899
- break;
15900
- case "region-labels":
15901
- dup(d.regionLabels);
15902
- if (value && !["full", "abbrev", "off"].includes(value))
15903
- pushWarning(
15904
- line12,
15905
- `Unknown region-labels "${value}" (expected full | abbrev | off).`
15906
- );
15907
- d.regionLabels = value;
15908
- break;
15909
- case "poi-labels":
15910
- dup(d.poiLabels);
15911
- if (value && !["off", "auto", "all"].includes(value))
15912
- pushWarning(
15913
- line12,
15914
- `Unknown poi-labels "${value}" (expected off | auto | all).`
15915
- );
15916
- d.poiLabels = value;
15917
- break;
15918
- case "default-country":
15919
- dup(d.defaultCountry);
15920
- d.defaultCountry = value;
15921
- break;
15922
- case "default-state":
15923
- dup(d.defaultState);
15924
- d.defaultState = value;
16307
+ case "locale":
16308
+ dup(d.locale);
16309
+ d.locale = value;
15925
16310
  break;
15926
16311
  case "active-tag":
15927
16312
  dup(d.activeTag);
15928
16313
  d.activeTag = value;
15929
16314
  break;
16315
+ case "caption":
16316
+ dup(d.caption);
16317
+ d.caption = value;
16318
+ break;
16319
+ // ── Cosmetic `no-*` opt-outs: bare flags, idempotent (mirror `no-legend`,
16320
+ // no dup warning); each defaults the feature ON when absent. ──
15930
16321
  case "no-legend":
15931
16322
  d.noLegend = true;
15932
16323
  break;
15933
- case "no-insets":
15934
- d.noInsets = true;
16324
+ case "no-coastline":
16325
+ d.noCoastline = true;
15935
16326
  break;
15936
- case "relief":
15937
- d.relief = true;
16327
+ case "no-relief":
16328
+ d.noRelief = true;
15938
16329
  break;
15939
- case "muted":
15940
- case "natural":
15941
- if (d.basemapStyle !== void 0 && d.basemapStyle !== key)
15942
- pushWarning(
15943
- line12,
15944
- `Conflicting basemap dress \u2014 "${d.basemapStyle}" then "${key}"; last wins.`
15945
- );
15946
- d.basemapStyle = key;
16330
+ case "no-context-labels":
16331
+ d.noContextLabels = true;
15947
16332
  break;
15948
- case "subtitle":
15949
- dup(d.subtitle);
15950
- d.subtitle = value;
16333
+ case "no-region-labels":
16334
+ d.noRegionLabels = true;
15951
16335
  break;
15952
- case "caption":
15953
- dup(d.caption);
15954
- d.caption = value;
16336
+ case "no-poi-labels":
16337
+ d.noPoiLabels = true;
16338
+ break;
16339
+ case "no-colorize":
16340
+ d.noColorize = true;
15955
16341
  break;
15956
16342
  }
15957
16343
  }
15958
- function parseScale(value, line12) {
15959
- const toks = value.split(/\s+/).filter(Boolean);
15960
- const min = Number(toks[0]);
15961
- const max = Number(toks[1]);
15962
- if (!Number.isFinite(min) || !Number.isFinite(max)) {
15963
- pushError(line12, `scale requires numeric <min> <max> (got "${value}").`);
15964
- return null;
15965
- }
15966
- const scale = { min, max };
15967
- if (toks[2] === "center") {
15968
- const c = Number(toks[3]);
15969
- if (Number.isFinite(c)) scale.center = c;
15970
- else
15971
- pushError(
15972
- line12,
15973
- `scale center requires a number (got "${toks[3] ?? ""}").`
15974
- );
15975
- }
15976
- return scale;
15977
- }
15978
16344
  function handleTag(trimmed, line12) {
15979
16345
  const m = matchTagBlockHeading(trimmed);
15980
16346
  if (!m) {
@@ -16174,13 +16540,15 @@ function parseMap(content) {
16174
16540
  pushError(line12, `Edge has an empty endpoint: "${trimmed}".`);
16175
16541
  continue;
16176
16542
  }
16177
- const meta = k === links.length - 1 ? lastSplit.meta : {};
16543
+ const isLast = k === links.length - 1;
16544
+ const meta = isLast ? lastSplit.meta : {};
16545
+ const style = links[k].style === "arc" ? "arc" : "straight";
16178
16546
  edges.push({
16179
16547
  from,
16180
16548
  to,
16181
16549
  ...links[k].label !== void 0 && { label: links[k].label },
16182
16550
  directed: links[k].directed,
16183
- style: links[k].style,
16551
+ style,
16184
16552
  meta,
16185
16553
  lineNumber: line12
16186
16554
  });
@@ -16266,22 +16634,19 @@ var init_parser12 = __esm({
16266
16634
  LEG_ARROW_RE = /^(-[^>]*?->|->|~[^>]*?~>|~>|--)\s+(.+)$/;
16267
16635
  AT_RE = /(^|[\s,])at\s*:/i;
16268
16636
  DIRECTIVE_SET = /* @__PURE__ */ new Set([
16269
- "region",
16270
- "projection",
16271
16637
  "region-metric",
16272
16638
  "poi-metric",
16273
16639
  "flow-metric",
16274
- "scale",
16275
- "region-labels",
16276
- "poi-labels",
16277
- "default-country",
16278
- "default-state",
16640
+ "locale",
16279
16641
  "active-tag",
16642
+ "caption",
16280
16643
  "no-legend",
16281
- "no-insets",
16282
- "relief",
16283
- "subtitle",
16284
- "caption"
16644
+ "no-coastline",
16645
+ "no-relief",
16646
+ "no-context-labels",
16647
+ "no-region-labels",
16648
+ "no-poi-labels",
16649
+ "no-colorize"
16285
16650
  ]);
16286
16651
  }
16287
16652
  });
@@ -24202,8 +24567,8 @@ function renderKanban(container, parsed, palette, isDark, options) {
24202
24567
  let metaY = separatorY + sCardSeparatorGap + sCardMetaFontSize;
24203
24568
  for (const meta of tagMeta) {
24204
24569
  cg.append("text").attr("x", cx + sCardPaddingX).attr("y", metaY).attr("font-size", sCardMetaFontSize).attr("fill", onCardText).text(`${meta.label}: `);
24205
- const labelWidth = (meta.label.length + 2) * sCardMetaFontSize * 0.6;
24206
- cg.append("text").attr("x", cx + sCardPaddingX + labelWidth).attr("y", metaY).attr("font-size", sCardMetaFontSize).attr("fill", onCardText).text(meta.value);
24570
+ const labelWidth2 = (meta.label.length + 2) * sCardMetaFontSize * 0.6;
24571
+ cg.append("text").attr("x", cx + sCardPaddingX + labelWidth2).attr("y", metaY).attr("font-size", sCardMetaFontSize).attr("fill", onCardText).text(meta.value);
24207
24572
  metaY += sCardMetaLineHeight;
24208
24573
  }
24209
24574
  for (const detail of card.details) {
@@ -24547,8 +24912,8 @@ function renderSwimlaneCard(parent, cardLayout, tagGroups, activeTagGroup, palet
24547
24912
  let metaY = separatorY + sCardSeparatorGap + sCardMetaFontSize;
24548
24913
  for (const meta of tagMeta) {
24549
24914
  cg.append("text").attr("x", cx + sCardPaddingX).attr("y", metaY).attr("font-size", sCardMetaFontSize).attr("fill", palette.textMuted).text(`${meta.label}: `);
24550
- const labelWidth = (meta.label.length + 2) * sCardMetaFontSize * 0.6;
24551
- cg.append("text").attr("x", cx + sCardPaddingX + labelWidth).attr("y", metaY).attr("font-size", sCardMetaFontSize).attr("fill", onCardText).text(meta.value);
24915
+ const labelWidth2 = (meta.label.length + 2) * sCardMetaFontSize * 0.6;
24916
+ cg.append("text").attr("x", cx + sCardPaddingX + labelWidth2).attr("y", metaY).attr("font-size", sCardMetaFontSize).attr("fill", onCardText).text(meta.value);
24552
24917
  metaY += sCardMetaLineHeight;
24553
24918
  }
24554
24919
  for (const detail of card.details) {
@@ -25383,8 +25748,8 @@ function classifyEREntities(tables, relationships) {
25383
25748
  }
25384
25749
  }
25385
25750
  const mmParticipants = /* @__PURE__ */ new Set();
25386
- for (const [id, neighbors] of tableStarNeighbors) {
25387
- if (neighbors.size >= 2) mmParticipants.add(id);
25751
+ for (const [id, neighbors2] of tableStarNeighbors) {
25752
+ if (neighbors2.size >= 2) mmParticipants.add(id);
25388
25753
  }
25389
25754
  const indegreeValues = Object.values(indegreeMap);
25390
25755
  const mean = indegreeValues.reduce((a, b) => a + b, 0) / indegreeValues.length;
@@ -26055,7 +26420,8 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26055
26420
  controlsExpanded,
26056
26421
  onToggleDescriptions,
26057
26422
  onToggleControlsExpand,
26058
- exportMode = false
26423
+ exportMode = false,
26424
+ controlsHost
26059
26425
  } = options ?? {};
26060
26426
  d3Selection6.select(container).selectAll(":not([data-d3-tooltip])").remove();
26061
26427
  const width = exportDims?.width ?? container.clientWidth;
@@ -26073,7 +26439,11 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26073
26439
  const sGroupLabelZone = sctx.structural(GROUP_LABEL_ZONE);
26074
26440
  const sTitleFontSize = sctx.text(TITLE_FONT_SIZE);
26075
26441
  const sTitleY = sctx.structural(TITLE_Y);
26076
- const sLegendHeight = sctx.structural(
26442
+ const reserveHasDescriptions = parsed.nodes.some(
26443
+ (n) => n.description && n.description.length > 0
26444
+ );
26445
+ const willRenderLegend = parsed.tagGroups.length > 0 || reserveHasDescriptions && controlsHost !== "app";
26446
+ const sLegendHeight = willRenderLegend ? sctx.structural(
26077
26447
  getMaxLegendReservedHeight(
26078
26448
  {
26079
26449
  groups: parsed.tagGroups,
@@ -26082,7 +26452,7 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26082
26452
  },
26083
26453
  width
26084
26454
  )
26085
- );
26455
+ ) : 0;
26086
26456
  const activeGroup = resolveActiveTagGroup(
26087
26457
  parsed.tagGroups,
26088
26458
  parsed.options["active-tag"],
@@ -26397,10 +26767,10 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26397
26767
  const hasDescriptions = parsed.nodes.some(
26398
26768
  (n) => n.description && n.description.length > 0
26399
26769
  );
26400
- const hasLegend = parsed.tagGroups.length > 0 || hasDescriptions;
26770
+ const hasLegend = parsed.tagGroups.length > 0 || hasDescriptions && controlsHost !== "app";
26401
26771
  if (hasLegend) {
26402
26772
  let controlsGroup;
26403
- if (hasDescriptions && onToggleDescriptions) {
26773
+ if (hasDescriptions && (onToggleDescriptions || controlsHost === "app")) {
26404
26774
  controlsGroup = {
26405
26775
  toggles: [
26406
26776
  {
@@ -26418,7 +26788,14 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26418
26788
  groups: parsed.tagGroups,
26419
26789
  position: { placement: "top-center", titleRelation: "below-title" },
26420
26790
  mode: exportMode ? "export" : "preview",
26421
- ...controlsGroup !== void 0 && { controlsGroup }
26791
+ // Keep inactive sibling tag groups visible as collapsed pills so the user
26792
+ // can click one to flip the active colouring dimension (preview only —
26793
+ // export shows just the active group). Without this, declaring a second
26794
+ // tag group (e.g. Team) leaves it invisible whenever another group is
26795
+ // active. The app's BoxesAndLinesPreview already wires pill clicks.
26796
+ showInactivePills: true,
26797
+ ...controlsGroup !== void 0 && { controlsGroup },
26798
+ ...controlsHost !== void 0 && { controlsHost }
26422
26799
  };
26423
26800
  const legendState = {
26424
26801
  activeGroup,
@@ -27666,8 +28043,9 @@ function renderMindmap(container, parsed, layout, palette, isDark, onClickItem,
27666
28043
  const containerHeight = exportDims?.height ?? (container.getBoundingClientRect().height || 600);
27667
28044
  d3Selection7.select(container).selectAll("*").remove();
27668
28045
  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);
28046
+ const appHosted = options?.controlsHost === "app";
27669
28047
  const hasControls = !!options?.onToggleColorByDepth || !!options?.onToggleDescriptions;
27670
- const hasLegend = parsed.tagGroups.length > 0 || hasControls;
28048
+ const hasLegend = parsed.tagGroups.length > 0 || hasControls && !appHosted;
27671
28049
  const fixedLegend = !isExport && hasLegend;
27672
28050
  const legendReserve = fixedLegend ? getMaxLegendReservedHeight(
27673
28051
  {
@@ -27761,7 +28139,10 @@ function renderMindmap(container, parsed, layout, palette, isDark, onClickItem,
27761
28139
  }),
27762
28140
  position: { placement: "top-center", titleRelation: "below-title" },
27763
28141
  mode: options?.exportMode ? "export" : "preview",
27764
- ...controlsToggles !== void 0 && { controlsGroup: controlsToggles }
28142
+ ...controlsToggles !== void 0 && { controlsGroup: controlsToggles },
28143
+ ...options?.controlsHost !== void 0 && {
28144
+ controlsHost: options.controlsHost
28145
+ }
27765
28146
  };
27766
28147
  const legendState = {
27767
28148
  activeGroup: options?.colorByDepth ? null : activeTagGroup !== void 0 ? activeTagGroup : parsed.options["active-tag"] ?? null,
@@ -28205,8 +28586,8 @@ function computeFieldAlignX(children) {
28205
28586
  for (const child of children) {
28206
28587
  if (child.metadata["_labelField"] === "true" && child.children.length >= 2) {
28207
28588
  const labelEl = child.children[0];
28208
- const labelWidth = labelEl.label.length * CHAR_WIDTH5;
28209
- maxLabelWidth = Math.max(maxLabelWidth, labelWidth);
28589
+ const labelWidth2 = labelEl.label.length * CHAR_WIDTH5;
28590
+ maxLabelWidth = Math.max(maxLabelWidth, labelWidth2);
28210
28591
  labelFieldCount++;
28211
28592
  }
28212
28593
  }
@@ -33170,7 +33551,7 @@ function hasRoles(node) {
33170
33551
  function computeNodeWidth2(node, expanded, options) {
33171
33552
  const badgeVal = node.computedConcurrentInvocations === 0 && node.computedInstances > 1 ? node.computedInstances : 0;
33172
33553
  const badgeLen = badgeVal > 0 ? `${badgeVal}x`.length + 2 : 0;
33173
- const labelWidth = (node.label.length + badgeLen) * CHAR_WIDTH7 + PADDING_X3;
33554
+ const labelWidth2 = (node.label.length + badgeLen) * CHAR_WIDTH7 + PADDING_X3;
33174
33555
  const allKeys = [];
33175
33556
  if (node.computedRps > 0) allKeys.push("RPS");
33176
33557
  if (expanded) {
@@ -33214,7 +33595,7 @@ function computeNodeWidth2(node, expanded, options) {
33214
33595
  allKeys.push("overflow");
33215
33596
  }
33216
33597
  }
33217
- if (allKeys.length === 0) return Math.max(MIN_NODE_WIDTH2, labelWidth);
33598
+ if (allKeys.length === 0) return Math.max(MIN_NODE_WIDTH2, labelWidth2);
33218
33599
  const maxKeyLen = Math.max(...allKeys.map((k) => k.length));
33219
33600
  let maxRowWidth = 0;
33220
33601
  if (node.computedRps > 0) {
@@ -33302,7 +33683,7 @@ function computeNodeWidth2(node, expanded, options) {
33302
33683
  truncated.length * META_CHAR_WIDTH3 + PADDING_X3
33303
33684
  );
33304
33685
  }
33305
- return Math.max(MIN_NODE_WIDTH2, labelWidth, maxRowWidth + 20, descWidth);
33686
+ return Math.max(MIN_NODE_WIDTH2, labelWidth2, maxRowWidth + 20, descWidth);
33306
33687
  }
33307
33688
  function computeNodeHeight2(node, expanded, options) {
33308
33689
  const propCount = countDisplayProps(node, expanded, options);
@@ -34851,8 +35232,9 @@ function computeInfraLegendGroups(nodes, tagGroups, palette, edges) {
34851
35232
  }
34852
35233
  return groups;
34853
35234
  }
34854
- function renderLegend3(rootSvg, legendGroups, totalWidth, legendY, palette, isDark, activeGroup, playback, exportMode = false) {
35235
+ function renderLegend3(rootSvg, legendGroups, totalWidth, legendY, palette, isDark, activeGroup, playback, exportMode = false, controlsHost) {
34855
35236
  if (legendGroups.length === 0 && !playback) return;
35237
+ const appHostedPlayback = controlsHost === "app" && !!playback;
34856
35238
  const legendG = rootSvg.append("g").attr("transform", `translate(0, ${legendY})`);
34857
35239
  if (activeGroup) {
34858
35240
  legendG.attr("data-legend-active", activeGroup.toLowerCase());
@@ -34861,14 +35243,29 @@ function renderLegend3(rootSvg, legendGroups, totalWidth, legendY, palette, isDa
34861
35243
  name: g.name,
34862
35244
  entries: g.entries.map((e) => ({ value: e.value, color: e.color }))
34863
35245
  }));
34864
- if (playback) {
35246
+ if (playback && !appHostedPlayback) {
34865
35247
  allGroups.push({ name: "Playback", entries: [] });
34866
35248
  }
34867
35249
  const legendConfig = {
34868
35250
  groups: allGroups,
34869
35251
  position: { placement: "top-center", titleRelation: "below-title" },
34870
35252
  mode: exportMode ? "export" : "preview",
34871
- showEmptyGroups: true
35253
+ showEmptyGroups: true,
35254
+ ...appHostedPlayback && {
35255
+ controlsHost: "app",
35256
+ controlsGroup: {
35257
+ toggles: [
35258
+ {
35259
+ id: "playback",
35260
+ type: "toggle",
35261
+ label: "Playback",
35262
+ active: true,
35263
+ onToggle: () => {
35264
+ }
35265
+ }
35266
+ ]
35267
+ }
35268
+ }
34872
35269
  };
34873
35270
  const legendState = { activeGroup };
34874
35271
  renderLegendD3(
@@ -34919,8 +35316,9 @@ function renderLegend3(rootSvg, legendGroups, totalWidth, legendY, palette, isDa
34919
35316
  }
34920
35317
  }
34921
35318
  }
34922
- function renderInfra(container, layout, palette, isDark, title, titleLineNumber, tagGroups, activeGroup, animate, playback, expandedNodeIds, exportMode, collapsedNodes) {
35319
+ function renderInfra(container, layout, palette, isDark, title, titleLineNumber, tagGroups, activeGroup, animate, playback, expandedNodeIds, exportMode, collapsedNodes, controlsHost) {
34923
35320
  d3Selection11.select(container).selectAll(":not([data-d3-tooltip])").remove();
35321
+ const appHostedPlayback = controlsHost === "app" && !!playback;
34924
35322
  const ctx = ScaleContext.identity();
34925
35323
  const sc = buildScaledConstants(ctx);
34926
35324
  const legendGroups = computeInfraLegendGroups(
@@ -34929,7 +35327,7 @@ function renderInfra(container, layout, palette, isDark, title, titleLineNumber,
34929
35327
  palette,
34930
35328
  layout.edges
34931
35329
  );
34932
- const hasLegend = legendGroups.length > 0 || !!playback;
35330
+ const hasLegend = legendGroups.length > 0 || !!playback && !appHostedPlayback;
34933
35331
  const fixedLegend = !exportMode && hasLegend;
34934
35332
  const legendDynamicH = hasLegend ? getMaxLegendReservedHeight(
34935
35333
  {
@@ -35073,7 +35471,8 @@ function renderInfra(container, layout, palette, isDark, title, titleLineNumber,
35073
35471
  isDark,
35074
35472
  activeGroup ?? null,
35075
35473
  playback ?? void 0,
35076
- exportMode
35474
+ exportMode,
35475
+ controlsHost
35077
35476
  );
35078
35477
  legendSvg.selectAll(".infra-legend-group").style("pointer-events", "auto");
35079
35478
  } else {
@@ -35086,7 +35485,8 @@ function renderInfra(container, layout, palette, isDark, title, titleLineNumber,
35086
35485
  isDark,
35087
35486
  activeGroup ?? null,
35088
35487
  playback ?? void 0,
35089
- exportMode
35488
+ exportMode,
35489
+ controlsHost
35090
35490
  );
35091
35491
  }
35092
35492
  }
@@ -42721,6 +43121,9 @@ function renderTechRadar(container, parsed, palette, isDark, onClickItem, export
42721
43121
  onToggle: (active) => options.onToggleListing(active)
42722
43122
  }
42723
43123
  ]
43124
+ },
43125
+ ...options.controlsHost !== void 0 && {
43126
+ controlsHost: options.controlsHost
42724
43127
  }
42725
43128
  };
42726
43129
  const legendState = {
@@ -44544,7 +44947,7 @@ function computeCycleLayout(parsed, options) {
44544
44947
  const circleNodes = parsed.options["circle-nodes"] === "true";
44545
44948
  const nodeDims = parsed.nodes.map((node) => {
44546
44949
  const hasDesc = !hideDescriptions && node.description.length > 0;
44547
- const labelWidth = Math.max(
44950
+ const labelWidth2 = Math.max(
44548
44951
  MIN_NODE_WIDTH4,
44549
44952
  node.label.length * LABEL_CHAR_W + NODE_PAD_X * 2
44550
44953
  );
@@ -44553,12 +44956,12 @@ function computeCycleLayout(parsed, options) {
44553
44956
  }
44554
44957
  if (!hasDesc) {
44555
44958
  return {
44556
- width: Math.min(MAX_NODE_WIDTH3, labelWidth),
44959
+ width: Math.min(MAX_NODE_WIDTH3, labelWidth2),
44557
44960
  height: PLAIN_NODE_HEIGHT,
44558
44961
  wrappedDesc: []
44559
44962
  };
44560
44963
  }
44561
- return chooseDescribedRectDims(node.description, labelWidth);
44964
+ return chooseDescribedRectDims(node.description, labelWidth2);
44562
44965
  });
44563
44966
  if (circleNodes) {
44564
44967
  const maxDiam = Math.max(...nodeDims.map((d) => d.width));
@@ -44754,10 +45157,10 @@ function computeCycleLayout(parsed, options) {
44754
45157
  scale
44755
45158
  };
44756
45159
  }
44757
- function chooseDescribedRectDims(description, labelWidth) {
45160
+ function chooseDescribedRectDims(description, labelWidth2) {
44758
45161
  const minW = Math.min(
44759
45162
  MAX_NODE_WIDTH3,
44760
- Math.max(MIN_NODE_WIDTH4, labelWidth, DESC_MIN_WIDTH)
45163
+ Math.max(MIN_NODE_WIDTH4, labelWidth2, DESC_MIN_WIDTH)
44761
45164
  );
44762
45165
  let best = null;
44763
45166
  let bestScore = Infinity;
@@ -45185,7 +45588,8 @@ function renderCycle(container, parsed, palette, isDark, onClickItem, exportDims
45185
45588
  const hideDescriptions = (renderOptions?.hideDescriptions ?? false) || parsed.options["no-descriptions"] === "true" || viewState?.hd === true;
45186
45589
  const showDescriptions = !hideDescriptions;
45187
45590
  const hasDescriptions = parsed.nodes.some((n) => n.description.length > 0) || parsed.edges.some((e) => e.description.length > 0);
45188
- const hasLegend = hasDescriptions && !!renderOptions?.onToggleDescriptions;
45591
+ const appHostedControls = renderOptions?.controlsHost === "app";
45592
+ const hasLegend = !appHostedControls && hasDescriptions && !!renderOptions?.onToggleDescriptions;
45189
45593
  const showTitle = !!parsed.title && parsed.options["no-title"] !== "on";
45190
45594
  const legendOffset = hasLegend ? sLegendHeight : 0;
45191
45595
  const layoutHeight = height - (showTitle ? sTitleAreaHeight : 0) - legendOffset;
@@ -45222,7 +45626,10 @@ function renderCycle(container, parsed, palette, isDark, onClickItem, exportDims
45222
45626
  groups: [],
45223
45627
  position: { placement: "top-center", titleRelation: "below-title" },
45224
45628
  mode: renderOptions?.exportMode ? "export" : "preview",
45225
- controlsGroup
45629
+ controlsGroup,
45630
+ ...renderOptions?.controlsHost !== void 0 && {
45631
+ controlsHost: renderOptions.controlsHost
45632
+ }
45226
45633
  };
45227
45634
  const legendState = {
45228
45635
  activeGroup: null,
@@ -45493,6 +45900,107 @@ function featureIndex(topo) {
45493
45900
  }
45494
45901
  return idx;
45495
45902
  }
45903
+ function buildAdjacency(topo) {
45904
+ const cached = adjacencyCache.get(topo);
45905
+ if (cached) return cached;
45906
+ const geometries = geomObject(topo).geometries;
45907
+ const nb = (0, import_topojson_client.neighbors)(geometries);
45908
+ const sets = /* @__PURE__ */ new Map();
45909
+ geometries.forEach((g, i) => {
45910
+ if (!g.type || g.type === "null") return;
45911
+ let set = sets.get(g.id);
45912
+ if (!set) {
45913
+ set = /* @__PURE__ */ new Set();
45914
+ sets.set(g.id, set);
45915
+ }
45916
+ for (const j of nb[i] ?? []) {
45917
+ const nid = geometries[j]?.id;
45918
+ if (nid && nid !== g.id) set.add(nid);
45919
+ }
45920
+ });
45921
+ const out = /* @__PURE__ */ new Map();
45922
+ for (const [iso, set] of sets) out.set(iso, [...set].sort());
45923
+ adjacencyCache.set(topo, out);
45924
+ return out;
45925
+ }
45926
+ function decodeFeatures(topo) {
45927
+ return geomObject(topo).geometries.map((g) => {
45928
+ const f = (0, import_topojson_client.feature)(topo, g);
45929
+ return {
45930
+ type: "Feature",
45931
+ id: g.id,
45932
+ properties: g.properties,
45933
+ geometry: f.geometry
45934
+ };
45935
+ });
45936
+ }
45937
+ function pointInRing(lon, lat, ring) {
45938
+ let inside = false;
45939
+ for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
45940
+ const xi = ring[i][0];
45941
+ const yi = ring[i][1];
45942
+ const xj = ring[j][0];
45943
+ const yj = ring[j][1];
45944
+ const intersect = yi > lat !== yj > lat && lon < (xj - xi) * (lat - yi) / (yj - yi) + xi;
45945
+ if (intersect) inside = !inside;
45946
+ }
45947
+ return inside;
45948
+ }
45949
+ function pointOnRingEdge(lon, lat, ring) {
45950
+ for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
45951
+ const xi = ring[i][0];
45952
+ const yi = ring[i][1];
45953
+ const xj = ring[j][0];
45954
+ const yj = ring[j][1];
45955
+ if (lon < Math.min(xi, xj) - EDGE_EPS || lon > Math.max(xi, xj) + EDGE_EPS)
45956
+ continue;
45957
+ if (lat < Math.min(yi, yj) - EDGE_EPS || lat > Math.max(yi, yj) + EDGE_EPS)
45958
+ continue;
45959
+ const cross = (xj - xi) * (lat - yi) - (yj - yi) * (lon - xi);
45960
+ if (Math.abs(cross) <= EDGE_EPS) return true;
45961
+ }
45962
+ return false;
45963
+ }
45964
+ function pointInGeometry(geometry, lon, lat) {
45965
+ const g = geometry;
45966
+ if (!g) return false;
45967
+ const polys = g.type === "Polygon" ? [g.coordinates] : g.type === "MultiPolygon" ? g.coordinates : [];
45968
+ for (const rings of polys) {
45969
+ if (!rings.length) continue;
45970
+ if (pointOnRingEdge(lon, lat, rings[0])) return true;
45971
+ if (!pointInRing(lon, lat, rings[0])) continue;
45972
+ let inHole = false;
45973
+ for (let h = 1; h < rings.length; h++) {
45974
+ if (pointInRing(lon, lat, rings[h]) && !pointOnRingEdge(lon, lat, rings[h])) {
45975
+ inHole = true;
45976
+ break;
45977
+ }
45978
+ }
45979
+ if (!inHole) return true;
45980
+ }
45981
+ return false;
45982
+ }
45983
+ function regionAt(lonLat, countries, states) {
45984
+ const lon = lonLat[0];
45985
+ const lat = lonLat[1];
45986
+ let country = null;
45987
+ for (const f of countries) {
45988
+ if (pointInGeometry(f.geometry, lon, lat)) {
45989
+ country = { iso: f.id, name: f.properties.name };
45990
+ break;
45991
+ }
45992
+ }
45993
+ let state = null;
45994
+ if (country?.iso === "US" && states) {
45995
+ for (const f of states) {
45996
+ if (pointInGeometry(f.geometry, lon, lat)) {
45997
+ state = { iso: f.id, name: f.properties.name };
45998
+ break;
45999
+ }
46000
+ }
46001
+ }
46002
+ return { country, state };
46003
+ }
45496
46004
  function featureBbox(topo, geomId) {
45497
46005
  const geom = geomObject(topo).geometries.find((g) => g.id === geomId);
45498
46006
  if (!geom) return null;
@@ -45610,13 +46118,15 @@ function unionLongitudes(lons) {
45610
46118
  }
45611
46119
  return { west: pts[gapIdx], east: pts[gapIdx - 1] + 360 };
45612
46120
  }
45613
- var import_topojson_client, import_d3_geo, fold, DETACH_GAP_DEG, DETACH_AREA_FRAC;
46121
+ var import_topojson_client, import_d3_geo, fold, adjacencyCache, EDGE_EPS, DETACH_GAP_DEG, DETACH_AREA_FRAC;
45614
46122
  var init_geo = __esm({
45615
46123
  "src/map/geo.ts"() {
45616
46124
  "use strict";
45617
46125
  import_topojson_client = require("topojson-client");
45618
46126
  import_d3_geo = require("d3-geo");
45619
46127
  fold = (s) => s.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLowerCase().trim();
46128
+ adjacencyCache = /* @__PURE__ */ new WeakMap();
46129
+ EDGE_EPS = 1e-9;
45620
46130
  DETACH_GAP_DEG = 10;
45621
46131
  DETACH_AREA_FRAC = 0.25;
45622
46132
  }
@@ -45636,6 +46146,12 @@ function looksUS(lat, lon) {
45636
46146
  if (lat < 15 || lat > 72) return false;
45637
46147
  return lon >= -180 && lon <= -64 || lon >= 172;
45638
46148
  }
46149
+ function looksNorthAmericaNeighbor(lat, lon) {
46150
+ return lat >= 14 && lat <= 72 && lon >= -141 && lon <= -52;
46151
+ }
46152
+ function isWholeSphere(bb) {
46153
+ return bb[0][0] <= -179 && bb[1][0] >= 179 && bb[0][1] <= -89 && bb[1][1] >= 89;
46154
+ }
45639
46155
  function resolveMap(parsed, data) {
45640
46156
  const diagnostics = [...parsed.diagnostics];
45641
46157
  const err = (line12, message, code) => {
@@ -45646,9 +46162,6 @@ function resolveMap(parsed, data) {
45646
46162
  };
45647
46163
  const result = {
45648
46164
  title: parsed.title,
45649
- ...parsed.directives.subtitle !== void 0 && {
45650
- subtitle: parsed.directives.subtitle
45651
- },
45652
46165
  ...parsed.directives.caption !== void 0 && {
45653
46166
  caption: parsed.directives.caption
45654
46167
  },
@@ -45658,7 +46171,7 @@ function resolveMap(parsed, data) {
45658
46171
  // renderer's job (step 4) — the resolver only carries `tags` + `tagGroups`
45659
46172
  // through; it never resolves a tag value to a palette color (#10).
45660
46173
  directives: { ...parsed.directives },
45661
- basemaps: { world: "coarse", subdivisions: [] },
46174
+ basemaps: { world: "detail", subdivisions: [] },
45662
46175
  regions: [],
45663
46176
  pois: [],
45664
46177
  edges: [],
@@ -45667,7 +46180,8 @@ function resolveMap(parsed, data) {
45667
46180
  [-180, -85],
45668
46181
  [180, 85]
45669
46182
  ],
45670
- projection: "natural-earth",
46183
+ projection: "equirectangular",
46184
+ poiFrameContainers: [],
45671
46185
  diagnostics,
45672
46186
  error: parsed.error
45673
46187
  };
@@ -45677,7 +46191,10 @@ function resolveMap(parsed, data) {
45677
46191
  ...[...countryIndex.values()].map((v) => v.name),
45678
46192
  ...[...usStateIndex.values()].map((v) => v.name)
45679
46193
  ];
45680
- const usScoped = parsed.directives.region === "us-states" || parsed.directives.defaultCountry?.toUpperCase() === "US" || parsed.regions.some((r) => {
46194
+ const localeRaw = parsed.directives.locale?.toUpperCase();
46195
+ const localeCountry = localeRaw ? localeRaw.split("-")[0] : void 0;
46196
+ const localeSubdivision = localeRaw && /^[A-Z]{2}-/.test(localeRaw) ? localeRaw : void 0;
46197
+ const usScoped = localeCountry === "US" || parsed.regions.some((r) => {
45681
46198
  const f = fold(r.name);
45682
46199
  return usStateIndex.has(f) && !countryIndex.has(f);
45683
46200
  }) || parsed.regions.some(
@@ -45828,7 +46345,7 @@ function resolveMap(parsed, data) {
45828
46345
  if (!scope)
45829
46346
  warn2(
45830
46347
  line12,
45831
- `"${name}" is ambiguous \u2014 resolved to the most-populous match.`,
46348
+ `"${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.`,
45832
46349
  "W_MAP_AMBIGUOUS_NAME"
45833
46350
  );
45834
46351
  }
@@ -45841,17 +46358,21 @@ function resolveMap(parsed, data) {
45841
46358
  return fold(pos.name);
45842
46359
  };
45843
46360
  const poiCountries = [];
45844
- let anyNonUsPoi = false;
46361
+ let anyUsPoi = false;
46362
+ let anyNonNaPoi = false;
45845
46363
  const noteCountry = (iso) => {
45846
46364
  if (iso) {
45847
46365
  poiCountries.push(iso);
45848
- if (iso !== "US") anyNonUsPoi = true;
46366
+ if (iso === "US") anyUsPoi = true;
46367
+ if (iso !== "US" && iso !== "CA" && iso !== "MX") anyNonNaPoi = true;
45849
46368
  }
45850
46369
  };
45851
46370
  const deferred = [];
45852
46371
  for (const p of parsed.pois) {
45853
46372
  if (p.pos.kind === "coords") {
45854
- if (!looksUS(p.pos.lat, p.pos.lon)) anyNonUsPoi = true;
46373
+ if (looksUS(p.pos.lat, p.pos.lon)) anyUsPoi = true;
46374
+ else if (!looksNorthAmericaNeighbor(p.pos.lat, p.pos.lon))
46375
+ anyNonNaPoi = true;
45855
46376
  addResolvedPoi(p.pos.lat, p.pos.lon, p);
45856
46377
  continue;
45857
46378
  }
@@ -45869,14 +46390,15 @@ function resolveMap(parsed, data) {
45869
46390
  deferred.push(p);
45870
46391
  }
45871
46392
  }
45872
- const inferredCountry = parsed.directives.defaultCountry?.toUpperCase() ?? mostCommonCountry(regions, poiCountries) ?? void 0;
46393
+ const inferredCountry = localeCountry ?? mostCommonCountry(regions, poiCountries) ?? void 0;
46394
+ const inferredScope = localeSubdivision ?? inferredCountry;
45873
46395
  for (const p of deferred) {
45874
46396
  if (p.pos.kind !== "name") continue;
45875
46397
  const got = lookupName(
45876
46398
  p.pos.name,
45877
46399
  p.pos.scope,
45878
46400
  p.lineNumber,
45879
- inferredCountry,
46401
+ inferredScope,
45880
46402
  true
45881
46403
  );
45882
46404
  if (got.kind === "ok") {
@@ -45946,7 +46468,8 @@ function resolveMap(parsed, data) {
45946
46468
  const meta = sizeValue !== void 0 ? { value: sizeValue } : {};
45947
46469
  if (pos.kind === "coords") {
45948
46470
  const id = alias ? fold(alias) : `@${pos.lat},${pos.lon}`;
45949
- if (!looksUS(pos.lat, pos.lon)) anyNonUsPoi = true;
46471
+ if (looksUS(pos.lat, pos.lon)) anyUsPoi = true;
46472
+ else if (!looksNorthAmericaNeighbor(pos.lat, pos.lon)) anyNonNaPoi = true;
45950
46473
  if (!registry.has(id)) {
45951
46474
  registerPoi(
45952
46475
  id,
@@ -45969,7 +46492,7 @@ function resolveMap(parsed, data) {
45969
46492
  if (registry.has(f)) return f;
45970
46493
  const aliased = declaredByName.get(f);
45971
46494
  if (aliased) return aliased;
45972
- const got = lookupName(pos.name, pos.scope, line12, inferredCountry, true);
46495
+ const got = lookupName(pos.name, pos.scope, line12, inferredScope, true);
45973
46496
  if (got.kind !== "ok") return null;
45974
46497
  noteCountry(got.iso);
45975
46498
  registerPoi(
@@ -46026,9 +46549,12 @@ function resolveMap(parsed, data) {
46026
46549
  }
46027
46550
  routes.push({ stopIds, legs, lineNumber: rt.lineNumber });
46028
46551
  }
46552
+ const hasUsContent = usSubdivisionReferenced || anyUsPoi || localeCountry === "US";
46553
+ const usOriented = !anyNonNaPoi && !regions.some(
46554
+ (r) => r.layer === "country" && !["US", "CA", "MX"].includes(r.iso)
46555
+ ) && hasUsContent;
46029
46556
  const subdivisions = [];
46030
- if (usSubdivisionReferenced || parsed.directives.region === "us-states")
46031
- subdivisions.push("us-states");
46557
+ if (usSubdivisionReferenced || usOriented) subdivisions.push("us-states");
46032
46558
  const regionBoxes = [];
46033
46559
  for (const ref of referencedRegionIds) {
46034
46560
  const bb = featureBbox(data.usStates, ref.id);
@@ -46046,17 +46572,51 @@ function resolveMap(parsed, data) {
46046
46572
  [-180, -85],
46047
46573
  [180, 85]
46048
46574
  ];
46049
- let extent2 = unioned ? pad(unioned, PAD_FRACTION) : DEFAULT_EXTENT;
46575
+ const basePad = regions.length > 0 ? REGION_PAD_FRACTION : PAD_FRACTION;
46576
+ let extent2 = unioned ? pad(unioned, basePad) : DEFAULT_EXTENT;
46577
+ const isPoiOnly = pois.length > 0 && regions.length === 0;
46578
+ const containerRegionIds = [];
46579
+ if (isPoiOnly) {
46580
+ const countries = decodeFeatures(data.worldDetail);
46581
+ const states = decodeFeatures(data.usStates);
46582
+ const seen = /* @__PURE__ */ new Set();
46583
+ const containerBoxes = [];
46584
+ for (const p of pois) {
46585
+ const { country, state } = regionAt([p.lon, p.lat], countries, states);
46586
+ const id = state?.iso ?? country?.iso;
46587
+ if (!id || seen.has(id)) continue;
46588
+ seen.add(id);
46589
+ containerRegionIds.push(id);
46590
+ const bb = state ? featureBbox(data.usStates, id) : featureBboxPrimary(data.worldCoarse, id);
46591
+ if (bb && !isWholeSphere(bb)) containerBoxes.push(bb);
46592
+ }
46593
+ const containerUnion = unionExtent(containerBoxes, points);
46594
+ if (containerUnion) extent2 = pad(containerUnion, PAD_FRACTION);
46595
+ }
46596
+ if (isPoiOnly) {
46597
+ const cx = (extent2[0][0] + extent2[1][0]) / 2;
46598
+ const cy = (extent2[0][1] + extent2[1][1]) / 2;
46599
+ const lon = extent2[1][0] - extent2[0][0];
46600
+ const lat = extent2[1][1] - extent2[0][1];
46601
+ const longer = Math.max(lon, lat);
46602
+ if (longer > 0 && longer < POI_ZOOM_FLOOR_DEG) {
46603
+ const k = POI_ZOOM_FLOOR_DEG / longer;
46604
+ const halfLon = lon * k / 2;
46605
+ const halfLat = lat * k / 2;
46606
+ extent2 = [
46607
+ [cx - halfLon, cy - halfLat],
46608
+ [cx + halfLon, cy + halfLat]
46609
+ ];
46610
+ }
46611
+ }
46050
46612
  const lonSpan = extent2[1][0] - extent2[0][0];
46051
46613
  const latSpan = extent2[1][1] - extent2[0][1];
46052
46614
  const span = Math.max(lonSpan, latSpan);
46053
46615
  const maxAbsLat = Math.max(Math.abs(extent2[0][1]), Math.abs(extent2[1][1]));
46054
- const usDominant = (subdivisions.includes("us-states") || regions.some((r) => r.layer === "us-state")) && !regions.some((r) => r.layer === "country" && r.iso !== "US") && !anyNonUsPoi;
46055
46616
  let projection;
46056
- const override = parsed.directives.projection;
46057
- if (override === "equirectangular" || override === "natural-earth" || override === "albers-usa" || override === "mercator") {
46058
- projection = override;
46059
- } else if (usDominant) {
46617
+ if (isPoiOnly && usOriented && lonSpan < US_NATIONAL_LON_SPAN) {
46618
+ projection = "mercator";
46619
+ } else if (usOriented) {
46060
46620
  projection = "albers-usa";
46061
46621
  } else if (span > WORLD_SPAN || maxAbsLat > MERCATOR_MAX_LAT) {
46062
46622
  projection = "equirectangular";
@@ -46074,11 +46634,20 @@ function resolveMap(parsed, data) {
46074
46634
  result.edges = edges;
46075
46635
  result.routes = routes;
46076
46636
  result.basemaps = {
46077
- world: span > WORLD_SPAN ? "coarse" : "detail",
46637
+ // Tier is intentionally pinned to detail (50m) at ALL scales. Diagrammo maps
46638
+ // are presentational (palette tints, relief hachures, POI hubs), not
46639
+ // survey-grade — recognizability > generalization: 110m coarse drops the
46640
+ // Italian boot to a stump at world scale. `WORLD_SPAN` lives on only for the
46641
+ // projection decision (the `usOriented`/`span > WORLD_SPAN` chain above); it
46642
+ // no longer gates basemap resolution.
46643
+ // `worldCoarse` is still loaded — it's the authoritative name/bbox index
46644
+ // (featureIndex, featureBboxPrimary), not dead code.
46645
+ world: "detail",
46078
46646
  subdivisions
46079
46647
  };
46080
46648
  result.extent = extent2;
46081
46649
  result.projection = projection;
46650
+ result.poiFrameContainers = containerRegionIds;
46082
46651
  result.error = parsed.error ?? firstError(diagnostics);
46083
46652
  return result;
46084
46653
  }
@@ -46115,7 +46684,7 @@ function firstError(diags) {
46115
46684
  const e = diags.find((d) => d.severity === "error");
46116
46685
  return e ? formatDgmoError(e) : null;
46117
46686
  }
46118
- var WORLD_SPAN, MERCATOR_MAX_LAT, PAD_FRACTION, WORLD_LAT_SOUTH, WORLD_LAT_NORTH, REGION_ALIASES, US_STATE_POSTAL;
46687
+ 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;
46119
46688
  var init_resolver2 = __esm({
46120
46689
  "src/map/resolver.ts"() {
46121
46690
  "use strict";
@@ -46124,8 +46693,11 @@ var init_resolver2 = __esm({
46124
46693
  WORLD_SPAN = 90;
46125
46694
  MERCATOR_MAX_LAT = 80;
46126
46695
  PAD_FRACTION = 0.05;
46696
+ REGION_PAD_FRACTION = 0.12;
46127
46697
  WORLD_LAT_SOUTH = -58;
46128
46698
  WORLD_LAT_NORTH = 78;
46699
+ POI_ZOOM_FLOOR_DEG = 7;
46700
+ US_NATIONAL_LON_SPAN = 48;
46129
46701
  REGION_ALIASES = {
46130
46702
  // Common everyday names → the Natural-Earth display name actually shipped.
46131
46703
  "united states": "united states of america",
@@ -46203,17 +46775,305 @@ var init_resolver2 = __esm({
46203
46775
  }
46204
46776
  });
46205
46777
 
46778
+ // src/map/colorize.ts
46779
+ function assignColors(isos, adjacency) {
46780
+ const sorted = [...isos].sort();
46781
+ const byIso = /* @__PURE__ */ new Map();
46782
+ let maxIndex = -1;
46783
+ for (const iso of sorted) {
46784
+ const taken = /* @__PURE__ */ new Set();
46785
+ for (const n of adjacency.get(iso) ?? []) {
46786
+ const c = byIso.get(n);
46787
+ if (c !== void 0) taken.add(c);
46788
+ }
46789
+ let h = 0;
46790
+ while (taken.has(h)) h++;
46791
+ byIso.set(iso, h);
46792
+ if (h > maxIndex) maxIndex = h;
46793
+ }
46794
+ return { byIso, huesNeeded: maxIndex + 1 };
46795
+ }
46796
+ var init_colorize = __esm({
46797
+ "src/map/colorize.ts"() {
46798
+ "use strict";
46799
+ }
46800
+ });
46801
+
46802
+ // src/map/context-labels.ts
46803
+ function tierBand(maxSpanDeg) {
46804
+ if (maxSpanDeg >= 90) return "world";
46805
+ if (maxSpanDeg >= 20) return "continental";
46806
+ if (maxSpanDeg >= 5) return "regional";
46807
+ return "local";
46808
+ }
46809
+ function labelBudget(width, height, band) {
46810
+ const bandCap = {
46811
+ world: 6,
46812
+ continental: 5,
46813
+ regional: 4,
46814
+ local: 3
46815
+ };
46816
+ const area2 = Math.floor(Math.sqrt(Math.max(0, width * height)) / 150);
46817
+ return Math.max(0, Math.min(area2, bandCap[band]));
46818
+ }
46819
+ function waterEligible(tier, kind, band) {
46820
+ switch (band) {
46821
+ case "world":
46822
+ return tier <= 1 && (kind === "ocean" || kind === "sea");
46823
+ case "continental":
46824
+ return tier <= 2;
46825
+ case "regional":
46826
+ return tier <= 3;
46827
+ case "local":
46828
+ return tier <= 4;
46829
+ }
46830
+ }
46831
+ function insideViewport(p, width, height) {
46832
+ return !!p && Number.isFinite(p[0]) && Number.isFinite(p[1]) && p[0] >= 0 && p[0] <= width && p[1] >= 0 && p[1] <= height;
46833
+ }
46834
+ function labelWidth(text, letterSpacing) {
46835
+ const spacing = letterSpacing > 0 ? Math.max(0, text.length - 1) * letterSpacing : 0;
46836
+ return measureLegendText(text, FONT) + spacing + 2 * PADX;
46837
+ }
46838
+ function wrapLabel2(text, letterSpacing) {
46839
+ const words = text.split(/\s+/).filter(Boolean);
46840
+ if (words.length <= 1) return [text];
46841
+ const maxLines = words.length >= 4 ? 3 : 2;
46842
+ const n = words.length;
46843
+ let best = null;
46844
+ for (let mask = 0; mask < 1 << n - 1; mask++) {
46845
+ const lines = [];
46846
+ let cur = [words[0]];
46847
+ for (let i = 1; i < n; i++) {
46848
+ if (mask & 1 << i - 1) {
46849
+ lines.push(cur.join(" "));
46850
+ cur = [words[i]];
46851
+ } else cur.push(words[i]);
46852
+ }
46853
+ lines.push(cur.join(" "));
46854
+ if (lines.length > maxLines) continue;
46855
+ const cost = Math.round(
46856
+ Math.max(...lines.map((l) => labelWidth(l, letterSpacing)))
46857
+ );
46858
+ const head = labelWidth(lines[0], letterSpacing);
46859
+ if (!best || cost < best.cost || cost === best.cost && lines.length < best.lines.length || cost === best.cost && lines.length === best.lines.length && head > best.head)
46860
+ best = { lines, cost, head };
46861
+ }
46862
+ return best?.lines ?? [text];
46863
+ }
46864
+ function rectAround(cx, cy, lines, letterSpacing) {
46865
+ const w = Math.max(...lines.map((l) => labelWidth(l, letterSpacing)));
46866
+ const h = (lines.length - 1) * LINE_HEIGHT + FONT + 2 * PADY;
46867
+ return { x: cx - w / 2, y: cy - h / 2, w, h };
46868
+ }
46869
+ function rectFits(r, width, height) {
46870
+ return r.x >= 0 && r.y >= 0 && r.x + r.w <= width && r.y + r.h <= height;
46871
+ }
46872
+ function overlapsPadded(a, b, pad2) {
46873
+ 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;
46874
+ }
46875
+ function placeContextLabels(args) {
46876
+ const {
46877
+ projection,
46878
+ dLonSpan,
46879
+ dLatSpan,
46880
+ width,
46881
+ height,
46882
+ waterBodies,
46883
+ countries,
46884
+ palette,
46885
+ project,
46886
+ collides,
46887
+ overLand
46888
+ } = args;
46889
+ void projection;
46890
+ const band = tierBand(Math.max(dLonSpan, dLatSpan));
46891
+ const budget = labelBudget(width, height, band);
46892
+ if (budget <= 0) return [];
46893
+ const waterColor = mix(palette.colors.blue, palette.textMuted, 50);
46894
+ const countryColor = palette.textMuted;
46895
+ const haloColor = palette.bg;
46896
+ const candidates = [];
46897
+ const center = [width / 2, height / 2];
46898
+ for (const e of waterBodies?.entries ?? []) {
46899
+ const [lat, lon, name, tier, kind, alt] = e;
46900
+ if (!waterEligible(tier, kind, band)) continue;
46901
+ const wlines = wrapLabel2(name, WATER_LETTER_SPACING);
46902
+ const anchorsLngLat = [[lon, lat]];
46903
+ for (const a of alt ?? []) anchorsLngLat.push([a[1], a[0]]);
46904
+ let best = null;
46905
+ let bestD = Infinity;
46906
+ let nearestProj = null;
46907
+ let nearestProjD = Infinity;
46908
+ for (const [aLon, aLat] of anchorsLngLat) {
46909
+ const p = project(aLon, aLat);
46910
+ if (!p || !Number.isFinite(p[0]) || !Number.isFinite(p[1])) continue;
46911
+ const d = (p[0] - center[0]) ** 2 + (p[1] - center[1]) ** 2;
46912
+ if (d < nearestProjD) {
46913
+ nearestProjD = d;
46914
+ nearestProj = p;
46915
+ }
46916
+ if (!insideViewport(p, width, height)) continue;
46917
+ if (d < bestD) {
46918
+ bestD = d;
46919
+ best = p;
46920
+ }
46921
+ }
46922
+ if (!best && tier === 0 && nearestProj) {
46923
+ const overX = Math.max(0, -nearestProj[0], nearestProj[0] - width);
46924
+ const overY = Math.max(0, -nearestProj[1], nearestProj[1] - height);
46925
+ if (overX <= width * EDGE_CLAMP_OVERSHOOT && overY <= height * EDGE_CLAMP_OVERSHOOT) {
46926
+ const halfW = Math.max(...wlines.map((l) => labelWidth(l, WATER_LETTER_SPACING))) / 2;
46927
+ const halfH = ((wlines.length - 1) * LINE_HEIGHT + FONT + 2 * PADY) / 2;
46928
+ const m = EDGE_CLAMP_MARGIN;
46929
+ best = [
46930
+ Math.min(Math.max(nearestProj[0], halfW + m), width - halfW - m),
46931
+ Math.min(Math.max(nearestProj[1], halfH + m), height - halfH - m)
46932
+ ];
46933
+ }
46934
+ }
46935
+ if (!best) continue;
46936
+ candidates.push({
46937
+ text: name,
46938
+ lines: wlines,
46939
+ cx: best[0],
46940
+ cy: best[1],
46941
+ italic: true,
46942
+ letterSpacing: WATER_LETTER_SPACING,
46943
+ color: waterColor,
46944
+ // Water before any country (×1000), then by tier, then kind, then name.
46945
+ sort: tier * 10 + KIND_ORDER[kind]
46946
+ });
46947
+ }
46948
+ const ranked = countries.map((c) => {
46949
+ const [x0, y0, x1, y1] = c.bbox;
46950
+ const w = x1 - x0;
46951
+ const h = y1 - y0;
46952
+ return { c, w, h, area: w * h };
46953
+ }).filter((r) => Number.isFinite(r.area) && r.area > 0).sort((a, b) => b.area - a.area);
46954
+ let ci = 0;
46955
+ for (const r of ranked) {
46956
+ const { c, w, h } = r;
46957
+ if (w > width * 0.66 || h > height * 0.66) continue;
46958
+ if (!insideViewport(c.anchor, width, height)) continue;
46959
+ const text = c.name;
46960
+ const tw = labelWidth(text, 0);
46961
+ if (tw > w || FONT + 2 * PADY > h) continue;
46962
+ candidates.push({
46963
+ text,
46964
+ lines: [text],
46965
+ cx: c.anchor[0],
46966
+ cy: c.anchor[1],
46967
+ italic: false,
46968
+ letterSpacing: 0,
46969
+ color: countryColor,
46970
+ // Always after every water body (+1e6); larger area = earlier.
46971
+ sort: 1e6 + ci++
46972
+ });
46973
+ }
46974
+ candidates.sort((a, b) => a.sort - b.sort);
46975
+ const placed = [];
46976
+ const placedRects = [];
46977
+ for (const cand of candidates) {
46978
+ if (placed.length >= budget) break;
46979
+ const rect = rectAround(cand.cx, cand.cy, cand.lines, cand.letterSpacing);
46980
+ if (!rectFits(rect, width, height)) continue;
46981
+ if (cand.italic && overLand) {
46982
+ const inset = 2;
46983
+ const top = cand.cy - (cand.lines.length - 1) / 2 * LINE_HEIGHT;
46984
+ const touchesLand = cand.lines.some((line12, li) => {
46985
+ const lw = labelWidth(line12, cand.letterSpacing);
46986
+ const x0 = cand.cx - lw / 2 + inset;
46987
+ const x1 = cand.cx + lw / 2 - inset;
46988
+ const xs = [x0, (x0 + cand.cx) / 2, cand.cx, (cand.cx + x1) / 2, x1];
46989
+ const base = top + li * LINE_HEIGHT;
46990
+ return [base, base - FONT * 0.4, base - FONT * 0.8].some(
46991
+ (y) => xs.some((x) => overLand(x, y))
46992
+ );
46993
+ });
46994
+ if (touchesLand) continue;
46995
+ }
46996
+ if (collides(rect)) continue;
46997
+ if (placedRects.some((r) => overlapsPadded(rect, r, CONTEXT_PAD))) continue;
46998
+ placedRects.push(rect);
46999
+ placed.push({
47000
+ x: cand.cx,
47001
+ y: cand.cy,
47002
+ text: cand.text,
47003
+ anchor: "middle",
47004
+ color: cand.color,
47005
+ // No halo: the bg-coloured outline reads as a ghost box behind the text
47006
+ // over the tinted water/land. Context labels are muted enough to sit
47007
+ // cleanly on the basemap without one.
47008
+ halo: false,
47009
+ haloColor,
47010
+ italic: cand.italic,
47011
+ letterSpacing: cand.letterSpacing,
47012
+ ...cand.lines.length > 1 ? { lines: cand.lines } : {},
47013
+ lineNumber: 0
47014
+ });
47015
+ }
47016
+ return placed;
47017
+ }
47018
+ var FONT, LINE_HEIGHT, PADX, PADY, WATER_LETTER_SPACING, CONTEXT_PAD, EDGE_CLAMP_MARGIN, EDGE_CLAMP_OVERSHOOT, KIND_ORDER;
47019
+ var init_context_labels = __esm({
47020
+ "src/map/context-labels.ts"() {
47021
+ "use strict";
47022
+ init_color_utils();
47023
+ init_legend_constants();
47024
+ FONT = 11;
47025
+ LINE_HEIGHT = FONT + 2;
47026
+ PADX = 4;
47027
+ PADY = 3;
47028
+ WATER_LETTER_SPACING = 1.5;
47029
+ CONTEXT_PAD = 4;
47030
+ EDGE_CLAMP_MARGIN = 8;
47031
+ EDGE_CLAMP_OVERSHOOT = 0.35;
47032
+ KIND_ORDER = {
47033
+ ocean: 0,
47034
+ sea: 1,
47035
+ gulf: 2,
47036
+ bay: 3,
47037
+ strait: 4,
47038
+ channel: 5,
47039
+ sound: 6
47040
+ };
47041
+ }
47042
+ });
47043
+
46206
47044
  // src/map/layout.ts
46207
47045
  function geomObject2(topo) {
46208
47046
  const key = Object.keys(topo.objects)[0];
46209
47047
  return topo.objects[key];
46210
47048
  }
47049
+ function mergeFeatures(a, b) {
47050
+ const polysOf = (f) => {
47051
+ const g = f.geometry;
47052
+ if (!g) return null;
47053
+ if (g.type === "Polygon") return [g.coordinates];
47054
+ if (g.type === "MultiPolygon") return g.coordinates;
47055
+ return null;
47056
+ };
47057
+ const pa = polysOf(a);
47058
+ const pb = polysOf(b);
47059
+ if (!pa || !pb) return a;
47060
+ return {
47061
+ ...a,
47062
+ geometry: { type: "MultiPolygon", coordinates: [...pa, ...pb] }
47063
+ };
47064
+ }
46211
47065
  function decodeLayer(topo) {
47066
+ const cached = decodeCache.get(topo);
47067
+ if (cached) return cached;
46212
47068
  const out = /* @__PURE__ */ new Map();
46213
47069
  for (const g of geomObject2(topo).geometries) {
46214
47070
  const f = (0, import_topojson_client2.feature)(topo, g);
46215
- out.set(g.id, { ...f, id: g.id });
47071
+ if (!f.geometry) continue;
47072
+ const tagged = { ...f, id: g.id };
47073
+ const existing = out.get(g.id);
47074
+ out.set(g.id, existing ? mergeFeatures(existing, tagged) : tagged);
46216
47075
  }
47076
+ decodeCache.set(topo, out);
46217
47077
  return out;
46218
47078
  }
46219
47079
  function projectionFor(family) {
@@ -46222,9 +47082,12 @@ function projectionFor(family) {
46222
47082
  return usConusProjection();
46223
47083
  case "mercator":
46224
47084
  return (0, import_d3_geo2.geoMercator)();
47085
+ case "equal-earth":
47086
+ return (0, import_d3_geo2.geoEqualEarth)();
47087
+ case "equirectangular":
47088
+ return (0, import_d3_geo2.geoEquirectangular)();
46225
47089
  case "natural-earth":
46226
47090
  return (0, import_d3_geo2.geoNaturalEarth1)();
46227
- case "equirectangular":
46228
47091
  default:
46229
47092
  return (0, import_d3_geo2.geoEquirectangular)();
46230
47093
  }
@@ -46243,13 +47106,11 @@ function mapNeutralLandColor(palette, isDark, _dataActive = false) {
46243
47106
  isDark ? LAND_TINT_DARK : LAND_TINT_LIGHT
46244
47107
  );
46245
47108
  }
46246
- function layoutMap(resolved, data, size, opts) {
46247
- const { palette, isDark } = opts;
46248
- const { width, height } = size;
47109
+ function buildMapProjection(resolved, data) {
46249
47110
  const wantsUsStates = resolved.basemaps.subdivisions.includes("us-states");
46250
- const usCrisp = resolved.projection === "albers-usa" && wantsUsStates && !!data.naLand;
47111
+ const usCrisp = (resolved.projection === "albers-usa" || resolved.projection === "mercator") && wantsUsStates && !!data.naLand;
46251
47112
  const worldTopo = usCrisp ? data.worldDetail : resolved.basemaps.world === "detail" ? data.worldDetail : data.worldCoarse;
46252
- const worldLayer = decodeLayer(worldTopo);
47113
+ const worldLayer = new Map(decodeLayer(worldTopo));
46253
47114
  if (usCrisp && data.naLand) {
46254
47115
  const [nbW, nbS, nbE, nbN] = [-140, 10, -52, 66];
46255
47116
  const crisp = decodeLayer(data.naLand);
@@ -46258,16 +47119,109 @@ function layoutMap(resolved, data, size, opts) {
46258
47119
  if (!base) continue;
46259
47120
  const [[bw, bs], [be, bn]] = (0, import_d3_geo2.geoBounds)(base);
46260
47121
  if (bw >= nbW && be <= nbE && bs >= nbS && bn <= nbN)
46261
- worldLayer.set(iso, cf);
47122
+ worldLayer.set(iso, { ...cf, properties: base.properties });
46262
47123
  }
46263
47124
  }
46264
47125
  const usLayer = wantsUsStates ? decodeLayer(data.usStates) : null;
47126
+ const extentOutline = () => {
47127
+ const [[w, s], [e, n]] = resolved.extent;
47128
+ const N = 16;
47129
+ const coords = [];
47130
+ for (let i = 0; i <= N; i++) {
47131
+ const t = i / N;
47132
+ const lon = w + (e - w) * t;
47133
+ const lat = s + (n - s) * t;
47134
+ coords.push([lon, s], [lon, n], [w, lat], [e, lat]);
47135
+ }
47136
+ return {
47137
+ type: "Feature",
47138
+ properties: {},
47139
+ geometry: { type: "MultiPoint", coordinates: coords }
47140
+ };
47141
+ };
47142
+ let fitFeatures;
47143
+ if (resolved.projection === "albers-usa" && usLayer) {
47144
+ fitFeatures = [...usLayer.entries()].filter(([iso]) => !US_NON_CONUS.has(iso)).map(([, f]) => f);
47145
+ const neighborPoints = resolved.pois.filter((p) => !inAlaska(p.lon, p.lat) && !inHawaii(p.lon, p.lat)).map((p) => [p.lon, p.lat]);
47146
+ if (neighborPoints.length > 0) {
47147
+ fitFeatures.push({
47148
+ type: "Feature",
47149
+ properties: {},
47150
+ geometry: { type: "MultiPoint", coordinates: neighborPoints }
47151
+ });
47152
+ }
47153
+ for (const r of resolved.regions) {
47154
+ if (r.layer === "country" && (r.iso === "CA" || r.iso === "MX")) {
47155
+ const cf = worldLayer.get(r.iso);
47156
+ if (cf) fitFeatures.push(cf);
47157
+ }
47158
+ }
47159
+ } else {
47160
+ fitFeatures = [extentOutline()];
47161
+ }
47162
+ const fitTarget = { type: "FeatureCollection", features: fitFeatures };
47163
+ const projection = projectionFor(resolved.projection);
47164
+ if (resolved.projection !== "albers-usa") {
47165
+ let centerLon = (resolved.extent[0][0] + resolved.extent[1][0]) / 2;
47166
+ if (centerLon > 180) centerLon -= 360;
47167
+ projection.rotate([-centerLon, 0]);
47168
+ }
47169
+ const fitGB = (0, import_d3_geo2.geoBounds)(fitTarget);
47170
+ const fitIsGlobal = fitGB[1][0] - fitGB[0][0] >= 270 || fitGB[1][1] - fitGB[0][1] >= 130;
47171
+ return {
47172
+ projection,
47173
+ fitTarget,
47174
+ fitIsGlobal,
47175
+ worldLayer,
47176
+ usLayer,
47177
+ usCrisp,
47178
+ wantsUsStates,
47179
+ worldTopo
47180
+ };
47181
+ }
47182
+ function parsePathRings(d) {
47183
+ const rings = [];
47184
+ let cur = [];
47185
+ const re = /([MLZ])([^MLZ]*)/g;
47186
+ let m;
47187
+ while (m = re.exec(d)) {
47188
+ if (m[1] === "Z") {
47189
+ if (cur.length) rings.push(cur);
47190
+ cur = [];
47191
+ continue;
47192
+ }
47193
+ if (m[1] === "M" && cur.length) {
47194
+ rings.push(cur);
47195
+ cur = [];
47196
+ }
47197
+ const nums = m[2].split(/[ ,]+/).map(Number);
47198
+ for (let i = 0; i + 1 < nums.length; i += 2) {
47199
+ const x = nums[i];
47200
+ const y = nums[i + 1];
47201
+ if (Number.isFinite(x) && Number.isFinite(y)) cur.push([x, y]);
47202
+ }
47203
+ }
47204
+ if (cur.length) rings.push(cur);
47205
+ return rings;
47206
+ }
47207
+ function layoutMap(resolved, data, size, opts) {
47208
+ const { palette, isDark } = opts;
47209
+ const { width, height } = size;
47210
+ const {
47211
+ projection,
47212
+ fitTarget,
47213
+ fitIsGlobal,
47214
+ worldLayer,
47215
+ usLayer,
47216
+ usCrisp,
47217
+ worldTopo
47218
+ } = buildMapProjection(resolved, data);
46265
47219
  const usContext = usLayer !== null;
46266
47220
  const regionStroke = isDark ? mix(palette.bg, palette.text, 78) : mix(palette.text, palette.bg, 78);
46267
47221
  const values = resolved.regions.filter((r) => r.value !== void 0).map((r) => r.value);
46268
- const scaleOverride = resolved.directives.scale;
46269
- const rampMin = scaleOverride ? scaleOverride.min : Math.min(...values);
46270
- const rampMax = scaleOverride ? scaleOverride.max : Math.max(...values);
47222
+ const allNonNegative = values.length > 0 && values.every((v) => v >= 0);
47223
+ const rampMin = allNonNegative ? 0 : Math.min(...values);
47224
+ const rampMax = Math.max(...values);
46271
47225
  const rampHue = resolveColor(resolved.directives.regionMetricColor ?? "", palette) ?? palette.colors.red;
46272
47226
  const hasRamp = values.length > 0;
46273
47227
  const VALUE_NAME = hasRamp ? resolved.directives.regionMetric?.trim() || "Value" : null;
@@ -46288,7 +47242,7 @@ function layoutMap(resolved, data, size, opts) {
46288
47242
  activeGroup = VALUE_NAME ?? (resolved.tagGroups.length > 0 ? resolved.tagGroups[0].name : null);
46289
47243
  }
46290
47244
  const activeIsScore = VALUE_NAME !== null && activeGroup === VALUE_NAME;
46291
- const mutedBasemap = resolved.directives.basemapStyle === "muted" ? true : resolved.directives.basemapStyle === "natural" ? false : activeGroup !== null;
47245
+ const mutedBasemap = activeGroup !== null;
46292
47246
  const neutralFill = mapNeutralLandColor(palette, isDark, mutedBasemap);
46293
47247
  const water = mapBackgroundColor(palette, isDark, mutedBasemap);
46294
47248
  const lakeStroke = mix(regionStroke, water, 45);
@@ -46297,6 +47251,39 @@ function layoutMap(resolved, data, size, opts) {
46297
47251
  palette.bg,
46298
47252
  mutedBasemap ? isDark ? MUTED_FOREIGN_DARK : MUTED_FOREIGN_LIGHT : isDark ? FOREIGN_TINT_DARK : FOREIGN_TINT_LIGHT
46299
47253
  );
47254
+ const colorizeActive = resolved.directives.noColorize !== true && !hasRamp && resolved.tagGroups.length === 0;
47255
+ const colorByIso = /* @__PURE__ */ new Map();
47256
+ if (colorizeActive) {
47257
+ const adjacency = /* @__PURE__ */ new Map();
47258
+ const addEdges = (src) => {
47259
+ for (const [iso, ns] of src) {
47260
+ const cur = adjacency.get(iso);
47261
+ if (cur) cur.push(...ns);
47262
+ else adjacency.set(iso, [...ns]);
47263
+ }
47264
+ };
47265
+ addEdges(buildAdjacency(worldTopo));
47266
+ if (usLayer) {
47267
+ addEdges(buildAdjacency(data.usStates));
47268
+ for (const [country, states] of Object.entries(FOREIGN_BORDER)) {
47269
+ const cn = adjacency.get(country);
47270
+ if (!cn) continue;
47271
+ for (const st of states) {
47272
+ const sn = adjacency.get(st);
47273
+ if (!sn) continue;
47274
+ cn.push(st);
47275
+ sn.push(country);
47276
+ }
47277
+ }
47278
+ }
47279
+ const { byIso, huesNeeded } = assignColors(
47280
+ [...adjacency.keys()],
47281
+ adjacency
47282
+ );
47283
+ const tints = politicalTints(palette, huesNeeded, isDark);
47284
+ for (const [iso, idx] of byIso) colorByIso.set(iso, tints[idx]);
47285
+ }
47286
+ const colorizeStroke = (fill2) => mix(fill2, palette.text, 35);
46300
47287
  const rampBase = isDark ? mix(palette.surface, palette.text, 28) : palette.bg;
46301
47288
  const fillForValue = (s) => {
46302
47289
  const t = rampMax > rampMin ? (s - rampMin) / (rampMax - rampMin) : 1;
@@ -46332,43 +47319,15 @@ function layoutMap(resolved, data, size, opts) {
46332
47319
  if (activeIsScore) {
46333
47320
  return r.value !== void 0 ? fillForValue(r.value) : neutralFill;
46334
47321
  }
47322
+ if (colorizeActive) return (r.iso && colorByIso.get(r.iso)) ?? neutralFill;
46335
47323
  return tagFill(r.tags, activeGroup) ?? neutralFill;
46336
47324
  };
46337
47325
  const regionById = new Map(resolved.regions.map((r) => [r.iso, r]));
46338
- const extentOutline = () => {
46339
- const [[w, s], [e, n]] = resolved.extent;
46340
- const N = 16;
46341
- const coords = [];
46342
- for (let i = 0; i <= N; i++) {
46343
- const t = i / N;
46344
- const lon = w + (e - w) * t;
46345
- const lat = s + (n - s) * t;
46346
- coords.push([lon, s], [lon, n], [w, lat], [e, lat]);
46347
- }
46348
- return {
46349
- type: "Feature",
46350
- properties: {},
46351
- geometry: { type: "MultiPoint", coordinates: coords }
46352
- };
46353
- };
46354
- let fitFeatures;
46355
- if (resolved.projection === "albers-usa" && usLayer) {
46356
- fitFeatures = [...usLayer.entries()].filter(([iso]) => !US_NON_CONUS.has(iso)).map(([, f]) => f);
46357
- } else {
46358
- fitFeatures = [extentOutline()];
46359
- }
46360
- const fitTarget = { type: "FeatureCollection", features: fitFeatures };
46361
- const projection = projectionFor(resolved.projection);
46362
- if (resolved.projection !== "albers-usa") {
46363
- let centerLon = (resolved.extent[0][0] + resolved.extent[1][0]) / 2;
46364
- if (centerLon > 180) centerLon -= 360;
46365
- projection.rotate([-centerLon, 0]);
46366
- }
46367
- const TITLE_GAP = 16;
47326
+ const TITLE_GAP2 = 16;
46368
47327
  let topPad = FIT_PAD;
46369
47328
  if (resolved.title && resolved.pois.length > 0) {
46370
47329
  const bannerBottom = (resolved.subtitle ? TITLE_Y + TITLE_FONT_SIZE : TITLE_Y) + TITLE_FONT_SIZE / 2;
46371
- topPad = Math.max(FIT_PAD, bannerBottom + TITLE_GAP);
47330
+ topPad = Math.max(FIT_PAD, bannerBottom + TITLE_GAP2);
46372
47331
  }
46373
47332
  const fitBox = [
46374
47333
  [FIT_PAD, topPad],
@@ -46378,12 +47337,10 @@ function layoutMap(resolved, data, size, opts) {
46378
47337
  ]
46379
47338
  ];
46380
47339
  projection.fitExtent(fitBox, fitTarget);
46381
- const fitGB = (0, import_d3_geo2.geoBounds)(fitTarget);
46382
- const fitIsGlobal = fitGB[1][0] - fitGB[0][0] >= 270 || fitGB[1][1] - fitGB[0][1] >= 130;
46383
47340
  let path;
46384
47341
  let project;
46385
47342
  let stretchParams = null;
46386
- if (fitIsGlobal) {
47343
+ if (fitIsGlobal && !opts.preferContain) {
46387
47344
  const cb = (0, import_d3_geo2.geoPath)(projection).bounds(fitTarget);
46388
47345
  const bx0 = cb[0][0];
46389
47346
  const by0 = cb[0][1];
@@ -46425,7 +47382,9 @@ function layoutMap(resolved, data, size, opts) {
46425
47382
  const insets = [];
46426
47383
  const insetRegions = [];
46427
47384
  const insetLabelSeeds = [];
46428
- if (resolved.projection === "albers-usa" && usLayer && !resolved.directives.noInsets) {
47385
+ const akRef = resolved.regions.some((r) => r.iso === "US-AK") || resolved.pois.some((p) => inAlaska(p.lon, p.lat));
47386
+ const hiRef = resolved.regions.some((r) => r.iso === "US-HI") || resolved.pois.some((p) => inHawaii(p.lon, p.lat));
47387
+ if (resolved.projection === "albers-usa" && usLayer && (akRef || hiRef)) {
46429
47388
  const PAD = 8;
46430
47389
  const GAP = 12;
46431
47390
  const yB = height - FIT_PAD;
@@ -46490,8 +47449,18 @@ function layoutMap(resolved, data, size, opts) {
46490
47449
  );
46491
47450
  const d = (0, import_d3_geo2.geoPath)(proj)(f) ?? "";
46492
47451
  if (!d) return xr;
47452
+ let contextLand;
47453
+ if (iso === "US-AK") {
47454
+ const can = worldLayer.get("CA");
47455
+ const cd = can ? (0, import_d3_geo2.geoPath)(proj)(can) ?? "" : "";
47456
+ if (cd)
47457
+ contextLand = {
47458
+ d: cd,
47459
+ fill: colorizeActive ? colorByIso.get("CA") ?? foreignFill : foreignFill
47460
+ };
47461
+ }
46493
47462
  const r = regionById.get(iso);
46494
- let fill2 = neutralFill;
47463
+ let fill2 = colorizeActive ? colorByIso.get(iso) ?? neutralFill : neutralFill;
46495
47464
  let lineNumber = -1;
46496
47465
  if (r?.layer === "us-state") {
46497
47466
  fill2 = regionFill(r);
@@ -46510,13 +47479,14 @@ function layoutMap(resolved, data, size, opts) {
46510
47479
  ],
46511
47480
  // The FITTED inset projection (just fit to this box) — captured so the
46512
47481
  // geo-query can invert pixels inside the frame back to AK/HI coords.
46513
- projection: proj
47482
+ projection: proj,
47483
+ ...contextLand && { contextLand }
46514
47484
  });
46515
47485
  insetRegions.push({
46516
47486
  id: iso,
46517
47487
  d,
46518
47488
  fill: fill2,
46519
- stroke: regionStroke,
47489
+ stroke: colorizeActive ? colorizeStroke(fill2) : regionStroke,
46520
47490
  lineNumber,
46521
47491
  layer: "us-state",
46522
47492
  ...r?.value !== void 0 && { value: r.value },
@@ -46529,13 +47499,16 @@ function layoutMap(resolved, data, size, opts) {
46529
47499
  }
46530
47500
  return xr;
46531
47501
  };
46532
- const akRight = placeInset(
46533
- "US-AK",
46534
- alaskaProjection(),
46535
- FIT_PAD,
46536
- width * 0.15
46537
- );
46538
- placeInset("US-HI", hawaiiProjection(), akRight + 24, width * 0.1);
47502
+ let akRight = FIT_PAD;
47503
+ if (akRef)
47504
+ akRight = placeInset("US-AK", alaskaProjection(), FIT_PAD, width * 0.15);
47505
+ if (hiRef)
47506
+ placeInset(
47507
+ "US-HI",
47508
+ hawaiiProjection(),
47509
+ akRef ? akRight + 24 : FIT_PAD,
47510
+ width * 0.1
47511
+ );
46539
47512
  }
46540
47513
  const conusFit = resolved.projection === "albers-usa" && !!usLayer;
46541
47514
  const classifyExtent = conusFit ? (0, import_d3_geo2.geoBounds)(fitTarget) : resolved.extent;
@@ -46551,15 +47524,24 @@ function layoutMap(resolved, data, size, opts) {
46551
47524
  };
46552
47525
  const ringOverlapsView = (ring) => {
46553
47526
  let loMin = Infinity, loMax = -Infinity, rawMin = Infinity, rawMax = -Infinity;
47527
+ const lons = [];
46554
47528
  for (const [rawLon] of ring) {
46555
47529
  const lon = normLon(rawLon);
47530
+ lons.push(lon);
46556
47531
  if (lon < loMin) loMin = lon;
46557
47532
  if (lon > loMax) loMax = lon;
46558
47533
  if (rawLon < rawMin) rawMin = rawLon;
46559
47534
  if (rawLon > rawMax) rawMax = rawLon;
46560
47535
  }
46561
- if (loMax - loMin > 270) return false;
46562
- if (rawMax - rawMin > 180 && loMax - loMin < 90) return false;
47536
+ lons.sort((a, b) => a - b);
47537
+ let maxGap = 0;
47538
+ for (let i = 1; i < lons.length; i++)
47539
+ maxGap = Math.max(maxGap, lons[i] - lons[i - 1]);
47540
+ if (lons.length > 1)
47541
+ maxGap = Math.max(maxGap, lons[0] + 360 - lons[lons.length - 1]);
47542
+ const occupiedArc = 360 - maxGap;
47543
+ if (occupiedArc > 270) return false;
47544
+ if (rawMax - rawMin > 180 && occupiedArc < 90) return false;
46563
47545
  let px0 = Infinity, py0 = Infinity, px1 = -Infinity, py1 = -Infinity, anyFinite = false;
46564
47546
  for (const [lon, lat] of ring) {
46565
47547
  const p = project(lon, lat);
@@ -46632,7 +47614,7 @@ function layoutMap(resolved, data, size, opts) {
46632
47614
  const regions = [];
46633
47615
  const pushRegionLayer = (layerFeatures, layerKind, shouldCull) => {
46634
47616
  for (const [iso, f] of layerFeatures) {
46635
- if (layerKind === "us-state" && usContext && INSET_STATES.has(iso))
47617
+ if (layerKind === "us-state" && usContext && resolved.projection === "albers-usa" && INSET_STATES.has(iso))
46636
47618
  continue;
46637
47619
  if (layerKind === "country" && usContext && iso === "US") continue;
46638
47620
  if (layerKind === "country" && iso === "AQ" && !regionById.has("AQ"))
@@ -46644,7 +47626,8 @@ function layoutMap(resolved, data, size, opts) {
46644
47626
  if (!d) continue;
46645
47627
  const isThisLayer = r?.layer === layerKind;
46646
47628
  const isForeign = layerKind === "country" && usContext && iso !== "US";
46647
- let fill2 = isForeign ? foreignFill : neutralFill;
47629
+ const baseFill = isForeign ? foreignFill : neutralFill;
47630
+ let fill2 = colorizeActive ? colorByIso.get(iso) ?? baseFill : baseFill;
46648
47631
  let label;
46649
47632
  let lineNumber = -1;
46650
47633
  let layer = "base";
@@ -46653,12 +47636,14 @@ function layoutMap(resolved, data, size, opts) {
46653
47636
  lineNumber = r.lineNumber;
46654
47637
  layer = layerKind;
46655
47638
  label = r.name;
47639
+ } else {
47640
+ label = f.properties?.name;
46656
47641
  }
46657
47642
  regions.push({
46658
47643
  id: iso,
46659
47644
  d,
46660
47645
  fill: fill2,
46661
- stroke: regionStroke,
47646
+ stroke: colorizeActive ? colorizeStroke(fill2) : regionStroke,
46662
47647
  lineNumber,
46663
47648
  layer,
46664
47649
  ...label !== void 0 && { label },
@@ -46686,9 +47671,41 @@ function layoutMap(resolved, data, size, opts) {
46686
47671
  });
46687
47672
  }
46688
47673
  }
47674
+ const pointInRings = (px, py, rings) => {
47675
+ let inside = false;
47676
+ for (const ring of rings) {
47677
+ for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
47678
+ const [xi, yi] = ring[i];
47679
+ const [xj, yj] = ring[j];
47680
+ if (yi > py !== yj > py && px < (xj - xi) * (py - yi) / (yj - yi) + xi)
47681
+ inside = !inside;
47682
+ }
47683
+ }
47684
+ return inside;
47685
+ };
47686
+ const fillHitTargets = [...regions, ...insetRegions].map((r) => ({
47687
+ fill: r.fill,
47688
+ rings: parsePathRings(r.d)
47689
+ }));
47690
+ const fillAt = (x, y) => {
47691
+ let hit = water;
47692
+ for (const t of fillHitTargets)
47693
+ if (pointInRings(x, y, t.rings)) hit = t.fill;
47694
+ return hit;
47695
+ };
47696
+ const labelOnFill = (fill2) => {
47697
+ const color = contrastRatio(fill2, palette.textOnFillDark) >= contrastRatio(fill2, palette.textOnFillLight) ? palette.textOnFillDark : palette.textOnFillLight;
47698
+ const haloColor = color === palette.textOnFillLight ? palette.textOnFillDark : palette.textOnFillLight;
47699
+ return {
47700
+ color,
47701
+ halo: contrastRatio(fill2, color) < REGION_LABEL_HALO_RATIO,
47702
+ haloColor
47703
+ };
47704
+ };
47705
+ const reliefAllowed = resolved.directives.noRelief !== true;
46689
47706
  const relief = [];
46690
47707
  let reliefHatch = null;
46691
- if (resolved.directives.relief === true && data.mountainRanges) {
47708
+ if (reliefAllowed && data.mountainRanges) {
46692
47709
  for (const [, f] of decodeLayer(data.mountainRanges)) {
46693
47710
  const viewF = isGlobalView ? dropFrameFillers(f) : cullFeatureToView(f);
46694
47711
  if (!viewF) continue;
@@ -46704,16 +47721,32 @@ function layoutMap(resolved, data, size, opts) {
46704
47721
  if (relief.length) {
46705
47722
  const darkTone = isDark ? palette.bg : palette.text;
46706
47723
  const lightTone = isDark ? palette.text : palette.bg;
46707
- const landLum = relativeLuminance(neutralFill);
47724
+ const reliefLandRef = colorizeActive ? isDark ? palette.surface : palette.bg : neutralFill;
47725
+ const landLum = relativeLuminance(reliefLandRef);
46708
47726
  const tone = Math.abs(landLum - relativeLuminance(darkTone)) > 0.04 ? darkTone : lightTone;
46709
47727
  reliefHatch = {
46710
- color: mix(tone, neutralFill, RELIEF_HATCH_STRENGTH),
47728
+ color: mix(tone, reliefLandRef, RELIEF_HATCH_STRENGTH),
46711
47729
  spacing: RELIEF_HATCH_SPACING,
46712
47730
  width: RELIEF_HATCH_WIDTH
46713
47731
  };
46714
47732
  }
46715
47733
  }
46716
- const riverColor = mix(water, regionStroke, 16);
47734
+ let coastlineStyle = null;
47735
+ if (resolved.directives.noCoastline !== true) {
47736
+ const minDim = Math.min(width, height);
47737
+ coastlineStyle = {
47738
+ color: mix(regionStroke, water, COASTLINE_STROKE_MIX),
47739
+ // N equal-width rings: distance steps outward by COASTLINE_STEP; opacity
47740
+ // fades linearly from NEAR (innermost) to FAR (outermost).
47741
+ lines: Array.from({ length: COASTLINE_RING_COUNT }, (_, k) => ({
47742
+ d: (COASTLINE_D0 + k * COASTLINE_STEP) * minDim,
47743
+ thickness: COASTLINE_THICKNESS * minDim,
47744
+ opacity: COASTLINE_OPACITY_NEAR + (COASTLINE_OPACITY_FAR - COASTLINE_OPACITY_NEAR) * k / (COASTLINE_RING_COUNT - 1)
47745
+ })),
47746
+ minExtent: (isGlobalView ? COASTLINE_MIN_EXTENT_GLOBAL : COASTLINE_MIN_EXTENT) * minDim
47747
+ };
47748
+ }
47749
+ const riverColor = mix(palette.colors.blue, water, 32);
46717
47750
  const rivers = [];
46718
47751
  if (data.rivers) {
46719
47752
  for (const [, f] of decodeLayer(data.rivers)) {
@@ -46769,38 +47802,108 @@ function layoutMap(resolved, data, size, opts) {
46769
47802
  const xy = project(p.lon, p.lat);
46770
47803
  if (xy) projected.push({ p, xy });
46771
47804
  }
46772
- const coloGroups = /* @__PURE__ */ new Map();
47805
+ const placePoi = (e, cx, cy, clusterId) => {
47806
+ const { fill: fill2, stroke: stroke2 } = poiFill(e.p);
47807
+ poiScreen.set(e.p.id, { cx, cy, r: radiusFor(e.p) });
47808
+ const num = routeNumberById.get(e.p.id);
47809
+ pois.push({
47810
+ id: e.p.id,
47811
+ cx,
47812
+ cy,
47813
+ r: radiusFor(e.p),
47814
+ fill: fill2,
47815
+ stroke: stroke2,
47816
+ lineNumber: e.p.lineNumber,
47817
+ implicit: !!e.p.implicit,
47818
+ isOrigin: originIds.has(e.p.id),
47819
+ ...num !== void 0 && { routeNumber: num },
47820
+ ...Object.keys(e.p.tags).length > 0 && { tags: e.p.tags },
47821
+ ...clusterId !== void 0 && { clusterId }
47822
+ });
47823
+ };
47824
+ const clusters = [];
47825
+ const connected = /* @__PURE__ */ new Set();
47826
+ for (const e of resolved.edges) {
47827
+ connected.add(e.fromId);
47828
+ connected.add(e.toId);
47829
+ }
47830
+ for (const rt of resolved.routes) {
47831
+ rt.stopIds.forEach((id) => connected.add(id));
47832
+ }
47833
+ const radiusOf = (e) => radiusFor(e.p);
46773
47834
  for (const e of projected) {
46774
- const key = `${Math.round(e.xy[0] / COLO_EPS)},${Math.round(e.xy[1] / COLO_EPS)}`;
46775
- const arr = coloGroups.get(key);
46776
- if (arr) arr.push(e);
46777
- else coloGroups.set(key, [e]);
46778
- }
46779
- for (const group of coloGroups.values()) {
46780
- group.forEach((e, i) => {
46781
- let cx = e.xy[0];
46782
- let cy = e.xy[1];
46783
- if (group.length > 1) {
46784
- const ang = i * GOLDEN_ANGLE;
46785
- cx += Math.cos(ang) * COLO_R;
46786
- cy += Math.sin(ang) * COLO_R;
46787
- }
46788
- const { fill: fill2, stroke: stroke2 } = poiFill(e.p);
46789
- poiScreen.set(e.p.id, { cx, cy, r: radiusFor(e.p) });
46790
- const num = routeNumberById.get(e.p.id);
46791
- pois.push({
46792
- id: e.p.id,
46793
- cx,
46794
- cy,
46795
- r: radiusFor(e.p),
46796
- fill: fill2,
46797
- stroke: stroke2,
46798
- lineNumber: e.p.lineNumber,
46799
- implicit: !!e.p.implicit,
46800
- isOrigin: originIds.has(e.p.id),
46801
- ...num !== void 0 && { routeNumber: num },
46802
- ...Object.keys(e.p.tags).length > 0 && { tags: e.p.tags }
46803
- });
47835
+ if (connected.has(e.p.id)) placePoi(e, e.xy[0], e.xy[1]);
47836
+ }
47837
+ const groups = [];
47838
+ for (const e of projected) {
47839
+ if (connected.has(e.p.id)) continue;
47840
+ const r = radiusOf(e);
47841
+ const near = groups.find(
47842
+ (g) => g.some(
47843
+ (q) => Math.hypot(q.xy[0] - e.xy[0], q.xy[1] - e.xy[1]) < (r + radiusOf(q)) * STACK_OVERLAP
47844
+ )
47845
+ );
47846
+ if (near) near.push(e);
47847
+ else groups.push([e]);
47848
+ }
47849
+ for (const g of groups) {
47850
+ if (g.length === 1) {
47851
+ placePoi(g[0], g[0].xy[0], g[0].xy[1]);
47852
+ continue;
47853
+ }
47854
+ const clusterId = g[0].p.id;
47855
+ const cx0 = g.reduce((s, e) => s + e.xy[0], 0) / g.length;
47856
+ const cy0 = g.reduce((s, e) => s + e.xy[1], 0) / g.length;
47857
+ const maxR = Math.max(...g.map(radiusOf));
47858
+ const sep = 2 * maxR + STACK_RING_GAP;
47859
+ const ringR = Math.max(
47860
+ COLO_R,
47861
+ sep / (2 * Math.sin(Math.PI / Math.max(g.length, 2)))
47862
+ );
47863
+ const positions = g.map((e, i) => {
47864
+ if (g.length <= STACK_RING_MAX) {
47865
+ const ang2 = -Math.PI / 2 + i * 2 * Math.PI / g.length;
47866
+ return {
47867
+ e,
47868
+ mx: cx0 + Math.cos(ang2) * ringR,
47869
+ my: cy0 + Math.sin(ang2) * ringR
47870
+ };
47871
+ }
47872
+ const ang = i * GOLDEN_ANGLE;
47873
+ const rr = ringR * Math.sqrt((i + 1) / g.length);
47874
+ return { e, mx: cx0 + Math.cos(ang) * rr, my: cy0 + Math.sin(ang) * rr };
47875
+ });
47876
+ let minX = cx0 - maxR;
47877
+ let maxX = cx0 + maxR;
47878
+ let minY = cy0 - maxR;
47879
+ let maxY = cy0 + maxR;
47880
+ for (const { mx, my, e } of positions) {
47881
+ const r = radiusOf(e);
47882
+ minX = Math.min(minX, mx - r);
47883
+ maxX = Math.max(maxX, mx + r);
47884
+ minY = Math.min(minY, my - r);
47885
+ maxY = Math.max(maxY, my + r);
47886
+ }
47887
+ let dx = 0;
47888
+ let dy = 0;
47889
+ if (minX + dx < 2) dx = 2 - minX;
47890
+ if (maxX + dx > width - 2) dx = width - 2 - maxX;
47891
+ if (minY + dy < 2) dy = 2 - minY;
47892
+ if (maxY + dy > height - 2) dy = height - 2 - maxY;
47893
+ const legsOut = [];
47894
+ for (const { e, mx, my } of positions) {
47895
+ const fx = mx + dx;
47896
+ const fy = my + dy;
47897
+ placePoi(e, fx, fy, clusterId);
47898
+ legsOut.push({ x2: fx, y2: fy, color: poiFill(e.p).fill });
47899
+ }
47900
+ clusters.push({
47901
+ id: clusterId,
47902
+ cx: cx0 + dx,
47903
+ cy: cy0 + dy,
47904
+ count: g.length,
47905
+ hitR: ringR + maxR + 6,
47906
+ legs: legsOut
46804
47907
  });
46805
47908
  }
46806
47909
  const legs = [];
@@ -46850,16 +47953,26 @@ function layoutMap(resolved, data, size, opts) {
46850
47953
  if (!a || !b) continue;
46851
47954
  const mx = (a.cx + b.cx) / 2;
46852
47955
  const my = (a.cy + b.cy) / 2;
47956
+ const bow = {
47957
+ curved: leg.style === "arc",
47958
+ offset: 0,
47959
+ labelX: mx,
47960
+ labelY: my - 4
47961
+ };
47962
+ const routeLabelStyle = leg.label !== void 0 ? labelOnFill(fillAt(bow.labelX, bow.labelY)) : void 0;
46853
47963
  legs.push({
46854
- d: legPath(a, b, leg.style === "arc", 0),
47964
+ d: legPath(a, b, bow.curved, bow.offset),
46855
47965
  width: routeWidthFor(Number(leg.value)),
46856
47966
  color: mix(palette.text, palette.bg, 72),
46857
47967
  arrow: true,
46858
47968
  lineNumber: leg.lineNumber,
46859
47969
  ...leg.label !== void 0 && {
46860
47970
  label: leg.label,
46861
- labelX: mx,
46862
- labelY: my - 4
47971
+ labelX: bow.labelX,
47972
+ labelY: bow.labelY,
47973
+ labelColor: routeLabelStyle.color,
47974
+ labelHalo: routeLabelStyle.halo,
47975
+ labelHaloColor: routeLabelStyle.haloColor
46863
47976
  }
46864
47977
  });
46865
47978
  }
@@ -46887,20 +48000,29 @@ function layoutMap(resolved, data, size, opts) {
46887
48000
  const a = poiScreen.get(e.fromId);
46888
48001
  const b = poiScreen.get(e.toId);
46889
48002
  if (!a || !b) return;
46890
- const curved = e.style === "arc" || n > 1;
46891
- const offset = n > 1 ? (i - (n - 1) / 2) * FAN_STEP : 0;
48003
+ const fanOffset = n > 1 ? (i - (n - 1) / 2) * FAN_STEP : 0;
46892
48004
  const mx = (a.cx + b.cx) / 2;
46893
48005
  const my = (a.cy + b.cy) / 2;
48006
+ const bow = {
48007
+ curved: e.style === "arc" || n > 1,
48008
+ offset: fanOffset,
48009
+ labelX: mx,
48010
+ labelY: my - 4
48011
+ };
48012
+ const edgeLabelStyle = e.label !== void 0 ? labelOnFill(fillAt(bow.labelX, bow.labelY)) : void 0;
46894
48013
  legs.push({
46895
- d: legPath(a, b, curved, offset),
48014
+ d: legPath(a, b, bow.curved, bow.offset),
46896
48015
  width: widthFor(e),
46897
48016
  color: mix(palette.text, palette.bg, 66),
46898
48017
  arrow: e.directed,
46899
48018
  lineNumber: e.lineNumber,
46900
48019
  ...e.label !== void 0 && {
46901
48020
  label: e.label,
46902
- labelX: mx,
46903
- labelY: my - 4
48021
+ labelX: bow.labelX,
48022
+ labelY: bow.labelY,
48023
+ labelColor: edgeLabelStyle.color,
48024
+ labelHalo: edgeLabelStyle.halo,
48025
+ labelHaloColor: edgeLabelStyle.haloColor
46904
48026
  }
46905
48027
  });
46906
48028
  });
@@ -46942,25 +48064,25 @@ function layoutMap(resolved, data, size, opts) {
46942
48064
  }
46943
48065
  }
46944
48066
  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));
46945
- const regionLabelMode = resolved.directives.regionLabels ?? "off";
48067
+ const showRegionLabels = resolved.directives.noRegionLabels !== true;
48068
+ const isCompact = width < COMPACT_WIDTH_PX;
46946
48069
  const LABEL_PADX = 6;
46947
48070
  const LABEL_PADY = 3;
46948
- const labelW = (text) => measureLegendText(text, FONT) + 2 * LABEL_PADX;
46949
- const labelH = FONT + 2 * LABEL_PADY;
48071
+ const labelW = (text) => measureLegendText(text, FONT2) + 2 * LABEL_PADX;
48072
+ const labelH = FONT2 + 2 * LABEL_PADY;
46950
48073
  const pushRegionLabel = (x, y, text, fill2, lineNumber) => {
46951
- const color = contrastText(
46952
- fill2,
46953
- palette.textOnFillLight,
46954
- palette.textOnFillDark
48074
+ const { color, haloColor } = labelOnFill(fill2);
48075
+ const halfW = measureLegendText(text, FONT2) / 2;
48076
+ const overflows = [y - FONT2 * 0.55, y - FONT2 * 0.1].some(
48077
+ (sy) => fillAt(x - halfW, sy) !== fill2 || fillAt(x + halfW, sy) !== fill2
46955
48078
  );
46956
- const haloColor = color === palette.textOnFillLight ? palette.textOnFillDark : palette.textOnFillLight;
46957
48079
  labels.push({
46958
48080
  x,
46959
48081
  y,
46960
48082
  text,
46961
48083
  anchor: "middle",
46962
48084
  color,
46963
- halo: true,
48085
+ halo: overflows,
46964
48086
  haloColor,
46965
48087
  lineNumber
46966
48088
  });
@@ -46969,21 +48091,50 @@ function layoutMap(resolved, data, size, opts) {
46969
48091
  US: [-98.5, 39.5]
46970
48092
  // CONUS geographic centre (near Lebanon, Kansas)
46971
48093
  };
46972
- if (regionLabelMode === "full" || regionLabelMode === "abbrev") {
46973
- for (const r of regions) {
46974
- if (r.layer === "base" || r.label === void 0) continue;
46975
- const f = r.layer === "us-state" ? usLayer?.get(r.id) : worldLayer.get(r.id);
46976
- if (!f) continue;
48094
+ const REGION_LABEL_GAP = 2;
48095
+ const regionLabelRect = (cx, cy, text) => {
48096
+ const w = measureLegendText(text, FONT2) + 2 * REGION_LABEL_GAP;
48097
+ return { x: cx - w / 2, y: cy - FONT2 / 2, w, h: FONT2 };
48098
+ };
48099
+ if (showRegionLabels) {
48100
+ const frameContainers = new Set(resolved.poiFrameContainers);
48101
+ const entries = regions.map((r) => {
48102
+ const isContainer = frameContainers.has(r.id);
48103
+ if (r.layer === "base" && !isContainer || r.label === void 0)
48104
+ return null;
48105
+ const isUsState = r.layer === "us-state" || r.id.startsWith("US-");
48106
+ const f = isUsState ? usLayer?.get(r.id) : worldLayer.get(r.id);
48107
+ if (!f) return null;
46977
48108
  const [[x0, y0], [x1, y1]] = path.bounds(f);
46978
- const text = regionLabelMode === "abbrev" ? r.id.replace(/^US-/, "") : r.label;
46979
- if (labelW(text) > x1 - x0 || labelH > y1 - y0) continue;
46980
- const anchor = r.layer !== "us-state" ? WORLD_LABEL_ANCHORS[r.id] : void 0;
48109
+ const boxW = x1 - x0;
48110
+ const boxH = y1 - y0;
48111
+ const abbrev = isUsState ? r.id.replace(/^US-/, "") : void 0;
48112
+ const candidates = abbrev !== void 0 ? isCompact ? [abbrev, r.label] : [r.label, abbrev] : [r.label];
48113
+ const anchor = !isUsState ? WORLD_LABEL_ANCHORS[r.id] : void 0;
46981
48114
  const c = anchor ? project(anchor[0], anchor[1]) : path.centroid(f);
46982
- if (!c || !Number.isFinite(c[0])) continue;
48115
+ if (!c || !Number.isFinite(c[0])) return null;
48116
+ return { r, c, boxW, boxH, area: boxW * boxH, candidates };
48117
+ }).filter((e) => e !== null).sort((a, b) => b.area - a.area || a.r.lineNumber - b.r.lineNumber);
48118
+ const placedRegionRects = [];
48119
+ const POI_LABEL_PAD = 14;
48120
+ const poiObstacles = pois.map((p) => ({
48121
+ x: p.cx - p.r - POI_LABEL_PAD,
48122
+ y: p.cy - p.r - POI_LABEL_PAD,
48123
+ w: 2 * (p.r + POI_LABEL_PAD),
48124
+ h: 2 * (p.r + POI_LABEL_PAD)
48125
+ }));
48126
+ for (const { r, c, boxW, boxH, candidates } of entries) {
48127
+ const text = candidates.find((t) => {
48128
+ if (labelW(t) > boxW || labelH > boxH) return false;
48129
+ const rect = regionLabelRect(c[0], c[1], t);
48130
+ return !placedRegionRects.some((p) => rectsOverlap(rect, p)) && !poiObstacles.some((o) => rectsOverlap(rect, o));
48131
+ });
48132
+ if (text === void 0) continue;
48133
+ placedRegionRects.push(regionLabelRect(c[0], c[1], text));
46983
48134
  pushRegionLabel(c[0], c[1], text, r.fill, r.lineNumber);
46984
48135
  }
46985
48136
  for (const seed of insetLabelSeeds) {
46986
- const text = regionLabelMode === "abbrev" ? seed.iso.replace(/^US-/, "") : seed.name;
48137
+ const text = isCompact ? seed.iso.replace(/^US-/, "") : seed.name;
46987
48138
  const src = regionById.get(seed.iso);
46988
48139
  pushRegionLabel(
46989
48140
  seed.x,
@@ -46994,22 +48145,26 @@ function layoutMap(resolved, data, size, opts) {
46994
48145
  );
46995
48146
  }
46996
48147
  }
46997
- const poiLabelMode = resolved.directives.poiLabels ?? "auto";
46998
- if (poiLabelMode !== "off") {
46999
- const ordered = [...pois].sort(
47000
- (a, b) => a.lineNumber - b.lineNumber || (a.id < b.id ? -1 : 1)
47001
- );
48148
+ if (resolved.directives.noPoiLabels !== true) {
48149
+ const ordered = [...pois].filter((p) => p.clusterId === void 0).sort((a, b) => a.lineNumber - b.lineNumber || (a.id < b.id ? -1 : 1));
47002
48150
  const poiById = new Map(resolved.pois.map((q) => [q.id, q]));
47003
48151
  const labelText = (p) => {
47004
48152
  const src = poiById.get(p.id);
47005
48153
  return src?.label ?? src?.name ?? p.id;
47006
48154
  };
47007
- const poiLabH = FONT * 1.25;
48155
+ const poiLabH = FONT2 * 1.25;
47008
48156
  const labelInfo = (p) => {
47009
48157
  const text = labelText(p);
47010
- return { text, w: measureLegendText(text, FONT) };
48158
+ return { text, w: measureLegendText(text, FONT2) };
47011
48159
  };
47012
48160
  const GAP = 3;
48161
+ const clusterMembersById = /* @__PURE__ */ new Map();
48162
+ for (const p of pois) {
48163
+ if (p.clusterId === void 0) continue;
48164
+ const arr = clusterMembersById.get(p.clusterId);
48165
+ if (arr) arr.push(p);
48166
+ else clusterMembersById.set(p.clusterId, [p]);
48167
+ }
47013
48168
  const inlineRect = (p, w, side) => {
47014
48169
  switch (side) {
47015
48170
  case "right":
@@ -47039,11 +48194,11 @@ function layoutMap(resolved, data, size, opts) {
47039
48194
  const x = side === "right" ? rect.x : side === "left" ? rect.x + w : p.cx;
47040
48195
  labels.push({
47041
48196
  x,
47042
- y: rect.y + poiLabH / 2 + FONT / 3,
48197
+ y: rect.y + poiLabH / 2 + FONT2 / 3,
47043
48198
  text,
47044
48199
  anchor,
47045
48200
  color: palette.text,
47046
- halo: true,
48201
+ halo: false,
47047
48202
  haloColor: palette.bg,
47048
48203
  poiId: p.id,
47049
48204
  lineNumber: p.lineNumber
@@ -47054,43 +48209,60 @@ function layoutMap(resolved, data, size, opts) {
47054
48209
  return rect.x >= 0 && rect.x + rect.w <= width && rect.y >= 0 && rect.y + rect.h <= height && !collides(rect);
47055
48210
  };
47056
48211
  const GROUP_R = 30;
47057
- const groups = [];
48212
+ const groups2 = [];
47058
48213
  for (const p of ordered) {
47059
- const near = groups.find(
48214
+ const near = groups2.find(
47060
48215
  (g) => g.some((q) => Math.hypot(q.cx - p.cx, q.cy - p.cy) < GROUP_R)
47061
48216
  );
47062
48217
  if (near) near.push(p);
47063
- else groups.push([p]);
48218
+ else groups2.push([p]);
47064
48219
  }
47065
48220
  const ROW_GAP2 = 3;
47066
48221
  const step = poiLabH + ROW_GAP2;
47067
48222
  const COL_GAP = 16;
47068
- const placeColumn = (group) => {
47069
- const items = group.map((p) => ({ p, ...labelInfo(p) })).sort((a, b) => a.p.cy - b.p.cy || (a.text < b.text ? -1 : 1));
48223
+ const makeItems = (group) => group.map((p) => ({ p, ...labelInfo(p) })).sort((a, b) => a.p.cy - b.p.cy || (a.text < b.text ? -1 : 1));
48224
+ const columnRows = (items, side) => {
47070
48225
  const left = Math.min(...items.map((o) => o.p.cx - o.p.r));
47071
48226
  const right = Math.max(...items.map((o) => o.p.cx + o.p.r));
47072
- const cyMid = (Math.min(...items.map((o) => o.p.cy)) + Math.max(...items.map((o) => o.p.cy))) / 2;
47073
48227
  const maxW = Math.max(...items.map((o) => o.w));
47074
- const side = right + COL_GAP + maxW <= width - 2 ? "right" : "left";
47075
- const colX = side === "right" ? right + COL_GAP : left - COL_GAP;
48228
+ const cyMid = (Math.min(...items.map((o) => o.p.cy)) + Math.max(...items.map((o) => o.p.cy))) / 2;
48229
+ const colX = side === "right" ? Math.min(right + COL_GAP, width - 2 - maxW) : Math.max(left - COL_GAP, 2 + maxW);
47076
48230
  const totalH = items.length * step;
47077
48231
  let startY = cyMid - totalH / 2;
47078
48232
  startY = Math.max(2, Math.min(startY, height - totalH - 2));
47079
- items.forEach((o, i) => {
48233
+ return items.map((o, i) => {
47080
48234
  const rowCy = startY + i * step + step / 2;
47081
- obstacles.push({
47082
- x: side === "right" ? colX : colX - o.w,
47083
- y: rowCy - poiLabH / 2,
47084
- w: o.w,
47085
- h: poiLabH
47086
- });
48235
+ return {
48236
+ o,
48237
+ colX,
48238
+ rowCy,
48239
+ rect: {
48240
+ x: side === "right" ? colX : colX - o.w,
48241
+ y: rowCy - poiLabH / 2,
48242
+ w: o.w,
48243
+ h: poiLabH
48244
+ }
48245
+ };
48246
+ });
48247
+ };
48248
+ const wouldColumnBeClean = (items, side) => columnRows(items, side).every(
48249
+ ({ rect }) => rect.x >= 0 && rect.x + rect.w <= width && rect.y >= 0 && rect.y + rect.h <= height && !collides(rect)
48250
+ );
48251
+ const defaultColumnSide = (items) => {
48252
+ const right = Math.max(...items.map((o) => o.p.cx + o.p.r));
48253
+ const maxW = Math.max(...items.map((o) => o.w));
48254
+ return right + COL_GAP + maxW <= width - 2 ? "right" : "left";
48255
+ };
48256
+ const commitColumn = (items, side, clusterId) => {
48257
+ for (const { o, colX, rowCy, rect } of columnRows(items, side)) {
48258
+ obstacles.push(rect);
47087
48259
  labels.push({
47088
48260
  x: colX,
47089
- y: rowCy + FONT / 3,
48261
+ y: rowCy + FONT2 / 3,
47090
48262
  text: o.text,
47091
48263
  anchor: side === "right" ? "start" : "end",
47092
48264
  color: palette.text,
47093
- halo: true,
48265
+ halo: false,
47094
48266
  haloColor: palette.bg,
47095
48267
  leader: {
47096
48268
  x1: o.p.cx,
@@ -47100,24 +48272,141 @@ function layoutMap(resolved, data, size, opts) {
47100
48272
  },
47101
48273
  leaderColor: o.p.fill,
47102
48274
  poiId: o.p.id,
47103
- lineNumber: o.p.lineNumber
48275
+ lineNumber: o.p.lineNumber,
48276
+ ...clusterId !== void 0 && { clusterMember: clusterId }
47104
48277
  });
48278
+ }
48279
+ };
48280
+ const pushHidden = (p) => {
48281
+ const { text, w } = labelInfo(p);
48282
+ let x = p.cx + p.r + GAP;
48283
+ let anchor = "start";
48284
+ if (x + w > width) {
48285
+ x = p.cx - p.r - GAP - w;
48286
+ anchor = "end";
48287
+ }
48288
+ const y = Math.max(0, Math.min(p.cy - poiLabH / 2, height - poiLabH));
48289
+ labels.push({
48290
+ x: anchor === "start" ? x : x + w,
48291
+ y: y + poiLabH / 2 + FONT2 / 3,
48292
+ text,
48293
+ anchor,
48294
+ color: palette.text,
48295
+ halo: false,
48296
+ haloColor: palette.bg,
48297
+ poiId: p.id,
48298
+ hidden: true,
48299
+ lineNumber: p.lineNumber
47105
48300
  });
47106
48301
  };
47107
- for (const g of groups) {
48302
+ for (const [clusterId, members] of clusterMembersById) {
48303
+ if (members.length === 0) continue;
48304
+ const items = makeItems(members);
48305
+ const side = wouldColumnBeClean(items, "right") ? "right" : wouldColumnBeClean(items, "left") ? "left" : defaultColumnSide(items);
48306
+ commitColumn(items, side, clusterId);
48307
+ }
48308
+ const maxExtent = MAX_CLUSTER_EXTENT_FACTOR * Math.min(width, height);
48309
+ const clusterPending = [];
48310
+ for (const g of groups2) {
48311
+ const items = makeItems(g);
47108
48312
  if (g.length === 1) {
47109
- const p = g[0];
47110
- const { text, w } = labelInfo(p);
48313
+ const { p, text, w } = items[0];
47111
48314
  const side = ["right", "left", "above", "below"].find(
47112
48315
  (s) => inlineFits(p, w, s)
47113
48316
  );
47114
- if (side) {
47115
- pushInline(p, text, w, side);
47116
- continue;
48317
+ if (side) pushInline(p, text, w, side);
48318
+ else commitColumn(items, defaultColumnSide(items));
48319
+ continue;
48320
+ }
48321
+ const left = Math.min(...items.map((o) => o.p.cx - o.p.r));
48322
+ const right = Math.max(...items.map((o) => o.p.cx + o.p.r));
48323
+ const minCy = Math.min(...items.map((o) => o.p.cy));
48324
+ const maxCy = Math.max(...items.map((o) => o.p.cy));
48325
+ const diag = Math.hypot(right - left, maxCy - minCy);
48326
+ if (diag > maxExtent || items.length > MAX_COLUMN_ROWS) {
48327
+ items.forEach((o) => pushHidden(o.p));
48328
+ } else {
48329
+ clusterPending.push(items);
48330
+ }
48331
+ }
48332
+ for (const items of clusterPending) {
48333
+ const side = ["right", "left"].find(
48334
+ (s) => wouldColumnBeClean(items, s)
48335
+ );
48336
+ if (side) commitColumn(items, side);
48337
+ else items.forEach((o) => pushHidden(o.p));
48338
+ }
48339
+ }
48340
+ if (resolved.directives.noContextLabels !== true) {
48341
+ for (const l of labels) {
48342
+ if (l.hidden) continue;
48343
+ const w = labelW(l.text);
48344
+ const x = l.anchor === "start" ? l.x : l.anchor === "end" ? l.x - w : l.x - w / 2;
48345
+ obstacles.push({ x, y: l.y - labelH / 2, w, h: labelH });
48346
+ }
48347
+ for (const box of insets)
48348
+ obstacles.push({ x: box.x, y: box.y, w: box.w, h: box.h });
48349
+ const countryCandidates = [];
48350
+ for (const f of worldLayer.values()) {
48351
+ const iso = typeof f.id === "string" ? f.id : String(f.id ?? "");
48352
+ if (!iso || regionById.has(iso)) continue;
48353
+ let hasReferencedSub = false;
48354
+ for (const k of regionById.keys())
48355
+ if (k.startsWith(iso + "-")) {
48356
+ hasReferencedSub = true;
48357
+ break;
47117
48358
  }
48359
+ if (hasReferencedSub) continue;
48360
+ const b = path.bounds(f);
48361
+ const [x0, y0] = b[0];
48362
+ const [x1, y1] = b[1];
48363
+ if (!Number.isFinite(x0) || !Number.isFinite(x1)) continue;
48364
+ const anchorLngLat = WORLD_LABEL_ANCHORS[iso];
48365
+ const a = anchorLngLat ? project(anchorLngLat[0], anchorLngLat[1]) : path.centroid(f);
48366
+ countryCandidates.push({
48367
+ name: f.properties?.name ?? iso,
48368
+ bbox: [x0, y0, x1, y1],
48369
+ anchor: a && Number.isFinite(a[0]) ? [a[0], a[1]] : null
48370
+ });
48371
+ }
48372
+ const framedStateContainers = (resolved.poiFrameContainers ?? []).some(
48373
+ (id) => id.startsWith("US-")
48374
+ );
48375
+ if (usLayer && framedStateContainers) {
48376
+ const containerSet = new Set(resolved.poiFrameContainers);
48377
+ for (const [iso, f] of usLayer) {
48378
+ if (containerSet.has(iso) || regionById.has(iso)) continue;
48379
+ const viewF = cullFeatureToView(f);
48380
+ if (!viewF) continue;
48381
+ const b = path.bounds(viewF);
48382
+ const [x0, y0] = b[0];
48383
+ const [x1, y1] = b[1];
48384
+ if (!Number.isFinite(x0) || !Number.isFinite(x1)) continue;
48385
+ const a = path.centroid(viewF);
48386
+ countryCandidates.push({
48387
+ name: f.properties?.name ?? iso,
48388
+ bbox: [x0, y0, x1, y1],
48389
+ anchor: a && Number.isFinite(a[0]) ? [a[0], a[1]] : null
48390
+ });
47118
48391
  }
47119
- placeColumn(g);
47120
48392
  }
48393
+ const contextLabels = placeContextLabels({
48394
+ projection: resolved.projection,
48395
+ dLonSpan,
48396
+ dLatSpan,
48397
+ width,
48398
+ height,
48399
+ waterBodies: data.waterBodies,
48400
+ countries: countryCandidates,
48401
+ palette,
48402
+ project,
48403
+ collides,
48404
+ // Water labels must stay over open water — `fillAt` returns the ocean
48405
+ // backdrop colour off-land and a region fill on-land (lakes/states count
48406
+ // as land here, which is the safe side for an ocean name).
48407
+ overLand: (x, y) => fillAt(x, y) !== water
48408
+ });
48409
+ labels.push(...contextLabels);
47121
48410
  }
47122
48411
  let legend = null;
47123
48412
  if (!resolved.directives.noLegend) {
@@ -47154,27 +48443,33 @@ function layoutMap(resolved, data, size, opts) {
47154
48443
  rivers,
47155
48444
  relief,
47156
48445
  reliefHatch,
48446
+ coastlineStyle,
47157
48447
  legs,
47158
48448
  pois,
48449
+ clusters,
47159
48450
  labels,
47160
48451
  legend,
47161
48452
  insets,
47162
48453
  insetRegions,
47163
48454
  projection,
47164
- stretch: stretchParams
48455
+ stretch: stretchParams,
48456
+ diagnostics: []
47165
48457
  };
47166
48458
  }
47167
- var import_d3_geo2, import_topojson_client2, 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;
48459
+ var import_d3_geo2, import_topojson_client2, 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;
47168
48460
  var init_layout15 = __esm({
47169
48461
  "src/map/layout.ts"() {
47170
48462
  "use strict";
47171
48463
  import_d3_geo2 = require("d3-geo");
47172
48464
  import_topojson_client2 = require("topojson-client");
47173
48465
  init_color_utils();
48466
+ init_geo();
48467
+ init_colorize();
47174
48468
  init_colors();
47175
48469
  init_label_layout();
47176
48470
  init_legend_constants();
47177
48471
  init_title_constants();
48472
+ init_context_labels();
47178
48473
  FIT_PAD = 24;
47179
48474
  RAMP_FLOOR = 15;
47180
48475
  R_DEFAULT = 6;
@@ -47182,32 +48477,66 @@ var init_layout15 = __esm({
47182
48477
  R_MAX = 22;
47183
48478
  W_MIN = 1.25;
47184
48479
  W_MAX = 8;
47185
- FONT = 11;
47186
- COLO_EPS = 1.5;
48480
+ FONT2 = 11;
48481
+ MAX_CLUSTER_EXTENT_FACTOR = 0.18;
48482
+ MAX_COLUMN_ROWS = 7;
48483
+ REGION_LABEL_HALO_RATIO = 4.5;
47187
48484
  LAND_TINT_LIGHT = 12;
47188
48485
  LAND_TINT_DARK = 24;
47189
48486
  TAG_TINT_LIGHT = 60;
47190
48487
  TAG_TINT_DARK = 68;
47191
- WATER_TINT_LIGHT = 13;
47192
- WATER_TINT_DARK = 14;
48488
+ WATER_TINT_LIGHT = 24;
48489
+ WATER_TINT_DARK = 24;
47193
48490
  RIVER_WIDTH = 1.3;
48491
+ COMPACT_WIDTH_PX = 480;
47194
48492
  RELIEF_MIN_AREA = 12;
47195
48493
  RELIEF_MIN_DIM = 2;
47196
- RELIEF_HATCH_SPACING = 3;
47197
- RELIEF_HATCH_WIDTH = 0.25;
48494
+ RELIEF_HATCH_SPACING = 2;
48495
+ RELIEF_HATCH_WIDTH = 0.15;
47198
48496
  RELIEF_HATCH_STRENGTH = 32;
48497
+ COASTLINE_RING_COUNT = 5;
48498
+ COASTLINE_D0 = 16e-4;
48499
+ COASTLINE_STEP = 28e-4;
48500
+ COASTLINE_THICKNESS = 14e-4;
48501
+ COASTLINE_OPACITY_NEAR = 0.5;
48502
+ COASTLINE_OPACITY_FAR = 0.1;
48503
+ COASTLINE_MIN_EXTENT = 6e-4;
48504
+ COASTLINE_MIN_EXTENT_GLOBAL = 6e-4;
48505
+ COASTLINE_STROKE_MIX = 32;
47199
48506
  FOREIGN_TINT_LIGHT = 30;
47200
48507
  FOREIGN_TINT_DARK = 62;
47201
48508
  MUTED_FOREIGN_LIGHT = 28;
47202
48509
  MUTED_FOREIGN_DARK = 16;
47203
48510
  COLO_R = 9;
47204
48511
  GOLDEN_ANGLE = 2.399963229728653;
48512
+ STACK_OVERLAP = 1;
48513
+ STACK_RING_MAX = 8;
48514
+ STACK_RING_GAP = 4;
47205
48515
  FAN_STEP = 16;
47206
48516
  ARC_CURVE_FRAC = 0.18;
48517
+ decodeCache = /* @__PURE__ */ new WeakMap();
47207
48518
  usConusProjection = () => (0, import_d3_geo2.geoConicEqualArea)().parallels([29.5, 45.5]).rotate([96, 0]);
47208
48519
  alaskaProjection = () => (0, import_d3_geo2.geoConicEqualArea)().rotate([154, 0]).center([-2, 58.5]).parallels([55, 65]);
47209
48520
  hawaiiProjection = () => (0, import_d3_geo2.geoMercator)();
47210
48521
  INSET_STATES = /* @__PURE__ */ new Set(["US-AK", "US-HI"]);
48522
+ inAlaska = (lon, lat) => lat >= 51 && (lon <= -129 || lon >= 172);
48523
+ inHawaii = (lon, lat) => lat >= 18 && lat <= 23 && lon >= -161 && lon <= -154;
48524
+ FOREIGN_BORDER = {
48525
+ CA: [
48526
+ "US-AK",
48527
+ "US-WA",
48528
+ "US-ID",
48529
+ "US-MT",
48530
+ "US-ND",
48531
+ "US-MN",
48532
+ "US-MI",
48533
+ "US-NY",
48534
+ "US-VT",
48535
+ "US-NH",
48536
+ "US-ME"
48537
+ ],
48538
+ MX: ["US-CA", "US-AZ", "US-NM", "US-TX"]
48539
+ };
47211
48540
  US_NON_CONUS = /* @__PURE__ */ new Set([
47212
48541
  "US-AK",
47213
48542
  "US-HI",
@@ -47226,6 +48555,58 @@ __export(renderer_exports16, {
47226
48555
  renderMap: () => renderMap,
47227
48556
  renderMapForExport: () => renderMapForExport
47228
48557
  });
48558
+ function pointInRing2(px, py, ring) {
48559
+ let inside = false;
48560
+ for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
48561
+ const [xi, yi] = ring[i];
48562
+ const [xj, yj] = ring[j];
48563
+ if (yi > py !== yj > py && px < (xj - xi) * (py - yi) / (yj - yi) + xi)
48564
+ inside = !inside;
48565
+ }
48566
+ return inside;
48567
+ }
48568
+ function ringToPath(ring) {
48569
+ let d = "";
48570
+ for (let i = 0; i < ring.length; i++)
48571
+ d += (i ? "L" : "M") + ring[i][0] + "," + ring[i][1];
48572
+ return d + "Z";
48573
+ }
48574
+ function coastlineOuterRings(regions, minExtent) {
48575
+ const paths = [];
48576
+ for (const r of regions) {
48577
+ const rings = parsePathRings(r.d);
48578
+ for (let i = 0; i < rings.length; i++) {
48579
+ const ring = rings[i];
48580
+ if (ring.length < 3) continue;
48581
+ let minX = Infinity;
48582
+ let minY = Infinity;
48583
+ let maxX = -Infinity;
48584
+ let maxY = -Infinity;
48585
+ for (const [x, y] of ring) {
48586
+ if (x < minX) minX = x;
48587
+ if (x > maxX) maxX = x;
48588
+ if (y < minY) minY = y;
48589
+ if (y > maxY) maxY = y;
48590
+ }
48591
+ if (Math.max(maxX - minX, maxY - minY) < minExtent) continue;
48592
+ const [fx, fy] = ring[0];
48593
+ let depth = 0;
48594
+ for (let j = 0; j < rings.length; j++)
48595
+ if (j !== i && pointInRing2(fx, fy, rings[j])) depth++;
48596
+ if (depth % 2 === 1) continue;
48597
+ paths.push(ringToPath(ring));
48598
+ }
48599
+ }
48600
+ return paths;
48601
+ }
48602
+ function appendWaterLines(g, outerRings, style, flatWater) {
48603
+ const d = outerRings.join(" ");
48604
+ const linesOuterFirst = [...style.lines].sort((a, b) => b.d - a.d);
48605
+ for (const line12 of linesOuterFirst) {
48606
+ 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");
48607
+ g.append("path").attr("d", d).attr("stroke", flatWater).attr("stroke-width", 2 * line12.d).attr("stroke-linejoin", "round").attr("stroke-linecap", "round");
48608
+ }
48609
+ }
47229
48610
  function renderMap(container, resolved, data, palette, isDark, onClickItem, exportDims, activeGroupOverride) {
47230
48611
  d3Selection18.select(container).selectAll(":not([data-d3-tooltip])").remove();
47231
48612
  const width = exportDims?.width ?? container.clientWidth;
@@ -47238,6 +48619,11 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47238
48619
  {
47239
48620
  palette,
47240
48621
  isDark,
48622
+ // Export-only: forward the contain-fit request from mapExportDimensions so a
48623
+ // clamped/floored (off-aspect) export canvas letterboxes instead of
48624
+ // stretch-distorting. The in-app preview pane passes no exportDims → unset →
48625
+ // keeps the global stretch-fill.
48626
+ preferContain: exportDims?.preferContain ?? false,
47241
48627
  ...activeGroupOverride !== void 0 && {
47242
48628
  activeGroup: activeGroupOverride
47243
48629
  }
@@ -47251,6 +48637,7 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47251
48637
  const gRegions = svg.append("g").attr("class", "dgmo-map-regions");
47252
48638
  const drawRegion = (g, r, strokeWidth) => {
47253
48639
  const p = g.append("path").attr("d", r.d).attr("fill", r.fill).attr("stroke", r.stroke).attr("stroke-width", strokeWidth);
48640
+ if (r.label) p.attr("data-region-name", r.label);
47254
48641
  if (r.layer !== "base") {
47255
48642
  p.classed("dgmo-map-region", true).attr("data-region", r.id);
47256
48643
  if (r.value !== void 0) p.attr("data-value", r.value);
@@ -47285,6 +48672,38 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47285
48672
  gRelief.append("line").attr("x1", 0).attr("y1", y).attr("x2", width).attr("y2", y);
47286
48673
  }
47287
48674
  }
48675
+ if (layout.coastlineStyle) {
48676
+ const cs = layout.coastlineStyle;
48677
+ const maskId = "dgmo-map-water-mask";
48678
+ const mask = defs.append("mask").attr("id", maskId).attr("maskUnits", "userSpaceOnUse").attr("x", 0).attr("y", 0).attr("width", width).attr("height", height);
48679
+ mask.append("rect").attr("x", 0).attr("y", 0).attr("width", width).attr("height", height).attr("fill", "white");
48680
+ const landD = layout.regions.filter((r) => r.id !== "lake").map((r) => r.d).join(" ");
48681
+ const lakeD = layout.regions.filter((r) => r.id === "lake").map((r) => r.d).join(" ");
48682
+ if (landD) mask.append("path").attr("d", landD).attr("fill", "black");
48683
+ if (lakeD) mask.append("path").attr("d", lakeD).attr("fill", "white");
48684
+ if (layout.insets.length) {
48685
+ const reach = Math.max(0, ...cs.lines.map((l) => l.d + l.thickness));
48686
+ for (const box of layout.insets) {
48687
+ const d = box.points.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join("") + "Z";
48688
+ mask.append("path").attr("d", d).attr("fill", "black").attr("stroke", "black").attr("stroke-width", 2 * reach).attr("stroke-linejoin", "round");
48689
+ }
48690
+ }
48691
+ const gWater = svg.append("g").attr("class", "dgmo-map-water-lines").attr("fill", "none").attr("mask", `url(#${maskId})`);
48692
+ appendWaterLines(
48693
+ gWater,
48694
+ coastlineOuterRings(layout.regions, cs.minExtent),
48695
+ cs,
48696
+ layout.background
48697
+ );
48698
+ const byStroke = /* @__PURE__ */ new Map();
48699
+ for (const r of layout.regions) {
48700
+ const arr = byStroke.get(r.stroke);
48701
+ if (arr) arr.push(r.d);
48702
+ else byStroke.set(r.stroke, [r.d]);
48703
+ }
48704
+ for (const [stroke2, ds] of byStroke)
48705
+ gWater.append("path").attr("d", ds.join(" ")).attr("stroke", stroke2).attr("stroke-width", 0.5).attr("stroke-linejoin", "round");
48706
+ }
47288
48707
  if (layout.rivers.length) {
47289
48708
  const gRivers = svg.append("g").attr("class", "dgmo-map-rivers").attr("fill", "none");
47290
48709
  for (const r of layout.rivers) {
@@ -47293,15 +48712,61 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47293
48712
  }
47294
48713
  if (layout.insets.length) {
47295
48714
  const insetG = svg.append("g").attr("class", "dgmo-map-insets");
47296
- for (const box of layout.insets) {
48715
+ layout.insets.forEach((box, bi) => {
47297
48716
  const d = box.points.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join("") + "Z";
47298
48717
  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");
47299
- }
48718
+ if (box.contextLand) {
48719
+ const clipId = `dgmo-map-inset-clip-${bi}`;
48720
+ defs.append("clipPath").attr("id", clipId).append("path").attr("d", d);
48721
+ insetG.append("path").attr("d", box.contextLand.d).attr("fill", box.contextLand.fill).attr("clip-path", `url(#${clipId})`);
48722
+ }
48723
+ });
47300
48724
  for (const r of layout.insetRegions) drawRegion(insetG, r, 0.5);
47301
- }
48725
+ if (layout.coastlineStyle) {
48726
+ const cs = layout.coastlineStyle;
48727
+ const maskId = "dgmo-map-inset-water-mask";
48728
+ const mask = defs.append("mask").attr("id", maskId).attr("maskUnits", "userSpaceOnUse").attr("x", 0).attr("y", 0).attr("width", width).attr("height", height);
48729
+ for (const box of layout.insets) {
48730
+ const d = box.points.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join("") + "Z";
48731
+ mask.append("path").attr("d", d).attr("fill", "white");
48732
+ }
48733
+ layout.insets.forEach((box, bi) => {
48734
+ if (box.contextLand)
48735
+ mask.append("path").attr("d", box.contextLand.d).attr("fill", "black").attr("clip-path", `url(#dgmo-map-inset-clip-${bi})`);
48736
+ });
48737
+ for (const r of layout.insetRegions)
48738
+ if (r.id !== "lake")
48739
+ mask.append("path").attr("d", r.d).attr("fill", "black");
48740
+ for (const r of layout.insetRegions)
48741
+ if (r.id === "lake")
48742
+ mask.append("path").attr("d", r.d).attr("fill", "white");
48743
+ const clipId = "dgmo-map-inset-water-clip";
48744
+ const clip = defs.append("clipPath").attr("id", clipId);
48745
+ for (const box of layout.insets) {
48746
+ const d = box.points.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join("") + "Z";
48747
+ clip.append("path").attr("d", d);
48748
+ }
48749
+ 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})`);
48750
+ appendWaterLines(
48751
+ gInsetWater,
48752
+ coastlineOuterRings(layout.insetRegions, cs.minExtent),
48753
+ cs,
48754
+ layout.background
48755
+ );
48756
+ for (const r of layout.insetRegions)
48757
+ gInsetWater.append("path").attr("d", r.d).attr("stroke", r.stroke).attr("stroke-width", 0.5).attr("stroke-linejoin", "round");
48758
+ }
48759
+ }
48760
+ const wireSync = (sel, lineNumber) => {
48761
+ if (lineNumber < 1) return;
48762
+ sel.attr("data-line-number", lineNumber);
48763
+ if (onClickItem)
48764
+ sel.style("cursor", "pointer").on("click", () => onClickItem(lineNumber));
48765
+ };
47302
48766
  const gLegs = svg.append("g").attr("class", "dgmo-map-legs").attr("fill", "none");
47303
48767
  layout.legs.forEach((leg, i) => {
47304
48768
  const p = gLegs.append("path").attr("d", leg.d).attr("stroke", leg.color).attr("stroke-width", leg.width).attr("stroke-linecap", "round");
48769
+ wireSync(p, leg.lineNumber);
47305
48770
  if (leg.arrow) {
47306
48771
  const id = `dgmo-map-arrow-${i}`;
47307
48772
  const s = arrowSize(leg.width);
@@ -47309,25 +48774,38 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47309
48774
  p.attr("marker-end", `url(#${id})`);
47310
48775
  }
47311
48776
  if (leg.label !== void 0 && leg.labelX !== void 0) {
47312
- emitText(
48777
+ const lt = emitText(
47313
48778
  gLegs,
47314
48779
  leg.labelX,
47315
48780
  leg.labelY ?? 0,
47316
48781
  leg.label,
47317
48782
  "middle",
47318
- palette.textMuted,
47319
- haloColor,
47320
- true,
48783
+ leg.labelColor ?? palette.textMuted,
48784
+ leg.labelHaloColor ?? haloColor,
48785
+ leg.labelHalo ?? true,
47321
48786
  LABEL_FONT - 1
47322
48787
  );
48788
+ wireSync(lt, leg.lineNumber);
47323
48789
  }
47324
48790
  });
48791
+ const gSpider = svg.append("g").attr("class", "dgmo-map-spider");
48792
+ for (const cl of layout.clusters) {
48793
+ if (!exportDims) {
48794
+ 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");
48795
+ }
48796
+ for (const leg of cl.legs) {
48797
+ 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");
48798
+ }
48799
+ 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");
48800
+ }
47325
48801
  const gPois = svg.append("g").attr("class", "dgmo-map-pois");
47326
48802
  for (const poi of layout.pois) {
47327
48803
  if (poi.isOrigin) {
47328
48804
  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);
47329
48805
  }
47330
48806
  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);
48807
+ if (poi.clusterId !== void 0)
48808
+ c.attr("data-cluster-member", poi.clusterId);
47331
48809
  if (poi.tags) {
47332
48810
  for (const [group, value] of Object.entries(poi.tags)) {
47333
48811
  c.attr(`data-tag-${group.toLowerCase()}`, value.toLowerCase());
@@ -47355,12 +48833,32 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47355
48833
  }
47356
48834
  const gLabels = svg.append("g").attr("class", "dgmo-map-labels");
47357
48835
  for (const lab of layout.labels) {
48836
+ if (lab.hidden) {
48837
+ if (exportDims) continue;
48838
+ emitText(
48839
+ gLabels,
48840
+ lab.x,
48841
+ lab.y,
48842
+ lab.text,
48843
+ lab.anchor,
48844
+ lab.color,
48845
+ lab.haloColor,
48846
+ lab.halo,
48847
+ LABEL_FONT,
48848
+ lab.italic,
48849
+ lab.letterSpacing
48850
+ ).attr("data-poi", lab.poiId ?? null).attr("data-poi-hidden", "").style("opacity", 0).style("pointer-events", "none");
48851
+ continue;
48852
+ }
47358
48853
  if (lab.leader) {
47359
48854
  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(
47360
48855
  "stroke",
47361
48856
  lab.leaderColor ?? mix(palette.textMuted, palette.bg, 60)
47362
48857
  ).attr("stroke-width", lab.leaderColor ? 1 : 0.75);
47363
48858
  if (lab.poiId !== void 0) line12.attr("data-poi", lab.poiId);
48859
+ if (lab.clusterMember !== void 0)
48860
+ line12.attr("data-cluster-member", lab.clusterMember);
48861
+ wireSync(line12, lab.lineNumber);
47364
48862
  }
47365
48863
  const t = emitText(
47366
48864
  gLabels,
@@ -47371,11 +48869,38 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47371
48869
  lab.color,
47372
48870
  lab.haloColor,
47373
48871
  lab.halo,
47374
- LABEL_FONT
48872
+ LABEL_FONT,
48873
+ lab.italic,
48874
+ lab.letterSpacing,
48875
+ lab.lines
47375
48876
  );
47376
48877
  if (lab.poiId !== void 0) {
47377
48878
  t.attr("data-poi", lab.poiId).style("cursor", "default");
47378
48879
  }
48880
+ if (lab.clusterMember !== void 0) {
48881
+ t.attr("data-cluster-member", lab.clusterMember);
48882
+ }
48883
+ wireSync(t, lab.lineNumber);
48884
+ }
48885
+ if (!exportDims && layout.clusters.length) {
48886
+ const gBadge = svg.append("g").attr("class", "dgmo-map-cluster-badges");
48887
+ for (const cl of layout.clusters) {
48888
+ const g = gBadge.append("g").attr("data-cluster", cl.id).style("opacity", 0).style("pointer-events", "none");
48889
+ const R = 9;
48890
+ 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);
48891
+ 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);
48892
+ emitText(
48893
+ g,
48894
+ cl.cx,
48895
+ cl.cy + 3,
48896
+ String(cl.count),
48897
+ "middle",
48898
+ palette.text,
48899
+ palette.bg,
48900
+ false,
48901
+ LABEL_FONT
48902
+ );
48903
+ }
47379
48904
  }
47380
48905
  if (layout.legend) {
47381
48906
  const legendY = (layout.title ? TITLE_Y + TITLE_FONT_SIZE : 0) + (layout.subtitle ? TITLE_FONT_SIZE : 0) + 8;
@@ -47412,7 +48937,7 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47412
48937
  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);
47413
48938
  }
47414
48939
  if (layout.subtitle) {
47415
- 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);
48940
+ 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);
47416
48941
  }
47417
48942
  if (layout.caption) {
47418
48943
  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);
@@ -47421,10 +48946,21 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47421
48946
  function renderMapForExport(container, resolved, data, palette, isDark, exportDims) {
47422
48947
  renderMap(container, resolved, data, palette, isDark, void 0, exportDims);
47423
48948
  }
47424
- function emitText(g, x, y, text, anchor, color, halo, withHalo, fontSize) {
47425
- const t = g.append("text").attr("x", x).attr("y", y).attr("text-anchor", anchor).attr("font-size", fontSize).attr("fill", color).text(text);
48949
+ function emitText(g, x, y, text, anchor, color, halo, withHalo, fontSize, italic, letterSpacing, lines) {
48950
+ const t = g.append("text").attr("x", x).attr("y", y).attr("text-anchor", anchor).attr("font-size", fontSize).attr("fill", color);
48951
+ if (lines && lines.length > 1) {
48952
+ const lineHeight = fontSize + 2;
48953
+ const startDy = -((lines.length - 1) / 2) * lineHeight;
48954
+ lines.forEach((ln, i) => {
48955
+ t.append("tspan").attr("x", x).attr("dy", i === 0 ? startDy : lineHeight).text(ln);
48956
+ });
48957
+ } else {
48958
+ t.text(text);
48959
+ }
48960
+ if (italic) t.attr("font-style", "italic");
48961
+ if (letterSpacing) t.attr("letter-spacing", letterSpacing);
47426
48962
  if (withHalo) {
47427
- t.attr("paint-order", "stroke fill").attr("stroke", halo).attr("stroke-width", 3).attr("stroke-linejoin", "round").attr("stroke-opacity", 0.7);
48963
+ 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);
47428
48964
  }
47429
48965
  return t;
47430
48966
  }
@@ -47442,6 +48978,56 @@ var init_renderer16 = __esm({
47442
48978
  }
47443
48979
  });
47444
48980
 
48981
+ // src/map/dimensions.ts
48982
+ var dimensions_exports = {};
48983
+ __export(dimensions_exports, {
48984
+ mapContentAspect: () => mapContentAspect,
48985
+ mapExportDimensions: () => mapExportDimensions
48986
+ });
48987
+ function mapContentAspect(resolved, data, ref = REF) {
48988
+ const { projection, fitTarget } = buildMapProjection(resolved, data);
48989
+ projection.fitSize([ref, ref], fitTarget);
48990
+ const b = (0, import_d3_geo3.geoPath)(projection).bounds(fitTarget);
48991
+ const w = b[1][0] - b[0][0];
48992
+ const h = b[1][1] - b[0][1];
48993
+ const aspect = w / h;
48994
+ return Number.isFinite(aspect) && aspect > 0 ? aspect : FALLBACK_ASPECT;
48995
+ }
48996
+ function mapExportDimensions(resolved, data, baseWidth = 1200) {
48997
+ const raw = mapContentAspect(resolved, data);
48998
+ const clamped = Math.max(ASPECT_MIN, Math.min(ASPECT_MAX, raw));
48999
+ const width = baseWidth;
49000
+ let height = Math.round(width / clamped);
49001
+ let chromeReserve = 0;
49002
+ if (resolved.title && resolved.pois.length > 0) {
49003
+ const bannerBottom = (resolved.subtitle ? TITLE_Y + TITLE_FONT_SIZE : TITLE_Y) + TITLE_FONT_SIZE / 2;
49004
+ chromeReserve += Math.max(FIT_PAD2, bannerBottom + TITLE_GAP) - FIT_PAD2;
49005
+ }
49006
+ let floored = false;
49007
+ if (height - chromeReserve < MIN_MAP_BAND) {
49008
+ height = Math.round(chromeReserve + MIN_MAP_BAND);
49009
+ floored = true;
49010
+ }
49011
+ const preferContain = clamped !== raw || floored;
49012
+ return { width, height, preferContain };
49013
+ }
49014
+ var import_d3_geo3, FIT_PAD2, TITLE_GAP, ASPECT_MAX, ASPECT_MIN, MIN_MAP_BAND, FALLBACK_ASPECT, REF;
49015
+ var init_dimensions = __esm({
49016
+ "src/map/dimensions.ts"() {
49017
+ "use strict";
49018
+ import_d3_geo3 = require("d3-geo");
49019
+ init_title_constants();
49020
+ init_layout15();
49021
+ FIT_PAD2 = 24;
49022
+ TITLE_GAP = 16;
49023
+ ASPECT_MAX = 3;
49024
+ ASPECT_MIN = 0.9;
49025
+ MIN_MAP_BAND = 200;
49026
+ FALLBACK_ASPECT = 1.5;
49027
+ REF = 1e3;
49028
+ }
49029
+ });
49030
+
47445
49031
  // src/map/load-data.ts
47446
49032
  var load_data_exports = {};
47447
49033
  __export(load_data_exports, {
@@ -47500,12 +49086,17 @@ function loadMapData() {
47500
49086
  mountainRanges,
47501
49087
  naLand,
47502
49088
  naLakes,
49089
+ waterBodies,
47503
49090
  gazetteer
47504
49091
  ] = await Promise.all([
49092
+ // worldCoarse (110m) is LOAD-BEARING but NOT a render source: the world
49093
+ // basemap renders from worldDetail (50m) at all scales (resolver pins
49094
+ // basemaps.world = 'detail'). Coarse stays as the authoritative region
49095
+ // name index + dominant-landmass bbox source in resolver.ts. Do not drop it.
47505
49096
  readJson(nb, dir, FILES.worldCoarse),
47506
49097
  readJson(nb, dir, FILES.worldDetail),
47507
49098
  readJson(nb, dir, FILES.usStates),
47508
- // Lakes/rivers/mountain/NA assets are optional — older bundles may predate them.
49099
+ // Lakes/rivers/mountain/NA/water assets are optional — older bundles may predate them.
47509
49100
  readJson(nb, dir, FILES.lakes).catch(() => void 0),
47510
49101
  readJson(nb, dir, FILES.rivers).catch(() => void 0),
47511
49102
  readJson(nb, dir, FILES.mountainRanges).catch(
@@ -47513,6 +49104,7 @@ function loadMapData() {
47513
49104
  ),
47514
49105
  readJson(nb, dir, FILES.naLand).catch(() => void 0),
47515
49106
  readJson(nb, dir, FILES.naLakes).catch(() => void 0),
49107
+ readJson(nb, dir, FILES.waterBodies).catch(() => void 0),
47516
49108
  readJson(nb, dir, FILES.gazetteer)
47517
49109
  ]);
47518
49110
  return validate({
@@ -47524,7 +49116,8 @@ function loadMapData() {
47524
49116
  ...rivers && { rivers },
47525
49117
  ...mountainRanges && { mountainRanges },
47526
49118
  ...naLand && { naLand },
47527
- ...naLakes && { naLakes }
49119
+ ...naLakes && { naLakes },
49120
+ ...waterBodies && { waterBodies }
47528
49121
  });
47529
49122
  })().catch((e) => {
47530
49123
  cache = void 0;
@@ -47546,6 +49139,7 @@ var init_load_data = __esm({
47546
49139
  mountainRanges: "mountain-ranges.json",
47547
49140
  naLand: "na-land.json",
47548
49141
  naLakes: "na-lakes.json",
49142
+ waterBodies: "water-bodies.json",
47549
49143
  gazetteer: "gazetteer.json"
47550
49144
  };
47551
49145
  CANDIDATE_DIRS = [
@@ -49558,8 +51152,8 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
49558
51152
  const lines = splitParticipantLabel(p.label, LABEL_MAX_CHARS);
49559
51153
  if (lines.length === 0) continue;
49560
51154
  const widest = Math.max(...lines.map((l) => l.length));
49561
- const labelWidth = widest * LABEL_CHAR_WIDTH + 10;
49562
- uniformBoxWidth = Math.max(uniformBoxWidth, labelWidth);
51155
+ const labelWidth2 = widest * LABEL_CHAR_WIDTH + 10;
51156
+ uniformBoxWidth = Math.max(uniformBoxWidth, labelWidth2);
49563
51157
  }
49564
51158
  uniformBoxWidth = Math.min(MAX_BOX_WIDTH, uniformBoxWidth);
49565
51159
  const effectiveGap = Math.max(PARTICIPANT_GAP, uniformBoxWidth + 30);
@@ -52254,15 +53848,15 @@ function renderArcDiagram(container, parsed, palette, _isDark, onClickItem, expo
52254
53848
  textColor,
52255
53849
  onClickItem
52256
53850
  );
52257
- const neighbors = /* @__PURE__ */ new Map();
52258
- for (const node of nodes) neighbors.set(node, /* @__PURE__ */ new Set());
53851
+ const neighbors2 = /* @__PURE__ */ new Map();
53852
+ for (const node of nodes) neighbors2.set(node, /* @__PURE__ */ new Set());
52259
53853
  for (const link of links) {
52260
- neighbors.get(link.source).add(link.target);
52261
- neighbors.get(link.target).add(link.source);
53854
+ neighbors2.get(link.source).add(link.target);
53855
+ neighbors2.get(link.target).add(link.source);
52262
53856
  }
52263
53857
  const FADE_OPACITY3 = 0.1;
52264
53858
  function handleMouseEnter(hovered) {
52265
- const connected = neighbors.get(hovered);
53859
+ const connected = neighbors2.get(hovered);
52266
53860
  g.selectAll(".arc-link").each(function() {
52267
53861
  const el = d3Selection23.select(this);
52268
53862
  const src = el.attr("data-source");
@@ -54197,7 +55791,7 @@ function renderVenn(container, parsed, palette, _isDark, onClickItem, exportDims
54197
55791
  8,
54198
55792
  Math.floor(OVERLAP_WRAP_TARGET_W / OVERLAP_CH_W)
54199
55793
  );
54200
- function wrapLabel2(text, maxChars) {
55794
+ function wrapLabel3(text, maxChars) {
54201
55795
  const words = text.split(/\s+/).filter(Boolean);
54202
55796
  const lines = [];
54203
55797
  let cur = "";
@@ -54243,7 +55837,7 @@ function renderVenn(container, parsed, palette, _isDark, onClickItem, exportDims
54243
55837
  if (!ov.label) continue;
54244
55838
  const idxs = ov.sets.map((s) => vennSets.findIndex((vs) => vs.name === s));
54245
55839
  if (idxs.some((idx) => idx < 0)) continue;
54246
- const lines = wrapLabel2(ov.label, MAX_WRAP_CHARS);
55840
+ const lines = wrapLabel3(ov.label, MAX_WRAP_CHARS);
54247
55841
  wrappedOverlapLabels.set(ov, lines);
54248
55842
  const dir = predictOverlapDirRaw(idxs);
54249
55843
  const longest = lines.reduce((m, l) => Math.max(m, l.length), 0);
@@ -55681,6 +57275,7 @@ async function renderForExport(content, theme, palette, viewState, options) {
55681
57275
  const { parseMap: parseMap2 } = await Promise.resolve().then(() => (init_parser12(), parser_exports11));
55682
57276
  const { resolveMap: resolveMap2 } = await Promise.resolve().then(() => (init_resolver2(), resolver_exports));
55683
57277
  const { renderMapForExport: renderMapForExport2 } = await Promise.resolve().then(() => (init_renderer16(), renderer_exports16));
57278
+ const { mapExportDimensions: mapExportDimensions2 } = await Promise.resolve().then(() => (init_dimensions(), dimensions_exports));
55684
57279
  const effectivePalette2 = await resolveExportPalette(theme, palette);
55685
57280
  const mapParsed = parseMap2(content);
55686
57281
  let mapData = options?.mapData;
@@ -55693,14 +57288,15 @@ async function renderForExport(content, theme, palette, viewState, options) {
55693
57288
  }
55694
57289
  }
55695
57290
  const mapResolved = resolveMap2(mapParsed, mapData);
55696
- const container2 = createExportContainer(EXPORT_WIDTH, EXPORT_HEIGHT);
57291
+ const dims2 = mapExportDimensions2(mapResolved, mapData, EXPORT_WIDTH);
57292
+ const container2 = createExportContainer(dims2.width, dims2.height);
55697
57293
  renderMapForExport2(
55698
57294
  container2,
55699
57295
  mapResolved,
55700
57296
  mapData,
55701
57297
  effectivePalette2,
55702
57298
  theme === "dark",
55703
- { width: EXPORT_WIDTH, height: EXPORT_HEIGHT }
57299
+ dims2
55704
57300
  );
55705
57301
  return finalizeSvgExport(container2, theme, effectivePalette2);
55706
57302
  }
@@ -56561,7 +58157,8 @@ async function render(content, options) {
56561
58157
  ...options?.c4Container !== void 0 && {
56562
58158
  c4Container: options.c4Container
56563
58159
  },
56564
- ...options?.tagGroup !== void 0 && { tagGroup: options.tagGroup }
58160
+ ...options?.tagGroup !== void 0 && { tagGroup: options.tagGroup },
58161
+ ...options?.mapData !== void 0 && { mapData: options.mapData }
56565
58162
  });
56566
58163
  if (chartType === "map") {
56567
58164
  try {
@@ -56572,7 +58169,7 @@ async function render(content, options) {
56572
58169
  Promise.resolve().then(() => (init_load_data(), load_data_exports))
56573
58170
  ]
56574
58171
  );
56575
- const data = await loadMapData2();
58172
+ const data = options?.mapData ?? await loadMapData2();
56576
58173
  diagnostics = [...resolveMap2(parseMap2(content), data).diagnostics];
56577
58174
  } catch {
56578
58175
  }
@@ -56740,22 +58337,20 @@ var DIRECTIVE_KEYWORDS = /* @__PURE__ */ new Set([
56740
58337
  // Sequence
56741
58338
  "activations",
56742
58339
  "no-activations",
56743
- // Map (§24B) directives
56744
- "region",
56745
- "projection",
58340
+ // Map (§24B) directives — cosmetics on by default, bare `no-*` opt-outs
56746
58341
  "region-metric",
56747
58342
  "poi-metric",
56748
58343
  "flow-metric",
56749
- "region-labels",
56750
- "poi-labels",
56751
- "default-country",
56752
- "default-state",
56753
- "no-legend",
56754
- "no-insets",
56755
- "muted",
56756
- "natural",
56757
- "subtitle",
58344
+ "locale",
58345
+ "active-tag",
56758
58346
  "caption",
58347
+ "no-legend",
58348
+ "no-coastline",
58349
+ "no-relief",
58350
+ "no-context-labels",
58351
+ "no-region-labels",
58352
+ "no-poi-labels",
58353
+ "no-colorize",
56759
58354
  "poi",
56760
58355
  "route",
56761
58356
  // Data charts
@@ -57048,7 +58643,11 @@ var ATTRIBUTE_KEYS = /* @__PURE__ */ new Set([
57048
58643
  "collapsed",
57049
58644
  "tech",
57050
58645
  "span",
57051
- "split"
58646
+ "split",
58647
+ // Map (§24B) reserved keys
58648
+ "value",
58649
+ "label",
58650
+ "style"
57052
58651
  ]);
57053
58652
  function applyAttributeKeys(tokens) {
57054
58653
  for (let i = 0; i < tokens.length - 1; i++) {
@@ -57421,7 +59020,7 @@ pre.dgmo, code.language-dgmo, pre > code.language-dgmo,
57421
59020
 
57422
59021
  // src/auto/index.ts
57423
59022
  init_safe_href();
57424
- var VERSION = "0.21.1";
59023
+ var VERSION = "0.22.0";
57425
59024
  var DEFAULTS = {
57426
59025
  theme: "auto",
57427
59026
  palette: "nord",