@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/advanced.cjs CHANGED
@@ -373,18 +373,18 @@ function computeQuadrantPointLabels(points, chartBounds, obstacles, pointRadius,
373
373
  const results = [];
374
374
  for (let i = 0; i < points.length; i++) {
375
375
  const pt = points[i];
376
- const labelWidth = pt.label.length * fontSize * CHAR_WIDTH_RATIO + 8;
376
+ const labelWidth2 = pt.label.length * fontSize * CHAR_WIDTH_RATIO + 8;
377
377
  let best = null;
378
378
  const directions = [
379
379
  {
380
380
  // Above
381
381
  gen: (offset) => {
382
- const lx = pt.cx - labelWidth / 2;
382
+ const lx = pt.cx - labelWidth2 / 2;
383
383
  const ly = pt.cy - offset - labelHeight;
384
- if (ly < chartBounds.top || lx < chartBounds.left || lx + labelWidth > chartBounds.right)
384
+ if (ly < chartBounds.top || lx < chartBounds.left || lx + labelWidth2 > chartBounds.right)
385
385
  return null;
386
386
  return {
387
- rect: { x: lx, y: ly, w: labelWidth, h: labelHeight },
387
+ rect: { x: lx, y: ly, w: labelWidth2, h: labelHeight },
388
388
  textX: pt.cx,
389
389
  textY: ly + labelHeight / 2,
390
390
  anchor: "middle"
@@ -394,12 +394,12 @@ function computeQuadrantPointLabels(points, chartBounds, obstacles, pointRadius,
394
394
  {
395
395
  // Below
396
396
  gen: (offset) => {
397
- const lx = pt.cx - labelWidth / 2;
397
+ const lx = pt.cx - labelWidth2 / 2;
398
398
  const ly = pt.cy + offset;
399
- if (ly + labelHeight > chartBounds.bottom || lx < chartBounds.left || lx + labelWidth > chartBounds.right)
399
+ if (ly + labelHeight > chartBounds.bottom || lx < chartBounds.left || lx + labelWidth2 > chartBounds.right)
400
400
  return null;
401
401
  return {
402
- rect: { x: lx, y: ly, w: labelWidth, h: labelHeight },
402
+ rect: { x: lx, y: ly, w: labelWidth2, h: labelHeight },
403
403
  textX: pt.cx,
404
404
  textY: ly + labelHeight / 2,
405
405
  anchor: "middle"
@@ -411,10 +411,10 @@ function computeQuadrantPointLabels(points, chartBounds, obstacles, pointRadius,
411
411
  gen: (offset) => {
412
412
  const lx = pt.cx + offset;
413
413
  const ly = pt.cy - labelHeight / 2;
414
- if (lx + labelWidth > chartBounds.right || ly < chartBounds.top || ly + labelHeight > chartBounds.bottom)
414
+ if (lx + labelWidth2 > chartBounds.right || ly < chartBounds.top || ly + labelHeight > chartBounds.bottom)
415
415
  return null;
416
416
  return {
417
- rect: { x: lx, y: ly, w: labelWidth, h: labelHeight },
417
+ rect: { x: lx, y: ly, w: labelWidth2, h: labelHeight },
418
418
  textX: lx,
419
419
  textY: pt.cy,
420
420
  anchor: "start"
@@ -424,13 +424,13 @@ function computeQuadrantPointLabels(points, chartBounds, obstacles, pointRadius,
424
424
  {
425
425
  // Left
426
426
  gen: (offset) => {
427
- const lx = pt.cx - offset - labelWidth;
427
+ const lx = pt.cx - offset - labelWidth2;
428
428
  const ly = pt.cy - labelHeight / 2;
429
429
  if (lx < chartBounds.left || ly < chartBounds.top || ly + labelHeight > chartBounds.bottom)
430
430
  return null;
431
431
  return {
432
- rect: { x: lx, y: ly, w: labelWidth, h: labelHeight },
433
- textX: lx + labelWidth,
432
+ rect: { x: lx, y: ly, w: labelWidth2, h: labelHeight },
433
+ textX: lx + labelWidth2,
434
434
  textY: pt.cy,
435
435
  anchor: "end"
436
436
  };
@@ -480,10 +480,10 @@ function computeQuadrantPointLabels(points, chartBounds, obstacles, pointRadius,
480
480
  }
481
481
  }
482
482
  if (!best) {
483
- const lx = pt.cx - labelWidth / 2;
483
+ const lx = pt.cx - labelWidth2 / 2;
484
484
  const ly = pt.cy - minGap - labelHeight;
485
485
  best = {
486
- rect: { x: lx, y: ly, w: labelWidth, h: labelHeight },
486
+ rect: { x: lx, y: ly, w: labelWidth2, h: labelHeight },
487
487
  textX: pt.cx,
488
488
  textY: ly + labelHeight / 2,
489
489
  anchor: "middle",
@@ -860,6 +860,9 @@ var init_reserved_key_registry = __esm({
860
860
  "value",
861
861
  "label",
862
862
  "style"
863
+ // `surface:` was removed in the 2026-06-02 defaults-on review — it is no longer
864
+ // a recognized metadata key (the route/edge surface feature was cut; §24B.7).
865
+ // A stray `surface: water` is no longer captured as a reserved key.
863
866
  ]);
864
867
  ORG_REGISTRY = staticRegistry([
865
868
  "color",
@@ -914,9 +917,7 @@ var init_reserved_key_registry = __esm({
914
917
  BOXES_AND_LINES_REGISTRY = staticRegistry([
915
918
  "color",
916
919
  "description",
917
- "width",
918
- "split",
919
- "fanout"
920
+ "value"
920
921
  ]);
921
922
  TIMELINE_REGISTRY = staticRegistry([
922
923
  "color",
@@ -1922,77 +1923,266 @@ function getSegmentColors(palette, count) {
1922
1923
  (_, i) => hslToHex(Math.round((startHue + i * step) % 360), avgS, avgL)
1923
1924
  );
1924
1925
  }
1926
+ function politicalTints(palette, count, isDark) {
1927
+ if (count <= 0) return [];
1928
+ const base = isDark ? palette.surface : palette.bg;
1929
+ const c = palette.colors;
1930
+ const swatches = [
1931
+ .../* @__PURE__ */ new Set([
1932
+ c.green,
1933
+ c.yellow,
1934
+ c.orange,
1935
+ c.purple,
1936
+ c.red,
1937
+ c.teal,
1938
+ c.cyan,
1939
+ c.blue
1940
+ ])
1941
+ ];
1942
+ const bands = isDark ? POLITICAL_TINT_BANDS.dark : POLITICAL_TINT_BANDS.light;
1943
+ const out = [];
1944
+ for (const pct of bands) {
1945
+ if (out.length >= count) break;
1946
+ for (const s of swatches) out.push(mix(s, base, pct));
1947
+ }
1948
+ return out.slice(0, count);
1949
+ }
1950
+ var POLITICAL_TINT_BANDS;
1925
1951
  var init_color_utils = __esm({
1926
1952
  "src/palettes/color-utils.ts"() {
1927
1953
  "use strict";
1954
+ POLITICAL_TINT_BANDS = {
1955
+ light: [32, 48, 64, 80],
1956
+ dark: [44, 58, 72, 86]
1957
+ };
1928
1958
  }
1929
1959
  });
1930
1960
 
1931
- // src/palettes/bold.ts
1932
- var boldPalette;
1933
- var init_bold = __esm({
1934
- "src/palettes/bold.ts"() {
1961
+ // src/palettes/atlas.ts
1962
+ var atlasPalette;
1963
+ var init_atlas = __esm({
1964
+ "src/palettes/atlas.ts"() {
1935
1965
  "use strict";
1936
1966
  init_registry();
1937
- boldPalette = {
1938
- id: "bold",
1939
- name: "Bold",
1967
+ atlasPalette = {
1968
+ id: "atlas",
1969
+ name: "Atlas",
1940
1970
  light: {
1941
- bg: "#ffffff",
1942
- surface: "#f0f0f0",
1943
- overlay: "#f0f0f0",
1944
- border: "#cccccc",
1945
- text: "#000000",
1946
- textMuted: "#666666",
1947
- textOnFillLight: "#ffffff",
1948
- textOnFillDark: "#000000",
1949
- primary: "#0000ff",
1950
- secondary: "#ff00ff",
1951
- accent: "#00cccc",
1952
- destructive: "#ff0000",
1971
+ bg: "#f3ead3",
1972
+ // warm manila / parchment
1973
+ surface: "#ece0c0",
1974
+ // deeper paper (cards, panels)
1975
+ overlay: "#e8dab8",
1976
+ // popovers, dropdowns
1977
+ border: "#bcaa86",
1978
+ // muted sepia rule line
1979
+ text: "#463a26",
1980
+ // aged sepia-brown ink
1981
+ textMuted: "#7a6a4f",
1982
+ // faded annotation ink
1983
+ textOnFillLight: "#f7f1de",
1984
+ // parchment (light text on dark fills)
1985
+ textOnFillDark: "#3a2e1c",
1986
+ // deep ink (dark text on light fills)
1987
+ primary: "#5b7a99",
1988
+ // pull-down map ocean (steel-blue)
1989
+ secondary: "#7e9a6f",
1990
+ // lowland sage / celadon
1991
+ accent: "#b07f7c",
1992
+ // dusty rose
1993
+ destructive: "#b25a45",
1994
+ // brick / terracotta
1953
1995
  colors: {
1954
- red: "#ff0000",
1955
- orange: "#ff8000",
1956
- yellow: "#ffcc00",
1957
- green: "#00cc00",
1958
- blue: "#0000ff",
1959
- purple: "#cc00cc",
1960
- teal: "#008080",
1961
- cyan: "#00cccc",
1962
- gray: "#808080",
1963
- black: "#000000",
1964
- white: "#f0f0f0"
1996
+ red: "#bf6a52",
1997
+ // terracotta brick
1998
+ orange: "#cf9a5c",
1999
+ // map tan / ochre
2000
+ yellow: "#cdb35e",
2001
+ // straw / muted lemon
2002
+ green: "#7e9a6f",
2003
+ // sage / celadon lowland
2004
+ blue: "#5b7a99",
2005
+ // steel-blue ocean
2006
+ purple: "#9a7fa6",
2007
+ // dusty lilac / mauve
2008
+ teal: "#6fa094",
2009
+ // muted seafoam
2010
+ cyan: "#79a7b5",
2011
+ // shallow-water blue
2012
+ gray: "#8a7d68",
2013
+ // warm taupe
2014
+ black: "#463a26",
2015
+ // ink
2016
+ white: "#ece0c0"
2017
+ // paper
1965
2018
  }
1966
2019
  },
1967
2020
  dark: {
1968
- bg: "#000000",
1969
- surface: "#111111",
1970
- overlay: "#1a1a1a",
1971
- border: "#333333",
1972
- text: "#ffffff",
1973
- textMuted: "#aaaaaa",
1974
- textOnFillLight: "#ffffff",
1975
- textOnFillDark: "#000000",
1976
- primary: "#00ccff",
1977
- secondary: "#ff00ff",
1978
- accent: "#ffff00",
1979
- destructive: "#ff0000",
2021
+ bg: "#1e2a33",
2022
+ // deep map ocean (night globe)
2023
+ surface: "#27353f",
2024
+ // raised ocean
2025
+ overlay: "#2e3d48",
2026
+ // popovers, dropdowns
2027
+ border: "#3d4f5c",
2028
+ // depth-contour line
2029
+ text: "#e8dcc0",
2030
+ // parchment ink, inverted
2031
+ textMuted: "#a89a7d",
2032
+ // faded label
2033
+ textOnFillLight: "#f7f1de",
2034
+ // parchment
2035
+ textOnFillDark: "#1a242c",
2036
+ // deep ocean ink
2037
+ primary: "#7ba0bf",
2038
+ // brighter ocean
2039
+ secondary: "#9bb588",
2040
+ // sage, lifted
2041
+ accent: "#cf9a96",
2042
+ // dusty rose, lifted
2043
+ destructive: "#c9745c",
2044
+ // brick, lifted
2045
+ colors: {
2046
+ red: "#cf7a60",
2047
+ // terracotta
2048
+ orange: "#d9a96a",
2049
+ // tan / ochre
2050
+ yellow: "#d8c074",
2051
+ // straw
2052
+ green: "#9bb588",
2053
+ // sage lowland
2054
+ blue: "#7ba0bf",
2055
+ // ocean
2056
+ purple: "#b59ac0",
2057
+ // lilac / mauve
2058
+ teal: "#85b3a6",
2059
+ // seafoam
2060
+ cyan: "#92bccb",
2061
+ // shallow-water blue
2062
+ gray: "#9a8d76",
2063
+ // warm taupe
2064
+ black: "#27353f",
2065
+ // raised ocean
2066
+ white: "#e8dcc0"
2067
+ // parchment
2068
+ }
2069
+ }
2070
+ };
2071
+ registerPalette(atlasPalette);
2072
+ }
2073
+ });
2074
+
2075
+ // src/palettes/blueprint.ts
2076
+ var blueprintPalette;
2077
+ var init_blueprint = __esm({
2078
+ "src/palettes/blueprint.ts"() {
2079
+ "use strict";
2080
+ init_registry();
2081
+ blueprintPalette = {
2082
+ id: "blueprint",
2083
+ name: "Blueprint",
2084
+ light: {
2085
+ bg: "#f4f8fb",
2086
+ // pale drafting white (faint cyan)
2087
+ surface: "#e6eef4",
2088
+ // drafting panel
2089
+ overlay: "#dde9f1",
2090
+ // popovers, dropdowns
2091
+ border: "#aac3d6",
2092
+ // pale blue grid line
2093
+ text: "#123a5e",
2094
+ // blueprint navy ink
2095
+ textMuted: "#4f7390",
2096
+ // faint draft note
2097
+ textOnFillLight: "#f4f8fb",
2098
+ // drafting white
2099
+ textOnFillDark: "#0c2f4d",
2100
+ // deep blueprint navy
2101
+ primary: "#1f5e8c",
2102
+ // blueprint blue
2103
+ secondary: "#5b7d96",
2104
+ // steel
2105
+ accent: "#b08a3e",
2106
+ // draftsman's ochre highlight
2107
+ destructive: "#c0504d",
2108
+ // correction red
2109
+ colors: {
2110
+ red: "#c25a4e",
2111
+ // correction red
2112
+ orange: "#c2823e",
2113
+ // ochre
2114
+ yellow: "#c2a843",
2115
+ // pencil gold
2116
+ green: "#4f8a6b",
2117
+ // drafting green
2118
+ blue: "#1f5e8c",
2119
+ // blueprint blue
2120
+ purple: "#6f5e96",
2121
+ // indigo pencil
2122
+ teal: "#3a8a8a",
2123
+ // teal
2124
+ cyan: "#3f8fb5",
2125
+ // cyan
2126
+ gray: "#7e8e98",
2127
+ // graphite
2128
+ black: "#123a5e",
2129
+ // navy ink
2130
+ white: "#e6eef4"
2131
+ // panel
2132
+ }
2133
+ },
2134
+ dark: {
2135
+ bg: "#103a5e",
2136
+ // deep blueprint blue (cyanotype ground)
2137
+ surface: "#16466e",
2138
+ // raised sheet
2139
+ overlay: "#1c5180",
2140
+ // popovers, dropdowns
2141
+ border: "#3a6f96",
2142
+ // grid line
2143
+ text: "#eaf2f8",
2144
+ // chalk white
2145
+ textMuted: "#9fc0d6",
2146
+ // faint chalk note
2147
+ textOnFillLight: "#eaf2f8",
2148
+ // chalk white
2149
+ textOnFillDark: "#0c2f4d",
2150
+ // deep blueprint navy
2151
+ primary: "#7fb8d8",
2152
+ // chalk cyan
2153
+ secondary: "#9fb8c8",
2154
+ // pale steel
2155
+ accent: "#d8c27a",
2156
+ // chalk amber
2157
+ destructive: "#e08a7a",
2158
+ // chalk correction red
1980
2159
  colors: {
1981
- red: "#ff0000",
1982
- orange: "#ff8000",
1983
- yellow: "#ffff00",
1984
- green: "#00ff00",
1985
- blue: "#0066ff",
1986
- purple: "#ff00ff",
1987
- teal: "#00cccc",
1988
- cyan: "#00ffff",
1989
- gray: "#808080",
1990
- black: "#111111",
1991
- white: "#ffffff"
2160
+ red: "#e0907e",
2161
+ // chalk red
2162
+ orange: "#e0ab78",
2163
+ // chalk amber
2164
+ yellow: "#e3d089",
2165
+ // chalk gold
2166
+ green: "#93c79e",
2167
+ // chalk green
2168
+ blue: "#8ec3e0",
2169
+ // chalk cyan-blue
2170
+ purple: "#b6a6d8",
2171
+ // chalk indigo
2172
+ teal: "#84c7c2",
2173
+ // chalk teal
2174
+ cyan: "#9fd6e0",
2175
+ // chalk cyan
2176
+ gray: "#aebecb",
2177
+ // chalk graphite
2178
+ black: "#16466e",
2179
+ // raised sheet
2180
+ white: "#eaf2f8"
2181
+ // chalk white
1992
2182
  }
1993
2183
  }
1994
2184
  };
1995
- registerPalette(boldPalette);
2185
+ registerPalette(blueprintPalette);
1996
2186
  }
1997
2187
  });
1998
2188
 
@@ -2489,6 +2679,120 @@ var init_rose_pine = __esm({
2489
2679
  }
2490
2680
  });
2491
2681
 
2682
+ // src/palettes/slate.ts
2683
+ var slatePalette;
2684
+ var init_slate = __esm({
2685
+ "src/palettes/slate.ts"() {
2686
+ "use strict";
2687
+ init_registry();
2688
+ slatePalette = {
2689
+ id: "slate",
2690
+ name: "Slate",
2691
+ light: {
2692
+ bg: "#ffffff",
2693
+ // clean slide white
2694
+ surface: "#f3f5f8",
2695
+ // light cool-gray panel
2696
+ overlay: "#eaeef3",
2697
+ // popovers, dropdowns
2698
+ border: "#d4dae1",
2699
+ // hairline rule
2700
+ text: "#1f2933",
2701
+ // near-black slate (softer than pure black)
2702
+ textMuted: "#5b6672",
2703
+ // secondary label
2704
+ textOnFillLight: "#ffffff",
2705
+ // light text on dark fills
2706
+ textOnFillDark: "#1f2933",
2707
+ // dark text on light fills
2708
+ primary: "#3b6ea5",
2709
+ // confident corporate blue
2710
+ secondary: "#5b6672",
2711
+ // slate gray
2712
+ accent: "#3a9188",
2713
+ // muted teal accent
2714
+ destructive: "#c0504d",
2715
+ // brick red
2716
+ colors: {
2717
+ red: "#c0504d",
2718
+ // brick
2719
+ orange: "#cc7a33",
2720
+ // muted amber
2721
+ yellow: "#c9a227",
2722
+ // gold (not neon)
2723
+ green: "#5b9357",
2724
+ // forest / sage
2725
+ blue: "#3b6ea5",
2726
+ // corporate blue
2727
+ purple: "#7d5ba6",
2728
+ // muted violet
2729
+ teal: "#3a9188",
2730
+ // teal
2731
+ cyan: "#4f96c4",
2732
+ // steel cyan
2733
+ gray: "#7e8a97",
2734
+ // cool gray
2735
+ black: "#1f2933",
2736
+ // slate ink
2737
+ white: "#f3f5f8"
2738
+ // panel
2739
+ }
2740
+ },
2741
+ dark: {
2742
+ bg: "#161b22",
2743
+ // deep slate (keynote dark)
2744
+ surface: "#202833",
2745
+ // raised panel
2746
+ overlay: "#29323e",
2747
+ // popovers, dropdowns
2748
+ border: "#38424f",
2749
+ // divider
2750
+ text: "#e6eaef",
2751
+ // off-white
2752
+ textMuted: "#9aa5b1",
2753
+ // secondary label
2754
+ textOnFillLight: "#ffffff",
2755
+ // light text on dark fills
2756
+ textOnFillDark: "#161b22",
2757
+ // dark text on light fills
2758
+ primary: "#5b9bd5",
2759
+ // lifted corporate blue
2760
+ secondary: "#8593a3",
2761
+ // slate gray, lifted
2762
+ accent: "#45b3a3",
2763
+ // teal, lifted
2764
+ destructive: "#e07b6e",
2765
+ // brick, lifted
2766
+ colors: {
2767
+ red: "#e07b6e",
2768
+ // brick
2769
+ orange: "#e0975a",
2770
+ // amber
2771
+ yellow: "#d9bd5a",
2772
+ // gold
2773
+ green: "#74b56e",
2774
+ // forest / sage
2775
+ blue: "#5b9bd5",
2776
+ // corporate blue
2777
+ purple: "#a585c9",
2778
+ // violet
2779
+ teal: "#45b3a3",
2780
+ // teal
2781
+ cyan: "#62b0d9",
2782
+ // steel cyan
2783
+ gray: "#95a1ae",
2784
+ // cool gray
2785
+ black: "#202833",
2786
+ // raised panel
2787
+ white: "#e6eaef"
2788
+ // off-white
2789
+ }
2790
+ }
2791
+ };
2792
+ registerPalette(slatePalette);
2793
+ }
2794
+ });
2795
+
2492
2796
  // src/palettes/solarized.ts
2493
2797
  var solarizedPalette;
2494
2798
  var init_solarized = __esm({
@@ -2584,6 +2888,120 @@ var init_solarized = __esm({
2584
2888
  }
2585
2889
  });
2586
2890
 
2891
+ // src/palettes/tidewater.ts
2892
+ var tidewaterPalette;
2893
+ var init_tidewater = __esm({
2894
+ "src/palettes/tidewater.ts"() {
2895
+ "use strict";
2896
+ init_registry();
2897
+ tidewaterPalette = {
2898
+ id: "tidewater",
2899
+ name: "Tidewater",
2900
+ light: {
2901
+ bg: "#eceff0",
2902
+ // weathered sea-mist paper
2903
+ surface: "#e0e4e3",
2904
+ // worn deck panel
2905
+ overlay: "#dadfdf",
2906
+ // popovers, dropdowns
2907
+ border: "#a9b2b3",
2908
+ // muted slate rule
2909
+ text: "#18313f",
2910
+ // ship's-log navy ink
2911
+ textMuted: "#51636b",
2912
+ // faded log entry
2913
+ textOnFillLight: "#f3f5f3",
2914
+ // weathered white
2915
+ textOnFillDark: "#162c38",
2916
+ // deep navy
2917
+ primary: "#1f4e6b",
2918
+ // deep-sea navy
2919
+ secondary: "#b08a4f",
2920
+ // rope / manila tan
2921
+ accent: "#c69a3e",
2922
+ // brass
2923
+ destructive: "#c1433a",
2924
+ // signal-flag red
2925
+ colors: {
2926
+ red: "#c1433a",
2927
+ // signal-flag red
2928
+ orange: "#cc7a38",
2929
+ // weathered amber
2930
+ yellow: "#d6bf5a",
2931
+ // brass gold
2932
+ green: "#4f8a6b",
2933
+ // sea-glass green
2934
+ blue: "#1f4e6b",
2935
+ // deep-sea navy
2936
+ purple: "#6a5a8c",
2937
+ // twilight harbor
2938
+ teal: "#3d8c8c",
2939
+ // sea-glass teal
2940
+ cyan: "#4f9bb5",
2941
+ // shallow water
2942
+ gray: "#8a8d86",
2943
+ // driftwood gray
2944
+ black: "#18313f",
2945
+ // navy ink
2946
+ white: "#e0e4e3"
2947
+ // deck panel
2948
+ }
2949
+ },
2950
+ dark: {
2951
+ bg: "#0f2230",
2952
+ // night-harbor deep sea
2953
+ surface: "#16303f",
2954
+ // raised hull
2955
+ overlay: "#1d3a4a",
2956
+ // popovers, dropdowns
2957
+ border: "#2c4856",
2958
+ // rigging line
2959
+ text: "#e6ebe8",
2960
+ // weathered white
2961
+ textMuted: "#9aaab0",
2962
+ // faded label
2963
+ textOnFillLight: "#f3f5f3",
2964
+ // weathered white
2965
+ textOnFillDark: "#0f2230",
2966
+ // deep sea
2967
+ primary: "#4f9bc4",
2968
+ // lifted sea blue
2969
+ secondary: "#c9a46a",
2970
+ // rope tan, lifted
2971
+ accent: "#d9b25a",
2972
+ // brass, lifted
2973
+ destructive: "#e06a5e",
2974
+ // signal red, lifted
2975
+ colors: {
2976
+ red: "#e06a5e",
2977
+ // signal-flag red
2978
+ orange: "#df9a52",
2979
+ // amber
2980
+ yellow: "#e0c662",
2981
+ // brass gold
2982
+ green: "#6fb58c",
2983
+ // sea-glass green
2984
+ blue: "#4f9bc4",
2985
+ // sea blue
2986
+ purple: "#9486bf",
2987
+ // twilight harbor
2988
+ teal: "#5cb0ac",
2989
+ // sea-glass teal
2990
+ cyan: "#62b4cf",
2991
+ // shallow water
2992
+ gray: "#9aa39c",
2993
+ // driftwood gray
2994
+ black: "#16303f",
2995
+ // raised hull
2996
+ white: "#e6ebe8"
2997
+ // weathered white
2998
+ }
2999
+ }
3000
+ };
3001
+ registerPalette(tidewaterPalette);
3002
+ }
3003
+ });
3004
+
2587
3005
  // src/palettes/tokyo-night.ts
2588
3006
  var tokyoNightPalette;
2589
3007
  var init_tokyo_night = __esm({
@@ -2859,7 +3277,8 @@ var init_monokai = __esm({
2859
3277
  // src/palettes/index.ts
2860
3278
  var palettes_exports = {};
2861
3279
  __export(palettes_exports, {
2862
- boldPalette: () => boldPalette,
3280
+ atlasPalette: () => atlasPalette,
3281
+ blueprintPalette: () => blueprintPalette,
2863
3282
  catppuccinPalette: () => catppuccinPalette,
2864
3283
  contrastText: () => contrastText,
2865
3284
  draculaPalette: () => draculaPalette,
@@ -2880,7 +3299,9 @@ __export(palettes_exports, {
2880
3299
  rosePinePalette: () => rosePinePalette,
2881
3300
  shade: () => shade,
2882
3301
  shapeFill: () => shapeFill,
3302
+ slatePalette: () => slatePalette,
2883
3303
  solarizedPalette: () => solarizedPalette,
3304
+ tidewaterPalette: () => tidewaterPalette,
2884
3305
  tint: () => tint,
2885
3306
  tokyoNightPalette: () => tokyoNightPalette
2886
3307
  });
@@ -2890,17 +3311,21 @@ var init_palettes = __esm({
2890
3311
  "use strict";
2891
3312
  init_registry();
2892
3313
  init_color_utils();
2893
- init_bold();
3314
+ init_atlas();
3315
+ init_blueprint();
2894
3316
  init_catppuccin();
2895
3317
  init_gruvbox();
2896
3318
  init_nord();
2897
3319
  init_one_dark();
2898
3320
  init_rose_pine();
3321
+ init_slate();
2899
3322
  init_solarized();
3323
+ init_tidewater();
2900
3324
  init_tokyo_night();
2901
3325
  init_dracula();
2902
3326
  init_monokai();
2903
- init_bold();
3327
+ init_atlas();
3328
+ init_blueprint();
2904
3329
  init_catppuccin();
2905
3330
  init_dracula();
2906
3331
  init_gruvbox();
@@ -2908,9 +3333,15 @@ var init_palettes = __esm({
2908
3333
  init_nord();
2909
3334
  init_one_dark();
2910
3335
  init_rose_pine();
3336
+ init_slate();
2911
3337
  init_solarized();
3338
+ init_tidewater();
2912
3339
  init_tokyo_night();
2913
3340
  palettes = {
3341
+ atlas: atlasPalette,
3342
+ blueprint: blueprintPalette,
3343
+ slate: slatePalette,
3344
+ tidewater: tidewaterPalette,
2914
3345
  nord: nordPalette,
2915
3346
  catppuccin: catppuccinPalette,
2916
3347
  solarized: solarizedPalette,
@@ -2919,8 +3350,7 @@ var init_palettes = __esm({
2919
3350
  oneDark: oneDarkPalette,
2920
3351
  rosePine: rosePinePalette,
2921
3352
  dracula: draculaPalette,
2922
- monokai: monokaiPalette,
2923
- bold: boldPalette
3353
+ monokai: monokaiPalette
2924
3354
  };
2925
3355
  }
2926
3356
  });
@@ -3430,6 +3860,9 @@ function controlsGroupCapsuleWidth(toggles) {
3430
3860
  }
3431
3861
  return w;
3432
3862
  }
3863
+ function isAppHostedControls(config, isExport) {
3864
+ return !isExport && config.controlsHost === "app" && !!config.controlsGroup && config.controlsGroup.toggles.length > 0;
3865
+ }
3433
3866
  function buildControlsGroupLayout(config, state) {
3434
3867
  const cg = config.controlsGroup;
3435
3868
  if (!cg || cg.toggles.length === 0) return void 0;
@@ -3483,6 +3916,7 @@ function buildControlsGroupLayout(config, state) {
3483
3916
  function computeLegendLayout(config, state, containerWidth) {
3484
3917
  const { groups, controls: configControls, mode } = config;
3485
3918
  const isExport = mode === "export";
3919
+ const gated = isAppHostedControls(config, isExport);
3486
3920
  const activeGroupName = state.activeGroup?.toLowerCase() ?? null;
3487
3921
  if (isExport && !activeGroupName) {
3488
3922
  return {
@@ -3493,7 +3927,7 @@ function computeLegendLayout(config, state, containerWidth) {
3493
3927
  pills: []
3494
3928
  };
3495
3929
  }
3496
- const controlsGroupLayout = isExport ? void 0 : buildControlsGroupLayout(config, state);
3930
+ const controlsGroupLayout = isExport || gated ? void 0 : buildControlsGroupLayout(config, state);
3497
3931
  const visibleGroups = config.showEmptyGroups ? groups : groups.filter((g) => g.entries.length > 0 || !!g.gradient);
3498
3932
  if (visibleGroups.length === 0 && (!configControls || configControls.length === 0) && !controlsGroupLayout) {
3499
3933
  return {
@@ -8325,8 +8759,8 @@ function computeScatterLabelGraphics(points, chartBounds, fontSize, symbolSize,
8325
8759
  const pt = points[i];
8326
8760
  const ptSize = pt.size ?? symbolSize;
8327
8761
  const minGap = ptSize / 2 + 4;
8328
- const labelWidth = pt.name.length * fontSize * 0.6 + 8;
8329
- const labelX = pt.px - labelWidth / 2;
8762
+ const labelWidth2 = pt.name.length * fontSize * 0.6 + 8;
8763
+ const labelX = pt.px - labelWidth2 / 2;
8330
8764
  let bestLabelY = 0;
8331
8765
  let bestOffset = Infinity;
8332
8766
  let placed = false;
@@ -8338,7 +8772,7 @@ function computeScatterLabelGraphics(points, chartBounds, fontSize, symbolSize,
8338
8772
  const candidate = {
8339
8773
  x: labelX,
8340
8774
  y: labelY,
8341
- w: labelWidth,
8775
+ w: labelWidth2,
8342
8776
  h: labelHeight
8343
8777
  };
8344
8778
  let collision = false;
@@ -8380,7 +8814,7 @@ function computeScatterLabelGraphics(points, chartBounds, fontSize, symbolSize,
8380
8814
  const labelRect = {
8381
8815
  x: labelX,
8382
8816
  y: bestLabelY,
8383
- w: labelWidth,
8817
+ w: labelWidth2,
8384
8818
  h: labelHeight
8385
8819
  };
8386
8820
  placedLabels.push(labelRect);
@@ -8416,7 +8850,7 @@ function computeScatterLabelGraphics(points, chartBounds, fontSize, symbolSize,
8416
8850
  shape: {
8417
8851
  x: labelX - bgPad,
8418
8852
  y: bestLabelY - bgPad,
8419
- width: labelWidth + bgPad * 2,
8853
+ width: labelWidth2 + bgPad * 2,
8420
8854
  height: labelHeight + bgPad * 2
8421
8855
  },
8422
8856
  style: { fill: bg },
@@ -15856,10 +16290,6 @@ function parseMap(content) {
15856
16290
  handleTag(trimmed, lineNumber);
15857
16291
  continue;
15858
16292
  }
15859
- if ((firstWord === "muted" || firstWord === "natural") && trimmed === firstWord) {
15860
- handleDirective(firstWord, "", lineNumber);
15861
- continue;
15862
- }
15863
16293
  if (DIRECTIVE_SET.has(firstWord) && !trimmed.slice(firstWord.length).trimStart().startsWith(":")) {
15864
16294
  handleDirective(
15865
16295
  firstWord,
@@ -15906,24 +16336,6 @@ function parseMap(content) {
15906
16336
  pushWarning(line12, `Duplicate directive "${key}" \u2014 last value wins.`);
15907
16337
  };
15908
16338
  switch (key) {
15909
- case "region":
15910
- dup(d.region);
15911
- d.region = value;
15912
- break;
15913
- case "projection":
15914
- dup(d.projection);
15915
- if (value && ![
15916
- "equirectangular",
15917
- "natural-earth",
15918
- "albers-usa",
15919
- "mercator"
15920
- ].includes(value))
15921
- pushWarning(
15922
- line12,
15923
- `Unknown projection "${value}" (expected equirectangular | natural-earth | albers-usa | mercator).`
15924
- );
15925
- d.projection = value;
15926
- break;
15927
16339
  case "region-metric": {
15928
16340
  dup(d.regionMetric);
15929
16341
  const { label: rmLabel, colorName: rmColor } = peelTrailingColorName(value);
@@ -15939,91 +16351,43 @@ function parseMap(content) {
15939
16351
  dup(d.flowMetric);
15940
16352
  d.flowMetric = value;
15941
16353
  break;
15942
- case "scale":
15943
- dup(d.scale);
15944
- {
15945
- const s = parseScale(value, line12);
15946
- if (s) d.scale = s;
15947
- }
15948
- break;
15949
- case "region-labels":
15950
- dup(d.regionLabels);
15951
- if (value && !["full", "abbrev", "off"].includes(value))
15952
- pushWarning(
15953
- line12,
15954
- `Unknown region-labels "${value}" (expected full | abbrev | off).`
15955
- );
15956
- d.regionLabels = value;
15957
- break;
15958
- case "poi-labels":
15959
- dup(d.poiLabels);
15960
- if (value && !["off", "auto", "all"].includes(value))
15961
- pushWarning(
15962
- line12,
15963
- `Unknown poi-labels "${value}" (expected off | auto | all).`
15964
- );
15965
- d.poiLabels = value;
15966
- break;
15967
- case "default-country":
15968
- dup(d.defaultCountry);
15969
- d.defaultCountry = value;
15970
- break;
15971
- case "default-state":
15972
- dup(d.defaultState);
15973
- d.defaultState = value;
16354
+ case "locale":
16355
+ dup(d.locale);
16356
+ d.locale = value;
15974
16357
  break;
15975
16358
  case "active-tag":
15976
16359
  dup(d.activeTag);
15977
16360
  d.activeTag = value;
15978
16361
  break;
16362
+ case "caption":
16363
+ dup(d.caption);
16364
+ d.caption = value;
16365
+ break;
16366
+ // ── Cosmetic `no-*` opt-outs: bare flags, idempotent (mirror `no-legend`,
16367
+ // no dup warning); each defaults the feature ON when absent. ──
15979
16368
  case "no-legend":
15980
16369
  d.noLegend = true;
15981
16370
  break;
15982
- case "no-insets":
15983
- d.noInsets = true;
16371
+ case "no-coastline":
16372
+ d.noCoastline = true;
15984
16373
  break;
15985
- case "relief":
15986
- d.relief = true;
16374
+ case "no-relief":
16375
+ d.noRelief = true;
15987
16376
  break;
15988
- case "muted":
15989
- case "natural":
15990
- if (d.basemapStyle !== void 0 && d.basemapStyle !== key)
15991
- pushWarning(
15992
- line12,
15993
- `Conflicting basemap dress \u2014 "${d.basemapStyle}" then "${key}"; last wins.`
15994
- );
15995
- d.basemapStyle = key;
16377
+ case "no-context-labels":
16378
+ d.noContextLabels = true;
15996
16379
  break;
15997
- case "subtitle":
15998
- dup(d.subtitle);
15999
- d.subtitle = value;
16380
+ case "no-region-labels":
16381
+ d.noRegionLabels = true;
16000
16382
  break;
16001
- case "caption":
16002
- dup(d.caption);
16003
- d.caption = value;
16383
+ case "no-poi-labels":
16384
+ d.noPoiLabels = true;
16385
+ break;
16386
+ case "no-colorize":
16387
+ d.noColorize = true;
16004
16388
  break;
16005
16389
  }
16006
16390
  }
16007
- function parseScale(value, line12) {
16008
- const toks = value.split(/\s+/).filter(Boolean);
16009
- const min = Number(toks[0]);
16010
- const max = Number(toks[1]);
16011
- if (!Number.isFinite(min) || !Number.isFinite(max)) {
16012
- pushError(line12, `scale requires numeric <min> <max> (got "${value}").`);
16013
- return null;
16014
- }
16015
- const scale = { min, max };
16016
- if (toks[2] === "center") {
16017
- const c = Number(toks[3]);
16018
- if (Number.isFinite(c)) scale.center = c;
16019
- else
16020
- pushError(
16021
- line12,
16022
- `scale center requires a number (got "${toks[3] ?? ""}").`
16023
- );
16024
- }
16025
- return scale;
16026
- }
16027
16391
  function handleTag(trimmed, line12) {
16028
16392
  const m = matchTagBlockHeading(trimmed);
16029
16393
  if (!m) {
@@ -16223,13 +16587,15 @@ function parseMap(content) {
16223
16587
  pushError(line12, `Edge has an empty endpoint: "${trimmed}".`);
16224
16588
  continue;
16225
16589
  }
16226
- const meta = k === links.length - 1 ? lastSplit.meta : {};
16590
+ const isLast = k === links.length - 1;
16591
+ const meta = isLast ? lastSplit.meta : {};
16592
+ const style = links[k].style === "arc" ? "arc" : "straight";
16227
16593
  edges.push({
16228
16594
  from,
16229
16595
  to,
16230
16596
  ...links[k].label !== void 0 && { label: links[k].label },
16231
16597
  directed: links[k].directed,
16232
- style: links[k].style,
16598
+ style,
16233
16599
  meta,
16234
16600
  lineNumber: line12
16235
16601
  });
@@ -16315,22 +16681,19 @@ var init_parser12 = __esm({
16315
16681
  LEG_ARROW_RE = /^(-[^>]*?->|->|~[^>]*?~>|~>|--)\s+(.+)$/;
16316
16682
  AT_RE = /(^|[\s,])at\s*:/i;
16317
16683
  DIRECTIVE_SET = /* @__PURE__ */ new Set([
16318
- "region",
16319
- "projection",
16320
16684
  "region-metric",
16321
16685
  "poi-metric",
16322
16686
  "flow-metric",
16323
- "scale",
16324
- "region-labels",
16325
- "poi-labels",
16326
- "default-country",
16327
- "default-state",
16687
+ "locale",
16328
16688
  "active-tag",
16689
+ "caption",
16329
16690
  "no-legend",
16330
- "no-insets",
16331
- "relief",
16332
- "subtitle",
16333
- "caption"
16691
+ "no-coastline",
16692
+ "no-relief",
16693
+ "no-context-labels",
16694
+ "no-region-labels",
16695
+ "no-poi-labels",
16696
+ "no-colorize"
16334
16697
  ]);
16335
16698
  }
16336
16699
  });
@@ -16508,6 +16871,21 @@ function parseBoxesAndLines(content) {
16508
16871
  }
16509
16872
  continue;
16510
16873
  }
16874
+ if (!contentStarted) {
16875
+ const metricMatch = trimmed.match(/^box-metric\s+(.+)$/i);
16876
+ if (metricMatch) {
16877
+ const { label, colorName } = peelTrailingColorName(
16878
+ metricMatch[1].trim()
16879
+ );
16880
+ result.boxMetric = label;
16881
+ if (colorName !== void 0) result.boxMetricColor = colorName;
16882
+ continue;
16883
+ }
16884
+ if (/^show-values$/i.test(trimmed)) {
16885
+ result.showValues = true;
16886
+ continue;
16887
+ }
16888
+ }
16511
16889
  if (!contentStarted) {
16512
16890
  const optMatch = trimmed.match(OPTION_NOCOLON_RE);
16513
16891
  if (optMatch) {
@@ -16886,6 +17264,19 @@ function parseNodeLine(trimmed, lineNum, metaAliasMap, diagnostics, nameAliasMap
16886
17264
  description = [metadata["description"]];
16887
17265
  delete metadata["description"];
16888
17266
  }
17267
+ let value;
17268
+ if (metadata["value"] !== void 0) {
17269
+ const raw = metadata["value"];
17270
+ const num = Number(raw);
17271
+ if (Number.isFinite(num)) {
17272
+ value = num;
17273
+ } else {
17274
+ diagnostics.push(
17275
+ makeDgmoError(lineNum, `value must be a number (got "${raw}")`, "error")
17276
+ );
17277
+ }
17278
+ delete metadata["value"];
17279
+ }
16889
17280
  if (split.alias) {
16890
17281
  nameAliasMap?.set(normalizeName(split.alias), label);
16891
17282
  }
@@ -16894,7 +17285,8 @@ function parseNodeLine(trimmed, lineNum, metaAliasMap, diagnostics, nameAliasMap
16894
17285
  label,
16895
17286
  lineNumber: lineNum,
16896
17287
  metadata,
16897
- ...description !== void 0 && { description }
17288
+ ...description !== void 0 && { description },
17289
+ ...value !== void 0 && { value }
16898
17290
  };
16899
17291
  }
16900
17292
  function splitTargetAndMeta(rest, metaAliasMap) {
@@ -24341,8 +24733,8 @@ function renderKanban(container, parsed, palette, isDark, options) {
24341
24733
  let metaY = separatorY + sCardSeparatorGap + sCardMetaFontSize;
24342
24734
  for (const meta of tagMeta) {
24343
24735
  cg.append("text").attr("x", cx + sCardPaddingX).attr("y", metaY).attr("font-size", sCardMetaFontSize).attr("fill", onCardText).text(`${meta.label}: `);
24344
- const labelWidth = (meta.label.length + 2) * sCardMetaFontSize * 0.6;
24345
- cg.append("text").attr("x", cx + sCardPaddingX + labelWidth).attr("y", metaY).attr("font-size", sCardMetaFontSize).attr("fill", onCardText).text(meta.value);
24736
+ const labelWidth2 = (meta.label.length + 2) * sCardMetaFontSize * 0.6;
24737
+ cg.append("text").attr("x", cx + sCardPaddingX + labelWidth2).attr("y", metaY).attr("font-size", sCardMetaFontSize).attr("fill", onCardText).text(meta.value);
24346
24738
  metaY += sCardMetaLineHeight;
24347
24739
  }
24348
24740
  for (const detail of card.details) {
@@ -24686,8 +25078,8 @@ function renderSwimlaneCard(parent, cardLayout, tagGroups, activeTagGroup, palet
24686
25078
  let metaY = separatorY + sCardSeparatorGap + sCardMetaFontSize;
24687
25079
  for (const meta of tagMeta) {
24688
25080
  cg.append("text").attr("x", cx + sCardPaddingX).attr("y", metaY).attr("font-size", sCardMetaFontSize).attr("fill", palette.textMuted).text(`${meta.label}: `);
24689
- const labelWidth = (meta.label.length + 2) * sCardMetaFontSize * 0.6;
24690
- cg.append("text").attr("x", cx + sCardPaddingX + labelWidth).attr("y", metaY).attr("font-size", sCardMetaFontSize).attr("fill", onCardText).text(meta.value);
25081
+ const labelWidth2 = (meta.label.length + 2) * sCardMetaFontSize * 0.6;
25082
+ cg.append("text").attr("x", cx + sCardPaddingX + labelWidth2).attr("y", metaY).attr("font-size", sCardMetaFontSize).attr("fill", onCardText).text(meta.value);
24691
25083
  metaY += sCardMetaLineHeight;
24692
25084
  }
24693
25085
  for (const detail of card.details) {
@@ -25522,8 +25914,8 @@ function classifyEREntities(tables, relationships) {
25522
25914
  }
25523
25915
  }
25524
25916
  const mmParticipants = /* @__PURE__ */ new Set();
25525
- for (const [id, neighbors] of tableStarNeighbors) {
25526
- if (neighbors.size >= 2) mmParticipants.add(id);
25917
+ for (const [id, neighbors2] of tableStarNeighbors) {
25918
+ if (neighbors2.size >= 2) mmParticipants.add(id);
25527
25919
  }
25528
25920
  const indegreeValues = Object.values(indegreeMap);
25529
25921
  const mean = indegreeValues.reduce((a, b) => a + b, 0) / indegreeValues.length;
@@ -26104,7 +26496,18 @@ function fitLabelToHeader(label, nodeWidth, maxLines) {
26104
26496
  const truncated = label.length > maxChars ? label.slice(0, maxChars - 1) + "\u2026" : label;
26105
26497
  return { lines: [truncated], fontSize: MIN_NODE_FONT_SIZE };
26106
26498
  }
26107
- function nodeColors(node, tagGroups, activeGroupName, palette, isDark, solid) {
26499
+ function nodeColors(node, tagGroups, activeGroupName, palette, isDark, value, solid) {
26500
+ const neutralFill = mix(palette.bg, palette.text, isDark ? 90 : 95);
26501
+ if (value.active) {
26502
+ const fill3 = node.value !== void 0 ? value.fillForValue(node.value) : neutralFill;
26503
+ const stroke3 = value.hue;
26504
+ const text2 = contrastText(
26505
+ fill3,
26506
+ palette.textOnFillLight,
26507
+ palette.textOnFillDark
26508
+ );
26509
+ return { fill: fill3, stroke: stroke3, text: text2 };
26510
+ }
26108
26511
  const tagColor = resolveTagColor(
26109
26512
  node.metadata,
26110
26513
  [...tagGroups],
@@ -26194,7 +26597,8 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26194
26597
  controlsExpanded,
26195
26598
  onToggleDescriptions,
26196
26599
  onToggleControlsExpand,
26197
- exportMode = false
26600
+ exportMode = false,
26601
+ controlsHost
26198
26602
  } = options ?? {};
26199
26603
  d3Selection6.select(container).selectAll(":not([data-d3-tooltip])").remove();
26200
26604
  const width = exportDims?.width ?? container.clientWidth;
@@ -26212,21 +26616,65 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26212
26616
  const sGroupLabelZone = sctx.structural(GROUP_LABEL_ZONE);
26213
26617
  const sTitleFontSize = sctx.text(TITLE_FONT_SIZE);
26214
26618
  const sTitleY = sctx.structural(TITLE_Y);
26215
- const sLegendHeight = sctx.structural(
26619
+ const nodeValues = parsed.nodes.filter((n) => n.value !== void 0).map((n) => n.value);
26620
+ const hasRamp = nodeValues.length > 0;
26621
+ const allNonNegative = hasRamp && nodeValues.every((v) => v >= 0);
26622
+ const rampMin = allNonNegative ? 0 : Math.min(...nodeValues);
26623
+ const rampMax = Math.max(...nodeValues);
26624
+ const rampHue = resolveColor(parsed.boxMetricColor ?? "", palette) ?? palette.primary;
26625
+ const rampBase = isDark ? mix(palette.surface, palette.text, 28) : palette.bg;
26626
+ const fillForValue = (v) => {
26627
+ const t = rampMax > rampMin ? (v - rampMin) / (rampMax - rampMin) : 1;
26628
+ const pct = RAMP_FLOOR + Math.max(0, Math.min(1, t)) * (100 - RAMP_FLOOR);
26629
+ return mix(rampHue, rampBase, pct);
26630
+ };
26631
+ const VALUE_NAME = hasRamp ? parsed.boxMetric?.trim() || "Value" : null;
26632
+ const matchColorGroup = (v) => {
26633
+ const lv = v.trim().toLowerCase();
26634
+ if (lv === "none") return null;
26635
+ const tg = parsed.tagGroups.find((g) => g.name.toLowerCase() === lv);
26636
+ if (tg) return tg.name;
26637
+ if (lv === VALUE_NAME?.toLowerCase()) return VALUE_NAME;
26638
+ return v;
26639
+ };
26640
+ const override = activeTagGroup;
26641
+ let activeGroup;
26642
+ if (override !== void 0) {
26643
+ activeGroup = override === null ? null : matchColorGroup(override);
26644
+ } else if (parsed.options["active-tag"] !== void 0) {
26645
+ activeGroup = matchColorGroup(parsed.options["active-tag"]);
26646
+ } else {
26647
+ activeGroup = VALUE_NAME ?? (parsed.tagGroups.length > 0 ? parsed.tagGroups[0].name : null);
26648
+ }
26649
+ const activeIsValue = VALUE_NAME !== null && activeGroup === VALUE_NAME;
26650
+ const valueGroup = VALUE_NAME !== null ? {
26651
+ name: VALUE_NAME,
26652
+ entries: [],
26653
+ gradient: {
26654
+ min: rampMin,
26655
+ max: rampMax,
26656
+ hue: rampHue,
26657
+ base: rampBase
26658
+ }
26659
+ } : null;
26660
+ const legendGroups = [
26661
+ ...valueGroup ? [valueGroup] : [],
26662
+ ...parsed.tagGroups
26663
+ ];
26664
+ const reserveHasDescriptions = parsed.nodes.some(
26665
+ (n) => n.description && n.description.length > 0
26666
+ );
26667
+ const willRenderLegend = legendGroups.length > 0 || reserveHasDescriptions && controlsHost !== "app";
26668
+ const sLegendHeight = willRenderLegend ? sctx.structural(
26216
26669
  getMaxLegendReservedHeight(
26217
26670
  {
26218
- groups: parsed.tagGroups,
26671
+ groups: legendGroups,
26219
26672
  position: { placement: "top-center", titleRelation: "below-title" },
26220
26673
  mode: exportMode ? "export" : "preview"
26221
26674
  },
26222
26675
  width
26223
26676
  )
26224
- );
26225
- const activeGroup = resolveActiveTagGroup(
26226
- parsed.tagGroups,
26227
- parsed.options["active-tag"],
26228
- activeTagGroup
26229
- );
26677
+ ) : 0;
26230
26678
  const hidden = hiddenTagValues ?? parsed.initialHiddenTagValues;
26231
26679
  const nodeMap = /* @__PURE__ */ new Map();
26232
26680
  for (const node of parsed.nodes) nodeMap.set(node.label, node);
@@ -26237,7 +26685,7 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26237
26685
  const hasAnyDescriptions = parsed.nodes.some(
26238
26686
  (n) => n.description && n.description.length > 0
26239
26687
  );
26240
- const needsLegend = parsed.tagGroups.length > 0 || hasAnyDescriptions && onToggleDescriptions;
26688
+ const needsLegend = legendGroups.length > 0 || hasAnyDescriptions && onToggleDescriptions;
26241
26689
  const legendH = needsLegend ? sLegendHeight + 8 : 0;
26242
26690
  const groupLabelsSet = new Set(layout.groups.map((g) => g.label));
26243
26691
  let labelZoneExtension = 0;
@@ -26443,12 +26891,16 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26443
26891
  activeGroup,
26444
26892
  palette,
26445
26893
  isDark,
26894
+ { active: activeIsValue, hue: rampHue, fillForValue },
26446
26895
  parsed.options["solid-fill"] === "on"
26447
26896
  );
26448
26897
  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);
26449
26898
  for (const [key, val] of Object.entries(node.metadata)) {
26450
26899
  nodeG.attr(`data-tag-${key.toLowerCase()}`, val.toLowerCase());
26451
26900
  }
26901
+ if (node.value !== void 0) {
26902
+ nodeG.attr("data-value", node.value);
26903
+ }
26452
26904
  if (onClickItem) {
26453
26905
  nodeG.on("click", (event) => {
26454
26906
  const target = event.target;
@@ -26532,14 +26984,30 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26532
26984
  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]);
26533
26985
  }
26534
26986
  }
26987
+ if (parsed.showValues && node.value !== void 0) {
26988
+ const valueText = String(node.value);
26989
+ const descShown = !!(desc && desc.length > 0 && !hideDescriptions);
26990
+ if (descShown) {
26991
+ const padX = 6;
26992
+ const padY = 5;
26993
+ const bw = valueText.length * VALUE_FONT_SIZE * CHAR_WIDTH_RATIO2 + 8;
26994
+ const bh = VALUE_FONT_SIZE + 4;
26995
+ const bx = ln.width / 2 - bw - 4;
26996
+ const by = -ln.height / 2 + 4;
26997
+ 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);
26998
+ 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);
26999
+ } else {
27000
+ 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);
27001
+ }
27002
+ }
26535
27003
  }
26536
27004
  const hasDescriptions = parsed.nodes.some(
26537
27005
  (n) => n.description && n.description.length > 0
26538
27006
  );
26539
- const hasLegend = parsed.tagGroups.length > 0 || hasDescriptions;
27007
+ const hasLegend = legendGroups.length > 0 || hasDescriptions && controlsHost !== "app";
26540
27008
  if (hasLegend) {
26541
27009
  let controlsGroup;
26542
- if (hasDescriptions && onToggleDescriptions) {
27010
+ if (hasDescriptions && (onToggleDescriptions || controlsHost === "app")) {
26543
27011
  controlsGroup = {
26544
27012
  toggles: [
26545
27013
  {
@@ -26554,10 +27022,17 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26554
27022
  };
26555
27023
  }
26556
27024
  const legendConfig = {
26557
- groups: parsed.tagGroups,
27025
+ groups: legendGroups,
26558
27026
  position: { placement: "top-center", titleRelation: "below-title" },
26559
27027
  mode: exportMode ? "export" : "preview",
26560
- ...controlsGroup !== void 0 && { controlsGroup }
27028
+ // Keep inactive sibling tag groups visible as collapsed pills so the user
27029
+ // can click one to flip the active colouring dimension (preview only —
27030
+ // export shows just the active group). Without this, declaring a second
27031
+ // tag group (e.g. Team) leaves it invisible whenever another group is
27032
+ // active. The app's BoxesAndLinesPreview already wires pill clicks.
27033
+ showInactivePills: true,
27034
+ ...controlsGroup !== void 0 && { controlsGroup },
27035
+ ...controlsHost !== void 0 && { controlsHost }
26561
27036
  };
26562
27037
  const legendState = {
26563
27038
  activeGroup,
@@ -26602,7 +27077,7 @@ function renderBoxesAndLinesForExport(container, parsed, layout, palette, isDark
26602
27077
  }
26603
27078
  });
26604
27079
  }
26605
- 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;
27080
+ 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;
26606
27081
  var init_renderer6 = __esm({
26607
27082
  "src/boxes-and-lines/renderer.ts"() {
26608
27083
  "use strict";
@@ -26613,6 +27088,7 @@ var init_renderer6 = __esm({
26613
27088
  init_legend_layout();
26614
27089
  init_title_constants();
26615
27090
  init_color_utils();
27091
+ init_colors();
26616
27092
  init_tag_groups();
26617
27093
  init_inline_markdown();
26618
27094
  init_wrapped_desc();
@@ -26635,6 +27111,8 @@ var init_renderer6 = __esm({
26635
27111
  GROUP_RX = 8;
26636
27112
  GROUP_LABEL_FONT_SIZE = 14;
26637
27113
  GROUP_LABEL_ZONE = 32;
27114
+ RAMP_FLOOR = 15;
27115
+ VALUE_FONT_SIZE = 11;
26638
27116
  lineGeneratorLR = d3Shape4.line().x((d) => d.x).y((d) => d.y).curve(d3Shape4.curveBasis);
26639
27117
  lineGeneratorTB = d3Shape4.line().x((d) => d.x).y((d) => d.y).curve(d3Shape4.curveBasis);
26640
27118
  }
@@ -27805,8 +28283,9 @@ function renderMindmap(container, parsed, layout, palette, isDark, onClickItem,
27805
28283
  const containerHeight = exportDims?.height ?? (container.getBoundingClientRect().height || 600);
27806
28284
  d3Selection7.select(container).selectAll("*").remove();
27807
28285
  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);
28286
+ const appHosted = options?.controlsHost === "app";
27808
28287
  const hasControls = !!options?.onToggleColorByDepth || !!options?.onToggleDescriptions;
27809
- const hasLegend = parsed.tagGroups.length > 0 || hasControls;
28288
+ const hasLegend = parsed.tagGroups.length > 0 || hasControls && !appHosted;
27810
28289
  const fixedLegend = !isExport && hasLegend;
27811
28290
  const legendReserve = fixedLegend ? getMaxLegendReservedHeight(
27812
28291
  {
@@ -27900,7 +28379,10 @@ function renderMindmap(container, parsed, layout, palette, isDark, onClickItem,
27900
28379
  }),
27901
28380
  position: { placement: "top-center", titleRelation: "below-title" },
27902
28381
  mode: options?.exportMode ? "export" : "preview",
27903
- ...controlsToggles !== void 0 && { controlsGroup: controlsToggles }
28382
+ ...controlsToggles !== void 0 && { controlsGroup: controlsToggles },
28383
+ ...options?.controlsHost !== void 0 && {
28384
+ controlsHost: options.controlsHost
28385
+ }
27904
28386
  };
27905
28387
  const legendState = {
27906
28388
  activeGroup: options?.colorByDepth ? null : activeTagGroup !== void 0 ? activeTagGroup : parsed.options["active-tag"] ?? null,
@@ -28344,8 +28826,8 @@ function computeFieldAlignX(children) {
28344
28826
  for (const child of children) {
28345
28827
  if (child.metadata["_labelField"] === "true" && child.children.length >= 2) {
28346
28828
  const labelEl = child.children[0];
28347
- const labelWidth = labelEl.label.length * CHAR_WIDTH5;
28348
- maxLabelWidth = Math.max(maxLabelWidth, labelWidth);
28829
+ const labelWidth2 = labelEl.label.length * CHAR_WIDTH5;
28830
+ maxLabelWidth = Math.max(maxLabelWidth, labelWidth2);
28349
28831
  labelFieldCount++;
28350
28832
  }
28351
28833
  }
@@ -33309,7 +33791,7 @@ function hasRoles(node) {
33309
33791
  function computeNodeWidth2(node, expanded, options) {
33310
33792
  const badgeVal = node.computedConcurrentInvocations === 0 && node.computedInstances > 1 ? node.computedInstances : 0;
33311
33793
  const badgeLen = badgeVal > 0 ? `${badgeVal}x`.length + 2 : 0;
33312
- const labelWidth = (node.label.length + badgeLen) * CHAR_WIDTH7 + PADDING_X3;
33794
+ const labelWidth2 = (node.label.length + badgeLen) * CHAR_WIDTH7 + PADDING_X3;
33313
33795
  const allKeys = [];
33314
33796
  if (node.computedRps > 0) allKeys.push("RPS");
33315
33797
  if (expanded) {
@@ -33353,7 +33835,7 @@ function computeNodeWidth2(node, expanded, options) {
33353
33835
  allKeys.push("overflow");
33354
33836
  }
33355
33837
  }
33356
- if (allKeys.length === 0) return Math.max(MIN_NODE_WIDTH2, labelWidth);
33838
+ if (allKeys.length === 0) return Math.max(MIN_NODE_WIDTH2, labelWidth2);
33357
33839
  const maxKeyLen = Math.max(...allKeys.map((k) => k.length));
33358
33840
  let maxRowWidth = 0;
33359
33841
  if (node.computedRps > 0) {
@@ -33441,7 +33923,7 @@ function computeNodeWidth2(node, expanded, options) {
33441
33923
  truncated.length * META_CHAR_WIDTH3 + PADDING_X3
33442
33924
  );
33443
33925
  }
33444
- return Math.max(MIN_NODE_WIDTH2, labelWidth, maxRowWidth + 20, descWidth);
33926
+ return Math.max(MIN_NODE_WIDTH2, labelWidth2, maxRowWidth + 20, descWidth);
33445
33927
  }
33446
33928
  function computeNodeHeight2(node, expanded, options) {
33447
33929
  const propCount = countDisplayProps(node, expanded, options);
@@ -34990,8 +35472,9 @@ function computeInfraLegendGroups(nodes, tagGroups, palette, edges) {
34990
35472
  }
34991
35473
  return groups;
34992
35474
  }
34993
- function renderLegend3(rootSvg, legendGroups, totalWidth, legendY, palette, isDark, activeGroup, playback, exportMode = false) {
35475
+ function renderLegend3(rootSvg, legendGroups, totalWidth, legendY, palette, isDark, activeGroup, playback, exportMode = false, controlsHost) {
34994
35476
  if (legendGroups.length === 0 && !playback) return;
35477
+ const appHostedPlayback = controlsHost === "app" && !!playback;
34995
35478
  const legendG = rootSvg.append("g").attr("transform", `translate(0, ${legendY})`);
34996
35479
  if (activeGroup) {
34997
35480
  legendG.attr("data-legend-active", activeGroup.toLowerCase());
@@ -35000,14 +35483,29 @@ function renderLegend3(rootSvg, legendGroups, totalWidth, legendY, palette, isDa
35000
35483
  name: g.name,
35001
35484
  entries: g.entries.map((e) => ({ value: e.value, color: e.color }))
35002
35485
  }));
35003
- if (playback) {
35486
+ if (playback && !appHostedPlayback) {
35004
35487
  allGroups.push({ name: "Playback", entries: [] });
35005
35488
  }
35006
35489
  const legendConfig = {
35007
35490
  groups: allGroups,
35008
35491
  position: { placement: "top-center", titleRelation: "below-title" },
35009
35492
  mode: exportMode ? "export" : "preview",
35010
- showEmptyGroups: true
35493
+ showEmptyGroups: true,
35494
+ ...appHostedPlayback && {
35495
+ controlsHost: "app",
35496
+ controlsGroup: {
35497
+ toggles: [
35498
+ {
35499
+ id: "playback",
35500
+ type: "toggle",
35501
+ label: "Playback",
35502
+ active: true,
35503
+ onToggle: () => {
35504
+ }
35505
+ }
35506
+ ]
35507
+ }
35508
+ }
35011
35509
  };
35012
35510
  const legendState = { activeGroup };
35013
35511
  renderLegendD3(
@@ -35058,8 +35556,9 @@ function renderLegend3(rootSvg, legendGroups, totalWidth, legendY, palette, isDa
35058
35556
  }
35059
35557
  }
35060
35558
  }
35061
- function renderInfra(container, layout, palette, isDark, title, titleLineNumber, tagGroups, activeGroup, animate, playback, expandedNodeIds, exportMode, collapsedNodes) {
35559
+ function renderInfra(container, layout, palette, isDark, title, titleLineNumber, tagGroups, activeGroup, animate, playback, expandedNodeIds, exportMode, collapsedNodes, controlsHost) {
35062
35560
  d3Selection11.select(container).selectAll(":not([data-d3-tooltip])").remove();
35561
+ const appHostedPlayback = controlsHost === "app" && !!playback;
35063
35562
  const ctx = ScaleContext.identity();
35064
35563
  const sc = buildScaledConstants(ctx);
35065
35564
  const legendGroups = computeInfraLegendGroups(
@@ -35068,7 +35567,7 @@ function renderInfra(container, layout, palette, isDark, title, titleLineNumber,
35068
35567
  palette,
35069
35568
  layout.edges
35070
35569
  );
35071
- const hasLegend = legendGroups.length > 0 || !!playback;
35570
+ const hasLegend = legendGroups.length > 0 || !!playback && !appHostedPlayback;
35072
35571
  const fixedLegend = !exportMode && hasLegend;
35073
35572
  const legendDynamicH = hasLegend ? getMaxLegendReservedHeight(
35074
35573
  {
@@ -35212,7 +35711,8 @@ function renderInfra(container, layout, palette, isDark, title, titleLineNumber,
35212
35711
  isDark,
35213
35712
  activeGroup ?? null,
35214
35713
  playback ?? void 0,
35215
- exportMode
35714
+ exportMode,
35715
+ controlsHost
35216
35716
  );
35217
35717
  legendSvg.selectAll(".infra-legend-group").style("pointer-events", "auto");
35218
35718
  } else {
@@ -35225,7 +35725,8 @@ function renderInfra(container, layout, palette, isDark, title, titleLineNumber,
35225
35725
  isDark,
35226
35726
  activeGroup ?? null,
35227
35727
  playback ?? void 0,
35228
- exportMode
35728
+ exportMode,
35729
+ controlsHost
35229
35730
  );
35230
35731
  }
35231
35732
  }
@@ -43083,6 +43584,9 @@ function renderTechRadar(container, parsed, palette, isDark, onClickItem, export
43083
43584
  onToggle: (active) => options.onToggleListing(active)
43084
43585
  }
43085
43586
  ]
43587
+ },
43588
+ ...options.controlsHost !== void 0 && {
43589
+ controlsHost: options.controlsHost
43086
43590
  }
43087
43591
  };
43088
43592
  const legendState = {
@@ -44906,7 +45410,7 @@ function computeCycleLayout(parsed, options) {
44906
45410
  const circleNodes = parsed.options["circle-nodes"] === "true";
44907
45411
  const nodeDims = parsed.nodes.map((node) => {
44908
45412
  const hasDesc = !hideDescriptions && node.description.length > 0;
44909
- const labelWidth = Math.max(
45413
+ const labelWidth2 = Math.max(
44910
45414
  MIN_NODE_WIDTH4,
44911
45415
  node.label.length * LABEL_CHAR_W + NODE_PAD_X * 2
44912
45416
  );
@@ -44915,12 +45419,12 @@ function computeCycleLayout(parsed, options) {
44915
45419
  }
44916
45420
  if (!hasDesc) {
44917
45421
  return {
44918
- width: Math.min(MAX_NODE_WIDTH3, labelWidth),
45422
+ width: Math.min(MAX_NODE_WIDTH3, labelWidth2),
44919
45423
  height: PLAIN_NODE_HEIGHT,
44920
45424
  wrappedDesc: []
44921
45425
  };
44922
45426
  }
44923
- return chooseDescribedRectDims(node.description, labelWidth);
45427
+ return chooseDescribedRectDims(node.description, labelWidth2);
44924
45428
  });
44925
45429
  if (circleNodes) {
44926
45430
  const maxDiam = Math.max(...nodeDims.map((d) => d.width));
@@ -45116,10 +45620,10 @@ function computeCycleLayout(parsed, options) {
45116
45620
  scale
45117
45621
  };
45118
45622
  }
45119
- function chooseDescribedRectDims(description, labelWidth) {
45623
+ function chooseDescribedRectDims(description, labelWidth2) {
45120
45624
  const minW = Math.min(
45121
45625
  MAX_NODE_WIDTH3,
45122
- Math.max(MIN_NODE_WIDTH4, labelWidth, DESC_MIN_WIDTH)
45626
+ Math.max(MIN_NODE_WIDTH4, labelWidth2, DESC_MIN_WIDTH)
45123
45627
  );
45124
45628
  let best = null;
45125
45629
  let bestScore = Infinity;
@@ -45547,7 +46051,8 @@ function renderCycle(container, parsed, palette, isDark, onClickItem, exportDims
45547
46051
  const hideDescriptions = (renderOptions?.hideDescriptions ?? false) || parsed.options["no-descriptions"] === "true" || viewState?.hd === true;
45548
46052
  const showDescriptions = !hideDescriptions;
45549
46053
  const hasDescriptions = parsed.nodes.some((n) => n.description.length > 0) || parsed.edges.some((e) => e.description.length > 0);
45550
- const hasLegend = hasDescriptions && !!renderOptions?.onToggleDescriptions;
46054
+ const appHostedControls = renderOptions?.controlsHost === "app";
46055
+ const hasLegend = !appHostedControls && hasDescriptions && !!renderOptions?.onToggleDescriptions;
45551
46056
  const showTitle = !!parsed.title && parsed.options["no-title"] !== "on";
45552
46057
  const legendOffset = hasLegend ? sLegendHeight : 0;
45553
46058
  const layoutHeight = height - (showTitle ? sTitleAreaHeight : 0) - legendOffset;
@@ -45584,7 +46089,10 @@ function renderCycle(container, parsed, palette, isDark, onClickItem, exportDims
45584
46089
  groups: [],
45585
46090
  position: { placement: "top-center", titleRelation: "below-title" },
45586
46091
  mode: renderOptions?.exportMode ? "export" : "preview",
45587
- controlsGroup
46092
+ controlsGroup,
46093
+ ...renderOptions?.controlsHost !== void 0 && {
46094
+ controlsHost: renderOptions.controlsHost
46095
+ }
45588
46096
  };
45589
46097
  const legendState = {
45590
46098
  activeGroup: null,
@@ -45855,6 +46363,29 @@ function featureIndex(topo) {
45855
46363
  }
45856
46364
  return idx;
45857
46365
  }
46366
+ function buildAdjacency(topo) {
46367
+ const cached = adjacencyCache.get(topo);
46368
+ if (cached) return cached;
46369
+ const geometries = geomObject(topo).geometries;
46370
+ const nb = (0, import_topojson_client.neighbors)(geometries);
46371
+ const sets = /* @__PURE__ */ new Map();
46372
+ geometries.forEach((g, i) => {
46373
+ if (!g.type || g.type === "null") return;
46374
+ let set = sets.get(g.id);
46375
+ if (!set) {
46376
+ set = /* @__PURE__ */ new Set();
46377
+ sets.set(g.id, set);
46378
+ }
46379
+ for (const j of nb[i] ?? []) {
46380
+ const nid = geometries[j]?.id;
46381
+ if (nid && nid !== g.id) set.add(nid);
46382
+ }
46383
+ });
46384
+ const out = /* @__PURE__ */ new Map();
46385
+ for (const [iso, set] of sets) out.set(iso, [...set].sort());
46386
+ adjacencyCache.set(topo, out);
46387
+ return out;
46388
+ }
45858
46389
  function decodeFeatures(topo) {
45859
46390
  return geomObject(topo).geometries.map((g) => {
45860
46391
  const f = (0, import_topojson_client.feature)(topo, g);
@@ -46050,13 +46581,14 @@ function unionLongitudes(lons) {
46050
46581
  }
46051
46582
  return { west: pts[gapIdx], east: pts[gapIdx - 1] + 360 };
46052
46583
  }
46053
- var import_topojson_client, import_d3_geo, fold, EDGE_EPS, DETACH_GAP_DEG, DETACH_AREA_FRAC;
46584
+ var import_topojson_client, import_d3_geo, fold, adjacencyCache, EDGE_EPS, DETACH_GAP_DEG, DETACH_AREA_FRAC;
46054
46585
  var init_geo = __esm({
46055
46586
  "src/map/geo.ts"() {
46056
46587
  "use strict";
46057
46588
  import_topojson_client = require("topojson-client");
46058
46589
  import_d3_geo = require("d3-geo");
46059
46590
  fold = (s) => s.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLowerCase().trim();
46591
+ adjacencyCache = /* @__PURE__ */ new WeakMap();
46060
46592
  EDGE_EPS = 1e-9;
46061
46593
  DETACH_GAP_DEG = 10;
46062
46594
  DETACH_AREA_FRAC = 0.25;
@@ -46077,6 +46609,12 @@ function looksUS(lat, lon) {
46077
46609
  if (lat < 15 || lat > 72) return false;
46078
46610
  return lon >= -180 && lon <= -64 || lon >= 172;
46079
46611
  }
46612
+ function looksNorthAmericaNeighbor(lat, lon) {
46613
+ return lat >= 14 && lat <= 72 && lon >= -141 && lon <= -52;
46614
+ }
46615
+ function isWholeSphere(bb) {
46616
+ return bb[0][0] <= -179 && bb[1][0] >= 179 && bb[0][1] <= -89 && bb[1][1] >= 89;
46617
+ }
46080
46618
  function resolveMap(parsed, data) {
46081
46619
  const diagnostics = [...parsed.diagnostics];
46082
46620
  const err = (line12, message, code) => {
@@ -46087,9 +46625,6 @@ function resolveMap(parsed, data) {
46087
46625
  };
46088
46626
  const result = {
46089
46627
  title: parsed.title,
46090
- ...parsed.directives.subtitle !== void 0 && {
46091
- subtitle: parsed.directives.subtitle
46092
- },
46093
46628
  ...parsed.directives.caption !== void 0 && {
46094
46629
  caption: parsed.directives.caption
46095
46630
  },
@@ -46099,7 +46634,7 @@ function resolveMap(parsed, data) {
46099
46634
  // renderer's job (step 4) — the resolver only carries `tags` + `tagGroups`
46100
46635
  // through; it never resolves a tag value to a palette color (#10).
46101
46636
  directives: { ...parsed.directives },
46102
- basemaps: { world: "coarse", subdivisions: [] },
46637
+ basemaps: { world: "detail", subdivisions: [] },
46103
46638
  regions: [],
46104
46639
  pois: [],
46105
46640
  edges: [],
@@ -46108,7 +46643,8 @@ function resolveMap(parsed, data) {
46108
46643
  [-180, -85],
46109
46644
  [180, 85]
46110
46645
  ],
46111
- projection: "natural-earth",
46646
+ projection: "equirectangular",
46647
+ poiFrameContainers: [],
46112
46648
  diagnostics,
46113
46649
  error: parsed.error
46114
46650
  };
@@ -46118,7 +46654,10 @@ function resolveMap(parsed, data) {
46118
46654
  ...[...countryIndex.values()].map((v) => v.name),
46119
46655
  ...[...usStateIndex.values()].map((v) => v.name)
46120
46656
  ];
46121
- const usScoped = parsed.directives.region === "us-states" || parsed.directives.defaultCountry?.toUpperCase() === "US" || parsed.regions.some((r) => {
46657
+ const localeRaw = parsed.directives.locale?.toUpperCase();
46658
+ const localeCountry = localeRaw ? localeRaw.split("-")[0] : void 0;
46659
+ const localeSubdivision = localeRaw && /^[A-Z]{2}-/.test(localeRaw) ? localeRaw : void 0;
46660
+ const usScoped = localeCountry === "US" || parsed.regions.some((r) => {
46122
46661
  const f = fold(r.name);
46123
46662
  return usStateIndex.has(f) && !countryIndex.has(f);
46124
46663
  }) || parsed.regions.some(
@@ -46269,7 +46808,7 @@ function resolveMap(parsed, data) {
46269
46808
  if (!scope)
46270
46809
  warn(
46271
46810
  line12,
46272
- `"${name}" is ambiguous \u2014 resolved to the most-populous match.`,
46811
+ `"${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.`,
46273
46812
  "W_MAP_AMBIGUOUS_NAME"
46274
46813
  );
46275
46814
  }
@@ -46282,17 +46821,21 @@ function resolveMap(parsed, data) {
46282
46821
  return fold(pos.name);
46283
46822
  };
46284
46823
  const poiCountries = [];
46285
- let anyNonUsPoi = false;
46824
+ let anyUsPoi = false;
46825
+ let anyNonNaPoi = false;
46286
46826
  const noteCountry = (iso) => {
46287
46827
  if (iso) {
46288
46828
  poiCountries.push(iso);
46289
- if (iso !== "US") anyNonUsPoi = true;
46829
+ if (iso === "US") anyUsPoi = true;
46830
+ if (iso !== "US" && iso !== "CA" && iso !== "MX") anyNonNaPoi = true;
46290
46831
  }
46291
46832
  };
46292
46833
  const deferred = [];
46293
46834
  for (const p of parsed.pois) {
46294
46835
  if (p.pos.kind === "coords") {
46295
- if (!looksUS(p.pos.lat, p.pos.lon)) anyNonUsPoi = true;
46836
+ if (looksUS(p.pos.lat, p.pos.lon)) anyUsPoi = true;
46837
+ else if (!looksNorthAmericaNeighbor(p.pos.lat, p.pos.lon))
46838
+ anyNonNaPoi = true;
46296
46839
  addResolvedPoi(p.pos.lat, p.pos.lon, p);
46297
46840
  continue;
46298
46841
  }
@@ -46310,14 +46853,15 @@ function resolveMap(parsed, data) {
46310
46853
  deferred.push(p);
46311
46854
  }
46312
46855
  }
46313
- const inferredCountry = parsed.directives.defaultCountry?.toUpperCase() ?? mostCommonCountry(regions, poiCountries) ?? void 0;
46856
+ const inferredCountry = localeCountry ?? mostCommonCountry(regions, poiCountries) ?? void 0;
46857
+ const inferredScope = localeSubdivision ?? inferredCountry;
46314
46858
  for (const p of deferred) {
46315
46859
  if (p.pos.kind !== "name") continue;
46316
46860
  const got = lookupName(
46317
46861
  p.pos.name,
46318
46862
  p.pos.scope,
46319
46863
  p.lineNumber,
46320
- inferredCountry,
46864
+ inferredScope,
46321
46865
  true
46322
46866
  );
46323
46867
  if (got.kind === "ok") {
@@ -46387,7 +46931,8 @@ function resolveMap(parsed, data) {
46387
46931
  const meta = sizeValue !== void 0 ? { value: sizeValue } : {};
46388
46932
  if (pos.kind === "coords") {
46389
46933
  const id = alias ? fold(alias) : `@${pos.lat},${pos.lon}`;
46390
- if (!looksUS(pos.lat, pos.lon)) anyNonUsPoi = true;
46934
+ if (looksUS(pos.lat, pos.lon)) anyUsPoi = true;
46935
+ else if (!looksNorthAmericaNeighbor(pos.lat, pos.lon)) anyNonNaPoi = true;
46391
46936
  if (!registry.has(id)) {
46392
46937
  registerPoi(
46393
46938
  id,
@@ -46410,7 +46955,7 @@ function resolveMap(parsed, data) {
46410
46955
  if (registry.has(f)) return f;
46411
46956
  const aliased = declaredByName.get(f);
46412
46957
  if (aliased) return aliased;
46413
- const got = lookupName(pos.name, pos.scope, line12, inferredCountry, true);
46958
+ const got = lookupName(pos.name, pos.scope, line12, inferredScope, true);
46414
46959
  if (got.kind !== "ok") return null;
46415
46960
  noteCountry(got.iso);
46416
46961
  registerPoi(
@@ -46467,9 +47012,12 @@ function resolveMap(parsed, data) {
46467
47012
  }
46468
47013
  routes.push({ stopIds, legs, lineNumber: rt.lineNumber });
46469
47014
  }
47015
+ const hasUsContent = usSubdivisionReferenced || anyUsPoi || localeCountry === "US";
47016
+ const usOriented = !anyNonNaPoi && !regions.some(
47017
+ (r) => r.layer === "country" && !["US", "CA", "MX"].includes(r.iso)
47018
+ ) && hasUsContent;
46470
47019
  const subdivisions = [];
46471
- if (usSubdivisionReferenced || parsed.directives.region === "us-states")
46472
- subdivisions.push("us-states");
47020
+ if (usSubdivisionReferenced || usOriented) subdivisions.push("us-states");
46473
47021
  const regionBoxes = [];
46474
47022
  for (const ref of referencedRegionIds) {
46475
47023
  const bb = featureBbox(data.usStates, ref.id);
@@ -46487,17 +47035,51 @@ function resolveMap(parsed, data) {
46487
47035
  [-180, -85],
46488
47036
  [180, 85]
46489
47037
  ];
46490
- let extent2 = unioned ? pad(unioned, PAD_FRACTION) : DEFAULT_EXTENT;
47038
+ const basePad = regions.length > 0 ? REGION_PAD_FRACTION : PAD_FRACTION;
47039
+ let extent2 = unioned ? pad(unioned, basePad) : DEFAULT_EXTENT;
47040
+ const isPoiOnly = pois.length > 0 && regions.length === 0;
47041
+ const containerRegionIds = [];
47042
+ if (isPoiOnly) {
47043
+ const countries = decodeFeatures(data.worldDetail);
47044
+ const states = decodeFeatures(data.usStates);
47045
+ const seen = /* @__PURE__ */ new Set();
47046
+ const containerBoxes = [];
47047
+ for (const p of pois) {
47048
+ const { country, state } = regionAt([p.lon, p.lat], countries, states);
47049
+ const id = state?.iso ?? country?.iso;
47050
+ if (!id || seen.has(id)) continue;
47051
+ seen.add(id);
47052
+ containerRegionIds.push(id);
47053
+ const bb = state ? featureBbox(data.usStates, id) : featureBboxPrimary(data.worldCoarse, id);
47054
+ if (bb && !isWholeSphere(bb)) containerBoxes.push(bb);
47055
+ }
47056
+ const containerUnion = unionExtent(containerBoxes, points);
47057
+ if (containerUnion) extent2 = pad(containerUnion, PAD_FRACTION);
47058
+ }
47059
+ if (isPoiOnly) {
47060
+ const cx = (extent2[0][0] + extent2[1][0]) / 2;
47061
+ const cy = (extent2[0][1] + extent2[1][1]) / 2;
47062
+ const lon = extent2[1][0] - extent2[0][0];
47063
+ const lat = extent2[1][1] - extent2[0][1];
47064
+ const longer = Math.max(lon, lat);
47065
+ if (longer > 0 && longer < POI_ZOOM_FLOOR_DEG) {
47066
+ const k = POI_ZOOM_FLOOR_DEG / longer;
47067
+ const halfLon = lon * k / 2;
47068
+ const halfLat = lat * k / 2;
47069
+ extent2 = [
47070
+ [cx - halfLon, cy - halfLat],
47071
+ [cx + halfLon, cy + halfLat]
47072
+ ];
47073
+ }
47074
+ }
46491
47075
  const lonSpan = extent2[1][0] - extent2[0][0];
46492
47076
  const latSpan = extent2[1][1] - extent2[0][1];
46493
47077
  const span = Math.max(lonSpan, latSpan);
46494
47078
  const maxAbsLat = Math.max(Math.abs(extent2[0][1]), Math.abs(extent2[1][1]));
46495
- const usDominant = (subdivisions.includes("us-states") || regions.some((r) => r.layer === "us-state")) && !regions.some((r) => r.layer === "country" && r.iso !== "US") && !anyNonUsPoi;
46496
47079
  let projection;
46497
- const override = parsed.directives.projection;
46498
- if (override === "equirectangular" || override === "natural-earth" || override === "albers-usa" || override === "mercator") {
46499
- projection = override;
46500
- } else if (usDominant) {
47080
+ if (isPoiOnly && usOriented && lonSpan < US_NATIONAL_LON_SPAN) {
47081
+ projection = "mercator";
47082
+ } else if (usOriented) {
46501
47083
  projection = "albers-usa";
46502
47084
  } else if (span > WORLD_SPAN || maxAbsLat > MERCATOR_MAX_LAT) {
46503
47085
  projection = "equirectangular";
@@ -46515,11 +47097,20 @@ function resolveMap(parsed, data) {
46515
47097
  result.edges = edges;
46516
47098
  result.routes = routes;
46517
47099
  result.basemaps = {
46518
- world: span > WORLD_SPAN ? "coarse" : "detail",
47100
+ // Tier is intentionally pinned to detail (50m) at ALL scales. Diagrammo maps
47101
+ // are presentational (palette tints, relief hachures, POI hubs), not
47102
+ // survey-grade — recognizability > generalization: 110m coarse drops the
47103
+ // Italian boot to a stump at world scale. `WORLD_SPAN` lives on only for the
47104
+ // projection decision (the `usOriented`/`span > WORLD_SPAN` chain above); it
47105
+ // no longer gates basemap resolution.
47106
+ // `worldCoarse` is still loaded — it's the authoritative name/bbox index
47107
+ // (featureIndex, featureBboxPrimary), not dead code.
47108
+ world: "detail",
46519
47109
  subdivisions
46520
47110
  };
46521
47111
  result.extent = extent2;
46522
47112
  result.projection = projection;
47113
+ result.poiFrameContainers = containerRegionIds;
46523
47114
  result.error = parsed.error ?? firstError(diagnostics);
46524
47115
  return result;
46525
47116
  }
@@ -46556,7 +47147,7 @@ function firstError(diags) {
46556
47147
  const e = diags.find((d) => d.severity === "error");
46557
47148
  return e ? formatDgmoError(e) : null;
46558
47149
  }
46559
- var WORLD_SPAN, MERCATOR_MAX_LAT, PAD_FRACTION, WORLD_LAT_SOUTH, WORLD_LAT_NORTH, REGION_ALIASES, US_STATE_POSTAL;
47150
+ 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;
46560
47151
  var init_resolver2 = __esm({
46561
47152
  "src/map/resolver.ts"() {
46562
47153
  "use strict";
@@ -46565,8 +47156,11 @@ var init_resolver2 = __esm({
46565
47156
  WORLD_SPAN = 90;
46566
47157
  MERCATOR_MAX_LAT = 80;
46567
47158
  PAD_FRACTION = 0.05;
47159
+ REGION_PAD_FRACTION = 0.12;
46568
47160
  WORLD_LAT_SOUTH = -58;
46569
47161
  WORLD_LAT_NORTH = 78;
47162
+ POI_ZOOM_FLOOR_DEG = 7;
47163
+ US_NATIONAL_LON_SPAN = 48;
46570
47164
  REGION_ALIASES = {
46571
47165
  // Common everyday names → the Natural-Earth display name actually shipped.
46572
47166
  "united states": "united states of america",
@@ -46644,17 +47238,305 @@ var init_resolver2 = __esm({
46644
47238
  }
46645
47239
  });
46646
47240
 
47241
+ // src/map/colorize.ts
47242
+ function assignColors(isos, adjacency) {
47243
+ const sorted = [...isos].sort();
47244
+ const byIso = /* @__PURE__ */ new Map();
47245
+ let maxIndex = -1;
47246
+ for (const iso of sorted) {
47247
+ const taken = /* @__PURE__ */ new Set();
47248
+ for (const n of adjacency.get(iso) ?? []) {
47249
+ const c = byIso.get(n);
47250
+ if (c !== void 0) taken.add(c);
47251
+ }
47252
+ let h = 0;
47253
+ while (taken.has(h)) h++;
47254
+ byIso.set(iso, h);
47255
+ if (h > maxIndex) maxIndex = h;
47256
+ }
47257
+ return { byIso, huesNeeded: maxIndex + 1 };
47258
+ }
47259
+ var init_colorize = __esm({
47260
+ "src/map/colorize.ts"() {
47261
+ "use strict";
47262
+ }
47263
+ });
47264
+
47265
+ // src/map/context-labels.ts
47266
+ function tierBand(maxSpanDeg) {
47267
+ if (maxSpanDeg >= 90) return "world";
47268
+ if (maxSpanDeg >= 20) return "continental";
47269
+ if (maxSpanDeg >= 5) return "regional";
47270
+ return "local";
47271
+ }
47272
+ function labelBudget(width, height, band) {
47273
+ const bandCap = {
47274
+ world: 6,
47275
+ continental: 5,
47276
+ regional: 4,
47277
+ local: 3
47278
+ };
47279
+ const area2 = Math.floor(Math.sqrt(Math.max(0, width * height)) / 150);
47280
+ return Math.max(0, Math.min(area2, bandCap[band]));
47281
+ }
47282
+ function waterEligible(tier, kind, band) {
47283
+ switch (band) {
47284
+ case "world":
47285
+ return tier <= 1 && (kind === "ocean" || kind === "sea");
47286
+ case "continental":
47287
+ return tier <= 2;
47288
+ case "regional":
47289
+ return tier <= 3;
47290
+ case "local":
47291
+ return tier <= 4;
47292
+ }
47293
+ }
47294
+ function insideViewport(p, width, height) {
47295
+ return !!p && Number.isFinite(p[0]) && Number.isFinite(p[1]) && p[0] >= 0 && p[0] <= width && p[1] >= 0 && p[1] <= height;
47296
+ }
47297
+ function labelWidth(text, letterSpacing) {
47298
+ const spacing = letterSpacing > 0 ? Math.max(0, text.length - 1) * letterSpacing : 0;
47299
+ return measureLegendText(text, FONT) + spacing + 2 * PADX;
47300
+ }
47301
+ function wrapLabel2(text, letterSpacing) {
47302
+ const words = text.split(/\s+/).filter(Boolean);
47303
+ if (words.length <= 1) return [text];
47304
+ const maxLines = words.length >= 4 ? 3 : 2;
47305
+ const n = words.length;
47306
+ let best = null;
47307
+ for (let mask = 0; mask < 1 << n - 1; mask++) {
47308
+ const lines = [];
47309
+ let cur = [words[0]];
47310
+ for (let i = 1; i < n; i++) {
47311
+ if (mask & 1 << i - 1) {
47312
+ lines.push(cur.join(" "));
47313
+ cur = [words[i]];
47314
+ } else cur.push(words[i]);
47315
+ }
47316
+ lines.push(cur.join(" "));
47317
+ if (lines.length > maxLines) continue;
47318
+ const cost = Math.round(
47319
+ Math.max(...lines.map((l) => labelWidth(l, letterSpacing)))
47320
+ );
47321
+ const head = labelWidth(lines[0], letterSpacing);
47322
+ if (!best || cost < best.cost || cost === best.cost && lines.length < best.lines.length || cost === best.cost && lines.length === best.lines.length && head > best.head)
47323
+ best = { lines, cost, head };
47324
+ }
47325
+ return best?.lines ?? [text];
47326
+ }
47327
+ function rectAround(cx, cy, lines, letterSpacing) {
47328
+ const w = Math.max(...lines.map((l) => labelWidth(l, letterSpacing)));
47329
+ const h = (lines.length - 1) * LINE_HEIGHT + FONT + 2 * PADY;
47330
+ return { x: cx - w / 2, y: cy - h / 2, w, h };
47331
+ }
47332
+ function rectFits(r, width, height) {
47333
+ return r.x >= 0 && r.y >= 0 && r.x + r.w <= width && r.y + r.h <= height;
47334
+ }
47335
+ function overlapsPadded(a, b, pad2) {
47336
+ 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;
47337
+ }
47338
+ function placeContextLabels(args) {
47339
+ const {
47340
+ projection,
47341
+ dLonSpan,
47342
+ dLatSpan,
47343
+ width,
47344
+ height,
47345
+ waterBodies,
47346
+ countries,
47347
+ palette,
47348
+ project,
47349
+ collides,
47350
+ overLand
47351
+ } = args;
47352
+ void projection;
47353
+ const band = tierBand(Math.max(dLonSpan, dLatSpan));
47354
+ const budget = labelBudget(width, height, band);
47355
+ if (budget <= 0) return [];
47356
+ const waterColor = mix(palette.colors.blue, palette.textMuted, 50);
47357
+ const countryColor = palette.textMuted;
47358
+ const haloColor = palette.bg;
47359
+ const candidates = [];
47360
+ const center = [width / 2, height / 2];
47361
+ for (const e of waterBodies?.entries ?? []) {
47362
+ const [lat, lon, name, tier, kind, alt] = e;
47363
+ if (!waterEligible(tier, kind, band)) continue;
47364
+ const wlines = wrapLabel2(name, WATER_LETTER_SPACING);
47365
+ const anchorsLngLat = [[lon, lat]];
47366
+ for (const a of alt ?? []) anchorsLngLat.push([a[1], a[0]]);
47367
+ let best = null;
47368
+ let bestD = Infinity;
47369
+ let nearestProj = null;
47370
+ let nearestProjD = Infinity;
47371
+ for (const [aLon, aLat] of anchorsLngLat) {
47372
+ const p = project(aLon, aLat);
47373
+ if (!p || !Number.isFinite(p[0]) || !Number.isFinite(p[1])) continue;
47374
+ const d = (p[0] - center[0]) ** 2 + (p[1] - center[1]) ** 2;
47375
+ if (d < nearestProjD) {
47376
+ nearestProjD = d;
47377
+ nearestProj = p;
47378
+ }
47379
+ if (!insideViewport(p, width, height)) continue;
47380
+ if (d < bestD) {
47381
+ bestD = d;
47382
+ best = p;
47383
+ }
47384
+ }
47385
+ if (!best && tier === 0 && nearestProj) {
47386
+ const overX = Math.max(0, -nearestProj[0], nearestProj[0] - width);
47387
+ const overY = Math.max(0, -nearestProj[1], nearestProj[1] - height);
47388
+ if (overX <= width * EDGE_CLAMP_OVERSHOOT && overY <= height * EDGE_CLAMP_OVERSHOOT) {
47389
+ const halfW = Math.max(...wlines.map((l) => labelWidth(l, WATER_LETTER_SPACING))) / 2;
47390
+ const halfH = ((wlines.length - 1) * LINE_HEIGHT + FONT + 2 * PADY) / 2;
47391
+ const m = EDGE_CLAMP_MARGIN;
47392
+ best = [
47393
+ Math.min(Math.max(nearestProj[0], halfW + m), width - halfW - m),
47394
+ Math.min(Math.max(nearestProj[1], halfH + m), height - halfH - m)
47395
+ ];
47396
+ }
47397
+ }
47398
+ if (!best) continue;
47399
+ candidates.push({
47400
+ text: name,
47401
+ lines: wlines,
47402
+ cx: best[0],
47403
+ cy: best[1],
47404
+ italic: true,
47405
+ letterSpacing: WATER_LETTER_SPACING,
47406
+ color: waterColor,
47407
+ // Water before any country (×1000), then by tier, then kind, then name.
47408
+ sort: tier * 10 + KIND_ORDER[kind]
47409
+ });
47410
+ }
47411
+ const ranked = countries.map((c) => {
47412
+ const [x0, y0, x1, y1] = c.bbox;
47413
+ const w = x1 - x0;
47414
+ const h = y1 - y0;
47415
+ return { c, w, h, area: w * h };
47416
+ }).filter((r) => Number.isFinite(r.area) && r.area > 0).sort((a, b) => b.area - a.area);
47417
+ let ci = 0;
47418
+ for (const r of ranked) {
47419
+ const { c, w, h } = r;
47420
+ if (w > width * 0.66 || h > height * 0.66) continue;
47421
+ if (!insideViewport(c.anchor, width, height)) continue;
47422
+ const text = c.name;
47423
+ const tw = labelWidth(text, 0);
47424
+ if (tw > w || FONT + 2 * PADY > h) continue;
47425
+ candidates.push({
47426
+ text,
47427
+ lines: [text],
47428
+ cx: c.anchor[0],
47429
+ cy: c.anchor[1],
47430
+ italic: false,
47431
+ letterSpacing: 0,
47432
+ color: countryColor,
47433
+ // Always after every water body (+1e6); larger area = earlier.
47434
+ sort: 1e6 + ci++
47435
+ });
47436
+ }
47437
+ candidates.sort((a, b) => a.sort - b.sort);
47438
+ const placed = [];
47439
+ const placedRects = [];
47440
+ for (const cand of candidates) {
47441
+ if (placed.length >= budget) break;
47442
+ const rect = rectAround(cand.cx, cand.cy, cand.lines, cand.letterSpacing);
47443
+ if (!rectFits(rect, width, height)) continue;
47444
+ if (cand.italic && overLand) {
47445
+ const inset = 2;
47446
+ const top = cand.cy - (cand.lines.length - 1) / 2 * LINE_HEIGHT;
47447
+ const touchesLand = cand.lines.some((line12, li) => {
47448
+ const lw = labelWidth(line12, cand.letterSpacing);
47449
+ const x0 = cand.cx - lw / 2 + inset;
47450
+ const x1 = cand.cx + lw / 2 - inset;
47451
+ const xs = [x0, (x0 + cand.cx) / 2, cand.cx, (cand.cx + x1) / 2, x1];
47452
+ const base = top + li * LINE_HEIGHT;
47453
+ return [base, base - FONT * 0.4, base - FONT * 0.8].some(
47454
+ (y) => xs.some((x) => overLand(x, y))
47455
+ );
47456
+ });
47457
+ if (touchesLand) continue;
47458
+ }
47459
+ if (collides(rect)) continue;
47460
+ if (placedRects.some((r) => overlapsPadded(rect, r, CONTEXT_PAD))) continue;
47461
+ placedRects.push(rect);
47462
+ placed.push({
47463
+ x: cand.cx,
47464
+ y: cand.cy,
47465
+ text: cand.text,
47466
+ anchor: "middle",
47467
+ color: cand.color,
47468
+ // No halo: the bg-coloured outline reads as a ghost box behind the text
47469
+ // over the tinted water/land. Context labels are muted enough to sit
47470
+ // cleanly on the basemap without one.
47471
+ halo: false,
47472
+ haloColor,
47473
+ italic: cand.italic,
47474
+ letterSpacing: cand.letterSpacing,
47475
+ ...cand.lines.length > 1 ? { lines: cand.lines } : {},
47476
+ lineNumber: 0
47477
+ });
47478
+ }
47479
+ return placed;
47480
+ }
47481
+ var FONT, LINE_HEIGHT, PADX, PADY, WATER_LETTER_SPACING, CONTEXT_PAD, EDGE_CLAMP_MARGIN, EDGE_CLAMP_OVERSHOOT, KIND_ORDER;
47482
+ var init_context_labels = __esm({
47483
+ "src/map/context-labels.ts"() {
47484
+ "use strict";
47485
+ init_color_utils();
47486
+ init_legend_constants();
47487
+ FONT = 11;
47488
+ LINE_HEIGHT = FONT + 2;
47489
+ PADX = 4;
47490
+ PADY = 3;
47491
+ WATER_LETTER_SPACING = 1.5;
47492
+ CONTEXT_PAD = 4;
47493
+ EDGE_CLAMP_MARGIN = 8;
47494
+ EDGE_CLAMP_OVERSHOOT = 0.35;
47495
+ KIND_ORDER = {
47496
+ ocean: 0,
47497
+ sea: 1,
47498
+ gulf: 2,
47499
+ bay: 3,
47500
+ strait: 4,
47501
+ channel: 5,
47502
+ sound: 6
47503
+ };
47504
+ }
47505
+ });
47506
+
46647
47507
  // src/map/layout.ts
46648
47508
  function geomObject2(topo) {
46649
47509
  const key = Object.keys(topo.objects)[0];
46650
47510
  return topo.objects[key];
46651
47511
  }
47512
+ function mergeFeatures(a, b) {
47513
+ const polysOf = (f) => {
47514
+ const g = f.geometry;
47515
+ if (!g) return null;
47516
+ if (g.type === "Polygon") return [g.coordinates];
47517
+ if (g.type === "MultiPolygon") return g.coordinates;
47518
+ return null;
47519
+ };
47520
+ const pa = polysOf(a);
47521
+ const pb = polysOf(b);
47522
+ if (!pa || !pb) return a;
47523
+ return {
47524
+ ...a,
47525
+ geometry: { type: "MultiPolygon", coordinates: [...pa, ...pb] }
47526
+ };
47527
+ }
46652
47528
  function decodeLayer(topo) {
47529
+ const cached = decodeCache.get(topo);
47530
+ if (cached) return cached;
46653
47531
  const out = /* @__PURE__ */ new Map();
46654
47532
  for (const g of geomObject2(topo).geometries) {
46655
47533
  const f = (0, import_topojson_client2.feature)(topo, g);
46656
- out.set(g.id, { ...f, id: g.id });
47534
+ if (!f.geometry) continue;
47535
+ const tagged = { ...f, id: g.id };
47536
+ const existing = out.get(g.id);
47537
+ out.set(g.id, existing ? mergeFeatures(existing, tagged) : tagged);
46657
47538
  }
47539
+ decodeCache.set(topo, out);
46658
47540
  return out;
46659
47541
  }
46660
47542
  function projectionFor(family) {
@@ -46663,9 +47545,12 @@ function projectionFor(family) {
46663
47545
  return usConusProjection();
46664
47546
  case "mercator":
46665
47547
  return (0, import_d3_geo2.geoMercator)();
47548
+ case "equal-earth":
47549
+ return (0, import_d3_geo2.geoEqualEarth)();
47550
+ case "equirectangular":
47551
+ return (0, import_d3_geo2.geoEquirectangular)();
46666
47552
  case "natural-earth":
46667
47553
  return (0, import_d3_geo2.geoNaturalEarth1)();
46668
- case "equirectangular":
46669
47554
  default:
46670
47555
  return (0, import_d3_geo2.geoEquirectangular)();
46671
47556
  }
@@ -46684,13 +47569,11 @@ function mapNeutralLandColor(palette, isDark, _dataActive = false) {
46684
47569
  isDark ? LAND_TINT_DARK : LAND_TINT_LIGHT
46685
47570
  );
46686
47571
  }
46687
- function layoutMap(resolved, data, size, opts) {
46688
- const { palette, isDark } = opts;
46689
- const { width, height } = size;
47572
+ function buildMapProjection(resolved, data) {
46690
47573
  const wantsUsStates = resolved.basemaps.subdivisions.includes("us-states");
46691
- const usCrisp = resolved.projection === "albers-usa" && wantsUsStates && !!data.naLand;
47574
+ const usCrisp = (resolved.projection === "albers-usa" || resolved.projection === "mercator") && wantsUsStates && !!data.naLand;
46692
47575
  const worldTopo = usCrisp ? data.worldDetail : resolved.basemaps.world === "detail" ? data.worldDetail : data.worldCoarse;
46693
- const worldLayer = decodeLayer(worldTopo);
47576
+ const worldLayer = new Map(decodeLayer(worldTopo));
46694
47577
  if (usCrisp && data.naLand) {
46695
47578
  const [nbW, nbS, nbE, nbN] = [-140, 10, -52, 66];
46696
47579
  const crisp = decodeLayer(data.naLand);
@@ -46699,16 +47582,141 @@ function layoutMap(resolved, data, size, opts) {
46699
47582
  if (!base) continue;
46700
47583
  const [[bw, bs], [be, bn]] = (0, import_d3_geo2.geoBounds)(base);
46701
47584
  if (bw >= nbW && be <= nbE && bs >= nbS && bn <= nbN)
46702
- worldLayer.set(iso, cf);
47585
+ worldLayer.set(iso, { ...cf, properties: base.properties });
46703
47586
  }
46704
47587
  }
46705
47588
  const usLayer = wantsUsStates ? decodeLayer(data.usStates) : null;
47589
+ const extentOutline = () => {
47590
+ const [[w, s], [e, n]] = resolved.extent;
47591
+ const N = 16;
47592
+ const coords = [];
47593
+ for (let i = 0; i <= N; i++) {
47594
+ const t = i / N;
47595
+ const lon = w + (e - w) * t;
47596
+ const lat = s + (n - s) * t;
47597
+ coords.push([lon, s], [lon, n], [w, lat], [e, lat]);
47598
+ }
47599
+ return {
47600
+ type: "Feature",
47601
+ properties: {},
47602
+ geometry: { type: "MultiPoint", coordinates: coords }
47603
+ };
47604
+ };
47605
+ let fitFeatures;
47606
+ if (resolved.projection === "albers-usa" && usLayer) {
47607
+ fitFeatures = [...usLayer.entries()].filter(([iso]) => !US_NON_CONUS.has(iso)).map(([, f]) => f);
47608
+ const neighborPoints = resolved.pois.filter((p) => !inAlaska(p.lon, p.lat) && !inHawaii(p.lon, p.lat)).map((p) => [p.lon, p.lat]);
47609
+ if (neighborPoints.length > 0) {
47610
+ fitFeatures.push({
47611
+ type: "Feature",
47612
+ properties: {},
47613
+ geometry: { type: "MultiPoint", coordinates: neighborPoints }
47614
+ });
47615
+ }
47616
+ for (const r of resolved.regions) {
47617
+ if (r.layer === "country" && (r.iso === "CA" || r.iso === "MX")) {
47618
+ const cf = worldLayer.get(r.iso);
47619
+ if (cf) fitFeatures.push(cf);
47620
+ }
47621
+ }
47622
+ } else {
47623
+ fitFeatures = [extentOutline()];
47624
+ }
47625
+ const fitTarget = { type: "FeatureCollection", features: fitFeatures };
47626
+ const projection = projectionFor(resolved.projection);
47627
+ if (resolved.projection !== "albers-usa") {
47628
+ let centerLon = (resolved.extent[0][0] + resolved.extent[1][0]) / 2;
47629
+ if (centerLon > 180) centerLon -= 360;
47630
+ projection.rotate([-centerLon, 0]);
47631
+ }
47632
+ const fitGB = (0, import_d3_geo2.geoBounds)(fitTarget);
47633
+ const fitIsGlobal = fitGB[1][0] - fitGB[0][0] >= 270 || fitGB[1][1] - fitGB[0][1] >= 130;
47634
+ return {
47635
+ projection,
47636
+ fitTarget,
47637
+ fitIsGlobal,
47638
+ worldLayer,
47639
+ usLayer,
47640
+ usCrisp,
47641
+ wantsUsStates,
47642
+ worldTopo
47643
+ };
47644
+ }
47645
+ function parsePathRings(d) {
47646
+ const rings = [];
47647
+ let cur = [];
47648
+ const re = /([MLZ])([^MLZ]*)/g;
47649
+ let m;
47650
+ while (m = re.exec(d)) {
47651
+ if (m[1] === "Z") {
47652
+ if (cur.length) rings.push(cur);
47653
+ cur = [];
47654
+ continue;
47655
+ }
47656
+ if (m[1] === "M" && cur.length) {
47657
+ rings.push(cur);
47658
+ cur = [];
47659
+ }
47660
+ const nums = m[2].split(/[ ,]+/).map(Number);
47661
+ for (let i = 0; i + 1 < nums.length; i += 2) {
47662
+ const x = nums[i];
47663
+ const y = nums[i + 1];
47664
+ if (Number.isFinite(x) && Number.isFinite(y)) cur.push([x, y]);
47665
+ }
47666
+ }
47667
+ if (cur.length) rings.push(cur);
47668
+ return rings;
47669
+ }
47670
+ function dropAntimeridianWrapSlivers(d, width, height) {
47671
+ const rings = parsePathRings(d);
47672
+ if (rings.length <= 1) return d;
47673
+ const eps = 0.75;
47674
+ const minArea = 3e-3 * width * height;
47675
+ const ringArea = (r) => {
47676
+ let s = 0;
47677
+ for (let i = 0; i < r.length; i++) {
47678
+ const a = r[i];
47679
+ const b = r[(i + 1) % r.length];
47680
+ s += a[0] * b[1] - b[0] * a[1];
47681
+ }
47682
+ return Math.abs(s) / 2;
47683
+ };
47684
+ const areas = rings.map(ringArea);
47685
+ const maxArea = Math.max(...areas);
47686
+ 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;
47687
+ let dropped = false;
47688
+ const kept = rings.filter((r, idx) => {
47689
+ if (areas[idx] >= maxArea || areas[idx] >= minArea) return true;
47690
+ const touches = r.some((p, i) => onVEdge(p, r[(i + 1) % r.length]));
47691
+ if (touches) {
47692
+ dropped = true;
47693
+ return false;
47694
+ }
47695
+ return true;
47696
+ });
47697
+ if (!dropped) return d;
47698
+ return kept.map(
47699
+ (r) => r.map((p, i) => (i ? "L" : "M") + p[0] + "," + p[1]).join("") + "Z"
47700
+ ).join("");
47701
+ }
47702
+ function layoutMap(resolved, data, size, opts) {
47703
+ const { palette, isDark } = opts;
47704
+ const { width, height } = size;
47705
+ const {
47706
+ projection,
47707
+ fitTarget,
47708
+ fitIsGlobal,
47709
+ worldLayer,
47710
+ usLayer,
47711
+ usCrisp,
47712
+ worldTopo
47713
+ } = buildMapProjection(resolved, data);
46706
47714
  const usContext = usLayer !== null;
46707
47715
  const regionStroke = isDark ? mix(palette.bg, palette.text, 78) : mix(palette.text, palette.bg, 78);
46708
47716
  const values = resolved.regions.filter((r) => r.value !== void 0).map((r) => r.value);
46709
- const scaleOverride = resolved.directives.scale;
46710
- const rampMin = scaleOverride ? scaleOverride.min : Math.min(...values);
46711
- const rampMax = scaleOverride ? scaleOverride.max : Math.max(...values);
47717
+ const allNonNegative = values.length > 0 && values.every((v) => v >= 0);
47718
+ const rampMin = allNonNegative ? 0 : Math.min(...values);
47719
+ const rampMax = Math.max(...values);
46712
47720
  const rampHue = resolveColor(resolved.directives.regionMetricColor ?? "", palette) ?? palette.colors.red;
46713
47721
  const hasRamp = values.length > 0;
46714
47722
  const VALUE_NAME = hasRamp ? resolved.directives.regionMetric?.trim() || "Value" : null;
@@ -46729,7 +47737,7 @@ function layoutMap(resolved, data, size, opts) {
46729
47737
  activeGroup = VALUE_NAME ?? (resolved.tagGroups.length > 0 ? resolved.tagGroups[0].name : null);
46730
47738
  }
46731
47739
  const activeIsScore = VALUE_NAME !== null && activeGroup === VALUE_NAME;
46732
- const mutedBasemap = resolved.directives.basemapStyle === "muted" ? true : resolved.directives.basemapStyle === "natural" ? false : activeGroup !== null;
47740
+ const mutedBasemap = activeGroup !== null;
46733
47741
  const neutralFill = mapNeutralLandColor(palette, isDark, mutedBasemap);
46734
47742
  const water = mapBackgroundColor(palette, isDark, mutedBasemap);
46735
47743
  const lakeStroke = mix(regionStroke, water, 45);
@@ -46738,10 +47746,43 @@ function layoutMap(resolved, data, size, opts) {
46738
47746
  palette.bg,
46739
47747
  mutedBasemap ? isDark ? MUTED_FOREIGN_DARK : MUTED_FOREIGN_LIGHT : isDark ? FOREIGN_TINT_DARK : FOREIGN_TINT_LIGHT
46740
47748
  );
47749
+ const colorizeActive = resolved.directives.noColorize !== true && !hasRamp && resolved.tagGroups.length === 0;
47750
+ const colorByIso = /* @__PURE__ */ new Map();
47751
+ if (colorizeActive) {
47752
+ const adjacency = /* @__PURE__ */ new Map();
47753
+ const addEdges = (src) => {
47754
+ for (const [iso, ns] of src) {
47755
+ const cur = adjacency.get(iso);
47756
+ if (cur) cur.push(...ns);
47757
+ else adjacency.set(iso, [...ns]);
47758
+ }
47759
+ };
47760
+ addEdges(buildAdjacency(worldTopo));
47761
+ if (usLayer) {
47762
+ addEdges(buildAdjacency(data.usStates));
47763
+ for (const [country, states] of Object.entries(FOREIGN_BORDER)) {
47764
+ const cn = adjacency.get(country);
47765
+ if (!cn) continue;
47766
+ for (const st of states) {
47767
+ const sn = adjacency.get(st);
47768
+ if (!sn) continue;
47769
+ cn.push(st);
47770
+ sn.push(country);
47771
+ }
47772
+ }
47773
+ }
47774
+ const { byIso, huesNeeded } = assignColors(
47775
+ [...adjacency.keys()],
47776
+ adjacency
47777
+ );
47778
+ const tints = politicalTints(palette, huesNeeded, isDark);
47779
+ for (const [iso, idx] of byIso) colorByIso.set(iso, tints[idx]);
47780
+ }
47781
+ const colorizeStroke = (fill2) => mix(fill2, palette.text, 35);
46741
47782
  const rampBase = isDark ? mix(palette.surface, palette.text, 28) : palette.bg;
46742
47783
  const fillForValue = (s) => {
46743
47784
  const t = rampMax > rampMin ? (s - rampMin) / (rampMax - rampMin) : 1;
46744
- const pct = RAMP_FLOOR + Math.max(0, Math.min(1, t)) * (100 - RAMP_FLOOR);
47785
+ const pct = RAMP_FLOOR2 + Math.max(0, Math.min(1, t)) * (100 - RAMP_FLOOR2);
46745
47786
  return mix(rampHue, rampBase, pct);
46746
47787
  };
46747
47788
  const tagFill = (tags, groupName) => {
@@ -46773,43 +47814,15 @@ function layoutMap(resolved, data, size, opts) {
46773
47814
  if (activeIsScore) {
46774
47815
  return r.value !== void 0 ? fillForValue(r.value) : neutralFill;
46775
47816
  }
47817
+ if (colorizeActive) return (r.iso && colorByIso.get(r.iso)) ?? neutralFill;
46776
47818
  return tagFill(r.tags, activeGroup) ?? neutralFill;
46777
47819
  };
46778
47820
  const regionById = new Map(resolved.regions.map((r) => [r.iso, r]));
46779
- const extentOutline = () => {
46780
- const [[w, s], [e, n]] = resolved.extent;
46781
- const N = 16;
46782
- const coords = [];
46783
- for (let i = 0; i <= N; i++) {
46784
- const t = i / N;
46785
- const lon = w + (e - w) * t;
46786
- const lat = s + (n - s) * t;
46787
- coords.push([lon, s], [lon, n], [w, lat], [e, lat]);
46788
- }
46789
- return {
46790
- type: "Feature",
46791
- properties: {},
46792
- geometry: { type: "MultiPoint", coordinates: coords }
46793
- };
46794
- };
46795
- let fitFeatures;
46796
- if (resolved.projection === "albers-usa" && usLayer) {
46797
- fitFeatures = [...usLayer.entries()].filter(([iso]) => !US_NON_CONUS.has(iso)).map(([, f]) => f);
46798
- } else {
46799
- fitFeatures = [extentOutline()];
46800
- }
46801
- const fitTarget = { type: "FeatureCollection", features: fitFeatures };
46802
- const projection = projectionFor(resolved.projection);
46803
- if (resolved.projection !== "albers-usa") {
46804
- let centerLon = (resolved.extent[0][0] + resolved.extent[1][0]) / 2;
46805
- if (centerLon > 180) centerLon -= 360;
46806
- projection.rotate([-centerLon, 0]);
46807
- }
46808
- const TITLE_GAP = 16;
47821
+ const TITLE_GAP2 = 16;
46809
47822
  let topPad = FIT_PAD;
46810
47823
  if (resolved.title && resolved.pois.length > 0) {
46811
47824
  const bannerBottom = (resolved.subtitle ? TITLE_Y + TITLE_FONT_SIZE : TITLE_Y) + TITLE_FONT_SIZE / 2;
46812
- topPad = Math.max(FIT_PAD, bannerBottom + TITLE_GAP);
47825
+ topPad = Math.max(FIT_PAD, bannerBottom + TITLE_GAP2);
46813
47826
  }
46814
47827
  const fitBox = [
46815
47828
  [FIT_PAD, topPad],
@@ -46819,21 +47832,20 @@ function layoutMap(resolved, data, size, opts) {
46819
47832
  ]
46820
47833
  ];
46821
47834
  projection.fitExtent(fitBox, fitTarget);
46822
- const fitGB = (0, import_d3_geo2.geoBounds)(fitTarget);
46823
- const fitIsGlobal = fitGB[1][0] - fitGB[0][0] >= 270 || fitGB[1][1] - fitGB[0][1] >= 130;
46824
47835
  let path;
46825
47836
  let project;
46826
47837
  let stretchParams = null;
46827
- if (fitIsGlobal) {
47838
+ if (fitIsGlobal && !opts.preferContain) {
46828
47839
  const cb = (0, import_d3_geo2.geoPath)(projection).bounds(fitTarget);
46829
47840
  const bx0 = cb[0][0];
46830
47841
  const by0 = cb[0][1];
46831
47842
  const cw = cb[1][0] - bx0;
46832
47843
  const ch = cb[1][1] - by0;
46833
- const ox = fitBox[0][0];
46834
- const oy = fitBox[0][1];
46835
- const sx = cw > 0 ? (fitBox[1][0] - ox) / cw : 1;
46836
- const sy = ch > 0 ? (fitBox[1][1] - oy) / ch : 1;
47844
+ const topReserve = resolved.title && resolved.pois.length > 0 ? topPad : 0;
47845
+ const ox = 0;
47846
+ const oy = topReserve;
47847
+ const sx = cw > 0 ? width / cw : 1;
47848
+ const sy = ch > 0 ? (height - topReserve) / ch : 1;
46837
47849
  stretchParams = { sx, sy, ox, oy, bx0, by0 };
46838
47850
  const stretch = (x, y) => [
46839
47851
  ox + (x - bx0) * sx,
@@ -46866,7 +47878,9 @@ function layoutMap(resolved, data, size, opts) {
46866
47878
  const insets = [];
46867
47879
  const insetRegions = [];
46868
47880
  const insetLabelSeeds = [];
46869
- if (resolved.projection === "albers-usa" && usLayer && !resolved.directives.noInsets) {
47881
+ const akRef = resolved.regions.some((r) => r.iso === "US-AK") || resolved.pois.some((p) => inAlaska(p.lon, p.lat));
47882
+ const hiRef = resolved.regions.some((r) => r.iso === "US-HI") || resolved.pois.some((p) => inHawaii(p.lon, p.lat));
47883
+ if (resolved.projection === "albers-usa" && usLayer && (akRef || hiRef)) {
46870
47884
  const PAD = 8;
46871
47885
  const GAP = 12;
46872
47886
  const yB = height - FIT_PAD;
@@ -46931,8 +47945,18 @@ function layoutMap(resolved, data, size, opts) {
46931
47945
  );
46932
47946
  const d = (0, import_d3_geo2.geoPath)(proj)(f) ?? "";
46933
47947
  if (!d) return xr;
47948
+ let contextLand;
47949
+ if (iso === "US-AK") {
47950
+ const can = worldLayer.get("CA");
47951
+ const cd = can ? (0, import_d3_geo2.geoPath)(proj)(can) ?? "" : "";
47952
+ if (cd)
47953
+ contextLand = {
47954
+ d: cd,
47955
+ fill: colorizeActive ? colorByIso.get("CA") ?? foreignFill : foreignFill
47956
+ };
47957
+ }
46934
47958
  const r = regionById.get(iso);
46935
- let fill2 = neutralFill;
47959
+ let fill2 = colorizeActive ? colorByIso.get(iso) ?? neutralFill : neutralFill;
46936
47960
  let lineNumber = -1;
46937
47961
  if (r?.layer === "us-state") {
46938
47962
  fill2 = regionFill(r);
@@ -46951,13 +47975,14 @@ function layoutMap(resolved, data, size, opts) {
46951
47975
  ],
46952
47976
  // The FITTED inset projection (just fit to this box) — captured so the
46953
47977
  // geo-query can invert pixels inside the frame back to AK/HI coords.
46954
- projection: proj
47978
+ projection: proj,
47979
+ ...contextLand && { contextLand }
46955
47980
  });
46956
47981
  insetRegions.push({
46957
47982
  id: iso,
46958
47983
  d,
46959
47984
  fill: fill2,
46960
- stroke: regionStroke,
47985
+ stroke: colorizeActive ? colorizeStroke(fill2) : regionStroke,
46961
47986
  lineNumber,
46962
47987
  layer: "us-state",
46963
47988
  ...r?.value !== void 0 && { value: r.value },
@@ -46970,13 +47995,16 @@ function layoutMap(resolved, data, size, opts) {
46970
47995
  }
46971
47996
  return xr;
46972
47997
  };
46973
- const akRight = placeInset(
46974
- "US-AK",
46975
- alaskaProjection(),
46976
- FIT_PAD,
46977
- width * 0.15
46978
- );
46979
- placeInset("US-HI", hawaiiProjection(), akRight + 24, width * 0.1);
47998
+ let akRight = FIT_PAD;
47999
+ if (akRef)
48000
+ akRight = placeInset("US-AK", alaskaProjection(), FIT_PAD, width * 0.15);
48001
+ if (hiRef)
48002
+ placeInset(
48003
+ "US-HI",
48004
+ hawaiiProjection(),
48005
+ akRef ? akRight + 24 : FIT_PAD,
48006
+ width * 0.1
48007
+ );
46980
48008
  }
46981
48009
  const conusFit = resolved.projection === "albers-usa" && !!usLayer;
46982
48010
  const classifyExtent = conusFit ? (0, import_d3_geo2.geoBounds)(fitTarget) : resolved.extent;
@@ -46992,15 +48020,24 @@ function layoutMap(resolved, data, size, opts) {
46992
48020
  };
46993
48021
  const ringOverlapsView = (ring) => {
46994
48022
  let loMin = Infinity, loMax = -Infinity, rawMin = Infinity, rawMax = -Infinity;
48023
+ const lons = [];
46995
48024
  for (const [rawLon] of ring) {
46996
48025
  const lon = normLon(rawLon);
48026
+ lons.push(lon);
46997
48027
  if (lon < loMin) loMin = lon;
46998
48028
  if (lon > loMax) loMax = lon;
46999
48029
  if (rawLon < rawMin) rawMin = rawLon;
47000
48030
  if (rawLon > rawMax) rawMax = rawLon;
47001
48031
  }
47002
- if (loMax - loMin > 270) return false;
47003
- if (rawMax - rawMin > 180 && loMax - loMin < 90) return false;
48032
+ lons.sort((a, b) => a - b);
48033
+ let maxGap = 0;
48034
+ for (let i = 1; i < lons.length; i++)
48035
+ maxGap = Math.max(maxGap, lons[i] - lons[i - 1]);
48036
+ if (lons.length > 1)
48037
+ maxGap = Math.max(maxGap, lons[0] + 360 - lons[lons.length - 1]);
48038
+ const occupiedArc = 360 - maxGap;
48039
+ if (occupiedArc > 270) return false;
48040
+ if (rawMax - rawMin > 180 && occupiedArc < 90) return false;
47004
48041
  let px0 = Infinity, py0 = Infinity, px1 = -Infinity, py1 = -Infinity, anyFinite = false;
47005
48042
  for (const [lon, lat] of ring) {
47006
48043
  const p = project(lon, lat);
@@ -47073,7 +48110,7 @@ function layoutMap(resolved, data, size, opts) {
47073
48110
  const regions = [];
47074
48111
  const pushRegionLayer = (layerFeatures, layerKind, shouldCull) => {
47075
48112
  for (const [iso, f] of layerFeatures) {
47076
- if (layerKind === "us-state" && usContext && INSET_STATES.has(iso))
48113
+ if (layerKind === "us-state" && usContext && resolved.projection === "albers-usa" && INSET_STATES.has(iso))
47077
48114
  continue;
47078
48115
  if (layerKind === "country" && usContext && iso === "US") continue;
47079
48116
  if (layerKind === "country" && iso === "AQ" && !regionById.has("AQ"))
@@ -47081,11 +48118,13 @@ function layoutMap(resolved, data, size, opts) {
47081
48118
  const r = regionById.get(iso);
47082
48119
  const viewF = shouldCull ? cullFeatureToView(f) : dropFrameFillers(f);
47083
48120
  if (!viewF) continue;
47084
- const d = path(viewF) ?? "";
48121
+ const raw = path(viewF) ?? "";
48122
+ const d = fitIsGlobal ? dropAntimeridianWrapSlivers(raw, width, height) : raw;
47085
48123
  if (!d) continue;
47086
48124
  const isThisLayer = r?.layer === layerKind;
47087
48125
  const isForeign = layerKind === "country" && usContext && iso !== "US";
47088
- let fill2 = isForeign ? foreignFill : neutralFill;
48126
+ const baseFill = isForeign ? foreignFill : neutralFill;
48127
+ let fill2 = colorizeActive ? colorByIso.get(iso) ?? baseFill : baseFill;
47089
48128
  let label;
47090
48129
  let lineNumber = -1;
47091
48130
  let layer = "base";
@@ -47094,15 +48133,21 @@ function layoutMap(resolved, data, size, opts) {
47094
48133
  lineNumber = r.lineNumber;
47095
48134
  layer = layerKind;
47096
48135
  label = r.name;
48136
+ } else {
48137
+ label = f.properties?.name;
47097
48138
  }
48139
+ const labelAnchor = WORLD_LABEL_ANCHORS[iso];
48140
+ const c = labelAnchor ? project(labelAnchor[0], labelAnchor[1]) : path.centroid(viewF);
48141
+ const hasCentroid = c != null && Number.isFinite(c[0]) && Number.isFinite(c[1]);
47098
48142
  regions.push({
47099
48143
  id: iso,
47100
48144
  d,
47101
48145
  fill: fill2,
47102
- stroke: regionStroke,
48146
+ stroke: colorizeActive ? colorizeStroke(fill2) : regionStroke,
47103
48147
  lineNumber,
47104
48148
  layer,
47105
48149
  ...label !== void 0 && { label },
48150
+ ...hasCentroid && { labelX: c[0], labelY: c[1] },
47106
48151
  ...isThisLayer && r.value !== void 0 && { value: r.value },
47107
48152
  ...isThisLayer && Object.keys(r.tags).length > 0 && { tags: r.tags }
47108
48153
  });
@@ -47127,9 +48172,41 @@ function layoutMap(resolved, data, size, opts) {
47127
48172
  });
47128
48173
  }
47129
48174
  }
48175
+ const pointInRings = (px, py, rings) => {
48176
+ let inside = false;
48177
+ for (const ring of rings) {
48178
+ for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
48179
+ const [xi, yi] = ring[i];
48180
+ const [xj, yj] = ring[j];
48181
+ if (yi > py !== yj > py && px < (xj - xi) * (py - yi) / (yj - yi) + xi)
48182
+ inside = !inside;
48183
+ }
48184
+ }
48185
+ return inside;
48186
+ };
48187
+ const fillHitTargets = [...regions, ...insetRegions].map((r) => ({
48188
+ fill: r.fill,
48189
+ rings: parsePathRings(r.d)
48190
+ }));
48191
+ const fillAt = (x, y) => {
48192
+ let hit = water;
48193
+ for (const t of fillHitTargets)
48194
+ if (pointInRings(x, y, t.rings)) hit = t.fill;
48195
+ return hit;
48196
+ };
48197
+ const labelOnFill = (fill2) => {
48198
+ const color = contrastRatio(fill2, palette.textOnFillDark) >= contrastRatio(fill2, palette.textOnFillLight) ? palette.textOnFillDark : palette.textOnFillLight;
48199
+ const haloColor = color === palette.textOnFillLight ? palette.textOnFillDark : palette.textOnFillLight;
48200
+ return {
48201
+ color,
48202
+ halo: contrastRatio(fill2, color) < REGION_LABEL_HALO_RATIO,
48203
+ haloColor
48204
+ };
48205
+ };
48206
+ const reliefAllowed = resolved.directives.noRelief !== true;
47130
48207
  const relief = [];
47131
48208
  let reliefHatch = null;
47132
- if (resolved.directives.relief === true && data.mountainRanges) {
48209
+ if (reliefAllowed && data.mountainRanges) {
47133
48210
  for (const [, f] of decodeLayer(data.mountainRanges)) {
47134
48211
  const viewF = isGlobalView ? dropFrameFillers(f) : cullFeatureToView(f);
47135
48212
  if (!viewF) continue;
@@ -47145,16 +48222,32 @@ function layoutMap(resolved, data, size, opts) {
47145
48222
  if (relief.length) {
47146
48223
  const darkTone = isDark ? palette.bg : palette.text;
47147
48224
  const lightTone = isDark ? palette.text : palette.bg;
47148
- const landLum = relativeLuminance(neutralFill);
48225
+ const reliefLandRef = colorizeActive ? isDark ? palette.surface : palette.bg : neutralFill;
48226
+ const landLum = relativeLuminance(reliefLandRef);
47149
48227
  const tone = Math.abs(landLum - relativeLuminance(darkTone)) > 0.04 ? darkTone : lightTone;
47150
48228
  reliefHatch = {
47151
- color: mix(tone, neutralFill, RELIEF_HATCH_STRENGTH),
48229
+ color: mix(tone, reliefLandRef, RELIEF_HATCH_STRENGTH),
47152
48230
  spacing: RELIEF_HATCH_SPACING,
47153
48231
  width: RELIEF_HATCH_WIDTH
47154
48232
  };
47155
48233
  }
47156
48234
  }
47157
- const riverColor = mix(water, regionStroke, 16);
48235
+ let coastlineStyle = null;
48236
+ if (resolved.directives.noCoastline !== true) {
48237
+ const minDim = Math.min(width, height);
48238
+ coastlineStyle = {
48239
+ color: mix(regionStroke, water, COASTLINE_STROKE_MIX),
48240
+ // N equal-width rings: distance steps outward by COASTLINE_STEP; opacity
48241
+ // fades linearly from NEAR (innermost) to FAR (outermost).
48242
+ lines: Array.from({ length: COASTLINE_RING_COUNT }, (_, k) => ({
48243
+ d: (COASTLINE_D0 + k * COASTLINE_STEP) * minDim,
48244
+ thickness: COASTLINE_THICKNESS * minDim,
48245
+ opacity: COASTLINE_OPACITY_NEAR + (COASTLINE_OPACITY_FAR - COASTLINE_OPACITY_NEAR) * k / (COASTLINE_RING_COUNT - 1)
48246
+ })),
48247
+ minExtent: (isGlobalView ? COASTLINE_MIN_EXTENT_GLOBAL : COASTLINE_MIN_EXTENT) * minDim
48248
+ };
48249
+ }
48250
+ const riverColor = mix(palette.colors.blue, water, 32);
47158
48251
  const rivers = [];
47159
48252
  if (data.rivers) {
47160
48253
  for (const [, f] of decodeLayer(data.rivers)) {
@@ -47210,38 +48303,108 @@ function layoutMap(resolved, data, size, opts) {
47210
48303
  const xy = project(p.lon, p.lat);
47211
48304
  if (xy) projected.push({ p, xy });
47212
48305
  }
47213
- const coloGroups = /* @__PURE__ */ new Map();
48306
+ const placePoi = (e, cx, cy, clusterId) => {
48307
+ const { fill: fill2, stroke: stroke2 } = poiFill(e.p);
48308
+ poiScreen.set(e.p.id, { cx, cy, r: radiusFor(e.p) });
48309
+ const num = routeNumberById.get(e.p.id);
48310
+ pois.push({
48311
+ id: e.p.id,
48312
+ cx,
48313
+ cy,
48314
+ r: radiusFor(e.p),
48315
+ fill: fill2,
48316
+ stroke: stroke2,
48317
+ lineNumber: e.p.lineNumber,
48318
+ implicit: !!e.p.implicit,
48319
+ isOrigin: originIds.has(e.p.id),
48320
+ ...num !== void 0 && { routeNumber: num },
48321
+ ...Object.keys(e.p.tags).length > 0 && { tags: e.p.tags },
48322
+ ...clusterId !== void 0 && { clusterId }
48323
+ });
48324
+ };
48325
+ const clusters = [];
48326
+ const connected = /* @__PURE__ */ new Set();
48327
+ for (const e of resolved.edges) {
48328
+ connected.add(e.fromId);
48329
+ connected.add(e.toId);
48330
+ }
48331
+ for (const rt of resolved.routes) {
48332
+ rt.stopIds.forEach((id) => connected.add(id));
48333
+ }
48334
+ const radiusOf = (e) => radiusFor(e.p);
47214
48335
  for (const e of projected) {
47215
- const key = `${Math.round(e.xy[0] / COLO_EPS)},${Math.round(e.xy[1] / COLO_EPS)}`;
47216
- const arr = coloGroups.get(key);
47217
- if (arr) arr.push(e);
47218
- else coloGroups.set(key, [e]);
47219
- }
47220
- for (const group of coloGroups.values()) {
47221
- group.forEach((e, i) => {
47222
- let cx = e.xy[0];
47223
- let cy = e.xy[1];
47224
- if (group.length > 1) {
47225
- const ang = i * GOLDEN_ANGLE;
47226
- cx += Math.cos(ang) * COLO_R;
47227
- cy += Math.sin(ang) * COLO_R;
47228
- }
47229
- const { fill: fill2, stroke: stroke2 } = poiFill(e.p);
47230
- poiScreen.set(e.p.id, { cx, cy, r: radiusFor(e.p) });
47231
- const num = routeNumberById.get(e.p.id);
47232
- pois.push({
47233
- id: e.p.id,
47234
- cx,
47235
- cy,
47236
- r: radiusFor(e.p),
47237
- fill: fill2,
47238
- stroke: stroke2,
47239
- lineNumber: e.p.lineNumber,
47240
- implicit: !!e.p.implicit,
47241
- isOrigin: originIds.has(e.p.id),
47242
- ...num !== void 0 && { routeNumber: num },
47243
- ...Object.keys(e.p.tags).length > 0 && { tags: e.p.tags }
47244
- });
48336
+ if (connected.has(e.p.id)) placePoi(e, e.xy[0], e.xy[1]);
48337
+ }
48338
+ const groups = [];
48339
+ for (const e of projected) {
48340
+ if (connected.has(e.p.id)) continue;
48341
+ const r = radiusOf(e);
48342
+ const near = groups.find(
48343
+ (g) => g.some(
48344
+ (q) => Math.hypot(q.xy[0] - e.xy[0], q.xy[1] - e.xy[1]) < (r + radiusOf(q)) * STACK_OVERLAP
48345
+ )
48346
+ );
48347
+ if (near) near.push(e);
48348
+ else groups.push([e]);
48349
+ }
48350
+ for (const g of groups) {
48351
+ if (g.length === 1) {
48352
+ placePoi(g[0], g[0].xy[0], g[0].xy[1]);
48353
+ continue;
48354
+ }
48355
+ const clusterId = g[0].p.id;
48356
+ const cx0 = g.reduce((s, e) => s + e.xy[0], 0) / g.length;
48357
+ const cy0 = g.reduce((s, e) => s + e.xy[1], 0) / g.length;
48358
+ const maxR = Math.max(...g.map(radiusOf));
48359
+ const sep = 2 * maxR + STACK_RING_GAP;
48360
+ const ringR = Math.max(
48361
+ COLO_R,
48362
+ sep / (2 * Math.sin(Math.PI / Math.max(g.length, 2)))
48363
+ );
48364
+ const positions = g.map((e, i) => {
48365
+ if (g.length <= STACK_RING_MAX) {
48366
+ const ang2 = -Math.PI / 2 + i * 2 * Math.PI / g.length;
48367
+ return {
48368
+ e,
48369
+ mx: cx0 + Math.cos(ang2) * ringR,
48370
+ my: cy0 + Math.sin(ang2) * ringR
48371
+ };
48372
+ }
48373
+ const ang = i * GOLDEN_ANGLE;
48374
+ const rr = ringR * Math.sqrt((i + 1) / g.length);
48375
+ return { e, mx: cx0 + Math.cos(ang) * rr, my: cy0 + Math.sin(ang) * rr };
48376
+ });
48377
+ let minX = cx0 - maxR;
48378
+ let maxX = cx0 + maxR;
48379
+ let minY = cy0 - maxR;
48380
+ let maxY = cy0 + maxR;
48381
+ for (const { mx, my, e } of positions) {
48382
+ const r = radiusOf(e);
48383
+ minX = Math.min(minX, mx - r);
48384
+ maxX = Math.max(maxX, mx + r);
48385
+ minY = Math.min(minY, my - r);
48386
+ maxY = Math.max(maxY, my + r);
48387
+ }
48388
+ let dx = 0;
48389
+ let dy = 0;
48390
+ if (minX + dx < 2) dx = 2 - minX;
48391
+ if (maxX + dx > width - 2) dx = width - 2 - maxX;
48392
+ if (minY + dy < 2) dy = 2 - minY;
48393
+ if (maxY + dy > height - 2) dy = height - 2 - maxY;
48394
+ const legsOut = [];
48395
+ for (const { e, mx, my } of positions) {
48396
+ const fx = mx + dx;
48397
+ const fy = my + dy;
48398
+ placePoi(e, fx, fy, clusterId);
48399
+ legsOut.push({ x2: fx, y2: fy, color: poiFill(e.p).fill });
48400
+ }
48401
+ clusters.push({
48402
+ id: clusterId,
48403
+ cx: cx0 + dx,
48404
+ cy: cy0 + dy,
48405
+ count: g.length,
48406
+ hitR: ringR + maxR + 6,
48407
+ legs: legsOut
47245
48408
  });
47246
48409
  }
47247
48410
  const legs = [];
@@ -47291,16 +48454,26 @@ function layoutMap(resolved, data, size, opts) {
47291
48454
  if (!a || !b) continue;
47292
48455
  const mx = (a.cx + b.cx) / 2;
47293
48456
  const my = (a.cy + b.cy) / 2;
48457
+ const bow = {
48458
+ curved: leg.style === "arc",
48459
+ offset: 0,
48460
+ labelX: mx,
48461
+ labelY: my - 4
48462
+ };
48463
+ const routeLabelStyle = leg.label !== void 0 ? labelOnFill(fillAt(bow.labelX, bow.labelY)) : void 0;
47294
48464
  legs.push({
47295
- d: legPath(a, b, leg.style === "arc", 0),
48465
+ d: legPath(a, b, bow.curved, bow.offset),
47296
48466
  width: routeWidthFor(Number(leg.value)),
47297
48467
  color: mix(palette.text, palette.bg, 72),
47298
48468
  arrow: true,
47299
48469
  lineNumber: leg.lineNumber,
47300
48470
  ...leg.label !== void 0 && {
47301
48471
  label: leg.label,
47302
- labelX: mx,
47303
- labelY: my - 4
48472
+ labelX: bow.labelX,
48473
+ labelY: bow.labelY,
48474
+ labelColor: routeLabelStyle.color,
48475
+ labelHalo: routeLabelStyle.halo,
48476
+ labelHaloColor: routeLabelStyle.haloColor
47304
48477
  }
47305
48478
  });
47306
48479
  }
@@ -47328,20 +48501,29 @@ function layoutMap(resolved, data, size, opts) {
47328
48501
  const a = poiScreen.get(e.fromId);
47329
48502
  const b = poiScreen.get(e.toId);
47330
48503
  if (!a || !b) return;
47331
- const curved = e.style === "arc" || n > 1;
47332
- const offset = n > 1 ? (i - (n - 1) / 2) * FAN_STEP : 0;
48504
+ const fanOffset = n > 1 ? (i - (n - 1) / 2) * FAN_STEP : 0;
47333
48505
  const mx = (a.cx + b.cx) / 2;
47334
48506
  const my = (a.cy + b.cy) / 2;
48507
+ const bow = {
48508
+ curved: e.style === "arc" || n > 1,
48509
+ offset: fanOffset,
48510
+ labelX: mx,
48511
+ labelY: my - 4
48512
+ };
48513
+ const edgeLabelStyle = e.label !== void 0 ? labelOnFill(fillAt(bow.labelX, bow.labelY)) : void 0;
47335
48514
  legs.push({
47336
- d: legPath(a, b, curved, offset),
48515
+ d: legPath(a, b, bow.curved, bow.offset),
47337
48516
  width: widthFor(e),
47338
48517
  color: mix(palette.text, palette.bg, 66),
47339
48518
  arrow: e.directed,
47340
48519
  lineNumber: e.lineNumber,
47341
48520
  ...e.label !== void 0 && {
47342
48521
  label: e.label,
47343
- labelX: mx,
47344
- labelY: my - 4
48522
+ labelX: bow.labelX,
48523
+ labelY: bow.labelY,
48524
+ labelColor: edgeLabelStyle.color,
48525
+ labelHalo: edgeLabelStyle.halo,
48526
+ labelHaloColor: edgeLabelStyle.haloColor
47345
48527
  }
47346
48528
  });
47347
48529
  });
@@ -47383,48 +48565,73 @@ function layoutMap(resolved, data, size, opts) {
47383
48565
  }
47384
48566
  }
47385
48567
  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));
47386
- const regionLabelMode = resolved.directives.regionLabels ?? "off";
48568
+ const showRegionLabels = resolved.directives.noRegionLabels !== true;
48569
+ const isCompact = width < COMPACT_WIDTH_PX;
47387
48570
  const LABEL_PADX = 6;
47388
48571
  const LABEL_PADY = 3;
47389
- const labelW = (text) => measureLegendText(text, FONT) + 2 * LABEL_PADX;
47390
- const labelH = FONT + 2 * LABEL_PADY;
48572
+ const labelW = (text) => measureLegendText(text, FONT2) + 2 * LABEL_PADX;
48573
+ const labelH = FONT2 + 2 * LABEL_PADY;
47391
48574
  const pushRegionLabel = (x, y, text, fill2, lineNumber) => {
47392
- const color = contrastText(
47393
- fill2,
47394
- palette.textOnFillLight,
47395
- palette.textOnFillDark
48575
+ const { color, haloColor } = labelOnFill(fill2);
48576
+ const halfW = measureLegendText(text, FONT2) / 2;
48577
+ const overflows = [y - FONT2 * 0.55, y - FONT2 * 0.1].some(
48578
+ (sy) => fillAt(x - halfW, sy) !== fill2 || fillAt(x + halfW, sy) !== fill2
47396
48579
  );
47397
- const haloColor = color === palette.textOnFillLight ? palette.textOnFillDark : palette.textOnFillLight;
47398
48580
  labels.push({
47399
48581
  x,
47400
48582
  y,
47401
48583
  text,
47402
48584
  anchor: "middle",
47403
48585
  color,
47404
- halo: true,
48586
+ halo: overflows,
47405
48587
  haloColor,
47406
48588
  lineNumber
47407
48589
  });
47408
48590
  };
47409
- const WORLD_LABEL_ANCHORS = {
47410
- US: [-98.5, 39.5]
47411
- // CONUS geographic centre (near Lebanon, Kansas)
48591
+ const REGION_LABEL_GAP = 2;
48592
+ const regionLabelRect = (cx, cy, text) => {
48593
+ const w = measureLegendText(text, FONT2) + 2 * REGION_LABEL_GAP;
48594
+ return { x: cx - w / 2, y: cy - FONT2 / 2, w, h: FONT2 };
47412
48595
  };
47413
- if (regionLabelMode === "full" || regionLabelMode === "abbrev") {
47414
- for (const r of regions) {
47415
- if (r.layer === "base" || r.label === void 0) continue;
47416
- const f = r.layer === "us-state" ? usLayer?.get(r.id) : worldLayer.get(r.id);
47417
- if (!f) continue;
48596
+ if (showRegionLabels) {
48597
+ const frameContainers = new Set(resolved.poiFrameContainers);
48598
+ const entries = regions.map((r) => {
48599
+ const isContainer = frameContainers.has(r.id);
48600
+ if (r.layer === "base" && !isContainer || r.label === void 0)
48601
+ return null;
48602
+ const isUsState = r.layer === "us-state" || r.id.startsWith("US-");
48603
+ const f = isUsState ? usLayer?.get(r.id) : worldLayer.get(r.id);
48604
+ if (!f) return null;
47418
48605
  const [[x0, y0], [x1, y1]] = path.bounds(f);
47419
- const text = regionLabelMode === "abbrev" ? r.id.replace(/^US-/, "") : r.label;
47420
- if (labelW(text) > x1 - x0 || labelH > y1 - y0) continue;
47421
- const anchor = r.layer !== "us-state" ? WORLD_LABEL_ANCHORS[r.id] : void 0;
48606
+ const boxW = x1 - x0;
48607
+ const boxH = y1 - y0;
48608
+ const abbrev = isUsState ? r.id.replace(/^US-/, "") : void 0;
48609
+ const candidates = abbrev !== void 0 ? isCompact ? [abbrev, r.label] : [r.label, abbrev] : [r.label];
48610
+ const anchor = !isUsState ? WORLD_LABEL_ANCHORS[r.id] : void 0;
47422
48611
  const c = anchor ? project(anchor[0], anchor[1]) : path.centroid(f);
47423
- if (!c || !Number.isFinite(c[0])) continue;
48612
+ if (!c || !Number.isFinite(c[0])) return null;
48613
+ return { r, c, boxW, boxH, area: boxW * boxH, candidates };
48614
+ }).filter((e) => e !== null).sort((a, b) => b.area - a.area || a.r.lineNumber - b.r.lineNumber);
48615
+ const placedRegionRects = [];
48616
+ const POI_LABEL_PAD = 14;
48617
+ const poiObstacles = pois.map((p) => ({
48618
+ x: p.cx - p.r - POI_LABEL_PAD,
48619
+ y: p.cy - p.r - POI_LABEL_PAD,
48620
+ w: 2 * (p.r + POI_LABEL_PAD),
48621
+ h: 2 * (p.r + POI_LABEL_PAD)
48622
+ }));
48623
+ for (const { r, c, boxW, boxH, candidates } of entries) {
48624
+ const text = candidates.find((t) => {
48625
+ if (labelW(t) > boxW || labelH > boxH) return false;
48626
+ const rect = regionLabelRect(c[0], c[1], t);
48627
+ return !placedRegionRects.some((p) => rectsOverlap(rect, p)) && !poiObstacles.some((o) => rectsOverlap(rect, o));
48628
+ });
48629
+ if (text === void 0) continue;
48630
+ placedRegionRects.push(regionLabelRect(c[0], c[1], text));
47424
48631
  pushRegionLabel(c[0], c[1], text, r.fill, r.lineNumber);
47425
48632
  }
47426
48633
  for (const seed of insetLabelSeeds) {
47427
- const text = regionLabelMode === "abbrev" ? seed.iso.replace(/^US-/, "") : seed.name;
48634
+ const text = isCompact ? seed.iso.replace(/^US-/, "") : seed.name;
47428
48635
  const src = regionById.get(seed.iso);
47429
48636
  pushRegionLabel(
47430
48637
  seed.x,
@@ -47435,22 +48642,26 @@ function layoutMap(resolved, data, size, opts) {
47435
48642
  );
47436
48643
  }
47437
48644
  }
47438
- const poiLabelMode = resolved.directives.poiLabels ?? "auto";
47439
- if (poiLabelMode !== "off") {
47440
- const ordered = [...pois].sort(
47441
- (a, b) => a.lineNumber - b.lineNumber || (a.id < b.id ? -1 : 1)
47442
- );
48645
+ if (resolved.directives.noPoiLabels !== true) {
48646
+ const ordered = [...pois].filter((p) => p.clusterId === void 0).sort((a, b) => a.lineNumber - b.lineNumber || (a.id < b.id ? -1 : 1));
47443
48647
  const poiById = new Map(resolved.pois.map((q) => [q.id, q]));
47444
48648
  const labelText = (p) => {
47445
48649
  const src = poiById.get(p.id);
47446
48650
  return src?.label ?? src?.name ?? p.id;
47447
48651
  };
47448
- const poiLabH = FONT * 1.25;
48652
+ const poiLabH = FONT2 * 1.25;
47449
48653
  const labelInfo = (p) => {
47450
48654
  const text = labelText(p);
47451
- return { text, w: measureLegendText(text, FONT) };
48655
+ return { text, w: measureLegendText(text, FONT2) };
47452
48656
  };
47453
48657
  const GAP = 3;
48658
+ const clusterMembersById = /* @__PURE__ */ new Map();
48659
+ for (const p of pois) {
48660
+ if (p.clusterId === void 0) continue;
48661
+ const arr = clusterMembersById.get(p.clusterId);
48662
+ if (arr) arr.push(p);
48663
+ else clusterMembersById.set(p.clusterId, [p]);
48664
+ }
47454
48665
  const inlineRect = (p, w, side) => {
47455
48666
  switch (side) {
47456
48667
  case "right":
@@ -47480,11 +48691,11 @@ function layoutMap(resolved, data, size, opts) {
47480
48691
  const x = side === "right" ? rect.x : side === "left" ? rect.x + w : p.cx;
47481
48692
  labels.push({
47482
48693
  x,
47483
- y: rect.y + poiLabH / 2 + FONT / 3,
48694
+ y: rect.y + poiLabH / 2 + FONT2 / 3,
47484
48695
  text,
47485
48696
  anchor,
47486
48697
  color: palette.text,
47487
- halo: true,
48698
+ halo: false,
47488
48699
  haloColor: palette.bg,
47489
48700
  poiId: p.id,
47490
48701
  lineNumber: p.lineNumber
@@ -47495,43 +48706,60 @@ function layoutMap(resolved, data, size, opts) {
47495
48706
  return rect.x >= 0 && rect.x + rect.w <= width && rect.y >= 0 && rect.y + rect.h <= height && !collides(rect);
47496
48707
  };
47497
48708
  const GROUP_R = 30;
47498
- const groups = [];
48709
+ const groups2 = [];
47499
48710
  for (const p of ordered) {
47500
- const near = groups.find(
48711
+ const near = groups2.find(
47501
48712
  (g) => g.some((q) => Math.hypot(q.cx - p.cx, q.cy - p.cy) < GROUP_R)
47502
48713
  );
47503
48714
  if (near) near.push(p);
47504
- else groups.push([p]);
48715
+ else groups2.push([p]);
47505
48716
  }
47506
48717
  const ROW_GAP2 = 3;
47507
48718
  const step = poiLabH + ROW_GAP2;
47508
48719
  const COL_GAP = 16;
47509
- const placeColumn = (group) => {
47510
- const items = group.map((p) => ({ p, ...labelInfo(p) })).sort((a, b) => a.p.cy - b.p.cy || (a.text < b.text ? -1 : 1));
48720
+ const makeItems = (group) => group.map((p) => ({ p, ...labelInfo(p) })).sort((a, b) => a.p.cy - b.p.cy || (a.text < b.text ? -1 : 1));
48721
+ const columnRows = (items, side) => {
47511
48722
  const left = Math.min(...items.map((o) => o.p.cx - o.p.r));
47512
48723
  const right = Math.max(...items.map((o) => o.p.cx + o.p.r));
47513
- const cyMid = (Math.min(...items.map((o) => o.p.cy)) + Math.max(...items.map((o) => o.p.cy))) / 2;
47514
48724
  const maxW = Math.max(...items.map((o) => o.w));
47515
- const side = right + COL_GAP + maxW <= width - 2 ? "right" : "left";
47516
- const colX = side === "right" ? right + COL_GAP : left - COL_GAP;
48725
+ const cyMid = (Math.min(...items.map((o) => o.p.cy)) + Math.max(...items.map((o) => o.p.cy))) / 2;
48726
+ const colX = side === "right" ? Math.min(right + COL_GAP, width - 2 - maxW) : Math.max(left - COL_GAP, 2 + maxW);
47517
48727
  const totalH = items.length * step;
47518
48728
  let startY = cyMid - totalH / 2;
47519
48729
  startY = Math.max(2, Math.min(startY, height - totalH - 2));
47520
- items.forEach((o, i) => {
48730
+ return items.map((o, i) => {
47521
48731
  const rowCy = startY + i * step + step / 2;
47522
- obstacles.push({
47523
- x: side === "right" ? colX : colX - o.w,
47524
- y: rowCy - poiLabH / 2,
47525
- w: o.w,
47526
- h: poiLabH
47527
- });
48732
+ return {
48733
+ o,
48734
+ colX,
48735
+ rowCy,
48736
+ rect: {
48737
+ x: side === "right" ? colX : colX - o.w,
48738
+ y: rowCy - poiLabH / 2,
48739
+ w: o.w,
48740
+ h: poiLabH
48741
+ }
48742
+ };
48743
+ });
48744
+ };
48745
+ const wouldColumnBeClean = (items, side) => columnRows(items, side).every(
48746
+ ({ rect }) => rect.x >= 0 && rect.x + rect.w <= width && rect.y >= 0 && rect.y + rect.h <= height && !collides(rect)
48747
+ );
48748
+ const defaultColumnSide = (items) => {
48749
+ const right = Math.max(...items.map((o) => o.p.cx + o.p.r));
48750
+ const maxW = Math.max(...items.map((o) => o.w));
48751
+ return right + COL_GAP + maxW <= width - 2 ? "right" : "left";
48752
+ };
48753
+ const commitColumn = (items, side, clusterId) => {
48754
+ for (const { o, colX, rowCy, rect } of columnRows(items, side)) {
48755
+ obstacles.push(rect);
47528
48756
  labels.push({
47529
48757
  x: colX,
47530
- y: rowCy + FONT / 3,
48758
+ y: rowCy + FONT2 / 3,
47531
48759
  text: o.text,
47532
48760
  anchor: side === "right" ? "start" : "end",
47533
48761
  color: palette.text,
47534
- halo: true,
48762
+ halo: false,
47535
48763
  haloColor: palette.bg,
47536
48764
  leader: {
47537
48765
  x1: o.p.cx,
@@ -47541,24 +48769,141 @@ function layoutMap(resolved, data, size, opts) {
47541
48769
  },
47542
48770
  leaderColor: o.p.fill,
47543
48771
  poiId: o.p.id,
47544
- lineNumber: o.p.lineNumber
48772
+ lineNumber: o.p.lineNumber,
48773
+ ...clusterId !== void 0 && { clusterMember: clusterId }
47545
48774
  });
48775
+ }
48776
+ };
48777
+ const pushHidden = (p) => {
48778
+ const { text, w } = labelInfo(p);
48779
+ let x = p.cx + p.r + GAP;
48780
+ let anchor = "start";
48781
+ if (x + w > width) {
48782
+ x = p.cx - p.r - GAP - w;
48783
+ anchor = "end";
48784
+ }
48785
+ const y = Math.max(0, Math.min(p.cy - poiLabH / 2, height - poiLabH));
48786
+ labels.push({
48787
+ x: anchor === "start" ? x : x + w,
48788
+ y: y + poiLabH / 2 + FONT2 / 3,
48789
+ text,
48790
+ anchor,
48791
+ color: palette.text,
48792
+ halo: false,
48793
+ haloColor: palette.bg,
48794
+ poiId: p.id,
48795
+ hidden: true,
48796
+ lineNumber: p.lineNumber
47546
48797
  });
47547
48798
  };
47548
- for (const g of groups) {
48799
+ for (const [clusterId, members] of clusterMembersById) {
48800
+ if (members.length === 0) continue;
48801
+ const items = makeItems(members);
48802
+ const side = wouldColumnBeClean(items, "right") ? "right" : wouldColumnBeClean(items, "left") ? "left" : defaultColumnSide(items);
48803
+ commitColumn(items, side, clusterId);
48804
+ }
48805
+ const maxExtent = MAX_CLUSTER_EXTENT_FACTOR * Math.min(width, height);
48806
+ const clusterPending = [];
48807
+ for (const g of groups2) {
48808
+ const items = makeItems(g);
47549
48809
  if (g.length === 1) {
47550
- const p = g[0];
47551
- const { text, w } = labelInfo(p);
48810
+ const { p, text, w } = items[0];
47552
48811
  const side = ["right", "left", "above", "below"].find(
47553
48812
  (s) => inlineFits(p, w, s)
47554
48813
  );
47555
- if (side) {
47556
- pushInline(p, text, w, side);
47557
- continue;
48814
+ if (side) pushInline(p, text, w, side);
48815
+ else commitColumn(items, defaultColumnSide(items));
48816
+ continue;
48817
+ }
48818
+ const left = Math.min(...items.map((o) => o.p.cx - o.p.r));
48819
+ const right = Math.max(...items.map((o) => o.p.cx + o.p.r));
48820
+ const minCy = Math.min(...items.map((o) => o.p.cy));
48821
+ const maxCy = Math.max(...items.map((o) => o.p.cy));
48822
+ const diag = Math.hypot(right - left, maxCy - minCy);
48823
+ if (diag > maxExtent || items.length > MAX_COLUMN_ROWS) {
48824
+ items.forEach((o) => pushHidden(o.p));
48825
+ } else {
48826
+ clusterPending.push(items);
48827
+ }
48828
+ }
48829
+ for (const items of clusterPending) {
48830
+ const side = ["right", "left"].find(
48831
+ (s) => wouldColumnBeClean(items, s)
48832
+ );
48833
+ if (side) commitColumn(items, side);
48834
+ else items.forEach((o) => pushHidden(o.p));
48835
+ }
48836
+ }
48837
+ if (resolved.directives.noContextLabels !== true) {
48838
+ for (const l of labels) {
48839
+ if (l.hidden) continue;
48840
+ const w = labelW(l.text);
48841
+ const x = l.anchor === "start" ? l.x : l.anchor === "end" ? l.x - w : l.x - w / 2;
48842
+ obstacles.push({ x, y: l.y - labelH / 2, w, h: labelH });
48843
+ }
48844
+ for (const box of insets)
48845
+ obstacles.push({ x: box.x, y: box.y, w: box.w, h: box.h });
48846
+ const countryCandidates = [];
48847
+ for (const f of worldLayer.values()) {
48848
+ const iso = typeof f.id === "string" ? f.id : String(f.id ?? "");
48849
+ if (!iso || regionById.has(iso)) continue;
48850
+ let hasReferencedSub = false;
48851
+ for (const k of regionById.keys())
48852
+ if (k.startsWith(iso + "-")) {
48853
+ hasReferencedSub = true;
48854
+ break;
47558
48855
  }
48856
+ if (hasReferencedSub) continue;
48857
+ const b = path.bounds(f);
48858
+ const [x0, y0] = b[0];
48859
+ const [x1, y1] = b[1];
48860
+ if (!Number.isFinite(x0) || !Number.isFinite(x1)) continue;
48861
+ const anchorLngLat = WORLD_LABEL_ANCHORS[iso];
48862
+ const a = anchorLngLat ? project(anchorLngLat[0], anchorLngLat[1]) : path.centroid(f);
48863
+ countryCandidates.push({
48864
+ name: f.properties?.name ?? iso,
48865
+ bbox: [x0, y0, x1, y1],
48866
+ anchor: a && Number.isFinite(a[0]) ? [a[0], a[1]] : null
48867
+ });
48868
+ }
48869
+ const framedStateContainers = (resolved.poiFrameContainers ?? []).some(
48870
+ (id) => id.startsWith("US-")
48871
+ );
48872
+ if (usLayer && framedStateContainers) {
48873
+ const containerSet = new Set(resolved.poiFrameContainers);
48874
+ for (const [iso, f] of usLayer) {
48875
+ if (containerSet.has(iso) || regionById.has(iso)) continue;
48876
+ const viewF = cullFeatureToView(f);
48877
+ if (!viewF) continue;
48878
+ const b = path.bounds(viewF);
48879
+ const [x0, y0] = b[0];
48880
+ const [x1, y1] = b[1];
48881
+ if (!Number.isFinite(x0) || !Number.isFinite(x1)) continue;
48882
+ const a = path.centroid(viewF);
48883
+ countryCandidates.push({
48884
+ name: f.properties?.name ?? iso,
48885
+ bbox: [x0, y0, x1, y1],
48886
+ anchor: a && Number.isFinite(a[0]) ? [a[0], a[1]] : null
48887
+ });
47559
48888
  }
47560
- placeColumn(g);
47561
48889
  }
48890
+ const contextLabels = placeContextLabels({
48891
+ projection: resolved.projection,
48892
+ dLonSpan,
48893
+ dLatSpan,
48894
+ width,
48895
+ height,
48896
+ waterBodies: data.waterBodies,
48897
+ countries: countryCandidates,
48898
+ palette,
48899
+ project,
48900
+ collides,
48901
+ // Water labels must stay over open water — `fillAt` returns the ocean
48902
+ // backdrop colour off-land and a region fill on-land (lakes/states count
48903
+ // as land here, which is the safe side for an ocean name).
48904
+ overLand: (x, y) => fillAt(x, y) !== water
48905
+ });
48906
+ labels.push(...contextLabels);
47562
48907
  }
47563
48908
  let legend = null;
47564
48909
  if (!resolved.directives.noLegend) {
@@ -47595,60 +48940,104 @@ function layoutMap(resolved, data, size, opts) {
47595
48940
  rivers,
47596
48941
  relief,
47597
48942
  reliefHatch,
48943
+ coastlineStyle,
47598
48944
  legs,
47599
48945
  pois,
48946
+ clusters,
47600
48947
  labels,
47601
48948
  legend,
47602
48949
  insets,
47603
48950
  insetRegions,
47604
48951
  projection,
47605
- stretch: stretchParams
48952
+ stretch: stretchParams,
48953
+ diagnostics: []
47606
48954
  };
47607
48955
  }
47608
- 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;
48956
+ 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;
47609
48957
  var init_layout15 = __esm({
47610
48958
  "src/map/layout.ts"() {
47611
48959
  "use strict";
47612
48960
  import_d3_geo2 = require("d3-geo");
47613
48961
  import_topojson_client2 = require("topojson-client");
47614
48962
  init_color_utils();
48963
+ init_geo();
48964
+ init_colorize();
47615
48965
  init_colors();
47616
48966
  init_label_layout();
47617
48967
  init_legend_constants();
47618
48968
  init_title_constants();
48969
+ init_context_labels();
47619
48970
  FIT_PAD = 24;
47620
- RAMP_FLOOR = 15;
48971
+ RAMP_FLOOR2 = 15;
47621
48972
  R_DEFAULT = 6;
47622
48973
  R_MIN = 4;
47623
48974
  R_MAX = 22;
47624
48975
  W_MIN = 1.25;
47625
48976
  W_MAX = 8;
47626
- FONT = 11;
47627
- COLO_EPS = 1.5;
48977
+ FONT2 = 11;
48978
+ WORLD_LABEL_ANCHORS = {
48979
+ US: [-98.5, 39.5]
48980
+ // CONUS geographic centre (near Lebanon, Kansas)
48981
+ };
48982
+ MAX_CLUSTER_EXTENT_FACTOR = 0.18;
48983
+ MAX_COLUMN_ROWS = 7;
48984
+ REGION_LABEL_HALO_RATIO = 4.5;
47628
48985
  LAND_TINT_LIGHT = 12;
47629
48986
  LAND_TINT_DARK = 24;
47630
48987
  TAG_TINT_LIGHT = 60;
47631
48988
  TAG_TINT_DARK = 68;
47632
- WATER_TINT_LIGHT = 13;
47633
- WATER_TINT_DARK = 14;
48989
+ WATER_TINT_LIGHT = 24;
48990
+ WATER_TINT_DARK = 24;
47634
48991
  RIVER_WIDTH = 1.3;
48992
+ COMPACT_WIDTH_PX = 480;
47635
48993
  RELIEF_MIN_AREA = 12;
47636
48994
  RELIEF_MIN_DIM = 2;
47637
- RELIEF_HATCH_SPACING = 3;
47638
- RELIEF_HATCH_WIDTH = 0.25;
47639
- RELIEF_HATCH_STRENGTH = 32;
48995
+ RELIEF_HATCH_SPACING = 1.5;
48996
+ RELIEF_HATCH_WIDTH = 0.2;
48997
+ RELIEF_HATCH_STRENGTH = 26;
48998
+ COASTLINE_RING_COUNT = 5;
48999
+ COASTLINE_D0 = 16e-4;
49000
+ COASTLINE_STEP = 28e-4;
49001
+ COASTLINE_THICKNESS = 14e-4;
49002
+ COASTLINE_OPACITY_NEAR = 0.5;
49003
+ COASTLINE_OPACITY_FAR = 0.1;
49004
+ COASTLINE_MIN_EXTENT = 6e-4;
49005
+ COASTLINE_MIN_EXTENT_GLOBAL = 6e-4;
49006
+ COASTLINE_STROKE_MIX = 32;
47640
49007
  FOREIGN_TINT_LIGHT = 30;
47641
49008
  FOREIGN_TINT_DARK = 62;
47642
49009
  MUTED_FOREIGN_LIGHT = 28;
47643
49010
  MUTED_FOREIGN_DARK = 16;
47644
49011
  COLO_R = 9;
47645
49012
  GOLDEN_ANGLE = 2.399963229728653;
49013
+ STACK_OVERLAP = 1;
49014
+ STACK_RING_MAX = 8;
49015
+ STACK_RING_GAP = 4;
47646
49016
  FAN_STEP = 16;
47647
49017
  ARC_CURVE_FRAC = 0.18;
49018
+ decodeCache = /* @__PURE__ */ new WeakMap();
47648
49019
  usConusProjection = () => (0, import_d3_geo2.geoConicEqualArea)().parallels([29.5, 45.5]).rotate([96, 0]);
47649
49020
  alaskaProjection = () => (0, import_d3_geo2.geoConicEqualArea)().rotate([154, 0]).center([-2, 58.5]).parallels([55, 65]);
47650
49021
  hawaiiProjection = () => (0, import_d3_geo2.geoMercator)();
47651
49022
  INSET_STATES = /* @__PURE__ */ new Set(["US-AK", "US-HI"]);
49023
+ inAlaska = (lon, lat) => lat >= 51 && (lon <= -129 || lon >= 172);
49024
+ inHawaii = (lon, lat) => lat >= 18 && lat <= 23 && lon >= -161 && lon <= -154;
49025
+ FOREIGN_BORDER = {
49026
+ CA: [
49027
+ "US-AK",
49028
+ "US-WA",
49029
+ "US-ID",
49030
+ "US-MT",
49031
+ "US-ND",
49032
+ "US-MN",
49033
+ "US-MI",
49034
+ "US-NY",
49035
+ "US-VT",
49036
+ "US-NH",
49037
+ "US-ME"
49038
+ ],
49039
+ MX: ["US-CA", "US-AZ", "US-NM", "US-TX"]
49040
+ };
47652
49041
  US_NON_CONUS = /* @__PURE__ */ new Set([
47653
49042
  "US-AK",
47654
49043
  "US-HI",
@@ -47667,6 +49056,98 @@ __export(renderer_exports16, {
47667
49056
  renderMap: () => renderMap,
47668
49057
  renderMapForExport: () => renderMapForExport
47669
49058
  });
49059
+ function pointInRing2(px, py, ring) {
49060
+ let inside = false;
49061
+ for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
49062
+ const [xi, yi] = ring[i];
49063
+ const [xj, yj] = ring[j];
49064
+ if (yi > py !== yj > py && px < (xj - xi) * (py - yi) / (yj - yi) + xi)
49065
+ inside = !inside;
49066
+ }
49067
+ return inside;
49068
+ }
49069
+ function ringToPath(ring) {
49070
+ let d = "";
49071
+ for (let i = 0; i < ring.length; i++)
49072
+ d += (i ? "L" : "M") + ring[i][0] + "," + ring[i][1];
49073
+ return d + "Z";
49074
+ }
49075
+ function polylineToPath(pts) {
49076
+ let d = "";
49077
+ for (let i = 0; i < pts.length; i++)
49078
+ d += (i ? "L" : "M") + pts[i][0] + "," + pts[i][1];
49079
+ return d;
49080
+ }
49081
+ function ringToCoastPaths(ring, frame) {
49082
+ if (!frame) return [ringToPath(ring)];
49083
+ const n = ring.length;
49084
+ const eps = 0.75;
49085
+ const onL = (x) => Math.abs(x) <= eps;
49086
+ const onR = (x) => Math.abs(x - frame.w) <= eps;
49087
+ const onT = (y) => Math.abs(y) <= eps;
49088
+ const onB = (y) => Math.abs(y - frame.h) <= eps;
49089
+ 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]);
49090
+ let firstBreak = -1;
49091
+ for (let i = 0; i < n; i++)
49092
+ if (isFrameEdge(ring[i], ring[(i + 1) % n])) {
49093
+ firstBreak = i;
49094
+ break;
49095
+ }
49096
+ if (firstBreak === -1) return [ringToPath(ring)];
49097
+ const paths = [];
49098
+ let cur = [];
49099
+ const start = (firstBreak + 1) % n;
49100
+ for (let k = 0; k < n; k++) {
49101
+ const i = (start + k) % n;
49102
+ const a = ring[i];
49103
+ const b = ring[(i + 1) % n];
49104
+ if (isFrameEdge(a, b)) {
49105
+ if (cur.length >= 2) paths.push(polylineToPath(cur));
49106
+ cur = [];
49107
+ continue;
49108
+ }
49109
+ if (cur.length === 0) cur.push(a);
49110
+ cur.push(b);
49111
+ }
49112
+ if (cur.length >= 2) paths.push(polylineToPath(cur));
49113
+ return paths;
49114
+ }
49115
+ function coastlineOuterRings(regions, minExtent, frame) {
49116
+ const paths = [];
49117
+ for (const r of regions) {
49118
+ const rings = parsePathRings(r.d);
49119
+ for (let i = 0; i < rings.length; i++) {
49120
+ const ring = rings[i];
49121
+ if (ring.length < 3) continue;
49122
+ let minX = Infinity;
49123
+ let minY = Infinity;
49124
+ let maxX = -Infinity;
49125
+ let maxY = -Infinity;
49126
+ for (const [x, y] of ring) {
49127
+ if (x < minX) minX = x;
49128
+ if (x > maxX) maxX = x;
49129
+ if (y < minY) minY = y;
49130
+ if (y > maxY) maxY = y;
49131
+ }
49132
+ if (Math.max(maxX - minX, maxY - minY) < minExtent) continue;
49133
+ const [fx, fy] = ring[0];
49134
+ let depth = 0;
49135
+ for (let j = 0; j < rings.length; j++)
49136
+ if (j !== i && pointInRing2(fx, fy, rings[j])) depth++;
49137
+ if (depth % 2 === 1) continue;
49138
+ paths.push(...ringToCoastPaths(ring, frame));
49139
+ }
49140
+ }
49141
+ return paths;
49142
+ }
49143
+ function appendWaterLines(g, outerRings, style, flatWater) {
49144
+ const d = outerRings.join(" ");
49145
+ const linesOuterFirst = [...style.lines].sort((a, b) => b.d - a.d);
49146
+ for (const line12 of linesOuterFirst) {
49147
+ 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");
49148
+ g.append("path").attr("d", d).attr("stroke", flatWater).attr("stroke-width", 2 * line12.d).attr("stroke-linejoin", "round").attr("stroke-linecap", "round");
49149
+ }
49150
+ }
47670
49151
  function renderMap(container, resolved, data, palette, isDark, onClickItem, exportDims, activeGroupOverride) {
47671
49152
  d3Selection18.select(container).selectAll(":not([data-d3-tooltip])").remove();
47672
49153
  const width = exportDims?.width ?? container.clientWidth;
@@ -47679,6 +49160,11 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47679
49160
  {
47680
49161
  palette,
47681
49162
  isDark,
49163
+ // Export-only: forward the contain-fit request from mapExportDimensions so a
49164
+ // clamped/floored (off-aspect) export canvas letterboxes instead of
49165
+ // stretch-distorting. The in-app preview pane passes no exportDims → unset →
49166
+ // keeps the global stretch-fill.
49167
+ preferContain: exportDims?.preferContain ?? false,
47682
49168
  ...activeGroupOverride !== void 0 && {
47683
49169
  activeGroup: activeGroupOverride
47684
49170
  }
@@ -47692,6 +49178,11 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47692
49178
  const gRegions = svg.append("g").attr("class", "dgmo-map-regions");
47693
49179
  const drawRegion = (g, r, strokeWidth) => {
47694
49180
  const p = g.append("path").attr("d", r.d).attr("fill", r.fill).attr("stroke", r.stroke).attr("stroke-width", strokeWidth);
49181
+ if (r.label) p.attr("data-region-name", r.label);
49182
+ if (r.id && r.id !== "lake") p.attr("data-iso", r.id);
49183
+ if (r.labelX !== void 0 && r.labelY !== void 0) {
49184
+ p.attr("data-label-x", r.labelX).attr("data-label-y", r.labelY);
49185
+ }
47695
49186
  if (r.layer !== "base") {
47696
49187
  p.classed("dgmo-map-region", true).attr("data-region", r.id);
47697
49188
  if (r.value !== void 0) p.attr("data-value", r.value);
@@ -47721,28 +49212,112 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47721
49212
  const landClip = defs.append("clipPath").attr("id", landClipId);
47722
49213
  for (const r of layout.regions)
47723
49214
  if (r.id !== "lake") landClip.append("path").attr("d", r.d);
47724
- 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");
49215
+ 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");
47725
49216
  for (let y = h.spacing; y < height; y += h.spacing) {
47726
49217
  gRelief.append("line").attr("x1", 0).attr("y1", y).attr("x2", width).attr("y2", y);
47727
49218
  }
47728
49219
  }
49220
+ if (layout.coastlineStyle) {
49221
+ const cs = layout.coastlineStyle;
49222
+ const maskId = "dgmo-map-water-mask";
49223
+ const mask = defs.append("mask").attr("id", maskId).attr("maskUnits", "userSpaceOnUse").attr("x", 0).attr("y", 0).attr("width", width).attr("height", height);
49224
+ mask.append("rect").attr("x", 0).attr("y", 0).attr("width", width).attr("height", height).attr("fill", "white");
49225
+ const landD = layout.regions.filter((r) => r.id !== "lake").map((r) => r.d).join(" ");
49226
+ const lakeD = layout.regions.filter((r) => r.id === "lake").map((r) => r.d).join(" ");
49227
+ if (landD) mask.append("path").attr("d", landD).attr("fill", "black");
49228
+ if (lakeD) mask.append("path").attr("d", lakeD).attr("fill", "white");
49229
+ if (layout.insets.length) {
49230
+ const reach = Math.max(0, ...cs.lines.map((l) => l.d + l.thickness));
49231
+ for (const box of layout.insets) {
49232
+ const d = box.points.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join("") + "Z";
49233
+ mask.append("path").attr("d", d).attr("fill", "black").attr("stroke", "black").attr("stroke-width", 2 * reach).attr("stroke-linejoin", "round");
49234
+ }
49235
+ }
49236
+ const gWater = svg.append("g").attr("class", "dgmo-map-water-lines").attr("fill", "none").style("pointer-events", "none").attr("mask", `url(#${maskId})`);
49237
+ appendWaterLines(
49238
+ gWater,
49239
+ // Pass the canvas frame so edges collinear with it (the antimeridian on a
49240
+ // world map, regional clipExtent cuts) don't get ringed as fake coast —
49241
+ // land runs cleanly to the render-area edge.
49242
+ coastlineOuterRings(layout.regions, cs.minExtent, {
49243
+ w: width,
49244
+ h: height
49245
+ }),
49246
+ cs,
49247
+ layout.background
49248
+ );
49249
+ const byStroke = /* @__PURE__ */ new Map();
49250
+ for (const r of layout.regions) {
49251
+ const arr = byStroke.get(r.stroke);
49252
+ if (arr) arr.push(r.d);
49253
+ else byStroke.set(r.stroke, [r.d]);
49254
+ }
49255
+ for (const [stroke2, ds] of byStroke)
49256
+ gWater.append("path").attr("d", ds.join(" ")).attr("stroke", stroke2).attr("stroke-width", 0.5).attr("stroke-linejoin", "round");
49257
+ }
47729
49258
  if (layout.rivers.length) {
47730
- const gRivers = svg.append("g").attr("class", "dgmo-map-rivers").attr("fill", "none");
49259
+ const gRivers = svg.append("g").attr("class", "dgmo-map-rivers").attr("fill", "none").style("pointer-events", "none");
47731
49260
  for (const r of layout.rivers) {
47732
49261
  gRivers.append("path").attr("d", r.d).attr("stroke", r.color).attr("stroke-width", r.width).attr("stroke-linecap", "round").attr("stroke-linejoin", "round");
47733
49262
  }
47734
49263
  }
47735
49264
  if (layout.insets.length) {
47736
49265
  const insetG = svg.append("g").attr("class", "dgmo-map-insets");
47737
- for (const box of layout.insets) {
49266
+ layout.insets.forEach((box, bi) => {
47738
49267
  const d = box.points.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join("") + "Z";
47739
49268
  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");
47740
- }
49269
+ if (box.contextLand) {
49270
+ const clipId = `dgmo-map-inset-clip-${bi}`;
49271
+ defs.append("clipPath").attr("id", clipId).append("path").attr("d", d);
49272
+ insetG.append("path").attr("d", box.contextLand.d).attr("fill", box.contextLand.fill).attr("clip-path", `url(#${clipId})`);
49273
+ }
49274
+ });
47741
49275
  for (const r of layout.insetRegions) drawRegion(insetG, r, 0.5);
47742
- }
49276
+ if (layout.coastlineStyle) {
49277
+ const cs = layout.coastlineStyle;
49278
+ const maskId = "dgmo-map-inset-water-mask";
49279
+ const mask = defs.append("mask").attr("id", maskId).attr("maskUnits", "userSpaceOnUse").attr("x", 0).attr("y", 0).attr("width", width).attr("height", height);
49280
+ for (const box of layout.insets) {
49281
+ const d = box.points.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join("") + "Z";
49282
+ mask.append("path").attr("d", d).attr("fill", "white");
49283
+ }
49284
+ layout.insets.forEach((box, bi) => {
49285
+ if (box.contextLand)
49286
+ mask.append("path").attr("d", box.contextLand.d).attr("fill", "black").attr("clip-path", `url(#dgmo-map-inset-clip-${bi})`);
49287
+ });
49288
+ for (const r of layout.insetRegions)
49289
+ if (r.id !== "lake")
49290
+ mask.append("path").attr("d", r.d).attr("fill", "black");
49291
+ for (const r of layout.insetRegions)
49292
+ if (r.id === "lake")
49293
+ mask.append("path").attr("d", r.d).attr("fill", "white");
49294
+ const clipId = "dgmo-map-inset-water-clip";
49295
+ const clip = defs.append("clipPath").attr("id", clipId);
49296
+ for (const box of layout.insets) {
49297
+ const d = box.points.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join("") + "Z";
49298
+ clip.append("path").attr("d", d);
49299
+ }
49300
+ 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})`);
49301
+ appendWaterLines(
49302
+ gInsetWater,
49303
+ coastlineOuterRings(layout.insetRegions, cs.minExtent),
49304
+ cs,
49305
+ layout.background
49306
+ );
49307
+ for (const r of layout.insetRegions)
49308
+ gInsetWater.append("path").attr("d", r.d).attr("stroke", r.stroke).attr("stroke-width", 0.5).attr("stroke-linejoin", "round");
49309
+ }
49310
+ }
49311
+ const wireSync = (sel, lineNumber) => {
49312
+ if (lineNumber < 1) return;
49313
+ sel.attr("data-line-number", lineNumber);
49314
+ if (onClickItem)
49315
+ sel.style("cursor", "pointer").on("click", () => onClickItem(lineNumber));
49316
+ };
47743
49317
  const gLegs = svg.append("g").attr("class", "dgmo-map-legs").attr("fill", "none");
47744
49318
  layout.legs.forEach((leg, i) => {
47745
49319
  const p = gLegs.append("path").attr("d", leg.d).attr("stroke", leg.color).attr("stroke-width", leg.width).attr("stroke-linecap", "round");
49320
+ wireSync(p, leg.lineNumber);
47746
49321
  if (leg.arrow) {
47747
49322
  const id = `dgmo-map-arrow-${i}`;
47748
49323
  const s = arrowSize(leg.width);
@@ -47750,25 +49325,38 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47750
49325
  p.attr("marker-end", `url(#${id})`);
47751
49326
  }
47752
49327
  if (leg.label !== void 0 && leg.labelX !== void 0) {
47753
- emitText(
49328
+ const lt = emitText(
47754
49329
  gLegs,
47755
49330
  leg.labelX,
47756
49331
  leg.labelY ?? 0,
47757
49332
  leg.label,
47758
49333
  "middle",
47759
- palette.textMuted,
47760
- haloColor,
47761
- true,
49334
+ leg.labelColor ?? palette.textMuted,
49335
+ leg.labelHaloColor ?? haloColor,
49336
+ leg.labelHalo ?? true,
47762
49337
  LABEL_FONT - 1
47763
49338
  );
49339
+ wireSync(lt, leg.lineNumber);
47764
49340
  }
47765
49341
  });
49342
+ const gSpider = svg.append("g").attr("class", "dgmo-map-spider");
49343
+ for (const cl of layout.clusters) {
49344
+ if (!exportDims) {
49345
+ 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");
49346
+ }
49347
+ for (const leg of cl.legs) {
49348
+ 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");
49349
+ }
49350
+ 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");
49351
+ }
47766
49352
  const gPois = svg.append("g").attr("class", "dgmo-map-pois");
47767
49353
  for (const poi of layout.pois) {
47768
49354
  if (poi.isOrigin) {
47769
49355
  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);
47770
49356
  }
47771
49357
  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);
49358
+ if (poi.clusterId !== void 0)
49359
+ c.attr("data-cluster-member", poi.clusterId);
47772
49360
  if (poi.tags) {
47773
49361
  for (const [group, value] of Object.entries(poi.tags)) {
47774
49362
  c.attr(`data-tag-${group.toLowerCase()}`, value.toLowerCase());
@@ -47796,12 +49384,32 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47796
49384
  }
47797
49385
  const gLabels = svg.append("g").attr("class", "dgmo-map-labels");
47798
49386
  for (const lab of layout.labels) {
49387
+ if (lab.hidden) {
49388
+ if (exportDims) continue;
49389
+ emitText(
49390
+ gLabels,
49391
+ lab.x,
49392
+ lab.y,
49393
+ lab.text,
49394
+ lab.anchor,
49395
+ lab.color,
49396
+ lab.haloColor,
49397
+ lab.halo,
49398
+ LABEL_FONT,
49399
+ lab.italic,
49400
+ lab.letterSpacing
49401
+ ).attr("data-poi", lab.poiId ?? null).attr("data-poi-hidden", "").style("opacity", 0).style("pointer-events", "none");
49402
+ continue;
49403
+ }
47799
49404
  if (lab.leader) {
47800
49405
  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(
47801
49406
  "stroke",
47802
49407
  lab.leaderColor ?? mix(palette.textMuted, palette.bg, 60)
47803
49408
  ).attr("stroke-width", lab.leaderColor ? 1 : 0.75);
47804
49409
  if (lab.poiId !== void 0) line12.attr("data-poi", lab.poiId);
49410
+ if (lab.clusterMember !== void 0)
49411
+ line12.attr("data-cluster-member", lab.clusterMember);
49412
+ wireSync(line12, lab.lineNumber);
47805
49413
  }
47806
49414
  const t = emitText(
47807
49415
  gLabels,
@@ -47812,11 +49420,38 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47812
49420
  lab.color,
47813
49421
  lab.haloColor,
47814
49422
  lab.halo,
47815
- LABEL_FONT
49423
+ LABEL_FONT,
49424
+ lab.italic,
49425
+ lab.letterSpacing,
49426
+ lab.lines
47816
49427
  );
47817
49428
  if (lab.poiId !== void 0) {
47818
49429
  t.attr("data-poi", lab.poiId).style("cursor", "default");
47819
49430
  }
49431
+ if (lab.clusterMember !== void 0) {
49432
+ t.attr("data-cluster-member", lab.clusterMember);
49433
+ }
49434
+ wireSync(t, lab.lineNumber);
49435
+ }
49436
+ if (!exportDims && layout.clusters.length) {
49437
+ const gBadge = svg.append("g").attr("class", "dgmo-map-cluster-badges");
49438
+ for (const cl of layout.clusters) {
49439
+ const g = gBadge.append("g").attr("data-cluster", cl.id).style("opacity", 0).style("pointer-events", "none");
49440
+ const R = 9;
49441
+ 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);
49442
+ 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);
49443
+ emitText(
49444
+ g,
49445
+ cl.cx,
49446
+ cl.cy + 3,
49447
+ String(cl.count),
49448
+ "middle",
49449
+ palette.text,
49450
+ palette.bg,
49451
+ false,
49452
+ LABEL_FONT
49453
+ );
49454
+ }
47820
49455
  }
47821
49456
  if (layout.legend) {
47822
49457
  const legendY = (layout.title ? TITLE_Y + TITLE_FONT_SIZE : 0) + (layout.subtitle ? TITLE_FONT_SIZE : 0) + 8;
@@ -47853,7 +49488,7 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47853
49488
  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);
47854
49489
  }
47855
49490
  if (layout.subtitle) {
47856
- 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);
49491
+ 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);
47857
49492
  }
47858
49493
  if (layout.caption) {
47859
49494
  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);
@@ -47862,10 +49497,21 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47862
49497
  function renderMapForExport(container, resolved, data, palette, isDark, exportDims) {
47863
49498
  renderMap(container, resolved, data, palette, isDark, void 0, exportDims);
47864
49499
  }
47865
- function emitText(g, x, y, text, anchor, color, halo, withHalo, fontSize) {
47866
- const t = g.append("text").attr("x", x).attr("y", y).attr("text-anchor", anchor).attr("font-size", fontSize).attr("fill", color).text(text);
49500
+ function emitText(g, x, y, text, anchor, color, halo, withHalo, fontSize, italic, letterSpacing, lines) {
49501
+ const t = g.append("text").attr("x", x).attr("y", y).attr("text-anchor", anchor).attr("font-size", fontSize).attr("fill", color);
49502
+ if (lines && lines.length > 1) {
49503
+ const lineHeight = fontSize + 2;
49504
+ const startDy = -((lines.length - 1) / 2) * lineHeight;
49505
+ lines.forEach((ln, i) => {
49506
+ t.append("tspan").attr("x", x).attr("dy", i === 0 ? startDy : lineHeight).text(ln);
49507
+ });
49508
+ } else {
49509
+ t.text(text);
49510
+ }
49511
+ if (italic) t.attr("font-style", "italic");
49512
+ if (letterSpacing) t.attr("letter-spacing", letterSpacing);
47867
49513
  if (withHalo) {
47868
- t.attr("paint-order", "stroke fill").attr("stroke", halo).attr("stroke-width", 3).attr("stroke-linejoin", "round").attr("stroke-opacity", 0.7);
49514
+ 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);
47869
49515
  }
47870
49516
  return t;
47871
49517
  }
@@ -47883,6 +49529,56 @@ var init_renderer16 = __esm({
47883
49529
  }
47884
49530
  });
47885
49531
 
49532
+ // src/map/dimensions.ts
49533
+ var dimensions_exports = {};
49534
+ __export(dimensions_exports, {
49535
+ mapContentAspect: () => mapContentAspect,
49536
+ mapExportDimensions: () => mapExportDimensions
49537
+ });
49538
+ function mapContentAspect(resolved, data, ref = REF) {
49539
+ const { projection, fitTarget } = buildMapProjection(resolved, data);
49540
+ projection.fitSize([ref, ref], fitTarget);
49541
+ const b = (0, import_d3_geo3.geoPath)(projection).bounds(fitTarget);
49542
+ const w = b[1][0] - b[0][0];
49543
+ const h = b[1][1] - b[0][1];
49544
+ const aspect = w / h;
49545
+ return Number.isFinite(aspect) && aspect > 0 ? aspect : FALLBACK_ASPECT;
49546
+ }
49547
+ function mapExportDimensions(resolved, data, baseWidth = 1200) {
49548
+ const raw = mapContentAspect(resolved, data);
49549
+ const clamped = Math.max(ASPECT_MIN, Math.min(ASPECT_MAX, raw));
49550
+ const width = baseWidth;
49551
+ let height = Math.round(width / clamped);
49552
+ let chromeReserve = 0;
49553
+ if (resolved.title && resolved.pois.length > 0) {
49554
+ const bannerBottom = (resolved.subtitle ? TITLE_Y + TITLE_FONT_SIZE : TITLE_Y) + TITLE_FONT_SIZE / 2;
49555
+ chromeReserve += Math.max(FIT_PAD2, bannerBottom + TITLE_GAP) - FIT_PAD2;
49556
+ }
49557
+ let floored = false;
49558
+ if (height - chromeReserve < MIN_MAP_BAND) {
49559
+ height = Math.round(chromeReserve + MIN_MAP_BAND);
49560
+ floored = true;
49561
+ }
49562
+ const preferContain = clamped !== raw || floored;
49563
+ return { width, height, preferContain };
49564
+ }
49565
+ var import_d3_geo3, FIT_PAD2, TITLE_GAP, ASPECT_MAX, ASPECT_MIN, MIN_MAP_BAND, FALLBACK_ASPECT, REF;
49566
+ var init_dimensions = __esm({
49567
+ "src/map/dimensions.ts"() {
49568
+ "use strict";
49569
+ import_d3_geo3 = require("d3-geo");
49570
+ init_title_constants();
49571
+ init_layout15();
49572
+ FIT_PAD2 = 24;
49573
+ TITLE_GAP = 16;
49574
+ ASPECT_MAX = 3;
49575
+ ASPECT_MIN = 0.9;
49576
+ MIN_MAP_BAND = 200;
49577
+ FALLBACK_ASPECT = 1.5;
49578
+ REF = 1e3;
49579
+ }
49580
+ });
49581
+
47886
49582
  // src/map/load-data.ts
47887
49583
  var load_data_exports = {};
47888
49584
  __export(load_data_exports, {
@@ -47941,12 +49637,17 @@ function loadMapData() {
47941
49637
  mountainRanges,
47942
49638
  naLand,
47943
49639
  naLakes,
49640
+ waterBodies,
47944
49641
  gazetteer
47945
49642
  ] = await Promise.all([
49643
+ // worldCoarse (110m) is LOAD-BEARING but NOT a render source: the world
49644
+ // basemap renders from worldDetail (50m) at all scales (resolver pins
49645
+ // basemaps.world = 'detail'). Coarse stays as the authoritative region
49646
+ // name index + dominant-landmass bbox source in resolver.ts. Do not drop it.
47946
49647
  readJson(nb, dir, FILES.worldCoarse),
47947
49648
  readJson(nb, dir, FILES.worldDetail),
47948
49649
  readJson(nb, dir, FILES.usStates),
47949
- // Lakes/rivers/mountain/NA assets are optional — older bundles may predate them.
49650
+ // Lakes/rivers/mountain/NA/water assets are optional — older bundles may predate them.
47950
49651
  readJson(nb, dir, FILES.lakes).catch(() => void 0),
47951
49652
  readJson(nb, dir, FILES.rivers).catch(() => void 0),
47952
49653
  readJson(nb, dir, FILES.mountainRanges).catch(
@@ -47954,6 +49655,7 @@ function loadMapData() {
47954
49655
  ),
47955
49656
  readJson(nb, dir, FILES.naLand).catch(() => void 0),
47956
49657
  readJson(nb, dir, FILES.naLakes).catch(() => void 0),
49658
+ readJson(nb, dir, FILES.waterBodies).catch(() => void 0),
47957
49659
  readJson(nb, dir, FILES.gazetteer)
47958
49660
  ]);
47959
49661
  return validate({
@@ -47965,7 +49667,8 @@ function loadMapData() {
47965
49667
  ...rivers && { rivers },
47966
49668
  ...mountainRanges && { mountainRanges },
47967
49669
  ...naLand && { naLand },
47968
- ...naLakes && { naLakes }
49670
+ ...naLakes && { naLakes },
49671
+ ...waterBodies && { waterBodies }
47969
49672
  });
47970
49673
  })().catch((e) => {
47971
49674
  cache = void 0;
@@ -47987,6 +49690,7 @@ var init_load_data = __esm({
47987
49690
  mountainRanges: "mountain-ranges.json",
47988
49691
  naLand: "na-land.json",
47989
49692
  naLakes: "na-lakes.json",
49693
+ waterBodies: "water-bodies.json",
47990
49694
  gazetteer: "gazetteer.json"
47991
49695
  };
47992
49696
  CANDIDATE_DIRS = [
@@ -49999,8 +51703,8 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
49999
51703
  const lines = splitParticipantLabel(p.label, LABEL_MAX_CHARS);
50000
51704
  if (lines.length === 0) continue;
50001
51705
  const widest = Math.max(...lines.map((l) => l.length));
50002
- const labelWidth = widest * LABEL_CHAR_WIDTH + 10;
50003
- uniformBoxWidth = Math.max(uniformBoxWidth, labelWidth);
51706
+ const labelWidth2 = widest * LABEL_CHAR_WIDTH + 10;
51707
+ uniformBoxWidth = Math.max(uniformBoxWidth, labelWidth2);
50004
51708
  }
50005
51709
  uniformBoxWidth = Math.min(MAX_BOX_WIDTH, uniformBoxWidth);
50006
51710
  const effectiveGap = Math.max(PARTICIPANT_GAP, uniformBoxWidth + 30);
@@ -52695,15 +54399,15 @@ function renderArcDiagram(container, parsed, palette, _isDark, onClickItem, expo
52695
54399
  textColor,
52696
54400
  onClickItem
52697
54401
  );
52698
- const neighbors = /* @__PURE__ */ new Map();
52699
- for (const node of nodes) neighbors.set(node, /* @__PURE__ */ new Set());
54402
+ const neighbors2 = /* @__PURE__ */ new Map();
54403
+ for (const node of nodes) neighbors2.set(node, /* @__PURE__ */ new Set());
52700
54404
  for (const link of links) {
52701
- neighbors.get(link.source).add(link.target);
52702
- neighbors.get(link.target).add(link.source);
54405
+ neighbors2.get(link.source).add(link.target);
54406
+ neighbors2.get(link.target).add(link.source);
52703
54407
  }
52704
54408
  const FADE_OPACITY3 = 0.1;
52705
54409
  function handleMouseEnter(hovered) {
52706
- const connected = neighbors.get(hovered);
54410
+ const connected = neighbors2.get(hovered);
52707
54411
  g.selectAll(".arc-link").each(function() {
52708
54412
  const el = d3Selection23.select(this);
52709
54413
  const src = el.attr("data-source");
@@ -53611,10 +55315,12 @@ function renderTimelineHorizontalTimeSort(container, parsed, palette, isDark, se
53611
55315
  const markerLabelY = markerReserve ? -(topScaleH + MARKER_ROW_H / 2) : 0;
53612
55316
  const eraLabelY = eraReserve ? -(topScaleH + markerReserve + ERA_ROW_H / 2) : 0;
53613
55317
  const innerWidth = width - margin.left - margin.right;
53614
- const innerHeight = height - margin.top - margin.bottom;
53615
- const rowH = Math.min(ctx.structural(28), innerHeight / sorted.length);
55318
+ const availInnerHeight = height - margin.top - margin.bottom;
55319
+ const rowH = Math.min(ctx.structural(28), availInnerHeight / sorted.length);
55320
+ const innerHeight = rowH * sorted.length;
55321
+ const usedHeight = margin.top + innerHeight + margin.bottom;
53616
55322
  const xScale = d3Scale2.scaleLinear().domain([minDate - datePadding, maxDate + datePadding]).range([0, innerWidth]);
53617
- 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);
55323
+ 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);
53618
55324
  if (ctx.isBelowFloor) {
53619
55325
  svg.attr("width", "100%");
53620
55326
  }
@@ -54694,7 +56400,7 @@ function renderVenn(container, parsed, palette, _isDark, onClickItem, exportDims
54694
56400
  8,
54695
56401
  Math.floor(OVERLAP_WRAP_TARGET_W / OVERLAP_CH_W)
54696
56402
  );
54697
- function wrapLabel2(text, maxChars) {
56403
+ function wrapLabel3(text, maxChars) {
54698
56404
  const words = text.split(/\s+/).filter(Boolean);
54699
56405
  const lines = [];
54700
56406
  let cur = "";
@@ -54740,7 +56446,7 @@ function renderVenn(container, parsed, palette, _isDark, onClickItem, exportDims
54740
56446
  if (!ov.label) continue;
54741
56447
  const idxs = ov.sets.map((s) => vennSets.findIndex((vs) => vs.name === s));
54742
56448
  if (idxs.some((idx) => idx < 0)) continue;
54743
- const lines = wrapLabel2(ov.label, MAX_WRAP_CHARS);
56449
+ const lines = wrapLabel3(ov.label, MAX_WRAP_CHARS);
54744
56450
  wrappedOverlapLabels.set(ov, lines);
54745
56451
  const dir = predictOverlapDirRaw(idxs);
54746
56452
  const longest = lines.reduce((m, l) => Math.max(m, l.length), 0);
@@ -56178,6 +57884,7 @@ async function renderForExport(content, theme, palette, viewState, options) {
56178
57884
  const { parseMap: parseMap2 } = await Promise.resolve().then(() => (init_parser12(), parser_exports11));
56179
57885
  const { resolveMap: resolveMap2 } = await Promise.resolve().then(() => (init_resolver2(), resolver_exports));
56180
57886
  const { renderMapForExport: renderMapForExport2 } = await Promise.resolve().then(() => (init_renderer16(), renderer_exports16));
57887
+ const { mapExportDimensions: mapExportDimensions2 } = await Promise.resolve().then(() => (init_dimensions(), dimensions_exports));
56181
57888
  const effectivePalette2 = await resolveExportPalette(theme, palette);
56182
57889
  const mapParsed = parseMap2(content);
56183
57890
  let mapData = options?.mapData;
@@ -56190,14 +57897,15 @@ async function renderForExport(content, theme, palette, viewState, options) {
56190
57897
  }
56191
57898
  }
56192
57899
  const mapResolved = resolveMap2(mapParsed, mapData);
56193
- const container2 = createExportContainer(EXPORT_WIDTH, EXPORT_HEIGHT);
57900
+ const dims2 = mapExportDimensions2(mapResolved, mapData, EXPORT_WIDTH);
57901
+ const container2 = createExportContainer(dims2.width, dims2.height);
56194
57902
  renderMapForExport2(
56195
57903
  container2,
56196
57904
  mapResolved,
56197
57905
  mapData,
56198
57906
  effectivePalette2,
56199
57907
  theme === "dark",
56200
- { width: EXPORT_WIDTH, height: EXPORT_HEIGHT }
57908
+ dims2
56201
57909
  );
56202
57910
  return finalizeSvgExport(container2, theme, effectivePalette2);
56203
57911
  }
@@ -57004,7 +58712,8 @@ __export(advanced_exports, {
57004
58712
  applyCollapseProjection: () => applyCollapseProjection,
57005
58713
  applyGroupOrdering: () => applyGroupOrdering,
57006
58714
  applyPositionOverrides: () => applyPositionOverrides,
57007
- boldPalette: () => boldPalette,
58715
+ atlasPalette: () => atlasPalette,
58716
+ blueprintPalette: () => blueprintPalette,
57008
58717
  buildExtendedChartOption: () => buildExtendedChartOption,
57009
58718
  buildNoteMessageMap: () => buildNoteMessageMap,
57010
58719
  buildRenderSequence: () => buildRenderSequence,
@@ -57107,6 +58816,8 @@ __export(advanced_exports, {
57107
58816
  looksLikeState: () => looksLikeState,
57108
58817
  makeDgmoError: () => makeDgmoError,
57109
58818
  mapBackgroundColor: () => mapBackgroundColor,
58819
+ mapContentAspect: () => mapContentAspect,
58820
+ mapExportDimensions: () => mapExportDimensions,
57110
58821
  mapNeutralLandColor: () => mapNeutralLandColor,
57111
58822
  matchesContiguously: () => matchesContiguously,
57112
58823
  measurePertAnalysisBlock: () => measurePertAnalysisBlock,
@@ -57242,9 +58953,11 @@ __export(advanced_exports, {
57242
58953
  shapeFill: () => shapeFill,
57243
58954
  simulateCanonical: () => simulateCanonical,
57244
58955
  simulateFast: () => simulateFast,
58956
+ slatePalette: () => slatePalette,
57245
58957
  solarizedPalette: () => solarizedPalette,
57246
58958
  suggestChartTypes: () => suggestChartTypes,
57247
58959
  themes: () => themes,
58960
+ tidewaterPalette: () => tidewaterPalette,
57248
58961
  tint: () => tint,
57249
58962
  tokyoNightPalette: () => tokyoNightPalette,
57250
58963
  transformLine: () => transformLine,
@@ -57328,7 +59041,8 @@ async function render(content, options) {
57328
59041
  ...options?.c4Container !== void 0 && {
57329
59042
  c4Container: options.c4Container
57330
59043
  },
57331
- ...options?.tagGroup !== void 0 && { tagGroup: options.tagGroup }
59044
+ ...options?.tagGroup !== void 0 && { tagGroup: options.tagGroup },
59045
+ ...options?.mapData !== void 0 && { mapData: options.mapData }
57332
59046
  });
57333
59047
  if (chartType === "map") {
57334
59048
  try {
@@ -57339,7 +59053,7 @@ async function render(content, options) {
57339
59053
  Promise.resolve().then(() => (init_load_data(), load_data_exports))
57340
59054
  ]
57341
59055
  );
57342
- const data = await loadMapData2();
59056
+ const data = options?.mapData ?? await loadMapData2();
57343
59057
  diagnostics = [...resolveMap2(parseMap2(content), data).diagnostics];
57344
59058
  } catch {
57345
59059
  }
@@ -57578,8 +59292,8 @@ function detectCycles(parsed) {
57578
59292
  const parent = /* @__PURE__ */ new Map();
57579
59293
  function dfs(nodeId3) {
57580
59294
  color.set(nodeId3, 1);
57581
- const neighbors = adj.get(nodeId3) ?? [];
57582
- for (const next of neighbors) {
59295
+ const neighbors2 = adj.get(nodeId3) ?? [];
59296
+ for (const next of neighbors2) {
57583
59297
  const c = color.get(next) ?? 0;
57584
59298
  if (c === 1) {
57585
59299
  const lineKey = `${nodeId3}->${next}`;
@@ -57764,6 +59478,7 @@ init_resolver2();
57764
59478
  init_load_data();
57765
59479
  init_layout15();
57766
59480
  init_renderer16();
59481
+ init_dimensions();
57767
59482
 
57768
59483
  // src/map/geo-query.ts
57769
59484
  init_parser12();
@@ -57837,7 +59552,9 @@ function nearestCity(lonLat, gazetteer) {
57837
59552
  name: c[4],
57838
59553
  iso: c[2],
57839
59554
  ...c[5] !== void 0 && { sub: c[5] },
57840
- distanceKm: best.dist
59555
+ distanceKm: best.dist,
59556
+ lat: c[0],
59557
+ lon: c[1]
57841
59558
  };
57842
59559
  }
57843
59560
  function roundCoord(n) {
@@ -57916,7 +59633,7 @@ function createMapGeoQuery(opts) {
57916
59633
  }
57917
59634
  return out;
57918
59635
  };
57919
- return { invert, project, locate, cities };
59636
+ return { invert, project, locate, cities, diagnostics: layout.diagnostics };
57920
59637
  }
57921
59638
 
57922
59639
  // src/map/completion.ts
@@ -58642,9 +60359,12 @@ var GLOBAL_DIRECTIVES = {
58642
60359
  "gruvbox",
58643
60360
  "tokyo-night",
58644
60361
  "one-dark",
58645
- "bold",
58646
60362
  "dracula",
58647
- "monokai"
60363
+ "monokai",
60364
+ "atlas",
60365
+ "blueprint",
60366
+ "slate",
60367
+ "tidewater"
58648
60368
  ]
58649
60369
  },
58650
60370
  theme: {
@@ -58975,7 +60695,9 @@ var COMPLETION_REGISTRY = /* @__PURE__ */ new Map([
58975
60695
  withGlobals({
58976
60696
  direction: { description: "Layout direction", values: ["LR", "TB"] },
58977
60697
  "active-tag": { description: "Active tag group name" },
58978
- hide: { description: "Hide tag:value pairs" }
60698
+ hide: { description: "Hide tag:value pairs" },
60699
+ "box-metric": { description: "Metric label for the value ramp" },
60700
+ "show-values": { description: "Print box values as text" }
58979
60701
  })
58980
60702
  ],
58981
60703
  [
@@ -59040,18 +60762,12 @@ var COMPLETION_REGISTRY = /* @__PURE__ */ new Map([
59040
60762
  ],
59041
60763
  [
59042
60764
  "map",
59043
- // Geographic map directives (§24B.2/.7). `poi`/`route` are content
59044
- // keywords, not directives; metadata keys (value/label/style) live in the
59045
- // reserved-key registry.
60765
+ // Geographic map directives (§24B.2/.7). Cosmetics are ON by default — the
60766
+ // only switches are bare `no-*` opt-outs, surfaced proactively so a
60767
+ // zero-config map still hints at what can be turned off. `poi`/`route` are
60768
+ // content keywords, not directives; metadata keys (value/label/style) live
60769
+ // in the reserved-key registry.
59046
60770
  withGlobals({
59047
- region: {
59048
- description: "Basemap: us-states (force US state mesh + scoping) | world (inert \u2014 already the default)",
59049
- values: ["us-states", "world"]
59050
- },
59051
- projection: {
59052
- description: "Override the auto projection",
59053
- values: ["equirectangular", "natural-earth", "albers-usa", "mercator"]
59054
- },
59055
60771
  "region-metric": { description: "Label for the region value ramp" },
59056
60772
  "poi-metric": {
59057
60773
  description: "Label for the POI value (marker size) channel"
@@ -59059,21 +60775,30 @@ var COMPLETION_REGISTRY = /* @__PURE__ */ new Map([
59059
60775
  "flow-metric": {
59060
60776
  description: "Label for the edge/leg value (thickness) channel"
59061
60777
  },
59062
- scale: { description: "Override value ramp anchors: scale <min> <max>" },
59063
- "region-labels": {
59064
- description: "Subdivision name labels",
59065
- values: ["full", "abbrev", "off"]
60778
+ locale: {
60779
+ description: "Default country/state for bare place names, e.g. locale US-GA"
59066
60780
  },
59067
- "poi-labels": {
59068
- description: "POI labels/values",
59069
- values: ["off", "auto", "all"]
60781
+ "active-tag": {
60782
+ description: "Which tag group leads when several are present"
59070
60783
  },
59071
- "default-country": { description: "ISO scope for bare city resolution" },
59072
- "default-state": { description: "ISO subdivision scope" },
60784
+ caption: { description: "Caption line (data-source attribution)" },
59073
60785
  "no-legend": { description: "Suppress the legend" },
59074
- relief: { description: "Subtle mountain-range relief shading" },
59075
- subtitle: { description: "Subtitle line" },
59076
- caption: { description: "Caption line" }
60786
+ "no-coastline": {
60787
+ description: "Turn off coastal water-lines (on by default)"
60788
+ },
60789
+ "no-relief": {
60790
+ description: "Turn off mountain-range relief shading (on by default)"
60791
+ },
60792
+ "no-context-labels": {
60793
+ description: "Turn off orientation labels for water + nearby countries"
60794
+ },
60795
+ "no-region-labels": {
60796
+ description: "Turn off subdivision name labels (on by default)"
60797
+ },
60798
+ "no-poi-labels": { description: "Turn off POI labels (on by default)" },
60799
+ "no-colorize": {
60800
+ description: "Force plain green-land reference dress (regions are auto-coloured by default)"
60801
+ }
59077
60802
  })
59078
60803
  ]
59079
60804
  ]);
@@ -59200,13 +60925,10 @@ var PIPE_METADATA = /* @__PURE__ */ new Map([
59200
60925
  "boxes-and-lines",
59201
60926
  {
59202
60927
  node: {
59203
- description: { description: "Node description text" }
60928
+ description: { description: "Node description text" },
60929
+ value: { description: "Numeric value for the metric ramp" }
59204
60930
  },
59205
- edge: {
59206
- width: { description: "Edge stroke width in pixels" },
59207
- split: { description: "Traffic split percentage" },
59208
- fanout: { description: "Fanout multiplier (integer >= 1)" }
59209
- }
60931
+ edge: {}
59210
60932
  }
59211
60933
  ],
59212
60934
  [
@@ -60543,7 +62265,8 @@ function formatLineDiff(path, original, migrated) {
60543
62265
  applyCollapseProjection,
60544
62266
  applyGroupOrdering,
60545
62267
  applyPositionOverrides,
60546
- boldPalette,
62268
+ atlasPalette,
62269
+ blueprintPalette,
60547
62270
  buildExtendedChartOption,
60548
62271
  buildNoteMessageMap,
60549
62272
  buildRenderSequence,
@@ -60646,6 +62369,8 @@ function formatLineDiff(path, original, migrated) {
60646
62369
  looksLikeState,
60647
62370
  makeDgmoError,
60648
62371
  mapBackgroundColor,
62372
+ mapContentAspect,
62373
+ mapExportDimensions,
60649
62374
  mapNeutralLandColor,
60650
62375
  matchesContiguously,
60651
62376
  measurePertAnalysisBlock,
@@ -60781,9 +62506,11 @@ function formatLineDiff(path, original, migrated) {
60781
62506
  shapeFill,
60782
62507
  simulateCanonical,
60783
62508
  simulateFast,
62509
+ slatePalette,
60784
62510
  solarizedPalette,
60785
62511
  suggestChartTypes,
60786
62512
  themes,
62513
+ tidewaterPalette,
60787
62514
  tint,
60788
62515
  tokyoNightPalette,
60789
62516
  transformLine,