@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.
@@ -1,4 +1,10 @@
1
1
  // src/atlas/s52-colors.ts
2
+ var EINK_MONO_GREYS = {
3
+ INK: "#000000",
4
+ DARK: "#555555",
5
+ MID: "#aaaaaa",
6
+ PAPER: "#ffffff"
7
+ };
2
8
  var S52_DAY = {
3
9
  // Depth
4
10
  NODTA: "#a4a4a4",
@@ -279,10 +285,234 @@ var S52_NIGHT = {
279
285
  RES02: "#383028",
280
286
  RES03: "#383028"
281
287
  };
288
+ var S52_EINK_COLOR = {
289
+ NODTA: "#b4b4b4",
290
+ DEPIT: "#4f8f3a",
291
+ DEPVS: "#74ade0",
292
+ DEPMS: "#bad6ef",
293
+ DEPMD: "#ffffff",
294
+ DEPDW: "#ffffff",
295
+ LANDA: "#f2e2b0",
296
+ LANDF: "#c49a4a",
297
+ CHBRN: "#8b5a2b",
298
+ CHGRF: "#9a9a9a",
299
+ CHGRD: "#444444",
300
+ CHBLK: "#000000",
301
+ CSTLN: "#000000",
302
+ OUTLW: "#000000",
303
+ BKAJ1: "#333333",
304
+ TRFCD: "#c800c8",
305
+ TRFCF: "#c800c8",
306
+ RESBL: "#0050c8",
307
+ APTS1: "#c800c8",
308
+ DNGHL: "#e00000",
309
+ ISHDG: "#e00000",
310
+ LITRD: "#e00000",
311
+ LITGN: "#00a000",
312
+ LITYW: "#f0c000",
313
+ LITWT: "#ffffff",
314
+ LITBL: "#0050c8",
315
+ LITOR: "#f07000",
316
+ RADHI: "#00a000",
317
+ RADLO: "#006000",
318
+ PLRTE: "#e00000",
319
+ ARPAT: "#008040",
320
+ ISDNG: "#c800c8",
321
+ CHRED: "#e00000",
322
+ CHGRN: "#00a000",
323
+ CHYLW: "#f0c000",
324
+ CHMGD: "#c800c8",
325
+ CHMGF: "#c800c8",
326
+ CHWHT: "#ffffff",
327
+ CHCOR: "#f07000",
328
+ // Deeper-than-safety soundings: dark grey, the one secondary ink. Shoal
329
+ // soundings are black — the louder numeral, as in every other table.
330
+ SNDG1: "#555555",
331
+ SNDG2: "#000000",
332
+ DEPSC: "#000000",
333
+ DEPCN: "#555555",
334
+ UINFD: "#000000",
335
+ UINFF: "#555555",
336
+ UINFR: "#e00000",
337
+ UINFG: "#00a000",
338
+ UINFO: "#f07000",
339
+ UINFB: "#0050c8",
340
+ UINFM: "#c800c8",
341
+ UIBCK: "#ffffff",
342
+ UIBDR: "#555555",
343
+ UIAFD: "#74ade0",
344
+ UIAFF: "#c49a4a",
345
+ PIPLN: "#008000",
346
+ OUTLL: "#c49a4a",
347
+ SCLBR: "#f07000",
348
+ NINFO: "#f07000",
349
+ ADINF: "#b08800",
350
+ RESGR: "#555555",
351
+ SHIPS: "#000000",
352
+ PSTRK: "#000000",
353
+ SYTRK: "#555555",
354
+ APLRT: "#f07000",
355
+ CURSR: "#f07000",
356
+ BKAJ2: "#000000",
357
+ RES01: "#808080",
358
+ RES02: "#808080",
359
+ RES03: "#808080"
360
+ };
361
+ var M = EINK_MONO_GREYS;
362
+ var S52_EINK_MONO = {
363
+ // No data is DARK, never the land tone: a no-data edge in the land tone
364
+ // reads as a coastline (seen on the Boston render, 2026-10-05).
365
+ NODTA: M.DARK,
366
+ DEPIT: M.PAPER,
367
+ DEPVS: M.PAPER,
368
+ DEPMS: M.PAPER,
369
+ DEPMD: M.PAPER,
370
+ DEPDW: M.PAPER,
371
+ LANDA: M.MID,
372
+ LANDF: M.DARK,
373
+ CHBRN: M.DARK,
374
+ CHGRF: M.MID,
375
+ CHGRD: M.DARK,
376
+ CHBLK: M.INK,
377
+ CSTLN: M.INK,
378
+ OUTLW: M.INK,
379
+ BKAJ1: M.DARK,
380
+ TRFCD: M.INK,
381
+ TRFCF: M.INK,
382
+ RESBL: M.INK,
383
+ APTS1: M.INK,
384
+ DNGHL: M.INK,
385
+ ISHDG: M.INK,
386
+ LITRD: M.INK,
387
+ LITGN: M.INK,
388
+ LITYW: M.INK,
389
+ LITWT: M.PAPER,
390
+ LITBL: M.INK,
391
+ LITOR: M.INK,
392
+ RADHI: M.INK,
393
+ RADLO: M.DARK,
394
+ PLRTE: M.INK,
395
+ ARPAT: M.INK,
396
+ ISDNG: M.INK,
397
+ CHRED: M.DARK,
398
+ CHGRN: M.MID,
399
+ CHYLW: M.MID,
400
+ CHMGD: M.INK,
401
+ CHMGF: M.INK,
402
+ CHWHT: M.PAPER,
403
+ CHCOR: M.MID,
404
+ SNDG1: M.DARK,
405
+ SNDG2: M.INK,
406
+ DEPSC: M.INK,
407
+ DEPCN: M.DARK,
408
+ UINFD: M.INK,
409
+ UINFF: M.DARK,
410
+ UINFR: M.INK,
411
+ UINFG: M.INK,
412
+ UINFO: M.INK,
413
+ UINFB: M.INK,
414
+ UINFM: M.INK,
415
+ UIBCK: M.PAPER,
416
+ UIBDR: M.DARK,
417
+ UIAFD: M.MID,
418
+ UIAFF: M.MID,
419
+ PIPLN: M.INK,
420
+ OUTLL: M.DARK,
421
+ SCLBR: M.INK,
422
+ NINFO: M.INK,
423
+ ADINF: M.DARK,
424
+ RESGR: M.DARK,
425
+ SHIPS: M.INK,
426
+ PSTRK: M.INK,
427
+ SYTRK: M.DARK,
428
+ APLRT: M.INK,
429
+ CURSR: M.INK,
430
+ BKAJ2: M.INK,
431
+ RES01: M.DARK,
432
+ RES02: M.DARK,
433
+ RES03: M.DARK
434
+ };
435
+ var N = { GROUND: M.INK, DIM: M.DARK, LINE: M.MID, BRIGHT: M.PAPER };
436
+ var S52_EINK_NIGHT = {
437
+ NODTA: N.GROUND,
438
+ DEPIT: N.GROUND,
439
+ DEPVS: N.GROUND,
440
+ DEPMS: N.GROUND,
441
+ DEPMD: N.GROUND,
442
+ DEPDW: N.GROUND,
443
+ LANDA: N.DIM,
444
+ LANDF: N.LINE,
445
+ CHBRN: N.LINE,
446
+ CHGRF: N.DIM,
447
+ CHGRD: N.LINE,
448
+ CHBLK: N.LINE,
449
+ CSTLN: N.LINE,
450
+ OUTLW: N.LINE,
451
+ BKAJ1: N.DIM,
452
+ TRFCD: N.LINE,
453
+ TRFCF: N.LINE,
454
+ RESBL: N.LINE,
455
+ APTS1: N.LINE,
456
+ DNGHL: N.BRIGHT,
457
+ ISHDG: N.BRIGHT,
458
+ LITRD: N.BRIGHT,
459
+ LITGN: N.BRIGHT,
460
+ LITYW: N.BRIGHT,
461
+ LITWT: N.BRIGHT,
462
+ LITBL: N.BRIGHT,
463
+ LITOR: N.BRIGHT,
464
+ RADHI: N.LINE,
465
+ RADLO: N.DIM,
466
+ PLRTE: N.LINE,
467
+ ARPAT: N.LINE,
468
+ ISDNG: N.BRIGHT,
469
+ // Mark bodies by tone, as in mono, so they stay apart from each other and
470
+ // from the black water; shape still says which side.
471
+ CHRED: N.DIM,
472
+ CHGRN: N.LINE,
473
+ CHYLW: N.LINE,
474
+ CHMGD: N.LINE,
475
+ CHMGF: N.LINE,
476
+ CHWHT: N.BRIGHT,
477
+ CHCOR: N.LINE,
478
+ SNDG1: N.LINE,
479
+ SNDG2: N.BRIGHT,
480
+ DEPSC: N.BRIGHT,
481
+ DEPCN: N.LINE,
482
+ UINFD: N.LINE,
483
+ UINFF: N.DIM,
484
+ UINFR: N.BRIGHT,
485
+ UINFG: N.LINE,
486
+ UINFO: N.LINE,
487
+ UINFB: N.LINE,
488
+ UINFM: N.LINE,
489
+ UIBCK: N.GROUND,
490
+ UIBDR: N.DIM,
491
+ UIAFD: N.DIM,
492
+ UIAFF: N.DIM,
493
+ PIPLN: N.LINE,
494
+ OUTLL: N.DIM,
495
+ SCLBR: N.LINE,
496
+ NINFO: N.LINE,
497
+ ADINF: N.DIM,
498
+ RESGR: N.DIM,
499
+ SHIPS: N.BRIGHT,
500
+ PSTRK: N.LINE,
501
+ SYTRK: N.DIM,
502
+ APLRT: N.BRIGHT,
503
+ CURSR: N.BRIGHT,
504
+ BKAJ2: N.GROUND,
505
+ RES01: N.DIM,
506
+ RES02: N.DIM,
507
+ RES03: N.DIM
508
+ };
282
509
  var COLOR_TABLES = {
283
510
  DAY: S52_DAY,
284
511
  DUSK: S52_DUSK,
285
- NIGHT: S52_NIGHT
512
+ NIGHT: S52_NIGHT,
513
+ EINK_COLOR: S52_EINK_COLOR,
514
+ EINK_MONO: S52_EINK_MONO,
515
+ EINK_NIGHT: S52_EINK_NIGHT
286
516
  };
287
517
  function resolveColor(token, table = "DAY") {
288
518
  const t = COLOR_TABLES[table];
@@ -1112,6 +1342,11 @@ function applyStrokeBoost(svgString, multiplier) {
1112
1342
  }
1113
1343
  var S52_STROKE_BOOST = 1;
1114
1344
 
1345
+ // src/atlas/map-live.ts
1346
+ function isMapLive(map) {
1347
+ return map._removed !== true;
1348
+ }
1349
+
1115
1350
  // src/atlas/symbol-images-browser.ts
1116
1351
  function dataUrl(svg2) {
1117
1352
  return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg2)}`;
@@ -1252,9 +1487,9 @@ function existingSize(map, id) {
1252
1487
  async function loadSymbolImageSpecs(map, specs) {
1253
1488
  await Promise.all(
1254
1489
  specs.map(async (spec) => {
1255
- if (map.hasImage(spec.id)) return;
1490
+ if (!isMapLive(map) || map.hasImage(spec.id)) return;
1256
1491
  const img = await decode(spec);
1257
- if (!img || map.hasImage(spec.id)) return;
1492
+ if (!img || !isMapLive(map) || map.hasImage(spec.id)) return;
1258
1493
  map.addImage(spec.id, img, { sdf: spec.sdf, pixelRatio: spec.pixelRatio });
1259
1494
  })
1260
1495
  );
@@ -1278,7 +1513,7 @@ async function rethemeSymbolImages(map, specs, opts = {}) {
1278
1513
  }
1279
1514
  const sources = await sourcesFor(unit, decoded[u]);
1280
1515
  for (let i = 0; i < unitSpecs.length; i++) {
1281
- if (opts.signal?.aborted) {
1516
+ if (opts.signal?.aborted || !isMapLive(map)) {
1282
1517
  result.aborted = true;
1283
1518
  break outer;
1284
1519
  }
@@ -1312,6 +1547,10 @@ async function rethemeSymbolImages(map, specs, opts = {}) {
1312
1547
  }
1313
1548
  }
1314
1549
  }
1550
+ if (!isMapLive(map)) {
1551
+ result.aborted = true;
1552
+ return result;
1553
+ }
1315
1554
  map.triggerRepaint();
1316
1555
  opts.onProgress?.({ done, total });
1317
1556
  return result;
@@ -1488,7 +1727,7 @@ async function imageToBitmap(img, size) {
1488
1727
  }
1489
1728
  }
1490
1729
  function addImageToMap(map, name, image) {
1491
- if (map.hasImage(name)) return;
1730
+ if (!isMapLive(map) || map.hasImage(name)) return;
1492
1731
  try {
1493
1732
  map.addImage(name, image, {
1494
1733
  sdf: SDF_SYMBOLS2.has(name),
@@ -1515,6 +1754,7 @@ async function loadS52SymbolImagesCached(map, colorTable = "DAY", opts = {}) {
1515
1754
  cached = /* @__PURE__ */ new Map();
1516
1755
  }
1517
1756
  }
1757
+ if (!isMapLive(map)) return;
1518
1758
  for (const [name, bitmap] of cached) {
1519
1759
  addImageToMap(map, name, bitmap);
1520
1760
  }
@@ -1528,7 +1768,7 @@ async function loadS52SymbolImagesCached(map, colorTable = "DAY", opts = {}) {
1528
1768
  const size = rasterSize2(name, themed);
1529
1769
  const img = await decodeSvgToImage(themed, size);
1530
1770
  if (!img) return;
1531
- addImageToMap(map, name, img);
1771
+ if (isMapLive(map)) addImageToMap(map, name, img);
1532
1772
  try {
1533
1773
  const bitmap = await imageToBitmap(img, size);
1534
1774
  if (!bitmap) return;
@@ -15543,7 +15783,7 @@ async function loadSymbolImages(map) {
15543
15783
  const svgStr = sym.getSvg();
15544
15784
  const img = new Image(SIZE2, SIZE2);
15545
15785
  img.onload = () => {
15546
- if (!map.hasImage(sym.id)) {
15786
+ if (isMapLive(map) && !map.hasImage(sym.id)) {
15547
15787
  map.addImage(sym.id, img, { sdf: false });
15548
15788
  }
15549
15789
  resolve();
@@ -15573,7 +15813,7 @@ async function loadThemedBridgeImages(map, colorTable) {
15573
15813
  const svgStr = sym.getThemedSvg(chblk, trfcd);
15574
15814
  const img = new Image(SIZE2, SIZE2);
15575
15815
  img.onload = () => {
15576
- if (!map.hasImage(sym.id)) {
15816
+ if (isMapLive(map) && !map.hasImage(sym.id)) {
15577
15817
  map.addImage(sym.id, img, { sdf: false });
15578
15818
  }
15579
15819
  resolve();
@@ -15607,6 +15847,9 @@ var CHRT_GLYPH_SVGS = {
15607
15847
  "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>',
15608
15848
  "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>',
15609
15849
  "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>',
15850
+ "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>',
15851
+ "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>',
15852
+ "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>',
15610
15853
  "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>',
15611
15854
  "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>',
15612
15855
  "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>',
@@ -15649,6 +15892,22 @@ function capLuminance(hex, ceiling) {
15649
15892
  }
15650
15893
  var NIGHT_INK = S52_NIGHT.CHBLK;
15651
15894
  var NIGHT_MUTED_INK = scaleLinearLight(S52_NIGHT.CHBLK, 0.43);
15895
+ function einkMonoGrey(hex) {
15896
+ const l = relativeLuminance(hex);
15897
+ const g = EINK_MONO_GREYS;
15898
+ const steps = [g.INK, g.DARK, g.MID, g.PAPER].map((c) => [c, relativeLuminance(c)]);
15899
+ let best = steps[0];
15900
+ for (const step of steps) if (Math.abs(step[1] - l) < Math.abs(best[1] - l)) best = step;
15901
+ return best[0];
15902
+ }
15903
+ function einkNightGrey(hex) {
15904
+ const l = relativeLuminance(hex);
15905
+ const g = EINK_MONO_GREYS;
15906
+ const steps = [g.DARK, g.MID, g.PAPER].map((c) => [c, relativeLuminance(c)]);
15907
+ let best = steps[0];
15908
+ for (const step of steps) if (Math.abs(step[1] - l) < Math.abs(best[1] - l)) best = step;
15909
+ return best[0];
15910
+ }
15652
15911
 
15653
15912
  // src/atlas/chrt-style.ts
15654
15913
  var CHRT_ICON_PREFIX = "chrt:";
@@ -15673,7 +15932,9 @@ var CHRT_SDF_GLYPHS = /* @__PURE__ */ new Set([
15673
15932
  var CHRT_GLYPH_NAMES = new Set(Object.keys(CHRT_GLYPH_SVGS));
15674
15933
  var HOUSE_BLACK = "#101418";
15675
15934
  function chrtHouseColor(dayHex, palette = "DAY") {
15676
- if (palette === "DAY") return dayHex;
15935
+ if (palette === "DAY" || palette === "EINK_COLOR") return dayHex;
15936
+ if (palette === "EINK_MONO") return einkMonoGrey(dayHex);
15937
+ if (palette === "EINK_NIGHT") return dayHex.toLowerCase() === HOUSE_BLACK.toLowerCase() || dayHex.toLowerCase() === "#000000" ? "#aaaaaa" : einkNightGrey(dayHex);
15677
15938
  const isBlack = dayHex.toLowerCase() === HOUSE_BLACK.toLowerCase();
15678
15939
  if (palette === "DUSK") {
15679
15940
  return isBlack ? dayHex : capLuminance(dayHex, relativeLuminance(resolveColor("CHBLK", "DUSK")));
@@ -15681,7 +15942,7 @@ function chrtHouseColor(dayHex, palette = "DAY") {
15681
15942
  return isBlack ? NIGHT_INK : capLuminance(dayHex, relativeLuminance(NIGHT_MUTED_INK));
15682
15943
  }
15683
15944
  function themeChrtGlyph(svg2, palette = "DAY") {
15684
- if (palette === "DAY") return svg2;
15945
+ if (palette === "DAY" || palette === "EINK_COLOR") return svg2;
15685
15946
  return svg2.replace(
15686
15947
  /(fill|stroke)="(#[0-9a-fA-F]{3}|#[0-9a-fA-F]{6})"/g,
15687
15948
  (_m, attr, hex) => {
@@ -15691,6 +15952,189 @@ function themeChrtGlyph(svg2, palette = "DAY") {
15691
15952
  );
15692
15953
  }
15693
15954
 
15955
+ // src/atlas/base-layers.ts
15956
+ var NOCOVERAGE_PATTERN = "chrt:hatch-nocoverage";
15957
+ var UNCHARTED_PATTERN = "chrt:hatch-uncharted";
15958
+ function inlandHatchColor(palette) {
15959
+ return (BASE_THEMES[palette] ?? BASE_THEMES.DAY).inlandHatch;
15960
+ }
15961
+ function basePatternSvg(name, palette) {
15962
+ const raw = CHRT_GLYPH_SVGS[name];
15963
+ if (!raw) return null;
15964
+ if (`${CHRT_ICON_PREFIX}${name}` === UNCHARTED_PATTERN) {
15965
+ return raw.replace(/stroke="#[0-9a-fA-F]{3,6}"/g, `stroke="${inlandHatchColor(palette)}"`);
15966
+ }
15967
+ if (palette === "EINK_NIGHT" && `${CHRT_ICON_PREFIX}${name}` === NOCOVERAGE_PATTERN) {
15968
+ return raw.replace(/stroke="#[0-9a-fA-F]{3,6}"/g, `stroke="#555555"`);
15969
+ }
15970
+ return themeChrtGlyph(raw, palette);
15971
+ }
15972
+ var BASE_THEMES = {
15973
+ DAY: {
15974
+ // ── `earth` is the jurisdiction tan DIMMED (~0.55), not a blue-grey ──
15975
+ // Outside the jurisdiction the map still owes the reader "what is land".
15976
+ // State 3 in the header is "the geography, under a crosshatch", and until
15977
+ // 2026-08-31 that geography was unreadable: earth #2A3439 against sea
15978
+ // #1B2328 measured 1.25:1, so Hispaniola showed charted boxes with tan
15979
+ // land inside and nothing recognisable around them (owner report: "no
15980
+ // land painting down near the DR at some zooms"). Land is warm and sea is
15981
+ // cool because that is what land and sea are; dimming the JURISDICTION
15982
+ // tan rather than inventing a colour says the true thing — this is the
15983
+ // same land, we simply do not carry charts for it. Held against BOTH
15984
+ // neighbours: legible over sea, and never mistakable for the full tan of
15985
+ // a country we do carry. DAY 3.59:1 vs sea, 3.17:1 vs tan.
15986
+ earth: "#807761",
15987
+ water: "#1B2328",
15988
+ river: "#24313A",
15989
+ boundary: "#4A575E",
15990
+ jurisdiction: "#D4C69D",
15991
+ // CHRTland's own tan — one land colour
15992
+ inlandWater: "#B9CFDD",
15993
+ // muted, unmistakably not a charted blue
15994
+ // The uncharted hatch's stripe, held to 2.3:1 against `inlandWater` in
15995
+ // every palette (`inland-hatch-contrast.test.ts`) — the hatch says "we have
15996
+ // not charted this water", and stripes the eye cannot find say nothing.
15997
+ // DAY 3.08:1, darker than its ground. Also the colour the artwork file
15998
+ // itself is drawn in.
15999
+ inlandHatch: "#5B7383",
16000
+ // State furniture. These three were referenced by the layers but missing
16001
+ // from this table (and its type — the long-standing TS2339 trio), so the
16002
+ // lines shipped on MapLibre's DEFAULT black. Now deliberate: a hairline
16003
+ // a shade deeper than the tan, quiet caps, tan halo.
16004
+ stateLine: "#B1A077",
16005
+ stateLabel: "#8E8468",
16006
+ stateHalo: "#D4C69D",
16007
+ // Rough-lake furniture (owner ask 2026-08-28: "the lakes need labels,
16008
+ // and shorelines drawn"): a shoreline a shade deeper than the rough
16009
+ // water, and a name in the water's own voice, haloed by the water.
16010
+ inlandShore: "#8FA9BA",
16011
+ inlandLabel: "#54707F",
16012
+ inlandHalo: "#B9CFDD"
16013
+ },
16014
+ DUSK: {
16015
+ // ── Re-derived 2026-09-01: the basemap sits UNDER the chart ──────────
16016
+ // These were the DAY tones scaled by a constant, never measured against
16017
+ // the DUSK chart — so `jurisdiction` came out at L=0.506 against a chart
16018
+ // whose own land (LANDA #8a7d5e) is L=0.209, i.e. the context layer was
16019
+ // 2.4x brighter than the data layer. Now capped at the chart's own land.
16020
+ // The rest of the block rides the same 0.666 channel factor so every
16021
+ // relationship the palette already chose is preserved, only moved down.
16022
+ // `water` is deliberately NOT scaled: it is the reference the land/sea
16023
+ // floor is measured against.
16024
+ earth: "#544E3E",
16025
+ water: "#161D21",
16026
+ river: "#141B20",
16027
+ boundary: "#293135",
16028
+ jurisdiction: "#867D64",
16029
+ inlandWater: "#62717A",
16030
+ // Owner's choice, 2026-10-04: dark stripes, 2.37:1. The DAY stripe here
16031
+ // was 1.02:1 — invisible, so an uncharted lake read as plain water.
16032
+ inlandHatch: "#2E383F",
16033
+ // Label stays DARK here — the halo is still mid-toned, so 3:1 is reachable
16034
+ // downward and a bright label would be a new light source at dusk.
16035
+ stateLine: "#B5A987",
16036
+ stateLabel: "#38342A",
16037
+ stateHalo: "#867D64",
16038
+ inlandShore: "#4D5B67",
16039
+ inlandLabel: "#212629",
16040
+ inlandHalo: "#62717A"
16041
+ },
16042
+ NIGHT: {
16043
+ // ── Re-derived 2026-09-01 — the owner-reported night defect ──────────
16044
+ // `jurisdiction` was L=0.240 against a chart whose own land (LANDA
16045
+ // #3a3020) is L=0.031: the world basemap was 7.7x BRIGHTER than the chart
16046
+ // it sits under, so at z<11 the uncharted interior read as a pale
16047
+ // continent-sized slab on a night chart. Capped at the chart's own land.
16048
+ //
16049
+ // ⚠ `earth` AND `jurisdiction` CONVERGE HERE, and that is forced, not
16050
+ // sloppy. Holding earth 2:1 above the ocean AND 2:1 below the jurisdiction
16051
+ // tan requires jurisdiction >= L 0.174 — 5.6x the chart's own land. At
16052
+ // night the range between the ocean (L 0.006) and the cap (L 0.031) is too
16053
+ // compressed to carry two land tones. COVERAGE IS STILL SAID, by the
16054
+ // `chrt:hatch-nocoverage` crosshatch, which is a stronger signal than tone
16055
+ // and is the one thing that cannot be lost to palette compression.
16056
+ //
16057
+ // Labels invert to light-on-dark: 3:1 downward is unreachable off a halo
16058
+ // this close to black, and CHRTland's own night town labels already sit at
16059
+ // L=0.177 for exactly this reason.
16060
+ earth: "#333026",
16061
+ water: "#10161A",
16062
+ river: "#090D0F",
16063
+ boundary: "#131719",
16064
+ jurisdiction: "#353128",
16065
+ inlandWater: "#232A30",
16066
+ // The DAY stripe, lighter than this ground: 2.92:1, and L 0.16, under the
16067
+ // NIGHT muted-ink ceiling (`night-luminance.test.ts`). Red night draws the
16068
+ // NIGHT chart, so this is its stripe too.
16069
+ inlandHatch: "#5B7383",
16070
+ stateLine: "#484236",
16071
+ stateLabel: "#837963",
16072
+ stateHalo: "#353128",
16073
+ inlandShore: "#1C2227",
16074
+ inlandLabel: "#617484",
16075
+ inlandHalo: "#232A30"
16076
+ },
16077
+ // ── E-ink: uncharted is DARK, never paper ─────────────────────────────
16078
+ // On an e-ink chart, charted safe water is paper. Water outside coverage
16079
+ // must never read as that, so it is the darkest fill on the page — the
16080
+ // e-ink twin of DAY's dark carto — with the no-coverage crosshatch over it
16081
+ // (invariant 5: the coverage portrayal survives). Land outside coverage
16082
+ // keeps the chart's own land tone.
16083
+ EINK_COLOR: {
16084
+ earth: "#d8ccb0",
16085
+ water: "#4f5a62",
16086
+ river: "#4f5a62",
16087
+ boundary: "#000000",
16088
+ jurisdiction: "#f2e2b0",
16089
+ inlandWater: "#c8d6e0",
16090
+ // The house stripe, 3.4:1 on this rough water — strong enough for the
16091
+ // 150 ppi colour layer.
16092
+ inlandHatch: "#5B7383",
16093
+ stateLine: "#555555",
16094
+ stateLabel: "#000000",
16095
+ stateHalo: "#ffffff",
16096
+ inlandShore: "#555555",
16097
+ inlandLabel: "#000000",
16098
+ inlandHalo: "#ffffff"
16099
+ },
16100
+ // Mono: the same rule in the four greys. Uncharted water DARK under a MID
16101
+ // crosshatch; land MID; rough inland water PAPER under the uncharted hatch.
16102
+ EINK_MONO: {
16103
+ earth: "#aaaaaa",
16104
+ water: "#555555",
16105
+ river: "#555555",
16106
+ boundary: "#000000",
16107
+ jurisdiction: "#aaaaaa",
16108
+ inlandWater: "#ffffff",
16109
+ inlandHatch: "#555555",
16110
+ stateLine: "#555555",
16111
+ stateLabel: "#000000",
16112
+ stateHalo: "#ffffff",
16113
+ inlandShore: "#555555",
16114
+ inlandLabel: "#000000",
16115
+ inlandHalo: "#ffffff"
16116
+ },
16117
+ // Night: the inverse. Water outside coverage is the black ground UNDER the
16118
+ // light crosshatch — charted safe water is plain black, so the hatch is what
16119
+ // tells them apart, here as everywhere. Land the DIM grey; labels the line
16120
+ // grey on black.
16121
+ EINK_NIGHT: {
16122
+ earth: "#555555",
16123
+ water: "#000000",
16124
+ river: "#000000",
16125
+ boundary: "#aaaaaa",
16126
+ jurisdiction: "#555555",
16127
+ inlandWater: "#000000",
16128
+ inlandHatch: "#aaaaaa",
16129
+ stateLine: "#aaaaaa",
16130
+ stateLabel: "#aaaaaa",
16131
+ stateHalo: "#000000",
16132
+ inlandShore: "#aaaaaa",
16133
+ inlandLabel: "#aaaaaa",
16134
+ inlandHalo: "#000000"
16135
+ }
16136
+ };
16137
+
15694
16138
  // src/atlas/chrtraster-svgs-generated.ts
15695
16139
  var CHRTRASTER_SVGS = {
15696
16140
  "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>',
@@ -16248,10 +16692,20 @@ var S101_NIGHT = {
16248
16692
  // src/atlas/s101-style.ts
16249
16693
  var S101_ICON_PREFIX = "s101:";
16250
16694
  var S101_SYMBOL_NAMES = new Set(Object.keys(S101_POINT_SVGS));
16695
+ function s101EinkTable(einkS52, fallback) {
16696
+ const out = {};
16697
+ for (const [token, dayHex] of Object.entries(S101_DAY)) {
16698
+ out[token] = einkS52[token] ?? fallback(dayHex);
16699
+ }
16700
+ return out;
16701
+ }
16251
16702
  var S101_TABLES = {
16252
16703
  DAY: S101_DAY,
16253
16704
  DUSK: S101_DUSK,
16254
- NIGHT: S101_NIGHT
16705
+ NIGHT: S101_NIGHT,
16706
+ EINK_COLOR: s101EinkTable(S52_EINK_COLOR, (hex) => hex),
16707
+ EINK_MONO: s101EinkTable(S52_EINK_MONO, einkMonoGrey),
16708
+ EINK_NIGHT: s101EinkTable(S52_EINK_NIGHT, einkNightGrey)
16255
16709
  };
16256
16710
  var warnedTokens = /* @__PURE__ */ new Set();
16257
16711
  function reportUnresolvedToken(token, table) {
@@ -16276,7 +16730,17 @@ function getThemedS101Svg(symbolName, table = "DAY") {
16276
16730
 
16277
16731
  // src/atlas/symbol-image-specs.ts
16278
16732
  var SUPERSAMPLE = 2;
16279
- var BASE_PATTERN_GLYPHS = ["hatch-nocoverage", "hatch-uncharted"];
16733
+ var BASE_PATTERN_GLYPHS = [
16734
+ "hatch-nocoverage",
16735
+ "hatch-uncharted",
16736
+ // The e-ink fill patterns (`eink-portrayal.ts`). Only the e-ink palettes
16737
+ // reference them, but they are registered with the hatches in every palette
16738
+ // so a client switching INTO e-ink never draws a pattern-less fill while a
16739
+ // late image load catches up. Three 8 px tiles.
16740
+ "eink-dots",
16741
+ "eink-hatch",
16742
+ "eink-danger"
16743
+ ];
16280
16744
  function upsample(svg2) {
16281
16745
  return svg2.replace(
16282
16746
  /width="(\d+)" height="(\d+)"/,
@@ -16290,10 +16754,9 @@ function declared(id, svg2) {
16290
16754
  }
16291
16755
  function basePatternImageSpecs(palette) {
16292
16756
  return BASE_PATTERN_GLYPHS.flatMap((name) => {
16293
- const raw = CHRT_GLYPH_SVGS[name];
16294
- if (!raw) return [];
16757
+ const svg2 = basePatternSvg(name, palette);
16758
+ if (!svg2) return [];
16295
16759
  const id = `${CHRT_ICON_PREFIX}${name}`;
16296
- const svg2 = themeChrtGlyph(raw, palette);
16297
16760
  return [{ id, svg: svg2, ...declared(id, svg2), pixelRatio: 1, sdf: false }];
16298
16761
  });
16299
16762
  }