@chrtco/sdk 0.10.45 → 0.10.47

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.
@@ -48,6 +48,12 @@ __export(browser_exports, {
48
48
  module.exports = __toCommonJS(browser_exports);
49
49
 
50
50
  // src/atlas/s52-colors.ts
51
+ var EINK_MONO_GREYS = {
52
+ INK: "#000000",
53
+ DARK: "#555555",
54
+ MID: "#aaaaaa",
55
+ PAPER: "#ffffff"
56
+ };
51
57
  var S52_DAY = {
52
58
  // Depth
53
59
  NODTA: "#a4a4a4",
@@ -328,10 +334,234 @@ var S52_NIGHT = {
328
334
  RES02: "#383028",
329
335
  RES03: "#383028"
330
336
  };
337
+ var S52_EINK_COLOR = {
338
+ NODTA: "#b4b4b4",
339
+ DEPIT: "#4f8f3a",
340
+ DEPVS: "#74ade0",
341
+ DEPMS: "#bad6ef",
342
+ DEPMD: "#ffffff",
343
+ DEPDW: "#ffffff",
344
+ LANDA: "#f2e2b0",
345
+ LANDF: "#c49a4a",
346
+ CHBRN: "#8b5a2b",
347
+ CHGRF: "#9a9a9a",
348
+ CHGRD: "#444444",
349
+ CHBLK: "#000000",
350
+ CSTLN: "#000000",
351
+ OUTLW: "#000000",
352
+ BKAJ1: "#333333",
353
+ TRFCD: "#c800c8",
354
+ TRFCF: "#c800c8",
355
+ RESBL: "#0050c8",
356
+ APTS1: "#c800c8",
357
+ DNGHL: "#e00000",
358
+ ISHDG: "#e00000",
359
+ LITRD: "#e00000",
360
+ LITGN: "#00a000",
361
+ LITYW: "#f0c000",
362
+ LITWT: "#ffffff",
363
+ LITBL: "#0050c8",
364
+ LITOR: "#f07000",
365
+ RADHI: "#00a000",
366
+ RADLO: "#006000",
367
+ PLRTE: "#e00000",
368
+ ARPAT: "#008040",
369
+ ISDNG: "#c800c8",
370
+ CHRED: "#e00000",
371
+ CHGRN: "#00a000",
372
+ CHYLW: "#f0c000",
373
+ CHMGD: "#c800c8",
374
+ CHMGF: "#c800c8",
375
+ CHWHT: "#ffffff",
376
+ CHCOR: "#f07000",
377
+ // Deeper-than-safety soundings: dark grey, the one secondary ink. Shoal
378
+ // soundings are black — the louder numeral, as in every other table.
379
+ SNDG1: "#555555",
380
+ SNDG2: "#000000",
381
+ DEPSC: "#000000",
382
+ DEPCN: "#555555",
383
+ UINFD: "#000000",
384
+ UINFF: "#555555",
385
+ UINFR: "#e00000",
386
+ UINFG: "#00a000",
387
+ UINFO: "#f07000",
388
+ UINFB: "#0050c8",
389
+ UINFM: "#c800c8",
390
+ UIBCK: "#ffffff",
391
+ UIBDR: "#555555",
392
+ UIAFD: "#74ade0",
393
+ UIAFF: "#c49a4a",
394
+ PIPLN: "#008000",
395
+ OUTLL: "#c49a4a",
396
+ SCLBR: "#f07000",
397
+ NINFO: "#f07000",
398
+ ADINF: "#b08800",
399
+ RESGR: "#555555",
400
+ SHIPS: "#000000",
401
+ PSTRK: "#000000",
402
+ SYTRK: "#555555",
403
+ APLRT: "#f07000",
404
+ CURSR: "#f07000",
405
+ BKAJ2: "#000000",
406
+ RES01: "#808080",
407
+ RES02: "#808080",
408
+ RES03: "#808080"
409
+ };
410
+ var M = EINK_MONO_GREYS;
411
+ var S52_EINK_MONO = {
412
+ // No data is DARK, never the land tone: a no-data edge in the land tone
413
+ // reads as a coastline (seen on the Boston render, 2026-10-05).
414
+ NODTA: M.DARK,
415
+ DEPIT: M.PAPER,
416
+ DEPVS: M.PAPER,
417
+ DEPMS: M.PAPER,
418
+ DEPMD: M.PAPER,
419
+ DEPDW: M.PAPER,
420
+ LANDA: M.MID,
421
+ LANDF: M.DARK,
422
+ CHBRN: M.DARK,
423
+ CHGRF: M.MID,
424
+ CHGRD: M.DARK,
425
+ CHBLK: M.INK,
426
+ CSTLN: M.INK,
427
+ OUTLW: M.INK,
428
+ BKAJ1: M.DARK,
429
+ TRFCD: M.INK,
430
+ TRFCF: M.INK,
431
+ RESBL: M.INK,
432
+ APTS1: M.INK,
433
+ DNGHL: M.INK,
434
+ ISHDG: M.INK,
435
+ LITRD: M.INK,
436
+ LITGN: M.INK,
437
+ LITYW: M.INK,
438
+ LITWT: M.PAPER,
439
+ LITBL: M.INK,
440
+ LITOR: M.INK,
441
+ RADHI: M.INK,
442
+ RADLO: M.DARK,
443
+ PLRTE: M.INK,
444
+ ARPAT: M.INK,
445
+ ISDNG: M.INK,
446
+ CHRED: M.DARK,
447
+ CHGRN: M.MID,
448
+ CHYLW: M.MID,
449
+ CHMGD: M.INK,
450
+ CHMGF: M.INK,
451
+ CHWHT: M.PAPER,
452
+ CHCOR: M.MID,
453
+ SNDG1: M.DARK,
454
+ SNDG2: M.INK,
455
+ DEPSC: M.INK,
456
+ DEPCN: M.DARK,
457
+ UINFD: M.INK,
458
+ UINFF: M.DARK,
459
+ UINFR: M.INK,
460
+ UINFG: M.INK,
461
+ UINFO: M.INK,
462
+ UINFB: M.INK,
463
+ UINFM: M.INK,
464
+ UIBCK: M.PAPER,
465
+ UIBDR: M.DARK,
466
+ UIAFD: M.MID,
467
+ UIAFF: M.MID,
468
+ PIPLN: M.INK,
469
+ OUTLL: M.DARK,
470
+ SCLBR: M.INK,
471
+ NINFO: M.INK,
472
+ ADINF: M.DARK,
473
+ RESGR: M.DARK,
474
+ SHIPS: M.INK,
475
+ PSTRK: M.INK,
476
+ SYTRK: M.DARK,
477
+ APLRT: M.INK,
478
+ CURSR: M.INK,
479
+ BKAJ2: M.INK,
480
+ RES01: M.DARK,
481
+ RES02: M.DARK,
482
+ RES03: M.DARK
483
+ };
484
+ var N = { GROUND: M.INK, DIM: M.DARK, LINE: M.MID, BRIGHT: M.PAPER };
485
+ var S52_EINK_NIGHT = {
486
+ NODTA: N.GROUND,
487
+ DEPIT: N.GROUND,
488
+ DEPVS: N.GROUND,
489
+ DEPMS: N.GROUND,
490
+ DEPMD: N.GROUND,
491
+ DEPDW: N.GROUND,
492
+ LANDA: N.DIM,
493
+ LANDF: N.LINE,
494
+ CHBRN: N.LINE,
495
+ CHGRF: N.DIM,
496
+ CHGRD: N.LINE,
497
+ CHBLK: N.LINE,
498
+ CSTLN: N.LINE,
499
+ OUTLW: N.LINE,
500
+ BKAJ1: N.DIM,
501
+ TRFCD: N.LINE,
502
+ TRFCF: N.LINE,
503
+ RESBL: N.LINE,
504
+ APTS1: N.LINE,
505
+ DNGHL: N.BRIGHT,
506
+ ISHDG: N.BRIGHT,
507
+ LITRD: N.BRIGHT,
508
+ LITGN: N.BRIGHT,
509
+ LITYW: N.BRIGHT,
510
+ LITWT: N.BRIGHT,
511
+ LITBL: N.BRIGHT,
512
+ LITOR: N.BRIGHT,
513
+ RADHI: N.LINE,
514
+ RADLO: N.DIM,
515
+ PLRTE: N.LINE,
516
+ ARPAT: N.LINE,
517
+ ISDNG: N.BRIGHT,
518
+ // Mark bodies by tone, as in mono, so they stay apart from each other and
519
+ // from the black water; shape still says which side.
520
+ CHRED: N.DIM,
521
+ CHGRN: N.LINE,
522
+ CHYLW: N.LINE,
523
+ CHMGD: N.LINE,
524
+ CHMGF: N.LINE,
525
+ CHWHT: N.BRIGHT,
526
+ CHCOR: N.LINE,
527
+ SNDG1: N.LINE,
528
+ SNDG2: N.BRIGHT,
529
+ DEPSC: N.BRIGHT,
530
+ DEPCN: N.LINE,
531
+ UINFD: N.LINE,
532
+ UINFF: N.DIM,
533
+ UINFR: N.BRIGHT,
534
+ UINFG: N.LINE,
535
+ UINFO: N.LINE,
536
+ UINFB: N.LINE,
537
+ UINFM: N.LINE,
538
+ UIBCK: N.GROUND,
539
+ UIBDR: N.DIM,
540
+ UIAFD: N.DIM,
541
+ UIAFF: N.DIM,
542
+ PIPLN: N.LINE,
543
+ OUTLL: N.DIM,
544
+ SCLBR: N.LINE,
545
+ NINFO: N.LINE,
546
+ ADINF: N.DIM,
547
+ RESGR: N.DIM,
548
+ SHIPS: N.BRIGHT,
549
+ PSTRK: N.LINE,
550
+ SYTRK: N.DIM,
551
+ APLRT: N.BRIGHT,
552
+ CURSR: N.BRIGHT,
553
+ BKAJ2: N.GROUND,
554
+ RES01: N.DIM,
555
+ RES02: N.DIM,
556
+ RES03: N.DIM
557
+ };
331
558
  var COLOR_TABLES = {
332
559
  DAY: S52_DAY,
333
560
  DUSK: S52_DUSK,
334
- NIGHT: S52_NIGHT
561
+ NIGHT: S52_NIGHT,
562
+ EINK_COLOR: S52_EINK_COLOR,
563
+ EINK_MONO: S52_EINK_MONO,
564
+ EINK_NIGHT: S52_EINK_NIGHT
335
565
  };
336
566
  function resolveColor(token, table = "DAY") {
337
567
  const t = COLOR_TABLES[table];
@@ -1161,6 +1391,11 @@ function applyStrokeBoost(svgString, multiplier) {
1161
1391
  }
1162
1392
  var S52_STROKE_BOOST = 1;
1163
1393
 
1394
+ // src/atlas/map-live.ts
1395
+ function isMapLive(map) {
1396
+ return map._removed !== true;
1397
+ }
1398
+
1164
1399
  // src/atlas/symbol-images-browser.ts
1165
1400
  function dataUrl(svg2) {
1166
1401
  return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg2)}`;
@@ -1301,9 +1536,9 @@ function existingSize(map, id) {
1301
1536
  async function loadSymbolImageSpecs(map, specs) {
1302
1537
  await Promise.all(
1303
1538
  specs.map(async (spec) => {
1304
- if (map.hasImage(spec.id)) return;
1539
+ if (!isMapLive(map) || map.hasImage(spec.id)) return;
1305
1540
  const img = await decode(spec);
1306
- if (!img || map.hasImage(spec.id)) return;
1541
+ if (!img || !isMapLive(map) || map.hasImage(spec.id)) return;
1307
1542
  map.addImage(spec.id, img, { sdf: spec.sdf, pixelRatio: spec.pixelRatio });
1308
1543
  })
1309
1544
  );
@@ -1327,7 +1562,7 @@ async function rethemeSymbolImages(map, specs, opts = {}) {
1327
1562
  }
1328
1563
  const sources = await sourcesFor(unit, decoded[u]);
1329
1564
  for (let i = 0; i < unitSpecs.length; i++) {
1330
- if (opts.signal?.aborted) {
1565
+ if (opts.signal?.aborted || !isMapLive(map)) {
1331
1566
  result.aborted = true;
1332
1567
  break outer;
1333
1568
  }
@@ -1361,6 +1596,10 @@ async function rethemeSymbolImages(map, specs, opts = {}) {
1361
1596
  }
1362
1597
  }
1363
1598
  }
1599
+ if (!isMapLive(map)) {
1600
+ result.aborted = true;
1601
+ return result;
1602
+ }
1364
1603
  map.triggerRepaint();
1365
1604
  opts.onProgress?.({ done, total });
1366
1605
  return result;
@@ -1537,7 +1776,7 @@ async function imageToBitmap(img, size) {
1537
1776
  }
1538
1777
  }
1539
1778
  function addImageToMap(map, name, image) {
1540
- if (map.hasImage(name)) return;
1779
+ if (!isMapLive(map) || map.hasImage(name)) return;
1541
1780
  try {
1542
1781
  map.addImage(name, image, {
1543
1782
  sdf: SDF_SYMBOLS2.has(name),
@@ -1564,6 +1803,7 @@ async function loadS52SymbolImagesCached(map, colorTable = "DAY", opts = {}) {
1564
1803
  cached = /* @__PURE__ */ new Map();
1565
1804
  }
1566
1805
  }
1806
+ if (!isMapLive(map)) return;
1567
1807
  for (const [name, bitmap] of cached) {
1568
1808
  addImageToMap(map, name, bitmap);
1569
1809
  }
@@ -1577,7 +1817,7 @@ async function loadS52SymbolImagesCached(map, colorTable = "DAY", opts = {}) {
1577
1817
  const size = rasterSize2(name, themed);
1578
1818
  const img = await decodeSvgToImage(themed, size);
1579
1819
  if (!img) return;
1580
- addImageToMap(map, name, img);
1820
+ if (isMapLive(map)) addImageToMap(map, name, img);
1581
1821
  try {
1582
1822
  const bitmap = await imageToBitmap(img, size);
1583
1823
  if (!bitmap) return;
@@ -15592,7 +15832,7 @@ async function loadSymbolImages(map) {
15592
15832
  const svgStr = sym.getSvg();
15593
15833
  const img = new Image(SIZE2, SIZE2);
15594
15834
  img.onload = () => {
15595
- if (!map.hasImage(sym.id)) {
15835
+ if (isMapLive(map) && !map.hasImage(sym.id)) {
15596
15836
  map.addImage(sym.id, img, { sdf: false });
15597
15837
  }
15598
15838
  resolve();
@@ -15622,7 +15862,7 @@ async function loadThemedBridgeImages(map, colorTable) {
15622
15862
  const svgStr = sym.getThemedSvg(chblk, trfcd);
15623
15863
  const img = new Image(SIZE2, SIZE2);
15624
15864
  img.onload = () => {
15625
- if (!map.hasImage(sym.id)) {
15865
+ if (isMapLive(map) && !map.hasImage(sym.id)) {
15626
15866
  map.addImage(sym.id, img, { sdf: false });
15627
15867
  }
15628
15868
  resolve();
@@ -15656,6 +15896,9 @@ var CHRT_GLYPH_SVGS = {
15656
15896
  "cardinal-w": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M16 6 12 1.5h8Z" fill="#101418"/><path d="M16 6.5 20 11h-8Z" fill="#101418"/><path d="M13 12h6l1 13H12Z" fill="#FFC805"/><path d="M6 28h20" stroke="#101418" stroke-width="2.4" stroke-linecap="round"/></svg>',
15657
15897
  "daymark-square": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><rect x="6" y="6" width="20" height="20" rx="1.5" fill="#fff"/></svg>',
15658
15898
  "daymark-triangle": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M16 5 28 26H4Z" fill="#fff"/></svg>',
15899
+ "eink-danger": '<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8"><path d="M-2 2 L2 -2 M0 8 L8 0 M6 10 L10 6" stroke="#000000" stroke-width="1" fill="none"/><path d="M-2 6 L2 10 M0 0 L8 8 M6 -2 L10 2" stroke="#000000" stroke-width="1" fill="none"/></svg>',
15900
+ "eink-dots": '<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 10 10"><circle cx="2.5" cy="2.5" r="0.9" fill="#000000"/><circle cx="7.5" cy="7.5" r="0.9" fill="#000000"/></svg>',
15901
+ "eink-hatch": '<svg xmlns="http://www.w3.org/2000/svg" width="8" height="8" viewBox="0 0 8 8"><path d="M-2 2 L2 -2 M0 8 L8 0 M6 10 L10 6" stroke="#000000" stroke-width="0.9" fill="none"/></svg>',
15659
15902
  "hatch-nocoverage": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M-3 9 L9 -3 M-3 21 L21 -3 M3 27 L27 3 M15 27 L27 15" stroke="#8A97A0" stroke-width="0.8" fill="none"/><path d="M-3 15 L15 33 M-3 3 L21 27 M3 -3 L27 21 M15 -3 L27 9" stroke="#8A97A0" stroke-width="0.8" fill="none"/></svg>',
15660
15903
  "hatch-uncharted": '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M-3 9 L9 -3 M-3 21 L21 -3 M3 27 L27 3 M15 27 L27 15" stroke="#5B7383" stroke-width="0.65" fill="none"/></svg>',
15661
15904
  "isolated-danger": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><circle cx="16" cy="3.5" r="2.4" fill="#101418"/><circle cx="16" cy="9.5" r="2.4" fill="#101418"/><path d="M13 13h6l1 12H12Z" fill="#101418"/><rect x="12.4" y="17.5" width="7.2" height="3.4" fill="#E5484D"/><path d="M6 28h20" stroke="#101418" stroke-width="2.4" stroke-linecap="round"/></svg>',
@@ -15698,6 +15941,22 @@ function capLuminance(hex, ceiling) {
15698
15941
  }
15699
15942
  var NIGHT_INK = S52_NIGHT.CHBLK;
15700
15943
  var NIGHT_MUTED_INK = scaleLinearLight(S52_NIGHT.CHBLK, 0.43);
15944
+ function einkMonoGrey(hex) {
15945
+ const l = relativeLuminance(hex);
15946
+ const g = EINK_MONO_GREYS;
15947
+ const steps = [g.INK, g.DARK, g.MID, g.PAPER].map((c) => [c, relativeLuminance(c)]);
15948
+ let best = steps[0];
15949
+ for (const step of steps) if (Math.abs(step[1] - l) < Math.abs(best[1] - l)) best = step;
15950
+ return best[0];
15951
+ }
15952
+ function einkNightGrey(hex) {
15953
+ const l = relativeLuminance(hex);
15954
+ const g = EINK_MONO_GREYS;
15955
+ const steps = [g.DARK, g.MID, g.PAPER].map((c) => [c, relativeLuminance(c)]);
15956
+ let best = steps[0];
15957
+ for (const step of steps) if (Math.abs(step[1] - l) < Math.abs(best[1] - l)) best = step;
15958
+ return best[0];
15959
+ }
15701
15960
 
15702
15961
  // src/atlas/chrt-style.ts
15703
15962
  var CHRT_ICON_PREFIX = "chrt:";
@@ -15722,7 +15981,9 @@ var CHRT_SDF_GLYPHS = /* @__PURE__ */ new Set([
15722
15981
  var CHRT_GLYPH_NAMES = new Set(Object.keys(CHRT_GLYPH_SVGS));
15723
15982
  var HOUSE_BLACK = "#101418";
15724
15983
  function chrtHouseColor(dayHex, palette = "DAY") {
15725
- if (palette === "DAY") return dayHex;
15984
+ if (palette === "DAY" || palette === "EINK_COLOR") return dayHex;
15985
+ if (palette === "EINK_MONO") return einkMonoGrey(dayHex);
15986
+ if (palette === "EINK_NIGHT") return dayHex.toLowerCase() === HOUSE_BLACK.toLowerCase() || dayHex.toLowerCase() === "#000000" ? "#aaaaaa" : einkNightGrey(dayHex);
15726
15987
  const isBlack = dayHex.toLowerCase() === HOUSE_BLACK.toLowerCase();
15727
15988
  if (palette === "DUSK") {
15728
15989
  return isBlack ? dayHex : capLuminance(dayHex, relativeLuminance(resolveColor("CHBLK", "DUSK")));
@@ -15730,7 +15991,7 @@ function chrtHouseColor(dayHex, palette = "DAY") {
15730
15991
  return isBlack ? NIGHT_INK : capLuminance(dayHex, relativeLuminance(NIGHT_MUTED_INK));
15731
15992
  }
15732
15993
  function themeChrtGlyph(svg2, palette = "DAY") {
15733
- if (palette === "DAY") return svg2;
15994
+ if (palette === "DAY" || palette === "EINK_COLOR") return svg2;
15734
15995
  return svg2.replace(
15735
15996
  /(fill|stroke)="(#[0-9a-fA-F]{3}|#[0-9a-fA-F]{6})"/g,
15736
15997
  (_m, attr, hex) => {
@@ -15740,6 +16001,189 @@ function themeChrtGlyph(svg2, palette = "DAY") {
15740
16001
  );
15741
16002
  }
15742
16003
 
16004
+ // src/atlas/base-layers.ts
16005
+ var NOCOVERAGE_PATTERN = "chrt:hatch-nocoverage";
16006
+ var UNCHARTED_PATTERN = "chrt:hatch-uncharted";
16007
+ function inlandHatchColor(palette) {
16008
+ return (BASE_THEMES[palette] ?? BASE_THEMES.DAY).inlandHatch;
16009
+ }
16010
+ function basePatternSvg(name, palette) {
16011
+ const raw = CHRT_GLYPH_SVGS[name];
16012
+ if (!raw) return null;
16013
+ if (`${CHRT_ICON_PREFIX}${name}` === UNCHARTED_PATTERN) {
16014
+ return raw.replace(/stroke="#[0-9a-fA-F]{3,6}"/g, `stroke="${inlandHatchColor(palette)}"`);
16015
+ }
16016
+ if (palette === "EINK_NIGHT" && `${CHRT_ICON_PREFIX}${name}` === NOCOVERAGE_PATTERN) {
16017
+ return raw.replace(/stroke="#[0-9a-fA-F]{3,6}"/g, `stroke="#555555"`);
16018
+ }
16019
+ return themeChrtGlyph(raw, palette);
16020
+ }
16021
+ var BASE_THEMES = {
16022
+ DAY: {
16023
+ // ── `earth` is the jurisdiction tan DIMMED (~0.55), not a blue-grey ──
16024
+ // Outside the jurisdiction the map still owes the reader "what is land".
16025
+ // State 3 in the header is "the geography, under a crosshatch", and until
16026
+ // 2026-08-31 that geography was unreadable: earth #2A3439 against sea
16027
+ // #1B2328 measured 1.25:1, so Hispaniola showed charted boxes with tan
16028
+ // land inside and nothing recognisable around them (owner report: "no
16029
+ // land painting down near the DR at some zooms"). Land is warm and sea is
16030
+ // cool because that is what land and sea are; dimming the JURISDICTION
16031
+ // tan rather than inventing a colour says the true thing — this is the
16032
+ // same land, we simply do not carry charts for it. Held against BOTH
16033
+ // neighbours: legible over sea, and never mistakable for the full tan of
16034
+ // a country we do carry. DAY 3.59:1 vs sea, 3.17:1 vs tan.
16035
+ earth: "#807761",
16036
+ water: "#1B2328",
16037
+ river: "#24313A",
16038
+ boundary: "#4A575E",
16039
+ jurisdiction: "#D4C69D",
16040
+ // CHRTland's own tan — one land colour
16041
+ inlandWater: "#B9CFDD",
16042
+ // muted, unmistakably not a charted blue
16043
+ // The uncharted hatch's stripe, held to 2.3:1 against `inlandWater` in
16044
+ // every palette (`inland-hatch-contrast.test.ts`) — the hatch says "we have
16045
+ // not charted this water", and stripes the eye cannot find say nothing.
16046
+ // DAY 3.08:1, darker than its ground. Also the colour the artwork file
16047
+ // itself is drawn in.
16048
+ inlandHatch: "#5B7383",
16049
+ // State furniture. These three were referenced by the layers but missing
16050
+ // from this table (and its type — the long-standing TS2339 trio), so the
16051
+ // lines shipped on MapLibre's DEFAULT black. Now deliberate: a hairline
16052
+ // a shade deeper than the tan, quiet caps, tan halo.
16053
+ stateLine: "#B1A077",
16054
+ stateLabel: "#8E8468",
16055
+ stateHalo: "#D4C69D",
16056
+ // Rough-lake furniture (owner ask 2026-08-28: "the lakes need labels,
16057
+ // and shorelines drawn"): a shoreline a shade deeper than the rough
16058
+ // water, and a name in the water's own voice, haloed by the water.
16059
+ inlandShore: "#8FA9BA",
16060
+ inlandLabel: "#54707F",
16061
+ inlandHalo: "#B9CFDD"
16062
+ },
16063
+ DUSK: {
16064
+ // ── Re-derived 2026-09-01: the basemap sits UNDER the chart ──────────
16065
+ // These were the DAY tones scaled by a constant, never measured against
16066
+ // the DUSK chart — so `jurisdiction` came out at L=0.506 against a chart
16067
+ // whose own land (LANDA #8a7d5e) is L=0.209, i.e. the context layer was
16068
+ // 2.4x brighter than the data layer. Now capped at the chart's own land.
16069
+ // The rest of the block rides the same 0.666 channel factor so every
16070
+ // relationship the palette already chose is preserved, only moved down.
16071
+ // `water` is deliberately NOT scaled: it is the reference the land/sea
16072
+ // floor is measured against.
16073
+ earth: "#544E3E",
16074
+ water: "#161D21",
16075
+ river: "#141B20",
16076
+ boundary: "#293135",
16077
+ jurisdiction: "#867D64",
16078
+ inlandWater: "#62717A",
16079
+ // Owner's choice, 2026-10-04: dark stripes, 2.37:1. The DAY stripe here
16080
+ // was 1.02:1 — invisible, so an uncharted lake read as plain water.
16081
+ inlandHatch: "#2E383F",
16082
+ // Label stays DARK here — the halo is still mid-toned, so 3:1 is reachable
16083
+ // downward and a bright label would be a new light source at dusk.
16084
+ stateLine: "#B5A987",
16085
+ stateLabel: "#38342A",
16086
+ stateHalo: "#867D64",
16087
+ inlandShore: "#4D5B67",
16088
+ inlandLabel: "#212629",
16089
+ inlandHalo: "#62717A"
16090
+ },
16091
+ NIGHT: {
16092
+ // ── Re-derived 2026-09-01 — the owner-reported night defect ──────────
16093
+ // `jurisdiction` was L=0.240 against a chart whose own land (LANDA
16094
+ // #3a3020) is L=0.031: the world basemap was 7.7x BRIGHTER than the chart
16095
+ // it sits under, so at z<11 the uncharted interior read as a pale
16096
+ // continent-sized slab on a night chart. Capped at the chart's own land.
16097
+ //
16098
+ // ⚠ `earth` AND `jurisdiction` CONVERGE HERE, and that is forced, not
16099
+ // sloppy. Holding earth 2:1 above the ocean AND 2:1 below the jurisdiction
16100
+ // tan requires jurisdiction >= L 0.174 — 5.6x the chart's own land. At
16101
+ // night the range between the ocean (L 0.006) and the cap (L 0.031) is too
16102
+ // compressed to carry two land tones. COVERAGE IS STILL SAID, by the
16103
+ // `chrt:hatch-nocoverage` crosshatch, which is a stronger signal than tone
16104
+ // and is the one thing that cannot be lost to palette compression.
16105
+ //
16106
+ // Labels invert to light-on-dark: 3:1 downward is unreachable off a halo
16107
+ // this close to black, and CHRTland's own night town labels already sit at
16108
+ // L=0.177 for exactly this reason.
16109
+ earth: "#333026",
16110
+ water: "#10161A",
16111
+ river: "#090D0F",
16112
+ boundary: "#131719",
16113
+ jurisdiction: "#353128",
16114
+ inlandWater: "#232A30",
16115
+ // The DAY stripe, lighter than this ground: 2.92:1, and L 0.16, under the
16116
+ // NIGHT muted-ink ceiling (`night-luminance.test.ts`). Red night draws the
16117
+ // NIGHT chart, so this is its stripe too.
16118
+ inlandHatch: "#5B7383",
16119
+ stateLine: "#484236",
16120
+ stateLabel: "#837963",
16121
+ stateHalo: "#353128",
16122
+ inlandShore: "#1C2227",
16123
+ inlandLabel: "#617484",
16124
+ inlandHalo: "#232A30"
16125
+ },
16126
+ // ── E-ink: uncharted is DARK, never paper ─────────────────────────────
16127
+ // On an e-ink chart, charted safe water is paper. Water outside coverage
16128
+ // must never read as that, so it is the darkest fill on the page — the
16129
+ // e-ink twin of DAY's dark carto — with the no-coverage crosshatch over it
16130
+ // (invariant 5: the coverage portrayal survives). Land outside coverage
16131
+ // keeps the chart's own land tone.
16132
+ EINK_COLOR: {
16133
+ earth: "#d8ccb0",
16134
+ water: "#4f5a62",
16135
+ river: "#4f5a62",
16136
+ boundary: "#000000",
16137
+ jurisdiction: "#f2e2b0",
16138
+ inlandWater: "#c8d6e0",
16139
+ // The house stripe, 3.4:1 on this rough water — strong enough for the
16140
+ // 150 ppi colour layer.
16141
+ inlandHatch: "#5B7383",
16142
+ stateLine: "#555555",
16143
+ stateLabel: "#000000",
16144
+ stateHalo: "#ffffff",
16145
+ inlandShore: "#555555",
16146
+ inlandLabel: "#000000",
16147
+ inlandHalo: "#ffffff"
16148
+ },
16149
+ // Mono: the same rule in the four greys. Uncharted water DARK under a MID
16150
+ // crosshatch; land MID; rough inland water PAPER under the uncharted hatch.
16151
+ EINK_MONO: {
16152
+ earth: "#aaaaaa",
16153
+ water: "#555555",
16154
+ river: "#555555",
16155
+ boundary: "#000000",
16156
+ jurisdiction: "#aaaaaa",
16157
+ inlandWater: "#ffffff",
16158
+ inlandHatch: "#555555",
16159
+ stateLine: "#555555",
16160
+ stateLabel: "#000000",
16161
+ stateHalo: "#ffffff",
16162
+ inlandShore: "#555555",
16163
+ inlandLabel: "#000000",
16164
+ inlandHalo: "#ffffff"
16165
+ },
16166
+ // Night: the inverse. Water outside coverage is the black ground UNDER the
16167
+ // light crosshatch — charted safe water is plain black, so the hatch is what
16168
+ // tells them apart, here as everywhere. Land the DIM grey; labels the line
16169
+ // grey on black.
16170
+ EINK_NIGHT: {
16171
+ earth: "#555555",
16172
+ water: "#000000",
16173
+ river: "#000000",
16174
+ boundary: "#aaaaaa",
16175
+ jurisdiction: "#555555",
16176
+ inlandWater: "#000000",
16177
+ inlandHatch: "#aaaaaa",
16178
+ stateLine: "#aaaaaa",
16179
+ stateLabel: "#aaaaaa",
16180
+ stateHalo: "#000000",
16181
+ inlandShore: "#aaaaaa",
16182
+ inlandLabel: "#aaaaaa",
16183
+ inlandHalo: "#000000"
16184
+ }
16185
+ };
16186
+
15743
16187
  // src/atlas/chrtraster-svgs-generated.ts
15744
16188
  var CHRTRASTER_SVGS = {
15745
16189
  "BCNGEN05": '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32"><path d="M 11.6 26.8 L 11.6 1.0 L 20.0 1.0 L 20.0 27.2 L 19.4 25.8 L 19.3 24.7 L 18.9 24.3 L 17.6 23.7 L 16.5 23.4 L 16.2 23.2 L 15.1 23.4 L 14.5 23.4 L 13.8 23.7 L 13.3 24.5 L 12.7 25.3 L 12.0 25.8 L 11.6 26.6 Z" fill="CHWHT" /><path d="M 11.6 26.8 L 11.6 1.0 L 20.0 1.0 L 20.0 27.2 L 19.4 25.8 L 19.3 24.7 L 18.9 24.3 L 17.6 23.7 L 16.5 23.4 L 16.2 23.2 L 15.1 23.4 L 14.5 23.4 L 13.8 23.7 L 13.3 24.5 L 12.7 25.3 L 12.0 25.8 L 11.6 26.6 Z" fill="none" stroke="CHBLK" stroke-width="1.2" stroke-linejoin="round" /><circle cx="16.00" cy="27.21" r="3.79" fill="none" stroke="CHBLK" stroke-width="1.2" /><path d="M 7.3 27.2 L 12.2 27.2" fill="none" stroke="CHBLK" stroke-width="1.2" /><path d="M 19.8 27.2 L 24.7 27.2" fill="none" stroke="CHBLK" stroke-width="1.2" /></svg>',
@@ -16297,10 +16741,20 @@ var S101_NIGHT = {
16297
16741
  // src/atlas/s101-style.ts
16298
16742
  var S101_ICON_PREFIX = "s101:";
16299
16743
  var S101_SYMBOL_NAMES = new Set(Object.keys(S101_POINT_SVGS));
16744
+ function s101EinkTable(einkS52, fallback) {
16745
+ const out = {};
16746
+ for (const [token, dayHex] of Object.entries(S101_DAY)) {
16747
+ out[token] = einkS52[token] ?? fallback(dayHex);
16748
+ }
16749
+ return out;
16750
+ }
16300
16751
  var S101_TABLES = {
16301
16752
  DAY: S101_DAY,
16302
16753
  DUSK: S101_DUSK,
16303
- NIGHT: S101_NIGHT
16754
+ NIGHT: S101_NIGHT,
16755
+ EINK_COLOR: s101EinkTable(S52_EINK_COLOR, (hex) => hex),
16756
+ EINK_MONO: s101EinkTable(S52_EINK_MONO, einkMonoGrey),
16757
+ EINK_NIGHT: s101EinkTable(S52_EINK_NIGHT, einkNightGrey)
16304
16758
  };
16305
16759
  var warnedTokens = /* @__PURE__ */ new Set();
16306
16760
  function reportUnresolvedToken(token, table) {
@@ -16325,7 +16779,17 @@ function getThemedS101Svg(symbolName, table = "DAY") {
16325
16779
 
16326
16780
  // src/atlas/symbol-image-specs.ts
16327
16781
  var SUPERSAMPLE = 2;
16328
- var BASE_PATTERN_GLYPHS = ["hatch-nocoverage", "hatch-uncharted"];
16782
+ var BASE_PATTERN_GLYPHS = [
16783
+ "hatch-nocoverage",
16784
+ "hatch-uncharted",
16785
+ // The e-ink fill patterns (`eink-portrayal.ts`). Only the e-ink palettes
16786
+ // reference them, but they are registered with the hatches in every palette
16787
+ // so a client switching INTO e-ink never draws a pattern-less fill while a
16788
+ // late image load catches up. Three 8 px tiles.
16789
+ "eink-dots",
16790
+ "eink-hatch",
16791
+ "eink-danger"
16792
+ ];
16329
16793
  function upsample(svg2) {
16330
16794
  return svg2.replace(
16331
16795
  /width="(\d+)" height="(\d+)"/,
@@ -16339,10 +16803,9 @@ function declared(id, svg2) {
16339
16803
  }
16340
16804
  function basePatternImageSpecs(palette) {
16341
16805
  return BASE_PATTERN_GLYPHS.flatMap((name) => {
16342
- const raw = CHRT_GLYPH_SVGS[name];
16343
- if (!raw) return [];
16806
+ const svg2 = basePatternSvg(name, palette);
16807
+ if (!svg2) return [];
16344
16808
  const id = `${CHRT_ICON_PREFIX}${name}`;
16345
- const svg2 = themeChrtGlyph(raw, palette);
16346
16809
  return [{ id, svg: svg2, ...declared(id, svg2), pixelRatio: 1, sdf: false }];
16347
16810
  });
16348
16811
  }