@unmap/themes 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +427 -0
- package/dist/index.js +1453 -0
- package/dist/index.js.map +1 -0
- package/package.json +36 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
import { Flavor } from '@protomaps/basemaps';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Named colour ramps: the single source of colour truth for unmap cartography.
|
|
5
|
+
*
|
|
6
|
+
* Moved here from apps/gateway/src/palette.ts (which now re-exports these) so themes, the
|
|
7
|
+
* gateway, and the builder all draw from one vocabulary. Flavors and theme recipes reference
|
|
8
|
+
* these by name and never inline a hex literal.
|
|
9
|
+
*
|
|
10
|
+
* Ramps run light to dark by ascending key.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Ember. Pre-rebrand brand accent; survives only as the `street` motorway orange, which is
|
|
14
|
+
* cartographic convention (orange motorways) rather than brand. Never the interface accent —
|
|
15
|
+
* that is magenta, which stays out of every basemap.
|
|
16
|
+
*/
|
|
17
|
+
declare const ember: {
|
|
18
|
+
readonly 200: "#ffe9c9";
|
|
19
|
+
readonly 300: "#ffc08c";
|
|
20
|
+
readonly 400: "#f7a35c";
|
|
21
|
+
readonly 500: "#f97316";
|
|
22
|
+
readonly 600: "#e07b2e";
|
|
23
|
+
readonly 700: "#b45309";
|
|
24
|
+
};
|
|
25
|
+
/** Cool neutrals, hue 212. The spine of every Process Ink flavour. */
|
|
26
|
+
declare const ink: {
|
|
27
|
+
readonly 25: "#F1F8FA";
|
|
28
|
+
readonly 50: "#EAF2F3";
|
|
29
|
+
readonly 100: "#E2E9EB";
|
|
30
|
+
readonly 200: "#D6DEE0";
|
|
31
|
+
readonly 300: "#C9D1D3";
|
|
32
|
+
readonly 400: "#B7C0C2";
|
|
33
|
+
readonly 500: "#9DA7A9";
|
|
34
|
+
readonly 600: "#7E888B";
|
|
35
|
+
readonly 700: "#5C6769";
|
|
36
|
+
readonly 800: "#3D494B";
|
|
37
|
+
readonly 850: "#2B3639";
|
|
38
|
+
readonly 900: "#1A2628";
|
|
39
|
+
readonly 950: "#0E191C";
|
|
40
|
+
readonly 1000: "#030E10";
|
|
41
|
+
};
|
|
42
|
+
/** Water, hue 200. Cooler and more chromatic than the land it meets. */
|
|
43
|
+
declare const wave: {
|
|
44
|
+
readonly 100: "#C7F1F3";
|
|
45
|
+
readonly 200: "#B3E2E4";
|
|
46
|
+
readonly 300: "#9CCFD2";
|
|
47
|
+
readonly 400: "#7FB8BB";
|
|
48
|
+
readonly 500: "#5F9FA2";
|
|
49
|
+
readonly 600: "#368286";
|
|
50
|
+
readonly 700: "#1C6265";
|
|
51
|
+
readonly 800: "#024347";
|
|
52
|
+
readonly 900: "#002A2C";
|
|
53
|
+
readonly 950: "#001C1E";
|
|
54
|
+
readonly 980: "#001012";
|
|
55
|
+
};
|
|
56
|
+
/** Vegetation, hue 152: park, wood, scrub. */
|
|
57
|
+
declare const verdant: {
|
|
58
|
+
readonly 100: "#D3F1DA";
|
|
59
|
+
readonly 200: "#C3E3CA";
|
|
60
|
+
readonly 300: "#AFD2B7";
|
|
61
|
+
readonly 400: "#96BC9F";
|
|
62
|
+
readonly 500: "#76A281";
|
|
63
|
+
readonly 600: "#50825E";
|
|
64
|
+
readonly 700: "#366042";
|
|
65
|
+
readonly 800: "#1E4128";
|
|
66
|
+
readonly 900: "#0B2815";
|
|
67
|
+
};
|
|
68
|
+
/** Warm landcover, hue 82: sand, beach, farmland. */
|
|
69
|
+
declare const sandstone: {
|
|
70
|
+
readonly 100: "#F5E8D0";
|
|
71
|
+
readonly 200: "#E7D9BF";
|
|
72
|
+
readonly 300: "#D6C7AC";
|
|
73
|
+
readonly 400: "#BFAF92";
|
|
74
|
+
readonly 500: "#A19070";
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Pure neutrals, zero chroma. The `muted` pair only.
|
|
78
|
+
*
|
|
79
|
+
* `ink` carries a hue-212 cool cast, which is right for every other flavour and wrong for this
|
|
80
|
+
* one: `muted` exists to vanish under a customer's data, and a basemap with *any* hue biases the
|
|
81
|
+
* colours laid on top of it. So muted gets its own ramp where R=G=B exactly.
|
|
82
|
+
*
|
|
83
|
+
* Calibrated against Mapbox Light and Protomaps' `WHITE`, whose land/water/building relationships
|
|
84
|
+
* this flavour follows: land a light grey, roads *brighter* than the land rather than darker,
|
|
85
|
+
* buildings a single step down, and water the only tone with any real weight.
|
|
86
|
+
*
|
|
87
|
+
* Their label colours are deliberately NOT copied. Protomaps `WHITE` puts `state_label` #b3b3b3
|
|
88
|
+
* on #ffffff, roughly 2:1, which fails the 4.5:1 floor `cartography.test.ts` enforces. The
|
|
89
|
+
* structure is worth borrowing; the label contrast is not.
|
|
90
|
+
*/
|
|
91
|
+
declare const graphite: {
|
|
92
|
+
readonly 25: "#FAFAFA";
|
|
93
|
+
readonly 50: "#F2F2F2";
|
|
94
|
+
readonly 100: "#E9E9E9";
|
|
95
|
+
readonly 200: "#DEDEDE";
|
|
96
|
+
readonly 300: "#D1D1D1";
|
|
97
|
+
readonly 400: "#BEBEBE";
|
|
98
|
+
readonly 500: "#A4A4A4";
|
|
99
|
+
readonly 600: "#868686";
|
|
100
|
+
readonly 700: "#656565";
|
|
101
|
+
readonly 800: "#464646";
|
|
102
|
+
readonly 900: "#232323";
|
|
103
|
+
readonly 1000: "#0B0B0B";
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The unmap cartography authoring surface, moved here from apps/gateway/src/flavors.ts so the
|
|
108
|
+
* gateway, the theme system, and the unmap.dev builder share one definition.
|
|
109
|
+
*
|
|
110
|
+
* A `Recipe` is what a theme can override: every colour a Flavor controls, expressed at the
|
|
111
|
+
* level we author at. Expanding through `makeFlavor` keeps the consistency guarantees a raw
|
|
112
|
+
* Flavor cannot: a casing is always derived beside its fill, and the tunnel and bridge families
|
|
113
|
+
* cannot drift out of step with the surface roads they mirror.
|
|
114
|
+
*/
|
|
115
|
+
/**
|
|
116
|
+
* Appended, never inserted. This array's order drives the style picker in the unmap.dev builder,
|
|
117
|
+
* so inserting a base mid-array silently reorders a control people have already learned.
|
|
118
|
+
*/
|
|
119
|
+
declare const THEME_BASES: readonly ["base", "muted", "relief", "street", "outdoor"];
|
|
120
|
+
type ThemeBase = (typeof THEME_BASES)[number];
|
|
121
|
+
declare const MODES: readonly ["light", "dark"];
|
|
122
|
+
type ThemeMode = (typeof MODES)[number];
|
|
123
|
+
interface RoadRamp {
|
|
124
|
+
/** Unclassified / track. The bottom of the hierarchy. */
|
|
125
|
+
other: string;
|
|
126
|
+
otherCasing: string;
|
|
127
|
+
/** Residential and service. */
|
|
128
|
+
minor: string;
|
|
129
|
+
minorCasing: string;
|
|
130
|
+
service: string;
|
|
131
|
+
serviceCasing: string;
|
|
132
|
+
/** Primary / secondary. */
|
|
133
|
+
major: string;
|
|
134
|
+
majorCasing: string;
|
|
135
|
+
/** Motorway. */
|
|
136
|
+
highway: string;
|
|
137
|
+
highwayCasing: string;
|
|
138
|
+
/** Slip roads. */
|
|
139
|
+
link: string;
|
|
140
|
+
linkCasing: string;
|
|
141
|
+
}
|
|
142
|
+
interface LabelRamp {
|
|
143
|
+
city: string;
|
|
144
|
+
cityHalo: string;
|
|
145
|
+
state: string;
|
|
146
|
+
stateHalo: string;
|
|
147
|
+
country: string;
|
|
148
|
+
subplace: string;
|
|
149
|
+
subplaceHalo: string;
|
|
150
|
+
address: string;
|
|
151
|
+
addressHalo: string;
|
|
152
|
+
roadMinor: string;
|
|
153
|
+
roadMinorHalo: string;
|
|
154
|
+
roadMajor: string;
|
|
155
|
+
roadMajorHalo: string;
|
|
156
|
+
ocean: string;
|
|
157
|
+
}
|
|
158
|
+
interface Recipe {
|
|
159
|
+
background: string;
|
|
160
|
+
earth: string;
|
|
161
|
+
water: string;
|
|
162
|
+
/** Park, wood and scrub each take two tones; `b` is the slightly deeper of the pair. */
|
|
163
|
+
parkA: string;
|
|
164
|
+
parkB: string;
|
|
165
|
+
woodA: string;
|
|
166
|
+
woodB: string;
|
|
167
|
+
scrubA: string;
|
|
168
|
+
scrubB: string;
|
|
169
|
+
sand: string;
|
|
170
|
+
beach: string;
|
|
171
|
+
glacier: string;
|
|
172
|
+
/** Institutional land: hospital, school, industrial, military, zoo, aerodrome. */
|
|
173
|
+
institutional: string;
|
|
174
|
+
pedestrian: string;
|
|
175
|
+
aerodrome: string;
|
|
176
|
+
runway: string;
|
|
177
|
+
pier: string;
|
|
178
|
+
buildings: string;
|
|
179
|
+
railway: string;
|
|
180
|
+
boundaries: string;
|
|
181
|
+
roads: RoadRamp;
|
|
182
|
+
labels: LabelRamp;
|
|
183
|
+
/**
|
|
184
|
+
* Tunnels read one step recessive against the surface road, bridges one step assertive.
|
|
185
|
+
* Expressed as the colours to substitute rather than a computed shift, so a style can opt
|
|
186
|
+
* out of the convention where it does not suit the palette.
|
|
187
|
+
*/
|
|
188
|
+
tunnelFill: string;
|
|
189
|
+
tunnelCasing: string;
|
|
190
|
+
bridgeCasing: string;
|
|
191
|
+
pois: Flavor["pois"];
|
|
192
|
+
landcover: Flavor["landcover"];
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Expand a recipe into the full Flavor. Every one of the interface's required slots is written
|
|
196
|
+
* here exactly once, so a missing slot is a compile error rather than a silently black layer.
|
|
197
|
+
*/
|
|
198
|
+
declare function makeFlavor(r: Recipe): Flavor;
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* The five ramp families (plus ember) a recipe draws from. `buildRecipes` is parameterized on
|
|
202
|
+
* this so the theme generator can re-emit the same recipes from *generated* ramps — same
|
|
203
|
+
* lightness ladders, a customer's hues — while the stock `RECIPES` stay byte-identical to the
|
|
204
|
+
* hand-tuned originals (they are `buildRecipes(STOCK_RAMPS)`).
|
|
205
|
+
*/
|
|
206
|
+
interface RampSet {
|
|
207
|
+
ink: typeof ink;
|
|
208
|
+
wave: typeof wave;
|
|
209
|
+
verdant: typeof verdant;
|
|
210
|
+
sandstone: typeof sandstone;
|
|
211
|
+
graphite: typeof graphite;
|
|
212
|
+
ember: typeof ember;
|
|
213
|
+
}
|
|
214
|
+
declare const STOCK_RAMPS: RampSet;
|
|
215
|
+
/** Build the eight recipes from a ramp set. Stock ramps in → the shipped cartography out. */
|
|
216
|
+
declare function buildRecipes(r: RampSet): Record<ThemeBase, Record<ThemeMode, Recipe>>;
|
|
217
|
+
declare const RECIPES: Record<ThemeBase, Record<ThemeMode, Recipe>>;
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* The unmap theme document: one theme, both modes, data only.
|
|
221
|
+
*
|
|
222
|
+
* A theme is colours, bounded numbers and one enum — never URLs, never expressions, never
|
|
223
|
+
* arbitrary style JSON. That is a security boundary, not a modelling choice: theme documents
|
|
224
|
+
* arrive on public query strings and are composed into style JSON the gateway signs its own
|
|
225
|
+
* asset URLs into. `validateTheme` is the only door in.
|
|
226
|
+
*
|
|
227
|
+
* `base` names the structural chassis (one of the four shipped styles); each half carries only
|
|
228
|
+
* sparse overrides on that chassis, so presets are near-empty documents and codes stay short.
|
|
229
|
+
*/
|
|
230
|
+
type DeepPartial<T> = {
|
|
231
|
+
[K in keyof T]?: NonNullable<T[K]> extends object ? DeepPartial<NonNullable<T[K]>> : T[K];
|
|
232
|
+
};
|
|
233
|
+
type BasemapOverrides = DeepPartial<Recipe>;
|
|
234
|
+
interface MarkerTheme {
|
|
235
|
+
/** Pin colour, #RRGGBB or #RRGGBBAA. */
|
|
236
|
+
color?: string;
|
|
237
|
+
/** Pin scale, 0.5–2. */
|
|
238
|
+
scale?: number;
|
|
239
|
+
}
|
|
240
|
+
interface RouteTheme {
|
|
241
|
+
/** Main stroke colour. */
|
|
242
|
+
color?: string;
|
|
243
|
+
/** Main stroke width in px, 1–12. */
|
|
244
|
+
width?: number;
|
|
245
|
+
/** Main stroke opacity, 0–1. */
|
|
246
|
+
opacity?: number;
|
|
247
|
+
/** Under-stroke (casing) colour. */
|
|
248
|
+
casingColor?: string;
|
|
249
|
+
/** Casing width in px, 0–24. 0 disables the casing. */
|
|
250
|
+
casingWidth?: number;
|
|
251
|
+
/** Casing opacity, 0–1. */
|
|
252
|
+
casingOpacity?: number;
|
|
253
|
+
}
|
|
254
|
+
interface ThemeHalf {
|
|
255
|
+
basemap?: BasemapOverrides;
|
|
256
|
+
marker?: MarkerTheme;
|
|
257
|
+
route?: RouteTheme;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* The simple-mode seed colours a generated theme was built from. Carried on the document so a
|
|
261
|
+
* shared code reopens the builder in simple mode with the seeds intact; the gateway validates
|
|
262
|
+
* them like any other field and otherwise ignores them — composition reads only the halves.
|
|
263
|
+
*/
|
|
264
|
+
interface ThemeSeeds {
|
|
265
|
+
/** Neutral cast: land, buildings, roads, labels. */
|
|
266
|
+
land: string;
|
|
267
|
+
water: string;
|
|
268
|
+
/** Vegetation: parks, wood, scrub, forest landcover. */
|
|
269
|
+
nature: string;
|
|
270
|
+
/** Marker + route accent. Defaults to the interface magenta. */
|
|
271
|
+
accent?: string;
|
|
272
|
+
}
|
|
273
|
+
interface ThemeDoc {
|
|
274
|
+
v: 1;
|
|
275
|
+
/** Display name, ≤ 40 chars. */
|
|
276
|
+
name?: string;
|
|
277
|
+
base: ThemeBase;
|
|
278
|
+
seeds?: ThemeSeeds;
|
|
279
|
+
light?: ThemeHalf;
|
|
280
|
+
dark?: ThemeHalf;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Validation failure. `detail` lists every bad path in one throw — collect-then-throw, so a
|
|
285
|
+
* builder user fixing a hand-edited document sees all of it at once rather than one field per
|
|
286
|
+
* round trip (the same reporting philosophy as the cartography contrast tests).
|
|
287
|
+
*/
|
|
288
|
+
declare class ThemeError extends Error {
|
|
289
|
+
readonly detail: string[];
|
|
290
|
+
constructor(message: string, detail: string[]);
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Validate an untrusted value as a ThemeDoc. Returns the (unchanged) document on success;
|
|
294
|
+
* throws a `ThemeError` naming every invalid path otherwise. Unknown fields anywhere are
|
|
295
|
+
* errors — a theme is data only, and the reject-unknown posture is what keeps it that way.
|
|
296
|
+
*/
|
|
297
|
+
declare function validateTheme(input: unknown): ThemeDoc;
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* The compact theme code: `ut1.` + base64url(deflate(minified JSON)).
|
|
301
|
+
*
|
|
302
|
+
* Web-standard streams only (CompressionStream/DecompressionStream, Blob, btoa/atob) so the
|
|
303
|
+
* same module runs in Workers, browsers and Node 18+. `deflate` (zlib) rather than
|
|
304
|
+
* `deflate-raw`, which Node 20 — the repo floor — does not implement; the wrapper costs six
|
|
305
|
+
* bytes and buys a checksum.
|
|
306
|
+
*
|
|
307
|
+
* `ut1` is the wire version. Bump it only for a breaking change, and keep decoding old
|
|
308
|
+
* prefixes forever: codes get hardcoded into customers' pages.
|
|
309
|
+
*/
|
|
310
|
+
declare const THEME_CODE_PREFIX = "ut1.";
|
|
311
|
+
declare const MAX_CODE_LENGTH = 4096;
|
|
312
|
+
/** Validate and encode a theme document to its compact `ut1.` code. Throws `ThemeError`. */
|
|
313
|
+
declare function encodeTheme(doc: ThemeDoc): Promise<string>;
|
|
314
|
+
/** Decode and validate a compact `ut1.` code. Every failure lands as `ThemeError`. */
|
|
315
|
+
declare function decodeTheme(code: string): Promise<ThemeDoc>;
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Platform drawing defaults for the two client-side overlays a theme styles. These are exactly
|
|
319
|
+
* the convention the unmap.dev hero has always drawn: a magenta pin, and a route as a solid
|
|
320
|
+
* line over a wide translucent casing so it stays legible on light and dark cartography alike.
|
|
321
|
+
*/
|
|
322
|
+
declare const DEFAULT_MARKER: Required<MarkerTheme>;
|
|
323
|
+
declare const DEFAULT_ROUTE: Required<RouteTheme>;
|
|
324
|
+
interface ResolvedTheme {
|
|
325
|
+
/** `<base>-<mode>`, the chassis name — what selects sprites and terrain behaviour. */
|
|
326
|
+
flavorName: `${ThemeBase}-${ThemeMode}`;
|
|
327
|
+
flavor: Flavor;
|
|
328
|
+
marker: Required<MarkerTheme>;
|
|
329
|
+
route: Required<RouteTheme>;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Merge sparse overrides onto a base recipe, walking the base's own shape. Composing from the
|
|
333
|
+
* base's keys (never spreading the override object) means an unexpected key cannot ride along
|
|
334
|
+
* even if a caller skipped validation — defence in depth behind `validateTheme`.
|
|
335
|
+
*/
|
|
336
|
+
declare function mergeRecipe(base: Recipe, overrides?: BasemapOverrides): Recipe;
|
|
337
|
+
/**
|
|
338
|
+
* Resolve one half of a theme into everything a renderer needs: the expanded Flavor, the
|
|
339
|
+
* chassis name, and marker/route styles with defaults filled in.
|
|
340
|
+
*/
|
|
341
|
+
declare function resolveTheme(doc: ThemeDoc, mode: ThemeMode): ResolvedTheme;
|
|
342
|
+
|
|
343
|
+
/** WCAG contrast ratio between two hex colours (1–21). */
|
|
344
|
+
declare function contrast(a: string, b: string): number;
|
|
345
|
+
/** Every enforced pair with its floor — the generator's contrast-clamp iterates these. */
|
|
346
|
+
declare const LABEL_PAIRS: Array<[keyof Flavor, keyof Flavor, number]>;
|
|
347
|
+
interface ContrastWarning {
|
|
348
|
+
mode: ThemeMode;
|
|
349
|
+
/** The Flavor label slot that breaches, e.g. "city_label". */
|
|
350
|
+
label: string;
|
|
351
|
+
/** The Flavor slot it was checked against, e.g. "city_label_halo". */
|
|
352
|
+
background: string;
|
|
353
|
+
ratio: number;
|
|
354
|
+
/** 4.5 for primary labels, 3 for secondary. */
|
|
355
|
+
floor: number;
|
|
356
|
+
}
|
|
357
|
+
/** Every label-contrast breach in the theme, across both modes. Empty means clean. */
|
|
358
|
+
declare function contrastWarnings(doc: ThemeDoc): ContrastWarning[];
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* The shipped styles as starting-point themes: zero overrides, so each is guaranteed identical
|
|
362
|
+
* to its `flavor=` twin (asserted in the gateway theme tests). The builder opens on these.
|
|
363
|
+
*/
|
|
364
|
+
declare const PRESET_THEMES: Record<ThemeBase, ThemeDoc>;
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Minimal sRGB ↔ OKLCH conversion (Björn Ottosson's OKLab constants), plus a chroma-reducing
|
|
368
|
+
* gamut map. Zero dependencies — the generator is the only consumer and needs exactly this.
|
|
369
|
+
*
|
|
370
|
+
* OKLCH is the working space for the same reason the hand-built ramps in `palette.ts` were
|
|
371
|
+
* generated in it: perceived lightness is carried almost entirely by L, so a generator can swap
|
|
372
|
+
* hue and chroma while *preserving a ramp's lightness ladder* — which is where the label
|
|
373
|
+
* contrast the cartography tests enforce actually lives.
|
|
374
|
+
*/
|
|
375
|
+
interface Oklch {
|
|
376
|
+
/** Perceptual lightness, 0–1. */
|
|
377
|
+
l: number;
|
|
378
|
+
/** Chroma, 0 upward (~0.37 max for sRGB). */
|
|
379
|
+
c: number;
|
|
380
|
+
/** Hue in degrees, 0–360. */
|
|
381
|
+
h: number;
|
|
382
|
+
}
|
|
383
|
+
/** Parse #RRGGBB (or #RRGGBBAA — alpha ignored) into OKLCH. */
|
|
384
|
+
declare function hexToOklch(hex: string): Oklch;
|
|
385
|
+
/**
|
|
386
|
+
* OKLCH → #RRGGBB. Out-of-gamut colours are mapped by *reducing chroma* at constant L and h
|
|
387
|
+
* (bisection), never by clipping channels — clipping shifts perceived lightness, and the whole
|
|
388
|
+
* generator contract is that L is preserved.
|
|
389
|
+
*/
|
|
390
|
+
declare function oklchToHex(color: Oklch): string;
|
|
391
|
+
|
|
392
|
+
/** The seeds that reproduce (approximately) the stock cartography. Simple mode opens on these. */
|
|
393
|
+
declare const DEFAULT_SEEDS: Required<ThemeSeeds>;
|
|
394
|
+
/**
|
|
395
|
+
* Generate a full theme from seed colours. Deterministic; the result always clears the label
|
|
396
|
+
* floors in both modes (enforced, and asserted by the hue-sweep test).
|
|
397
|
+
*
|
|
398
|
+
* Family mapping: `land` recolours the neutral spine (and `muted`'s graphite, at a whisper of
|
|
399
|
+
* chroma — that flavour exists to vanish under data), `water` the wave ramp, `nature` the
|
|
400
|
+
* verdant ramp. Warm landcover (sandstone), POI accents, white road fills and the street
|
|
401
|
+
* motorway orange stay stock: they are tuned relationships, not identity. `accent` styles the
|
|
402
|
+
* marker and route overlays only — it never enters the basemap, same rule as the brand magenta.
|
|
403
|
+
*/
|
|
404
|
+
declare function generateTheme(base: ThemeBase, seeds: ThemeSeeds): ThemeDoc;
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Curated seed palettes for the builder's simple mode — the hand-picked starting points, named
|
|
408
|
+
* for Canadian landscapes. Each is just a seed trio + accent; `generateTheme` does the rest,
|
|
409
|
+
* and its contrast guarantee holds for these like any other seeds (asserted in tests).
|
|
410
|
+
*/
|
|
411
|
+
interface SeedPalette {
|
|
412
|
+
/** Display name, lowercase like the flavour names. Not translated — these are names. */
|
|
413
|
+
name: string;
|
|
414
|
+
seeds: Required<ThemeSeeds>;
|
|
415
|
+
}
|
|
416
|
+
declare const SEED_PALETTES: SeedPalette[];
|
|
417
|
+
/**
|
|
418
|
+
* A random but *harmonic* seed trio — the shuffle button. Hues are related (water lands
|
|
419
|
+
* cool-of-complementary, nature analogous-of-triadic, the accent opposes the land) and chroma
|
|
420
|
+
* stays inside a tasteful band, so shuffles read as designed rather than random; the
|
|
421
|
+
* generator's contrast guarantee makes every one of them legible regardless.
|
|
422
|
+
*
|
|
423
|
+
* `rng` is injectable (returns [0,1)) so tests can drive it deterministically.
|
|
424
|
+
*/
|
|
425
|
+
declare function shuffleSeeds(rng?: () => number): Required<ThemeSeeds>;
|
|
426
|
+
|
|
427
|
+
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_THEMES, RECIPES, type RampSet, type Recipe, type ResolvedTheme, type RoadRamp, type RouteTheme, SEED_PALETTES, STOCK_RAMPS, type SeedPalette, THEME_BASES, THEME_CODE_PREFIX, type ThemeBase, type ThemeDoc, ThemeError, type ThemeHalf, type ThemeMode, type ThemeSeeds, buildRecipes, contrast, contrastWarnings, decodeTheme, ember, encodeTheme, generateTheme, graphite, hexToOklch, ink, makeFlavor, mergeRecipe, oklchToHex, resolveTheme, sandstone, shuffleSeeds, validateTheme, verdant, wave };
|