@diagrammo/dgmo 0.21.1 → 0.23.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 (87) hide show
  1. package/README.md +16 -6
  2. package/dist/advanced.cjs +2230 -503
  3. package/dist/advanced.d.cts +5731 -0
  4. package/dist/advanced.d.ts +5731 -0
  5. package/dist/advanced.js +2226 -503
  6. package/dist/auto.cjs +2272 -479
  7. package/dist/auto.d.cts +39 -0
  8. package/dist/auto.d.ts +39 -0
  9. package/dist/auto.js +124 -124
  10. package/dist/auto.mjs +2274 -480
  11. package/dist/cli.cjs +170 -170
  12. package/dist/editor.cjs +16 -16
  13. package/dist/editor.js +16 -16
  14. package/dist/highlight.cjs +18 -13
  15. package/dist/highlight.js +18 -13
  16. package/dist/index.cjs +2253 -465
  17. package/dist/index.d.cts +339 -0
  18. package/dist/index.d.ts +339 -0
  19. package/dist/index.js +2255 -466
  20. package/dist/internal.cjs +2230 -503
  21. package/dist/internal.d.cts +5731 -0
  22. package/dist/internal.d.ts +5731 -0
  23. package/dist/internal.js +2226 -503
  24. package/dist/map-data/PROVENANCE.json +1 -1
  25. package/dist/map-data/gazetteer.json +1 -1
  26. package/dist/map-data/mountain-ranges.json +1 -1
  27. package/dist/map-data/water-bodies.json +1 -0
  28. package/dist/map-data/world-coarse.json +1 -1
  29. package/dist/map-data/world-detail.json +1 -1
  30. package/docs/language-reference.md +55 -9
  31. package/gallery/fixtures/boxes-and-lines.dgmo +6 -4
  32. package/gallery/fixtures/map-categorical-world.dgmo +16 -0
  33. package/gallery/fixtures/map-categorical.dgmo +0 -1
  34. package/gallery/fixtures/map-choropleth.dgmo +0 -1
  35. package/gallery/fixtures/map-coastline.dgmo +7 -0
  36. package/gallery/fixtures/map-colorize.dgmo +11 -0
  37. package/gallery/fixtures/map-direct-color.dgmo +0 -1
  38. package/gallery/fixtures/map-reference-world.dgmo +11 -0
  39. package/gallery/fixtures/map-region-scope.dgmo +0 -3
  40. package/gallery/fixtures/map-route.dgmo +0 -1
  41. package/package.json +1 -1
  42. package/src/advanced.ts +12 -1
  43. package/src/boxes-and-lines/parser.ts +39 -0
  44. package/src/boxes-and-lines/renderer.ts +205 -20
  45. package/src/boxes-and-lines/types.ts +9 -0
  46. package/src/cli.ts +1 -1
  47. package/src/completion.ts +36 -30
  48. package/src/cycle/renderer.ts +14 -1
  49. package/src/d3.ts +20 -6
  50. package/src/editor/highlight-api.ts +4 -0
  51. package/src/editor/keywords.ts +16 -16
  52. package/src/infra/renderer.ts +35 -7
  53. package/src/map/colorize.ts +54 -0
  54. package/src/map/context-labels.ts +429 -0
  55. package/src/map/data/PROVENANCE.json +1 -1
  56. package/src/map/data/README.md +6 -0
  57. package/src/map/data/gazetteer.json +1 -1
  58. package/src/map/data/mountain-ranges.json +1 -1
  59. package/src/map/data/types.ts +34 -0
  60. package/src/map/data/water-bodies.json +1 -0
  61. package/src/map/data/world-coarse.json +1 -1
  62. package/src/map/data/world-detail.json +1 -1
  63. package/src/map/dimensions.ts +117 -0
  64. package/src/map/geo-query.ts +21 -3
  65. package/src/map/geo.ts +47 -1
  66. package/src/map/layout.ts +1408 -266
  67. package/src/map/load-data.ts +10 -2
  68. package/src/map/parser.ts +42 -116
  69. package/src/map/renderer.ts +604 -14
  70. package/src/map/resolved-types.ts +16 -2
  71. package/src/map/resolver.ts +208 -59
  72. package/src/map/types.ts +30 -32
  73. package/src/mindmap/renderer.ts +10 -1
  74. package/src/palettes/atlas.ts +77 -0
  75. package/src/palettes/blueprint.ts +73 -0
  76. package/src/palettes/color-utils.ts +58 -1
  77. package/src/palettes/index.ts +12 -3
  78. package/src/palettes/slate.ts +73 -0
  79. package/src/palettes/tidewater.ts +73 -0
  80. package/src/render.ts +8 -1
  81. package/src/tech-radar/renderer.ts +3 -0
  82. package/src/tech-radar/types.ts +3 -0
  83. package/src/utils/d3-types.ts +5 -0
  84. package/src/utils/legend-layout.ts +21 -4
  85. package/src/utils/legend-types.ts +7 -0
  86. package/src/utils/reserved-key-registry.ts +8 -3
  87. 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",
@@ -817,9 +820,7 @@ var init_reserved_key_registry = __esm({
817
820
  BOXES_AND_LINES_REGISTRY = staticRegistry([
818
821
  "color",
819
822
  "description",
820
- "width",
821
- "split",
822
- "fanout"
823
+ "value"
823
824
  ]);
824
825
  TIMELINE_REGISTRY = staticRegistry([
825
826
  "color",
@@ -1825,77 +1826,266 @@ function getSegmentColors(palette, count) {
1825
1826
  (_, i) => hslToHex(Math.round((startHue + i * step) % 360), avgS, avgL)
1826
1827
  );
1827
1828
  }
1829
+ function politicalTints(palette, count, isDark) {
1830
+ if (count <= 0) return [];
1831
+ const base = isDark ? palette.surface : palette.bg;
1832
+ const c = palette.colors;
1833
+ const swatches = [
1834
+ .../* @__PURE__ */ new Set([
1835
+ c.green,
1836
+ c.yellow,
1837
+ c.orange,
1838
+ c.purple,
1839
+ c.red,
1840
+ c.teal,
1841
+ c.cyan,
1842
+ c.blue
1843
+ ])
1844
+ ];
1845
+ const bands = isDark ? POLITICAL_TINT_BANDS.dark : POLITICAL_TINT_BANDS.light;
1846
+ const out = [];
1847
+ for (const pct of bands) {
1848
+ if (out.length >= count) break;
1849
+ for (const s of swatches) out.push(mix(s, base, pct));
1850
+ }
1851
+ return out.slice(0, count);
1852
+ }
1853
+ var POLITICAL_TINT_BANDS;
1828
1854
  var init_color_utils = __esm({
1829
1855
  "src/palettes/color-utils.ts"() {
1830
1856
  "use strict";
1857
+ POLITICAL_TINT_BANDS = {
1858
+ light: [32, 48, 64, 80],
1859
+ dark: [44, 58, 72, 86]
1860
+ };
1831
1861
  }
1832
1862
  });
1833
1863
 
1834
- // src/palettes/bold.ts
1835
- var boldPalette;
1836
- var init_bold = __esm({
1837
- "src/palettes/bold.ts"() {
1864
+ // src/palettes/atlas.ts
1865
+ var atlasPalette;
1866
+ var init_atlas = __esm({
1867
+ "src/palettes/atlas.ts"() {
1838
1868
  "use strict";
1839
1869
  init_registry();
1840
- boldPalette = {
1841
- id: "bold",
1842
- name: "Bold",
1870
+ atlasPalette = {
1871
+ id: "atlas",
1872
+ name: "Atlas",
1843
1873
  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",
1874
+ bg: "#f3ead3",
1875
+ // warm manila / parchment
1876
+ surface: "#ece0c0",
1877
+ // deeper paper (cards, panels)
1878
+ overlay: "#e8dab8",
1879
+ // popovers, dropdowns
1880
+ border: "#bcaa86",
1881
+ // muted sepia rule line
1882
+ text: "#463a26",
1883
+ // aged sepia-brown ink
1884
+ textMuted: "#7a6a4f",
1885
+ // faded annotation ink
1886
+ textOnFillLight: "#f7f1de",
1887
+ // parchment (light text on dark fills)
1888
+ textOnFillDark: "#3a2e1c",
1889
+ // deep ink (dark text on light fills)
1890
+ primary: "#5b7a99",
1891
+ // pull-down map ocean (steel-blue)
1892
+ secondary: "#7e9a6f",
1893
+ // lowland sage / celadon
1894
+ accent: "#b07f7c",
1895
+ // dusty rose
1896
+ destructive: "#b25a45",
1897
+ // brick / terracotta
1856
1898
  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"
1899
+ red: "#bf6a52",
1900
+ // terracotta brick
1901
+ orange: "#cf9a5c",
1902
+ // map tan / ochre
1903
+ yellow: "#cdb35e",
1904
+ // straw / muted lemon
1905
+ green: "#7e9a6f",
1906
+ // sage / celadon lowland
1907
+ blue: "#5b7a99",
1908
+ // steel-blue ocean
1909
+ purple: "#9a7fa6",
1910
+ // dusty lilac / mauve
1911
+ teal: "#6fa094",
1912
+ // muted seafoam
1913
+ cyan: "#79a7b5",
1914
+ // shallow-water blue
1915
+ gray: "#8a7d68",
1916
+ // warm taupe
1917
+ black: "#463a26",
1918
+ // ink
1919
+ white: "#ece0c0"
1920
+ // paper
1868
1921
  }
1869
1922
  },
1870
1923
  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",
1924
+ bg: "#1e2a33",
1925
+ // deep map ocean (night globe)
1926
+ surface: "#27353f",
1927
+ // raised ocean
1928
+ overlay: "#2e3d48",
1929
+ // popovers, dropdowns
1930
+ border: "#3d4f5c",
1931
+ // depth-contour line
1932
+ text: "#e8dcc0",
1933
+ // parchment ink, inverted
1934
+ textMuted: "#a89a7d",
1935
+ // faded label
1936
+ textOnFillLight: "#f7f1de",
1937
+ // parchment
1938
+ textOnFillDark: "#1a242c",
1939
+ // deep ocean ink
1940
+ primary: "#7ba0bf",
1941
+ // brighter ocean
1942
+ secondary: "#9bb588",
1943
+ // sage, lifted
1944
+ accent: "#cf9a96",
1945
+ // dusty rose, lifted
1946
+ destructive: "#c9745c",
1947
+ // brick, lifted
1883
1948
  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"
1949
+ red: "#cf7a60",
1950
+ // terracotta
1951
+ orange: "#d9a96a",
1952
+ // tan / ochre
1953
+ yellow: "#d8c074",
1954
+ // straw
1955
+ green: "#9bb588",
1956
+ // sage lowland
1957
+ blue: "#7ba0bf",
1958
+ // ocean
1959
+ purple: "#b59ac0",
1960
+ // lilac / mauve
1961
+ teal: "#85b3a6",
1962
+ // seafoam
1963
+ cyan: "#92bccb",
1964
+ // shallow-water blue
1965
+ gray: "#9a8d76",
1966
+ // warm taupe
1967
+ black: "#27353f",
1968
+ // raised ocean
1969
+ white: "#e8dcc0"
1970
+ // parchment
1895
1971
  }
1896
1972
  }
1897
1973
  };
1898
- registerPalette(boldPalette);
1974
+ registerPalette(atlasPalette);
1975
+ }
1976
+ });
1977
+
1978
+ // src/palettes/blueprint.ts
1979
+ var blueprintPalette;
1980
+ var init_blueprint = __esm({
1981
+ "src/palettes/blueprint.ts"() {
1982
+ "use strict";
1983
+ init_registry();
1984
+ blueprintPalette = {
1985
+ id: "blueprint",
1986
+ name: "Blueprint",
1987
+ light: {
1988
+ bg: "#f4f8fb",
1989
+ // pale drafting white (faint cyan)
1990
+ surface: "#e6eef4",
1991
+ // drafting panel
1992
+ overlay: "#dde9f1",
1993
+ // popovers, dropdowns
1994
+ border: "#aac3d6",
1995
+ // pale blue grid line
1996
+ text: "#123a5e",
1997
+ // blueprint navy ink
1998
+ textMuted: "#4f7390",
1999
+ // faint draft note
2000
+ textOnFillLight: "#f4f8fb",
2001
+ // drafting white
2002
+ textOnFillDark: "#0c2f4d",
2003
+ // deep blueprint navy
2004
+ primary: "#1f5e8c",
2005
+ // blueprint blue
2006
+ secondary: "#5b7d96",
2007
+ // steel
2008
+ accent: "#b08a3e",
2009
+ // draftsman's ochre highlight
2010
+ destructive: "#c0504d",
2011
+ // correction red
2012
+ colors: {
2013
+ red: "#c25a4e",
2014
+ // correction red
2015
+ orange: "#c2823e",
2016
+ // ochre
2017
+ yellow: "#c2a843",
2018
+ // pencil gold
2019
+ green: "#4f8a6b",
2020
+ // drafting green
2021
+ blue: "#1f5e8c",
2022
+ // blueprint blue
2023
+ purple: "#6f5e96",
2024
+ // indigo pencil
2025
+ teal: "#3a8a8a",
2026
+ // teal
2027
+ cyan: "#3f8fb5",
2028
+ // cyan
2029
+ gray: "#7e8e98",
2030
+ // graphite
2031
+ black: "#123a5e",
2032
+ // navy ink
2033
+ white: "#e6eef4"
2034
+ // panel
2035
+ }
2036
+ },
2037
+ dark: {
2038
+ bg: "#103a5e",
2039
+ // deep blueprint blue (cyanotype ground)
2040
+ surface: "#16466e",
2041
+ // raised sheet
2042
+ overlay: "#1c5180",
2043
+ // popovers, dropdowns
2044
+ border: "#3a6f96",
2045
+ // grid line
2046
+ text: "#eaf2f8",
2047
+ // chalk white
2048
+ textMuted: "#9fc0d6",
2049
+ // faint chalk note
2050
+ textOnFillLight: "#eaf2f8",
2051
+ // chalk white
2052
+ textOnFillDark: "#0c2f4d",
2053
+ // deep blueprint navy
2054
+ primary: "#7fb8d8",
2055
+ // chalk cyan
2056
+ secondary: "#9fb8c8",
2057
+ // pale steel
2058
+ accent: "#d8c27a",
2059
+ // chalk amber
2060
+ destructive: "#e08a7a",
2061
+ // chalk correction red
2062
+ colors: {
2063
+ red: "#e0907e",
2064
+ // chalk red
2065
+ orange: "#e0ab78",
2066
+ // chalk amber
2067
+ yellow: "#e3d089",
2068
+ // chalk gold
2069
+ green: "#93c79e",
2070
+ // chalk green
2071
+ blue: "#8ec3e0",
2072
+ // chalk cyan-blue
2073
+ purple: "#b6a6d8",
2074
+ // chalk indigo
2075
+ teal: "#84c7c2",
2076
+ // chalk teal
2077
+ cyan: "#9fd6e0",
2078
+ // chalk cyan
2079
+ gray: "#aebecb",
2080
+ // chalk graphite
2081
+ black: "#16466e",
2082
+ // raised sheet
2083
+ white: "#eaf2f8"
2084
+ // chalk white
2085
+ }
2086
+ }
2087
+ };
2088
+ registerPalette(blueprintPalette);
1899
2089
  }
1900
2090
  });
1901
2091
 
@@ -2392,6 +2582,120 @@ var init_rose_pine = __esm({
2392
2582
  }
2393
2583
  });
2394
2584
 
2585
+ // src/palettes/slate.ts
2586
+ var slatePalette;
2587
+ var init_slate = __esm({
2588
+ "src/palettes/slate.ts"() {
2589
+ "use strict";
2590
+ init_registry();
2591
+ slatePalette = {
2592
+ id: "slate",
2593
+ name: "Slate",
2594
+ light: {
2595
+ bg: "#ffffff",
2596
+ // clean slide white
2597
+ surface: "#f3f5f8",
2598
+ // light cool-gray panel
2599
+ overlay: "#eaeef3",
2600
+ // popovers, dropdowns
2601
+ border: "#d4dae1",
2602
+ // hairline rule
2603
+ text: "#1f2933",
2604
+ // near-black slate (softer than pure black)
2605
+ textMuted: "#5b6672",
2606
+ // secondary label
2607
+ textOnFillLight: "#ffffff",
2608
+ // light text on dark fills
2609
+ textOnFillDark: "#1f2933",
2610
+ // dark text on light fills
2611
+ primary: "#3b6ea5",
2612
+ // confident corporate blue
2613
+ secondary: "#5b6672",
2614
+ // slate gray
2615
+ accent: "#3a9188",
2616
+ // muted teal accent
2617
+ destructive: "#c0504d",
2618
+ // brick red
2619
+ colors: {
2620
+ red: "#c0504d",
2621
+ // brick
2622
+ orange: "#cc7a33",
2623
+ // muted amber
2624
+ yellow: "#c9a227",
2625
+ // gold (not neon)
2626
+ green: "#5b9357",
2627
+ // forest / sage
2628
+ blue: "#3b6ea5",
2629
+ // corporate blue
2630
+ purple: "#7d5ba6",
2631
+ // muted violet
2632
+ teal: "#3a9188",
2633
+ // teal
2634
+ cyan: "#4f96c4",
2635
+ // steel cyan
2636
+ gray: "#7e8a97",
2637
+ // cool gray
2638
+ black: "#1f2933",
2639
+ // slate ink
2640
+ white: "#f3f5f8"
2641
+ // panel
2642
+ }
2643
+ },
2644
+ dark: {
2645
+ bg: "#161b22",
2646
+ // deep slate (keynote dark)
2647
+ surface: "#202833",
2648
+ // raised panel
2649
+ overlay: "#29323e",
2650
+ // popovers, dropdowns
2651
+ border: "#38424f",
2652
+ // divider
2653
+ text: "#e6eaef",
2654
+ // off-white
2655
+ textMuted: "#9aa5b1",
2656
+ // secondary label
2657
+ textOnFillLight: "#ffffff",
2658
+ // light text on dark fills
2659
+ textOnFillDark: "#161b22",
2660
+ // dark text on light fills
2661
+ primary: "#5b9bd5",
2662
+ // lifted corporate blue
2663
+ secondary: "#8593a3",
2664
+ // slate gray, lifted
2665
+ accent: "#45b3a3",
2666
+ // teal, lifted
2667
+ destructive: "#e07b6e",
2668
+ // brick, lifted
2669
+ colors: {
2670
+ red: "#e07b6e",
2671
+ // brick
2672
+ orange: "#e0975a",
2673
+ // amber
2674
+ yellow: "#d9bd5a",
2675
+ // gold
2676
+ green: "#74b56e",
2677
+ // forest / sage
2678
+ blue: "#5b9bd5",
2679
+ // corporate blue
2680
+ purple: "#a585c9",
2681
+ // violet
2682
+ teal: "#45b3a3",
2683
+ // teal
2684
+ cyan: "#62b0d9",
2685
+ // steel cyan
2686
+ gray: "#95a1ae",
2687
+ // cool gray
2688
+ black: "#202833",
2689
+ // raised panel
2690
+ white: "#e6eaef"
2691
+ // off-white
2692
+ }
2693
+ }
2694
+ };
2695
+ registerPalette(slatePalette);
2696
+ }
2697
+ });
2698
+
2395
2699
  // src/palettes/solarized.ts
2396
2700
  var solarizedPalette;
2397
2701
  var init_solarized = __esm({
@@ -2487,6 +2791,120 @@ var init_solarized = __esm({
2487
2791
  }
2488
2792
  });
2489
2793
 
2794
+ // src/palettes/tidewater.ts
2795
+ var tidewaterPalette;
2796
+ var init_tidewater = __esm({
2797
+ "src/palettes/tidewater.ts"() {
2798
+ "use strict";
2799
+ init_registry();
2800
+ tidewaterPalette = {
2801
+ id: "tidewater",
2802
+ name: "Tidewater",
2803
+ light: {
2804
+ bg: "#eceff0",
2805
+ // weathered sea-mist paper
2806
+ surface: "#e0e4e3",
2807
+ // worn deck panel
2808
+ overlay: "#dadfdf",
2809
+ // popovers, dropdowns
2810
+ border: "#a9b2b3",
2811
+ // muted slate rule
2812
+ text: "#18313f",
2813
+ // ship's-log navy ink
2814
+ textMuted: "#51636b",
2815
+ // faded log entry
2816
+ textOnFillLight: "#f3f5f3",
2817
+ // weathered white
2818
+ textOnFillDark: "#162c38",
2819
+ // deep navy
2820
+ primary: "#1f4e6b",
2821
+ // deep-sea navy
2822
+ secondary: "#b08a4f",
2823
+ // rope / manila tan
2824
+ accent: "#c69a3e",
2825
+ // brass
2826
+ destructive: "#c1433a",
2827
+ // signal-flag red
2828
+ colors: {
2829
+ red: "#c1433a",
2830
+ // signal-flag red
2831
+ orange: "#cc7a38",
2832
+ // weathered amber
2833
+ yellow: "#d6bf5a",
2834
+ // brass gold
2835
+ green: "#4f8a6b",
2836
+ // sea-glass green
2837
+ blue: "#1f4e6b",
2838
+ // deep-sea navy
2839
+ purple: "#6a5a8c",
2840
+ // twilight harbor
2841
+ teal: "#3d8c8c",
2842
+ // sea-glass teal
2843
+ cyan: "#4f9bb5",
2844
+ // shallow water
2845
+ gray: "#8a8d86",
2846
+ // driftwood gray
2847
+ black: "#18313f",
2848
+ // navy ink
2849
+ white: "#e0e4e3"
2850
+ // deck panel
2851
+ }
2852
+ },
2853
+ dark: {
2854
+ bg: "#0f2230",
2855
+ // night-harbor deep sea
2856
+ surface: "#16303f",
2857
+ // raised hull
2858
+ overlay: "#1d3a4a",
2859
+ // popovers, dropdowns
2860
+ border: "#2c4856",
2861
+ // rigging line
2862
+ text: "#e6ebe8",
2863
+ // weathered white
2864
+ textMuted: "#9aaab0",
2865
+ // faded label
2866
+ textOnFillLight: "#f3f5f3",
2867
+ // weathered white
2868
+ textOnFillDark: "#0f2230",
2869
+ // deep sea
2870
+ primary: "#4f9bc4",
2871
+ // lifted sea blue
2872
+ secondary: "#c9a46a",
2873
+ // rope tan, lifted
2874
+ accent: "#d9b25a",
2875
+ // brass, lifted
2876
+ destructive: "#e06a5e",
2877
+ // signal red, lifted
2878
+ colors: {
2879
+ red: "#e06a5e",
2880
+ // signal-flag red
2881
+ orange: "#df9a52",
2882
+ // amber
2883
+ yellow: "#e0c662",
2884
+ // brass gold
2885
+ green: "#6fb58c",
2886
+ // sea-glass green
2887
+ blue: "#4f9bc4",
2888
+ // sea blue
2889
+ purple: "#9486bf",
2890
+ // twilight harbor
2891
+ teal: "#5cb0ac",
2892
+ // sea-glass teal
2893
+ cyan: "#62b4cf",
2894
+ // shallow water
2895
+ gray: "#9aa39c",
2896
+ // driftwood gray
2897
+ black: "#16303f",
2898
+ // raised hull
2899
+ white: "#e6ebe8"
2900
+ // weathered white
2901
+ }
2902
+ }
2903
+ };
2904
+ registerPalette(tidewaterPalette);
2905
+ }
2906
+ });
2907
+
2490
2908
  // src/palettes/tokyo-night.ts
2491
2909
  var tokyoNightPalette;
2492
2910
  var init_tokyo_night = __esm({
@@ -2762,7 +3180,8 @@ var init_monokai = __esm({
2762
3180
  // src/palettes/index.ts
2763
3181
  var palettes_exports = {};
2764
3182
  __export(palettes_exports, {
2765
- boldPalette: () => boldPalette,
3183
+ atlasPalette: () => atlasPalette,
3184
+ blueprintPalette: () => blueprintPalette,
2766
3185
  catppuccinPalette: () => catppuccinPalette,
2767
3186
  contrastText: () => contrastText,
2768
3187
  draculaPalette: () => draculaPalette,
@@ -2783,7 +3202,9 @@ __export(palettes_exports, {
2783
3202
  rosePinePalette: () => rosePinePalette,
2784
3203
  shade: () => shade,
2785
3204
  shapeFill: () => shapeFill,
3205
+ slatePalette: () => slatePalette,
2786
3206
  solarizedPalette: () => solarizedPalette,
3207
+ tidewaterPalette: () => tidewaterPalette,
2787
3208
  tint: () => tint,
2788
3209
  tokyoNightPalette: () => tokyoNightPalette
2789
3210
  });
@@ -2793,17 +3214,21 @@ var init_palettes = __esm({
2793
3214
  "use strict";
2794
3215
  init_registry();
2795
3216
  init_color_utils();
2796
- init_bold();
3217
+ init_atlas();
3218
+ init_blueprint();
2797
3219
  init_catppuccin();
2798
3220
  init_gruvbox();
2799
3221
  init_nord();
2800
3222
  init_one_dark();
2801
3223
  init_rose_pine();
3224
+ init_slate();
2802
3225
  init_solarized();
3226
+ init_tidewater();
2803
3227
  init_tokyo_night();
2804
3228
  init_dracula();
2805
3229
  init_monokai();
2806
- init_bold();
3230
+ init_atlas();
3231
+ init_blueprint();
2807
3232
  init_catppuccin();
2808
3233
  init_dracula();
2809
3234
  init_gruvbox();
@@ -2811,9 +3236,15 @@ var init_palettes = __esm({
2811
3236
  init_nord();
2812
3237
  init_one_dark();
2813
3238
  init_rose_pine();
3239
+ init_slate();
2814
3240
  init_solarized();
3241
+ init_tidewater();
2815
3242
  init_tokyo_night();
2816
3243
  palettes = {
3244
+ atlas: atlasPalette,
3245
+ blueprint: blueprintPalette,
3246
+ slate: slatePalette,
3247
+ tidewater: tidewaterPalette,
2817
3248
  nord: nordPalette,
2818
3249
  catppuccin: catppuccinPalette,
2819
3250
  solarized: solarizedPalette,
@@ -2822,8 +3253,7 @@ var init_palettes = __esm({
2822
3253
  oneDark: oneDarkPalette,
2823
3254
  rosePine: rosePinePalette,
2824
3255
  dracula: draculaPalette,
2825
- monokai: monokaiPalette,
2826
- bold: boldPalette
3256
+ monokai: monokaiPalette
2827
3257
  };
2828
3258
  }
2829
3259
  });
@@ -3333,6 +3763,9 @@ function controlsGroupCapsuleWidth(toggles) {
3333
3763
  }
3334
3764
  return w;
3335
3765
  }
3766
+ function isAppHostedControls(config, isExport) {
3767
+ return !isExport && config.controlsHost === "app" && !!config.controlsGroup && config.controlsGroup.toggles.length > 0;
3768
+ }
3336
3769
  function buildControlsGroupLayout(config, state) {
3337
3770
  const cg = config.controlsGroup;
3338
3771
  if (!cg || cg.toggles.length === 0) return void 0;
@@ -3386,6 +3819,7 @@ function buildControlsGroupLayout(config, state) {
3386
3819
  function computeLegendLayout(config, state, containerWidth) {
3387
3820
  const { groups, controls: configControls, mode } = config;
3388
3821
  const isExport = mode === "export";
3822
+ const gated = isAppHostedControls(config, isExport);
3389
3823
  const activeGroupName = state.activeGroup?.toLowerCase() ?? null;
3390
3824
  if (isExport && !activeGroupName) {
3391
3825
  return {
@@ -3396,7 +3830,7 @@ function computeLegendLayout(config, state, containerWidth) {
3396
3830
  pills: []
3397
3831
  };
3398
3832
  }
3399
- const controlsGroupLayout = isExport ? void 0 : buildControlsGroupLayout(config, state);
3833
+ const controlsGroupLayout = isExport || gated ? void 0 : buildControlsGroupLayout(config, state);
3400
3834
  const visibleGroups = config.showEmptyGroups ? groups : groups.filter((g) => g.entries.length > 0 || !!g.gradient);
3401
3835
  if (visibleGroups.length === 0 && (!configControls || configControls.length === 0) && !controlsGroupLayout) {
3402
3836
  return {
@@ -8276,8 +8710,8 @@ function computeScatterLabelGraphics(points, chartBounds, fontSize, symbolSize,
8276
8710
  const pt = points[i];
8277
8711
  const ptSize = pt.size ?? symbolSize;
8278
8712
  const minGap = ptSize / 2 + 4;
8279
- const labelWidth = pt.name.length * fontSize * 0.6 + 8;
8280
- const labelX = pt.px - labelWidth / 2;
8713
+ const labelWidth2 = pt.name.length * fontSize * 0.6 + 8;
8714
+ const labelX = pt.px - labelWidth2 / 2;
8281
8715
  let bestLabelY = 0;
8282
8716
  let bestOffset = Infinity;
8283
8717
  let placed = false;
@@ -8289,7 +8723,7 @@ function computeScatterLabelGraphics(points, chartBounds, fontSize, symbolSize,
8289
8723
  const candidate = {
8290
8724
  x: labelX,
8291
8725
  y: labelY,
8292
- w: labelWidth,
8726
+ w: labelWidth2,
8293
8727
  h: labelHeight
8294
8728
  };
8295
8729
  let collision = false;
@@ -8331,7 +8765,7 @@ function computeScatterLabelGraphics(points, chartBounds, fontSize, symbolSize,
8331
8765
  const labelRect = {
8332
8766
  x: labelX,
8333
8767
  y: bestLabelY,
8334
- w: labelWidth,
8768
+ w: labelWidth2,
8335
8769
  h: labelHeight
8336
8770
  };
8337
8771
  placedLabels.push(labelRect);
@@ -8367,7 +8801,7 @@ function computeScatterLabelGraphics(points, chartBounds, fontSize, symbolSize,
8367
8801
  shape: {
8368
8802
  x: labelX - bgPad,
8369
8803
  y: bestLabelY - bgPad,
8370
- width: labelWidth + bgPad * 2,
8804
+ width: labelWidth2 + bgPad * 2,
8371
8805
  height: labelHeight + bgPad * 2
8372
8806
  },
8373
8807
  style: { fill: bg },
@@ -15807,10 +16241,6 @@ function parseMap(content) {
15807
16241
  handleTag(trimmed, lineNumber);
15808
16242
  continue;
15809
16243
  }
15810
- if ((firstWord === "muted" || firstWord === "natural") && trimmed === firstWord) {
15811
- handleDirective(firstWord, "", lineNumber);
15812
- continue;
15813
- }
15814
16244
  if (DIRECTIVE_SET.has(firstWord) && !trimmed.slice(firstWord.length).trimStart().startsWith(":")) {
15815
16245
  handleDirective(
15816
16246
  firstWord,
@@ -15857,24 +16287,6 @@ function parseMap(content) {
15857
16287
  pushWarning(line12, `Duplicate directive "${key}" \u2014 last value wins.`);
15858
16288
  };
15859
16289
  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
16290
  case "region-metric": {
15879
16291
  dup(d.regionMetric);
15880
16292
  const { label: rmLabel, colorName: rmColor } = peelTrailingColorName(value);
@@ -15890,91 +16302,43 @@ function parseMap(content) {
15890
16302
  dup(d.flowMetric);
15891
16303
  d.flowMetric = value;
15892
16304
  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;
16305
+ case "locale":
16306
+ dup(d.locale);
16307
+ d.locale = value;
15925
16308
  break;
15926
16309
  case "active-tag":
15927
16310
  dup(d.activeTag);
15928
16311
  d.activeTag = value;
15929
16312
  break;
16313
+ case "caption":
16314
+ dup(d.caption);
16315
+ d.caption = value;
16316
+ break;
16317
+ // ── Cosmetic `no-*` opt-outs: bare flags, idempotent (mirror `no-legend`,
16318
+ // no dup warning); each defaults the feature ON when absent. ──
15930
16319
  case "no-legend":
15931
16320
  d.noLegend = true;
15932
16321
  break;
15933
- case "no-insets":
15934
- d.noInsets = true;
16322
+ case "no-coastline":
16323
+ d.noCoastline = true;
15935
16324
  break;
15936
- case "relief":
15937
- d.relief = true;
16325
+ case "no-relief":
16326
+ d.noRelief = true;
15938
16327
  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;
16328
+ case "no-context-labels":
16329
+ d.noContextLabels = true;
15947
16330
  break;
15948
- case "subtitle":
15949
- dup(d.subtitle);
15950
- d.subtitle = value;
16331
+ case "no-region-labels":
16332
+ d.noRegionLabels = true;
15951
16333
  break;
15952
- case "caption":
15953
- dup(d.caption);
15954
- d.caption = value;
16334
+ case "no-poi-labels":
16335
+ d.noPoiLabels = true;
16336
+ break;
16337
+ case "no-colorize":
16338
+ d.noColorize = true;
15955
16339
  break;
15956
16340
  }
15957
16341
  }
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
16342
  function handleTag(trimmed, line12) {
15979
16343
  const m = matchTagBlockHeading(trimmed);
15980
16344
  if (!m) {
@@ -16174,13 +16538,15 @@ function parseMap(content) {
16174
16538
  pushError(line12, `Edge has an empty endpoint: "${trimmed}".`);
16175
16539
  continue;
16176
16540
  }
16177
- const meta = k === links.length - 1 ? lastSplit.meta : {};
16541
+ const isLast = k === links.length - 1;
16542
+ const meta = isLast ? lastSplit.meta : {};
16543
+ const style = links[k].style === "arc" ? "arc" : "straight";
16178
16544
  edges.push({
16179
16545
  from,
16180
16546
  to,
16181
16547
  ...links[k].label !== void 0 && { label: links[k].label },
16182
16548
  directed: links[k].directed,
16183
- style: links[k].style,
16549
+ style,
16184
16550
  meta,
16185
16551
  lineNumber: line12
16186
16552
  });
@@ -16266,22 +16632,19 @@ var init_parser12 = __esm({
16266
16632
  LEG_ARROW_RE = /^(-[^>]*?->|->|~[^>]*?~>|~>|--)\s+(.+)$/;
16267
16633
  AT_RE = /(^|[\s,])at\s*:/i;
16268
16634
  DIRECTIVE_SET = /* @__PURE__ */ new Set([
16269
- "region",
16270
- "projection",
16271
16635
  "region-metric",
16272
16636
  "poi-metric",
16273
16637
  "flow-metric",
16274
- "scale",
16275
- "region-labels",
16276
- "poi-labels",
16277
- "default-country",
16278
- "default-state",
16638
+ "locale",
16279
16639
  "active-tag",
16640
+ "caption",
16280
16641
  "no-legend",
16281
- "no-insets",
16282
- "relief",
16283
- "subtitle",
16284
- "caption"
16642
+ "no-coastline",
16643
+ "no-relief",
16644
+ "no-context-labels",
16645
+ "no-region-labels",
16646
+ "no-poi-labels",
16647
+ "no-colorize"
16285
16648
  ]);
16286
16649
  }
16287
16650
  });
@@ -16459,6 +16822,21 @@ function parseBoxesAndLines(content) {
16459
16822
  }
16460
16823
  continue;
16461
16824
  }
16825
+ if (!contentStarted) {
16826
+ const metricMatch = trimmed.match(/^box-metric\s+(.+)$/i);
16827
+ if (metricMatch) {
16828
+ const { label, colorName } = peelTrailingColorName(
16829
+ metricMatch[1].trim()
16830
+ );
16831
+ result.boxMetric = label;
16832
+ if (colorName !== void 0) result.boxMetricColor = colorName;
16833
+ continue;
16834
+ }
16835
+ if (/^show-values$/i.test(trimmed)) {
16836
+ result.showValues = true;
16837
+ continue;
16838
+ }
16839
+ }
16462
16840
  if (!contentStarted) {
16463
16841
  const optMatch = trimmed.match(OPTION_NOCOLON_RE);
16464
16842
  if (optMatch) {
@@ -16837,6 +17215,19 @@ function parseNodeLine(trimmed, lineNum, metaAliasMap, diagnostics, nameAliasMap
16837
17215
  description = [metadata["description"]];
16838
17216
  delete metadata["description"];
16839
17217
  }
17218
+ let value;
17219
+ if (metadata["value"] !== void 0) {
17220
+ const raw = metadata["value"];
17221
+ const num = Number(raw);
17222
+ if (Number.isFinite(num)) {
17223
+ value = num;
17224
+ } else {
17225
+ diagnostics.push(
17226
+ makeDgmoError(lineNum, `value must be a number (got "${raw}")`, "error")
17227
+ );
17228
+ }
17229
+ delete metadata["value"];
17230
+ }
16840
17231
  if (split.alias) {
16841
17232
  nameAliasMap?.set(normalizeName(split.alias), label);
16842
17233
  }
@@ -16845,7 +17236,8 @@ function parseNodeLine(trimmed, lineNum, metaAliasMap, diagnostics, nameAliasMap
16845
17236
  label,
16846
17237
  lineNumber: lineNum,
16847
17238
  metadata,
16848
- ...description !== void 0 && { description }
17239
+ ...description !== void 0 && { description },
17240
+ ...value !== void 0 && { value }
16849
17241
  };
16850
17242
  }
16851
17243
  function splitTargetAndMeta(rest, metaAliasMap) {
@@ -24202,8 +24594,8 @@ function renderKanban(container, parsed, palette, isDark, options) {
24202
24594
  let metaY = separatorY + sCardSeparatorGap + sCardMetaFontSize;
24203
24595
  for (const meta of tagMeta) {
24204
24596
  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);
24597
+ const labelWidth2 = (meta.label.length + 2) * sCardMetaFontSize * 0.6;
24598
+ cg.append("text").attr("x", cx + sCardPaddingX + labelWidth2).attr("y", metaY).attr("font-size", sCardMetaFontSize).attr("fill", onCardText).text(meta.value);
24207
24599
  metaY += sCardMetaLineHeight;
24208
24600
  }
24209
24601
  for (const detail of card.details) {
@@ -24547,8 +24939,8 @@ function renderSwimlaneCard(parent, cardLayout, tagGroups, activeTagGroup, palet
24547
24939
  let metaY = separatorY + sCardSeparatorGap + sCardMetaFontSize;
24548
24940
  for (const meta of tagMeta) {
24549
24941
  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);
24942
+ const labelWidth2 = (meta.label.length + 2) * sCardMetaFontSize * 0.6;
24943
+ cg.append("text").attr("x", cx + sCardPaddingX + labelWidth2).attr("y", metaY).attr("font-size", sCardMetaFontSize).attr("fill", onCardText).text(meta.value);
24552
24944
  metaY += sCardMetaLineHeight;
24553
24945
  }
24554
24946
  for (const detail of card.details) {
@@ -25383,8 +25775,8 @@ function classifyEREntities(tables, relationships) {
25383
25775
  }
25384
25776
  }
25385
25777
  const mmParticipants = /* @__PURE__ */ new Set();
25386
- for (const [id, neighbors] of tableStarNeighbors) {
25387
- if (neighbors.size >= 2) mmParticipants.add(id);
25778
+ for (const [id, neighbors2] of tableStarNeighbors) {
25779
+ if (neighbors2.size >= 2) mmParticipants.add(id);
25388
25780
  }
25389
25781
  const indegreeValues = Object.values(indegreeMap);
25390
25782
  const mean = indegreeValues.reduce((a, b) => a + b, 0) / indegreeValues.length;
@@ -25965,7 +26357,18 @@ function fitLabelToHeader(label, nodeWidth, maxLines) {
25965
26357
  const truncated = label.length > maxChars ? label.slice(0, maxChars - 1) + "\u2026" : label;
25966
26358
  return { lines: [truncated], fontSize: MIN_NODE_FONT_SIZE };
25967
26359
  }
25968
- function nodeColors(node, tagGroups, activeGroupName, palette, isDark, solid) {
26360
+ function nodeColors(node, tagGroups, activeGroupName, palette, isDark, value, solid) {
26361
+ const neutralFill = mix(palette.bg, palette.text, isDark ? 90 : 95);
26362
+ if (value.active) {
26363
+ const fill3 = node.value !== void 0 ? value.fillForValue(node.value) : neutralFill;
26364
+ const stroke3 = value.hue;
26365
+ const text2 = contrastText(
26366
+ fill3,
26367
+ palette.textOnFillLight,
26368
+ palette.textOnFillDark
26369
+ );
26370
+ return { fill: fill3, stroke: stroke3, text: text2 };
26371
+ }
25969
26372
  const tagColor = resolveTagColor(
25970
26373
  node.metadata,
25971
26374
  [...tagGroups],
@@ -26055,7 +26458,8 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26055
26458
  controlsExpanded,
26056
26459
  onToggleDescriptions,
26057
26460
  onToggleControlsExpand,
26058
- exportMode = false
26461
+ exportMode = false,
26462
+ controlsHost
26059
26463
  } = options ?? {};
26060
26464
  d3Selection6.select(container).selectAll(":not([data-d3-tooltip])").remove();
26061
26465
  const width = exportDims?.width ?? container.clientWidth;
@@ -26073,21 +26477,65 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26073
26477
  const sGroupLabelZone = sctx.structural(GROUP_LABEL_ZONE);
26074
26478
  const sTitleFontSize = sctx.text(TITLE_FONT_SIZE);
26075
26479
  const sTitleY = sctx.structural(TITLE_Y);
26076
- const sLegendHeight = sctx.structural(
26480
+ const nodeValues = parsed.nodes.filter((n) => n.value !== void 0).map((n) => n.value);
26481
+ const hasRamp = nodeValues.length > 0;
26482
+ const allNonNegative = hasRamp && nodeValues.every((v) => v >= 0);
26483
+ const rampMin = allNonNegative ? 0 : Math.min(...nodeValues);
26484
+ const rampMax = Math.max(...nodeValues);
26485
+ const rampHue = resolveColor(parsed.boxMetricColor ?? "", palette) ?? palette.primary;
26486
+ const rampBase = isDark ? mix(palette.surface, palette.text, 28) : palette.bg;
26487
+ const fillForValue = (v) => {
26488
+ const t = rampMax > rampMin ? (v - rampMin) / (rampMax - rampMin) : 1;
26489
+ const pct = RAMP_FLOOR + Math.max(0, Math.min(1, t)) * (100 - RAMP_FLOOR);
26490
+ return mix(rampHue, rampBase, pct);
26491
+ };
26492
+ const VALUE_NAME = hasRamp ? parsed.boxMetric?.trim() || "Value" : null;
26493
+ const matchColorGroup = (v) => {
26494
+ const lv = v.trim().toLowerCase();
26495
+ if (lv === "none") return null;
26496
+ const tg = parsed.tagGroups.find((g) => g.name.toLowerCase() === lv);
26497
+ if (tg) return tg.name;
26498
+ if (lv === VALUE_NAME?.toLowerCase()) return VALUE_NAME;
26499
+ return v;
26500
+ };
26501
+ const override = activeTagGroup;
26502
+ let activeGroup;
26503
+ if (override !== void 0) {
26504
+ activeGroup = override === null ? null : matchColorGroup(override);
26505
+ } else if (parsed.options["active-tag"] !== void 0) {
26506
+ activeGroup = matchColorGroup(parsed.options["active-tag"]);
26507
+ } else {
26508
+ activeGroup = VALUE_NAME ?? (parsed.tagGroups.length > 0 ? parsed.tagGroups[0].name : null);
26509
+ }
26510
+ const activeIsValue = VALUE_NAME !== null && activeGroup === VALUE_NAME;
26511
+ const valueGroup = VALUE_NAME !== null ? {
26512
+ name: VALUE_NAME,
26513
+ entries: [],
26514
+ gradient: {
26515
+ min: rampMin,
26516
+ max: rampMax,
26517
+ hue: rampHue,
26518
+ base: rampBase
26519
+ }
26520
+ } : null;
26521
+ const legendGroups = [
26522
+ ...valueGroup ? [valueGroup] : [],
26523
+ ...parsed.tagGroups
26524
+ ];
26525
+ const reserveHasDescriptions = parsed.nodes.some(
26526
+ (n) => n.description && n.description.length > 0
26527
+ );
26528
+ const willRenderLegend = legendGroups.length > 0 || reserveHasDescriptions && controlsHost !== "app";
26529
+ const sLegendHeight = willRenderLegend ? sctx.structural(
26077
26530
  getMaxLegendReservedHeight(
26078
26531
  {
26079
- groups: parsed.tagGroups,
26532
+ groups: legendGroups,
26080
26533
  position: { placement: "top-center", titleRelation: "below-title" },
26081
26534
  mode: exportMode ? "export" : "preview"
26082
26535
  },
26083
26536
  width
26084
26537
  )
26085
- );
26086
- const activeGroup = resolveActiveTagGroup(
26087
- parsed.tagGroups,
26088
- parsed.options["active-tag"],
26089
- activeTagGroup
26090
- );
26538
+ ) : 0;
26091
26539
  const hidden = hiddenTagValues ?? parsed.initialHiddenTagValues;
26092
26540
  const nodeMap = /* @__PURE__ */ new Map();
26093
26541
  for (const node of parsed.nodes) nodeMap.set(node.label, node);
@@ -26098,7 +26546,7 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26098
26546
  const hasAnyDescriptions = parsed.nodes.some(
26099
26547
  (n) => n.description && n.description.length > 0
26100
26548
  );
26101
- const needsLegend = parsed.tagGroups.length > 0 || hasAnyDescriptions && onToggleDescriptions;
26549
+ const needsLegend = legendGroups.length > 0 || hasAnyDescriptions && onToggleDescriptions;
26102
26550
  const legendH = needsLegend ? sLegendHeight + 8 : 0;
26103
26551
  const groupLabelsSet = new Set(layout.groups.map((g) => g.label));
26104
26552
  let labelZoneExtension = 0;
@@ -26304,12 +26752,16 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26304
26752
  activeGroup,
26305
26753
  palette,
26306
26754
  isDark,
26755
+ { active: activeIsValue, hue: rampHue, fillForValue },
26307
26756
  parsed.options["solid-fill"] === "on"
26308
26757
  );
26309
26758
  const nodeG = diagramG.append("g").attr("class", "bl-node").attr("transform", `translate(${ln.x},${ln.y})`).attr("data-line-number", node.lineNumber).attr("data-node-id", node.label).style("cursor", onClickItem ? "pointer" : "default").style("--bl-node-stroke", colors.stroke);
26310
26759
  for (const [key, val] of Object.entries(node.metadata)) {
26311
26760
  nodeG.attr(`data-tag-${key.toLowerCase()}`, val.toLowerCase());
26312
26761
  }
26762
+ if (node.value !== void 0) {
26763
+ nodeG.attr("data-value", node.value);
26764
+ }
26313
26765
  if (onClickItem) {
26314
26766
  nodeG.on("click", (event) => {
26315
26767
  const target = event.target;
@@ -26393,14 +26845,30 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26393
26845
  nodeG.append("text").attr("x", 0).attr("y", -totalH / 2 + lineH / 2 + li * lineH).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-size", fitted.fontSize).attr("font-weight", "600").attr("fill", colors.text).text(fitted.lines[li]);
26394
26846
  }
26395
26847
  }
26848
+ if (parsed.showValues && node.value !== void 0) {
26849
+ const valueText = String(node.value);
26850
+ const descShown = !!(desc && desc.length > 0 && !hideDescriptions);
26851
+ if (descShown) {
26852
+ const padX = 6;
26853
+ const padY = 5;
26854
+ const bw = valueText.length * VALUE_FONT_SIZE * CHAR_WIDTH_RATIO2 + 8;
26855
+ const bh = VALUE_FONT_SIZE + 4;
26856
+ const bx = ln.width / 2 - bw - 4;
26857
+ const by = -ln.height / 2 + 4;
26858
+ nodeG.append("rect").attr("x", bx).attr("y", by).attr("width", bw).attr("height", bh).attr("rx", 3).attr("fill", palette.bg).attr("opacity", 0.85);
26859
+ nodeG.append("text").attr("class", "bl-node-value").attr("x", bx + bw - padX).attr("y", by + padY).attr("text-anchor", "end").attr("dominant-baseline", "central").attr("font-size", VALUE_FONT_SIZE).attr("font-weight", "600").attr("fill", palette.textMuted).text(valueText);
26860
+ } else {
26861
+ nodeG.append("text").attr("class", "bl-node-value").attr("x", 0).attr("y", ln.height / 2 - VALUE_FONT_SIZE).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-size", VALUE_FONT_SIZE).attr("font-weight", "600").attr("fill", colors.text).attr("opacity", 0.8).text(valueText);
26862
+ }
26863
+ }
26396
26864
  }
26397
26865
  const hasDescriptions = parsed.nodes.some(
26398
26866
  (n) => n.description && n.description.length > 0
26399
26867
  );
26400
- const hasLegend = parsed.tagGroups.length > 0 || hasDescriptions;
26868
+ const hasLegend = legendGroups.length > 0 || hasDescriptions && controlsHost !== "app";
26401
26869
  if (hasLegend) {
26402
26870
  let controlsGroup;
26403
- if (hasDescriptions && onToggleDescriptions) {
26871
+ if (hasDescriptions && (onToggleDescriptions || controlsHost === "app")) {
26404
26872
  controlsGroup = {
26405
26873
  toggles: [
26406
26874
  {
@@ -26415,10 +26883,17 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26415
26883
  };
26416
26884
  }
26417
26885
  const legendConfig = {
26418
- groups: parsed.tagGroups,
26886
+ groups: legendGroups,
26419
26887
  position: { placement: "top-center", titleRelation: "below-title" },
26420
26888
  mode: exportMode ? "export" : "preview",
26421
- ...controlsGroup !== void 0 && { controlsGroup }
26889
+ // Keep inactive sibling tag groups visible as collapsed pills so the user
26890
+ // can click one to flip the active colouring dimension (preview only —
26891
+ // export shows just the active group). Without this, declaring a second
26892
+ // tag group (e.g. Team) leaves it invisible whenever another group is
26893
+ // active. The app's BoxesAndLinesPreview already wires pill clicks.
26894
+ showInactivePills: true,
26895
+ ...controlsGroup !== void 0 && { controlsGroup },
26896
+ ...controlsHost !== void 0 && { controlsHost }
26422
26897
  };
26423
26898
  const legendState = {
26424
26899
  activeGroup,
@@ -26463,7 +26938,7 @@ function renderBoxesAndLinesForExport(container, parsed, layout, palette, isDark
26463
26938
  }
26464
26939
  });
26465
26940
  }
26466
- var d3Selection6, d3Shape4, DIAGRAM_PADDING6, NODE_FONT_SIZE, MIN_NODE_FONT_SIZE, EDGE_LABEL_FONT_SIZE4, EDGE_STROKE_WIDTH5, NODE_STROKE_WIDTH5, NODE_RX, COLLAPSE_BAR_HEIGHT3, ARROWHEAD_W2, ARROWHEAD_H2, DESC_FONT_SIZE, DESC_LINE_HEIGHT, MAX_DESC_LINES, CHAR_WIDTH_RATIO2, NODE_TEXT_PADDING, GROUP_RX, GROUP_LABEL_FONT_SIZE, GROUP_LABEL_ZONE, lineGeneratorLR, lineGeneratorTB;
26941
+ var d3Selection6, d3Shape4, DIAGRAM_PADDING6, NODE_FONT_SIZE, MIN_NODE_FONT_SIZE, EDGE_LABEL_FONT_SIZE4, EDGE_STROKE_WIDTH5, NODE_STROKE_WIDTH5, NODE_RX, COLLAPSE_BAR_HEIGHT3, ARROWHEAD_W2, ARROWHEAD_H2, DESC_FONT_SIZE, DESC_LINE_HEIGHT, MAX_DESC_LINES, CHAR_WIDTH_RATIO2, NODE_TEXT_PADDING, GROUP_RX, GROUP_LABEL_FONT_SIZE, GROUP_LABEL_ZONE, RAMP_FLOOR, VALUE_FONT_SIZE, lineGeneratorLR, lineGeneratorTB;
26467
26942
  var init_renderer6 = __esm({
26468
26943
  "src/boxes-and-lines/renderer.ts"() {
26469
26944
  "use strict";
@@ -26474,6 +26949,7 @@ var init_renderer6 = __esm({
26474
26949
  init_legend_layout();
26475
26950
  init_title_constants();
26476
26951
  init_color_utils();
26952
+ init_colors();
26477
26953
  init_tag_groups();
26478
26954
  init_inline_markdown();
26479
26955
  init_wrapped_desc();
@@ -26496,6 +26972,8 @@ var init_renderer6 = __esm({
26496
26972
  GROUP_RX = 8;
26497
26973
  GROUP_LABEL_FONT_SIZE = 14;
26498
26974
  GROUP_LABEL_ZONE = 32;
26975
+ RAMP_FLOOR = 15;
26976
+ VALUE_FONT_SIZE = 11;
26499
26977
  lineGeneratorLR = d3Shape4.line().x((d) => d.x).y((d) => d.y).curve(d3Shape4.curveBasis);
26500
26978
  lineGeneratorTB = d3Shape4.line().x((d) => d.x).y((d) => d.y).curve(d3Shape4.curveBasis);
26501
26979
  }
@@ -27666,8 +28144,9 @@ function renderMindmap(container, parsed, layout, palette, isDark, onClickItem,
27666
28144
  const containerHeight = exportDims?.height ?? (container.getBoundingClientRect().height || 600);
27667
28145
  d3Selection7.select(container).selectAll("*").remove();
27668
28146
  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);
28147
+ const appHosted = options?.controlsHost === "app";
27669
28148
  const hasControls = !!options?.onToggleColorByDepth || !!options?.onToggleDescriptions;
27670
- const hasLegend = parsed.tagGroups.length > 0 || hasControls;
28149
+ const hasLegend = parsed.tagGroups.length > 0 || hasControls && !appHosted;
27671
28150
  const fixedLegend = !isExport && hasLegend;
27672
28151
  const legendReserve = fixedLegend ? getMaxLegendReservedHeight(
27673
28152
  {
@@ -27761,7 +28240,10 @@ function renderMindmap(container, parsed, layout, palette, isDark, onClickItem,
27761
28240
  }),
27762
28241
  position: { placement: "top-center", titleRelation: "below-title" },
27763
28242
  mode: options?.exportMode ? "export" : "preview",
27764
- ...controlsToggles !== void 0 && { controlsGroup: controlsToggles }
28243
+ ...controlsToggles !== void 0 && { controlsGroup: controlsToggles },
28244
+ ...options?.controlsHost !== void 0 && {
28245
+ controlsHost: options.controlsHost
28246
+ }
27765
28247
  };
27766
28248
  const legendState = {
27767
28249
  activeGroup: options?.colorByDepth ? null : activeTagGroup !== void 0 ? activeTagGroup : parsed.options["active-tag"] ?? null,
@@ -28205,8 +28687,8 @@ function computeFieldAlignX(children) {
28205
28687
  for (const child of children) {
28206
28688
  if (child.metadata["_labelField"] === "true" && child.children.length >= 2) {
28207
28689
  const labelEl = child.children[0];
28208
- const labelWidth = labelEl.label.length * CHAR_WIDTH5;
28209
- maxLabelWidth = Math.max(maxLabelWidth, labelWidth);
28690
+ const labelWidth2 = labelEl.label.length * CHAR_WIDTH5;
28691
+ maxLabelWidth = Math.max(maxLabelWidth, labelWidth2);
28210
28692
  labelFieldCount++;
28211
28693
  }
28212
28694
  }
@@ -33170,7 +33652,7 @@ function hasRoles(node) {
33170
33652
  function computeNodeWidth2(node, expanded, options) {
33171
33653
  const badgeVal = node.computedConcurrentInvocations === 0 && node.computedInstances > 1 ? node.computedInstances : 0;
33172
33654
  const badgeLen = badgeVal > 0 ? `${badgeVal}x`.length + 2 : 0;
33173
- const labelWidth = (node.label.length + badgeLen) * CHAR_WIDTH7 + PADDING_X3;
33655
+ const labelWidth2 = (node.label.length + badgeLen) * CHAR_WIDTH7 + PADDING_X3;
33174
33656
  const allKeys = [];
33175
33657
  if (node.computedRps > 0) allKeys.push("RPS");
33176
33658
  if (expanded) {
@@ -33214,7 +33696,7 @@ function computeNodeWidth2(node, expanded, options) {
33214
33696
  allKeys.push("overflow");
33215
33697
  }
33216
33698
  }
33217
- if (allKeys.length === 0) return Math.max(MIN_NODE_WIDTH2, labelWidth);
33699
+ if (allKeys.length === 0) return Math.max(MIN_NODE_WIDTH2, labelWidth2);
33218
33700
  const maxKeyLen = Math.max(...allKeys.map((k) => k.length));
33219
33701
  let maxRowWidth = 0;
33220
33702
  if (node.computedRps > 0) {
@@ -33302,7 +33784,7 @@ function computeNodeWidth2(node, expanded, options) {
33302
33784
  truncated.length * META_CHAR_WIDTH3 + PADDING_X3
33303
33785
  );
33304
33786
  }
33305
- return Math.max(MIN_NODE_WIDTH2, labelWidth, maxRowWidth + 20, descWidth);
33787
+ return Math.max(MIN_NODE_WIDTH2, labelWidth2, maxRowWidth + 20, descWidth);
33306
33788
  }
33307
33789
  function computeNodeHeight2(node, expanded, options) {
33308
33790
  const propCount = countDisplayProps(node, expanded, options);
@@ -34851,8 +35333,9 @@ function computeInfraLegendGroups(nodes, tagGroups, palette, edges) {
34851
35333
  }
34852
35334
  return groups;
34853
35335
  }
34854
- function renderLegend3(rootSvg, legendGroups, totalWidth, legendY, palette, isDark, activeGroup, playback, exportMode = false) {
35336
+ function renderLegend3(rootSvg, legendGroups, totalWidth, legendY, palette, isDark, activeGroup, playback, exportMode = false, controlsHost) {
34855
35337
  if (legendGroups.length === 0 && !playback) return;
35338
+ const appHostedPlayback = controlsHost === "app" && !!playback;
34856
35339
  const legendG = rootSvg.append("g").attr("transform", `translate(0, ${legendY})`);
34857
35340
  if (activeGroup) {
34858
35341
  legendG.attr("data-legend-active", activeGroup.toLowerCase());
@@ -34861,14 +35344,29 @@ function renderLegend3(rootSvg, legendGroups, totalWidth, legendY, palette, isDa
34861
35344
  name: g.name,
34862
35345
  entries: g.entries.map((e) => ({ value: e.value, color: e.color }))
34863
35346
  }));
34864
- if (playback) {
35347
+ if (playback && !appHostedPlayback) {
34865
35348
  allGroups.push({ name: "Playback", entries: [] });
34866
35349
  }
34867
35350
  const legendConfig = {
34868
35351
  groups: allGroups,
34869
35352
  position: { placement: "top-center", titleRelation: "below-title" },
34870
35353
  mode: exportMode ? "export" : "preview",
34871
- showEmptyGroups: true
35354
+ showEmptyGroups: true,
35355
+ ...appHostedPlayback && {
35356
+ controlsHost: "app",
35357
+ controlsGroup: {
35358
+ toggles: [
35359
+ {
35360
+ id: "playback",
35361
+ type: "toggle",
35362
+ label: "Playback",
35363
+ active: true,
35364
+ onToggle: () => {
35365
+ }
35366
+ }
35367
+ ]
35368
+ }
35369
+ }
34872
35370
  };
34873
35371
  const legendState = { activeGroup };
34874
35372
  renderLegendD3(
@@ -34919,8 +35417,9 @@ function renderLegend3(rootSvg, legendGroups, totalWidth, legendY, palette, isDa
34919
35417
  }
34920
35418
  }
34921
35419
  }
34922
- function renderInfra(container, layout, palette, isDark, title, titleLineNumber, tagGroups, activeGroup, animate, playback, expandedNodeIds, exportMode, collapsedNodes) {
35420
+ function renderInfra(container, layout, palette, isDark, title, titleLineNumber, tagGroups, activeGroup, animate, playback, expandedNodeIds, exportMode, collapsedNodes, controlsHost) {
34923
35421
  d3Selection11.select(container).selectAll(":not([data-d3-tooltip])").remove();
35422
+ const appHostedPlayback = controlsHost === "app" && !!playback;
34924
35423
  const ctx = ScaleContext.identity();
34925
35424
  const sc = buildScaledConstants(ctx);
34926
35425
  const legendGroups = computeInfraLegendGroups(
@@ -34929,7 +35428,7 @@ function renderInfra(container, layout, palette, isDark, title, titleLineNumber,
34929
35428
  palette,
34930
35429
  layout.edges
34931
35430
  );
34932
- const hasLegend = legendGroups.length > 0 || !!playback;
35431
+ const hasLegend = legendGroups.length > 0 || !!playback && !appHostedPlayback;
34933
35432
  const fixedLegend = !exportMode && hasLegend;
34934
35433
  const legendDynamicH = hasLegend ? getMaxLegendReservedHeight(
34935
35434
  {
@@ -35073,7 +35572,8 @@ function renderInfra(container, layout, palette, isDark, title, titleLineNumber,
35073
35572
  isDark,
35074
35573
  activeGroup ?? null,
35075
35574
  playback ?? void 0,
35076
- exportMode
35575
+ exportMode,
35576
+ controlsHost
35077
35577
  );
35078
35578
  legendSvg.selectAll(".infra-legend-group").style("pointer-events", "auto");
35079
35579
  } else {
@@ -35086,7 +35586,8 @@ function renderInfra(container, layout, palette, isDark, title, titleLineNumber,
35086
35586
  isDark,
35087
35587
  activeGroup ?? null,
35088
35588
  playback ?? void 0,
35089
- exportMode
35589
+ exportMode,
35590
+ controlsHost
35090
35591
  );
35091
35592
  }
35092
35593
  }
@@ -42721,6 +43222,9 @@ function renderTechRadar(container, parsed, palette, isDark, onClickItem, export
42721
43222
  onToggle: (active) => options.onToggleListing(active)
42722
43223
  }
42723
43224
  ]
43225
+ },
43226
+ ...options.controlsHost !== void 0 && {
43227
+ controlsHost: options.controlsHost
42724
43228
  }
42725
43229
  };
42726
43230
  const legendState = {
@@ -44544,7 +45048,7 @@ function computeCycleLayout(parsed, options) {
44544
45048
  const circleNodes = parsed.options["circle-nodes"] === "true";
44545
45049
  const nodeDims = parsed.nodes.map((node) => {
44546
45050
  const hasDesc = !hideDescriptions && node.description.length > 0;
44547
- const labelWidth = Math.max(
45051
+ const labelWidth2 = Math.max(
44548
45052
  MIN_NODE_WIDTH4,
44549
45053
  node.label.length * LABEL_CHAR_W + NODE_PAD_X * 2
44550
45054
  );
@@ -44553,12 +45057,12 @@ function computeCycleLayout(parsed, options) {
44553
45057
  }
44554
45058
  if (!hasDesc) {
44555
45059
  return {
44556
- width: Math.min(MAX_NODE_WIDTH3, labelWidth),
45060
+ width: Math.min(MAX_NODE_WIDTH3, labelWidth2),
44557
45061
  height: PLAIN_NODE_HEIGHT,
44558
45062
  wrappedDesc: []
44559
45063
  };
44560
45064
  }
44561
- return chooseDescribedRectDims(node.description, labelWidth);
45065
+ return chooseDescribedRectDims(node.description, labelWidth2);
44562
45066
  });
44563
45067
  if (circleNodes) {
44564
45068
  const maxDiam = Math.max(...nodeDims.map((d) => d.width));
@@ -44754,10 +45258,10 @@ function computeCycleLayout(parsed, options) {
44754
45258
  scale
44755
45259
  };
44756
45260
  }
44757
- function chooseDescribedRectDims(description, labelWidth) {
45261
+ function chooseDescribedRectDims(description, labelWidth2) {
44758
45262
  const minW = Math.min(
44759
45263
  MAX_NODE_WIDTH3,
44760
- Math.max(MIN_NODE_WIDTH4, labelWidth, DESC_MIN_WIDTH)
45264
+ Math.max(MIN_NODE_WIDTH4, labelWidth2, DESC_MIN_WIDTH)
44761
45265
  );
44762
45266
  let best = null;
44763
45267
  let bestScore = Infinity;
@@ -45185,7 +45689,8 @@ function renderCycle(container, parsed, palette, isDark, onClickItem, exportDims
45185
45689
  const hideDescriptions = (renderOptions?.hideDescriptions ?? false) || parsed.options["no-descriptions"] === "true" || viewState?.hd === true;
45186
45690
  const showDescriptions = !hideDescriptions;
45187
45691
  const hasDescriptions = parsed.nodes.some((n) => n.description.length > 0) || parsed.edges.some((e) => e.description.length > 0);
45188
- const hasLegend = hasDescriptions && !!renderOptions?.onToggleDescriptions;
45692
+ const appHostedControls = renderOptions?.controlsHost === "app";
45693
+ const hasLegend = !appHostedControls && hasDescriptions && !!renderOptions?.onToggleDescriptions;
45189
45694
  const showTitle = !!parsed.title && parsed.options["no-title"] !== "on";
45190
45695
  const legendOffset = hasLegend ? sLegendHeight : 0;
45191
45696
  const layoutHeight = height - (showTitle ? sTitleAreaHeight : 0) - legendOffset;
@@ -45222,7 +45727,10 @@ function renderCycle(container, parsed, palette, isDark, onClickItem, exportDims
45222
45727
  groups: [],
45223
45728
  position: { placement: "top-center", titleRelation: "below-title" },
45224
45729
  mode: renderOptions?.exportMode ? "export" : "preview",
45225
- controlsGroup
45730
+ controlsGroup,
45731
+ ...renderOptions?.controlsHost !== void 0 && {
45732
+ controlsHost: renderOptions.controlsHost
45733
+ }
45226
45734
  };
45227
45735
  const legendState = {
45228
45736
  activeGroup: null,
@@ -45493,6 +46001,107 @@ function featureIndex(topo) {
45493
46001
  }
45494
46002
  return idx;
45495
46003
  }
46004
+ function buildAdjacency(topo) {
46005
+ const cached = adjacencyCache.get(topo);
46006
+ if (cached) return cached;
46007
+ const geometries = geomObject(topo).geometries;
46008
+ const nb = (0, import_topojson_client.neighbors)(geometries);
46009
+ const sets = /* @__PURE__ */ new Map();
46010
+ geometries.forEach((g, i) => {
46011
+ if (!g.type || g.type === "null") return;
46012
+ let set = sets.get(g.id);
46013
+ if (!set) {
46014
+ set = /* @__PURE__ */ new Set();
46015
+ sets.set(g.id, set);
46016
+ }
46017
+ for (const j of nb[i] ?? []) {
46018
+ const nid = geometries[j]?.id;
46019
+ if (nid && nid !== g.id) set.add(nid);
46020
+ }
46021
+ });
46022
+ const out = /* @__PURE__ */ new Map();
46023
+ for (const [iso, set] of sets) out.set(iso, [...set].sort());
46024
+ adjacencyCache.set(topo, out);
46025
+ return out;
46026
+ }
46027
+ function decodeFeatures(topo) {
46028
+ return geomObject(topo).geometries.map((g) => {
46029
+ const f = (0, import_topojson_client.feature)(topo, g);
46030
+ return {
46031
+ type: "Feature",
46032
+ id: g.id,
46033
+ properties: g.properties,
46034
+ geometry: f.geometry
46035
+ };
46036
+ });
46037
+ }
46038
+ function pointInRing(lon, lat, ring) {
46039
+ let inside = false;
46040
+ for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
46041
+ const xi = ring[i][0];
46042
+ const yi = ring[i][1];
46043
+ const xj = ring[j][0];
46044
+ const yj = ring[j][1];
46045
+ const intersect = yi > lat !== yj > lat && lon < (xj - xi) * (lat - yi) / (yj - yi) + xi;
46046
+ if (intersect) inside = !inside;
46047
+ }
46048
+ return inside;
46049
+ }
46050
+ function pointOnRingEdge(lon, lat, ring) {
46051
+ for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
46052
+ const xi = ring[i][0];
46053
+ const yi = ring[i][1];
46054
+ const xj = ring[j][0];
46055
+ const yj = ring[j][1];
46056
+ if (lon < Math.min(xi, xj) - EDGE_EPS || lon > Math.max(xi, xj) + EDGE_EPS)
46057
+ continue;
46058
+ if (lat < Math.min(yi, yj) - EDGE_EPS || lat > Math.max(yi, yj) + EDGE_EPS)
46059
+ continue;
46060
+ const cross = (xj - xi) * (lat - yi) - (yj - yi) * (lon - xi);
46061
+ if (Math.abs(cross) <= EDGE_EPS) return true;
46062
+ }
46063
+ return false;
46064
+ }
46065
+ function pointInGeometry(geometry, lon, lat) {
46066
+ const g = geometry;
46067
+ if (!g) return false;
46068
+ const polys = g.type === "Polygon" ? [g.coordinates] : g.type === "MultiPolygon" ? g.coordinates : [];
46069
+ for (const rings of polys) {
46070
+ if (!rings.length) continue;
46071
+ if (pointOnRingEdge(lon, lat, rings[0])) return true;
46072
+ if (!pointInRing(lon, lat, rings[0])) continue;
46073
+ let inHole = false;
46074
+ for (let h = 1; h < rings.length; h++) {
46075
+ if (pointInRing(lon, lat, rings[h]) && !pointOnRingEdge(lon, lat, rings[h])) {
46076
+ inHole = true;
46077
+ break;
46078
+ }
46079
+ }
46080
+ if (!inHole) return true;
46081
+ }
46082
+ return false;
46083
+ }
46084
+ function regionAt(lonLat, countries, states) {
46085
+ const lon = lonLat[0];
46086
+ const lat = lonLat[1];
46087
+ let country = null;
46088
+ for (const f of countries) {
46089
+ if (pointInGeometry(f.geometry, lon, lat)) {
46090
+ country = { iso: f.id, name: f.properties.name };
46091
+ break;
46092
+ }
46093
+ }
46094
+ let state = null;
46095
+ if (country?.iso === "US" && states) {
46096
+ for (const f of states) {
46097
+ if (pointInGeometry(f.geometry, lon, lat)) {
46098
+ state = { iso: f.id, name: f.properties.name };
46099
+ break;
46100
+ }
46101
+ }
46102
+ }
46103
+ return { country, state };
46104
+ }
45496
46105
  function featureBbox(topo, geomId) {
45497
46106
  const geom = geomObject(topo).geometries.find((g) => g.id === geomId);
45498
46107
  if (!geom) return null;
@@ -45610,13 +46219,15 @@ function unionLongitudes(lons) {
45610
46219
  }
45611
46220
  return { west: pts[gapIdx], east: pts[gapIdx - 1] + 360 };
45612
46221
  }
45613
- var import_topojson_client, import_d3_geo, fold, DETACH_GAP_DEG, DETACH_AREA_FRAC;
46222
+ var import_topojson_client, import_d3_geo, fold, adjacencyCache, EDGE_EPS, DETACH_GAP_DEG, DETACH_AREA_FRAC;
45614
46223
  var init_geo = __esm({
45615
46224
  "src/map/geo.ts"() {
45616
46225
  "use strict";
45617
46226
  import_topojson_client = require("topojson-client");
45618
46227
  import_d3_geo = require("d3-geo");
45619
46228
  fold = (s) => s.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLowerCase().trim();
46229
+ adjacencyCache = /* @__PURE__ */ new WeakMap();
46230
+ EDGE_EPS = 1e-9;
45620
46231
  DETACH_GAP_DEG = 10;
45621
46232
  DETACH_AREA_FRAC = 0.25;
45622
46233
  }
@@ -45636,6 +46247,12 @@ function looksUS(lat, lon) {
45636
46247
  if (lat < 15 || lat > 72) return false;
45637
46248
  return lon >= -180 && lon <= -64 || lon >= 172;
45638
46249
  }
46250
+ function looksNorthAmericaNeighbor(lat, lon) {
46251
+ return lat >= 14 && lat <= 72 && lon >= -141 && lon <= -52;
46252
+ }
46253
+ function isWholeSphere(bb) {
46254
+ return bb[0][0] <= -179 && bb[1][0] >= 179 && bb[0][1] <= -89 && bb[1][1] >= 89;
46255
+ }
45639
46256
  function resolveMap(parsed, data) {
45640
46257
  const diagnostics = [...parsed.diagnostics];
45641
46258
  const err = (line12, message, code) => {
@@ -45646,9 +46263,6 @@ function resolveMap(parsed, data) {
45646
46263
  };
45647
46264
  const result = {
45648
46265
  title: parsed.title,
45649
- ...parsed.directives.subtitle !== void 0 && {
45650
- subtitle: parsed.directives.subtitle
45651
- },
45652
46266
  ...parsed.directives.caption !== void 0 && {
45653
46267
  caption: parsed.directives.caption
45654
46268
  },
@@ -45658,7 +46272,7 @@ function resolveMap(parsed, data) {
45658
46272
  // renderer's job (step 4) — the resolver only carries `tags` + `tagGroups`
45659
46273
  // through; it never resolves a tag value to a palette color (#10).
45660
46274
  directives: { ...parsed.directives },
45661
- basemaps: { world: "coarse", subdivisions: [] },
46275
+ basemaps: { world: "detail", subdivisions: [] },
45662
46276
  regions: [],
45663
46277
  pois: [],
45664
46278
  edges: [],
@@ -45667,7 +46281,8 @@ function resolveMap(parsed, data) {
45667
46281
  [-180, -85],
45668
46282
  [180, 85]
45669
46283
  ],
45670
- projection: "natural-earth",
46284
+ projection: "equirectangular",
46285
+ poiFrameContainers: [],
45671
46286
  diagnostics,
45672
46287
  error: parsed.error
45673
46288
  };
@@ -45677,7 +46292,10 @@ function resolveMap(parsed, data) {
45677
46292
  ...[...countryIndex.values()].map((v) => v.name),
45678
46293
  ...[...usStateIndex.values()].map((v) => v.name)
45679
46294
  ];
45680
- const usScoped = parsed.directives.region === "us-states" || parsed.directives.defaultCountry?.toUpperCase() === "US" || parsed.regions.some((r) => {
46295
+ const localeRaw = parsed.directives.locale?.toUpperCase();
46296
+ const localeCountry = localeRaw ? localeRaw.split("-")[0] : void 0;
46297
+ const localeSubdivision = localeRaw && /^[A-Z]{2}-/.test(localeRaw) ? localeRaw : void 0;
46298
+ const usScoped = localeCountry === "US" || parsed.regions.some((r) => {
45681
46299
  const f = fold(r.name);
45682
46300
  return usStateIndex.has(f) && !countryIndex.has(f);
45683
46301
  }) || parsed.regions.some(
@@ -45828,7 +46446,7 @@ function resolveMap(parsed, data) {
45828
46446
  if (!scope)
45829
46447
  warn2(
45830
46448
  line12,
45831
- `"${name}" is ambiguous \u2014 resolved to the most-populous match.`,
46449
+ `"${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
46450
  "W_MAP_AMBIGUOUS_NAME"
45833
46451
  );
45834
46452
  }
@@ -45841,17 +46459,21 @@ function resolveMap(parsed, data) {
45841
46459
  return fold(pos.name);
45842
46460
  };
45843
46461
  const poiCountries = [];
45844
- let anyNonUsPoi = false;
46462
+ let anyUsPoi = false;
46463
+ let anyNonNaPoi = false;
45845
46464
  const noteCountry = (iso) => {
45846
46465
  if (iso) {
45847
46466
  poiCountries.push(iso);
45848
- if (iso !== "US") anyNonUsPoi = true;
46467
+ if (iso === "US") anyUsPoi = true;
46468
+ if (iso !== "US" && iso !== "CA" && iso !== "MX") anyNonNaPoi = true;
45849
46469
  }
45850
46470
  };
45851
46471
  const deferred = [];
45852
46472
  for (const p of parsed.pois) {
45853
46473
  if (p.pos.kind === "coords") {
45854
- if (!looksUS(p.pos.lat, p.pos.lon)) anyNonUsPoi = true;
46474
+ if (looksUS(p.pos.lat, p.pos.lon)) anyUsPoi = true;
46475
+ else if (!looksNorthAmericaNeighbor(p.pos.lat, p.pos.lon))
46476
+ anyNonNaPoi = true;
45855
46477
  addResolvedPoi(p.pos.lat, p.pos.lon, p);
45856
46478
  continue;
45857
46479
  }
@@ -45869,14 +46491,15 @@ function resolveMap(parsed, data) {
45869
46491
  deferred.push(p);
45870
46492
  }
45871
46493
  }
45872
- const inferredCountry = parsed.directives.defaultCountry?.toUpperCase() ?? mostCommonCountry(regions, poiCountries) ?? void 0;
46494
+ const inferredCountry = localeCountry ?? mostCommonCountry(regions, poiCountries) ?? void 0;
46495
+ const inferredScope = localeSubdivision ?? inferredCountry;
45873
46496
  for (const p of deferred) {
45874
46497
  if (p.pos.kind !== "name") continue;
45875
46498
  const got = lookupName(
45876
46499
  p.pos.name,
45877
46500
  p.pos.scope,
45878
46501
  p.lineNumber,
45879
- inferredCountry,
46502
+ inferredScope,
45880
46503
  true
45881
46504
  );
45882
46505
  if (got.kind === "ok") {
@@ -45946,7 +46569,8 @@ function resolveMap(parsed, data) {
45946
46569
  const meta = sizeValue !== void 0 ? { value: sizeValue } : {};
45947
46570
  if (pos.kind === "coords") {
45948
46571
  const id = alias ? fold(alias) : `@${pos.lat},${pos.lon}`;
45949
- if (!looksUS(pos.lat, pos.lon)) anyNonUsPoi = true;
46572
+ if (looksUS(pos.lat, pos.lon)) anyUsPoi = true;
46573
+ else if (!looksNorthAmericaNeighbor(pos.lat, pos.lon)) anyNonNaPoi = true;
45950
46574
  if (!registry.has(id)) {
45951
46575
  registerPoi(
45952
46576
  id,
@@ -45969,7 +46593,7 @@ function resolveMap(parsed, data) {
45969
46593
  if (registry.has(f)) return f;
45970
46594
  const aliased = declaredByName.get(f);
45971
46595
  if (aliased) return aliased;
45972
- const got = lookupName(pos.name, pos.scope, line12, inferredCountry, true);
46596
+ const got = lookupName(pos.name, pos.scope, line12, inferredScope, true);
45973
46597
  if (got.kind !== "ok") return null;
45974
46598
  noteCountry(got.iso);
45975
46599
  registerPoi(
@@ -46026,9 +46650,12 @@ function resolveMap(parsed, data) {
46026
46650
  }
46027
46651
  routes.push({ stopIds, legs, lineNumber: rt.lineNumber });
46028
46652
  }
46653
+ const hasUsContent = usSubdivisionReferenced || anyUsPoi || localeCountry === "US";
46654
+ const usOriented = !anyNonNaPoi && !regions.some(
46655
+ (r) => r.layer === "country" && !["US", "CA", "MX"].includes(r.iso)
46656
+ ) && hasUsContent;
46029
46657
  const subdivisions = [];
46030
- if (usSubdivisionReferenced || parsed.directives.region === "us-states")
46031
- subdivisions.push("us-states");
46658
+ if (usSubdivisionReferenced || usOriented) subdivisions.push("us-states");
46032
46659
  const regionBoxes = [];
46033
46660
  for (const ref of referencedRegionIds) {
46034
46661
  const bb = featureBbox(data.usStates, ref.id);
@@ -46046,17 +46673,51 @@ function resolveMap(parsed, data) {
46046
46673
  [-180, -85],
46047
46674
  [180, 85]
46048
46675
  ];
46049
- let extent2 = unioned ? pad(unioned, PAD_FRACTION) : DEFAULT_EXTENT;
46676
+ const basePad = regions.length > 0 ? REGION_PAD_FRACTION : PAD_FRACTION;
46677
+ let extent2 = unioned ? pad(unioned, basePad) : DEFAULT_EXTENT;
46678
+ const isPoiOnly = pois.length > 0 && regions.length === 0;
46679
+ const containerRegionIds = [];
46680
+ if (isPoiOnly) {
46681
+ const countries = decodeFeatures(data.worldDetail);
46682
+ const states = decodeFeatures(data.usStates);
46683
+ const seen = /* @__PURE__ */ new Set();
46684
+ const containerBoxes = [];
46685
+ for (const p of pois) {
46686
+ const { country, state } = regionAt([p.lon, p.lat], countries, states);
46687
+ const id = state?.iso ?? country?.iso;
46688
+ if (!id || seen.has(id)) continue;
46689
+ seen.add(id);
46690
+ containerRegionIds.push(id);
46691
+ const bb = state ? featureBbox(data.usStates, id) : featureBboxPrimary(data.worldCoarse, id);
46692
+ if (bb && !isWholeSphere(bb)) containerBoxes.push(bb);
46693
+ }
46694
+ const containerUnion = unionExtent(containerBoxes, points);
46695
+ if (containerUnion) extent2 = pad(containerUnion, PAD_FRACTION);
46696
+ }
46697
+ if (isPoiOnly) {
46698
+ const cx = (extent2[0][0] + extent2[1][0]) / 2;
46699
+ const cy = (extent2[0][1] + extent2[1][1]) / 2;
46700
+ const lon = extent2[1][0] - extent2[0][0];
46701
+ const lat = extent2[1][1] - extent2[0][1];
46702
+ const longer = Math.max(lon, lat);
46703
+ if (longer > 0 && longer < POI_ZOOM_FLOOR_DEG) {
46704
+ const k = POI_ZOOM_FLOOR_DEG / longer;
46705
+ const halfLon = lon * k / 2;
46706
+ const halfLat = lat * k / 2;
46707
+ extent2 = [
46708
+ [cx - halfLon, cy - halfLat],
46709
+ [cx + halfLon, cy + halfLat]
46710
+ ];
46711
+ }
46712
+ }
46050
46713
  const lonSpan = extent2[1][0] - extent2[0][0];
46051
46714
  const latSpan = extent2[1][1] - extent2[0][1];
46052
46715
  const span = Math.max(lonSpan, latSpan);
46053
46716
  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
46717
  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) {
46718
+ if (isPoiOnly && usOriented && lonSpan < US_NATIONAL_LON_SPAN) {
46719
+ projection = "mercator";
46720
+ } else if (usOriented) {
46060
46721
  projection = "albers-usa";
46061
46722
  } else if (span > WORLD_SPAN || maxAbsLat > MERCATOR_MAX_LAT) {
46062
46723
  projection = "equirectangular";
@@ -46074,11 +46735,20 @@ function resolveMap(parsed, data) {
46074
46735
  result.edges = edges;
46075
46736
  result.routes = routes;
46076
46737
  result.basemaps = {
46077
- world: span > WORLD_SPAN ? "coarse" : "detail",
46738
+ // Tier is intentionally pinned to detail (50m) at ALL scales. Diagrammo maps
46739
+ // are presentational (palette tints, relief hachures, POI hubs), not
46740
+ // survey-grade — recognizability > generalization: 110m coarse drops the
46741
+ // Italian boot to a stump at world scale. `WORLD_SPAN` lives on only for the
46742
+ // projection decision (the `usOriented`/`span > WORLD_SPAN` chain above); it
46743
+ // no longer gates basemap resolution.
46744
+ // `worldCoarse` is still loaded — it's the authoritative name/bbox index
46745
+ // (featureIndex, featureBboxPrimary), not dead code.
46746
+ world: "detail",
46078
46747
  subdivisions
46079
46748
  };
46080
46749
  result.extent = extent2;
46081
46750
  result.projection = projection;
46751
+ result.poiFrameContainers = containerRegionIds;
46082
46752
  result.error = parsed.error ?? firstError(diagnostics);
46083
46753
  return result;
46084
46754
  }
@@ -46115,7 +46785,7 @@ function firstError(diags) {
46115
46785
  const e = diags.find((d) => d.severity === "error");
46116
46786
  return e ? formatDgmoError(e) : null;
46117
46787
  }
46118
- var WORLD_SPAN, MERCATOR_MAX_LAT, PAD_FRACTION, WORLD_LAT_SOUTH, WORLD_LAT_NORTH, REGION_ALIASES, US_STATE_POSTAL;
46788
+ 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
46789
  var init_resolver2 = __esm({
46120
46790
  "src/map/resolver.ts"() {
46121
46791
  "use strict";
@@ -46124,8 +46794,11 @@ var init_resolver2 = __esm({
46124
46794
  WORLD_SPAN = 90;
46125
46795
  MERCATOR_MAX_LAT = 80;
46126
46796
  PAD_FRACTION = 0.05;
46797
+ REGION_PAD_FRACTION = 0.12;
46127
46798
  WORLD_LAT_SOUTH = -58;
46128
46799
  WORLD_LAT_NORTH = 78;
46800
+ POI_ZOOM_FLOOR_DEG = 7;
46801
+ US_NATIONAL_LON_SPAN = 48;
46129
46802
  REGION_ALIASES = {
46130
46803
  // Common everyday names → the Natural-Earth display name actually shipped.
46131
46804
  "united states": "united states of america",
@@ -46203,17 +46876,305 @@ var init_resolver2 = __esm({
46203
46876
  }
46204
46877
  });
46205
46878
 
46879
+ // src/map/colorize.ts
46880
+ function assignColors(isos, adjacency) {
46881
+ const sorted = [...isos].sort();
46882
+ const byIso = /* @__PURE__ */ new Map();
46883
+ let maxIndex = -1;
46884
+ for (const iso of sorted) {
46885
+ const taken = /* @__PURE__ */ new Set();
46886
+ for (const n of adjacency.get(iso) ?? []) {
46887
+ const c = byIso.get(n);
46888
+ if (c !== void 0) taken.add(c);
46889
+ }
46890
+ let h = 0;
46891
+ while (taken.has(h)) h++;
46892
+ byIso.set(iso, h);
46893
+ if (h > maxIndex) maxIndex = h;
46894
+ }
46895
+ return { byIso, huesNeeded: maxIndex + 1 };
46896
+ }
46897
+ var init_colorize = __esm({
46898
+ "src/map/colorize.ts"() {
46899
+ "use strict";
46900
+ }
46901
+ });
46902
+
46903
+ // src/map/context-labels.ts
46904
+ function tierBand(maxSpanDeg) {
46905
+ if (maxSpanDeg >= 90) return "world";
46906
+ if (maxSpanDeg >= 20) return "continental";
46907
+ if (maxSpanDeg >= 5) return "regional";
46908
+ return "local";
46909
+ }
46910
+ function labelBudget(width, height, band) {
46911
+ const bandCap = {
46912
+ world: 6,
46913
+ continental: 5,
46914
+ regional: 4,
46915
+ local: 3
46916
+ };
46917
+ const area2 = Math.floor(Math.sqrt(Math.max(0, width * height)) / 150);
46918
+ return Math.max(0, Math.min(area2, bandCap[band]));
46919
+ }
46920
+ function waterEligible(tier, kind, band) {
46921
+ switch (band) {
46922
+ case "world":
46923
+ return tier <= 1 && (kind === "ocean" || kind === "sea");
46924
+ case "continental":
46925
+ return tier <= 2;
46926
+ case "regional":
46927
+ return tier <= 3;
46928
+ case "local":
46929
+ return tier <= 4;
46930
+ }
46931
+ }
46932
+ function insideViewport(p, width, height) {
46933
+ return !!p && Number.isFinite(p[0]) && Number.isFinite(p[1]) && p[0] >= 0 && p[0] <= width && p[1] >= 0 && p[1] <= height;
46934
+ }
46935
+ function labelWidth(text, letterSpacing) {
46936
+ const spacing = letterSpacing > 0 ? Math.max(0, text.length - 1) * letterSpacing : 0;
46937
+ return measureLegendText(text, FONT) + spacing + 2 * PADX;
46938
+ }
46939
+ function wrapLabel2(text, letterSpacing) {
46940
+ const words = text.split(/\s+/).filter(Boolean);
46941
+ if (words.length <= 1) return [text];
46942
+ const maxLines = words.length >= 4 ? 3 : 2;
46943
+ const n = words.length;
46944
+ let best = null;
46945
+ for (let mask = 0; mask < 1 << n - 1; mask++) {
46946
+ const lines = [];
46947
+ let cur = [words[0]];
46948
+ for (let i = 1; i < n; i++) {
46949
+ if (mask & 1 << i - 1) {
46950
+ lines.push(cur.join(" "));
46951
+ cur = [words[i]];
46952
+ } else cur.push(words[i]);
46953
+ }
46954
+ lines.push(cur.join(" "));
46955
+ if (lines.length > maxLines) continue;
46956
+ const cost = Math.round(
46957
+ Math.max(...lines.map((l) => labelWidth(l, letterSpacing)))
46958
+ );
46959
+ const head = labelWidth(lines[0], letterSpacing);
46960
+ if (!best || cost < best.cost || cost === best.cost && lines.length < best.lines.length || cost === best.cost && lines.length === best.lines.length && head > best.head)
46961
+ best = { lines, cost, head };
46962
+ }
46963
+ return best?.lines ?? [text];
46964
+ }
46965
+ function rectAround(cx, cy, lines, letterSpacing) {
46966
+ const w = Math.max(...lines.map((l) => labelWidth(l, letterSpacing)));
46967
+ const h = (lines.length - 1) * LINE_HEIGHT + FONT + 2 * PADY;
46968
+ return { x: cx - w / 2, y: cy - h / 2, w, h };
46969
+ }
46970
+ function rectFits(r, width, height) {
46971
+ return r.x >= 0 && r.y >= 0 && r.x + r.w <= width && r.y + r.h <= height;
46972
+ }
46973
+ function overlapsPadded(a, b, pad2) {
46974
+ 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;
46975
+ }
46976
+ function placeContextLabels(args) {
46977
+ const {
46978
+ projection,
46979
+ dLonSpan,
46980
+ dLatSpan,
46981
+ width,
46982
+ height,
46983
+ waterBodies,
46984
+ countries,
46985
+ palette,
46986
+ project,
46987
+ collides,
46988
+ overLand
46989
+ } = args;
46990
+ void projection;
46991
+ const band = tierBand(Math.max(dLonSpan, dLatSpan));
46992
+ const budget = labelBudget(width, height, band);
46993
+ if (budget <= 0) return [];
46994
+ const waterColor = mix(palette.colors.blue, palette.textMuted, 50);
46995
+ const countryColor = palette.textMuted;
46996
+ const haloColor = palette.bg;
46997
+ const candidates = [];
46998
+ const center = [width / 2, height / 2];
46999
+ for (const e of waterBodies?.entries ?? []) {
47000
+ const [lat, lon, name, tier, kind, alt] = e;
47001
+ if (!waterEligible(tier, kind, band)) continue;
47002
+ const wlines = wrapLabel2(name, WATER_LETTER_SPACING);
47003
+ const anchorsLngLat = [[lon, lat]];
47004
+ for (const a of alt ?? []) anchorsLngLat.push([a[1], a[0]]);
47005
+ let best = null;
47006
+ let bestD = Infinity;
47007
+ let nearestProj = null;
47008
+ let nearestProjD = Infinity;
47009
+ for (const [aLon, aLat] of anchorsLngLat) {
47010
+ const p = project(aLon, aLat);
47011
+ if (!p || !Number.isFinite(p[0]) || !Number.isFinite(p[1])) continue;
47012
+ const d = (p[0] - center[0]) ** 2 + (p[1] - center[1]) ** 2;
47013
+ if (d < nearestProjD) {
47014
+ nearestProjD = d;
47015
+ nearestProj = p;
47016
+ }
47017
+ if (!insideViewport(p, width, height)) continue;
47018
+ if (d < bestD) {
47019
+ bestD = d;
47020
+ best = p;
47021
+ }
47022
+ }
47023
+ if (!best && tier === 0 && nearestProj) {
47024
+ const overX = Math.max(0, -nearestProj[0], nearestProj[0] - width);
47025
+ const overY = Math.max(0, -nearestProj[1], nearestProj[1] - height);
47026
+ if (overX <= width * EDGE_CLAMP_OVERSHOOT && overY <= height * EDGE_CLAMP_OVERSHOOT) {
47027
+ const halfW = Math.max(...wlines.map((l) => labelWidth(l, WATER_LETTER_SPACING))) / 2;
47028
+ const halfH = ((wlines.length - 1) * LINE_HEIGHT + FONT + 2 * PADY) / 2;
47029
+ const m = EDGE_CLAMP_MARGIN;
47030
+ best = [
47031
+ Math.min(Math.max(nearestProj[0], halfW + m), width - halfW - m),
47032
+ Math.min(Math.max(nearestProj[1], halfH + m), height - halfH - m)
47033
+ ];
47034
+ }
47035
+ }
47036
+ if (!best) continue;
47037
+ candidates.push({
47038
+ text: name,
47039
+ lines: wlines,
47040
+ cx: best[0],
47041
+ cy: best[1],
47042
+ italic: true,
47043
+ letterSpacing: WATER_LETTER_SPACING,
47044
+ color: waterColor,
47045
+ // Water before any country (×1000), then by tier, then kind, then name.
47046
+ sort: tier * 10 + KIND_ORDER[kind]
47047
+ });
47048
+ }
47049
+ const ranked = countries.map((c) => {
47050
+ const [x0, y0, x1, y1] = c.bbox;
47051
+ const w = x1 - x0;
47052
+ const h = y1 - y0;
47053
+ return { c, w, h, area: w * h };
47054
+ }).filter((r) => Number.isFinite(r.area) && r.area > 0).sort((a, b) => b.area - a.area);
47055
+ let ci = 0;
47056
+ for (const r of ranked) {
47057
+ const { c, w, h } = r;
47058
+ if (w > width * 0.66 || h > height * 0.66) continue;
47059
+ if (!insideViewport(c.anchor, width, height)) continue;
47060
+ const text = c.name;
47061
+ const tw = labelWidth(text, 0);
47062
+ if (tw > w || FONT + 2 * PADY > h) continue;
47063
+ candidates.push({
47064
+ text,
47065
+ lines: [text],
47066
+ cx: c.anchor[0],
47067
+ cy: c.anchor[1],
47068
+ italic: false,
47069
+ letterSpacing: 0,
47070
+ color: countryColor,
47071
+ // Always after every water body (+1e6); larger area = earlier.
47072
+ sort: 1e6 + ci++
47073
+ });
47074
+ }
47075
+ candidates.sort((a, b) => a.sort - b.sort);
47076
+ const placed = [];
47077
+ const placedRects = [];
47078
+ for (const cand of candidates) {
47079
+ if (placed.length >= budget) break;
47080
+ const rect = rectAround(cand.cx, cand.cy, cand.lines, cand.letterSpacing);
47081
+ if (!rectFits(rect, width, height)) continue;
47082
+ if (cand.italic && overLand) {
47083
+ const inset = 2;
47084
+ const top = cand.cy - (cand.lines.length - 1) / 2 * LINE_HEIGHT;
47085
+ const touchesLand = cand.lines.some((line12, li) => {
47086
+ const lw = labelWidth(line12, cand.letterSpacing);
47087
+ const x0 = cand.cx - lw / 2 + inset;
47088
+ const x1 = cand.cx + lw / 2 - inset;
47089
+ const xs = [x0, (x0 + cand.cx) / 2, cand.cx, (cand.cx + x1) / 2, x1];
47090
+ const base = top + li * LINE_HEIGHT;
47091
+ return [base, base - FONT * 0.4, base - FONT * 0.8].some(
47092
+ (y) => xs.some((x) => overLand(x, y))
47093
+ );
47094
+ });
47095
+ if (touchesLand) continue;
47096
+ }
47097
+ if (collides(rect)) continue;
47098
+ if (placedRects.some((r) => overlapsPadded(rect, r, CONTEXT_PAD))) continue;
47099
+ placedRects.push(rect);
47100
+ placed.push({
47101
+ x: cand.cx,
47102
+ y: cand.cy,
47103
+ text: cand.text,
47104
+ anchor: "middle",
47105
+ color: cand.color,
47106
+ // No halo: the bg-coloured outline reads as a ghost box behind the text
47107
+ // over the tinted water/land. Context labels are muted enough to sit
47108
+ // cleanly on the basemap without one.
47109
+ halo: false,
47110
+ haloColor,
47111
+ italic: cand.italic,
47112
+ letterSpacing: cand.letterSpacing,
47113
+ ...cand.lines.length > 1 ? { lines: cand.lines } : {},
47114
+ lineNumber: 0
47115
+ });
47116
+ }
47117
+ return placed;
47118
+ }
47119
+ var FONT, LINE_HEIGHT, PADX, PADY, WATER_LETTER_SPACING, CONTEXT_PAD, EDGE_CLAMP_MARGIN, EDGE_CLAMP_OVERSHOOT, KIND_ORDER;
47120
+ var init_context_labels = __esm({
47121
+ "src/map/context-labels.ts"() {
47122
+ "use strict";
47123
+ init_color_utils();
47124
+ init_legend_constants();
47125
+ FONT = 11;
47126
+ LINE_HEIGHT = FONT + 2;
47127
+ PADX = 4;
47128
+ PADY = 3;
47129
+ WATER_LETTER_SPACING = 1.5;
47130
+ CONTEXT_PAD = 4;
47131
+ EDGE_CLAMP_MARGIN = 8;
47132
+ EDGE_CLAMP_OVERSHOOT = 0.35;
47133
+ KIND_ORDER = {
47134
+ ocean: 0,
47135
+ sea: 1,
47136
+ gulf: 2,
47137
+ bay: 3,
47138
+ strait: 4,
47139
+ channel: 5,
47140
+ sound: 6
47141
+ };
47142
+ }
47143
+ });
47144
+
46206
47145
  // src/map/layout.ts
46207
47146
  function geomObject2(topo) {
46208
47147
  const key = Object.keys(topo.objects)[0];
46209
47148
  return topo.objects[key];
46210
47149
  }
47150
+ function mergeFeatures(a, b) {
47151
+ const polysOf = (f) => {
47152
+ const g = f.geometry;
47153
+ if (!g) return null;
47154
+ if (g.type === "Polygon") return [g.coordinates];
47155
+ if (g.type === "MultiPolygon") return g.coordinates;
47156
+ return null;
47157
+ };
47158
+ const pa = polysOf(a);
47159
+ const pb = polysOf(b);
47160
+ if (!pa || !pb) return a;
47161
+ return {
47162
+ ...a,
47163
+ geometry: { type: "MultiPolygon", coordinates: [...pa, ...pb] }
47164
+ };
47165
+ }
46211
47166
  function decodeLayer(topo) {
47167
+ const cached = decodeCache.get(topo);
47168
+ if (cached) return cached;
46212
47169
  const out = /* @__PURE__ */ new Map();
46213
47170
  for (const g of geomObject2(topo).geometries) {
46214
47171
  const f = (0, import_topojson_client2.feature)(topo, g);
46215
- out.set(g.id, { ...f, id: g.id });
47172
+ if (!f.geometry) continue;
47173
+ const tagged = { ...f, id: g.id };
47174
+ const existing = out.get(g.id);
47175
+ out.set(g.id, existing ? mergeFeatures(existing, tagged) : tagged);
46216
47176
  }
47177
+ decodeCache.set(topo, out);
46217
47178
  return out;
46218
47179
  }
46219
47180
  function projectionFor(family) {
@@ -46222,9 +47183,12 @@ function projectionFor(family) {
46222
47183
  return usConusProjection();
46223
47184
  case "mercator":
46224
47185
  return (0, import_d3_geo2.geoMercator)();
47186
+ case "equal-earth":
47187
+ return (0, import_d3_geo2.geoEqualEarth)();
47188
+ case "equirectangular":
47189
+ return (0, import_d3_geo2.geoEquirectangular)();
46225
47190
  case "natural-earth":
46226
47191
  return (0, import_d3_geo2.geoNaturalEarth1)();
46227
- case "equirectangular":
46228
47192
  default:
46229
47193
  return (0, import_d3_geo2.geoEquirectangular)();
46230
47194
  }
@@ -46243,13 +47207,11 @@ function mapNeutralLandColor(palette, isDark, _dataActive = false) {
46243
47207
  isDark ? LAND_TINT_DARK : LAND_TINT_LIGHT
46244
47208
  );
46245
47209
  }
46246
- function layoutMap(resolved, data, size, opts) {
46247
- const { palette, isDark } = opts;
46248
- const { width, height } = size;
47210
+ function buildMapProjection(resolved, data) {
46249
47211
  const wantsUsStates = resolved.basemaps.subdivisions.includes("us-states");
46250
- const usCrisp = resolved.projection === "albers-usa" && wantsUsStates && !!data.naLand;
47212
+ const usCrisp = (resolved.projection === "albers-usa" || resolved.projection === "mercator") && wantsUsStates && !!data.naLand;
46251
47213
  const worldTopo = usCrisp ? data.worldDetail : resolved.basemaps.world === "detail" ? data.worldDetail : data.worldCoarse;
46252
- const worldLayer = decodeLayer(worldTopo);
47214
+ const worldLayer = new Map(decodeLayer(worldTopo));
46253
47215
  if (usCrisp && data.naLand) {
46254
47216
  const [nbW, nbS, nbE, nbN] = [-140, 10, -52, 66];
46255
47217
  const crisp = decodeLayer(data.naLand);
@@ -46258,16 +47220,141 @@ function layoutMap(resolved, data, size, opts) {
46258
47220
  if (!base) continue;
46259
47221
  const [[bw, bs], [be, bn]] = (0, import_d3_geo2.geoBounds)(base);
46260
47222
  if (bw >= nbW && be <= nbE && bs >= nbS && bn <= nbN)
46261
- worldLayer.set(iso, cf);
47223
+ worldLayer.set(iso, { ...cf, properties: base.properties });
46262
47224
  }
46263
47225
  }
46264
47226
  const usLayer = wantsUsStates ? decodeLayer(data.usStates) : null;
47227
+ const extentOutline = () => {
47228
+ const [[w, s], [e, n]] = resolved.extent;
47229
+ const N = 16;
47230
+ const coords = [];
47231
+ for (let i = 0; i <= N; i++) {
47232
+ const t = i / N;
47233
+ const lon = w + (e - w) * t;
47234
+ const lat = s + (n - s) * t;
47235
+ coords.push([lon, s], [lon, n], [w, lat], [e, lat]);
47236
+ }
47237
+ return {
47238
+ type: "Feature",
47239
+ properties: {},
47240
+ geometry: { type: "MultiPoint", coordinates: coords }
47241
+ };
47242
+ };
47243
+ let fitFeatures;
47244
+ if (resolved.projection === "albers-usa" && usLayer) {
47245
+ fitFeatures = [...usLayer.entries()].filter(([iso]) => !US_NON_CONUS.has(iso)).map(([, f]) => f);
47246
+ const neighborPoints = resolved.pois.filter((p) => !inAlaska(p.lon, p.lat) && !inHawaii(p.lon, p.lat)).map((p) => [p.lon, p.lat]);
47247
+ if (neighborPoints.length > 0) {
47248
+ fitFeatures.push({
47249
+ type: "Feature",
47250
+ properties: {},
47251
+ geometry: { type: "MultiPoint", coordinates: neighborPoints }
47252
+ });
47253
+ }
47254
+ for (const r of resolved.regions) {
47255
+ if (r.layer === "country" && (r.iso === "CA" || r.iso === "MX")) {
47256
+ const cf = worldLayer.get(r.iso);
47257
+ if (cf) fitFeatures.push(cf);
47258
+ }
47259
+ }
47260
+ } else {
47261
+ fitFeatures = [extentOutline()];
47262
+ }
47263
+ const fitTarget = { type: "FeatureCollection", features: fitFeatures };
47264
+ const projection = projectionFor(resolved.projection);
47265
+ if (resolved.projection !== "albers-usa") {
47266
+ let centerLon = (resolved.extent[0][0] + resolved.extent[1][0]) / 2;
47267
+ if (centerLon > 180) centerLon -= 360;
47268
+ projection.rotate([-centerLon, 0]);
47269
+ }
47270
+ const fitGB = (0, import_d3_geo2.geoBounds)(fitTarget);
47271
+ const fitIsGlobal = fitGB[1][0] - fitGB[0][0] >= 270 || fitGB[1][1] - fitGB[0][1] >= 130;
47272
+ return {
47273
+ projection,
47274
+ fitTarget,
47275
+ fitIsGlobal,
47276
+ worldLayer,
47277
+ usLayer,
47278
+ usCrisp,
47279
+ wantsUsStates,
47280
+ worldTopo
47281
+ };
47282
+ }
47283
+ function parsePathRings(d) {
47284
+ const rings = [];
47285
+ let cur = [];
47286
+ const re = /([MLZ])([^MLZ]*)/g;
47287
+ let m;
47288
+ while (m = re.exec(d)) {
47289
+ if (m[1] === "Z") {
47290
+ if (cur.length) rings.push(cur);
47291
+ cur = [];
47292
+ continue;
47293
+ }
47294
+ if (m[1] === "M" && cur.length) {
47295
+ rings.push(cur);
47296
+ cur = [];
47297
+ }
47298
+ const nums = m[2].split(/[ ,]+/).map(Number);
47299
+ for (let i = 0; i + 1 < nums.length; i += 2) {
47300
+ const x = nums[i];
47301
+ const y = nums[i + 1];
47302
+ if (Number.isFinite(x) && Number.isFinite(y)) cur.push([x, y]);
47303
+ }
47304
+ }
47305
+ if (cur.length) rings.push(cur);
47306
+ return rings;
47307
+ }
47308
+ function dropAntimeridianWrapSlivers(d, width, height) {
47309
+ const rings = parsePathRings(d);
47310
+ if (rings.length <= 1) return d;
47311
+ const eps = 0.75;
47312
+ const minArea = 3e-3 * width * height;
47313
+ const ringArea = (r) => {
47314
+ let s = 0;
47315
+ for (let i = 0; i < r.length; i++) {
47316
+ const a = r[i];
47317
+ const b = r[(i + 1) % r.length];
47318
+ s += a[0] * b[1] - b[0] * a[1];
47319
+ }
47320
+ return Math.abs(s) / 2;
47321
+ };
47322
+ const areas = rings.map(ringArea);
47323
+ const maxArea = Math.max(...areas);
47324
+ const onVEdge = (a, b) => Math.abs(a[0]) <= eps && Math.abs(b[0]) <= eps || Math.abs(a[0] - width) <= eps && Math.abs(b[0] - width) <= eps;
47325
+ let dropped = false;
47326
+ const kept = rings.filter((r, idx) => {
47327
+ if (areas[idx] >= maxArea || areas[idx] >= minArea) return true;
47328
+ const touches = r.some((p, i) => onVEdge(p, r[(i + 1) % r.length]));
47329
+ if (touches) {
47330
+ dropped = true;
47331
+ return false;
47332
+ }
47333
+ return true;
47334
+ });
47335
+ if (!dropped) return d;
47336
+ return kept.map(
47337
+ (r) => r.map((p, i) => (i ? "L" : "M") + p[0] + "," + p[1]).join("") + "Z"
47338
+ ).join("");
47339
+ }
47340
+ function layoutMap(resolved, data, size, opts) {
47341
+ const { palette, isDark } = opts;
47342
+ const { width, height } = size;
47343
+ const {
47344
+ projection,
47345
+ fitTarget,
47346
+ fitIsGlobal,
47347
+ worldLayer,
47348
+ usLayer,
47349
+ usCrisp,
47350
+ worldTopo
47351
+ } = buildMapProjection(resolved, data);
46265
47352
  const usContext = usLayer !== null;
46266
47353
  const regionStroke = isDark ? mix(palette.bg, palette.text, 78) : mix(palette.text, palette.bg, 78);
46267
47354
  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);
47355
+ const allNonNegative = values.length > 0 && values.every((v) => v >= 0);
47356
+ const rampMin = allNonNegative ? 0 : Math.min(...values);
47357
+ const rampMax = Math.max(...values);
46271
47358
  const rampHue = resolveColor(resolved.directives.regionMetricColor ?? "", palette) ?? palette.colors.red;
46272
47359
  const hasRamp = values.length > 0;
46273
47360
  const VALUE_NAME = hasRamp ? resolved.directives.regionMetric?.trim() || "Value" : null;
@@ -46288,7 +47375,7 @@ function layoutMap(resolved, data, size, opts) {
46288
47375
  activeGroup = VALUE_NAME ?? (resolved.tagGroups.length > 0 ? resolved.tagGroups[0].name : null);
46289
47376
  }
46290
47377
  const activeIsScore = VALUE_NAME !== null && activeGroup === VALUE_NAME;
46291
- const mutedBasemap = resolved.directives.basemapStyle === "muted" ? true : resolved.directives.basemapStyle === "natural" ? false : activeGroup !== null;
47378
+ const mutedBasemap = activeGroup !== null;
46292
47379
  const neutralFill = mapNeutralLandColor(palette, isDark, mutedBasemap);
46293
47380
  const water = mapBackgroundColor(palette, isDark, mutedBasemap);
46294
47381
  const lakeStroke = mix(regionStroke, water, 45);
@@ -46297,10 +47384,43 @@ function layoutMap(resolved, data, size, opts) {
46297
47384
  palette.bg,
46298
47385
  mutedBasemap ? isDark ? MUTED_FOREIGN_DARK : MUTED_FOREIGN_LIGHT : isDark ? FOREIGN_TINT_DARK : FOREIGN_TINT_LIGHT
46299
47386
  );
47387
+ const colorizeActive = resolved.directives.noColorize !== true && !hasRamp && resolved.tagGroups.length === 0;
47388
+ const colorByIso = /* @__PURE__ */ new Map();
47389
+ if (colorizeActive) {
47390
+ const adjacency = /* @__PURE__ */ new Map();
47391
+ const addEdges = (src) => {
47392
+ for (const [iso, ns] of src) {
47393
+ const cur = adjacency.get(iso);
47394
+ if (cur) cur.push(...ns);
47395
+ else adjacency.set(iso, [...ns]);
47396
+ }
47397
+ };
47398
+ addEdges(buildAdjacency(worldTopo));
47399
+ if (usLayer) {
47400
+ addEdges(buildAdjacency(data.usStates));
47401
+ for (const [country, states] of Object.entries(FOREIGN_BORDER)) {
47402
+ const cn = adjacency.get(country);
47403
+ if (!cn) continue;
47404
+ for (const st of states) {
47405
+ const sn = adjacency.get(st);
47406
+ if (!sn) continue;
47407
+ cn.push(st);
47408
+ sn.push(country);
47409
+ }
47410
+ }
47411
+ }
47412
+ const { byIso, huesNeeded } = assignColors(
47413
+ [...adjacency.keys()],
47414
+ adjacency
47415
+ );
47416
+ const tints = politicalTints(palette, huesNeeded, isDark);
47417
+ for (const [iso, idx] of byIso) colorByIso.set(iso, tints[idx]);
47418
+ }
47419
+ const colorizeStroke = (fill2) => mix(fill2, palette.text, 35);
46300
47420
  const rampBase = isDark ? mix(palette.surface, palette.text, 28) : palette.bg;
46301
47421
  const fillForValue = (s) => {
46302
47422
  const t = rampMax > rampMin ? (s - rampMin) / (rampMax - rampMin) : 1;
46303
- const pct = RAMP_FLOOR + Math.max(0, Math.min(1, t)) * (100 - RAMP_FLOOR);
47423
+ const pct = RAMP_FLOOR2 + Math.max(0, Math.min(1, t)) * (100 - RAMP_FLOOR2);
46304
47424
  return mix(rampHue, rampBase, pct);
46305
47425
  };
46306
47426
  const tagFill = (tags, groupName) => {
@@ -46332,43 +47452,15 @@ function layoutMap(resolved, data, size, opts) {
46332
47452
  if (activeIsScore) {
46333
47453
  return r.value !== void 0 ? fillForValue(r.value) : neutralFill;
46334
47454
  }
47455
+ if (colorizeActive) return (r.iso && colorByIso.get(r.iso)) ?? neutralFill;
46335
47456
  return tagFill(r.tags, activeGroup) ?? neutralFill;
46336
47457
  };
46337
47458
  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;
47459
+ const TITLE_GAP2 = 16;
46368
47460
  let topPad = FIT_PAD;
46369
47461
  if (resolved.title && resolved.pois.length > 0) {
46370
47462
  const bannerBottom = (resolved.subtitle ? TITLE_Y + TITLE_FONT_SIZE : TITLE_Y) + TITLE_FONT_SIZE / 2;
46371
- topPad = Math.max(FIT_PAD, bannerBottom + TITLE_GAP);
47463
+ topPad = Math.max(FIT_PAD, bannerBottom + TITLE_GAP2);
46372
47464
  }
46373
47465
  const fitBox = [
46374
47466
  [FIT_PAD, topPad],
@@ -46378,21 +47470,20 @@ function layoutMap(resolved, data, size, opts) {
46378
47470
  ]
46379
47471
  ];
46380
47472
  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
47473
  let path;
46384
47474
  let project;
46385
47475
  let stretchParams = null;
46386
- if (fitIsGlobal) {
47476
+ if (fitIsGlobal && !opts.preferContain) {
46387
47477
  const cb = (0, import_d3_geo2.geoPath)(projection).bounds(fitTarget);
46388
47478
  const bx0 = cb[0][0];
46389
47479
  const by0 = cb[0][1];
46390
47480
  const cw = cb[1][0] - bx0;
46391
47481
  const ch = cb[1][1] - by0;
46392
- const ox = fitBox[0][0];
46393
- const oy = fitBox[0][1];
46394
- const sx = cw > 0 ? (fitBox[1][0] - ox) / cw : 1;
46395
- const sy = ch > 0 ? (fitBox[1][1] - oy) / ch : 1;
47482
+ const topReserve = resolved.title && resolved.pois.length > 0 ? topPad : 0;
47483
+ const ox = 0;
47484
+ const oy = topReserve;
47485
+ const sx = cw > 0 ? width / cw : 1;
47486
+ const sy = ch > 0 ? (height - topReserve) / ch : 1;
46396
47487
  stretchParams = { sx, sy, ox, oy, bx0, by0 };
46397
47488
  const stretch = (x, y) => [
46398
47489
  ox + (x - bx0) * sx,
@@ -46425,7 +47516,9 @@ function layoutMap(resolved, data, size, opts) {
46425
47516
  const insets = [];
46426
47517
  const insetRegions = [];
46427
47518
  const insetLabelSeeds = [];
46428
- if (resolved.projection === "albers-usa" && usLayer && !resolved.directives.noInsets) {
47519
+ const akRef = resolved.regions.some((r) => r.iso === "US-AK") || resolved.pois.some((p) => inAlaska(p.lon, p.lat));
47520
+ const hiRef = resolved.regions.some((r) => r.iso === "US-HI") || resolved.pois.some((p) => inHawaii(p.lon, p.lat));
47521
+ if (resolved.projection === "albers-usa" && usLayer && (akRef || hiRef)) {
46429
47522
  const PAD = 8;
46430
47523
  const GAP = 12;
46431
47524
  const yB = height - FIT_PAD;
@@ -46490,8 +47583,18 @@ function layoutMap(resolved, data, size, opts) {
46490
47583
  );
46491
47584
  const d = (0, import_d3_geo2.geoPath)(proj)(f) ?? "";
46492
47585
  if (!d) return xr;
47586
+ let contextLand;
47587
+ if (iso === "US-AK") {
47588
+ const can = worldLayer.get("CA");
47589
+ const cd = can ? (0, import_d3_geo2.geoPath)(proj)(can) ?? "" : "";
47590
+ if (cd)
47591
+ contextLand = {
47592
+ d: cd,
47593
+ fill: colorizeActive ? colorByIso.get("CA") ?? foreignFill : foreignFill
47594
+ };
47595
+ }
46493
47596
  const r = regionById.get(iso);
46494
- let fill2 = neutralFill;
47597
+ let fill2 = colorizeActive ? colorByIso.get(iso) ?? neutralFill : neutralFill;
46495
47598
  let lineNumber = -1;
46496
47599
  if (r?.layer === "us-state") {
46497
47600
  fill2 = regionFill(r);
@@ -46510,13 +47613,14 @@ function layoutMap(resolved, data, size, opts) {
46510
47613
  ],
46511
47614
  // The FITTED inset projection (just fit to this box) — captured so the
46512
47615
  // geo-query can invert pixels inside the frame back to AK/HI coords.
46513
- projection: proj
47616
+ projection: proj,
47617
+ ...contextLand && { contextLand }
46514
47618
  });
46515
47619
  insetRegions.push({
46516
47620
  id: iso,
46517
47621
  d,
46518
47622
  fill: fill2,
46519
- stroke: regionStroke,
47623
+ stroke: colorizeActive ? colorizeStroke(fill2) : regionStroke,
46520
47624
  lineNumber,
46521
47625
  layer: "us-state",
46522
47626
  ...r?.value !== void 0 && { value: r.value },
@@ -46529,13 +47633,16 @@ function layoutMap(resolved, data, size, opts) {
46529
47633
  }
46530
47634
  return xr;
46531
47635
  };
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);
47636
+ let akRight = FIT_PAD;
47637
+ if (akRef)
47638
+ akRight = placeInset("US-AK", alaskaProjection(), FIT_PAD, width * 0.15);
47639
+ if (hiRef)
47640
+ placeInset(
47641
+ "US-HI",
47642
+ hawaiiProjection(),
47643
+ akRef ? akRight + 24 : FIT_PAD,
47644
+ width * 0.1
47645
+ );
46539
47646
  }
46540
47647
  const conusFit = resolved.projection === "albers-usa" && !!usLayer;
46541
47648
  const classifyExtent = conusFit ? (0, import_d3_geo2.geoBounds)(fitTarget) : resolved.extent;
@@ -46551,15 +47658,24 @@ function layoutMap(resolved, data, size, opts) {
46551
47658
  };
46552
47659
  const ringOverlapsView = (ring) => {
46553
47660
  let loMin = Infinity, loMax = -Infinity, rawMin = Infinity, rawMax = -Infinity;
47661
+ const lons = [];
46554
47662
  for (const [rawLon] of ring) {
46555
47663
  const lon = normLon(rawLon);
47664
+ lons.push(lon);
46556
47665
  if (lon < loMin) loMin = lon;
46557
47666
  if (lon > loMax) loMax = lon;
46558
47667
  if (rawLon < rawMin) rawMin = rawLon;
46559
47668
  if (rawLon > rawMax) rawMax = rawLon;
46560
47669
  }
46561
- if (loMax - loMin > 270) return false;
46562
- if (rawMax - rawMin > 180 && loMax - loMin < 90) return false;
47670
+ lons.sort((a, b) => a - b);
47671
+ let maxGap = 0;
47672
+ for (let i = 1; i < lons.length; i++)
47673
+ maxGap = Math.max(maxGap, lons[i] - lons[i - 1]);
47674
+ if (lons.length > 1)
47675
+ maxGap = Math.max(maxGap, lons[0] + 360 - lons[lons.length - 1]);
47676
+ const occupiedArc = 360 - maxGap;
47677
+ if (occupiedArc > 270) return false;
47678
+ if (rawMax - rawMin > 180 && occupiedArc < 90) return false;
46563
47679
  let px0 = Infinity, py0 = Infinity, px1 = -Infinity, py1 = -Infinity, anyFinite = false;
46564
47680
  for (const [lon, lat] of ring) {
46565
47681
  const p = project(lon, lat);
@@ -46632,7 +47748,7 @@ function layoutMap(resolved, data, size, opts) {
46632
47748
  const regions = [];
46633
47749
  const pushRegionLayer = (layerFeatures, layerKind, shouldCull) => {
46634
47750
  for (const [iso, f] of layerFeatures) {
46635
- if (layerKind === "us-state" && usContext && INSET_STATES.has(iso))
47751
+ if (layerKind === "us-state" && usContext && resolved.projection === "albers-usa" && INSET_STATES.has(iso))
46636
47752
  continue;
46637
47753
  if (layerKind === "country" && usContext && iso === "US") continue;
46638
47754
  if (layerKind === "country" && iso === "AQ" && !regionById.has("AQ"))
@@ -46640,11 +47756,13 @@ function layoutMap(resolved, data, size, opts) {
46640
47756
  const r = regionById.get(iso);
46641
47757
  const viewF = shouldCull ? cullFeatureToView(f) : dropFrameFillers(f);
46642
47758
  if (!viewF) continue;
46643
- const d = path(viewF) ?? "";
47759
+ const raw = path(viewF) ?? "";
47760
+ const d = fitIsGlobal ? dropAntimeridianWrapSlivers(raw, width, height) : raw;
46644
47761
  if (!d) continue;
46645
47762
  const isThisLayer = r?.layer === layerKind;
46646
47763
  const isForeign = layerKind === "country" && usContext && iso !== "US";
46647
- let fill2 = isForeign ? foreignFill : neutralFill;
47764
+ const baseFill = isForeign ? foreignFill : neutralFill;
47765
+ let fill2 = colorizeActive ? colorByIso.get(iso) ?? baseFill : baseFill;
46648
47766
  let label;
46649
47767
  let lineNumber = -1;
46650
47768
  let layer = "base";
@@ -46653,15 +47771,21 @@ function layoutMap(resolved, data, size, opts) {
46653
47771
  lineNumber = r.lineNumber;
46654
47772
  layer = layerKind;
46655
47773
  label = r.name;
47774
+ } else {
47775
+ label = f.properties?.name;
46656
47776
  }
47777
+ const labelAnchor = WORLD_LABEL_ANCHORS[iso];
47778
+ const c = labelAnchor ? project(labelAnchor[0], labelAnchor[1]) : path.centroid(viewF);
47779
+ const hasCentroid = c != null && Number.isFinite(c[0]) && Number.isFinite(c[1]);
46657
47780
  regions.push({
46658
47781
  id: iso,
46659
47782
  d,
46660
47783
  fill: fill2,
46661
- stroke: regionStroke,
47784
+ stroke: colorizeActive ? colorizeStroke(fill2) : regionStroke,
46662
47785
  lineNumber,
46663
47786
  layer,
46664
47787
  ...label !== void 0 && { label },
47788
+ ...hasCentroid && { labelX: c[0], labelY: c[1] },
46665
47789
  ...isThisLayer && r.value !== void 0 && { value: r.value },
46666
47790
  ...isThisLayer && Object.keys(r.tags).length > 0 && { tags: r.tags }
46667
47791
  });
@@ -46686,9 +47810,41 @@ function layoutMap(resolved, data, size, opts) {
46686
47810
  });
46687
47811
  }
46688
47812
  }
47813
+ const pointInRings = (px, py, rings) => {
47814
+ let inside = false;
47815
+ for (const ring of rings) {
47816
+ for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
47817
+ const [xi, yi] = ring[i];
47818
+ const [xj, yj] = ring[j];
47819
+ if (yi > py !== yj > py && px < (xj - xi) * (py - yi) / (yj - yi) + xi)
47820
+ inside = !inside;
47821
+ }
47822
+ }
47823
+ return inside;
47824
+ };
47825
+ const fillHitTargets = [...regions, ...insetRegions].map((r) => ({
47826
+ fill: r.fill,
47827
+ rings: parsePathRings(r.d)
47828
+ }));
47829
+ const fillAt = (x, y) => {
47830
+ let hit = water;
47831
+ for (const t of fillHitTargets)
47832
+ if (pointInRings(x, y, t.rings)) hit = t.fill;
47833
+ return hit;
47834
+ };
47835
+ const labelOnFill = (fill2) => {
47836
+ const color = contrastRatio(fill2, palette.textOnFillDark) >= contrastRatio(fill2, palette.textOnFillLight) ? palette.textOnFillDark : palette.textOnFillLight;
47837
+ const haloColor = color === palette.textOnFillLight ? palette.textOnFillDark : palette.textOnFillLight;
47838
+ return {
47839
+ color,
47840
+ halo: contrastRatio(fill2, color) < REGION_LABEL_HALO_RATIO,
47841
+ haloColor
47842
+ };
47843
+ };
47844
+ const reliefAllowed = resolved.directives.noRelief !== true;
46689
47845
  const relief = [];
46690
47846
  let reliefHatch = null;
46691
- if (resolved.directives.relief === true && data.mountainRanges) {
47847
+ if (reliefAllowed && data.mountainRanges) {
46692
47848
  for (const [, f] of decodeLayer(data.mountainRanges)) {
46693
47849
  const viewF = isGlobalView ? dropFrameFillers(f) : cullFeatureToView(f);
46694
47850
  if (!viewF) continue;
@@ -46704,16 +47860,32 @@ function layoutMap(resolved, data, size, opts) {
46704
47860
  if (relief.length) {
46705
47861
  const darkTone = isDark ? palette.bg : palette.text;
46706
47862
  const lightTone = isDark ? palette.text : palette.bg;
46707
- const landLum = relativeLuminance(neutralFill);
47863
+ const reliefLandRef = colorizeActive ? isDark ? palette.surface : palette.bg : neutralFill;
47864
+ const landLum = relativeLuminance(reliefLandRef);
46708
47865
  const tone = Math.abs(landLum - relativeLuminance(darkTone)) > 0.04 ? darkTone : lightTone;
46709
47866
  reliefHatch = {
46710
- color: mix(tone, neutralFill, RELIEF_HATCH_STRENGTH),
47867
+ color: mix(tone, reliefLandRef, RELIEF_HATCH_STRENGTH),
46711
47868
  spacing: RELIEF_HATCH_SPACING,
46712
47869
  width: RELIEF_HATCH_WIDTH
46713
47870
  };
46714
47871
  }
46715
47872
  }
46716
- const riverColor = mix(water, regionStroke, 16);
47873
+ let coastlineStyle = null;
47874
+ if (resolved.directives.noCoastline !== true) {
47875
+ const minDim = Math.min(width, height);
47876
+ coastlineStyle = {
47877
+ color: mix(regionStroke, water, COASTLINE_STROKE_MIX),
47878
+ // N equal-width rings: distance steps outward by COASTLINE_STEP; opacity
47879
+ // fades linearly from NEAR (innermost) to FAR (outermost).
47880
+ lines: Array.from({ length: COASTLINE_RING_COUNT }, (_, k) => ({
47881
+ d: (COASTLINE_D0 + k * COASTLINE_STEP) * minDim,
47882
+ thickness: COASTLINE_THICKNESS * minDim,
47883
+ opacity: COASTLINE_OPACITY_NEAR + (COASTLINE_OPACITY_FAR - COASTLINE_OPACITY_NEAR) * k / (COASTLINE_RING_COUNT - 1)
47884
+ })),
47885
+ minExtent: (isGlobalView ? COASTLINE_MIN_EXTENT_GLOBAL : COASTLINE_MIN_EXTENT) * minDim
47886
+ };
47887
+ }
47888
+ const riverColor = mix(palette.colors.blue, water, 32);
46717
47889
  const rivers = [];
46718
47890
  if (data.rivers) {
46719
47891
  for (const [, f] of decodeLayer(data.rivers)) {
@@ -46769,38 +47941,108 @@ function layoutMap(resolved, data, size, opts) {
46769
47941
  const xy = project(p.lon, p.lat);
46770
47942
  if (xy) projected.push({ p, xy });
46771
47943
  }
46772
- const coloGroups = /* @__PURE__ */ new Map();
47944
+ const placePoi = (e, cx, cy, clusterId) => {
47945
+ const { fill: fill2, stroke: stroke2 } = poiFill(e.p);
47946
+ poiScreen.set(e.p.id, { cx, cy, r: radiusFor(e.p) });
47947
+ const num = routeNumberById.get(e.p.id);
47948
+ pois.push({
47949
+ id: e.p.id,
47950
+ cx,
47951
+ cy,
47952
+ r: radiusFor(e.p),
47953
+ fill: fill2,
47954
+ stroke: stroke2,
47955
+ lineNumber: e.p.lineNumber,
47956
+ implicit: !!e.p.implicit,
47957
+ isOrigin: originIds.has(e.p.id),
47958
+ ...num !== void 0 && { routeNumber: num },
47959
+ ...Object.keys(e.p.tags).length > 0 && { tags: e.p.tags },
47960
+ ...clusterId !== void 0 && { clusterId }
47961
+ });
47962
+ };
47963
+ const clusters = [];
47964
+ const connected = /* @__PURE__ */ new Set();
47965
+ for (const e of resolved.edges) {
47966
+ connected.add(e.fromId);
47967
+ connected.add(e.toId);
47968
+ }
47969
+ for (const rt of resolved.routes) {
47970
+ rt.stopIds.forEach((id) => connected.add(id));
47971
+ }
47972
+ const radiusOf = (e) => radiusFor(e.p);
46773
47973
  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
- });
47974
+ if (connected.has(e.p.id)) placePoi(e, e.xy[0], e.xy[1]);
47975
+ }
47976
+ const groups = [];
47977
+ for (const e of projected) {
47978
+ if (connected.has(e.p.id)) continue;
47979
+ const r = radiusOf(e);
47980
+ const near = groups.find(
47981
+ (g) => g.some(
47982
+ (q) => Math.hypot(q.xy[0] - e.xy[0], q.xy[1] - e.xy[1]) < (r + radiusOf(q)) * STACK_OVERLAP
47983
+ )
47984
+ );
47985
+ if (near) near.push(e);
47986
+ else groups.push([e]);
47987
+ }
47988
+ for (const g of groups) {
47989
+ if (g.length === 1) {
47990
+ placePoi(g[0], g[0].xy[0], g[0].xy[1]);
47991
+ continue;
47992
+ }
47993
+ const clusterId = g[0].p.id;
47994
+ const cx0 = g.reduce((s, e) => s + e.xy[0], 0) / g.length;
47995
+ const cy0 = g.reduce((s, e) => s + e.xy[1], 0) / g.length;
47996
+ const maxR = Math.max(...g.map(radiusOf));
47997
+ const sep = 2 * maxR + STACK_RING_GAP;
47998
+ const ringR = Math.max(
47999
+ COLO_R,
48000
+ sep / (2 * Math.sin(Math.PI / Math.max(g.length, 2)))
48001
+ );
48002
+ const positions = g.map((e, i) => {
48003
+ if (g.length <= STACK_RING_MAX) {
48004
+ const ang2 = -Math.PI / 2 + i * 2 * Math.PI / g.length;
48005
+ return {
48006
+ e,
48007
+ mx: cx0 + Math.cos(ang2) * ringR,
48008
+ my: cy0 + Math.sin(ang2) * ringR
48009
+ };
48010
+ }
48011
+ const ang = i * GOLDEN_ANGLE;
48012
+ const rr = ringR * Math.sqrt((i + 1) / g.length);
48013
+ return { e, mx: cx0 + Math.cos(ang) * rr, my: cy0 + Math.sin(ang) * rr };
48014
+ });
48015
+ let minX = cx0 - maxR;
48016
+ let maxX = cx0 + maxR;
48017
+ let minY = cy0 - maxR;
48018
+ let maxY = cy0 + maxR;
48019
+ for (const { mx, my, e } of positions) {
48020
+ const r = radiusOf(e);
48021
+ minX = Math.min(minX, mx - r);
48022
+ maxX = Math.max(maxX, mx + r);
48023
+ minY = Math.min(minY, my - r);
48024
+ maxY = Math.max(maxY, my + r);
48025
+ }
48026
+ let dx = 0;
48027
+ let dy = 0;
48028
+ if (minX + dx < 2) dx = 2 - minX;
48029
+ if (maxX + dx > width - 2) dx = width - 2 - maxX;
48030
+ if (minY + dy < 2) dy = 2 - minY;
48031
+ if (maxY + dy > height - 2) dy = height - 2 - maxY;
48032
+ const legsOut = [];
48033
+ for (const { e, mx, my } of positions) {
48034
+ const fx = mx + dx;
48035
+ const fy = my + dy;
48036
+ placePoi(e, fx, fy, clusterId);
48037
+ legsOut.push({ x2: fx, y2: fy, color: poiFill(e.p).fill });
48038
+ }
48039
+ clusters.push({
48040
+ id: clusterId,
48041
+ cx: cx0 + dx,
48042
+ cy: cy0 + dy,
48043
+ count: g.length,
48044
+ hitR: ringR + maxR + 6,
48045
+ legs: legsOut
46804
48046
  });
46805
48047
  }
46806
48048
  const legs = [];
@@ -46850,16 +48092,26 @@ function layoutMap(resolved, data, size, opts) {
46850
48092
  if (!a || !b) continue;
46851
48093
  const mx = (a.cx + b.cx) / 2;
46852
48094
  const my = (a.cy + b.cy) / 2;
48095
+ const bow = {
48096
+ curved: leg.style === "arc",
48097
+ offset: 0,
48098
+ labelX: mx,
48099
+ labelY: my - 4
48100
+ };
48101
+ const routeLabelStyle = leg.label !== void 0 ? labelOnFill(fillAt(bow.labelX, bow.labelY)) : void 0;
46853
48102
  legs.push({
46854
- d: legPath(a, b, leg.style === "arc", 0),
48103
+ d: legPath(a, b, bow.curved, bow.offset),
46855
48104
  width: routeWidthFor(Number(leg.value)),
46856
48105
  color: mix(palette.text, palette.bg, 72),
46857
48106
  arrow: true,
46858
48107
  lineNumber: leg.lineNumber,
46859
48108
  ...leg.label !== void 0 && {
46860
48109
  label: leg.label,
46861
- labelX: mx,
46862
- labelY: my - 4
48110
+ labelX: bow.labelX,
48111
+ labelY: bow.labelY,
48112
+ labelColor: routeLabelStyle.color,
48113
+ labelHalo: routeLabelStyle.halo,
48114
+ labelHaloColor: routeLabelStyle.haloColor
46863
48115
  }
46864
48116
  });
46865
48117
  }
@@ -46887,20 +48139,29 @@ function layoutMap(resolved, data, size, opts) {
46887
48139
  const a = poiScreen.get(e.fromId);
46888
48140
  const b = poiScreen.get(e.toId);
46889
48141
  if (!a || !b) return;
46890
- const curved = e.style === "arc" || n > 1;
46891
- const offset = n > 1 ? (i - (n - 1) / 2) * FAN_STEP : 0;
48142
+ const fanOffset = n > 1 ? (i - (n - 1) / 2) * FAN_STEP : 0;
46892
48143
  const mx = (a.cx + b.cx) / 2;
46893
48144
  const my = (a.cy + b.cy) / 2;
48145
+ const bow = {
48146
+ curved: e.style === "arc" || n > 1,
48147
+ offset: fanOffset,
48148
+ labelX: mx,
48149
+ labelY: my - 4
48150
+ };
48151
+ const edgeLabelStyle = e.label !== void 0 ? labelOnFill(fillAt(bow.labelX, bow.labelY)) : void 0;
46894
48152
  legs.push({
46895
- d: legPath(a, b, curved, offset),
48153
+ d: legPath(a, b, bow.curved, bow.offset),
46896
48154
  width: widthFor(e),
46897
48155
  color: mix(palette.text, palette.bg, 66),
46898
48156
  arrow: e.directed,
46899
48157
  lineNumber: e.lineNumber,
46900
48158
  ...e.label !== void 0 && {
46901
48159
  label: e.label,
46902
- labelX: mx,
46903
- labelY: my - 4
48160
+ labelX: bow.labelX,
48161
+ labelY: bow.labelY,
48162
+ labelColor: edgeLabelStyle.color,
48163
+ labelHalo: edgeLabelStyle.halo,
48164
+ labelHaloColor: edgeLabelStyle.haloColor
46904
48165
  }
46905
48166
  });
46906
48167
  });
@@ -46942,48 +48203,73 @@ function layoutMap(resolved, data, size, opts) {
46942
48203
  }
46943
48204
  }
46944
48205
  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";
48206
+ const showRegionLabels = resolved.directives.noRegionLabels !== true;
48207
+ const isCompact = width < COMPACT_WIDTH_PX;
46946
48208
  const LABEL_PADX = 6;
46947
48209
  const LABEL_PADY = 3;
46948
- const labelW = (text) => measureLegendText(text, FONT) + 2 * LABEL_PADX;
46949
- const labelH = FONT + 2 * LABEL_PADY;
48210
+ const labelW = (text) => measureLegendText(text, FONT2) + 2 * LABEL_PADX;
48211
+ const labelH = FONT2 + 2 * LABEL_PADY;
46950
48212
  const pushRegionLabel = (x, y, text, fill2, lineNumber) => {
46951
- const color = contrastText(
46952
- fill2,
46953
- palette.textOnFillLight,
46954
- palette.textOnFillDark
48213
+ const { color, haloColor } = labelOnFill(fill2);
48214
+ const halfW = measureLegendText(text, FONT2) / 2;
48215
+ const overflows = [y - FONT2 * 0.55, y - FONT2 * 0.1].some(
48216
+ (sy) => fillAt(x - halfW, sy) !== fill2 || fillAt(x + halfW, sy) !== fill2
46955
48217
  );
46956
- const haloColor = color === palette.textOnFillLight ? palette.textOnFillDark : palette.textOnFillLight;
46957
48218
  labels.push({
46958
48219
  x,
46959
48220
  y,
46960
48221
  text,
46961
48222
  anchor: "middle",
46962
48223
  color,
46963
- halo: true,
48224
+ halo: overflows,
46964
48225
  haloColor,
46965
48226
  lineNumber
46966
48227
  });
46967
48228
  };
46968
- const WORLD_LABEL_ANCHORS = {
46969
- US: [-98.5, 39.5]
46970
- // CONUS geographic centre (near Lebanon, Kansas)
48229
+ const REGION_LABEL_GAP = 2;
48230
+ const regionLabelRect = (cx, cy, text) => {
48231
+ const w = measureLegendText(text, FONT2) + 2 * REGION_LABEL_GAP;
48232
+ return { x: cx - w / 2, y: cy - FONT2 / 2, w, h: FONT2 };
46971
48233
  };
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;
48234
+ if (showRegionLabels) {
48235
+ const frameContainers = new Set(resolved.poiFrameContainers);
48236
+ const entries = regions.map((r) => {
48237
+ const isContainer = frameContainers.has(r.id);
48238
+ if (r.layer === "base" && !isContainer || r.label === void 0)
48239
+ return null;
48240
+ const isUsState = r.layer === "us-state" || r.id.startsWith("US-");
48241
+ const f = isUsState ? usLayer?.get(r.id) : worldLayer.get(r.id);
48242
+ if (!f) return null;
46977
48243
  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;
48244
+ const boxW = x1 - x0;
48245
+ const boxH = y1 - y0;
48246
+ const abbrev = isUsState ? r.id.replace(/^US-/, "") : void 0;
48247
+ const candidates = abbrev !== void 0 ? isCompact ? [abbrev, r.label] : [r.label, abbrev] : [r.label];
48248
+ const anchor = !isUsState ? WORLD_LABEL_ANCHORS[r.id] : void 0;
46981
48249
  const c = anchor ? project(anchor[0], anchor[1]) : path.centroid(f);
46982
- if (!c || !Number.isFinite(c[0])) continue;
48250
+ if (!c || !Number.isFinite(c[0])) return null;
48251
+ return { r, c, boxW, boxH, area: boxW * boxH, candidates };
48252
+ }).filter((e) => e !== null).sort((a, b) => b.area - a.area || a.r.lineNumber - b.r.lineNumber);
48253
+ const placedRegionRects = [];
48254
+ const POI_LABEL_PAD = 14;
48255
+ const poiObstacles = pois.map((p) => ({
48256
+ x: p.cx - p.r - POI_LABEL_PAD,
48257
+ y: p.cy - p.r - POI_LABEL_PAD,
48258
+ w: 2 * (p.r + POI_LABEL_PAD),
48259
+ h: 2 * (p.r + POI_LABEL_PAD)
48260
+ }));
48261
+ for (const { r, c, boxW, boxH, candidates } of entries) {
48262
+ const text = candidates.find((t) => {
48263
+ if (labelW(t) > boxW || labelH > boxH) return false;
48264
+ const rect = regionLabelRect(c[0], c[1], t);
48265
+ return !placedRegionRects.some((p) => rectsOverlap(rect, p)) && !poiObstacles.some((o) => rectsOverlap(rect, o));
48266
+ });
48267
+ if (text === void 0) continue;
48268
+ placedRegionRects.push(regionLabelRect(c[0], c[1], text));
46983
48269
  pushRegionLabel(c[0], c[1], text, r.fill, r.lineNumber);
46984
48270
  }
46985
48271
  for (const seed of insetLabelSeeds) {
46986
- const text = regionLabelMode === "abbrev" ? seed.iso.replace(/^US-/, "") : seed.name;
48272
+ const text = isCompact ? seed.iso.replace(/^US-/, "") : seed.name;
46987
48273
  const src = regionById.get(seed.iso);
46988
48274
  pushRegionLabel(
46989
48275
  seed.x,
@@ -46994,22 +48280,26 @@ function layoutMap(resolved, data, size, opts) {
46994
48280
  );
46995
48281
  }
46996
48282
  }
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
- );
48283
+ if (resolved.directives.noPoiLabels !== true) {
48284
+ const ordered = [...pois].filter((p) => p.clusterId === void 0).sort((a, b) => a.lineNumber - b.lineNumber || (a.id < b.id ? -1 : 1));
47002
48285
  const poiById = new Map(resolved.pois.map((q) => [q.id, q]));
47003
48286
  const labelText = (p) => {
47004
48287
  const src = poiById.get(p.id);
47005
48288
  return src?.label ?? src?.name ?? p.id;
47006
48289
  };
47007
- const poiLabH = FONT * 1.25;
48290
+ const poiLabH = FONT2 * 1.25;
47008
48291
  const labelInfo = (p) => {
47009
48292
  const text = labelText(p);
47010
- return { text, w: measureLegendText(text, FONT) };
48293
+ return { text, w: measureLegendText(text, FONT2) };
47011
48294
  };
47012
48295
  const GAP = 3;
48296
+ const clusterMembersById = /* @__PURE__ */ new Map();
48297
+ for (const p of pois) {
48298
+ if (p.clusterId === void 0) continue;
48299
+ const arr = clusterMembersById.get(p.clusterId);
48300
+ if (arr) arr.push(p);
48301
+ else clusterMembersById.set(p.clusterId, [p]);
48302
+ }
47013
48303
  const inlineRect = (p, w, side) => {
47014
48304
  switch (side) {
47015
48305
  case "right":
@@ -47039,11 +48329,11 @@ function layoutMap(resolved, data, size, opts) {
47039
48329
  const x = side === "right" ? rect.x : side === "left" ? rect.x + w : p.cx;
47040
48330
  labels.push({
47041
48331
  x,
47042
- y: rect.y + poiLabH / 2 + FONT / 3,
48332
+ y: rect.y + poiLabH / 2 + FONT2 / 3,
47043
48333
  text,
47044
48334
  anchor,
47045
48335
  color: palette.text,
47046
- halo: true,
48336
+ halo: false,
47047
48337
  haloColor: palette.bg,
47048
48338
  poiId: p.id,
47049
48339
  lineNumber: p.lineNumber
@@ -47054,43 +48344,60 @@ function layoutMap(resolved, data, size, opts) {
47054
48344
  return rect.x >= 0 && rect.x + rect.w <= width && rect.y >= 0 && rect.y + rect.h <= height && !collides(rect);
47055
48345
  };
47056
48346
  const GROUP_R = 30;
47057
- const groups = [];
48347
+ const groups2 = [];
47058
48348
  for (const p of ordered) {
47059
- const near = groups.find(
48349
+ const near = groups2.find(
47060
48350
  (g) => g.some((q) => Math.hypot(q.cx - p.cx, q.cy - p.cy) < GROUP_R)
47061
48351
  );
47062
48352
  if (near) near.push(p);
47063
- else groups.push([p]);
48353
+ else groups2.push([p]);
47064
48354
  }
47065
48355
  const ROW_GAP2 = 3;
47066
48356
  const step = poiLabH + ROW_GAP2;
47067
48357
  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));
48358
+ const makeItems = (group) => group.map((p) => ({ p, ...labelInfo(p) })).sort((a, b) => a.p.cy - b.p.cy || (a.text < b.text ? -1 : 1));
48359
+ const columnRows = (items, side) => {
47070
48360
  const left = Math.min(...items.map((o) => o.p.cx - o.p.r));
47071
48361
  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
48362
  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;
48363
+ const cyMid = (Math.min(...items.map((o) => o.p.cy)) + Math.max(...items.map((o) => o.p.cy))) / 2;
48364
+ const colX = side === "right" ? Math.min(right + COL_GAP, width - 2 - maxW) : Math.max(left - COL_GAP, 2 + maxW);
47076
48365
  const totalH = items.length * step;
47077
48366
  let startY = cyMid - totalH / 2;
47078
48367
  startY = Math.max(2, Math.min(startY, height - totalH - 2));
47079
- items.forEach((o, i) => {
48368
+ return items.map((o, i) => {
47080
48369
  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
- });
48370
+ return {
48371
+ o,
48372
+ colX,
48373
+ rowCy,
48374
+ rect: {
48375
+ x: side === "right" ? colX : colX - o.w,
48376
+ y: rowCy - poiLabH / 2,
48377
+ w: o.w,
48378
+ h: poiLabH
48379
+ }
48380
+ };
48381
+ });
48382
+ };
48383
+ const wouldColumnBeClean = (items, side) => columnRows(items, side).every(
48384
+ ({ rect }) => rect.x >= 0 && rect.x + rect.w <= width && rect.y >= 0 && rect.y + rect.h <= height && !collides(rect)
48385
+ );
48386
+ const defaultColumnSide = (items) => {
48387
+ const right = Math.max(...items.map((o) => o.p.cx + o.p.r));
48388
+ const maxW = Math.max(...items.map((o) => o.w));
48389
+ return right + COL_GAP + maxW <= width - 2 ? "right" : "left";
48390
+ };
48391
+ const commitColumn = (items, side, clusterId) => {
48392
+ for (const { o, colX, rowCy, rect } of columnRows(items, side)) {
48393
+ obstacles.push(rect);
47087
48394
  labels.push({
47088
48395
  x: colX,
47089
- y: rowCy + FONT / 3,
48396
+ y: rowCy + FONT2 / 3,
47090
48397
  text: o.text,
47091
48398
  anchor: side === "right" ? "start" : "end",
47092
48399
  color: palette.text,
47093
- halo: true,
48400
+ halo: false,
47094
48401
  haloColor: palette.bg,
47095
48402
  leader: {
47096
48403
  x1: o.p.cx,
@@ -47100,24 +48407,141 @@ function layoutMap(resolved, data, size, opts) {
47100
48407
  },
47101
48408
  leaderColor: o.p.fill,
47102
48409
  poiId: o.p.id,
47103
- lineNumber: o.p.lineNumber
48410
+ lineNumber: o.p.lineNumber,
48411
+ ...clusterId !== void 0 && { clusterMember: clusterId }
47104
48412
  });
48413
+ }
48414
+ };
48415
+ const pushHidden = (p) => {
48416
+ const { text, w } = labelInfo(p);
48417
+ let x = p.cx + p.r + GAP;
48418
+ let anchor = "start";
48419
+ if (x + w > width) {
48420
+ x = p.cx - p.r - GAP - w;
48421
+ anchor = "end";
48422
+ }
48423
+ const y = Math.max(0, Math.min(p.cy - poiLabH / 2, height - poiLabH));
48424
+ labels.push({
48425
+ x: anchor === "start" ? x : x + w,
48426
+ y: y + poiLabH / 2 + FONT2 / 3,
48427
+ text,
48428
+ anchor,
48429
+ color: palette.text,
48430
+ halo: false,
48431
+ haloColor: palette.bg,
48432
+ poiId: p.id,
48433
+ hidden: true,
48434
+ lineNumber: p.lineNumber
47105
48435
  });
47106
48436
  };
47107
- for (const g of groups) {
48437
+ for (const [clusterId, members] of clusterMembersById) {
48438
+ if (members.length === 0) continue;
48439
+ const items = makeItems(members);
48440
+ const side = wouldColumnBeClean(items, "right") ? "right" : wouldColumnBeClean(items, "left") ? "left" : defaultColumnSide(items);
48441
+ commitColumn(items, side, clusterId);
48442
+ }
48443
+ const maxExtent = MAX_CLUSTER_EXTENT_FACTOR * Math.min(width, height);
48444
+ const clusterPending = [];
48445
+ for (const g of groups2) {
48446
+ const items = makeItems(g);
47108
48447
  if (g.length === 1) {
47109
- const p = g[0];
47110
- const { text, w } = labelInfo(p);
48448
+ const { p, text, w } = items[0];
47111
48449
  const side = ["right", "left", "above", "below"].find(
47112
48450
  (s) => inlineFits(p, w, s)
47113
48451
  );
47114
- if (side) {
47115
- pushInline(p, text, w, side);
47116
- continue;
48452
+ if (side) pushInline(p, text, w, side);
48453
+ else commitColumn(items, defaultColumnSide(items));
48454
+ continue;
48455
+ }
48456
+ const left = Math.min(...items.map((o) => o.p.cx - o.p.r));
48457
+ const right = Math.max(...items.map((o) => o.p.cx + o.p.r));
48458
+ const minCy = Math.min(...items.map((o) => o.p.cy));
48459
+ const maxCy = Math.max(...items.map((o) => o.p.cy));
48460
+ const diag = Math.hypot(right - left, maxCy - minCy);
48461
+ if (diag > maxExtent || items.length > MAX_COLUMN_ROWS) {
48462
+ items.forEach((o) => pushHidden(o.p));
48463
+ } else {
48464
+ clusterPending.push(items);
48465
+ }
48466
+ }
48467
+ for (const items of clusterPending) {
48468
+ const side = ["right", "left"].find(
48469
+ (s) => wouldColumnBeClean(items, s)
48470
+ );
48471
+ if (side) commitColumn(items, side);
48472
+ else items.forEach((o) => pushHidden(o.p));
48473
+ }
48474
+ }
48475
+ if (resolved.directives.noContextLabels !== true) {
48476
+ for (const l of labels) {
48477
+ if (l.hidden) continue;
48478
+ const w = labelW(l.text);
48479
+ const x = l.anchor === "start" ? l.x : l.anchor === "end" ? l.x - w : l.x - w / 2;
48480
+ obstacles.push({ x, y: l.y - labelH / 2, w, h: labelH });
48481
+ }
48482
+ for (const box of insets)
48483
+ obstacles.push({ x: box.x, y: box.y, w: box.w, h: box.h });
48484
+ const countryCandidates = [];
48485
+ for (const f of worldLayer.values()) {
48486
+ const iso = typeof f.id === "string" ? f.id : String(f.id ?? "");
48487
+ if (!iso || regionById.has(iso)) continue;
48488
+ let hasReferencedSub = false;
48489
+ for (const k of regionById.keys())
48490
+ if (k.startsWith(iso + "-")) {
48491
+ hasReferencedSub = true;
48492
+ break;
47117
48493
  }
48494
+ if (hasReferencedSub) continue;
48495
+ const b = path.bounds(f);
48496
+ const [x0, y0] = b[0];
48497
+ const [x1, y1] = b[1];
48498
+ if (!Number.isFinite(x0) || !Number.isFinite(x1)) continue;
48499
+ const anchorLngLat = WORLD_LABEL_ANCHORS[iso];
48500
+ const a = anchorLngLat ? project(anchorLngLat[0], anchorLngLat[1]) : path.centroid(f);
48501
+ countryCandidates.push({
48502
+ name: f.properties?.name ?? iso,
48503
+ bbox: [x0, y0, x1, y1],
48504
+ anchor: a && Number.isFinite(a[0]) ? [a[0], a[1]] : null
48505
+ });
48506
+ }
48507
+ const framedStateContainers = (resolved.poiFrameContainers ?? []).some(
48508
+ (id) => id.startsWith("US-")
48509
+ );
48510
+ if (usLayer && framedStateContainers) {
48511
+ const containerSet = new Set(resolved.poiFrameContainers);
48512
+ for (const [iso, f] of usLayer) {
48513
+ if (containerSet.has(iso) || regionById.has(iso)) continue;
48514
+ const viewF = cullFeatureToView(f);
48515
+ if (!viewF) continue;
48516
+ const b = path.bounds(viewF);
48517
+ const [x0, y0] = b[0];
48518
+ const [x1, y1] = b[1];
48519
+ if (!Number.isFinite(x0) || !Number.isFinite(x1)) continue;
48520
+ const a = path.centroid(viewF);
48521
+ countryCandidates.push({
48522
+ name: f.properties?.name ?? iso,
48523
+ bbox: [x0, y0, x1, y1],
48524
+ anchor: a && Number.isFinite(a[0]) ? [a[0], a[1]] : null
48525
+ });
47118
48526
  }
47119
- placeColumn(g);
47120
48527
  }
48528
+ const contextLabels = placeContextLabels({
48529
+ projection: resolved.projection,
48530
+ dLonSpan,
48531
+ dLatSpan,
48532
+ width,
48533
+ height,
48534
+ waterBodies: data.waterBodies,
48535
+ countries: countryCandidates,
48536
+ palette,
48537
+ project,
48538
+ collides,
48539
+ // Water labels must stay over open water — `fillAt` returns the ocean
48540
+ // backdrop colour off-land and a region fill on-land (lakes/states count
48541
+ // as land here, which is the safe side for an ocean name).
48542
+ overLand: (x, y) => fillAt(x, y) !== water
48543
+ });
48544
+ labels.push(...contextLabels);
47121
48545
  }
47122
48546
  let legend = null;
47123
48547
  if (!resolved.directives.noLegend) {
@@ -47154,60 +48578,104 @@ function layoutMap(resolved, data, size, opts) {
47154
48578
  rivers,
47155
48579
  relief,
47156
48580
  reliefHatch,
48581
+ coastlineStyle,
47157
48582
  legs,
47158
48583
  pois,
48584
+ clusters,
47159
48585
  labels,
47160
48586
  legend,
47161
48587
  insets,
47162
48588
  insetRegions,
47163
48589
  projection,
47164
- stretch: stretchParams
48590
+ stretch: stretchParams,
48591
+ diagnostics: []
47165
48592
  };
47166
48593
  }
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;
48594
+ var import_d3_geo2, import_topojson_client2, FIT_PAD, RAMP_FLOOR2, R_DEFAULT, R_MIN, R_MAX, W_MIN, W_MAX, FONT2, WORLD_LABEL_ANCHORS, 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
48595
  var init_layout15 = __esm({
47169
48596
  "src/map/layout.ts"() {
47170
48597
  "use strict";
47171
48598
  import_d3_geo2 = require("d3-geo");
47172
48599
  import_topojson_client2 = require("topojson-client");
47173
48600
  init_color_utils();
48601
+ init_geo();
48602
+ init_colorize();
47174
48603
  init_colors();
47175
48604
  init_label_layout();
47176
48605
  init_legend_constants();
47177
48606
  init_title_constants();
48607
+ init_context_labels();
47178
48608
  FIT_PAD = 24;
47179
- RAMP_FLOOR = 15;
48609
+ RAMP_FLOOR2 = 15;
47180
48610
  R_DEFAULT = 6;
47181
48611
  R_MIN = 4;
47182
48612
  R_MAX = 22;
47183
48613
  W_MIN = 1.25;
47184
48614
  W_MAX = 8;
47185
- FONT = 11;
47186
- COLO_EPS = 1.5;
48615
+ FONT2 = 11;
48616
+ WORLD_LABEL_ANCHORS = {
48617
+ US: [-98.5, 39.5]
48618
+ // CONUS geographic centre (near Lebanon, Kansas)
48619
+ };
48620
+ MAX_CLUSTER_EXTENT_FACTOR = 0.18;
48621
+ MAX_COLUMN_ROWS = 7;
48622
+ REGION_LABEL_HALO_RATIO = 4.5;
47187
48623
  LAND_TINT_LIGHT = 12;
47188
48624
  LAND_TINT_DARK = 24;
47189
48625
  TAG_TINT_LIGHT = 60;
47190
48626
  TAG_TINT_DARK = 68;
47191
- WATER_TINT_LIGHT = 13;
47192
- WATER_TINT_DARK = 14;
48627
+ WATER_TINT_LIGHT = 24;
48628
+ WATER_TINT_DARK = 24;
47193
48629
  RIVER_WIDTH = 1.3;
48630
+ COMPACT_WIDTH_PX = 480;
47194
48631
  RELIEF_MIN_AREA = 12;
47195
48632
  RELIEF_MIN_DIM = 2;
47196
- RELIEF_HATCH_SPACING = 3;
47197
- RELIEF_HATCH_WIDTH = 0.25;
47198
- RELIEF_HATCH_STRENGTH = 32;
48633
+ RELIEF_HATCH_SPACING = 1.5;
48634
+ RELIEF_HATCH_WIDTH = 0.2;
48635
+ RELIEF_HATCH_STRENGTH = 26;
48636
+ COASTLINE_RING_COUNT = 5;
48637
+ COASTLINE_D0 = 16e-4;
48638
+ COASTLINE_STEP = 28e-4;
48639
+ COASTLINE_THICKNESS = 14e-4;
48640
+ COASTLINE_OPACITY_NEAR = 0.5;
48641
+ COASTLINE_OPACITY_FAR = 0.1;
48642
+ COASTLINE_MIN_EXTENT = 6e-4;
48643
+ COASTLINE_MIN_EXTENT_GLOBAL = 6e-4;
48644
+ COASTLINE_STROKE_MIX = 32;
47199
48645
  FOREIGN_TINT_LIGHT = 30;
47200
48646
  FOREIGN_TINT_DARK = 62;
47201
48647
  MUTED_FOREIGN_LIGHT = 28;
47202
48648
  MUTED_FOREIGN_DARK = 16;
47203
48649
  COLO_R = 9;
47204
48650
  GOLDEN_ANGLE = 2.399963229728653;
48651
+ STACK_OVERLAP = 1;
48652
+ STACK_RING_MAX = 8;
48653
+ STACK_RING_GAP = 4;
47205
48654
  FAN_STEP = 16;
47206
48655
  ARC_CURVE_FRAC = 0.18;
48656
+ decodeCache = /* @__PURE__ */ new WeakMap();
47207
48657
  usConusProjection = () => (0, import_d3_geo2.geoConicEqualArea)().parallels([29.5, 45.5]).rotate([96, 0]);
47208
48658
  alaskaProjection = () => (0, import_d3_geo2.geoConicEqualArea)().rotate([154, 0]).center([-2, 58.5]).parallels([55, 65]);
47209
48659
  hawaiiProjection = () => (0, import_d3_geo2.geoMercator)();
47210
48660
  INSET_STATES = /* @__PURE__ */ new Set(["US-AK", "US-HI"]);
48661
+ inAlaska = (lon, lat) => lat >= 51 && (lon <= -129 || lon >= 172);
48662
+ inHawaii = (lon, lat) => lat >= 18 && lat <= 23 && lon >= -161 && lon <= -154;
48663
+ FOREIGN_BORDER = {
48664
+ CA: [
48665
+ "US-AK",
48666
+ "US-WA",
48667
+ "US-ID",
48668
+ "US-MT",
48669
+ "US-ND",
48670
+ "US-MN",
48671
+ "US-MI",
48672
+ "US-NY",
48673
+ "US-VT",
48674
+ "US-NH",
48675
+ "US-ME"
48676
+ ],
48677
+ MX: ["US-CA", "US-AZ", "US-NM", "US-TX"]
48678
+ };
47211
48679
  US_NON_CONUS = /* @__PURE__ */ new Set([
47212
48680
  "US-AK",
47213
48681
  "US-HI",
@@ -47226,6 +48694,98 @@ __export(renderer_exports16, {
47226
48694
  renderMap: () => renderMap,
47227
48695
  renderMapForExport: () => renderMapForExport
47228
48696
  });
48697
+ function pointInRing2(px, py, ring) {
48698
+ let inside = false;
48699
+ for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
48700
+ const [xi, yi] = ring[i];
48701
+ const [xj, yj] = ring[j];
48702
+ if (yi > py !== yj > py && px < (xj - xi) * (py - yi) / (yj - yi) + xi)
48703
+ inside = !inside;
48704
+ }
48705
+ return inside;
48706
+ }
48707
+ function ringToPath(ring) {
48708
+ let d = "";
48709
+ for (let i = 0; i < ring.length; i++)
48710
+ d += (i ? "L" : "M") + ring[i][0] + "," + ring[i][1];
48711
+ return d + "Z";
48712
+ }
48713
+ function polylineToPath(pts) {
48714
+ let d = "";
48715
+ for (let i = 0; i < pts.length; i++)
48716
+ d += (i ? "L" : "M") + pts[i][0] + "," + pts[i][1];
48717
+ return d;
48718
+ }
48719
+ function ringToCoastPaths(ring, frame) {
48720
+ if (!frame) return [ringToPath(ring)];
48721
+ const n = ring.length;
48722
+ const eps = 0.75;
48723
+ const onL = (x) => Math.abs(x) <= eps;
48724
+ const onR = (x) => Math.abs(x - frame.w) <= eps;
48725
+ const onT = (y) => Math.abs(y) <= eps;
48726
+ const onB = (y) => Math.abs(y - frame.h) <= eps;
48727
+ const isFrameEdge = (a, b) => onL(a[0]) && onL(b[0]) || onR(a[0]) && onR(b[0]) || onT(a[1]) && onT(b[1]) || onB(a[1]) && onB(b[1]);
48728
+ let firstBreak = -1;
48729
+ for (let i = 0; i < n; i++)
48730
+ if (isFrameEdge(ring[i], ring[(i + 1) % n])) {
48731
+ firstBreak = i;
48732
+ break;
48733
+ }
48734
+ if (firstBreak === -1) return [ringToPath(ring)];
48735
+ const paths = [];
48736
+ let cur = [];
48737
+ const start = (firstBreak + 1) % n;
48738
+ for (let k = 0; k < n; k++) {
48739
+ const i = (start + k) % n;
48740
+ const a = ring[i];
48741
+ const b = ring[(i + 1) % n];
48742
+ if (isFrameEdge(a, b)) {
48743
+ if (cur.length >= 2) paths.push(polylineToPath(cur));
48744
+ cur = [];
48745
+ continue;
48746
+ }
48747
+ if (cur.length === 0) cur.push(a);
48748
+ cur.push(b);
48749
+ }
48750
+ if (cur.length >= 2) paths.push(polylineToPath(cur));
48751
+ return paths;
48752
+ }
48753
+ function coastlineOuterRings(regions, minExtent, frame) {
48754
+ const paths = [];
48755
+ for (const r of regions) {
48756
+ const rings = parsePathRings(r.d);
48757
+ for (let i = 0; i < rings.length; i++) {
48758
+ const ring = rings[i];
48759
+ if (ring.length < 3) continue;
48760
+ let minX = Infinity;
48761
+ let minY = Infinity;
48762
+ let maxX = -Infinity;
48763
+ let maxY = -Infinity;
48764
+ for (const [x, y] of ring) {
48765
+ if (x < minX) minX = x;
48766
+ if (x > maxX) maxX = x;
48767
+ if (y < minY) minY = y;
48768
+ if (y > maxY) maxY = y;
48769
+ }
48770
+ if (Math.max(maxX - minX, maxY - minY) < minExtent) continue;
48771
+ const [fx, fy] = ring[0];
48772
+ let depth = 0;
48773
+ for (let j = 0; j < rings.length; j++)
48774
+ if (j !== i && pointInRing2(fx, fy, rings[j])) depth++;
48775
+ if (depth % 2 === 1) continue;
48776
+ paths.push(...ringToCoastPaths(ring, frame));
48777
+ }
48778
+ }
48779
+ return paths;
48780
+ }
48781
+ function appendWaterLines(g, outerRings, style, flatWater) {
48782
+ const d = outerRings.join(" ");
48783
+ const linesOuterFirst = [...style.lines].sort((a, b) => b.d - a.d);
48784
+ for (const line12 of linesOuterFirst) {
48785
+ 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");
48786
+ g.append("path").attr("d", d).attr("stroke", flatWater).attr("stroke-width", 2 * line12.d).attr("stroke-linejoin", "round").attr("stroke-linecap", "round");
48787
+ }
48788
+ }
47229
48789
  function renderMap(container, resolved, data, palette, isDark, onClickItem, exportDims, activeGroupOverride) {
47230
48790
  d3Selection18.select(container).selectAll(":not([data-d3-tooltip])").remove();
47231
48791
  const width = exportDims?.width ?? container.clientWidth;
@@ -47238,6 +48798,11 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47238
48798
  {
47239
48799
  palette,
47240
48800
  isDark,
48801
+ // Export-only: forward the contain-fit request from mapExportDimensions so a
48802
+ // clamped/floored (off-aspect) export canvas letterboxes instead of
48803
+ // stretch-distorting. The in-app preview pane passes no exportDims → unset →
48804
+ // keeps the global stretch-fill.
48805
+ preferContain: exportDims?.preferContain ?? false,
47241
48806
  ...activeGroupOverride !== void 0 && {
47242
48807
  activeGroup: activeGroupOverride
47243
48808
  }
@@ -47251,6 +48816,11 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47251
48816
  const gRegions = svg.append("g").attr("class", "dgmo-map-regions");
47252
48817
  const drawRegion = (g, r, strokeWidth) => {
47253
48818
  const p = g.append("path").attr("d", r.d).attr("fill", r.fill).attr("stroke", r.stroke).attr("stroke-width", strokeWidth);
48819
+ if (r.label) p.attr("data-region-name", r.label);
48820
+ if (r.id && r.id !== "lake") p.attr("data-iso", r.id);
48821
+ if (r.labelX !== void 0 && r.labelY !== void 0) {
48822
+ p.attr("data-label-x", r.labelX).attr("data-label-y", r.labelY);
48823
+ }
47254
48824
  if (r.layer !== "base") {
47255
48825
  p.classed("dgmo-map-region", true).attr("data-region", r.id);
47256
48826
  if (r.value !== void 0) p.attr("data-value", r.value);
@@ -47280,28 +48850,112 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47280
48850
  const landClip = defs.append("clipPath").attr("id", landClipId);
47281
48851
  for (const r of layout.regions)
47282
48852
  if (r.id !== "lake") landClip.append("path").attr("d", r.d);
47283
- const gRelief = svg.append("g").attr("clip-path", `url(#${landClipId})`).append("g").attr("class", "dgmo-map-relief").attr("clip-path", `url(#${rangeClipId})`).attr("stroke", h.color).attr("stroke-width", h.width).attr("vector-effect", "non-scaling-stroke");
48853
+ const gRelief = svg.append("g").attr("clip-path", `url(#${landClipId})`).style("pointer-events", "none").append("g").attr("class", "dgmo-map-relief").attr("clip-path", `url(#${rangeClipId})`).attr("stroke", h.color).attr("stroke-width", h.width).attr("vector-effect", "non-scaling-stroke");
47284
48854
  for (let y = h.spacing; y < height; y += h.spacing) {
47285
48855
  gRelief.append("line").attr("x1", 0).attr("y1", y).attr("x2", width).attr("y2", y);
47286
48856
  }
47287
48857
  }
48858
+ if (layout.coastlineStyle) {
48859
+ const cs = layout.coastlineStyle;
48860
+ const maskId = "dgmo-map-water-mask";
48861
+ const mask = defs.append("mask").attr("id", maskId).attr("maskUnits", "userSpaceOnUse").attr("x", 0).attr("y", 0).attr("width", width).attr("height", height);
48862
+ mask.append("rect").attr("x", 0).attr("y", 0).attr("width", width).attr("height", height).attr("fill", "white");
48863
+ const landD = layout.regions.filter((r) => r.id !== "lake").map((r) => r.d).join(" ");
48864
+ const lakeD = layout.regions.filter((r) => r.id === "lake").map((r) => r.d).join(" ");
48865
+ if (landD) mask.append("path").attr("d", landD).attr("fill", "black");
48866
+ if (lakeD) mask.append("path").attr("d", lakeD).attr("fill", "white");
48867
+ if (layout.insets.length) {
48868
+ const reach = Math.max(0, ...cs.lines.map((l) => l.d + l.thickness));
48869
+ for (const box of layout.insets) {
48870
+ const d = box.points.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join("") + "Z";
48871
+ mask.append("path").attr("d", d).attr("fill", "black").attr("stroke", "black").attr("stroke-width", 2 * reach).attr("stroke-linejoin", "round");
48872
+ }
48873
+ }
48874
+ const gWater = svg.append("g").attr("class", "dgmo-map-water-lines").attr("fill", "none").style("pointer-events", "none").attr("mask", `url(#${maskId})`);
48875
+ appendWaterLines(
48876
+ gWater,
48877
+ // Pass the canvas frame so edges collinear with it (the antimeridian on a
48878
+ // world map, regional clipExtent cuts) don't get ringed as fake coast —
48879
+ // land runs cleanly to the render-area edge.
48880
+ coastlineOuterRings(layout.regions, cs.minExtent, {
48881
+ w: width,
48882
+ h: height
48883
+ }),
48884
+ cs,
48885
+ layout.background
48886
+ );
48887
+ const byStroke = /* @__PURE__ */ new Map();
48888
+ for (const r of layout.regions) {
48889
+ const arr = byStroke.get(r.stroke);
48890
+ if (arr) arr.push(r.d);
48891
+ else byStroke.set(r.stroke, [r.d]);
48892
+ }
48893
+ for (const [stroke2, ds] of byStroke)
48894
+ gWater.append("path").attr("d", ds.join(" ")).attr("stroke", stroke2).attr("stroke-width", 0.5).attr("stroke-linejoin", "round");
48895
+ }
47288
48896
  if (layout.rivers.length) {
47289
- const gRivers = svg.append("g").attr("class", "dgmo-map-rivers").attr("fill", "none");
48897
+ const gRivers = svg.append("g").attr("class", "dgmo-map-rivers").attr("fill", "none").style("pointer-events", "none");
47290
48898
  for (const r of layout.rivers) {
47291
48899
  gRivers.append("path").attr("d", r.d).attr("stroke", r.color).attr("stroke-width", r.width).attr("stroke-linecap", "round").attr("stroke-linejoin", "round");
47292
48900
  }
47293
48901
  }
47294
48902
  if (layout.insets.length) {
47295
48903
  const insetG = svg.append("g").attr("class", "dgmo-map-insets");
47296
- for (const box of layout.insets) {
48904
+ layout.insets.forEach((box, bi) => {
47297
48905
  const d = box.points.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join("") + "Z";
47298
48906
  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
- }
48907
+ if (box.contextLand) {
48908
+ const clipId = `dgmo-map-inset-clip-${bi}`;
48909
+ defs.append("clipPath").attr("id", clipId).append("path").attr("d", d);
48910
+ insetG.append("path").attr("d", box.contextLand.d).attr("fill", box.contextLand.fill).attr("clip-path", `url(#${clipId})`);
48911
+ }
48912
+ });
47300
48913
  for (const r of layout.insetRegions) drawRegion(insetG, r, 0.5);
47301
- }
48914
+ if (layout.coastlineStyle) {
48915
+ const cs = layout.coastlineStyle;
48916
+ const maskId = "dgmo-map-inset-water-mask";
48917
+ const mask = defs.append("mask").attr("id", maskId).attr("maskUnits", "userSpaceOnUse").attr("x", 0).attr("y", 0).attr("width", width).attr("height", height);
48918
+ for (const box of layout.insets) {
48919
+ const d = box.points.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join("") + "Z";
48920
+ mask.append("path").attr("d", d).attr("fill", "white");
48921
+ }
48922
+ layout.insets.forEach((box, bi) => {
48923
+ if (box.contextLand)
48924
+ mask.append("path").attr("d", box.contextLand.d).attr("fill", "black").attr("clip-path", `url(#dgmo-map-inset-clip-${bi})`);
48925
+ });
48926
+ for (const r of layout.insetRegions)
48927
+ if (r.id !== "lake")
48928
+ mask.append("path").attr("d", r.d).attr("fill", "black");
48929
+ for (const r of layout.insetRegions)
48930
+ if (r.id === "lake")
48931
+ mask.append("path").attr("d", r.d).attr("fill", "white");
48932
+ const clipId = "dgmo-map-inset-water-clip";
48933
+ const clip = defs.append("clipPath").attr("id", clipId);
48934
+ for (const box of layout.insets) {
48935
+ const d = box.points.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join("") + "Z";
48936
+ clip.append("path").attr("d", d);
48937
+ }
48938
+ const gInsetWater = insetG.append("g").attr("clip-path", `url(#${clipId})`).append("g").attr("class", "dgmo-map-inset-water-lines").attr("fill", "none").style("pointer-events", "none").attr("mask", `url(#${maskId})`);
48939
+ appendWaterLines(
48940
+ gInsetWater,
48941
+ coastlineOuterRings(layout.insetRegions, cs.minExtent),
48942
+ cs,
48943
+ layout.background
48944
+ );
48945
+ for (const r of layout.insetRegions)
48946
+ gInsetWater.append("path").attr("d", r.d).attr("stroke", r.stroke).attr("stroke-width", 0.5).attr("stroke-linejoin", "round");
48947
+ }
48948
+ }
48949
+ const wireSync = (sel, lineNumber) => {
48950
+ if (lineNumber < 1) return;
48951
+ sel.attr("data-line-number", lineNumber);
48952
+ if (onClickItem)
48953
+ sel.style("cursor", "pointer").on("click", () => onClickItem(lineNumber));
48954
+ };
47302
48955
  const gLegs = svg.append("g").attr("class", "dgmo-map-legs").attr("fill", "none");
47303
48956
  layout.legs.forEach((leg, i) => {
47304
48957
  const p = gLegs.append("path").attr("d", leg.d).attr("stroke", leg.color).attr("stroke-width", leg.width).attr("stroke-linecap", "round");
48958
+ wireSync(p, leg.lineNumber);
47305
48959
  if (leg.arrow) {
47306
48960
  const id = `dgmo-map-arrow-${i}`;
47307
48961
  const s = arrowSize(leg.width);
@@ -47309,25 +48963,38 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47309
48963
  p.attr("marker-end", `url(#${id})`);
47310
48964
  }
47311
48965
  if (leg.label !== void 0 && leg.labelX !== void 0) {
47312
- emitText(
48966
+ const lt = emitText(
47313
48967
  gLegs,
47314
48968
  leg.labelX,
47315
48969
  leg.labelY ?? 0,
47316
48970
  leg.label,
47317
48971
  "middle",
47318
- palette.textMuted,
47319
- haloColor,
47320
- true,
48972
+ leg.labelColor ?? palette.textMuted,
48973
+ leg.labelHaloColor ?? haloColor,
48974
+ leg.labelHalo ?? true,
47321
48975
  LABEL_FONT - 1
47322
48976
  );
48977
+ wireSync(lt, leg.lineNumber);
47323
48978
  }
47324
48979
  });
48980
+ const gSpider = svg.append("g").attr("class", "dgmo-map-spider");
48981
+ for (const cl of layout.clusters) {
48982
+ if (!exportDims) {
48983
+ 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");
48984
+ }
48985
+ for (const leg of cl.legs) {
48986
+ 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");
48987
+ }
48988
+ 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");
48989
+ }
47325
48990
  const gPois = svg.append("g").attr("class", "dgmo-map-pois");
47326
48991
  for (const poi of layout.pois) {
47327
48992
  if (poi.isOrigin) {
47328
48993
  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
48994
  }
47330
48995
  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);
48996
+ if (poi.clusterId !== void 0)
48997
+ c.attr("data-cluster-member", poi.clusterId);
47331
48998
  if (poi.tags) {
47332
48999
  for (const [group, value] of Object.entries(poi.tags)) {
47333
49000
  c.attr(`data-tag-${group.toLowerCase()}`, value.toLowerCase());
@@ -47355,12 +49022,32 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47355
49022
  }
47356
49023
  const gLabels = svg.append("g").attr("class", "dgmo-map-labels");
47357
49024
  for (const lab of layout.labels) {
49025
+ if (lab.hidden) {
49026
+ if (exportDims) continue;
49027
+ emitText(
49028
+ gLabels,
49029
+ lab.x,
49030
+ lab.y,
49031
+ lab.text,
49032
+ lab.anchor,
49033
+ lab.color,
49034
+ lab.haloColor,
49035
+ lab.halo,
49036
+ LABEL_FONT,
49037
+ lab.italic,
49038
+ lab.letterSpacing
49039
+ ).attr("data-poi", lab.poiId ?? null).attr("data-poi-hidden", "").style("opacity", 0).style("pointer-events", "none");
49040
+ continue;
49041
+ }
47358
49042
  if (lab.leader) {
47359
49043
  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
49044
  "stroke",
47361
49045
  lab.leaderColor ?? mix(palette.textMuted, palette.bg, 60)
47362
49046
  ).attr("stroke-width", lab.leaderColor ? 1 : 0.75);
47363
49047
  if (lab.poiId !== void 0) line12.attr("data-poi", lab.poiId);
49048
+ if (lab.clusterMember !== void 0)
49049
+ line12.attr("data-cluster-member", lab.clusterMember);
49050
+ wireSync(line12, lab.lineNumber);
47364
49051
  }
47365
49052
  const t = emitText(
47366
49053
  gLabels,
@@ -47371,11 +49058,38 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47371
49058
  lab.color,
47372
49059
  lab.haloColor,
47373
49060
  lab.halo,
47374
- LABEL_FONT
49061
+ LABEL_FONT,
49062
+ lab.italic,
49063
+ lab.letterSpacing,
49064
+ lab.lines
47375
49065
  );
47376
49066
  if (lab.poiId !== void 0) {
47377
49067
  t.attr("data-poi", lab.poiId).style("cursor", "default");
47378
49068
  }
49069
+ if (lab.clusterMember !== void 0) {
49070
+ t.attr("data-cluster-member", lab.clusterMember);
49071
+ }
49072
+ wireSync(t, lab.lineNumber);
49073
+ }
49074
+ if (!exportDims && layout.clusters.length) {
49075
+ const gBadge = svg.append("g").attr("class", "dgmo-map-cluster-badges");
49076
+ for (const cl of layout.clusters) {
49077
+ const g = gBadge.append("g").attr("data-cluster", cl.id).style("opacity", 0).style("pointer-events", "none");
49078
+ const R = 9;
49079
+ 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);
49080
+ 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);
49081
+ emitText(
49082
+ g,
49083
+ cl.cx,
49084
+ cl.cy + 3,
49085
+ String(cl.count),
49086
+ "middle",
49087
+ palette.text,
49088
+ palette.bg,
49089
+ false,
49090
+ LABEL_FONT
49091
+ );
49092
+ }
47379
49093
  }
47380
49094
  if (layout.legend) {
47381
49095
  const legendY = (layout.title ? TITLE_Y + TITLE_FONT_SIZE : 0) + (layout.subtitle ? TITLE_FONT_SIZE : 0) + 8;
@@ -47412,7 +49126,7 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47412
49126
  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
49127
  }
47414
49128
  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);
49129
+ 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
49130
  }
47417
49131
  if (layout.caption) {
47418
49132
  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 +49135,21 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47421
49135
  function renderMapForExport(container, resolved, data, palette, isDark, exportDims) {
47422
49136
  renderMap(container, resolved, data, palette, isDark, void 0, exportDims);
47423
49137
  }
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);
49138
+ function emitText(g, x, y, text, anchor, color, halo, withHalo, fontSize, italic, letterSpacing, lines) {
49139
+ const t = g.append("text").attr("x", x).attr("y", y).attr("text-anchor", anchor).attr("font-size", fontSize).attr("fill", color);
49140
+ if (lines && lines.length > 1) {
49141
+ const lineHeight = fontSize + 2;
49142
+ const startDy = -((lines.length - 1) / 2) * lineHeight;
49143
+ lines.forEach((ln, i) => {
49144
+ t.append("tspan").attr("x", x).attr("dy", i === 0 ? startDy : lineHeight).text(ln);
49145
+ });
49146
+ } else {
49147
+ t.text(text);
49148
+ }
49149
+ if (italic) t.attr("font-style", "italic");
49150
+ if (letterSpacing) t.attr("letter-spacing", letterSpacing);
47426
49151
  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);
49152
+ 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
49153
  }
47429
49154
  return t;
47430
49155
  }
@@ -47442,6 +49167,56 @@ var init_renderer16 = __esm({
47442
49167
  }
47443
49168
  });
47444
49169
 
49170
+ // src/map/dimensions.ts
49171
+ var dimensions_exports = {};
49172
+ __export(dimensions_exports, {
49173
+ mapContentAspect: () => mapContentAspect,
49174
+ mapExportDimensions: () => mapExportDimensions
49175
+ });
49176
+ function mapContentAspect(resolved, data, ref = REF) {
49177
+ const { projection, fitTarget } = buildMapProjection(resolved, data);
49178
+ projection.fitSize([ref, ref], fitTarget);
49179
+ const b = (0, import_d3_geo3.geoPath)(projection).bounds(fitTarget);
49180
+ const w = b[1][0] - b[0][0];
49181
+ const h = b[1][1] - b[0][1];
49182
+ const aspect = w / h;
49183
+ return Number.isFinite(aspect) && aspect > 0 ? aspect : FALLBACK_ASPECT;
49184
+ }
49185
+ function mapExportDimensions(resolved, data, baseWidth = 1200) {
49186
+ const raw = mapContentAspect(resolved, data);
49187
+ const clamped = Math.max(ASPECT_MIN, Math.min(ASPECT_MAX, raw));
49188
+ const width = baseWidth;
49189
+ let height = Math.round(width / clamped);
49190
+ let chromeReserve = 0;
49191
+ if (resolved.title && resolved.pois.length > 0) {
49192
+ const bannerBottom = (resolved.subtitle ? TITLE_Y + TITLE_FONT_SIZE : TITLE_Y) + TITLE_FONT_SIZE / 2;
49193
+ chromeReserve += Math.max(FIT_PAD2, bannerBottom + TITLE_GAP) - FIT_PAD2;
49194
+ }
49195
+ let floored = false;
49196
+ if (height - chromeReserve < MIN_MAP_BAND) {
49197
+ height = Math.round(chromeReserve + MIN_MAP_BAND);
49198
+ floored = true;
49199
+ }
49200
+ const preferContain = clamped !== raw || floored;
49201
+ return { width, height, preferContain };
49202
+ }
49203
+ var import_d3_geo3, FIT_PAD2, TITLE_GAP, ASPECT_MAX, ASPECT_MIN, MIN_MAP_BAND, FALLBACK_ASPECT, REF;
49204
+ var init_dimensions = __esm({
49205
+ "src/map/dimensions.ts"() {
49206
+ "use strict";
49207
+ import_d3_geo3 = require("d3-geo");
49208
+ init_title_constants();
49209
+ init_layout15();
49210
+ FIT_PAD2 = 24;
49211
+ TITLE_GAP = 16;
49212
+ ASPECT_MAX = 3;
49213
+ ASPECT_MIN = 0.9;
49214
+ MIN_MAP_BAND = 200;
49215
+ FALLBACK_ASPECT = 1.5;
49216
+ REF = 1e3;
49217
+ }
49218
+ });
49219
+
47445
49220
  // src/map/load-data.ts
47446
49221
  var load_data_exports = {};
47447
49222
  __export(load_data_exports, {
@@ -47500,12 +49275,17 @@ function loadMapData() {
47500
49275
  mountainRanges,
47501
49276
  naLand,
47502
49277
  naLakes,
49278
+ waterBodies,
47503
49279
  gazetteer
47504
49280
  ] = await Promise.all([
49281
+ // worldCoarse (110m) is LOAD-BEARING but NOT a render source: the world
49282
+ // basemap renders from worldDetail (50m) at all scales (resolver pins
49283
+ // basemaps.world = 'detail'). Coarse stays as the authoritative region
49284
+ // name index + dominant-landmass bbox source in resolver.ts. Do not drop it.
47505
49285
  readJson(nb, dir, FILES.worldCoarse),
47506
49286
  readJson(nb, dir, FILES.worldDetail),
47507
49287
  readJson(nb, dir, FILES.usStates),
47508
- // Lakes/rivers/mountain/NA assets are optional — older bundles may predate them.
49288
+ // Lakes/rivers/mountain/NA/water assets are optional — older bundles may predate them.
47509
49289
  readJson(nb, dir, FILES.lakes).catch(() => void 0),
47510
49290
  readJson(nb, dir, FILES.rivers).catch(() => void 0),
47511
49291
  readJson(nb, dir, FILES.mountainRanges).catch(
@@ -47513,6 +49293,7 @@ function loadMapData() {
47513
49293
  ),
47514
49294
  readJson(nb, dir, FILES.naLand).catch(() => void 0),
47515
49295
  readJson(nb, dir, FILES.naLakes).catch(() => void 0),
49296
+ readJson(nb, dir, FILES.waterBodies).catch(() => void 0),
47516
49297
  readJson(nb, dir, FILES.gazetteer)
47517
49298
  ]);
47518
49299
  return validate({
@@ -47524,7 +49305,8 @@ function loadMapData() {
47524
49305
  ...rivers && { rivers },
47525
49306
  ...mountainRanges && { mountainRanges },
47526
49307
  ...naLand && { naLand },
47527
- ...naLakes && { naLakes }
49308
+ ...naLakes && { naLakes },
49309
+ ...waterBodies && { waterBodies }
47528
49310
  });
47529
49311
  })().catch((e) => {
47530
49312
  cache = void 0;
@@ -47546,6 +49328,7 @@ var init_load_data = __esm({
47546
49328
  mountainRanges: "mountain-ranges.json",
47547
49329
  naLand: "na-land.json",
47548
49330
  naLakes: "na-lakes.json",
49331
+ waterBodies: "water-bodies.json",
47549
49332
  gazetteer: "gazetteer.json"
47550
49333
  };
47551
49334
  CANDIDATE_DIRS = [
@@ -49558,8 +51341,8 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
49558
51341
  const lines = splitParticipantLabel(p.label, LABEL_MAX_CHARS);
49559
51342
  if (lines.length === 0) continue;
49560
51343
  const widest = Math.max(...lines.map((l) => l.length));
49561
- const labelWidth = widest * LABEL_CHAR_WIDTH + 10;
49562
- uniformBoxWidth = Math.max(uniformBoxWidth, labelWidth);
51344
+ const labelWidth2 = widest * LABEL_CHAR_WIDTH + 10;
51345
+ uniformBoxWidth = Math.max(uniformBoxWidth, labelWidth2);
49563
51346
  }
49564
51347
  uniformBoxWidth = Math.min(MAX_BOX_WIDTH, uniformBoxWidth);
49565
51348
  const effectiveGap = Math.max(PARTICIPANT_GAP, uniformBoxWidth + 30);
@@ -52254,15 +54037,15 @@ function renderArcDiagram(container, parsed, palette, _isDark, onClickItem, expo
52254
54037
  textColor,
52255
54038
  onClickItem
52256
54039
  );
52257
- const neighbors = /* @__PURE__ */ new Map();
52258
- for (const node of nodes) neighbors.set(node, /* @__PURE__ */ new Set());
54040
+ const neighbors2 = /* @__PURE__ */ new Map();
54041
+ for (const node of nodes) neighbors2.set(node, /* @__PURE__ */ new Set());
52259
54042
  for (const link of links) {
52260
- neighbors.get(link.source).add(link.target);
52261
- neighbors.get(link.target).add(link.source);
54043
+ neighbors2.get(link.source).add(link.target);
54044
+ neighbors2.get(link.target).add(link.source);
52262
54045
  }
52263
54046
  const FADE_OPACITY3 = 0.1;
52264
54047
  function handleMouseEnter(hovered) {
52265
- const connected = neighbors.get(hovered);
54048
+ const connected = neighbors2.get(hovered);
52266
54049
  g.selectAll(".arc-link").each(function() {
52267
54050
  const el = d3Selection23.select(this);
52268
54051
  const src = el.attr("data-source");
@@ -53170,10 +54953,12 @@ function renderTimelineHorizontalTimeSort(container, parsed, palette, isDark, se
53170
54953
  const markerLabelY = markerReserve ? -(topScaleH + MARKER_ROW_H / 2) : 0;
53171
54954
  const eraLabelY = eraReserve ? -(topScaleH + markerReserve + ERA_ROW_H / 2) : 0;
53172
54955
  const innerWidth = width - margin.left - margin.right;
53173
- const innerHeight = height - margin.top - margin.bottom;
53174
- const rowH = Math.min(ctx.structural(28), innerHeight / sorted.length);
54956
+ const availInnerHeight = height - margin.top - margin.bottom;
54957
+ const rowH = Math.min(ctx.structural(28), availInnerHeight / sorted.length);
54958
+ const innerHeight = rowH * sorted.length;
54959
+ const usedHeight = margin.top + innerHeight + margin.bottom;
53175
54960
  const xScale = d3Scale2.scaleLinear().domain([minDate - datePadding, maxDate + datePadding]).range([0, innerWidth]);
53176
- const svg = d3Selection23.select(container).append("svg").attr("width", width).attr("height", height).attr("viewBox", `0 0 ${width} ${height}`).attr("preserveAspectRatio", "xMidYMin meet").style("background", bgColor);
54961
+ const svg = d3Selection23.select(container).append("svg").attr("width", width).attr("height", usedHeight).attr("viewBox", `0 0 ${width} ${usedHeight}`).attr("preserveAspectRatio", "xMidYMin meet").style("background", bgColor);
53177
54962
  if (ctx.isBelowFloor) {
53178
54963
  svg.attr("width", "100%");
53179
54964
  }
@@ -54197,7 +55982,7 @@ function renderVenn(container, parsed, palette, _isDark, onClickItem, exportDims
54197
55982
  8,
54198
55983
  Math.floor(OVERLAP_WRAP_TARGET_W / OVERLAP_CH_W)
54199
55984
  );
54200
- function wrapLabel2(text, maxChars) {
55985
+ function wrapLabel3(text, maxChars) {
54201
55986
  const words = text.split(/\s+/).filter(Boolean);
54202
55987
  const lines = [];
54203
55988
  let cur = "";
@@ -54243,7 +56028,7 @@ function renderVenn(container, parsed, palette, _isDark, onClickItem, exportDims
54243
56028
  if (!ov.label) continue;
54244
56029
  const idxs = ov.sets.map((s) => vennSets.findIndex((vs) => vs.name === s));
54245
56030
  if (idxs.some((idx) => idx < 0)) continue;
54246
- const lines = wrapLabel2(ov.label, MAX_WRAP_CHARS);
56031
+ const lines = wrapLabel3(ov.label, MAX_WRAP_CHARS);
54247
56032
  wrappedOverlapLabels.set(ov, lines);
54248
56033
  const dir = predictOverlapDirRaw(idxs);
54249
56034
  const longest = lines.reduce((m, l) => Math.max(m, l.length), 0);
@@ -55681,6 +57466,7 @@ async function renderForExport(content, theme, palette, viewState, options) {
55681
57466
  const { parseMap: parseMap2 } = await Promise.resolve().then(() => (init_parser12(), parser_exports11));
55682
57467
  const { resolveMap: resolveMap2 } = await Promise.resolve().then(() => (init_resolver2(), resolver_exports));
55683
57468
  const { renderMapForExport: renderMapForExport2 } = await Promise.resolve().then(() => (init_renderer16(), renderer_exports16));
57469
+ const { mapExportDimensions: mapExportDimensions2 } = await Promise.resolve().then(() => (init_dimensions(), dimensions_exports));
55684
57470
  const effectivePalette2 = await resolveExportPalette(theme, palette);
55685
57471
  const mapParsed = parseMap2(content);
55686
57472
  let mapData = options?.mapData;
@@ -55693,14 +57479,15 @@ async function renderForExport(content, theme, palette, viewState, options) {
55693
57479
  }
55694
57480
  }
55695
57481
  const mapResolved = resolveMap2(mapParsed, mapData);
55696
- const container2 = createExportContainer(EXPORT_WIDTH, EXPORT_HEIGHT);
57482
+ const dims2 = mapExportDimensions2(mapResolved, mapData, EXPORT_WIDTH);
57483
+ const container2 = createExportContainer(dims2.width, dims2.height);
55697
57484
  renderMapForExport2(
55698
57485
  container2,
55699
57486
  mapResolved,
55700
57487
  mapData,
55701
57488
  effectivePalette2,
55702
57489
  theme === "dark",
55703
- { width: EXPORT_WIDTH, height: EXPORT_HEIGHT }
57490
+ dims2
55704
57491
  );
55705
57492
  return finalizeSvgExport(container2, theme, effectivePalette2);
55706
57493
  }
@@ -56561,7 +58348,8 @@ async function render(content, options) {
56561
58348
  ...options?.c4Container !== void 0 && {
56562
58349
  c4Container: options.c4Container
56563
58350
  },
56564
- ...options?.tagGroup !== void 0 && { tagGroup: options.tagGroup }
58351
+ ...options?.tagGroup !== void 0 && { tagGroup: options.tagGroup },
58352
+ ...options?.mapData !== void 0 && { mapData: options.mapData }
56565
58353
  });
56566
58354
  if (chartType === "map") {
56567
58355
  try {
@@ -56572,7 +58360,7 @@ async function render(content, options) {
56572
58360
  Promise.resolve().then(() => (init_load_data(), load_data_exports))
56573
58361
  ]
56574
58362
  );
56575
- const data = await loadMapData2();
58363
+ const data = options?.mapData ?? await loadMapData2();
56576
58364
  diagnostics = [...resolveMap2(parseMap2(content), data).diagnostics];
56577
58365
  } catch {
56578
58366
  }
@@ -56701,6 +58489,9 @@ var DIRECTIVE_KEYWORDS = /* @__PURE__ */ new Set([
56701
58489
  "hide",
56702
58490
  "mode",
56703
58491
  "direction",
58492
+ // Boxes-and-lines
58493
+ "box-metric",
58494
+ "show-values",
56704
58495
  // ER
56705
58496
  "notation",
56706
58497
  // Class
@@ -56740,22 +58531,20 @@ var DIRECTIVE_KEYWORDS = /* @__PURE__ */ new Set([
56740
58531
  // Sequence
56741
58532
  "activations",
56742
58533
  "no-activations",
56743
- // Map (§24B) directives
56744
- "region",
56745
- "projection",
58534
+ // Map (§24B) directives — cosmetics on by default, bare `no-*` opt-outs
56746
58535
  "region-metric",
56747
58536
  "poi-metric",
56748
58537
  "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",
58538
+ "locale",
58539
+ "active-tag",
56758
58540
  "caption",
58541
+ "no-legend",
58542
+ "no-coastline",
58543
+ "no-relief",
58544
+ "no-context-labels",
58545
+ "no-region-labels",
58546
+ "no-poi-labels",
58547
+ "no-colorize",
56759
58548
  "poi",
56760
58549
  "route",
56761
58550
  // Data charts
@@ -57048,7 +58837,11 @@ var ATTRIBUTE_KEYS = /* @__PURE__ */ new Set([
57048
58837
  "collapsed",
57049
58838
  "tech",
57050
58839
  "span",
57051
- "split"
58840
+ "split",
58841
+ // Map (§24B) reserved keys
58842
+ "value",
58843
+ "label",
58844
+ "style"
57052
58845
  ]);
57053
58846
  function applyAttributeKeys(tokens) {
57054
58847
  for (let i = 0; i < tokens.length - 1; i++) {
@@ -57421,7 +59214,7 @@ pre.dgmo, code.language-dgmo, pre > code.language-dgmo,
57421
59214
 
57422
59215
  // src/auto/index.ts
57423
59216
  init_safe_href();
57424
- var VERSION = "0.21.1";
59217
+ var VERSION = "0.23.0";
57425
59218
  var DEFAULTS = {
57426
59219
  theme: "auto",
57427
59220
  palette: "nord",