@diagrammo/dgmo 0.19.0 → 0.20.1

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 (48) hide show
  1. package/dist/advanced.cjs +948 -321
  2. package/dist/advanced.d.cts +148 -54
  3. package/dist/advanced.d.ts +148 -54
  4. package/dist/advanced.js +949 -321
  5. package/dist/auto.cjs +930 -317
  6. package/dist/auto.js +117 -117
  7. package/dist/auto.mjs +934 -318
  8. package/dist/cli.cjs +160 -160
  9. package/dist/index.cjs +929 -316
  10. package/dist/index.js +933 -317
  11. package/dist/internal.cjs +948 -321
  12. package/dist/internal.d.cts +148 -54
  13. package/dist/internal.d.ts +148 -54
  14. package/dist/internal.js +949 -321
  15. package/dist/map-data/PROVENANCE.json +1 -1
  16. package/dist/map-data/lakes.json +1 -0
  17. package/dist/map-data/na-lakes.json +1 -0
  18. package/dist/map-data/na-land.json +1 -0
  19. package/dist/map-data/rivers.json +1 -0
  20. package/docs/language-reference.md +12 -7
  21. package/gallery/fixtures/map-region-scope.dgmo +15 -0
  22. package/package.json +4 -4
  23. package/src/advanced.ts +7 -6
  24. package/src/c4/parser.ts +6 -6
  25. package/src/completion.ts +6 -2
  26. package/src/echarts.ts +1 -1
  27. package/src/infra/parser.ts +10 -10
  28. package/src/journey-map/parser.ts +1 -1
  29. package/src/label-layout.ts +36 -0
  30. package/src/map/data/PROVENANCE.json +1 -1
  31. package/src/map/data/README.md +2 -0
  32. package/src/map/data/lakes.json +1 -0
  33. package/src/map/data/na-lakes.json +1 -0
  34. package/src/map/data/na-land.json +1 -0
  35. package/src/map/data/rivers.json +1 -0
  36. package/src/map/layout.ts +1022 -205
  37. package/src/map/load-data.ts +73 -17
  38. package/src/map/parser.ts +22 -13
  39. package/src/map/renderer.ts +200 -219
  40. package/src/map/resolved-types.ts +18 -1
  41. package/src/map/resolver.ts +79 -7
  42. package/src/map/types.ts +4 -0
  43. package/src/mindmap/parser.ts +1 -1
  44. package/src/sitemap/parser.ts +1 -1
  45. package/src/utils/legend-d3.ts +42 -0
  46. package/src/utils/legend-layout.ts +83 -3
  47. package/src/utils/legend-svg.ts +1 -8
  48. package/src/utils/legend-types.ts +44 -1
package/dist/auto.mjs CHANGED
@@ -51,6 +51,33 @@ function rectCircleOverlap(rect, circle) {
51
51
  const dy = nearestY - circle.cy;
52
52
  return dx * dx + dy * dy < circle.r * circle.r;
53
53
  }
54
+ function segmentRectOverlap(x0, y0, x1, y1, rect) {
55
+ const dx = x1 - x0;
56
+ const dy = y1 - y0;
57
+ let t0 = 0;
58
+ let t1 = 1;
59
+ const edges = [
60
+ [-dx, x0 - rect.x],
61
+ [dx, rect.x + rect.w - x0],
62
+ [-dy, y0 - rect.y],
63
+ [dy, rect.y + rect.h - y0]
64
+ ];
65
+ for (const [p, q] of edges) {
66
+ if (p === 0) {
67
+ if (q < 0) return false;
68
+ } else {
69
+ const t = q / p;
70
+ if (p < 0) {
71
+ if (t > t1) return false;
72
+ if (t > t0) t0 = t;
73
+ } else {
74
+ if (t < t0) return false;
75
+ if (t < t1) t1 = t;
76
+ }
77
+ }
78
+ }
79
+ return true;
80
+ }
54
81
  function computeQuadrantPointLabels(points, chartBounds, obstacles, pointRadius, fontSize) {
55
82
  const labelHeight = fontSize + 4;
56
83
  const stepSize = labelHeight + 2;
@@ -3179,6 +3206,57 @@ var init_legend_constants = __esm({
3179
3206
  });
3180
3207
 
3181
3208
  // src/utils/legend-layout.ts
3209
+ function fmtRamp(n) {
3210
+ return Number.isInteger(n) ? String(n) : String(Math.round(n * 10) / 10);
3211
+ }
3212
+ function gradientCapsuleWidth(name, gradient) {
3213
+ const pw = pillWidth(name);
3214
+ const minW = measureLegendText(fmtRamp(gradient.min), LEGEND_ENTRY_FONT_SIZE);
3215
+ const maxW = measureLegendText(fmtRamp(gradient.max), LEGEND_ENTRY_FONT_SIZE);
3216
+ return LEGEND_CAPSULE_PAD + pw + 4 + minW + RAMP_LABEL_GAP + RAMP_LEGEND_W + RAMP_LABEL_GAP + maxW + LEGEND_CAPSULE_PAD;
3217
+ }
3218
+ function buildGradientCapsuleLayout(group, gradient) {
3219
+ const pw = pillWidth(group.name);
3220
+ const minText = fmtRamp(gradient.min);
3221
+ const maxText = fmtRamp(gradient.max);
3222
+ const minW = measureLegendText(minText, LEGEND_ENTRY_FONT_SIZE);
3223
+ const gx = LEGEND_CAPSULE_PAD + pw + 4;
3224
+ const minX = gx;
3225
+ const rampX = gx + minW + RAMP_LABEL_GAP;
3226
+ const maxX = rampX + RAMP_LEGEND_W + RAMP_LABEL_GAP;
3227
+ const width = gradientCapsuleWidth(group.name, gradient);
3228
+ return {
3229
+ groupName: group.name,
3230
+ x: 0,
3231
+ y: 0,
3232
+ width,
3233
+ height: LEGEND_HEIGHT,
3234
+ pill: {
3235
+ groupName: group.name,
3236
+ x: LEGEND_CAPSULE_PAD,
3237
+ y: LEGEND_CAPSULE_PAD,
3238
+ width: pw,
3239
+ height: LEGEND_HEIGHT - LEGEND_CAPSULE_PAD * 2,
3240
+ isActive: true
3241
+ },
3242
+ entries: [],
3243
+ gradient: {
3244
+ rampX,
3245
+ rampY: (LEGEND_HEIGHT - RAMP_LEGEND_H) / 2,
3246
+ rampW: RAMP_LEGEND_W,
3247
+ rampH: RAMP_LEGEND_H,
3248
+ min: gradient.min,
3249
+ max: gradient.max,
3250
+ minText,
3251
+ minX,
3252
+ maxText,
3253
+ maxX,
3254
+ textY: LEGEND_HEIGHT / 2,
3255
+ hue: gradient.hue,
3256
+ base: gradient.base
3257
+ }
3258
+ };
3259
+ }
3182
3260
  function pillWidth(name) {
3183
3261
  return measureLegendText(name, LEGEND_PILL_FONT_SIZE) + LEGEND_PILL_PAD;
3184
3262
  }
@@ -3321,7 +3399,7 @@ function computeLegendLayout(config, state, containerWidth) {
3321
3399
  };
3322
3400
  }
3323
3401
  const controlsGroupLayout = isExport ? void 0 : buildControlsGroupLayout(config, state);
3324
- const visibleGroups = config.showEmptyGroups ? groups : groups.filter((g) => g.entries.length > 0);
3402
+ const visibleGroups = config.showEmptyGroups ? groups : groups.filter((g) => g.entries.length > 0 || !!g.gradient);
3325
3403
  if (visibleGroups.length === 0 && (!configControls || configControls.length === 0) && !controlsGroupLayout) {
3326
3404
  return {
3327
3405
  height: 0,
@@ -3400,7 +3478,7 @@ function computeLegendLayout(config, state, containerWidth) {
3400
3478
  groupAvailW,
3401
3479
  config.capsulePillAddonWidth ?? 0
3402
3480
  );
3403
- } else if (!activeGroupName) {
3481
+ } else if (!activeGroupName || config.showInactivePills) {
3404
3482
  const pw = pillWidth(g.name);
3405
3483
  pills.push({
3406
3484
  groupName: g.name,
@@ -3438,6 +3516,7 @@ function computeLegendLayout(config, state, containerWidth) {
3438
3516
  };
3439
3517
  }
3440
3518
  function buildCapsuleLayout(group, containerWidth, addonWidth = 0) {
3519
+ if (group.gradient) return buildGradientCapsuleLayout(group, group.gradient);
3441
3520
  const pw = pillWidth(group.name);
3442
3521
  const info = capsuleWidth(
3443
3522
  group.name,
@@ -3608,7 +3687,7 @@ function getMaxLegendReservedHeight(config, containerWidth) {
3608
3687
  }
3609
3688
  return max;
3610
3689
  }
3611
- var CONTROL_PILL_PAD, CONTROL_FONT_SIZE, CONTROL_ICON_GAP, CONTROL_GAP;
3690
+ var CONTROL_PILL_PAD, CONTROL_FONT_SIZE, CONTROL_ICON_GAP, CONTROL_GAP, RAMP_LEGEND_W, RAMP_LEGEND_H, RAMP_LABEL_GAP;
3612
3691
  var init_legend_layout = __esm({
3613
3692
  "src/utils/legend-layout.ts"() {
3614
3693
  "use strict";
@@ -3617,6 +3696,9 @@ var init_legend_layout = __esm({
3617
3696
  CONTROL_FONT_SIZE = 11;
3618
3697
  CONTROL_ICON_GAP = 4;
3619
3698
  CONTROL_GAP = 8;
3699
+ RAMP_LEGEND_W = 80;
3700
+ RAMP_LEGEND_H = 8;
3701
+ RAMP_LABEL_GAP = 6;
3620
3702
  }
3621
3703
  });
3622
3704
 
@@ -3704,6 +3786,16 @@ function renderCapsule(parent, capsule, palette, groupBg, pillBorder, _isDark, c
3704
3786
  g.append("rect").attr("x", pill.x).attr("y", pill.y).attr("width", pill.width).attr("height", pill.height).attr("rx", pill.height / 2).attr("fill", palette.bg);
3705
3787
  g.append("rect").attr("x", pill.x).attr("y", pill.y).attr("width", pill.width).attr("height", pill.height).attr("rx", pill.height / 2).attr("fill", "none").attr("stroke", pillBorder).attr("stroke-width", 0.75);
3706
3788
  g.append("text").attr("x", pill.x + pill.width / 2).attr("y", LEGEND_HEIGHT / 2).attr("text-anchor", "middle").attr("dominant-baseline", "central").attr("font-size", LEGEND_PILL_FONT_SIZE).attr("font-weight", 500).attr("fill", palette.text).attr("pointer-events", "none").attr("font-family", FONT_FAMILY).text(capsule.groupName);
3789
+ if (capsule.gradient) {
3790
+ const gr = capsule.gradient;
3791
+ const gradId = `dgmo-legend-ramp-${capsule.groupName.toLowerCase().replace(/[^a-z0-9]+/g, "-")}`;
3792
+ const def = g.append("defs").append("linearGradient").attr("id", gradId);
3793
+ def.append("stop").attr("offset", "0%").attr("stop-color", mix(gr.hue, gr.base, 15));
3794
+ def.append("stop").attr("offset", "100%").attr("stop-color", gr.hue);
3795
+ g.append("text").attr("x", gr.minX).attr("y", gr.textY).attr("dominant-baseline", "central").attr("font-size", LEGEND_ENTRY_FONT_SIZE).attr("fill", palette.textMuted).attr("pointer-events", "none").attr("font-family", FONT_FAMILY).text(gr.minText);
3796
+ g.append("rect").attr("class", "dgmo-legend-gradient-ramp").attr("data-ramp-min", gr.min).attr("data-ramp-max", gr.max).attr("x", gr.rampX).attr("y", gr.rampY).attr("width", gr.rampW).attr("height", gr.rampH).attr("rx", 2).attr("fill", `url(#${gradId})`);
3797
+ g.append("text").attr("x", gr.maxX).attr("y", gr.textY).attr("dominant-baseline", "central").attr("font-size", LEGEND_ENTRY_FONT_SIZE).attr("fill", palette.textMuted).attr("pointer-events", "none").attr("font-family", FONT_FAMILY).text(gr.maxText);
3798
+ }
3707
3799
  for (const entry of capsule.entries) {
3708
3800
  const entryG = g.append("g").attr("data-legend-entry", entry.value.toLowerCase()).attr("data-series-name", entry.value).style("cursor", "pointer");
3709
3801
  entryG.append("circle").attr("cx", entry.dotCx).attr("cy", entry.dotCy).attr("r", LEGEND_DOT_R).attr("fill", entry.color);
@@ -11195,23 +11287,22 @@ function parseC4(content, palette) {
11195
11287
  }
11196
11288
  }
11197
11289
  const parent = findParentElement(indent, stack);
11198
- if (parent) {
11199
- const descResult = tryStripDescriptionKeyword(trimmed);
11290
+ const descResult = tryStripDescriptionKeyword(trimmed);
11291
+ if (parent && descResult.isKeyword) {
11200
11292
  if (descResult.needsColon) {
11201
11293
  pushError(
11202
11294
  lineNumber,
11203
- `Use "description: ${descResult.text}" \u2014 colon is required.`,
11295
+ `Use "description: ${descResult.text}" \u2014 bare "description" is deprecated.`,
11204
11296
  "warning"
11205
11297
  );
11206
11298
  }
11207
- const descText = descResult.isKeyword ? descResult.text : trimmed;
11208
11299
  let desc = elementDescription.get(parent.element);
11209
11300
  if (!desc) {
11210
11301
  desc = [];
11211
11302
  elementDescription.set(parent.element, desc);
11212
11303
  parent.element.description = desc;
11213
11304
  }
11214
- desc.push(descText);
11305
+ desc.push(descResult.text);
11215
11306
  } else {
11216
11307
  pushError(lineNumber, `Unexpected content: "${trimmed}"`);
11217
11308
  }
@@ -11678,7 +11769,7 @@ function parseSitemap(content, palette) {
11678
11769
  if (descResult.needsColon) {
11679
11770
  pushWarning(
11680
11771
  lineNumber,
11681
- `Use "description: ${descResult.text}" \u2014 colon is required.`
11772
+ `Use "description: ${descResult.text}" \u2014 bare "description" is deprecated.`
11682
11773
  );
11683
11774
  }
11684
11775
  const parent = findParentNode(indent, indentStack);
@@ -12506,23 +12597,22 @@ function parseInfra(content) {
12506
12597
  }
12507
12598
  }
12508
12599
  const descResult = tryStripDescriptionKeyword(trimmed);
12509
- if (descResult.isKeyword && currentNode.isEdge) {
12510
- continue;
12511
- }
12512
- if (!currentNode.isEdge) {
12600
+ if (descResult.isKeyword) {
12601
+ if (currentNode.isEdge) {
12602
+ continue;
12603
+ }
12513
12604
  if (descResult.needsColon) {
12514
12605
  warn2(
12515
12606
  lineNumber,
12516
- `Use "description: ${descResult.text}" \u2014 colon is required.`
12607
+ `Use "description: ${descResult.text}" \u2014 bare "description" is deprecated.`
12517
12608
  );
12518
12609
  }
12519
- const descText = descResult.isKeyword ? descResult.text : trimmed;
12520
- pushDescription(currentNode, descText);
12610
+ pushDescription(currentNode, descResult.text);
12521
12611
  continue;
12522
12612
  }
12523
12613
  warn2(
12524
12614
  lineNumber,
12525
- `Unexpected line inside component '${currentNode.label}'. Expected a property (key: value), connection (-> Target), or description text.`
12615
+ `Unexpected line inside component '${currentNode.label}'. Expected a property (key: value), connection (-> Target), or a description (description: text).`
12526
12616
  );
12527
12617
  continue;
12528
12618
  }
@@ -15659,9 +15749,6 @@ function parseMap(content) {
15659
15749
  const pushWarning = (line12, message) => {
15660
15750
  diagnostics.push(makeDgmoError(line12, message, "warning"));
15661
15751
  };
15662
- const pushInfo = (line12, message) => {
15663
- diagnostics.push(makeDgmoError(line12, message, "warning"));
15664
- };
15665
15752
  const lines = content.split("\n");
15666
15753
  let firstIdx = 0;
15667
15754
  while (firstIdx < lines.length) {
@@ -15791,10 +15878,15 @@ function parseMap(content) {
15791
15878
  break;
15792
15879
  case "projection":
15793
15880
  dup(d.projection);
15794
- if (value && !["natural-earth", "albers-usa", "mercator"].includes(value))
15881
+ if (value && ![
15882
+ "equirectangular",
15883
+ "natural-earth",
15884
+ "albers-usa",
15885
+ "mercator"
15886
+ ].includes(value))
15795
15887
  pushWarning(
15796
15888
  line12,
15797
- `Unknown projection "${value}" (expected natural-earth | albers-usa | mercator).`
15889
+ `Unknown projection "${value}" (expected equirectangular | natural-earth | albers-usa | mercator).`
15798
15890
  );
15799
15891
  d.projection = value;
15800
15892
  break;
@@ -15933,17 +16025,21 @@ function parseMap(content) {
15933
16025
  scoreNum = void 0;
15934
16026
  }
15935
16027
  }
15936
- if (scoreNum !== void 0 && Object.keys(tags).length)
15937
- pushInfo(
15938
- line12,
15939
- "A region has both `score:` and a tag value \u2014 v1 renders only the score (bivariate is a future seam)."
15940
- );
16028
+ let regionName = split.name;
16029
+ let regionScope;
16030
+ const rToks = regionName.split(/\s+/);
16031
+ const rLast = rToks[rToks.length - 1];
16032
+ if (rToks.length > 1 && SCOPE_RE.test(rLast)) {
16033
+ regionName = rToks.slice(0, -1).join(" ");
16034
+ regionScope = rLast;
16035
+ }
15941
16036
  const region = {
15942
- name: split.name,
16037
+ name: regionName,
15943
16038
  tags,
15944
16039
  meta,
15945
16040
  lineNumber: line12
15946
16041
  };
16042
+ if (regionScope !== void 0) region.scope = regionScope;
15947
16043
  if (scoreNum !== void 0) region.score = scoreNum;
15948
16044
  regions.push(region);
15949
16045
  }
@@ -17067,7 +17163,7 @@ function parseMindmap(content, palette) {
17067
17163
  if (descResult.needsColon) {
17068
17164
  pushWarning(
17069
17165
  lineNumber,
17070
- `Use "description: ${descResult.text}" \u2014 colon is required.`
17166
+ `Use "description: ${descResult.text}" \u2014 bare "description" is deprecated.`
17071
17167
  );
17072
17168
  }
17073
17169
  const parent = findMetadataParent2(indent, indentStack);
@@ -18855,7 +18951,7 @@ function parseJourneyMap(content, palette) {
18855
18951
  if (descResult.needsColon) {
18856
18952
  warn2(
18857
18953
  lineNumber,
18858
- `Use "description: ${descResult.text}" \u2014 colon is required.`
18954
+ `Use "description: ${descResult.text}" \u2014 bare "description" is deprecated.`
18859
18955
  );
18860
18956
  }
18861
18957
  currentStep.description = descResult.text;
@@ -45444,7 +45540,9 @@ function resolveMap(parsed, data) {
45444
45540
  const usScoped = parsed.directives.region === "us-states" || parsed.directives.defaultCountry?.toUpperCase() === "US" || parsed.regions.some((r) => {
45445
45541
  const f = fold(r.name);
45446
45542
  return usStateIndex.has(f) && !countryIndex.has(f);
45447
- }) || parsed.pois.some(
45543
+ }) || parsed.regions.some(
45544
+ (r) => r.scope === "US" || r.scope?.startsWith("US-")
45545
+ ) || parsed.pois.some(
45448
45546
  (p) => p.pos.kind === "name" && p.pos.scope?.startsWith("US-")
45449
45547
  );
45450
45548
  const regions = [];
@@ -45456,7 +45554,30 @@ function resolveMap(parsed, data) {
45456
45554
  const inCountry = countryIndex.get(f) ?? (REGION_ALIASES[f] ? countryIndex.get(REGION_ALIASES[f]) : void 0);
45457
45555
  const inState = usStateIndex.get(f);
45458
45556
  let chosen = null;
45459
- if (inCountry && inState) {
45557
+ const scope = r.scope;
45558
+ if (scope) {
45559
+ const wantsState = scope === "US" || scope.startsWith("US-");
45560
+ if (wantsState && inState) {
45561
+ if (scope.startsWith("US-") && inState.id !== scope) {
45562
+ err(
45563
+ r.lineNumber,
45564
+ `No subdivision "${r.name}" in scope ${scope} (it is ${inState.id}).`,
45565
+ "E_MAP_SCOPE_MISS"
45566
+ );
45567
+ continue;
45568
+ }
45569
+ chosen = { ...inState, layer: "us-state" };
45570
+ } else if (!wantsState && inCountry) {
45571
+ chosen = { ...inCountry, layer: "country" };
45572
+ } else {
45573
+ err(
45574
+ r.lineNumber,
45575
+ `No region "${r.name}" found in scope ${scope}.`,
45576
+ "E_MAP_SCOPE_MISS"
45577
+ );
45578
+ continue;
45579
+ }
45580
+ } else if (inCountry && inState) {
45460
45581
  if (usScoped) {
45461
45582
  chosen = { ...inState, layer: "us-state" };
45462
45583
  } else {
@@ -45464,7 +45585,7 @@ function resolveMap(parsed, data) {
45464
45585
  }
45465
45586
  warn2(
45466
45587
  r.lineNumber,
45467
- `"${r.name}" is both a country and a US state \u2014 resolved as ${chosen.layer} (${chosen.id}).`,
45588
+ `"${r.name}" is both a country and a US state \u2014 resolved as ${chosen.layer} (${chosen.id}). Pin it with an ISO code (${inState.id} / ${inCountry.id}) or name + scope ("${r.name} US" / "${r.name} ${inCountry.id}").`,
45468
45589
  "W_MAP_REGION_AMBIGUOUS"
45469
45590
  );
45470
45591
  } else if (inState) {
@@ -45636,9 +45757,17 @@ function resolveMap(parsed, data) {
45636
45757
  };
45637
45758
  registerPoi(id, poi, p.lineNumber);
45638
45759
  }
45760
+ const declaredByName = /* @__PURE__ */ new Map();
45761
+ for (const p of pois) {
45762
+ const fn = p.name ? fold(p.name) : void 0;
45763
+ if (fn && fn !== p.id && !declaredByName.has(fn))
45764
+ declaredByName.set(fn, p.id);
45765
+ }
45639
45766
  const resolveEndpoint2 = (ref, line12) => {
45640
45767
  const f = fold(ref);
45641
45768
  if (registry.has(f)) return f;
45769
+ const aliased = declaredByName.get(f);
45770
+ if (aliased) return aliased;
45642
45771
  const got = lookupName(ref, void 0, line12, inferredCountry, true);
45643
45772
  if (got.kind !== "ok") return null;
45644
45773
  noteCountry(got.iso);
@@ -45718,23 +45847,29 @@ function resolveMap(parsed, data) {
45718
45847
  [-180, -85],
45719
45848
  [180, 85]
45720
45849
  ];
45721
- const extent2 = unioned ? pad(unioned, PAD_FRACTION) : DEFAULT_EXTENT;
45850
+ let extent2 = unioned ? pad(unioned, PAD_FRACTION) : DEFAULT_EXTENT;
45722
45851
  const lonSpan = extent2[1][0] - extent2[0][0];
45723
45852
  const latSpan = extent2[1][1] - extent2[0][1];
45724
45853
  const span = Math.max(lonSpan, latSpan);
45725
45854
  const usDominant = (inferredCountry === "US" || subdivisions.includes("us-states")) && !regions.some((r) => r.layer === "country" && r.iso !== "US") && !anyNonUsPoi;
45726
45855
  let projection;
45727
45856
  const override = parsed.directives.projection;
45728
- if (override === "natural-earth" || override === "albers-usa" || override === "mercator") {
45857
+ if (override === "equirectangular" || override === "natural-earth" || override === "albers-usa" || override === "mercator") {
45729
45858
  projection = override;
45730
45859
  } else if (usDominant) {
45731
45860
  projection = "albers-usa";
45732
45861
  } else if (span > WORLD_SPAN) {
45733
- projection = "natural-earth";
45862
+ projection = "equirectangular";
45734
45863
  } else if (span < MERCATOR_MAX_SPAN) {
45735
45864
  projection = "mercator";
45736
45865
  } else {
45737
- projection = "natural-earth";
45866
+ projection = "equirectangular";
45867
+ }
45868
+ if (lonSpan >= 180) {
45869
+ extent2 = [
45870
+ [-180, Math.min(extent2[0][1], WORLD_LAT_SOUTH)],
45871
+ [180, Math.max(extent2[1][1], WORLD_LAT_NORTH)]
45872
+ ];
45738
45873
  }
45739
45874
  result.regions = regions;
45740
45875
  result.pois = pois;
@@ -45782,7 +45917,7 @@ function firstError(diags) {
45782
45917
  const e = diags.find((d) => d.severity === "error");
45783
45918
  return e ? formatDgmoError(e) : null;
45784
45919
  }
45785
- var WORLD_SPAN, MERCATOR_MAX_SPAN, PAD_FRACTION, REGION_ALIASES;
45920
+ var WORLD_SPAN, MERCATOR_MAX_SPAN, PAD_FRACTION, WORLD_LAT_SOUTH, WORLD_LAT_NORTH, REGION_ALIASES;
45786
45921
  var init_resolver2 = __esm({
45787
45922
  "src/map/resolver.ts"() {
45788
45923
  "use strict";
@@ -45791,6 +45926,8 @@ var init_resolver2 = __esm({
45791
45926
  WORLD_SPAN = 90;
45792
45927
  MERCATOR_MAX_SPAN = 25;
45793
45928
  PAD_FRACTION = 0.05;
45929
+ WORLD_LAT_SOUTH = -58;
45930
+ WORLD_LAT_NORTH = 78;
45794
45931
  REGION_ALIASES = {
45795
45932
  // Common everyday names → the Natural-Earth display name actually shipped.
45796
45933
  "united states": "united states of america",
@@ -45820,17 +45957,22 @@ var load_data_exports = {};
45820
45957
  __export(load_data_exports, {
45821
45958
  loadMapData: () => loadMapData
45822
45959
  });
45823
- import { readFile } from "fs/promises";
45824
- import { fileURLToPath } from "url";
45825
- import { dirname, resolve } from "path";
45826
- async function readJson(dir, name) {
45827
- return JSON.parse(await readFile(resolve(dir, name), "utf8"));
45960
+ async function loadNodeBuiltins() {
45961
+ const [{ readFile }, { fileURLToPath }, { dirname, resolve }] = await Promise.all([
45962
+ import("fs/promises"),
45963
+ import("url"),
45964
+ import("path")
45965
+ ]);
45966
+ return { readFile, fileURLToPath, dirname, resolve };
45967
+ }
45968
+ async function readJson(nb, dir, name) {
45969
+ return JSON.parse(await nb.readFile(nb.resolve(dir, name), "utf8"));
45828
45970
  }
45829
- async function firstExistingDir(baseDir) {
45971
+ async function firstExistingDir(nb, baseDir) {
45830
45972
  for (const rel of CANDIDATE_DIRS) {
45831
- const dir = resolve(baseDir, rel);
45973
+ const dir = nb.resolve(baseDir, rel);
45832
45974
  try {
45833
- await readFile(resolve(dir, FILES.gazetteer), "utf8");
45975
+ await nb.readFile(nb.resolve(dir, FILES.gazetteer), "utf8");
45834
45976
  return dir;
45835
45977
  } catch {
45836
45978
  }
@@ -45846,10 +45988,10 @@ function validate(data) {
45846
45988
  }
45847
45989
  return data;
45848
45990
  }
45849
- function moduleBaseDir() {
45991
+ function moduleBaseDir(nb) {
45850
45992
  try {
45851
45993
  const url = import.meta.url;
45852
- if (url) return dirname(fileURLToPath(url));
45994
+ if (url) return nb.dirname(nb.fileURLToPath(url));
45853
45995
  } catch {
45854
45996
  }
45855
45997
  if (typeof __dirname !== "undefined") return __dirname;
@@ -45857,14 +45999,38 @@ function moduleBaseDir() {
45857
45999
  }
45858
46000
  function loadMapData() {
45859
46001
  cache ??= (async () => {
45860
- const dir = await firstExistingDir(moduleBaseDir());
45861
- const [worldCoarse, worldDetail, usStates, gazetteer] = await Promise.all([
45862
- readJson(dir, FILES.worldCoarse),
45863
- readJson(dir, FILES.worldDetail),
45864
- readJson(dir, FILES.usStates),
45865
- readJson(dir, FILES.gazetteer)
46002
+ const nb = await loadNodeBuiltins();
46003
+ const dir = await firstExistingDir(nb, moduleBaseDir(nb));
46004
+ const [
46005
+ worldCoarse,
46006
+ worldDetail,
46007
+ usStates,
46008
+ lakes,
46009
+ rivers,
46010
+ naLand,
46011
+ naLakes,
46012
+ gazetteer
46013
+ ] = await Promise.all([
46014
+ readJson(nb, dir, FILES.worldCoarse),
46015
+ readJson(nb, dir, FILES.worldDetail),
46016
+ readJson(nb, dir, FILES.usStates),
46017
+ // Lakes/rivers/NA assets are optional — older bundles may predate them.
46018
+ readJson(nb, dir, FILES.lakes).catch(() => void 0),
46019
+ readJson(nb, dir, FILES.rivers).catch(() => void 0),
46020
+ readJson(nb, dir, FILES.naLand).catch(() => void 0),
46021
+ readJson(nb, dir, FILES.naLakes).catch(() => void 0),
46022
+ readJson(nb, dir, FILES.gazetteer)
45866
46023
  ]);
45867
- return validate({ worldCoarse, worldDetail, usStates, gazetteer });
46024
+ return validate({
46025
+ worldCoarse,
46026
+ worldDetail,
46027
+ usStates,
46028
+ gazetteer,
46029
+ ...lakes && { lakes },
46030
+ ...rivers && { rivers },
46031
+ ...naLand && { naLand },
46032
+ ...naLakes && { naLakes }
46033
+ });
45868
46034
  })().catch((e) => {
45869
46035
  cache = void 0;
45870
46036
  throw e;
@@ -45879,6 +46045,10 @@ var init_load_data = __esm({
45879
46045
  worldCoarse: "world-coarse.json",
45880
46046
  worldDetail: "world-detail.json",
45881
46047
  usStates: "us-states.json",
46048
+ lakes: "lakes.json",
46049
+ rivers: "rivers.json",
46050
+ naLand: "na-land.json",
46051
+ naLakes: "na-lakes.json",
45882
46052
  gazetteer: "gazetteer.json"
45883
46053
  };
45884
46054
  CANDIDATE_DIRS = [
@@ -45894,8 +46064,11 @@ var init_load_data = __esm({
45894
46064
  import {
45895
46065
  geoPath,
45896
46066
  geoNaturalEarth1,
45897
- geoAlbersUsa,
45898
- geoMercator
46067
+ geoEquirectangular,
46068
+ geoConicEqualArea,
46069
+ geoMercator,
46070
+ geoBounds as geoBounds2,
46071
+ geoTransform
45899
46072
  } from "d3-geo";
45900
46073
  import { feature as feature2 } from "topojson-client";
45901
46074
  function geomObject2(topo) {
@@ -45913,36 +46086,67 @@ function decodeLayer(topo) {
45913
46086
  function projectionFor(family) {
45914
46087
  switch (family) {
45915
46088
  case "albers-usa":
45916
- return geoAlbersUsa();
46089
+ return usConusProjection();
45917
46090
  case "mercator":
45918
46091
  return geoMercator();
45919
46092
  case "natural-earth":
45920
- default:
45921
46093
  return geoNaturalEarth1();
46094
+ case "equirectangular":
46095
+ default:
46096
+ return geoEquirectangular();
45922
46097
  }
45923
46098
  }
46099
+ function mapBackgroundColor(palette) {
46100
+ return mix(palette.colors.blue, palette.bg, WATER_TINT);
46101
+ }
45924
46102
  function layoutMap(resolved, data, size, opts) {
45925
46103
  const { palette, isDark } = opts;
45926
46104
  const { width, height } = size;
45927
- const worldTopo = resolved.basemaps.world === "detail" ? data.worldDetail : data.worldCoarse;
46105
+ const wantsUsStates = resolved.basemaps.subdivisions.includes("us-states");
46106
+ const usCrisp = resolved.projection === "albers-usa" && wantsUsStates && !!data.naLand;
46107
+ const worldTopo = usCrisp ? data.naLand : resolved.basemaps.world === "detail" ? data.worldDetail : data.worldCoarse;
45928
46108
  const worldLayer = decodeLayer(worldTopo);
45929
- const usLayer = resolved.basemaps.subdivisions.includes("us-states") ? decodeLayer(data.usStates) : null;
45930
- const neutralFill = mix(palette.border, palette.bg, 32);
45931
- const regionStroke = mix(palette.border, palette.bg, 70);
46109
+ const usLayer = wantsUsStates ? decodeLayer(data.usStates) : null;
46110
+ const landTint = isDark ? LAND_TINT_DARK : LAND_TINT_LIGHT;
46111
+ const neutralFill = mix(palette.colors.green, palette.bg, landTint);
46112
+ const water = mapBackgroundColor(palette);
46113
+ const usContext = usLayer !== null;
46114
+ const foreignFill = mix(
46115
+ palette.colors.gray,
46116
+ palette.bg,
46117
+ isDark ? FOREIGN_TINT_DARK : FOREIGN_TINT_LIGHT
46118
+ );
46119
+ const regionStroke = isDark ? mix(palette.bg, palette.text, 78) : mix(palette.text, palette.bg, 78);
45932
46120
  const scores = resolved.regions.filter((r) => r.score !== void 0).map((r) => r.score);
45933
46121
  const scaleOverride = resolved.directives.scale;
45934
46122
  const rampMin = scaleOverride ? scaleOverride.min : Math.min(...scores);
45935
46123
  const rampMax = scaleOverride ? scaleOverride.max : Math.max(...scores);
45936
- const rampHue = palette.primary;
46124
+ const rampHue = palette.colors.red;
45937
46125
  const hasRamp = scores.length > 0;
45938
- const activeGroup = resolveActiveTagGroup(
45939
- resolved.tagGroups,
45940
- resolved.directives.activeTag
45941
- );
46126
+ const SCORE_NAME = hasRamp ? resolved.directives.metric?.trim() || "Score" : null;
46127
+ const matchColorGroup = (v) => {
46128
+ const lv = v.trim().toLowerCase();
46129
+ if (lv === "none") return null;
46130
+ if (SCORE_NAME && (lv === "score" || lv === SCORE_NAME.toLowerCase()))
46131
+ return SCORE_NAME;
46132
+ const tg = resolved.tagGroups.find((g) => g.name.toLowerCase() === lv);
46133
+ return tg ? tg.name : v;
46134
+ };
46135
+ const override = opts.activeGroup;
46136
+ let activeGroup;
46137
+ if (override !== void 0) {
46138
+ activeGroup = override === null ? null : matchColorGroup(override);
46139
+ } else if (resolved.directives.activeTag !== void 0) {
46140
+ activeGroup = matchColorGroup(resolved.directives.activeTag);
46141
+ } else {
46142
+ activeGroup = SCORE_NAME ?? (resolved.tagGroups.length > 0 ? resolved.tagGroups[0].name : null);
46143
+ }
46144
+ const activeIsScore = SCORE_NAME !== null && activeGroup === SCORE_NAME;
46145
+ const rampBase = isDark ? mix(palette.surface, palette.text, 28) : palette.bg;
45942
46146
  const fillForScore = (s) => {
45943
46147
  const t = rampMax > rampMin ? (s - rampMin) / (rampMax - rampMin) : 1;
45944
46148
  const pct = RAMP_FLOOR + Math.max(0, Math.min(1, t)) * (100 - RAMP_FLOOR);
45945
- return mix(rampHue, isDark ? palette.surface : palette.bg, pct);
46149
+ return mix(rampHue, rampBase, pct);
45946
46150
  };
45947
46151
  const tagFill = (tags, groupName) => {
45948
46152
  if (!groupName) return null;
@@ -45956,40 +46160,40 @@ function layoutMap(resolved, data, size, opts) {
45956
46160
  (e) => e.value.toLowerCase() === val.toLowerCase()
45957
46161
  );
45958
46162
  if (!entry?.color) return null;
45959
- return shapeFill(palette, entry.color, isDark);
46163
+ return mix(
46164
+ entry.color,
46165
+ palette.bg,
46166
+ isDark ? TAG_TINT_DARK : TAG_TINT_LIGHT
46167
+ );
46168
+ };
46169
+ const regionFill = (r) => {
46170
+ if (activeIsScore) {
46171
+ return r.score !== void 0 ? fillForScore(r.score) : neutralFill;
46172
+ }
46173
+ return tagFill(r.tags, activeGroup) ?? neutralFill;
45960
46174
  };
45961
46175
  const regionById = new Map(resolved.regions.map((r) => [r.iso, r]));
45962
- const regionFeatures = [];
45963
- for (const r of resolved.regions) {
45964
- const f = r.layer === "us-state" ? usLayer?.get(r.iso) : worldLayer.get(r.iso);
45965
- if (f) regionFeatures.push(f);
45966
- }
45967
- const extentCorners = () => {
46176
+ const extentOutline = () => {
45968
46177
  const [[w, s], [e, n]] = resolved.extent;
46178
+ const N = 16;
46179
+ const coords = [];
46180
+ for (let i = 0; i <= N; i++) {
46181
+ const t = i / N;
46182
+ const lon = w + (e - w) * t;
46183
+ const lat = s + (n - s) * t;
46184
+ coords.push([lon, s], [lon, n], [w, lat], [e, lat]);
46185
+ }
45969
46186
  return {
45970
46187
  type: "Feature",
45971
46188
  properties: {},
45972
- geometry: {
45973
- type: "MultiPoint",
45974
- coordinates: [
45975
- [w, s],
45976
- [e, s],
45977
- [e, n],
45978
- [w, n]
45979
- ]
45980
- }
46189
+ geometry: { type: "MultiPoint", coordinates: coords }
45981
46190
  };
45982
46191
  };
45983
46192
  let fitFeatures;
45984
- if (resolved.projection === "albers-usa") {
45985
- if (regionFeatures.length > 0) fitFeatures = regionFeatures;
45986
- else if (usLayer) fitFeatures = [...usLayer.values()];
45987
- else {
45988
- const us = worldLayer.get("US");
45989
- fitFeatures = us ? [us] : [...worldLayer.values()];
45990
- }
46193
+ if (resolved.projection === "albers-usa" && usLayer) {
46194
+ fitFeatures = [...usLayer.entries()].filter(([iso]) => !US_NON_CONUS.has(iso)).map(([, f]) => f);
45991
46195
  } else {
45992
- fitFeatures = [extentCorners()];
46196
+ fitFeatures = [extentOutline()];
45993
46197
  }
45994
46198
  const fitTarget = { type: "FeatureCollection", features: fitFeatures };
45995
46199
  const projection = projectionFor(resolved.projection);
@@ -45998,32 +46202,311 @@ function layoutMap(resolved, data, size, opts) {
45998
46202
  if (centerLon > 180) centerLon -= 360;
45999
46203
  projection.rotate([-centerLon, 0]);
46000
46204
  }
46001
- projection.fitExtent(
46205
+ const TITLE_GAP = 16;
46206
+ let topPad = FIT_PAD;
46207
+ if (resolved.title && resolved.pois.length > 0) {
46208
+ const bannerBottom = (resolved.subtitle ? TITLE_Y + TITLE_FONT_SIZE : TITLE_Y) + TITLE_FONT_SIZE / 2;
46209
+ topPad = Math.max(FIT_PAD, bannerBottom + TITLE_GAP);
46210
+ }
46211
+ const fitBox = [
46212
+ [FIT_PAD, topPad],
46002
46213
  [
46003
- [FIT_PAD, FIT_PAD],
46004
- [
46005
- Math.max(FIT_PAD + 1, width - FIT_PAD),
46006
- Math.max(FIT_PAD + 1, height - FIT_PAD)
46007
- ]
46008
- ],
46009
- fitTarget
46010
- );
46011
- const path = geoPath(projection);
46012
- const project = (lon, lat) => projection([lon, lat]) ?? null;
46214
+ Math.max(FIT_PAD + 1, width - FIT_PAD),
46215
+ Math.max(topPad + 1, height - FIT_PAD)
46216
+ ]
46217
+ ];
46218
+ projection.fitExtent(fitBox, fitTarget);
46219
+ const fitGB = geoBounds2(fitTarget);
46220
+ const fitIsGlobal = fitGB[1][0] - fitGB[0][0] >= 270 || fitGB[1][1] - fitGB[0][1] >= 130;
46221
+ let path;
46222
+ let project;
46223
+ if (fitIsGlobal) {
46224
+ const cb = geoPath(projection).bounds(fitTarget);
46225
+ const bx0 = cb[0][0];
46226
+ const by0 = cb[0][1];
46227
+ const cw = cb[1][0] - bx0;
46228
+ const ch = cb[1][1] - by0;
46229
+ const ox = fitBox[0][0];
46230
+ const oy = fitBox[0][1];
46231
+ const sx = cw > 0 ? (fitBox[1][0] - ox) / cw : 1;
46232
+ const sy = ch > 0 ? (fitBox[1][1] - oy) / ch : 1;
46233
+ const stretch = (x, y) => [
46234
+ ox + (x - bx0) * sx,
46235
+ oy + (y - by0) * sy
46236
+ ];
46237
+ const baseProjection = projection;
46238
+ const tx = geoTransform({
46239
+ point(x, y) {
46240
+ const [px, py] = stretch(x, y);
46241
+ this.stream.point(px, py);
46242
+ }
46243
+ });
46244
+ path = geoPath({
46245
+ stream: (s) => baseProjection.stream(
46246
+ tx.stream(s)
46247
+ )
46248
+ });
46249
+ project = (lon, lat) => {
46250
+ const p = baseProjection([lon, lat]);
46251
+ return p ? stretch(p[0], p[1]) : null;
46252
+ };
46253
+ } else {
46254
+ path = geoPath(projection);
46255
+ project = (lon, lat) => projection([lon, lat]) ?? null;
46256
+ }
46257
+ const insets = [];
46258
+ const insetRegions = [];
46259
+ const insetLabelSeeds = [];
46260
+ if (resolved.projection === "albers-usa" && usLayer) {
46261
+ const PAD = 8;
46262
+ const GAP = 12;
46263
+ const yB = height - FIT_PAD;
46264
+ const BW = 8;
46265
+ const coast = /* @__PURE__ */ new Map();
46266
+ const addPt = (lon, lat) => {
46267
+ const p = projection([lon, lat]);
46268
+ if (!p) return;
46269
+ const bi = Math.floor(p[0] / BW);
46270
+ const cur = coast.get(bi);
46271
+ if (cur === void 0 || p[1] > cur) coast.set(bi, p[1]);
46272
+ };
46273
+ const walk = (co) => {
46274
+ if (Array.isArray(co) && typeof co[0] === "number")
46275
+ addPt(co[0], co[1]);
46276
+ else if (Array.isArray(co)) for (const c of co) walk(c);
46277
+ };
46278
+ for (const [iso, f] of usLayer) {
46279
+ if (US_NON_CONUS.has(iso)) continue;
46280
+ walk(f.geometry.coordinates);
46281
+ }
46282
+ const at = (x) => {
46283
+ const bi = Math.floor(x / BW);
46284
+ let y = -Infinity;
46285
+ for (let k = bi - 1; k <= bi + 1; k++) {
46286
+ const v = coast.get(k);
46287
+ if (v !== void 0 && v > y) y = v;
46288
+ }
46289
+ return y;
46290
+ };
46291
+ const coastTop = (x0, xr) => {
46292
+ const n = 24;
46293
+ const pts = [];
46294
+ let maxY = -Infinity;
46295
+ for (let i = 0; i <= n; i++) {
46296
+ const x = x0 + (xr - x0) * i / n;
46297
+ const y = at(x);
46298
+ if (y > -Infinity) {
46299
+ pts.push([x, y]);
46300
+ if (y > maxY) maxY = y;
46301
+ }
46302
+ }
46303
+ if (pts.length === 0) return () => yB - height * 0.42;
46304
+ let m = 0;
46305
+ if (pts.length >= 2) {
46306
+ let sx = 0, sy = 0, sxx = 0, sxy = 0;
46307
+ for (const [x, y] of pts) {
46308
+ sx += x;
46309
+ sy += y;
46310
+ sxx += x * x;
46311
+ sxy += x * y;
46312
+ }
46313
+ const den = pts.length * sxx - sx * sx;
46314
+ if (den !== 0) m = (pts.length * sxy - sx * sy) / den;
46315
+ }
46316
+ m = Math.max(-0.35, Math.min(0.35, m));
46317
+ let c = -Infinity;
46318
+ for (const [x, y] of pts) {
46319
+ const need = y - m * x + GAP;
46320
+ if (need > c) c = need;
46321
+ }
46322
+ return (x) => m * x + c;
46323
+ };
46324
+ const placeInset = (iso, proj, boxX, iwReq) => {
46325
+ const f = usLayer.get(iso);
46326
+ if (!f) return boxX;
46327
+ const x0 = boxX;
46328
+ const iw = Math.min(iwReq, width - FIT_PAD - x0 - 2 * PAD);
46329
+ if (iw < 24) return boxX;
46330
+ const xr = x0 + iw + 2 * PAD;
46331
+ const top = coastTop(x0, xr);
46332
+ const yL = top(x0);
46333
+ const yR = top(xr);
46334
+ proj.fitWidth(iw, f);
46335
+ const bb = geoPath(proj).bounds(f);
46336
+ const sh = Number.isFinite(bb[0][0]) ? bb[1][1] - bb[0][1] : iw;
46337
+ const needH = sh + 2 * PAD;
46338
+ let topFit = Math.max(yL, yR);
46339
+ const bottom = Math.min(topFit + needH, yB);
46340
+ if (bottom - topFit < needH) topFit = bottom - needH;
46341
+ const lift = topFit - Math.max(yL, yR);
46342
+ const topL = yL + lift;
46343
+ const topR = yR + lift;
46344
+ proj.fitExtent(
46345
+ [
46346
+ [x0 + PAD, topFit + PAD],
46347
+ [xr - PAD, bottom - PAD]
46348
+ ],
46349
+ f
46350
+ );
46351
+ const d = geoPath(proj)(f) ?? "";
46352
+ if (!d) return xr;
46353
+ const r = regionById.get(iso);
46354
+ let fill2 = neutralFill;
46355
+ let lineNumber = -1;
46356
+ if (r?.layer === "us-state") {
46357
+ fill2 = regionFill(r);
46358
+ lineNumber = r.lineNumber;
46359
+ }
46360
+ insets.push({
46361
+ x: x0,
46362
+ y: Math.min(topL, topR),
46363
+ w: xr - x0,
46364
+ h: bottom - Math.min(topL, topR),
46365
+ points: [
46366
+ [x0, topL],
46367
+ [xr, topR],
46368
+ [xr, bottom],
46369
+ [x0, bottom]
46370
+ ]
46371
+ });
46372
+ insetRegions.push({
46373
+ id: iso,
46374
+ d,
46375
+ fill: fill2,
46376
+ stroke: regionStroke,
46377
+ lineNumber,
46378
+ layer: "us-state",
46379
+ ...r?.score !== void 0 && { score: r.score },
46380
+ ...r && Object.keys(r.tags).length > 0 && { tags: r.tags }
46381
+ });
46382
+ const ctr = geoPath(proj).centroid(f);
46383
+ if (Number.isFinite(ctr[0])) {
46384
+ const name = f.properties?.name ?? iso;
46385
+ insetLabelSeeds.push({ x: ctr[0], y: ctr[1], iso, name, lineNumber });
46386
+ }
46387
+ return xr;
46388
+ };
46389
+ const akRight = placeInset(
46390
+ "US-AK",
46391
+ alaskaProjection(),
46392
+ FIT_PAD,
46393
+ width * 0.15
46394
+ );
46395
+ placeInset("US-HI", hawaiiProjection(), akRight + 24, width * 0.1);
46396
+ }
46397
+ const conusFit = resolved.projection === "albers-usa" && !!usLayer;
46398
+ const cullExtent = conusFit ? geoBounds2(fitTarget) : resolved.extent;
46399
+ const [[exW, exS], [exE, exN]] = cullExtent;
46400
+ const lonSpan = exE - exW;
46401
+ const latSpan = exN - exS;
46402
+ const isGlobalView = lonSpan >= 270 || latSpan >= 130;
46403
+ const padLon = Math.max(8, lonSpan * 0.35);
46404
+ const padLat = Math.max(8, latSpan * 0.35);
46405
+ const vW = exW - padLon;
46406
+ const vE = exE + padLon;
46407
+ const vS = exS - padLat;
46408
+ const vN = exN + padLat;
46409
+ const vLonCenter = (exW + exE) / 2;
46410
+ const normLon = (lon) => {
46411
+ let L = lon;
46412
+ while (L < vLonCenter - 180) L += 360;
46413
+ while (L > vLonCenter + 180) L -= 360;
46414
+ return L;
46415
+ };
46416
+ const ringOverlapsView = (ring) => {
46417
+ let anyIn = false;
46418
+ let loMin = Infinity, loMax = -Infinity, laMin = Infinity, laMax = -Infinity, rawMin = Infinity, rawMax = -Infinity;
46419
+ for (const [rawLon, lat] of ring) {
46420
+ const lon = normLon(rawLon);
46421
+ if (lon >= vW && lon <= vE && lat >= vS && lat <= vN) anyIn = true;
46422
+ if (lon < loMin) loMin = lon;
46423
+ if (lon > loMax) loMax = lon;
46424
+ if (rawLon < rawMin) rawMin = rawLon;
46425
+ if (rawLon > rawMax) rawMax = rawLon;
46426
+ if (lat < laMin) laMin = lat;
46427
+ if (lat > laMax) laMax = lat;
46428
+ }
46429
+ if (loMax - loMin > 270) return false;
46430
+ if (rawMax - rawMin > 180 && loMax - loMin < 90) return false;
46431
+ if (anyIn) return true;
46432
+ if (loMax - loMin > 180) return false;
46433
+ return !(loMax < vW || loMin > vE || laMax < vS || laMin > vN);
46434
+ };
46435
+ const cullFeatureToView = (f) => {
46436
+ if (isGlobalView) return f;
46437
+ const g = f.geometry;
46438
+ if (!g) return f;
46439
+ if (g.type === "Polygon") {
46440
+ const ring = g.coordinates[0];
46441
+ return ringOverlapsView(ring) ? f : null;
46442
+ }
46443
+ if (g.type === "MultiPolygon") {
46444
+ const polys = g.coordinates;
46445
+ const keep = polys.filter(
46446
+ (p) => ringOverlapsView(p[0])
46447
+ );
46448
+ if (!keep.length) return null;
46449
+ if (keep.length === polys.length) return f;
46450
+ return { ...f, geometry: { ...g, coordinates: keep } };
46451
+ }
46452
+ return f;
46453
+ };
46454
+ const SEAM_SLIVER_MAX_SPAN = 100;
46455
+ const ringIsFrameFiller = (ring) => {
46456
+ const lons = ring.map(([lon]) => lon).sort((a, b) => a - b);
46457
+ if (lons.length < 2) return false;
46458
+ let maxGap = -1;
46459
+ let gapIdx = 0;
46460
+ for (let i = 1; i < lons.length; i++) {
46461
+ const g = lons[i] - lons[i - 1];
46462
+ if (g > maxGap) {
46463
+ maxGap = g;
46464
+ gapIdx = i;
46465
+ }
46466
+ }
46467
+ const wrapGap = lons[0] + 360 - lons[lons.length - 1];
46468
+ if (wrapGap >= maxGap) return false;
46469
+ const span = 360 - maxGap;
46470
+ const east = lons[gapIdx - 1] + 360;
46471
+ return east > 180 && span < SEAM_SLIVER_MAX_SPAN;
46472
+ };
46473
+ const dropFrameFillers = (f) => {
46474
+ const g = f.geometry;
46475
+ if (!g) return f;
46476
+ if (g.type === "Polygon") {
46477
+ const ring = g.coordinates[0];
46478
+ return ringIsFrameFiller(ring) ? null : f;
46479
+ }
46480
+ if (g.type === "MultiPolygon") {
46481
+ const polys = g.coordinates;
46482
+ const keep = polys.filter(
46483
+ (p) => !ringIsFrameFiller(p[0])
46484
+ );
46485
+ if (!keep.length) return null;
46486
+ if (keep.length === polys.length) return f;
46487
+ return { ...f, geometry: { ...g, coordinates: keep } };
46488
+ }
46489
+ return f;
46490
+ };
46013
46491
  const regions = [];
46014
- const pushRegionLayer = (layerFeatures, layerKind) => {
46492
+ const pushRegionLayer = (layerFeatures, layerKind, shouldCull) => {
46015
46493
  for (const [iso, f] of layerFeatures) {
46016
- const d = path(f) ?? "";
46017
- if (!d) continue;
46494
+ if (layerKind === "us-state" && usContext && INSET_STATES.has(iso))
46495
+ continue;
46496
+ if (layerKind === "country" && usContext && iso === "US") continue;
46018
46497
  const r = regionById.get(iso);
46498
+ const viewF = shouldCull ? cullFeatureToView(f) : dropFrameFillers(f);
46499
+ if (!viewF) continue;
46500
+ const d = path(viewF) ?? "";
46501
+ if (!d) continue;
46019
46502
  const isThisLayer = r?.layer === layerKind;
46020
- let fill2 = neutralFill;
46503
+ const isForeign = layerKind === "country" && usContext && iso !== "US";
46504
+ let fill2 = isForeign ? foreignFill : neutralFill;
46021
46505
  let label;
46022
46506
  let lineNumber = -1;
46023
46507
  let layer = "base";
46024
46508
  if (isThisLayer) {
46025
- if (r.score !== void 0) fill2 = fillForScore(r.score);
46026
- else fill2 = tagFill(r.tags, activeGroup) ?? neutralFill;
46509
+ fill2 = regionFill(r);
46027
46510
  lineNumber = r.lineNumber;
46028
46511
  layer = layerKind;
46029
46512
  label = r.name;
@@ -46035,12 +46518,42 @@ function layoutMap(resolved, data, size, opts) {
46035
46518
  stroke: regionStroke,
46036
46519
  lineNumber,
46037
46520
  layer,
46038
- ...label !== void 0 && { label }
46521
+ ...label !== void 0 && { label },
46522
+ ...isThisLayer && r.score !== void 0 && { score: r.score },
46523
+ ...isThisLayer && Object.keys(r.tags).length > 0 && { tags: r.tags }
46039
46524
  });
46040
46525
  }
46041
46526
  };
46042
- pushRegionLayer(worldLayer, "country");
46043
- if (usLayer) pushRegionLayer(usLayer, "us-state");
46527
+ pushRegionLayer(worldLayer, "country", !isGlobalView);
46528
+ if (usLayer) pushRegionLayer(usLayer, "us-state", !conusFit && !isGlobalView);
46529
+ const lakesTopo = usCrisp && data.naLakes ? data.naLakes : data.lakes;
46530
+ if (lakesTopo) {
46531
+ for (const [, f] of decodeLayer(lakesTopo)) {
46532
+ const viewF = isGlobalView ? dropFrameFillers(f) : cullFeatureToView(f);
46533
+ if (!viewF) continue;
46534
+ const d = path(viewF) ?? "";
46535
+ if (!d) continue;
46536
+ regions.push({
46537
+ id: "lake",
46538
+ d,
46539
+ fill: water,
46540
+ stroke: "none",
46541
+ lineNumber: -1,
46542
+ layer: "base"
46543
+ });
46544
+ }
46545
+ }
46546
+ const riverColor = water;
46547
+ const rivers = [];
46548
+ if (data.rivers) {
46549
+ for (const [, f] of decodeLayer(data.rivers)) {
46550
+ const viewF = isGlobalView ? dropFrameFillers(f) : cullFeatureToView(f);
46551
+ if (!viewF) continue;
46552
+ const d = path(viewF) ?? "";
46553
+ if (!d) continue;
46554
+ rivers.push({ d, color: riverColor, width: RIVER_WIDTH });
46555
+ }
46556
+ }
46044
46557
  const sizeVals = resolved.pois.map((p) => Number(p.meta["size"])).filter((n) => Number.isFinite(n) && n > 0);
46045
46558
  const sizeMin = sizeVals.length ? Math.min(...sizeVals) : 0;
46046
46559
  const sizeMax = sizeVals.length ? Math.max(...sizeVals) : 0;
@@ -46061,8 +46574,8 @@ function layoutMap(resolved, data, size, opts) {
46061
46574
  if (hex) return { fill: hex, stroke: mix(hex, palette.text, 18) };
46062
46575
  }
46063
46576
  return {
46064
- fill: palette.accent,
46065
- stroke: mix(palette.accent, palette.text, 18)
46577
+ fill: palette.colors.orange,
46578
+ stroke: mix(palette.colors.orange, palette.text, 18)
46066
46579
  };
46067
46580
  };
46068
46581
  const routeNumberById = /* @__PURE__ */ new Map();
@@ -46100,7 +46613,7 @@ function layoutMap(resolved, data, size, opts) {
46100
46613
  cy += Math.sin(ang) * COLO_R;
46101
46614
  }
46102
46615
  const { fill: fill2, stroke: stroke2 } = poiFill(e.p);
46103
- poiScreen.set(e.p.id, { cx, cy });
46616
+ poiScreen.set(e.p.id, { cx, cy, r: radiusFor(e.p) });
46104
46617
  const num = routeNumberById.get(e.p.id);
46105
46618
  pois.push({
46106
46619
  id: e.p.id,
@@ -46117,17 +46630,36 @@ function layoutMap(resolved, data, size, opts) {
46117
46630
  });
46118
46631
  }
46119
46632
  const legs = [];
46633
+ const RIM_GAP = 1.5;
46120
46634
  const legPath = (a, b, curved, offset) => {
46121
- if (!curved && offset === 0) return `M${a.cx},${a.cy}L${b.cx},${b.cy}`;
46122
46635
  const mx = (a.cx + b.cx) / 2;
46123
46636
  const my = (a.cy + b.cy) / 2;
46124
46637
  const dx = b.cx - a.cx;
46125
46638
  const dy = b.cy - a.cy;
46126
46639
  const len = Math.hypot(dx, dy) || 1;
46640
+ const trimA = Math.min(a.r + RIM_GAP, len * 0.45);
46641
+ const trimB = Math.min(b.r + RIM_GAP, len * 0.45);
46642
+ if (!curved && offset === 0) {
46643
+ const ux = dx / len;
46644
+ const uy = dy / len;
46645
+ const ax2 = a.cx + ux * trimA;
46646
+ const ay2 = a.cy + uy * trimA;
46647
+ const bx2 = b.cx - ux * trimB;
46648
+ const by2 = b.cy - uy * trimB;
46649
+ return `M${ax2},${ay2}L${bx2},${by2}`;
46650
+ }
46127
46651
  const nx = -dy / len;
46128
46652
  const ny = dx / len;
46129
46653
  const bow = offset !== 0 ? offset : len * ARC_CURVE_FRAC;
46130
- return `M${a.cx},${a.cy}Q${mx + nx * bow},${my + ny * bow} ${b.cx},${b.cy}`;
46654
+ const px = mx + nx * bow;
46655
+ const py = my + ny * bow;
46656
+ const ta = Math.hypot(px - a.cx, py - a.cy) || 1;
46657
+ const tb = Math.hypot(b.cx - px, b.cy - py) || 1;
46658
+ const ax = a.cx + (px - a.cx) / ta * trimA;
46659
+ const ay = a.cy + (py - a.cy) / ta * trimA;
46660
+ const bx = b.cx - (b.cx - px) / tb * trimB;
46661
+ const by = b.cy - (b.cy - py) / tb * trimB;
46662
+ return `M${ax},${ay}Q${px},${py} ${bx},${by}`;
46131
46663
  };
46132
46664
  for (const rt of resolved.routes) {
46133
46665
  const curved = rt.meta["style"] === "arc";
@@ -46138,7 +46670,7 @@ function layoutMap(resolved, data, size, opts) {
46138
46670
  legs.push({
46139
46671
  d: legPath(a, b, curved, 0),
46140
46672
  width: W_MIN,
46141
- color: mix(palette.text, palette.bg, 55),
46673
+ color: mix(palette.text, palette.bg, 72),
46142
46674
  arrow: true,
46143
46675
  lineNumber: rt.lineNumber
46144
46676
  });
@@ -46174,7 +46706,7 @@ function layoutMap(resolved, data, size, opts) {
46174
46706
  legs.push({
46175
46707
  d: legPath(a, b, curved, offset),
46176
46708
  width: widthFor(e),
46177
- color: mix(palette.text, palette.bg, 45),
46709
+ color: mix(palette.text, palette.bg, 66),
46178
46710
  arrow: e.directed,
46179
46711
  lineNumber: e.lineNumber,
46180
46712
  ...e.label !== void 0 && {
@@ -46186,38 +46718,92 @@ function layoutMap(resolved, data, size, opts) {
46186
46718
  });
46187
46719
  }
46188
46720
  const labels = [];
46189
- const pinList = [];
46190
46721
  const obstacles = [];
46191
46722
  const markers = pois.map((p) => ({
46192
46723
  cx: p.cx,
46193
46724
  cy: p.cy,
46194
46725
  r: p.r
46195
46726
  }));
46196
- const collides = (rect) => markers.some((m) => rectCircleOverlap(rect, m)) || obstacles.some((o) => rectsOverlap(rect, o));
46727
+ const legSegments = [];
46728
+ for (const leg of legs) {
46729
+ const m = /^M(-?[\d.]+),(-?[\d.]+)(?:L(-?[\d.]+),(-?[\d.]+)|Q(-?[\d.]+),(-?[\d.]+) (-?[\d.]+),(-?[\d.]+))$/.exec(
46730
+ leg.d
46731
+ );
46732
+ if (!m) continue;
46733
+ const x0 = +m[1];
46734
+ const y0 = +m[2];
46735
+ if (m[3] !== void 0) {
46736
+ legSegments.push([x0, y0, +m[3], +m[4]]);
46737
+ } else {
46738
+ const cx = +m[5];
46739
+ const cy = +m[6];
46740
+ const ex = +m[7];
46741
+ const ey = +m[8];
46742
+ const N = 8;
46743
+ let px = x0;
46744
+ let py = y0;
46745
+ for (let i = 1; i <= N; i++) {
46746
+ const t = i / N;
46747
+ const u = 1 - t;
46748
+ const qx = u * u * x0 + 2 * u * t * cx + t * t * ex;
46749
+ const qy = u * u * y0 + 2 * u * t * cy + t * t * ey;
46750
+ legSegments.push([px, py, qx, qy]);
46751
+ px = qx;
46752
+ py = qy;
46753
+ }
46754
+ }
46755
+ }
46756
+ 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));
46197
46757
  const regionLabelMode = resolved.directives.regionLabels ?? "off";
46758
+ const LABEL_PADX = 6;
46759
+ const LABEL_PADY = 3;
46760
+ const labelW = (text) => measureLegendText(text, FONT) + 2 * LABEL_PADX;
46761
+ const labelH = FONT + 2 * LABEL_PADY;
46762
+ const pushRegionLabel = (x, y, text, fill2, lineNumber) => {
46763
+ const color = contrastText(
46764
+ fill2,
46765
+ palette.textOnFillLight,
46766
+ palette.textOnFillDark
46767
+ );
46768
+ const haloColor = color === palette.textOnFillLight ? palette.textOnFillDark : palette.textOnFillLight;
46769
+ labels.push({
46770
+ x,
46771
+ y,
46772
+ text,
46773
+ anchor: "middle",
46774
+ color,
46775
+ halo: true,
46776
+ haloColor,
46777
+ lineNumber
46778
+ });
46779
+ };
46780
+ const WORLD_LABEL_ANCHORS = {
46781
+ US: [-98.5, 39.5]
46782
+ // CONUS geographic centre (near Lebanon, Kansas)
46783
+ };
46198
46784
  if (regionLabelMode === "full" || regionLabelMode === "abbrev") {
46199
46785
  for (const r of regions) {
46200
46786
  if (r.layer === "base" || r.label === void 0) continue;
46201
46787
  const f = r.layer === "us-state" ? usLayer?.get(r.id) : worldLayer.get(r.id);
46202
46788
  if (!f) continue;
46203
46789
  const [[x0, y0], [x1, y1]] = path.bounds(f);
46204
- if ((x1 - x0) * (y1 - y0) < TINY_REGION_AREA) continue;
46205
- const c = path.centroid(f);
46206
- if (!Number.isFinite(c[0])) continue;
46207
46790
  const text = regionLabelMode === "abbrev" ? r.id.replace(/^US-/, "") : r.label;
46208
- labels.push({
46209
- x: c[0],
46210
- y: c[1],
46791
+ if (labelW(text) > x1 - x0 || labelH > y1 - y0) continue;
46792
+ const anchor = r.layer !== "us-state" ? WORLD_LABEL_ANCHORS[r.id] : void 0;
46793
+ const c = anchor ? project(anchor[0], anchor[1]) : path.centroid(f);
46794
+ if (!c || !Number.isFinite(c[0])) continue;
46795
+ pushRegionLabel(c[0], c[1], text, r.fill, r.lineNumber);
46796
+ }
46797
+ for (const seed of insetLabelSeeds) {
46798
+ const text = regionLabelMode === "abbrev" ? seed.iso.replace(/^US-/, "") : seed.name;
46799
+ const src = regionById.get(seed.iso);
46800
+ pushRegionLabel(
46801
+ seed.x,
46802
+ seed.y,
46211
46803
  text,
46212
- anchor: "middle",
46213
- color: contrastText(
46214
- r.fill,
46215
- palette.textOnFillLight,
46216
- palette.textOnFillDark
46217
- ),
46218
- halo: true,
46219
- lineNumber: r.lineNumber
46220
- });
46804
+ src ? regionFill(src) : neutralFill,
46805
+ seed.lineNumber
46806
+ );
46221
46807
  }
46222
46808
  }
46223
46809
  const poiLabelMode = resolved.directives.poiLabels ?? "auto";
@@ -46230,68 +46816,106 @@ function layoutMap(resolved, data, size, opts) {
46230
46816
  const src = poiById.get(p.id);
46231
46817
  return src?.label ?? src?.name ?? p.id;
46232
46818
  };
46233
- let pinCounter = 0;
46234
- for (const p of ordered) {
46819
+ const poiLabH = FONT * 1.25;
46820
+ const labelInfo = (p) => {
46235
46821
  const text = labelText(p);
46236
- const w = measureLegendText(text, FONT);
46237
- const h = FONT * 1.25;
46238
- const inline = { x: p.cx + p.r + 3, y: p.cy - h / 2, w, h };
46239
- if (!collides(inline)) {
46240
- obstacles.push(inline);
46822
+ return { text, w: measureLegendText(text, FONT) };
46823
+ };
46824
+ const pushInline = (p, text, w, side) => {
46825
+ const tx = side === "right" ? p.cx + p.r + 3 : p.cx - p.r - 3;
46826
+ obstacles.push({
46827
+ x: side === "right" ? tx : tx - w,
46828
+ y: p.cy - poiLabH / 2,
46829
+ w,
46830
+ h: poiLabH
46831
+ });
46832
+ labels.push({
46833
+ x: tx,
46834
+ y: p.cy + FONT / 3,
46835
+ text,
46836
+ anchor: side === "right" ? "start" : "end",
46837
+ color: palette.text,
46838
+ halo: true,
46839
+ haloColor: palette.bg,
46840
+ poiId: p.id,
46841
+ lineNumber: p.lineNumber
46842
+ });
46843
+ };
46844
+ const inlineFits = (p, w, side) => {
46845
+ const tx = side === "right" ? p.cx + p.r + 3 : p.cx - p.r - 3;
46846
+ const rect = {
46847
+ x: side === "right" ? tx : tx - w,
46848
+ y: p.cy - poiLabH / 2,
46849
+ w,
46850
+ h: poiLabH
46851
+ };
46852
+ return rect.x >= 0 && rect.x + rect.w <= width && !collides(rect);
46853
+ };
46854
+ const GROUP_R = 30;
46855
+ const groups = [];
46856
+ for (const p of ordered) {
46857
+ const near = groups.find(
46858
+ (g) => g.some((q) => Math.hypot(q.cx - p.cx, q.cy - p.cy) < GROUP_R)
46859
+ );
46860
+ if (near) near.push(p);
46861
+ else groups.push([p]);
46862
+ }
46863
+ const ROW_GAP2 = 3;
46864
+ const step = poiLabH + ROW_GAP2;
46865
+ const COL_GAP = 16;
46866
+ const placeColumn = (group) => {
46867
+ const items = group.map((p) => ({ p, ...labelInfo(p) })).sort((a, b) => a.p.cy - b.p.cy || (a.text < b.text ? -1 : 1));
46868
+ const left = Math.min(...items.map((o) => o.p.cx - o.p.r));
46869
+ const right = Math.max(...items.map((o) => o.p.cx + o.p.r));
46870
+ const cyMid = (Math.min(...items.map((o) => o.p.cy)) + Math.max(...items.map((o) => o.p.cy))) / 2;
46871
+ const maxW = Math.max(...items.map((o) => o.w));
46872
+ const side = right + COL_GAP + maxW <= width - 2 ? "right" : "left";
46873
+ const colX = side === "right" ? right + COL_GAP : left - COL_GAP;
46874
+ const totalH = items.length * step;
46875
+ let startY = cyMid - totalH / 2;
46876
+ startY = Math.max(2, Math.min(startY, height - totalH - 2));
46877
+ items.forEach((o, i) => {
46878
+ const rowCy = startY + i * step + step / 2;
46879
+ obstacles.push({
46880
+ x: side === "right" ? colX : colX - o.w,
46881
+ y: rowCy - poiLabH / 2,
46882
+ w: o.w,
46883
+ h: poiLabH
46884
+ });
46241
46885
  labels.push({
46242
- x: inline.x,
46243
- y: p.cy + FONT / 3,
46244
- text,
46245
- anchor: "start",
46886
+ x: colX,
46887
+ y: rowCy + FONT / 3,
46888
+ text: o.text,
46889
+ anchor: side === "right" ? "start" : "end",
46246
46890
  color: palette.text,
46247
46891
  halo: true,
46248
- lineNumber: p.lineNumber
46892
+ haloColor: palette.bg,
46893
+ leader: {
46894
+ x1: o.p.cx,
46895
+ y1: o.p.cy,
46896
+ x2: side === "right" ? colX - 2 : colX + 2,
46897
+ y2: rowCy
46898
+ },
46899
+ leaderColor: o.p.fill,
46900
+ poiId: o.p.id,
46901
+ lineNumber: o.p.lineNumber
46249
46902
  });
46250
- continue;
46251
- }
46252
- let placed = false;
46253
- for (let k = 1; k <= 2 && !placed; k++) {
46254
- for (const [dx, dy] of RING_DIRS) {
46255
- const cx = p.cx + dx * LEADER_STEP * k;
46256
- const cy = p.cy + dy * LEADER_STEP * k;
46257
- const rect = {
46258
- x: dx >= 0 ? cx : cx - w,
46259
- y: cy - h / 2,
46260
- w,
46261
- h
46262
- };
46263
- if (rect.x < 0 || rect.x + rect.w > width || rect.y < 0 || rect.y + rect.h > height) {
46264
- continue;
46265
- }
46266
- if (collides(rect)) continue;
46267
- obstacles.push(rect);
46268
- labels.push({
46269
- x: cx,
46270
- y: cy + FONT / 3,
46271
- text,
46272
- anchor: dx >= 0 ? "start" : "end",
46273
- color: palette.text,
46274
- halo: true,
46275
- leader: { x1: p.cx, y1: p.cy, x2: cx, y2: cy },
46276
- lineNumber: p.lineNumber
46277
- });
46278
- placed = true;
46279
- break;
46903
+ });
46904
+ };
46905
+ for (const g of groups) {
46906
+ if (g.length === 1) {
46907
+ const p = g[0];
46908
+ const { text, w } = labelInfo(p);
46909
+ if (inlineFits(p, w, "right")) {
46910
+ pushInline(p, text, w, "right");
46911
+ continue;
46912
+ }
46913
+ if (inlineFits(p, w, "left")) {
46914
+ pushInline(p, text, w, "left");
46915
+ continue;
46280
46916
  }
46281
46917
  }
46282
- if (placed) continue;
46283
- pinCounter += 1;
46284
- pinList.push({ pin: pinCounter, label: text });
46285
- labels.push({
46286
- x: p.cx + p.r + 2,
46287
- y: p.cy - p.r,
46288
- text: String(pinCounter),
46289
- anchor: "start",
46290
- color: palette.text,
46291
- halo: true,
46292
- pin: pinCounter,
46293
- lineNumber: p.lineNumber
46294
- });
46918
+ placeColumn(g);
46295
46919
  }
46296
46920
  }
46297
46921
  let legend = null;
@@ -46300,8 +46924,7 @@ function layoutMap(resolved, data, size, opts) {
46300
46924
  name: g.name,
46301
46925
  entries: g.entries.map((e) => ({ value: e.value, color: e.color }))
46302
46926
  }));
46303
- const hasAnything = tagGroups.length > 0 || hasRamp || sizeVals.length > 0 || weightVals.length > 0;
46304
- if (hasAnything) {
46927
+ if (tagGroups.length > 0 || hasRamp) {
46305
46928
  legend = {
46306
46929
  tagGroups,
46307
46930
  activeGroup,
@@ -46312,20 +46935,9 @@ function layoutMap(resolved, data, size, opts) {
46312
46935
  },
46313
46936
  min: rampMin,
46314
46937
  max: rampMax,
46315
- hue: rampHue
46316
- }
46317
- },
46318
- ...sizeVals.length > 0 && {
46319
- size: {
46320
- ...resolved.directives.sizeMetric !== void 0 && {
46321
- metric: resolved.directives.sizeMetric
46322
- },
46323
- min: sizeMin,
46324
- max: sizeMax
46938
+ hue: rampHue,
46939
+ base: rampBase
46325
46940
  }
46326
- },
46327
- ...weightVals.length > 0 && {
46328
- weight: { min: wMin, max: wMax }
46329
46941
  }
46330
46942
  };
46331
46943
  }
@@ -46333,26 +46945,28 @@ function layoutMap(resolved, data, size, opts) {
46333
46945
  return {
46334
46946
  width,
46335
46947
  height,
46336
- background: palette.bg,
46948
+ background: water,
46337
46949
  title: resolved.title,
46338
46950
  ...resolved.subtitle !== void 0 && { subtitle: resolved.subtitle },
46339
46951
  ...resolved.caption !== void 0 && { caption: resolved.caption },
46340
46952
  regions,
46953
+ rivers,
46341
46954
  legs,
46342
46955
  pois,
46343
46956
  labels,
46344
- pinList,
46345
- legend
46957
+ legend,
46958
+ insets,
46959
+ insetRegions
46346
46960
  };
46347
46961
  }
46348
- var FIT_PAD, RAMP_FLOOR, R_DEFAULT, R_MIN, R_MAX, W_MIN, W_MAX, FONT, LEADER_STEP, COLO_EPS, COLO_R, GOLDEN_ANGLE, FAN_STEP, TINY_REGION_AREA, ARC_CURVE_FRAC, RING_DIRS;
46962
+ var FIT_PAD, RAMP_FLOOR, R_DEFAULT, R_MIN, R_MAX, W_MIN, W_MAX, FONT, COLO_EPS, LAND_TINT_LIGHT, LAND_TINT_DARK, TAG_TINT_LIGHT, TAG_TINT_DARK, WATER_TINT, RIVER_WIDTH, FOREIGN_TINT_LIGHT, FOREIGN_TINT_DARK, COLO_R, GOLDEN_ANGLE, FAN_STEP, ARC_CURVE_FRAC, usConusProjection, alaskaProjection, hawaiiProjection, INSET_STATES, US_NON_CONUS;
46349
46963
  var init_layout15 = __esm({
46350
46964
  "src/map/layout.ts"() {
46351
46965
  "use strict";
46352
46966
  init_color_utils();
46353
- init_tag_groups();
46354
46967
  init_label_layout();
46355
46968
  init_legend_constants();
46969
+ init_title_constants();
46356
46970
  FIT_PAD = 24;
46357
46971
  RAMP_FLOOR = 15;
46358
46972
  R_DEFAULT = 6;
@@ -46361,23 +46975,32 @@ var init_layout15 = __esm({
46361
46975
  W_MIN = 1.25;
46362
46976
  W_MAX = 8;
46363
46977
  FONT = 11;
46364
- LEADER_STEP = 14;
46365
46978
  COLO_EPS = 1.5;
46979
+ LAND_TINT_LIGHT = 58;
46980
+ LAND_TINT_DARK = 75;
46981
+ TAG_TINT_LIGHT = 60;
46982
+ TAG_TINT_DARK = 68;
46983
+ WATER_TINT = 55;
46984
+ RIVER_WIDTH = 1.3;
46985
+ FOREIGN_TINT_LIGHT = 30;
46986
+ FOREIGN_TINT_DARK = 62;
46366
46987
  COLO_R = 9;
46367
46988
  GOLDEN_ANGLE = 2.399963229728653;
46368
46989
  FAN_STEP = 16;
46369
- TINY_REGION_AREA = 600;
46370
46990
  ARC_CURVE_FRAC = 0.18;
46371
- RING_DIRS = [
46372
- [1, 0],
46373
- [0, 1],
46374
- [-1, 0],
46375
- [0, -1],
46376
- [1, 1],
46377
- [-1, 1],
46378
- [-1, -1],
46379
- [1, -1]
46380
- ];
46991
+ usConusProjection = () => geoConicEqualArea().parallels([29.5, 45.5]).rotate([96, 0]);
46992
+ alaskaProjection = () => geoConicEqualArea().rotate([154, 0]).center([-2, 58.5]).parallels([55, 65]);
46993
+ hawaiiProjection = () => geoMercator();
46994
+ INSET_STATES = /* @__PURE__ */ new Set(["US-AK", "US-HI"]);
46995
+ US_NON_CONUS = /* @__PURE__ */ new Set([
46996
+ "US-AK",
46997
+ "US-HI",
46998
+ "US-AS",
46999
+ "US-GU",
47000
+ "US-MP",
47001
+ "US-PR",
47002
+ "US-VI"
47003
+ ]);
46381
47004
  }
46382
47005
  });
46383
47006
 
@@ -46388,7 +47011,7 @@ __export(renderer_exports16, {
46388
47011
  renderMapForExport: () => renderMapForExport
46389
47012
  });
46390
47013
  import * as d3Selection18 from "d3-selection";
46391
- function renderMap(container, resolved, data, palette, isDark, onClickItem, exportDims) {
47014
+ function renderMap(container, resolved, data, palette, isDark, onClickItem, exportDims, activeGroupOverride) {
46392
47015
  d3Selection18.select(container).selectAll(":not([data-d3-tooltip])").remove();
46393
47016
  const width = exportDims?.width ?? container.clientWidth;
46394
47017
  const height = exportDims?.height ?? container.clientHeight;
@@ -46399,27 +47022,29 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
46399
47022
  { width, height },
46400
47023
  {
46401
47024
  palette,
46402
- isDark
47025
+ isDark,
47026
+ ...activeGroupOverride !== void 0 && {
47027
+ activeGroup: activeGroupOverride
47028
+ }
46403
47029
  }
46404
47030
  );
46405
- const svg = d3Selection18.select(container).append("svg").attr("width", width).attr("height", height).attr("viewBox", `0 0 ${width} ${height}`).attr("preserveAspectRatio", "xMidYMin meet").attr("xmlns", "http://www.w3.org/2000/svg").style("font-family", FONT_FAMILY);
47031
+ const svg = d3Selection18.select(container).append("svg").attr("width", width).attr("height", height).attr("viewBox", `0 0 ${width} ${height}`).attr("preserveAspectRatio", "xMidYMin meet").attr("xmlns", "http://www.w3.org/2000/svg").style("font-family", FONT_FAMILY).style("background", layout.background);
46406
47032
  svg.append("rect").attr("width", width).attr("height", height).attr("fill", layout.background);
46407
- const arrowColor = mix(palette.text, palette.bg, 50);
46408
47033
  const defs = svg.append("defs");
46409
- defs.append("marker").attr("id", "dgmo-map-arrow").attr("viewBox", "0 0 10 10").attr("refX", 9).attr("refY", 5).attr("markerWidth", 7).attr("markerHeight", 7).attr("orient", "auto-start-reverse").append("path").attr("d", "M0,0L10,5L0,10z").attr("fill", arrowColor);
46410
- const haloColor = layout.background;
46411
- if (layout.title) {
46412
- svg.append("text").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).text(layout.title);
46413
- }
46414
- if (layout.subtitle) {
46415
- 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).text(layout.subtitle);
46416
- }
46417
- if (layout.caption) {
46418
- svg.append("text").attr("x", width / 2).attr("y", height - 8).attr("text-anchor", "middle").attr("font-size", LABEL_FONT).attr("fill", palette.textMuted).text(layout.caption);
46419
- }
47034
+ const arrowSize = (w) => Math.min(15, 7 + w * 0.95);
47035
+ const haloColor = palette.bg;
46420
47036
  const gRegions = svg.append("g").attr("class", "dgmo-map-regions");
46421
- for (const r of layout.regions) {
46422
- const p = gRegions.append("path").attr("d", r.d).attr("fill", r.fill).attr("stroke", r.stroke).attr("stroke-width", 0.5);
47037
+ const drawRegion = (g, r, strokeWidth) => {
47038
+ const p = g.append("path").attr("d", r.d).attr("fill", r.fill).attr("stroke", r.stroke).attr("stroke-width", strokeWidth);
47039
+ if (r.layer !== "base") {
47040
+ p.classed("dgmo-map-region", true).attr("data-region", r.id);
47041
+ if (r.score !== void 0) p.attr("data-score", r.score);
47042
+ if (r.tags) {
47043
+ for (const [group, value] of Object.entries(r.tags)) {
47044
+ p.attr(`data-tag-${group.toLowerCase()}`, value.toLowerCase());
47045
+ }
47046
+ }
47047
+ }
46423
47048
  if (r.lineNumber >= 0) {
46424
47049
  p.attr("data-line-number", r.lineNumber);
46425
47050
  if (onClickItem) {
@@ -46429,11 +47054,31 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
46429
47054
  );
46430
47055
  }
46431
47056
  }
47057
+ };
47058
+ for (const r of layout.regions) drawRegion(gRegions, r, 0.5);
47059
+ if (layout.rivers.length) {
47060
+ const gRivers = svg.append("g").attr("class", "dgmo-map-rivers").attr("fill", "none");
47061
+ for (const r of layout.rivers) {
47062
+ gRivers.append("path").attr("d", r.d).attr("stroke", r.color).attr("stroke-width", r.width).attr("stroke-linecap", "round").attr("stroke-linejoin", "round");
47063
+ }
47064
+ }
47065
+ if (layout.insets.length) {
47066
+ const insetG = svg.append("g").attr("class", "dgmo-map-insets");
47067
+ for (const box of layout.insets) {
47068
+ const d = box.points.map((p, i) => `${i ? "L" : "M"}${p[0]},${p[1]}`).join("") + "Z";
47069
+ 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");
47070
+ }
47071
+ for (const r of layout.insetRegions) drawRegion(insetG, r, 0.5);
46432
47072
  }
46433
47073
  const gLegs = svg.append("g").attr("class", "dgmo-map-legs").attr("fill", "none");
46434
- for (const leg of layout.legs) {
47074
+ layout.legs.forEach((leg, i) => {
46435
47075
  const p = gLegs.append("path").attr("d", leg.d).attr("stroke", leg.color).attr("stroke-width", leg.width).attr("stroke-linecap", "round");
46436
- if (leg.arrow) p.attr("marker-end", "url(#dgmo-map-arrow)");
47076
+ if (leg.arrow) {
47077
+ const id = `dgmo-map-arrow-${i}`;
47078
+ const s = arrowSize(leg.width);
47079
+ defs.append("marker").attr("id", id).attr("viewBox", "0 0 10 10").attr("refX", 10).attr("refY", 5).attr("markerUnits", "userSpaceOnUse").attr("markerWidth", s).attr("markerHeight", s).attr("orient", "auto-start-reverse").append("path").attr("d", "M0,0L10,5L0,10z").attr("fill", leg.color);
47080
+ p.attr("marker-end", `url(#${id})`);
47081
+ }
46437
47082
  if (leg.label !== void 0 && leg.labelX !== void 0) {
46438
47083
  emitText(
46439
47084
  gLegs,
@@ -46447,13 +47092,13 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
46447
47092
  LABEL_FONT - 1
46448
47093
  );
46449
47094
  }
46450
- }
47095
+ });
46451
47096
  const gPois = svg.append("g").attr("class", "dgmo-map-pois");
46452
47097
  for (const poi of layout.pois) {
46453
47098
  if (poi.isOrigin) {
46454
47099
  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);
46455
47100
  }
46456
- 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);
47101
+ 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);
46457
47102
  if (onClickItem) {
46458
47103
  c.style("cursor", "pointer").on(
46459
47104
  "click",
@@ -46477,48 +47122,66 @@ function renderMap(container, resolved, data, palette, isDark, onClickItem, expo
46477
47122
  const gLabels = svg.append("g").attr("class", "dgmo-map-labels");
46478
47123
  for (const lab of layout.labels) {
46479
47124
  if (lab.leader) {
46480
- gLabels.append("line").attr("x1", lab.leader.x1).attr("y1", lab.leader.y1).attr("x2", lab.leader.x2).attr("y2", lab.leader.y2).attr("stroke", mix(palette.textMuted, palette.bg, 60)).attr("stroke-width", 0.75);
47125
+ 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(
47126
+ "stroke",
47127
+ lab.leaderColor ?? mix(palette.textMuted, palette.bg, 60)
47128
+ ).attr("stroke-width", lab.leaderColor ? 1 : 0.75);
47129
+ if (lab.poiId !== void 0) line12.attr("data-poi", lab.poiId);
46481
47130
  }
46482
- if (lab.pin !== void 0) {
46483
- gLabels.append("rect").attr("x", lab.x - 1).attr("y", lab.y - LABEL_FONT).attr("width", LABEL_FONT * 1.3).attr("height", LABEL_FONT * 1.3).attr("rx", 2).attr("fill", palette.surface).attr("stroke", palette.border);
46484
- }
46485
- emitText(
47131
+ const t = emitText(
46486
47132
  gLabels,
46487
47133
  lab.x,
46488
47134
  lab.y,
46489
47135
  lab.text,
46490
47136
  lab.anchor,
46491
47137
  lab.color,
46492
- haloColor,
47138
+ lab.haloColor,
46493
47139
  lab.halo,
46494
47140
  LABEL_FONT
46495
47141
  );
46496
- }
46497
- if (layout.pinList.length > 0) {
46498
- const gPins = svg.append("g").attr("class", "dgmo-map-pin-list").attr(
46499
- "transform",
46500
- `translate(12, ${height - layout.pinList.length * 14 - 8})`
46501
- );
46502
- layout.pinList.forEach((entry, i) => {
46503
- gPins.append("text").attr("x", 0).attr("y", i * 14).attr("font-size", LABEL_FONT - 1).attr("fill", palette.textMuted).text(`${entry.pin} \u2014 ${entry.label}`);
46504
- });
47142
+ if (lab.poiId !== void 0) {
47143
+ t.attr("data-poi", lab.poiId).style("cursor", "default");
47144
+ }
46505
47145
  }
46506
47146
  if (layout.legend) {
46507
47147
  const legendY = (layout.title ? TITLE_Y + TITLE_FONT_SIZE : 0) + (layout.subtitle ? TITLE_FONT_SIZE : 0) + 8;
46508
47148
  const legendG = svg.append("g").attr("class", "dgmo-map-legend").attr("transform", `translate(0, ${legendY})`);
46509
- const groups = layout.legend.tagGroups.filter((g) => g.entries.length > 0);
47149
+ const ramp = layout.legend.ramp;
47150
+ const scoreGroup = ramp ? {
47151
+ name: ramp.metric?.trim() || "Score",
47152
+ entries: [],
47153
+ gradient: {
47154
+ min: ramp.min,
47155
+ max: ramp.max,
47156
+ hue: ramp.hue,
47157
+ base: ramp.base
47158
+ }
47159
+ } : null;
47160
+ const tagGroups = layout.legend.tagGroups.filter((g) => g.entries.length > 0).map((g) => ({ name: g.name, entries: [...g.entries] }));
47161
+ const groups = [...scoreGroup ? [scoreGroup] : [], ...tagGroups];
46510
47162
  if (groups.length > 0) {
46511
47163
  const config = {
46512
- groups: groups.map((g) => ({ name: g.name, entries: [...g.entries] })),
47164
+ groups,
46513
47165
  position: { placement: "top-center", titleRelation: "below-title" },
46514
47166
  mode: exportDims ? "export" : "preview",
46515
- showEmptyGroups: false
47167
+ showEmptyGroups: false,
47168
+ // Keep inactive siblings visible as pills so the user can click to flip
47169
+ // the active colouring dimension (preview only — export shows just the
47170
+ // active group).
47171
+ showInactivePills: true
46516
47172
  };
46517
47173
  const state = { activeGroup: layout.legend.activeGroup };
46518
47174
  renderLegendD3(legendG, config, state, palette, isDark, void 0, width);
46519
47175
  }
46520
- const pinGap = layout.pinList.length ? layout.pinList.length * 14 + 14 : 0;
46521
- emitExtraLegend(svg, layout, palette, height, pinGap);
47176
+ }
47177
+ if (layout.title) {
47178
+ svg.append("text").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);
47179
+ }
47180
+ if (layout.subtitle) {
47181
+ 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);
47182
+ }
47183
+ if (layout.caption) {
47184
+ 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);
46522
47185
  }
46523
47186
  }
46524
47187
  function renderMapForExport(container, resolved, data, palette, isDark, exportDims) {
@@ -46529,54 +47192,7 @@ function emitText(g, x, y, text, anchor, color, halo, withHalo, fontSize) {
46529
47192
  if (withHalo) {
46530
47193
  t.attr("paint-order", "stroke fill").attr("stroke", halo).attr("stroke-width", 3).attr("stroke-linejoin", "round").attr("stroke-opacity", 0.7);
46531
47194
  }
46532
- }
46533
- function emitExtraLegend(svg, layout, palette, height, bottomGap) {
46534
- const { legend } = layout;
46535
- if (!legend) return;
46536
- if (!legend.ramp && !legend.size && !legend.weight) return;
46537
- const blocks = [];
46538
- const g = svg.append("g").attr("class", "dgmo-map-legend-keys").attr("transform", `translate(12, ${height - 56 - bottomGap})`);
46539
- let xCursor = 0;
46540
- if (legend.ramp) {
46541
- const ramp = legend.ramp;
46542
- blocks.push(() => {
46543
- const block = g.append("g").attr("transform", `translate(${xCursor},0)`);
46544
- const gradId = "dgmo-map-ramp";
46545
- const grad = block.append("defs").append("linearGradient").attr("id", gradId).attr("x1", "0%").attr("x2", "100%");
46546
- grad.append("stop").attr("offset", "0%").attr("stop-color", mix(ramp.hue, palette.bg, 15));
46547
- grad.append("stop").attr("offset", "100%").attr("stop-color", ramp.hue);
46548
- block.append("rect").attr("width", 80).attr("height", 8).attr("fill", `url(#${gradId})`);
46549
- block.append("text").attr("x", 0).attr("y", 22).attr("font-size", 9).attr("fill", palette.textMuted).text(String(ramp.min));
46550
- block.append("text").attr("x", 80).attr("y", 22).attr("text-anchor", "end").attr("font-size", 9).attr("fill", palette.textMuted).text(String(ramp.max));
46551
- if (ramp.metric) {
46552
- block.append("text").attr("x", 0).attr("y", -4).attr("font-size", 9).attr("fill", palette.textMuted).text(ramp.metric);
46553
- }
46554
- xCursor += 110;
46555
- });
46556
- }
46557
- if (legend.size) {
46558
- const sz = legend.size;
46559
- blocks.push(() => {
46560
- const block = g.append("g").attr("transform", `translate(${xCursor},0)`);
46561
- [3, 6, 10].forEach((r, i) => {
46562
- block.append("circle").attr("cx", i * 26 + r).attr("cy", 8).attr("r", r).attr("fill", "none").attr("stroke", palette.textMuted);
46563
- });
46564
- block.append("text").attr("x", 0).attr("y", -4).attr("font-size", 9).attr("fill", palette.textMuted).text(sz.metric ?? "size");
46565
- xCursor += 110;
46566
- });
46567
- }
46568
- if (legend.weight) {
46569
- const wt = legend.weight;
46570
- blocks.push(() => {
46571
- const block = g.append("g").attr("transform", `translate(${xCursor},0)`);
46572
- [1, 3, 6].forEach((w, i) => {
46573
- block.append("line").attr("x1", i * 26).attr("y1", 8).attr("x2", i * 26 + 20).attr("y2", 8).attr("stroke", palette.textMuted).attr("stroke-width", w);
46574
- });
46575
- block.append("text").attr("x", 0).attr("y", -4).attr("font-size", 9).attr("fill", palette.textMuted).text(wt.metric ?? "weight");
46576
- xCursor += 110;
46577
- });
46578
- }
46579
- for (const draw of blocks) draw();
47195
+ return t;
46580
47196
  }
46581
47197
  var LABEL_FONT;
46582
47198
  var init_renderer16 = __esm({
@@ -53048,18 +53664,18 @@ function getRotateFn(mode) {
53048
53664
  return () => 0;
53049
53665
  }
53050
53666
  function renderWordCloudAsync(container, parsed, palette, _isDark, exportDims) {
53051
- return new Promise((resolve2) => {
53667
+ return new Promise((resolve) => {
53052
53668
  d3Selection23.select(container).selectAll(":not([data-d3-tooltip])").remove();
53053
53669
  const { words, cloudOptions } = parsed;
53054
53670
  const title = parsed.noTitle ? null : parsed.title;
53055
53671
  if (words.length === 0) {
53056
- resolve2();
53672
+ resolve();
53057
53673
  return;
53058
53674
  }
53059
53675
  const width = exportDims?.width ?? container.clientWidth;
53060
53676
  const height = exportDims?.height ?? container.clientHeight;
53061
53677
  if (width <= 0 || height <= 0) {
53062
- resolve2();
53678
+ resolve();
53063
53679
  return;
53064
53680
  }
53065
53681
  const titleHeight = title ? 40 : 0;
@@ -53088,7 +53704,7 @@ function renderWordCloudAsync(container, parsed, palette, _isDark, exportDims) {
53088
53704
  "transform",
53089
53705
  (d) => `translate(${d.x},${d.y}) rotate(${d.rotate})`
53090
53706
  ).text((d) => d.text);
53091
- resolve2();
53707
+ resolve();
53092
53708
  }).start();
53093
53709
  });
53094
53710
  }
@@ -56435,7 +57051,7 @@ pre.dgmo, code.language-dgmo, pre > code.language-dgmo,
56435
57051
 
56436
57052
  // src/auto/index.ts
56437
57053
  init_safe_href();
56438
- var VERSION = "0.19.0";
57054
+ var VERSION = "0.20.1";
56439
57055
  var DEFAULTS = {
56440
57056
  theme: "auto",
56441
57057
  palette: "nord",