@diagrammo/dgmo 0.21.1 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +16 -6
  2. package/dist/advanced.cjs +2003 -466
  3. package/dist/advanced.d.cts +5714 -0
  4. package/dist/advanced.d.ts +5714 -0
  5. package/dist/advanced.js +1999 -466
  6. package/dist/auto.cjs +2048 -449
  7. package/dist/auto.d.cts +39 -0
  8. package/dist/auto.d.ts +39 -0
  9. package/dist/auto.js +121 -121
  10. package/dist/auto.mjs +2050 -450
  11. package/dist/cli.cjs +170 -170
  12. package/dist/editor.cjs +13 -16
  13. package/dist/editor.js +13 -16
  14. package/dist/highlight.cjs +15 -13
  15. package/dist/highlight.js +15 -13
  16. package/dist/index.cjs +2032 -435
  17. package/dist/index.d.cts +339 -0
  18. package/dist/index.d.ts +339 -0
  19. package/dist/index.js +2034 -436
  20. package/dist/internal.cjs +2003 -466
  21. package/dist/internal.d.cts +5714 -0
  22. package/dist/internal.d.ts +5714 -0
  23. package/dist/internal.js +1999 -466
  24. package/dist/map-data/water-bodies.json +1 -0
  25. package/docs/language-reference.md +20 -9
  26. package/gallery/fixtures/map-categorical-world.dgmo +16 -0
  27. package/gallery/fixtures/map-categorical.dgmo +0 -1
  28. package/gallery/fixtures/map-choropleth.dgmo +0 -1
  29. package/gallery/fixtures/map-coastline.dgmo +7 -0
  30. package/gallery/fixtures/map-colorize.dgmo +11 -0
  31. package/gallery/fixtures/map-direct-color.dgmo +0 -1
  32. package/gallery/fixtures/map-reference-world.dgmo +11 -0
  33. package/gallery/fixtures/map-region-scope.dgmo +0 -3
  34. package/gallery/fixtures/map-route.dgmo +0 -1
  35. package/package.json +1 -1
  36. package/src/advanced.ts +12 -1
  37. package/src/boxes-and-lines/renderer.ts +39 -12
  38. package/src/cli.ts +1 -1
  39. package/src/completion.ts +32 -25
  40. package/src/cycle/renderer.ts +14 -1
  41. package/src/d3.ts +8 -2
  42. package/src/editor/highlight-api.ts +4 -0
  43. package/src/editor/keywords.ts +13 -16
  44. package/src/infra/renderer.ts +35 -7
  45. package/src/map/colorize.ts +54 -0
  46. package/src/map/context-labels.ts +429 -0
  47. package/src/map/data/types.ts +34 -0
  48. package/src/map/data/water-bodies.json +1 -0
  49. package/src/map/dimensions.ts +117 -0
  50. package/src/map/geo-query.ts +21 -3
  51. package/src/map/geo.ts +47 -1
  52. package/src/map/layout.ts +1300 -251
  53. package/src/map/load-data.ts +10 -2
  54. package/src/map/parser.ts +42 -116
  55. package/src/map/renderer.ts +512 -13
  56. package/src/map/resolved-types.ts +16 -2
  57. package/src/map/resolver.ts +208 -59
  58. package/src/map/types.ts +30 -32
  59. package/src/mindmap/renderer.ts +10 -1
  60. package/src/palettes/atlas.ts +77 -0
  61. package/src/palettes/blueprint.ts +73 -0
  62. package/src/palettes/color-utils.ts +58 -1
  63. package/src/palettes/index.ts +12 -3
  64. package/src/palettes/slate.ts +73 -0
  65. package/src/palettes/tidewater.ts +73 -0
  66. package/src/render.ts +8 -1
  67. package/src/tech-radar/renderer.ts +3 -0
  68. package/src/tech-radar/types.ts +3 -0
  69. package/src/utils/d3-types.ts +5 -0
  70. package/src/utils/legend-layout.ts +21 -4
  71. package/src/utils/legend-types.ts +7 -0
  72. package/src/utils/reserved-key-registry.ts +3 -0
  73. package/src/palettes/bold.ts +0 -67
package/dist/internal.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",
@@ -1922,77 +1925,266 @@ function getSegmentColors(palette, count) {
1922
1925
  (_, i) => hslToHex(Math.round((startHue + i * step) % 360), avgS, avgL)
1923
1926
  );
1924
1927
  }
1928
+ function politicalTints(palette, count, isDark) {
1929
+ if (count <= 0) return [];
1930
+ const base = isDark ? palette.surface : palette.bg;
1931
+ const c = palette.colors;
1932
+ const swatches = [
1933
+ .../* @__PURE__ */ new Set([
1934
+ c.green,
1935
+ c.yellow,
1936
+ c.orange,
1937
+ c.purple,
1938
+ c.red,
1939
+ c.teal,
1940
+ c.cyan,
1941
+ c.blue
1942
+ ])
1943
+ ];
1944
+ const bands = isDark ? POLITICAL_TINT_BANDS.dark : POLITICAL_TINT_BANDS.light;
1945
+ const out = [];
1946
+ for (const pct of bands) {
1947
+ if (out.length >= count) break;
1948
+ for (const s of swatches) out.push(mix(s, base, pct));
1949
+ }
1950
+ return out.slice(0, count);
1951
+ }
1952
+ var POLITICAL_TINT_BANDS;
1925
1953
  var init_color_utils = __esm({
1926
1954
  "src/palettes/color-utils.ts"() {
1927
1955
  "use strict";
1956
+ POLITICAL_TINT_BANDS = {
1957
+ light: [32, 48, 64, 80],
1958
+ dark: [44, 58, 72, 86]
1959
+ };
1928
1960
  }
1929
1961
  });
1930
1962
 
1931
- // src/palettes/bold.ts
1932
- var boldPalette;
1933
- var init_bold = __esm({
1934
- "src/palettes/bold.ts"() {
1963
+ // src/palettes/atlas.ts
1964
+ var atlasPalette;
1965
+ var init_atlas = __esm({
1966
+ "src/palettes/atlas.ts"() {
1935
1967
  "use strict";
1936
1968
  init_registry();
1937
- boldPalette = {
1938
- id: "bold",
1939
- name: "Bold",
1969
+ atlasPalette = {
1970
+ id: "atlas",
1971
+ name: "Atlas",
1940
1972
  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",
1973
+ bg: "#f3ead3",
1974
+ // warm manila / parchment
1975
+ surface: "#ece0c0",
1976
+ // deeper paper (cards, panels)
1977
+ overlay: "#e8dab8",
1978
+ // popovers, dropdowns
1979
+ border: "#bcaa86",
1980
+ // muted sepia rule line
1981
+ text: "#463a26",
1982
+ // aged sepia-brown ink
1983
+ textMuted: "#7a6a4f",
1984
+ // faded annotation ink
1985
+ textOnFillLight: "#f7f1de",
1986
+ // parchment (light text on dark fills)
1987
+ textOnFillDark: "#3a2e1c",
1988
+ // deep ink (dark text on light fills)
1989
+ primary: "#5b7a99",
1990
+ // pull-down map ocean (steel-blue)
1991
+ secondary: "#7e9a6f",
1992
+ // lowland sage / celadon
1993
+ accent: "#b07f7c",
1994
+ // dusty rose
1995
+ destructive: "#b25a45",
1996
+ // brick / terracotta
1953
1997
  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"
1998
+ red: "#bf6a52",
1999
+ // terracotta brick
2000
+ orange: "#cf9a5c",
2001
+ // map tan / ochre
2002
+ yellow: "#cdb35e",
2003
+ // straw / muted lemon
2004
+ green: "#7e9a6f",
2005
+ // sage / celadon lowland
2006
+ blue: "#5b7a99",
2007
+ // steel-blue ocean
2008
+ purple: "#9a7fa6",
2009
+ // dusty lilac / mauve
2010
+ teal: "#6fa094",
2011
+ // muted seafoam
2012
+ cyan: "#79a7b5",
2013
+ // shallow-water blue
2014
+ gray: "#8a7d68",
2015
+ // warm taupe
2016
+ black: "#463a26",
2017
+ // ink
2018
+ white: "#ece0c0"
2019
+ // paper
1965
2020
  }
1966
2021
  },
1967
2022
  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",
2023
+ bg: "#1e2a33",
2024
+ // deep map ocean (night globe)
2025
+ surface: "#27353f",
2026
+ // raised ocean
2027
+ overlay: "#2e3d48",
2028
+ // popovers, dropdowns
2029
+ border: "#3d4f5c",
2030
+ // depth-contour line
2031
+ text: "#e8dcc0",
2032
+ // parchment ink, inverted
2033
+ textMuted: "#a89a7d",
2034
+ // faded label
2035
+ textOnFillLight: "#f7f1de",
2036
+ // parchment
2037
+ textOnFillDark: "#1a242c",
2038
+ // deep ocean ink
2039
+ primary: "#7ba0bf",
2040
+ // brighter ocean
2041
+ secondary: "#9bb588",
2042
+ // sage, lifted
2043
+ accent: "#cf9a96",
2044
+ // dusty rose, lifted
2045
+ destructive: "#c9745c",
2046
+ // brick, lifted
2047
+ colors: {
2048
+ red: "#cf7a60",
2049
+ // terracotta
2050
+ orange: "#d9a96a",
2051
+ // tan / ochre
2052
+ yellow: "#d8c074",
2053
+ // straw
2054
+ green: "#9bb588",
2055
+ // sage lowland
2056
+ blue: "#7ba0bf",
2057
+ // ocean
2058
+ purple: "#b59ac0",
2059
+ // lilac / mauve
2060
+ teal: "#85b3a6",
2061
+ // seafoam
2062
+ cyan: "#92bccb",
2063
+ // shallow-water blue
2064
+ gray: "#9a8d76",
2065
+ // warm taupe
2066
+ black: "#27353f",
2067
+ // raised ocean
2068
+ white: "#e8dcc0"
2069
+ // parchment
2070
+ }
2071
+ }
2072
+ };
2073
+ registerPalette(atlasPalette);
2074
+ }
2075
+ });
2076
+
2077
+ // src/palettes/blueprint.ts
2078
+ var blueprintPalette;
2079
+ var init_blueprint = __esm({
2080
+ "src/palettes/blueprint.ts"() {
2081
+ "use strict";
2082
+ init_registry();
2083
+ blueprintPalette = {
2084
+ id: "blueprint",
2085
+ name: "Blueprint",
2086
+ light: {
2087
+ bg: "#f4f8fb",
2088
+ // pale drafting white (faint cyan)
2089
+ surface: "#e6eef4",
2090
+ // drafting panel
2091
+ overlay: "#dde9f1",
2092
+ // popovers, dropdowns
2093
+ border: "#aac3d6",
2094
+ // pale blue grid line
2095
+ text: "#123a5e",
2096
+ // blueprint navy ink
2097
+ textMuted: "#4f7390",
2098
+ // faint draft note
2099
+ textOnFillLight: "#f4f8fb",
2100
+ // drafting white
2101
+ textOnFillDark: "#0c2f4d",
2102
+ // deep blueprint navy
2103
+ primary: "#1f5e8c",
2104
+ // blueprint blue
2105
+ secondary: "#5b7d96",
2106
+ // steel
2107
+ accent: "#b08a3e",
2108
+ // draftsman's ochre highlight
2109
+ destructive: "#c0504d",
2110
+ // correction red
1980
2111
  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"
2112
+ red: "#c25a4e",
2113
+ // correction red
2114
+ orange: "#c2823e",
2115
+ // ochre
2116
+ yellow: "#c2a843",
2117
+ // pencil gold
2118
+ green: "#4f8a6b",
2119
+ // drafting green
2120
+ blue: "#1f5e8c",
2121
+ // blueprint blue
2122
+ purple: "#6f5e96",
2123
+ // indigo pencil
2124
+ teal: "#3a8a8a",
2125
+ // teal
2126
+ cyan: "#3f8fb5",
2127
+ // cyan
2128
+ gray: "#7e8e98",
2129
+ // graphite
2130
+ black: "#123a5e",
2131
+ // navy ink
2132
+ white: "#e6eef4"
2133
+ // panel
2134
+ }
2135
+ },
2136
+ dark: {
2137
+ bg: "#103a5e",
2138
+ // deep blueprint blue (cyanotype ground)
2139
+ surface: "#16466e",
2140
+ // raised sheet
2141
+ overlay: "#1c5180",
2142
+ // popovers, dropdowns
2143
+ border: "#3a6f96",
2144
+ // grid line
2145
+ text: "#eaf2f8",
2146
+ // chalk white
2147
+ textMuted: "#9fc0d6",
2148
+ // faint chalk note
2149
+ textOnFillLight: "#eaf2f8",
2150
+ // chalk white
2151
+ textOnFillDark: "#0c2f4d",
2152
+ // deep blueprint navy
2153
+ primary: "#7fb8d8",
2154
+ // chalk cyan
2155
+ secondary: "#9fb8c8",
2156
+ // pale steel
2157
+ accent: "#d8c27a",
2158
+ // chalk amber
2159
+ destructive: "#e08a7a",
2160
+ // chalk correction red
2161
+ colors: {
2162
+ red: "#e0907e",
2163
+ // chalk red
2164
+ orange: "#e0ab78",
2165
+ // chalk amber
2166
+ yellow: "#e3d089",
2167
+ // chalk gold
2168
+ green: "#93c79e",
2169
+ // chalk green
2170
+ blue: "#8ec3e0",
2171
+ // chalk cyan-blue
2172
+ purple: "#b6a6d8",
2173
+ // chalk indigo
2174
+ teal: "#84c7c2",
2175
+ // chalk teal
2176
+ cyan: "#9fd6e0",
2177
+ // chalk cyan
2178
+ gray: "#aebecb",
2179
+ // chalk graphite
2180
+ black: "#16466e",
2181
+ // raised sheet
2182
+ white: "#eaf2f8"
2183
+ // chalk white
1992
2184
  }
1993
2185
  }
1994
2186
  };
1995
- registerPalette(boldPalette);
2187
+ registerPalette(blueprintPalette);
1996
2188
  }
1997
2189
  });
1998
2190
 
@@ -2489,6 +2681,120 @@ var init_rose_pine = __esm({
2489
2681
  }
2490
2682
  });
2491
2683
 
2684
+ // src/palettes/slate.ts
2685
+ var slatePalette;
2686
+ var init_slate = __esm({
2687
+ "src/palettes/slate.ts"() {
2688
+ "use strict";
2689
+ init_registry();
2690
+ slatePalette = {
2691
+ id: "slate",
2692
+ name: "Slate",
2693
+ light: {
2694
+ bg: "#ffffff",
2695
+ // clean slide white
2696
+ surface: "#f3f5f8",
2697
+ // light cool-gray panel
2698
+ overlay: "#eaeef3",
2699
+ // popovers, dropdowns
2700
+ border: "#d4dae1",
2701
+ // hairline rule
2702
+ text: "#1f2933",
2703
+ // near-black slate (softer than pure black)
2704
+ textMuted: "#5b6672",
2705
+ // secondary label
2706
+ textOnFillLight: "#ffffff",
2707
+ // light text on dark fills
2708
+ textOnFillDark: "#1f2933",
2709
+ // dark text on light fills
2710
+ primary: "#3b6ea5",
2711
+ // confident corporate blue
2712
+ secondary: "#5b6672",
2713
+ // slate gray
2714
+ accent: "#3a9188",
2715
+ // muted teal accent
2716
+ destructive: "#c0504d",
2717
+ // brick red
2718
+ colors: {
2719
+ red: "#c0504d",
2720
+ // brick
2721
+ orange: "#cc7a33",
2722
+ // muted amber
2723
+ yellow: "#c9a227",
2724
+ // gold (not neon)
2725
+ green: "#5b9357",
2726
+ // forest / sage
2727
+ blue: "#3b6ea5",
2728
+ // corporate blue
2729
+ purple: "#7d5ba6",
2730
+ // muted violet
2731
+ teal: "#3a9188",
2732
+ // teal
2733
+ cyan: "#4f96c4",
2734
+ // steel cyan
2735
+ gray: "#7e8a97",
2736
+ // cool gray
2737
+ black: "#1f2933",
2738
+ // slate ink
2739
+ white: "#f3f5f8"
2740
+ // panel
2741
+ }
2742
+ },
2743
+ dark: {
2744
+ bg: "#161b22",
2745
+ // deep slate (keynote dark)
2746
+ surface: "#202833",
2747
+ // raised panel
2748
+ overlay: "#29323e",
2749
+ // popovers, dropdowns
2750
+ border: "#38424f",
2751
+ // divider
2752
+ text: "#e6eaef",
2753
+ // off-white
2754
+ textMuted: "#9aa5b1",
2755
+ // secondary label
2756
+ textOnFillLight: "#ffffff",
2757
+ // light text on dark fills
2758
+ textOnFillDark: "#161b22",
2759
+ // dark text on light fills
2760
+ primary: "#5b9bd5",
2761
+ // lifted corporate blue
2762
+ secondary: "#8593a3",
2763
+ // slate gray, lifted
2764
+ accent: "#45b3a3",
2765
+ // teal, lifted
2766
+ destructive: "#e07b6e",
2767
+ // brick, lifted
2768
+ colors: {
2769
+ red: "#e07b6e",
2770
+ // brick
2771
+ orange: "#e0975a",
2772
+ // amber
2773
+ yellow: "#d9bd5a",
2774
+ // gold
2775
+ green: "#74b56e",
2776
+ // forest / sage
2777
+ blue: "#5b9bd5",
2778
+ // corporate blue
2779
+ purple: "#a585c9",
2780
+ // violet
2781
+ teal: "#45b3a3",
2782
+ // teal
2783
+ cyan: "#62b0d9",
2784
+ // steel cyan
2785
+ gray: "#95a1ae",
2786
+ // cool gray
2787
+ black: "#202833",
2788
+ // raised panel
2789
+ white: "#e6eaef"
2790
+ // off-white
2791
+ }
2792
+ }
2793
+ };
2794
+ registerPalette(slatePalette);
2795
+ }
2796
+ });
2797
+
2492
2798
  // src/palettes/solarized.ts
2493
2799
  var solarizedPalette;
2494
2800
  var init_solarized = __esm({
@@ -2584,6 +2890,120 @@ var init_solarized = __esm({
2584
2890
  }
2585
2891
  });
2586
2892
 
2893
+ // src/palettes/tidewater.ts
2894
+ var tidewaterPalette;
2895
+ var init_tidewater = __esm({
2896
+ "src/palettes/tidewater.ts"() {
2897
+ "use strict";
2898
+ init_registry();
2899
+ tidewaterPalette = {
2900
+ id: "tidewater",
2901
+ name: "Tidewater",
2902
+ light: {
2903
+ bg: "#eceff0",
2904
+ // weathered sea-mist paper
2905
+ surface: "#e0e4e3",
2906
+ // worn deck panel
2907
+ overlay: "#dadfdf",
2908
+ // popovers, dropdowns
2909
+ border: "#a9b2b3",
2910
+ // muted slate rule
2911
+ text: "#18313f",
2912
+ // ship's-log navy ink
2913
+ textMuted: "#51636b",
2914
+ // faded log entry
2915
+ textOnFillLight: "#f3f5f3",
2916
+ // weathered white
2917
+ textOnFillDark: "#162c38",
2918
+ // deep navy
2919
+ primary: "#1f4e6b",
2920
+ // deep-sea navy
2921
+ secondary: "#b08a4f",
2922
+ // rope / manila tan
2923
+ accent: "#c69a3e",
2924
+ // brass
2925
+ destructive: "#c1433a",
2926
+ // signal-flag red
2927
+ colors: {
2928
+ red: "#c1433a",
2929
+ // signal-flag red
2930
+ orange: "#cc7a38",
2931
+ // weathered amber
2932
+ yellow: "#d6bf5a",
2933
+ // brass gold
2934
+ green: "#4f8a6b",
2935
+ // sea-glass green
2936
+ blue: "#1f4e6b",
2937
+ // deep-sea navy
2938
+ purple: "#6a5a8c",
2939
+ // twilight harbor
2940
+ teal: "#3d8c8c",
2941
+ // sea-glass teal
2942
+ cyan: "#4f9bb5",
2943
+ // shallow water
2944
+ gray: "#8a8d86",
2945
+ // driftwood gray
2946
+ black: "#18313f",
2947
+ // navy ink
2948
+ white: "#e0e4e3"
2949
+ // deck panel
2950
+ }
2951
+ },
2952
+ dark: {
2953
+ bg: "#0f2230",
2954
+ // night-harbor deep sea
2955
+ surface: "#16303f",
2956
+ // raised hull
2957
+ overlay: "#1d3a4a",
2958
+ // popovers, dropdowns
2959
+ border: "#2c4856",
2960
+ // rigging line
2961
+ text: "#e6ebe8",
2962
+ // weathered white
2963
+ textMuted: "#9aaab0",
2964
+ // faded label
2965
+ textOnFillLight: "#f3f5f3",
2966
+ // weathered white
2967
+ textOnFillDark: "#0f2230",
2968
+ // deep sea
2969
+ primary: "#4f9bc4",
2970
+ // lifted sea blue
2971
+ secondary: "#c9a46a",
2972
+ // rope tan, lifted
2973
+ accent: "#d9b25a",
2974
+ // brass, lifted
2975
+ destructive: "#e06a5e",
2976
+ // signal red, lifted
2977
+ colors: {
2978
+ red: "#e06a5e",
2979
+ // signal-flag red
2980
+ orange: "#df9a52",
2981
+ // amber
2982
+ yellow: "#e0c662",
2983
+ // brass gold
2984
+ green: "#6fb58c",
2985
+ // sea-glass green
2986
+ blue: "#4f9bc4",
2987
+ // sea blue
2988
+ purple: "#9486bf",
2989
+ // twilight harbor
2990
+ teal: "#5cb0ac",
2991
+ // sea-glass teal
2992
+ cyan: "#62b4cf",
2993
+ // shallow water
2994
+ gray: "#9aa39c",
2995
+ // driftwood gray
2996
+ black: "#16303f",
2997
+ // raised hull
2998
+ white: "#e6ebe8"
2999
+ // weathered white
3000
+ }
3001
+ }
3002
+ };
3003
+ registerPalette(tidewaterPalette);
3004
+ }
3005
+ });
3006
+
2587
3007
  // src/palettes/tokyo-night.ts
2588
3008
  var tokyoNightPalette;
2589
3009
  var init_tokyo_night = __esm({
@@ -2859,7 +3279,8 @@ var init_monokai = __esm({
2859
3279
  // src/palettes/index.ts
2860
3280
  var palettes_exports = {};
2861
3281
  __export(palettes_exports, {
2862
- boldPalette: () => boldPalette,
3282
+ atlasPalette: () => atlasPalette,
3283
+ blueprintPalette: () => blueprintPalette,
2863
3284
  catppuccinPalette: () => catppuccinPalette,
2864
3285
  contrastText: () => contrastText,
2865
3286
  draculaPalette: () => draculaPalette,
@@ -2880,7 +3301,9 @@ __export(palettes_exports, {
2880
3301
  rosePinePalette: () => rosePinePalette,
2881
3302
  shade: () => shade,
2882
3303
  shapeFill: () => shapeFill,
3304
+ slatePalette: () => slatePalette,
2883
3305
  solarizedPalette: () => solarizedPalette,
3306
+ tidewaterPalette: () => tidewaterPalette,
2884
3307
  tint: () => tint,
2885
3308
  tokyoNightPalette: () => tokyoNightPalette
2886
3309
  });
@@ -2890,17 +3313,21 @@ var init_palettes = __esm({
2890
3313
  "use strict";
2891
3314
  init_registry();
2892
3315
  init_color_utils();
2893
- init_bold();
3316
+ init_atlas();
3317
+ init_blueprint();
2894
3318
  init_catppuccin();
2895
3319
  init_gruvbox();
2896
3320
  init_nord();
2897
3321
  init_one_dark();
2898
3322
  init_rose_pine();
3323
+ init_slate();
2899
3324
  init_solarized();
3325
+ init_tidewater();
2900
3326
  init_tokyo_night();
2901
3327
  init_dracula();
2902
3328
  init_monokai();
2903
- init_bold();
3329
+ init_atlas();
3330
+ init_blueprint();
2904
3331
  init_catppuccin();
2905
3332
  init_dracula();
2906
3333
  init_gruvbox();
@@ -2908,9 +3335,15 @@ var init_palettes = __esm({
2908
3335
  init_nord();
2909
3336
  init_one_dark();
2910
3337
  init_rose_pine();
3338
+ init_slate();
2911
3339
  init_solarized();
3340
+ init_tidewater();
2912
3341
  init_tokyo_night();
2913
3342
  palettes = {
3343
+ atlas: atlasPalette,
3344
+ blueprint: blueprintPalette,
3345
+ slate: slatePalette,
3346
+ tidewater: tidewaterPalette,
2914
3347
  nord: nordPalette,
2915
3348
  catppuccin: catppuccinPalette,
2916
3349
  solarized: solarizedPalette,
@@ -2919,8 +3352,7 @@ var init_palettes = __esm({
2919
3352
  oneDark: oneDarkPalette,
2920
3353
  rosePine: rosePinePalette,
2921
3354
  dracula: draculaPalette,
2922
- monokai: monokaiPalette,
2923
- bold: boldPalette
3355
+ monokai: monokaiPalette
2924
3356
  };
2925
3357
  }
2926
3358
  });
@@ -3430,6 +3862,9 @@ function controlsGroupCapsuleWidth(toggles) {
3430
3862
  }
3431
3863
  return w;
3432
3864
  }
3865
+ function isAppHostedControls(config, isExport) {
3866
+ return !isExport && config.controlsHost === "app" && !!config.controlsGroup && config.controlsGroup.toggles.length > 0;
3867
+ }
3433
3868
  function buildControlsGroupLayout(config, state) {
3434
3869
  const cg = config.controlsGroup;
3435
3870
  if (!cg || cg.toggles.length === 0) return void 0;
@@ -3483,6 +3918,7 @@ function buildControlsGroupLayout(config, state) {
3483
3918
  function computeLegendLayout(config, state, containerWidth) {
3484
3919
  const { groups, controls: configControls, mode } = config;
3485
3920
  const isExport = mode === "export";
3921
+ const gated = isAppHostedControls(config, isExport);
3486
3922
  const activeGroupName = state.activeGroup?.toLowerCase() ?? null;
3487
3923
  if (isExport && !activeGroupName) {
3488
3924
  return {
@@ -3493,7 +3929,7 @@ function computeLegendLayout(config, state, containerWidth) {
3493
3929
  pills: []
3494
3930
  };
3495
3931
  }
3496
- const controlsGroupLayout = isExport ? void 0 : buildControlsGroupLayout(config, state);
3932
+ const controlsGroupLayout = isExport || gated ? void 0 : buildControlsGroupLayout(config, state);
3497
3933
  const visibleGroups = config.showEmptyGroups ? groups : groups.filter((g) => g.entries.length > 0 || !!g.gradient);
3498
3934
  if (visibleGroups.length === 0 && (!configControls || configControls.length === 0) && !controlsGroupLayout) {
3499
3935
  return {
@@ -8325,8 +8761,8 @@ function computeScatterLabelGraphics(points, chartBounds, fontSize, symbolSize,
8325
8761
  const pt = points[i];
8326
8762
  const ptSize = pt.size ?? symbolSize;
8327
8763
  const minGap = ptSize / 2 + 4;
8328
- const labelWidth = pt.name.length * fontSize * 0.6 + 8;
8329
- const labelX = pt.px - labelWidth / 2;
8764
+ const labelWidth2 = pt.name.length * fontSize * 0.6 + 8;
8765
+ const labelX = pt.px - labelWidth2 / 2;
8330
8766
  let bestLabelY = 0;
8331
8767
  let bestOffset = Infinity;
8332
8768
  let placed = false;
@@ -8338,7 +8774,7 @@ function computeScatterLabelGraphics(points, chartBounds, fontSize, symbolSize,
8338
8774
  const candidate = {
8339
8775
  x: labelX,
8340
8776
  y: labelY,
8341
- w: labelWidth,
8777
+ w: labelWidth2,
8342
8778
  h: labelHeight
8343
8779
  };
8344
8780
  let collision = false;
@@ -8380,7 +8816,7 @@ function computeScatterLabelGraphics(points, chartBounds, fontSize, symbolSize,
8380
8816
  const labelRect = {
8381
8817
  x: labelX,
8382
8818
  y: bestLabelY,
8383
- w: labelWidth,
8819
+ w: labelWidth2,
8384
8820
  h: labelHeight
8385
8821
  };
8386
8822
  placedLabels.push(labelRect);
@@ -8416,7 +8852,7 @@ function computeScatterLabelGraphics(points, chartBounds, fontSize, symbolSize,
8416
8852
  shape: {
8417
8853
  x: labelX - bgPad,
8418
8854
  y: bestLabelY - bgPad,
8419
- width: labelWidth + bgPad * 2,
8855
+ width: labelWidth2 + bgPad * 2,
8420
8856
  height: labelHeight + bgPad * 2
8421
8857
  },
8422
8858
  style: { fill: bg },
@@ -15856,10 +16292,6 @@ function parseMap(content) {
15856
16292
  handleTag(trimmed, lineNumber);
15857
16293
  continue;
15858
16294
  }
15859
- if ((firstWord === "muted" || firstWord === "natural") && trimmed === firstWord) {
15860
- handleDirective(firstWord, "", lineNumber);
15861
- continue;
15862
- }
15863
16295
  if (DIRECTIVE_SET.has(firstWord) && !trimmed.slice(firstWord.length).trimStart().startsWith(":")) {
15864
16296
  handleDirective(
15865
16297
  firstWord,
@@ -15906,24 +16338,6 @@ function parseMap(content) {
15906
16338
  pushWarning(line12, `Duplicate directive "${key}" \u2014 last value wins.`);
15907
16339
  };
15908
16340
  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
16341
  case "region-metric": {
15928
16342
  dup(d.regionMetric);
15929
16343
  const { label: rmLabel, colorName: rmColor } = peelTrailingColorName(value);
@@ -15939,91 +16353,43 @@ function parseMap(content) {
15939
16353
  dup(d.flowMetric);
15940
16354
  d.flowMetric = value;
15941
16355
  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;
16356
+ case "locale":
16357
+ dup(d.locale);
16358
+ d.locale = value;
15974
16359
  break;
15975
16360
  case "active-tag":
15976
16361
  dup(d.activeTag);
15977
16362
  d.activeTag = value;
15978
16363
  break;
16364
+ case "caption":
16365
+ dup(d.caption);
16366
+ d.caption = value;
16367
+ break;
16368
+ // ── Cosmetic `no-*` opt-outs: bare flags, idempotent (mirror `no-legend`,
16369
+ // no dup warning); each defaults the feature ON when absent. ──
15979
16370
  case "no-legend":
15980
16371
  d.noLegend = true;
15981
16372
  break;
15982
- case "no-insets":
15983
- d.noInsets = true;
16373
+ case "no-coastline":
16374
+ d.noCoastline = true;
15984
16375
  break;
15985
- case "relief":
15986
- d.relief = true;
16376
+ case "no-relief":
16377
+ d.noRelief = true;
15987
16378
  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;
16379
+ case "no-context-labels":
16380
+ d.noContextLabels = true;
15996
16381
  break;
15997
- case "subtitle":
15998
- dup(d.subtitle);
15999
- d.subtitle = value;
16382
+ case "no-region-labels":
16383
+ d.noRegionLabels = true;
16000
16384
  break;
16001
- case "caption":
16002
- dup(d.caption);
16003
- d.caption = value;
16385
+ case "no-poi-labels":
16386
+ d.noPoiLabels = true;
16387
+ break;
16388
+ case "no-colorize":
16389
+ d.noColorize = true;
16004
16390
  break;
16005
16391
  }
16006
16392
  }
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
16393
  function handleTag(trimmed, line12) {
16028
16394
  const m = matchTagBlockHeading(trimmed);
16029
16395
  if (!m) {
@@ -16223,13 +16589,15 @@ function parseMap(content) {
16223
16589
  pushError(line12, `Edge has an empty endpoint: "${trimmed}".`);
16224
16590
  continue;
16225
16591
  }
16226
- const meta = k === links.length - 1 ? lastSplit.meta : {};
16592
+ const isLast = k === links.length - 1;
16593
+ const meta = isLast ? lastSplit.meta : {};
16594
+ const style = links[k].style === "arc" ? "arc" : "straight";
16227
16595
  edges.push({
16228
16596
  from,
16229
16597
  to,
16230
16598
  ...links[k].label !== void 0 && { label: links[k].label },
16231
16599
  directed: links[k].directed,
16232
- style: links[k].style,
16600
+ style,
16233
16601
  meta,
16234
16602
  lineNumber: line12
16235
16603
  });
@@ -16315,22 +16683,19 @@ var init_parser12 = __esm({
16315
16683
  LEG_ARROW_RE = /^(-[^>]*?->|->|~[^>]*?~>|~>|--)\s+(.+)$/;
16316
16684
  AT_RE = /(^|[\s,])at\s*:/i;
16317
16685
  DIRECTIVE_SET = /* @__PURE__ */ new Set([
16318
- "region",
16319
- "projection",
16320
16686
  "region-metric",
16321
16687
  "poi-metric",
16322
16688
  "flow-metric",
16323
- "scale",
16324
- "region-labels",
16325
- "poi-labels",
16326
- "default-country",
16327
- "default-state",
16689
+ "locale",
16328
16690
  "active-tag",
16691
+ "caption",
16329
16692
  "no-legend",
16330
- "no-insets",
16331
- "relief",
16332
- "subtitle",
16333
- "caption"
16693
+ "no-coastline",
16694
+ "no-relief",
16695
+ "no-context-labels",
16696
+ "no-region-labels",
16697
+ "no-poi-labels",
16698
+ "no-colorize"
16334
16699
  ]);
16335
16700
  }
16336
16701
  });
@@ -24341,8 +24706,8 @@ function renderKanban(container, parsed, palette, isDark, options) {
24341
24706
  let metaY = separatorY + sCardSeparatorGap + sCardMetaFontSize;
24342
24707
  for (const meta of tagMeta) {
24343
24708
  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);
24709
+ const labelWidth2 = (meta.label.length + 2) * sCardMetaFontSize * 0.6;
24710
+ cg.append("text").attr("x", cx + sCardPaddingX + labelWidth2).attr("y", metaY).attr("font-size", sCardMetaFontSize).attr("fill", onCardText).text(meta.value);
24346
24711
  metaY += sCardMetaLineHeight;
24347
24712
  }
24348
24713
  for (const detail of card.details) {
@@ -24686,8 +25051,8 @@ function renderSwimlaneCard(parent, cardLayout, tagGroups, activeTagGroup, palet
24686
25051
  let metaY = separatorY + sCardSeparatorGap + sCardMetaFontSize;
24687
25052
  for (const meta of tagMeta) {
24688
25053
  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);
25054
+ const labelWidth2 = (meta.label.length + 2) * sCardMetaFontSize * 0.6;
25055
+ cg.append("text").attr("x", cx + sCardPaddingX + labelWidth2).attr("y", metaY).attr("font-size", sCardMetaFontSize).attr("fill", onCardText).text(meta.value);
24691
25056
  metaY += sCardMetaLineHeight;
24692
25057
  }
24693
25058
  for (const detail of card.details) {
@@ -25522,8 +25887,8 @@ function classifyEREntities(tables, relationships) {
25522
25887
  }
25523
25888
  }
25524
25889
  const mmParticipants = /* @__PURE__ */ new Set();
25525
- for (const [id, neighbors] of tableStarNeighbors) {
25526
- if (neighbors.size >= 2) mmParticipants.add(id);
25890
+ for (const [id, neighbors2] of tableStarNeighbors) {
25891
+ if (neighbors2.size >= 2) mmParticipants.add(id);
25527
25892
  }
25528
25893
  const indegreeValues = Object.values(indegreeMap);
25529
25894
  const mean = indegreeValues.reduce((a, b) => a + b, 0) / indegreeValues.length;
@@ -26194,7 +26559,8 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26194
26559
  controlsExpanded,
26195
26560
  onToggleDescriptions,
26196
26561
  onToggleControlsExpand,
26197
- exportMode = false
26562
+ exportMode = false,
26563
+ controlsHost
26198
26564
  } = options ?? {};
26199
26565
  d3Selection6.select(container).selectAll(":not([data-d3-tooltip])").remove();
26200
26566
  const width = exportDims?.width ?? container.clientWidth;
@@ -26212,7 +26578,11 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26212
26578
  const sGroupLabelZone = sctx.structural(GROUP_LABEL_ZONE);
26213
26579
  const sTitleFontSize = sctx.text(TITLE_FONT_SIZE);
26214
26580
  const sTitleY = sctx.structural(TITLE_Y);
26215
- const sLegendHeight = sctx.structural(
26581
+ const reserveHasDescriptions = parsed.nodes.some(
26582
+ (n) => n.description && n.description.length > 0
26583
+ );
26584
+ const willRenderLegend = parsed.tagGroups.length > 0 || reserveHasDescriptions && controlsHost !== "app";
26585
+ const sLegendHeight = willRenderLegend ? sctx.structural(
26216
26586
  getMaxLegendReservedHeight(
26217
26587
  {
26218
26588
  groups: parsed.tagGroups,
@@ -26221,7 +26591,7 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26221
26591
  },
26222
26592
  width
26223
26593
  )
26224
- );
26594
+ ) : 0;
26225
26595
  const activeGroup = resolveActiveTagGroup(
26226
26596
  parsed.tagGroups,
26227
26597
  parsed.options["active-tag"],
@@ -26536,10 +26906,10 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26536
26906
  const hasDescriptions = parsed.nodes.some(
26537
26907
  (n) => n.description && n.description.length > 0
26538
26908
  );
26539
- const hasLegend = parsed.tagGroups.length > 0 || hasDescriptions;
26909
+ const hasLegend = parsed.tagGroups.length > 0 || hasDescriptions && controlsHost !== "app";
26540
26910
  if (hasLegend) {
26541
26911
  let controlsGroup;
26542
- if (hasDescriptions && onToggleDescriptions) {
26912
+ if (hasDescriptions && (onToggleDescriptions || controlsHost === "app")) {
26543
26913
  controlsGroup = {
26544
26914
  toggles: [
26545
26915
  {
@@ -26557,7 +26927,14 @@ function renderBoxesAndLines(container, parsed, layout, palette, isDark, options
26557
26927
  groups: parsed.tagGroups,
26558
26928
  position: { placement: "top-center", titleRelation: "below-title" },
26559
26929
  mode: exportMode ? "export" : "preview",
26560
- ...controlsGroup !== void 0 && { controlsGroup }
26930
+ // Keep inactive sibling tag groups visible as collapsed pills so the user
26931
+ // can click one to flip the active colouring dimension (preview only —
26932
+ // export shows just the active group). Without this, declaring a second
26933
+ // tag group (e.g. Team) leaves it invisible whenever another group is
26934
+ // active. The app's BoxesAndLinesPreview already wires pill clicks.
26935
+ showInactivePills: true,
26936
+ ...controlsGroup !== void 0 && { controlsGroup },
26937
+ ...controlsHost !== void 0 && { controlsHost }
26561
26938
  };
26562
26939
  const legendState = {
26563
26940
  activeGroup,
@@ -27805,8 +28182,9 @@ function renderMindmap(container, parsed, layout, palette, isDark, onClickItem,
27805
28182
  const containerHeight = exportDims?.height ?? (container.getBoundingClientRect().height || 600);
27806
28183
  d3Selection7.select(container).selectAll("*").remove();
27807
28184
  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);
28185
+ const appHosted = options?.controlsHost === "app";
27808
28186
  const hasControls = !!options?.onToggleColorByDepth || !!options?.onToggleDescriptions;
27809
- const hasLegend = parsed.tagGroups.length > 0 || hasControls;
28187
+ const hasLegend = parsed.tagGroups.length > 0 || hasControls && !appHosted;
27810
28188
  const fixedLegend = !isExport && hasLegend;
27811
28189
  const legendReserve = fixedLegend ? getMaxLegendReservedHeight(
27812
28190
  {
@@ -27900,7 +28278,10 @@ function renderMindmap(container, parsed, layout, palette, isDark, onClickItem,
27900
28278
  }),
27901
28279
  position: { placement: "top-center", titleRelation: "below-title" },
27902
28280
  mode: options?.exportMode ? "export" : "preview",
27903
- ...controlsToggles !== void 0 && { controlsGroup: controlsToggles }
28281
+ ...controlsToggles !== void 0 && { controlsGroup: controlsToggles },
28282
+ ...options?.controlsHost !== void 0 && {
28283
+ controlsHost: options.controlsHost
28284
+ }
27904
28285
  };
27905
28286
  const legendState = {
27906
28287
  activeGroup: options?.colorByDepth ? null : activeTagGroup !== void 0 ? activeTagGroup : parsed.options["active-tag"] ?? null,
@@ -28344,8 +28725,8 @@ function computeFieldAlignX(children) {
28344
28725
  for (const child of children) {
28345
28726
  if (child.metadata["_labelField"] === "true" && child.children.length >= 2) {
28346
28727
  const labelEl = child.children[0];
28347
- const labelWidth = labelEl.label.length * CHAR_WIDTH5;
28348
- maxLabelWidth = Math.max(maxLabelWidth, labelWidth);
28728
+ const labelWidth2 = labelEl.label.length * CHAR_WIDTH5;
28729
+ maxLabelWidth = Math.max(maxLabelWidth, labelWidth2);
28349
28730
  labelFieldCount++;
28350
28731
  }
28351
28732
  }
@@ -33309,7 +33690,7 @@ function hasRoles(node) {
33309
33690
  function computeNodeWidth2(node, expanded, options) {
33310
33691
  const badgeVal = node.computedConcurrentInvocations === 0 && node.computedInstances > 1 ? node.computedInstances : 0;
33311
33692
  const badgeLen = badgeVal > 0 ? `${badgeVal}x`.length + 2 : 0;
33312
- const labelWidth = (node.label.length + badgeLen) * CHAR_WIDTH7 + PADDING_X3;
33693
+ const labelWidth2 = (node.label.length + badgeLen) * CHAR_WIDTH7 + PADDING_X3;
33313
33694
  const allKeys = [];
33314
33695
  if (node.computedRps > 0) allKeys.push("RPS");
33315
33696
  if (expanded) {
@@ -33353,7 +33734,7 @@ function computeNodeWidth2(node, expanded, options) {
33353
33734
  allKeys.push("overflow");
33354
33735
  }
33355
33736
  }
33356
- if (allKeys.length === 0) return Math.max(MIN_NODE_WIDTH2, labelWidth);
33737
+ if (allKeys.length === 0) return Math.max(MIN_NODE_WIDTH2, labelWidth2);
33357
33738
  const maxKeyLen = Math.max(...allKeys.map((k) => k.length));
33358
33739
  let maxRowWidth = 0;
33359
33740
  if (node.computedRps > 0) {
@@ -33441,7 +33822,7 @@ function computeNodeWidth2(node, expanded, options) {
33441
33822
  truncated.length * META_CHAR_WIDTH3 + PADDING_X3
33442
33823
  );
33443
33824
  }
33444
- return Math.max(MIN_NODE_WIDTH2, labelWidth, maxRowWidth + 20, descWidth);
33825
+ return Math.max(MIN_NODE_WIDTH2, labelWidth2, maxRowWidth + 20, descWidth);
33445
33826
  }
33446
33827
  function computeNodeHeight2(node, expanded, options) {
33447
33828
  const propCount = countDisplayProps(node, expanded, options);
@@ -34990,8 +35371,9 @@ function computeInfraLegendGroups(nodes, tagGroups, palette, edges) {
34990
35371
  }
34991
35372
  return groups;
34992
35373
  }
34993
- function renderLegend3(rootSvg, legendGroups, totalWidth, legendY, palette, isDark, activeGroup, playback, exportMode = false) {
35374
+ function renderLegend3(rootSvg, legendGroups, totalWidth, legendY, palette, isDark, activeGroup, playback, exportMode = false, controlsHost) {
34994
35375
  if (legendGroups.length === 0 && !playback) return;
35376
+ const appHostedPlayback = controlsHost === "app" && !!playback;
34995
35377
  const legendG = rootSvg.append("g").attr("transform", `translate(0, ${legendY})`);
34996
35378
  if (activeGroup) {
34997
35379
  legendG.attr("data-legend-active", activeGroup.toLowerCase());
@@ -35000,14 +35382,29 @@ function renderLegend3(rootSvg, legendGroups, totalWidth, legendY, palette, isDa
35000
35382
  name: g.name,
35001
35383
  entries: g.entries.map((e) => ({ value: e.value, color: e.color }))
35002
35384
  }));
35003
- if (playback) {
35385
+ if (playback && !appHostedPlayback) {
35004
35386
  allGroups.push({ name: "Playback", entries: [] });
35005
35387
  }
35006
35388
  const legendConfig = {
35007
35389
  groups: allGroups,
35008
35390
  position: { placement: "top-center", titleRelation: "below-title" },
35009
35391
  mode: exportMode ? "export" : "preview",
35010
- showEmptyGroups: true
35392
+ showEmptyGroups: true,
35393
+ ...appHostedPlayback && {
35394
+ controlsHost: "app",
35395
+ controlsGroup: {
35396
+ toggles: [
35397
+ {
35398
+ id: "playback",
35399
+ type: "toggle",
35400
+ label: "Playback",
35401
+ active: true,
35402
+ onToggle: () => {
35403
+ }
35404
+ }
35405
+ ]
35406
+ }
35407
+ }
35011
35408
  };
35012
35409
  const legendState = { activeGroup };
35013
35410
  renderLegendD3(
@@ -35058,8 +35455,9 @@ function renderLegend3(rootSvg, legendGroups, totalWidth, legendY, palette, isDa
35058
35455
  }
35059
35456
  }
35060
35457
  }
35061
- function renderInfra(container, layout, palette, isDark, title, titleLineNumber, tagGroups, activeGroup, animate, playback, expandedNodeIds, exportMode, collapsedNodes) {
35458
+ function renderInfra(container, layout, palette, isDark, title, titleLineNumber, tagGroups, activeGroup, animate, playback, expandedNodeIds, exportMode, collapsedNodes, controlsHost) {
35062
35459
  d3Selection11.select(container).selectAll(":not([data-d3-tooltip])").remove();
35460
+ const appHostedPlayback = controlsHost === "app" && !!playback;
35063
35461
  const ctx = ScaleContext.identity();
35064
35462
  const sc = buildScaledConstants(ctx);
35065
35463
  const legendGroups = computeInfraLegendGroups(
@@ -35068,7 +35466,7 @@ function renderInfra(container, layout, palette, isDark, title, titleLineNumber,
35068
35466
  palette,
35069
35467
  layout.edges
35070
35468
  );
35071
- const hasLegend = legendGroups.length > 0 || !!playback;
35469
+ const hasLegend = legendGroups.length > 0 || !!playback && !appHostedPlayback;
35072
35470
  const fixedLegend = !exportMode && hasLegend;
35073
35471
  const legendDynamicH = hasLegend ? getMaxLegendReservedHeight(
35074
35472
  {
@@ -35212,7 +35610,8 @@ function renderInfra(container, layout, palette, isDark, title, titleLineNumber,
35212
35610
  isDark,
35213
35611
  activeGroup ?? null,
35214
35612
  playback ?? void 0,
35215
- exportMode
35613
+ exportMode,
35614
+ controlsHost
35216
35615
  );
35217
35616
  legendSvg.selectAll(".infra-legend-group").style("pointer-events", "auto");
35218
35617
  } else {
@@ -35225,7 +35624,8 @@ function renderInfra(container, layout, palette, isDark, title, titleLineNumber,
35225
35624
  isDark,
35226
35625
  activeGroup ?? null,
35227
35626
  playback ?? void 0,
35228
- exportMode
35627
+ exportMode,
35628
+ controlsHost
35229
35629
  );
35230
35630
  }
35231
35631
  }
@@ -43083,6 +43483,9 @@ function renderTechRadar(container, parsed, palette, isDark, onClickItem, export
43083
43483
  onToggle: (active) => options.onToggleListing(active)
43084
43484
  }
43085
43485
  ]
43486
+ },
43487
+ ...options.controlsHost !== void 0 && {
43488
+ controlsHost: options.controlsHost
43086
43489
  }
43087
43490
  };
43088
43491
  const legendState = {
@@ -44906,7 +45309,7 @@ function computeCycleLayout(parsed, options) {
44906
45309
  const circleNodes = parsed.options["circle-nodes"] === "true";
44907
45310
  const nodeDims = parsed.nodes.map((node) => {
44908
45311
  const hasDesc = !hideDescriptions && node.description.length > 0;
44909
- const labelWidth = Math.max(
45312
+ const labelWidth2 = Math.max(
44910
45313
  MIN_NODE_WIDTH4,
44911
45314
  node.label.length * LABEL_CHAR_W + NODE_PAD_X * 2
44912
45315
  );
@@ -44915,12 +45318,12 @@ function computeCycleLayout(parsed, options) {
44915
45318
  }
44916
45319
  if (!hasDesc) {
44917
45320
  return {
44918
- width: Math.min(MAX_NODE_WIDTH3, labelWidth),
45321
+ width: Math.min(MAX_NODE_WIDTH3, labelWidth2),
44919
45322
  height: PLAIN_NODE_HEIGHT,
44920
45323
  wrappedDesc: []
44921
45324
  };
44922
45325
  }
44923
- return chooseDescribedRectDims(node.description, labelWidth);
45326
+ return chooseDescribedRectDims(node.description, labelWidth2);
44924
45327
  });
44925
45328
  if (circleNodes) {
44926
45329
  const maxDiam = Math.max(...nodeDims.map((d) => d.width));
@@ -45116,10 +45519,10 @@ function computeCycleLayout(parsed, options) {
45116
45519
  scale
45117
45520
  };
45118
45521
  }
45119
- function chooseDescribedRectDims(description, labelWidth) {
45522
+ function chooseDescribedRectDims(description, labelWidth2) {
45120
45523
  const minW = Math.min(
45121
45524
  MAX_NODE_WIDTH3,
45122
- Math.max(MIN_NODE_WIDTH4, labelWidth, DESC_MIN_WIDTH)
45525
+ Math.max(MIN_NODE_WIDTH4, labelWidth2, DESC_MIN_WIDTH)
45123
45526
  );
45124
45527
  let best = null;
45125
45528
  let bestScore = Infinity;
@@ -45547,7 +45950,8 @@ function renderCycle(container, parsed, palette, isDark, onClickItem, exportDims
45547
45950
  const hideDescriptions = (renderOptions?.hideDescriptions ?? false) || parsed.options["no-descriptions"] === "true" || viewState?.hd === true;
45548
45951
  const showDescriptions = !hideDescriptions;
45549
45952
  const hasDescriptions = parsed.nodes.some((n) => n.description.length > 0) || parsed.edges.some((e) => e.description.length > 0);
45550
- const hasLegend = hasDescriptions && !!renderOptions?.onToggleDescriptions;
45953
+ const appHostedControls = renderOptions?.controlsHost === "app";
45954
+ const hasLegend = !appHostedControls && hasDescriptions && !!renderOptions?.onToggleDescriptions;
45551
45955
  const showTitle = !!parsed.title && parsed.options["no-title"] !== "on";
45552
45956
  const legendOffset = hasLegend ? sLegendHeight : 0;
45553
45957
  const layoutHeight = height - (showTitle ? sTitleAreaHeight : 0) - legendOffset;
@@ -45584,7 +45988,10 @@ function renderCycle(container, parsed, palette, isDark, onClickItem, exportDims
45584
45988
  groups: [],
45585
45989
  position: { placement: "top-center", titleRelation: "below-title" },
45586
45990
  mode: renderOptions?.exportMode ? "export" : "preview",
45587
- controlsGroup
45991
+ controlsGroup,
45992
+ ...renderOptions?.controlsHost !== void 0 && {
45993
+ controlsHost: renderOptions.controlsHost
45994
+ }
45588
45995
  };
45589
45996
  const legendState = {
45590
45997
  activeGroup: null,
@@ -45855,6 +46262,29 @@ function featureIndex(topo) {
45855
46262
  }
45856
46263
  return idx;
45857
46264
  }
46265
+ function buildAdjacency(topo) {
46266
+ const cached = adjacencyCache.get(topo);
46267
+ if (cached) return cached;
46268
+ const geometries = geomObject(topo).geometries;
46269
+ const nb = (0, import_topojson_client.neighbors)(geometries);
46270
+ const sets = /* @__PURE__ */ new Map();
46271
+ geometries.forEach((g, i) => {
46272
+ if (!g.type || g.type === "null") return;
46273
+ let set = sets.get(g.id);
46274
+ if (!set) {
46275
+ set = /* @__PURE__ */ new Set();
46276
+ sets.set(g.id, set);
46277
+ }
46278
+ for (const j of nb[i] ?? []) {
46279
+ const nid = geometries[j]?.id;
46280
+ if (nid && nid !== g.id) set.add(nid);
46281
+ }
46282
+ });
46283
+ const out = /* @__PURE__ */ new Map();
46284
+ for (const [iso, set] of sets) out.set(iso, [...set].sort());
46285
+ adjacencyCache.set(topo, out);
46286
+ return out;
46287
+ }
45858
46288
  function decodeFeatures(topo) {
45859
46289
  return geomObject(topo).geometries.map((g) => {
45860
46290
  const f = (0, import_topojson_client.feature)(topo, g);
@@ -46050,13 +46480,14 @@ function unionLongitudes(lons) {
46050
46480
  }
46051
46481
  return { west: pts[gapIdx], east: pts[gapIdx - 1] + 360 };
46052
46482
  }
46053
- var import_topojson_client, import_d3_geo, fold, EDGE_EPS, DETACH_GAP_DEG, DETACH_AREA_FRAC;
46483
+ var import_topojson_client, import_d3_geo, fold, adjacencyCache, EDGE_EPS, DETACH_GAP_DEG, DETACH_AREA_FRAC;
46054
46484
  var init_geo = __esm({
46055
46485
  "src/map/geo.ts"() {
46056
46486
  "use strict";
46057
46487
  import_topojson_client = require("topojson-client");
46058
46488
  import_d3_geo = require("d3-geo");
46059
46489
  fold = (s) => s.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLowerCase().trim();
46490
+ adjacencyCache = /* @__PURE__ */ new WeakMap();
46060
46491
  EDGE_EPS = 1e-9;
46061
46492
  DETACH_GAP_DEG = 10;
46062
46493
  DETACH_AREA_FRAC = 0.25;
@@ -46077,6 +46508,12 @@ function looksUS(lat, lon) {
46077
46508
  if (lat < 15 || lat > 72) return false;
46078
46509
  return lon >= -180 && lon <= -64 || lon >= 172;
46079
46510
  }
46511
+ function looksNorthAmericaNeighbor(lat, lon) {
46512
+ return lat >= 14 && lat <= 72 && lon >= -141 && lon <= -52;
46513
+ }
46514
+ function isWholeSphere(bb) {
46515
+ return bb[0][0] <= -179 && bb[1][0] >= 179 && bb[0][1] <= -89 && bb[1][1] >= 89;
46516
+ }
46080
46517
  function resolveMap(parsed, data) {
46081
46518
  const diagnostics = [...parsed.diagnostics];
46082
46519
  const err = (line12, message, code) => {
@@ -46087,9 +46524,6 @@ function resolveMap(parsed, data) {
46087
46524
  };
46088
46525
  const result = {
46089
46526
  title: parsed.title,
46090
- ...parsed.directives.subtitle !== void 0 && {
46091
- subtitle: parsed.directives.subtitle
46092
- },
46093
46527
  ...parsed.directives.caption !== void 0 && {
46094
46528
  caption: parsed.directives.caption
46095
46529
  },
@@ -46099,7 +46533,7 @@ function resolveMap(parsed, data) {
46099
46533
  // renderer's job (step 4) — the resolver only carries `tags` + `tagGroups`
46100
46534
  // through; it never resolves a tag value to a palette color (#10).
46101
46535
  directives: { ...parsed.directives },
46102
- basemaps: { world: "coarse", subdivisions: [] },
46536
+ basemaps: { world: "detail", subdivisions: [] },
46103
46537
  regions: [],
46104
46538
  pois: [],
46105
46539
  edges: [],
@@ -46108,7 +46542,8 @@ function resolveMap(parsed, data) {
46108
46542
  [-180, -85],
46109
46543
  [180, 85]
46110
46544
  ],
46111
- projection: "natural-earth",
46545
+ projection: "equirectangular",
46546
+ poiFrameContainers: [],
46112
46547
  diagnostics,
46113
46548
  error: parsed.error
46114
46549
  };
@@ -46118,7 +46553,10 @@ function resolveMap(parsed, data) {
46118
46553
  ...[...countryIndex.values()].map((v) => v.name),
46119
46554
  ...[...usStateIndex.values()].map((v) => v.name)
46120
46555
  ];
46121
- const usScoped = parsed.directives.region === "us-states" || parsed.directives.defaultCountry?.toUpperCase() === "US" || parsed.regions.some((r) => {
46556
+ const localeRaw = parsed.directives.locale?.toUpperCase();
46557
+ const localeCountry = localeRaw ? localeRaw.split("-")[0] : void 0;
46558
+ const localeSubdivision = localeRaw && /^[A-Z]{2}-/.test(localeRaw) ? localeRaw : void 0;
46559
+ const usScoped = localeCountry === "US" || parsed.regions.some((r) => {
46122
46560
  const f = fold(r.name);
46123
46561
  return usStateIndex.has(f) && !countryIndex.has(f);
46124
46562
  }) || parsed.regions.some(
@@ -46269,7 +46707,7 @@ function resolveMap(parsed, data) {
46269
46707
  if (!scope)
46270
46708
  warn(
46271
46709
  line12,
46272
- `"${name}" is ambiguous \u2014 resolved to the most-populous match.`,
46710
+ `"${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
46711
  "W_MAP_AMBIGUOUS_NAME"
46274
46712
  );
46275
46713
  }
@@ -46282,17 +46720,21 @@ function resolveMap(parsed, data) {
46282
46720
  return fold(pos.name);
46283
46721
  };
46284
46722
  const poiCountries = [];
46285
- let anyNonUsPoi = false;
46723
+ let anyUsPoi = false;
46724
+ let anyNonNaPoi = false;
46286
46725
  const noteCountry = (iso) => {
46287
46726
  if (iso) {
46288
46727
  poiCountries.push(iso);
46289
- if (iso !== "US") anyNonUsPoi = true;
46728
+ if (iso === "US") anyUsPoi = true;
46729
+ if (iso !== "US" && iso !== "CA" && iso !== "MX") anyNonNaPoi = true;
46290
46730
  }
46291
46731
  };
46292
46732
  const deferred = [];
46293
46733
  for (const p of parsed.pois) {
46294
46734
  if (p.pos.kind === "coords") {
46295
- if (!looksUS(p.pos.lat, p.pos.lon)) anyNonUsPoi = true;
46735
+ if (looksUS(p.pos.lat, p.pos.lon)) anyUsPoi = true;
46736
+ else if (!looksNorthAmericaNeighbor(p.pos.lat, p.pos.lon))
46737
+ anyNonNaPoi = true;
46296
46738
  addResolvedPoi(p.pos.lat, p.pos.lon, p);
46297
46739
  continue;
46298
46740
  }
@@ -46310,14 +46752,15 @@ function resolveMap(parsed, data) {
46310
46752
  deferred.push(p);
46311
46753
  }
46312
46754
  }
46313
- const inferredCountry = parsed.directives.defaultCountry?.toUpperCase() ?? mostCommonCountry(regions, poiCountries) ?? void 0;
46755
+ const inferredCountry = localeCountry ?? mostCommonCountry(regions, poiCountries) ?? void 0;
46756
+ const inferredScope = localeSubdivision ?? inferredCountry;
46314
46757
  for (const p of deferred) {
46315
46758
  if (p.pos.kind !== "name") continue;
46316
46759
  const got = lookupName(
46317
46760
  p.pos.name,
46318
46761
  p.pos.scope,
46319
46762
  p.lineNumber,
46320
- inferredCountry,
46763
+ inferredScope,
46321
46764
  true
46322
46765
  );
46323
46766
  if (got.kind === "ok") {
@@ -46387,7 +46830,8 @@ function resolveMap(parsed, data) {
46387
46830
  const meta = sizeValue !== void 0 ? { value: sizeValue } : {};
46388
46831
  if (pos.kind === "coords") {
46389
46832
  const id = alias ? fold(alias) : `@${pos.lat},${pos.lon}`;
46390
- if (!looksUS(pos.lat, pos.lon)) anyNonUsPoi = true;
46833
+ if (looksUS(pos.lat, pos.lon)) anyUsPoi = true;
46834
+ else if (!looksNorthAmericaNeighbor(pos.lat, pos.lon)) anyNonNaPoi = true;
46391
46835
  if (!registry.has(id)) {
46392
46836
  registerPoi(
46393
46837
  id,
@@ -46410,7 +46854,7 @@ function resolveMap(parsed, data) {
46410
46854
  if (registry.has(f)) return f;
46411
46855
  const aliased = declaredByName.get(f);
46412
46856
  if (aliased) return aliased;
46413
- const got = lookupName(pos.name, pos.scope, line12, inferredCountry, true);
46857
+ const got = lookupName(pos.name, pos.scope, line12, inferredScope, true);
46414
46858
  if (got.kind !== "ok") return null;
46415
46859
  noteCountry(got.iso);
46416
46860
  registerPoi(
@@ -46467,9 +46911,12 @@ function resolveMap(parsed, data) {
46467
46911
  }
46468
46912
  routes.push({ stopIds, legs, lineNumber: rt.lineNumber });
46469
46913
  }
46914
+ const hasUsContent = usSubdivisionReferenced || anyUsPoi || localeCountry === "US";
46915
+ const usOriented = !anyNonNaPoi && !regions.some(
46916
+ (r) => r.layer === "country" && !["US", "CA", "MX"].includes(r.iso)
46917
+ ) && hasUsContent;
46470
46918
  const subdivisions = [];
46471
- if (usSubdivisionReferenced || parsed.directives.region === "us-states")
46472
- subdivisions.push("us-states");
46919
+ if (usSubdivisionReferenced || usOriented) subdivisions.push("us-states");
46473
46920
  const regionBoxes = [];
46474
46921
  for (const ref of referencedRegionIds) {
46475
46922
  const bb = featureBbox(data.usStates, ref.id);
@@ -46487,17 +46934,51 @@ function resolveMap(parsed, data) {
46487
46934
  [-180, -85],
46488
46935
  [180, 85]
46489
46936
  ];
46490
- let extent2 = unioned ? pad(unioned, PAD_FRACTION) : DEFAULT_EXTENT;
46937
+ const basePad = regions.length > 0 ? REGION_PAD_FRACTION : PAD_FRACTION;
46938
+ let extent2 = unioned ? pad(unioned, basePad) : DEFAULT_EXTENT;
46939
+ const isPoiOnly = pois.length > 0 && regions.length === 0;
46940
+ const containerRegionIds = [];
46941
+ if (isPoiOnly) {
46942
+ const countries = decodeFeatures(data.worldDetail);
46943
+ const states = decodeFeatures(data.usStates);
46944
+ const seen = /* @__PURE__ */ new Set();
46945
+ const containerBoxes = [];
46946
+ for (const p of pois) {
46947
+ const { country, state } = regionAt([p.lon, p.lat], countries, states);
46948
+ const id = state?.iso ?? country?.iso;
46949
+ if (!id || seen.has(id)) continue;
46950
+ seen.add(id);
46951
+ containerRegionIds.push(id);
46952
+ const bb = state ? featureBbox(data.usStates, id) : featureBboxPrimary(data.worldCoarse, id);
46953
+ if (bb && !isWholeSphere(bb)) containerBoxes.push(bb);
46954
+ }
46955
+ const containerUnion = unionExtent(containerBoxes, points);
46956
+ if (containerUnion) extent2 = pad(containerUnion, PAD_FRACTION);
46957
+ }
46958
+ if (isPoiOnly) {
46959
+ const cx = (extent2[0][0] + extent2[1][0]) / 2;
46960
+ const cy = (extent2[0][1] + extent2[1][1]) / 2;
46961
+ const lon = extent2[1][0] - extent2[0][0];
46962
+ const lat = extent2[1][1] - extent2[0][1];
46963
+ const longer = Math.max(lon, lat);
46964
+ if (longer > 0 && longer < POI_ZOOM_FLOOR_DEG) {
46965
+ const k = POI_ZOOM_FLOOR_DEG / longer;
46966
+ const halfLon = lon * k / 2;
46967
+ const halfLat = lat * k / 2;
46968
+ extent2 = [
46969
+ [cx - halfLon, cy - halfLat],
46970
+ [cx + halfLon, cy + halfLat]
46971
+ ];
46972
+ }
46973
+ }
46491
46974
  const lonSpan = extent2[1][0] - extent2[0][0];
46492
46975
  const latSpan = extent2[1][1] - extent2[0][1];
46493
46976
  const span = Math.max(lonSpan, latSpan);
46494
46977
  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
46978
  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) {
46979
+ if (isPoiOnly && usOriented && lonSpan < US_NATIONAL_LON_SPAN) {
46980
+ projection = "mercator";
46981
+ } else if (usOriented) {
46501
46982
  projection = "albers-usa";
46502
46983
  } else if (span > WORLD_SPAN || maxAbsLat > MERCATOR_MAX_LAT) {
46503
46984
  projection = "equirectangular";
@@ -46515,11 +46996,20 @@ function resolveMap(parsed, data) {
46515
46996
  result.edges = edges;
46516
46997
  result.routes = routes;
46517
46998
  result.basemaps = {
46518
- world: span > WORLD_SPAN ? "coarse" : "detail",
46999
+ // Tier is intentionally pinned to detail (50m) at ALL scales. Diagrammo maps
47000
+ // are presentational (palette tints, relief hachures, POI hubs), not
47001
+ // survey-grade — recognizability > generalization: 110m coarse drops the
47002
+ // Italian boot to a stump at world scale. `WORLD_SPAN` lives on only for the
47003
+ // projection decision (the `usOriented`/`span > WORLD_SPAN` chain above); it
47004
+ // no longer gates basemap resolution.
47005
+ // `worldCoarse` is still loaded — it's the authoritative name/bbox index
47006
+ // (featureIndex, featureBboxPrimary), not dead code.
47007
+ world: "detail",
46519
47008
  subdivisions
46520
47009
  };
46521
47010
  result.extent = extent2;
46522
47011
  result.projection = projection;
47012
+ result.poiFrameContainers = containerRegionIds;
46523
47013
  result.error = parsed.error ?? firstError(diagnostics);
46524
47014
  return result;
46525
47015
  }
@@ -46556,7 +47046,7 @@ function firstError(diags) {
46556
47046
  const e = diags.find((d) => d.severity === "error");
46557
47047
  return e ? formatDgmoError(e) : null;
46558
47048
  }
46559
- var WORLD_SPAN, MERCATOR_MAX_LAT, PAD_FRACTION, WORLD_LAT_SOUTH, WORLD_LAT_NORTH, REGION_ALIASES, US_STATE_POSTAL;
47049
+ 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
47050
  var init_resolver2 = __esm({
46561
47051
  "src/map/resolver.ts"() {
46562
47052
  "use strict";
@@ -46565,8 +47055,11 @@ var init_resolver2 = __esm({
46565
47055
  WORLD_SPAN = 90;
46566
47056
  MERCATOR_MAX_LAT = 80;
46567
47057
  PAD_FRACTION = 0.05;
47058
+ REGION_PAD_FRACTION = 0.12;
46568
47059
  WORLD_LAT_SOUTH = -58;
46569
47060
  WORLD_LAT_NORTH = 78;
47061
+ POI_ZOOM_FLOOR_DEG = 7;
47062
+ US_NATIONAL_LON_SPAN = 48;
46570
47063
  REGION_ALIASES = {
46571
47064
  // Common everyday names → the Natural-Earth display name actually shipped.
46572
47065
  "united states": "united states of america",
@@ -46644,17 +47137,305 @@ var init_resolver2 = __esm({
46644
47137
  }
46645
47138
  });
46646
47139
 
47140
+ // src/map/colorize.ts
47141
+ function assignColors(isos, adjacency) {
47142
+ const sorted = [...isos].sort();
47143
+ const byIso = /* @__PURE__ */ new Map();
47144
+ let maxIndex = -1;
47145
+ for (const iso of sorted) {
47146
+ const taken = /* @__PURE__ */ new Set();
47147
+ for (const n of adjacency.get(iso) ?? []) {
47148
+ const c = byIso.get(n);
47149
+ if (c !== void 0) taken.add(c);
47150
+ }
47151
+ let h = 0;
47152
+ while (taken.has(h)) h++;
47153
+ byIso.set(iso, h);
47154
+ if (h > maxIndex) maxIndex = h;
47155
+ }
47156
+ return { byIso, huesNeeded: maxIndex + 1 };
47157
+ }
47158
+ var init_colorize = __esm({
47159
+ "src/map/colorize.ts"() {
47160
+ "use strict";
47161
+ }
47162
+ });
47163
+
47164
+ // src/map/context-labels.ts
47165
+ function tierBand(maxSpanDeg) {
47166
+ if (maxSpanDeg >= 90) return "world";
47167
+ if (maxSpanDeg >= 20) return "continental";
47168
+ if (maxSpanDeg >= 5) return "regional";
47169
+ return "local";
47170
+ }
47171
+ function labelBudget(width, height, band) {
47172
+ const bandCap = {
47173
+ world: 6,
47174
+ continental: 5,
47175
+ regional: 4,
47176
+ local: 3
47177
+ };
47178
+ const area2 = Math.floor(Math.sqrt(Math.max(0, width * height)) / 150);
47179
+ return Math.max(0, Math.min(area2, bandCap[band]));
47180
+ }
47181
+ function waterEligible(tier, kind, band) {
47182
+ switch (band) {
47183
+ case "world":
47184
+ return tier <= 1 && (kind === "ocean" || kind === "sea");
47185
+ case "continental":
47186
+ return tier <= 2;
47187
+ case "regional":
47188
+ return tier <= 3;
47189
+ case "local":
47190
+ return tier <= 4;
47191
+ }
47192
+ }
47193
+ function insideViewport(p, width, height) {
47194
+ return !!p && Number.isFinite(p[0]) && Number.isFinite(p[1]) && p[0] >= 0 && p[0] <= width && p[1] >= 0 && p[1] <= height;
47195
+ }
47196
+ function labelWidth(text, letterSpacing) {
47197
+ const spacing = letterSpacing > 0 ? Math.max(0, text.length - 1) * letterSpacing : 0;
47198
+ return measureLegendText(text, FONT) + spacing + 2 * PADX;
47199
+ }
47200
+ function wrapLabel2(text, letterSpacing) {
47201
+ const words = text.split(/\s+/).filter(Boolean);
47202
+ if (words.length <= 1) return [text];
47203
+ const maxLines = words.length >= 4 ? 3 : 2;
47204
+ const n = words.length;
47205
+ let best = null;
47206
+ for (let mask = 0; mask < 1 << n - 1; mask++) {
47207
+ const lines = [];
47208
+ let cur = [words[0]];
47209
+ for (let i = 1; i < n; i++) {
47210
+ if (mask & 1 << i - 1) {
47211
+ lines.push(cur.join(" "));
47212
+ cur = [words[i]];
47213
+ } else cur.push(words[i]);
47214
+ }
47215
+ lines.push(cur.join(" "));
47216
+ if (lines.length > maxLines) continue;
47217
+ const cost = Math.round(
47218
+ Math.max(...lines.map((l) => labelWidth(l, letterSpacing)))
47219
+ );
47220
+ const head = labelWidth(lines[0], letterSpacing);
47221
+ if (!best || cost < best.cost || cost === best.cost && lines.length < best.lines.length || cost === best.cost && lines.length === best.lines.length && head > best.head)
47222
+ best = { lines, cost, head };
47223
+ }
47224
+ return best?.lines ?? [text];
47225
+ }
47226
+ function rectAround(cx, cy, lines, letterSpacing) {
47227
+ const w = Math.max(...lines.map((l) => labelWidth(l, letterSpacing)));
47228
+ const h = (lines.length - 1) * LINE_HEIGHT + FONT + 2 * PADY;
47229
+ return { x: cx - w / 2, y: cy - h / 2, w, h };
47230
+ }
47231
+ function rectFits(r, width, height) {
47232
+ return r.x >= 0 && r.y >= 0 && r.x + r.w <= width && r.y + r.h <= height;
47233
+ }
47234
+ function overlapsPadded(a, b, pad2) {
47235
+ 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;
47236
+ }
47237
+ function placeContextLabels(args) {
47238
+ const {
47239
+ projection,
47240
+ dLonSpan,
47241
+ dLatSpan,
47242
+ width,
47243
+ height,
47244
+ waterBodies,
47245
+ countries,
47246
+ palette,
47247
+ project,
47248
+ collides,
47249
+ overLand
47250
+ } = args;
47251
+ void projection;
47252
+ const band = tierBand(Math.max(dLonSpan, dLatSpan));
47253
+ const budget = labelBudget(width, height, band);
47254
+ if (budget <= 0) return [];
47255
+ const waterColor = mix(palette.colors.blue, palette.textMuted, 50);
47256
+ const countryColor = palette.textMuted;
47257
+ const haloColor = palette.bg;
47258
+ const candidates = [];
47259
+ const center = [width / 2, height / 2];
47260
+ for (const e of waterBodies?.entries ?? []) {
47261
+ const [lat, lon, name, tier, kind, alt] = e;
47262
+ if (!waterEligible(tier, kind, band)) continue;
47263
+ const wlines = wrapLabel2(name, WATER_LETTER_SPACING);
47264
+ const anchorsLngLat = [[lon, lat]];
47265
+ for (const a of alt ?? []) anchorsLngLat.push([a[1], a[0]]);
47266
+ let best = null;
47267
+ let bestD = Infinity;
47268
+ let nearestProj = null;
47269
+ let nearestProjD = Infinity;
47270
+ for (const [aLon, aLat] of anchorsLngLat) {
47271
+ const p = project(aLon, aLat);
47272
+ if (!p || !Number.isFinite(p[0]) || !Number.isFinite(p[1])) continue;
47273
+ const d = (p[0] - center[0]) ** 2 + (p[1] - center[1]) ** 2;
47274
+ if (d < nearestProjD) {
47275
+ nearestProjD = d;
47276
+ nearestProj = p;
47277
+ }
47278
+ if (!insideViewport(p, width, height)) continue;
47279
+ if (d < bestD) {
47280
+ bestD = d;
47281
+ best = p;
47282
+ }
47283
+ }
47284
+ if (!best && tier === 0 && nearestProj) {
47285
+ const overX = Math.max(0, -nearestProj[0], nearestProj[0] - width);
47286
+ const overY = Math.max(0, -nearestProj[1], nearestProj[1] - height);
47287
+ if (overX <= width * EDGE_CLAMP_OVERSHOOT && overY <= height * EDGE_CLAMP_OVERSHOOT) {
47288
+ const halfW = Math.max(...wlines.map((l) => labelWidth(l, WATER_LETTER_SPACING))) / 2;
47289
+ const halfH = ((wlines.length - 1) * LINE_HEIGHT + FONT + 2 * PADY) / 2;
47290
+ const m = EDGE_CLAMP_MARGIN;
47291
+ best = [
47292
+ Math.min(Math.max(nearestProj[0], halfW + m), width - halfW - m),
47293
+ Math.min(Math.max(nearestProj[1], halfH + m), height - halfH - m)
47294
+ ];
47295
+ }
47296
+ }
47297
+ if (!best) continue;
47298
+ candidates.push({
47299
+ text: name,
47300
+ lines: wlines,
47301
+ cx: best[0],
47302
+ cy: best[1],
47303
+ italic: true,
47304
+ letterSpacing: WATER_LETTER_SPACING,
47305
+ color: waterColor,
47306
+ // Water before any country (×1000), then by tier, then kind, then name.
47307
+ sort: tier * 10 + KIND_ORDER[kind]
47308
+ });
47309
+ }
47310
+ const ranked = countries.map((c) => {
47311
+ const [x0, y0, x1, y1] = c.bbox;
47312
+ const w = x1 - x0;
47313
+ const h = y1 - y0;
47314
+ return { c, w, h, area: w * h };
47315
+ }).filter((r) => Number.isFinite(r.area) && r.area > 0).sort((a, b) => b.area - a.area);
47316
+ let ci = 0;
47317
+ for (const r of ranked) {
47318
+ const { c, w, h } = r;
47319
+ if (w > width * 0.66 || h > height * 0.66) continue;
47320
+ if (!insideViewport(c.anchor, width, height)) continue;
47321
+ const text = c.name;
47322
+ const tw = labelWidth(text, 0);
47323
+ if (tw > w || FONT + 2 * PADY > h) continue;
47324
+ candidates.push({
47325
+ text,
47326
+ lines: [text],
47327
+ cx: c.anchor[0],
47328
+ cy: c.anchor[1],
47329
+ italic: false,
47330
+ letterSpacing: 0,
47331
+ color: countryColor,
47332
+ // Always after every water body (+1e6); larger area = earlier.
47333
+ sort: 1e6 + ci++
47334
+ });
47335
+ }
47336
+ candidates.sort((a, b) => a.sort - b.sort);
47337
+ const placed = [];
47338
+ const placedRects = [];
47339
+ for (const cand of candidates) {
47340
+ if (placed.length >= budget) break;
47341
+ const rect = rectAround(cand.cx, cand.cy, cand.lines, cand.letterSpacing);
47342
+ if (!rectFits(rect, width, height)) continue;
47343
+ if (cand.italic && overLand) {
47344
+ const inset = 2;
47345
+ const top = cand.cy - (cand.lines.length - 1) / 2 * LINE_HEIGHT;
47346
+ const touchesLand = cand.lines.some((line12, li) => {
47347
+ const lw = labelWidth(line12, cand.letterSpacing);
47348
+ const x0 = cand.cx - lw / 2 + inset;
47349
+ const x1 = cand.cx + lw / 2 - inset;
47350
+ const xs = [x0, (x0 + cand.cx) / 2, cand.cx, (cand.cx + x1) / 2, x1];
47351
+ const base = top + li * LINE_HEIGHT;
47352
+ return [base, base - FONT * 0.4, base - FONT * 0.8].some(
47353
+ (y) => xs.some((x) => overLand(x, y))
47354
+ );
47355
+ });
47356
+ if (touchesLand) continue;
47357
+ }
47358
+ if (collides(rect)) continue;
47359
+ if (placedRects.some((r) => overlapsPadded(rect, r, CONTEXT_PAD))) continue;
47360
+ placedRects.push(rect);
47361
+ placed.push({
47362
+ x: cand.cx,
47363
+ y: cand.cy,
47364
+ text: cand.text,
47365
+ anchor: "middle",
47366
+ color: cand.color,
47367
+ // No halo: the bg-coloured outline reads as a ghost box behind the text
47368
+ // over the tinted water/land. Context labels are muted enough to sit
47369
+ // cleanly on the basemap without one.
47370
+ halo: false,
47371
+ haloColor,
47372
+ italic: cand.italic,
47373
+ letterSpacing: cand.letterSpacing,
47374
+ ...cand.lines.length > 1 ? { lines: cand.lines } : {},
47375
+ lineNumber: 0
47376
+ });
47377
+ }
47378
+ return placed;
47379
+ }
47380
+ var FONT, LINE_HEIGHT, PADX, PADY, WATER_LETTER_SPACING, CONTEXT_PAD, EDGE_CLAMP_MARGIN, EDGE_CLAMP_OVERSHOOT, KIND_ORDER;
47381
+ var init_context_labels = __esm({
47382
+ "src/map/context-labels.ts"() {
47383
+ "use strict";
47384
+ init_color_utils();
47385
+ init_legend_constants();
47386
+ FONT = 11;
47387
+ LINE_HEIGHT = FONT + 2;
47388
+ PADX = 4;
47389
+ PADY = 3;
47390
+ WATER_LETTER_SPACING = 1.5;
47391
+ CONTEXT_PAD = 4;
47392
+ EDGE_CLAMP_MARGIN = 8;
47393
+ EDGE_CLAMP_OVERSHOOT = 0.35;
47394
+ KIND_ORDER = {
47395
+ ocean: 0,
47396
+ sea: 1,
47397
+ gulf: 2,
47398
+ bay: 3,
47399
+ strait: 4,
47400
+ channel: 5,
47401
+ sound: 6
47402
+ };
47403
+ }
47404
+ });
47405
+
46647
47406
  // src/map/layout.ts
46648
47407
  function geomObject2(topo) {
46649
47408
  const key = Object.keys(topo.objects)[0];
46650
47409
  return topo.objects[key];
46651
47410
  }
47411
+ function mergeFeatures(a, b) {
47412
+ const polysOf = (f) => {
47413
+ const g = f.geometry;
47414
+ if (!g) return null;
47415
+ if (g.type === "Polygon") return [g.coordinates];
47416
+ if (g.type === "MultiPolygon") return g.coordinates;
47417
+ return null;
47418
+ };
47419
+ const pa = polysOf(a);
47420
+ const pb = polysOf(b);
47421
+ if (!pa || !pb) return a;
47422
+ return {
47423
+ ...a,
47424
+ geometry: { type: "MultiPolygon", coordinates: [...pa, ...pb] }
47425
+ };
47426
+ }
46652
47427
  function decodeLayer(topo) {
47428
+ const cached = decodeCache.get(topo);
47429
+ if (cached) return cached;
46653
47430
  const out = /* @__PURE__ */ new Map();
46654
47431
  for (const g of geomObject2(topo).geometries) {
46655
47432
  const f = (0, import_topojson_client2.feature)(topo, g);
46656
- out.set(g.id, { ...f, id: g.id });
47433
+ if (!f.geometry) continue;
47434
+ const tagged = { ...f, id: g.id };
47435
+ const existing = out.get(g.id);
47436
+ out.set(g.id, existing ? mergeFeatures(existing, tagged) : tagged);
46657
47437
  }
47438
+ decodeCache.set(topo, out);
46658
47439
  return out;
46659
47440
  }
46660
47441
  function projectionFor(family) {
@@ -46663,9 +47444,12 @@ function projectionFor(family) {
46663
47444
  return usConusProjection();
46664
47445
  case "mercator":
46665
47446
  return (0, import_d3_geo2.geoMercator)();
47447
+ case "equal-earth":
47448
+ return (0, import_d3_geo2.geoEqualEarth)();
47449
+ case "equirectangular":
47450
+ return (0, import_d3_geo2.geoEquirectangular)();
46666
47451
  case "natural-earth":
46667
47452
  return (0, import_d3_geo2.geoNaturalEarth1)();
46668
- case "equirectangular":
46669
47453
  default:
46670
47454
  return (0, import_d3_geo2.geoEquirectangular)();
46671
47455
  }
@@ -46684,13 +47468,11 @@ function mapNeutralLandColor(palette, isDark, _dataActive = false) {
46684
47468
  isDark ? LAND_TINT_DARK : LAND_TINT_LIGHT
46685
47469
  );
46686
47470
  }
46687
- function layoutMap(resolved, data, size, opts) {
46688
- const { palette, isDark } = opts;
46689
- const { width, height } = size;
47471
+ function buildMapProjection(resolved, data) {
46690
47472
  const wantsUsStates = resolved.basemaps.subdivisions.includes("us-states");
46691
- const usCrisp = resolved.projection === "albers-usa" && wantsUsStates && !!data.naLand;
47473
+ const usCrisp = (resolved.projection === "albers-usa" || resolved.projection === "mercator") && wantsUsStates && !!data.naLand;
46692
47474
  const worldTopo = usCrisp ? data.worldDetail : resolved.basemaps.world === "detail" ? data.worldDetail : data.worldCoarse;
46693
- const worldLayer = decodeLayer(worldTopo);
47475
+ const worldLayer = new Map(decodeLayer(worldTopo));
46694
47476
  if (usCrisp && data.naLand) {
46695
47477
  const [nbW, nbS, nbE, nbN] = [-140, 10, -52, 66];
46696
47478
  const crisp = decodeLayer(data.naLand);
@@ -46699,16 +47481,109 @@ function layoutMap(resolved, data, size, opts) {
46699
47481
  if (!base) continue;
46700
47482
  const [[bw, bs], [be, bn]] = (0, import_d3_geo2.geoBounds)(base);
46701
47483
  if (bw >= nbW && be <= nbE && bs >= nbS && bn <= nbN)
46702
- worldLayer.set(iso, cf);
47484
+ worldLayer.set(iso, { ...cf, properties: base.properties });
46703
47485
  }
46704
47486
  }
46705
47487
  const usLayer = wantsUsStates ? decodeLayer(data.usStates) : null;
47488
+ const extentOutline = () => {
47489
+ const [[w, s], [e, n]] = resolved.extent;
47490
+ const N = 16;
47491
+ const coords = [];
47492
+ for (let i = 0; i <= N; i++) {
47493
+ const t = i / N;
47494
+ const lon = w + (e - w) * t;
47495
+ const lat = s + (n - s) * t;
47496
+ coords.push([lon, s], [lon, n], [w, lat], [e, lat]);
47497
+ }
47498
+ return {
47499
+ type: "Feature",
47500
+ properties: {},
47501
+ geometry: { type: "MultiPoint", coordinates: coords }
47502
+ };
47503
+ };
47504
+ let fitFeatures;
47505
+ if (resolved.projection === "albers-usa" && usLayer) {
47506
+ fitFeatures = [...usLayer.entries()].filter(([iso]) => !US_NON_CONUS.has(iso)).map(([, f]) => f);
47507
+ const neighborPoints = resolved.pois.filter((p) => !inAlaska(p.lon, p.lat) && !inHawaii(p.lon, p.lat)).map((p) => [p.lon, p.lat]);
47508
+ if (neighborPoints.length > 0) {
47509
+ fitFeatures.push({
47510
+ type: "Feature",
47511
+ properties: {},
47512
+ geometry: { type: "MultiPoint", coordinates: neighborPoints }
47513
+ });
47514
+ }
47515
+ for (const r of resolved.regions) {
47516
+ if (r.layer === "country" && (r.iso === "CA" || r.iso === "MX")) {
47517
+ const cf = worldLayer.get(r.iso);
47518
+ if (cf) fitFeatures.push(cf);
47519
+ }
47520
+ }
47521
+ } else {
47522
+ fitFeatures = [extentOutline()];
47523
+ }
47524
+ const fitTarget = { type: "FeatureCollection", features: fitFeatures };
47525
+ const projection = projectionFor(resolved.projection);
47526
+ if (resolved.projection !== "albers-usa") {
47527
+ let centerLon = (resolved.extent[0][0] + resolved.extent[1][0]) / 2;
47528
+ if (centerLon > 180) centerLon -= 360;
47529
+ projection.rotate([-centerLon, 0]);
47530
+ }
47531
+ const fitGB = (0, import_d3_geo2.geoBounds)(fitTarget);
47532
+ const fitIsGlobal = fitGB[1][0] - fitGB[0][0] >= 270 || fitGB[1][1] - fitGB[0][1] >= 130;
47533
+ return {
47534
+ projection,
47535
+ fitTarget,
47536
+ fitIsGlobal,
47537
+ worldLayer,
47538
+ usLayer,
47539
+ usCrisp,
47540
+ wantsUsStates,
47541
+ worldTopo
47542
+ };
47543
+ }
47544
+ function parsePathRings(d) {
47545
+ const rings = [];
47546
+ let cur = [];
47547
+ const re = /([MLZ])([^MLZ]*)/g;
47548
+ let m;
47549
+ while (m = re.exec(d)) {
47550
+ if (m[1] === "Z") {
47551
+ if (cur.length) rings.push(cur);
47552
+ cur = [];
47553
+ continue;
47554
+ }
47555
+ if (m[1] === "M" && cur.length) {
47556
+ rings.push(cur);
47557
+ cur = [];
47558
+ }
47559
+ const nums = m[2].split(/[ ,]+/).map(Number);
47560
+ for (let i = 0; i + 1 < nums.length; i += 2) {
47561
+ const x = nums[i];
47562
+ const y = nums[i + 1];
47563
+ if (Number.isFinite(x) && Number.isFinite(y)) cur.push([x, y]);
47564
+ }
47565
+ }
47566
+ if (cur.length) rings.push(cur);
47567
+ return rings;
47568
+ }
47569
+ function layoutMap(resolved, data, size, opts) {
47570
+ const { palette, isDark } = opts;
47571
+ const { width, height } = size;
47572
+ const {
47573
+ projection,
47574
+ fitTarget,
47575
+ fitIsGlobal,
47576
+ worldLayer,
47577
+ usLayer,
47578
+ usCrisp,
47579
+ worldTopo
47580
+ } = buildMapProjection(resolved, data);
46706
47581
  const usContext = usLayer !== null;
46707
47582
  const regionStroke = isDark ? mix(palette.bg, palette.text, 78) : mix(palette.text, palette.bg, 78);
46708
47583
  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);
47584
+ const allNonNegative = values.length > 0 && values.every((v) => v >= 0);
47585
+ const rampMin = allNonNegative ? 0 : Math.min(...values);
47586
+ const rampMax = Math.max(...values);
46712
47587
  const rampHue = resolveColor(resolved.directives.regionMetricColor ?? "", palette) ?? palette.colors.red;
46713
47588
  const hasRamp = values.length > 0;
46714
47589
  const VALUE_NAME = hasRamp ? resolved.directives.regionMetric?.trim() || "Value" : null;
@@ -46729,7 +47604,7 @@ function layoutMap(resolved, data, size, opts) {
46729
47604
  activeGroup = VALUE_NAME ?? (resolved.tagGroups.length > 0 ? resolved.tagGroups[0].name : null);
46730
47605
  }
46731
47606
  const activeIsScore = VALUE_NAME !== null && activeGroup === VALUE_NAME;
46732
- const mutedBasemap = resolved.directives.basemapStyle === "muted" ? true : resolved.directives.basemapStyle === "natural" ? false : activeGroup !== null;
47607
+ const mutedBasemap = activeGroup !== null;
46733
47608
  const neutralFill = mapNeutralLandColor(palette, isDark, mutedBasemap);
46734
47609
  const water = mapBackgroundColor(palette, isDark, mutedBasemap);
46735
47610
  const lakeStroke = mix(regionStroke, water, 45);
@@ -46738,6 +47613,39 @@ function layoutMap(resolved, data, size, opts) {
46738
47613
  palette.bg,
46739
47614
  mutedBasemap ? isDark ? MUTED_FOREIGN_DARK : MUTED_FOREIGN_LIGHT : isDark ? FOREIGN_TINT_DARK : FOREIGN_TINT_LIGHT
46740
47615
  );
47616
+ const colorizeActive = resolved.directives.noColorize !== true && !hasRamp && resolved.tagGroups.length === 0;
47617
+ const colorByIso = /* @__PURE__ */ new Map();
47618
+ if (colorizeActive) {
47619
+ const adjacency = /* @__PURE__ */ new Map();
47620
+ const addEdges = (src) => {
47621
+ for (const [iso, ns] of src) {
47622
+ const cur = adjacency.get(iso);
47623
+ if (cur) cur.push(...ns);
47624
+ else adjacency.set(iso, [...ns]);
47625
+ }
47626
+ };
47627
+ addEdges(buildAdjacency(worldTopo));
47628
+ if (usLayer) {
47629
+ addEdges(buildAdjacency(data.usStates));
47630
+ for (const [country, states] of Object.entries(FOREIGN_BORDER)) {
47631
+ const cn = adjacency.get(country);
47632
+ if (!cn) continue;
47633
+ for (const st of states) {
47634
+ const sn = adjacency.get(st);
47635
+ if (!sn) continue;
47636
+ cn.push(st);
47637
+ sn.push(country);
47638
+ }
47639
+ }
47640
+ }
47641
+ const { byIso, huesNeeded } = assignColors(
47642
+ [...adjacency.keys()],
47643
+ adjacency
47644
+ );
47645
+ const tints = politicalTints(palette, huesNeeded, isDark);
47646
+ for (const [iso, idx] of byIso) colorByIso.set(iso, tints[idx]);
47647
+ }
47648
+ const colorizeStroke = (fill2) => mix(fill2, palette.text, 35);
46741
47649
  const rampBase = isDark ? mix(palette.surface, palette.text, 28) : palette.bg;
46742
47650
  const fillForValue = (s) => {
46743
47651
  const t = rampMax > rampMin ? (s - rampMin) / (rampMax - rampMin) : 1;
@@ -46773,43 +47681,15 @@ function layoutMap(resolved, data, size, opts) {
46773
47681
  if (activeIsScore) {
46774
47682
  return r.value !== void 0 ? fillForValue(r.value) : neutralFill;
46775
47683
  }
47684
+ if (colorizeActive) return (r.iso && colorByIso.get(r.iso)) ?? neutralFill;
46776
47685
  return tagFill(r.tags, activeGroup) ?? neutralFill;
46777
47686
  };
46778
47687
  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;
47688
+ const TITLE_GAP2 = 16;
46809
47689
  let topPad = FIT_PAD;
46810
47690
  if (resolved.title && resolved.pois.length > 0) {
46811
47691
  const bannerBottom = (resolved.subtitle ? TITLE_Y + TITLE_FONT_SIZE : TITLE_Y) + TITLE_FONT_SIZE / 2;
46812
- topPad = Math.max(FIT_PAD, bannerBottom + TITLE_GAP);
47692
+ topPad = Math.max(FIT_PAD, bannerBottom + TITLE_GAP2);
46813
47693
  }
46814
47694
  const fitBox = [
46815
47695
  [FIT_PAD, topPad],
@@ -46819,12 +47699,10 @@ function layoutMap(resolved, data, size, opts) {
46819
47699
  ]
46820
47700
  ];
46821
47701
  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
47702
  let path;
46825
47703
  let project;
46826
47704
  let stretchParams = null;
46827
- if (fitIsGlobal) {
47705
+ if (fitIsGlobal && !opts.preferContain) {
46828
47706
  const cb = (0, import_d3_geo2.geoPath)(projection).bounds(fitTarget);
46829
47707
  const bx0 = cb[0][0];
46830
47708
  const by0 = cb[0][1];
@@ -46866,7 +47744,9 @@ function layoutMap(resolved, data, size, opts) {
46866
47744
  const insets = [];
46867
47745
  const insetRegions = [];
46868
47746
  const insetLabelSeeds = [];
46869
- if (resolved.projection === "albers-usa" && usLayer && !resolved.directives.noInsets) {
47747
+ const akRef = resolved.regions.some((r) => r.iso === "US-AK") || resolved.pois.some((p) => inAlaska(p.lon, p.lat));
47748
+ const hiRef = resolved.regions.some((r) => r.iso === "US-HI") || resolved.pois.some((p) => inHawaii(p.lon, p.lat));
47749
+ if (resolved.projection === "albers-usa" && usLayer && (akRef || hiRef)) {
46870
47750
  const PAD = 8;
46871
47751
  const GAP = 12;
46872
47752
  const yB = height - FIT_PAD;
@@ -46931,8 +47811,18 @@ function layoutMap(resolved, data, size, opts) {
46931
47811
  );
46932
47812
  const d = (0, import_d3_geo2.geoPath)(proj)(f) ?? "";
46933
47813
  if (!d) return xr;
47814
+ let contextLand;
47815
+ if (iso === "US-AK") {
47816
+ const can = worldLayer.get("CA");
47817
+ const cd = can ? (0, import_d3_geo2.geoPath)(proj)(can) ?? "" : "";
47818
+ if (cd)
47819
+ contextLand = {
47820
+ d: cd,
47821
+ fill: colorizeActive ? colorByIso.get("CA") ?? foreignFill : foreignFill
47822
+ };
47823
+ }
46934
47824
  const r = regionById.get(iso);
46935
- let fill2 = neutralFill;
47825
+ let fill2 = colorizeActive ? colorByIso.get(iso) ?? neutralFill : neutralFill;
46936
47826
  let lineNumber = -1;
46937
47827
  if (r?.layer === "us-state") {
46938
47828
  fill2 = regionFill(r);
@@ -46951,13 +47841,14 @@ function layoutMap(resolved, data, size, opts) {
46951
47841
  ],
46952
47842
  // The FITTED inset projection (just fit to this box) — captured so the
46953
47843
  // geo-query can invert pixels inside the frame back to AK/HI coords.
46954
- projection: proj
47844
+ projection: proj,
47845
+ ...contextLand && { contextLand }
46955
47846
  });
46956
47847
  insetRegions.push({
46957
47848
  id: iso,
46958
47849
  d,
46959
47850
  fill: fill2,
46960
- stroke: regionStroke,
47851
+ stroke: colorizeActive ? colorizeStroke(fill2) : regionStroke,
46961
47852
  lineNumber,
46962
47853
  layer: "us-state",
46963
47854
  ...r?.value !== void 0 && { value: r.value },
@@ -46970,13 +47861,16 @@ function layoutMap(resolved, data, size, opts) {
46970
47861
  }
46971
47862
  return xr;
46972
47863
  };
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);
47864
+ let akRight = FIT_PAD;
47865
+ if (akRef)
47866
+ akRight = placeInset("US-AK", alaskaProjection(), FIT_PAD, width * 0.15);
47867
+ if (hiRef)
47868
+ placeInset(
47869
+ "US-HI",
47870
+ hawaiiProjection(),
47871
+ akRef ? akRight + 24 : FIT_PAD,
47872
+ width * 0.1
47873
+ );
46980
47874
  }
46981
47875
  const conusFit = resolved.projection === "albers-usa" && !!usLayer;
46982
47876
  const classifyExtent = conusFit ? (0, import_d3_geo2.geoBounds)(fitTarget) : resolved.extent;
@@ -46992,15 +47886,24 @@ function layoutMap(resolved, data, size, opts) {
46992
47886
  };
46993
47887
  const ringOverlapsView = (ring) => {
46994
47888
  let loMin = Infinity, loMax = -Infinity, rawMin = Infinity, rawMax = -Infinity;
47889
+ const lons = [];
46995
47890
  for (const [rawLon] of ring) {
46996
47891
  const lon = normLon(rawLon);
47892
+ lons.push(lon);
46997
47893
  if (lon < loMin) loMin = lon;
46998
47894
  if (lon > loMax) loMax = lon;
46999
47895
  if (rawLon < rawMin) rawMin = rawLon;
47000
47896
  if (rawLon > rawMax) rawMax = rawLon;
47001
47897
  }
47002
- if (loMax - loMin > 270) return false;
47003
- if (rawMax - rawMin > 180 && loMax - loMin < 90) return false;
47898
+ lons.sort((a, b) => a - b);
47899
+ let maxGap = 0;
47900
+ for (let i = 1; i < lons.length; i++)
47901
+ maxGap = Math.max(maxGap, lons[i] - lons[i - 1]);
47902
+ if (lons.length > 1)
47903
+ maxGap = Math.max(maxGap, lons[0] + 360 - lons[lons.length - 1]);
47904
+ const occupiedArc = 360 - maxGap;
47905
+ if (occupiedArc > 270) return false;
47906
+ if (rawMax - rawMin > 180 && occupiedArc < 90) return false;
47004
47907
  let px0 = Infinity, py0 = Infinity, px1 = -Infinity, py1 = -Infinity, anyFinite = false;
47005
47908
  for (const [lon, lat] of ring) {
47006
47909
  const p = project(lon, lat);
@@ -47073,7 +47976,7 @@ function layoutMap(resolved, data, size, opts) {
47073
47976
  const regions = [];
47074
47977
  const pushRegionLayer = (layerFeatures, layerKind, shouldCull) => {
47075
47978
  for (const [iso, f] of layerFeatures) {
47076
- if (layerKind === "us-state" && usContext && INSET_STATES.has(iso))
47979
+ if (layerKind === "us-state" && usContext && resolved.projection === "albers-usa" && INSET_STATES.has(iso))
47077
47980
  continue;
47078
47981
  if (layerKind === "country" && usContext && iso === "US") continue;
47079
47982
  if (layerKind === "country" && iso === "AQ" && !regionById.has("AQ"))
@@ -47085,7 +47988,8 @@ function layoutMap(resolved, data, size, opts) {
47085
47988
  if (!d) continue;
47086
47989
  const isThisLayer = r?.layer === layerKind;
47087
47990
  const isForeign = layerKind === "country" && usContext && iso !== "US";
47088
- let fill2 = isForeign ? foreignFill : neutralFill;
47991
+ const baseFill = isForeign ? foreignFill : neutralFill;
47992
+ let fill2 = colorizeActive ? colorByIso.get(iso) ?? baseFill : baseFill;
47089
47993
  let label;
47090
47994
  let lineNumber = -1;
47091
47995
  let layer = "base";
@@ -47094,12 +47998,14 @@ function layoutMap(resolved, data, size, opts) {
47094
47998
  lineNumber = r.lineNumber;
47095
47999
  layer = layerKind;
47096
48000
  label = r.name;
48001
+ } else {
48002
+ label = f.properties?.name;
47097
48003
  }
47098
48004
  regions.push({
47099
48005
  id: iso,
47100
48006
  d,
47101
48007
  fill: fill2,
47102
- stroke: regionStroke,
48008
+ stroke: colorizeActive ? colorizeStroke(fill2) : regionStroke,
47103
48009
  lineNumber,
47104
48010
  layer,
47105
48011
  ...label !== void 0 && { label },
@@ -47127,9 +48033,41 @@ function layoutMap(resolved, data, size, opts) {
47127
48033
  });
47128
48034
  }
47129
48035
  }
48036
+ const pointInRings = (px, py, rings) => {
48037
+ let inside = false;
48038
+ for (const ring of rings) {
48039
+ for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
48040
+ const [xi, yi] = ring[i];
48041
+ const [xj, yj] = ring[j];
48042
+ if (yi > py !== yj > py && px < (xj - xi) * (py - yi) / (yj - yi) + xi)
48043
+ inside = !inside;
48044
+ }
48045
+ }
48046
+ return inside;
48047
+ };
48048
+ const fillHitTargets = [...regions, ...insetRegions].map((r) => ({
48049
+ fill: r.fill,
48050
+ rings: parsePathRings(r.d)
48051
+ }));
48052
+ const fillAt = (x, y) => {
48053
+ let hit = water;
48054
+ for (const t of fillHitTargets)
48055
+ if (pointInRings(x, y, t.rings)) hit = t.fill;
48056
+ return hit;
48057
+ };
48058
+ const labelOnFill = (fill2) => {
48059
+ const color = contrastRatio(fill2, palette.textOnFillDark) >= contrastRatio(fill2, palette.textOnFillLight) ? palette.textOnFillDark : palette.textOnFillLight;
48060
+ const haloColor = color === palette.textOnFillLight ? palette.textOnFillDark : palette.textOnFillLight;
48061
+ return {
48062
+ color,
48063
+ halo: contrastRatio(fill2, color) < REGION_LABEL_HALO_RATIO,
48064
+ haloColor
48065
+ };
48066
+ };
48067
+ const reliefAllowed = resolved.directives.noRelief !== true;
47130
48068
  const relief = [];
47131
48069
  let reliefHatch = null;
47132
- if (resolved.directives.relief === true && data.mountainRanges) {
48070
+ if (reliefAllowed && data.mountainRanges) {
47133
48071
  for (const [, f] of decodeLayer(data.mountainRanges)) {
47134
48072
  const viewF = isGlobalView ? dropFrameFillers(f) : cullFeatureToView(f);
47135
48073
  if (!viewF) continue;
@@ -47145,16 +48083,32 @@ function layoutMap(resolved, data, size, opts) {
47145
48083
  if (relief.length) {
47146
48084
  const darkTone = isDark ? palette.bg : palette.text;
47147
48085
  const lightTone = isDark ? palette.text : palette.bg;
47148
- const landLum = relativeLuminance(neutralFill);
48086
+ const reliefLandRef = colorizeActive ? isDark ? palette.surface : palette.bg : neutralFill;
48087
+ const landLum = relativeLuminance(reliefLandRef);
47149
48088
  const tone = Math.abs(landLum - relativeLuminance(darkTone)) > 0.04 ? darkTone : lightTone;
47150
48089
  reliefHatch = {
47151
- color: mix(tone, neutralFill, RELIEF_HATCH_STRENGTH),
48090
+ color: mix(tone, reliefLandRef, RELIEF_HATCH_STRENGTH),
47152
48091
  spacing: RELIEF_HATCH_SPACING,
47153
48092
  width: RELIEF_HATCH_WIDTH
47154
48093
  };
47155
48094
  }
47156
48095
  }
47157
- const riverColor = mix(water, regionStroke, 16);
48096
+ let coastlineStyle = null;
48097
+ if (resolved.directives.noCoastline !== true) {
48098
+ const minDim = Math.min(width, height);
48099
+ coastlineStyle = {
48100
+ color: mix(regionStroke, water, COASTLINE_STROKE_MIX),
48101
+ // N equal-width rings: distance steps outward by COASTLINE_STEP; opacity
48102
+ // fades linearly from NEAR (innermost) to FAR (outermost).
48103
+ lines: Array.from({ length: COASTLINE_RING_COUNT }, (_, k) => ({
48104
+ d: (COASTLINE_D0 + k * COASTLINE_STEP) * minDim,
48105
+ thickness: COASTLINE_THICKNESS * minDim,
48106
+ opacity: COASTLINE_OPACITY_NEAR + (COASTLINE_OPACITY_FAR - COASTLINE_OPACITY_NEAR) * k / (COASTLINE_RING_COUNT - 1)
48107
+ })),
48108
+ minExtent: (isGlobalView ? COASTLINE_MIN_EXTENT_GLOBAL : COASTLINE_MIN_EXTENT) * minDim
48109
+ };
48110
+ }
48111
+ const riverColor = mix(palette.colors.blue, water, 32);
47158
48112
  const rivers = [];
47159
48113
  if (data.rivers) {
47160
48114
  for (const [, f] of decodeLayer(data.rivers)) {
@@ -47210,38 +48164,108 @@ function layoutMap(resolved, data, size, opts) {
47210
48164
  const xy = project(p.lon, p.lat);
47211
48165
  if (xy) projected.push({ p, xy });
47212
48166
  }
47213
- const coloGroups = /* @__PURE__ */ new Map();
48167
+ const placePoi = (e, cx, cy, clusterId) => {
48168
+ const { fill: fill2, stroke: stroke2 } = poiFill(e.p);
48169
+ poiScreen.set(e.p.id, { cx, cy, r: radiusFor(e.p) });
48170
+ const num = routeNumberById.get(e.p.id);
48171
+ pois.push({
48172
+ id: e.p.id,
48173
+ cx,
48174
+ cy,
48175
+ r: radiusFor(e.p),
48176
+ fill: fill2,
48177
+ stroke: stroke2,
48178
+ lineNumber: e.p.lineNumber,
48179
+ implicit: !!e.p.implicit,
48180
+ isOrigin: originIds.has(e.p.id),
48181
+ ...num !== void 0 && { routeNumber: num },
48182
+ ...Object.keys(e.p.tags).length > 0 && { tags: e.p.tags },
48183
+ ...clusterId !== void 0 && { clusterId }
48184
+ });
48185
+ };
48186
+ const clusters = [];
48187
+ const connected = /* @__PURE__ */ new Set();
48188
+ for (const e of resolved.edges) {
48189
+ connected.add(e.fromId);
48190
+ connected.add(e.toId);
48191
+ }
48192
+ for (const rt of resolved.routes) {
48193
+ rt.stopIds.forEach((id) => connected.add(id));
48194
+ }
48195
+ const radiusOf = (e) => radiusFor(e.p);
47214
48196
  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
- });
48197
+ if (connected.has(e.p.id)) placePoi(e, e.xy[0], e.xy[1]);
48198
+ }
48199
+ const groups = [];
48200
+ for (const e of projected) {
48201
+ if (connected.has(e.p.id)) continue;
48202
+ const r = radiusOf(e);
48203
+ const near = groups.find(
48204
+ (g) => g.some(
48205
+ (q) => Math.hypot(q.xy[0] - e.xy[0], q.xy[1] - e.xy[1]) < (r + radiusOf(q)) * STACK_OVERLAP
48206
+ )
48207
+ );
48208
+ if (near) near.push(e);
48209
+ else groups.push([e]);
48210
+ }
48211
+ for (const g of groups) {
48212
+ if (g.length === 1) {
48213
+ placePoi(g[0], g[0].xy[0], g[0].xy[1]);
48214
+ continue;
48215
+ }
48216
+ const clusterId = g[0].p.id;
48217
+ const cx0 = g.reduce((s, e) => s + e.xy[0], 0) / g.length;
48218
+ const cy0 = g.reduce((s, e) => s + e.xy[1], 0) / g.length;
48219
+ const maxR = Math.max(...g.map(radiusOf));
48220
+ const sep = 2 * maxR + STACK_RING_GAP;
48221
+ const ringR = Math.max(
48222
+ COLO_R,
48223
+ sep / (2 * Math.sin(Math.PI / Math.max(g.length, 2)))
48224
+ );
48225
+ const positions = g.map((e, i) => {
48226
+ if (g.length <= STACK_RING_MAX) {
48227
+ const ang2 = -Math.PI / 2 + i * 2 * Math.PI / g.length;
48228
+ return {
48229
+ e,
48230
+ mx: cx0 + Math.cos(ang2) * ringR,
48231
+ my: cy0 + Math.sin(ang2) * ringR
48232
+ };
48233
+ }
48234
+ const ang = i * GOLDEN_ANGLE;
48235
+ const rr = ringR * Math.sqrt((i + 1) / g.length);
48236
+ return { e, mx: cx0 + Math.cos(ang) * rr, my: cy0 + Math.sin(ang) * rr };
48237
+ });
48238
+ let minX = cx0 - maxR;
48239
+ let maxX = cx0 + maxR;
48240
+ let minY = cy0 - maxR;
48241
+ let maxY = cy0 + maxR;
48242
+ for (const { mx, my, e } of positions) {
48243
+ const r = radiusOf(e);
48244
+ minX = Math.min(minX, mx - r);
48245
+ maxX = Math.max(maxX, mx + r);
48246
+ minY = Math.min(minY, my - r);
48247
+ maxY = Math.max(maxY, my + r);
48248
+ }
48249
+ let dx = 0;
48250
+ let dy = 0;
48251
+ if (minX + dx < 2) dx = 2 - minX;
48252
+ if (maxX + dx > width - 2) dx = width - 2 - maxX;
48253
+ if (minY + dy < 2) dy = 2 - minY;
48254
+ if (maxY + dy > height - 2) dy = height - 2 - maxY;
48255
+ const legsOut = [];
48256
+ for (const { e, mx, my } of positions) {
48257
+ const fx = mx + dx;
48258
+ const fy = my + dy;
48259
+ placePoi(e, fx, fy, clusterId);
48260
+ legsOut.push({ x2: fx, y2: fy, color: poiFill(e.p).fill });
48261
+ }
48262
+ clusters.push({
48263
+ id: clusterId,
48264
+ cx: cx0 + dx,
48265
+ cy: cy0 + dy,
48266
+ count: g.length,
48267
+ hitR: ringR + maxR + 6,
48268
+ legs: legsOut
47245
48269
  });
47246
48270
  }
47247
48271
  const legs = [];
@@ -47291,16 +48315,26 @@ function layoutMap(resolved, data, size, opts) {
47291
48315
  if (!a || !b) continue;
47292
48316
  const mx = (a.cx + b.cx) / 2;
47293
48317
  const my = (a.cy + b.cy) / 2;
48318
+ const bow = {
48319
+ curved: leg.style === "arc",
48320
+ offset: 0,
48321
+ labelX: mx,
48322
+ labelY: my - 4
48323
+ };
48324
+ const routeLabelStyle = leg.label !== void 0 ? labelOnFill(fillAt(bow.labelX, bow.labelY)) : void 0;
47294
48325
  legs.push({
47295
- d: legPath(a, b, leg.style === "arc", 0),
48326
+ d: legPath(a, b, bow.curved, bow.offset),
47296
48327
  width: routeWidthFor(Number(leg.value)),
47297
48328
  color: mix(palette.text, palette.bg, 72),
47298
48329
  arrow: true,
47299
48330
  lineNumber: leg.lineNumber,
47300
48331
  ...leg.label !== void 0 && {
47301
48332
  label: leg.label,
47302
- labelX: mx,
47303
- labelY: my - 4
48333
+ labelX: bow.labelX,
48334
+ labelY: bow.labelY,
48335
+ labelColor: routeLabelStyle.color,
48336
+ labelHalo: routeLabelStyle.halo,
48337
+ labelHaloColor: routeLabelStyle.haloColor
47304
48338
  }
47305
48339
  });
47306
48340
  }
@@ -47328,20 +48362,29 @@ function layoutMap(resolved, data, size, opts) {
47328
48362
  const a = poiScreen.get(e.fromId);
47329
48363
  const b = poiScreen.get(e.toId);
47330
48364
  if (!a || !b) return;
47331
- const curved = e.style === "arc" || n > 1;
47332
- const offset = n > 1 ? (i - (n - 1) / 2) * FAN_STEP : 0;
48365
+ const fanOffset = n > 1 ? (i - (n - 1) / 2) * FAN_STEP : 0;
47333
48366
  const mx = (a.cx + b.cx) / 2;
47334
48367
  const my = (a.cy + b.cy) / 2;
48368
+ const bow = {
48369
+ curved: e.style === "arc" || n > 1,
48370
+ offset: fanOffset,
48371
+ labelX: mx,
48372
+ labelY: my - 4
48373
+ };
48374
+ const edgeLabelStyle = e.label !== void 0 ? labelOnFill(fillAt(bow.labelX, bow.labelY)) : void 0;
47335
48375
  legs.push({
47336
- d: legPath(a, b, curved, offset),
48376
+ d: legPath(a, b, bow.curved, bow.offset),
47337
48377
  width: widthFor(e),
47338
48378
  color: mix(palette.text, palette.bg, 66),
47339
48379
  arrow: e.directed,
47340
48380
  lineNumber: e.lineNumber,
47341
48381
  ...e.label !== void 0 && {
47342
48382
  label: e.label,
47343
- labelX: mx,
47344
- labelY: my - 4
48383
+ labelX: bow.labelX,
48384
+ labelY: bow.labelY,
48385
+ labelColor: edgeLabelStyle.color,
48386
+ labelHalo: edgeLabelStyle.halo,
48387
+ labelHaloColor: edgeLabelStyle.haloColor
47345
48388
  }
47346
48389
  });
47347
48390
  });
@@ -47383,25 +48426,25 @@ function layoutMap(resolved, data, size, opts) {
47383
48426
  }
47384
48427
  }
47385
48428
  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";
48429
+ const showRegionLabels = resolved.directives.noRegionLabels !== true;
48430
+ const isCompact = width < COMPACT_WIDTH_PX;
47387
48431
  const LABEL_PADX = 6;
47388
48432
  const LABEL_PADY = 3;
47389
- const labelW = (text) => measureLegendText(text, FONT) + 2 * LABEL_PADX;
47390
- const labelH = FONT + 2 * LABEL_PADY;
48433
+ const labelW = (text) => measureLegendText(text, FONT2) + 2 * LABEL_PADX;
48434
+ const labelH = FONT2 + 2 * LABEL_PADY;
47391
48435
  const pushRegionLabel = (x, y, text, fill2, lineNumber) => {
47392
- const color = contrastText(
47393
- fill2,
47394
- palette.textOnFillLight,
47395
- palette.textOnFillDark
48436
+ const { color, haloColor } = labelOnFill(fill2);
48437
+ const halfW = measureLegendText(text, FONT2) / 2;
48438
+ const overflows = [y - FONT2 * 0.55, y - FONT2 * 0.1].some(
48439
+ (sy) => fillAt(x - halfW, sy) !== fill2 || fillAt(x + halfW, sy) !== fill2
47396
48440
  );
47397
- const haloColor = color === palette.textOnFillLight ? palette.textOnFillDark : palette.textOnFillLight;
47398
48441
  labels.push({
47399
48442
  x,
47400
48443
  y,
47401
48444
  text,
47402
48445
  anchor: "middle",
47403
48446
  color,
47404
- halo: true,
48447
+ halo: overflows,
47405
48448
  haloColor,
47406
48449
  lineNumber
47407
48450
  });
@@ -47410,21 +48453,50 @@ function layoutMap(resolved, data, size, opts) {
47410
48453
  US: [-98.5, 39.5]
47411
48454
  // CONUS geographic centre (near Lebanon, Kansas)
47412
48455
  };
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;
48456
+ const REGION_LABEL_GAP = 2;
48457
+ const regionLabelRect = (cx, cy, text) => {
48458
+ const w = measureLegendText(text, FONT2) + 2 * REGION_LABEL_GAP;
48459
+ return { x: cx - w / 2, y: cy - FONT2 / 2, w, h: FONT2 };
48460
+ };
48461
+ if (showRegionLabels) {
48462
+ const frameContainers = new Set(resolved.poiFrameContainers);
48463
+ const entries = regions.map((r) => {
48464
+ const isContainer = frameContainers.has(r.id);
48465
+ if (r.layer === "base" && !isContainer || r.label === void 0)
48466
+ return null;
48467
+ const isUsState = r.layer === "us-state" || r.id.startsWith("US-");
48468
+ const f = isUsState ? usLayer?.get(r.id) : worldLayer.get(r.id);
48469
+ if (!f) return null;
47418
48470
  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;
48471
+ const boxW = x1 - x0;
48472
+ const boxH = y1 - y0;
48473
+ const abbrev = isUsState ? r.id.replace(/^US-/, "") : void 0;
48474
+ const candidates = abbrev !== void 0 ? isCompact ? [abbrev, r.label] : [r.label, abbrev] : [r.label];
48475
+ const anchor = !isUsState ? WORLD_LABEL_ANCHORS[r.id] : void 0;
47422
48476
  const c = anchor ? project(anchor[0], anchor[1]) : path.centroid(f);
47423
- if (!c || !Number.isFinite(c[0])) continue;
48477
+ if (!c || !Number.isFinite(c[0])) return null;
48478
+ return { r, c, boxW, boxH, area: boxW * boxH, candidates };
48479
+ }).filter((e) => e !== null).sort((a, b) => b.area - a.area || a.r.lineNumber - b.r.lineNumber);
48480
+ const placedRegionRects = [];
48481
+ const POI_LABEL_PAD = 14;
48482
+ const poiObstacles = pois.map((p) => ({
48483
+ x: p.cx - p.r - POI_LABEL_PAD,
48484
+ y: p.cy - p.r - POI_LABEL_PAD,
48485
+ w: 2 * (p.r + POI_LABEL_PAD),
48486
+ h: 2 * (p.r + POI_LABEL_PAD)
48487
+ }));
48488
+ for (const { r, c, boxW, boxH, candidates } of entries) {
48489
+ const text = candidates.find((t) => {
48490
+ if (labelW(t) > boxW || labelH > boxH) return false;
48491
+ const rect = regionLabelRect(c[0], c[1], t);
48492
+ return !placedRegionRects.some((p) => rectsOverlap(rect, p)) && !poiObstacles.some((o) => rectsOverlap(rect, o));
48493
+ });
48494
+ if (text === void 0) continue;
48495
+ placedRegionRects.push(regionLabelRect(c[0], c[1], text));
47424
48496
  pushRegionLabel(c[0], c[1], text, r.fill, r.lineNumber);
47425
48497
  }
47426
48498
  for (const seed of insetLabelSeeds) {
47427
- const text = regionLabelMode === "abbrev" ? seed.iso.replace(/^US-/, "") : seed.name;
48499
+ const text = isCompact ? seed.iso.replace(/^US-/, "") : seed.name;
47428
48500
  const src = regionById.get(seed.iso);
47429
48501
  pushRegionLabel(
47430
48502
  seed.x,
@@ -47435,22 +48507,26 @@ function layoutMap(resolved, data, size, opts) {
47435
48507
  );
47436
48508
  }
47437
48509
  }
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
- );
48510
+ if (resolved.directives.noPoiLabels !== true) {
48511
+ const ordered = [...pois].filter((p) => p.clusterId === void 0).sort((a, b) => a.lineNumber - b.lineNumber || (a.id < b.id ? -1 : 1));
47443
48512
  const poiById = new Map(resolved.pois.map((q) => [q.id, q]));
47444
48513
  const labelText = (p) => {
47445
48514
  const src = poiById.get(p.id);
47446
48515
  return src?.label ?? src?.name ?? p.id;
47447
48516
  };
47448
- const poiLabH = FONT * 1.25;
48517
+ const poiLabH = FONT2 * 1.25;
47449
48518
  const labelInfo = (p) => {
47450
48519
  const text = labelText(p);
47451
- return { text, w: measureLegendText(text, FONT) };
48520
+ return { text, w: measureLegendText(text, FONT2) };
47452
48521
  };
47453
48522
  const GAP = 3;
48523
+ const clusterMembersById = /* @__PURE__ */ new Map();
48524
+ for (const p of pois) {
48525
+ if (p.clusterId === void 0) continue;
48526
+ const arr = clusterMembersById.get(p.clusterId);
48527
+ if (arr) arr.push(p);
48528
+ else clusterMembersById.set(p.clusterId, [p]);
48529
+ }
47454
48530
  const inlineRect = (p, w, side) => {
47455
48531
  switch (side) {
47456
48532
  case "right":
@@ -47480,11 +48556,11 @@ function layoutMap(resolved, data, size, opts) {
47480
48556
  const x = side === "right" ? rect.x : side === "left" ? rect.x + w : p.cx;
47481
48557
  labels.push({
47482
48558
  x,
47483
- y: rect.y + poiLabH / 2 + FONT / 3,
48559
+ y: rect.y + poiLabH / 2 + FONT2 / 3,
47484
48560
  text,
47485
48561
  anchor,
47486
48562
  color: palette.text,
47487
- halo: true,
48563
+ halo: false,
47488
48564
  haloColor: palette.bg,
47489
48565
  poiId: p.id,
47490
48566
  lineNumber: p.lineNumber
@@ -47495,43 +48571,60 @@ function layoutMap(resolved, data, size, opts) {
47495
48571
  return rect.x >= 0 && rect.x + rect.w <= width && rect.y >= 0 && rect.y + rect.h <= height && !collides(rect);
47496
48572
  };
47497
48573
  const GROUP_R = 30;
47498
- const groups = [];
48574
+ const groups2 = [];
47499
48575
  for (const p of ordered) {
47500
- const near = groups.find(
48576
+ const near = groups2.find(
47501
48577
  (g) => g.some((q) => Math.hypot(q.cx - p.cx, q.cy - p.cy) < GROUP_R)
47502
48578
  );
47503
48579
  if (near) near.push(p);
47504
- else groups.push([p]);
48580
+ else groups2.push([p]);
47505
48581
  }
47506
48582
  const ROW_GAP2 = 3;
47507
48583
  const step = poiLabH + ROW_GAP2;
47508
48584
  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));
48585
+ const makeItems = (group) => group.map((p) => ({ p, ...labelInfo(p) })).sort((a, b) => a.p.cy - b.p.cy || (a.text < b.text ? -1 : 1));
48586
+ const columnRows = (items, side) => {
47511
48587
  const left = Math.min(...items.map((o) => o.p.cx - o.p.r));
47512
48588
  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
48589
  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;
48590
+ const cyMid = (Math.min(...items.map((o) => o.p.cy)) + Math.max(...items.map((o) => o.p.cy))) / 2;
48591
+ const colX = side === "right" ? Math.min(right + COL_GAP, width - 2 - maxW) : Math.max(left - COL_GAP, 2 + maxW);
47517
48592
  const totalH = items.length * step;
47518
48593
  let startY = cyMid - totalH / 2;
47519
48594
  startY = Math.max(2, Math.min(startY, height - totalH - 2));
47520
- items.forEach((o, i) => {
48595
+ return items.map((o, i) => {
47521
48596
  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
- });
48597
+ return {
48598
+ o,
48599
+ colX,
48600
+ rowCy,
48601
+ rect: {
48602
+ x: side === "right" ? colX : colX - o.w,
48603
+ y: rowCy - poiLabH / 2,
48604
+ w: o.w,
48605
+ h: poiLabH
48606
+ }
48607
+ };
48608
+ });
48609
+ };
48610
+ const wouldColumnBeClean = (items, side) => columnRows(items, side).every(
48611
+ ({ rect }) => rect.x >= 0 && rect.x + rect.w <= width && rect.y >= 0 && rect.y + rect.h <= height && !collides(rect)
48612
+ );
48613
+ const defaultColumnSide = (items) => {
48614
+ const right = Math.max(...items.map((o) => o.p.cx + o.p.r));
48615
+ const maxW = Math.max(...items.map((o) => o.w));
48616
+ return right + COL_GAP + maxW <= width - 2 ? "right" : "left";
48617
+ };
48618
+ const commitColumn = (items, side, clusterId) => {
48619
+ for (const { o, colX, rowCy, rect } of columnRows(items, side)) {
48620
+ obstacles.push(rect);
47528
48621
  labels.push({
47529
48622
  x: colX,
47530
- y: rowCy + FONT / 3,
48623
+ y: rowCy + FONT2 / 3,
47531
48624
  text: o.text,
47532
48625
  anchor: side === "right" ? "start" : "end",
47533
48626
  color: palette.text,
47534
- halo: true,
48627
+ halo: false,
47535
48628
  haloColor: palette.bg,
47536
48629
  leader: {
47537
48630
  x1: o.p.cx,
@@ -47541,24 +48634,141 @@ function layoutMap(resolved, data, size, opts) {
47541
48634
  },
47542
48635
  leaderColor: o.p.fill,
47543
48636
  poiId: o.p.id,
47544
- lineNumber: o.p.lineNumber
48637
+ lineNumber: o.p.lineNumber,
48638
+ ...clusterId !== void 0 && { clusterMember: clusterId }
47545
48639
  });
48640
+ }
48641
+ };
48642
+ const pushHidden = (p) => {
48643
+ const { text, w } = labelInfo(p);
48644
+ let x = p.cx + p.r + GAP;
48645
+ let anchor = "start";
48646
+ if (x + w > width) {
48647
+ x = p.cx - p.r - GAP - w;
48648
+ anchor = "end";
48649
+ }
48650
+ const y = Math.max(0, Math.min(p.cy - poiLabH / 2, height - poiLabH));
48651
+ labels.push({
48652
+ x: anchor === "start" ? x : x + w,
48653
+ y: y + poiLabH / 2 + FONT2 / 3,
48654
+ text,
48655
+ anchor,
48656
+ color: palette.text,
48657
+ halo: false,
48658
+ haloColor: palette.bg,
48659
+ poiId: p.id,
48660
+ hidden: true,
48661
+ lineNumber: p.lineNumber
47546
48662
  });
47547
48663
  };
47548
- for (const g of groups) {
48664
+ for (const [clusterId, members] of clusterMembersById) {
48665
+ if (members.length === 0) continue;
48666
+ const items = makeItems(members);
48667
+ const side = wouldColumnBeClean(items, "right") ? "right" : wouldColumnBeClean(items, "left") ? "left" : defaultColumnSide(items);
48668
+ commitColumn(items, side, clusterId);
48669
+ }
48670
+ const maxExtent = MAX_CLUSTER_EXTENT_FACTOR * Math.min(width, height);
48671
+ const clusterPending = [];
48672
+ for (const g of groups2) {
48673
+ const items = makeItems(g);
47549
48674
  if (g.length === 1) {
47550
- const p = g[0];
47551
- const { text, w } = labelInfo(p);
48675
+ const { p, text, w } = items[0];
47552
48676
  const side = ["right", "left", "above", "below"].find(
47553
48677
  (s) => inlineFits(p, w, s)
47554
48678
  );
47555
- if (side) {
47556
- pushInline(p, text, w, side);
47557
- continue;
48679
+ if (side) pushInline(p, text, w, side);
48680
+ else commitColumn(items, defaultColumnSide(items));
48681
+ continue;
48682
+ }
48683
+ const left = Math.min(...items.map((o) => o.p.cx - o.p.r));
48684
+ const right = Math.max(...items.map((o) => o.p.cx + o.p.r));
48685
+ const minCy = Math.min(...items.map((o) => o.p.cy));
48686
+ const maxCy = Math.max(...items.map((o) => o.p.cy));
48687
+ const diag = Math.hypot(right - left, maxCy - minCy);
48688
+ if (diag > maxExtent || items.length > MAX_COLUMN_ROWS) {
48689
+ items.forEach((o) => pushHidden(o.p));
48690
+ } else {
48691
+ clusterPending.push(items);
48692
+ }
48693
+ }
48694
+ for (const items of clusterPending) {
48695
+ const side = ["right", "left"].find(
48696
+ (s) => wouldColumnBeClean(items, s)
48697
+ );
48698
+ if (side) commitColumn(items, side);
48699
+ else items.forEach((o) => pushHidden(o.p));
48700
+ }
48701
+ }
48702
+ if (resolved.directives.noContextLabels !== true) {
48703
+ for (const l of labels) {
48704
+ if (l.hidden) continue;
48705
+ const w = labelW(l.text);
48706
+ const x = l.anchor === "start" ? l.x : l.anchor === "end" ? l.x - w : l.x - w / 2;
48707
+ obstacles.push({ x, y: l.y - labelH / 2, w, h: labelH });
48708
+ }
48709
+ for (const box of insets)
48710
+ obstacles.push({ x: box.x, y: box.y, w: box.w, h: box.h });
48711
+ const countryCandidates = [];
48712
+ for (const f of worldLayer.values()) {
48713
+ const iso = typeof f.id === "string" ? f.id : String(f.id ?? "");
48714
+ if (!iso || regionById.has(iso)) continue;
48715
+ let hasReferencedSub = false;
48716
+ for (const k of regionById.keys())
48717
+ if (k.startsWith(iso + "-")) {
48718
+ hasReferencedSub = true;
48719
+ break;
47558
48720
  }
48721
+ if (hasReferencedSub) continue;
48722
+ const b = path.bounds(f);
48723
+ const [x0, y0] = b[0];
48724
+ const [x1, y1] = b[1];
48725
+ if (!Number.isFinite(x0) || !Number.isFinite(x1)) continue;
48726
+ const anchorLngLat = WORLD_LABEL_ANCHORS[iso];
48727
+ const a = anchorLngLat ? project(anchorLngLat[0], anchorLngLat[1]) : path.centroid(f);
48728
+ countryCandidates.push({
48729
+ name: f.properties?.name ?? iso,
48730
+ bbox: [x0, y0, x1, y1],
48731
+ anchor: a && Number.isFinite(a[0]) ? [a[0], a[1]] : null
48732
+ });
48733
+ }
48734
+ const framedStateContainers = (resolved.poiFrameContainers ?? []).some(
48735
+ (id) => id.startsWith("US-")
48736
+ );
48737
+ if (usLayer && framedStateContainers) {
48738
+ const containerSet = new Set(resolved.poiFrameContainers);
48739
+ for (const [iso, f] of usLayer) {
48740
+ if (containerSet.has(iso) || regionById.has(iso)) continue;
48741
+ const viewF = cullFeatureToView(f);
48742
+ if (!viewF) continue;
48743
+ const b = path.bounds(viewF);
48744
+ const [x0, y0] = b[0];
48745
+ const [x1, y1] = b[1];
48746
+ if (!Number.isFinite(x0) || !Number.isFinite(x1)) continue;
48747
+ const a = path.centroid(viewF);
48748
+ countryCandidates.push({
48749
+ name: f.properties?.name ?? iso,
48750
+ bbox: [x0, y0, x1, y1],
48751
+ anchor: a && Number.isFinite(a[0]) ? [a[0], a[1]] : null
48752
+ });
47559
48753
  }
47560
- placeColumn(g);
47561
48754
  }
48755
+ const contextLabels = placeContextLabels({
48756
+ projection: resolved.projection,
48757
+ dLonSpan,
48758
+ dLatSpan,
48759
+ width,
48760
+ height,
48761
+ waterBodies: data.waterBodies,
48762
+ countries: countryCandidates,
48763
+ palette,
48764
+ project,
48765
+ collides,
48766
+ // Water labels must stay over open water — `fillAt` returns the ocean
48767
+ // backdrop colour off-land and a region fill on-land (lakes/states count
48768
+ // as land here, which is the safe side for an ocean name).
48769
+ overLand: (x, y) => fillAt(x, y) !== water
48770
+ });
48771
+ labels.push(...contextLabels);
47562
48772
  }
47563
48773
  let legend = null;
47564
48774
  if (!resolved.directives.noLegend) {
@@ -47595,27 +48805,33 @@ function layoutMap(resolved, data, size, opts) {
47595
48805
  rivers,
47596
48806
  relief,
47597
48807
  reliefHatch,
48808
+ coastlineStyle,
47598
48809
  legs,
47599
48810
  pois,
48811
+ clusters,
47600
48812
  labels,
47601
48813
  legend,
47602
48814
  insets,
47603
48815
  insetRegions,
47604
48816
  projection,
47605
- stretch: stretchParams
48817
+ stretch: stretchParams,
48818
+ diagnostics: []
47606
48819
  };
47607
48820
  }
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;
48821
+ var import_d3_geo2, import_topojson_client2, FIT_PAD, RAMP_FLOOR, R_DEFAULT, R_MIN, R_MAX, W_MIN, W_MAX, FONT2, MAX_CLUSTER_EXTENT_FACTOR, MAX_COLUMN_ROWS, REGION_LABEL_HALO_RATIO, LAND_TINT_LIGHT, LAND_TINT_DARK, TAG_TINT_LIGHT, TAG_TINT_DARK, WATER_TINT_LIGHT, WATER_TINT_DARK, RIVER_WIDTH, COMPACT_WIDTH_PX, RELIEF_MIN_AREA, RELIEF_MIN_DIM, RELIEF_HATCH_SPACING, RELIEF_HATCH_WIDTH, RELIEF_HATCH_STRENGTH, COASTLINE_RING_COUNT, COASTLINE_D0, COASTLINE_STEP, COASTLINE_THICKNESS, COASTLINE_OPACITY_NEAR, COASTLINE_OPACITY_FAR, COASTLINE_MIN_EXTENT, COASTLINE_MIN_EXTENT_GLOBAL, COASTLINE_STROKE_MIX, FOREIGN_TINT_LIGHT, FOREIGN_TINT_DARK, MUTED_FOREIGN_LIGHT, MUTED_FOREIGN_DARK, COLO_R, GOLDEN_ANGLE, STACK_OVERLAP, STACK_RING_MAX, STACK_RING_GAP, FAN_STEP, ARC_CURVE_FRAC, decodeCache, usConusProjection, alaskaProjection, hawaiiProjection, INSET_STATES, inAlaska, inHawaii, FOREIGN_BORDER, US_NON_CONUS;
47609
48822
  var init_layout15 = __esm({
47610
48823
  "src/map/layout.ts"() {
47611
48824
  "use strict";
47612
48825
  import_d3_geo2 = require("d3-geo");
47613
48826
  import_topojson_client2 = require("topojson-client");
47614
48827
  init_color_utils();
48828
+ init_geo();
48829
+ init_colorize();
47615
48830
  init_colors();
47616
48831
  init_label_layout();
47617
48832
  init_legend_constants();
47618
48833
  init_title_constants();
48834
+ init_context_labels();
47619
48835
  FIT_PAD = 24;
47620
48836
  RAMP_FLOOR = 15;
47621
48837
  R_DEFAULT = 6;
@@ -47623,32 +48839,66 @@ var init_layout15 = __esm({
47623
48839
  R_MAX = 22;
47624
48840
  W_MIN = 1.25;
47625
48841
  W_MAX = 8;
47626
- FONT = 11;
47627
- COLO_EPS = 1.5;
48842
+ FONT2 = 11;
48843
+ MAX_CLUSTER_EXTENT_FACTOR = 0.18;
48844
+ MAX_COLUMN_ROWS = 7;
48845
+ REGION_LABEL_HALO_RATIO = 4.5;
47628
48846
  LAND_TINT_LIGHT = 12;
47629
48847
  LAND_TINT_DARK = 24;
47630
48848
  TAG_TINT_LIGHT = 60;
47631
48849
  TAG_TINT_DARK = 68;
47632
- WATER_TINT_LIGHT = 13;
47633
- WATER_TINT_DARK = 14;
48850
+ WATER_TINT_LIGHT = 24;
48851
+ WATER_TINT_DARK = 24;
47634
48852
  RIVER_WIDTH = 1.3;
48853
+ COMPACT_WIDTH_PX = 480;
47635
48854
  RELIEF_MIN_AREA = 12;
47636
48855
  RELIEF_MIN_DIM = 2;
47637
- RELIEF_HATCH_SPACING = 3;
47638
- RELIEF_HATCH_WIDTH = 0.25;
48856
+ RELIEF_HATCH_SPACING = 2;
48857
+ RELIEF_HATCH_WIDTH = 0.15;
47639
48858
  RELIEF_HATCH_STRENGTH = 32;
48859
+ COASTLINE_RING_COUNT = 5;
48860
+ COASTLINE_D0 = 16e-4;
48861
+ COASTLINE_STEP = 28e-4;
48862
+ COASTLINE_THICKNESS = 14e-4;
48863
+ COASTLINE_OPACITY_NEAR = 0.5;
48864
+ COASTLINE_OPACITY_FAR = 0.1;
48865
+ COASTLINE_MIN_EXTENT = 6e-4;
48866
+ COASTLINE_MIN_EXTENT_GLOBAL = 6e-4;
48867
+ COASTLINE_STROKE_MIX = 32;
47640
48868
  FOREIGN_TINT_LIGHT = 30;
47641
48869
  FOREIGN_TINT_DARK = 62;
47642
48870
  MUTED_FOREIGN_LIGHT = 28;
47643
48871
  MUTED_FOREIGN_DARK = 16;
47644
48872
  COLO_R = 9;
47645
48873
  GOLDEN_ANGLE = 2.399963229728653;
48874
+ STACK_OVERLAP = 1;
48875
+ STACK_RING_MAX = 8;
48876
+ STACK_RING_GAP = 4;
47646
48877
  FAN_STEP = 16;
47647
48878
  ARC_CURVE_FRAC = 0.18;
48879
+ decodeCache = /* @__PURE__ */ new WeakMap();
47648
48880
  usConusProjection = () => (0, import_d3_geo2.geoConicEqualArea)().parallels([29.5, 45.5]).rotate([96, 0]);
47649
48881
  alaskaProjection = () => (0, import_d3_geo2.geoConicEqualArea)().rotate([154, 0]).center([-2, 58.5]).parallels([55, 65]);
47650
48882
  hawaiiProjection = () => (0, import_d3_geo2.geoMercator)();
47651
48883
  INSET_STATES = /* @__PURE__ */ new Set(["US-AK", "US-HI"]);
48884
+ inAlaska = (lon, lat) => lat >= 51 && (lon <= -129 || lon >= 172);
48885
+ inHawaii = (lon, lat) => lat >= 18 && lat <= 23 && lon >= -161 && lon <= -154;
48886
+ FOREIGN_BORDER = {
48887
+ CA: [
48888
+ "US-AK",
48889
+ "US-WA",
48890
+ "US-ID",
48891
+ "US-MT",
48892
+ "US-ND",
48893
+ "US-MN",
48894
+ "US-MI",
48895
+ "US-NY",
48896
+ "US-VT",
48897
+ "US-NH",
48898
+ "US-ME"
48899
+ ],
48900
+ MX: ["US-CA", "US-AZ", "US-NM", "US-TX"]
48901
+ };
47652
48902
  US_NON_CONUS = /* @__PURE__ */ new Set([
47653
48903
  "US-AK",
47654
48904
  "US-HI",
@@ -47667,6 +48917,58 @@ __export(renderer_exports16, {
47667
48917
  renderMap: () => renderMap,
47668
48918
  renderMapForExport: () => renderMapForExport
47669
48919
  });
48920
+ function pointInRing2(px, py, ring) {
48921
+ let inside = false;
48922
+ for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
48923
+ const [xi, yi] = ring[i];
48924
+ const [xj, yj] = ring[j];
48925
+ if (yi > py !== yj > py && px < (xj - xi) * (py - yi) / (yj - yi) + xi)
48926
+ inside = !inside;
48927
+ }
48928
+ return inside;
48929
+ }
48930
+ function ringToPath(ring) {
48931
+ let d = "";
48932
+ for (let i = 0; i < ring.length; i++)
48933
+ d += (i ? "L" : "M") + ring[i][0] + "," + ring[i][1];
48934
+ return d + "Z";
48935
+ }
48936
+ function coastlineOuterRings(regions, minExtent) {
48937
+ const paths = [];
48938
+ for (const r of regions) {
48939
+ const rings = parsePathRings(r.d);
48940
+ for (let i = 0; i < rings.length; i++) {
48941
+ const ring = rings[i];
48942
+ if (ring.length < 3) continue;
48943
+ let minX = Infinity;
48944
+ let minY = Infinity;
48945
+ let maxX = -Infinity;
48946
+ let maxY = -Infinity;
48947
+ for (const [x, y] of ring) {
48948
+ if (x < minX) minX = x;
48949
+ if (x > maxX) maxX = x;
48950
+ if (y < minY) minY = y;
48951
+ if (y > maxY) maxY = y;
48952
+ }
48953
+ if (Math.max(maxX - minX, maxY - minY) < minExtent) continue;
48954
+ const [fx, fy] = ring[0];
48955
+ let depth = 0;
48956
+ for (let j = 0; j < rings.length; j++)
48957
+ if (j !== i && pointInRing2(fx, fy, rings[j])) depth++;
48958
+ if (depth % 2 === 1) continue;
48959
+ paths.push(ringToPath(ring));
48960
+ }
48961
+ }
48962
+ return paths;
48963
+ }
48964
+ function appendWaterLines(g, outerRings, style, flatWater) {
48965
+ const d = outerRings.join(" ");
48966
+ const linesOuterFirst = [...style.lines].sort((a, b) => b.d - a.d);
48967
+ for (const line12 of linesOuterFirst) {
48968
+ 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");
48969
+ g.append("path").attr("d", d).attr("stroke", flatWater).attr("stroke-width", 2 * line12.d).attr("stroke-linejoin", "round").attr("stroke-linecap", "round");
48970
+ }
48971
+ }
47670
48972
  function renderMap(container, resolved, data, palette, isDark, onClickItem, exportDims, activeGroupOverride) {
47671
48973
  d3Selection18.select(container).selectAll(":not([data-d3-tooltip])").remove();
47672
48974
  const width = exportDims?.width ?? container.clientWidth;
@@ -47679,6 +48981,11 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47679
48981
  {
47680
48982
  palette,
47681
48983
  isDark,
48984
+ // Export-only: forward the contain-fit request from mapExportDimensions so a
48985
+ // clamped/floored (off-aspect) export canvas letterboxes instead of
48986
+ // stretch-distorting. The in-app preview pane passes no exportDims → unset →
48987
+ // keeps the global stretch-fill.
48988
+ preferContain: exportDims?.preferContain ?? false,
47682
48989
  ...activeGroupOverride !== void 0 && {
47683
48990
  activeGroup: activeGroupOverride
47684
48991
  }
@@ -47692,6 +48999,7 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47692
48999
  const gRegions = svg.append("g").attr("class", "dgmo-map-regions");
47693
49000
  const drawRegion = (g, r, strokeWidth) => {
47694
49001
  const p = g.append("path").attr("d", r.d).attr("fill", r.fill).attr("stroke", r.stroke).attr("stroke-width", strokeWidth);
49002
+ if (r.label) p.attr("data-region-name", r.label);
47695
49003
  if (r.layer !== "base") {
47696
49004
  p.classed("dgmo-map-region", true).attr("data-region", r.id);
47697
49005
  if (r.value !== void 0) p.attr("data-value", r.value);
@@ -47726,6 +49034,38 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47726
49034
  gRelief.append("line").attr("x1", 0).attr("y1", y).attr("x2", width).attr("y2", y);
47727
49035
  }
47728
49036
  }
49037
+ if (layout.coastlineStyle) {
49038
+ const cs = layout.coastlineStyle;
49039
+ const maskId = "dgmo-map-water-mask";
49040
+ const mask = defs.append("mask").attr("id", maskId).attr("maskUnits", "userSpaceOnUse").attr("x", 0).attr("y", 0).attr("width", width).attr("height", height);
49041
+ mask.append("rect").attr("x", 0).attr("y", 0).attr("width", width).attr("height", height).attr("fill", "white");
49042
+ const landD = layout.regions.filter((r) => r.id !== "lake").map((r) => r.d).join(" ");
49043
+ const lakeD = layout.regions.filter((r) => r.id === "lake").map((r) => r.d).join(" ");
49044
+ if (landD) mask.append("path").attr("d", landD).attr("fill", "black");
49045
+ if (lakeD) mask.append("path").attr("d", lakeD).attr("fill", "white");
49046
+ if (layout.insets.length) {
49047
+ const reach = Math.max(0, ...cs.lines.map((l) => l.d + l.thickness));
49048
+ for (const box of layout.insets) {
49049
+ const d = box.points.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join("") + "Z";
49050
+ mask.append("path").attr("d", d).attr("fill", "black").attr("stroke", "black").attr("stroke-width", 2 * reach).attr("stroke-linejoin", "round");
49051
+ }
49052
+ }
49053
+ const gWater = svg.append("g").attr("class", "dgmo-map-water-lines").attr("fill", "none").attr("mask", `url(#${maskId})`);
49054
+ appendWaterLines(
49055
+ gWater,
49056
+ coastlineOuterRings(layout.regions, cs.minExtent),
49057
+ cs,
49058
+ layout.background
49059
+ );
49060
+ const byStroke = /* @__PURE__ */ new Map();
49061
+ for (const r of layout.regions) {
49062
+ const arr = byStroke.get(r.stroke);
49063
+ if (arr) arr.push(r.d);
49064
+ else byStroke.set(r.stroke, [r.d]);
49065
+ }
49066
+ for (const [stroke2, ds] of byStroke)
49067
+ gWater.append("path").attr("d", ds.join(" ")).attr("stroke", stroke2).attr("stroke-width", 0.5).attr("stroke-linejoin", "round");
49068
+ }
47729
49069
  if (layout.rivers.length) {
47730
49070
  const gRivers = svg.append("g").attr("class", "dgmo-map-rivers").attr("fill", "none");
47731
49071
  for (const r of layout.rivers) {
@@ -47734,15 +49074,61 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47734
49074
  }
47735
49075
  if (layout.insets.length) {
47736
49076
  const insetG = svg.append("g").attr("class", "dgmo-map-insets");
47737
- for (const box of layout.insets) {
49077
+ layout.insets.forEach((box, bi) => {
47738
49078
  const d = box.points.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join("") + "Z";
47739
49079
  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
- }
49080
+ if (box.contextLand) {
49081
+ const clipId = `dgmo-map-inset-clip-${bi}`;
49082
+ defs.append("clipPath").attr("id", clipId).append("path").attr("d", d);
49083
+ insetG.append("path").attr("d", box.contextLand.d).attr("fill", box.contextLand.fill).attr("clip-path", `url(#${clipId})`);
49084
+ }
49085
+ });
47741
49086
  for (const r of layout.insetRegions) drawRegion(insetG, r, 0.5);
47742
- }
49087
+ if (layout.coastlineStyle) {
49088
+ const cs = layout.coastlineStyle;
49089
+ const maskId = "dgmo-map-inset-water-mask";
49090
+ const mask = defs.append("mask").attr("id", maskId).attr("maskUnits", "userSpaceOnUse").attr("x", 0).attr("y", 0).attr("width", width).attr("height", height);
49091
+ for (const box of layout.insets) {
49092
+ const d = box.points.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join("") + "Z";
49093
+ mask.append("path").attr("d", d).attr("fill", "white");
49094
+ }
49095
+ layout.insets.forEach((box, bi) => {
49096
+ if (box.contextLand)
49097
+ mask.append("path").attr("d", box.contextLand.d).attr("fill", "black").attr("clip-path", `url(#dgmo-map-inset-clip-${bi})`);
49098
+ });
49099
+ for (const r of layout.insetRegions)
49100
+ if (r.id !== "lake")
49101
+ mask.append("path").attr("d", r.d).attr("fill", "black");
49102
+ for (const r of layout.insetRegions)
49103
+ if (r.id === "lake")
49104
+ mask.append("path").attr("d", r.d).attr("fill", "white");
49105
+ const clipId = "dgmo-map-inset-water-clip";
49106
+ const clip = defs.append("clipPath").attr("id", clipId);
49107
+ for (const box of layout.insets) {
49108
+ const d = box.points.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join("") + "Z";
49109
+ clip.append("path").attr("d", d);
49110
+ }
49111
+ const gInsetWater = insetG.append("g").attr("clip-path", `url(#${clipId})`).append("g").attr("class", "dgmo-map-inset-water-lines").attr("fill", "none").attr("mask", `url(#${maskId})`);
49112
+ appendWaterLines(
49113
+ gInsetWater,
49114
+ coastlineOuterRings(layout.insetRegions, cs.minExtent),
49115
+ cs,
49116
+ layout.background
49117
+ );
49118
+ for (const r of layout.insetRegions)
49119
+ gInsetWater.append("path").attr("d", r.d).attr("stroke", r.stroke).attr("stroke-width", 0.5).attr("stroke-linejoin", "round");
49120
+ }
49121
+ }
49122
+ const wireSync = (sel, lineNumber) => {
49123
+ if (lineNumber < 1) return;
49124
+ sel.attr("data-line-number", lineNumber);
49125
+ if (onClickItem)
49126
+ sel.style("cursor", "pointer").on("click", () => onClickItem(lineNumber));
49127
+ };
47743
49128
  const gLegs = svg.append("g").attr("class", "dgmo-map-legs").attr("fill", "none");
47744
49129
  layout.legs.forEach((leg, i) => {
47745
49130
  const p = gLegs.append("path").attr("d", leg.d).attr("stroke", leg.color).attr("stroke-width", leg.width).attr("stroke-linecap", "round");
49131
+ wireSync(p, leg.lineNumber);
47746
49132
  if (leg.arrow) {
47747
49133
  const id = `dgmo-map-arrow-${i}`;
47748
49134
  const s = arrowSize(leg.width);
@@ -47750,25 +49136,38 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47750
49136
  p.attr("marker-end", `url(#${id})`);
47751
49137
  }
47752
49138
  if (leg.label !== void 0 && leg.labelX !== void 0) {
47753
- emitText(
49139
+ const lt = emitText(
47754
49140
  gLegs,
47755
49141
  leg.labelX,
47756
49142
  leg.labelY ?? 0,
47757
49143
  leg.label,
47758
49144
  "middle",
47759
- palette.textMuted,
47760
- haloColor,
47761
- true,
49145
+ leg.labelColor ?? palette.textMuted,
49146
+ leg.labelHaloColor ?? haloColor,
49147
+ leg.labelHalo ?? true,
47762
49148
  LABEL_FONT - 1
47763
49149
  );
49150
+ wireSync(lt, leg.lineNumber);
47764
49151
  }
47765
49152
  });
49153
+ const gSpider = svg.append("g").attr("class", "dgmo-map-spider");
49154
+ for (const cl of layout.clusters) {
49155
+ if (!exportDims) {
49156
+ 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");
49157
+ }
49158
+ for (const leg of cl.legs) {
49159
+ 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");
49160
+ }
49161
+ 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");
49162
+ }
47766
49163
  const gPois = svg.append("g").attr("class", "dgmo-map-pois");
47767
49164
  for (const poi of layout.pois) {
47768
49165
  if (poi.isOrigin) {
47769
49166
  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
49167
  }
47771
49168
  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);
49169
+ if (poi.clusterId !== void 0)
49170
+ c.attr("data-cluster-member", poi.clusterId);
47772
49171
  if (poi.tags) {
47773
49172
  for (const [group, value] of Object.entries(poi.tags)) {
47774
49173
  c.attr(`data-tag-${group.toLowerCase()}`, value.toLowerCase());
@@ -47796,12 +49195,32 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47796
49195
  }
47797
49196
  const gLabels = svg.append("g").attr("class", "dgmo-map-labels");
47798
49197
  for (const lab of layout.labels) {
49198
+ if (lab.hidden) {
49199
+ if (exportDims) continue;
49200
+ emitText(
49201
+ gLabels,
49202
+ lab.x,
49203
+ lab.y,
49204
+ lab.text,
49205
+ lab.anchor,
49206
+ lab.color,
49207
+ lab.haloColor,
49208
+ lab.halo,
49209
+ LABEL_FONT,
49210
+ lab.italic,
49211
+ lab.letterSpacing
49212
+ ).attr("data-poi", lab.poiId ?? null).attr("data-poi-hidden", "").style("opacity", 0).style("pointer-events", "none");
49213
+ continue;
49214
+ }
47799
49215
  if (lab.leader) {
47800
49216
  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
49217
  "stroke",
47802
49218
  lab.leaderColor ?? mix(palette.textMuted, palette.bg, 60)
47803
49219
  ).attr("stroke-width", lab.leaderColor ? 1 : 0.75);
47804
49220
  if (lab.poiId !== void 0) line12.attr("data-poi", lab.poiId);
49221
+ if (lab.clusterMember !== void 0)
49222
+ line12.attr("data-cluster-member", lab.clusterMember);
49223
+ wireSync(line12, lab.lineNumber);
47805
49224
  }
47806
49225
  const t = emitText(
47807
49226
  gLabels,
@@ -47812,11 +49231,38 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47812
49231
  lab.color,
47813
49232
  lab.haloColor,
47814
49233
  lab.halo,
47815
- LABEL_FONT
49234
+ LABEL_FONT,
49235
+ lab.italic,
49236
+ lab.letterSpacing,
49237
+ lab.lines
47816
49238
  );
47817
49239
  if (lab.poiId !== void 0) {
47818
49240
  t.attr("data-poi", lab.poiId).style("cursor", "default");
47819
49241
  }
49242
+ if (lab.clusterMember !== void 0) {
49243
+ t.attr("data-cluster-member", lab.clusterMember);
49244
+ }
49245
+ wireSync(t, lab.lineNumber);
49246
+ }
49247
+ if (!exportDims && layout.clusters.length) {
49248
+ const gBadge = svg.append("g").attr("class", "dgmo-map-cluster-badges");
49249
+ for (const cl of layout.clusters) {
49250
+ const g = gBadge.append("g").attr("data-cluster", cl.id).style("opacity", 0).style("pointer-events", "none");
49251
+ const R = 9;
49252
+ 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);
49253
+ 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);
49254
+ emitText(
49255
+ g,
49256
+ cl.cx,
49257
+ cl.cy + 3,
49258
+ String(cl.count),
49259
+ "middle",
49260
+ palette.text,
49261
+ palette.bg,
49262
+ false,
49263
+ LABEL_FONT
49264
+ );
49265
+ }
47820
49266
  }
47821
49267
  if (layout.legend) {
47822
49268
  const legendY = (layout.title ? TITLE_Y + TITLE_FONT_SIZE : 0) + (layout.subtitle ? TITLE_FONT_SIZE : 0) + 8;
@@ -47853,7 +49299,7 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47853
49299
  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
49300
  }
47855
49301
  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);
49302
+ 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
49303
  }
47858
49304
  if (layout.caption) {
47859
49305
  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 +49308,21 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
47862
49308
  function renderMapForExport(container, resolved, data, palette, isDark, exportDims) {
47863
49309
  renderMap(container, resolved, data, palette, isDark, void 0, exportDims);
47864
49310
  }
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);
49311
+ function emitText(g, x, y, text, anchor, color, halo, withHalo, fontSize, italic, letterSpacing, lines) {
49312
+ const t = g.append("text").attr("x", x).attr("y", y).attr("text-anchor", anchor).attr("font-size", fontSize).attr("fill", color);
49313
+ if (lines && lines.length > 1) {
49314
+ const lineHeight = fontSize + 2;
49315
+ const startDy = -((lines.length - 1) / 2) * lineHeight;
49316
+ lines.forEach((ln, i) => {
49317
+ t.append("tspan").attr("x", x).attr("dy", i === 0 ? startDy : lineHeight).text(ln);
49318
+ });
49319
+ } else {
49320
+ t.text(text);
49321
+ }
49322
+ if (italic) t.attr("font-style", "italic");
49323
+ if (letterSpacing) t.attr("letter-spacing", letterSpacing);
47867
49324
  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);
49325
+ 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
49326
  }
47870
49327
  return t;
47871
49328
  }
@@ -47883,6 +49340,56 @@ var init_renderer16 = __esm({
47883
49340
  }
47884
49341
  });
47885
49342
 
49343
+ // src/map/dimensions.ts
49344
+ var dimensions_exports = {};
49345
+ __export(dimensions_exports, {
49346
+ mapContentAspect: () => mapContentAspect,
49347
+ mapExportDimensions: () => mapExportDimensions
49348
+ });
49349
+ function mapContentAspect(resolved, data, ref = REF) {
49350
+ const { projection, fitTarget } = buildMapProjection(resolved, data);
49351
+ projection.fitSize([ref, ref], fitTarget);
49352
+ const b = (0, import_d3_geo3.geoPath)(projection).bounds(fitTarget);
49353
+ const w = b[1][0] - b[0][0];
49354
+ const h = b[1][1] - b[0][1];
49355
+ const aspect = w / h;
49356
+ return Number.isFinite(aspect) && aspect > 0 ? aspect : FALLBACK_ASPECT;
49357
+ }
49358
+ function mapExportDimensions(resolved, data, baseWidth = 1200) {
49359
+ const raw = mapContentAspect(resolved, data);
49360
+ const clamped = Math.max(ASPECT_MIN, Math.min(ASPECT_MAX, raw));
49361
+ const width = baseWidth;
49362
+ let height = Math.round(width / clamped);
49363
+ let chromeReserve = 0;
49364
+ if (resolved.title && resolved.pois.length > 0) {
49365
+ const bannerBottom = (resolved.subtitle ? TITLE_Y + TITLE_FONT_SIZE : TITLE_Y) + TITLE_FONT_SIZE / 2;
49366
+ chromeReserve += Math.max(FIT_PAD2, bannerBottom + TITLE_GAP) - FIT_PAD2;
49367
+ }
49368
+ let floored = false;
49369
+ if (height - chromeReserve < MIN_MAP_BAND) {
49370
+ height = Math.round(chromeReserve + MIN_MAP_BAND);
49371
+ floored = true;
49372
+ }
49373
+ const preferContain = clamped !== raw || floored;
49374
+ return { width, height, preferContain };
49375
+ }
49376
+ var import_d3_geo3, FIT_PAD2, TITLE_GAP, ASPECT_MAX, ASPECT_MIN, MIN_MAP_BAND, FALLBACK_ASPECT, REF;
49377
+ var init_dimensions = __esm({
49378
+ "src/map/dimensions.ts"() {
49379
+ "use strict";
49380
+ import_d3_geo3 = require("d3-geo");
49381
+ init_title_constants();
49382
+ init_layout15();
49383
+ FIT_PAD2 = 24;
49384
+ TITLE_GAP = 16;
49385
+ ASPECT_MAX = 3;
49386
+ ASPECT_MIN = 0.9;
49387
+ MIN_MAP_BAND = 200;
49388
+ FALLBACK_ASPECT = 1.5;
49389
+ REF = 1e3;
49390
+ }
49391
+ });
49392
+
47886
49393
  // src/map/load-data.ts
47887
49394
  var load_data_exports = {};
47888
49395
  __export(load_data_exports, {
@@ -47941,12 +49448,17 @@ function loadMapData() {
47941
49448
  mountainRanges,
47942
49449
  naLand,
47943
49450
  naLakes,
49451
+ waterBodies,
47944
49452
  gazetteer
47945
49453
  ] = await Promise.all([
49454
+ // worldCoarse (110m) is LOAD-BEARING but NOT a render source: the world
49455
+ // basemap renders from worldDetail (50m) at all scales (resolver pins
49456
+ // basemaps.world = 'detail'). Coarse stays as the authoritative region
49457
+ // name index + dominant-landmass bbox source in resolver.ts. Do not drop it.
47946
49458
  readJson(nb, dir, FILES.worldCoarse),
47947
49459
  readJson(nb, dir, FILES.worldDetail),
47948
49460
  readJson(nb, dir, FILES.usStates),
47949
- // Lakes/rivers/mountain/NA assets are optional — older bundles may predate them.
49461
+ // Lakes/rivers/mountain/NA/water assets are optional — older bundles may predate them.
47950
49462
  readJson(nb, dir, FILES.lakes).catch(() => void 0),
47951
49463
  readJson(nb, dir, FILES.rivers).catch(() => void 0),
47952
49464
  readJson(nb, dir, FILES.mountainRanges).catch(
@@ -47954,6 +49466,7 @@ function loadMapData() {
47954
49466
  ),
47955
49467
  readJson(nb, dir, FILES.naLand).catch(() => void 0),
47956
49468
  readJson(nb, dir, FILES.naLakes).catch(() => void 0),
49469
+ readJson(nb, dir, FILES.waterBodies).catch(() => void 0),
47957
49470
  readJson(nb, dir, FILES.gazetteer)
47958
49471
  ]);
47959
49472
  return validate({
@@ -47965,7 +49478,8 @@ function loadMapData() {
47965
49478
  ...rivers && { rivers },
47966
49479
  ...mountainRanges && { mountainRanges },
47967
49480
  ...naLand && { naLand },
47968
- ...naLakes && { naLakes }
49481
+ ...naLakes && { naLakes },
49482
+ ...waterBodies && { waterBodies }
47969
49483
  });
47970
49484
  })().catch((e) => {
47971
49485
  cache = void 0;
@@ -47987,6 +49501,7 @@ var init_load_data = __esm({
47987
49501
  mountainRanges: "mountain-ranges.json",
47988
49502
  naLand: "na-land.json",
47989
49503
  naLakes: "na-lakes.json",
49504
+ waterBodies: "water-bodies.json",
47990
49505
  gazetteer: "gazetteer.json"
47991
49506
  };
47992
49507
  CANDIDATE_DIRS = [
@@ -49999,8 +51514,8 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
49999
51514
  const lines = splitParticipantLabel(p.label, LABEL_MAX_CHARS);
50000
51515
  if (lines.length === 0) continue;
50001
51516
  const widest = Math.max(...lines.map((l) => l.length));
50002
- const labelWidth = widest * LABEL_CHAR_WIDTH + 10;
50003
- uniformBoxWidth = Math.max(uniformBoxWidth, labelWidth);
51517
+ const labelWidth2 = widest * LABEL_CHAR_WIDTH + 10;
51518
+ uniformBoxWidth = Math.max(uniformBoxWidth, labelWidth2);
50004
51519
  }
50005
51520
  uniformBoxWidth = Math.min(MAX_BOX_WIDTH, uniformBoxWidth);
50006
51521
  const effectiveGap = Math.max(PARTICIPANT_GAP, uniformBoxWidth + 30);
@@ -52695,15 +54210,15 @@ function renderArcDiagram(container, parsed, palette, _isDark, onClickItem, expo
52695
54210
  textColor,
52696
54211
  onClickItem
52697
54212
  );
52698
- const neighbors = /* @__PURE__ */ new Map();
52699
- for (const node of nodes) neighbors.set(node, /* @__PURE__ */ new Set());
54213
+ const neighbors2 = /* @__PURE__ */ new Map();
54214
+ for (const node of nodes) neighbors2.set(node, /* @__PURE__ */ new Set());
52700
54215
  for (const link of links) {
52701
- neighbors.get(link.source).add(link.target);
52702
- neighbors.get(link.target).add(link.source);
54216
+ neighbors2.get(link.source).add(link.target);
54217
+ neighbors2.get(link.target).add(link.source);
52703
54218
  }
52704
54219
  const FADE_OPACITY3 = 0.1;
52705
54220
  function handleMouseEnter(hovered) {
52706
- const connected = neighbors.get(hovered);
54221
+ const connected = neighbors2.get(hovered);
52707
54222
  g.selectAll(".arc-link").each(function() {
52708
54223
  const el = d3Selection23.select(this);
52709
54224
  const src = el.attr("data-source");
@@ -54694,7 +56209,7 @@ function renderVenn(container, parsed, palette, _isDark, onClickItem, exportDims
54694
56209
  8,
54695
56210
  Math.floor(OVERLAP_WRAP_TARGET_W / OVERLAP_CH_W)
54696
56211
  );
54697
- function wrapLabel2(text, maxChars) {
56212
+ function wrapLabel3(text, maxChars) {
54698
56213
  const words = text.split(/\s+/).filter(Boolean);
54699
56214
  const lines = [];
54700
56215
  let cur = "";
@@ -54740,7 +56255,7 @@ function renderVenn(container, parsed, palette, _isDark, onClickItem, exportDims
54740
56255
  if (!ov.label) continue;
54741
56256
  const idxs = ov.sets.map((s) => vennSets.findIndex((vs) => vs.name === s));
54742
56257
  if (idxs.some((idx) => idx < 0)) continue;
54743
- const lines = wrapLabel2(ov.label, MAX_WRAP_CHARS);
56258
+ const lines = wrapLabel3(ov.label, MAX_WRAP_CHARS);
54744
56259
  wrappedOverlapLabels.set(ov, lines);
54745
56260
  const dir = predictOverlapDirRaw(idxs);
54746
56261
  const longest = lines.reduce((m, l) => Math.max(m, l.length), 0);
@@ -56178,6 +57693,7 @@ async function renderForExport(content, theme, palette, viewState, options) {
56178
57693
  const { parseMap: parseMap2 } = await Promise.resolve().then(() => (init_parser12(), parser_exports11));
56179
57694
  const { resolveMap: resolveMap2 } = await Promise.resolve().then(() => (init_resolver2(), resolver_exports));
56180
57695
  const { renderMapForExport: renderMapForExport2 } = await Promise.resolve().then(() => (init_renderer16(), renderer_exports16));
57696
+ const { mapExportDimensions: mapExportDimensions2 } = await Promise.resolve().then(() => (init_dimensions(), dimensions_exports));
56181
57697
  const effectivePalette2 = await resolveExportPalette(theme, palette);
56182
57698
  const mapParsed = parseMap2(content);
56183
57699
  let mapData = options?.mapData;
@@ -56190,14 +57706,15 @@ async function renderForExport(content, theme, palette, viewState, options) {
56190
57706
  }
56191
57707
  }
56192
57708
  const mapResolved = resolveMap2(mapParsed, mapData);
56193
- const container2 = createExportContainer(EXPORT_WIDTH, EXPORT_HEIGHT);
57709
+ const dims2 = mapExportDimensions2(mapResolved, mapData, EXPORT_WIDTH);
57710
+ const container2 = createExportContainer(dims2.width, dims2.height);
56194
57711
  renderMapForExport2(
56195
57712
  container2,
56196
57713
  mapResolved,
56197
57714
  mapData,
56198
57715
  effectivePalette2,
56199
57716
  theme === "dark",
56200
- { width: EXPORT_WIDTH, height: EXPORT_HEIGHT }
57717
+ dims2
56201
57718
  );
56202
57719
  return finalizeSvgExport(container2, theme, effectivePalette2);
56203
57720
  }
@@ -57004,7 +58521,8 @@ __export(internal_exports, {
57004
58521
  applyCollapseProjection: () => applyCollapseProjection,
57005
58522
  applyGroupOrdering: () => applyGroupOrdering,
57006
58523
  applyPositionOverrides: () => applyPositionOverrides,
57007
- boldPalette: () => boldPalette,
58524
+ atlasPalette: () => atlasPalette,
58525
+ blueprintPalette: () => blueprintPalette,
57008
58526
  buildExtendedChartOption: () => buildExtendedChartOption,
57009
58527
  buildNoteMessageMap: () => buildNoteMessageMap,
57010
58528
  buildRenderSequence: () => buildRenderSequence,
@@ -57107,6 +58625,8 @@ __export(internal_exports, {
57107
58625
  looksLikeState: () => looksLikeState,
57108
58626
  makeDgmoError: () => makeDgmoError,
57109
58627
  mapBackgroundColor: () => mapBackgroundColor,
58628
+ mapContentAspect: () => mapContentAspect,
58629
+ mapExportDimensions: () => mapExportDimensions,
57110
58630
  mapNeutralLandColor: () => mapNeutralLandColor,
57111
58631
  matchesContiguously: () => matchesContiguously,
57112
58632
  measurePertAnalysisBlock: () => measurePertAnalysisBlock,
@@ -57242,9 +58762,11 @@ __export(internal_exports, {
57242
58762
  shapeFill: () => shapeFill,
57243
58763
  simulateCanonical: () => simulateCanonical,
57244
58764
  simulateFast: () => simulateFast,
58765
+ slatePalette: () => slatePalette,
57245
58766
  solarizedPalette: () => solarizedPalette,
57246
58767
  suggestChartTypes: () => suggestChartTypes,
57247
58768
  themes: () => themes,
58769
+ tidewaterPalette: () => tidewaterPalette,
57248
58770
  tint: () => tint,
57249
58771
  tokyoNightPalette: () => tokyoNightPalette,
57250
58772
  transformLine: () => transformLine,
@@ -57330,7 +58852,8 @@ async function render(content, options) {
57330
58852
  ...options?.c4Container !== void 0 && {
57331
58853
  c4Container: options.c4Container
57332
58854
  },
57333
- ...options?.tagGroup !== void 0 && { tagGroup: options.tagGroup }
58855
+ ...options?.tagGroup !== void 0 && { tagGroup: options.tagGroup },
58856
+ ...options?.mapData !== void 0 && { mapData: options.mapData }
57334
58857
  });
57335
58858
  if (chartType === "map") {
57336
58859
  try {
@@ -57341,7 +58864,7 @@ async function render(content, options) {
57341
58864
  Promise.resolve().then(() => (init_load_data(), load_data_exports))
57342
58865
  ]
57343
58866
  );
57344
- const data = await loadMapData2();
58867
+ const data = options?.mapData ?? await loadMapData2();
57345
58868
  diagnostics = [...resolveMap2(parseMap2(content), data).diagnostics];
57346
58869
  } catch {
57347
58870
  }
@@ -57580,8 +59103,8 @@ function detectCycles(parsed) {
57580
59103
  const parent = /* @__PURE__ */ new Map();
57581
59104
  function dfs(nodeId3) {
57582
59105
  color.set(nodeId3, 1);
57583
- const neighbors = adj.get(nodeId3) ?? [];
57584
- for (const next of neighbors) {
59106
+ const neighbors2 = adj.get(nodeId3) ?? [];
59107
+ for (const next of neighbors2) {
57585
59108
  const c = color.get(next) ?? 0;
57586
59109
  if (c === 1) {
57587
59110
  const lineKey = `${nodeId3}->${next}`;
@@ -57766,6 +59289,7 @@ init_resolver2();
57766
59289
  init_load_data();
57767
59290
  init_layout15();
57768
59291
  init_renderer16();
59292
+ init_dimensions();
57769
59293
 
57770
59294
  // src/map/geo-query.ts
57771
59295
  init_parser12();
@@ -57839,7 +59363,9 @@ function nearestCity(lonLat, gazetteer) {
57839
59363
  name: c[4],
57840
59364
  iso: c[2],
57841
59365
  ...c[5] !== void 0 && { sub: c[5] },
57842
- distanceKm: best.dist
59366
+ distanceKm: best.dist,
59367
+ lat: c[0],
59368
+ lon: c[1]
57843
59369
  };
57844
59370
  }
57845
59371
  function roundCoord(n) {
@@ -57918,7 +59444,7 @@ function createMapGeoQuery(opts) {
57918
59444
  }
57919
59445
  return out;
57920
59446
  };
57921
- return { invert, project, locate, cities };
59447
+ return { invert, project, locate, cities, diagnostics: layout.diagnostics };
57922
59448
  }
57923
59449
 
57924
59450
  // src/map/completion.ts
@@ -58644,9 +60170,12 @@ var GLOBAL_DIRECTIVES = {
58644
60170
  "gruvbox",
58645
60171
  "tokyo-night",
58646
60172
  "one-dark",
58647
- "bold",
58648
60173
  "dracula",
58649
- "monokai"
60174
+ "monokai",
60175
+ "atlas",
60176
+ "blueprint",
60177
+ "slate",
60178
+ "tidewater"
58650
60179
  ]
58651
60180
  },
58652
60181
  theme: {
@@ -59042,18 +60571,12 @@ var COMPLETION_REGISTRY = /* @__PURE__ */ new Map([
59042
60571
  ],
59043
60572
  [
59044
60573
  "map",
59045
- // Geographic map directives (§24B.2/.7). `poi`/`route` are content
59046
- // keywords, not directives; metadata keys (value/label/style) live in the
59047
- // reserved-key registry.
60574
+ // Geographic map directives (§24B.2/.7). Cosmetics are ON by default — the
60575
+ // only switches are bare `no-*` opt-outs, surfaced proactively so a
60576
+ // zero-config map still hints at what can be turned off. `poi`/`route` are
60577
+ // content keywords, not directives; metadata keys (value/label/style) live
60578
+ // in the reserved-key registry.
59048
60579
  withGlobals({
59049
- region: {
59050
- description: "Basemap: us-states (force US state mesh + scoping) | world (inert \u2014 already the default)",
59051
- values: ["us-states", "world"]
59052
- },
59053
- projection: {
59054
- description: "Override the auto projection",
59055
- values: ["equirectangular", "natural-earth", "albers-usa", "mercator"]
59056
- },
59057
60580
  "region-metric": { description: "Label for the region value ramp" },
59058
60581
  "poi-metric": {
59059
60582
  description: "Label for the POI value (marker size) channel"
@@ -59061,21 +60584,30 @@ var COMPLETION_REGISTRY = /* @__PURE__ */ new Map([
59061
60584
  "flow-metric": {
59062
60585
  description: "Label for the edge/leg value (thickness) channel"
59063
60586
  },
59064
- scale: { description: "Override value ramp anchors: scale <min> <max>" },
59065
- "region-labels": {
59066
- description: "Subdivision name labels",
59067
- values: ["full", "abbrev", "off"]
60587
+ locale: {
60588
+ description: "Default country/state for bare place names, e.g. locale US-GA"
59068
60589
  },
59069
- "poi-labels": {
59070
- description: "POI labels/values",
59071
- values: ["off", "auto", "all"]
60590
+ "active-tag": {
60591
+ description: "Which tag group leads when several are present"
59072
60592
  },
59073
- "default-country": { description: "ISO scope for bare city resolution" },
59074
- "default-state": { description: "ISO subdivision scope" },
60593
+ caption: { description: "Caption line (data-source attribution)" },
59075
60594
  "no-legend": { description: "Suppress the legend" },
59076
- relief: { description: "Subtle mountain-range relief shading" },
59077
- subtitle: { description: "Subtitle line" },
59078
- caption: { description: "Caption line" }
60595
+ "no-coastline": {
60596
+ description: "Turn off coastal water-lines (on by default)"
60597
+ },
60598
+ "no-relief": {
60599
+ description: "Turn off mountain-range relief shading (on by default)"
60600
+ },
60601
+ "no-context-labels": {
60602
+ description: "Turn off orientation labels for water + nearby countries"
60603
+ },
60604
+ "no-region-labels": {
60605
+ description: "Turn off subdivision name labels (on by default)"
60606
+ },
60607
+ "no-poi-labels": { description: "Turn off POI labels (on by default)" },
60608
+ "no-colorize": {
60609
+ description: "Force plain green-land reference dress (regions are auto-coloured by default)"
60610
+ }
59079
60611
  })
59080
60612
  ]
59081
60613
  ]);
@@ -60545,7 +62077,8 @@ function formatLineDiff(path, original, migrated) {
60545
62077
  applyCollapseProjection,
60546
62078
  applyGroupOrdering,
60547
62079
  applyPositionOverrides,
60548
- boldPalette,
62080
+ atlasPalette,
62081
+ blueprintPalette,
60549
62082
  buildExtendedChartOption,
60550
62083
  buildNoteMessageMap,
60551
62084
  buildRenderSequence,
@@ -60648,6 +62181,8 @@ function formatLineDiff(path, original, migrated) {
60648
62181
  looksLikeState,
60649
62182
  makeDgmoError,
60650
62183
  mapBackgroundColor,
62184
+ mapContentAspect,
62185
+ mapExportDimensions,
60651
62186
  mapNeutralLandColor,
60652
62187
  matchesContiguously,
60653
62188
  measurePertAnalysisBlock,
@@ -60783,9 +62318,11 @@ function formatLineDiff(path, original, migrated) {
60783
62318
  shapeFill,
60784
62319
  simulateCanonical,
60785
62320
  simulateFast,
62321
+ slatePalette,
60786
62322
  solarizedPalette,
60787
62323
  suggestChartTypes,
60788
62324
  themes,
62325
+ tidewaterPalette,
60789
62326
  tint,
60790
62327
  tokyoNightPalette,
60791
62328
  transformLine,