@unmap/themes 0.2.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -20,7 +20,7 @@ import { encodeTheme, decodeTheme, validateTheme, ThemeError } from '@unmap/them
20
20
  const code = await encodeTheme({
21
21
  v: 1,
22
22
  name: 'copper',
23
- base: 'outdoor', // base | muted | outdoor | decimal | blueprint
23
+ base: 'outdoor', // base | muted | outdoor | decimal | blueprint | blush | orchid | canopy | lagoon
24
24
  light: { basemap: { water: '#9CCFD2' }, route: { color: '#B85C00', width: 4 } },
25
25
  dark: { basemap: { water: '#12303A' }, route: { color: '#FFA24D', width: 4 } },
26
26
  })
@@ -31,7 +31,7 @@ const doc = await decodeTheme(code) // validated ThemeDoc, or th
31
31
 
32
32
  ## What is in a theme
33
33
 
34
- - `base` picks the style: `base`, `muted`, `outdoor`, `decimal` or `blueprint`. Each of `light` and `dark` carries
34
+ - `base` picks the style: `base`, `muted`, `outdoor`, `decimal`, `blueprint`, `blush`, `orchid`, `canopy` or `lagoon`. Each of `light` and `dark` carries
35
35
  sparse overrides on that style, so a theme with no overrides renders exactly like its
36
36
  `flavor=` twin.
37
37
  - `basemap` overrides are colours on the style's recipe (land, water, vegetation, roads, labels,
package/dist/index.d.ts CHANGED
@@ -42,7 +42,7 @@ declare const ink: {
42
42
  /**
43
43
  * Warm off-white neutrals, hue 50, chroma 0.008. The `base` pair only.
44
44
  *
45
- * Same lightness ladder as `ink`, re-hued to the land of the Mapbox Streets reference `base`
45
+ * Same lightness ladder as `ink`, re-hued to the conventional street-map land tone `base`
46
46
  * follows (`hsl(20, 20%, 95%)`): a faint warm tint rather than the cream it had, so parks,
47
47
  * water and the motorway orange read as colour against it instead of competing with it.
48
48
  */
@@ -199,6 +199,65 @@ declare const cobalt: {
199
199
  readonly 950: "#0F1629";
200
200
  readonly 1000: "#080B15";
201
201
  };
202
+ /**
203
+ * Rose, hue 20. The `blush` pair's land, buildings and labels: a pink that stays a field at the
204
+ * light steps and becomes a wine at the dark ones. Same lightness ladder as `ink`; chroma rises
205
+ * with lightness to the mid steps and eases off toward white.
206
+ */
207
+ declare const rose: {
208
+ readonly 25: "#FFF3F3";
209
+ readonly 50: "#FFEBEA";
210
+ readonly 100: "#FFDFDE";
211
+ readonly 200: "#FECFCE";
212
+ readonly 300: "#FCBDBC";
213
+ readonly 400: "#F8A4A4";
214
+ readonly 500: "#E48687";
215
+ readonly 600: "#BA6D6D";
216
+ readonly 700: "#8D5151";
217
+ readonly 800: "#643838";
218
+ readonly 850: "#4B2929";
219
+ readonly 900: "#351B1B";
220
+ readonly 950: "#241111";
221
+ readonly 1000: "#150808";
222
+ };
223
+ /**
224
+ * Lilac, hue 300, low chroma. The `orchid` pair's land, buildings and labels — a lavender grey
225
+ * rather than a purple, so the vivid teal parks and sky water are what carry the colour. Chroma
226
+ * grows a little toward the dark steps so the labels read as violet rather than as grey.
227
+ */
228
+ declare const lilac: {
229
+ readonly 25: "#F8F5FF";
230
+ readonly 50: "#F2EDFF";
231
+ readonly 100: "#EAE4F9";
232
+ readonly 200: "#DFD8EE";
233
+ readonly 300: "#D2CBE1";
234
+ readonly 400: "#C1BACF";
235
+ readonly 500: "#A7A1B5";
236
+ readonly 600: "#888296";
237
+ readonly 700: "#676174";
238
+ readonly 800: "#4A415A";
239
+ readonly 850: "#382E4A";
240
+ readonly 900: "#281C3C";
241
+ readonly 950: "#1C0E30";
242
+ readonly 1000: "#110225";
243
+ };
244
+ /**
245
+ * Aqua, hue 205. Vivid teal on the `wave` ladder for `orchid`'s and `lagoon`'s parks — the
246
+ * only park colour on the map that is not a green. Chroma peaks at the mid steps.
247
+ */
248
+ declare const aqua: {
249
+ readonly 100: "#C7F1F5";
250
+ readonly 200: "#ACE3E9";
251
+ readonly 300: "#89D2DB";
252
+ readonly 400: "#57BEC9";
253
+ readonly 500: "#00A6B3";
254
+ readonly 600: "#00858F";
255
+ readonly 700: "#00636B";
256
+ readonly 800: "#004349";
257
+ readonly 900: "#05292D";
258
+ readonly 950: "#081B1D";
259
+ readonly 980: "#060E0F";
260
+ };
202
261
 
203
262
  /**
204
263
  * The unmap cartography authoring surface, moved here from apps/gateway/src/flavors.ts so the
@@ -217,7 +276,7 @@ declare const cobalt: {
217
276
  * `relief` and `street` were retired; they are not listed here. Short preset codes still
218
277
  * decode those slots via `normalizeThemeBase` / the slot table in `preset.ts`.
219
278
  */
220
- declare const THEME_BASES: readonly ["base", "muted", "outdoor", "decimal", "blueprint"];
279
+ declare const THEME_BASES: readonly ["base", "muted", "outdoor", "decimal", "blueprint", "blush", "orchid", "canopy", "lagoon"];
221
280
  type ThemeBase = (typeof THEME_BASES)[number];
222
281
  /** Map a theme `base` — living or retired — onto a shipped style. `null` if unknown. */
223
282
  declare function normalizeThemeBase(base: string): ThemeBase | null;
@@ -317,6 +376,9 @@ interface RampSet {
317
376
  mist: typeof mist;
318
377
  jade: typeof jade;
319
378
  cobalt: typeof cobalt;
379
+ rose: typeof rose;
380
+ lilac: typeof lilac;
381
+ aqua: typeof aqua;
320
382
  ember: typeof ember;
321
383
  }
322
384
  declare const STOCK_RAMPS: RampSet;
@@ -378,12 +440,41 @@ interface ThemeSeeds {
378
440
  /** Marker + route accent. Defaults to the interface magenta. */
379
441
  accent?: string;
380
442
  }
443
+ /**
444
+ * The major layer groups a theme can hide or reorder, in their stock bottom-to-top draw order.
445
+ * Every layer the cartography draws belongs to exactly one of these (see `layerGroupOf`) except
446
+ * the background, which always stays underneath. `buildings3d` is the one group that is off by
447
+ * default: it draws building extrusions from the tiles' `height` property on top of the roads.
448
+ *
449
+ * Append only, like `THEME_BASES`: the names are on the wire in every `ut1.` code.
450
+ */
451
+ declare const LAYER_GROUPS: readonly ["land", "water", "buildings", "roads", "boundaries", "buildings3d", "labels"];
452
+ type LayerGroup = (typeof LAYER_GROUPS)[number];
453
+ /**
454
+ * Structural overrides on the layer groups. Doc-level, not per half: which layers draw, and in
455
+ * what order, is one decision for both modes — colour is the only thing that differs between
456
+ * light and dark. Sparse like the halves: an absent field means stock.
457
+ */
458
+ interface ThemeLayers {
459
+ /**
460
+ * Per-group visibility; a missing key means the group's default (every group on except
461
+ * `buildings3d`). Hidden groups are dropped from the style entirely.
462
+ */
463
+ visible?: Partial<Record<LayerGroup, boolean>>;
464
+ /**
465
+ * Bottom-to-top draw order, always the full set of groups exactly once. Absent means the
466
+ * stock cartography order, in which the groups interleave (tunnels under buildings, bridges
467
+ * over boundaries); an explicit order regroups every layer under its group.
468
+ */
469
+ order?: LayerGroup[];
470
+ }
381
471
  interface ThemeDoc {
382
472
  v: 1;
383
473
  /** Display name, ≤ 40 chars. */
384
474
  name?: string;
385
475
  base: ThemeBase;
386
476
  seeds?: ThemeSeeds;
477
+ layers?: ThemeLayers;
387
478
  light?: ThemeHalf;
388
479
  dark?: ThemeHalf;
389
480
  }
@@ -429,9 +520,10 @@ declare function decodeTheme(code: string): Promise<ThemeDoc>;
429
520
  *
430
521
  * u00, u01, u04 living styles from the original lineup (base, muted, outdoor)
431
522
  * u02, u03 retired styles (relief → outdoor, street → base)
432
- * u05, u06 styles added later (decimal, blueprint); u07 is the one spare slot
523
+ * u05, u06 the second wave (decimal, blueprint)
524
+ * u5K, u5N, … the third wave (blush, orchid, canopy, lagoon) and any style after them
433
525
  * u08… named theme preset on one of the original style slots
434
- * u5K… named theme preset on one of the later slots
526
+ * u5L… named theme preset on a later style
435
527
  * longer custom seed colours (4× RGB) on any style
436
528
  *
437
529
  * Advanced overrides — anything `generateTheme` would not emit — still use the `ut1.` deflate
@@ -485,6 +577,58 @@ declare function mergeRecipe(base: Recipe, overrides?: BasemapOverrides): Recipe
485
577
  */
486
578
  declare function resolveTheme(doc: ThemeDoc, mode: ThemeMode): ResolvedTheme;
487
579
 
580
+ /**
581
+ * Layer groups: the structural half of a theme. A theme can hide any of the seven major groups
582
+ * and redraw them in another order, the way kepler.gl's base-map panel does. This module owns
583
+ * the mapping from the expanded Protomaps layer list onto those groups and applies a document's
584
+ * `layers` field to a style's layer array.
585
+ *
586
+ * It works on the expanded layers rather than the Flavor because the groups are about what
587
+ * draws, not what colour it draws in — the Recipe stays the colour surface, this is the only
588
+ * structural one, and both are still data (booleans and one enum list on the wire).
589
+ */
590
+ /** Stock visibility: every group draws except the extrusions, which are opt-in. */
591
+ declare const DEFAULT_LAYER_VISIBILITY: Readonly<Record<LayerGroup, boolean>>;
592
+ /** The id of the synthetic extrusion layer `buildings3d` draws. */
593
+ declare const BUILDINGS_3D_LAYER_ID = "buildings_3d";
594
+ /**
595
+ * Which group an expanded Protomaps layer belongs to, or `null` for the background, which is
596
+ * never hidden or moved: with land off it is what you see. Symbol layers are labels regardless
597
+ * of their source, so the POI icons, shields and one-way arrows hide with the text.
598
+ *
599
+ * The contract test asserts every stock layer lands in a group, so an upstream layer this
600
+ * cannot place fails loudly rather than becoming an unmovable, unhideable stray.
601
+ */
602
+ declare function layerGroupOf(layer: {
603
+ id: string;
604
+ type: string;
605
+ }): LayerGroup | null;
606
+ /** Whether a group draws under this `layers` field (absent field means stock). */
607
+ declare function isLayerVisible(layers: ThemeLayers | undefined, group: LayerGroup): boolean;
608
+ /** The bottom-to-top group order this `layers` field asks for; stock when it names none. */
609
+ declare function effectiveLayerOrder(layers: ThemeLayers | undefined): readonly LayerGroup[];
610
+ /** True when `order` is the stock order (or absent), so nothing needs regrouping. */
611
+ declare function isStockLayerOrder(order: readonly LayerGroup[] | undefined): boolean;
612
+ /** True when the field changes nothing — the builder strips such a field back to absent. */
613
+ declare function isStockLayers(layers: ThemeLayers | undefined): boolean;
614
+ /**
615
+ * Apply a theme's `layers` field to an expanded style layer array. Returns the input untouched
616
+ * when the field changes nothing, so a zero-override theme stays byte-identical to its flavor.
617
+ *
618
+ * Hidden groups are removed outright rather than set to `visibility: none` — a smaller style,
619
+ * and nothing left for a client to accidentally switch back on. With the stock order the
620
+ * remaining layers keep their stock positions (and the extrusions, if on, slot in just under
621
+ * the labels). With a custom order every group's layers are gathered under it, in stock
622
+ * relative order within the group; the background and any layer outside every group stay
623
+ * first, in their stock order.
624
+ *
625
+ * `buildingsColor` paints the extrusions: the resolved theme's `buildings` slot.
626
+ */
627
+ declare function applyThemeLayers<T extends {
628
+ id: string;
629
+ type: string;
630
+ }>(styleLayers: T[], layers: ThemeLayers | undefined, buildingsColor: string): T[];
631
+
488
632
  /** WCAG contrast ratio between two hex colours (1–21). */
489
633
  declare function contrast(a: string, b: string): number;
490
634
  /** Every enforced pair with its floor — the generator's contrast-clamp iterates these. */
@@ -506,7 +650,7 @@ declare function contrastWarnings(doc: ThemeDoc): ContrastWarning[];
506
650
  * The shipped styles as starting-point themes: zero overrides, so each is guaranteed identical
507
651
  * to its `flavor=` twin (asserted in the gateway theme tests). The builder opens on these.
508
652
  *
509
- * Named after the style itself — `base`, `muted`, `outdoor`, `decimal`, `blueprint` — the same word the homepage, the
653
+ * Named after the style itself — `base`, `muted`, `outdoor`, `decimal`, `blueprint`, `blush`, … — the same word the homepage, the
510
654
  * SDK's `style` option and the gateway URL use. The theme presets (`process ink`, `boreal`, …)
511
655
  * are a separate set layered on a style; a style never borrows one's name.
512
656
  */
@@ -547,7 +691,8 @@ declare const DEFAULT_SEEDS: Required<ThemeSeeds>;
547
691
  * Family mapping: `land` recolours the land spines — `ink`, `base`'s `paper`, `muted`'s
548
692
  * `graphite` at a whisper of chroma (that style exists to vanish under data), and the
549
693
  * single-hue families of `decimal` (`jade`) and `blueprint` (`cobalt`) at full chroma, so a
550
- * seed can carry them to any hue. `water` recolours the wave, sky, and mist ramps (`mist` is
694
+ * seed can carry them to any hue, plus `blush`'s `rose` and `orchid`'s `lilac`. `nature`
695
+ * recolours `verdant` and the teal `aqua` of `orchid` and `lagoon`. `water` recolours the wave, sky, and mist ramps (`mist` is
551
696
  * greyscale in stock `muted` and takes the water seed's hue outright), `nature` the verdant
552
697
  * ramp. Warm landcover (sandstone), POI accents and white road fills stay stock: they
553
698
  * are tuned relationships, not identity. `accent` styles the marker and route overlays only —
@@ -576,4 +721,4 @@ declare const THEME_PRESETS: ThemePreset[];
576
721
  */
577
722
  declare function shuffleSeeds(rng?: () => number): Required<ThemeSeeds>;
578
723
 
579
- export { type BasemapOverrides, type ContrastWarning, DEFAULT_MARKER, DEFAULT_ROUTE, DEFAULT_SEEDS, type DeepPartial, LABEL_PAIRS, type LabelRamp, MAX_CODE_LENGTH, MODES, type MarkerTheme, type Oklch, PRESET_CODE_PREFIX, PRESET_CODE_RE, RECIPES, type RampSet, type Recipe, type ResolvedTheme, type RoadRamp, type RouteTheme, STOCK_RAMPS, STYLE_THEMES, THEME_BASES, THEME_CODE_PREFIX, THEME_PRESETS, type ThemeBase, type ThemeDoc, ThemeError, type ThemeHalf, type ThemeMode, type ThemePreset, type ThemeSeeds, buildRecipes, cobalt, contrast, contrastWarnings, decodePreset, decodeTheme, ember, encodePreset, encodeTheme, generateTheme, graphite, hexToOklch, ink, isThemePresetCode, jade, makeFlavor, mergeRecipe, mist, normalizeThemeBase, oklchToHex, paper, presetId, registryThemeKey, resolveTheme, sandstone, shuffleSeeds, sky, validateTheme, verdant, wave };
724
+ export { BUILDINGS_3D_LAYER_ID, type BasemapOverrides, type ContrastWarning, DEFAULT_LAYER_VISIBILITY, DEFAULT_MARKER, DEFAULT_ROUTE, DEFAULT_SEEDS, type DeepPartial, LABEL_PAIRS, LAYER_GROUPS, type LabelRamp, type LayerGroup, MAX_CODE_LENGTH, MODES, type MarkerTheme, type Oklch, PRESET_CODE_PREFIX, PRESET_CODE_RE, RECIPES, type RampSet, type Recipe, type ResolvedTheme, type RoadRamp, type RouteTheme, STOCK_RAMPS, STYLE_THEMES, THEME_BASES, THEME_CODE_PREFIX, THEME_PRESETS, type ThemeBase, type ThemeDoc, ThemeError, type ThemeHalf, type ThemeLayers, type ThemeMode, type ThemePreset, type ThemeSeeds, applyThemeLayers, aqua, buildRecipes, cobalt, contrast, contrastWarnings, decodePreset, decodeTheme, effectiveLayerOrder, ember, encodePreset, encodeTheme, generateTheme, graphite, hexToOklch, ink, isLayerVisible, isStockLayerOrder, isStockLayers, isThemePresetCode, jade, layerGroupOf, lilac, makeFlavor, mergeRecipe, mist, normalizeThemeBase, oklchToHex, paper, presetId, registryThemeKey, resolveTheme, rose, sandstone, shuffleSeeds, sky, validateTheme, verdant, wave };