@chrtco/sdk 0.10.46 → 0.10.48

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.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,28 @@ All notable changes to `@chrtco/sdk` will be documented in this file.
5
5
  ## [Unreleased]
6
6
 
7
7
  ### Added
8
+ - **`EINK_NIGHT`** — the e-ink night palette (`theme=eink_night`, offline `palette=EINK_NIGHT`,
9
+ `EINK_COLOR_TABLES.night`): `EINK_MONO`'s four greys on a black page, for any e-ink screen at
10
+ night with the front light on, in place of red night. Water black; lines and labels `#aaaaaa`;
11
+ only the safety contour, dangers, lights, shoal soundings and white marks white; land `#555555`;
12
+ depth patterns drawn light; the chart background crosshatched so no-data never reads as black safe
13
+ water. `isEinkGreyTable` (mono or night), `S52_EINK_NIGHT`. In every e-ink palette, label halos
14
+ are now the page's own ground (`UIBCK`).
15
+ - **E-ink chart palettes: `EINK_COLOR` and `EINK_MONO`.** Two new `ColorTableName`s for
16
+ reflective e-ink screens — colour e-ink (E Ink Kaleido 3) and monochrome e-ink — usable
17
+ everywhere a palette is: `chrt.style({ palette: 'EINK_MONO' })`, `chrt.styleUrl({ theme:
18
+ 'eink_mono' })` (`ChrtStyleTheme` gains `eink_color` / `eink_mono`), the offline style
19
+ (`palette: 'EINK_COLOR' | 'EINK_MONO'`), and every symbol-image loader. Same features, classes
20
+ and priorities as `DAY`; `generateStyle` runs an e-ink portrayal pass for them
21
+ (`applyEinkPortrayal`): lines ×1.5, text ×1.15 on paper halos, a heavy safety-contour line
22
+ (`safetyContourValues`), danger symbols ×1.35 and cross-hatched danger areas, depth in at most
23
+ four steps (mono: by fill pattern), no translucent washes, hillshade, glows or transitions.
24
+ `EINK_MONO` paints only the four `EINK_MONO_GREYS`. New exports: `EINK_COLOR_TABLES`,
25
+ `isEinkColorTable`, `EINK_MONO_GREYS`, `S52_EINK_COLOR`, `S52_EINK_MONO`,
26
+ `applyEinkPortrayal`, `safetyContourValues`, and the three pattern ids. The world-hatch image set
27
+ (`basePatternImageSpecs` / `loadBasePatternImages`) gains the three e-ink fill patterns in every
28
+ palette. `normalizeTheme` accepts `-` for `_`, and `resolveCustomTheme` serves the e-ink themes
29
+ even when a custom style has narrowed its palettes.
8
30
  - **`isMapLive(map)`** — `false` once MapLibre's `map.remove()` has run (it
9
31
  reads the `_removed` flag `remove()` sets last). After `remove()`, `hasImage`,
10
32
  `addImage`, `getSource` and their siblings throw, so anything that awaits and
package/README.md CHANGED
@@ -150,7 +150,7 @@ const chrt = new Chrt(process.env.CHRT_API_KEY!);
150
150
  // Locally generated style — no network, no custom style applied.
151
151
  const map = new maplibregl.Map({
152
152
  container: 'map',
153
- style: chrt.style({ palette: 'DAY' }), // 'DAY' | 'DUSK' | 'NIGHT'
153
+ style: chrt.style({ palette: 'DAY' }), // 'DAY' | 'DUSK' | 'NIGHT' | 'EINK_COLOR' | 'EINK_MONO' | 'EINK_NIGHT'
154
154
  center: [-70.25, 43.66],
155
155
  zoom: 12,
156
156
  transformRequest: chrt.transformRequest(),
@@ -162,7 +162,7 @@ Or use the **served** style, which honours the CHRTstyle custom style bound to y
162
162
  ```typescript
163
163
  const map = new maplibregl.Map({
164
164
  container: 'map',
165
- style: await chrt.styleUrl({ theme: 'dusk' }), // 'day' | 'dusk' | 'night'
165
+ style: await chrt.styleUrl({ theme: 'dusk' }), // 'day' | 'dusk' | 'night' | 'eink_color' | 'eink_mono' | 'eink_night'
166
166
  transformRequest: chrt.transformRequest(), // required — authenticates the style request too
167
167
  });
168
168
  ```
@@ -174,14 +174,37 @@ Tiles are served from `tiles.chrt.co` and gated by a short-lived ES256 JWT. The
174
174
  your key, attaches it as `Authorization: Bearer`, and refreshes it before expiry — you never handle
175
175
  tokens directly.
176
176
 
177
+ ### E-ink screens
178
+
179
+ Three palettes are for reflective e-ink (e-paper) screens, on every plan: **`EINK_COLOR`** for colour
180
+ e-ink (E Ink Kaleido 3 — Boox Note Air C, Go Color 7, Tab X C), **`EINK_MONO`** for monochrome
181
+ e-ink panels and monitors, or colour e-ink run in black and white, and **`EINK_NIGHT`** for any e-ink
182
+ screen at night with the front light on (mono inverted onto a black page, so the lit area stays
183
+ small — use it instead of a red night palette). Same features and meaning as
184
+ `DAY`; drawn for paper: black ink, heavier lines, a heavy safety-contour line, larger danger
185
+ symbols, depth in at most four steps (by fill pattern in mono, which uses only four greys), and no
186
+ translucency or transitions.
187
+
188
+ ```typescript
189
+ import { EINK_COLOR_TABLES } from '@chrtco/sdk'; // { color, mono, night }
190
+
191
+ chrt.style({ palette: EINK_COLOR_TABLES.mono }); // local
192
+ await chrt.styleUrl({ theme: 'eink_mono' }); // served (wire names are lowercase)
193
+ ```
194
+
195
+ A locally generated e-ink style also needs `loadBasePatternImages(map, palette)` from
196
+ `@chrtco/sdk/atlas/browser` (the fill patterns); a served style declares its own sprite. Use these
197
+ rather than recolouring the chart, and set MapLibre's `fadeDuration: 0` — renderer animation is a
198
+ client setting the style cannot turn off.
199
+
177
200
  ### Symbol families
178
201
 
179
202
  `style()`, `styleUrl()` and `fetchStyle()` all select symbol artwork, but **the option is spelled
180
203
  differently** on the local and served paths:
181
204
 
182
205
  ```typescript
183
- chrt.style({ symbolSet: 's101' }); // local — symbolSet, palette: 'DAY' | 'DUSK' | 'NIGHT'
184
- await chrt.styleUrl({ symbols: 's101' }); // served — symbols, theme: 'day' | 'dusk' | 'night'
206
+ chrt.style({ symbolSet: 's101' }); // local — symbolSet, palette: 'DAY' | 'DUSK' | 'NIGHT' | 'EINK_COLOR' | 'EINK_MONO' | 'EINK_NIGHT'
207
+ await chrt.styleUrl({ symbols: 's101' }); // served — symbols, theme: 'day' | 'dusk' | 'night' | 'eink_color' | 'eink_mono' | 'eink_night'
185
208
  ```
186
209
 
187
210
  | Value | Artwork |
@@ -482,14 +505,14 @@ valid — passing an agency name anywhere else silently matches nothing.
482
505
  ### `chrt.style(opts?)` — **JS-only**
483
506
  Returns a ready-to-use MapLibre/Mapbox GL **S-52** style object. Generated locally: no network, works
484
507
  offline, and **does not apply a custom style bound to your key**. Options: `palette`
485
- (`'DAY' | 'DUSK' | 'NIGHT'`), `symbolSet`, `region`, `depthUnits`, `safetyContour`, `chartMode`, plus
508
+ (`'DAY' | 'DUSK' | 'NIGHT' | 'EINK_COLOR' | 'EINK_MONO' | 'EINK_NIGHT'`), `symbolSet`, `region`, `depthUnits`, `safetyContour`, `chartMode`, plus
486
509
  the other atlas style options. Non-JS clients want `styleUrl()` instead.
487
510
 
488
511
  ### `chrt.styleUrl(opts?)`
489
512
  URL of the **served** S-52 style — the CHRTstyle Style API at
490
513
  `{tileBaseUrl}/v1/styles/{region}/style.json`. `async`: it pre-mints the tile token so the style
491
514
  request (which MapLibre does not retry) carries auth. Options: `theme`
492
- (`'day' | 'dusk' | 'night'`), `symbols`. Always pair with `transformRequest()`.
515
+ (`'day' | 'dusk' | 'night' | 'eink_color' | 'eink_mono' | 'eink_night'`), `symbols`. Always pair with `transformRequest()`.
493
516
 
494
517
  The served document honours the CHRTstyle custom style bound to your key (a **Dev+** feature; Free
495
518
  keys and keys with no bound style get the default themes). This is the style path for any client that
@@ -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];
@@ -15666,6 +15896,9 @@ var CHRT_GLYPH_SVGS = {
15666
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>',
15667
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>',
15668
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>',
15669
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>',
15670
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>',
15671
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>',
@@ -15708,6 +15941,22 @@ function capLuminance(hex, ceiling) {
15708
15941
  }
15709
15942
  var NIGHT_INK = S52_NIGHT.CHBLK;
15710
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
+ }
15711
15960
 
15712
15961
  // src/atlas/chrt-style.ts
15713
15962
  var CHRT_ICON_PREFIX = "chrt:";
@@ -15732,7 +15981,9 @@ var CHRT_SDF_GLYPHS = /* @__PURE__ */ new Set([
15732
15981
  var CHRT_GLYPH_NAMES = new Set(Object.keys(CHRT_GLYPH_SVGS));
15733
15982
  var HOUSE_BLACK = "#101418";
15734
15983
  function chrtHouseColor(dayHex, palette = "DAY") {
15735
- 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);
15736
15987
  const isBlack = dayHex.toLowerCase() === HOUSE_BLACK.toLowerCase();
15737
15988
  if (palette === "DUSK") {
15738
15989
  return isBlack ? dayHex : capLuminance(dayHex, relativeLuminance(resolveColor("CHBLK", "DUSK")));
@@ -15740,7 +15991,7 @@ function chrtHouseColor(dayHex, palette = "DAY") {
15740
15991
  return isBlack ? NIGHT_INK : capLuminance(dayHex, relativeLuminance(NIGHT_MUTED_INK));
15741
15992
  }
15742
15993
  function themeChrtGlyph(svg2, palette = "DAY") {
15743
- if (palette === "DAY") return svg2;
15994
+ if (palette === "DAY" || palette === "EINK_COLOR") return svg2;
15744
15995
  return svg2.replace(
15745
15996
  /(fill|stroke)="(#[0-9a-fA-F]{3}|#[0-9a-fA-F]{6})"/g,
15746
15997
  (_m, attr, hex) => {
@@ -15751,6 +16002,7 @@ function themeChrtGlyph(svg2, palette = "DAY") {
15751
16002
  }
15752
16003
 
15753
16004
  // src/atlas/base-layers.ts
16005
+ var NOCOVERAGE_PATTERN = "chrt:hatch-nocoverage";
15754
16006
  var UNCHARTED_PATTERN = "chrt:hatch-uncharted";
15755
16007
  function inlandHatchColor(palette) {
15756
16008
  return (BASE_THEMES[palette] ?? BASE_THEMES.DAY).inlandHatch;
@@ -15761,6 +16013,9 @@ function basePatternSvg(name, palette) {
15761
16013
  if (`${CHRT_ICON_PREFIX}${name}` === UNCHARTED_PATTERN) {
15762
16014
  return raw.replace(/stroke="#[0-9a-fA-F]{3,6}"/g, `stroke="${inlandHatchColor(palette)}"`);
15763
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
+ }
15764
16019
  return themeChrtGlyph(raw, palette);
15765
16020
  }
15766
16021
  var BASE_THEMES = {
@@ -15867,6 +16122,65 @@ var BASE_THEMES = {
15867
16122
  inlandShore: "#1C2227",
15868
16123
  inlandLabel: "#617484",
15869
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"
15870
16184
  }
15871
16185
  };
15872
16186
 
@@ -16427,10 +16741,20 @@ var S101_NIGHT = {
16427
16741
  // src/atlas/s101-style.ts
16428
16742
  var S101_ICON_PREFIX = "s101:";
16429
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
+ }
16430
16751
  var S101_TABLES = {
16431
16752
  DAY: S101_DAY,
16432
16753
  DUSK: S101_DUSK,
16433
- 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)
16434
16758
  };
16435
16759
  var warnedTokens = /* @__PURE__ */ new Set();
16436
16760
  function reportUnresolvedToken(token, table) {
@@ -16455,7 +16779,17 @@ function getThemedS101Svg(symbolName, table = "DAY") {
16455
16779
 
16456
16780
  // src/atlas/symbol-image-specs.ts
16457
16781
  var SUPERSAMPLE = 2;
16458
- 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
+ ];
16459
16793
  function upsample(svg2) {
16460
16794
  return svg2.replace(
16461
16795
  /width="(\d+)" height="(\d+)"/,